clang: Add support for C++ modules in deps
This adds support for {{module_deps}}. This expansion is to be used by
the "cxx" tool. When the deps of a target include dependencies on
modules (defined as a target that includes a .modulemap file, which is
built to a .pcm), module_deps will be filled out with the required flags
to depend on those modules.
The targets that are depended upon must currently have a (handwritten)
.modulemap file in their sources.
The dependency semantics (as described in NinjaCBinaryTargetWriterTest)
are:
- .modulemap are built to .pcm by cxx_module
- .pcm aren't linked against
- The .cc of a target that uses modules depend on the .pcm ("implicit"
dependency in ninja terminology)
- The .cc sources of targets that depend on a module have implicit
dependencies on the pcm of the dependent modules target as well.
- A .a or executable does not depend on the .pcm.
Bug: fuchsia:27276
Change-Id: I84c26975b93db71e5309ad607fce900fe2705f90
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/9602
Commit-Queue: Scott Graham <scottmg@chromium.org>
Reviewed-by: Brett Wilson <brettw@chromium.org>
Reviewed-by: Petr Hosek <phosek@google.com>
diff --git a/docs/reference.md b/docs/reference.md
index 168d64e..257f474 100644
--- a/docs/reference.md
+++ b/docs/reference.md
@@ -3674,6 +3674,11 @@
{{target_output_name}}, this is not affected by the "output_prefix" in
the tool or the "output_name" set on the target.
+ {{label_no_toolchain}}
+ The label of the current target, never including the toolchain
+ (otherwise, this is identical to {{label}}). This is used as the module
+ name when using .modulemap files.
+
{{output}}
The relative path and name of the output(s) of the current build step.
If there is more than one output, this will expand to a list of all of
diff --git a/src/gn/c_substitution_type.cc b/src/gn/c_substitution_type.cc
index d0c04c8..618e846 100644
--- a/src/gn/c_substitution_type.cc
+++ b/src/gn/c_substitution_type.cc
@@ -14,7 +14,8 @@
&CSubstitutionCFlagsC, &CSubstitutionCFlagsCc,
&CSubstitutionCFlagsObjC, &CSubstitutionCFlagsObjCc,
&CSubstitutionDefines, &CSubstitutionFrameworkDirs,
- &CSubstitutionIncludeDirs, &CSubstitutionSwiftModules,
+ &CSubstitutionIncludeDirs, &CSubstitutionModuleDeps,
+ &CSubstitutionSwiftModules,
&CSubstitutionLinkerInputs, &CSubstitutionLinkerInputsNewline,
&CSubstitutionLdFlags, &CSubstitutionLibs,
@@ -40,11 +41,12 @@
"framework_dirs"};
const Substitution CSubstitutionIncludeDirs = {"{{include_dirs}}",
"include_dirs"};
+const Substitution CSubstitutionModuleDeps = {"{{module_deps}}", "module_deps"};
// Valid for linker tools.
const Substitution CSubstitutionLinkerInputs = {"{{inputs}}", "in"};
const Substitution CSubstitutionLinkerInputsNewline = {"{{inputs_newline}}",
- "in_newline"};
+ "in_newline"};
const Substitution CSubstitutionLdFlags = {"{{ldflags}}", "ldflags"};
const Substitution CSubstitutionLibs = {"{{libs}}", "libs"};
const Substitution CSubstitutionSoLibs = {"{{solibs}}", "solibs"};
@@ -72,7 +74,7 @@
type == &CSubstitutionCFlagsCc || type == &CSubstitutionCFlagsObjC ||
type == &CSubstitutionCFlagsObjCc || type == &CSubstitutionDefines ||
type == &CSubstitutionFrameworkDirs ||
- type == &CSubstitutionIncludeDirs;
+ type == &CSubstitutionIncludeDirs || type == &CSubstitutionModuleDeps;
}
bool IsValidCompilerOutputsSubstitution(const Substitution* type) {
diff --git a/src/gn/c_substitution_type.h b/src/gn/c_substitution_type.h
index 63eb627..e4255fd 100644
--- a/src/gn/c_substitution_type.h
+++ b/src/gn/c_substitution_type.h
@@ -23,6 +23,7 @@
extern const Substitution CSubstitutionDefines;
extern const Substitution CSubstitutionFrameworkDirs;
extern const Substitution CSubstitutionIncludeDirs;
+extern const Substitution CSubstitutionModuleDeps;
// Valid for linker tools.
extern const Substitution CSubstitutionLinkerInputs;
diff --git a/src/gn/compile_commands_writer.cc b/src/gn/compile_commands_writer.cc
index 05e78d0..9f8ef63 100644
--- a/src/gn/compile_commands_writer.cc
+++ b/src/gn/compile_commands_writer.cc
@@ -187,6 +187,7 @@
out << flags.cflags_objcc;
} else if (range.type == &SubstitutionLabel ||
range.type == &SubstitutionLabelName ||
+ range.type == &SubstitutionLabelNoToolchain ||
range.type == &SubstitutionRootGenDir ||
range.type == &SubstitutionRootOutDir ||
range.type == &SubstitutionTargetGenDir ||
diff --git a/src/gn/function_toolchain.cc b/src/gn/function_toolchain.cc
index 1288511..52ac8f7 100644
--- a/src/gn/function_toolchain.cc
+++ b/src/gn/function_toolchain.cc
@@ -603,6 +603,11 @@
{{target_output_name}}, this is not affected by the "output_prefix" in
the tool or the "output_name" set on the target.
+ {{label_no_toolchain}}
+ The label of the current target, never including the toolchain
+ (otherwise, this is identical to {{label}}). This is used as the module
+ name when using .modulemap files.
+
{{output}}
The relative path and name of the output(s) of the current build step.
If there is more than one output, this will expand to a list of all of
diff --git a/src/gn/ninja_c_binary_target_writer.cc b/src/gn/ninja_c_binary_target_writer.cc
index 62edd4f..5f4a14b 100644
--- a/src/gn/ninja_c_binary_target_writer.cc
+++ b/src/gn/ninja_c_binary_target_writer.cc
@@ -28,6 +28,22 @@
#include "gn/substitution_writer.h"
#include "gn/target.h"
+struct ModuleDep {
+ ModuleDep(const SourceFile* modulemap,
+ const std::string& module_name,
+ const OutputFile& pcm)
+ : modulemap(modulemap), module_name(module_name), pcm(pcm) {}
+
+ // The input module.modulemap source file.
+ const SourceFile* modulemap;
+
+ // The internal module name, in GN this is the target's label.
+ std::string module_name;
+
+ // The compiled version of the module.
+ OutputFile pcm;
+};
+
namespace {
// Returns the proper escape options for writing compiler and linker flags.
@@ -52,6 +68,49 @@
return "";
}
+const SourceFile* GetModuleMapFromTargetSources(const Target* target) {
+ for (const SourceFile& sf : target->sources()) {
+ if (sf.type() == SourceFile::SOURCE_MODULEMAP) {
+ return &sf;
+ }
+ }
+ return nullptr;
+}
+
+std::vector<ModuleDep> GetModuleDepsInformation(const Target* target) {
+ std::vector<ModuleDep> ret;
+
+ auto add = [&ret](const Target* t) {
+ const SourceFile* modulemap = GetModuleMapFromTargetSources(t);
+ CHECK(modulemap);
+
+ std::string label;
+ CHECK(SubstitutionWriter::GetTargetSubstitution(
+ t, &SubstitutionLabelNoToolchain, &label));
+
+ const char* tool_type;
+ std::vector<OutputFile> modulemap_outputs;
+ CHECK(
+ t->GetOutputFilesForSource(*modulemap, &tool_type, &modulemap_outputs));
+ // Must be only one .pcm from .modulemap.
+ CHECK(modulemap_outputs.size() == 1u);
+ ret.emplace_back(modulemap, label, modulemap_outputs[0]);
+ };
+
+ if (target->source_types_used().Get(SourceFile::SOURCE_MODULEMAP)) {
+ add(target);
+ }
+
+ for (const auto& pair: target->GetDeps(Target::DEPS_LINKED)) {
+ // Having a .modulemap source means that the dependency is modularized.
+ if (pair.ptr->source_types_used().Get(SourceFile::SOURCE_MODULEMAP)) {
+ add(pair.ptr);
+ }
+ }
+
+ return ret;
+}
+
} // namespace
NinjaCBinaryTargetWriter::NinjaCBinaryTargetWriter(const Target* target,
@@ -62,7 +121,9 @@
NinjaCBinaryTargetWriter::~NinjaCBinaryTargetWriter() = default;
void NinjaCBinaryTargetWriter::Run() {
- WriteCompilerVars();
+ std::vector<ModuleDep> module_dep_info = GetModuleDepsInformation(target_);
+
+ WriteCompilerVars(module_dep_info);
size_t num_stamp_uses = target_->sources().size();
@@ -127,8 +188,8 @@
std::vector<OutputFile> obj_files;
std::vector<SourceFile> other_files;
if (!target_->source_types_used().SwiftSourceUsed()) {
- WriteSources(*pch_files, input_deps, order_only_deps, &obj_files,
- &other_files);
+ WriteSources(*pch_files, input_deps, order_only_deps, module_dep_info,
+ &obj_files, &other_files);
} else {
WriteSwiftSources(input_deps, order_only_deps, &obj_files);
}
@@ -154,7 +215,8 @@
}
}
-void NinjaCBinaryTargetWriter::WriteCompilerVars() {
+void NinjaCBinaryTargetWriter::WriteCompilerVars(
+ const std::vector<ModuleDep>& module_dep_info) {
const SubstitutionBits& subst = target_->toolchain()->substitution_bits();
// Defines.
@@ -193,6 +255,29 @@
out_ << std::endl;
}
+ if (!module_dep_info.empty()) {
+ // TODO(scottmg): Currently clang modules only supported for C++.
+ if (target_->source_types_used().Get(SourceFile::SOURCE_CPP)) {
+ if (target_->toolchain()->substitution_bits().used.count(
+ &CSubstitutionModuleDeps)) {
+ EscapeOptions options;
+ options.mode = ESCAPE_NINJA_COMMAND;
+
+ out_ << CSubstitutionModuleDeps.ninja_name << " = ";
+ EscapeStringToStream(out_, "-fmodules-embed-all-files", options);
+
+ for (const auto& module_dep : module_dep_info) {
+ out_ << " ";
+ EscapeStringToStream(
+ out_, "-fmodule-file=" + module_dep.module_name + "=", options);
+ path_output_.WriteFile(out_, module_dep.pcm);
+ }
+
+ out_ << std::endl;
+ }
+ }
+ }
+
bool has_precompiled_headers =
target_->config_values().has_precompiled_headers();
@@ -427,6 +512,7 @@
const std::vector<OutputFile>& pch_deps,
const std::vector<OutputFile>& input_deps,
const std::vector<OutputFile>& order_only_deps,
+ const std::vector<ModuleDep>& module_dep_info,
std::vector<OutputFile>* object_files,
std::vector<SourceFile>* other_files) {
DCHECK(!target_->source_types_used().SwiftSourceUsed());
@@ -446,7 +532,6 @@
continue; // No output for this source.
}
-
std::copy(input_deps.begin(), input_deps.end(), std::back_inserter(deps));
if (tool_name != Tool::kToolNone) {
@@ -478,13 +563,21 @@
}
}
}
+
+ for (const auto& module_dep : module_dep_info) {
+ if (tool_outputs[0] != module_dep.pcm)
+ deps.push_back(module_dep.pcm);
+ }
+
WriteCompilerBuildLine({source}, deps, order_only_deps, tool_name,
tool_outputs);
}
// It's theoretically possible for a compiler to produce more than one
// output, but we'll only link to the first output.
- object_files->push_back(tool_outputs[0]);
+ if (source.type() != SourceFile::SOURCE_MODULEMAP) {
+ object_files->push_back(tool_outputs[0]);
+ }
}
out_ << std::endl;
diff --git a/src/gn/ninja_c_binary_target_writer.h b/src/gn/ninja_c_binary_target_writer.h
index 695c400..c2e63e4 100644
--- a/src/gn/ninja_c_binary_target_writer.h
+++ b/src/gn/ninja_c_binary_target_writer.h
@@ -12,6 +12,7 @@
#include "gn/unique_vector.h"
struct EscapeOptions;
+struct ModuleDep;
// Writes a .ninja file for a binary target type (an executable, a shared
// library, or a static library).
@@ -26,7 +27,7 @@
using OutputFileSet = std::set<OutputFile>;
// Writes all flags for the compiler: includes, defines, cflags, etc.
- void WriteCompilerVars();
+ void WriteCompilerVars(const std::vector<ModuleDep>& module_dep_info);
// Writes build lines required for precompiled headers. Any generated
// object files will be appended to the |object_files|. Any generated
@@ -71,6 +72,7 @@
void WriteSources(const std::vector<OutputFile>& pch_deps,
const std::vector<OutputFile>& input_deps,
const std::vector<OutputFile>& order_only_deps,
+ const std::vector<ModuleDep>& module_dep_info,
std::vector<OutputFile>* object_files,
std::vector<SourceFile>* other_files);
void WriteSwiftSources(const std::vector<OutputFile>& input_deps,
diff --git a/src/gn/ninja_c_binary_target_writer_unittest.cc b/src/gn/ninja_c_binary_target_writer_unittest.cc
index f650e44..391d1d7 100644
--- a/src/gn/ninja_c_binary_target_writer_unittest.cc
+++ b/src/gn/ninja_c_binary_target_writer_unittest.cc
@@ -1504,7 +1504,9 @@
setup.toolchain()->SetTool(std::move(cxx_module_tool));
TestTarget target(setup, "//foo:bar", Target::STATIC_LIBRARY);
+ target.sources().push_back(SourceFile("//foo/bar.cc"));
target.sources().push_back(SourceFile("//foo/bar.modulemap"));
+ target.source_types_used().Set(SourceFile::SOURCE_CPP);
target.source_types_used().Set(SourceFile::SOURCE_MODULEMAP);
ASSERT_TRUE(target.OnResolved(&err));
@@ -1515,13 +1517,16 @@
const char expected[] =
"defines =\n"
"include_dirs =\n"
+ "cflags =\n"
+ "cflags_cc =\n"
"root_out_dir = .\n"
"target_out_dir = obj/foo\n"
"target_output_name = libbar\n"
"\n"
+ "build obj/foo/libbar.bar.o: cxx ../../foo/bar.cc | obj/foo/libbar.bar.pcm\n"
"build obj/foo/libbar.bar.pcm: cxx_module ../../foo/bar.modulemap\n"
"\n"
- "build obj/foo/libbar.a: alink obj/foo/libbar.bar.pcm\n"
+ "build obj/foo/libbar.a: alink obj/foo/libbar.bar.o\n"
" arflags =\n"
" output_extension = \n"
" output_dir = \n";
@@ -1690,3 +1695,183 @@
EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
}
}
+
+TEST_F(NinjaCBinaryTargetWriterTest, DependOnModule) {
+ TestWithScope setup;
+ Err err;
+
+ // There's no cxx_module or flags in the test toolchain, set up a
+ // custom one here.
+ Settings module_settings(setup.build_settings(), "withmodules/");
+ Toolchain module_toolchain(&module_settings,
+ Label(SourceDir("//toolchain/"), "withmodules"));
+ module_settings.set_toolchain_label(module_toolchain.label());
+ module_settings.set_default_toolchain_label(module_toolchain.label());
+
+ std::unique_ptr<Tool> cxx = std::make_unique<CTool>(CTool::kCToolCxx);
+ CTool* cxx_tool = cxx->AsC();
+ TestWithScope::SetCommandForTool(
+ "c++ {{source}} {{cflags}} {{cflags_cc}} {{module_deps}} "
+ "{{defines}} {{include_dirs}} -o {{output}}",
+ cxx_tool);
+ cxx_tool->set_outputs(SubstitutionList::MakeForTest(
+ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o"));
+ cxx_tool->set_precompiled_header_type(CTool::PCH_GCC);
+ module_toolchain.SetTool(std::move(cxx));
+
+ std::unique_ptr<Tool> cxx_module_tool =
+ Tool::CreateTool(CTool::kCToolCxxModule);
+ TestWithScope::SetCommandForTool(
+ "c++ {{source}} {{cflags}} {{cflags_cc}} {{defines}} {{include_dirs}} "
+ "-fmodule-name={{label}} -c -x c++ -Xclang -emit-module -o {{output}}",
+ cxx_module_tool.get());
+ cxx_module_tool->set_outputs(SubstitutionList::MakeForTest(
+ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.pcm"));
+ module_toolchain.SetTool(std::move(cxx_module_tool));
+
+ std::unique_ptr<Tool> alink = Tool::CreateTool(CTool::kCToolAlink);
+ CTool* alink_tool = alink->AsC();
+ TestWithScope::SetCommandForTool("ar {{output}} {{source}}", alink_tool);
+ alink_tool->set_lib_switch("-l");
+ alink_tool->set_lib_dir_switch("-L");
+ alink_tool->set_output_prefix("lib");
+ alink_tool->set_outputs(SubstitutionList::MakeForTest(
+ "{{target_out_dir}}/{{target_output_name}}.a"));
+ module_toolchain.SetTool(std::move(alink));
+
+ std::unique_ptr<Tool> link = Tool::CreateTool(CTool::kCToolLink);
+ CTool* link_tool = link->AsC();
+ TestWithScope::SetCommandForTool(
+ "ld -o {{target_output_name}} {{source}} "
+ "{{ldflags}} {{libs}}",
+ link_tool);
+ link_tool->set_lib_switch("-l");
+ link_tool->set_lib_dir_switch("-L");
+ link_tool->set_outputs(
+ SubstitutionList::MakeForTest("{{root_out_dir}}/{{target_output_name}}"));
+ module_toolchain.SetTool(std::move(link));
+
+ module_toolchain.ToolchainSetupComplete();
+
+ Target target(&module_settings, Label(SourceDir("//blah/"), "a"));
+ target.set_output_type(Target::STATIC_LIBRARY);
+ target.visibility().SetPublic();
+ target.sources().push_back(SourceFile("//blah/a.modulemap"));
+ target.sources().push_back(SourceFile("//blah/a.cc"));
+ target.sources().push_back(SourceFile("//blah/a.h"));
+ target.source_types_used().Set(SourceFile::SOURCE_CPP);
+ target.source_types_used().Set(SourceFile::SOURCE_MODULEMAP);
+ target.SetToolchain(&module_toolchain);
+ ASSERT_TRUE(target.OnResolved(&err));
+
+ // The library first.
+ {
+ std::ostringstream out;
+ NinjaCBinaryTargetWriter writer(&target, out);
+ writer.Run();
+
+ const char expected[] = R"(defines =
+include_dirs =
+module_deps = -fmodules-embed-all-files -fmodule-file=//blah$:a=obj/blah/liba.a.pcm
+cflags =
+cflags_cc =
+label = //blah$:a
+root_out_dir = withmodules
+target_out_dir = obj/blah
+target_output_name = liba
+
+build obj/blah/liba.a.pcm: cxx_module ../../blah/a.modulemap
+build obj/blah/liba.a.o: cxx ../../blah/a.cc | obj/blah/liba.a.pcm
+
+build obj/blah/liba.a: alink obj/blah/liba.a.o
+ arflags =
+ output_extension =
+ output_dir =
+)";
+
+ std::string out_str = out.str();
+ EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+ }
+
+ Target target2(&module_settings, Label(SourceDir("//stuff/"), "b"));
+ target2.set_output_type(Target::STATIC_LIBRARY);
+ target2.visibility().SetPublic();
+ target2.sources().push_back(SourceFile("//stuff/b.modulemap"));
+ target2.sources().push_back(SourceFile("//stuff/b.cc"));
+ target2.sources().push_back(SourceFile("//stuff/b.h"));
+ target2.source_types_used().Set(SourceFile::SOURCE_CPP);
+ target2.source_types_used().Set(SourceFile::SOURCE_MODULEMAP);
+ target2.SetToolchain(&module_toolchain);
+ ASSERT_TRUE(target2.OnResolved(&err));
+
+ // A second library to make sure the depender includes both.
+ {
+ std::ostringstream out;
+ NinjaCBinaryTargetWriter writer(&target2, out);
+ writer.Run();
+
+ const char expected[] = R"(defines =
+include_dirs =
+module_deps = -fmodules-embed-all-files -fmodule-file=//stuff$:b=obj/stuff/libb.b.pcm
+cflags =
+cflags_cc =
+label = //stuff$:b
+root_out_dir = withmodules
+target_out_dir = obj/stuff
+target_output_name = libb
+
+build obj/stuff/libb.b.pcm: cxx_module ../../stuff/b.modulemap
+build obj/stuff/libb.b.o: cxx ../../stuff/b.cc | obj/stuff/libb.b.pcm
+
+build obj/stuff/libb.a: alink obj/stuff/libb.b.o
+ arflags =
+ output_extension =
+ output_dir =
+)";
+
+ std::string out_str = out.str();
+ EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+ }
+
+ Target depender(&module_settings, Label(SourceDir("//zap/"), "c"));
+ depender.set_output_type(Target::EXECUTABLE);
+ depender.sources().push_back(SourceFile("//zap/x.cc"));
+ depender.sources().push_back(SourceFile("//zap/y.cc"));
+ depender.source_types_used().Set(SourceFile::SOURCE_CPP);
+ depender.private_deps().push_back(LabelTargetPair(&target));
+ depender.private_deps().push_back(LabelTargetPair(&target2));
+ depender.SetToolchain(&module_toolchain);
+ ASSERT_TRUE(depender.OnResolved(&err));
+
+ // Then the executable that depends on it.
+ {
+ std::ostringstream out;
+ NinjaCBinaryTargetWriter writer(&depender, out);
+ writer.Run();
+
+ const char expected[] = R"(defines =
+include_dirs =
+module_deps = -fmodules-embed-all-files -fmodule-file=//blah$:a=obj/blah/liba.a.pcm -fmodule-file=//stuff$:b=obj/stuff/libb.b.pcm
+cflags =
+cflags_cc =
+label = //zap$:c
+root_out_dir = withmodules
+target_out_dir = obj/zap
+target_output_name = c
+
+build obj/zap/c.x.o: cxx ../../zap/x.cc | obj/blah/liba.a.pcm obj/stuff/libb.b.pcm
+build obj/zap/c.y.o: cxx ../../zap/y.cc | obj/blah/liba.a.pcm obj/stuff/libb.b.pcm
+
+build withmodules/c: link obj/zap/c.x.o obj/zap/c.y.o obj/blah/liba.a obj/stuff/libb.a
+ ldflags =
+ libs =
+ frameworks =
+ swiftmodules =
+ output_extension =
+ output_dir =
+)";
+
+ std::string out_str = out.str();
+ EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+ }
+}
diff --git a/src/gn/ninja_target_writer.cc b/src/gn/ninja_target_writer.cc
index e284f24..b6bbb9f 100644
--- a/src/gn/ninja_target_writer.cc
+++ b/src/gn/ninja_target_writer.cc
@@ -139,12 +139,18 @@
written_anything = true;
}
- // Target label name
+ // Target label name.
if (bits.used.count(&SubstitutionLabelName)) {
WriteEscapedSubstitution(&SubstitutionLabelName);
written_anything = true;
}
+ // Target label name without toolchain.
+ if (bits.used.count(&SubstitutionLabelNoToolchain)) {
+ WriteEscapedSubstitution(&SubstitutionLabelNoToolchain);
+ written_anything = true;
+ }
+
// Root gen dir.
if (bits.used.count(&SubstitutionRootGenDir)) {
WriteEscapedSubstitution(&SubstitutionRootGenDir);
diff --git a/src/gn/substitution_type.cc b/src/gn/substitution_type.cc
index a4d1384..06c21b3 100644
--- a/src/gn/substitution_type.cc
+++ b/src/gn/substitution_type.cc
@@ -20,6 +20,7 @@
&SubstitutionOutput,
&SubstitutionLabel,
&SubstitutionLabelName,
+ &SubstitutionLabelNoToolchain,
&SubstitutionRootGenDir,
&SubstitutionRootOutDir,
&SubstitutionOutputDir,
@@ -72,6 +73,8 @@
// do not vary on a per-file basis.
const Substitution SubstitutionLabel = {"{{label}}", "label"};
const Substitution SubstitutionLabelName = {"{{label_name}}", "label_name"};
+const Substitution SubstitutionLabelNoToolchain = {"{{label_no_toolchain}}",
+ "label_no_toolchain"};
const Substitution SubstitutionRootGenDir = {"{{root_gen_dir}}",
"root_gen_dir"};
const Substitution SubstitutionRootOutDir = {"{{root_out_dir}}",
@@ -166,6 +169,7 @@
bool IsValidToolSubstitution(const Substitution* type) {
return type == &SubstitutionLiteral || type == &SubstitutionOutput ||
type == &SubstitutionLabel || type == &SubstitutionLabelName ||
+ type == &SubstitutionLabelNoToolchain ||
type == &SubstitutionRootGenDir || type == &SubstitutionRootOutDir ||
type == &SubstitutionTargetGenDir ||
type == &SubstitutionTargetOutDir ||
diff --git a/src/gn/substitution_type.h b/src/gn/substitution_type.h
index 7ec929f..28f6c66 100644
--- a/src/gn/substitution_type.h
+++ b/src/gn/substitution_type.h
@@ -37,6 +37,7 @@
extern const Substitution SubstitutionOutput;
extern const Substitution SubstitutionLabel;
extern const Substitution SubstitutionLabelName;
+extern const Substitution SubstitutionLabelNoToolchain;
extern const Substitution SubstitutionRootGenDir;
extern const Substitution SubstitutionRootOutDir;
extern const Substitution SubstitutionOutputDir;
diff --git a/src/gn/substitution_writer.cc b/src/gn/substitution_writer.cc
index 6fe83ce..c9624d7 100644
--- a/src/gn/substitution_writer.cc
+++ b/src/gn/substitution_writer.cc
@@ -436,6 +436,8 @@
target->label().GetUserVisibleName(!target->settings()->is_default());
} else if (type == &SubstitutionLabelName) {
*result = target->label().name();
+ } else if (type == &SubstitutionLabelNoToolchain) {
+ *result = target->label().GetUserVisibleName(false);
} else if (type == &SubstitutionRootGenDir) {
SetDirOrDotWithNoSlash(
GetBuildDirAsOutputFile(BuildDirContext(target), BuildDirType::GEN)
diff --git a/src/gn/substitution_writer_unittest.cc b/src/gn/substitution_writer_unittest.cc
index 8e5709b..fc3c446 100644
--- a/src/gn/substitution_writer_unittest.cc
+++ b/src/gn/substitution_writer_unittest.cc
@@ -200,6 +200,10 @@
EXPECT_EQ("baz", result);
EXPECT_TRUE(SubstitutionWriter::GetTargetSubstitution(
+ &target, &SubstitutionLabelNoToolchain, &result));
+ EXPECT_EQ("//foo/bar:baz", result);
+
+ EXPECT_TRUE(SubstitutionWriter::GetTargetSubstitution(
&target, &SubstitutionRootGenDir, &result));
EXPECT_EQ("gen", result);