| /* |
| * 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 : "<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 ) |
| { |
| 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 |