/* * 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" #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 */ typedef struct hcachedata HCACHEDATA ; struct hcachedata { 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 */ HCACHEDATA * next; }; 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 not change the cache file during header scanning. */ static char * cache_name( void ) { static char * name = 0; if ( !name ) { LIST * hcachevar = var_get( "HCACHEFILE" ); if ( hcachevar ) { TARGET * t = bindtarget( hcachevar->string ); pushsettings( t->settings ); /* Do not expect the cache file to be generated, so pass 0 as the * third argument to search. Expect the location to be specified via * LOCATE, so pass 0 as the fourth arugment. */ t->boundname = search( t->name, &t->time, 0, 0 ); popsettings( t->settings ); if ( hcachevar ) name = copystr( t->boundname ); } } return name; } /* * Return the maximum age a cache entry can have before it is purged ftom 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 not contain ASCII 0. The * returned value is as returned by newstr(), so it need not be freed. */ 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 = (char *)BJAM_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, char const * s ) { if ( !s ) s = ""; fprintf( f, "%lu\t%s\n", (long unsigned)strlen( s ), s ); } void hcache_init() { HCACHEDATA cachedata; HCACHEDATA * c; FILE * f; char * version; int header_count = 0; 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 ) { char * record_type; char * time_str; char * age_str; char * includes_count_str; char * hdrscan_count_str; int i; int 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 : "" ); 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 ) { char * s = read_netstring( f ); if ( !s ) { fprintf( stderr, "invalid %s\n", hcachename ); goto bail; } l = list_new( l, s ); } 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 ) { char * s = read_netstring( f ); if ( !s ) { fprintf( stderr, "invalid %s\n", hcachename ); goto bail; } l = list_new( l, s ); } 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; 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, "%lu", (long unsigned) list_length( c->includes ) ); sprintf( hdrscan_count_str, "%lu", (long unsigned) list_length( c->hdrscan ) ); sprintf( time_str, "%lu", (long unsigned) c->time ); sprintf( age_str, "%lu", (long unsigned) 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, int rec, regexp * re[], LIST * hdrscan ) { HCACHEDATA cachedata; HCACHEDATA * c = &cachedata; LIST * l = 0; ++queries; 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 (0, t->boundname, rec, re); c->time = t->time; c->age = 0; c->includes = list_copy (0, l); c->hdrscan = list_copy(0, hdrscan); return l; } #endif