[llvm] 4e22a6e - [llvm-rc] Add /showIncludes dependency reporting (#217102)

via llvm-commits llvm-commits at lists.llvm.org
Thu Sep 10 00:25:50 PDT 2026


Author: Oren Azad
Date: 2026-09-10T10:25:45+03:00
New Revision: 4e22a6ef80ac0ac234acca473706cc5cce17d6c4

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

LOG: [llvm-rc] Add /showIncludes dependency reporting (#217102)

Add `/showIncludes` support to `llvm-rc` so build systems can discover
the files a resource compile depends on. This enables Ninja and other
build systems to track and correctly recompile the `.rc` file when
inputs change.

Two kinds of dependencies are reported:

- Preprocessor header dependencies, by forwarding `--show-includes` to
the clang preprocessor.
- Resource files, (bitmaps, icons, manifests, etc) that are used in the
resource file.

The output format matches the `/showIncludes` format that cl.exe and
clang-cl.exe use, and that Ninja can consume natively via `deps=msvc`.

Assisted-by: Anthropic Claude Code (Opus 4.8)
Assisted-by: OpenAI Codex (GPT 5.6 Sol)

Added: 
    llvm/test/tools/llvm-rc/show-includes.test

Modified: 
    llvm/docs/ReleaseNotes.md
    llvm/test/tools/llvm-rc/helpmsg.test
    llvm/tools/llvm-rc/Opts.td
    llvm/tools/llvm-rc/ResourceFileWriter.cpp
    llvm/tools/llvm-rc/ResourceFileWriter.h
    llvm/tools/llvm-rc/llvm-rc.cpp

Removed: 
    


################################################################################
diff  --git a/llvm/docs/ReleaseNotes.md b/llvm/docs/ReleaseNotes.md
index 4718d47bf8c10..739cf86ad54bb 100644
--- a/llvm/docs/ReleaseNotes.md
+++ b/llvm/docs/ReleaseNotes.md
@@ -278,6 +278,9 @@ Makes programs 10x faster by doing Special New Thing.
 
 * llvm-mca no longer defaults -mcpu to "native"
 
+* llvm-rc now supports `/showIncludes` to report header and resource-file
+  dependencies in a format compatible with Ninja's `deps = msvc` mode.
+
 ### Changes to LLDB
 
 * `platform.plugin.wasm.runtime-args` now precede the port argument on the Wasm

diff  --git a/llvm/test/tools/llvm-rc/helpmsg.test b/llvm/test/tools/llvm-rc/helpmsg.test
index ecda426e3280a..6230ddb6e4667 100644
--- a/llvm/test/tools/llvm-rc/helpmsg.test
+++ b/llvm/test/tools/llvm-rc/helpmsg.test
@@ -17,6 +17,7 @@
 ; CHECK-NEXT:    /L <value>  Set the default language identifier.
 ; CHECK-NEXT:    /no-preprocess Don't try to preprocess the input file.
 ; CHECK-NEXT:    /N          Null-terminate all strings in the string table.
+; CHECK-NEXT:    /showIncludes Print cl.exe-style include information to stderr.
 ; CHECK-NEXT:    /U <value>  Undefine a symbol for the C preprocessor.
 ; CHECK-NEXT:    /V          Be verbose.
 ; CHECK-NEXT:    /X          Ignore 'include' variable.

diff  --git a/llvm/test/tools/llvm-rc/show-includes.test b/llvm/test/tools/llvm-rc/show-includes.test
new file mode 100644
index 0000000000000..d32bf3c6dc820
--- /dev/null
+++ b/llvm/test/tools/llvm-rc/show-includes.test
@@ -0,0 +1,26 @@
+; Verify /showIncludes forwards --show-includes and -sys-header-deps through to
+; clang cc1 so that all preprocessor-phase includes, including system headers,
+; are reported in MSVC-compatible form.
+; RUN: llvm-rc -### /showIncludes -- %p/Inputs/empty.rc | FileCheck --check-prefix=CMD %s
+; RUN: llvm-rc -### -- %p/Inputs/empty.rc | FileCheck --check-prefix=NOCMD %s
+
+; CMD: "-Xclang" "--show-includes" "-Xclang" "-sys-header-deps"
+; NOCMD-NOT: --show-includes
+; NOCMD-NOT: -sys-header-deps
+
+; Verify /showIncludes emits a "Note: including file:" line for resource files
+; (icons, bitmaps, etc.) loaded after parsing.
+; RUN: rm -f %t.res
+; RUN: llvm-rc -no-preprocess /showIncludes /FO %t.res -- %p/Inputs/include.rc \
+; RUN:   2> %t.includes
+; RUN: FileCheck --check-prefix=RES --input-file=%t.includes %s
+
+; RES: Note: including file: {{.*}}bitmap.bmp
+
+; Without /showIncludes, the note must not be emitted.
+; RUN: rm -f %t.res
+; RUN: llvm-rc -no-preprocess /FO %t.res -- %p/Inputs/include.rc 2> %t.no-includes
+; RUN: FileCheck --check-prefix=NORES --allow-empty \
+; RUN:   --input-file=%t.no-includes %s
+
+; NORES-NOT: Note: including file:

diff  --git a/llvm/tools/llvm-rc/Opts.td b/llvm/tools/llvm-rc/Opts.td
index 6d9c0e2601a46..ee16a2d51c392 100644
--- a/llvm/tools/llvm-rc/Opts.td
+++ b/llvm/tools/llvm-rc/Opts.td
@@ -42,6 +42,9 @@ def dry_run : F<"dry-run", "Don't compile the input; only try to parse it.">;
 
 def no_preprocess : F<"no-preprocess", "Don't try to preprocess the input file.">;
 
+def show_includes : F<"showIncludes", "Print cl.exe-style include information "
+                                      "to stderr.">;
+
 // Print (but do not run) the commands to run for preprocessing
 def _HASH_HASH_HASH : F_nodoc<"###">;
 

diff  --git a/llvm/tools/llvm-rc/ResourceFileWriter.cpp b/llvm/tools/llvm-rc/ResourceFileWriter.cpp
index 0c3c4abc6606c..fe88ce45bc878 100644
--- a/llvm/tools/llvm-rc/ResourceFileWriter.cpp
+++ b/llvm/tools/llvm-rc/ResourceFileWriter.cpp
@@ -19,6 +19,7 @@
 #include "llvm/Support/MemoryBuffer.h"
 #include "llvm/Support/Path.h"
 #include "llvm/Support/Process.h"
+#include "llvm/Support/raw_ostream.h"
 
 using namespace llvm::support;
 
@@ -1565,6 +1566,14 @@ ResourceFileWriter::loadFile(StringRef File) const {
   SmallString<128> Path;
   SmallString<128> Cwd;
 
+  auto Open = [&](StringRef Resolved) {
+    auto Buffer = MemoryBuffer::getFile(Resolved, /*IsText=*/false,
+                                        /*RequiresNullTerminator=*/false);
+    if (Buffer && Params.ShowIncludes)
+      errs() << "Note: including file: " << Resolved << "\n";
+    return errorOrToExpected(std::move(Buffer));
+  };
+
   // 0. The file path is absolute or has a root directory, so we shouldn't
   // try to append it on top of other base directories. (An absolute path
   // must have a root directory, but e.g. the path "\dir\file" on windows
@@ -1575,16 +1584,14 @@ ResourceFileWriter::loadFile(StringRef File) const {
   // properly though, so if using that to append paths below, this early
   // exception case could be removed.)
   if (sys::path::has_root_directory(File))
-    return errorOrToExpected(MemoryBuffer::getFile(
-        File, /*IsText=*/false, /*RequiresNullTerminator=*/false));
+    return Open(File);
 
   // 1. The current working directory.
   sys::fs::current_path(Cwd);
   Path.assign(Cwd.begin(), Cwd.end());
   sys::path::append(Path, File);
   if (sys::fs::exists(Path))
-    return errorOrToExpected(MemoryBuffer::getFile(
-        Path, /*IsText=*/false, /*RequiresNullTerminator=*/false));
+    return Open(Path);
 
   // 2. The directory of the input resource file, if it is 
diff erent from the
   // current working directory.
@@ -1592,22 +1599,19 @@ ResourceFileWriter::loadFile(StringRef File) const {
   Path.assign(InputFileDir.begin(), InputFileDir.end());
   sys::path::append(Path, File);
   if (sys::fs::exists(Path))
-    return errorOrToExpected(MemoryBuffer::getFile(
-        Path, /*IsText=*/false, /*RequiresNullTerminator=*/false));
+    return Open(Path);
 
   // 3. All of the include directories specified on the command line.
   for (StringRef ForceInclude : Params.Include) {
     Path.assign(ForceInclude.begin(), ForceInclude.end());
     sys::path::append(Path, File);
     if (sys::fs::exists(Path))
-      return errorOrToExpected(MemoryBuffer::getFile(
-          Path, /*IsText=*/false, /*RequiresNullTerminator=*/false));
+      return Open(Path);
   }
 
   if (!Params.NoInclude) {
     if (auto Result = llvm::sys::Process::FindInEnvPath("INCLUDE", File))
-      return errorOrToExpected(MemoryBuffer::getFile(
-          *Result, /*IsText=*/false, /*RequiresNullTerminator=*/false));
+      return Open(*Result);
   }
 
   return make_error<StringError>("error : file not found : " + Twine(File),

diff  --git a/llvm/tools/llvm-rc/ResourceFileWriter.h b/llvm/tools/llvm-rc/ResourceFileWriter.h
index a13af45512625..74dadfa13c9b6 100644
--- a/llvm/tools/llvm-rc/ResourceFileWriter.h
+++ b/llvm/tools/llvm-rc/ResourceFileWriter.h
@@ -41,6 +41,7 @@ struct WriterParams {
   bool NoInclude;                     // Ignore the INCLUDE variable.
   StringRef InputFilePath;            // The full path of the input file.
   int CodePage = CpAcp;               // The codepage for interpreting characters.
+  bool ShowIncludes = false;          // Report resource-file dependencies.
 };
 
 class ResourceFileWriter : public Visitor {

diff  --git a/llvm/tools/llvm-rc/llvm-rc.cpp b/llvm/tools/llvm-rc/llvm-rc.cpp
index 471a4467e6303..7afecf667fed0 100644
--- a/llvm/tools/llvm-rc/llvm-rc.cpp
+++ b/llvm/tools/llvm-rc/llvm-rc.cpp
@@ -267,6 +267,12 @@ void preprocess(StringRef Src, StringRef Dst, const RcOptions &Opts,
     }
   }
   llvm::append_range(Args, Opts.PreprocessArgs);
+  if (Opts.Params.ShowIncludes) {
+    Args.push_back("-Xclang");
+    Args.push_back("--show-includes");
+    Args.push_back("-Xclang");
+    Args.push_back("-sys-header-deps");
+  }
   Args.push_back(Src);
   Args.push_back("-o");
   Args.push_back(Dst);
@@ -547,6 +553,7 @@ RcOptions parseRcOptions(ArrayRef<const char *> ArgsArr,
   Opts.Preprocess = !InputArgs.hasArg(OPT_no_preprocess);
   Opts.Params.Include = InputArgs.getAllArgValues(OPT_includepath);
   Opts.Params.NoInclude = InputArgs.hasArg(OPT_noinclude);
+  Opts.Params.ShowIncludes = InputArgs.hasArg(OPT_show_includes);
   if (Opts.Params.NoInclude) {
     // Clear the INLCUDE variable for the external preprocessor
 #ifdef _WIN32


        


More information about the llvm-commits mailing list