Add support for running a custom command to identify the compiler

This commit is contained in:
Joel Rosdahl 2010-08-10 20:26:10 +02:00
parent 0e828cb8b9
commit 18cc772aa6
7 changed files with 110 additions and 18 deletions

View File

@ -211,6 +211,32 @@ cases you won't need any of these as the defaults will be fine.
compiler's source has not changed, or if the compiler only has changes that
don't affect code generation). You should only use the *none* setting if
you know what you are doing.
_a command string_::
Hash the standard output and standard error output of the specified
command. The command is passed to +/bin/sh+ for execution. You can use
``$compiler'' in the command string to refer to the compiler. Example
commands:
+
--
* +$compiler -v+
* +$compiler -dumpmachine && $compiler -dumpversion+
You should make sure that the specified command is as fast as possible since it
will be run once for each ccache invocation.
Identifying the compiler using a command is useful if you want to avoid cache
misses when the compiler has been rebuilt but not changed.
Another case is when the compiler (as seen by ccache) actually isn't the real
compiler but another compiler wrapper -- in that case, the default *mtime*
method will hash the mtime and size of the other compiler wrapper, which means
that ccache won't be able to detect a compiler upgrade. Using a suitable
command to identify the compiler is thus safer, but it's also slower, so you
should consider continue using the *mtime* method in combination with
*CCACHE_PREFIX* if possible. See
<<_using_ccache_with_other_compiler_wrappers,USING CCACHE WITH OTHER COMPILER
WRAPPERS>>.
--
--
*CCACHE_CPP2*::
@ -572,17 +598,20 @@ environment variable *CCACHE_PREFIX* to the name of the wrapper (e.g. *distcc*)
and ccache will prefix the command line with the specified command when running
the compiler.
It is not recommended to use the form *ccache anotherwrapper compiler args* as
the compilation command. It's also not recommended to use the masquerading
technique for the other compiler wrapper. The reason is that ccache will in
both cases hash the mtime of the other wrapper instead of the real compiler
(see the *CCACHE_COMPILERCHECK* option), which means that:
Unless you set *CCACHE_COMPILERCHECK* to a suitable command (see the
description of that configuration option), it is not recommended to use the
form *ccache anotherwrapper compiler args* as the compilation command. It's
also not recommended to use the masquerading technique for the other compiler
wrapper. The reason is that by default, ccache will in both cases hash the
mtime and size of the other wrapper instead of the real compiler, which means
that:
* Compiler upgrades will not be detected properly.
* The cached results will not be shared between compilations with and without
the other wrapper.
* ccache will needlessly invoke the other wrapper when running the
preprocessor.
Another minor thing is that if *CCACHE_PREFIX* is not used, ccache will
needlessly invoke the other wrapper when running the preprocessor.
Bugs

View File

