/* Target file management for GNU Make. Copyright (C) 1988-2023 Free Software Foundation, Inc. This file is part of GNU Make. GNU Make is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "makeint.h" #include #include "filedef.h" #include "dep.h" #include "job.h" #include "commands.h" #include "variable.h" #include "debug.h" #include "hash.h" #include "shuffle.h" /* Remember whether snap_deps has been invoked: we need this to be sure we don't add new rules (via $(eval ...)) afterwards. In the future it would be nice to support this, but it means we'd need to re-run snap_deps() or at least its functionality... it might mean changing snap_deps() to be run per-file, so we can invoke it after the eval... or remembering which files in the hash have been snapped (a new boolean flag?) and having snap_deps() only work on files which have not yet been snapped. */ int snapped_deps = 0; /* Hash table of files the makefile knows how to make. */ static unsigned long file_hash_1 (const void *key) { return_ISTRING_HASH_1 (((struct file const *) key)->hname); } static unsigned long file_hash_2 (const void *key) { return_ISTRING_HASH_2 (((struct file const *) key)->hname); } static int file_hash_cmp (const void *x, const void *y) { return_ISTRING_COMPARE (((struct file const *) x)->hname, ((struct file const *) y)->hname); } static struct hash_table files; /* We can't free files we take out of the hash table, because they are still likely pointed to in various places. The check_renamed() will be used if we come across these, to find the new correct file. This is mainly to prevent leak checkers from complaining. */ static struct file **rehashed_files = NULL; static size_t rehashed_files_len = 0; #define REHASHED_FILES_INCR 5 /* Whether or not .SECONDARY with no prerequisites was given. */ static int all_secondary = 0; /* Access the hash table of all file records. lookup_file given a name, return the struct file * for that name, or nil if there is none. */ struct file * lookup_file (const char *name) { struct file *f; struct file file_key; #if MK_OS_VMS int want_vmsify; #ifndef WANT_CASE_SENSITIVE_TARGETS char *lname; #endif #endif assert (*name != '\0'); /* This is also done in parse_file_seq, so this is redundant for names read from makefiles. It is here for names passed on the command line. */ #if MK_OS_VMS want_vmsify = (strpbrk (name, "]>:^") != NULL); # ifndef WANT_CASE_SENSITIVE_TARGETS if (*name != '.') { const char *n; char *ln; lname = xstrdup (name); for (n = name, ln = lname; *n != '\0'; ++n, ++ln) *ln = isupper ((unsigned char)*n) ? tolower ((unsigned char)*n) : *n; *ln = '\0'; name = lname; } # endif while (name[0] == '[' && name[1] == ']' && name[2] != '\0') name += 2; while (name[0] == '<' && name[1] == '>' && name[2] != '\0') name += 2; #endif while (name[0] == '.' && ISDIRSEP (name[1]) && name[2] != '\0') { name += 2; while (ISDIRSEP (*name)) /* Skip following slashes: ".//foo" is "foo", not "/foo". */ ++name; } if (*name == '\0') { /* It was all slashes after a dot. */ name = "./"; #if MK_OS_VMS /* TODO - This section is probably not needed. */ if (want_vmsify) name = "[]"; #endif } file_key.hname = name; f = hash_find_item (&files, &file_key); #if MK_OS_VMS && !defined(WANT_CASE_SENSITIVE_TARGETS) if (*name != '.') free (lname); #endif return f; } /* Look up a file record for file NAME and return it. Create a new record if one doesn't exist. NAME will be stored in the new record so it should be constant or in the strcache etc. */ struct file * enter_file (const char *name) { struct file *f; struct file *new; struct file **file_slot; struct file file_key; assert (*name != '\0'); assert (! verify_flag || strcache_iscached (name)); #if MK_OS_VMS && !defined(WANT_CASE_SENSITIVE_TARGETS) if (*name != '.') { const char *n; char *lname, *ln; lname = xstrdup (name); for (n = name, ln = lname; *n != '\0'; ++n, ++ln) if (isupper ((unsigned char)*n)) *ln = tolower ((unsigned char)*n); else *ln = *n; *ln = '\0'; name = strcache_add (lname); free (lname); } #endif file_key.hname = name; file_slot = (struct file **) hash_find_slot (&files, &file_key); f = *file_slot; if (! HASH_VACANT (f) && !f->double_colon) { f->builtin = 0; return f; } new = xcalloc (sizeof (struct file)); new->name = new->hname = name; new->update_status = us_none; if (HASH_VACANT (f)) { new->last = new; hash_insert_at (&files, new, file_slot); } else { /* There is already a double-colon entry for this file. */ new->double_colon = f; f->last->prev = new; f->last = new; } return new; } /* Rehash FILE to NAME. This is not as simple as resetting the 'hname' member, since it must be put in a new hash bucket, and possibly merged with an existing file called NAME. */ void rehash_file (struct file *from_file, const char *to_hname) { struct file file_key; struct file **file_slot; struct file *to_file; struct file *deleted_file; struct file *f; /* If it's already that name, we're done. */ from_file->builtin = 0; file_key.hname = to_hname; if (! file_hash_cmp (from_file, &file_key)) return; /* Find the end of the renamed list for the "from" file. */ file_key.hname = from_file->hname; check_renamed (from_file); if (file_hash_cmp (from_file, &file_key)) /* hname changed unexpectedly!! */ abort (); /* Remove the "from" file from the hash. */ deleted_file = hash_delete (&files, from_file); if (deleted_file != from_file) /* from_file isn't the one stored in files */ abort (); /* Find where the newly renamed file will go in the hash. */ file_key.hname = to_hname; file_slot = (struct file **) hash_find_slot (&files, &file_key); to_file = *file_slot; /* Change the hash name for this file. */ from_file->hname = to_hname; for (f = from_file->double_colon; f != 0; f = f->prev) f->hname = to_hname; /* If the new name doesn't exist yet just set it to the renamed file. */ if (HASH_VACANT (to_file)) { hash_insert_at (&files, from_file, file_slot); return; } /* TO_FILE already exists under TO_HNAME. We must retain TO_FILE and merge FROM_FILE into it. */ if (from_file->cmds != 0) { if (to_file->cmds == 0) to_file->cmds = from_file->cmds; else if (from_file->cmds != to_file->cmds) { size_t l = strlen (from_file->name); /* We have two sets of commands. We will go with the one given in the rule found through directory search, but give a message to let the user know what's going on. */ if (to_file->cmds->fileinfo.filenm != 0) error (&from_file->cmds->fileinfo, l + strlen (to_file->cmds->fileinfo.filenm) + INTSTR_LENGTH, _("recipe was specified for file '%s' at %s:%lu,"), from_file->name, from_file->cmds->fileinfo.filenm, from_file->cmds->fileinfo.lineno); else error (&from_file->cmds->fileinfo, l, _("recipe for file '%s' was found by implicit rule search,"), from_file->name); l += strlen (to_hname); error (&from_file->cmds->fileinfo, l, _("but '%s' is now considered the same file as '%s'"), from_file->name, to_hname); error (&from_file->cmds->fileinfo, l, _("recipe for '%s' will be ignored in favor of the one for '%s'"), from_file->name, to_hname); } } /* Merge the dependencies of the two files. */ if (to_file->deps == 0) to_file->deps = from_file->deps; else { struct dep *deps = to_file->deps; while (deps->next != 0) deps = deps->next; deps->next = from_file->deps; } merge_variable_set_lists (&to_file->variables, from_file->variables); if (to_file->double_colon && from_file->is_target && !from_file->double_colon) OSS (fatal, NILF, _("can't rename single-colon '%s' to double-colon '%s'"), from_file->name, to_hname); if (!to_file->double_colon && from_file->double_colon) { if (to_file->is_target) OSS (fatal, NILF, _("can't rename double-colon '%s' to single-colon '%s'"), from_file->name, to_hname); else to_file->double_colon = from_file->double_colon; } if (from_file->last_mtime > to_file->last_mtime) /* %%% Kludge so -W wins on a file that gets vpathized. */ to_file->last_mtime = from_file->last_mtime; to_file->mtime_before_update = from_file->mtime_before_update; #define MERGE(field) to_file->field |= from_file->field MERGE (precious); MERGE (loaded); MERGE (tried_implicit); MERGE (updating); MERGE (updated); MERGE (is_target); MERGE (cmd_target); MERGE (phony); /* Don't merge intermediate because this file might be pre-existing */ MERGE (is_explicit); MERGE (secondary); MERGE (notintermediate); MERGE (ignore_vpath); MERGE (snapped); #undef MERGE to_file->builtin = 0; from_file->renamed = to_file; if (rehashed_files_len % REHASHED_FILES_INCR == 0) rehashed_files = xrealloc (rehashed_files, sizeof (struct file *) * (rehashed_files_len + REHASHED_FILES_INCR)); rehashed_files[rehashed_files_len++] = from_file; } /* Rename FILE to NAME. This is not as simple as resetting the 'name' member, since it must be put in a new hash bucket, and possibly merged with an existing file called NAME. */ void rename_file (struct file *from_file, const char *to_hname) { rehash_file (from_file, to_hname); while (from_file) { from_file->name = from_file->hname; from_file = from_file->prev; } } /* Remove all nonprecious intermediate files. If SIG is nonzero, this was caused by a fatal signal, meaning that a different message will be printed, and the message will go to stderr rather than stdout. */ void remove_intermediates (int sig) { struct file **file_slot; struct file **file_end; int doneany = 0; /* If there's no way we will ever remove anything anyway, punt early. */ if (question_flag || touch_flag || all_secondary || no_intermediates) return; if (sig && just_print_flag) return; file_slot = (struct file **) files.ht_vec; file_end = file_slot + files.ht_size; for ( ; file_slot < file_end; file_slot++) if (! HASH_VACANT (*file_slot)) { struct file *f = *file_slot; /* Is this file eligible for automatic deletion? Yes, IFF: it's marked intermediate, it's not secondary, it wasn't given on the command line, and it's either a -include makefile or it's not precious. */ if (f->intermediate && (f->dontcare || !f->precious) && !f->secondary && !f->notintermediate && !f->cmd_target) { int status; if (f->update_status == us_none) /* If nothing would have created this file yet, don't print an "rm" command for it. */ continue; if (just_print_flag) status = 0; else { status = unlink (f->name); if (status < 0 && errno == ENOENT) continue; } if (!f->dontcare) { if (sig) OS (error, NILF, _("*** deleting intermediate file '%s'"), f->name); else { if (! doneany) DB (DB_BASIC, (_("Removing intermediate files...\n"))); if (!run_silent) { if (! doneany) { fputs ("rm ", stdout); doneany = 1; } else putchar (' '); fputs (f->name, stdout); fflush (stdout); } } if (status < 0) { perror_with_name ("\nunlink: ", f->name); /* Start printing over. */ doneany = 0; } } } } if (doneany && !sig) { putchar ('\n'); fflush (stdout); } } /* Given a string containing prerequisites (fully expanded), break it up into a struct dep list. Enter each of these prereqs into the file database. */ struct dep * split_prereqs (char *p) { struct dep *new = PARSE_FILE_SEQ (&p, struct dep, MAP_PIPE, NULL, PARSEFS_WAIT); if (*p) { /* Files that follow '|' are "order-only" prerequisites that satisfy the dependency by existing: their modification times are irrelevant. */ struct dep *ood; ++p; ood = PARSE_FILE_SEQ (&p, struct dep, MAP_NUL, NULL, PARSEFS_WAIT); if (! new) new = ood; else { struct dep *dp; for (dp = new; dp->next != NULL; dp = dp->next) ; dp->next = ood; } for (; ood != NULL; ood = ood->next) ood->ignore_mtime = 1; } return new; } /* Given a list of prerequisites, enter them into the file database. If STEM is set then first expand patterns using STEM. */ struct dep * enter_prereqs (struct dep *deps, const char *stem) { struct dep *d1; if (deps == 0) return 0; /* If we have a stem, expand the %'s. We use patsubst_expand to translate the prerequisites' patterns into plain prerequisite names. */ if (stem) { const char *pattern = "%"; struct dep *dp = deps, *dl = 0; while (dp != 0) { char *percent; size_t nl = strlen (dp->name) + 1; char *nm = alloca (nl); memcpy (nm, dp->name, nl); percent = find_percent (nm); if (percent) { char *o; /* We have to handle empty stems specially, because that would be equivalent to $(patsubst %,dp->name,) which will always be empty. */ if (stem[0] == '\0') { memmove (percent, percent+1, strlen (percent)); o = variable_buffer_output (variable_buffer, nm, strlen (nm) + 1); } else o = patsubst_expand_pat (variable_buffer, stem, pattern, nm, pattern+1, percent+1); /* If the name expanded to the empty string, ignore it. */ if (variable_buffer[0] == '\0') { struct dep *df = dp; if (dp == deps) dp = deps = deps->next; else dp = dl->next = dp->next; free_dep (df); continue; } /* Save the name. */ dp->name = strcache_add_len (variable_buffer, o - variable_buffer); } dp->stem = stem; dp->staticpattern = 1; dl = dp; dp = dp->next; } } /* Enter them as files, unless they need a 2nd expansion. */ for (d1 = deps; d1 != 0; d1 = d1->next) { if (d1->need_2nd_expansion) continue; d1->file = lookup_file (d1->name); if (d1->file == 0) d1->file = enter_file (d1->name); d1->staticpattern = 0; d1->name = 0; if (!stem) /* This file is explicitly mentioned as a prereq. */ d1->file->is_explicit = 1; } return deps; } /* Expand and parse each dependency line. For each dependency of the file, make the 'struct dep' point at the appropriate 'struct file' (which may have to be created). */ void expand_deps (struct file *f) { struct dep *d; struct dep **dp; const char *fstem; int initialized = 0; int changed_dep = 0; if (f->snapped) return; f->snapped = 1; /* Walk through the dependencies. For any dependency that needs 2nd expansion, expand it then insert the result into the list. */ dp = &f->deps; d = f->deps; while (d != 0) { char *p; struct dep *new, *next; if (! d->name || ! d->need_2nd_expansion) { /* This one is all set already. */ dp = &d->next; d = d->next; continue; } /* If it's from a static pattern rule, convert the initial pattern in each word to "$*" so they'll expand properly. */ if (d->staticpattern) { const char *cs = d->name; size_t nperc = 0; /* Count the number of % in the string. */ while ((cs = strchr (cs, '%')) != NULL) { ++nperc; ++cs; } if (nperc) { /* Allocate enough space to replace all % with $*. */ size_t slen = strlen (d->name) + nperc + 1; const char *pcs = d->name; char *name = xmalloc (slen); char *s = name; /* Substitute the first % in each word. */ cs = strchr (pcs, '%'); while (cs) { s = mempcpy (s, pcs, cs - pcs); *(s++) = '$'; *(s++) = '*'; pcs = ++cs; /* Find the first % after the next whitespace. */ cs = strchr (end_of_token (cs), '%'); } strcpy (s, pcs); free ((char*)d->name); d->name = name; } } /* We're going to do second expansion so initialize file variables for the file. Since the stem for static pattern rules comes from individual dep lines, we will temporarily set f->stem to d->stem. */ if (!initialized) { initialize_file_variables (f, 0); initialized = 1; } set_file_variables (f, d->stem ? d->stem : f->stem); /* Perform second expansion. */ p = expand_string_for_file (d->name, f); /* Free the un-expanded name. */ free ((char*)d->name); /* Parse the prerequisites and enter them into the file database. */ new = split_prereqs (p); /* If there were no prereqs here (blank!) then throw this one out. */ if (new == 0) { *dp = d->next; changed_dep = 1; free_dep (d); d = *dp; continue; } /* Add newly parsed prerequisites. */ fstem = d->stem; next = d->next; changed_dep = 1; free_dep (d); *dp = new; for (dp = &new, d = new; d != 0; dp = &d->next, d = d->next) { d->file = lookup_file (d->name); if (d->file == 0) d->file = enter_file (d->name); d->name = 0; d->stem = fstem; if (!fstem) /* This file is explicitly mentioned as a prereq. */ d->file->is_explicit = 1; } *dp = next; d = *dp; } /* Shuffle mode assumes '->next' and '->shuf' links both traverse the same dependencies (in different sequences). Regenerate '->shuf' so we don't refer to stale data. */ if (changed_dep) shuffle_deps_recursive (f->deps); } /* Add extra prereqs to the file in question. */ struct dep * expand_extra_prereqs (const struct variable *extra) { struct dep *d; struct dep *prereqs = extra ? split_prereqs (expand_string (extra->value)) : NULL; for (d = prereqs; d; d = d->next) { d->file = lookup_file (d->name); if (!d->file) d->file = enter_file (d->name); d->name = NULL; d->ignore_automatic_vars = 1; } return prereqs; } /* Perform per-file snap operations. */ static void snap_file (const void *item, void *arg) { struct file *f = (struct file*)item; struct dep *prereqs = NULL; /* If we're not doing second expansion then reset updating. */ if (!second_expansion) f->updating = 0; /* More specific setting has priority. */ /* If .SECONDARY is set with no deps, mark all targets as intermediate, unless the target is a prereq of .NOTINTERMEDIATE. */ if (all_secondary && !f->notintermediate) f->intermediate = 1; /* If .NOTINTERMEDIATE is set with no deps, mark all targets as notintermediate, unless the target is a prereq of .INTERMEDIATE. */ if (no_intermediates && !f->intermediate && !f->secondary) f->notintermediate = 1; /* If .EXTRA_PREREQS is set, add them as ignored by automatic variables. */ if (f->variables) prereqs = expand_extra_prereqs (lookup_variable_in_set (STRING_SIZE_TUPLE(".EXTRA_PREREQS"), f->variables->set)); else if (f->is_target) prereqs = copy_dep_chain (arg); if (prereqs) { struct dep *d; for (d = prereqs; d; d = d->next) if (streq (f->name, dep_name (d))) /* Skip circular dependencies. */ break; if (d) /* We broke early: must have found a circular dependency. */ free_dep_chain (prereqs); else if (!f->deps) f->deps = prereqs; else { d = f->deps; while (d->next) d = d->next; d->next = prereqs; } } } /* Mark the files depended on by .PRECIOUS, .PHONY, .SILENT, and various other special targets. */ void snap_deps (void) { struct file *f; struct file *f2; struct dep *d; /* Remember that we've done this. Once we start snapping deps we can no longer define new targets. */ snapped_deps = 1; /* Now manage all the special targets. */ for (f = lookup_file (".PRECIOUS"); f != 0; f = f->prev) for (d = f->deps; d != 0; d = d->next) for (f2 = d->file; f2 != 0; f2 = f2->prev) f2->precious = 1; for (f = lookup_file (".LOW_RESOLUTION_TIME"); f != 0; f = f->prev) for (d = f->deps; d != 0; d = d->next) for (f2 = d->file; f2 != 0; f2 = f2->prev) f2->low_resolution_time = 1; for (f = lookup_file (".PHONY"); f != 0; f = f->prev) for (d = f->deps; d != 0; d = d->next) for (f2 = d->file; f2 != 0; f2 = f2->prev) { /* Mark this file as phony nonexistent target. */ f2->phony = 1; f2->is_target = 1; f2->last_mtime = NONEXISTENT_MTIME; f2->mtime_before_update = NONEXISTENT_MTIME; } for (f = lookup_file (".NOTINTERMEDIATE"); f != 0; f = f->prev) /* Mark .NOTINTERMEDIATE deps as notintermediate files. */ if (f->deps) for (d = f->deps; d != 0; d = d->next) for (f2 = d->file; f2 != 0; f2 = f2->prev) f2->notintermediate = 1; /* .NOTINTERMEDIATE with no deps marks all files as notintermediate. */ else no_intermediates = 1; /* The same file cannot be both .INTERMEDIATE and .NOTINTERMEDIATE. However, it is possible for a file to be .INTERMEDIATE and also match a .NOTINTERMEDIATE pattern. In that case, the intermediate file has priority over the notintermediate pattern. This priority is enforced by pattern_search. */ for (f = lookup_file (".INTERMEDIATE"); f != 0; f = f->prev) /* Mark .INTERMEDIATE deps as intermediate files. */ for (d = f->deps; d != 0; d = d->next) for (f2 = d->file; f2 != 0; f2 = f2->prev) if (f2->notintermediate) OS (fatal, NILF, _("%s cannot be both .NOTINTERMEDIATE and .INTERMEDIATE"), f2->name); else f2->intermediate = 1; /* .INTERMEDIATE with no deps does nothing. Marking all files as intermediates is useless since the goal targets would be deleted after they are built. */ for (f = lookup_file (".SECONDARY"); f != 0; f = f->prev) /* Mark .SECONDARY deps as both intermediate and secondary. */ if (f->deps) for (d = f->deps; d != 0; d = d->next) for (f2 = d->file; f2 != 0; f2 = f2->prev) if (f2->notintermediate) OS (fatal, NILF, _("%s cannot be both .NOTINTERMEDIATE and .SECONDARY"), f2->name); else f2->intermediate = f2->secondary = 1; /* .SECONDARY with no deps listed marks *all* files that way. */ else all_secondary = 1; if (no_intermediates && all_secondary) O (fatal, NILF, _(".NOTINTERMEDIATE and .SECONDARY are mutually exclusive")); f = lookup_file (".EXPORT_ALL_VARIABLES"); if (f != 0 && f->is_target) export_all_variables = 1; f = lookup_file (".IGNORE"); if (f != 0 && f->is_target) { if (f->deps == 0) ignore_errors_flag = 1; else for (d = f->deps; d != 0; d = d->next) for (f2 = d->file; f2 != 0; f2 = f2->prev) f2->command_flags |= COMMANDS_NOERROR; } f = lookup_file (".SILENT"); if (f != 0 && f->is_target) { if (f->deps == 0) run_silent = 1; else for (d = f->deps; d != 0; d = d->next) for (f2 = d->file; f2 != 0; f2 = f2->prev) f2->command_flags |= COMMANDS_SILENT; } f = lookup_file (".NOTPARALLEL"); if (f != 0 && f->is_target) { struct dep *d2; if (!f->deps) not_parallel = 1; else /* Set a wait point between every prerequisite of each target. */ for (d = f->deps; d != NULL; d = d->next) for (f2 = d->file; f2 != NULL; f2 = f2->prev) if (f2->deps) for (d2 = f2->deps->next; d2 != NULL; d2 = d2->next) d2->wait_here = 1; } { struct dep *prereqs = expand_extra_prereqs (lookup_variable (STRING_SIZE_TUPLE(".EXTRA_PREREQS"))); /* Perform per-file snap operations. */ hash_map_arg(&files, snap_file, prereqs); free_dep_chain (prereqs); } #ifndef NO_MINUS_C_MINUS_O /* If .POSIX was defined, remove OUTPUT_OPTION to comply. */ /* This needs more work: what if the user sets this in the makefile? if (posix_pedantic) define_variable_cname ("OUTPUT_OPTION", "", o_default, 1); */ #endif } /* Set the 'command_state' member of FILE and all its 'also_make's. Don't decrease the state of also_make's (e.g., don't downgrade a 'running' also_make to a 'deps_running' also_make). */ void set_command_state (struct file *file, enum cmd_state state) { struct dep *d; file->command_state = state; for (d = file->also_make; d != 0; d = d->next) if (state > d->file->command_state) d->file->command_state = state; } /* Convert an external file timestamp to internal form. */ FILE_TIMESTAMP file_timestamp_cons (const char *fname, time_t stamp, long int ns) { int offset = ORDINARY_MTIME_MIN + (FILE_TIMESTAMP_HI_RES ? ns : 0); FILE_TIMESTAMP s = stamp; FILE_TIMESTAMP product = (FILE_TIMESTAMP) s << FILE_TIMESTAMP_LO_BITS; FILE_TIMESTAMP ts = product + offset; if (! (s <= FILE_TIMESTAMP_S (ORDINARY_MTIME_MAX) && product <= ts && ts <= ORDINARY_MTIME_MAX)) { char buf[FILE_TIMESTAMP_PRINT_LEN_BOUND + 1]; const char *f = fname ? fname : _("Current time"); ts = s <= OLD_MTIME ? ORDINARY_MTIME_MIN : ORDINARY_MTIME_MAX; file_timestamp_sprintf (buf, ts); OSS (error, NILF, _("%s: timestamp out of range: substituting %s"), f, buf); } return ts; } /* Return the current time as a file timestamp, setting *RESOLUTION to its resolution. */ FILE_TIMESTAMP file_timestamp_now (int *resolution) { int r; time_t s; int ns; /* Don't bother with high-resolution clocks if file timestamps have only one-second resolution. The code below should work, but it's not worth the hassle of debugging it on hosts where it fails. */ #if FILE_TIMESTAMP_HI_RES # if HAVE_CLOCK_GETTIME && defined CLOCK_REALTIME { struct timespec timespec; if (clock_gettime (CLOCK_REALTIME, ×pec) == 0) { r = 1; s = timespec.tv_sec; ns = timespec.tv_nsec; goto got_time; } } # endif # if HAVE_GETTIMEOFDAY { struct timeval timeval; if (gettimeofday (&timeval, 0) == 0) { r = 1000; s = timeval.tv_sec; ns = timeval.tv_usec * 1000; goto got_time; } } # endif #endif r = 1000000000; s = time ((time_t *) 0); ns = 0; #if FILE_TIMESTAMP_HI_RES got_time: #endif *resolution = r; return file_timestamp_cons (0, s, ns); } /* Place into the buffer P a printable representation of the file timestamp TS. */ void file_timestamp_sprintf (char *p, FILE_TIMESTAMP ts) { time_t t = FILE_TIMESTAMP_S (ts); struct tm *tm = localtime (&t); if (tm) { intmax_t year = tm->tm_year; sprintf (p, "%04" PRIdMAX "-%02d-%02d %02d:%02d:%02d", year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); } else if (t < 0) sprintf (p, "%" PRIdMAX, (intmax_t) t); else sprintf (p, "%" PRIuMAX, (uintmax_t) t); p += strlen (p); /* Append nanoseconds as a fraction, but remove trailing zeros. We don't know the actual timestamp resolution, since clock_getres applies only to local times, whereas this timestamp might come from a remote filesystem. So removing trailing zeros is the best guess that we can do. */ sprintf (p, ".%09d", FILE_TIMESTAMP_NS (ts)); p += strlen (p) - 1; while (*p == '0') p--; p += *p != '.'; *p = '\0'; } /* Print the data base of files. */ void print_prereqs (const struct dep *deps) { const struct dep *ood = 0; /* Print all normal dependencies; note any order-only deps. */ for (; deps != 0; deps = deps->next) if (! deps->ignore_mtime) printf (" %s%s", deps->wait_here ? ".WAIT " : "", dep_name (deps)); else if (! ood) ood = deps; /* Print order-only deps, if we have any. */ if (ood) { printf (" | %s%s", ood->wait_here ? ".WAIT " : "", dep_name (ood)); for (ood = ood->next; ood != 0; ood = ood->next) if (ood->ignore_mtime) printf (" %s%s", ood->wait_here ? ".WAIT " : "", dep_name (ood)); } putchar ('\n'); } static void print_file (const void *item) { const struct file *f = item; /* If we're not using builtin targets, don't show them. Ideally we'd be able to delete them altogether but currently there's no facility to ever delete a file once it's been added. */ if (no_builtin_rules_flag && f->builtin) return; putchar ('\n'); if (f->cmds && f->cmds->recipe_prefix != cmd_prefix) { fputs (".RECIPEPREFIX = ", stdout); cmd_prefix = f->cmds->recipe_prefix; if (cmd_prefix != RECIPEPREFIX_DEFAULT) putchar (cmd_prefix); putchar ('\n'); } if (f->variables != 0) print_target_variables (f); if (!f->is_target) puts (_("# Not a target:")); printf ("%s:%s", f->name, f->double_colon ? ":" : ""); print_prereqs (f->deps); if (f->precious) puts (_("# Precious file (prerequisite of .PRECIOUS).")); if (f->phony) puts (_("# Phony target (prerequisite of .PHONY).")); if (f->cmd_target) puts (_("# Command line target.")); if (f->dontcare) puts (_("# A default, MAKEFILES, or -include/sinclude makefile.")); if (f->builtin) puts (_("# Builtin rule")); puts (f->tried_implicit ? _("# Implicit rule search has been done.") : _("# Implicit rule search has not been done.")); if (f->stem != 0) printf (_("# Implicit/static pattern stem: '%s'\n"), f->stem); if (f->intermediate) puts (_("# File is an intermediate prerequisite.")); if (f->notintermediate) puts (_("# File is a prerequisite of .NOTINTERMEDIATE.")); if (f->secondary) puts (_("# File is secondary (prerequisite of .SECONDARY).")); if (f->also_make != 0) { const struct dep *d; fputs (_("# Also makes:"), stdout); for (d = f->also_make; d != 0; d = d->next) printf (" %s", dep_name (d)); putchar ('\n'); } if (f->last_mtime == UNKNOWN_MTIME) puts (_("# Modification time never checked.")); else if (f->last_mtime == NONEXISTENT_MTIME) puts (_("# File does not exist.")); else if (f->last_mtime == OLD_MTIME) puts (_("# File is very old.")); else { char buf[FILE_TIMESTAMP_PRINT_LEN_BOUND + 1]; file_timestamp_sprintf (buf, f->last_mtime); printf (_("# Last modified %s\n"), buf); } puts (f->updated ? _("# File has been updated.") : _("# File has not been updated.")); switch (f->command_state) { case cs_running: puts (_("# Recipe currently running (THIS IS A BUG).")); break; case cs_deps_running: puts (_("# Dependencies recipe running (THIS IS A BUG).")); break; case cs_not_started: case cs_finished: switch (f->update_status) { case us_none: break; case us_success: puts (_("# Successfully updated.")); break; case us_question: assert (question_flag); puts (_("# Needs to be updated (-q is set).")); break; case us_failed: puts (_("# Failed to be updated.")); break; } break; default: puts (_("# Invalid value in 'command_state' member!")); fflush (stdout); fflush (stderr); abort (); } if (f->variables != 0) print_file_variables (f); if (f->cmds != 0) print_commands (f->cmds); if (f->prev) print_file ((const void *) f->prev); } void print_file_data_base (void) { puts (_("\n# Files")); hash_map (&files, print_file); fputs (_("\n# files hash-table stats:\n# "), stdout); hash_print_stats (&files, stdout); } /* Verify the integrity of the data base of files. */ #define VERIFY_CACHED(_p,_n) \ do{ \ if (_p->_n && _p->_n[0] && !strcache_iscached (_p->_n)) \ error (NULL, strlen (_p->name) + CSTRLEN (# _n) + strlen (_p->_n), \ _("%s: field '%s' not cached: %s"), _p->name, # _n, _p->_n); \ }while(0) static void verify_file (const void *item) { const struct file *f = item; const struct dep *d; VERIFY_CACHED (f, name); VERIFY_CACHED (f, hname); VERIFY_CACHED (f, vpath); VERIFY_CACHED (f, stem); /* Check the deps. */ for (d = f->deps; d != 0; d = d->next) { if (! d->need_2nd_expansion) VERIFY_CACHED (d, name); VERIFY_CACHED (d, stem); } } void verify_file_data_base (void) { hash_map (&files, verify_file); } #define EXPANSION_INCREMENT(_l) ((((_l) / 500) + 1) * 500) char * build_target_list (char *value) { static unsigned long last_targ_count = 0; if (files.ht_fill != last_targ_count) { size_t max = EXPANSION_INCREMENT (strlen (value)); size_t len; char *p; struct file **fp = (struct file **) files.ht_vec; struct file **end = &fp[files.ht_size]; /* Make sure we have at least MAX bytes in the allocated buffer. */ value = xrealloc (value, max); p = value; len = 0; for (; fp < end; ++fp) if (!HASH_VACANT (*fp) && (*fp)->is_target) { struct file *f = *fp; size_t l = strlen (f->name); len += l + 1; if (len > max) { size_t off = p - value; max += EXPANSION_INCREMENT (l + 1); value = xrealloc (value, max); p = &value[off]; } p = mempcpy (p, f->name, l); *(p++) = ' '; } *(p-1) = '\0'; last_targ_count = files.ht_fill; } return value; } void init_hash_files (void) { hash_init (&files, 1000, file_hash_1, file_hash_2, file_hash_cmp); } /* EOF */