Add an "outputs" command.
"gn outputs ..." is a new command-line tool that will list the outputs
for a target or source file. It is mostly intended to help users find
how to express a compile command that just compiles what they're working
on (often the output file to write is difficult to figure out).
Do some refactoring work to share this computation with
get_target_outputs and the "gn desc" code (both of which duplicated it
before).
Change-Id: I5b4a0ef30b106598900a96d9eee1ba516f7ac1f7
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/7740
Commit-Queue: Brett Wilson <brettw@chromium.org>
Reviewed-by: Scott Graham <scottmg@chromium.org>
diff --git a/build/gen.py b/build/gen.py
index bcd9f15..95d9b44 100755
--- a/build/gen.py
+++ b/build/gen.py
@@ -466,6 +466,7 @@
'src/gn/command_help.cc',
'src/gn/command_ls.cc',
'src/gn/command_meta.cc',
+ 'src/gn/command_outputs.cc',
'src/gn/command_path.cc',
'src/gn/command_refs.cc',
'src/gn/commands.cc',
diff --git a/docs/reference.md b/docs/reference.md
index c158fa4..2cdff65 100644
--- a/docs/reference.md
+++ b/docs/reference.md
@@ -15,6 +15,7 @@
* [help: Does what you think.](#cmd_help)
* [ls: List matching targets.](#cmd_ls)
* [meta: List target metadata collection results.](#cmd_meta)
+ * [outputs: Which files a source/target make.](#cmd_outputs)
* [path: Find paths between two targets.](#cmd_path)
* [refs: Find stuff referencing a target or file.](#cmd_refs)
* [Target declarations](#targets)
@@ -971,6 +972,60 @@
target and all of its dependency tree, rebasing the strings in the `files`
key onto the source directory of the target's declaration relative to "/".
```
+### <a name="cmd_outputs"></a>**gn outputs <out_dir> <list of target or file names...> ***
+
+```
+ Lists the output files corresponding to the given target(s) or file name(s).
+ There can be multiple outputs because there can be more than one output
+ generated by a build step, and there can be more than one toolchain matched.
+ You can also list multiple inputs which will generate a union of all the
+ outputs from those inputs.
+
+ - The input target/file names are relative to the current directory.
+
+ - The output file names are relative to the root build directory.
+
+ This command is useful for finding a ninja command that will build only a
+ portion of the build.
+```
+
+#### **Target outputs**
+
+```
+ If the parameter is a target name that includes a toolchain, it will match
+ only that target in that toolchain. If no toolchain is specified, it will
+ match all targets with that name in any toolchain.
+
+ The result will be the outputs specified by that target which could be a
+ library, executable, output of an action, a stamp file, etc.
+```
+
+#### **File outputs**
+
+```
+ If the parameter is a file name it will compute the output for that compile
+ step for all targets in all toolchains that contain that file as a source
+ file.
+
+ If the source is not compiled (e.g. a header or text file), the command will
+ produce no output.
+
+ If the source is listed as an "input" to a binary target or action will
+ resolve to that target's outputs.
+```
+
+#### **Example**
+
+```
+ gn outputs out/debug some/directory:some_target
+ Find the outputs of a given target.
+
+ gn outputs out/debug src/project/my_file.cc | xargs ninja -C out/debug
+ Compiles just the given source file in all toolchains it's referenced in.
+
+ git diff --name-only | xargs gn outputs out/x64 | xargs ninja -C out/x64
+ Compiles all files changed in git.
+```
### <a name="cmd_path"></a>**gn path <out_dir> <target_one> <target_two>**
```
diff --git a/src/gn/command_outputs.cc b/src/gn/command_outputs.cc
new file mode 100644
index 0000000..376405b
--- /dev/null
+++ b/src/gn/command_outputs.cc
@@ -0,0 +1,155 @@
+// Copyright 2020 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 <stddef.h>
+
+#include <algorithm>
+
+#include "base/command_line.h"
+#include "base/strings/stringprintf.h"
+#include "gn/commands.h"
+#include "gn/setup.h"
+#include "gn/standard_out.h"
+
+namespace commands {
+
+const char kOutputs[] = "outputs";
+const char kOutputs_HelpShort[] = "outputs: Which files a source/target make.";
+const char kOutputs_Help[] =
+ R"(gn outputs <out_dir> <list of target or file names...> *
+
+ Lists the output files corresponding to the given target(s) or file name(s).
+ There can be multiple outputs because there can be more than one output
+ generated by a build step, and there can be more than one toolchain matched.
+ You can also list multiple inputs which will generate a union of all the
+ outputs from those inputs.
+
+ - The input target/file names are relative to the current directory.
+
+ - The output file names are relative to the root build directory.
+
+ This command is useful for finding a ninja command that will build only a
+ portion of the build.
+
+Target outputs
+
+ If the parameter is a target name that includes a toolchain, it will match
+ only that target in that toolchain. If no toolchain is specified, it will
+ match all targets with that name in any toolchain.
+
+ The result will be the outputs specified by that target which could be a
+ library, executable, output of an action, a stamp file, etc.
+
+File outputs
+
+ If the parameter is a file name it will compute the output for that compile
+ step for all targets in all toolchains that contain that file as a source
+ file.
+
+ If the source is not compiled (e.g. a header or text file), the command will
+ produce no output.
+
+ If the source is listed as an "input" to a binary target or action will
+ resolve to that target's outputs.
+
+Example
+
+ gn outputs out/debug some/directory:some_target
+ Find the outputs of a given target.
+
+ gn outputs out/debug src/project/my_file.cc | xargs ninja -C out/debug
+ Compiles just the given source file in all toolchains it's referenced in.
+
+ git diff --name-only | xargs gn outputs out/x64 | xargs ninja -C out/x64
+ Compiles all files changed in git.
+)";
+
+int RunOutputs(const std::vector<std::string>& args) {
+ if (args.size() < 2) {
+ Err(Location(),
+ "Expected a build dir and one or more input files or targets.\n"
+ "Usage: \"gn outputs <out_dir> <target-or-file>*\"")
+ .PrintToStdout();
+ return 1;
+ }
+
+ // Deliberately leaked to avoid expensive process teardown.
+ Setup* setup = new Setup;
+ if (!setup->DoSetup(args[0], false))
+ return 1;
+ if (!setup->Run())
+ return 1;
+
+ std::vector<std::string> inputs(args.begin() + 1, args.end());
+
+ UniqueVector<const Target*> target_matches;
+ UniqueVector<const Config*> config_matches;
+ UniqueVector<const Toolchain*> toolchain_matches;
+ UniqueVector<SourceFile> file_matches;
+ if (!ResolveFromCommandLineInput(setup, inputs, true, &target_matches,
+ &config_matches, &toolchain_matches,
+ &file_matches))
+ return 1;
+
+ // We only care about targets and files.
+ if (target_matches.empty() && file_matches.empty()) {
+ Err(Location(), "The input matched no targets or files.").PrintToStdout();
+ return 1;
+ }
+
+ // Resulting outputs.
+ std::vector<OutputFile> outputs;
+
+ // Files. This must go first because it may add to the "targets" list.
+ std::vector<const Target*> all_targets =
+ setup->builder().GetAllResolvedTargets();
+ for (const SourceFile& file : file_matches) {
+ std::vector<TargetContainingFile> targets;
+ GetTargetsContainingFile(setup, all_targets, file, true, &targets);
+ if (targets.empty()) {
+ Err(Location(), base::StringPrintf("No targets reference the file '%s'.",
+ file.value().c_str()))
+ .PrintToStdout();
+ return 1;
+ }
+
+ // There can be more than one target that references this file, evaluate the
+ // output name in all of them.
+ for (const TargetContainingFile& pair : targets) {
+ if (pair.second == HowTargetContainsFile::kInputs) {
+ // Inputs maps to the target itself. This will be evaluated below.
+ target_matches.push_back(pair.first);
+ } else if (pair.second == HowTargetContainsFile::kSources) {
+ // Source file, check it.
+ const char* computed_tool = nullptr;
+ std::vector<OutputFile> file_outputs;
+ pair.first->GetOutputFilesForSource(file, &computed_tool,
+ &file_outputs);
+ outputs.insert(outputs.end(), file_outputs.begin(), file_outputs.end());
+ }
+ }
+ }
+
+ // Targets.
+ for (const Target* target : target_matches) {
+ std::vector<SourceFile> output_files;
+ Err err;
+ if (!target->GetOutputsAsSourceFiles(LocationRange(), true, &output_files,
+ &err)) {
+ err.PrintToStdout();
+ return 1;
+ }
+
+ // Convert to OutputFiles.
+ for (const SourceFile& file : output_files)
+ outputs.emplace_back(&setup->build_settings(), file);
+ }
+
+ // Print.
+ for (const OutputFile& output_file : outputs)
+ printf("%s\n", output_file.value().c_str());
+ return 0;
+}
+
+} // namespace commands
diff --git a/src/gn/command_refs.cc b/src/gn/command_refs.cc
index 843a9ba..4f9ba05 100644
--- a/src/gn/command_refs.cc
+++ b/src/gn/command_refs.cc
@@ -129,63 +129,6 @@
RecursiveCollectRefs(dep_map, cur_dep->second, results);
}
-bool TargetContainsFile(const Target* target, const SourceFile& file) {
- for (const auto& cur_file : target->sources()) {
- if (cur_file == file)
- return true;
- }
- for (const auto& cur_file : target->public_headers()) {
- if (cur_file == file)
- return true;
- }
- for (ConfigValuesIterator iter(target); !iter.done(); iter.Next()) {
- for (const auto& cur_file : iter.cur().inputs()) {
- if (cur_file == file)
- return true;
- }
- }
- for (const auto& cur_file : target->data()) {
- if (cur_file == file.value())
- return true;
- if (cur_file.back() == '/' &&
- base::StartsWith(file.value(), cur_file, base::CompareCase::SENSITIVE))
- return true;
- }
-
- if (target->action_values().script().value() == file.value())
- return true;
-
- std::vector<SourceFile> output_sources;
- target->action_values().GetOutputsAsSourceFiles(target, &output_sources);
- for (const auto& cur_file : output_sources) {
- if (cur_file == file)
- return true;
- }
-
- for (const auto& cur_file : target->computed_outputs()) {
- if (cur_file.AsSourceFile(target->settings()->build_settings()) == file)
- return true;
- }
- return false;
-}
-
-void GetTargetsContainingFile(Setup* setup,
- const std::vector<const Target*>& all_targets,
- const SourceFile& file,
- bool all_toolchains,
- UniqueVector<const Target*>* matches) {
- Label default_toolchain = setup->loader()->default_toolchain_label();
- for (auto* target : all_targets) {
- if (!all_toolchains) {
- // Only check targets in the default toolchain.
- if (target->label().GetToolchainLabel() != default_toolchain)
- continue;
- }
- if (TargetContainsFile(target, file))
- matches->push_back(target);
- }
-}
-
bool TargetReferencesConfig(const Target* target, const Config* config) {
for (const LabelConfigPair& cur : target->configs()) {
if (cur.ptr == config)
@@ -457,8 +400,13 @@
setup->builder().GetAllResolvedTargets();
UniqueVector<const Target*> explicit_target_matches;
for (const auto& file : file_matches) {
+ std::vector<TargetContainingFile> target_containing;
GetTargetsContainingFile(setup, all_targets, file, all_toolchains,
- &explicit_target_matches);
+ &target_containing);
+
+ // Extract just the Target*.
+ for (const TargetContainingFile& pair : target_containing)
+ explicit_target_matches.push_back(pair.first);
}
for (auto* config : config_matches) {
GetTargetsReferencingConfig(setup, all_targets, config, all_toolchains,
diff --git a/src/gn/commands.cc b/src/gn/commands.cc
index 6dcf16a..1941cba 100644
--- a/src/gn/commands.cc
+++ b/src/gn/commands.cc
@@ -4,11 +4,15 @@
#include "gn/commands.h"
+#include <optional>
+
#include "base/command_line.h"
#include "base/environment.h"
#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
#include "base/values.h"
#include "gn/builder.h"
+#include "gn/config_values_extractors.h"
#include "gn/filesystem_utils.h"
#include "gn/item.h"
#include "gn/label.h"
@@ -362,6 +366,48 @@
}
#endif
+std::optional<HowTargetContainsFile> TargetContainsFile(
+ const Target* target,
+ const SourceFile& file) {
+ for (const auto& cur_file : target->sources()) {
+ if (cur_file == file)
+ return HowTargetContainsFile::kSources;
+ }
+ for (const auto& cur_file : target->public_headers()) {
+ if (cur_file == file)
+ return HowTargetContainsFile::kPublic;
+ }
+ for (ConfigValuesIterator iter(target); !iter.done(); iter.Next()) {
+ for (const auto& cur_file : iter.cur().inputs()) {
+ if (cur_file == file)
+ return HowTargetContainsFile::kInputs;
+ }
+ }
+ for (const auto& cur_file : target->data()) {
+ if (cur_file == file.value())
+ return HowTargetContainsFile::kData;
+ if (cur_file.back() == '/' &&
+ base::StartsWith(file.value(), cur_file, base::CompareCase::SENSITIVE))
+ return HowTargetContainsFile::kData;
+ }
+
+ if (target->action_values().script().value() == file.value())
+ return HowTargetContainsFile::kScript;
+
+ std::vector<SourceFile> output_sources;
+ target->action_values().GetOutputsAsSourceFiles(target, &output_sources);
+ for (const auto& cur_file : output_sources) {
+ if (cur_file == file)
+ return HowTargetContainsFile::kOutput;
+ }
+
+ for (const auto& cur_file : target->computed_outputs()) {
+ if (cur_file.AsSourceFile(target->settings()->build_settings()) == file)
+ return HowTargetContainsFile::kOutput;
+ }
+ return std::nullopt;
+}
+
} // namespace
CommandInfo::CommandInfo()
@@ -388,6 +434,7 @@
INSERT_COMMAND(Help)
INSERT_COMMAND(Meta)
INSERT_COMMAND(Ls)
+ INSERT_COMMAND(Outputs)
INSERT_COMMAND(Path)
INSERT_COMMAND(Refs)
@@ -558,4 +605,21 @@
FilterAndPrintTargets(&target_vector, out);
}
+void GetTargetsContainingFile(Setup* setup,
+ const std::vector<const Target*>& all_targets,
+ const SourceFile& file,
+ bool all_toolchains,
+ std::vector<TargetContainingFile>* matches) {
+ Label default_toolchain = setup->loader()->default_toolchain_label();
+ for (auto* target : all_targets) {
+ if (!all_toolchains) {
+ // Only check targets in the default toolchain.
+ if (target->label().GetToolchainLabel() != default_toolchain)
+ continue;
+ }
+ if (auto how = TargetContainsFile(target, file))
+ matches->emplace_back(target, *how);
+ }
+}
+
} // namespace commands
diff --git a/src/gn/commands.h b/src/gn/commands.h
index d9b6862..a3e6e42 100644
--- a/src/gn/commands.h
+++ b/src/gn/commands.h
@@ -79,6 +79,11 @@
extern const char kLs_Help[];
int RunLs(const std::vector<std::string>& args);
+extern const char kOutputs[];
+extern const char kOutputs_HelpShort[];
+extern const char kOutputs_Help[];
+int RunOutputs(const std::vector<std::string>& args);
+
extern const char kPath[];
extern const char kPath_HelpShort[];
extern const char kPath_Help[];
@@ -118,6 +123,9 @@
// Resolves a vector of command line inputs and figures out the full set of
// things they resolve to.
//
+// On success, returns true and populates the vectors. On failure, prints the
+// error and returns false.
+//
// Patterns with wildcards will only match targets. The file_matches aren't
// validated that they are real files or referenced by any targets. They're just
// the set of things that didn't match anything else.
@@ -204,6 +212,23 @@
void FilterAndPrintTargetSet(const std::set<const Target*>& targets,
base::ListValue* out);
+// Computes which targets reference the given file and also stores how the
+// target references the file.
+enum class HowTargetContainsFile {
+ kSources,
+ kPublic,
+ kInputs,
+ kData,
+ kScript,
+ kOutput,
+};
+using TargetContainingFile = std::pair<const Target*, HowTargetContainsFile>;
+void GetTargetsContainingFile(Setup* setup,
+ const std::vector<const Target*>& all_targets,
+ const SourceFile& file,
+ bool all_toolchains,
+ std::vector<TargetContainingFile>* matches);
+
// Extra help from command_check.cc
extern const char kNoGnCheck_Help[];
diff --git a/src/gn/desc_builder.cc b/src/gn/desc_builder.cc
index f2a57e4..8ef77a6 100644
--- a/src/gn/desc_builder.cc
+++ b/src/gn/desc_builder.cc
@@ -61,14 +61,20 @@
// }
//
// Optionally, if "what" is specified while generating description, two other
-// properties can be requested that are not included by default
+// properties can be requested that are not included by default. First the
+// runtime dependendencies (see "gn help runtime_deps"):
//
-// "runtime_deps" : [list of computed runtime dependencies]
-// "source_outputs" : {
-// "source_file x" : [ list of outputs for source file x ]
-// "source_file y" : [ list of outputs for source file y ]
-// ...
-// }
+// "runtime_deps" : [list of computed runtime dependencies]
+//
+// Second, for targets whose sources map to outputs (binary targets,
+// action_foreach, and copies with non-constant outputs), the "source_outputs"
+// indicates the mapping from source to output file(s):
+//
+// "source_outputs" : {
+// "source_file x" : [ list of outputs for source file x ]
+// "source_file y" : [ list of outputs for source file y ]
+// ...
+// }
namespace {
@@ -684,6 +690,19 @@
}
void FillInSourceOutputs(base::DictionaryValue* res) {
+ // Only include "source outputs" if there are sources that map to outputs.
+ // Things like actions have constant per-target outputs that don't depend on
+ // the list of sources. These don't need source outputs.
+ if (target_->output_type() != Target::ACTION_FOREACH &&
+ target_->output_type() != Target::COPY_FILES && !target_->IsBinary())
+ return; // Everything else has constant outputs.
+
+ // "copy" targets may have patterns or not. If there's only one file, the
+ // user can specify a constant output name.
+ if (target_->output_type() == Target::COPY_FILES &&
+ target_->action_values().outputs().required_types().empty())
+ return; // Constant output.
+
auto dict = std::make_unique<base::DictionaryValue>();
for (const auto& source : target_->sources()) {
std::vector<OutputFile> outputs;
@@ -728,53 +747,28 @@
}
void FillInOutputs(base::DictionaryValue* res) {
- if (target_->output_type() == Target::ACTION) {
- auto list = std::make_unique<base::ListValue>();
- for (const auto& elem : target_->action_values().outputs().list())
- list->AppendString(elem.AsString());
+ std::vector<SourceFile> output_files;
+ Err err;
+ if (!target_->GetOutputsAsSourceFiles(LocationRange(), true, &output_files,
+ &err)) {
+ err.PrintToStdout();
+ return;
+ }
+ res->SetWithoutPathExpansion(variables::kOutputs,
+ RenderValue(output_files));
- res->SetWithoutPathExpansion(variables::kOutputs, std::move(list));
- } else if (target_->output_type() == Target::CREATE_BUNDLE ||
- target_->output_type() == Target::GENERATED_FILE) {
- Err err;
- std::vector<SourceFile> output_files;
- if (!target_->bundle_data().GetOutputsAsSourceFiles(
- target_->settings(), target_, &output_files, &err)) {
- err.PrintToStdout();
- }
- res->SetWithoutPathExpansion(variables::kOutputs,
- RenderValue(output_files));
- } else if (target_->output_type() == Target::ACTION_FOREACH ||
- target_->output_type() == Target::COPY_FILES) {
+ // Write some extra data for certain output types.
+ if (target_->output_type() == Target::ACTION_FOREACH ||
+ target_->output_type() == Target::COPY_FILES) {
const SubstitutionList& outputs = target_->action_values().outputs();
if (!outputs.required_types().empty()) {
+ // Write out the output patterns if there are any.
auto patterns = std::make_unique<base::ListValue>();
for (const auto& elem : outputs.list())
patterns->AppendString(elem.AsString());
res->SetWithoutPathExpansion("output_patterns", std::move(patterns));
}
- std::vector<SourceFile> output_files;
- SubstitutionWriter::ApplyListToSources(target_, target_->settings(),
- outputs, target_->sources(),
- &output_files);
- res->SetWithoutPathExpansion(variables::kOutputs,
- RenderValue(output_files));
- } else {
- DCHECK(target_->IsBinary());
- const Tool* tool =
- target_->toolchain()->GetToolForTargetFinalOutput(target_);
-
- std::vector<OutputFile> output_files;
- SubstitutionWriter::ApplyListToLinkerAsOutputFile(
- target_, tool, tool->outputs(), &output_files);
- std::vector<SourceFile> output_files_as_source_file;
- for (const OutputFile& output_file : output_files)
- output_files_as_source_file.push_back(
- output_file.AsSourceFile(target_->settings()->build_settings()));
-
- res->SetWithoutPathExpansion(variables::kOutputs,
- RenderValue(output_files_as_source_file));
}
}
diff --git a/src/gn/function_get_target_outputs.cc b/src/gn/function_get_target_outputs.cc
index 7995a49..fa851f3 100644
--- a/src/gn/function_get_target_outputs.cc
+++ b/src/gn/function_get_target_outputs.cc
@@ -113,21 +113,19 @@
return Value();
}
- // Compute the output list.
+ // Range for GetOutputsAsSourceFiles to blame for errors.
+ LocationRange arg_range;
+ if (args[0].origin())
+ arg_range = args[0].origin()->GetRange();
+
std::vector<SourceFile> files;
- if (target->output_type() == Target::ACTION ||
- target->output_type() == Target::COPY_FILES ||
- target->output_type() == Target::ACTION_FOREACH ||
- target->output_type() == Target::GENERATED_FILE) {
- target->action_values().GetOutputsAsSourceFiles(target, &files);
- } else {
- // Other types of targets are not supported.
- *err =
- Err(args[0],
- "Target is not an action, action_foreach, generated_file, or copy.",
- "Only these target types are supported by get_target_outputs.");
+
+ // The build is currently running so only non-binary targets (they don't
+ // depend on the toolchain definition which may not have been loaded yet) can
+ // be queried. Pass false for build_complete so it will flag such queries as
+ // an error.
+ if (!target->GetOutputsAsSourceFiles(arg_range, false, &files, err))
return Value();
- }
// Convert to Values.
Value ret(function, Value::LIST);
diff --git a/src/gn/json_project_writer_unittest.cc b/src/gn/json_project_writer_unittest.cc
index 8a29425..f642ea6 100644
--- a/src/gn/json_project_writer_unittest.cc
+++ b/src/gn/json_project_writer_unittest.cc
@@ -2,12 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "base/strings/string_util.h"
#include "gn/json_project_writer.h"
+#include "base/strings/string_util.h"
#include "gn/substitution_list.h"
#include "gn/target.h"
-#include "gn/test_with_scope.h"
#include "gn/test_with_scheduler.h"
+#include "gn/test_with_scope.h"
#include "util/build_config.h"
#include "util/test/test.h"
@@ -41,14 +41,17 @@
std::vector<const Target*> targets;
targets.push_back(&target);
#if defined(OS_WIN)
- base::FilePath root_path = base::FilePath(FILE_PATH_LITERAL("c:/path/to/src"));
+ base::FilePath root_path =
+ base::FilePath(FILE_PATH_LITERAL("c:/path/to/src"));
#else
base::FilePath root_path = base::FilePath(FILE_PATH_LITERAL("/path/to/src"));
#endif
setup.build_settings()->SetRootPath(root_path);
g_scheduler->AddGenDependency(root_path.Append(FILE_PATH_LITERAL(".gn")));
- g_scheduler->AddGenDependency(root_path.Append(FILE_PATH_LITERAL("BUILD.gn")));
- g_scheduler->AddGenDependency(root_path.Append(FILE_PATH_LITERAL("build/BUILD.gn")));
+ g_scheduler->AddGenDependency(
+ root_path.Append(FILE_PATH_LITERAL("BUILD.gn")));
+ g_scheduler->AddGenDependency(
+ root_path.Append(FILE_PATH_LITERAL("build/BUILD.gn")));
std::string out =
JSONProjectWriter::RenderJSON(setup.build_settings(), targets);
#if defined(OS_WIN)
@@ -59,7 +62,8 @@
" \"build_settings\": {\n"
" \"build_dir\": \"//out/Debug/\",\n"
" \"default_toolchain\": \"//toolchain:default\",\n"
- " \"gen_input_files\": [ \"//.gn\", \"//BUILD.gn\", \"//build/BUILD.gn\" ],\n"
+ " \"gen_input_files\": [ \"//.gn\", \"//BUILD.gn\", "
+ "\"//build/BUILD.gn\" ],\n"
#if defined(OS_WIN)
" \"root_path\": \"c:/path/to/src\"\n"
#else
@@ -86,7 +90,7 @@
" }\n"
" }\n"
"}\n";
- EXPECT_EQ(expected_json, out);
+ EXPECT_EQ(expected_json, out) << out;
}
TEST_F(JSONWriter, RustTarget) {
@@ -172,13 +176,15 @@
std::vector<const Target*> targets;
targets.push_back(&target);
#if defined(OS_WIN)
- base::FilePath root_path = base::FilePath(FILE_PATH_LITERAL("c:/path/to/src"));
+ base::FilePath root_path =
+ base::FilePath(FILE_PATH_LITERAL("c:/path/to/src"));
#else
base::FilePath root_path = base::FilePath(FILE_PATH_LITERAL("/path/to/src"));
#endif
setup.build_settings()->SetRootPath(root_path);
g_scheduler->AddGenDependency(root_path.Append(FILE_PATH_LITERAL(".gn")));
- g_scheduler->AddGenDependency(root_path.Append(FILE_PATH_LITERAL("BUILD.gn")));
+ g_scheduler->AddGenDependency(
+ root_path.Append(FILE_PATH_LITERAL("BUILD.gn")));
std::string out =
JSONProjectWriter::RenderJSON(setup.build_settings(), targets);
#if defined(OS_WIN)
@@ -211,6 +217,9 @@
" \"response_file_contents\": [ \"-j\", \"{{source_name_part}}\" "
"],\n"
" \"script\": \"//foo/script.py\",\n"
+ " \"source_outputs\": {\n"
+ " \"//foo/input1.txt\": [ \"input1.out\" ]\n"
+ " },\n"
" \"sources\": [ \"//foo/input1.txt\" ],\n"
" \"testonly\": false,\n"
" \"toolchain\": \"\",\n"
diff --git a/src/gn/target.cc b/src/gn/target.cc
index 2469fba..93ad7c0 100644
--- a/src/gn/target.cc
+++ b/src/gn/target.cc
@@ -486,36 +486,115 @@
return false;
}
+bool Target::GetOutputsAsSourceFiles(const LocationRange& loc_for_error,
+ bool build_complete,
+ std::vector<SourceFile>* outputs,
+ Err* err) const {
+ const static char kBuildIncompleteMsg[] =
+ "This target is a binary target which can't be queried for its "
+ "outputs\nduring the build. It will work for action, action_foreach, "
+ "generated_file,\nand copy targets.";
+
+ outputs->clear();
+
+ std::vector<SourceFile> files;
+ if (output_type() == Target::ACTION || output_type() == Target::COPY_FILES ||
+ output_type() == Target::ACTION_FOREACH ||
+ output_type() == Target::GENERATED_FILE) {
+ action_values().GetOutputsAsSourceFiles(this, outputs);
+ } else if (output_type() == Target::CREATE_BUNDLE ||
+ output_type() == Target::GENERATED_FILE) {
+ if (!bundle_data().GetOutputsAsSourceFiles(settings(), this, outputs, err))
+ return false;
+ } else if (IsBinary() && output_type() != Target::SOURCE_SET) {
+ // Binary target with normal outputs (source sets have stamp outputs like
+ // groups).
+ DCHECK(IsBinary()) << static_cast<int>(output_type());
+ if (!build_complete) {
+ // Can't access the toolchain for a target before the build is complete.
+ // Otherwise it will race with loading and setting the toolchain
+ // definition.
+ *err = Err(loc_for_error, kBuildIncompleteMsg);
+ return false;
+ }
+
+ const Tool* tool = toolchain()->GetToolForTargetFinalOutput(this);
+
+ std::vector<OutputFile> output_files;
+ SubstitutionWriter::ApplyListToLinkerAsOutputFile(
+ this, tool, tool->outputs(), &output_files);
+ for (const OutputFile& output_file : output_files) {
+ outputs->push_back(
+ output_file.AsSourceFile(settings()->build_settings()));
+ }
+ } else {
+ // Everything else (like a group or something) has a stamp output. The
+ // dependency output file should have computed what this is. This won't be
+ // valid unless the build is complete.
+ if (!build_complete) {
+ *err = Err(loc_for_error, kBuildIncompleteMsg);
+ return false;
+ }
+ outputs->push_back(
+ dependency_output_file().AsSourceFile(settings()->build_settings()));
+ }
+ return true;
+}
+
bool Target::GetOutputFilesForSource(const SourceFile& source,
const char** computed_tool_type,
std::vector<OutputFile>* outputs) const {
+ DCHECK(toolchain()); // Should be resolved before calling.
+
outputs->clear();
*computed_tool_type = Tool::kToolNone;
- SourceFile::Type file_type = source.type();
- if (file_type == SourceFile::SOURCE_UNKNOWN)
- return false;
- if (file_type == SourceFile::SOURCE_O) {
- // Object files just get passed to the output and not compiled.
- outputs->push_back(OutputFile(settings()->build_settings(), source));
- return true;
+ if (output_type() == Target::COPY_FILES ||
+ output_type() == Target::ACTION_FOREACH) {
+ // These target types apply the output pattern to the input.
+ std::vector<SourceFile> output_files;
+ SubstitutionWriter::ApplyListToSourceAsOutputFile(
+ this, settings(), action_values().outputs(), source, outputs);
+ } else if (!IsBinary()) {
+ // All other non-binary target types just return the target outputs. We
+ // don't know if the build is complete and it doesn't matter for non-binary
+ // targets, so just assume it's not and pass "false".
+ std::vector<SourceFile> outputs_as_source_files;
+ Err err; // We can ignore the error and return empty for failure.
+ GetOutputsAsSourceFiles(LocationRange(), false, &outputs_as_source_files,
+ &err);
+
+ // Convert to output files.
+ for (const auto& cur : outputs_as_source_files)
+ outputs->emplace_back(OutputFile(settings()->build_settings(), cur));
+ } else {
+ // All binary targets do a tool lookup.
+ DCHECK(IsBinary());
+
+ SourceFile::Type file_type = source.type();
+ if (file_type == SourceFile::SOURCE_UNKNOWN)
+ return false;
+ if (file_type == SourceFile::SOURCE_O) {
+ // Object files just get passed to the output and not compiled.
+ outputs->emplace_back(OutputFile(settings()->build_settings(), source));
+ return true;
+ }
+
+ // Rust generates on a module level, not source.
+ if (file_type == SourceFile::SOURCE_RS)
+ return false;
+
+ *computed_tool_type = Tool::GetToolTypeForSourceType(file_type);
+ if (*computed_tool_type == Tool::kToolNone)
+ return false; // No tool for this file (it's a header file or something).
+ const Tool* tool = toolchain_->GetTool(*computed_tool_type);
+ if (!tool)
+ return false; // Tool does not apply for this toolchain.file.
+
+ // Figure out what output(s) this compiler produces.
+ SubstitutionWriter::ApplyListToCompilerAsOutputFile(
+ this, source, tool->outputs(), outputs);
}
-
- // Rust generates on a module level, not source.
- if (file_type == SourceFile::SOURCE_RS) {
- return false;
- }
-
- *computed_tool_type = Tool::GetToolTypeForSourceType(file_type);
- if (*computed_tool_type == Tool::kToolNone)
- return false; // No tool for this file (it's a header file or something).
- const Tool* tool = toolchain_->GetTool(*computed_tool_type);
- if (!tool)
- return false; // Tool does not apply for this toolchain.file.
-
- // Figure out what output(s) this compiler produces.
- SubstitutionWriter::ApplyListToCompilerAsOutputFile(this, source,
- tool->outputs(), outputs);
return !outputs->empty();
}
diff --git a/src/gn/target.h b/src/gn/target.h
index 3c7ebf9..700859c 100644
--- a/src/gn/target.h
+++ b/src/gn/target.h
@@ -340,10 +340,41 @@
return runtime_outputs_;
}
+ // Computes and returns the outputs of this target expressed as SourceFiles.
+ //
+ // For binary target this depends on the tool for this target so the toolchain
+ // must have been loaded beforehand. This will happen asynchronously so
+ // calling this on a binary target before the build is complete will produce a
+ // race condition.
+ //
+ // To resolve this, the caller passes in whether the entire build is complete
+ // (this is used for the introspection commands which run after everything
+ // else).
+ //
+ // If the build is complete, the toolchain will be used for binary targets to
+ // compute the outputs. If the build is not complete, calling this function
+ // for binary targets will produce an error.
+ //
+ // The |loc_for_error| is used to blame a location for any errors produced. It
+ // can be empty if there is no range (like this is being called based on the
+ // command-line.
+ bool GetOutputsAsSourceFiles(const LocationRange& loc_for_error,
+ bool build_complete,
+ std::vector<SourceFile>* outputs,
+ Err* err) const;
+
// Computes the set of output files resulting from compiling the given source
- // file. If the file can be compiled and the tool exists, fills the outputs
- // in and writes the tool type to computed_tool_type. If the file is not
- // compilable, returns false.
+ // file.
+ //
+ // For binary targets, if the file can be compiled and the tool exists, fills
+ // the outputs in and writes the tool type to computed_tool_type. If the file
+ // is not compilable, returns false.
+ //
+ // For action_foreach and copy targets, applies the output pattern to the
+ // given file name to compute the outputs.
+ //
+ // For all other target types, just returns the target outputs because such
+ // targets conceptually process all of their inputs as one step.
//
// The function can succeed with a "NONE" tool type for object files which
// are just passed to the output. The output will always be overwritten, not
diff --git a/src/gn/target_unittest.cc b/src/gn/target_unittest.cc
index 3844d04..09d7a1a 100644
--- a/src/gn/target_unittest.cc
+++ b/src/gn/target_unittest.cc
@@ -719,6 +719,8 @@
// Tests that runtime_outputs works without an explicit link_output for
// solink tools.
+//
+// Also tests GetOutputsAsSourceFiles() for binaries (the setup is the same).
TEST_F(TargetTest, RuntimeOuputs) {
TestWithScope setup;
Err err;
@@ -760,9 +762,146 @@
ASSERT_EQ(2u, target.runtime_outputs().size());
EXPECT_EQ("./a.dll", target.runtime_outputs()[0].value());
EXPECT_EQ("./a.pdb", target.runtime_outputs()[1].value());
+
+ // Test GetOutputsAsSourceFiles().
+ std::vector<SourceFile> computed_outputs;
+ EXPECT_TRUE(target.GetOutputsAsSourceFiles(LocationRange(), true,
+ &computed_outputs, &err));
+ ASSERT_EQ(3u, computed_outputs.size());
+ EXPECT_EQ("//out/Debug/a.dll.lib", computed_outputs[0].value());
+ EXPECT_EQ("//out/Debug/a.dll", computed_outputs[1].value());
+ EXPECT_EQ("//out/Debug/a.pdb", computed_outputs[2].value());
}
-// Shared libraries should be inherited across public shared liobrary
+// Tests Target::GetOutputFilesForSource for binary targets (these require a
+// tool definition). Also tests GetOutputsAsSourceFiles() for source sets.
+TEST_F(TargetTest, GetOutputFilesForSource_Binary) {
+ TestWithScope setup;
+
+ Toolchain toolchain(setup.settings(), Label(SourceDir("//tc/"), "tc"));
+
+ std::unique_ptr<Tool> tool = Tool::CreateTool(CTool::kCToolCxx);
+ CTool* cxx = tool->AsC();
+ cxx->set_outputs(SubstitutionList::MakeForTest("{{source_file_part}}.o"));
+ toolchain.SetTool(std::move(tool));
+
+ Target target(setup.settings(), Label(SourceDir("//a/"), "a"));
+ target.set_output_type(Target::SOURCE_SET);
+ target.SetToolchain(&toolchain);
+ Err err;
+ ASSERT_TRUE(target.OnResolved(&err));
+
+ const char* computed_tool_type = nullptr;
+ std::vector<OutputFile> output;
+ bool result = target.GetOutputFilesForSource(SourceFile("//source/input.cc"),
+ &computed_tool_type, &output);
+ ASSERT_TRUE(result);
+ EXPECT_EQ(std::string("cxx"), std::string(computed_tool_type));
+
+ // Outputs are relative to the build directory "//out/Debug/".
+ ASSERT_EQ(1u, output.size());
+ EXPECT_EQ("input.cc.o", output[0].value()) << output[0].value();
+
+ // Test GetOutputsAsSourceFiles(). Since this is a source set it should give a
+ // stamp file.
+ std::vector<SourceFile> computed_outputs;
+ EXPECT_TRUE(target.GetOutputsAsSourceFiles(LocationRange(), true,
+ &computed_outputs, &err));
+ ASSERT_EQ(1u, computed_outputs.size());
+ EXPECT_EQ("//out/Debug/obj/a/a.stamp", computed_outputs[0].value());
+}
+
+// Tests Target::GetOutputFilesForSource for action_foreach targets (these, like
+// copy targets, apply a pattern to the source file). Also tests
+// GetOutputsAsSourceFiles() for action_foreach().
+TEST_F(TargetTest, GetOutputFilesForSource_ActionForEach) {
+ TestWithScope setup;
+
+ TestTarget target(setup, "//a:a", Target::ACTION_FOREACH);
+ target.sources().push_back(SourceFile("//a/source_file1.txt"));
+ target.sources().push_back(SourceFile("//a/source_file2.txt"));
+ target.action_values().outputs() =
+ SubstitutionList::MakeForTest("//out/Debug/{{source_file_part}}.one",
+ "//out/Debug/{{source_file_part}}.two");
+ Err err;
+ ASSERT_TRUE(target.OnResolved(&err));
+
+ const char* computed_tool_type = nullptr;
+ std::vector<OutputFile> output;
+ bool result = target.GetOutputFilesForSource(SourceFile("//source/input.txt"),
+ &computed_tool_type, &output);
+ ASSERT_TRUE(result);
+
+ // Outputs are relative to the build directory "//out/Debug/".
+ ASSERT_EQ(2u, output.size());
+ EXPECT_EQ("input.txt.one", output[0].value());
+ EXPECT_EQ("input.txt.two", output[1].value());
+
+ // Test GetOutputsAsSourceFiles(). It should give both outputs for each of the
+ // two inputs.
+ std::vector<SourceFile> computed_outputs;
+ EXPECT_TRUE(target.GetOutputsAsSourceFiles(LocationRange(), true,
+ &computed_outputs, &err));
+ ASSERT_EQ(4u, computed_outputs.size());
+ EXPECT_EQ("//out/Debug/source_file1.txt.one", computed_outputs[0].value());
+ EXPECT_EQ("//out/Debug/source_file1.txt.two", computed_outputs[1].value());
+ EXPECT_EQ("//out/Debug/source_file2.txt.one", computed_outputs[2].value());
+ EXPECT_EQ("//out/Debug/source_file2.txt.two", computed_outputs[3].value());
+}
+
+// Tests Target::GetOutputFilesForSource for action targets (these just list the
+// output of the action as the result of all possible inputs). This should also
+// cover everything other than binary, action_foreach, and copy targets.
+TEST_F(TargetTest, GetOutputFilesForSource_Action) {
+ TestWithScope setup;
+
+ TestTarget target(setup, "//a:a", Target::ACTION);
+ target.sources().push_back(SourceFile("//a/source_file1.txt"));
+ target.action_values().outputs() =
+ SubstitutionList::MakeForTest("//out/Debug/one", "//out/Debug/two");
+ Err err;
+ ASSERT_TRUE(target.OnResolved(&err));
+
+ const char* computed_tool_type = nullptr;
+ std::vector<OutputFile> output;
+ bool result = target.GetOutputFilesForSource(SourceFile("//source/input.txt"),
+ &computed_tool_type, &output);
+ ASSERT_TRUE(result);
+
+ // Outputs are relative to the build directory "//out/Debug/".
+ ASSERT_EQ(2u, output.size());
+ EXPECT_EQ("one", output[0].value());
+ EXPECT_EQ("two", output[1].value());
+
+ // Test GetOutputsAsSourceFiles(). It should give the listed outputs.
+ std::vector<SourceFile> computed_outputs;
+ EXPECT_TRUE(target.GetOutputsAsSourceFiles(LocationRange(), true,
+ &computed_outputs, &err));
+ ASSERT_EQ(2u, computed_outputs.size());
+ EXPECT_EQ("//out/Debug/one", computed_outputs[0].value());
+ EXPECT_EQ("//out/Debug/two", computed_outputs[1].value());
+
+ // Test that the copy target type behaves the same. This target requires only
+ // one output.
+ target.action_values().outputs() =
+ SubstitutionList::MakeForTest("//out/Debug/one");
+ target.set_output_type(Target::COPY_FILES);
+
+ // Outputs are relative to the build directory "//out/Debug/".
+ result = target.GetOutputFilesForSource(SourceFile("//source/input.txt"),
+ &computed_tool_type, &output);
+ ASSERT_TRUE(result);
+ ASSERT_EQ(1u, output.size());
+ EXPECT_EQ("one", output[0].value());
+
+ // Test GetOutputsAsSourceFiles() for the copy case.
+ EXPECT_TRUE(target.GetOutputsAsSourceFiles(LocationRange(), true,
+ &computed_outputs, &err));
+ ASSERT_EQ(1u, computed_outputs.size()) << computed_outputs.size();
+ EXPECT_EQ("//out/Debug/one", computed_outputs[0].value());
+}
+
+// Shared libraries should be inherited across public shared library
// boundaries.
TEST_F(TargetTest, SharedInheritance) {
TestWithScope setup;
@@ -1234,8 +1373,7 @@
std::pair<std::string_view, Value>("a", a_expected));
Value walk_expected(nullptr, Value::LIST);
- walk_expected.list_value().push_back(
- Value(nullptr, "two"));
+ walk_expected.list_value().push_back(Value(nullptr, "two"));
one.metadata().contents().insert(
std::pair<std::string_view, Value>("walk", walk_expected));