Automatic generation of make dependency rules
"David M. Warme" <[email protected]> Fri, 11 Feb 2011 10:17:21 -0500
| Newsgroups | gmane.comp.gnu.m4.patches |
|---|---|
| Message-ID | <1297437441.21034.23.camel@d3ri01> |
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.6.
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
_______________________________________________
M4-patches mailing list
[email protected]
http://lists.gnu.org/mailman/listinfo/m4-patches
branch-1.6.patch
(text/x-patch, 23 KB)
ChangeLog | 14 +++++ NEWS | 2 THANKS | 2 doc/m4.texinfo | 116 +++++++++++++++++++++++++++++++++++++++++++ src/builtin.c | 17 ++++-- src/m4.c | 151 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-- src/m4.h | 10 +++ src/path.c | 79 +++++++++++++++++++++++++++++ 8 files changed, 384 insertions(+), 7 deletions(-) diff --git a/ChangeLog b/ChangeLog index 5e5889e..88e3d6d 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. + 2010-08-30 Eric Blake <[email protected]> Clean up compiler warnings. diff --git a/NEWS b/NEWS index 1afe792..468c677 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,8 @@ GNU M4 NEWS - User visible changes. * Noteworthy changes in Version 1.6 (????-??-??) [stable] Released by ????, based on git versions 1.4.10b.x-* and 1.5.* +** Add new capability to automatically generate make dependency rules. + ** Fix regressions introduced in 1.4.10b: *** Using `builtin' or `indir' to perform nested `shift' calls triggered an assertion failure (not present in 1.4.11). diff --git a/THANKS b/THANKS index 227654c..cc3e833 100644 --- a/THANKS +++ b/THANKS @@ -33,6 +33,7 @@ Dan Jacobson [email protected] Daniel Richard G. [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] @@ -73,6 +74,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 91b8e00..d91340d 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 @@ -570,6 +571,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 @@ -894,6 +896,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 bc0cde2..4651c5f 100644 --- a/src/builtin.c +++ b/src/builtin.c @@ -1447,7 +1447,7 @@ m4_changeword (struct obstack *obs M4_GNUC_UNUSED, int argc, argument, if it exists. Complain about inaccessible files iff SILENT is false. */ static void -include (int argc, macro_arguments *argv, bool silent) +include (int argc, macro_arguments *argv, bool silent, int ref_from) { const call_info *me = arg_info (argv); FILE *fp; @@ -1466,12 +1466,21 @@ include (int argc, macro_arguments *argv, bool silent) fp = m4_path_search (arg, &name); if (fp == NULL) { - if (!silent) + if ((makedep_gen_missing & ref_from) != 0) + { + record_dependency (ARG (1), ref_from); + /* Give same message, but do not affect exit status. */ + if (!silent) + m4_warn (errno, me, _("cannot open %s"), + quotearg_style (locale_quoting_style, arg)); + } + else if (!silent) m4_error (0, errno, me, _("cannot open %s"), quotearg_style (locale_quoting_style, arg)); return; } + record_dependency (name, ref_from); push_file (fp, name, true); free (name); } @@ -1481,7 +1490,7 @@ static void m4_include (struct obstack *obs M4_GNUC_UNUSED, int argc, macro_arguments *argv) { - include (argc, argv, false); + include (argc, argv, false, REF_INCLUDE); } /* Include a file, ignoring errors. */ @@ -1489,7 +1498,7 @@ static void m4_sinclude (struct obstack *obs M4_GNUC_UNUSED, int argc, macro_arguments *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 92eede1..f3d2676 100644 --- a/src/m4.c +++ b/src/m4.c @@ -62,6 +62,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; @@ -237,6 +252,36 @@ Frozen state files:\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 (_("\ Debugging:\n\ -d, --debug[=[-|+]FLAGS], --debugmode[=[-|+]FLAGS]\n\ @@ -288,6 +333,16 @@ enum { DEBUGFILE_OPTION = CHAR_MAX + 1, /* no short opt */ 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 */ @@ -319,6 +374,20 @@ static const struct option long_options[] = {"debugfile", optional_argument, NULL, DEBUGFILE_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}, @@ -349,11 +418,15 @@ process_file (const char *name) { error (0, errno, _("cannot open %s"), quotearg_style (locale_quoting_style, 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); } @@ -558,6 +631,50 @@ main (int argc, char *const *argv, char *const *envp M4_GNUC_UNUSED) 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); @@ -575,6 +692,32 @@ main (int argc, char *const *argv, char *const *envp M4_GNUC_UNUSED) m4_error (0, errno, NULL, _("cannot set debug file %s"), quotearg_style (locale_quoting_style, 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) + m4_error (EXIT_FAILURE, 0, NULL, + _("--makedep-gen-missing-* requires --makedep and --makedep-target")); + if (makedep_phony != 0) + m4_error (EXIT_FAILURE, 0, NULL, + _("--makedep-phony-* requires --makedep and --makedep-target")); + if ((makedep_gen_missing | makedep_phony) != 0) + exit (EXIT_FAILURE); + } + else if ((makedep_path != NULL) && (makedep_target != NULL)) + { + /* Makedep mode is active. */ + } + else + { + m4_error (EXIT_FAILURE, 0, NULL, + _("--makedep and --makedep-target cannot be used independently.")); + exit (EXIT_FAILURE); + } +#undef MKDEP_OPTS + input_init (); output_init (); symtab_init (hash_table_size); @@ -685,6 +828,8 @@ main (int argc, char *const *argv, char *const *envp M4_GNUC_UNUSED) undivert_all (); } output_exit (); + if (makedep_path != NULL) + generate_make_dependencies (makedep_path, makedep_target, makedep_phony); #ifndef NDEBUG /* Only spend time freeing memory to help isolate leaks; if assertions are disabled, save the time and exit now. */ diff --git a/src/m4.h b/src/m4.h index d333c24..dfc6bcb 100644 --- a/src/m4.h +++ b/src/m4.h @@ -141,6 +141,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. */ @@ -551,6 +559,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 4d2920a..2867d49 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) @@ -193,6 +204,74 @@ 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) + { + m4_error (0, errno, NULL, _("Unable to open %s"), + quotearg_style (locale_quoting_style, 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