| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "tools/gn/metadata.h" |
| |
| #include "tools/gn/filesystem_utils.h" |
| |
| const char kMetadata_Help[] = |
| R"(Metadata Collection |
| |
| Metadata is information attached to targets throughout the dependency tree. GN |
| allows for the collection of this data into files written during the generation |
| step, enabling users to expose and aggregate this data based on the dependency |
| tree. |
| |
| generated_file targets |
| |
| Similar to the write_file() function, the generated_file target type |
| creates a file in the specified location with the specified content. The |
| primary difference between write_file() and this target type is that the |
| write_file function does the file write at parse time, while the |
| generated_file target type writes at target resolution time. See |
| "gn help generated_file" for more detail. |
| |
| When written at target resolution time, generated_file enables GN to |
| collect and write aggregated metadata from dependents. |
| |
| A generated_file target can declare either 'contents' to write statically |
| known contents to a file or 'data_keys' to aggregate metadata and write the |
| result to a file. It can also specify 'walk_keys' (to restrict the metadata |
| collection), 'output_conversion', and 'rebase'. |
| |
| |
| Collection and Aggregation |
| |
| Targets can declare a 'metadata' variable containing a scope, and this |
| metadata may be collected and written out to a file specified by |
| generated_file aggregation targets. The 'metadata' scope must contain |
| only list values since the aggregation step collects a list of these values. |
| |
| During the target resolution, generated_file targets will walk their |
| dependencies recursively, collecting metadata based on the specified |
| 'data_keys'. 'data_keys' is specified as a list of strings, used by the walk |
| to identify which variables in dependencies' 'metadata' scopes to collect. |
| |
| The walk begins with the listed dependencies of the 'generated_file' target. |
| The 'metadata' scope for each dependency is inspected for matching elements |
| of the 'generated_file' target's 'data_keys' list. If a match is found, the |
| data from the dependent's matching key list is appended to the aggregate walk |
| list. Note that this means that if more than one walk key is specified, the |
| data in all of them will be aggregated into one list. From there, the walk |
| will then recurse into the dependencies of each target it encounters, |
| collecting the specified metadata for each. |
| |
| For example: |
| |
| group("a") { |
| metadata = { |
| doom_melon = [ "enable" ] |
| my_files = [ "foo.cpp" ] |
| my_extra_files = [ "bar.cpp" ] |
| } |
| |
| deps = [ ":b" ] |
| } |
| |
| group("b") { |
| metadata = { |
| my_files = [ "baz.cpp" ] |
| } |
| } |
| |
| generated_file("metadata") { |
| outputs = [ "$root_build_dir/my_files.json" ] |
| data_keys = [ "my_files", "my_extra_files" ] |
| |
| deps = [ ":a" ] |
| } |
| |
| The above will produce the following file data: |
| |
| foo.cpp |
| bar.cpp |
| baz.cpp |
| |
| The dependency walk can be limited by using the 'walk_keys'. This is a list of |
| labels that should be included in the walk. All labels specified here should |
| also be in one of the deps lists. These keys act as barriers, where the walk |
| will only recurse into the targets listed. An empty list in all specified |
| barriers will end that portion of the walk. |
| |
| group("a") { |
| metadata = { |
| my_files = [ "foo.cpp" ] |
| my_files_barrier [ ":b" ] |
| } |
| |
| deps = [ ":b", ":c" ] |
| } |
| |
| group("b") { |
| metadata = { |
| my_files = [ "bar.cpp" ] |
| } |
| } |
| |
| group("c") { |
| metadata = { |
| my_files = [ "doom_melon.cpp" ] |
| } |
| } |
| |
| generated_file("metadata") { |
| outputs = [ "$root_build_dir/my_files.json" ] |
| data_keys = [ "my_files", "my_extra_files" ] |
| |
| deps = [ ":a" ] |
| } |
| |
| The above will produce the following file data (note that `doom_melon.cpp` is |
| not included): |
| |
| foo.cpp |
| bar.cpp |
| |
| A common example of this sort of barrier is in builds that have host tools |
| built as part of the tree, but do not want the metadata from those host tools |
| to be collected with the target-side code. |
| |
| Common Uses |
| |
| Metadata can be used to collect information about the different targets in the |
| build, and so a common use is to provide post-build tooling with a set of data |
| necessary to do aggregation tasks. For example, if each test target specifies |
| the output location of its binary to run in a metadata field, that can be |
| collected into a single file listing the locations of all tests in the |
| dependency tree. A local build tool (or continuous integration infrastructure) |
| can then use that file to know which tests exist, and where, and run them |
| accordingly. |
| |
| Another use is in image creation, where a post-build image tool needs to know |
| various pieces of information about the components it should include in order |
| to put together the correct image. |
| )"; |
| |
| bool Metadata::WalkStep(const BuildSettings* settings, |
| const std::vector<std::string>& keys_to_extract, |
| const std::vector<std::string>& keys_to_walk, |
| const SourceDir& rebase_dir, |
| std::vector<Value>* next_walk_keys, |
| std::vector<Value>* result, |
| Err* err) const { |
| // If there's no metadata, there's nothing to find, so quick exit. |
| if (contents_.empty()) { |
| next_walk_keys->emplace_back(nullptr, ""); |
| return true; |
| } |
| |
| // Pull the data from each specified key. |
| for (const auto& key : keys_to_extract) { |
| auto iter = contents_.find(key); |
| if (iter == contents_.end()) |
| continue; |
| assert(iter->second.type() == Value::LIST); |
| |
| if (!rebase_dir.is_null()) { |
| for (const auto& val : iter->second.list_value()) { |
| std::pair<Value, bool> pair = |
| this->RebaseValue(settings, rebase_dir, val, err); |
| if (!pair.second) |
| return false; |
| result->push_back(pair.first); |
| } |
| } else { |
| result->insert(result->end(), iter->second.list_value().begin(), |
| iter->second.list_value().end()); |
| } |
| } |
| |
| // Get the targets to look at next. If no keys_to_walk are present, we push |
| // the empty string to the list so that the target knows to include its deps |
| // and data_deps. The values used here must be lists of strings. |
| bool found_walk_key = false; |
| for (const auto& key : keys_to_walk) { |
| auto iter = contents_.find(key); |
| if (iter != contents_.end()) { |
| found_walk_key = true; |
| assert(iter->second.type() == Value::LIST); |
| for (const auto& val : iter->second.list_value()) { |
| if (!val.VerifyTypeIs(Value::STRING, err)) |
| return false; |
| next_walk_keys->emplace_back(val); |
| } |
| } |
| } |
| |
| if (!found_walk_key) |
| next_walk_keys->emplace_back(nullptr, ""); |
| |
| return true; |
| } |
| |
| std::pair<Value, bool> Metadata::RebaseValue(const BuildSettings* settings, |
| const SourceDir& rebase_dir, |
| const Value& value, |
| Err* err) const { |
| switch (value.type()) { |
| case Value::STRING: |
| return this->RebaseStringValue(settings, rebase_dir, value, err); |
| case Value::LIST: |
| return this->RebaseListValue(settings, rebase_dir, value, err); |
| case Value::SCOPE: |
| return this->RebaseScopeValue(settings, rebase_dir, value, err); |
| default: |
| return std::make_pair(value, true); |
| } |
| } |
| |
| std::pair<Value, bool> Metadata::RebaseStringValue( |
| const BuildSettings* settings, |
| const SourceDir& rebase_dir, |
| const Value& value, |
| Err* err) const { |
| if (!value.VerifyTypeIs(Value::STRING, err)) |
| return std::make_pair(value, false); |
| std::string filename = source_dir_.ResolveRelativeAs( |
| /*as_file = */ true, value, err, settings->root_path_utf8()); |
| if (err->has_error()) |
| return std::make_pair(value, false); |
| Value rebased_value(value.origin(), RebasePath(filename, rebase_dir, |
| settings->root_path_utf8())); |
| return std::make_pair(rebased_value, true); |
| } |
| |
| std::pair<Value, bool> Metadata::RebaseListValue(const BuildSettings* settings, |
| const SourceDir& rebase_dir, |
| const Value& value, |
| Err* err) const { |
| if (!value.VerifyTypeIs(Value::LIST, err)) |
| return std::make_pair(value, false); |
| |
| Value rebased_list_value(value.origin(), Value::LIST); |
| for (auto& val : value.list_value()) { |
| std::pair<Value, bool> pair = RebaseValue(settings, rebase_dir, val, err); |
| if (!pair.second) |
| return std::make_pair(value, false); |
| rebased_list_value.list_value().push_back(pair.first); |
| } |
| return std::make_pair(rebased_list_value, true); |
| } |
| |
| std::pair<Value, bool> Metadata::RebaseScopeValue(const BuildSettings* settings, |
| const SourceDir& rebase_dir, |
| const Value& value, |
| Err* err) const { |
| if (!value.VerifyTypeIs(Value::SCOPE, err)) |
| return std::make_pair(value, false); |
| Value rebased_scope_value(value); |
| Scope::KeyValueMap scope_values; |
| value.scope_value()->GetCurrentScopeValues(&scope_values); |
| for (auto& value_pair : scope_values) { |
| std::pair<Value, bool> pair = |
| RebaseValue(settings, rebase_dir, value_pair.second, err); |
| if (!pair.second) |
| return std::make_pair(value, false); |
| |
| rebased_scope_value.scope_value()->SetValue(value_pair.first, pair.first, |
| value.origin()); |
| } |
| return std::make_pair(rebased_scope_value, true); |
| } |