Index: ccache.c
===================================================================
RCS file: /cvsroot/ccache/ccache.c,v
retrieving revision 1.100
diff -u -p -r1.100 ccache.c
--- ccache.c	28 Feb 2007 04:24:04 -0000	1.100
+++ ccache.c	2 Jul 2007 14:04:59 -0000
@@ -65,6 +65,12 @@ char *stats_file = NULL;
 /* can we safely use the unification hashing backend? */
 static int enable_unify;
 
+/* what compiler are we using? */
+static compiler_type compiler = COMPILER_GCC;
+
+/* should we output all the options into a file? */
+static int args_into_file = 0;
+
 /* a list of supported file extensions, and the equivalent
    extension for code that has been through the pre-processor
 */
@@ -153,17 +159,29 @@ static const char *tmp_string(void)
 /* run the real compiler and put the result in cache */
 static void to_cache(ARGS *args)
 {
-	char *path_stderr;
+	char *path_stdout, *path_stderr;
 	char *tmp_stdout, *tmp_stderr, *tmp_hashname;
+	char *tmp;
 	struct stat st1, st2;
-	int status;
+	int status = -1;
 
 	x_asprintf(&tmp_stdout, "%s.tmp.stdout.%s", hashname, tmp_string());
 	x_asprintf(&tmp_stderr, "%s.tmp.stderr.%s", hashname, tmp_string());
 	x_asprintf(&tmp_hashname, "%s.tmp.%s", hashname, tmp_string());
 
-	args_add(args, "-o");
-	args_add(args, tmp_hashname);
+	switch (compiler) {
+	case COMPILER_GCC:
+		args_add(args, "-o");
+		args_add(args, tmp_hashname);
+		break;
+	case COMPILER_MSVC:
+		x_asprintf(&tmp, "-Fo%s", tmp_hashname);
+		args_add(args, tmp);
+		break;
+	default:
+		cc_log("Unknown compiler!? (argv[0]='%s')\n", args->argv[0]);
+		failed();
+	}
 
 	/* Turn off DEPENDENCIES_OUTPUT when running cc1, because
 	 * otherwise it will emit a line like
@@ -178,18 +196,41 @@ static void to_cache(ARGS *args)
 	} else {
 		args_add(args, i_tmpfile);
 	}
-	status = execute(args->argv, tmp_stdout, tmp_stderr);
-	args_pop(args, 3);
 
-	if (stat(tmp_stdout, &st1) != 0 || st1.st_size != 0) {
-		cc_log("compiler produced stdout for %s\n", output_file);
-		stats_update(STATS_STDOUT);
+	switch (compiler) {
+	case COMPILER_GCC:
+		status = execute(args->argv, tmp_stdout, tmp_stderr);
+		args_pop(args, 3);
+		if (stat(tmp_stdout, &st1) != 0 || st1.st_size != 0) {
+			cc_log("compiler produced stdout for %s\n", output_file);
+			stats_update(STATS_STDOUT);
+			unlink(tmp_stdout);
+			unlink(tmp_stderr);
+			unlink(tmp_hashname);
+			failed();
+		}
 		unlink(tmp_stdout);
-		unlink(tmp_stderr);
-		unlink(tmp_hashname);
+		break;
+	case COMPILER_MSVC:
+		if (!args_into_file) {
+			status = execute(args->argv, tmp_stdout, tmp_stderr);
+		}
+		else {
+			char *tmp_args;
+			x_asprintf(&tmp_args, "%s.tmp.%s.arg", hashname, tmp_string());
+
+			status = execute_msvc_external_args(
+					args->argv,
+					tmp_args,
+					tmp_stdout,
+					tmp_stderr);
+		}
+		args_pop(args, 2);
+		break;
+	default:
+		/* just for case */
 		failed();
 	}
-	unlink(tmp_stdout);
 
 	if (status != 0) {
 		int fd;
@@ -228,11 +269,13 @@ static void to_cache(ARGS *args)
 		failed();
 	}
 
