1/* 2 * This file has been donated to Jam. 3 */ 4 5# include "jam.h" 6# include "lists.h" 7# include "parse.h" 8# include "rules.h" 9# include "regexp.h" 10# include "headers.h" 11# include "newstr.h" 12# include "hash.h" 13# include "hcache.h" 14# include "variable.h" 15# include "search.h" 16# include "pathsys.h" 17 18#ifdef OPT_HEADER_CACHE_EXT 19 20/* 21 * Craig W. McPheeters, Alias|Wavefront. 22 * 23 * hcache.c hcache.h - handle cacheing of #includes in source files 24 * 25 * Create a cache of files scanned for headers. When starting jam, 26 * look for the cache file and load it if present. When finished the 27 * binding phase, create a new header cache. The cache contains 28 * files, their timestamps and the header files found in their scan. 29 * During the binding phase of jam, look in the header cache first for 30 * the headers contained in a file. If the cache is present and 31 * valid, use its contents. This results in dramatic speedups with 32 * large projects (eg. 3min -> 1min startup for one project.) 33 * 34 * External routines: 35 * hcache_init() - read and parse the local .jamdeps file. 36 * hcache_done() - write a new .jamdeps file 37 * hcache() - return list of headers on target. Use cache or do a scan. 38 * 39 * The dependency file format is an ascii file with 1 line per target. 40 * Each line has the following fields: 41 * @boundname@ timestamp @file@ @file@ @file@ ... \n 42 * */ 43 44struct hcachedata { 45 const char *boundname; 46 time_t time; 47 LIST *includes; 48 LIST *hdrscan; /* the HDRSCAN value for this target */ 49 int age; /* if too old, we'll remove it from cache */ 50 struct hcachedata *next; 51} ; 52 53typedef struct hcachedata HCACHEDATA ; 54 55 56static struct hash *hcachehash = 0; 57static HCACHEDATA *hcachelist = 0; 58 59static int queries = 0; 60static int hits = 0; 61 62#define CACHE_FILE_VERSION "version 4" 63#define CACHE_RECORD_HEADER "header" 64#define CACHE_RECORD_END "end" 65 66/* 67 * Return the name of the header cache file. May return NULL. 68 * 69 * The user sets this by setting the HCACHEFILE variable in a Jamfile. 70 * We cache the result so the user can't change the cache file during 71 * header scanning. 72 */ 73static const char* 74cache_name(void) 75{ 76 static const char* name = 0; 77 if (!name) { 78 LIST *hcachevar = var_get("HCACHEFILE"); 79 80 if (hcachevar) { 81 TARGET *t = bindtarget( hcachevar->string ); 82 83 pushsettings( t->settings ); 84 t->boundname = search( t->name, &t->time ); 85 popsettings( t->settings ); 86 87 if (hcachevar) { 88 name = copystr(t->boundname); 89 } 90 } 91 } 92 return name; 93} 94 95/* 96 * Return the maximum age a cache entry can have before it is purged 97 * from the cache. 98 */ 99static int 100cache_maxage(void) 101{ 102 int age = 100; 103 LIST *var = var_get("HCACHEMAXAGE"); 104 105 if (var) { 106 age = atoi(var->string); 107 if (age < 0) 108 age = 0; 109 } 110 111 return age; 112} 113 114/* 115 * Read a netstring. The caveat is that the string can't contain 116 * ASCII 0. The returned value is as returned by newstr(), so it need 117 * not be freed. 118 */ 119const char* 120read_netstring(FILE* f) 121{ 122 unsigned long len; 123 static char* buf = NULL; 124 static unsigned long buf_len = 0; 125 126 if (fscanf(f, " %9lu", &len) != 1) 127 return NULL; 128 if (fgetc(f) != (int)'\t') 129 return NULL; 130 131 if (len > 1024 * 64) 132 return NULL; /* sanity check */ 133 134 if (len > buf_len) 135 { 136 unsigned long new_len = buf_len * 2; 137 if (new_len < len) 138 new_len = len; 139 buf = realloc(buf, new_len + 1); 140 if (buf) 141 buf_len = new_len; 142 } 143 144 if (!buf) 145 return NULL; 146 147 if (fread(buf, 1, len, f) != len) 148 return NULL; 149 if (fgetc(f) != (int)'\n') 150 return NULL; 151 152 buf[len] = 0; 153 return newstr(buf); 154} 155 156/* 157 * Write a netstring. 158 */ 159void 160write_netstring(FILE* f, const char* s) 161{ 162 if (!s) 163 s = ""; 164 fprintf(f, "%lu\t%s\n", strlen(s), s); 165} 166 167void 168hcache_init() 169{ 170 HCACHEDATA cachedata, *c; 171 FILE *f; 172 const char *version; 173 int header_count = 0; 174 const char* hcachename; 175 176 hcachehash = hashinit (sizeof (HCACHEDATA), "hcache"); 177 178 if (! (hcachename = cache_name())) 179 return; 180 181 if (! (f = fopen (hcachename, "rb" ))) 182 return; 183 184 version = read_netstring(f); 185 if (!version || strcmp(version, CACHE_FILE_VERSION)) { 186 fclose(f); 187 return; 188 } 189 190 while (1) 191 { 192 const char* record_type; 193 const char *time_str; 194 const char *age_str; 195 const char *includes_count_str; 196 const char *hdrscan_count_str; 197 int i, count; 198 LIST *l; 199 200 record_type = read_netstring(f); 201 if (!record_type) { 202 fprintf(stderr, "invalid %s\n", hcachename); 203 goto bail; 204 } 205 if (!strcmp(record_type, CACHE_RECORD_END)) { 206 break; 207 } 208 if (strcmp(record_type, CACHE_RECORD_HEADER)) { 209 fprintf(stderr, "invalid %s with record separator <%s>\n", 210 hcachename, record_type ? record_type : "<null>"); 211 goto bail; 212 } 213 214 c = &cachedata; 215 216 c->boundname = read_netstring(f); 217 time_str = read_netstring(f); 218 age_str = read_netstring(f); 219 includes_count_str = read_netstring(f); 220 221 if (!c->boundname || !time_str || !age_str 222 || !includes_count_str) 223 { 224 fprintf(stderr, "invalid %s\n", hcachename); 225 goto bail; 226 } 227 228 c->time = atoi(time_str); 229 c->age = atoi(age_str) + 1; 230 231 count = atoi(includes_count_str); 232 for (l = 0, i = 0; i < count; i++) { 233 const char* s = read_netstring(f); 234 if (!s) { 235 fprintf(stderr, "invalid %s\n", hcachename); 236 goto bail; 237 } 238 l = list_new(l, s, 1); 239 } 240 c->includes = l; 241 242 hdrscan_count_str = read_netstring(f); 243 if (!includes_count_str) { 244 list_free(c->includes); 245 fprintf(stderr, "invalid %s\n", hcachename); 246 goto bail; 247 } 248 249 count = atoi(hdrscan_count_str); 250 for (l = 0, i = 0; i < count; i++) { 251 const char* s = read_netstring(f); 252 if (!s) { 253 fprintf(stderr, "invalid %s\n", hcachename); 254 goto bail; 255 } 256 l = list_new(l, s, 1); 257 } 258 c->hdrscan = l; 259 260 if (!hashenter(hcachehash, (HASHDATA **)&c)) { 261 fprintf(stderr, "can't insert header cache item, bailing on %s\n", 262 hcachename); 263 goto bail; 264 } 265 266 c->next = hcachelist; 267 hcachelist = c; 268 269 header_count++; 270 } 271 272 if (DEBUG_HEADER) { 273 printf("hcache read from file %s\n", hcachename); 274 } 275 276 bail: 277 fclose(f); 278} 279 280void 281hcache_done() 282{ 283 FILE *f; 284 HCACHEDATA *c; 285 int header_count = 0; 286 const char* hcachename; 287 int maxage; 288 289 if (!hcachehash) 290 return; 291 292 if (! (hcachename = cache_name())) 293 return; 294 295 if (! (f = fopen (hcachename, "wb" ))) 296 return; 297 298 maxage = cache_maxage(); 299 300 /* print out the version */ 301 write_netstring(f, CACHE_FILE_VERSION); 302 303 c = hcachelist; 304 for (c = hcachelist; c; c = c->next) { 305 LIST *l; 306 char time_str[30]; 307 char age_str[30]; 308 char includes_count_str[30]; 309 char hdrscan_count_str[30]; 310 311 if (maxage == 0) 312 c->age = 0; 313 else if (c->age > maxage) 314 continue; 315 316 sprintf(includes_count_str, "%u", list_length(c->includes)); 317 sprintf(hdrscan_count_str, "%u", list_length(c->hdrscan)); 318 sprintf(time_str, "%lu", c->time); 319 sprintf(age_str, "%u", c->age); 320 321 write_netstring(f, CACHE_RECORD_HEADER); 322 write_netstring(f, c->boundname); 323 write_netstring(f, time_str); 324 write_netstring(f, age_str); 325 write_netstring(f, includes_count_str); 326 for (l = c->includes; l; l = list_next(l)) { 327 write_netstring(f, l->string); 328 } 329 write_netstring(f, hdrscan_count_str); 330 for (l = c->hdrscan; l; l = list_next(l)) { 331 write_netstring(f, l->string); 332 } 333 fputs("\n", f); 334 header_count++; 335 } 336 write_netstring(f, CACHE_RECORD_END); 337 338 if (DEBUG_HEADER) { 339 printf("hcache written to %s. %d dependencies, %.0f%% hit rate\n", 340 hcachename, header_count, 341 queries ? 100.0 * hits / queries : 0); 342 } 343 344 fclose (f); 345} 346 347LIST * 348hcache (TARGET *t, LIST *hdrscan) 349{ 350 HCACHEDATA cachedata, *c = &cachedata; 351 LIST *l = 0; 352 char _normalizedPath[PATH_MAX]; 353 char *normalizedPath = normalize_path(t->boundname, _normalizedPath, 354 sizeof(_normalizedPath)); 355 356 ++queries; 357 358 if (normalizedPath) 359 c->boundname = normalizedPath; 360 else 361 c->boundname = t->boundname; 362 363 if (hashcheck (hcachehash, (HASHDATA **) &c)) 364 { 365 if (c->time == t->time) 366 { 367 LIST *l1 = hdrscan, *l2 = c->hdrscan; 368 while (l1 && l2) { 369 if (l1->string != l2->string) { 370 l1 = NULL; 371 } else { 372 l1 = list_next(l1); 373 l2 = list_next(l2); 374 } 375 } 376 if (l1 || l2) { 377 if (DEBUG_HEADER) 378 printf("HDRSCAN out of date in cache for %s\n", 379 t->boundname); 380 381 printf("HDRSCAN out of date for %s\n", t->boundname); 382 printf(" real : "); 383 list_print(hdrscan); 384 printf("\n cached: "); 385 list_print(c->hdrscan); 386 printf("\n"); 387 388 list_free(c->includes); 389 list_free(c->hdrscan); 390 c->includes = 0; 391 c->hdrscan = 0; 392 } else { 393 if (DEBUG_HEADER) 394 printf ("using header cache for %s\n", t->boundname); 395 c->age = 0; 396 ++hits; 397 l = list_copy (0, c->includes); 398 return l; 399 } 400 } else { 401 if (DEBUG_HEADER) 402 printf ("header cache out of date for %s\n", t->boundname); 403 list_free (c->includes); 404 list_free(c->hdrscan); 405 c->includes = 0; 406 c->hdrscan = 0; 407 } 408 } else { 409 if (hashenter (hcachehash, (HASHDATA **)&c)) { 410 c->boundname = newstr (c->boundname); 411 c->next = hcachelist; 412 hcachelist = c; 413 } 414 } 415 416 /* 'c' points at the cache entry. Its out of date. */ 417 418 l = headers1 (t->boundname, hdrscan); 419 420 c->time = t->time; 421 c->age = 0; 422 c->includes = list_copy (0, l); 423 c->hdrscan = list_copy(0, hdrscan); 424 425 return l; 426} 427 428#endif 429