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 "master".

All of the regression tests have been incorporated into the
existing autotest facility in this version.

David Warme

 ChangeLog          |   17 ++
 NEWS               |    2 
 THANKS             |    2 
 doc/m4.texinfo     |  116 +++++++++++++++
 m4/m4.c            |   53 ++++++
 m4/m4module.h      |   22 ++
 m4/m4private.h     |   49 ++++++
 m4/path.c          |  142 ++++++++++++++++++
 modules/m4.c       |   19 +-
 src/main.c         |  113 ++++++++++++++
 tests/makedep.at   |  407 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 tests/testsuite.at |    3 
 12 files changed, 938 insertions(+), 7 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index d5f2ad7..f7ef3f3 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,20 @@
+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.
+	* m4/m4.c: Create/delete context->makedep.  Add module
+	interfaces to new makedep info.
+	* m4/m4module.h: Add new makedep interfaces.
+	* m4/m4private.h: Added context->makedep stuff.
+	* m4/path.c: Add new interfaces to record dependencies and
+	generate the dependency rules.
+	* modules/m4.c: Record dependencies for include() / sinclude().
+	* src/main.c: Add new options.  Record dependencies for files
+	specified on the command line.  Generate dependencies at end.
+	* NEWS: Added a blurb for this new feature.
+	* THANKS: Added us to the list.
+
 2010-03-02  Giuseppe Scrivano  <[email protected]>  (tiny change)
 
 	Support bootstrap --gnulib-srcdir=DIR.
diff --git a/NEWS b/NEWS
index 142e1e5..d951ed9 100644
--- a/NEWS
+++ b/NEWS
@@ -6,6 +6,8 @@ GNU M4 NEWS - History of user-visible changes.       -*- outline -*-
 NOTE - there are still a number of FIXMEs to resolve before this can be
 promoted to 2.0.
 
+** Add new capability to automatically generate make dependency rules.
+
 ** Building M4
 
 *** The build environment has been updated to modern GNU practices,
diff --git a/THANKS b/THANKS
index 7bd8b0a..fc8ce02 100644
--- a/THANKS
+++ b/THANKS
@@ -39,6 +39,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]
@@ -88,6 +89,7 @@ Konrad Schwarz          [email protected]
 Kristine Lund           [email protected]
 Krste Asanovic          [email protected]
 Lawson Chan             [email protected]
+Lorenzo Di Gregorio     [email protected]
 M. Levinson             [email protected]
 Marcus Daniels          [email protected]
 Marion Hakanson         [email protected]
diff --git a/doc/m4.texinfo b/doc/m4.texinfo
index fa0c6fd..b5283c2 100644
--- a/doc/m4.texinfo
+++ b/doc/m4.texinfo
@@ -162,6 +162,7 @@ Invoking @code{m4}
 * 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
 
@@ -602,6 +603,7 @@ version of @code{m4} introduces an option named @option{--default}).
 * 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
@@ -988,6 +990,120 @@ frozen @var{file}.  The options @option{-D}, @option{-U}, @option{-t},
 after state is reloaded, but before the input 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/m4/m4.c b/m4/m4.c
index 1ba5a6c..7ac78f7 100644
--- a/m4/m4.c
+++ b/m4/m4.c
@@ -44,6 +44,10 @@ m4_create (void)
     (m4__search_path_info *) xzalloc (sizeof *context->search_path);
   m4__include_init (context);
 
+  context->makedep =
+    (m4__makedep_info *) xzalloc (sizeof *context->makedep);
+  m4__makedep_init (context);
+
   return context;
 }
 
@@ -80,6 +84,8 @@ m4_delete (m4 *context)
       free (context->search_path);
     }
 
