[metadata] Actually collect the metadata
Change-Id: I30aa7b457da815f2adb30b207e1c68c421304b6d
Reviewed-on: https://gn-review.googlesource.com/c/3422
Commit-Queue: Julie Hockett <juliehockett@google.com>
Reviewed-by: Brett Wilson <brettw@chromium.org>
diff --git a/tools/gn/functions_target.cc b/tools/gn/functions_target.cc
index 282b793..93553b9 100644
--- a/tools/gn/functions_target.cc
+++ b/tools/gn/functions_target.cc
@@ -789,12 +789,108 @@
The `output_conversion` variable specified the format to write the
value. See `gn help output_conversion`.
- One of `data` or `data_keys` must be specified; use of `data` will write the
- contents of that value to file, while use of `data_keys` will trigger a
+ One of `contents` or `data_keys` must be specified; use of `data` will write
+ the contents of that value to file, while use of `data_keys` will trigger a
metadata collection walk based on the dependencies of the target and the
optional values of the `rebase` and `walk_keys` variables. See
`gn help metadata`.
+Example (metadata collection)
+
+ Given the following targets defined in //base/BUILD.gn, where A -> B and B -> C and D:
+
+ group("a") {
+ metadata = {
+ doom_melon = [ "enable" ]
+ my_files = [ "foo.cpp" ]
+
+ // Note: this is functionally equivalent to not defining `my_barrier`
+ // at all in this target's metadata.
+ my_barrier = [ "" ]
+ }
+
+ deps = [ ":b" ]
+ }
+
+ group("c") {
+ metadata = {
+ my_files = [ "bar.cpp" ]
+ my_barrier = [ ":c" ]
+ }
+
+ deps = [ ":c", ":d" ]
+ }
+
+ group("c") {
+ metadata = {
+ doom_melon = [ "disable" ]
+ my_files = [ "baz.cpp" ]
+ }
+ }
+
+ group("d") {
+ metadata = {
+ my_files = [ "missing.cpp" ]
+ }
+ }
+
+ If the following generated_file target is defined:
+
+ generated_file("my_files_metadata") {
+ outputs = [ "$root_build_dir/my_files.json" ]
+ data_keys = [ "my_files" ]
+
+ deps = [ "//base:a" ]
+ }
+
+ The following will be written to "$root_build_dir/my_files.json" (less the
+ comments):
+ [
+ "foo.cpp", // from //base:a
+ "bar.cpp", // from //base:b via //base:a
+ "baz.cpp", // from //base:c via //base:b
+ "missing.cpp" // from //base:d via //base:b
+ ]
+
+ Alternatively, as an example of using walk_keys, if the following
+ generated_file target is defined:
+
+ generated_file("my_files_metadata") {
+ outputs = [ "$root_build_dir/my_files.json" ]
+ data_keys = [ "my_files" ]
+ walk_keys = [ "my_barrier" ]
+
+ deps = [ "//base:a" ]
+ }
+
+ The following will be written to "$root_build_dir/my_files.json" (again less
+ the comments):
+ [
+ "foo.cpp", // from //base:a
+ "bar.cpp", // from //base:b via //base:a
+ "baz.cpp", // from //base:c via //base:b
+ ]
+
+ If `rebase` is used in the following generated_file target:
+
+ generated_file("my_files_metadata") {
+ outputs = [ "$root_build_dir/my_files.json" ]
+ data_keys = [ "my_files" ]
+ walk_keys = [ "my_barrier" ]
+ rebase = root_build_dir
+
+ deps = [ "//base:a" ]
+ }
+
+ The following will be written to "$root_build_dir/my_files.json" (again less
+ the comments) (assuming root_build_dir = "//out"):
+ [
+ "../base/foo.cpp", // from //base:a
+ "../base/bar.cpp", // from //base:b via //base:a
+ "../base/baz.cpp", // from //base:c via //base:b
+ ]
+
+
Variables
data_keys
diff --git a/tools/gn/metadata_walk.cc b/tools/gn/metadata_walk.cc
index dabe285..3022e5e 100644
--- a/tools/gn/metadata_walk.cc
+++ b/tools/gn/metadata_walk.cc
@@ -15,7 +15,7 @@
for (const auto* target : targets_to_walk) {
auto pair = targets_walked->insert(target);
if (pair.second) {
- if (!target->GetMetadata(keys_to_extract, keys_to_walk, rebase_dir,
+ if (!target->GetMetadata(keys_to_extract, keys_to_walk, rebase_dir, false,
&result, targets_walked, err))
return std::vector<Value>();
}
diff --git a/tools/gn/ninja_generated_file_target_writer.cc b/tools/gn/ninja_generated_file_target_writer.cc
index bf57f0c..7dc60b0 100644
--- a/tools/gn/ninja_generated_file_target_writer.cc
+++ b/tools/gn/ninja_generated_file_target_writer.cc
@@ -42,6 +42,28 @@
}
void NinjaGeneratedFileTargetWriter::GenerateFile() {
+ Err err;
+
+ // If this is a metadata target, populate the write value with the appropriate
+ // data.
+ Value contents;
+ if (target_->contents().type() == Value::NONE) {
+ // Origin is set to the outputs location, so that errors with this value
+ // get flagged on the right target.
+ CHECK(target_->action_values().outputs().list().size() == 1U);
+ contents = Value(target_->action_values().outputs().list()[0].origin(),
+ Value::LIST);
+ std::set<const Target*> targets_walked;
+ if (!target_->GetMetadata(target_->data_keys(), target_->walk_keys(),
+ target_->rebase(), /*deps_only = */ true,
+ &contents.list_value(), &targets_walked, &err)) {
+ g_scheduler->FailWithError(err);
+ return;
+ }
+ } else {
+ contents = target_->contents();
+ }
+
std::vector<SourceFile> outputs_as_sources;
target_->action_values().GetOutputsAsSourceFiles(target_,
&outputs_as_sources);
@@ -52,17 +74,16 @@
ScopedTrace trace(TraceItem::TRACE_FILE_WRITE, outputs_as_sources[0].value());
// Compute output.
- Err err;
- std::ostringstream contents;
- ConvertValueToOutput(settings_, target_->contents(),
- target_->output_conversion(), contents, &err);
+ std::ostringstream out;
+ ConvertValueToOutput(settings_, contents, target_->output_conversion(), out,
+ &err);
if (err.has_error()) {
g_scheduler->FailWithError(err);
return;
}
- WriteFileIfChanged(output, contents.str(), &err);
+ WriteFileIfChanged(output, out.str(), &err);
if (err.has_error()) {
g_scheduler->FailWithError(err);
diff --git a/tools/gn/scheduler.cc b/tools/gn/scheduler.cc
index bca1472..3e4eee9 100644
--- a/tools/gn/scheduler.cc
+++ b/tools/gn/scheduler.cc
@@ -123,6 +123,16 @@
return false;
}
+void Scheduler::AddGeneratedFile(const SourceFile& entry) {
+ std::lock_guard<std::mutex> lock(lock_);
+ generated_files_.insert(std::make_pair(entry, true));
+}
+
+bool Scheduler::IsFileGeneratedByTarget(const SourceFile& file) const {
+ std::lock_guard<std::mutex> lock(lock_);
+ return generated_files_.find(file) != generated_files_.end();
+}
+
std::multimap<SourceFile, const Target*> Scheduler::GetUnknownGeneratedInputs()
const {
std::lock_guard<std::mutex> lock(lock_);
diff --git a/tools/gn/scheduler.h b/tools/gn/scheduler.h
index b6f5532..160921e 100644
--- a/tools/gn/scheduler.h
+++ b/tools/gn/scheduler.h
@@ -68,6 +68,10 @@
std::vector<const Target*> GetWriteRuntimeDepsTargets() const;
bool IsFileGeneratedByWriteRuntimeDeps(const OutputFile& file) const;
+ // Tracks generated_file calls.
+ void AddGeneratedFile(const SourceFile& entry);
+ bool IsFileGeneratedByTarget(const SourceFile& file) const;
+
// Unknown generated inputs are files that a target declares as an input
// in the output directory, but which aren't generated by any dependency.
//
@@ -140,6 +144,7 @@
std::vector<SourceFile> written_files_;
std::vector<const Target*> write_runtime_deps_targets_;
std::multimap<SourceFile, const Target*> unknown_generated_inputs_;
+ std::map<SourceFile, bool> generated_files_;
DISALLOW_COPY_AND_ASSIGN(Scheduler);
};
diff --git a/tools/gn/substitution_pattern.h b/tools/gn/substitution_pattern.h
index 2f01e6b..850d736 100644
--- a/tools/gn/substitution_pattern.h
+++ b/tools/gn/substitution_pattern.h
@@ -67,6 +67,8 @@
const std::vector<Subrange>& ranges() const { return ranges_; }
bool empty() const { return ranges_.empty(); }
+ const ParseNode* origin() const { return origin_; }
+
private:
std::vector<Subrange> ranges_;
const ParseNode* origin_;
diff --git a/tools/gn/target.cc b/tools/gn/target.cc
index c5eebd3..0e9b245 100644
--- a/tools/gn/target.cc
+++ b/tools/gn/target.cc
@@ -394,6 +394,12 @@
if (!write_runtime_deps_output_.value().empty())
g_scheduler->AddWriteRuntimeDepsTarget(this);
+ if (output_type_ == GENERATED_FILE) {
+ DCHECK(!computed_outputs_.empty());
+ g_scheduler->AddGeneratedFile(
+ computed_outputs_[0].AsSourceFile(settings()->build_settings()));
+ }
+
return true;
}
@@ -866,7 +872,9 @@
&seen_targets)) {
seen_targets.clear();
// Allow dependency to be through data_deps for files generated by gn.
- check_data_deps = g_scheduler->IsFileGeneratedByWriteRuntimeDeps(out_file);
+ check_data_deps =
+ g_scheduler->IsFileGeneratedByWriteRuntimeDeps(out_file) ||
+ g_scheduler->IsFileGeneratedByTarget(source);
// Check object files (much slower and very rare) only if the "normal"
// output check failed.
consider_object_files = !check_data_deps;
@@ -880,14 +888,25 @@
bool Target::GetMetadata(const std::vector<std::string>& keys_to_extract,
const std::vector<std::string>& keys_to_walk,
const SourceDir& rebase_dir,
+ bool deps_only,
std::vector<Value>* result,
std::set<const Target*>* targets_walked,
Err* err) const {
std::vector<Value> next_walk_keys;
- if (!metadata_.WalkStep(settings()->build_settings(), keys_to_extract,
- keys_to_walk, rebase_dir, &next_walk_keys, result,
- err))
- return false;
+ // If deps_only, this is the top-level target and thus we don't want to
+ // collect its metadata, only that of its deps and data_deps.
+ if (deps_only) {
+ // Empty string will be converted below to mean all deps and data_deps.
+ // Origin is null because this isn't declared anywhere, and should never
+ // trigger any errors.
+ next_walk_keys.push_back(Value(nullptr, ""));
+ } else {
+ // Otherwise, we walk this target and collect the appropriate data.
+ if (!metadata_.WalkStep(settings()->build_settings(), keys_to_extract,
+ keys_to_walk, rebase_dir, &next_walk_keys, result,
+ err))
+ return false;
+ }
// Gather walk keys and find the appropriate target. Targets identified in
// the walk key set must be deps or data_deps of the declaring target.
@@ -905,7 +924,7 @@
auto pair = targets_walked->insert(dep.ptr);
if (pair.second) {
if (!dep.ptr->GetMetadata(keys_to_extract, keys_to_walk, rebase_dir,
- result, targets_walked, err))
+ false, result, targets_walked, err))
return false;
}
}
@@ -924,7 +943,7 @@
auto pair = targets_walked->insert(dep.ptr);
if (pair.second) {
if (!dep.ptr->GetMetadata(keys_to_extract, keys_to_walk, rebase_dir,
- result, targets_walked, err))
+ false, result, targets_walked, err))
return false;
}
// We found it, so we can exit this search now.
diff --git a/tools/gn/target.h b/tools/gn/target.h
index 0087e9e..abc357e 100644
--- a/tools/gn/target.h
+++ b/tools/gn/target.h
@@ -155,6 +155,7 @@
bool GetMetadata(const std::vector<std::string>& keys_to_extract,
const std::vector<std::string>& keys_to_walk,
const SourceDir& rebase_dir,
+ bool deps_only,
std::vector<Value>* result,
std::set<const Target*>* targets_walked,
Err* err) const;
@@ -167,7 +168,7 @@
const Value& output_conversion() const { return output_conversion_; }
void set_output_conversion(const Value& value) { output_conversion_ = value; }
- // Metadata collection methods for WriteData targets.
+ // Metadata collection methods for GeneratedFile targets.
const SourceDir& rebase() const { return rebase_; }
void set_rebase(const SourceDir& value) { rebase_ = value; }
const std::vector<std::string>& data_keys() const { return data_keys_; }
diff --git a/tools/gn/target_unittest.cc b/tools/gn/target_unittest.cc
index 55605c6..1cc4d96 100644
--- a/tools/gn/target_unittest.cc
+++ b/tools/gn/target_unittest.cc
@@ -1118,7 +1118,8 @@
Err err;
std::vector<Value> result;
std::set<const Target*> targets;
- one.GetMetadata(data_keys, walk_keys, SourceDir(), &result, &targets, &err);
+ one.GetMetadata(data_keys, walk_keys, SourceDir(), false, &result, &targets,
+ &err);
EXPECT_FALSE(err.has_error());
std::vector<Value> expected;
@@ -1158,7 +1159,8 @@
Err err;
std::vector<Value> result;
std::set<const Target*> targets;
- one.GetMetadata(data_keys, walk_keys, SourceDir(), &result, &targets, &err);
+ one.GetMetadata(data_keys, walk_keys, SourceDir(), false, &result, &targets,
+ &err);
EXPECT_FALSE(err.has_error());
std::vector<Value> expected;
@@ -1207,7 +1209,8 @@
Err err;
std::vector<Value> result;
std::set<const Target*> targets;
- one.GetMetadata(data_keys, walk_keys, SourceDir(), &result, &targets, &err);
+ one.GetMetadata(data_keys, walk_keys, SourceDir(), false, &result, &targets,
+ &err);
EXPECT_FALSE(err.has_error()) << err.message();
std::vector<Value> expected;
@@ -1239,7 +1242,8 @@
Err err;
std::vector<Value> result;
std::set<const Target*> targets;
- one.GetMetadata(data_keys, walk_keys, SourceDir(), &result, &targets, &err);
+ one.GetMetadata(data_keys, walk_keys, SourceDir(), false, &result, &targets,
+ &err);
EXPECT_TRUE(err.has_error());
EXPECT_EQ(err.message(),
"I was expecting //foo:missing to be a dependency of "
@@ -1248,3 +1252,51 @@
"specified the appropriate toolchain.")
<< err.message();
}
+
+TEST_F(TargetTest, WriteMetadataCollection) {
+ TestWithScope setup;
+ Err err;
+
+ SourceFile source_file("//out/Debug/metadata.json");
+ OutputFile output_file(setup.build_settings(), source_file);
+
+ TestTarget generator(setup, "//foo:write", Target::GENERATED_FILE);
+ generator.action_values().outputs() =
+ SubstitutionList::MakeForTest("//out/Debug/metadata.json");
+ EXPECT_TRUE(generator.OnResolved(&err));
+
+ TestTarget middle_data_dep(setup, "//foo:middle", Target::EXECUTABLE);
+ middle_data_dep.data_deps().push_back(LabelTargetPair(&generator));
+ EXPECT_TRUE(middle_data_dep.OnResolved(&err));
+
+ // This target has a generated metadata input and no dependency makes it.
+ TestTarget dep_missing(setup, "//foo:no_dep", Target::EXECUTABLE);
+ dep_missing.sources().push_back(source_file);
+ EXPECT_TRUE(dep_missing.OnResolved(&err));
+ AssertSchedulerHasOneUnknownFileMatching(&dep_missing, source_file);
+ scheduler().ClearUnknownGeneratedInputsAndWrittenFiles();
+
+ // This target has a generated file and we've directly dependended on it.
+ TestTarget dep_present(setup, "//foo:with_dep", Target::EXECUTABLE);
+ dep_present.sources().push_back(source_file);
+ dep_present.private_deps().push_back(LabelTargetPair(&generator));
+ EXPECT_TRUE(dep_present.OnResolved(&err));
+ EXPECT_TRUE(scheduler().GetUnknownGeneratedInputs().empty());
+
+ // This target has a generated file and we've indirectly dependended on it
+ // via data_deps.
+ TestTarget dep_indirect(setup, "//foo:indirect_dep", Target::EXECUTABLE);
+ dep_indirect.sources().push_back(source_file);
+ dep_indirect.data_deps().push_back(LabelTargetPair(&middle_data_dep));
+ EXPECT_TRUE(dep_indirect.OnResolved(&err));
+ AssertSchedulerHasOneUnknownFileMatching(&dep_indirect, source_file);
+ scheduler().ClearUnknownGeneratedInputsAndWrittenFiles();
+
+ // This target has a generated file and we've directly dependended on it
+ // via data_deps.
+ TestTarget data_dep_present(setup, "//foo:with_data_dep", Target::EXECUTABLE);
+ data_dep_present.sources().push_back(source_file);
+ data_dep_present.data_deps().push_back(LabelTargetPair(&generator));
+ EXPECT_TRUE(data_dep_present.OnResolved(&err));
+ EXPECT_TRUE(scheduler().GetUnknownGeneratedInputs().empty());
+}