Author: Thirumalai Shaktivel
Date: 2026-08-05T09:23:12+05:30
New Revision: da9ca9c5aeb29e5c1f1bfc27c746d03da1e1974d

URL: 
https://github.com/llvm/llvm-project/commit/da9ca9c5aeb29e5c1f1bfc27c746d03da1e1974d
DIFF: 
https://github.com/llvm/llvm-project/commit/da9ca9c5aeb29e5c1f1bfc27c746d03da1e1974d.diff

LOG: [flang][driver] Support Makefile dependency generation (#209379)

Implement the GCC/Clang dependency-file flags for flang, which it
previously rejected as unknown arguments: -M, -MM, -MD, -MMD, -MF,
-MT and -MQ.

Feature:
 -  -MD/-MMD : write a .d file alongside a normal compile
 -  -M/-MM   : run through semantics; emit deps to stdout by default
 -  -MF      : dependency-file path
 -  -MT/-MQ  : target name (verbatim / Make-quoted)
  (-M==-MM and -MD==-MMD; Fortran has no system/user header split.)

Design:
- `-M`/`-MM`: driver passes `-fsyntax-only -dependency-file - -MT
  <target>` to `-fc1`. Running through semantics resolves USE
  statements so module file dependencies are captured in the output.
  This deviates from clang's prescan-only behaviour for -M, but is
  intentional: unlike C/C++ #include, Fortran's USE statement is
  resolved at the semantic stage, not during preprocessing.

- `-MD`/`-MMD`: driver passes `-dependency-file foo.d -MT <target>`
  alongside the normal compile action. The object file is produced as
  usual, and the dependency file is written as a side effect.

- `-MF <path>` overrides where the dependency file is written.
  `-MT <target>` sets the target name (the part before the colon in
  the Make rule) verbatim. `-MQ` does the same but additionally
  quotes characters that are special to Make.

- In `-fc1`, `-dependency-file` and `-MT` are stored in the frontend
  options. After the action, `writeDependencyFile()` is called
  unconditionally. It collects every file opened during the action —
  the source plus all INCLUDE/#include/USE module files — via
  `GetIncludedFilePaths()`, then writes the
  `target: source includes...` Make rule to the specified file, or
  stdout if the path is `-`.

Added: 
    flang/test/Driver/dependency-file-gen-mod.f90
    flang/test/Driver/dependency-file-gen.f90
    flang/test/Driver/dependency-file.f90

Modified: 
    clang/include/clang/Basic/MakeSupport.h
    clang/include/clang/Frontend/DependencyOutputOptions.h
    clang/include/clang/Options/Options.td
    clang/lib/Basic/MakeSupport.cpp
    clang/lib/Driver/ToolChains/Flang.cpp
    clang/lib/Frontend/DependencyFile.cpp
    flang/docs/FlangDriver.md
    flang/include/flang/Frontend/FrontendAction.h
    flang/include/flang/Frontend/FrontendOptions.h
    flang/include/flang/Parser/provenance.h
    flang/lib/Frontend/CompilerInvocation.cpp
    flang/lib/Frontend/FrontendAction.cpp
    flang/lib/Parser/provenance.cpp

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/Basic/MakeSupport.h 
b/clang/include/clang/Basic/MakeSupport.h
index c663014ba7bcf..72e9500eae631 100644
--- a/clang/include/clang/Basic/MakeSupport.h
+++ b/clang/include/clang/Basic/MakeSupport.h
@@ -10,6 +10,7 @@
 #define LLVM_CLANG_BASIC_MAKESUPPORT_H
 
 #include "clang/Basic/LLVM.h"
+#include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/StringRef.h"
 
 namespace clang {
@@ -18,6 +19,26 @@ namespace clang {
 /// Only the characters '$', '#', ' ', '\t' are quoted.
 void quoteMakeTarget(StringRef Target, SmallVectorImpl<char> &Res);
 
+/// DependencyOutputFormat - Format for the compiler dependency file.
+enum class DependencyOutputFormat { Make, NMake };
+
+/// Write Make-style dependency output to the output stream, in the form:
+///   target1 target2 ...: prereq1 prereq2 ...
+///
+/// \param Targets The targets, already quoted for Make via quoteMakeTarget().
+/// \param Files The prerequisites; each is escaped for \p Format when written.
+/// \param Format Escape prerequisites for GNU Make or NMake.
+/// \param PhonyTargets If true, also emit an empty "prereq:" line for each
+///   prerequisite (except \p InputFileIndex), so later deleting a prerequisite
+///   doesn't break the build.
+/// \param InputFileIndex Index in \p Files of the main input, skipped for
+///   phony target emission.
+void printMakeDependencyFile(
+    llvm::raw_ostream &OS, llvm::ArrayRef<std::string> Targets,
+    llvm::ArrayRef<std::string> Files,
+    DependencyOutputFormat Format = DependencyOutputFormat::Make,
+    bool PhonyTargets = false, unsigned InputFileIndex = 0);
+
 } // namespace clang
 
 #endif // LLVM_CLANG_BASIC_MAKESUPPORT_H

diff  --git a/clang/include/clang/Frontend/DependencyOutputOptions.h 
b/clang/include/clang/Frontend/DependencyOutputOptions.h
index 98728f4d53b43..c2607b833a9e4 100644
--- a/clang/include/clang/Frontend/DependencyOutputOptions.h
+++ b/clang/include/clang/Frontend/DependencyOutputOptions.h
@@ -10,6 +10,7 @@
 #define LLVM_CLANG_FRONTEND_DEPENDENCYOUTPUTOPTIONS_H
 
 #include "clang/Basic/HeaderInclude.h"
+#include "clang/Basic/MakeSupport.h"
 #include <string>
 #include <vector>
 
@@ -18,9 +19,6 @@ namespace clang {
 /// ShowIncludesDestination - Destination for /showIncludes output.
 enum class ShowIncludesDestination { None, Stdout, Stderr };
 
-/// DependencyOutputFormat - Format for the compiler dependency file.
-enum class DependencyOutputFormat { Make, NMake };
-
 /// ExtraDepKind - The kind of extra dependency file.
 enum ExtraDepKind {
   EDK_SanitizeIgnorelist,

diff  --git a/clang/include/clang/Options/Options.td 
b/clang/include/clang/Options/Options.td
index 2467ebd0abe19..e859be134096a 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -807,14 +807,19 @@ def embed_dir_EQ : Joined<["--"], "embed-dir=">, 
Group<Preprocessor_Group>,
     Visibility<[ClangOption, CC1Option]>, MetaVarName<"<dir>">,
     HelpText<"Add directory to embed search path">;
 def MD : Flag<["-"], "MD">, Group<M_Group>,
+    Visibility<[ClangOption, FlangOption]>,
     HelpText<"Write a depfile containing user and system headers">;
 def MMD : Flag<["-"], "MMD">, Group<M_Group>,
+    Visibility<[ClangOption, FlangOption]>,
     HelpText<"Write a depfile containing user headers">;
 def M : Flag<["-"], "M">, Group<M_Group>,
+    Visibility<[ClangOption, FlangOption]>,
     HelpText<"Like -MD, but also implies -E and writes to stdout by default">;
 def MM : Flag<["-"], "MM">, Group<M_Group>,
+    Visibility<[ClangOption, FlangOption]>,
     HelpText<"Like -MMD, but also implies -E and writes to stdout by default">;
 def MF : JoinedOrSeparate<["-"], "MF">, Group<M_Group>,
+    Visibility<[ClangOption, FlangOption]>,
     HelpText<"Write depfile output from -MMD, -MD, -MM, or -M to <file>">,
     MetaVarName<"<file>">;
 def MG : Flag<["-"], "MG">, Group<M_Group>, Visibility<[ClangOption, 
CC1Option]>,
@@ -826,10 +831,10 @@ def MP : Flag<["-"], "MP">, Group<M_Group>, 
Visibility<[ClangOption, CC1Option]>
     HelpText<"Create phony target for each dependency (other than main file)">,
     MarshallingInfoFlag<DependencyOutputOpts<"UsePhonyTargets">>;
 def MQ : JoinedOrSeparate<["-"], "MQ">, Group<M_Group>,
-  Visibility<[ClangOption, CC1Option]>,
+  Visibility<[ClangOption, CC1Option, FlangOption, FC1Option]>,
     HelpText<"Specify name of main file output to quote in depfile">;
 def MT : JoinedOrSeparate<["-"], "MT">, Group<M_Group>,
-  Visibility<[ClangOption, CC1Option]>,
+  Visibility<[ClangOption, CC1Option, FlangOption, FC1Option]>,
     HelpText<"Specify name of main file output in depfile">,
     MarshallingInfoStringVector<DependencyOutputOpts<"Targets">>;
 def MV : Flag<["-"], "MV">, Group<M_Group>, Visibility<[ClangOption, 
CC1Option]>,
@@ -1573,7 +1578,7 @@ def dM : Flag<["-"], "dM">, Group<d_Group>, 
Visibility<[ClangOption, CC1Option,
   HelpText<"Print macro definitions in -E mode instead of normal output">;
 def dead__strip : Flag<["-"], "dead_strip">;
 def dependency_file : Separate<["-"], "dependency-file">,
-  Visibility<[ClangOption, CC1Option]>,
+  Visibility<[ClangOption, CC1Option, FlangOption, FC1Option]>,
   HelpText<"Filename (or -) to write dependency output to">,
   MarshallingInfoString<DependencyOutputOpts<"OutputFile">>;
 def dependency_dot : Separate<["-"], "dependency-dot">,

diff  --git a/clang/lib/Basic/MakeSupport.cpp b/clang/lib/Basic/MakeSupport.cpp
index 4ddfcc350410c..afc22493aff0d 100644
--- a/clang/lib/Basic/MakeSupport.cpp
+++ b/clang/lib/Basic/MakeSupport.cpp
@@ -7,6 +7,9 @@
 
//===----------------------------------------------------------------------===//
 
 #include "clang/Basic/MakeSupport.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/raw_ostream.h"
 
 void clang::quoteMakeTarget(StringRef Target, SmallVectorImpl<char> &Res) {
   for (unsigned i = 0, e = Target.size(); i != e; ++i) {
@@ -33,3 +36,144 @@ void clang::quoteMakeTarget(StringRef Target, 
SmallVectorImpl<char> &Res) {
     Res.push_back(Target[i]);
   }
 }
+
+/// Print the filename, with escaping or quoting that accommodates the three
+/// most likely tools that use dependency files: GNU Make, BSD Make, and
+/// NMake/Jom.
+///
+/// BSD Make is the simplest case: It does no escaping at all.  This means
+/// characters that are normally delimiters, i.e. space and # (the comment
+/// character) simply aren't supported in filenames.
+///
+/// GNU Make does allow space and # in filenames, but to avoid being treated
+/// as a delimiter or comment, these must be escaped with a backslash. Because
+/// backslash is itself the escape character, if a backslash appears in a
+/// filename, it should be escaped as well.  (As a special case, $ is escaped
+/// as $$, which is the normal Make way to handle the $ character.)
+/// For compatibility with BSD Make and historical practice, if GNU Make
+/// un-escapes characters in a filename but doesn't find a match, it will
+/// retry with the unmodified original string.
+///
+/// GCC tries to accommodate both Make formats by escaping any space or #
+/// characters in the original filename, but not escaping backslashes.  The
+/// apparent intent is so that filenames with backslashes will be handled
+/// correctly by BSD Make, and by GNU Make in its fallback mode of using the
+/// unmodified original string; filenames with # or space characters aren't
+/// supported by BSD Make at all, but will be handled correctly by GNU Make
+/// due to the escaping.
+///
+/// A corner case that GCC gets only partly right is when the original filename
+/// has a backslash immediately followed by space or #.  GNU Make would expect
+/// this backslash to be escaped; however GCC escapes the original backslash
+/// only when followed by space, not #.  It will therefore take a dependency
+/// from a directive such as
+///     #include "a\ b\#c.h"
+/// and emit it as
+///     a\\\ b\\#c.h
+/// which GNU Make will interpret as
+///     a\ b\
+/// followed by a comment. Failing to find this file, it will fall back to the
+/// original string, which probably doesn't exist either; in any case it won't
+/// find
+///     a\ b\#c.h
+/// which is the actual filename specified by the include directive.
+///
+/// Clang does what GCC does, rather than what GNU Make expects.
+///
+/// NMake/Jom has a 
diff erent set of scary characters, but wraps filespecs in
+/// double-quotes to avoid misinterpreting them; see
+/// https://msdn.microsoft.com/en-us/library/dd9y37ha.aspx for NMake info,
+/// 
https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
+/// for Windows file-naming info.
+static void printFilename(llvm::raw_ostream &OS, llvm::StringRef Filename,
+                          clang::DependencyOutputFormat OutputFormat) {
+  // Convert filename to platform native path
+  llvm::SmallString<256> NativePath;
+  llvm::sys::path::native(Filename, NativePath);
+
+  if (OutputFormat == clang::DependencyOutputFormat::NMake) {
+    // Add quotes if needed. These are the characters listed as "special" to
+    // NMake, that are legal in a Windows filespec, and that could cause
+    // misinterpretation of the dependency string.
+    if (NativePath.find_first_of(" #${}^!") != llvm::StringRef::npos)
+      OS << '\"' << NativePath << '\"';
+    else
+      OS << NativePath;
+    return;
+  }
+  assert(OutputFormat == clang::DependencyOutputFormat::Make);
+  for (unsigned i = 0, e = NativePath.size(); i != e; ++i) {
+    if (NativePath[i] == '#') // Handle '#' the broken gcc way.
+      OS << '\\';
+    else if (NativePath[i] == ' ') { // Handle space correctly.
+      OS << '\\';
+      unsigned j = i;
+      while (j > 0 && NativePath[--j] == '\\')
+        OS << '\\';
+    } else if (NativePath[i] == '$') // $ is escaped by $$.
+      OS << '$';
+    OS << NativePath[i];
+  }
+}
+
+void clang::printMakeDependencyFile(llvm::raw_ostream &OS,
+                                    llvm::ArrayRef<std::string> Targets,
+                                    llvm::ArrayRef<std::string> Files,
+                                    DependencyOutputFormat Format,
+                                    bool PhonyTargets,
+                                    unsigned InputFileIndex) {
+  // Write out the dependency targets, trying to avoid overly long
+  // lines when possible. We try our best to emit exactly the same
+  // dependency file as GCC>=10, assuming the included files are the
+  // same.
+  const unsigned MaxColumns = 75;
+  unsigned Columns = 0;
+
+  for (StringRef Target : Targets) {
+    unsigned N = Target.size();
+    if (Columns == 0) {
+      Columns += N;
+    } else if (Columns + N + 2 > MaxColumns) {
+      Columns = N + 2;
+      OS << " \\\n  ";
+    } else {
+      Columns += N + 1;
+      OS << ' ';
+    }
+    // Targets already quoted as needed.
+    OS << Target;
+  }
+
+  OS << ':';
+  Columns += 1;
+
+  // Now add each dependency in the order it was seen, but avoiding
+  // duplicates.
+  for (StringRef File : Files) {
+    if (File == "<stdin>")
+      continue;
+    // Start a new line if this would exceed the column limit. Make
+    // sure to leave space for a trailing " \" in case we need to
+    // break the line on the next iteration.
+    unsigned N = File.size();
+    if (Columns + (N + 1) + 2 > MaxColumns) {
+      OS << " \\\n ";
+      Columns = 2;
+    }
+    OS << ' ';
+    printFilename(OS, File, Format);
+    Columns += N + 1;
+  }
+  OS << '\n';
+
+  // Create phony targets if requested.
+  if (PhonyTargets && !Files.empty()) {
+    unsigned Index = 0;
+    for (auto I = Files.begin(), E = Files.end(); I != E; ++I) {
+      if (Index++ == InputFileIndex)
+        continue;
+      printFilename(OS, *I, Format);
+      OS << ":\n";
+    }
+  }
+}

diff  --git a/clang/lib/Driver/ToolChains/Flang.cpp 
b/clang/lib/Driver/ToolChains/Flang.cpp
index b223550da1983..7ea657ad49474 100644
--- a/clang/lib/Driver/ToolChains/Flang.cpp
+++ b/clang/lib/Driver/ToolChains/Flang.cpp
@@ -11,6 +11,7 @@
 #include "Cuda.h"
 
 #include "clang/Basic/CodeGenOptions.h"
+#include "clang/Basic/MakeSupport.h"
 #include "clang/Driver/CommonArgs.h"
 #include "clang/Options/OptionUtils.h"
 #include "clang/Options/Options.h"
@@ -35,6 +36,98 @@ static void addDashXForInput(const ArgList &Args, const 
InputInfo &Input,
   CmdArgs.push_back(types::getTypeName(Input.getType()));
 }
 
+// Translate the dependency-file options into the arguments understood by
+// `flang -fc1`. The options handled here:
+//   -M    Emit only the dependencies and skip code generation. They are
+//         written to stdout unless -MF redirects them.
+//   -MM   Treated identically to -M. The -MM/-M split exists to omit system
+//         headers, but Fortran has no notion of system vs user headers, so
+//         there is nothing for -MM to exclude.
+//   -MD   Compile normally and produce the object file, while also writing the
+//         dependency file. Its name defaults to the -o value, or the input
+//         file name when -o is absent, with the extension replaced by .d.
+//   -MMD  Treated identically to -MD, for the same reason -MM equals -M.
+//   -MF   Set the path of the dependency file to write.
+//   -MT   Set the dependency target name (the part before the colon).
+//   -MQ   Like -MT, but additionally quotes characters special to Make.
+static void renderDependencyGenerationOptions(Compilation &C,
+                                              const JobAction &JA,
+                                              const ArgList &Args,
+                                              const InputInfo &Output,
+                                              const InputInfoList &Inputs,
+                                              ArgStringList &CmdArgs) {
+  Arg *ArgM = Args.getLastArg(options::OPT_M, options::OPT_MM);
+  Arg *ArgMD = Args.getLastArg(options::OPT_MD, options::OPT_MMD);
+
+  if (!ArgM && !ArgMD)
+    return;
+
+  // Drop warnings for -M/-MM so they don't mix into the dependency output.
+  if (ArgM)
+    CmdArgs.push_back("-w");
+  else
+    ArgM = ArgMD;
+
+  // Emit "-MT <target>", quoting Make metacharacters when requested.
+  auto addTarget = [&](StringRef Target, bool Quote) {
+    CmdArgs.push_back("-MT");
+    if (Quote) {
+      SmallString<128> Quoted;
+      clang::quoteMakeTarget(Target, Quoted);
+      CmdArgs.push_back(Args.MakeArgString(Quoted));
+    } else {
+      CmdArgs.push_back(Args.MakeArgString(Target));
+    }
+  };
+
+  // Decide where to write the dependency file.
+  const char *DepFile;
+  if (Arg *MF = Args.getLastArg(options::OPT_MF)) {
+    // -MF gives the path explicitly.
+    DepFile = MF->getValue();
+    C.addFailureResultFile(DepFile, &JA);
+  } else if (Output.getType() == types::TY_Dependencies) {
+    // Plain -M/-MM: the dependency file is the output, so use its name
+    DepFile = Output.getFilename();
+  } else if (!ArgMD) {
+    // -M/-MM with no -o: write the dependencies to stdout.
+    DepFile = "-";
+  } else {
+    // -MD/-MMD: name it after -o, else the input, with a .d extension.
+    SmallString<128> P;
+    if (Arg *OutputOpt = Args.getLastArg(options::OPT_o))
+      P = OutputOpt->getValue();
+    else
+      P = llvm::sys::path::filename(Inputs[0].getBaseInput());
+    llvm::sys::path::replace_extension(P, "d");
+    DepFile = Args.MakeArgString(P);
+    C.addFailureResultFile(DepFile, &JA);
+  }
+  CmdArgs.push_back("-dependency-file");
+  CmdArgs.push_back(DepFile);
+
+  // Render the explicit target(s). -MT is verbatim, -MQ is Make-quoted.
+  bool HasTarget = false;
+  for (const Arg *A : Args.filtered(options::OPT_MT, options::OPT_MQ)) {
+    HasTarget = true;
+    A->claim();
+    addTarget(A->getValue(), A->getOption().matches(options::OPT_MQ));
+  }
+
+  // With no explicit target, default to the object file. In -M/-MM mode -o
+  // names the dependency file, not the target, so derive <base>.o instead.
+  if (!HasTarget) {
+    Arg *OutputOpt = Args.getLastArg(options::OPT_o);
+    if (OutputOpt && Output.getType() != types::TY_Dependencies) {
+      addTarget(OutputOpt->getValue(), /*Quote=*/true);
+    } else {
+      SmallString<128> P(llvm::sys::path::filename(Inputs[0].getBaseInput()));
+      llvm::sys::path::replace_extension(P, "o");
+      addTarget(P, /*Quote=*/true);
+    }
+  }
+}
+
 void Flang::addFortranDialectOptions(const ArgList &Args,
                                      ArgStringList &CmdArgs) const {
   Args.addAllArgs(CmdArgs, {options::OPT_ffixed_form,
@@ -1075,9 +1168,12 @@ void Flang::ConstructJob(Compilation &C, const JobAction 
&JA,
   CmdArgs.push_back(Args.MakeArgString(TripleStr));
 
   if (isa<PreprocessJobAction>(JA)) {
-    CmdArgs.push_back("-E");
-    if (Args.getLastArg(options::OPT_dM)) {
-      CmdArgs.push_back("-dM");
+    if (Output.getType() == types::TY_Dependencies) {
+      CmdArgs.push_back("-fsyntax-only");
+    } else {
+      CmdArgs.push_back("-E");
+      if (Args.getLastArg(options::OPT_dM))
+        CmdArgs.push_back("-dM");
     }
   } else if (isa<CompileJobAction>(JA) || isa<BackendJobAction>(JA)) {
     if (JA.getType() == types::TY_Nothing) {
@@ -1333,6 +1429,8 @@ void Flang::ConstructJob(Compilation &C, const JobAction 
&JA,
   if (Args.getLastArg(options::OPT_save_temps_EQ))
     Args.AddLastArg(CmdArgs, options::OPT_save_temps_EQ);
 
+  renderDependencyGenerationOptions(C, JA, Args, Output, Inputs, CmdArgs);
+
   addDashXForInput(Args, Input, CmdArgs);
 
   bool FRecordCmdLine = false;

diff  --git a/clang/lib/Frontend/DependencyFile.cpp 
b/clang/lib/Frontend/DependencyFile.cpp
index 00f4a54269cfa..5702d1dabcc8b 100644
--- a/clang/lib/Frontend/DependencyFile.cpp
+++ b/clang/lib/Frontend/DependencyFile.cpp
@@ -12,6 +12,7 @@
 
 #include "clang/Basic/DiagnosticFrontend.h"
 #include "clang/Basic/FileManager.h"
+#include "clang/Basic/MakeSupport.h"
 #include "clang/Basic/SourceManager.h"
 #include "clang/Frontend/DependencyOutputOptions.h"
 #include "clang/Frontend/Utils.h"
@@ -293,85 +294,6 @@ void 
DependencyFileGenerator::finishedMainFile(DiagnosticsEngine &Diags) {
   outputDependencyFile(Diags);
 }
 
-/// Print the filename, with escaping or quoting that accommodates the three
-/// most likely tools that use dependency files: GNU Make, BSD Make, and
-/// NMake/Jom.
-///
-/// BSD Make is the simplest case: It does no escaping at all.  This means
-/// characters that are normally delimiters, i.e. space and # (the comment
-/// character) simply aren't supported in filenames.
-///
-/// GNU Make does allow space and # in filenames, but to avoid being treated
-/// as a delimiter or comment, these must be escaped with a backslash. Because
-/// backslash is itself the escape character, if a backslash appears in a
-/// filename, it should be escaped as well.  (As a special case, $ is escaped
-/// as $$, which is the normal Make way to handle the $ character.)
-/// For compatibility with BSD Make and historical practice, if GNU Make
-/// un-escapes characters in a filename but doesn't find a match, it will
-/// retry with the unmodified original string.
-///
-/// GCC tries to accommodate both Make formats by escaping any space or #
-/// characters in the original filename, but not escaping backslashes.  The
-/// apparent intent is so that filenames with backslashes will be handled
-/// correctly by BSD Make, and by GNU Make in its fallback mode of using the
-/// unmodified original string; filenames with # or space characters aren't
-/// supported by BSD Make at all, but will be handled correctly by GNU Make
-/// due to the escaping.
-///
-/// A corner case that GCC gets only partly right is when the original filename
-/// has a backslash immediately followed by space or #.  GNU Make would expect
-/// this backslash to be escaped; however GCC escapes the original backslash
-/// only when followed by space, not #.  It will therefore take a dependency
-/// from a directive such as
-///     #include "a\ b\#c.h"
-/// and emit it as
-///     a\\\ b\\#c.h
-/// which GNU Make will interpret as
-///     a\ b\
-/// followed by a comment. Failing to find this file, it will fall back to the
-/// original string, which probably doesn't exist either; in any case it won't
-/// find
-///     a\ b\#c.h
-/// which is the actual filename specified by the include directive.
-///
-/// Clang does what GCC does, rather than what GNU Make expects.
-///
-/// NMake/Jom has a 
diff erent set of scary characters, but wraps filespecs in
-/// double-quotes to avoid misinterpreting them; see
-/// https://msdn.microsoft.com/en-us/library/dd9y37ha.aspx for NMake info,
-/// 
https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
-/// for Windows file-naming info.
-static void PrintFilename(raw_ostream &OS, StringRef Filename,
-                          DependencyOutputFormat OutputFormat) {
-  // Convert filename to platform native path
-  llvm::SmallString<256> NativePath;
-  llvm::sys::path::native(Filename, NativePath);
-
-  if (OutputFormat == DependencyOutputFormat::NMake) {
-    // Add quotes if needed. These are the characters listed as "special" to
-    // NMake, that are legal in a Windows filespec, and that could cause
-    // misinterpretation of the dependency string.
-    if (NativePath.find_first_of(" #${}^!") != StringRef::npos)
-      OS << '\"' << NativePath << '\"';
-    else
-      OS << NativePath;
-    return;
-  }
-  assert(OutputFormat == DependencyOutputFormat::Make);
-  for (unsigned i = 0, e = NativePath.size(); i != e; ++i) {
-    if (NativePath[i] == '#') // Handle '#' the broken gcc way.
-      OS << '\\';
-    else if (NativePath[i] == ' ') { // Handle space correctly.
-      OS << '\\';
-      unsigned j = i;
-      while (j > 0 && NativePath[--j] == '\\')
-        OS << '\\';
-    } else if (NativePath[i] == '$') // $ is escaped by $$.
-      OS << '$';
-    OS << NativePath[i];
-  }
-}
-
 void DependencyFileGenerator::outputDependencyFile(DiagnosticsEngine &Diags) {
   if (SeenMissingHeader) {
     llvm::sys::fs::remove(OutputFile);
@@ -389,59 +311,6 @@ void 
DependencyFileGenerator::outputDependencyFile(DiagnosticsEngine &Diags) {
 }
 
 void DependencyFileGenerator::outputDependencyFile(llvm::raw_ostream &OS) {
-  // Write out the dependency targets, trying to avoid overly long
-  // lines when possible. We try our best to emit exactly the same
-  // dependency file as GCC>=10, assuming the included files are the
-  // same.
-  const unsigned MaxColumns = 75;
-  unsigned Columns = 0;
-
-  for (StringRef Target : Targets) {
-    unsigned N = Target.size();
-    if (Columns == 0) {
-      Columns += N;
-    } else if (Columns + N + 2 > MaxColumns) {
-      Columns = N + 2;
-      OS << " \\\n  ";
-    } else {
-      Columns += N + 1;
-      OS << ' ';
-    }
-    // Targets already quoted as needed.
-    OS << Target;
-  }
-
-  OS << ':';
-  Columns += 1;
-
-  // Now add each dependency in the order it was seen, but avoiding
-  // duplicates.
-  ArrayRef<std::string> Files = getDependencies();
-  for (StringRef File : Files) {
-    if (File == "<stdin>")
-      continue;
-    // Start a new line if this would exceed the column limit. Make
-    // sure to leave space for a trailing " \" in case we need to
-    // break the line on the next iteration.
-    unsigned N = File.size();
-    if (Columns + (N + 1) + 2 > MaxColumns) {
-      OS << " \\\n ";
-      Columns = 2;
-    }
-    OS << ' ';
-    PrintFilename(OS, File, OutputFormat);
-    Columns += N + 1;
-  }
-  OS << '\n';
-
-  // Create phony targets if requested.
-  if (PhonyTarget && !Files.empty()) {
-    unsigned Index = 0;
-    for (auto I = Files.begin(), E = Files.end(); I != E; ++I) {
-      if (Index++ == InputFileIndex)
-        continue;
-      PrintFilename(OS, *I, OutputFormat);
-      OS << ":\n";
-    }
-  }
+  clang::printMakeDependencyFile(OS, Targets, getDependencies(), OutputFormat,
+                                 PhonyTarget, InputFileIndex);
 }

diff  --git a/flang/docs/FlangDriver.md b/flang/docs/FlangDriver.md
index 4edc99944ad44..21ae600711741 100644
--- a/flang/docs/FlangDriver.md
+++ b/flang/docs/FlangDriver.md
@@ -236,6 +236,23 @@ is `ParseSyntaxOnlyAction`, which corresponds to 
`-fsyntax-only`. In other
 words, `flang -fc1 <input-file>` is equivalent to `flang -fc1 -fsyntax-only
 <input-file>`.
 
+## Dependency File Generation
+Flang can emit Makefile-style dependency rules with `-M`, `-MM`, `-MD` and
+`-MMD` (paired with `-MF`, `-MT` and `-MQ` to control the output file and the
+rule target).
+
+Both `-M`/`-MM` and `-MD`/`-MMD` run through semantic analysis (equivalent to
+`-fsyntax-only`), so `use` statements are resolved and the `.mod` files opened
+during semantic analysis are recorded and appear in the dependency rule.
+
+The one behavioural 
diff erence between `-M`/`-MM` and `-MD`/`-MMD` is the
+output destination and whether object code is emitted:
+
+* `-MD` and `-MMD` run a full compilation and emit object code; the dependency
+  file is written alongside the object file.
+* `-M` and `-MM` skip code generation and write the dependency rule to stdout
+  (or to the file named by `-MF` / `-o`).
+
 ## Adding new Compiler Options
 Adding a new compiler option in Flang consists of two steps:
 * define the new option in a dedicated TableGen file,

diff  --git a/flang/include/flang/Frontend/FrontendAction.h 
b/flang/include/flang/Frontend/FrontendAction.h
index 1a800cc681cf9..4e2a9ead1a205 100644
--- a/flang/include/flang/Frontend/FrontendAction.h
+++ b/flang/include/flang/Frontend/FrontendAction.h
@@ -110,6 +110,8 @@ class FrontendAction {
   // Prescan the current input file. Return False if fatal errors are reported,
   // True otherwise.
   bool runPrescan();
+  // Write the dependency (.d) file if -dependency-file was given.
+  void writeDependencyFile();
   // Parse the current input file. Return False if fatal errors are reported,
   // True otherwise.
   bool runParse(bool emitMessages);

diff  --git a/flang/include/flang/Frontend/FrontendOptions.h 
b/flang/include/flang/Frontend/FrontendOptions.h
index 0bd2e621813ca..b0e4918d6b4a0 100644
--- a/flang/include/flang/Frontend/FrontendOptions.h
+++ b/flang/include/flang/Frontend/FrontendOptions.h
@@ -273,6 +273,12 @@ struct FrontendOptions {
   /// The output file, if any.
   std::string outputFile;
 
+  /// The dependency-file (.d) to write, if any (-dependency-file).
+  std::string dependencyOutputFile;
+
+  /// Target name(s) for the dependency rule (-MT), already quoted for Make.
+  std::vector<std::string> dependencyTargets;
+
   /// The frontend action to perform.
   frontend::ActionKind programAction = ParseSyntaxOnly;
 

diff  --git a/flang/include/flang/Parser/provenance.h 
b/flang/include/flang/Parser/provenance.h
index a9224b727fd05..75dda030f1e17 100644
--- a/flang/include/flang/Parser/provenance.h
+++ b/flang/include/flang/Parser/provenance.h
@@ -156,6 +156,11 @@ class AllSources {
       std::optional<std::string> &&prependPath = std::nullopt);
   const SourceFile *ReadStandardInput(llvm::raw_ostream &error);
 
+  // Returns the paths of every source file opened so far: the main file and 
any
+  // files pulled in by INCLUDE lines or #include directives. Paths are 
returned
+  // without duplicates, in the order they were first opened.
+  std::vector<std::string> GetIncludedFilePaths() const;
+
   ProvenanceRange AddIncludedFile(
       const SourceFile &, ProvenanceRange, bool isModule = false);
   ProvenanceRange AddMacroCall(

diff  --git a/flang/lib/Frontend/CompilerInvocation.cpp 
b/flang/lib/Frontend/CompilerInvocation.cpp
index a925ef696133a..da9877cf2e417 100644
--- a/flang/lib/Frontend/CompilerInvocation.cpp
+++ b/flang/lib/Frontend/CompilerInvocation.cpp
@@ -780,6 +780,9 @@ static bool parseFrontendArgs(FrontendOptions &opts, 
llvm::opt::ArgList &args,
   }
 
   opts.outputFile = args.getLastArgValue(clang::options::OPT_o);
+  opts.dependencyOutputFile =
+      args.getLastArgValue(clang::options::OPT_dependency_file);
+  opts.dependencyTargets = args.getAllArgValues(clang::options::OPT_MT);
   opts.showHelp = args.hasArg(clang::options::OPT_help);
   opts.showVersion = args.hasArg(clang::options::OPT_version);
   opts.printSupportedCPUs =

diff  --git a/flang/lib/Frontend/FrontendAction.cpp 
b/flang/lib/Frontend/FrontendAction.cpp
index 2974b2b562eba..a385e639a6d67 100644
--- a/flang/lib/Frontend/FrontendAction.cpp
+++ b/flang/lib/Frontend/FrontendAction.cpp
@@ -17,7 +17,11 @@
 #include "flang/Frontend/FrontendPluginRegistry.h"
 #include "flang/Parser/parsing.h"
 #include "clang/Basic/DiagnosticFrontend.h"
+#include "clang/Basic/MakeSupport.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/Path.h"
 #include "llvm/Support/VirtualFileSystem.h"
+#include "llvm/Support/raw_ostream.h"
 
 using namespace Fortran::frontend;
 
@@ -111,6 +115,9 @@ bool FrontendAction::beginSourceFile(CompilerInstance &ci,
     return false;
   }
 
+  // Written after semantics so -MD/-MMD also capture .mod files from `use`.
+  writeDependencyFile();
+
   return true;
 }
 
@@ -158,6 +165,41 @@ bool FrontendAction::runPrescan() {
   return !reportFatalScanningErrors();
 }
 
+void FrontendAction::writeDependencyFile() {
+  CompilerInstance &ci = this->getInstance();
+  const FrontendOptions &opts = ci.getFrontendOpts();
+  if (opts.dependencyOutputFile.empty())
+    return;
+
+  // Use the -MT targets, or derive one from the output/input file name.
+  std::vector<std::string> targets = opts.dependencyTargets;
+  if (targets.empty()) {
+    llvm::SmallString<128> target;
+    if (opts.outputFile.empty()) {
+      target = llvm::sys::path::filename(getCurrentFileOrBufferName());
+      llvm::sys::path::replace_extension(target, "o");
+    } else {
+      target = opts.outputFile;
+    }
+    llvm::SmallString<128> quoted;
+    clang::quoteMakeTarget(target, quoted);
+    targets.push_back(std::string(quoted));
+  }
+
+  std::error_code ec;
+  llvm::raw_fd_ostream os(opts.dependencyOutputFile, ec,
+                          llvm::sys::fs::OF_TextWithCRLF);
+  if (ec) {
+    unsigned diagID = ci.getDiagnostics().getCustomDiagID(
+        clang::DiagnosticsEngine::Error, "unable to open dependency file %0");
+    ci.getDiagnostics().Report(diagID) << opts.dependencyOutputFile;
+    return;
+  }
+
+  clang::printMakeDependencyFile(os, targets,
+                                 ci.getAllSources().GetIncludedFilePaths());
+}
+
 bool FrontendAction::runParse(bool emitMessages) {
   CompilerInstance &ci = this->getInstance();
 

diff  --git a/flang/lib/Parser/provenance.cpp b/flang/lib/Parser/provenance.cpp
index fe92aa7f64fb1..ab75fe145c707 100644
--- a/flang/lib/Parser/provenance.cpp
+++ b/flang/lib/Parser/provenance.cpp
@@ -206,6 +206,21 @@ const SourceFile 
*AllSources::ReadStandardInput(llvm::raw_ostream &error) {
   return nullptr;
 }
 
+std::vector<std::string> AllSources::GetIncludedFilePaths() const {
+  std::vector<std::string> paths;
+  std::set<std::string> seen;
+  for (const auto &source : ownedSourceFiles_) {
+    const std::string &path{source->path()};
+    if (path.empty() || path == "-") {
+      continue;
+    }
+    if (seen.insert(path).second) {
+      paths.push_back(path);
+    }
+  }
+  return paths;
+}
+
 ProvenanceRange AllSources::AddIncludedFile(
     const SourceFile &source, ProvenanceRange from, bool isModule) {
   ProvenanceRange covers{range_.NextAfter(), source.bytes()};

diff  --git a/flang/test/Driver/dependency-file-gen-mod.f90 
b/flang/test/Driver/dependency-file-gen-mod.f90
new file mode 100644
index 0000000000000..756fa519769a8
--- /dev/null
+++ b/flang/test/Driver/dependency-file-gen-mod.f90
@@ -0,0 +1,33 @@
+! Module files used via `use` are real build dependencies. Both -M/-MM and
+! -MD/-MMD run through semantics, so the .mod opened during semantics is
+! recorded in the output for all four flags.
+
+! RUN: rm -rf %t && split-file %s %t
+! RUN: %flang -fsyntax-only %t/mymod.f90 -J %t
+
+! -MD: the output lists the user module plus the intrinsic modules opened 
during
+! semantics (compiler-provided __fortran_builtins.mod / 
__fortran_type_info.mod).
+! RUN: %flang -MD -c %t/main.f90 -J %t -o %t/main.o
+! RUN: FileCheck %s --input-file=%t/main.d --check-prefix=MD
+! MD: main.o:
+! MD-DAG: main.f90
+! MD-DAG: mymod.mod
+! MD-DAG: __fortran_builtins.mod
+! MD-DAG: __fortran_type_info.mod
+
+! -M runs through semantics and also lists module dependencies.
+! RUN: %flang -M %t/main.f90 -J %t 2>&1 | FileCheck %s --check-prefix=M
+! M: main.o:
+! M-DAG: mymod.mod
+
+!--- mymod.f90
+module mymod
+  integer :: v = 1
+end module mymod
+
+!--- main.f90
+program main
+  use mymod
+  implicit none
+  print *, v
+end program main

diff  --git a/flang/test/Driver/dependency-file-gen.f90 
b/flang/test/Driver/dependency-file-gen.f90
new file mode 100644
index 0000000000000..232e319929b8f
--- /dev/null
+++ b/flang/test/Driver/dependency-file-gen.f90
@@ -0,0 +1,37 @@
+! Frontend (-fc1) dependency-file generation: list INCLUDE and #include files,
+! and derive a default target.
+
+! RUN: rm -rf %t && split-file %s %t
+
+! INCLUDE statement.
+! RUN: %flang_fc1 -fsyntax-only %t/use-include.f90 -dependency-file %t/inc.d 
-MT custom.o
+! RUN: FileCheck %s --input-file=%t/inc.d --check-prefix=INCLUDE
+! INCLUDE: custom.o:
+! INCLUDE: use-include.f90
+! INCLUDE: header.h
+
+! Preprocessor #include directive.
+! RUN: %flang_fc1 -fsyntax-only -cpp %t/use-cpp.F90 -dependency-file %t/cpp.d 
-MT custom.o
+! RUN: FileCheck %s --input-file=%t/cpp.d --check-prefix=CPP
+! CPP: custom.o:
+! CPP: use-cpp.F90
+! CPP: header.h
+
+! No -MT (and no -o): the target is derived from the input file name.
+! RUN: %flang_fc1 -fsyntax-only %t/use-include.f90 -dependency-file %t/def.d
+! RUN: FileCheck %s --input-file=%t/def.d --check-prefix=DEFAULT
+! DEFAULT: use-include.o:
+! DEFAULT: use-include.f90
+
+!--- header.h
+      integer :: x
+
+!--- use-include.f90
+program test
+  include 'header.h'
+end program test
+
+!--- use-cpp.F90
+program test
+#include "header.h"
+end program test

diff  --git a/flang/test/Driver/dependency-file.f90 
b/flang/test/Driver/dependency-file.f90
new file mode 100644
index 0000000000000..e43cf223693ec
--- /dev/null
+++ b/flang/test/Driver/dependency-file.f90
@@ -0,0 +1,108 @@
+! Test how the driver lowers -M, -MM, -MD, -MMD, -MF, -MT and -MQ to `flang
+! -fc1` and the generated dependency file. Fortran has no system/user header
+! split, so -M behaves like -MM and -MD like -MMD. Unlike clang, -M/-MM use
+! -fsyntax-only (not -Eonly) so that USE module dependencies are resolved.
+
+!--------------------------------------------------------------------------
+! -M / -MM: emit dependencies via -fsyntax-only (runs through semantics to
+! resolve USE statements), default to stdout, imply -w, and use the object
+! file as the default target.
+!--------------------------------------------------------------------------
+! RUN: %flang -### -M  %s 2>&1 | FileCheck %s --check-prefix=M
+! RUN: %flang -### -MM %s 2>&1 | FileCheck %s --check-prefix=M
+! M: "-fc1"
+! M-SAME: "-fsyntax-only"
+! M-SAME: "-w"
+! M-SAME: "-dependency-file" "-"
+! M-SAME: "-MT" "dependency-file.o"
+
+!--------------------------------------------------------------------------
+! -M with -o: -o names the dependency file, NOT the dependency target. The 
target
+! still defaults to the object file derived from the input.
+!--------------------------------------------------------------------------
+! RUN: %flang -### -M  -o named.d %s 2>&1 | FileCheck %s --check-prefix=M-O
+! RUN: %flang -### -MM -o named.d %s 2>&1 | FileCheck %s --check-prefix=M-O
+! M-O: "-fsyntax-only"
+! M-O-SAME: "-dependency-file" "named.d"
+! M-O-SAME: "-MT" "dependency-file.o"
+
+!--------------------------------------------------------------------------
+! -M with -o -: dependencies go to stdout, the target is still the object.
+!--------------------------------------------------------------------------
+! RUN: %flang -### -M -o - %s 2>&1 | FileCheck %s --check-prefix=M-O-STDOUT
+! M-O-STDOUT: "-dependency-file" "-"
+! M-O-STDOUT-SAME: "-MT" "dependency-file.o"
+
+!--------------------------------------------------------------------------
+! -MF redirects the dependency file; the target is unaffected.
+!--------------------------------------------------------------------------
+! RUN: %flang -### -M -MF custom.d %s 2>&1 | FileCheck %s --check-prefix=M-MF
+! M-MF: "-fsyntax-only"
+! M-MF-SAME: "-dependency-file" "custom.d"
+! M-MF-SAME: "-MT" "dependency-file.o"
+
+!--------------------------------------------------------------------------
+! -MT sets the target verbatim and replaces the default object target.
+!--------------------------------------------------------------------------
+! RUN: %flang -### -M -MT my_target %s 2>&1 | FileCheck %s --check-prefix=M-MT
+! M-MT: "-dependency-file" "-"
+! M-MT-SAME: "-MT" "my_target"
+! M-MT-NOT: "-MT" "dependency-file.o"
+
+!--------------------------------------------------------------------------
+! -MQ sets the target but quotes characters special to Make ($ -> $$), whereas
+! -MT writes the target verbatim. Checked on the written output.
+!--------------------------------------------------------------------------
+! RUN: %flang -M -MQ 'a$b' %s 2>&1 | FileCheck %s --check-prefix=MQ
+! MQ: a$$b:
+! RUN: %flang -M -MT 'a$b' %s 2>&1 | FileCheck %s --check-prefix=MT
+! MT: a$b:
+
+!--------------------------------------------------------------------------
+! -MD / -MMD: compile normally (no -Eonly) and also write the dependency
+! file, defaulting its name and the target to those derived from the input.
+!--------------------------------------------------------------------------
+! RUN: %flang -### -MD  -c %s 2>&1 | FileCheck %s --check-prefix=MD
+! RUN: %flang -### -MMD -c %s 2>&1 | FileCheck %s --check-prefix=MD
+! MD: "-fc1"
+! MD-NOT: "-fsyntax-only"
+! MD-SAME: "-dependency-file" "dependency-file.d"
+! MD-SAME: "-MT" "dependency-file.o"
+
+!--------------------------------------------------------------------------
+! -MD with -o: the dependency file name and target both follow -o.
+!--------------------------------------------------------------------------
+! RUN: %flang -### -MD -c -o out.o %s 2>&1 | FileCheck %s --check-prefix=MD-O
+! MD-O: "-dependency-file" "out.d"
+! MD-O-SAME: "-MT" "out.o"
+
+!--------------------------------------------------------------------------
+! -MD combined with -MF and -MQ.
+!--------------------------------------------------------------------------
+! RUN: %flang -### -MD -MF dep.d -MQ obj.o -c -o out.o %s 2>&1 | FileCheck %s 
--check-prefix=MD-MIX
+! MD-MIX: "-dependency-file" "dep.d"
+! MD-MIX-SAME: "-MT" "obj.o"
+
+!--------------------------------------------------------------------------
+! End-to-end: the dependencies are written and no preprocessed source leaks.
+!--------------------------------------------------------------------------
+! RUN: %flang -M %s -MT custom.o 2>&1 | FileCheck %s --check-prefix=DEPS
+! DEPS: custom.o:
+! DEPS: dependency-file.f90
+
+! End-to-end with -o: dependencies are written to the named file with the
+! object as the dependency target.
+! RUN: rm -rf %t && mkdir %t
+! RUN: %flang -M -o %t/deps.d %s
+! RUN: FileCheck %s --check-prefix=DEPS-O --input-file=%t/deps.d
+! DEPS-O: dependency-file.o:
+! DEPS-O: dependency-file.f90
+
+! End-to-end with -o -: dependencies go to stdout, object is the target.
+! RUN: %flang -M -o - %s 2>&1 | FileCheck %s --check-prefix=DEPS-STDOUT
+! DEPS-STDOUT: dependency-file.o:
+! DEPS-STDOUT: dependency-file.f90
+
+program test
+  x = 1
+end program test


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to