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));