+	x_asprintf(&path_stdout, "%s.stdout", hashname);
 	x_asprintf(&path_stderr, "%s.stderr", hashname);
 
 	if (stat(tmp_stderr, &st1) != 0 ||
 	    stat(tmp_hashname, &st2) != 0 ||
 	    rename(tmp_hashname, hashname) != 0 ||
+	    (compiler == COMPILER_MSVC && rename(tmp_stdout, path_stdout) != 0) ||
 	    rename(tmp_stderr, path_stderr) != 0) {
 		cc_log("failed to rename tmp files - %s\n", strerror(errno));
 		stats_update(STATS_ERROR);
@@ -246,6 +289,7 @@ static void to_cache(ARGS *args)
 	free(tmp_stderr);
 	free(tmp_stdout);
 	free(path_stderr);
+	free(path_stdout);
 }
 
 /* find the hash for a command. The hash includes all argument lists,
@@ -257,7 +301,7 @@ static void find_hash(ARGS *args)
 	char *hash_dir;
 	char *s;
 	struct stat st;
-	int status;
+	int status = -1;
 	int nlevels = 2;
 	char *input_base;
 	char *tmp;
@@ -282,35 +326,61 @@ static void find_hash(ARGS *args)
 
 	/* first the arguments */
 	for (i=1;i<args->argc;i++) {
-		/* some arguments don't contribute to the hash. The
-		   theory is that these arguments will change the
-		   output of -E if they are going to have any effect
-		   at all, or they only affect linking */
-		if (i < args->argc-1) {
-			if (strcmp(args->argv[i], "-I") == 0 ||
-			    strcmp(args->argv[i], "-include") == 0 ||
-			    strcmp(args->argv[i], "-L") == 0 ||
-			    strcmp(args->argv[i], "-D") == 0 ||
-			    strcmp(args->argv[i], "-idirafter") == 0 ||
-			    strcmp(args->argv[i], "-isystem") == 0) {
-				i++;
+		switch (compiler) {
+		case COMPILER_GCC:
+			/* some arguments don't contribute to the hash. The
+			   theory is that these arguments will change the
+			   output of -E if they are going to have any effect
+			   at all, or they only affect linking */
+			if (i < args->argc-1) {
+				if (strcmp(args->argv[i], "-I") == 0 ||
+				    strcmp(args->argv[i], "-include") == 0 ||
+				    strcmp(args->argv[i], "-L") == 0 ||
+				    strcmp(args->argv[i], "-D") == 0 ||
+				    strcmp(args->argv[i], "-idirafter") == 0 ||
+				    strcmp(args->argv[i], "-isystem") == 0) {
+					i++;
+					continue;
+				}
+			}
+			if (strncmp(args->argv[i], "-I", 2) == 0 ||
+			    strncmp(args->argv[i], "-L", 2) == 0 ||
+			    strncmp(args->argv[i], "-D", 2) == 0 ||
+			    strncmp(args->argv[i], "-idirafter", 10) == 0 ||
+			    strncmp(args->argv[i], "-isystem", 8) == 0) {
 				continue;
 			}
-		}
-		if (strncmp(args->argv[i], "-I", 2) == 0 ||
-		    strncmp(args->argv[i], "-L", 2) == 0 ||
-		    strncmp(args->argv[i], "-D", 2) == 0 ||
-		    strncmp(args->argv[i], "-idirafter", 10) == 0 ||
-		    strncmp(args->argv[i], "-isystem", 8) == 0) {
-			continue;
-		}
 
-		if (strncmp(args->argv[i], "--specs=", 8) == 0 &&
-		    stat(args->argv[i]+8, &st) == 0) {
-			/* if given a explicit specs file, then hash that file, but
-			   don't include the path to it in the hash */
-			hash_file(args->argv[i]+8);
-			continue;
+			if (strncmp(args->argv[i], "--specs=", 8) == 0 &&
+			    stat(args->argv[i]+8, &st) == 0) {
+				/* if given a explicit specs file, then hash that file, but
+				   don't include the path to it in the hash */
+				hash_file(args->argv[i]+8);
+				continue;
+			}
+			break;
+		case COMPILER_MSVC:
+			/* some arguments don't contribute to the hash. The
+			   theory is that these arguments will change the
+			   output of -E if they are going to have any effect
+			   at all, or they only affect linking */
+			if (strncmp(args->argv[i], "/I", 2) == 0 ||
+			    strncmp(args->argv[i], "-I", 2) == 0 ||
+			    strncmp(args->argv[i], "/D", 2) == 0 ||
+			    strncmp(args->argv[i], "-D", 2) == 0 ||
+			    strncmp(args->argv[i], "/u", 2) == 0 ||
+			    strncmp(args->argv[i], "-u", 2) == 0 ||
+			    strncmp(args->argv[i], "/U", 2) == 0 ||
+			    strncmp(args->argv[i], "-U", 2) == 0 ||
+			    strncmp(args->argv[i], "/AI", 3) == 0 ||
+			    strncmp(args->argv[i], "-AI", 3) == 0) {
+				continue;
+			}
+			/* TODO more of them to ignore? */
+			break;
+		default:
+			cc_log("Unknown compiler!? (argv[0]='%s')\n", args->argv[0]);
+			failed();
 		}
 
 		/* all other arguments are included in the hash */
@@ -368,10 +438,37 @@ static void find_hash(ARGS *args)
 
 	if (!direct_i_file) {
 		/* run cpp on the input file to obtain the .i */
-		args_add(args, "-E");
-		args_add(args, input_file);
-		status = execute(args->argv, path_stdout, path_stderr);
-		args_pop(args, 2);
+		switch (compiler) {
+		case COMPILER_GCC:
+			args_add(args, "-E");
+			args_add(args, input_file);
+			status = execute(args->argv, path_stdout, path_stderr);
+			args_pop(args, 2);
+			break;
+		case COMPILER_MSVC:
+			args_add(args, "-E");
+			args_add(args, input_file);
+			if (!args_into_file) {
+				status = execute(args->argv, path_stdout, path_stderr);
+			}
+			else {
+				char *path_args;
+				x_asprintf(&path_args, "%s/%s.tmp.%s.arg",
+						temp_dir,
+						input_base, tmp_string());
+
+				status = execute_msvc_external_args(
+						args->argv,
+						path_args,
+						path_stdout,
+						path_stderr);
+			}
+			args_pop(args, 2);
+			break;
+		default:
+			cc_log("Unknown compiler!? (argv[0]='%s')\n", args->argv[0]);
+			failed();
+		}
 	} else {
 		/* we are compiling a .i or .ii file - that means we
 		   can skip the cpp stage and directly form the
@@ -581,6 +678,11 @@ static void find_compiler(int argc, char
 		base = strdup(path);
 	}
 
+	/* do we use Visual C++? */
+	if (strcmp(base, "cl") == 0 || strcmp(base, "cl.exe") == 0) {
+		compiler = COMPILER_MSVC;
+	}
+
 	orig_args->argv[0] = find_executable(base, MYNAME);
 
 	/* can't find the compiler! */
@@ -598,6 +700,7 @@ static const char *check_extension(const
 {
 	int i;
 	const char *p;
+	char *tmp;
 
 	if (direct_i) {
 		*direct_i = 0;
@@ -606,25 +709,39 @@ static const char *check_extension(const
 	p = strrchr(fname, '.');
 	if (!p) return NULL;
 	p++;
-	for (i=0; extensions[i].extension; i++) {
-		if (strcmp(p, extensions[i].extension) == 0) {
-			if (direct_i && strcmp(p, extensions[i].i_extension) == 0) {
-				*direct_i = 1;
-			}
-			p = getenv("CCACHE_EXTENSION");
-			if (p) return p;
-			return extensions[i].i_extension;
+
+	switch (compiler) {
+	case COMPILER_GCC:
+		for (i=0; extensions[i].extension; i++) {
+			if (strcmp(p, extensions[i].extension) == 0) {
+				if (direct_i && strcmp(p, extensions[i].i_extension) == 0) {
+					*direct_i = 1;
+				}
+				p = getenv("CCACHE_EXTENSION");
+				if (p) return p;
+				return extensions[i].i_extension;
+			}
 		}
+		break;
+	case COMPILER_MSVC:
+		if (direct_i) {
+			/* no direct_i support */
+			*direct_i = 0;
+		}
+		x_asprintf(&tmp, "pre.%s", p);
+		return tmp;
+		break;
+	default:
+		cc_log("Unknown compiler!?\n");
+		failed();
 	}
+
 	return NULL;
 }
 
 
-/* 
-   process the compiler options to form the correct set of options 
-   for obtaining the preprocessor output
-*/
-static void process_args(int argc, char **argv)
+/* gcc compiler options */
+static void process_args_gcc(int argc, char **argv)
 {
 	int i;
 	int found_c_opt = 0;
@@ -837,6 +954,248 @@ static void process_args(int argc, char 
 	}
 }
 
+
+/* process the MSVC arguments */
+static void msvc_options(char *arg, int *c_opt)
+{
+	struct stat st;
+
+	if (strlen(arg) == 0) {
+		return;
+	}
+
+	/* some options will never work ... */
+	if (strcmp(arg, "/E") == 0 ||
+	    strcmp(arg, "-E") == 0 ||
+	    strcmp(arg, "/EP") == 0 ||
+	    strcmp(arg, "-EP") == 0 ||
+	    strcmp(arg, "/P") == 0 ||
+	    strcmp(arg, "-P") == 0) {
+		failed();
+	}
+
+	/* we must have /c */
+	if (strcmp(arg, "/c") == 0 ||
+	    strcmp(arg, "-c") == 0) {
+		args_add(stripped_args, arg);
+		if (c_opt) {
+			*c_opt = 1;
+		}
+		else {
+			failed();
+		}
+		return;
+	}
+
+	/* the definition of the output file */
+	if (strncmp(arg, "/Fo", 3) == 0 ||
+	    strncmp(arg, "-Fo", 3) == 0) {
+		output_file = x_strdup(&arg[3]);
+		return;
+	}
+
+	/* other options */
+	if (arg[0] == '/' ||
+	    arg[0] == '-') {
+		args_add(stripped_args, arg);
+		return;
+	}
+
+	/* if an argument isn't a plain file then assume its
+	   an option, not an input file. This allows us to
+	   cope better with unusual compiler options */
+	if (stat(arg, &st) != 0 || !S_ISREG(st.st_mode)) {
+		args_add(stripped_args, arg);
+		return;			
+	}
+
+	if (input_file) {
+		if (check_extension(arg, NULL)) {
+			cc_log("multiple input files (%s and %s)\n",
+			       input_file, arg);
+			stats_update(STATS_MULTIPLE);
+		} else if (c_opt && !*c_opt) {
+			cc_log("called for link with %s\n", arg);
+			if (strstr(arg, "conftest.")) {
+				stats_update(STATS_CONFTEST);
+			} else {
+				stats_update(STATS_LINK);
+			}
+		} else {
+			cc_log("non C/C++ file %s\n", arg);
+			stats_update(STATS_NOTC);
+		}
+		failed();
+	}
+
+	input_file = x_strdup(arg);
+}
+
+/* MSVC compiler options */
+static void process_args_msvc(int argc, char **argv)
+{
+	int i;
+	int found_c_opt = 0;
+	/* FIXME do we have to handle it?
+	int found_S_opt = 0;*/
+	struct stat st;
+	char *e;
+
+	stripped_args = args_init(0, NULL);
+
+	args_add(stripped_args, argv[0]);
+
+	for (i=1; i<argc; i++) {
+		/* we have to look into the @file */
+		if (argv[i][0] == '@') {
+			FILE *file;
+			char buf[4096];
+
+			args_into_file = 1;
+
+			file = fopen(argv[i]+1, "r");
+			if (!file) {
+				cc_log("fopen failed! %s\n", argv[i]+1);
+				failed();
+			}
+			while (!feof(file)) {
+				char *begin, *end;
+				fgets(buf, 4096, file);
+
+				begin = end = buf;
+				while (begin && *begin) {
+					int finish = 0;
+
+					/* FIXME handle escapes, ", ', etc. */
+					for (; *end &&
+					       *end != ' '  &&
+					       *end != '\n' &&
+					       *end != '\r';
+					     ++end);
+
+					if (*end) {
+						*end = 0;
+						++end;
+					}
+					else {
+						finish = 1;
+					}
+
+					/* process the options */
+					msvc_options(begin, &found_c_opt);
+
+					begin = finish? NULL: end;
+				}
+			}
+			fclose(file);
+			continue;
+		}
+
+		/* The user knows best: just swallow the next arg */
+		if (strcmp(argv[i], "--ccache-skip") == 0) {
+			i++;
+			if (i == argc) {
+				failed();
+			}
+			args_add(stripped_args, argv[i]);
+			continue;
+		}
+
+		/* process the options */
+		msvc_options(argv[i], &found_c_opt);
+	}
+
+	if (!input_file) {
+		cc_log("No input file found\n");
+		stats_update(STATS_NOINPUT);
+		failed();
+	}
+
+	i_extension = check_extension(input_file, &direct_i_file);
+	if (i_extension == NULL) {
+		cc_log("Not a C/C++ file - %s\n", input_file);
+		stats_update(STATS_NOTC);
+		failed();
+	}
+
+	if (!found_c_opt) {
+		cc_log("No -c option found for %s\n", input_file);
+		/* I find that having a separate statistic for autoconf tests is useful,
+		   as they are the dominant form of "called for link" in many cases */
+		if (strstr(input_file, "conftest.")) {
+			stats_update(STATS_CONFTEST);
+		} else {
+			stats_update(STATS_LINK);
+		}
+		failed();
+	}
+
+
+	/* don't try to second guess the compilers heuristics for stdout handling */
+	if (output_file && strcmp(output_file, "-") == 0) {
+		stats_update(STATS_OUTSTDOUT);
+		failed();
+	}
+
+	if (!output_file) {
+		char *p,*q;
+		char *tmp;
+		tmp = x_strdup(input_file);
+		p = strrchr(tmp, '\\');
+		q = strrchr(tmp, '/');
+		if (p || q) {
+			tmp = (p > q)? p+1: q+1;
+		}
+		p = strrchr(tmp, '.');
+		if (!p) {
+			cc_log("badly formed tmp %s\n", tmp);
+			stats_update(STATS_ARGS);
+			failed();
+		}
+		*p = 0;
+		x_asprintf(&output_file, "%s.obj", tmp);
+		/* FIXME something for the found_S_opt case as well?
+		p[1] = found_S_opt ? 's' : 'o';
+		p[2] = 0;*/
+	}
+
+	/* cope with -o /dev/null */
+	if (strcmp(output_file,"/dev/null") != 0 && stat(output_file, &st) == 0 && !S_ISREG(st.st_mode)) {
+		cc_log("Not a regular file %s\n", output_file);
+		stats_update(STATS_DEVICE);
+		failed();
+	}
+
+	if ((e=getenv("CCACHE_PREFIX"))) {
+		char *p = find_executable(e, MYNAME);
+		if (!p) {
+			perror(e);
+			exit(1);
+		}
+		args_add_prefix(stripped_args, p);
+	}
+}
+
+
+/* 
+   process the compiler options to form the correct set of options 
+   for obtaining the preprocessor output
+*/
+static void process_args(int argc, char **argv)
+{
+	switch (compiler) {
+	case COMPILER_GCC:
+		process_args_gcc(argc, argv);
+		break;
+	case COMPILER_MSVC:
+		process_args_msvc(argc, argv);
+		break;
+	default:
+		cc_log("Unknown compiler!? (argv[0]='%s')\n", argv[0]);
+		failed();
+	}
+}
+
 /* the main ccache driver function */
 static void ccache(int argc, char *argv[])
 {
Index: ccache.h
===================================================================
RCS file: /cvsroot/ccache/ccache.h,v
retrieving revision 1.54
diff -u -p -r1.54 ccache.h
--- ccache.h	25 Jul 2005 07:05:46 -0000	1.54
+++ ccache.h	2 Jul 2007 14:04:59 -0000
@@ -63,6 +63,11 @@ enum stats {
 	STATS_END
 };
 
+typedef enum {
+	COMPILER_GCC,
+	COMPILER_MSVC
+} compiler_type;
+
 typedef unsigned uint32;
 
 #include "mdfour.h"
@@ -126,6 +131,10 @@ void wipe_all(const char *dir);
 int execute(char **argv, 
 	    const char *path_stdout,
 	    const char *path_stderr);
+int execute_msvc_external_args(char **argv, 
+	    const char *path_args,
+	    const char *path_stdout,
+	    const char *path_stderr);
 char *find_executable(const char *name, const char *exclude_name);
 
 typedef struct {
Index: execute.c
===================================================================
RCS file: /cvsroot/ccache/execute.c,v
retrieving revision 1.10
diff -u -p -r1.10 execute.c
--- execute.c	6 Sep 2004 13:11:15 -0000	1.10
+++ execute.c	2 Jul 2007 14:04:59 -0000
@@ -68,6 +68,57 @@ int execute(char **argv, 
 
 
 /*
+  execute a compiler backend, capturing all output to the given paths
+  the full path to the compiler to run is in argv[0]
+  all the other args are stored in a @file
+*/
+int execute_msvc_external_args(char **argv, 
+	    const char *path_args,
+	    const char *path_stdout,
+	    const char *path_stderr)
+{
+	char *tmp;
+	FILE *file;
+	char **a;
+	static ARGS *tmp_args;
+	int status = -1;
+
+	/* create the file */
+	file = fopen(path_args, "w");
+	a = argv;
+	if (!file || !(*a)) {
+		return -1;
+	}
+	/* skip the compiler (argv[0]) */
+	++a;
+	if (!(*a)) {
+		return -1;
+	}
+
+	for (; *a; ++a) {
+		if (strchr(*a, '"')) {
+			/* FIXME escape properly instead of failing */
+			return -1;
+		}
+		fprintf(file, "\"%s\" ", *a);
+	}
+	fprintf(file, "\n");
+	fclose(file);
+
+	/* create temporary args with @file as the only param */
+	tmp_args = args_init(0, NULL);
+	args_add(tmp_args, argv[0]);
+	x_asprintf(&tmp, "@%s", path_args);
+	args_add(tmp_args, tmp);
+
+	status = execute(tmp_args->argv, path_stdout, path_stderr);
+
+	unlink(path_args);
+	return status;
+}
+
+
+/*
   find an executable by name in $PATH. Exclude any that are links to exclude_name 
 */
 char *find_executable(const char *name, const char *exclude_name)
Index: util.c
===================================================================
RCS file: /cvsroot/ccache/util.c,v
retrieving revision 1.37
diff -u -p -r1.37 util.c
--- util.c	17 Jul 2006 03:41:12 -0000	1.37
+++ util.c	2 Jul 2007 14:04:59 -0000
@@ -241,6 +241,12 @@ void traverse(const char *dir, void (*fn
 char *str_basename(const char *s)
 {
 	char *p = strrchr(s, '/');
+#ifdef __CYGWIN__
+	char *q = strrchr(s, '\\');
+	if (!p || (q && (q > p))) {
+		p = q;
+	}
+#endif
 	if (p) {
 		return x_strdup(p+1);
 	} 
@@ -433,6 +439,21 @@ int create_empty_file(const char *fname)
 const char *get_home_directory(void)
 {
 	const char *p = getenv("HOME");
+
+	/* FIXME this is just for MSVC - do it better */
+	const char *homedrive = getenv("HOMEDRIVE");
+	const char *homepath = getenv("HOMEPATH");
+	char *tmp;
+
+	if (homedrive) {
+		if (homepath) {
+			x_asprintf(&tmp, "%s\\%s", homedrive, homepath);
+			return tmp;
+		}
+		else {
+			return homedrive;
+		}
+	}
 	if (p) {
 		return p;
 	}

