From: Joel Rosdahl Date: Thu, 26 Nov 2009 22:36:19 +0000 (+0100) Subject: Rewrite absolute paths under CCACHE_BASEDIR to relative X-Git-Tag: v3.0pre0~156 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f5bb7e4e5a4f05ea66b37d417c270462559f376a;p=thirdparty%2Fccache.git Rewrite absolute paths under CCACHE_BASEDIR to relative --- diff --git a/NEWS b/NEWS index abf2b3d2d..47f2744c3 100644 --- a/NEWS +++ b/NEWS @@ -10,6 +10,14 @@ New features and improvements: speedup will be higher when I/O is fast (e.g., when files are in the disk cache). The direct mode can be disabled by setting CCACHE_NODIRECT. + - When hashing the output from the preprocessor, absolute paths for which the + path specified by CCACHE_BASEDIR is a prefix are rewritten to relative. + Paths specified by -I and similar flags get the same treatment. This is + done to get cache hits even when compiling with -g and when using absolute + include directory paths. Absolute paths in the standard error text will + also be more accurate. CCACHE_BASEDIR defaults to the current working + directory. + - Object files are now by default stored compressed in the cache. The runtime cost is negligible, and more files will fit in the ccache directory and in the disk cache. CCACHE_NOCOMPRESS can be set to disable object file diff --git a/ccache.c b/ccache.c index 2dc1e5691..ef64fca25 100644 --- a/ccache.c +++ b/ccache.c @@ -30,6 +30,9 @@ #include +/* current working directory taken from $PWD, or getcwd() if $PWD is bad */ +char *current_working_dir; + /* the base cache directory */ char *cache_dir = NULL; @@ -39,6 +42,9 @@ char *temp_dir = NULL; /* the debug logfile name, if set */ char *cache_logfile = NULL; +/* base directory (from CCACHE_BASEDIR) */ +char *base_dir; + /* the argument list after processing */ static ARGS *stripped_args; @@ -306,11 +312,28 @@ ignore: } } +/* + * Make a relative path from CCACHE_BASEDIR to path. Takes over ownership of + * path. Caller frees. + */ +static char *make_relative_path(char *path) +{ + char *relpath; + + if (!base_dir || strncmp(path, base_dir, strlen(base_dir)) != 0) { + return path; + } + + relpath = get_relative_path(current_working_dir, path); + free(path); + return relpath; +} + /* * This function reads and hashes a file. While doing this, it also does these * things with preprocessor lines starting with a hash: * - * - TODO: Makes include file paths that match CCACHE_RELDIRS relative. + * - Makes include file paths whose prefix is CCACHE_BASEDIR relative. * - Stores the paths of included files in the global variable included_files. */ static void process_preprocessed_file(struct mdfour *hash, const char *path) @@ -367,10 +390,12 @@ static void process_preprocessed_file(struct mdfour *hash, const char *path) } /* p and q span the include file path */ path = x_strndup(p, q - p); - /* TODO: Rewrite path using CCACHE_RELDIRS here. */ + path = make_relative_path(path); hash_string(hash, path); if (enable_direct) { remember_include_file(path, q - p); + } else { + free(path); } p = q; } else { @@ -644,28 +669,36 @@ static int find_hash(ARGS *args, enum findhash_call_mode mode) continue; } - if (mode == FINDHASH_CPP_MODE) { - /* When using the preprocessor, 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. */ - if (i < args->argc-1) { - if (strcmp(args->argv[i], "-I") == 0 || - strcmp(args->argv[i], "-include") == 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], "-D", 2) == 0 || - strncmp(args->argv[i], "-idirafter", 10) == 0 || - strncmp(args->argv[i], "-isystem", 8) == 0) { + /* When using the preprocessor, 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. */ + if (i < args->argc-1) { + if (strcmp(args->argv[i], "-I") == 0 || + strcmp(args->argv[i], "-include") == 0 || + strcmp(args->argv[i], "-D") == 0 || + strcmp(args->argv[i], "-idirafter") == 0 || + strcmp(args->argv[i], "-isystem") == 0) { + if (mode == FINDHASH_DIRECT_MODE) { + hash_string(&hash, args->argv[i]); + s = make_relative_path(x_strdup(args->argv[i+1])); + hash_string(&hash, s); + free(s); + } /* else: skip from hash */ + i++; continue; } } + if (strncmp(args->argv[i], "-I", 2) == 0 || + strncmp(args->argv[i], "-D", 2) == 0) { + if (mode == FINDHASH_DIRECT_MODE) { + hash_buffer(&hash, args->argv[i], 2); + s = make_relative_path(x_strdup(args->argv[i] + 2)); + hash_string(&hash, s); + free(s); + } /* else: skip from hash */ + continue; + } if (strncmp(args->argv[i], "--specs=", 8) == 0 && stat(args->argv[i]+8, &st) == 0) { @@ -928,7 +961,7 @@ static void from_cache(enum fromcache_call_mode mode, int put_object_in_manifest close(fd_stderr); /* Create or update the manifest file. */ - if (put_object_in_manifest) { + if (put_object_in_manifest && included_files) { if (manifest_put(manifest_path, object_hash, included_files)) { cc_log("Added object file hash to manifest\n"); /* Update timestamp for LRU cleanup. */ @@ -1315,6 +1348,8 @@ static void ccache(int argc, char *argv[]) cc_log("=== %s ===\n", now); + cc_log("Base directory: %s\n", base_dir); + /* find the real compiler */ find_compiler(argc, argv); @@ -1549,6 +1584,8 @@ int main(int argc, char *argv[]) { char *p; + current_working_dir = get_cwd(); + cache_dir = getenv("CCACHE_DIR"); if (!cache_dir) { const char *home_directory = get_home_directory(); @@ -1564,8 +1601,16 @@ int main(int argc, char *argv[]) cache_logfile = getenv("CCACHE_LOGFILE"); - setup_uncached_err(); + base_dir = getenv("CCACHE_BASEDIR"); + if (base_dir) { + if (strcmp(base_dir, "") == 0) { + base_dir = NULL; + } + } else { + base_dir = get_cwd(); + } + setup_uncached_err(); /* the user might have set CCACHE_UMASK */ p = getenv("CCACHE_UMASK"); diff --git a/ccache.h b/ccache.h index 08c5fdc71..0095dabda 100644 --- a/ccache.h +++ b/ccache.h @@ -129,6 +129,9 @@ char *x_realpath(const char *path); char *gnu_getcwd(void); int create_empty_file(const char *fname); const char *get_home_directory(void); +char *get_cwd(); +size_t common_dir_prefix_length(const char *s1, const char *s2); +char *get_relative_path(const char *from, const char *to); void stats_update(enum stats stat); void stats_zero(void); diff --git a/test.sh b/test.sh index f778a8665..90f9b4bd3 100755 --- a/test.sh +++ b/test.sh @@ -9,7 +9,7 @@ else COMPILER=cc fi -CCACHE=../ccache +CCACHE=$PWD/ccache TESTDIR=testdir.$$ unset CCACHE_DISABLE @@ -63,7 +63,7 @@ checkfile() { basetests() { echo "starting testsuite $testsuite" - rm -rf .ccache + rm -rf $CCACHE_DIR checkstat 'cache hit (preprocessed)' 0 checkstat 'cache miss' 0 @@ -253,13 +253,12 @@ basetests() { $CCACHE -C > /dev/null checkstat 'files in cache' 0 - rm -f test1.c } direct_tests() { echo "starting testsuite $testsuite" - rm -rf .ccache + rm -rf $CCACHE_DIR unset CCACHE_NODIRECT ################################################################## @@ -285,7 +284,7 @@ EOF ################################################################## # First compilation is a miss. testname="first compilation" - $CCACHE -C >/dev/null + $CCACHE -z >/dev/null $CCACHE $COMPILER -c test.c checkstat 'cache hit (direct)' 0 checkstat 'cache hit (preprocessed)' 0 @@ -447,19 +446,118 @@ EOF $CCACHE -C >/dev/null } +basedir_tests() { + echo "starting testsuite $testsuite" + rm -rf $CCACHE_DIR + + ################################################################## + # Create some code to compile. + mkdir -p dir1/src dir1/include + cat <dir1/src/test.c +#include +EOF + cat <dir1/include/test.h +int test; +EOF + cp -r dir1 dir2 + + sleep 1 # Sleep to make the include files trusted. + + ################################################################## + # CCACHE_BASEDIR="" and using absolute include path will result in a cache + # miss. + testname="empty CCACHE_BASEDIR" + $CCACHE -z >/dev/null + + cd dir1 + CCACHE_BASEDIR="" $CCACHE $COMPILER -I$PWD/include -c src/test.c + checkstat 'cache hit (direct)' 0 + checkstat 'cache hit (preprocessed)' 0 + checkstat 'cache miss' 1 + cd .. + + cd dir2 + CCACHE_BASEDIR="" $CCACHE $COMPILER -I$PWD/include -c src/test.c + checkstat 'cache hit (direct)' 0 + checkstat 'cache hit (preprocessed)' 0 + checkstat 'cache miss' 2 + cd .. + + ################################################################## + # Setting CCACHE_BASEDIR will result in a cache hit because include paths + # in the preprocessed output are rewritten. + testname="set CCACHE_BASEDIR" + $CCACHE -z >/dev/null + $CCACHE -C >/dev/null + + cd dir1 + CCACHE_BASEDIR="$PWD" $CCACHE $COMPILER -I$PWD/include -c src/test.c + checkstat 'cache hit (direct)' 0 + checkstat 'cache hit (preprocessed)' 0 + checkstat 'cache miss' 1 + cd .. + + cd dir2 + CCACHE_BASEDIR="$PWD" $CCACHE $COMPILER -I $PWD/include -c src/test.c + checkstat 'cache hit (direct)' 0 + checkstat 'cache hit (preprocessed)' 1 + checkstat 'cache miss' 1 + cd .. + + ################################################################## + # Setting CCACHE_BASEDIR will result in a cache hit because -I arguments + # are rewritten, as are the paths stored in the manifest. + testname="set CCACHE_BASEDIR, direct lookup" + $CCACHE -z >/dev/null + $CCACHE -C >/dev/null + unset CCACHE_NODIRECT + + cd dir1 + CCACHE_BASEDIR="$PWD" $CCACHE $COMPILER -I$PWD/include -c src/test.c + checkstat 'cache hit (direct)' 0 + checkstat 'cache hit (preprocessed)' 0 + checkstat 'cache miss' 1 + cd .. + + cd dir2 + CCACHE_BASEDIR="$PWD" $CCACHE $COMPILER -I $PWD/include -c src/test.c + checkstat 'cache hit (direct)' 1 + checkstat 'cache hit (preprocessed)' 0 + checkstat 'cache miss' 1 + cd .. + + CCACHE_NODIRECT=1 + export CCACHE_NODIRECT + + ################################################################## + # CCACHE_BASEDIR="$PWD" is the default. + testname="default CCACHE_BASEDIR" + cd dir1 + $CCACHE -z >/dev/null + $CCACHE $COMPILER -I$PWD/include -c src/test.c + checkstat 'cache hit (direct)' 0 + checkstat 'cache hit (preprocessed)' 1 + checkstat 'cache miss' 0 + cd .. +} + ###### # main program rm -rf $TESTDIR mkdir $TESTDIR cd $TESTDIR || exit 1 -mkdir .ccache -CCACHE_DIR=.ccache + +CCACHE_DIR=$PWD/.ccache export CCACHE_DIR -CCACHE_LOGFILE=ccache.log +CCACHE_LOGFILE=$PWD/ccache.log export CCACHE_LOGFILE CCACHE_NODIRECT=1 export CCACHE_NODIRECT +mkdir $CCACHE_DIR + +# --------------------------------------- + testsuite="base" CCACHE_COMPILE="$CCACHE $COMPILER" basetests @@ -500,6 +598,11 @@ unset CCACHE_NLEVELS testsuite="direct" direct_tests +testsuite="basedir" +basedir_tests + +# --------------------------------------- + cd .. rm -rf $TESTDIR echo test done - OK diff --git a/util.c b/util.c index c5199d16e..fab119093 100644 --- a/util.c +++ b/util.c @@ -472,6 +472,27 @@ void *x_realloc(void *ptr, size_t size) } +/* + * This is like x_asprintf() but frees *ptr if *ptr != NULL. + */ +void x_asprintf2(char **ptr, const char *format, ...) +{ + char *saved = *ptr; + va_list ap; + + *ptr = NULL; + va_start(ap, format); + if (vasprintf(ptr, format, ap) == -1) { + fatal("Out of memory in x_asprintf2\n"); + } + va_end(ap); + + if (!ptr) fatal("Out of memory in x_asprintf2\n"); + if (saved) { + free(saved); + } +} + /* revsusive directory traversal - used for cleanup fn() is called on all files/dirs in the tree @@ -734,3 +755,89 @@ const char *get_home_directory(void) cc_log("Unable to determine home directory"); return NULL; } + +/* + * Get the current directory by reading $PWD. If $PWD isn't sane, gnu_getcwd() + * is used. + */ +char *get_cwd() +{ + char *pwd; + char *cwd; + struct stat st_pwd; + struct stat st_cwd; + + cwd = gnu_getcwd(); + pwd = getenv("PWD"); + if (!pwd) { + return cwd; + } + if (stat(pwd, &st_pwd) != 0) { + return cwd; + } + if (stat(cwd, &st_cwd) != 0) { + return cwd; + } + if (st_pwd.st_dev == st_cwd.st_dev && st_pwd.st_ino == st_cwd.st_ino) { + return x_strdup(pwd); + } else { + return cwd; + } +} + +/* + * Compute the length of the longest directory path that is common to two + * strings. + */ +size_t common_dir_prefix_length(const char *s1, const char *s2) +{ + const char *p1 = s1; + const char *p2 = s2; + + while (*p1 && *p2 && *p1 == *p2) { + ++p1; + ++p2; + } + while (p1 > s1 && ((*p1 && *p1 != '/' ) || (*p2 && *p2 != '/'))) { + p1--; + p2--; + } + return p1 - s1; +} + +/* + * Compute a relative path from from to to. Caller frees. + */ +char *get_relative_path(const char *from, const char *to) +{ + size_t common_prefix_len; + int i; + const char *p; + char *result; + + if (!*to || *to != '/') { + return x_strdup(to); + } + + result = x_strdup(""); + common_prefix_len = common_dir_prefix_length(from, to); + for (p = from + common_prefix_len; *p; p++) { + if (*p == '/') { + x_asprintf2(&result, "../%s", result); + } + } + if (strlen(to) > common_prefix_len) { + x_asprintf2(&result, "%s%s", result, + to + common_prefix_len + 1); + } + i = strlen(result) - 1; + while (i >= 0 && result[i] == '/') { + result[i] = '\0'; + i--; + } + if (strcmp(result, "") == 0) { + free(result); + result = x_strdup("."); + } + return result; +}