Add validation support to gn analyze/desc/path/refs Given target `foo` having validation `clippy` desc output: ``` Target //:foo type: group toolchain: //toolchain:toolchain ... Direct dependencies (try also "--all", "--tree", or even "--all --tree") //:foo.dep validations //:clippy ``` path output: ``` //:foo --[validation]--> //:clippy ``` refs output: ``` //:foo ``` Bug: 478798763 Change-Id: Ica196b758411cf502e0fdf0c5a8566d5615f9711 Reviewed-on: https://gn-review.googlesource.com/c/gn/+/20900 Reviewed-by: Takuto Ikuta <tikuta@google.com> Commit-Queue: Neri Marschik <nerima@google.com>
diff --git a/build/gen.py b/build/gen.py index e5e2d7e..29085c9 100755 --- a/build/gen.py +++ b/build/gen.py
@@ -827,6 +827,7 @@ 'src/gn/compile_commands_writer_unittest.cc', 'src/gn/config_unittest.cc', 'src/gn/config_values_extractors_unittest.cc', + 'src/gn/desc_builder_unittest.cc', 'src/gn/escape_unittest.cc', 'src/gn/exec_process_unittest.cc', 'src/gn/filesystem_utils_unittest.cc',
diff --git a/docs/reference.md b/docs/reference.md index a2231e6..6d951a5 100644 --- a/docs/reference.md +++ b/docs/reference.md
@@ -569,6 +569,7 @@ script sources testonly + validations visibility walk_keys weak_frameworks @@ -1331,7 +1332,7 @@ Restricts output to targets which refer to input files by a specific relation. Defaults to any relation. Can be provided multiple times to include multiple relations. - + ``` #### **Examples (target input)** @@ -3465,7 +3466,7 @@ hash = string_hash(long_string) `string_hash` returns a string that contains a hash of the argument. The hash - is computed by first calculating a SHA256 hash of the argument, and then + is computed by first calculating the SHA256 hash of the argument, and then returning the first 8 characters of the lowercase-ASCII, hexadecimal encoding of the SHA256 hash. @@ -8499,3 +8500,4 @@ * -v: Verbose logging. * --version: Prints the GN version number and exits. ``` +
diff --git a/src/gn/analyzer.cc b/src/gn/analyzer.cc index a4ee31f..5c1d5e0 100644 --- a/src/gn/analyzer.cc +++ b/src/gn/analyzer.cc
@@ -249,6 +249,9 @@ item->AsTarget()->GetDeps(Target::DEPS_ALL)) dep_map_.insert(std::make_pair(dep_target_pair.ptr, item)); + for (const auto& validation_target_pair : item->AsTarget()->validations()) + dep_map_.insert(std::make_pair(validation_target_pair.ptr, item)); + for (const auto& dep_config_pair : item->AsTarget()->configs()) dep_map_.insert(std::make_pair(dep_config_pair.ptr, item)); @@ -405,6 +408,8 @@ } else { for (const auto& pair : target->GetDeps(Target::DEPS_ALL)) FilterTarget(pair.ptr, seen, filtered); + for (const auto& pair : target->validations()) + FilterTarget(pair.ptr, seen, filtered); } } }
diff --git a/src/gn/analyzer_unittest.cc b/src/gn/analyzer_unittest.cc index 5952562..c5aa8d4 100644 --- a/src/gn/analyzer_unittest.cc +++ b/src/gn/analyzer_unittest.cc
@@ -754,4 +754,47 @@ "}"); } +// Tests that a target is marked as affected if its validations are modified. +TEST_F(AnalyzerTest, TargetRefersToValidations) { + std::unique_ptr<Target> t = MakeTarget("//dir", "target_name"); + Target* t_raw = t.get(); + std::unique_ptr<Target> v = MakeTarget("//dir", "validation_name"); + Target* v_raw = v.get(); + v_raw->set_output_type(Target::ACTION); + v_raw->action_values().set_script(SourceFile("//dir/other.py")); + + t_raw->validations().push_back(LabelTargetPair(v.get())); + + builder_.ItemDefined(std::move(t)); + builder_.ItemDefined(std::move(v)); + + // Initially no dependency. + RunAnalyzerTest( + R"({ + "files": [ "//dir/script.py" ], + "additional_compile_targets": [ "//dir:target_name" ], + "test_targets": [] + })", + "{" + R"("compile_targets":[],)" + R"/("status":"No dependency",)/" + R"("test_targets":[])" + "}"); + + // Now change validation target to use the script. + v_raw->action_values().set_script(SourceFile("//dir/script.py")); + + RunAnalyzerTest( + R"({ + "files": [ "//dir/script.py" ], + "additional_compile_targets": [ "//dir:target_name" ], + "test_targets": [] + })", + "{" + R"("compile_targets":["//dir:target_name"],)" + R"/("status":"Found dependency",)/" + R"("test_targets":[])" + "}"); +} + } // namespace gn_analyzer_unittest
diff --git a/src/gn/command_desc.cc b/src/gn/command_desc.cc index 491afa3..2e8f18a 100644 --- a/src/gn/command_desc.cc +++ b/src/gn/command_desc.cc
@@ -266,6 +266,7 @@ std::map<std::string, DescHandlerFunc> GetHandlers() { return {{"type", LabelHandler}, {"toolchain", LabelHandler}, + {variables::kValidations, DefaultHandler}, {variables::kVisibility, VisibilityHandler}, {variables::kMetadata, MetadataHandler}, {variables::kTestonly, DefaultHandler}, @@ -397,6 +398,7 @@ HandleProperty(variables::kPrecompiledHeader, handler_map, v, dict); HandleProperty(variables::kPrecompiledSource, handler_map, v, dict); HandleProperty(variables::kDeps, handler_map, v, dict); + HandleProperty(variables::kValidations, handler_map, v, dict); HandleProperty(variables::kLibs, handler_map, v, dict); HandleProperty(variables::kLibDirs, handler_map, v, dict); HandleProperty(variables::kDataKeys, handler_map, v, dict); @@ -529,6 +531,7 @@ script sources testonly + validations visibility walk_keys weak_frameworks
diff --git a/src/gn/command_path.cc b/src/gn/command_path.cc index 4ee1345..e3efdd5 100644 --- a/src/gn/command_path.cc +++ b/src/gn/command_path.cc
@@ -16,7 +16,7 @@ namespace { -enum class DepType { NONE, PUBLIC, PRIVATE, DATA }; +enum class DepType { NONE, PUBLIC, PRIVATE, DATA, VALIDATION }; // The dependency paths are stored in a vector. Assuming the chain: // A --[public]--> B --[private]--> C @@ -30,6 +30,7 @@ // How to search. enum class PrivateDeps { INCLUDE, EXCLUDE }; enum class DataDeps { INCLUDE, EXCLUDE }; +enum class ValidationDeps { INCLUDE, EXCLUDE }; enum class PrintWhat { ONE, ALL }; struct Options { @@ -74,6 +75,8 @@ result = DepType::PRIVATE; } else if (path[i].second == DepType::DATA) { result = DepType::DATA; + } else if (path[i].second == DepType::VALIDATION) { + result = DepType::VALIDATION; } } return result; @@ -87,7 +90,8 @@ return "private"; case DepType::DATA: return "data"; - break; + case DepType::VALIDATION: + return "validation"; case DepType::NONE: default: return ""; @@ -170,6 +174,7 @@ const Target* to, PrivateDeps private_deps, DataDeps data_deps, + ValidationDeps validation_deps, PrintWhat print_what, Stats* stats) { // Seed the initial stack with just the "from" target. @@ -244,6 +249,14 @@ work_queue.back().push_back(TargetDep(pair.ptr, DepType::DATA)); } } + + if (validation_deps == ValidationDeps::INCLUDE) { + // Add validations. + for (const auto& pair : current_target->validations()) { + work_queue.push_back(current_path); + work_queue.back().push_back(TargetDep(pair.ptr, DepType::VALIDATION)); + } + } } } @@ -252,15 +265,15 @@ const Options& options, Stats* stats) { BreadthFirstSearch(from, to, PrivateDeps::EXCLUDE, DataDeps::EXCLUDE, - options.print_what, stats); + ValidationDeps::EXCLUDE, options.print_what, stats); if (!options.public_only) { - // Check private deps. + // Check private deps and validations. BreadthFirstSearch(from, to, PrivateDeps::INCLUDE, DataDeps::EXCLUDE, - options.print_what, stats); + ValidationDeps::INCLUDE, options.print_what, stats); if (options.with_data) { // Check data deps. BreadthFirstSearch(from, to, PrivateDeps::INCLUDE, DataDeps::INCLUDE, - options.print_what, stats); + ValidationDeps::INCLUDE, options.print_what, stats); } } }
diff --git a/src/gn/command_refs.cc b/src/gn/command_refs.cc index b5ad605..0e7770b 100644 --- a/src/gn/command_refs.cc +++ b/src/gn/command_refs.cc
@@ -38,6 +38,8 @@ for (auto* target : setup->builder().GetAllResolvedTargets()) { for (const auto& dep_pair : target->GetDeps(Target::DEPS_ALL)) dep_map->insert(std::make_pair(dep_pair.ptr, target)); + for (const auto& validation_pair : target->validations()) + dep_map->insert(std::make_pair(validation_pair.ptr, target)); } }
diff --git a/src/gn/desc_builder.cc b/src/gn/desc_builder.cc index 1b80302..8c6d897 100644 --- a/src/gn/desc_builder.cc +++ b/src/gn/desc_builder.cc
@@ -575,6 +575,10 @@ if (what(variables::kGenDeps) && !target_->gen_deps().empty()) res->SetWithoutPathExpansion(variables::kGenDeps, RenderGenDeps()); + if (what(variables::kValidations) && !target_->validations().empty()) + res->SetWithoutPathExpansion(variables::kValidations, + RenderValidations()); + // Runtime deps are special, print only when explicitly asked for and not in // overview mode. if (what_.find("runtime_deps") != what_.end()) @@ -743,6 +747,20 @@ return res; } + ValuePtr RenderValidations() { + auto res = std::make_unique<base::ListValue>(); + Label default_tc = target_->settings()->default_toolchain_label(); + const auto& validation_pairs = target_->validations(); + std::vector<std::string> validations; + validations.reserve(validation_pairs.size()); + for (const auto& pair : validation_pairs) + validations.push_back(pair.label.GetUserVisibleName(default_tc)); + std::sort(validations.begin(), validations.end()); + for (const auto& dep : validations) + res->AppendString(dep); + return res; + } + ValuePtr RenderRuntimeDeps() { auto res = std::make_unique<base::ListValue>();
diff --git a/src/gn/desc_builder_unittest.cc b/src/gn/desc_builder_unittest.cc new file mode 100644 index 0000000..23f1772 --- /dev/null +++ b/src/gn/desc_builder_unittest.cc
@@ -0,0 +1,38 @@ +// Copyright (c) 2026 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 "gn/desc_builder.h" + +#include "gn/test_with_scope.h" +#include "util/test/test.h" + +TEST(DescBuilder, TargetWithValidations) { + TestWithScope setup; + Err err; + + Target validation_target(setup.settings(), Label(SourceDir("//foo/"), "val")); + validation_target.set_output_type(Target::ACTION); + validation_target.visibility().SetPublic(); + validation_target.SetToolchain(setup.toolchain()); + validation_target.action_values().set_script(SourceFile("//foo/script.py")); + validation_target.action_values().outputs() = + SubstitutionList::MakeForTest("//out/Debug/val.out"); + ASSERT_TRUE(validation_target.OnResolved(&err)); + + Target target(setup.settings(), Label(SourceDir("//foo/"), "target")); + target.set_output_type(Target::GROUP); + target.visibility().SetPublic(); + target.SetToolchain(setup.toolchain()); + target.validations().push_back(LabelTargetPair(&validation_target)); + ASSERT_TRUE(target.OnResolved(&err)); + + std::unique_ptr<base::DictionaryValue> desc = + DescBuilder::DescriptionForTarget(&target, "", false, false, false); + + base::Value* validations = desc->FindKey("validations"); + ASSERT_TRUE(validations); + ASSERT_TRUE(validations->is_list()); + ASSERT_EQ(1u, validations->GetList().size()); + EXPECT_EQ("//foo:val()", validations->GetList()[0].GetString()); +}