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
#include <getopt.h>
+/* current working directory taken from $PWD, or getcwd() if $PWD is bad */
+char *current_working_dir;
+
/* the base cache directory */
char *cache_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;
}
}
+/*
+ * 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)
}
/* 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 {
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) {
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. */
cc_log("=== %s ===\n", now);
+ cc_log("Base directory: %s\n", base_dir);
+
/* find the real compiler */
find_compiler(argc, argv);
{
char *p;
+ current_working_dir = get_cwd();
+
cache_dir = getenv("CCACHE_DIR");
if (!cache_dir) {
const char *home_directory = get_home_directory();
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");
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);
COMPILER=cc
fi
-CCACHE=../ccache
+CCACHE=$PWD/ccache
TESTDIR=testdir.$$
unset CCACHE_DISABLE
basetests() {
echo "starting testsuite $testsuite"
- rm -rf .ccache
+ rm -rf $CCACHE_DIR
checkstat 'cache hit (preprocessed)' 0
checkstat 'cache miss' 0
$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
##################################################################
##################################################################
# 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
$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 <<EOF >dir1/src/test.c
+#include <test.h>
+EOF
+ cat <<EOF >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
testsuite="direct"
direct_tests
+testsuite="basedir"
+basedir_tests
+
+# ---------------------------------------
+
cd ..
rm -rf $TESTDIR
echo test done - OK
}
+/*
+ * 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
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;
+}