2005-12-29 19:40:48 +01:00
|
|
|
/*
|
|
|
|
* 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.
|
2014-05-12 23:17:14 +02:00
|
|
|
*
|
2005-12-29 19:40:48 +01:00
|
|
|
* 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 {
|
2014-05-12 23:17:14 +02:00
|
|
|
const char *boundname;
|
2005-12-29 19:40:48 +01:00
|
|
|
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;
|
2014-05-12 23:17:14 +02:00
|
|
|
static HCACHEDATA *hcachelist = 0;
|
2005-12-29 19:40:48 +01:00
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
2014-05-12 23:17:14 +02:00
|
|
|
static const char*
|
2005-12-29 19:40:48 +01:00
|
|
|
cache_name(void)
|
|
|
|
{
|
2014-05-12 23:17:14 +02:00
|
|
|
static const char* name = 0;
|
2005-12-29 19:40:48 +01:00
|
|
|
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.
|
|
|
|
*/
|
2014-05-12 23:17:14 +02:00
|
|
|
const char*
|
2005-12-29 19:40:48 +01:00
|
|
|
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;
|
2014-05-12 23:17:14 +02:00
|
|
|
FILE *f;
|
|
|
|
const char *version;
|
|
|
|
int header_count = 0;
|
|
|
|
const char* hcachename;
|
2005-12-29 19:40:48 +01:00
|
|
|
|
|
|
|
hcachehash = hashinit (sizeof (HCACHEDATA), "hcache");
|
|
|
|
|
|
|
|
if (! (hcachename = cache_name()))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (! (f = fopen (hcachename, "rb" )))
|
|
|
|
return;
|
2014-05-12 23:17:14 +02:00
|
|
|
|
2005-12-29 19:40:48 +01:00
|
|
|
version = read_netstring(f);
|
|
|
|
if (!version || strcmp(version, CACHE_FILE_VERSION)) {
|
|
|
|
fclose(f);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (1)
|
|
|
|
{
|
2014-05-12 23:17:14 +02:00
|
|
|
const char* record_type;
|
|
|
|
const char *time_str;
|
|
|
|
const char *age_str;
|
|
|
|
const char *includes_count_str;
|
|
|
|
const char *hdrscan_count_str;
|
2005-12-29 19:40:48 +01:00
|
|
|
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;
|
|
|
|
}
|
2014-05-12 23:17:14 +02:00
|
|
|
|
2005-12-29 19:40:48 +01:00
|
|
|
c = &cachedata;
|
2014-05-12 23:17:14 +02:00
|
|
|
|
2005-12-29 19:40:48 +01:00
|
|
|
c->boundname = read_netstring(f);
|
|
|
|
time_str = read_netstring(f);
|
|
|
|
age_str = read_netstring(f);
|
|
|
|
includes_count_str = read_netstring(f);
|
2014-05-12 23:17:14 +02:00
|
|
|
|
2005-12-29 19:40:48 +01:00
|
|
|
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++) {
|
2014-05-12 23:17:14 +02:00
|
|
|
const char* s = read_netstring(f);
|
2005-12-29 19:40:48 +01:00
|
|
|
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++) {
|
2014-05-12 23:17:14 +02:00
|
|
|
const char* s = read_netstring(f);
|
2005-12-29 19:40:48 +01:00
|
|
|
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);
|
|
|
|
}
|
2014-05-12 23:17:14 +02:00
|
|
|
|
2005-12-29 19:40:48 +01:00
|
|
|
bail:
|
|
|
|
fclose(f);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
hcache_done()
|
|
|
|
{
|
2014-05-12 23:17:14 +02:00
|
|
|
FILE *f;
|
2005-12-29 19:40:48 +01:00
|
|
|
HCACHEDATA *c;
|
2014-05-12 23:17:14 +02:00
|
|
|
int header_count = 0;
|
|
|
|
const char* hcachename;
|
|
|
|
int maxage;
|
|
|
|
|
2005-12-29 19:40:48 +01:00
|
|
|
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;
|
|
|
|
|
2014-08-10 17:16:21 +02:00
|
|
|
sprintf(includes_count_str, "%u", list_length(c->includes));
|
|
|
|
sprintf(hdrscan_count_str, "%u", list_length(c->hdrscan));
|
2005-12-29 19:40:48 +01:00
|
|
|
sprintf(time_str, "%lu", c->time);
|
2014-08-10 17:16:21 +02:00
|
|
|
sprintf(age_str, "%u", c->age);
|
2005-12-29 19:40:48 +01:00
|
|
|
|
|
|
|
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
|