+  m4__makedep_delete (context);
+
   for (i = 0; i < context->stacks_count; i++)
     {
       assert (context->arg_stacks[i].refcount == 0
@@ -142,3 +148,50 @@ m4_context_opt_bit_table
         }
 m4_context_opt_bit_table
 #undef M4OPT_BIT
+
+/* Define the makedep accessor / manipulator functions. */
+
+int (CONC(m4_makedep_, gen_missing_opt_enabled)) (m4 *context, int mask)
+{
+  return (context->makedep->gen_missing & mask);
+}
+
+const char * (CONC(m4_makedep_, get_path)) (m4 * context)
+{
+  return (context->makedep->path);
+}
+
+void (CONC(m4_makedep_, set_path)) (m4 * context, const char *path)
+{
+  context->makedep->path = path;
+}
+
+const char * (CONC(m4_makedep_, get_target)) (m4 * context)
+{
+  return (context->makedep->target);
+}
+
+void (CONC(m4_makedep_, set_target)) (m4 * context, const char *target)
+{
+  context->makedep->target = target;
+}
+
+int (CONC(m4_makedep_, get_gen_missing_opt_bits)) (m4 * context)
+{
+  return (context->makedep->gen_missing);
+}
+
+void (CONC(m4_makedep_, set_gen_missing_opt_bits)) (m4 * context, int mask)
+{
+  context->makedep->gen_missing |= mask;
+}
+
+int (CONC(m4_makedep_, get_phony_opt_bits)) (m4 * context)
+{
+  return (context->makedep->phony);
+}
+
+void (CONC(m4_makedep_, set_phony_opt_bits)) (m4 * context, int mask)
+{
+  context->makedep->phony |= mask;
+}
diff --git a/m4/m4module.h b/m4/m4module.h
index fa245ec..f68ced9 100644
--- a/m4/m4module.h
+++ b/m4/m4module.h
@@ -560,6 +560,28 @@ extern FILE *   m4_path_search           (m4 *, const char *, char **);
 
 
 
+/* -- MAKEDEP MANAGEMENT --- */
+
+/* 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 */
+
+extern int              (m4_makedep_gen_missing_opt_enabled) (m4 *, int);
+extern const char *     (m4_makedep_get_path) (m4 *);
+extern void             (m4_makedep_set_path) (m4 *, const char *);
+extern const char *     (m4_makedep_get_target) (m4 *);
+extern void             (m4_makedep_set_target) (m4 *, const char *);
+extern int              (m4_makedep_get_gen_missing_opt_bits) (m4 *);
+extern void             (m4_makedep_set_gen_missing_opt_bits) (m4 *, int);
+extern int              (m4_makedep_get_phony_opt_bits) (m4 *);
+extern void             (m4_makedep_set_phony_opt_bits) (m4 *, int);
+extern void             m4_makedep_env_init (m4 *);
+extern void             m4_record_dependency (m4 *, const char *, int);
+extern void             m4_generate_make_dependencies (m4 *);
+
 #define obstack_chunk_alloc     xmalloc
 #define obstack_chunk_free      free
 
diff --git a/m4/m4private.h b/m4/m4private.h
index f7b47f8..2eb87ab 100644
--- a/m4/m4private.h
+++ b/m4/m4private.h
@@ -30,6 +30,7 @@
 
 typedef struct m4__search_path_info m4__search_path_info;
 typedef struct m4__macro_arg_stacks m4__macro_arg_stacks;
+typedef struct m4__makedep_info m4__makedep_info;
 typedef struct m4__symbol_chain m4__symbol_chain;
 
 typedef enum {
@@ -81,6 +82,7 @@ struct m4 {
   m4__macro_arg_stacks  *arg_stacks;    /* Array of current argv refs.  */
   size_t                stacks_count;   /* Size of arg_stacks.  */
   size_t                expansion_level;/* Macro call nesting level.  */
+  m4__makedep_info      *makedep;       /* Dependency info. */
 };
 
 #define M4_OPT_PREFIX_BUILTINS_BIT      (1 << 0) /* -P */
@@ -149,6 +151,7 @@ struct m4 {
 /* Accessors for private fields of m4, which have no function version
    exported in m4module.h.  */
 #define m4__get_search_path(C)                  ((C)->search_path)
+#define m4__get_makedep_info(C)                 ((C)->makedep)
 
 
 /* --- BUILTIN MANAGEMENT --- */
@@ -606,6 +609,52 @@ struct m4__search_path_info {
 extern void m4__include_init (m4 *);
 
 
+/* --- MAKEDEP MANAGEMENT --- */
+
+typedef struct m4__dependency m4__dependency;
+
+struct m4__dependency {
+  m4__dependency *next;         /* next in list of dependencies */
+  int ref_from;                 /* bit mask: places file referenced from */
+  char path [1];                /* pathname of this dependency */
+};
+
+struct m4__makedep_info {
+  m4__dependency *dep_list;     /* the list of dependencies */
+  m4__dependency *dep_list_end; /* the end of same */
+  const char    *path;          /* pathname of dependency rule being made */
+                                /* (--makedep=PATH) */
+  const char    *target;        /* target for dependency rule being made */
+                                /* (--makedep-target=TARGET) */
+  int           gen_missing;    /* Bitmask of places that assume non- */
+                                /* existent files are actually generated, */
+                                /* and so a dependency should be listed */
+                                /* regardless (--makedep-gen-missing-*) */
+  int           phony;          /* Bitmask of which places files are */
+                                /* referenced from that will trigger phony */
+                                /* rules to be generated (--makedep-phony-*) */
+};
+
+#define m4_makedep_gen_missing_opt_enabled(C, MASK) \
+                ((m4__get_makedep_info(C)->gen_missing & (MASK)) != 0)
+
+#define m4_makedep_get_path(C) (m4__get_makedep_info(C)->path)
+#define m4_makedep_set_path(C,PATH) (m4__get_makedep_info(C)->path = (PATH))
+#define m4_makedep_get_target(C) (m4__get_makedep_info(C)->target)
+#define m4_makedep_set_target(C,TGT) (m4__get_makedep_info(C)->target = (TGT))
+#define m4_makedep_get_gen_missing_opt_bits(C) \
+                (m4__get_makedep_info(C)->gen_missing)
+#define m4_makedep_set_gen_missing_opt_bits(C, MASK) \
+                (m4__get_makedep_info(C)->gen_missing |= (MASK))
+#define m4_makedep_get_phony_opt_bits(C) \
+                (m4__get_makedep_info(C)->phony)
+#define m4_makedep_set_phony_opt_bits(C, MASK) \
+                (m4__get_makedep_info(C)->phony |= (MASK))
+
+extern void m4__makedep_init (m4 *);
+extern void m4__makedep_delete (m4 *);
+
+
 /* Debugging the memory allocator.  */
 
 #if WITH_DMALLOC
diff --git a/m4/path.c b/m4/path.c
index 8ba1caa..d55f851 100644
--- a/m4/path.c
+++ b/m4/path.c
@@ -227,6 +227,148 @@ m4__include_init (m4 *context)
 }
 
 
+
+void
+m4__makedep_init (m4 *context)
+{
+  m4__makedep_info *info = m4__get_makedep_info (context);
+
+  assert (info);
+  info->gen_missing = REF_NONE;
+  info->phony       = REF_NONE;
+}
+
+void
+m4_makedep_env_init (m4 *context)
+{
+  m4__makedep_info *info = m4__get_makedep_info (context);
+
+  assert (info);
+
+  /* Verify mutual consistency of makedep options. */
+  if ((info->path == NULL) && (info->target == NULL))
+    {
+      /* Makedep mode is NOT active. */
+      if (info->gen_missing != REF_NONE)
+        m4_error (context, EXIT_FAILURE, 0, NULL,
+                  _("--makedep-gen-missing-* requires --makedep and --makedep-target"));
+      if (info->phony != REF_NONE)
+        m4_error (context, EXIT_FAILURE, 0, NULL,
+                  _("--makedep-phony-* requires --makedep and --makedep-target"));
+      if ((info->gen_missing | info->phony) != REF_NONE)
+        exit (EXIT_FAILURE);
+    }
+  else if ((info->path != NULL) && (info->target != NULL))
+    {
+      /* Makedep mode is active. */
+    }
+  else
+    {
+      m4_error (context, EXIT_FAILURE, 0, NULL,
+                _("--makedep and --makedep-target cannot be used independently."));
+      exit (EXIT_FAILURE);
+    }
+}
+
+void
+m4_record_dependency (m4 *context, const char * path, int ref_from)
+{
+  m4__makedep_info *info = m4__get_makedep_info (context);
+  m4__dependency *dp;
+  size_t len, nbytes;
+  assert (info);
+  for (dp = info->dep_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(m4__dependency, path[0]) + len + 1;
+  dp = xmalloc (nbytes);
+  dp->next = NULL;
+  dp->ref_from = ref_from;
+  strcpy (dp->path, path);
+
+  if (info->dep_list_end == NULL)
+    info->dep_list = dp;
+  else
+    info->dep_list_end->next = dp;
+  info->dep_list_end = dp;
+}
+
+void
+m4_generate_make_dependencies (m4 *context)
+{
+  m4__makedep_info *info = m4__get_makedep_info (context);
+  FILE *fp;
+  int col, len, maxcol;
+  m4__dependency *dp;
+
+  assert (info);
+
+  if (info->path == NULL)
+    return;     /* --makedep mode is not active. */
+  assert (info->target);
+
+  fp = fopen (info->path, "w");
+  if (fp == NULL)
+    {
+      m4_error (context, 0, errno, NULL, _("Unable to open %s"),
+                quotearg_style (locale_quoting_style, info->path));
+      return;
+    }
+
+  /* Generate the main dependency rule. */
+  maxcol = 78;
+  fprintf (fp, "%s:", info->target);
+  col = strlen (info->target) + 1;
+  for (dp = info->dep_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 (info->phony != 0)
+    for (dp = info->dep_list; dp != NULL; dp = dp->next)
+      if ((dp->ref_from & info->phony) != 0)
+        fprintf (fp, "\n%s:\n", dp->path);
+
+  fclose (fp);
+}
+
+void
+m4__makedep_delete (m4 *context)
+{
+  m4__makedep_info *info = m4__get_makedep_info (context);
+  m4__dependency *dp;
+
+  if (!info)
+    return;
+
+  while ((dp = info->dep_list) != NULL)
+    {
+      info->dep_list = dp->next;
+      free (dp);
+    }
+  if (info->path != NULL)
+    DELETE (info->path);
+  if (info->target != NULL)
+    DELETE (info->target);
+  free (info);
+}
+
+
 #ifdef DEBUG_INCL
 
 static void M4_GNUC_UNUSED
diff --git a/modules/m4.c b/modules/m4.c
index 9091d81..da0029a 100644
--- a/modules/m4.c
+++ b/modules/m4.c
@@ -99,7 +99,7 @@ typedef intmax_t number;
 typedef uintmax_t unumber;
 
 static void     include         (m4 *context, int argc, m4_macro_args *argv,
-                                 bool silent);
+                                 bool silent, int ref_from);
 static int      dumpdef_cmp_CB  (const void *s1, const void *s2);
 static void *   dump_symbol_CB  (m4_symbol_table *, const char *, size_t,
                                  m4_symbol *symbol, void *userdata);
@@ -664,7 +664,7 @@ M4BUILTIN_HANDLER (changecom)
    argument, if it exists.  Complain about inaccessible files iff
    SILENT is false.  */
 static void
-include (m4 *context, int argc, m4_macro_args *argv, bool silent)
+include (m4 *context, int argc, m4_macro_args *argv, bool silent, int ref_from)
 {
   FILE *fp;
   char *name = NULL;
@@ -678,12 +678,21 @@ include (m4 *context, int argc, m4_macro_args *argv, bool silent)
   fp = m4_path_search (context, arg, &name);
   if (fp == NULL)
     {
-      if (!silent)
+      if (m4_makedep_gen_missing_opt_enabled (context, ref_from))
+        {
+          m4_record_dependency (context, M4ARG (1), ref_from);
+          /* Give same message, but do not affect exit status. */
+          if (!silent)
+            m4_warn (context, errno, m4_arg_info (argv), _("cannot open %s"),
+                     quotearg_style (locale_quoting_style, arg));
+        }
+      else if (!silent)
         m4_error (context, 0, errno, m4_arg_info (argv), _("cannot open %s"),
                   quotearg_style (locale_quoting_style, arg));
       return;
     }
 
+  m4_record_dependency (context, name, ref_from);
   m4_push_file (context, fp, name, true);
   free (name);
 }
@@ -691,13 +700,13 @@ include (m4 *context, int argc, m4_macro_args *argv, bool silent)
 /* Include a file, complaining in case of errors.  */
 M4BUILTIN_HANDLER (include)
 {
-  include (context, argc, argv, false);
+  include (context, argc, argv, false, REF_INCLUDE);
 }
 
 /* Include a file, ignoring errors.  */
 M4BUILTIN_HANDLER (sinclude)
 {
-  include (context, argc, argv, true);
+  include (context, argc, argv, true, REF_SINCLUDE);
 }
 
 
diff --git a/src/main.c b/src/main.c
index 240e241..4437636 100644
--- a/src/main.c
+++ b/src/main.c
@@ -146,6 +146,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\
@@ -212,6 +242,16 @@ enum
   TRACEOFF_OPTION,                      /* no short opt */
   UNLOAD_MODULE_OPTION,                 /* no short opt */
   WORD_REGEXP_OPTION,                   /* deprecated, used to be -W */
+  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 */
@@ -260,6 +300,20 @@ static const struct option long_options[] =
   {"traceoff", required_argument, NULL, TRACEOFF_OPTION},
   {"unload-module", required_argument, NULL, UNLOAD_MODULE_OPTION},
   {"word-regexp", required_argument, NULL, WORD_REGEXP_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},
@@ -311,10 +365,19 @@ process_file (m4 *context, const char *name)
       FILE *fp = m4_path_search (context, name, &full_name);
       if (fp == NULL)
         {
-          m4_error (context, 0, errno, NULL, _("cannot open %s"),
-                    quotearg_style (locale_quoting_style, name));
+          if (m4_makedep_gen_missing_opt_enabled (context, REF_CMD_LINE))
+            {
+              m4_record_dependency (context, name, REF_CMD_LINE);
+              /* Same message, but warning only. */
+              m4_warn (context, errno, NULL, _("cannot open %s"),
+                       quotearg_style (locale_quoting_style, name));
+            }
+          else
+            m4_error (context, 0, errno, NULL, _("cannot open %s"),
+                      quotearg_style (locale_quoting_style, name));
           return;
         }
+      m4_record_dependency (context, full_name, REF_CMD_LINE);
       m4_push_file (context, fp, full_name, true);
       free (full_name);
     }
@@ -592,6 +655,50 @@ main (int argc, char *const *argv, char *const *envp)
           m4_set_safer_opt (context, true);
           break;
 
+        case MAKEDEP_OPTION:
+          if (m4_makedep_get_path (context) != NULL)
+            usage (EXIT_FAILURE);
+          m4_makedep_set_path (context, xstrdup (optarg));
+          break;
+
+        case MAKEDEP_TARGET_OPTION:
+          if (m4_makedep_get_target (context) != NULL)
+            usage (EXIT_FAILURE);
+          m4_makedep_set_target (context, xstrdup (optarg));
+          break;
+
+        case MAKEDEP_GEN_MISSING_ARGFILES_OPTION:
+          m4_makedep_set_gen_missing_opt_bits (context, REF_CMD_LINE);
+          break;
+
+        case MAKEDEP_GEN_MISSING_INCLUDE_OPTION:
+          m4_makedep_set_gen_missing_opt_bits (context, REF_INCLUDE);
+          break;
+
+        case MAKEDEP_GEN_MISSING_SINCLUDE_OPTION:
+          m4_makedep_set_gen_missing_opt_bits (context, REF_SINCLUDE);
+          break;
+
+        case MAKEDEP_GEN_MISSING_ALL_OPTION:
+          m4_makedep_set_gen_missing_opt_bits (context, REF_ALL);
+          break;
+
+        case MAKEDEP_PHONY_ARGFILES_OPTION:
+          m4_makedep_set_phony_opt_bits (context, REF_CMD_LINE);
+          break;
+
+        case MAKEDEP_PHONY_INCLUDE_OPTION:
+          m4_makedep_set_phony_opt_bits (context, REF_INCLUDE);
+          break;
+
+        case MAKEDEP_PHONY_SINCLUDE_OPTION:
+          m4_makedep_set_phony_opt_bits (context, REF_SINCLUDE);
+          break;
+
+        case MAKEDEP_PHONY_ALL_OPTION:
+          m4_makedep_set_phony_opt_bits (context, REF_ALL);
+          break;
+
         case VERSION_OPTION:
           version_etc (stdout, PACKAGE, PACKAGE_NAME, VERSION, AUTHORS, NULL);
           exit (EXIT_SUCCESS);
@@ -628,6 +735,7 @@ main (int argc, char *const *argv, char *const *envp)
   m4_input_init (context);
   m4_output_init (context);
   m4_include_env_init (context);
+  m4_makedep_env_init (context);
 
   if (frozen_file_to_read)
     reload_frozen_state (context, frozen_file_to_read);
@@ -794,6 +902,7 @@ main (int argc, char *const *argv, char *const *envp)
   m4__module_exit (context);
   m4_output_exit ();
   m4_input_exit ();
+  m4_generate_make_dependencies (context);
 
   /* Change debug stream back to stderr, to force flushing the debug
      stream and detect any errors it might have encountered.  The
diff --git a/tests/makedep.at b/tests/makedep.at
new file mode 100644
index 0000000..cf82ea9
--- /dev/null
+++ b/tests/makedep.at
@@ -0,0 +1,407 @@
+# Hand crafted tests for GNU M4.                               -*- Autotest -*-
+# Copyright (C) 2001, 2006, 2007, 2008, 2009, 2010 Free Software Foundation,
+# Inc.
+
+# This file is part of GNU M4.
+#
+# GNU M4 is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# GNU M4 is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+AT_BANNER([Makedep.])
+
+
+# Generate the following subdir and input files:
+#  tmp/ tmp/f1 tmp/f2 tmp/f3 tmp/f4 tmp/f5 tmp/f6 tmp/f7.
+#  The tmp/f1 file includes the others in various ways.
+m4_define([AT_MAKEDEP_CREATE_INPUT_FILES],
+[dnl lots of data to set up.
+AT_CHECK([mkdir tmp])
+AT_DATA([[tmp/f1]], [[File 1
+include(`f2')dnl
+include(`f3')dnl
+sinclude(`f4')dnl
+sinclude(`f5')dnl
+include(`f6')dnl
+sinclude(`f6')dnl
+include(`f7')dnl
+sinclude(`f7')dnl
+]])
+AT_DATA([[tmp/f2]], [[File 2
+]])
+AT_DATA([[tmp/f3]], [[File 3
+]])
+AT_DATA([[tmp/f4]], [[File 4
+]])
+AT_DATA([[tmp/f5]], [[File 5
+]])
+AT_DATA([[tmp/f6]], [[File 6
+]])
+AT_DATA([[tmp/f7]], [[File 7
+]])])
+
+# The standard -I option and input files we invoke m4 with.
+m4_define([AT_MAKEDEP_INFILES], [-Itmp tmp/f1 tmp/f3 tmp/f5 tmp/f7])
+
+# The standard arguments we invoke m4 with to exercise --makedep mode.
+m4_define([AT_MAKEDEP_ARGS],
+[--makedep=dep --makedep-target="t1 t2" AT_MAKEDEP_INFILES])
+
+# The standard output we expect to get.
+m4_define([AT_MAKEDEP_EXPECTED_OUTPUT], [File 1
+File 2
+File 3
+File 4
+File 5
+File 6
+File 6
+File 7
+File 7
+File 3
+File 5
+File 7
+])
+
+# The standard dependency rule we expect to get.
+m4_define([AT_MAKEDEP_EXPECTED_DEP],
+          [t1 t2: tmp/f1 tmp/f2 tmp/f3 tmp/f4 tmp/f5 tmp/f6 tmp/f7])
+
+# Verify that the m4 stdout is correct.
+m4_define([AT_MKDEP_CHECK_OUTPUT],
+[AT_CHECK([echo -n "AT_MAKEDEP_EXPECTED_OUTPUT()" | cmp -s stdout -])])
+
+# Verify that the m4 generated dependency is correct.
+m4_define([AT_MKDEP_CHECK_DEP],
+[AT_CHECK([echo "AT_MAKEDEP_EXPECTED_DEP()$1" | cmp -s dep -])])
+
+
+## --------------- ##
+## no makedep args ##
+## --------------- ##
+
+AT_SETUP([no makedep args])
+
+AT_MAKEDEP_CREATE_INPUT_FILES
+
+AT_CHECK_M4([AT_MAKEDEP_INFILES], [0],
+[AT_MAKEDEP_EXPECTED_OUTPUT])
+
+AT_CLEANUP
+
+
+## ------------------------------ ##
+## makedep without makedep-target ##
+## ------------------------------ ##
+
+AT_SETUP([makedep without makedep-target])
+
+AT_MAKEDEP_CREATE_INPUT_FILES
+
+AT_CHECK_M4([--makedep=dep AT_MAKEDEP_INFILES], [1], [],
+[m4: --makedep and --makedep-target cannot be used independently.
+])
+
+AT_CLEANUP
+
+
+## ------------------------------ ##
+## makedep-target without makedep ##
+## ------------------------------ ##
+
+AT_SETUP([makedep-target without makedep])
+
+AT_MAKEDEP_CREATE_INPUT_FILES
+
+AT_CHECK_M4([--makedep-target="t1 t2" AT_MAKEDEP_INFILES], [1], [],
+[m4: --makedep and --makedep-target cannot be used independently.
+])
+
+AT_CLEANUP
+
+
+## --------------------------- ##
+## basic makedep functionality ##
+## --------------------------- ##
+
+AT_SETUP([basic makedep functionality])
+
+AT_MAKEDEP_CREATE_INPUT_FILES
+
+AT_CHECK_M4([AT_MAKEDEP_ARGS], [0], [stdout])
+AT_MKDEP_CHECK_OUTPUT()
+AT_MKDEP_CHECK_DEP()
+
+AT_CLEANUP
+
+
+## -------------- ##
+## phony argfiles ##
+## -------------- ##
+
+AT_SETUP([phony argfiles])
+
+AT_MAKEDEP_CREATE_INPUT_FILES
+
+AT_CHECK_M4([--makedep-phony-argfiles AT_MAKEDEP_ARGS], [0], [stdout])
+AT_MKDEP_CHECK_OUTPUT()
+AT_MKDEP_CHECK_DEP([
+
+tmp/f1:
+
+tmp/f3:
+
+tmp/f5:
+
+tmp/f7:])
+
+AT_CLEANUP
+
+
+## ------------- ##
+## phony include ##
+## ------------- ##
+
+AT_SETUP([phony include])
+
+AT_MAKEDEP_CREATE_INPUT_FILES
+
+AT_CHECK_M4([--makedep-phony-include AT_MAKEDEP_ARGS], [0], [stdout])
+AT_MKDEP_CHECK_OUTPUT()
+AT_MKDEP_CHECK_DEP([
+
+tmp/f2:
+
+tmp/f3:
+
+tmp/f6:
+
+tmp/f7:])
+
+AT_CLEANUP
+
+
+## -------------- ##
+## phony sinclude ##
+## -------------- ##
+
+AT_SETUP([phony sinclude])
+
+AT_MAKEDEP_CREATE_INPUT_FILES
+
+AT_CHECK_M4([--makedep-phony-sinclude AT_MAKEDEP_ARGS], [0], [stdout])
+AT_MKDEP_CHECK_OUTPUT()
+AT_MKDEP_CHECK_DEP([
+
+tmp/f4:
+
+tmp/f5:
+
+tmp/f6:
+
+tmp/f7:])
+
+AT_CLEANUP
+
+
+## ---------------------- ##
+## phony argfiles+include ##
+## ---------------------- ##
+
+AT_SETUP([phony argfiles+include])
+
+AT_MAKEDEP_CREATE_INPUT_FILES
+
+AT_CHECK_M4([--makedep-phony-argfiles --makedep-phony-include AT_MAKEDEP_ARGS], [0], [stdout])
+AT_MKDEP_CHECK_OUTPUT()
+AT_MKDEP_CHECK_DEP([
+
+tmp/f1:
+
+tmp/f2:
+
+tmp/f3:
+
+tmp/f5:
+
+tmp/f6:
+
+tmp/f7:])
+
+AT_CLEANUP
+
+
+## ----------------------- ##
+## phony argfiles+sinclude ##
+## ----------------------- ##
+
+AT_SETUP([phony argfiles+sinclude])
+
+AT_MAKEDEP_CREATE_INPUT_FILES
+
+AT_CHECK_M4([--makedep-phony-argfiles --makedep-phony-sinclude AT_MAKEDEP_ARGS], [0], [stdout])
+AT_MKDEP_CHECK_OUTPUT()
+AT_MKDEP_CHECK_DEP([
+
+tmp/f1:
+
+tmp/f3:
+
+tmp/f4:
+
+tmp/f5:
+
+tmp/f6:
+
+tmp/f7:])
+
+AT_CLEANUP
+
+
+## ---------------------- ##
+## phony include+sinclude ##
+## ---------------------- ##
+
+AT_SETUP([phony include+sinclude])
+
+AT_MAKEDEP_CREATE_INPUT_FILES
+
+AT_CHECK_M4([--makedep-phony-include --makedep-phony-sinclude AT_MAKEDEP_ARGS], [0], [stdout])
+AT_MKDEP_CHECK_OUTPUT()
+AT_MKDEP_CHECK_DEP([
+
+tmp/f2:
+
+tmp/f3:
+
+tmp/f4:
+
+tmp/f5:
+
+tmp/f6:
+
+tmp/f7:])
+
+AT_CLEANUP
+
+
+## ------------------------------- ##
+## phony argfiles+include+sinclude ##
+## ------------------------------- ##
+
+AT_SETUP([phony argfiles+include+sinclude])
+
+AT_MAKEDEP_CREATE_INPUT_FILES
+
+AT_CHECK_M4([--makedep-phony-argfiles --makedep-phony-include --makedep-phony-sinclude AT_MAKEDEP_ARGS], [0], [stdout])
+AT_MKDEP_CHECK_OUTPUT()
+AT_MKDEP_CHECK_DEP([
+
+tmp/f1:
+
+tmp/f2:
+
+tmp/f3:
+
+tmp/f4:
+
+tmp/f5:
+
+tmp/f6:
+
+tmp/f7:])
+
+AT_CLEANUP
+
+
+## --------- ##
+## phony all ##
+## --------- ##
+
+AT_SETUP([phony all])
+
+AT_MAKEDEP_CREATE_INPUT_FILES
+
+AT_CHECK_M4([--makedep-phony-all AT_MAKEDEP_ARGS], [0], [stdout])
+AT_MKDEP_CHECK_OUTPUT()
+AT_MKDEP_CHECK_DEP([
+
+tmp/f1:
+
+tmp/f2:
+
+tmp/f3:
+
+tmp/f4:
+
+tmp/f5:
+
+tmp/f6:
+
+tmp/f7:])
+
+AT_CLEANUP
+
+
+## -------------------- ##
+## gen missing argfiles ##
+## -------------------- ##
+
+AT_SETUP([gen missing argfiles])
+
+AT_MAKEDEP_CREATE_INPUT_FILES
+
+AT_CHECK_M4([--makedep-gen-missing-argfiles AT_MAKEDEP_ARGS missing],
+            [0], [stdout],
+[m4: warning: cannot open `missing': No such file or directory
+])
+AT_MKDEP_CHECK_OUTPUT()
+AT_MKDEP_CHECK_DEP([ missing])
+
+AT_CLEANUP
+
+
+## ------------------- ##
+## gen missing include ##
+## ------------------- ##
+
+AT_SETUP([gen missing include])
+
+AT_MAKEDEP_CREATE_INPUT_FILES
+AT_DATA([[tmp/f8]], [[include(`missing')dnl
+]])
+
+AT_CHECK_M4([--makedep-gen-missing-include AT_MAKEDEP_ARGS tmp/f8],
+            [0], [stdout],
+[m4:tmp/f8:1: warning: include: cannot open `missing': No such file or directory
+])
+AT_MKDEP_CHECK_OUTPUT()
+AT_MKDEP_CHECK_DEP([ tmp/f8 missing])
+
+AT_CLEANUP
+
+
+## -------------------- ##
+## gen missing sinclude ##
+## -------------------- ##
+
+AT_SETUP([gen missing sinclude])
+
+AT_MAKEDEP_CREATE_INPUT_FILES
+AT_DATA([[tmp/f8]], [[sinclude(`missing')dnl
+]])
+
+AT_CHECK_M4([--makedep-gen-missing-sinclude AT_MAKEDEP_ARGS tmp/f8],
+            [0], [stdout])
+AT_MKDEP_CHECK_OUTPUT()
+AT_MKDEP_CHECK_DEP([ tmp/f8 missing])
+
+AT_CLEANUP
diff --git a/tests/testsuite.at b/tests/testsuite.at
index 0d65042..3bba0a7 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -181,6 +181,9 @@ m4_include([options.at])
 # Frozen files.
 m4_include([freeze.at])
 
+# Makedep options.
+m4_include([makedep.at])
+
 # Hand crafted tests.
 m4_include([others.at])
 
_______________________________________________
M4-patches mailing list
[email protected]
http://lists.gnu.org/mailman/listinfo/m4-patches

Reply via email to