/*- * main -- * The main function, for obvious reasons. Initializes variables * and a few modules, then parses the arguments give it in the * environment and on the command line. Reads the system makefile * followed by either Makefile, makefile or the file given by the * -f argument. Sets the .MAKEFLAGS PMake variable based on all the * flags it has received by then uses either the Make or the Compat * module to create the initial list of targets. * * Results: * If -q was given, exits -1 if anything was out-of-date. Else it exits * 0. * * Side Effects: * The program exits when done. Targets are created. etc. etc. etc. */ int main(int argc, char **argv) { Lst targs; /* target nodes to create -- passed to Make_Init */ Boolean outOfDate = FALSE; /* FALSE if all targets up to date */ struct stat sb, sa; char *p1, *path; char mdpath[MAXPATHLEN]; #ifdef FORCE_MACHINE const char *machine = FORCE_MACHINE; #else const char *machine = getenv("MACHINE"); #endif const char *machine_arch = getenv("MACHINE_ARCH"); char *syspath = getenv("MAKESYSPATH"); Lst sysMkPath; /* Path of sys.mk */ char *cp = NULL, *start; /* avoid faults on read-only strings */ static char defsyspath[] = _PATH_DEFSYSPATH; char found_path[MAXPATHLEN + 1]; /* for searching for sys.mk */ struct timeval rightnow; /* to initialize random seed */ struct utsname utsname; /* default to writing debug to stderr */ debug_file = stderr; #ifdef SIGINFO (void)bmake_signal(SIGINFO, siginfo); #endif /* * Set the seed to produce a different random sequence * on each program execution. */ gettimeofday(&rightnow, NULL); srandom(rightnow.tv_sec + rightnow.tv_usec); if ((progname = strrchr(argv[0], '/')) != NULL) progname++; else progname = argv[0]; #if defined(MAKE_NATIVE) || (defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)) /* * get rid of resource limit on file descriptors */ { struct rlimit rl; if (getrlimit(RLIMIT_NOFILE, &rl) != -1 && rl.rlim_cur != rl.rlim_max) { rl.rlim_cur = rl.rlim_max; (void)setrlimit(RLIMIT_NOFILE, &rl); } } #endif if (uname(&utsname) == -1) { (void)fprintf(stderr, "%s: uname failed (%s).\n", progname, strerror(errno)); exit(2); } /* * Get the name of this type of MACHINE from utsname * so we can share an executable for similar machines. * (i.e. m68k: amiga hp300, mac68k, sun3, ...) * * Note that both MACHINE and MACHINE_ARCH are decided at * run-time. */ if (!machine) { #ifdef MAKE_NATIVE machine = utsname.machine; #else #ifdef MAKE_MACHINE machine = MAKE_MACHINE; #else machine = "unknown"; #endif #endif } if (!machine_arch) { #if defined(MAKE_NATIVE) && defined(HAVE_SYSCTL) && defined(CTL_HW) && defined(HW_MACHINE_ARCH) static char machine_arch_buf[sizeof(utsname.machine)]; int mib[2] = { CTL_HW, HW_MACHINE_ARCH }; size_t len = sizeof(machine_arch_buf); if (sysctl(mib, __arraycount(mib), machine_arch_buf, &len, NULL, 0) < 0) { (void)fprintf(stderr, "%s: sysctl failed (%s).\n", progname, strerror(errno)); exit(2); } machine_arch = machine_arch_buf; #else #ifndef MACHINE_ARCH #ifdef MAKE_MACHINE_ARCH machine_arch = MAKE_MACHINE_ARCH; #else machine_arch = "unknown"; #endif #else machine_arch = MACHINE_ARCH; #endif #endif } myPid = getpid(); /* remember this for vFork() */ /* * Just in case MAKEOBJDIR wants us to do something tricky. */ Var_Init(); /* Initialize the lists of variables for * parsing arguments */ Var_Set(".MAKE.OS", utsname.sysname, VAR_GLOBAL, 0); Var_Set("MACHINE", machine, VAR_GLOBAL, 0); Var_Set("MACHINE_ARCH", machine_arch, VAR_GLOBAL, 0); #ifdef MAKE_VERSION Var_Set("MAKE_VERSION", MAKE_VERSION, VAR_GLOBAL, 0); #endif Var_Set(".newline", "\n", VAR_GLOBAL, 0); /* handy for :@ loops */ /* * This is the traditional preference for makefiles. */ #ifndef MAKEFILE_PREFERENCE_LIST # define MAKEFILE_PREFERENCE_LIST "makefile Makefile" #endif Var_Set(MAKEFILE_PREFERENCE, MAKEFILE_PREFERENCE_LIST, VAR_GLOBAL, 0); Var_Set(MAKE_DEPENDFILE, ".depend", VAR_GLOBAL, 0); create = Lst_Init(FALSE); makefiles = Lst_Init(FALSE); printVars = FALSE; debugVflag = FALSE; variables = Lst_Init(FALSE); beSilent = FALSE; /* Print commands as executed */ ignoreErrors = FALSE; /* Pay attention to non-zero returns */ noExecute = FALSE; /* Execute all commands */ noRecursiveExecute = FALSE; /* Execute all .MAKE targets */ keepgoing = FALSE; /* Stop on error */ allPrecious = FALSE; /* Remove targets when interrupted */ queryFlag = FALSE; /* This is not just a check-run */ noBuiltins = FALSE; /* Read the built-in rules */ touchFlag = FALSE; /* Actually update targets */ debug = 0; /* No debug verbosity, please. */ jobsRunning = FALSE; maxJobs = DEFMAXLOCAL; /* Set default local max concurrency */ maxJobTokens = maxJobs; compatMake = FALSE; /* No compat mode */ ignorePWD = FALSE; /* * Initialize the parsing, directory and variable modules to prepare * for the reading of inclusion paths and variable settings on the * command line */ /* * Initialize various variables. * MAKE also gets this name, for compatibility * .MAKEFLAGS gets set to the empty string just in case. * MFLAGS also gets initialized empty, for compatibility. */ Parse_Init(); if (argv[0][0] == '/' || strchr(argv[0], '/') == NULL) { /* * Leave alone if it is an absolute path, or if it does * not contain a '/' in which case we need to find it in * the path, like execvp(3) and the shells do. */ p1 = argv[0]; } else { /* * A relative path, canonicalize it. */ p1 = cached_realpath(argv[0], mdpath); if (!p1 || *p1 != '/' || stat(p1, &sb) < 0) { p1 = argv[0]; /* realpath failed */ } } Var_Set("MAKE", p1, VAR_GLOBAL, 0); Var_Set(".MAKE", p1, VAR_GLOBAL, 0); Var_Set(MAKEFLAGS, "", VAR_GLOBAL, 0); Var_Set(MAKEOVERRIDES, "", VAR_GLOBAL, 0); Var_Set("MFLAGS", "", VAR_GLOBAL, 0); Var_Set(".ALLTARGETS", "", VAR_GLOBAL, 0); /* some makefiles need to know this */ Var_Set(MAKE_LEVEL ".ENV", MAKE_LEVEL_ENV, VAR_CMD, 0); /* * Set some other useful macros */ { char tmp[64], *ep; makelevel = ((ep = getenv(MAKE_LEVEL_ENV)) && *ep) ? atoi(ep) : 0; if (makelevel < 0) makelevel = 0; snprintf(tmp, sizeof(tmp), "%d", makelevel); Var_Set(MAKE_LEVEL, tmp, VAR_GLOBAL, 0); snprintf(tmp, sizeof(tmp), "%u", myPid); Var_Set(".MAKE.PID", tmp, VAR_GLOBAL, 0); snprintf(tmp, sizeof(tmp), "%u", getppid()); Var_Set(".MAKE.PPID", tmp, VAR_GLOBAL, 0); } if (makelevel > 0) { char pn[1024]; snprintf(pn, sizeof(pn), "%s[%d]", progname, makelevel); progname = bmake_strdup(pn); } #ifdef USE_META meta_init(); #endif /* * First snag any flags out of the MAKE environment variable. * (Note this is *not* MAKEFLAGS since /bin/make uses that and it's * in a different format). */ #ifdef POSIX p1 = explode(getenv("MAKEFLAGS")); Main_ParseArgLine(p1); free(p1); #else Main_ParseArgLine(getenv("MAKE")); #endif /* * Find where we are (now). * We take care of PWD for the automounter below... */ if (getcwd(curdir, MAXPATHLEN) == NULL) { (void)fprintf(stderr, "%s: getcwd: %s.\n", progname, strerror(errno)); exit(2); } MainParseArgs(argc, argv); if (enterFlag) printf("%s: Entering directory `%s'\n", progname, curdir); /* * Verify that cwd is sane. */ if (stat(curdir, &sa) == -1) { (void)fprintf(stderr, "%s: %s: %s.\n", progname, curdir, strerror(errno)); exit(2); } /* * All this code is so that we know where we are when we start up * on a different machine with pmake. * Overriding getcwd() with $PWD totally breaks MAKEOBJDIRPREFIX * since the value of curdir can vary depending on how we got * here. Ie sitting at a shell prompt (shell that provides $PWD) * or via subdir.mk in which case its likely a shell which does * not provide it. * So, to stop it breaking this case only, we ignore PWD if * MAKEOBJDIRPREFIX is set or MAKEOBJDIR contains a transform. */ #ifndef NO_PWD_OVERRIDE if (!ignorePWD) { char *pwd, *ptmp1 = NULL, *ptmp2 = NULL; if ((pwd = getenv("PWD")) != NULL && Var_Value("MAKEOBJDIRPREFIX", VAR_CMD, &ptmp1) == NULL) { const char *makeobjdir = Var_Value("MAKEOBJDIR", VAR_CMD, &ptmp2); if (makeobjdir == NULL || !strchr(makeobjdir, '$')) { if (stat(pwd, &sb) == 0 && sa.st_ino == sb.st_ino && sa.st_dev == sb.st_dev) (void)strncpy(curdir, pwd, MAXPATHLEN); } } free(ptmp1); free(ptmp2); } #endif Var_Set(".CURDIR", curdir, VAR_GLOBAL, 0); /* * Find the .OBJDIR. If MAKEOBJDIRPREFIX, or failing that, * MAKEOBJDIR is set in the environment, try only that value * and fall back to .CURDIR if it does not exist. * * Otherwise, try _PATH_OBJDIR.MACHINE, _PATH_OBJDIR, and * finally _PATH_OBJDIRPREFIX`pwd`, in that order. If none * of these paths exist, just use .CURDIR. */ Dir_Init(curdir); (void)Main_SetObjdir(curdir); if ((path = Var_Value("MAKEOBJDIRPREFIX", VAR_CMD, &p1)) != NULL) { (void)snprintf(mdpath, MAXPATHLEN, "%s%s", path, curdir); (void)Main_SetObjdir(mdpath); free(p1); } else if ((path = Var_Value("MAKEOBJDIR", VAR_CMD, &p1)) != NULL) { (void)Main_SetObjdir(path); free(p1); } else { (void)snprintf(mdpath, MAXPATHLEN, "%s.%s", _PATH_OBJDIR, machine); if (!Main_SetObjdir(mdpath) && !Main_SetObjdir(_PATH_OBJDIR)) { (void)snprintf(mdpath, MAXPATHLEN, "%s%s", _PATH_OBJDIRPREFIX, curdir); (void)Main_SetObjdir(mdpath); } } /* * Initialize archive, target and suffix modules in preparation for * parsing the makefile(s) */ Arch_Init(); Targ_Init(); Suff_Init(); Trace_Init(tracefile); DEFAULT = NULL; (void)time(&now); Trace_Log(MAKESTART, NULL); /* * Set up the .TARGETS variable to contain the list of targets to be * created. If none specified, make the variable empty -- the parser * will fill the thing in with the default or .MAIN target. */ if (!Lst_IsEmpty(create)) { LstNode ln; for (ln = Lst_First(create); ln != NULL; ln = Lst_Succ(ln)) { char *name = (char *)Lst_Datum(ln); Var_Append(".TARGETS", name, VAR_GLOBAL); } } else Var_Set(".TARGETS", "", VAR_GLOBAL, 0); /* * If no user-supplied system path was given (through the -m option) * add the directories from the DEFSYSPATH (more than one may be given * as dir1:...:dirn) to the system include path. */ if (syspath == NULL || *syspath == '\0') syspath = defsyspath; else syspath = bmake_strdup(syspath); for (start = syspath; *start != '\0'; start = cp) { for (cp = start; *cp != '\0' && *cp != ':'; cp++) continue; if (*cp == ':') { *cp++ = '\0'; } /* look for magic parent directory search string */ if (strncmp(".../", start, 4) != 0) { (void)Dir_AddDir(defIncPath, start); } else { if (Dir_FindHereOrAbove(curdir, start+4, found_path, sizeof(found_path))) { (void)Dir_AddDir(defIncPath, found_path); } } } if (syspath != defsyspath) free(syspath); /* * Read in the built-in rules first, followed by the specified * makefile, if it was (makefile != NULL), or the default * makefile and Makefile, in that order, if it wasn't. */ if (!noBuiltins) { LstNode ln; sysMkPath = Lst_Init(FALSE); Dir_Expand(_PATH_DEFSYSMK, Lst_IsEmpty(sysIncPath) ? defIncPath : sysIncPath, sysMkPath); if (Lst_IsEmpty(sysMkPath)) Fatal("%s: no system rules (%s).", progname, _PATH_DEFSYSMK); ln = Lst_Find(sysMkPath, NULL, ReadMakefile); if (ln == NULL) Fatal("%s: cannot open %s.", progname, (char *)Lst_Datum(ln)); } if (!Lst_IsEmpty(makefiles)) { LstNode ln; ln = Lst_Find(makefiles, NULL, ReadAllMakefiles); if (ln != NULL) Fatal("%s: cannot open %s.", progname, (char *)Lst_Datum(ln)); } else { p1 = Var_Subst(NULL, "${" MAKEFILE_PREFERENCE "}", VAR_CMD, VARF_WANTRES); if (p1) { (void)str2Lst_Append(makefiles, p1, NULL); (void)Lst_Find(makefiles, NULL, ReadMakefile); free(p1); } } /* In particular suppress .depend for '-r -V .OBJDIR -f /dev/null' */ if (!noBuiltins || !printVars) { makeDependfile = Var_Subst(NULL, "${.MAKE.DEPENDFILE:T}", VAR_CMD, VARF_WANTRES); doing_depend = TRUE; (void)ReadMakefile(makeDependfile, NULL); doing_depend = FALSE; } if (enterFlagObj) printf("%s: Entering directory `%s'\n", progname, objdir); MakeMode(NULL); Var_Append("MFLAGS", Var_Value(MAKEFLAGS, VAR_GLOBAL, &p1), VAR_GLOBAL); free(p1); if (!forceJobs && !compatMake && Var_Exists(".MAKE.JOBS", VAR_GLOBAL)) { char *value; int n; value = Var_Subst(NULL, "${.MAKE.JOBS}", VAR_GLOBAL, VARF_WANTRES); n = strtol(value, NULL, 0); if (n < 1) { (void)fprintf(stderr, "%s: illegal value for .MAKE.JOBS -- must be positive integer!\n", progname); exit(1); } if (n != maxJobs) { Var_Append(MAKEFLAGS, "-j", VAR_GLOBAL); Var_Append(MAKEFLAGS, value, VAR_GLOBAL); } maxJobs = n; maxJobTokens = maxJobs; forceJobs = TRUE; free(value); } /* * Be compatible if user did not specify -j and did not explicitly * turned compatibility on */ if (!compatMake && !forceJobs) { compatMake = TRUE; } if (!compatMake) Job_ServerStart(maxJobTokens, jp_0, jp_1); if (DEBUG(JOB)) fprintf(debug_file, "job_pipe %d %d, maxjobs %d, tokens %d, compat %d\n", jp_0, jp_1, maxJobs, maxJobTokens, compatMake); Main_ExportMAKEFLAGS(TRUE); /* initial export */ /* * For compatibility, look at the directories in the VPATH variable * and add them to the search path, if the variable is defined. The * variable's value is in the same format as the PATH envariable, i.e. * <directory>:<directory>:<directory>... */ if (Var_Exists("VPATH", VAR_CMD)) { char *vpath, savec; /* * GCC stores string constants in read-only memory, but * Var_Subst will want to write this thing, so store it * in an array */ static char VPATH[] = "${VPATH}"; vpath = Var_Subst(NULL, VPATH, VAR_CMD, VARF_WANTRES); path = vpath; do { /* skip to end of directory */ for (cp = path; *cp != ':' && *cp != '\0'; cp++) continue; /* Save terminator character so know when to stop */ savec = *cp; *cp = '\0'; /* Add directory to search path */ (void)Dir_AddDir(dirSearchPath, path); *cp = savec; path = cp + 1; } while (savec == ':'); free(vpath); } /* * Now that all search paths have been read for suffixes et al, it's * time to add the default search path to their lists... */ Suff_DoPaths(); /* * Propagate attributes through :: dependency lists. */ Targ_Propagate(); /* print the initial graph, if the user requested it */ if (DEBUG(GRAPH1)) Targ_PrintGraph(1); /* print the values of any variables requested by the user */ if (printVars) { LstNode ln; Boolean expandVars; if (debugVflag) expandVars = FALSE; else expandVars = getBoolean(".MAKE.EXPAND_VARIABLES", FALSE); for (ln = Lst_First(variables); ln != NULL; ln = Lst_Succ(ln)) { char *var = (char *)Lst_Datum(ln); char *value; if (strchr(var, '$')) { value = p1 = Var_Subst(NULL, var, VAR_GLOBAL, VARF_WANTRES); } else if (expandVars) { char tmp[128]; if (snprintf(tmp, sizeof(tmp), "${%s}", var) >= (int)(sizeof(tmp))) Fatal("%s: variable name too big: %s", progname, var); value = p1 = Var_Subst(NULL, tmp, VAR_GLOBAL, VARF_WANTRES); } else { value = Var_Value(var, VAR_GLOBAL, &p1); } printf("%s\n", value ? value : ""); free(p1); } } else { /* * Have now read the entire graph and need to make a list of * targets to create. If none was given on the command line, * we consult the parsing module to find the main target(s) * to create. */ if (Lst_IsEmpty(create)) targs = Parse_MainName(); else targs = Targ_FindList(create, TARG_CREATE); if (!compatMake) { /* * Initialize job module before traversing the graph * now that any .BEGIN and .END targets have been read. * This is done only if the -q flag wasn't given * (to prevent the .BEGIN from being executed should * it exist). */ if (!queryFlag) { Job_Init(); jobsRunning = TRUE; } /* Traverse the graph, checking on all the targets */ outOfDate = Make_Run(targs); } else { /* * Compat_Init will take care of creating all the * targets as well as initializing the module. */ Compat_Run(targs); } } #ifdef CLEANUP Lst_Destroy(targs, NULL); Lst_Destroy(variables, NULL); Lst_Destroy(makefiles, NULL); Lst_Destroy(create, (FreeProc *)free); #endif /* print the graph now it's been processed if the user requested it */ if (DEBUG(GRAPH2)) Targ_PrintGraph(2); Trace_Log(MAKEEND, 0); if (enterFlagObj) printf("%s: Leaving directory `%s'\n", progname, objdir); if (enterFlag) printf("%s: Leaving directory `%s'\n", progname, curdir); #ifdef USE_META meta_finish(); #endif Suff_End(); Targ_End(); Arch_End(); Var_End(); Parse_End(); Dir_End(); Job_End(); Trace_End(); return outOfDate ? 1 : 0; }
Boolean meta_oodate(GNode *gn, Boolean oodate) { static char *tmpdir = NULL; static char cwd[MAXPATHLEN]; char ldir_vname[64]; char latestdir[MAXPATHLEN]; char fname[MAXPATHLEN]; char fname1[MAXPATHLEN]; char fname2[MAXPATHLEN]; char *p; char *cp; static size_t cwdlen = 0; static size_t tmplen = 0; FILE *fp; Boolean ignoreOODATE = FALSE; Lst missingFiles; if (oodate) return oodate; /* we're done */ missingFiles = Lst_Init(FALSE); /* * We need to check if the target is out-of-date. This includes * checking if the expanded command has changed. This in turn * requires that all variables are set in the same way that they * would be if the target needs to be re-built. */ Make_DoAllVar(gn); meta_name(gn, fname, sizeof(fname), NULL, NULL); #ifdef DEBUG_META_MODE if (DEBUG(META)) fprintf(debug_file, "meta_oodate: %s\n", fname); #endif if ((fp = fopen(fname, "r")) != NULL) { static char *buf = NULL; static size_t bufsz; int lineno = 0; int lastpid = 0; int pid; int f = 0; int x; LstNode ln; struct stat fs; if (!buf) { bufsz = 8 * BUFSIZ; buf = bmake_malloc(bufsz); } if (!cwdlen) { if (getcwd(cwd, sizeof(cwd)) == NULL) err(1, "Could not get current working directory"); cwdlen = strlen(cwd); } if (!tmpdir) { tmpdir = getTmpdir(); tmplen = strlen(tmpdir); } /* we want to track all the .meta we read */ Var_Append(".MAKE.META.FILES", fname, VAR_GLOBAL); ln = Lst_First(gn->commands); while (!oodate && (x = fgetLine(&buf, &bufsz, 0, fp)) > 0) { lineno++; if (buf[x - 1] == '\n') buf[x - 1] = '\0'; else { warnx("%s: %d: line truncated at %u", fname, lineno, x); oodate = TRUE; break; } /* Find the start of the build monitor section. */ if (!f) { if (strncmp(buf, "-- filemon", 10) == 0) { f = 1; continue; } if (strncmp(buf, "# buildmon", 10) == 0) { f = 1; continue; } } /* Delimit the record type. */ p = buf; #ifdef DEBUG_META_MODE if (DEBUG(META)) fprintf(debug_file, "%s: %d: %s\n", fname, lineno, buf); #endif strsep(&p, " "); if (f) { /* * We are in the 'filemon' output section. * Each record from filemon follows the general form: * * <key> <pid> <data> * * Where: * <key> is a single letter, denoting the syscall. * <pid> is the process that made the syscall. * <data> is the arguments (of interest). */ switch(buf[0]) { case '#': /* comment */ case 'V': /* version */ break; default: /* * We need to track pathnames per-process. * * Each process run by make, starts off in the 'CWD' * recorded in the .meta file, if it chdirs ('C') * elsewhere we need to track that - but only for * that process. If it forks ('F'), we initialize * the child to have the same cwd as its parent. * * We also need to track the 'latestdir' of * interest. This is usually the same as cwd, but * not if a process is reading directories. * * Each time we spot a different process ('pid') * we save the current value of 'latestdir' in a * variable qualified by 'lastpid', and * re-initialize 'latestdir' to any pre-saved * value for the current 'pid' and 'CWD' if none. */ CHECK_VALID_META(p); pid = atoi(p); if (pid > 0 && pid != lastpid) { char *ldir; char *tp; if (lastpid > 0) { /* We need to remember this. */ Var_Set(ldir_vname, latestdir, VAR_GLOBAL, 0); } snprintf(ldir_vname, sizeof(ldir_vname), LDIR_VNAME_FMT, pid); lastpid = pid; ldir = Var_Value(ldir_vname, VAR_GLOBAL, &tp); if (ldir) { strlcpy(latestdir, ldir, sizeof(latestdir)); if (tp) free(tp); } else strlcpy(latestdir, cwd, sizeof(latestdir)); } /* Skip past the pid. */ if (strsep(&p, " ") == NULL) continue; #ifdef DEBUG_META_MODE if (DEBUG(META)) fprintf(debug_file, "%s: %d: cwd=%s ldir=%s\n", fname, lineno, cwd, latestdir); #endif break; } CHECK_VALID_META(p); /* Process according to record type. */ switch (buf[0]) { case 'X': /* eXit */ Var_Delete(ldir_vname, VAR_GLOBAL); lastpid = 0; /* no need to save ldir_vname */ break; case 'F': /* [v]Fork */ { char cldir[64]; int child; child = atoi(p); if (child > 0) { snprintf(cldir, sizeof(cldir), LDIR_VNAME_FMT, child); Var_Set(cldir, latestdir, VAR_GLOBAL, 0); } } break; case 'C': /* Chdir */ /* Update the latest directory. */ strlcpy(latestdir, p, sizeof(latestdir)); break; case 'M': /* renaMe */ if (Lst_IsEmpty(missingFiles)) break; /* 'L' and 'M' put single quotes around the args */ if (*p == '\'') { char *ep; p++; if ((ep = strchr(p, '\''))) *ep = '\0'; } /* FALLTHROUGH */ case 'D': /* unlink */ if (*p == '/' && !Lst_IsEmpty(missingFiles)) { /* remove p from the missingFiles list if present */ if ((ln = Lst_Find(missingFiles, p, string_match)) != NULL) { char *tp = Lst_Datum(ln); Lst_Remove(missingFiles, ln); free(tp); } } break; case 'L': /* Link */ /* we want the target */ if (strsep(&p, " ") == NULL) continue; CHECK_VALID_META(p); /* 'L' and 'M' put single quotes around the args */ if (*p == '\'') { char *ep; p++; if ((ep = strchr(p, '\''))) *ep = '\0'; } /* FALLTHROUGH */ case 'W': /* Write */ /* * If a file we generated within our bailiwick * but outside of .OBJDIR is missing, * we need to do it again. */ /* ignore non-absolute paths */ if (*p != '/') break; if (Lst_IsEmpty(metaBailiwick)) break; /* ignore cwd - normal dependencies handle those */ if (strncmp(p, cwd, cwdlen) == 0) break; if (!Lst_ForEach(metaBailiwick, prefix_match, p)) break; /* tmpdir might be within */ if (tmplen > 0 && strncmp(p, tmpdir, tmplen) == 0) break; /* ignore anything containing the string "tmp" */ if ((strstr("tmp", p))) break; if (stat(p, &fs) < 0) { Lst_AtEnd(missingFiles, bmake_strdup(p)); } break; case 'R': /* Read */ case 'E': /* Exec */ /* * Check for runtime files that can't * be part of the dependencies because * they are _expected_ to change. */ if (strncmp(p, "/tmp/", 5) == 0 || (tmplen > 0 && strncmp(p, tmpdir, tmplen) == 0)) break; if (strncmp(p, "/var/", 5) == 0) break; /* Ignore device files. */ if (strncmp(p, "/dev/", 5) == 0) break; /* Ignore /etc/ files. */ if (strncmp(p, "/etc/", 5) == 0) break; if ((cp = strrchr(p, '/'))) { cp++; /* * We don't normally expect to see this, * but we do expect it to change. */ if (strcmp(cp, makeDependfile) == 0) break; } /* * The rest of the record is the file name. * Check if it's not an absolute path. */ { char *sdirs[4]; char **sdp; int sdx = 0; int found = 0; if (*p == '/') { sdirs[sdx++] = p; /* done */ } else { if (strcmp(".", p) == 0) continue; /* no point */ /* Check vs latestdir */ snprintf(fname1, sizeof(fname1), "%s/%s", latestdir, p); sdirs[sdx++] = fname1; if (strcmp(latestdir, cwd) != 0) { /* Check vs cwd */ snprintf(fname2, sizeof(fname2), "%s/%s", cwd, p); sdirs[sdx++] = fname2; } } sdirs[sdx++] = NULL; for (sdp = sdirs; *sdp && !found; sdp++) { #ifdef DEBUG_META_MODE if (DEBUG(META)) fprintf(debug_file, "%s: %d: looking for: %s\n", fname, lineno, *sdp); #endif if (stat(*sdp, &fs) == 0) { found = 1; p = *sdp; } } if (found) { #ifdef DEBUG_META_MODE if (DEBUG(META)) fprintf(debug_file, "%s: %d: found: %s\n", fname, lineno, p); #endif if (!S_ISDIR(fs.st_mode) && fs.st_mtime > gn->mtime) { if (DEBUG(META)) fprintf(debug_file, "%s: %d: file '%s' is newer than the target...\n", fname, lineno, p); oodate = TRUE; } else if (S_ISDIR(fs.st_mode)) { /* Update the latest directory. */ realpath(p, latestdir); } } else if (errno == ENOENT && *p == '/' && strncmp(p, cwd, cwdlen) != 0) { /* * A referenced file outside of CWD is missing. * We cannot catch every eventuality here... */ if (DEBUG(META)) fprintf(debug_file, "%s: %d: file '%s' may have moved?...\n", fname, lineno, p); oodate = TRUE; } } break; default: break; } } else if (strcmp(buf, "CMD") == 0) { /* * Compare the current command with the one in the * meta data file. */ if (ln == NULL) { if (DEBUG(META)) fprintf(debug_file, "%s: %d: there were more build commands in the meta data file than there are now...\n", fname, lineno); oodate = TRUE; } else { char *cmd = (char *)Lst_Datum(ln); if (!ignoreOODATE) { if (strstr(cmd, "$?")) ignoreOODATE = TRUE; else if ((cp = strstr(cmd, ".OODATE"))) { /* check for $[{(].OODATE[)}] */ if (cp > cmd + 2 && cp[-2] == '$') ignoreOODATE = TRUE; } if (ignoreOODATE && DEBUG(META)) fprintf(debug_file, "%s: %d: cannot compare commands using .OODATE\n", fname, lineno); } cmd = Var_Subst(NULL, cmd, gn, TRUE); if ((cp = strchr(cmd, '\n'))) { int n; /* * This command contains newlines, we need to * fetch more from the .meta file before we * attempt a comparison. */ /* first put the newline back at buf[x - 1] */ buf[x - 1] = '\n'; do { /* now fetch the next line */ if ((n = fgetLine(&buf, &bufsz, x, fp)) <= 0) break; x = n; lineno++; if (buf[x - 1] != '\n') { warnx("%s: %d: line truncated at %u", fname, lineno, x); break; } cp = strchr(++cp, '\n'); } while (cp); if (buf[x - 1] == '\n') buf[x - 1] = '\0'; } if (!ignoreOODATE && !(gn->type & OP_NOMETA_CMP) && strcmp(p, cmd) != 0) { if (DEBUG(META)) fprintf(debug_file, "%s: %d: a build command has changed\n%s\nvs\n%s\n", fname, lineno, p, cmd); if (!metaIgnoreCMDs) oodate = TRUE; } free(cmd); ln = Lst_Succ(ln); } } else if (strcmp(buf, "CWD") == 0) { /* * Check if there are extra commands now * that weren't in the meta data file. */ if (!oodate && ln != NULL) { if (DEBUG(META)) fprintf(debug_file, "%s: %d: there are extra build commands now that weren't in the meta data file\n", fname, lineno); oodate = TRUE; } if (strcmp(p, cwd) != 0) { if (DEBUG(META)) fprintf(debug_file, "%s: %d: the current working directory has changed from '%s' to '%s'\n", fname, lineno, p, curdir); oodate = TRUE; } } } fclose(fp); if (!Lst_IsEmpty(missingFiles)) { if (DEBUG(META)) fprintf(debug_file, "%s: missing files: %s...\n", fname, (char *)Lst_Datum(Lst_First(missingFiles))); oodate = TRUE; Lst_Destroy(missingFiles, (FreeProc *)free); } } else { if ((gn->type & OP_META)) { if (DEBUG(META)) fprintf(debug_file, "%s: required but missing\n", fname); oodate = TRUE; } } if (oodate && ignoreOODATE) { /* * Target uses .OODATE, so we need to re-compute it. * We need to clean up what Make_DoAllVar() did. */ Var_Delete(ALLSRC, gn); Var_Delete(OODATE, gn); gn->flags &= ~DONE_ALLSRC; } return oodate; }
Boolean meta_oodate(GNode *gn, Boolean oodate) { static char *tmpdir = NULL; static char cwd[MAXPATHLEN]; char lcwd_vname[64]; char ldir_vname[64]; char lcwd[MAXPATHLEN]; char latestdir[MAXPATHLEN]; char fname[MAXPATHLEN]; char fname1[MAXPATHLEN]; char fname2[MAXPATHLEN]; char fname3[MAXPATHLEN]; const char *dname; const char *tname; char *p; char *cp; char *link_src; char *move_target; static size_t cwdlen = 0; static size_t tmplen = 0; FILE *fp; Boolean needOODATE = FALSE; Lst missingFiles; char *pa[4]; /* >= possible uses */ int i; int have_filemon = FALSE; if (oodate) return oodate; /* we're done */ i = 0; dname = Var_Value(".OBJDIR", gn, &pa[i++]); tname = Var_Value(TARGET, gn, &pa[i++]); /* if this succeeds fname3 is realpath of dname */ if (!meta_needed(gn, dname, tname, fname3, FALSE)) goto oodate_out; dname = fname3; missingFiles = Lst_Init(FALSE); /* * We need to check if the target is out-of-date. This includes * checking if the expanded command has changed. This in turn * requires that all variables are set in the same way that they * would be if the target needs to be re-built. */ Make_DoAllVar(gn); meta_name(gn, fname, sizeof(fname), dname, tname, dname); #ifdef DEBUG_META_MODE if (DEBUG(META)) fprintf(debug_file, "meta_oodate: %s\n", fname); #endif if ((fp = fopen(fname, "r")) != NULL) { static char *buf = NULL; static size_t bufsz; int lineno = 0; int lastpid = 0; int pid; int x; LstNode ln; struct stat fs; if (!buf) { bufsz = 8 * BUFSIZ; buf = bmake_malloc(bufsz); } if (!cwdlen) { if (getcwd(cwd, sizeof(cwd)) == NULL) err(1, "Could not get current working directory"); cwdlen = strlen(cwd); } strlcpy(lcwd, cwd, sizeof(lcwd)); strlcpy(latestdir, cwd, sizeof(latestdir)); if (!tmpdir) { tmpdir = getTmpdir(); tmplen = strlen(tmpdir); } /* we want to track all the .meta we read */ Var_Append(".MAKE.META.FILES", fname, VAR_GLOBAL); ln = Lst_First(gn->commands); while (!oodate && (x = fgetLine(&buf, &bufsz, 0, fp)) > 0) { lineno++; if (buf[x - 1] == '\n') buf[x - 1] = '\0'; else { warnx("%s: %d: line truncated at %u", fname, lineno, x); oodate = TRUE; break; } link_src = NULL; move_target = NULL; /* Find the start of the build monitor section. */ if (!have_filemon) { if (strncmp(buf, "-- filemon", 10) == 0) { have_filemon = TRUE; continue; } if (strncmp(buf, "# buildmon", 10) == 0) { have_filemon = TRUE; continue; } } /* Delimit the record type. */ p = buf; #ifdef DEBUG_META_MODE if (DEBUG(META)) fprintf(debug_file, "%s: %d: %s\n", fname, lineno, buf); #endif strsep(&p, " "); if (have_filemon) { /* * We are in the 'filemon' output section. * Each record from filemon follows the general form: * * <key> <pid> <data> * * Where: * <key> is a single letter, denoting the syscall. * <pid> is the process that made the syscall. * <data> is the arguments (of interest). */ switch(buf[0]) { case '#': /* comment */ case 'V': /* version */ break; default: /* * We need to track pathnames per-process. * * Each process run by make, starts off in the 'CWD' * recorded in the .meta file, if it chdirs ('C') * elsewhere we need to track that - but only for * that process. If it forks ('F'), we initialize * the child to have the same cwd as its parent. * * We also need to track the 'latestdir' of * interest. This is usually the same as cwd, but * not if a process is reading directories. * * Each time we spot a different process ('pid') * we save the current value of 'latestdir' in a * variable qualified by 'lastpid', and * re-initialize 'latestdir' to any pre-saved * value for the current 'pid' and 'CWD' if none. */ CHECK_VALID_META(p); pid = atoi(p); if (pid > 0 && pid != lastpid) { char *ldir; char *tp; if (lastpid > 0) { /* We need to remember these. */ Var_Set(lcwd_vname, lcwd, VAR_GLOBAL, 0); Var_Set(ldir_vname, latestdir, VAR_GLOBAL, 0); } snprintf(lcwd_vname, sizeof(lcwd_vname), LCWD_VNAME_FMT, pid); snprintf(ldir_vname, sizeof(ldir_vname), LDIR_VNAME_FMT, pid); lastpid = pid; ldir = Var_Value(ldir_vname, VAR_GLOBAL, &tp); if (ldir) { strlcpy(latestdir, ldir, sizeof(latestdir)); free(tp); } ldir = Var_Value(lcwd_vname, VAR_GLOBAL, &tp); if (ldir) { strlcpy(lcwd, ldir, sizeof(lcwd)); free(tp); } } /* Skip past the pid. */ if (strsep(&p, " ") == NULL) continue; #ifdef DEBUG_META_MODE if (DEBUG(META)) fprintf(debug_file, "%s: %d: %d: %c: cwd=%s lcwd=%s ldir=%s\n", fname, lineno, pid, buf[0], cwd, lcwd, latestdir); #endif break; } CHECK_VALID_META(p); /* Process according to record type. */ switch (buf[0]) { case 'X': /* eXit */ Var_Delete(lcwd_vname, VAR_GLOBAL); Var_Delete(ldir_vname, VAR_GLOBAL); lastpid = 0; /* no need to save ldir_vname */ break; case 'F': /* [v]Fork */ { char cldir[64]; int child; child = atoi(p); if (child > 0) { snprintf(cldir, sizeof(cldir), LCWD_VNAME_FMT, child); Var_Set(cldir, lcwd, VAR_GLOBAL, 0); snprintf(cldir, sizeof(cldir), LDIR_VNAME_FMT, child); Var_Set(cldir, latestdir, VAR_GLOBAL, 0); #ifdef DEBUG_META_MODE if (DEBUG(META)) fprintf(debug_file, "%s: %d: %d: cwd=%s lcwd=%s ldir=%s\n", fname, lineno, child, cwd, lcwd, latestdir); #endif } } break; case 'C': /* Chdir */ /* Update lcwd and latest directory. */ strlcpy(latestdir, p, sizeof(latestdir)); strlcpy(lcwd, p, sizeof(lcwd)); Var_Set(lcwd_vname, lcwd, VAR_GLOBAL, 0); Var_Set(ldir_vname, lcwd, VAR_GLOBAL, 0); #ifdef DEBUG_META_MODE if (DEBUG(META)) fprintf(debug_file, "%s: %d: cwd=%s ldir=%s\n", fname, lineno, cwd, lcwd); #endif break; case 'M': /* renaMe */ /* * For 'M'oves we want to check * the src as for 'R'ead * and the target as for 'W'rite. */ cp = p; /* save this for a second */ /* now get target */ if (strsep(&p, " ") == NULL) continue; CHECK_VALID_META(p); move_target = p; p = cp; /* 'L' and 'M' put single quotes around the args */ DEQUOTE(p); DEQUOTE(move_target); /* FALLTHROUGH */ case 'D': /* unlink */ if (*p == '/' && !Lst_IsEmpty(missingFiles)) { /* remove any missingFiles entries that match p */ if ((ln = Lst_Find(missingFiles, p, path_match)) != NULL) { LstNode nln; char *tp; do { nln = Lst_FindFrom(missingFiles, Lst_Succ(ln), p, path_match); tp = Lst_Datum(ln); Lst_Remove(missingFiles, ln); free(tp); } while ((ln = nln) != NULL); } } if (buf[0] == 'M') { /* the target of the mv is a file 'W'ritten */ #ifdef DEBUG_META_MODE if (DEBUG(META)) fprintf(debug_file, "meta_oodate: M %s -> %s\n", p, move_target); #endif p = move_target; goto check_write; } break; case 'L': /* Link */ /* * For 'L'inks check * the src as for 'R'ead * and the target as for 'W'rite. */ link_src = p; /* now get target */ if (strsep(&p, " ") == NULL) continue; CHECK_VALID_META(p); /* 'L' and 'M' put single quotes around the args */ DEQUOTE(p); DEQUOTE(link_src); #ifdef DEBUG_META_MODE if (DEBUG(META)) fprintf(debug_file, "meta_oodate: L %s -> %s\n", link_src, p); #endif /* FALLTHROUGH */ case 'W': /* Write */ check_write: /* * If a file we generated within our bailiwick * but outside of .OBJDIR is missing, * we need to do it again. */ /* ignore non-absolute paths */ if (*p != '/') break; if (Lst_IsEmpty(metaBailiwick)) break; /* ignore cwd - normal dependencies handle those */ if (strncmp(p, cwd, cwdlen) == 0) break; if (!Lst_ForEach(metaBailiwick, prefix_match, p)) break; /* tmpdir might be within */ if (tmplen > 0 && strncmp(p, tmpdir, tmplen) == 0) break; /* ignore anything containing the string "tmp" */ if ((strstr("tmp", p))) break; if ((link_src != NULL && cached_lstat(p, &fs) < 0) || (link_src == NULL && cached_stat(p, &fs) < 0)) { if (!meta_ignore(gn, p)) { if (Lst_Find(missingFiles, p, string_match) == NULL) Lst_AtEnd(missingFiles, bmake_strdup(p)); } } break; check_link_src: p = link_src; link_src = NULL; #ifdef DEBUG_META_MODE if (DEBUG(META)) fprintf(debug_file, "meta_oodate: L src %s\n", p); #endif /* FALLTHROUGH */ case 'R': /* Read */ case 'E': /* Exec */ /* * Check for runtime files that can't * be part of the dependencies because * they are _expected_ to change. */ if (meta_ignore(gn, p)) break; /* * The rest of the record is the file name. * Check if it's not an absolute path. */ { char *sdirs[4]; char **sdp; int sdx = 0; int found = 0; if (*p == '/') { sdirs[sdx++] = p; /* done */ } else { if (strcmp(".", p) == 0) continue; /* no point */ /* Check vs latestdir */ snprintf(fname1, sizeof(fname1), "%s/%s", latestdir, p); sdirs[sdx++] = fname1; if (strcmp(latestdir, lcwd) != 0) { /* Check vs lcwd */ snprintf(fname2, sizeof(fname2), "%s/%s", lcwd, p); sdirs[sdx++] = fname2; } if (strcmp(lcwd, cwd) != 0) { /* Check vs cwd */ snprintf(fname3, sizeof(fname3), "%s/%s", cwd, p); sdirs[sdx++] = fname3; } } sdirs[sdx++] = NULL; for (sdp = sdirs; *sdp && !found; sdp++) { #ifdef DEBUG_META_MODE if (DEBUG(META)) fprintf(debug_file, "%s: %d: looking for: %s\n", fname, lineno, *sdp); #endif if (cached_stat(*sdp, &fs) == 0) { found = 1; p = *sdp; } } if (found) { #ifdef DEBUG_META_MODE if (DEBUG(META)) fprintf(debug_file, "%s: %d: found: %s\n", fname, lineno, p); #endif if (!S_ISDIR(fs.st_mode) && fs.st_mtime > gn->mtime) { if (DEBUG(META)) fprintf(debug_file, "%s: %d: file '%s' is newer than the target...\n", fname, lineno, p); oodate = TRUE; } else if (S_ISDIR(fs.st_mode)) { /* Update the latest directory. */ cached_realpath(p, latestdir); } } else if (errno == ENOENT && *p == '/' && strncmp(p, cwd, cwdlen) != 0) { /* * A referenced file outside of CWD is missing. * We cannot catch every eventuality here... */ if (Lst_Find(missingFiles, p, string_match) == NULL) Lst_AtEnd(missingFiles, bmake_strdup(p)); } } if (buf[0] == 'E') { /* previous latestdir is no longer relevant */ strlcpy(latestdir, lcwd, sizeof(latestdir)); } break; default: break; } if (!oodate && buf[0] == 'L' && link_src != NULL) goto check_link_src; } else if (strcmp(buf, "CMD") == 0) { /* * Compare the current command with the one in the * meta data file. */ if (ln == NULL) { if (DEBUG(META)) fprintf(debug_file, "%s: %d: there were more build commands in the meta data file than there are now...\n", fname, lineno); oodate = TRUE; } else { char *cmd = (char *)Lst_Datum(ln); Boolean hasOODATE = FALSE; if (strstr(cmd, "$?")) hasOODATE = TRUE; else if ((cp = strstr(cmd, ".OODATE"))) { /* check for $[{(].OODATE[:)}] */ if (cp > cmd + 2 && cp[-2] == '$') hasOODATE = TRUE; } if (hasOODATE) { needOODATE = TRUE; if (DEBUG(META)) fprintf(debug_file, "%s: %d: cannot compare command using .OODATE\n", fname, lineno); } cmd = Var_Subst(NULL, cmd, gn, VARF_WANTRES|VARF_UNDEFERR); if ((cp = strchr(cmd, '\n'))) { int n; /* * This command contains newlines, we need to * fetch more from the .meta file before we * attempt a comparison. */ /* first put the newline back at buf[x - 1] */ buf[x - 1] = '\n'; do { /* now fetch the next line */ if ((n = fgetLine(&buf, &bufsz, x, fp)) <= 0) break; x = n; lineno++; if (buf[x - 1] != '\n') { warnx("%s: %d: line truncated at %u", fname, lineno, x); break; } cp = strchr(++cp, '\n'); } while (cp); if (buf[x - 1] == '\n') buf[x - 1] = '\0'; } if (!hasOODATE && !(gn->type & OP_NOMETA_CMP) && strcmp(p, cmd) != 0) { if (DEBUG(META)) fprintf(debug_file, "%s: %d: a build command has changed\n%s\nvs\n%s\n", fname, lineno, p, cmd); if (!metaIgnoreCMDs) oodate = TRUE; } free(cmd); ln = Lst_Succ(ln); } } else if (strcmp(buf, "CWD") == 0) { /* * Check if there are extra commands now * that weren't in the meta data file. */ if (!oodate && ln != NULL) { if (DEBUG(META)) fprintf(debug_file, "%s: %d: there are extra build commands now that weren't in the meta data file\n", fname, lineno); oodate = TRUE; } if (strcmp(p, cwd) != 0) { if (DEBUG(META)) fprintf(debug_file, "%s: %d: the current working directory has changed from '%s' to '%s'\n", fname, lineno, p, curdir); oodate = TRUE; } } } fclose(fp); if (!Lst_IsEmpty(missingFiles)) { if (DEBUG(META)) fprintf(debug_file, "%s: missing files: %s...\n", fname, (char *)Lst_Datum(Lst_First(missingFiles))); oodate = TRUE; } if (!oodate && !have_filemon && filemonMissing) { if (DEBUG(META)) fprintf(debug_file, "%s: missing filemon data\n", fname); oodate = TRUE; } } else { if (writeMeta && metaMissing) { cp = NULL; /* if target is in .CURDIR we do not need a meta file */ if (gn->path && (cp = strrchr(gn->path, '/')) && cp > gn->path) { if (strncmp(curdir, gn->path, (cp - gn->path)) != 0) { cp = NULL; /* not in .CURDIR */ } } if (!cp) { if (DEBUG(META)) fprintf(debug_file, "%s: required but missing\n", fname); oodate = TRUE; needOODATE = TRUE; /* assume the worst */ } } } Lst_Destroy(missingFiles, (FreeProc *)free); if (oodate && needOODATE) { /* * Target uses .OODATE which is empty; or we wouldn't be here. * We have decided it is oodate, so .OODATE needs to be set. * All we can sanely do is set it to .ALLSRC. */ Var_Delete(OODATE, gn); Var_Set(OODATE, Var_Value(ALLSRC, gn, &cp), gn, 0); free(cp); } oodate_out: for (i--; i >= 0; i--) { free(pa[i]); } return oodate; }
/*- * main -- * The main function, for obvious reasons. Initializes variables * and a few modules, then parses the arguments give it in the * environment and on the command line. Reads the system makefile * followed by either Makefile, makefile or the file given by the * -f argument. Sets the MAKEFLAGS PMake variable based on all the * flags it has received by then uses either the Make or the Compat * module to create the initial list of targets. * * Results: * If -q was given, exits -1 if anything was out-of-date. Else it exits * 0. * * Side Effects: * The program exits when done. Targets are created. etc. etc. etc. */ int main(int argc, char **argv) { Lst targs; /* target nodes to create -- passed to Make_Init */ Boolean outOfDate; /* FALSE if all targets up to date */ Boolean hadErrors; /* TRUE if we had errors */ const char *p; create = Lst_Init(); makefiles = Lst_Init(); beSilent = FALSE; /* Print commands as executed */ ignoreErrors = FALSE; /* Pay attention to non-zero returns */ noExecute = FALSE; /* Execute all commands */ keepgoing = FALSE; /* Stop on error */ allPrecious = FALSE; /* Remove targets when interrupted */ queryFlag = FALSE; /* This is not just a check-run */ noBuiltins = FALSE; /* Read the built-in rules */ touchFlag = FALSE; /* Actually update targets */ debug = 0; /* No debug verbosity, please. */ jobsRunning = FALSE; noisy = FALSE; maxJobs = 0; /* Set default max concurrency */ maxLocal = -1; /* Set default local max concurrency */ /* * create internal commonly used strings */ string_init(); sNULL = string_create(""); sDOT = string_create("."); sTARGET = string_create("@"); sOODATE = string_create("?"); sALLSRC = string_create(">"); sIMPSRC = string_create("<"); sPREFIX = string_create("*"); sARCHIVE = string_create("!"); sMEMBER = string_create("%"); s_TARGET = string_create(".TARGET"); s_OODATE = string_create(".OODATE"); s_ALLSRC = string_create(".ALLSRC"); s_IMPSRC = string_create(".IMPSRC"); s_PREFIX = string_create(".PREFIX"); s_ARCHIVE = string_create(".ARCHIVE"); s_MEMBER = string_create(".MEMBER"); s_TARGETS = string_create(".TARGETS"); s_INCLUDES = string_create(".INCLUDES"); s_LIBS = string_create(".LIBS"); sMAKE = string_create("MAKE"); sMAKEFLAGS = string_create("MAKEFLAGS"); sMFLAGS = string_create("MFLAGS"); sMACHINE = string_create("MACHINE"); sNPROC = string_create("NPROC"); sVPATH = string_create("VPATH"); sMAKEFILE = string_create("MAKEFILE"); sLIBSUFF = string_create(LIBSUFF); sMAKEOBJDIR = string_create("MAKEOBJDIR"); sMAKESRCDIRPATH = string_create("MAKESRCDIRPATH"); sMAKEDIR = string_create("MAKEDIR"); sMAKETOP = string_create("MAKETOP"); sMAKESUB = string_create("MAKESUB"); s_DEFAULT = string_create(".DEFAULT"); s_INTERRUPT = string_create(".INTERRUPT"); s_BEGIN = string_create(".BEGIN"); s_END = string_create(".END"); s_ERROR = string_create(".ERROR"); s_EXIT = string_create(".EXIT"); /* * Initialize the parsing, directory and variable modules to prepare * for the reading of inclusion paths and variable settings on the * command line */ Dir_Init(); /* Initialize directory structures so -I flags * can be processed correctly */ Parse_Init(); /* Need to initialize the paths of #include * directories */ Var_Init(); /* As well as the lists of variables for * parsing arguments */ /* * Initialize various variables. * MAKE also gets this name, for compatibility * MAKEFLAGS gets set to the empty string just in case. * MFLAGS also gets initialized empty, for compatibility. */ Var_Set(sMAKE, string_create(argv[0]), VAR_GLOBAL); Var_Set(sMAKEFLAGS, sNULL, VAR_GLOBAL); Var_Set(sMFLAGS, sNULL, VAR_GLOBAL); Var_Set(sMACHINE, string_create(MACHINE), VAR_GLOBAL); Var_Set(sNPROC, string_create((p = getenv("NPROC")) ? p : "1"), VAR_GLOBAL); /* * First snag any flags out of the MAKE environment variable. * (Note this is *not* MAKEFLAGS since /bin/make uses that and it's * in a different format). */ Main_ParseArgLine(getenv("MAKEFLAGS")); MainParseArgs(argc, argv); Cond_Setup(); /* * Now make sure that maxJobs and maxLocal are positive. If * not, use the value of NPROC (or 1 if NPROC is not positive) */ { int nproc = atoi(Var_Value(sNPROC, VAR_GLOBAL)); if (nproc < 1) nproc = 1; if (maxJobs < 1) maxJobs = nproc; } /* * Initialize archive, target and suffix modules in preparation for * parsing the makefile(s) */ Arch_Init(); Targ_Init(); Suff_Init(); DEFAULT = NILGNODE; (void)time(&now); /* * Set up the .TARGETS variable to contain the list of targets to be * created. If none specified, make the variable empty -- the parser * will fill the thing in with the default or .MAIN target. */ if (!Lst_IsEmpty(create)) { LstNode ln; for (ln = Lst_First(create); ln != NILLNODE; ln = Lst_Succ(ln)) { string_t name = (string_t)Lst_Datum(ln); Var_Append(s_TARGETS, name, VAR_GLOBAL); } } else Var_Set(s_TARGETS, sNULL, VAR_GLOBAL); /* * Locate the correct working directory for make. Adjust for any * movement taken from the directory of our parent if we are a * sub-make. */ MakeSetWorkingDir("Makeconf"); /* * Read in the built-in rules first, followed by the specified * makefile, if it was (makefile != (char *) NULL), or the default * Makefile and makefile, in that order, if it wasn't. */ if (!noBuiltins && !ReadMakefile(_PATH_DEFSYSMK, TRUE)) Fatal("make: no system rules (%s).", _PATH_DEFSYSMK); if (!Lst_IsEmpty(makefiles)) { LstNode ln; ln = Lst_Find(makefiles, (ClientData)FALSE, ReadMakefileP); if (ln != NILLNODE) Fatal("make: cannot open %s.", (char *)Lst_Datum(ln)); } else if (!ReadMakefile("makefile", FALSE)) (void)ReadMakefile("Makefile", FALSE); Var_Append(sMFLAGS, Var_StrValue(sMAKEFLAGS, VAR_GLOBAL), VAR_GLOBAL); /* Install all the flags into the MAKEFLAGS envariable. */ if ((p = Var_Value(sMAKEFLAGS, VAR_GLOBAL)) && *p) setenv("MAKEFLAGS", p, 1); /* * For compatibility, look at the directories in the VPATH variable * and add them to the search path, if the variable is defined. The * variable's value is in the same format as the PATH envariable, i.e. * <directory>:<directory>:<directory>... */ if (Var_Exists(sVPATH, VAR_CMD)) { char *vpath, *path, *cp, savec; /* * GCC stores string constants in read-only memory, but * Var_Subst will want to write this thing, so store it * in an array */ static char VPATH[] = "${VPATH}"; vpath = Var_Subst(VPATH, VAR_CMD, FALSE); path = vpath; do { /* skip to end of directory */ for (cp = path; *cp != ':' && *cp != '\0'; cp++); /* Save terminator character so know when to stop */ savec = *cp; *cp = '\0'; /* Add directory to search path */ Dir_AddDir(dirSearchPath, string_create(path)); *cp = savec; path = cp + 1; } while (savec == ':'); (void)free((Address)vpath); } /* * Now that all search paths have been read for suffixes et al, it's * time to add the default search path to their lists... */ Suff_DoPaths(); /* print the initial graph, if the user requested it */ if (DEBUG(GRAPH1)) Targ_PrintGraph(1); /* * Have now read the entire graph and need to make a list of targets * to create. If none was given on the command line, we consult the * parsing module to find the main target(s) to create. */ if (Lst_IsEmpty(create)) targs = Parse_MainName(); else targs = Targ_FindList(create, TARG_CREATE); /* * Initialize job module before traversing the graph, now that * any .BEGIN and .END targets have been read. This is done * only if the -q flag wasn't given (to prevent the .BEGIN from * being executed should it exist). */ if (!queryFlag) { if (maxLocal < 0 || maxLocal > maxJobs) maxLocal = maxJobs; Job_Init(maxJobs, maxLocal); jobsRunning = TRUE; } /* Traverse the graph, checking on all the targets */ outOfDate = Make_Run(targs, &hadErrors); /* print the graph now it's been processed if the user requested it */ if (DEBUG(GRAPH2)) Targ_PrintGraph(2); return (hadErrors || (queryFlag && outOfDate)); }
/*- * main -- * The main function, for obvious reasons. Initializes variables * and a few modules, then parses the arguments give it in the * environment and on the command line. Reads the system makefile * followed by either Makefile, makefile or the file given by the * -f argument. Sets the .MAKEFLAGS PMake variable based on all the * flags it has received by then uses either the Make or the Compat * module to create the initial list of targets. * * Results: * If -q was given, exits -1 if anything was out-of-date. Else it exits * 0. * * Side Effects: * The program exits when done. Targets are created. etc. etc. etc. */ int main(int argc, char **argv) { Lst targs; /* target nodes to create -- passed to Make_Init */ Boolean outOfDate = TRUE; /* FALSE if all targets up to date */ struct stat sb, sa; char *p1, *path, *pwd; char mdpath[MAXPATHLEN]; #ifdef FORCE_MACHINE char *machine = FORCE_MACHINE; #else char *machine = getenv("MACHINE"); #endif const char *machine_arch = getenv("MACHINE_ARCH"); char *syspath = getenv("MAKESYSPATH"); Lst sysMkPath; /* Path of sys.mk */ char *cp = NULL, *start; /* avoid faults on read-only strings */ static char defsyspath[] = DEFAULT_SYS_PATH; char found_path[MAXPATHLEN + 1]; /* for searching for sys.mk */ struct timeval rightnow; /* to initialize random seed */ /* * Set the seed to produce a different random sequences * on each program execution. */ gettimeofday(&rightnow, NULL); srandom(rightnow.tv_sec + rightnow.tv_usec); if ((progname = strrchr(argv[0], '/')) != NULL) progname++; else progname = argv[0]; #ifdef RLIMIT_NOFILE /* * get rid of resource limit on file descriptors */ { struct rlimit rl; if (getrlimit(RLIMIT_NOFILE, &rl) != -1 && rl.rlim_cur != rl.rlim_max) { rl.rlim_cur = rl.rlim_max; (void)setrlimit(RLIMIT_NOFILE, &rl); } } #endif /* * Find where we are and take care of PWD for the automounter... * All this code is so that we know where we are when we start up * on a different machine with pmake. */ if (getcwd(curdir, MAXPATHLEN) == NULL) { (void)fprintf(stderr, "%s: %s.\n", progname, strerror(errno)); exit(2); } if (stat(curdir, &sa) == -1) { (void)fprintf(stderr, "%s: %s: %s.\n", progname, curdir, strerror(errno)); exit(2); } /* * Overriding getcwd() with $PWD totally breaks MAKEOBJDIRPREFIX * since the value of curdir can very depending on how we got * here. Ie sitting at a shell prompt (shell that provides $PWD) * or via subdir.mk in which case its likely a shell which does * not provide it. * So, to stop it breaking this case only, we ignore PWD if * MAKEOBJDIRPREFIX is set or MAKEOBJDIR contains a transform. */ #ifndef NO_PWD_OVERRIDE if ((pwd = getenv("PWD")) != NULL && getenv("MAKEOBJDIRPREFIX") == NULL) { const char *makeobjdir = getenv("MAKEOBJDIR"); if (makeobjdir == NULL || !strchr(makeobjdir, '$')) { if (stat(pwd, &sb) == 0 && sa.st_ino == sb.st_ino && sa.st_dev == sb.st_dev) (void)strncpy(curdir, pwd, MAXPATHLEN); } } #endif /* * Get the name of this type of MACHINE from utsname * so we can share an executable for similar machines. * (i.e. m68k: amiga hp300, mac68k, sun3, ...) * * Note that both MACHINE and MACHINE_ARCH are decided at * run-time. */ if (!machine) { #ifdef MAKE_NATIVE struct utsname utsname; if (uname(&utsname) == -1) { (void)fprintf(stderr, "%s: uname failed (%s).\n", progname, strerror(errno)); exit(2); } machine = utsname.machine; #else #ifdef MAKE_MACHINE machine = MAKE_MACHINE; #else machine = "unknown"; #endif #endif } if (!machine_arch) { #ifndef MACHINE_ARCH #ifdef MAKE_MACHINE_ARCH machine_arch = MAKE_MACHINE_ARCH; #else machine_arch = "unknown"; #endif #else machine_arch = MACHINE_ARCH; #endif } /* * Just in case MAKEOBJDIR wants us to do something tricky. */ Var_Init(); /* Initialize the lists of variables for * parsing arguments */ Var_Set(".CURDIR", curdir, VAR_GLOBAL, 0); Var_Set("MACHINE", machine, VAR_GLOBAL, 0); Var_Set("MACHINE_ARCH", machine_arch, VAR_GLOBAL, 0); #ifdef MAKE_VERSION Var_Set("MAKE_VERSION", MAKE_VERSION, VAR_GLOBAL, 0); #endif Var_Set(".newline", "\n", VAR_GLOBAL, 0); /* handy for :@ loops */ /* * Find the .OBJDIR. If MAKEOBJDIRPREFIX, or failing that, * MAKEOBJDIR is set in the environment, try only that value * and fall back to .CURDIR if it does not exist. * * Otherwise, try _PATH_OBJDIR.MACHINE, _PATH_OBJDIR, and * finally _PATH_OBJDIRPREFIX`pwd`, in that order. If none * of these paths exist, just use .CURDIR. */ Dir_Init(curdir); (void)Main_SetObjdir(curdir); if ((path = getenv("MAKEOBJDIRPREFIX")) != NULL) { (void)snprintf(mdpath, MAXPATHLEN, "%s%s", path, curdir); (void)Main_SetObjdir(mdpath); } else if ((path = getenv("MAKEOBJDIR")) != NULL) { (void)Main_SetObjdir(path); } else { (void)snprintf(mdpath, MAXPATHLEN, "%s.%s", _PATH_OBJDIR, machine); if (!Main_SetObjdir(mdpath) && !Main_SetObjdir(_PATH_OBJDIR)) { (void)snprintf(mdpath, MAXPATHLEN, "%s%s", _PATH_OBJDIRPREFIX, curdir); (void)Main_SetObjdir(mdpath); } } create = Lst_Init(FALSE); makefiles = Lst_Init(FALSE); printVars = FALSE; variables = Lst_Init(FALSE); beSilent = FALSE; /* Print commands as executed */ ignoreErrors = FALSE; /* Pay attention to non-zero returns */ noExecute = FALSE; /* Execute all commands */ noRecursiveExecute = FALSE; /* Execute all .MAKE targets */ keepgoing = FALSE; /* Stop on error */ allPrecious = FALSE; /* Remove targets when interrupted */ queryFlag = FALSE; /* This is not just a check-run */ noBuiltins = FALSE; /* Read the built-in rules */ touchFlag = FALSE; /* Actually update targets */ usePipes = TRUE; /* Catch child output in pipes */ debug = 0; /* No debug verbosity, please. */ jobsRunning = FALSE; maxLocal = DEFMAXLOCAL; /* Set default local max concurrency */ #ifdef REMOTE maxJobs = DEFMAXJOBS; /* Set default max concurrency */ #else maxJobs = maxLocal; #endif compatMake = FALSE; /* No compat mode */ /* * Initialize the parsing, directory and variable modules to prepare * for the reading of inclusion paths and variable settings on the * command line */ /* * Initialize various variables. * MAKE also gets this name, for compatibility * .MAKEFLAGS gets set to the empty string just in case. * MFLAGS also gets initialized empty, for compatibility. */ Parse_Init(); Var_Set("MAKE", argv[0], VAR_GLOBAL, 0); Var_Set(".MAKE", argv[0], VAR_GLOBAL, 0); Var_Set(MAKEFLAGS, "", VAR_GLOBAL, 0); Var_Set(MAKEOVERRIDES, "", VAR_GLOBAL, 0); Var_Set("MFLAGS", "", VAR_GLOBAL, 0); Var_Set(".ALLTARGETS", "", VAR_GLOBAL, 0); /* * First snag any flags out of the MAKE environment variable. * (Note this is *not* MAKEFLAGS since /bin/make uses that and it's * in a different format). */ #ifdef POSIX Main_ParseArgLine(getenv("MAKEFLAGS")); #else Main_ParseArgLine(getenv("MAKE")); #endif MainParseArgs(argc, argv); /* * Be compatible if user did not specify -j and did not explicitly * turned compatibility on */ if (!compatMake && !forceJobs) { compatMake = TRUE; } /* * Initialize archive, target and suffix modules in preparation for * parsing the makefile(s) */ Arch_Init(); Targ_Init(); Suff_Init(); Trace_Init(tracefile); DEFAULT = NILGNODE; (void)time(&now); Trace_Log(MAKESTART, NULL); /* * Set up the .TARGETS variable to contain the list of targets to be * created. If none specified, make the variable empty -- the parser * will fill the thing in with the default or .MAIN target. */ if (!Lst_IsEmpty(create)) { LstNode ln; for (ln = Lst_First(create); ln != NILLNODE; ln = Lst_Succ(ln)) { char *name = (char *)Lst_Datum(ln); Var_Append(".TARGETS", name, VAR_GLOBAL); } } else Var_Set(".TARGETS", "", VAR_GLOBAL, 0); /* * If no user-supplied system path was given (through the -m option) * add the directories from the DEFSYSPATH (more than one may be given * as dir1:...:dirn) to the system include path. */ if (syspath == NULL || *syspath == '\0') syspath = defsyspath; else syspath = strdup(syspath); for (start = syspath; *start != '\0'; start = cp) { for (cp = start; *cp != '\0' && *cp != ':'; cp++) continue; if (*cp == ':') { *cp++ = '\0'; } /* look for magic parent directory search string */ if (strncmp(".../", start, 4) != 0) { (void)Dir_AddDir(defIncPath, start); } else { if (Dir_FindHereOrAbove(curdir, start+4, found_path, sizeof(found_path))) { (void)Dir_AddDir(defIncPath, found_path); } } } if (syspath != defsyspath) free(syspath); /* * Read in the built-in rules first, followed by the specified * makefile, if it was (makefile != NULL), or the default * makefile and Makefile, in that order, if it wasn't. */ if (!noBuiltins) { LstNode ln; sysMkPath = Lst_Init(FALSE); Dir_Expand(_PATH_DEFSYSMK, Lst_IsEmpty(sysIncPath) ? defIncPath : sysIncPath, sysMkPath); if (Lst_IsEmpty(sysMkPath)) Fatal("%s: no system rules (%s).", progname, _PATH_DEFSYSMK); ln = Lst_Find(sysMkPath, (ClientData)NULL, ReadMakefile); if (ln != NILLNODE) Fatal("%s: cannot open %s.", progname, (char *)Lst_Datum(ln)); } if (!Lst_IsEmpty(makefiles)) { LstNode ln; ln = Lst_Find(makefiles, (ClientData)NULL, ReadMakefile); if (ln != NILLNODE) Fatal("%s: cannot open %s.", progname, (char *)Lst_Datum(ln)); } else if (!ReadMakefile(UNCONST("makefile"), NULL)) (void)ReadMakefile(UNCONST("Makefile"), NULL); (void)ReadMakefile(UNCONST(".depend"), NULL); Var_Append("MFLAGS", Var_Value(MAKEFLAGS, VAR_GLOBAL, &p1), VAR_GLOBAL); if (p1) free(p1); if (!jobServer && !compatMake) Job_ServerStart(maxJobs); if (DEBUG(JOB)) printf("job_pipe %d %d, maxjobs %d maxlocal %d compat %d\n", job_pipe[0], job_pipe[1], maxJobs, maxLocal, compatMake); Main_ExportMAKEFLAGS(TRUE); /* initial export */ #ifndef NO_CHECK_MAKE_CHDIR Check_Cwd_av(0, NULL, 0); /* initialize it */ #endif /* * For compatibility, look at the directories in the VPATH variable * and add them to the search path, if the variable is defined. The * variable's value is in the same format as the PATH envariable, i.e. * <directory>:<directory>:<directory>... */ if (Var_Exists("VPATH", VAR_CMD)) { char *vpath, savec; /* * GCC stores string constants in read-only memory, but * Var_Subst will want to write this thing, so store it * in an array */ static char VPATH[] = "${VPATH}"; vpath = Var_Subst(NULL, VPATH, VAR_CMD, FALSE); path = vpath; do { /* skip to end of directory */ for (cp = path; *cp != ':' && *cp != '\0'; cp++) continue; /* Save terminator character so know when to stop */ savec = *cp; *cp = '\0'; /* Add directory to search path */ (void)Dir_AddDir(dirSearchPath, path); *cp = savec; path = cp + 1; } while (savec == ':'); free(vpath); } /* * Now that all search paths have been read for suffixes et al, it's * time to add the default search path to their lists... */ Suff_DoPaths(); /* * Propagate attributes through :: dependency lists. */ Targ_Propagate(); /* print the initial graph, if the user requested it */ if (DEBUG(GRAPH1)) Targ_PrintGraph(1); /* print the values of any variables requested by the user */ if (printVars) { LstNode ln; for (ln = Lst_First(variables); ln != NILLNODE; ln = Lst_Succ(ln)) { char *var = (char *)Lst_Datum(ln); char *value; if (strchr(var, '$')) { value = p1 = Var_Subst(NULL, var, VAR_GLOBAL, 0); } else { value = Var_Value(var, VAR_GLOBAL, &p1); } printf("%s\n", value ? value : ""); if (p1) free(p1); } } /* * Have now read the entire graph and need to make a list of targets * to create. If none was given on the command line, we consult the * parsing module to find the main target(s) to create. */ if (Lst_IsEmpty(create)) targs = Parse_MainName(); else targs = Targ_FindList(create, TARG_CREATE); if (!compatMake && !printVars) { /* * Initialize job module before traversing the graph, now that * any .BEGIN and .END targets have been read. This is done * only if the -q flag wasn't given (to prevent the .BEGIN from * being executed should it exist). */ if (!queryFlag) { if (maxLocal == -1) maxLocal = maxJobs; Job_Init(maxJobs, maxLocal); jobsRunning = TRUE; } /* Traverse the graph, checking on all the targets */ outOfDate = Make_Run(targs); } else if (!printVars) { /* * Compat_Init will take care of creating all the targets as * well as initializing the module. */ Compat_Run(targs); } #ifdef CLEANUP Lst_Destroy(targs, NOFREE); Lst_Destroy(variables, NOFREE); Lst_Destroy(makefiles, NOFREE); Lst_Destroy(create, (FreeProc *)free); #endif /* print the graph now it's been processed if the user requested it */ if (DEBUG(GRAPH2)) Targ_PrintGraph(2); Trace_Log(MAKEEND, 0); Suff_End(); Targ_End(); Arch_End(); Var_End(); Parse_End(); Dir_End(); Job_End(); Trace_End(); if (queryFlag && outOfDate) return(1); else return(0); }