/* Target file hash table management for GNU Make. Copyright (C) 1988,89,90,91,92,93,94,95,96,97 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 2, 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 GNU Make; see the file COPYING. If not, write to the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "make.h" #include "dep.h" #include "filedef.h" #include "job.h" #include "commands.h" #include "variable.h" #include /* Hash table of files the makefile knows how to make. */ #ifndef FILE_BUCKETS #define FILE_BUCKETS 1007 #endif static struct file *files[FILE_BUCKETS]; /* Number of files with the `intermediate' flag set. */ unsigned int num_intermediates = 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. enter_file similar, but create one if there is none. */ struct file * lookup_file (name) char *name; { register struct file *f; register char *n; register unsigned int hashval; if (*name == '\0') abort (); /* 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. */ #ifdef VMS while (name[0] == '[' && name[1] == ']' && name[2] != '\0') name += 2; #endif while (name[0] == '.' && name[1] == '/' && name[2] != '\0') { name += 2; while (*name == '/') /* Skip following slashes: ".//foo" is "foo", not "/foo". */ ++name; } if (*name == '\0') /* It was all slashes after a dot. */ #ifdef VMS name = "[]"; #else #ifdef _AMIGA name = ""; #else name = "./"; #endif /* AMIGA */ #endif /* VMS */ hashval = 0; for (n = name; *n != '\0'; ++n) HASHI (hashval, *n); hashval %= FILE_BUCKETS; for (f = files[hashval]; f != 0; f = f->next) { if (strieq (f->hname, name)) { return f; } } return 0; } struct file * enter_file (name) char *name; { register struct file *f, *new; register char *n; register unsigned int hashval; #ifdef VMS char *lname, *ln; #endif if (*name == '\0') abort (); #ifdef VMS lname = (char *)malloc (strlen (name) + 1); for (n = name, ln = lname; *n != '\0'; ++n, ++ln) { if (isupper(*n)) *ln = tolower(*n); else *ln = *n; } *ln = 0; name = lname; #endif hashval = 0; for (n = name; *n != '\0'; ++n) HASHI (hashval, *n); hashval %= FILE_BUCKETS; for (f = files[hashval]; f != 0; f = f->next) if (strieq (f->hname, name)) break; if (f != 0 && !f->double_colon) { #ifdef VMS free(lname); #endif return f; } new = (struct file *) xmalloc (sizeof (struct file)); bzero ((char *) new, sizeof (struct file)); new->name = new->hname = name; new->update_status = -1; if (f == 0) { /* This is a completely new file. */ new->next = files[hashval]; files[hashval] = new; } else { /* There is already a double-colon entry for this file. */ new->double_colon = f; while (f->prev != 0) f = f->prev; f->prev = 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 (file, name) register struct file *file; char *name; { char *oldname = file->hname; register unsigned int oldhash; register char *n; while (file->renamed != 0) file = file->renamed; /* Find the hash values of the old and new names. */ oldhash = 0; for (n = oldname; *n != '\0'; ++n) HASHI (oldhash, *n); file_hash_enter (file, name, oldhash, file->name); } /* 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 (file, name) register struct file *file; char *name; { rehash_file(file, name); while (file) { file->name = file->hname; file = file->prev; } } void file_hash_enter (file, name, oldhash, oldname) register struct file *file; char *name; unsigned int oldhash; char *oldname; { unsigned int oldbucket = oldhash % FILE_BUCKETS; register unsigned int newhash, newbucket; struct file *oldfile; register char *n; register struct file *f; newhash = 0; for (n = name; *n != '\0'; ++n) HASHI (newhash, *n); newbucket = newhash % FILE_BUCKETS; /* Look for an existing file under the new name. */ for (oldfile = files[newbucket]; oldfile != 0; oldfile = oldfile->next) if (strieq (oldfile->hname, name)) break; /* If the old file is the same as the new file, something's wrong. */ assert (oldfile != file); if (oldhash != 0 && (newbucket != oldbucket || oldfile != 0)) { /* Remove FILE from its hash bucket. */ struct file *lastf = 0; for (f = files[oldbucket]; f != file; f = f->next) lastf = f; if (lastf == 0) files[oldbucket] = f->next; else lastf->next = f->next; } /* Give FILE its new name. */ file->hname = name; for (f = file->double_colon; f != 0; f = f->prev) f->hname = name; if (oldfile == 0) { /* There is no existing file with the new name. */ if (newbucket != oldbucket) { /* Put FILE in its new hash bucket. */ file->next = files[newbucket]; files[newbucket] = file; } } else { /* There is an existing file with the new name. We must merge FILE into the existing file. */ register struct dep *d; if (file->cmds != 0) { if (oldfile->cmds == 0) oldfile->cmds = file->cmds; else if (file->cmds != oldfile->cmds) { /* We have two sets of commands. We will go with the one given in the rule explicitly mentioning this name, but give a message to let the user know what's going on. */ if (oldfile->cmds->filename != 0) makefile_error (file->cmds->filename, file->cmds->lineno, "Commands were specified for \ file `%s' at %s:%u,", oldname, oldfile->cmds->filename, oldfile->cmds->lineno); else makefile_error (file->cmds->filename, file->cmds->lineno, "Commands for file `%s' were found by \ implicit rule search,", oldname); makefile_error (file->cmds->filename, file->cmds->lineno, "but `%s' is now considered the same file \ as `%s'.", oldname, name); makefile_error (file->cmds->filename, file->cmds->lineno, "Commands for `%s' will be ignored \ in favor of those for `%s'.", name, oldname); } } /* Merge the dependencies of the two files. */ d = oldfile->deps; if (d == 0) oldfile->deps = file->deps; else { while (d->next != 0) d = d->next; d->next = file->deps; } merge_variable_set_lists (&oldfile->variables, file->variables); if (oldfile->double_colon && file->is_target && !file->double_colon) fatal ("can't rename single-colon `%s' to double-colon `%s'", oldname, name); if (!oldfile->double_colon && file->double_colon) { if (oldfile->is_target) fatal ("can't rename double-colon `%s' to single-colon `%s'", oldname, name); else oldfile->double_colon = file->double_colon; } if (file->last_mtime > oldfile->last_mtime) /* %%% Kludge so -W wins on a file that gets vpathized. */ oldfile->last_mtime = file->last_mtime; #define MERGE(field) oldfile->field |= file->field MERGE (precious); MERGE (tried_implicit); MERGE (updating); MERGE (updated); MERGE (is_target); MERGE (cmd_target); MERGE (phony); MERGE (ignore_vpath); #undef MERGE file->renamed = oldfile; } } /* 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 (sig) int sig; { register int i; register struct file *f; char doneany; if (question_flag || touch_flag) return; if (sig && just_print_flag) return; doneany = 0; for (i = 0; i < FILE_BUCKETS; ++i) for (f = files[i]; f != 0; f = f->next) if (f->intermediate && (f->dontcare || !f->precious) && !f->secondary) { int status; if (f->update_status == -1) /* If nothing would have created this file yet, don't print an "rm" command for it. */ continue; else if (just_print_flag) status = 0; else { status = unlink (f->name); if (status < 0 && errno == ENOENT) continue; } if (!f->dontcare) { if (sig) error ("*** Deleting intermediate file `%s'", f->name); else if (!silent_flag) { if (! doneany) { fputs ("rm ", stdout); doneany = 1; } else putchar (' '); fputs (f->name, stdout); fflush (stdout); } if (status < 0) perror_with_name ("unlink: ", f->name); } } if (doneany && !sig) { putchar ('\n'); fflush (stdout); } } /* For each dependency of each file, make the `struct dep' point at the appropriate `struct file' (which may have to be created). Also mark the files depended on by .PRECIOUS, .PHONY, .SILENT, and various other special targets. */ void snap_deps () { register struct file *f, *f2; register struct dep *d; register int i; /* Enter each dependency name as a file. */ for (i = 0; i < FILE_BUCKETS; ++i) for (f = files[i]; f != 0; f = f->next) for (f2 = f; f2 != 0; f2 = f2->prev) for (d = f2->deps; d != 0; d = d->next) if (d->name != 0) { d->file = lookup_file (d->name); if (d->file == 0) d->file = enter_file (d->name); else free (d->name); d->name = 0; } 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 (".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 and nonexistent. */ f2->phony = 1; f2->last_mtime = (time_t) -1; } for (f = lookup_file (".INTERMEDIATE"); f != 0; f = f->prev) { /* .INTERMEDIATE with deps listed marks those deps as intermediate files. */ for (d = f->deps; d != 0; d = d->next) for (f2 = d->file; f2 != 0; f2 = f2->prev) 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) { /* .SECONDARY with deps listed marks those deps as intermediate files in that they don't get rebuilt if not actually needed; but unlike real intermediate files, these are not deleted after make finishes. */ if (f->deps) { for (d = f->deps; d != 0; d = d->next) for (f2 = d->file; f2 != 0; f2 = f2->prev) f2->intermediate = f2->secondary = 1; } /* .SECONDARY with no deps listed marks *all* files that way. */ else { int i; for (i = 0; i < FILE_BUCKETS; i++) for (f2 = files[i]; f2; f2= f2->next) f2->intermediate = f2->secondary = 1; } } 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) silent_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_SILENT; } f = lookup_file (".POSIX"); if (f != 0 && f->is_target) posix_pedantic = 1; } /* Set the `command_state' member of FILE and all its `also_make's. */ void set_command_state (file, state) struct file *file; int state; { struct dep *d; file->command_state = state; for (d = file->also_make; d != 0; d = d->next) d->file->command_state = state; } /* Print the data base of files. */ static void print_file (f) struct file *f; { register struct dep *d; putchar ('\n'); if (!f->is_target) puts ("# Not a target:"); printf ("%s:%s", f->name, f->double_colon ? ":" : ""); for (d = f->deps; d != 0; d = d->next) printf (" %s", dep_name (d)); putchar ('\n'); if (f->precious) puts ("# Precious file (dependency of .PRECIOUS)."); if (f->phony) puts ("# Phony target (dependency of .PHONY)."); if (f->cmd_target) puts ("# Command-line target."); if (f->dontcare) puts ("# A default or MAKEFILES makefile."); printf ("# Implicit rule search has%s been done.\n", f->tried_implicit ? "" : " not"); if (f->stem != 0) printf ("# Implicit/static pattern stem: `%s'\n", f->stem); if (f->intermediate) puts ("# File is an intermediate dependency."); if (f->also_make != 0) { 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 == (time_t) 0) puts ("# Modification time never checked."); else if (f->last_mtime == (time_t) -1) puts ("# File does not exist."); else printf ("# Last modified %.24s (%ld)\n", ctime (&f->last_mtime), (long int) f->last_mtime); printf ("# File has%s been updated.\n", f->updated ? "" : " not"); switch (f->command_state) { case cs_running: puts ("# Commands currently running (THIS IS A BUG)."); break; case cs_deps_running: puts ("# Dependencies commands running (THIS IS A BUG)."); break; case cs_not_started: case cs_finished: switch (f->update_status) { case -1: break; case 0: puts ("# Successfully updated."); break; case 1: assert (question_flag); puts ("# Needs to be updated (-q is set)."); break; case 2: puts ("# Failed to be updated."); break; default: puts ("# Invalid value in `update_status' member!"); fflush (stdout); fflush (stderr); abort (); } 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); } void print_file_data_base () { register unsigned int i, nfiles, per_bucket; register struct file *file; puts ("\n# Files"); per_bucket = nfiles = 0; for (i = 0; i < FILE_BUCKETS; ++i) { register unsigned int this_bucket = 0; for (file = files[i]; file != 0; file = file->next) { register struct file *f; ++this_bucket; for (f = file; f != 0; f = f->prev) print_file (f); } nfiles += this_bucket; if (this_bucket > per_bucket) per_bucket = this_bucket; } if (nfiles == 0) puts ("\n# No files."); else { printf ("\n# %u files in %u hash buckets.\n", nfiles, FILE_BUCKETS); #ifndef NO_FLOAT printf ("# average %.1f files per bucket, max %u files in one bucket.\n", ((double) nfiles) / ((double) FILE_BUCKETS) * 100.0, per_bucket); #endif } } /* EOF */