From: David Warme <a136...@warme.net> 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 references, 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. Co-authored-by: Lorenzo Di Gregorio <lorenzo.digrego...@gmail.com> ---
This is NOT yet ready for inclusion - I insist that we NEED to have something that tests the functionality during 'make check' to ensure it doesn't regress. And there are probably other nitpicks that I will clean up (such as ChangeLog style, error message tweaks, figuring out the preferred way to do the flexible allocation of the file name in storage alongside 'struct dependency', and so forth). However, I'm reposting David's original patch as tentatively rebased to branch-1.6, to make it easier to continue to get this ready for inclusion before I cut the next beta release in preparation for an eventual 1.6. NEWS | 8 +++ THANKS | 2 + doc/m4.texi | 116 ++++++++++++++++++++++++++++++++++++++++ src/builtin.c | 16 ++++-- src/m4.c | 144 ++++++++++++++++++++++++++++++++++++++++++++++++-- src/m4.h | 10 ++++ src/path.c | 80 ++++++++++++++++++++++++++++ 7 files changed, 370 insertions(+), 6 deletions(-) diff --git a/NEWS b/NEWS index be3afe23..38b976a0 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,14 @@ 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. + This is enabled by the new command-line option pair '--makedep=FILE' + and '--makedep-target=TARGET' and further tuned by + '--makedep-gen-missing-argfiles', '--makedep-gen-missing-include', + '--makedep-gen-missing-sinclude', '--makedep-gen-missing-all', + '--makedep-phony-argfiles', '--makedep-phony-include', + '--makedep-phony-sinclude', and '--makedep-phony-all'. + ** 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 6898133e..8b5cce66 100644 --- a/THANKS +++ b/THANKS @@ -39,6 +39,7 @@ David Apps unlisted David Arnstein arnst...@pobox.com David J. MacKenzie d...@uunet.uu.net David Perlin da...@nanosoft.com +David Warme a136...@warme.net Elbert Pol elbert....@gmail.com Elias Benali el...@users.sourceforge.net Erez Zadok e...@cs.columbia.edu @@ -81,6 +82,7 @@ Konrad Schwarz konrad.schw...@siemens.com Kristine Lund l...@lpnaxp.in2p3.fr Krste Asanovic kr...@icsi.berkeley.edu Lawson Chan lawson.c...@tdsecurities.com +Lorenzo Di Gregorio lorenzo.digrego...@gmail.com Marijn Schouten unlisted Marion Hakanson hakan...@cse.ogi.edu Mark Seiden m...@seiden.com diff --git a/doc/m4.texi b/doc/m4.texi index 10314d42..25bf76db 100644 --- a/doc/m4.texi +++ b/doc/m4.texi @@ -145,6 +145,7 @@ Top * Operation modes:: Command line options for operation modes * Preprocessor features:: Command line options for preprocessor features +* Make dependency generation:: Generating make depencency rules * Limits control:: Command line options for limits control * Frozen state:: Command line options for frozen state * Debugging options:: Command line options for debugging @@ -577,6 +578,7 @@ Invoking m4 @menu * Operation modes:: Command line options for operation modes * Preprocessor features:: Command line options for preprocessor features +* Make dependency generation:: Generating make dependency rules * Limits control:: Command line options for limits control * Frozen state:: Command line options for frozen state * Debugging options:: Command line options for debugging @@ -793,6 +795,120 @@ Preprocessor features file names. @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 +@item --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}. + +@item --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 +@item --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. + +@item --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}. + +@item --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. + +@item --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 +@item --makedep-phony-argfiles +Causes @code{m4} to generate a ``phony'' target for each file that is +specified on the command line. + +@item --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}. + +@item --makedep-phony-sinclude +Causes @code{m4} to generate a ``phony'' target for each file that is +the subject of an @code{sinclude()} macro. + +@item --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 Limits control @section Command line options for limits control diff --git a/src/builtin.c b/src/builtin.c index 65e0ff72..3353e6db 100644 --- a/src/builtin.c +++ b/src/builtin.c @@ -1472,13 +1472,14 @@ m4_changecom (struct obstack *obs MAYBE_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; char *name; const char *arg; size_t len; + bool fail; if (bad_argc (me, argc, 1, 1)) return; @@ -1491,12 +1492,21 @@ include (int argc, macro_arguments *argv, bool silent) fp = m4_path_search (arg, false, &name); if (fp == NULL) { + fail = !silent; + if (makedep_gen_missing & ref_from) + { + record_dependency (arg, ref_from); + fail = false; + } if (!silent) m4_error (0, errno, me, _("cannot open %s"), quotearg_style (locale_quoting_style, arg)); + if (fail) + retcode = EXIT_FAILURE; return; } + record_dependency (name, ref_from); push_file (fp, name, true); free (name); } @@ -1505,7 +1515,7 @@ include (int argc, macro_arguments *argv, bool silent) static void m4_include (struct obstack *obs MAYBE_UNUSED, int argc, macro_arguments *argv) { - include (argc, argv, false); + include (argc, argv, false, REF_INCLUDE); } /* Include a file, ignoring errors. */ @@ -1513,7 +1523,7 @@ static void m4_sinclude (struct obstack *obs MAYBE_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 89a2ec2f..a9935bfd 100644 --- a/src/m4.c +++ b/src/m4.c @@ -65,6 +65,21 @@ int warning_status = 0; /* Artificial limit for expansion_level in macro.c. */ int nesting_limit = 1024; +/* 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; @@ -283,6 +298,37 @@ Frozen state files:\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); + puts (""); + fputs (_("\ Debugging:\n\ -d, --debug[=[-|+]FLAGS], --debugmode[=[-|+]FLAGS]\n\ set debug level (no FLAGS implies `+adeq')\n\ @@ -332,6 +378,16 @@ mismatch, or whatever value was passed to the m4exit macro.\n\ enum { DEBUGFILE_OPTION = CHAR_MAX + 1, /* 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 */ WARN_MACRO_SEQUENCE_OPTION, /* no short opt */ HELP_OPTION, /* no short opt */ @@ -365,6 +421,21 @@ static const struct option long_options[] = { {"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}, @@ -394,11 +465,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); } @@ -642,6 +717,50 @@ main (int argc, char *const *argv, char *const *envp MAYBE_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); @@ -659,6 +778,23 @@ main (int argc, char *const *argv, char *const *envp MAYBE_UNUSED) m4_error (0, errno, NULL, _("cannot set debug file %s"), quotearg_style (locale_quoting_style, debugfile)); + /* Verify mutual consistency of makedep options. */ + if (!makedep_path && !makedep_target) + { + /* Makedep mode is NOT active. */ + if (makedep_gen_missing != 0) + m4_error (EXIT_FAILURE, 0, NULL, + _("option %s requires --makedep and --makedep-target"), + "--makedep-gen-missing-*"); + if (makedep_phony != 0) + m4_error (EXIT_FAILURE, 0, NULL, + _("option %s requires --makedep and --makedep-target"), + "--makedep-phony-*"); + } + else if (!makedep_path != !makedep_target) + m4_error (EXIT_FAILURE, 0, NULL, + _("--makedep and --makedep-target cannot be used independently")); + input_init (); output_init (); symtab_init (hash_table_size); @@ -769,6 +905,8 @@ main (int argc, char *const *argv, char *const *envp MAYBE_UNUSED) undivert_all (); } output_exit (); + if (makedep_path) + 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 fa2cdf65..cad8ef5a 100644 --- a/src/m4.h +++ b/src/m4.h @@ -138,6 +138,14 @@ extern size_t max_debug_argument_length; /* -l */ extern int suppress_warnings; /* -Q */ extern int warning_status; /* -E */ extern int nesting_limit; /* -L */ +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. */ @@ -543,6 +551,8 @@ extern void include_init (void); extern void include_env_init (void); extern void add_include_directory (const char *); extern FILE *m4_path_search (const char *, bool, char **); +extern void record_dependency (const char *, int); +extern 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 1c9993d6..fa97d8f6 100644 --- a/src/path.c +++ b/src/path.c @@ -36,6 +36,18 @@ typedef struct includes includes; 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 @@ -194,6 +206,74 @@ m4_path_search (const char *file, bool binary, 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) + { + m4_error (0, errno, NULL, "unable to open %s", + quotearg_style_mem (locale_quoting_style, path, strlen (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; + } + fputs ("\n", fp); + + /* 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 MAYBE_UNUSED base-commit: ee7d9b7b8563610956d2e47c01c4cbf4d6f8b7b0 -- 2.49.0 _______________________________________________ M4-patches mailing list M4-patches@gnu.org https://lists.gnu.org/mailman/listinfo/m4-patches