Add meta.c which implements "meta" mode for make.

In this mode, a .meta file is created for each target, capturing
the expanded commands used, any command output, and if filemon(9)
is available, a record of system calls which are of interest.
Not enabled unless USE_META=yes is set when building make.
Also, if FILEMON_H exists, meta.c will be compiled to use filemon(9).
This commit is contained in:
sjg 2010-09-13 15:36:57 +00:00
parent 11eacabe5a
commit 4db43f7e0c
11 changed files with 1201 additions and 22 deletions

View File

@ -1,4 +1,4 @@
# $NetBSD: Makefile,v 1.50 2010/04/22 19:15:23 sjg Exp $
# $NetBSD: Makefile,v 1.51 2010/09/13 15:36:57 sjg Exp $
# @(#)Makefile 5.2 (Berkeley) 12/28/90
PROG= make
@ -13,6 +13,17 @@ SRCS+= lstAppend.c lstAtEnd.c lstAtFront.c lstClose.c lstConcat.c \
lstMember.c lstNext.c lstOpen.c lstRemove.c lstReplace.c lstSucc.c
SRCS += lstPrev.c
# let people experiment for a bit
USE_META ?= no
.if ${USE_META:tl} != "no"
SRCS+= meta.c
CPPFLAGS+= -DUSE_META
FILEMON_H ?= ${.CURDIR:H:H}/sys/dev/filemon/filemon.h
.if exists(${FILEMON_H}) && ${FILEMON_H:T} == "filemon.h"
COPTS.meta.c += -DHAVE_FILEMON_H -I${FILEMON_H:H}
.endif
.endif
.PATH: ${.CURDIR}/lst.lib
.if make(install)
SUBDIR= PSD.doc

View File

