--- Begin Message ---
The patch adds a new option which is documented below. It is very useful
to get more hits when building with pbuilder and the only change is
version number of the package (provided build system locally uses relative
paths whenever possible). For example:
CACHE_SUBST_COMPILATION_DIR='s#^/tmp/buildd/\([^/]\+\)-[^/]\+/#/tmp/buildd/\1/#'
will always change compilation directory from /tmp/buildd/package-X.Y.Z/ to
/tmp/buildd/package/.
=========== Excerpt from the cmake.1 ====================
CCACHE_SUBST_COMPILATION_DIR
If you set this environment variable, ccache will attempt to perform
the specified substitution on the compilation directory path the C
pre-processor generates. This option will help to get much more cache
hits if the build directory path keeps changing (e.g. version embeded is
included in it) while changes to the actual code stay minor. Please note:
* Currently, this option will probably only work with GNU C/C++ compilers.
* GNU C pre-processor emits compilation directory only if debugging
(-g) option was enabled. Therefore, if you do not use the -g option,
this variable probably won't be useful for you. You could have a look at
CCACHE_UNIFY instead though.
* You should make sure that the application build system uses relative
paths to the local source code whenever possible otherwise specifying
this option won't have the desired effect.
* Use the substitution to stabilize the compilation directory path so it
won't be the only factor keeping the source file hash different.
* Please make sure that the new compilation directory actually exists if
you intend to debug the application once it is built.
* This option won't have any effect if CCACHE_CPP2 is enabled.
The value of this option should be an expression of the form
s/search_regex/replacement/ (or any other character might be used
instead of /) where "search_regex" is a basic POSIX regular expresion
(see regex(7)) to be matched against the compilation directory path and
"replacement" is the replacement string for the match which can contain
up to 9 backreferences to the match groups of the form \N starting
from 1.
Signed-off-by: Modestas Vainius <[email protected]>
---
Makefile.in | 2 +-
ccache.1 | 36 +++++++++++
ccache.c | 136 +++++++++++++++++++++++++++++++++++++++-
ccache.h | 7 ++
execute.c | 96 ++++++++++++++++++++++++++++
regexp.c | 202 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 477 insertions(+), 2 deletions(-)
diff --git a/Makefile.in b/Makefile.in
index 0bf3eb9..214fe08 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -12,7 +12,7 @@ cfla...@cflags@ -I.
exee...@exeext@
OBJS= ccache.o mdfour.o hash.o execute.o util.o args.o stats.o \
- cleanup.o snprintf.o unify.o
+ cleanup.o snprintf.o unify.o regexp.o
HEADERS = ccache.h mdfour.h
all: ccache$(EXEEXT)
diff --git a/ccache.1 b/ccache.1
index daec8ee..e059138 100644
--- a/ccache.1
+++ b/ccache.1
@@ -247,6 +247,42 @@ cached compiles with CCACHE_UNIFY set cannot be used when
CCACHE_UNIFY is not set and vice versa\&. The reason the unifier is off
by default is that it can give incorrect line number information in
compiler warning messages\&.
+.IP
+.IP "\fBCCACHE_SUBST_COMPILATION_DIR\fP"
+If you set this environment variable, ccache will attempt to perform
+the specified substitution on the compilation directory path the C
+pre-processor generates. This option will help to get much more cache
+hits if the build directory path keeps changing (e.g. version embeded is
+included in it) while changes to the actual code stay minor. Please note:
+.RS
+.IP o
+Currently, this option will probably only work with GNU C/C++ compilers.
+.IP o
+GNU C pre-processor emits compilation directory only if debugging
+(-g) option was enabled. Therefore, if you do not use the -g option,
+this variable probably won't be useful for you. You could have a look at
+CCACHE_UNIFY instead though.
+.IP o
+You should make sure that the application build system uses relative
+paths to the local source code whenever possible otherwise specifying
+this option won't have the desired effect.
+.IP o
+Use the substitution to stabilize the compilation directory path so it
+won't be the only factor keeping the source file hash different.
+.IP o
+Please make sure that the new compilation directory actually exists if
+you intend to debug the application once it is built.
+.IP o
+This option won't have any effect if CCACHE_CPP2 is enabled.
+.RE
+.IP
+The value of this option should be an expression of the form
+\fBs/search_regex/replacement/\fP (or any other character might be used
+instead of /) where \fIsearch_regex\fP is a basic POSIX regular expresion
+(see regex(7)) to be matched against the compilation directory path and
+\fIreplacement\fP is the replacement string for the match which can contain
+up to 9 backreferences to the match groups of the form \fB\\N\fP starting
+from 1.
.IP
.IP "\fBCCACHE_EXTENSION\fP"
Normally ccache tries to automatically
diff --git a/ccache.c b/ccache.c
index c15ced5..f51e370 100644
--- a/ccache.c
+++ b/ccache.c
@@ -248,6 +248,126 @@ static void to_cache(ARGS *args)
free(path_stderr);
}
+struct cds {
+ const char* substitution_pattern;
+ char skip_line;
+ char state;
+ char path[PATH_MAX+1];
+ int path_l;
+ char* buf;
+ int bufsize;
+ char* newpath;
+};
+
+int compilation_dir_substitution(char **data, int *size, void *priv)
+{
+ struct cds *cds;
+ int i;
+ char* buf;
+
+ cds = (struct cds*) priv;
+
+ // Clean up/restore from the previous run
+ if (cds->state == 6) {
+ // Now write the newpath itself
+ *data = cds->newpath;
+ *size = strlen(cds->newpath);
+ cds->state = 7;
+ return -1; // again
+ } else if (cds->newpath) {
+ // New path was just written. We are done.
+ // Write the rest and quit.
+ free(cds->newpath);
+ *data = cds->buf;
+ *size = cds->bufsize;
+ return 0; // done
+ } else if (cds->buf) {
+ // Continue processing the buffer
+ *data = buf = cds->buf;
+ *size = cds->bufsize;
+ cds->buf = 0;
+ cds->bufsize = 0;
+ } else {
+ buf = *data;
+ }
+
+ for (i = 0; i < *size; i++) {
+ if (cds->skip_line) {
+ for (; i < *size && buf[i] != '\n'; i++);
+ cds->skip_line = !!(i == *size);
+ } else {
+ if ((cds->state == 0 && buf[i] == '#') ||
+ (cds->state == 1 && buf[i] == ' ') ||
+ (cds->state == 2 && buf[i] == '1') ||
+ (cds->state == 3 && buf[i] == ' ') ||
+ (cds->state == 4 && buf[i] == '"')) {
+ // Beginning of our statement: # 1 "
+ cds->state++;
+ continue;
+ } else if ((cds->state == 2 && buf[i] != '1') ||
+ (cds->state == 3 && buf[i] >= '0' && buf[i]
<= '9')) {
+ // Line number is no longer 1. We are done.
+ return 0;
+ } else if (cds->state == 5) {
+ // state=5 is processing of the compilation
directory path
+ if (buf[i] == '"') {
+ // End of compilation directory path
+ // It should end with //
+ cds->path[cds->path_l] = 0;
+ if (cds->path_l > 2 &&
+ cds->path[cds->path_l-1] == '/'
&&
+ cds->path[cds->path_l-2] ==
'/') {
+
+ cds->newpath =
regexp_substitute(cds->substitution_pattern,
+
cds->path);
+ if (cds->newpath) {
+ // Write what's before
the path if there is anything
+ cds->buf = buf + i;
+ cds->bufsize = *size -
i;
+ *size = i - cds->path_l;
+ cds->state = 6;
+ return -1; // again
+ }
+ }
+ } else if (buf[i] != '\n' && buf[i] != '\r') {
+ // Put the character into our path
buffer
+ if (cds->path_l < PATH_MAX) {
+ cds->path[cds->path_l++] =
buf[i];
+ continue;
+ }
+ }
+ // Whatever was it, it was too long for the
path or
+ // the path was not the one we need.
+ if (i+1 < cds->path_l) {
+ // The path was held in the buffer and
needs to be
+ // written now.
+ cds->buf = buf + i;
+ cds->bufsize = *size - i;
+ *data = cds->path;
+ *size = cds->path_l;
+ cds->skip_line = (buf[i] != '\n' &&
buf[i] != '\r');
+ cds->state = 0;
+ cds->path_l = 0;
+ return -1; // again
+ }
+ // Otherwise just continue processing
+ }
+ // If we are here, nothing interesting was found
+ // Reset state and move on.
+ cds->skip_line = (buf[i] != '\n' && buf[i] != '\r');
+ cds->state = 0;
+ cds->path_l = 0;
+ }
+ }
+
+ if (cds->state >= 5) {
+ // Processing in progress. Write stuff before the path
+ // if there is any
+ *size -= cds->path_l;
+ }
+ return 1; // get me the next buffer
+}
+
/* find the hash for a command. The hash includes all argument lists,
plus the output from running the compiler with -E */
static void find_hash(ARGS *args)
@@ -363,10 +483,24 @@ static void find_hash(ARGS *args)
x_asprintf(&path_stderr, "%s/tmp.cpp_stderr.%s", temp_dir,
tmp_string());
if (!direct_i_file) {
+ char *substitution_pattern;
/* 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);
+
+ // Incompatible with the CCACHE_CPP2 option.
+ if (!getenv("CCACHE_CPP2") &&
+ (substitution_pattern =
getenv("CCACHE_SUBST_COMPILATION_DIR"))) {
+
+ struct cds cds;
+ memset(&cds, 0, sizeof(struct cds));
+ cds.substitution_pattern = substitution_pattern;
+
+ status = execute_and_process(args->argv, path_stdout,
path_stderr,
+ compilation_dir_substitution, &cds);
+ } else {
+ status = execute(args->argv, path_stdout, path_stderr);
+ }
args_pop(args, 2);
} else {
/* we are compiling a .i or .ii file - that means we
diff --git a/ccache.h b/ccache.h
index faec597..5d87a5f 100644
--- a/ccache.h
+++ b/ccache.h
@@ -123,6 +123,13 @@ void cleanup_dir(const char *dir, size_t maxfiles, size_t
maxsize);
void cleanup_all(const char *dir);
void wipe_all(const char *dir);
+char* regexp_substitute(const char* pattern, const char* string);
+
+typedef int (*execute_and_process_proc)(char** data, int *size, void *priv);
+int execute_and_process(char **argv,
+ const char *path_stdout,
+ const char *path_stderr,
+ execute_and_process_proc proc, void* priv);
int execute(char **argv,
const char *path_stdout,
const char *path_stderr);
diff --git a/execute.c b/execute.c
index 4b98ab7..68a65a4 100644
--- a/execute.c
+++ b/execute.c
@@ -1,5 +1,6 @@
/*
Copyright (C) Andrew Tridgell 2002
+ Copyright (C) Modestas Vainius 2008
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -66,6 +67,101 @@ int execute(char **argv,
return WEXITSTATUS(status);
}
+/*
+ Execute function with output postprocessing (via proc callback)
+*/
+int execute_and_process(char **argv,
+ const char *path_stdout,
+ const char *path_stderr,
+ execute_and_process_proc proc, void* priv)
+{
+ pid_t pid;
+ int status;
+ int pipedes[2];
+ int fd_stdout;
+ char buf[4096];
+ int nread;
+
+ if (pipe(pipedes))
+ fatal("Unable to create a pipe");
+
+ pid = fork();
+ if (pid == -1) fatal("Failed to fork");
+
+ if (pid == 0) {
+ int fd;
+
+ close(pipedes[0]); // Do not need this end
+
+ dup2(pipedes[1], 1);
+ close(pipedes[1]);
+
+ unlink(path_stderr);
+ fd = open(path_stderr,
O_WRONLY|O_CREAT|O_TRUNC|O_EXCL|O_BINARY, 0666);
+ if (fd == -1) {
+ exit(STATUS_NOCACHE);
+ }
+ dup2(fd, 2);
+ close(fd);
+
+ exit(execv(argv[0], argv));
+ }
+
+ close(pipedes[1]); // Do not need this end
+
+ // Open a file for gcc output
+ unlink(path_stdout);
+ fd_stdout = open(path_stdout, O_WRONLY|O_CREAT|O_TRUNC|O_EXCL|O_BINARY,
0666);
+ if (fd_stdout == -1) {
+ exit(STATUS_NOCACHE);
+ }
+
+ // Read from pipe
+ while ((nread = read(pipedes[0], buf, sizeof(buf)))) {
+ char *data;
+ int ret;
+ data = buf;
+
+ if (nread < 0) {
+ perror("Failure reading data from the compiler");
+ fatal("Failure reading data from the compiler");
+ }
+ do {
+ ret = proc(&data, &nread, priv);
+ if (nread > 0) {
+ if (write(fd_stdout, data, nread) != nread) {
+ perror("Failure to write");
+ fatal("Failure to write");
+ }
+ }
+ } while (ret == -1); // again
+ if (ret == 0)
+ break; // we are done with proc
+ }
+ // Write what's left
+ while ((nread = read(pipedes[0], buf, sizeof(buf)))) {
+ if (nread < 0) {
+ perror("Failure reading data from the compiler");
+ fatal("Failure reading data from the compiler");
+ }
+ if (write(fd_stdout, buf, nread) != nread) {
+ perror("Failure to write");
+ fatal("Failure to write");
+ }
+ }
+ close(pipedes[0]);
+ close(fd_stdout);
+
+ if (waitpid(pid, &status, 0) != pid) {
+ fatal("waitpid failed");
+ }
+
+ if (WEXITSTATUS(status) == 0 && WIFSIGNALED(status)) {
+ return -1;
+ }
+
+ return WEXITSTATUS(status);
+}
/*
find an executable by name in $PATH. Exclude any that are links to
exclude_name
diff --git a/regexp.c b/regexp.c
new file mode 100644
index 0000000..08aa45d
--- /dev/null
+++ b/regexp.c
@@ -0,0 +1,202 @@
+/*
+ Copyright (C) Modestas Vainius 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include <stdlib.h>
+#include <string.h>
+#include <regex.h>
+
+#include "ccache.h"
+
+char regexp_error(int ret, regex_t *regex, const char* msg)
+{
+ if (ret) {
+ size_t size = regerror(ret, regex, 0, 0);
+ char* errmsg = malloc(size);
+ if (errmsg) {
+ if (regerror(ret, regex, errmsg, size) > 0) {
+ cc_log("%s: %s", msg, errmsg);
+ }
+ free(errmsg);
+ } else {
+ cc_log(msg);
+ }
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+
+/*
+ Matches the 'string' against the 'pattern', performs requested
+ substitutions and returns a new resulting string with replacements made.
+
+ The pattern is of the 's/search_pattern_regexp/replacement/' form (like
+ sed(1) or perl). Basic POSIX regular expressions are supported in the
+ 'search_pattern_regexp' and up to 9 backreferences (\N) can be
specified in
+ the 'replacement' string.
+
+ The function returns a new dinamically allocated string which you should
+ free with free() when it is no longer needed. In case of failure, the
+ function returns 0.
+*/
+char* regexp_substitute(const char* pattern, const char* string)
+{
+ int i, l, subst_length;
+ char sep;
+ char *pat, *search, *subst;
+ char *res;
+
+ regex_t regex;
+ int backrefcount;
+ regoff_t backrefs[9];
+ regmatch_t matches[10];
+
+ res = 0;
+ if (!(pat = strdup(pattern)))
+ return 0;
+
+ l = strlen(pat);
+ if (l < 4) // at least s///
+ return 0;
+
+ if (pat[0] != 's')
+ return 0;
+
+ // Search for separators and split the pattern
+ sep = pat[1]; // separator
+ search = subst = 0;
+ subst_length = 0;
+ for (i = 2; i < l; i++) {
+ if (pat[i] == sep && (pat[i-1] != '\\' || pat[i-2] == '\\')) {
+ if (!search) {
+ search = pat + 2;
+ pat[i] = 0;
+ } else if (!subst) {
+ subst = search + strlen(search) + 1;
+ pat[i] = 0;
+ subst_length = strlen(subst);
+ } else {
+ pat[i] = 0;
+ break;
+ }
+ }
+ }
+
+ // Incomplete pattern
+ if (!search || !subst) {
+ cc_log("Substitution pattern %s is incomplete", pat);
+ goto exit;
+ }
+
+ // Analize the 'subst' pattern and count backreferences
+ backrefcount = 0;
+ for (i = 0; i < subst_length - 1; i++) {
+ if (subst[i] == '\\' && subst[i+1] >= '0' && subst[i+1] <= '9')
{
+ backrefs[backrefcount] = i;
+ if (++backrefcount == 9)
+ break;
+ }
+ }
+
+ // Now do regexp matching. Compile first
+ if (regexp_error(
+ regcomp(®ex, search, 0),
+ ®ex,
+ "Unable to compile regexp")) {
+ search = 0;
+ goto exit;
+ }
+
+ for (i = 0; i < 10; i++)
+ matches[i].rm_so = -10;
+ i = regexec(®ex, string, backrefcount + 1, matches, 0);
+ if (!i) {
+ int backref, backrefstrlen;
+ int stringlen, reslen;
+ char *resi;
+
+ // Calculate length of the result
+ backrefstrlen = 0;
+ for (i = 0; i < backrefcount; i++) {
+ backref = subst[backrefs[i]+1] - '0';
+ backrefstrlen +=
+ matches[backref].rm_eo -
+ matches[backref].rm_so;
+ }
+ stringlen = strlen(string);
+ reslen = matches[0].rm_so +
+ subst_length - (backrefcount*2) + backrefstrlen +
+ stringlen - matches[0].rm_eo;
+
+ if (!(resi = res = malloc(reslen+1)))
+ goto exit;
+ res[reslen] = 0;
+
+ // Copy the start that didn't match
+ if (matches[0].rm_so > 0) {
+ memcpy(resi, string, matches[0].rm_so);
+ resi += matches[0].rm_so;
+ }
+ if (backrefcount == 0) {
+ // No backreferences, just copy the 'subst' string
+ memcpy(resi, subst, subst_length);
+ resi += subst_length;
+ } else {
+ // Process backreferences
+ // Start
+ if (backrefs[0] > 0) {
+ memcpy(resi, subst, backrefs[0]);
+ resi += backrefs[0];
+ }
+ for (i = 0; i < backrefcount; i++) {
+ // Current backref
+ backref = subst[backrefs[i]+1] - '0';
+ if (matches[backref].rm_so >= 0) {
+ l = matches[backref].rm_eo -
matches[backref].rm_so;
+ memcpy(resi, string +
matches[backref].rm_so, l);
+ resi += l;
+ }
+ // Copy gap between this and next backref
+ l = (i == backrefcount - 1) ?
+ subst_length - backrefs[i] - 2 : // copy to the
end
+ backrefs[i+1] - backrefs[i] - 2;
+ if (l) {
+ memcpy(resi, subst + backrefs[i] + 2, l);
+ resi += l;
+ }
+ }
+
+ }
+ // Copy the end that didn't match
+ l = stringlen - matches[0].rm_eo;
+ if (l)
+ memcpy(resi, string + matches[0].rm_eo, l);
+ } else if (i == REG_NOMATCH) {
+ goto exit;
+ } else {
+ regexp_error(i, ®ex, "Problem mathing regexp");
+ goto exit;
+ }
+
+exit:
+ free(pat);
+ if (search)
+ regfree(®ex);
+ return res;
+}
--
tg: (5f3a2ff..) subst_compilation_dir_feature (depends on: upstream)
--- End Message ---