rust: Treat config inputs as implicit rebuild deps
NinjaRustBinaryTargetWriter previously placed the result of
WriteInputsStampOrPhonyAndGetDep() into order-only dependencies ("||")
while only adding target_->config_values().inputs() to implicit
dependencies ("|") and to the `sources` Ninja variable ("{{sources}}").
As a result, `inputs` specified on `config()` definitions (via
`configs`, `public_configs`, `all_dependent_configs`, or sub-configs)
were omitted from `sources` and treated only as order-only dependencies,
so changes to config inputs such as custom `rustc --target` JSON specs
failed to trigger incremental rebuilds (see https://fxrev.dev/1967814).
Refactor NinjaBinaryTargetWriter to share input and dependency
collection cleanly between NinjaCBinaryTargetWriter and
NinjaRustBinaryTargetWriter:
- Extract GetInputs() using UniqueVector<SourceFile> so duplicate
SourceFile entries across targets and configs are deduplicated by
value rather than element pointer address, and share it with
NinjaRustBinaryTargetWriter::WriteSourcesAndInputs().
- Extract WriteCompilerInputDeps(num_output_uses) combining implicit
`inputs`, `public_inputs`, and toolchain `deps` with order-only
upstream hard dependencies.
- Pass num_output_uses = 1 in NinjaRustBinaryTargetWriter::Run() since
Rust compiles a crate in a single build statement, avoiding redundant
per-target `.inputs` / `.inputdeps` phony or stamp rules.
- Deduplicate `order_only_deps` with UniqueVector<OutputFile> and drop
the duplicate tool_->inputs_phony_or_file() addition already handled
by WriteCompilerBuildLine().
Change-Id: I89c123308a0c7f8f2f6844cc50a2dce0a0214cba
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/28740
Commit-Queue: Petr Hosek <phosek@google.com>
Reviewed-by: Petr Hosek <phosek@google.com>
Reviewed-by: Aaron Wood <aaronwood@google.com>
diff --git a/src/gn/ninja_binary_target_writer.cc b/src/gn/ninja_binary_target_writer.cc
index 420ac0a..e9e23cf 100644
--- a/src/gn/ninja_binary_target_writer.cc
+++ b/src/gn/ninja_binary_target_writer.cc
@@ -6,6 +6,7 @@
#include <algorithm>
#include <unordered_set>
+#include <utility>
#include "base/strings/string_util.h"
#include "gn/builtin_tool.h"
@@ -157,18 +158,23 @@
out << "}\n";
}
+UniqueVector<SourceFile> NinjaBinaryTargetWriter::GetInputs() const {
+ UniqueVector<SourceFile> inputs;
+ // ConfigValuesIterator yields all inputs from the target itself as well as
+ // from all its configs (configs, public_configs, all_dependent_configs).
+ for (ConfigValuesIterator iter(target_); !iter.done(); iter.Next()) {
+ inputs.Append(iter.cur().inputs());
+ }
+ return inputs;
+}
+
std::vector<OutputFile>
NinjaBinaryTargetWriter::WriteInputsStampOrPhonyAndGetDep(
size_t num_output_uses) const {
CHECK(target_->toolchain()) << "Toolchain not set on target "
<< target_->label().GetUserVisibleName(true);
- UniqueVector<const SourceFile*> inputs;
- for (ConfigValuesIterator iter(target_); !iter.done(); iter.Next()) {
- for (const auto& input : iter.cur().inputs()) {
- inputs.push_back(&input);
- }
- }
+ UniqueVector<SourceFile> inputs = GetInputs();
if (inputs.size() == 0)
return std::vector<OutputFile>(); // No inputs
@@ -177,17 +183,18 @@
// target for it.
if (inputs.size() == 1) {
return std::vector<OutputFile>{
- OutputFile(settings_->build_settings(), *inputs[0])};
+ OutputFile(settings_->build_settings(), inputs[0])};
}
- std::vector<OutputFile> outs;
- for (const SourceFile* source : inputs)
- outs.push_back(OutputFile(settings_->build_settings(), *source));
-
// If there are multiple inputs, but the phony target would be referenced only
// once, don't write it but depend on the inputs directly.
- if (num_output_uses == 1u)
+ if (num_output_uses == 1u) {
+ std::vector<OutputFile> outs;
+ outs.reserve(inputs.size());
+ for (const SourceFile& source : inputs)
+ outs.push_back(OutputFile(settings_->build_settings(), source));
return outs;
+ }
OutputFile stamp_or_phony;
std::string tool;
@@ -211,15 +218,56 @@
out_ << ": " << tool;
// File inputs.
- for (const auto* input : inputs) {
+ for (const auto& input : inputs) {
out_ << " ";
- path_output_.WriteFile(out_, *input);
+ path_output_.WriteFile(out_, input);
}
out_ << '\n';
return {stamp_or_phony};
}
+NinjaTargetWriter::InputDeps NinjaBinaryTargetWriter::WriteCompilerInputDeps(
+ size_t num_output_uses) const {
+ InputDeps deps;
+ deps.implicit = WriteInputsStampOrPhonyAndGetDep(num_output_uses);
+
+ // Upstream hard dependencies will be an order-only dependency. This will
+ // cause Ninja to make sure the inputs are up to date before compiling this
+ // source, but changes in the input deps won't cause the file to be
+ // recompiled.
+ //
+ // This is important to prevent changes in unrelated actions that are
+ // upstream of this target from causing everything to be recompiled.
+ //
+ // Why can we get away with this for upstream target actions rather than using
+ // implicit deps ("|", which will force rebuilds when the inputs change)? For
+ // source code, the computed dependencies of all headers and included files
+ // will be computed by the compiler via depfiles, which will cause source
+ // rebuilds if any "real" upstream dependencies change. Explicit `inputs` (on
+ // the target or any applied configs), `public_inputs`, and toolchain `deps`
+ // are placed in `deps.implicit` ("|") so changes to them always force
+ // recompilation (for instance, `rustc --emit=dep-info` omits command-line
+ // files such as custom target JSON specs).
+ //
+ // If a source file is generated by an input dependency, Ninja will see the
+ // input to the build rule doesn't exist, and that it is an output from a
+ // previous step, and build the previous step first. This is a "real"
+ // dependency and doesn't need | or || to express.
+ //
+ // The only case where this order-only rule matters is for the first build
+ // where no .d files exist, and Ninja doesn't know what that source file
+ // depends on. In this case it's sufficient to ensure that the upstream
+ // dependencies are built first. This is exactly what Ninja's order-only
+ // dependencies express.
+ InputDeps stamp_deps = WriteInputDepsStampOrPhonyAndGetDep(
+ std::vector<const Target*>(), num_output_uses);
+ deps.implicit.insert(deps.implicit.end(), stamp_deps.implicit.begin(),
+ stamp_deps.implicit.end());
+ deps.order_only = std::move(stamp_deps.order_only);
+ return deps;
+}
+
NinjaBinaryTargetWriter::ClassifiedDeps
NinjaBinaryTargetWriter::GetClassifiedDeps() const {
ClassifiedDeps classified_deps;
diff --git a/src/gn/ninja_binary_target_writer.h b/src/gn/ninja_binary_target_writer.h
index f942297..7166589 100644
--- a/src/gn/ninja_binary_target_writer.h
+++ b/src/gn/ninja_binary_target_writer.h
@@ -34,15 +34,21 @@
UniqueVector<const Target*> swiftmodule_deps;
};
- // Writes to the output stream a phony rule for inputs, and
- // returns the target to be appended to source rules that encodes the
- // implicit dependencies for the current target.
- // If num_output_uses is small, this might return all input dependencies
- // directly, without writing a phony rule.
- // If there are no implicit dependencies and no extra target dependencies
- // are passed in, this returns an empty vector.
+ // Collects all deduplicated input files from the target and its configs.
+ UniqueVector<SourceFile> GetInputs() const;
+
+ // Writes to the output stream a stamp or phony rule for inputs (if needed),
+ // and returns the files to be appended to source rules that encode the
+ // implicit `inputs` dependencies for the current target.
+ // If num_output_uses is 1 or there is at most one input, this returns the
+ // input dependencies directly without writing an intermediate rule.
std::vector<OutputFile> WriteInputsStampOrPhonyAndGetDep(
- size_t num_phony_uses) const;
+ size_t num_output_uses) const;
+
+ // Combines implicit dependencies from target/config `inputs`,
+ // `public_inputs`, and toolchain `deps` with order-only dependencies from
+ // upstream hard deps.
+ InputDeps WriteCompilerInputDeps(size_t num_output_uses) const;
// Gets all target dependencies and classifies them, as well as accumulates
// object files from source sets we need to link.
diff --git a/src/gn/ninja_c_binary_target_writer.cc b/src/gn/ninja_c_binary_target_writer.cc
index b750cda..027ba06 100644
--- a/src/gn/ninja_c_binary_target_writer.cc
+++ b/src/gn/ninja_c_binary_target_writer.cc
@@ -68,54 +68,22 @@
WriteCompilerVars(module_dep_info);
- size_t num_output_uses = target_->sources().size();
-
- std::vector<OutputFile> input_deps =
- WriteInputsStampOrPhonyAndGetDep(num_output_uses);
-
- // The input dependencies will be an order-only dependency. This will cause
- // Ninja to make sure the inputs are up to date before compiling this source,
- // but changes in the inputs deps won't cause the file to be recompiled.
- //
- // This is important to prevent changes in unrelated actions that are
- // upstream of this target from causing everything to be recompiled.
- //
- // Why can we get away with this rather than using implicit deps ("|", which
- // will force rebuilds when the inputs change)? For source code, the
- // computed dependencies of all headers will be computed by the compiler,
- // which will cause source rebuilds if any "real" upstream dependencies
- // change.
- //
- // If a .cc file is generated by an input dependency, Ninja will see the
- // input to the build rule doesn't exist, and that it is an output from a
- // previous step, and build the previous step first. This is a "real"
- // dependency and doesn't need | or || to express.
- //
- // The only case where this rule matters is for the first build where no .d
- // files exist, and Ninja doesn't know what that source file depends on. In
- // this case it's sufficient to ensure that the upstream dependencies are
- // built first. This is exactly what Ninja's order-only dependencies
- // expresses.
- //
- // The order only deps are referenced by each source file compile,
+ // The order-only deps are referenced by each source file compile,
// but also by PCH compiles. The latter are annoying to count, so omit
// them here. This means that binary targets with a single source file
// that also use PCH files won't have a phony target even though having
// one would make output ninja file size a bit lower. That's ok, binary
// targets with a single source are rare.
- NinjaTargetWriter::InputDeps stamp_deps = WriteInputDepsStampOrPhonyAndGetDep(
- std::vector<const Target*>(), num_output_uses);
- input_deps.insert(input_deps.end(), stamp_deps.implicit.begin(),
- stamp_deps.implicit.end());
- std::vector<OutputFile> order_only_deps = stamp_deps.order_only;
+ InputDeps compiler_input_deps =
+ WriteCompilerInputDeps(target_->sources().size());
// For GCC builds, the .gch files are not object files, but still need to be
// added as explicit dependencies below. The .gch output files are placed in
// |pch_other_files|. This is to prevent linking against them.
std::vector<OutputFile> pch_obj_files;
std::vector<OutputFile> pch_other_files;
- WritePCHCommands(input_deps, order_only_deps, &pch_obj_files,
- &pch_other_files);
+ WritePCHCommands(compiler_input_deps.implicit, compiler_input_deps.order_only,
+ &pch_obj_files, &pch_other_files);
std::vector<OutputFile>* pch_files =
!pch_obj_files.empty() ? &pch_obj_files : &pch_other_files;
@@ -136,11 +104,13 @@
std::vector<SourceFile> other_files;
std::vector<OutputFile>* stamp_files = &obj_files; // default
if (!target_->source_types_used().SwiftSourceUsed()) {
- WriteSources(*pch_files, input_deps, order_only_deps, module_dep_info,
- &obj_files, &extra_files, &other_files);
+ WriteSources(*pch_files, compiler_input_deps.implicit,
+ compiler_input_deps.order_only, module_dep_info, &obj_files,
+ &extra_files, &other_files);
} else {
stamp_files = &extra_files; // Swift generates more than object files
- WriteSwiftSources(input_deps, order_only_deps, &obj_files, &extra_files);
+ WriteSwiftSources(compiler_input_deps.implicit,
+ compiler_input_deps.order_only, &obj_files, &extra_files);
}
// Link all MSVC pch object files. The vector will be empty on GCC toolchains.
@@ -167,7 +137,7 @@
}
WriteSourceSetStamp(*stamp_files);
} else {
- WriteLinkerStuff(obj_files, other_files, input_deps);
+ WriteLinkerStuff(obj_files, other_files, compiler_input_deps.implicit);
}
}
diff --git a/src/gn/ninja_c_binary_target_writer_unittest.cc b/src/gn/ninja_c_binary_target_writer_unittest.cc
index edaf89f..e446574 100644
--- a/src/gn/ninja_c_binary_target_writer_unittest.cc
+++ b/src/gn/ninja_c_binary_target_writer_unittest.cc
@@ -1747,6 +1747,8 @@
{
Config far_config(setup.settings(), Label(SourceDir("//foo/"), "qux"));
far_config.own_values().inputs().push_back(SourceFile("//foo/input3.data"));
+ // Duplicate of target's input1.data to verify value-based deduplication.
+ far_config.own_values().inputs().push_back(SourceFile("//foo/input1.data"));
ASSERT_TRUE(far_config.OnResolved(&err));
Config config(setup.settings(), Label(SourceDir("//foo/"), "baz"));
diff --git a/src/gn/ninja_rust_binary_target_writer.cc b/src/gn/ninja_rust_binary_target_writer.cc
index 7cf7256..c263c75 100644
--- a/src/gn/ninja_rust_binary_target_writer.cc
+++ b/src/gn/ninja_rust_binary_target_writer.cc
@@ -5,6 +5,7 @@
#include "gn/ninja_rust_binary_target_writer.h"
#include <unordered_set>
+#include <utility>
#include "base/strings/string_util.h"
#include "gn/deps_iterator.h"
@@ -113,25 +114,17 @@
void NinjaRustBinaryTargetWriter::Run() {
DCHECK(target_->output_type() != Target::SOURCE_SET);
- size_t num_output_uses = target_->sources().size();
-
- std::vector<OutputFile> input_deps =
- WriteInputsStampOrPhonyAndGetDep(num_output_uses);
-
WriteCompilerVars();
// Classify our dependencies.
ClassifiedDeps classified_deps = GetClassifiedDeps();
- // The input dependencies will be an order-only dependency. This will cause
- // Ninja to make sure the inputs are up to date before compiling this source,
- // but changes in the inputs deps won't cause the file to be recompiled. See
- // the comment on NinjaCBinaryTargetWriter::Run for more detailed explanation.
- NinjaTargetWriter::InputDeps stamp_deps = WriteInputDepsStampOrPhonyAndGetDep(
- std::vector<const Target*>(), num_output_uses);
- std::vector<OutputFile> order_only_deps = stamp_deps.order_only;
- std::copy(input_deps.begin(), input_deps.end(),
- std::back_inserter(order_only_deps));
+ // Unlike C/C++, Rust compiles all sources of a crate in one command, so
+ // input dependencies are referenced only once in this ninja file.
+ InputDeps compiler_input_deps =
+ WriteCompilerInputDeps(/*num_output_uses=*/1u);
+ UniqueVector<OutputFile> order_only_deps;
+ order_only_deps.Append(std::move(compiler_input_deps.order_only));
// rustc does not invoke a linker when building an rlib, so non-Rust native
// dependencies (object files, static and shared libraries) are not used to
@@ -147,19 +140,18 @@
// -Ldependency. Also assemble a list of extra (i.e. implicit) deps
// for ninja dependency tracking.
UniqueVector<OutputFile> implicit_deps;
- implicit_deps.Append(stamp_deps.implicit.begin(), stamp_deps.implicit.end());
- AppendSourcesAndInputsToImplicitDeps(&implicit_deps);
+ // Only the crate_root file needs to be given to rustc as explicit input.
+ // Any other 'sources' are implicit deps.
+ for (const auto& source : target_->sources()) {
+ implicit_deps.push_back(OutputFile(settings_->build_settings(), source));
+ }
+ implicit_deps.Append(compiler_input_deps.implicit.begin(),
+ compiler_input_deps.implicit.end());
if (!is_rlib) {
implicit_deps.Append(classified_deps.extra_object_files.begin(),
classified_deps.extra_object_files.end());
}
- if (auto phony = tool_->inputs_phony_or_file(rule_prefix_,
- *settings_->build_settings());
- phony) {
- implicit_deps.emplace_back(std::move(*phony));
- }
-
std::vector<OutputFile> rustdeps;
std::vector<OutputFile> nonrustdeps;
std::vector<OutputFile> swiftmodules;
@@ -190,8 +182,7 @@
std::vector<OutputFile> group_order_only_deps =
GetOrderOnlyDepsFromNonLinkableDeps(classified_deps.non_linkable_deps);
- order_only_deps.insert(order_only_deps.end(), group_order_only_deps.begin(),
- group_order_only_deps.end());
+ order_only_deps.Append(std::move(group_order_only_deps));
for (const auto* linkable_dep : classified_deps.linkable_deps) {
// Rust cdylibs are treated as non-Rust dependencies for linking purposes.
if (linkable_dep->source_types_used().RustSourceUsed() &&
@@ -295,8 +286,8 @@
SubstitutionWriter::ApplyListToLinkerAsOutputFile(
target_, tool_, tool_->outputs(), &tool_outputs);
WriteCompilerBuildLine({target_->rust_values().crate_root()},
- implicit_deps.vector(), order_only_deps, tool_,
- tool_outputs);
+ implicit_deps.vector(), order_only_deps.vector(),
+ tool_, tool_outputs);
std::vector<const Target*> extern_deps(
classified_deps.linkable_deps.vector());
@@ -321,31 +312,15 @@
WriteSharedVars(subst);
}
-void NinjaRustBinaryTargetWriter::AppendSourcesAndInputsToImplicitDeps(
- UniqueVector<OutputFile>* deps) const {
- // Only the crate_root file needs to be given to rustc as input.
- // Any other 'sources' are just implicit deps.
- // Most Rust targets won't bother specifying the "sources =" line
- // because it is handled sufficiently by crate_root and the generation
- // of depfiles by rustc. But for those which do...
- for (const auto& source : target_->sources()) {
- deps->push_back(OutputFile(settings_->build_settings(), source));
- }
- for (const auto& data : target_->config_values().inputs()) {
- deps->push_back(OutputFile(settings_->build_settings(), data));
- }
-}
-
void NinjaRustBinaryTargetWriter::WriteSourcesAndInputs() {
+ UniqueVector<SourceFile> files;
+ files.Append(target_->sources());
+ files.Append(GetInputs());
+
out_ << " sources =";
- for (const auto& source : target_->sources()) {
+ for (const auto& file : files) {
out_ << " ";
- path_output_.WriteFile(out_,
- OutputFile(settings_->build_settings(), source));
- }
- for (const auto& data : target_->config_values().inputs()) {
- out_ << " ";
- path_output_.WriteFile(out_, OutputFile(settings_->build_settings(), data));
+ path_output_.WriteFile(out_, OutputFile(settings_->build_settings(), file));
}
out_ << '\n';
}
diff --git a/src/gn/ninja_rust_binary_target_writer.h b/src/gn/ninja_rust_binary_target_writer.h
index 99f95fe..44e598a 100644
--- a/src/gn/ninja_rust_binary_target_writer.h
+++ b/src/gn/ninja_rust_binary_target_writer.h
@@ -26,8 +26,6 @@
};
void WriteCompilerVars();
- void WriteSources(const OutputFile& input_dep,
- const std::vector<OutputFile>& order_only_deps);
void WriteExternsAndDeps(const std::vector<const Target*>& deps,
const std::vector<ExternCrate>& transitive_rust_deps,
const std::vector<OutputFile>& rustdeps,
@@ -36,9 +34,6 @@
// Unlike C/C++, Rust compiles all sources of a crate in one command.
// Write a ninja variable `sources` that contains all sources and input files.
void WriteSourcesAndInputs();
- void WriteEdition();
- void AppendSourcesAndInputsToImplicitDeps(
- UniqueVector<OutputFile>* deps) const;
const RustTool* tool_;
diff --git a/src/gn/ninja_rust_binary_target_writer_unittest.cc b/src/gn/ninja_rust_binary_target_writer_unittest.cc
index 3d0733a..4f5282e 100644
--- a/src/gn/ninja_rust_binary_target_writer_unittest.cc
+++ b/src/gn/ninja_rust_binary_target_writer_unittest.cc
@@ -1788,8 +1788,6 @@
writer.Run();
const char expected[] =
- "build phony/foo/bar.inputs: phony ../../foo/config.json "
- "../../foo/template.h\n"
"crate_name = foo_bar\n"
"crate_type = bin\n"
"output_extension = \n"
@@ -1802,8 +1800,7 @@
"target_output_name = bar\n"
"\n"
"build ./foo_bar: rust_bin ../../foo/main.rs | ../../foo/source.rs "
- "../../foo/main.rs ../../foo/config.json ../../foo/template.h "
- "|| phony/foo/bar.inputs\n"
+ "../../foo/main.rs ../../foo/config.json ../../foo/template.h\n"
" source_file_part = main.rs\n"
" source_name_part = main\n"
" externs =\n"
@@ -1815,6 +1812,391 @@
}
}
+TEST_F(NinjaRustBinaryTargetWriterTest, ConfigInputsAndOrderOnlyDeps) {
+ Err err;
+ TestWithScope setup;
+
+ // Sub-config attached to a direct config (e.g. a custom target JSON spec).
+ Config sub_config(setup.settings(), Label(SourceDir("//cfg/"), "sub_cfg"));
+ sub_config.visibility().SetPublic();
+ sub_config.own_values().rustflags().push_back("--target=gen/cfg/target.json");
+ sub_config.own_values().inputs().push_back(
+ SourceFile("//out/Debug/gen/cfg/target.json"));
+ // Duplicate of direct_config's input to verify value-based deduplication.
+ sub_config.own_values().inputs().push_back(SourceFile("//cfg/direct.json"));
+ ASSERT_TRUE(sub_config.OnResolved(&err));
+
+ Config direct_config(setup.settings(), Label(SourceDir("//cfg/"), "direct"));
+ direct_config.visibility().SetPublic();
+ direct_config.own_values().inputs().push_back(
+ SourceFile("//cfg/direct.json"));
+ direct_config.configs().push_back(LabelConfigPair(&sub_config));
+ ASSERT_TRUE(direct_config.OnResolved(&err));
+
+ Config pub_config(setup.settings(), Label(SourceDir("//cfg/"), "pub"));
+ pub_config.visibility().SetPublic();
+ pub_config.own_values().inputs().push_back(SourceFile("//cfg/public.json"));
+ ASSERT_TRUE(pub_config.OnResolved(&err));
+
+ Config all_dep_config(setup.settings(), Label(SourceDir("//cfg/"), "all"));
+ all_dep_config.visibility().SetPublic();
+ all_dep_config.own_values().inputs().push_back(SourceFile("//cfg/all.json"));
+ ASSERT_TRUE(all_dep_config.OnResolved(&err));
+
+ // Upstream action target: its output should be an order-only dependency
+ // ("||"), while its public_configs / all_dependent_configs `inputs` and its
+ // `public_inputs` must be implicit rebuild dependencies ("|").
+ Target gen_action(setup.settings(), Label(SourceDir("//cfg/"), "gen_target"));
+ gen_action.set_output_type(Target::ACTION);
+ gen_action.visibility().SetPublic();
+ gen_action.action_values().set_script(SourceFile("//cfg/gen.py"));
+ gen_action.action_values().outputs() =
+ SubstitutionList::MakeForTest("//out/Debug/gen/cfg/target.json");
+ gen_action.public_configs().push_back(LabelConfigPair(&pub_config));
+ gen_action.all_dependent_configs().push_back(
+ LabelConfigPair(&all_dep_config));
+ gen_action.public_inputs().push_back(SourceFile("//cfg/pub_input.txt"));
+ gen_action.SetToolchain(setup.toolchain());
+ ASSERT_TRUE(gen_action.OnResolved(&err));
+
+ // 1. Test RUST_LIBRARY (rlib) with target inputs, config inputs,
+ // public_inputs, and an order-only action dependency.
+ Target rlib(setup.settings(), Label(SourceDir("//foo/"), "mylib"));
+ rlib.set_output_type(Target::RUST_LIBRARY);
+ rlib.visibility().SetPublic();
+ SourceFile lib_rs("//foo/lib.rs");
+ rlib.sources().push_back(SourceFile("//foo/extra.rs"));
+ rlib.sources().push_back(lib_rs);
+ rlib.source_types_used().Set(SourceFile::SOURCE_RS);
+ rlib.rust_values().set_crate_root(lib_rs);
+ rlib.rust_values().crate_name() = "mylib";
+ rlib.config_values().inputs().push_back(SourceFile("//foo/own.json"));
+ // Also duplicate //cfg/direct.json on the target itself to test deduping.
+ rlib.config_values().inputs().push_back(SourceFile("//cfg/direct.json"));
+ rlib.configs().push_back(LabelConfigPair(&direct_config));
+ rlib.public_deps().push_back(LabelTargetPair(&gen_action));
+ rlib.SetToolchain(setup.toolchain());
+ ASSERT_TRUE(rlib.OnResolved(&err));
+
+ {
+ StringOutputBuffer out;
+ NinjaRustBinaryTargetWriter writer(&rlib, out);
+ writer.Run();
+
+ const char expected[] =
+ "crate_name = mylib\n"
+ "crate_type = rlib\n"
+ "output_extension = .rlib\n"
+ "output_dir = \n"
+ "rustflags = --target=gen/cfg/target.json\n"
+ "rustenv =\n"
+ "root_out_dir = .\n"
+ "target_gen_dir = gen/foo\n"
+ "target_out_dir = obj/foo\n"
+ "target_output_name = libmylib\n"
+ "\n"
+ "build obj/foo/libmylib.rlib: rust_rlib ../../foo/lib.rs | "
+ "../../foo/extra.rs ../../foo/lib.rs ../../foo/own.json "
+ "../../cfg/direct.json gen/cfg/target.json ../../cfg/all.json "
+ "../../cfg/public.json phony/cfg/gen_target.public_inputs || "
+ "phony/cfg/gen_target\n"
+ " source_file_part = lib.rs\n"
+ " source_name_part = lib\n"
+ " externs =\n"
+ " rustdeps =\n"
+ " ldflags =\n"
+ " sources = ../../foo/extra.rs ../../foo/lib.rs ../../foo/own.json "
+ "../../cfg/direct.json gen/cfg/target.json ../../cfg/all.json "
+ "../../cfg/public.json\n";
+ EXPECT_EQ(expected, out);
+ }
+
+ // 2. Test RUST_PROC_MACRO, SHARED_LIBRARY (dylib), LOADABLE_MODULE (cdylib),
+ // and STATIC_LIBRARY receiving inherited configs (all_dependent_configs +
+ // transitive public_configs via a group), toolchain deps, and multiple
+ // upstream action hard_deps, with stamp files enabled.
+ {
+ TestWithScope stamp_setup;
+ stamp_setup.build_settings()->set_no_stamp_files(false);
+
+ Target tc_dep(stamp_setup.settings(), Label(SourceDir("//tc/"), "setup"));
+ tc_dep.set_output_type(Target::ACTION);
+ tc_dep.visibility().SetPublic();
+ tc_dep.action_values().set_script(SourceFile("//tc/setup.py"));
+ tc_dep.SetToolchain(stamp_setup.toolchain());
+ ASSERT_TRUE(tc_dep.OnResolved(&err));
+ stamp_setup.toolchain()->deps().push_back(LabelTargetPair(&tc_dep));
+
+ Config transitive_pub_cfg(stamp_setup.settings(),
+ Label(SourceDir("//cfg/"), "trans_pub"));
+ transitive_pub_cfg.visibility().SetPublic();
+ transitive_pub_cfg.own_values().inputs().push_back(
+ SourceFile("//cfg/spec1.json"));
+ ASSERT_TRUE(transitive_pub_cfg.OnResolved(&err));
+
+ Config all_dep_spec_cfg(stamp_setup.settings(),
+ Label(SourceDir("//cfg/"), "all_spec"));
+ all_dep_spec_cfg.visibility().SetPublic();
+ all_dep_spec_cfg.own_values().inputs().push_back(
+ SourceFile("//cfg/spec2.json"));
+ ASSERT_TRUE(all_dep_spec_cfg.OnResolved(&err));
+
+ Target action_dep(stamp_setup.settings(),
+ Label(SourceDir("//cfg/"), "act"));
+ action_dep.set_output_type(Target::ACTION);
+ action_dep.visibility().SetPublic();
+ action_dep.action_values().set_script(SourceFile("//cfg/gen.py"));
+ action_dep.action_values().outputs() =
+ SubstitutionList::MakeForTest("//out/Debug/gen/cfg/out.txt");
+ action_dep.public_configs().push_back(LabelConfigPair(&transitive_pub_cfg));
+ action_dep.all_dependent_configs().push_back(
+ LabelConfigPair(&all_dep_spec_cfg));
+ action_dep.SetToolchain(stamp_setup.toolchain());
+ ASSERT_TRUE(action_dep.OnResolved(&err));
+
+ Target action_dep2(stamp_setup.settings(),
+ Label(SourceDir("//cfg/"), "act2"));
+ action_dep2.set_output_type(Target::ACTION);
+ action_dep2.visibility().SetPublic();
+ action_dep2.action_values().set_script(SourceFile("//cfg/gen2.py"));
+ action_dep2.action_values().outputs() =
+ SubstitutionList::MakeForTest("//out/Debug/gen/cfg/out2.txt");
+ action_dep2.SetToolchain(stamp_setup.toolchain());
+ ASSERT_TRUE(action_dep2.OnResolved(&err));
+
+ Target cfg_group(stamp_setup.settings(), Label(SourceDir("//cfg/"), "grp"));
+ cfg_group.set_output_type(Target::GROUP);
+ cfg_group.visibility().SetPublic();
+ cfg_group.public_deps().push_back(LabelTargetPair(&action_dep));
+ cfg_group.public_deps().push_back(LabelTargetPair(&action_dep2));
+ cfg_group.SetToolchain(stamp_setup.toolchain());
+ ASSERT_TRUE(cfg_group.OnResolved(&err));
+
+ Target procmacro(stamp_setup.settings(),
+ Label(SourceDir("//macro/"), "pm"));
+ procmacro.set_output_type(Target::RUST_PROC_MACRO);
+ procmacro.visibility().SetPublic();
+ SourceFile pm_root("//macro/lib.rs");
+ procmacro.sources().push_back(SourceFile("//macro/helper.rs"));
+ procmacro.sources().push_back(pm_root);
+ procmacro.source_types_used().Set(SourceFile::SOURCE_RS);
+ procmacro.rust_values().set_crate_root(pm_root);
+ procmacro.rust_values().crate_name() = "pm";
+ procmacro.private_deps().push_back(LabelTargetPair(&cfg_group));
+ procmacro.SetToolchain(stamp_setup.toolchain());
+ ASSERT_TRUE(procmacro.OnResolved(&err));
+
+ StringOutputBuffer pm_out;
+ NinjaRustBinaryTargetWriter pm_writer(&procmacro, pm_out);
+ pm_writer.Run();
+
+ const char pm_expected[] =
+ "crate_name = pm\n"
+ "crate_type = proc-macro\n"
+ "output_extension = .so\n"
+ "output_dir = \n"
+ "rustflags =\n"
+ "rustenv =\n"
+ "root_out_dir = .\n"
+ "target_gen_dir = gen/macro\n"
+ "target_out_dir = obj/macro\n"
+ "target_output_name = libpm\n"
+ "\n"
+ "build obj/macro/libpm.so: rust_macro ../../macro/lib.rs | "
+ "../../macro/helper.rs ../../macro/lib.rs ../../cfg/spec2.json "
+ "../../cfg/spec1.json obj/tc/setup.stamp || obj/cfg/grp.harddeps.stamp "
+ "obj/cfg/act.stamp obj/cfg/act2.stamp\n"
+ " source_file_part = lib.rs\n"
+ " source_name_part = lib\n"
+ " externs =\n"
+ " rustdeps =\n"
+ " ldflags =\n"
+ " sources = ../../macro/helper.rs ../../macro/lib.rs "
+ "../../cfg/spec2.json ../../cfg/spec1.json\n";
+ EXPECT_EQ(pm_expected, pm_out);
+
+ Target dylib(stamp_setup.settings(), Label(SourceDir("//dylib/"), "dy"));
+ dylib.set_output_type(Target::SHARED_LIBRARY);
+ dylib.visibility().SetPublic();
+ SourceFile dy_root("//dylib/lib.rs");
+ dylib.sources().push_back(dy_root);
+ dylib.source_types_used().Set(SourceFile::SOURCE_RS);
+ dylib.rust_values().set_crate_root(dy_root);
+ dylib.rust_values().set_crate_type(RustValues::CRATE_DYLIB);
+ dylib.rust_values().crate_name() = "dy";
+ dylib.private_deps().push_back(LabelTargetPair(&cfg_group));
+ dylib.SetToolchain(stamp_setup.toolchain());
+ ASSERT_TRUE(dylib.OnResolved(&err));
+
+ StringOutputBuffer dy_out;
+ NinjaRustBinaryTargetWriter dy_writer(&dylib, dy_out);
+ dy_writer.Run();
+
+ const char dy_expected[] =
+ "crate_name = dy\n"
+ "crate_type = dylib\n"
+ "output_extension = .so\n"
+ "output_dir = \n"
+ "rustflags =\n"
+ "rustenv =\n"
+ "root_out_dir = .\n"
+ "target_gen_dir = gen/dylib\n"
+ "target_out_dir = obj/dylib\n"
+ "target_output_name = libdy\n"
+ "\n"
+ "build obj/dylib/libdy.so: rust_dylib ../../dylib/lib.rs | "
+ "../../dylib/lib.rs ../../cfg/spec2.json ../../cfg/spec1.json "
+ "obj/tc/setup.stamp || obj/cfg/grp.harddeps.stamp obj/cfg/act.stamp "
+ "obj/cfg/act2.stamp\n"
+ " source_file_part = lib.rs\n"
+ " source_name_part = lib\n"
+ " externs =\n"
+ " rustdeps =\n"
+ " ldflags =\n"
+ " sources = ../../dylib/lib.rs ../../cfg/spec2.json "
+ "../../cfg/spec1.json\n";
+ EXPECT_EQ(dy_expected, dy_out);
+
+ Target cdylib(stamp_setup.settings(), Label(SourceDir("//cdylib/"), "cd"));
+ cdylib.set_output_type(Target::LOADABLE_MODULE);
+ cdylib.visibility().SetPublic();
+ SourceFile cd_root("//cdylib/lib.rs");
+ cdylib.sources().push_back(cd_root);
+ cdylib.source_types_used().Set(SourceFile::SOURCE_RS);
+ cdylib.rust_values().set_crate_root(cd_root);
+ cdylib.rust_values().set_crate_type(RustValues::CRATE_CDYLIB);
+ cdylib.rust_values().crate_name() = "cd";
+ cdylib.private_deps().push_back(LabelTargetPair(&cfg_group));
+ cdylib.SetToolchain(stamp_setup.toolchain());
+ ASSERT_TRUE(cdylib.OnResolved(&err));
+
+ StringOutputBuffer cd_out;
+ NinjaRustBinaryTargetWriter cd_writer(&cdylib, cd_out);
+ cd_writer.Run();
+
+ const char cd_expected[] =
+ "crate_name = cd\n"
+ "crate_type = cdylib\n"
+ "output_extension = .so\n"
+ "output_dir = \n"
+ "rustflags =\n"
+ "rustenv =\n"
+ "root_out_dir = .\n"
+ "target_gen_dir = gen/cdylib\n"
+ "target_out_dir = obj/cdylib\n"
+ "target_output_name = libcd\n"
+ "\n"
+ "build obj/cdylib/libcd.so: rust_cdylib ../../cdylib/lib.rs | "
+ "../../cdylib/lib.rs ../../cfg/spec2.json ../../cfg/spec1.json "
+ "obj/tc/setup.stamp || obj/cfg/grp.harddeps.stamp obj/cfg/act.stamp "
+ "obj/cfg/act2.stamp\n"
+ " source_file_part = lib.rs\n"
+ " source_name_part = lib\n"
+ " externs =\n"
+ " rustdeps =\n"
+ " ldflags =\n"
+ " sources = ../../cdylib/lib.rs ../../cfg/spec2.json "
+ "../../cfg/spec1.json\n";
+ EXPECT_EQ(cd_expected, cd_out);
+
+ Target staticlib(stamp_setup.settings(),
+ Label(SourceDir("//static/"), "st"));
+ staticlib.set_output_type(Target::STATIC_LIBRARY);
+ staticlib.visibility().SetPublic();
+ SourceFile st_root("//static/lib.rs");
+ staticlib.sources().push_back(st_root);
+ staticlib.source_types_used().Set(SourceFile::SOURCE_RS);
+ staticlib.rust_values().set_crate_root(st_root);
+ staticlib.rust_values().crate_name() = "st";
+ staticlib.private_deps().push_back(LabelTargetPair(&cfg_group));
+ staticlib.SetToolchain(stamp_setup.toolchain());
+ ASSERT_TRUE(staticlib.OnResolved(&err));
+
+ StringOutputBuffer st_out;
+ NinjaRustBinaryTargetWriter st_writer(&staticlib, st_out);
+ st_writer.Run();
+
+ const char st_expected[] =
+ "crate_name = st\n"
+ "crate_type = staticlib\n"
+ "output_extension = .a\n"
+ "output_dir = \n"
+ "rustflags =\n"
+ "rustenv =\n"
+ "root_out_dir = .\n"
+ "target_gen_dir = gen/static\n"
+ "target_out_dir = obj/static\n"
+ "target_output_name = libst\n"
+ "\n"
+ "build obj/static/libst.a: rust_staticlib ../../static/lib.rs | "
+ "../../static/lib.rs ../../cfg/spec2.json ../../cfg/spec1.json "
+ "obj/tc/setup.stamp || obj/cfg/grp.harddeps.stamp obj/cfg/act.stamp "
+ "obj/cfg/act2.stamp\n"
+ " source_file_part = lib.rs\n"
+ " source_name_part = lib\n"
+ " externs =\n"
+ " rustdeps =\n"
+ " ldflags =\n"
+ " sources = ../../static/lib.rs ../../cfg/spec2.json "
+ "../../cfg/spec1.json\n";
+ EXPECT_EQ(st_expected, st_out);
+ }
+}
+
+TEST_F(NinjaRustBinaryTargetWriterTest, ToolInputs) {
+ Err err;
+ TestWithScope setup;
+
+ Toolchain toolchain_with_inputs(
+ setup.settings(),
+ Label(SourceDir("//toolchain_with_inputs/"), "with_inputs"));
+ std::unique_ptr<Tool> rust_bin = Tool::CreateTool(RustTool::kRsToolBin);
+ TestWithScope::SetCommandForTool(
+ "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} "
+ "--crate-type {{crate_type}} {{rustflags}} -o {{output}} "
+ "{{rustdeps}} {{externs}}",
+ rust_bin.get());
+ rust_bin->set_outputs(SubstitutionList::MakeForTest(
+ "{{root_out_dir}}/{{crate_name}}{{output_extension}}"));
+ rust_bin->set_inputs({SourceFile("//bin/rustc")});
+ toolchain_with_inputs.SetTool(std::move(rust_bin));
+ toolchain_with_inputs.ToolchainSetupComplete();
+
+ Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+ target.set_output_type(Target::EXECUTABLE);
+ target.visibility().SetPublic();
+ SourceFile main("//foo/main.rs");
+ target.sources().push_back(main);
+ target.source_types_used().Set(SourceFile::SOURCE_RS);
+ target.rust_values().set_crate_root(main);
+ target.rust_values().crate_name() = "foo_bar";
+ target.SetToolchain(&toolchain_with_inputs);
+ ASSERT_TRUE(target.OnResolved(&err));
+
+ StringOutputBuffer out;
+ NinjaRustBinaryTargetWriter writer(&target, out);
+ writer.Run();
+
+ const char expected[] =
+ "crate_name = foo_bar\n"
+ "crate_type = bin\n"
+ "output_extension = \n"
+ "output_dir = \n"
+ "rustflags =\n"
+ "rustenv =\n"
+ "root_out_dir = .\n"
+ "\n"
+ "build ./foo_bar: rust_bin ../../foo/main.rs | ../../foo/main.rs "
+ "../../bin/rustc\n"
+ " source_file_part = main.rs\n"
+ " source_name_part = main\n"
+ " externs =\n"
+ " rustdeps =\n"
+ " ldflags =\n"
+ " sources = ../../foo/main.rs\n";
+ EXPECT_EQ(expected, out);
+}
+
TEST_F(NinjaRustBinaryTargetWriterTest, CdylibDeps) {
Err err;
TestWithScope setup;