@ -1,4 +1,4 @@
/* $NetBSD: compat.c,v 1.80 2010/08/07 06:44:08 sjg Exp $ */
/* $NetBSD: compat.c,v 1.81 2010/09/13 15:36:57 sjg Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@ -70,14 +70,14 @@
*/
#ifndef MAKE_NATIVE
static char rcsid[] = "$NetBSD: compat.c,v 1.80 2010/08/07 06:44:08 sjg Exp $";
static char rcsid[] = "$NetBSD: compat.c,v 1.81 2010/09/13 15:36:57 sjg Exp $";
#else
#include <sys/cdefs.h>
#ifndef lint
#if 0
static char sccsid[] = "@(#)compat.c 8.2 (Berkeley) 3/19/94";
#else
__RCSID("$NetBSD: compat.c,v 1.80 2010/08/07 06:44:08 sjg Exp $");
__RCSID("$NetBSD: compat.c,v 1.81 2010/09/13 15:36:57 sjg Exp $");
#endif
#endif /* not lint */
#endif
@ -352,6 +352,12 @@ again:
local = TRUE;
#ifdef USE_META
if (useMeta) {
meta_compat_start();
}
#endif
/*
* Fork and execute the single command. If the fork fails, we abort.
*/
@ -362,6 +368,11 @@ again:
if (cpid == 0) {
Check_Cwd(av);
Var_ExportVars();
#ifdef USE_META
if (useMeta) {
meta_compat_child();
}
#endif
if (local)
(void)execvp(av[0], (char *const *)UNCONST(av));
else
@ -375,6 +386,12 @@ again:
free(bp);
Lst_Replace(cmdNode, NULL);
#ifdef USE_META
if (useMeta) {
meta_compat_parent();
}
#endif
/*
* The child is off and running. Now all we can do is wait...
*/
@ -393,6 +410,11 @@ again:
status = WSTOPSIG(reason); /* stopped */
} else if (WIFEXITED(reason)) {
status = WEXITSTATUS(reason); /* exited */
#if defined(USE_META) && defined(USE_FILEMON_ONCE)
if (useMeta) {
meta_cmd_finish(NULL);
}
#endif
if (status != 0) {
if (DEBUG(ERROR)) {
fprintf(debug_file, "\n*** Failed target: %s\n*** Failed command: ",
@ -419,6 +441,11 @@ again:
if (!WIFEXITED(reason) || (status != 0)) {
if (errCheck) {
#ifdef USE_META
if (useMeta) {
meta_job_error(NULL, gn, 0, status);
}
#endif
gn->made = ERROR;
if (keepgoing) {
/*
@ -551,6 +578,11 @@ Compat_Make(void *gnp, void *pgnp)
*/
if (!touchFlag || (gn->type & OP_MAKE)) {
curTarg = gn;
#ifdef USE_META
if (useMeta && !NoExecute(gn)) {
meta_job_start(NULL, gn);
}
#endif
Lst_ForEach(gn->commands, CompatRunCommand, gn);
curTarg = NULL;
} else {
@ -559,6 +591,11 @@ Compat_Make(void *gnp, void *pgnp)
} else {
gn->made = ERROR;
}
#ifdef USE_META
if (useMeta && !NoExecute(gn)) {
meta_job_finish(NULL);
}
#endif
if (gn->made != ERROR) {
/*

View File

@ -1,4 +1,4 @@
/* $NetBSD: job.c,v 1.154 2010/08/07 21:28:40 sjg Exp $ */
/* $NetBSD: job.c,v 1.155 2010/09/13 15:36:57 sjg Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@ -70,14 +70,14 @@
*/
#ifndef MAKE_NATIVE
static char rcsid[] = "$NetBSD: job.c,v 1.154 2010/08/07 21:28:40 sjg Exp $";
static char rcsid[] = "$NetBSD: job.c,v 1.155 2010/09/13 15:36:57 sjg Exp $";
#else
#include <sys/cdefs.h>
#ifndef lint
#if 0
static char sccsid[] = "@(#)job.c 8.2 (Berkeley) 3/19/94";
#else
__RCSID("$NetBSD: job.c,v 1.154 2010/08/07 21:28:40 sjg Exp $");
__RCSID("$NetBSD: job.c,v 1.155 2010/09/13 15:36:57 sjg Exp $");
#endif
#endif /* not lint */
#endif
@ -960,6 +960,12 @@ JobFinish(Job *job, int status)
{
Boolean done, return_job_token;
#ifdef USE_META
if (useMeta) {
meta_job_finish(job);
}
#endif
if (DEBUG(JOB)) {
fprintf(debug_file, "Jobfinish: %d [%s], status %d\n",
job->pid, job->node->name, status);
@ -1017,6 +1023,11 @@ JobFinish(Job *job, int status)
MESSAGE(stdout, job->node);
lastNode = job->node;
}
#ifdef USE_META
if (useMeta) {
meta_job_error(job, job->node, job->flags, WEXITSTATUS(status));
}
#endif
(void)printf("*** [%s] Error code %d%s\n",
job->node->name,
WEXITSTATUS(status),
@ -1311,6 +1322,11 @@ JobExec(Job *job, char **argv)
/* Child */
sigset_t tmask;
#ifdef USE_META
if (useMeta) {
meta_job_child(job);
}
#endif
/*
* Reset all signal handlers; this is necessary because we also
* need to unblock signals before we exec(2).
@ -1574,6 +1590,11 @@ JobStart(GNode *gn, int flags)
*/
noExec = FALSE;
#ifdef USE_META
if (useMeta) {
meta_job_start(job, gn);
}
#endif
/*
* We can do all the commands at once. hooray for sanity
*/
@ -1846,6 +1867,11 @@ end_loop:
MESSAGE(stdout, job->node);
lastNode = job->node;
}
#ifdef USE_META
if (useMeta) {
meta_job_output(job, cp, gotNL ? "\n" : "");
}
#endif
(void)fprintf(stdout, "%s%s", cp, gotNL ? "\n" : "");
(void)fflush(stdout);
}

View File

@ -1,4 +1,4 @@
/* $NetBSD: job.h,v 1.39 2009/04/11 09:41:18 apb Exp $ */
/* $NetBSD: job.h,v 1.40 2010/09/13 15:36:57 sjg Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@ -135,6 +135,11 @@ emul_poll(struct pollfd *fd, int nfd, int timeout);
*/
struct pollfd;
#ifdef USE_META
# include "meta.h"
#endif
#define JOB_BUFSIZE 1024
typedef struct Job {
int pid; /* The child's process ID */
@ -165,6 +170,10 @@ typedef struct Job {
/* Buffer for storing the output of the
* job, line by line */
int curPos; /* Current position in op_outBuf */
#ifdef USE_META
struct BuildMon bm;
#endif
} Job;
#define inPipe jobPipe[0]

View File

@ -1,4 +1,4 @@
/* $NetBSD: main.c,v 1.189 2010/08/07 06:44:08 sjg Exp $ */
/* $NetBSD: main.c,v 1.190 2010/09/13 15:36:57 sjg Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@ -69,7 +69,7 @@
*/
#ifndef MAKE_NATIVE
static char rcsid[] = "$NetBSD: main.c,v 1.189 2010/08/07 06:44:08 sjg Exp $";
static char rcsid[] = "$NetBSD: main.c,v 1.190 2010/09/13 15:36:57 sjg Exp $";
#else
#include <sys/cdefs.h>
#ifndef lint
@ -81,7 +81,7 @@ __COPYRIGHT("@(#) Copyright (c) 1988, 1989, 1990, 1993\
#if 0
static char sccsid[] = "@(#)main.c 8.3 (Berkeley) 3/19/94";
#else
__RCSID("$NetBSD: main.c,v 1.189 2010/08/07 06:44:08 sjg Exp $");
__RCSID("$NetBSD: main.c,v 1.190 2010/09/13 15:36:57 sjg Exp $");
#endif
#endif /* not lint */
#endif
@ -242,6 +242,9 @@ parse_debug_options(const char *argvalue)
case 'l':
debug |= DEBUG_LOUD;
break;
case 'M':
debug |= DEBUG_META;
break;
case 'm':
debug |= DEBUG_MAKE;
break;
@ -732,6 +735,10 @@ MakeMode(const char *mode)
compatMake = TRUE;
forceJobs = FALSE;
}
#if USE_META
if (strstr(mode, "meta"))
meta_init(mode);
#endif
}
if (mp)
free(mp);

View File

@ -1,4 +1,4 @@
.\" $NetBSD: make.1,v 1.179 2010/06/30 01:03:54 dholland Exp $
.\" $NetBSD: make.1,v 1.180 2010/09/13 15:36:57 sjg Exp $
.\"
.\" Copyright (c) 1990, 1993
.\" The Regents of the University of California. All rights reserved.
@ -194,6 +194,8 @@ Print commands in Makefiles regardless of whether or not they are prefixed by
.Ql @
or other "quiet" flags.
Also known as "loud" behavior.
.It Ar M
Print debugging information about "meta" mode decisions about targets.
.It Ar m
Print debugging information about making targets, including modification
dates.
@ -266,6 +268,8 @@ cooperate to avoid overloading the system.
Specify the maximum number of jobs that
.Nm
may have running at any one time.
The value is saved in
.Va .MAKE.JOBS .
Turns compatibility mode off, unless the
.Ar B
flag is also specified.
@ -759,9 +763,49 @@ Processed after reading all makefiles.
Can affect the mode that
.Nm
runs in.
Currently just
.Ql Pa compat
mode.
It can contain a number of keywords:
.Bl -hang -width ignore-cmd
.It Pa compat
Like
.Fl B
puts
.Nm
into "compat" mode.
.It Pa meta
Puts
.Nm
into "meta" mode, where meta files are created for each target
to capture the command run, the output generated and if
.Xr filemon 4
is available, the system calls which are of interest to
.Nm .
The captured output can be very useful when diagnosing errors.
.It Pa verbose
If in "meta" mode, print a clue about the target being built.
This is useful if the build is otherwise running silently.
The message printed the value of:
.Va .MAKE.META.PREFIX .
.It Pa ignore-cmd
Some makefiles have commands which are simply not stable.
This keyword causes them to be ignored for
determining whether a target is out of date in "meta" mode.
See also
.Ic .NOMETA_CMP .
.El
.It Va .MAKE.META.CREATED
In "meta" mode, this variable contains a list of all the meta files
updated.
If not empty, it can be used to trigger processing of
.Va .MAKE.META.FILES .
.It Va .MAKE.META.FILES
In "meta" mode, this variable contains a list of all the meta files
used (updated or not).
This list can be used to process the meta files to extract dependency
information.
.It Va .MAKE.META.PREFIX
Defines the message printed for each meta file updated in "meta verbose" mode.
The default value is:
.Dl Building ${.TARGET:H:tA}/${.TARGET:T}
.It Va .MAKEOVERRIDES
This variable is used to record the names of variables assigned to
on the command line, so that they may be exported as part of
@ -1612,6 +1656,27 @@ or
options were specified.
Normally used to mark recursive
.Nm Ns 's .
.It Ic .META
Create a meta file for the target, even if it is flagged as
.Ic .PHONY ,
.Ic .MAKE
or
.Ic .SPECIAL .
Usage in conjunction with
.Ic .MAKE
is the most likely case.
.It Ic .NOMETA
Do not create a meta file for the target.
Meta files are also not created for
.Ic .PHONY ,
.Ic .MAKE
or
.Ic .SPECIAL
targets.
.It Ic .NOMETA_CMP
Ignore differences in commands when deciding if target is out of date.
This is useful if the command contains a value which always changes.
If the number of commands change, though the target will still be out of date.
.It Ic .NOPATH
Do not search for the target in the directories specified by
.Ic .PATH .

View File

@ -1,4 +1,4 @@
/* $NetBSD: make.c,v 1.81 2010/07/06 03:56:59 dholland Exp $ */
/* $NetBSD: make.c,v 1.82 2010/09/13 15:36:57 sjg Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@ -69,14 +69,14 @@
*/
#ifndef MAKE_NATIVE
static char rcsid[] = "$NetBSD: make.c,v 1.81 2010/07/06 03:56:59 dholland Exp $";
static char rcsid[] = "$NetBSD: make.c,v 1.82 2010/09/13 15:36:57 sjg Exp $";
#else
#include <sys/cdefs.h>
#ifndef lint
#if 0
static char sccsid[] = "@(#)make.c 8.1 (Berkeley) 6/6/93";
#else
__RCSID("$NetBSD: make.c,v 1.81 2010/07/06 03:56:59 dholland Exp $");
__RCSID("$NetBSD: make.c,v 1.82 2010/09/13 15:36:57 sjg Exp $");
#endif
#endif /* not lint */
#endif
@ -330,6 +330,12 @@ Make_OODate(GNode *gn)
oodate = (gn->flags & FORCE) ? TRUE : FALSE;
}
#ifdef USE_META
if (useMeta) {
oodate = meta_oodate(gn, oodate);
}
#endif
/*
* If the target isn't out-of-date, the parents need to know its
* modification time. Note that targets that appear to be out-of-date

View File

@ -1,4 +1,4 @@
/* $NetBSD: make.h,v 1.82 2010/04/23 00:18:50 sjg Exp $ */
/* $NetBSD: make.h,v 1.83 2010/09/13 15:36:57 sjg Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@ -260,6 +260,9 @@ typedef struct GNode {
#define OP_PHONY 0x00010000 /* Not a file target; run always */
#define OP_NOPATH 0x00020000 /* Don't search for file in the path */
#define OP_WAIT 0x00040000 /* .WAIT phony node */
#define OP_NOMETA 0x00080000 /* .NOMETA do not create a .meta file */
#define OP_META 0x00100000 /* .META we _do_ want a .meta file */
#define OP_NOMETA_CMP 0x00200000 /* Do not compare commands in .meta file */
/* Attributes applied by PMake */
#define OP_TRANSFORM 0x80000000 /* The node is a transformation rule */
#define OP_MEMBER 0x40000000 /* Target is a member of an archive */
@ -433,6 +436,8 @@ extern int debug;
#define DEBUG_SHELL 0x00800
#define DEBUG_ERROR 0x01000
#define DEBUG_LOUD 0x02000
#define DEBUG_META 0x04000
#define DEBUG_GRAPH3 0x10000
#define DEBUG_SCRIPT 0x20000
#define DEBUG_PARSE 0x40000

955
usr.bin/make/meta.c Normal file
View File

@ -0,0 +1,955 @@
/*
* Implement 'meta' mode.
* Adapted from John Birrell's patches to FreeBSD make.
* --sjg
*/
/*
* Copyright (c) 2009-2010, Juniper Networks, Inc.
* Portions Copyright (c) 2009, John Birrell.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <libgen.h>
#include <errno.h>
#include <err.h>
#include "make.h"
#include "job.h"
#ifdef HAVE_FILEMON_H
# include <filemon.h>
#endif
#if !defined(USE_FILEMON) && defined(FILEMON_SET_FD)
# define USE_FILEMON
#endif
static BuildMon Mybm; /* for compat */
Boolean useMeta = FALSE;
static Boolean useFilemon = FALSE;
static Boolean writeMeta = FALSE;
static Boolean metaEnv = FALSE; /* don't save env unless asked */
static Boolean metaVerbose = FALSE;
static Boolean metaIgnoreCMDs = FALSE; /* ignore CMDs in .meta files */
extern Boolean forceJobs;
extern Boolean comatMake;
#define MAKE_META_PREFIX ".MAKE.META.PREFIX"
#ifndef N2U
# define N2U(n, u) (((n) + ((u) - 1)) / (u))
#endif
#ifndef ROUNDUP
# define ROUNDUP(n, u) (N2U((n), (u)) * (u))
#endif
/*
* Filemon is a kernel module which snoops certain syscalls.
*
* C chdir
* E exec
* F [v]fork
* L [sym]link
* M rename
* R read
* W write
* S stat
*
* See meta_oodate below - we mainly care about 'E' and 'R'.
*
* We can still use meta mode without filemon, but
* the benefits are more limited.
*/
#ifdef USE_FILEMON
# ifndef _PATH_FILEMON
# define _PATH_FILEMON "/dev/filemon"
# endif
/*
* Open the filemon device.
*/
static void
filemon_open(BuildMon *pbm)
{
int retry;
pbm->mon_fd = pbm->filemon_fd = -1;
if (!useFilemon)
return;
for (retry = 5; retry >= 0; retry--) {
if ((pbm->filemon_fd = open(_PATH_FILEMON, O_RDWR)) >= 0)
break;
}
if (pbm->filemon_fd < 0) {
useFilemon = FALSE;
warn("Could not open %s", _PATH_FILEMON);
return;
}
/*
* We use a file outside of '.'
* to avoid a FreeBSD kernel bug where unlink invalidates
* cwd causing getcwd to do a lot more work.
* We only care about the descriptor.
*/
pbm->mon_fd = mkTempFile("filemon.XXXXXX", NULL);
if (ioctl(pbm->filemon_fd, FILEMON_SET_FD, &pbm->mon_fd) < 0) {
err(1, "Could not set filemon file descriptor!");
}
/* we don't need these once we exec */
(void)fcntl(pbm->mon_fd, F_SETFD, 1);
(void)fcntl(pbm->filemon_fd, F_SETFD, 1);
}
/*
* Read the build monitor output file and write records to the target's
* metadata file.
*/
static void
filemon_read(FILE *mfp, int fd)
{
FILE *fp;
char buf[BUFSIZ];
/* Check if we're not writing to a meta data file.*/
if (mfp == NULL) {
if (fd >= 0)
close(fd); /* not interested */
return;
}
/* rewind */
lseek(fd, SEEK_SET, 0);
if ((fp = fdopen(fd, "r")) == NULL)
err(1, "Could not read build monitor file '%d'", fd);
fprintf(mfp, "-- filemon acquired metadata --\n");
while (fgets(buf, sizeof(buf), fp)) {
fprintf(mfp, "%s", buf);
}
fflush(mfp);
clearerr(fp);
fclose(fp);
}
#endif
static char *
meta_name(struct GNode *gn, char *mname, size_t mnamelen,
const char *dname,
const char *tname)
{
char buf[MAXPATHLEN];
char cwd[MAXPATHLEN];
char *rp;
char *cp;
char *tp;
char *p[4]; /* >= number of possible uses */
int i;
i = 0;
if (!dname)
dname = Var_Value(".OBJDIR", gn, &p[i++]);
if (!tname)
tname = Var_Value(TARGET, gn, &p[i++]);
/*
* Weed out relative paths from the target file name.
* We have to be careful though since if target is a
* symlink, the result will be unstable.
* So we use realpath() just to get the dirname, and leave the
* basename as given to us.
*/
if ((cp = strrchr(tname, '/'))) {
if (realpath(tname, buf)) {
if ((rp = strrchr(buf, '/'))) {
rp++;
cp++;
if (strcmp(cp, rp) != 0)
strlcpy(rp, cp, sizeof(buf) - (rp - buf));
}
tname = buf;
}
}
if (realpath(dname, cwd))
dname = cwd;
/* on some systems dirname may modify its arg */
tp = bmake_strdup(tname);
if (strcmp(dname, dirname(tp)) == 0)
snprintf(mname, mnamelen, "%s.meta", tname);
else {
snprintf(mname, mnamelen, "%s/%s.meta", dname, tname);
/*
* Replace path separators in the file name after the
* current object directory path.
*/
cp = mname + strlen(dname) + 1;
while (*cp != '\0') {
if (*cp == '/')
*cp = '_';
cp++;
}
}
free(tp);
for (i--; i >= 0; i--) {
if (p[i])
free(p[i]);
}
return (mname);
}
/*
* Return true if running ${.MAKE}
* Bypassed if target is flagged .MAKE
*/
static int
is_submake(void *cmdp, void *gnp)
{
static char *p_make = NULL;
static int p_len;
char *cmd = cmdp;
GNode *gn = gnp;
char *mp = NULL;
char *cp;
char *cp2;
int rc = 0; /* keep looking */
if (!p_make) {
p_make = Var_Value(".MAKE", gn, &cp);
p_len = strlen(p_make);
}
cp = strchr(cmd, '$');
if ((cp)) {
mp = Var_Subst(NULL, cmd, gn, FALSE);
cmd = mp;
}
cp2 = strstr(cmd, p_make);
if ((cp2)) {
switch (cp2[p_len]) {
case '\0':
case ' ':
case '\t':
case '\n':
rc = 1;
break;
}
if (cp2 > cmd && rc > 0) {
switch (cp2[-1]) {
case ' ':
case '\t':
case '\n':
break;
default:
rc = 0; /* no match */
break;
}
}
}
if (mp)
free(mp);
return (rc);
}
typedef struct meta_file_s {
FILE *fp;
GNode *gn;
} meta_file_t;
static int
printCMD(void *cmdp, void *mfpp)
{
meta_file_t *mfp = mfpp;
char *cmd = cmdp;
char *cp = NULL;
if (strchr(cmd, '$')) {
cmd = cp = Var_Subst(NULL, cmd, mfp->gn, FALSE);
}
fprintf(mfp->fp, "CMD %s\n", cmd);
if (cp)
free(cp);
return 0;
}
/*
* Certain node types never get a .meta file
*/
#define SKIP_META_TYPE(_type) do { \
if ((gn->type & __CONCAT(OP_, _type))) { \
if (DEBUG(META)) { \
fprintf(debug_file, "Skipping meta for %s: .%s\n", \
gn->name, __STRING(_type)); \
} \
return (NULL); \
} \
} while (0)
static FILE *
meta_create(BuildMon *pbm, GNode *gn)
{
extern char **environ;
meta_file_t mf;
char buf[MAXPATHLEN];
char curdir[MAXPATHLEN];
char objdir[MAXPATHLEN];
char **ptr;
const char *cname;
const char *dname;
const char *tname;
char *fname;
const char *cp;
char *p[4]; /* >= possible uses */
int i;
struct stat fs;
/* This may be a phony node which we don't want meta data for... */
/* Skip .meta for .BEGIN, .END, .ERROR etc as well. */
/* Or it may be explicitly flagged as .NOMETA */
SKIP_META_TYPE(NOMETA);
/* Unless it is explicitly flagged as .META */
if (!(gn->type & OP_META)) {
SKIP_META_TYPE(PHONY);
SKIP_META_TYPE(SPECIAL);
SKIP_META_TYPE(MAKE);
}
mf.fp = NULL;
i = 0;
dname = Var_Value(".OBJDIR", gn, &p[i++]);
cname = Var_Value(".CURDIR", gn, &p[i++]);
tname = Var_Value(TARGET, gn, &p[i++]);
/* The object directory may not exist. Check it.. */
if (stat(dname, &fs) != 0) {
if (DEBUG(META))
fprintf(debug_file, "Skipping meta for %s: no .OBJDIR\n",
gn->name);
goto out;
}
/* Check if there are no commands to execute. */
if (Lst_IsEmpty(gn->commands)) {
if (DEBUG(META))
fprintf(debug_file, "Skipping meta for %s: no commands\n",
gn->name);
goto out;
}
/* make sure these are canonical */
if (realpath(dname, objdir))
dname = objdir;
if (realpath(cname, curdir))
cname = curdir;
/* If we aren't in the object directory, don't create a meta file. */
if (strcmp(cname, dname) == 0) {
if (DEBUG(META))
fprintf(debug_file, "Skipping meta for %s: .OBJDIR == .CURDIR\n",
gn->name);
goto out;
}
if (!(gn->type & OP_META)) {
/* We do not generate .meta files for sub-makes */
if (Lst_ForEach(gn->commands, is_submake, gn)) {
if (DEBUG(META))
fprintf(debug_file, "Skipping meta for %s: .MAKE\n",
gn->name);
goto out;
}
}
if (metaVerbose) {
char *mp;
/* Describe the target we are building */
mp = Var_Subst(NULL, "${" MAKE_META_PREFIX "}", gn, 0);
if (*mp)
fprintf(stdout, "%s\n", mp);
free(mp);
}
/* Get the basename of the target */
if ((cp = strrchr(tname, '/')) == NULL) {
cp = tname;
} else {
cp++;
}
fflush(stdout);
if (strcmp(cp, makeDependfile) == 0)
goto out;
if (!writeMeta)
/* Don't create meta data. */
goto out;
fname = meta_name(gn, pbm->meta_fname, sizeof(pbm->meta_fname),
dname, tname);
if ((mf.fp = fopen(fname, "w")) == NULL)
err(1, "Could not open meta file '%s'", fname);
fprintf(mf.fp, "# Meta data file %s\n", fname);
mf.gn = gn;
Lst_ForEach(gn->commands, printCMD, &mf);
fprintf(mf.fp, "CWD %s\n", getcwd(buf, sizeof(buf)));
fprintf(mf.fp, "TARGET %s\n", tname);
if (metaEnv) {
for (ptr = environ; *ptr != NULL; ptr++)
fprintf(mf.fp, "ENV %s\n", *ptr);
}
fprintf(mf.fp, "-- command output --\n");
fflush(mf.fp);
Var_Append(".MAKE.META.FILES", fname, VAR_GLOBAL);
Var_Append(".MAKE.META.CREATED", fname, VAR_GLOBAL);
out:
for (i--; i >= 0; i--) {
if (p[i])
free(p[i]);
}
return (mf.fp);
}
void
meta_init(const char *make_mode)
{
static int once = 0;
useMeta = TRUE;
useFilemon = TRUE;
writeMeta = TRUE;
if (make_mode) {
if (strstr(make_mode, "env"))
metaEnv = TRUE;
if (strstr(make_mode, "verb"))
metaVerbose = TRUE;
if (strstr(make_mode, "read"))
writeMeta = FALSE;
if (strstr(make_mode, "nofilemon"))
useFilemon = FALSE;
if (strstr(make_mode, "ignore-cmd"))
metaIgnoreCMDs = TRUE;
/* for backwards compatability */
Var_Set(".MAKE.META_CREATED", "${.MAKE.META.CREATED}", VAR_GLOBAL, 0);
Var_Set(".MAKE.META_FILES", "${.MAKE.META.FILES}", VAR_GLOBAL, 0);
}
if (metaVerbose && !Var_Exists(MAKE_META_PREFIX, VAR_GLOBAL)) {
/*
* The default value for MAKE_META_PREFIX
* prints the absolute path of the target.
* This works be cause :H will generate '.' if there is no /
* and :tA will resolve that to cwd.
*/
Var_Set(MAKE_META_PREFIX, "Building ${.TARGET:H:tA}/${.TARGET:T}", VAR_GLOBAL, 0);
}
if (once)
return;
once = 1;
memset(&Mybm, 0, sizeof(Mybm));
}
/*
* In each case below we allow for job==NULL
*/
void
meta_job_start(Job *job, GNode *gn)
{
BuildMon *pbm;
if (job != NULL) {
pbm = &job->bm;
} else {
pbm = &Mybm;
}
pbm->mfp = meta_create(pbm, gn);
#ifdef USE_FILEMON_ONCE
/* compat mode we open the filemon dev once per command */
if (job == NULL)
return;
#endif
#ifdef USE_FILEMON
if (pbm->mfp != NULL && useFilemon) {
filemon_open(pbm);
} else {
pbm->mon_fd = pbm->filemon_fd = -1;
}
#endif
}
/*
* The child calls this before doing anything.
* It does not disturb our state.
*/
void
meta_job_child(Job *job)
{
#ifdef USE_FILEMON
BuildMon *pbm;
pid_t pid;
if (job != NULL) {
pbm = &job->bm;
} else {
pbm = &Mybm;
}
pid = getpid();
if (pbm->mfp != NULL && useFilemon) {
if (ioctl(pbm->filemon_fd, FILEMON_SET_PID, &pid) < 0) {
err(1, "Could not set filemon pid!");
}
}
#endif
}
void
meta_job_error(Job *job, GNode *gn, int flags, int status)
{
char cwd[MAXPATHLEN];
BuildMon *pbm;
if (job != NULL) {
pbm = &job->bm;
} else {
if (!gn)
gn = job->node;
pbm = &Mybm;
}
if (pbm->mfp != NULL) {
fprintf(pbm->mfp, "*** Error code %d%s\n",
status,
(flags & JOB_IGNERR) ?
"(ignored)" : "");
}
if (gn) {
Var_Set(".ERROR_TARGET", gn->path ? gn->path : gn->name, VAR_GLOBAL, 0);
}
getcwd(cwd, sizeof(cwd));
Var_Set(".ERROR_CWD", cwd, VAR_GLOBAL, 0);
#ifdef USE_FILEMON
if (pbm) {
Var_Set(".ERROR_META_FILE", pbm->meta_fname, VAR_GLOBAL, 0);
}
#endif
}
void
meta_job_output(Job *job, char *cp, const char *nl)
{
BuildMon *pbm;
if (job != NULL) {
pbm = &job->bm;
} else {
pbm = &Mybm;
}
if (pbm->mfp != NULL) {
if (metaVerbose) {
static char *meta_prefix = NULL;
static int meta_prefix_len;
if (!meta_prefix) {
char *cp2;
meta_prefix = Var_Subst(NULL, "${" MAKE_META_PREFIX "}", VAR_GLOBAL, 0);
if ((cp2 = strchr(meta_prefix, '$')))
meta_prefix_len = cp2 - meta_prefix;
else
meta_prefix_len = strlen(meta_prefix);
}
if (strncmp(cp, meta_prefix, meta_prefix_len) == 0) {
cp = strchr(cp+1, '\n');
if (!cp++)
return;
}
}
fprintf(pbm->mfp, "%s%s", cp, nl);
}
}
void
meta_cmd_finish(void *pbmp)
{
#ifdef USE_FILEMON
BuildMon *pbm = pbmp;
if (!pbm)
pbm = &Mybm;
if (pbm->filemon_fd >= 0) {
close(pbm->filemon_fd);
filemon_read(pbm->mfp, pbm->mon_fd);
pbm->filemon_fd = pbm->mon_fd = -1;
}
#endif
}
void
meta_job_finish(Job *job)
{
BuildMon *pbm;
if (job != NULL) {
pbm = &job->bm;
} else {
pbm = &Mybm;
}
if (pbm->mfp != NULL) {
meta_cmd_finish(pbm);
fclose(pbm->mfp);
pbm->mfp = NULL;
}
}
/*
* Fetch a full line from fp - growing bufp if needed
* Return length in bufp.
*/
static int
fgetLine(char **bufp, size_t *szp, int o, FILE *fp)
{
char *buf = *bufp;
size_t bufsz = *szp;
struct stat fs;
int x;
if (fgets(&buf[o], bufsz - o, fp) != NULL) {
check_newline:
x = o + strlen(&buf[o]);
if (buf[x - 1] == '\n')
return x;
/*
* We need to grow the buffer.
* The meta file can give us a clue.
*/
if (fstat(fileno(fp), &fs) == 0) {
size_t newsz;
char *p;
newsz = ROUNDUP((fs.st_size / 2), BUFSIZ);
if (newsz <= bufsz)
newsz = ROUNDUP(fs.st_size, BUFSIZ);
if (DEBUG(META))
fprintf(debug_file, "growing buffer %u -> %u\n",
bufsz, newsz);
p = bmake_realloc(buf, newsz);
if (p) {
*bufp = buf = p;
*szp = bufsz = newsz;
/* fetch the rest */
if (!fgets(&buf[x], bufsz - x, fp))
return x; /* truncated! */
goto check_newline;
}
}
}
return 0;
}
/*
* When running with 'meta' functionality, a target can be out-of-date
* if any of the references in it's meta data file is more recent.
*/
Boolean
meta_oodate(GNode *gn, Boolean oodate)
{
char latestdir[MAXPATHLEN];
char fname[MAXPATHLEN];
char fname1[MAXPATHLEN];
char *p;
char *cp;
FILE *fp;
Boolean ignoreOODATE = 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);
if (oodate)
return oodate; /* we're done */
if (getcwd(latestdir, sizeof(latestdir)) == NULL)
err(1, "Could not get current working directory");
meta_name(gn, fname, sizeof(fname), NULL, NULL);
if ((fp = fopen(fname, "r")) != NULL) {
static char *buf = NULL;
static size_t bufsz;
int lineno = 0;
int f = 0;
int x;
LstNode ln;
struct stat fs;
if (!buf) {
bufsz = 8 * BUFSIZ;
buf = bmake_malloc(bufsz);
}
/* 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);
/* 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;
strsep(&p, " ");
if (f) {
/* Process according to record type. */
switch (buf[0]) {
case 'C':
/* Skip the pid. */
if (strsep(&p, " ") == NULL)
break;
/* Update the latest directory. */
strlcpy(latestdir, p, sizeof(latestdir));
break;
case 'R':
case 'E':
/* Skip the pid. */
if (strsep(&p, " ") == NULL)
break;
/*
* Check for runtime files that can't
* be part of the dependencies because
* they are _expected_ to change.
*/
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;
/*
* The rest of the record is the
* file name.
* Check if it's not an absolute
* path.
*/
if (*p != '/') {
/* Use the latest path seen. */
snprintf(fname1, sizeof(fname1), "%s/%s", latestdir, p);
p = fname1;
}
if (stat(p, &fs) == 0 &&
!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;
}
break;
default:
break;
}
/*
* Compare the current command with the one in the
* meta data file.
*/
} else if (strcmp(buf, "CMD") == 0) {
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) {
char curdir[MAXPATHLEN];
if (strcmp(p, getcwd(curdir, sizeof(curdir))) != 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;
}
}
}
/*
* 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;
}
fclose(fp);
}
return oodate;
}
/* support for compat mode */
static int childPipe[2];
void
meta_compat_start(void)
{
#ifdef USE_FILEMON_ONCE
/*
* We need to re-open filemon for each cmd.
*/
BuildMon *pbm = &Mybm;
if (pbm->mfp != NULL && useFilemon) {
filemon_open(pbm);
} else {
pbm->mon_fd = pbm->filemon_fd = -1;
}
#endif
if (pipe(childPipe) < 0)
Punt("Cannot create pipe: %s", strerror(errno));
/* Set close-on-exec flag for both */
(void)fcntl(childPipe[0], F_SETFD, 1);
(void)fcntl(childPipe[1], F_SETFD, 1);
}
void
meta_compat_child(void)
{
meta_job_child(NULL);
if (dup2(childPipe[1], 1) < 0 ||
dup2(1, 2) < 0) {
execError("dup2", "pipe");
_exit(1);
}
}
void
meta_compat_parent(void)
{
FILE *fp;
char buf[BUFSIZ];
close(childPipe[1]); /* child side */
fp = fdopen(childPipe[0], "r");
while (fgets(buf, sizeof(buf), fp)) {
meta_job_output(NULL, buf, "");
printf("%s", buf);
}
fclose(fp);
}

52
usr.bin/make/meta.h Normal file
View File

@ -0,0 +1,52 @@
/*
* Things needed for 'meta' mode.
*/
/*
* Copyright (c) 2009-2010, Juniper Networks, Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
typedef struct BuildMon {
char meta_fname[MAXPATHLEN];
int filemon_fd;
int mon_fd;
FILE *mfp;
} BuildMon;
extern Boolean useMeta;
struct Job; /* not defined yet */
void meta_init(const char *);
void meta_job_start(struct Job *, GNode *);
void meta_job_child(struct Job *);
void meta_job_error(struct Job *, GNode *, int, int);
void meta_job_output(struct Job *, char *, const char *);
void meta_cmd_finish(void *);
void meta_job_finish(struct Job *);
Boolean meta_oodate(GNode *, Boolean);
void meta_compat_start(void);
void meta_compat_child(void);
void meta_compat_parent(void);

View File

@ -1,4 +1,4 @@
/* $NetBSD: parse.c,v 1.164 2010/05/24 21:04:49 sjg Exp $ */
/* $NetBSD: parse.c,v 1.165 2010/09/13 15:36:57 sjg Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@ -69,14 +69,14 @@
*/
#ifndef MAKE_NATIVE
static char rcsid[] = "$NetBSD: parse.c,v 1.164 2010/05/24 21:04:49 sjg Exp $";
static char rcsid[] = "$NetBSD: parse.c,v 1.165 2010/09/13 15:36:57 sjg Exp $";
#else
#include <sys/cdefs.h>
#ifndef lint
#if 0
static char sccsid[] = "@(#)parse.c 8.3 (Berkeley) 3/19/94";
#else
__RCSID("$NetBSD: parse.c,v 1.164 2010/05/24 21:04:49 sjg Exp $");
__RCSID("$NetBSD: parse.c,v 1.165 2010/09/13 15:36:57 sjg Exp $");
#endif
#endif /* not lint */
#endif
@ -196,10 +196,13 @@ typedef enum {
Includes, /* .INCLUDES */
Interrupt, /* .INTERRUPT */
Libs, /* .LIBS */
Meta, /* .META */
MFlags, /* .MFLAGS or .MAKEFLAGS */
Main, /* .MAIN and we don't have anything user-specified to
* make */
NoExport, /* .NOEXPORT */
NoMeta, /* .NOMETA */
NoMetaCmp, /* .NOMETA_CMP */
NoPath, /* .NOPATH */
Not, /* Not special */
NotParallel, /* .NOTPARALLEL */
@ -258,7 +261,10 @@ static struct {
{ ".MAIN", Main, 0 },
{ ".MAKE", Attribute, OP_MAKE },
{ ".MAKEFLAGS", MFlags, 0 },
{ ".META", Meta, OP_META },
{ ".MFLAGS", MFlags, 0 },
{ ".NOMETA", NoMeta, OP_NOMETA },
{ ".NOMETA_CMP", NoMetaCmp, OP_NOMETA_CMP },
{ ".NOPATH", NoPath, OP_NOPATH },
{ ".NOTMAIN", Attribute, OP_NOTMAIN },
{ ".NOTPARALLEL", NotParallel, 0 },