@ -32,6 +32,7 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
@ -914,10 +915,47 @@ calculate_common_hash(struct args *args, struct mdfour *hash)
} else if (str_eq(compilercheck, "content")) {
hash_delimiter(hash, "cc_content");
hash_file(hash, args->argv[0]);
} else { /* mtime */
} else if (str_eq(compilercheck, "mtime")) {
hash_delimiter(hash, "cc_mtime");
hash_int(hash, st.st_size);
hash_int(hash, st.st_mtime);
} else { /* command string */
char buf[8192];
static char compiler_env[1024];
size_t n;
char *command;
FILE *f;
int status;
cc_log("Running compiler check command: %s", compilercheck);
snprintf(compiler_env, sizeof(compiler_env),
"compiler=%s", orig_args->argv[0]);
putenv(compiler_env);
command = format("{ %s ; } </dev/null 2>&1", compilercheck);
f = popen(command, "r");
free(command);
if (!f) {
stats_update(STATS_COMPCHECK);
fatal("Compiler check popen failed");
}
hash_delimiter(hash, "cc_command");
while (1) {
n = fread(buf, 1, sizeof(buf), f);
hash_buffer(hash, buf, n);
if (n < sizeof(buf)) {
if (feof(f)) {
break;
} else {
stats_update(STATS_COMPCHECK);
fatal("Failed reading from compiler check command");
}
}
}
status = pclose(f);
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
stats_update(STATS_COMPCHECK);
fatal("Compiler check command returned %d", WEXITSTATUS(status));
}
}
/*

View File

@ -52,6 +52,7 @@ enum stats {
STATS_NOOUTPUT = 23,
STATS_EMPTYOUTPUT = 24,
STATS_BADEXTRAFILE = 25,
STATS_COMPCHECK = 26,
STATS_END
};

View File

@ -79,6 +79,7 @@ def test(tmp_dir, options, compiler_args, source_file):
fp.close()
environment = {"CCACHE_DIR": ccache_dir, "PATH": environ["PATH"]}
environment["CCACHE_COMPILERCHECK"] = options.compilercheck
if options.compression:
environment["CCACHE_COMPRESS"] = "1"
if options.hardlink:
@ -204,6 +205,9 @@ def main(argv):
op.add_option(
"--ccache",
help="location of ccache (default: %s)" % DEFAULT_CCACHE)
op.add_option(
"--compilercheck",
help="specify compilercheck (default: mtime)")
op.add_option(
"--compression",
help="use compression",
@ -236,6 +240,7 @@ def main(argv):
action="store_true")
op.set_defaults(
ccache=DEFAULT_CCACHE,
compilercheck="mtime",
directory=DEFAULT_DIRECTORY,
times=DEFAULT_TIMES)
(options, args) = op.parse_args(argv[1:])
@ -259,6 +264,7 @@ def main(argv):
print "Compilation command: %s -c -o %s.o" % (
" ".join(args),
splitext(argv[-1])[0])
print "Compilercheck:", options.compilercheck
print "Compression:", on_off(options.compression)
print "Hardlink:", on_off(options.hardlink)
print "Nostats:", on_off(options.nostats)

View File

@ -71,6 +71,7 @@ static struct {
{ STATS_MISSING, "cache file missing ", NULL, 0 },
{ STATS_ARGS, "bad compiler arguments ", NULL, 0 },
{ STATS_SOURCELANG, "unsupported source language ", NULL, 0 },
{ STATS_COMPCHECK, "compiler check failed ", NULL, 0 },
{ STATS_CONFTEST, "autoconf compile/link ", NULL, 0 },
{ STATS_UNSUPPORTED, "unsupported compiler option ", NULL, 0 },
{ STATS_OUTSTDOUT, "output to stdout ", NULL, 0 },

26
test.sh
View File

@ -388,6 +388,32 @@ EOF
checkstat 'cache hit (preprocessed)' 2
checkstat 'cache miss' 1
testname="compilercheck=command"
$CCACHE -z >/dev/null
backdate compiler.sh
CCACHE_COMPILERCHECK='echo $compiler' $CCACHE ./compiler.sh -c test1.c
checkstat 'cache hit (preprocessed)' 0
checkstat 'cache miss' 1
echo "# Compiler upgrade" >>compiler.sh
CCACHE_COMPILERCHECK="echo ./compiler.sh" $CCACHE ./compiler.sh -c test1.c
checkstat 'cache hit (preprocessed)' 1
checkstat 'cache miss' 1
CCACHE_COMPILERCHECK='echo bar >&2' $CCACHE ./compiler.sh -c test1.c
checkstat 'cache hit (preprocessed)' 1
checkstat 'cache miss' 2
CCACHE_COMPILERCHECK='read x; echo -n b >&2; echo ar >&2' $CCACHE ./compiler.sh -c test1.c
checkstat 'cache hit (preprocessed)' 2
checkstat 'cache miss' 2
testname="compilercheck=unknown_command"
$CCACHE -z >/dev/null
backdate compiler.sh
CCACHE_COMPILERCHECK="unknown_command" $CCACHE ./compiler.sh -c test1.c 2>/dev/null
if [ "$?" -eq 0 ]; then
test_failed "Expected failure running unknown_command to verify compiler but was success"
fi
checkstat 'compiler check failed' 1
testname="no object file"
cat <<'EOF' >test_no_obj.c
int test_no_obj;

11
util.c
View File

@ -140,16 +140,7 @@ fatal(const char *format, ...)
vsnprintf(msg, sizeof(msg), format, ap);
va_end(ap);
if (cache_logfile) {
if (!logfile) {
logfile = fopen(cache_logfile, "a");
}
if (logfile) {
fprintf(logfile, "[%-5d] FATAL: %s\n", (int)getpid(), msg);
fflush(logfile);
}
}
cc_log("FATAL: %s", msg);
fprintf(stderr, "ccache: FATAL: %s\n", msg);
exit(1);