buildtools/jam/hcache.c
2014-08-10 17:16:21 +02:00

429 lines
9.1 KiB
C

/*
* This file has been donated to Jam.
*/
# include "jam.h"
# include "lists.h"
# include "parse.h"
# include "rules.h"
# include "regexp.h"
# include "headers.h"
# include "newstr.h"
# include "hash.h"
# include "hcache.h"
# include "variable.h"
# include "search.h"
# include "pathsys.h"
#ifdef OPT_HEADER_CACHE_EXT
/*
* Craig W. McPheeters, Alias|Wavefront.
*
* hcache.c hcache.h - handle cacheing of #includes in source files
*
* Create a cache of files scanned for headers. When starting jam,
* look for the cache file and load it if present. When finished the
* binding phase, create a new header cache. The cache contains
* files, their timestamps and the header files found in their scan.
* During the binding phase of jam, look in the header cache first for
* the headers contained in a file. If the cache is present and
* valid, use its contents. This results in dramatic speedups with
* large projects (eg. 3min -> 1min startup for one project.)
*
* External routines:
* hcache_init() - read and parse the local .jamdeps file.
* hcache_done() - write a new .jamdeps file
* hcache() - return list of headers on target. Use cache or do a scan.
*
* The dependency file format is an ascii file with 1 line per target.
* Each line has the following fields:
* @boundname@ timestamp @file@ @file@ @file@ ... \n
* */
struct hcachedata {
const char *boundname;
time_t time;
LIST *includes;
LIST *hdrscan; /* the HDRSCAN value for this target */
int age; /* if too old, we'll remove it from cache */
struct hcachedata *next;
} ;
typedef struct hcachedata HCACHEDATA ;
static struct hash *hcachehash = 0;
static HCACHEDATA *hcachelist = 0;
static int queries = 0;
static int hits = 0;
#define CACHE_FILE_VERSION "version 4"
#define CACHE_RECORD_HEADER "header"
#define CACHE_RECORD_END "end"
/*
* Return the name of the header cache file. May return NULL.
*
* The user sets this by setting the HCACHEFILE variable in a Jamfile.
* We cache the result so the user can't change the cache file during
* header scanning.
*/
static const char*
cache_name(void)
{
static const char* name = 0;
if (!name) {
LIST *hcachevar = var_get("HCACHEFILE");
if (hcachevar) {
TARGET *t = bindtarget( hcachevar->string );
pushsettings( t->settings );
t->boundname = search( t->name, &t->time );
popsettings( t->settings );
if (hcachevar) {
name = copystr(t->boundname);
}
}
}
return name;
}
/*
* Return the maximum age a cache entry can have before it is purged
* from the cache.
*/
static int
cache_maxage(void)
{
int age = 100;
LIST *var = var_get("HCACHEMAXAGE");
if (var) {
age = atoi(var->string);
if (age < 0)
age = 0;
}
return age;
}
/*
* Read a netstring. The caveat is that the string can't contain
* ASCII 0. The returned value is as returned by newstr(), so it need
* not be freed.
*/
const char*
read_netstring(FILE* f)
{
unsigned long len;
static char* buf = NULL;
static unsigned long buf_len = 0;
if (fscanf(f, " %9lu", &len) != 1)
return NULL;
if (fgetc(f) != (int)'\t')
return NULL;
if (len > 1024 * 64)
return NULL; /* sanity check */
if (len > buf_len)
{
unsigned long new_len = buf_len * 2;
if (new_len < len)
new_len = len;
buf = realloc(buf, new_len + 1);
if (buf)
buf_len = new_len;
}
if (!buf)
return NULL;
if (fread(buf, 1, len, f) != len)
return NULL;
if (fgetc(f) != (int)'\n')
return NULL;
buf[len] = 0;
return newstr(buf);
}
/*
* Write a netstring.
*/
void
write_netstring(FILE* f, const char* s)
{
if (!s)
s = "";
fprintf(f, "%lu\t%s\n", strlen(s), s);
}
void
hcache_init()
{
HCACHEDATA cachedata, *c;
FILE *f;
const char *version;
int header_count = 0;
const char* hcachename;
hcachehash = hashinit (sizeof (HCACHEDATA), "hcache");
if (! (hcachename = cache_name()))
return;
if (! (f = fopen (hcachename, "rb" )))
return;
version = read_netstring(f);
if (!version || strcmp(version, CACHE_FILE_VERSION)) {
fclose(f);
return;
}
while (1)
{
const char* record_type;
const char *time_str;
const char *age_str;
const char *includes_count_str;
const char *hdrscan_count_str;
int i, count;
LIST *l;
record_type = read_netstring(f);
if (!record_type) {
fprintf(stderr, "invalid %s\n", hcachename);
goto bail;
}
if (!strcmp(record_type, CACHE_RECORD_END)) {
break;
}
if (strcmp(record_type, CACHE_RECORD_HEADER)) {
fprintf(stderr, "invalid %s with record separator <%s>\n",
hcachename, record_type ? record_type : "<null>");
goto bail;
}
c = &cachedata;
c->boundname = read_netstring(f);
time_str = read_netstring(f);
age_str = read_netstring(f);
includes_count_str = read_netstring(f);
if (!c->boundname || !time_str || !age_str
|| !includes_count_str)
{
fprintf(stderr, "invalid %s\n", hcachename);
goto bail;
}
c->time = atoi(time_str);
c->age = atoi(age_str) + 1;
count = atoi(includes_count_str);
for (l = 0, i = 0; i < count; i++) {
const char* s = read_netstring(f);
if (!s) {
fprintf(stderr, "invalid %s\n", hcachename);
goto bail;
}
l = list_new(l, s, 1);
}
c->includes = l;
hdrscan_count_str = read_netstring(f);
if (!includes_count_str) {
list_free(c->includes);
fprintf(stderr, "invalid %s\n", hcachename);
goto bail;
}
count = atoi(hdrscan_count_str);
for (l = 0, i = 0; i < count; i++) {
const char* s = read_netstring(f);
if (!s) {
fprintf(stderr, "invalid %s\n", hcachename);
goto bail;
}
l = list_new(l, s, 1);
}
c->hdrscan = l;
if (!hashenter(hcachehash, (HASHDATA **)&c)) {
fprintf(stderr, "can't insert header cache item, bailing on %s\n",
hcachename);
goto bail;
}
c->next = hcachelist;
hcachelist = c;
header_count++;
}
if (DEBUG_HEADER) {
printf("hcache read from file %s\n", hcachename);
}
bail:
fclose(f);
}
void
hcache_done()
{
FILE *f;
HCACHEDATA *c;
int header_count = 0;
const char* hcachename;
int maxage;
if (!hcachehash)
return;
if (! (hcachename = cache_name()))
return;
if (! (f = fopen (hcachename, "wb" )))
return;
maxage = cache_maxage();
/* print out the version */
write_netstring(f, CACHE_FILE_VERSION);
c = hcachelist;
for (c = hcachelist; c; c = c->next) {
LIST *l;
char time_str[30];
char age_str[30];
char includes_count_str[30];
char hdrscan_count_str[30];
if (maxage == 0)
c->age = 0;
else if (c->age > maxage)
continue;
sprintf(includes_count_str, "%u", list_length(c->includes));
sprintf(hdrscan_count_str, "%u", list_length(c->hdrscan));
sprintf(time_str, "%lu", c->time);
sprintf(age_str, "%u", c->age);
write_netstring(f, CACHE_RECORD_HEADER);
write_netstring(f, c->boundname);
write_netstring(f, time_str);
write_netstring(f, age_str);
write_netstring(f, includes_count_str);
for (l = c->includes; l; l = list_next(l)) {
write_netstring(f, l->string);
}
write_netstring(f, hdrscan_count_str);
for (l = c->hdrscan; l; l = list_next(l)) {
write_netstring(f, l->string);
}
fputs("\n", f);
header_count++;
}
write_netstring(f, CACHE_RECORD_END);
if (DEBUG_HEADER) {
printf("hcache written to %s. %d dependencies, %.0f%% hit rate\n",
hcachename, header_count,
queries ? 100.0 * hits / queries : 0);
}
fclose (f);
}
LIST *
hcache (TARGET *t, LIST *hdrscan)
{
HCACHEDATA cachedata, *c = &cachedata;
LIST *l = 0;
char _normalizedPath[PATH_MAX];
char *normalizedPath = normalize_path(t->boundname, _normalizedPath,
sizeof(_normalizedPath));
++queries;
if (normalizedPath)
c->boundname = normalizedPath;
else
c->boundname = t->boundname;
if (hashcheck (hcachehash, (HASHDATA **) &c))
{
if (c->time == t->time)
{
LIST *l1 = hdrscan, *l2 = c->hdrscan;
while (l1 && l2) {
if (l1->string != l2->string) {
l1 = NULL;
} else {
l1 = list_next(l1);
l2 = list_next(l2);
}
}
if (l1 || l2) {
if (DEBUG_HEADER)
printf("HDRSCAN out of date in cache for %s\n",
t->boundname);
printf("HDRSCAN out of date for %s\n", t->boundname);
printf(" real : ");
list_print(hdrscan);
printf("\n cached: ");
list_print(c->hdrscan);
printf("\n");
list_free(c->includes);
list_free(c->hdrscan);
c->includes = 0;
c->hdrscan = 0;
} else {
if (DEBUG_HEADER)
printf ("using header cache for %s\n", t->boundname);
c->age = 0;
++hits;
l = list_copy (0, c->includes);
return l;
}
} else {
if (DEBUG_HEADER)
printf ("header cache out of date for %s\n", t->boundname);
list_free (c->includes);
list_free(c->hdrscan);
c->includes = 0;
c->hdrscan = 0;
}
} else {
if (hashenter (hcachehash, (HASHDATA **)&c)) {
c->boundname = newstr (c->boundname);
c->next = hcachelist;
hcachelist = c;
}
}
/* 'c' points at the cache entry. Its out of date. */
l = headers1 (t->boundname, hdrscan);
c->time = t->time;
c->age = 0;
c->includes = list_copy (0, l);
c->hdrscan = list_copy(0, hdrscan);
return l;
}
#endif