[flang-commits] [clang] [flang] [flang][driver] Support Makefile dependency generation (PR #209379)
Thirumalai Shaktivel via flang-commits
flang-commits at lists.llvm.org
Mon Jul 13 22:46:02 PDT 2026
https://github.com/Thirumalai-Shaktivel created https://github.com/llvm/llvm-project/pull/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 : prescan only; 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 `-Eonly -dependency-file - -MT <target>`
to `-fc1`. `-Eonly` selects `RunPreprocessorOnly`, which runs only
the prescanner and produces no compiled output. The dependency file
is the sole result, written to stdout by default.
- `-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 prescan, `writeDependencyFile()` is called
unconditionally regardless of the action. It collects every file
opened during prescan — the source plus all `INCLUDE`/`#include`
files — via `GetIncludedFilePaths()`, then writes the
`target: source includes...` Make rule to the specified file, or
stdout if the path is `-`.
>From 95ff6cb5b546f40bb0378d2b53a068cc0a6f58ea Mon Sep 17 00:00:00 2001
From: Thirumalai-Shaktivel <thirumalaishaktivel at gmail.com>
Date: Wed, 24 Jun 2026 13:13:27 +0530
Subject: [PATCH 1/2] [flang][driver] Support Makefile dependency generation
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
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 : prescan only; 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 `-Eonly -dependency-file - -MT <target>`
to `-fc1`. `-Eonly` selects `RunPreprocessorOnly`, which runs only
the prescanner and produces no compiled output. The dependency file
is the sole result, written to stdout by default.
- `-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 prescan, `writeDependencyFile()` is called
unconditionally regardless of the action. It collects every file
opened during prescan — the source plus all `INCLUDE`/`#include`
files — via `GetIncludedFilePaths()`, then writes the
`target: source includes...` Make rule to the specified file, or
stdout if the path is `-`.
---
clang/include/clang/Basic/MakeSupport.h | 20 +++
.../clang/Frontend/DependencyOutputOptions.h | 4 +-
clang/include/clang/Options/Options.td | 18 ++-
clang/lib/Basic/MakeSupport.cpp | 144 ++++++++++++++++++
clang/lib/Driver/ToolChains/Flang.cpp | 107 ++++++++++++-
clang/lib/Frontend/DependencyFile.cpp | 137 +----------------
flang/docs/FlangDriver.md | 15 ++
flang/include/flang/Frontend/FrontendAction.h | 2 +
.../include/flang/Frontend/FrontendActions.h | 6 +
.../include/flang/Frontend/FrontendOptions.h | 9 ++
flang/include/flang/Parser/provenance.h | 5 +
flang/lib/Frontend/CompilerInvocation.cpp | 6 +
flang/lib/Frontend/FrontendAction.cpp | 42 +++++
.../ExecuteCompilerInvocation.cpp | 2 +
flang/lib/Parser/provenance.cpp | 15 ++
flang/test/Driver/dependency-file-gen-mod.f90 | 33 ++++
flang/test/Driver/dependency-file-gen.f90 | 31 ++++
flang/test/Driver/dependency-file.f90 | 106 +++++++++++++
18 files changed, 557 insertions(+), 145 deletions(-)
create mode 100644 flang/test/Driver/dependency-file-gen-mod.f90
create mode 100644 flang/test/Driver/dependency-file-gen.f90
create mode 100644 flang/test/Driver/dependency-file.f90
diff --git a/clang/include/clang/Basic/MakeSupport.h b/clang/include/clang/Basic/MakeSupport.h
index c663014ba7bcf..5cd5dd92c5476 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,25 @@ 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 above.
+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 4974209b8db30..767c9044e44e3 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]>,
HelpText<"Specify name of main file output to quote in depfile">;
def MT : JoinedOrSeparate<["-"], "MT">, Group<M_Group>,
- Visibility<[ClangOption, CC1Option]>,
+ Visibility<[ClangOption, CC1Option, FC1Option, FlangOption]>,
HelpText<"Specify name of main file output in depfile">,
MarshallingInfoStringVector<DependencyOutputOpts<"Targets">>;
def MV : Flag<["-"], "MV">, Group<M_Group>, Visibility<[ClangOption, CC1Option]>,
@@ -1528,7 +1533,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, FC1Option]>,
HelpText<"Filename (or -) to write dependency output to">,
MarshallingInfoString<DependencyOutputOpts<"OutputFile">>;
def dependency_dot : Separate<["-"], "dependency-dot">,
@@ -8529,8 +8534,6 @@ defm recovery_ast_type : BoolOption<"f", "recovery-ast-type",
let Group = Action_Group in {
-def Eonly : Flag<["-"], "Eonly">,
- HelpText<"Just run preprocessor, no output (for timings)">;
def dump_raw_tokens : Flag<["-"], "dump-raw-tokens">,
HelpText<"Lex file in raw mode and dump raw tokens">;
def analyze : Flag<["-"], "analyze">,
@@ -8673,6 +8676,11 @@ def finitial_counter_value_EQ : Joined<["-"], "finitial-counter-value=">,
} // let Visibility = [CC1Option]
+def Eonly : Flag<["-"], "Eonly">, Group<Action_Group>,
+ Visibility<[CC1Option, FC1Option]>,
+ HelpText<"Just run preprocessor, no output (for timings or dependency "
+ "generation)">;
+
//===----------------------------------------------------------------------===//
// Language Options
//===----------------------------------------------------------------------===//
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 different 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 d900037230f20..a69913fc4872e 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,100 @@ 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);
+
+ // Drop warnings for -M/-MM so they don't mix into the dependency output.
+ if (ArgM) {
+ CmdArgs.push_back("-w");
+ } else {
+ ArgM = ArgMD;
+ }
+
+ if (!ArgM) {
+ return;
+ }
+
+ // Emit "-MT <target>", quoting Make metacharacters when requested.
+ auto addTarget = [&](StringRef Target, bool Quote) {
+ SmallString<128> Quoted;
+ if (Quote) {
+ clang::quoteMakeTarget(Target, Quoted);
+ Target = Quoted;
+ }
+ CmdArgs.push_back("-MT");
+ 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) {
+ if (Arg *OutputOpt = Args.getLastArg(options::OPT_o);
+ 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,
@@ -1052,9 +1147,13 @@ 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("-Eonly");
+ } 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) {
@@ -1310,6 +1409,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 different 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..d2b2e44c6dd58 100644
--- a/flang/docs/FlangDriver.md
+++ b/flang/docs/FlangDriver.md
@@ -236,6 +236,21 @@ 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).
+
+There is one behavioural difference to be aware of regarding module
+dependencies (the `.mod` files brought in by `use` statements):
+
+* `-MD` and `-MMD` run a full compilation, so the `.mod` files opened during
+ semantic analysis are recorded and appear in the dependency rule.
+* `-M` and `-MM` run the prescanner only. They report textual dependencies
+ (`INCLUDE` and `#include`) but do **not** resolve `use` statements, so module
+ `.mod` files are not listed. If you need module dependencies, use `-MD` or
+ `-MMD`.
+
## 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/FrontendActions.h b/flang/include/flang/Frontend/FrontendActions.h
index f9a45bd6c0a56..926fbd28c357b 100644
--- a/flang/include/flang/Frontend/FrontendActions.h
+++ b/flang/include/flang/Frontend/FrontendActions.h
@@ -48,6 +48,12 @@ class PrintPreprocessedAction : public PrescanAction {
void executeAction() override;
};
+// Runs the prescanner only (for -Eonly). The dependency file, if requested, is
+// written by PrescanAction::beginSourceFileAction; no other output is produced.
+class RunPreprocessorOnlyAction : public PrescanAction {
+ void executeAction() override {}
+};
+
class DebugDumpProvenanceAction : public PrescanAction {
void executeAction() override;
};
diff --git a/flang/include/flang/Frontend/FrontendOptions.h b/flang/include/flang/Frontend/FrontendOptions.h
index 0bd2e621813ca..30968d7cd9618 100644
--- a/flang/include/flang/Frontend/FrontendOptions.h
+++ b/flang/include/flang/Frontend/FrontendOptions.h
@@ -31,6 +31,9 @@ enum ActionKind {
/// -E mode
PrintPreprocessedInput,
+ /// -Eonly mode: prescan only and write just the dependency file (-M, -MM)
+ RunPreprocessorOnly,
+
/// -fsyntax-only
ParseSyntaxOnly,
@@ -273,6 +276,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 79ad08353b64c..c9ab8a763d9a0 100644
--- a/flang/lib/Frontend/CompilerInvocation.cpp
+++ b/flang/lib/Frontend/CompilerInvocation.cpp
@@ -651,6 +651,9 @@ static bool parseFrontendArgs(FrontendOptions &opts, llvm::opt::ArgList &args,
case clang::options::OPT_E:
opts.programAction = PrintPreprocessedInput;
break;
+ case clang::options::OPT_Eonly:
+ opts.programAction = RunPreprocessorOnly;
+ break;
case clang::options::OPT_fsyntax_only:
opts.programAction = ParseSyntaxOnly;
break;
@@ -763,6 +766,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 e17f7ac42b5e6..8bd98705d530d 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/FrontendTool/ExecuteCompilerInvocation.cpp b/flang/lib/FrontendTool/ExecuteCompilerInvocation.cpp
index 0396889fc57a4..7a883eba941b1 100644
--- a/flang/lib/FrontendTool/ExecuteCompilerInvocation.cpp
+++ b/flang/lib/FrontendTool/ExecuteCompilerInvocation.cpp
@@ -40,6 +40,8 @@ createFrontendAction(CompilerInstance &ci) {
return std::make_unique<InputOutputTestAction>();
case PrintPreprocessedInput:
return std::make_unique<PrintPreprocessedAction>();
+ case RunPreprocessorOnly:
+ return std::make_unique<RunPreprocessorOnlyAction>();
case ParseSyntaxOnly:
return std::make_unique<ParseSyntaxOnlyAction>();
case EmitFIR:
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..b99d331833c35
--- /dev/null
+++ b/flang/test/Driver/dependency-file-gen-mod.f90
@@ -0,0 +1,33 @@
+! Module files used via `use` are real build dependencies. -MD/-MMD run a full
+! compile, so the .mod opened during semantics is recorded in the output. Plain
+! -M/-MM is prescan-only and does not resolve modules, so it lists includes only.
+
+! 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 stays prescan-only: no module dependency.
+! RUN: %flang -M %t/main.f90 -J %t 2>&1 | FileCheck %s --check-prefix=M
+! M: main.o:
+! M-NOT: 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..256aa5feccfac
--- /dev/null
+++ b/flang/test/Driver/dependency-file-gen.f90
@@ -0,0 +1,31 @@
+! Ensure the frontend lists files brought in by an INCLUDE line or by a
+! preprocessor #include directive in the generated dependency file.
+
+! 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
+
+!--- 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..18c3b8ddb70ac
--- /dev/null
+++ b/flang/test/Driver/dependency-file.f90
@@ -0,0 +1,106 @@
+! 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.
+
+!--------------------------------------------------------------------------
+! -M / -MM: emit only the dependencies (prescan-only -Eonly), 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: "-Eonly"
+! 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: "-Eonly"
+! 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: "-Eonly"
+! 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: "-Eonly"
+! 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
>From c5c851cd74b7e4c5f63040a84faf2b06f7340e54 Mon Sep 17 00:00:00 2001
From: Thirumalai-Shaktivel <thirumalaishaktivel at gmail.com>
Date: Wed, 24 Jun 2026 16:28:56 +0530
Subject: [PATCH 2/2] [flang][Test] Add more FC1 tests
---
flang/test/Driver/dependency-file-gen.f90 | 17 +++++++++++++++--
1 file changed, 15 insertions(+), 2 deletions(-)
diff --git a/flang/test/Driver/dependency-file-gen.f90 b/flang/test/Driver/dependency-file-gen.f90
index 256aa5feccfac..375c0b8cb28a1 100644
--- a/flang/test/Driver/dependency-file-gen.f90
+++ b/flang/test/Driver/dependency-file-gen.f90
@@ -1,5 +1,5 @@
-! Ensure the frontend lists files brought in by an INCLUDE line or by a
-! preprocessor #include directive in the generated dependency file.
+! Frontend (-fc1) dependency-file generation: list INCLUDE and #include files,
+! derive a default target, and support -Eonly (dependencies only, no source).
! RUN: rm -rf %t && split-file %s %t
@@ -17,6 +17,19 @@
! 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
+
+! -Eonly: run the prescanner only, writing the dependencies to stdout with no
+! preprocessed source.
+! RUN: %flang_fc1 -Eonly %t/use-include.f90 -dependency-file - -MT custom.o 2>&1 \
+! RUN: | FileCheck %s --check-prefix=EONLY
+! EONLY: custom.o:
+! EONLY: use-include.f90
+
!--- header.h
integer :: x
More information about the flang-commits
mailing list