GNU m4 developers,
Here are patches that Lorenzo Di Gregorio and I are submitting
for the long-needed capability for m4 to automatically generate
make dependency rules.
This version of the patch is for branch-1.4.
I have a stand-alone shell script that can be invoked as follows:
test-m4-makedep.sh m4_executable_path
that tests all of the functionality of these features. This
script is NOT part of this patch because I could not find any
simple way to incorporate it into the make procedure. (These
same tests are done using autotest in my patch for master.)
If you know of a simple way to hook this script up in the
Makefiles, I will be happy to send in the script. Otherwise
we can omit the regression tests on branches other than master.
David Warme
ChangeLog | 14 +++++
NEWS | 2
THANKS | 2
doc/m4.texinfo | 116 +++++++++++++++++++++++++++++++++++++++++++
src/builtin.c | 19 ++++---
src/m4.c | 152 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--
src/m4.h | 10 +++
src/path.c | 78 +++++++++++++++++++++++++++++
8 files changed, 384 insertions(+), 9 deletions(-)
diff --git a/ChangeLog b/ChangeLog
index 71ec8ae..c6a45b0 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,17 @@
+2011-02-10 David Warme <[email protected]>
+ Lorenzo Di Gregorio <[email protected]>
+
+ Add ability to generate make dependency rules.
+ * doc/m4.texinfo: Document new options.
+ * src/builtin.c: Record dependencies for include() / sinclude().
+ * src/m4.c: Add new options. Record dependencies for files
+ specified on the command line. Generate dependencies at end.
+ * src/m4.h: Add defines for tracking how files are referenced, and
+ new functions for recording / generation of dependencies.
+ * src/path.c: Added recording / generation of dependencies.
+ * NEWS: Added a blurb for this new feature.
+ * THANKS: Added us to the list.
+
2011-02-01 Eric Blake <[email protected]>
maint: update to latest gnulib
diff --git a/NEWS b/NEWS
index 54c58ca..a3f08d8 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,8 @@ GNU M4 NEWS - User visible changes.
* Noteworthy changes in release 1.4.16 (2011-01-??) [?]
+** Add new capability to automatically generate make dependency rules.
+
** Ensure the `index' builtin cannot give false positive results. On
glibc platforms, this avoids a strstr bug in glibc 2.9 through 2.12;
on many other platforms, it fixes a regression introduced in 1.4.11.
diff --git a/THANKS b/THANKS
index 17d39bb..402baf1 100644
--- a/THANKS
+++ b/THANKS
@@ -32,6 +32,7 @@ Damian Menscher [email protected]
Dan Jacobson [email protected]
David J. MacKenzie [email protected]
David Perlin [email protected]
+David Warme [email protected]
Elbert Pol [email protected]
Elias Benali [email protected]
Erez Zadok [email protected]
@@ -72,6 +73,7 @@ Konrad Schwarz [email protected]
Kristine Lund [email protected]
Krste Asanovic [email protected]
Lawson Chan [email protected]
+Lorenzo Di Gregorio [email protected]
Marion Hakanson [email protected]
Mark Seiden [email protected]
Martin Koeppe [email protected]
diff --git a/doc/m4.texinfo b/doc/m4.texinfo
index 1512ae1..e159ef5 100644
--- a/doc/m4.texinfo
+++ b/doc/m4.texinfo
@@ -159,6 +159,7 @@
* Preprocessor features:: Command line options for preprocessor features
* Limits control:: Command line options for limits control
* Frozen state:: Command line options for frozen state
+* Make dependency generation:: Generating make dependency rules
* Debugging options:: Command line options for debugging
* Command line files:: Specifying input files on the command line
@@ -566,6 +567,7 @@
* Preprocessor features:: Command line options for preprocessor features
* Limits control:: Command line options for limits control
* Frozen state:: Command line options for frozen state
+* Make dependency generation:: Generating make dependency rules
* Debugging options:: Command line options for debugging
* Command line files:: Specifying input files on the command line
@end menu
@@ -841,6 +843,120 @@
files are read.
@end table
+@node Make dependency generation
+@section Command line options for generating Makefile dependency rules
+
+Makefile dependency rules can be automatically generated by specifying
+both the @code{--makedep=}@var{file} and
+@code{--makedep-target=}@var{target} options.
+
+@table @code
+@itemx --makedep=@var{file}
+Causes @code{m4} to generate a dependency rule into the specified
+@var{file}. Macro expansion output is still written to stdout as
+normal. This option is analogous to the @code{-MF} option of
+@code{gcc}.
+
+@itemx --makedep-target=@var{target}
+Specifies @var{target} to be the target of the generated dependency
+rule. The string @var{target} is used verbatim, and can contain several
+logical targets separated by spaces. It is the user's responsibility to
+properly express characters that @code{make} handles specially (such as
+'@code{$}', or spaces within file names). Since @code{m4} sends its
+macro expansion output to stdout, it never really knows the name of the
+target file being generated, so the target must always be specified
+explicitly by the user with this option. This option is analogous to
+the @code{-MT} option of @code{gcc} (except that @code{gcc} allows
+@code{-MT} to be specified multiple times).
+@end table
+
+Note that the @code{--makedep=}@var{file} and
+@code{makedep-target=}@var{target} options must either (a) both be
+specified, or (b) neither be specified. They cannot be used
+independently of each other.
+
+The following additional options can also be used when dependency rules
+are being generated (these options are only valid when both
+@code{makedep=}@var{file} and @code{makedep-target=}@var{target} have
+also been specified):
+
+@table @code
+@itemx --makedep-gen-missing-argfiles
+Causes @code{m4} to assume that any file listed on the command line that
+is missing (i.e., does not exist) is an automatically generated file.
+@code{M4} includes such missing files as dependencies in the generated
+rule regardless. In this case the dependency appears exactly as
+specified on the command line and is not modified by any
+@code{-I}@var{searchdir} prefixes. Note that the macro expansion output
+generated to stdout will be incorrect when this happens because the
+missing file is assumed to be an empty file. A warning is produced on
+stderr for each missing command line file handled in this manner.
+
+@itemx --makedep-gen-missing-include
+Causes @code{m4} to assume that any file included via the
+@code{include()} macro that is missing (i.e., does not exist) is an
+automatically generated file. @code{M4} includes such missing files as
+dependencies in the generated rule regardless. In this case the
+dependency appears exactly as specified in the argument to
+@code{include()} and is not modified by any @code{-I}@var{searchdir}
+prefixes. Note that the macro expansion output generated to stdout will
+be incorrect when this happens because the missing file is assumed to be
+an empty file. This option causes the @code{m4} @code{include()} macro
+to behave like @code{sinclude()}, except that a warning message is
+produced on stderr to indicate that the requested file was missing.
+This option is analogous to the @code{-MG} option of @code{gcc}.
+
+@itemx --makedep-gen-missing-sinclude
+Causes @code{m4} to assume that any file included via the
+@code{sinclude()} macro that is missing (i.e., does not exist) is an
+automatically generated file. @code{M4} includes such missing files as
+dependencies in the generated rule regardless. In this case the
+dependency appears exactly as specified in the argument to
+@code{sinclude()} and is not modified by any @code{-I}@var{searchdir}
+prefixes. Note that the macro expansion output generated to stdout will
+be incorrect when this happens because the missing file is assumed to be
+an empty file. This option does not alter @code{sinclude()}'s behavior
+of silently ignoring requests to @code{sinclude()} files that do not exist.
+
+@itemx --makedep-gen-missing-all
+This option is equivalent to specifying all three options
+@code{--makedep-gen-missing-argfiles},
+@code{--makedep-gen-missing-include} and
+@code{--makedep-gen-missing-sinclude}.
+@end table
+
+Note that the above @code{makedep-gen-missing-*} options assume that the
+missing files will ultimately not @code{include()} or @code{sinclude()}
+any additional files -- if they do, then these additional files will be
+missing from the generated dependency rules.
+
+The following options control the generation of ``phony'' targets for
+certain classes of dependencies. These dummy rules are used to work
+around errors @code{make} gives if you remove files without updating the
+@code{Makefile} to match. Dependencies that match one or more of these
+classes cause a single dummy rule to be generated for them:
+
+@table @code
+@itemx --makedep-phony-argfiles
+Causes @code{m4} to generate a ``phony'' target for each file that is
+specified on the command line.
+
+@itemx --makedep-phony-include
+Causes @code{m4} to generate a ``phony'' target for each file that is
+the subject of an @code{include()} macro. This option is analogous to
+the @code{-MP} option of @code{gcc}.
+
+@itemx --makedep-phony-sinclude
+Causes @code{m4} to generate a ``phony'' target for each file that is
+the subject of an @code{sinclude()} macro.
+
+@itemx --makedep-phony-all
+is equivalent to specifying all three options:
+@code{--makedep-phony-argfiles}
+@code{--makedep-phony-include}
+@code{--makedep-phony-sinclude}.
+@end table
+
@node Debugging options
@section Command line options for debugging
diff --git a/src/builtin.c b/src/builtin.c
index 8ff03e8..210b238 100644
--- a/src/builtin.c
+++ b/src/builtin.c
@@ -1334,10 +1334,11 @@ m4_changeword (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv)
`---------------------------------------------------------------*/
static void
-include (int argc, token_data **argv, bool silent)
+include (int argc, token_data **argv, bool silent, int ref_from)
{
FILE *fp;
char *name;
+ int fail;
if (bad_argc (argv[0], argc, 2, 2))
return;
@@ -1345,14 +1346,20 @@ include (int argc, token_data **argv, bool silent)
fp = m4_path_search (ARG (1), &name);
if (fp == NULL)
{
- if (!silent)
+ fail = !silent;
+ if ((makedep_gen_missing & ref_from) != 0)
{
- M4ERROR ((warning_status, errno, "cannot open `%s'", ARG (1)));
- retcode = EXIT_FAILURE;
+ record_dependency (ARG (1), ref_from);
+ fail = 0;
}
+ if (!silent)
+ M4ERROR ((warning_status, errno, "cannot open `%s'", ARG (1)));
+ if (fail)
+ retcode = EXIT_FAILURE;
return;
}
+ record_dependency (name, ref_from);
push_file (fp, name, true);
free (name);
}
@@ -1364,7 +1371,7 @@ include (int argc, token_data **argv, bool silent)
static void
m4_include (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv)
{
- include (argc, argv, false);
+ include (argc, argv, false, REF_INCLUDE);
}
/*----------------------------------.
@@ -1374,7 +1381,7 @@ m4_include (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv)
static void
m4_sinclude (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv)
{
- include (argc, argv, true);
+ include (argc, argv, true, REF_SINCLUDE);
}
/* More miscellaneous builtins -- "maketemp", "errprint", "__file__",
diff --git a/src/m4.c b/src/m4.c
index d7ad7cd..5b7f11e 100644
--- a/src/m4.c
+++ b/src/m4.c
@@ -72,6 +72,21 @@ int nesting_limit = 1024;
const char *user_word_regexp = "";
#endif
+/* Pathname of dependency file being made (--makedep=PATH). */
+static const char *makedep_path = NULL;
+
+/* Target for dependency rule being made (--makedep-target=TARGET). */
+static const char *makedep_target = NULL;
+
+/* Bitmask of places that will assume non-existent files are actually
+ generated, and so a dependency should be listed regardless
+ (--makedep-gen-missing-*). */
+int makedep_gen_missing = REF_NONE;
+
+/* Bitmask of which places files are referenced from that will trigger
+ phony rules to be generated (--makedep-phony-*). */
+static int makedep_phony = REF_NONE;
+
/* Global catchall for any errors that should affect final error status, but
where we try to continue execution in the meantime. */
int retcode;
@@ -229,6 +244,37 @@ Frozen state files:\n\
-F, --freeze-state=FILE produce a frozen state on FILE at end\n\
-R, --reload-state=FILE reload a frozen state from FILE at start\n\
", stdout);
+ puts ("");
+ fputs ("\
+Make dependency generation:\n\
+ --makedep=FILE write make dependency rule(s) into FILE\n\
+ --makedep-target=TARGET specify target of generated dependency rule\n\
+ The --makedep and --makedep-target options\n\
+ must be used together, either both present,\n\
+ or neither present.\n\
+ --makedep-gen-missing-argfiles\n\
+ --makedep-gen-missing-include\n\
+ --makedep-gen-missing-sinclude\n\
+ files that do not exist (on command line,\n\
+ via include(), or via sinclude(),\n\
+ respectively) are assumed to be generated\n\
+ files and become dependencies regardless.\n\
+ --makedep-gen-missing-all\n\
+ equivalent to --makedep-gen-missing-argfiles\n\
+ --makedep-gen-missing-include\n\
+ --makedep-gen-missing-sinclude\n\
+ --makedep-phony-argfiles\n\
+ --makedep-phony-include\n\
+ --makedep-phony-sinclude\n\
+ generate a \"phony\" target for each file\n\
+ that is specified on the command line, the\n\
+ subject of an include() macro, or the\n\
+ subject of an sinclude() macro,\n\
+ respectively.\n\
+ --makedep-phony-all equivalent to --makedep-phony-argfiles\n\
+ --makedep-phony-include\n\
+ --makedep-phony-sinclude\n\
+", stdout);
fputs ("\
\n\
Debugging:\n\
@@ -279,6 +325,16 @@ enum
DEBUGFILE_OPTION = CHAR_MAX + 1, /* no short opt */
DIVERSIONS_OPTION, /* not quite -N, because of message */
WARN_MACRO_SEQUENCE_OPTION, /* no short opt */
+ MAKEDEP_OPTION, /* no short opt */
+ MAKEDEP_TARGET_OPTION, /* no short opt */
+ MAKEDEP_GEN_MISSING_ARGFILES_OPTION, /* no short opt */
+ MAKEDEP_GEN_MISSING_INCLUDE_OPTION, /* no short opt */
+ MAKEDEP_GEN_MISSING_SINCLUDE_OPTION, /* no short opt */
+ MAKEDEP_GEN_MISSING_ALL_OPTION, /* no short opt */
+ MAKEDEP_PHONY_ARGFILES_OPTION, /* no short opt */
+ MAKEDEP_PHONY_INCLUDE_OPTION, /* no short opt */
+ MAKEDEP_PHONY_SINCLUDE_OPTION, /* no short opt */
+ MAKEDEP_PHONY_ALL_OPTION, /* no short opt */
HELP_OPTION, /* no short opt */
VERSION_OPTION /* no short opt */
@@ -310,6 +366,20 @@ static const struct option long_options[] =
{"debugfile", optional_argument, NULL, DEBUGFILE_OPTION},
{"diversions", required_argument, NULL, DIVERSIONS_OPTION},
{"warn-macro-sequence", optional_argument, NULL, WARN_MACRO_SEQUENCE_OPTION},
+ {"makedep", required_argument, NULL, MAKEDEP_OPTION},
+ {"makedep-target", required_argument, NULL, MAKEDEP_TARGET_OPTION},
+ {"makedep-gen-missing-argfiles", no_argument, NULL,
+ MAKEDEP_GEN_MISSING_ARGFILES_OPTION},
+ {"makedep-gen-missing-include", no_argument, NULL,
+ MAKEDEP_GEN_MISSING_INCLUDE_OPTION},
+ {"makedep-gen-missing-sinclude", no_argument, NULL,
+ MAKEDEP_GEN_MISSING_SINCLUDE_OPTION},
+ {"makedep-gen-missing-all", no_argument, NULL,
+ MAKEDEP_GEN_MISSING_ALL_OPTION},
+ {"makedep-phony-argfiles", no_argument, NULL, MAKEDEP_PHONY_ARGFILES_OPTION},
+ {"makedep-phony-include", no_argument, NULL, MAKEDEP_PHONY_INCLUDE_OPTION},
+ {"makedep-phony-sinclude", no_argument, NULL, MAKEDEP_PHONY_SINCLUDE_OPTION},
+ {"makedep-phony-all", no_argument, NULL, MAKEDEP_PHONY_ALL_OPTION},
{"help", no_argument, NULL, HELP_OPTION},
{"version", no_argument, NULL, VERSION_OPTION},
@@ -337,11 +407,15 @@ process_file (const char *name)
if (fp == NULL)
{
error (0, errno, _("cannot open `%s'"), name);
- /* Set the status to EXIT_FAILURE, even though we
- continue to process files after a missing file. */
- retcode = EXIT_FAILURE;
+ if ((makedep_gen_missing & REF_CMD_LINE) != 0)
+ record_dependency (name, REF_CMD_LINE);
+ else
+ /* Set the status to EXIT_FAILURE, even though we
+ continue to process files after a missing file. */
+ retcode = EXIT_FAILURE;
return;
}
+ record_dependency (full_name, REF_CMD_LINE);
push_file (fp, full_name, true);
free (full_name);
}
@@ -565,6 +639,50 @@ main (int argc, char *const *argv)
macro_sequence = optarg;
break;
+ case MAKEDEP_OPTION:
+ if (makedep_path != NULL)
+ usage (EXIT_FAILURE);
+ makedep_path = optarg;
+ break;
+
+ case MAKEDEP_TARGET_OPTION:
+ if (makedep_target != NULL)
+ usage (EXIT_FAILURE);
+ makedep_target = optarg;
+ break;
+
+ case MAKEDEP_GEN_MISSING_ARGFILES_OPTION:
+ makedep_gen_missing |= REF_CMD_LINE;
+ break;
+
+ case MAKEDEP_GEN_MISSING_INCLUDE_OPTION:
+ makedep_gen_missing |= REF_INCLUDE;
+ break;
+
+ case MAKEDEP_GEN_MISSING_SINCLUDE_OPTION:
+ makedep_gen_missing |= REF_SINCLUDE;
+ break;
+
+ case MAKEDEP_GEN_MISSING_ALL_OPTION:
+ makedep_gen_missing |= REF_ALL;
+ break;
+
+ case MAKEDEP_PHONY_ARGFILES_OPTION:
+ makedep_phony |= REF_CMD_LINE;
+ break;
+
+ case MAKEDEP_PHONY_INCLUDE_OPTION:
+ makedep_phony |= REF_INCLUDE;
+ break;
+
+ case MAKEDEP_PHONY_SINCLUDE_OPTION:
+ makedep_phony |= REF_SINCLUDE;
+ break;
+
+ case MAKEDEP_PHONY_ALL_OPTION:
+ makedep_phony |= REF_ALL;
+ break;
+
case VERSION_OPTION:
version_etc (stdout, PACKAGE, PACKAGE_NAME, VERSION, AUTHORS, NULL);
exit (EXIT_SUCCESS);
@@ -581,6 +699,32 @@ main (int argc, char *const *argv)
if (debugfile && !debug_set_output (debugfile))
M4ERROR ((warning_status, errno, "cannot set debug file `%s'", debugfile));
+ /* Verify mutual consistency of makedep options. */
+#define MKDEP_OPTS "--makedep and --makedep-target"
+ if ((makedep_path == NULL) && (makedep_target == NULL))
+ {
+ /* Makedep mode is NOT active. */
+ if (makedep_gen_missing != 0)
+ M4ERROR ((EXIT_FAILURE, 0,
+ "--makedep-gen-missing-* requires " MKDEP_OPTS));
+ if (makedep_phony != 0)
+ M4ERROR ((EXIT_FAILURE, 0,
+ "--makedep-phony-* requires " MKDEP_OPTS));
+ if ((makedep_gen_missing | makedep_phony) != 0)
+ exit (EXIT_FAILURE);
+ }
+ else if ((makedep_path != NULL) && (makedep_target != NULL))
+ {
+ /* Makedep mode is active. */
+ }
+ else
+ {
+ M4ERROR ((EXIT_FAILURE, 0,
+ MKDEP_OPTS " cannot be used independently."));
+ exit (EXIT_FAILURE);
+ }
+#undef MKDEP_OPTS
+
input_init ();
output_init ();
symtab_init ();
@@ -683,6 +827,8 @@ main (int argc, char *const *argv)
undivert_all ();
}
output_exit ();
+ if (makedep_path != NULL)
+ generate_make_dependencies (makedep_path, makedep_target, makedep_phony);
free_macro_sequence ();
exit (retcode);
}
diff --git a/src/m4.h b/src/m4.h
index 3708a7c..c9e5b6c 100644
--- a/src/m4.h
+++ b/src/m4.h
@@ -134,6 +134,14 @@ extern int nesting_limit; /* -L */
#ifdef ENABLE_CHANGEWORD
extern const char *user_word_regexp; /* -W */
#endif
+extern int makedep_gen_missing; /* --makedep-gen-missing-* */
+
+/* Bit masks indicating places a file is referenced from. */
+#define REF_CMD_LINE 0x01 /* File referenced from command line */
+#define REF_INCLUDE 0x02 /* File referenced from m4_include() */
+#define REF_SINCLUDE 0x04 /* File referenced from m4_sinclude() */
+#define REF_ALL 0x07 /* All of the above */
+#define REF_NONE 0x00 /* None of the above */
/* Error handling. */
extern int retcode;
@@ -442,6 +450,8 @@ void include_init (void);
void include_env_init (void);
void add_include_directory (const char *);
FILE *m4_path_search (const char *, char **);
+void record_dependency (const char *, int);
+void generate_make_dependencies (const char *, const char *, int);
/* File: eval.c --- expression evaluation. */
diff --git a/src/path.c b/src/path.c
index 0e3c6e5..9a2c06d 100644
--- a/src/path.c
+++ b/src/path.c
@@ -37,6 +37,17 @@ static includes *dir_list; /* the list of path directories */
static includes *dir_list_end; /* the end of same */
static int dir_max_length; /* length of longest directory name */
+struct dependency
+{
+ struct dependency *next; /* next in list of dependencies */
+ int ref_from; /* bit mask: places file referenced from */
+ char path [1]; /* pathname of this dependency */
+};
+
+typedef struct dependency dependency;
+
+static dependency *dependency_list; /* the list of dependencies */
+static dependency *dependency_list_end; /* the end of same */
void
include_init (void)
@@ -190,6 +201,73 @@ m4_path_search (const char *file, char **result)
return fp;
}
+void
+record_dependency (const char * path, int ref_from)
+{
+ dependency *dp;
+ size_t len, nbytes;
+ for (dp = dependency_list; dp != NULL; dp = dp->next)
+ if (strcmp (path, dp->path) == 0)
+ {
+ /* Remember all the places this file has been referenced from. */
+ dp->ref_from |= ref_from;
+ return;
+ }
+
+ len = strlen (path);
+ nbytes = offsetof(dependency, path[0]) + len + 1;
+ dp = xmalloc (nbytes);
+ dp->next = NULL;
+ dp->ref_from = ref_from;
+ strcpy (dp->path, path);
+
+ if (dependency_list_end == NULL)
+ dependency_list = dp;
+ else
+ dependency_list_end->next = dp;
+ dependency_list_end = dp;
+}
+
+void
+generate_make_dependencies (const char *path, const char *target, int phony)
+{
+ FILE *fp;
+ int col, len, maxcol;
+ dependency *dp;
+
+ fp = fopen (path, "w");
+ if (fp == NULL)
+ {
+ M4ERROR ((0, errno, "Unable to open `%s'.\n", path));
+ return;
+ }
+
+ /* Generate the main dependency rule. */
+ maxcol = 78;
+ fprintf (fp, "%s:", target);
+ col = strlen (target) + 1;
+ for (dp = dependency_list; dp != NULL; dp = dp->next)
+ {
+ len = 1 + strlen (dp->path);
+ if (col + len + 2 > maxcol) /* +2 is for trailing space/backslash */
+ {
+ fprintf (fp," \\\n ");
+ col = 1;
+ }
+ fprintf (fp, " %s", dp->path);
+ col += len;
+ }
+ fprintf (fp, "\n");
+
+ /* Generate phony targets for user-specified subset of dependencies. */
+ if (phony != 0)
+ for (dp = dependency_list; dp != NULL; dp = dp->next)
+ if ((dp->ref_from & phony) != 0)
+ fprintf (fp, "\n%s:\n", dp->path);
+
+ fclose (fp);
+}
+
#ifdef DEBUG_INCL
static void M4_GNUC_UNUSED
_______________________________________________
M4-patches mailing list
[email protected]
http://lists.gnu.org/mailman/listinfo/m4-patches