From 2a2787f0189943c5b01d442cea283c7b56f62e2b Mon Sep 17 00:00:00 2001 From: LN Liberda Date: Mon, 27 Apr 2026 04:40:34 +0200 Subject: [PATCH] community/gn: backport changes needed for chromium M148 --- ...-Add-function-expand_directory-to-gn.patch | 697 ++++++++++++++++++ ...in-string-config-values-defines-cfla.patch | 134 ++++ .../0003-Add-inputs-parameter-to-tool.patch | 613 +++++++++++++++ community/gn/APKBUILD | 8 +- 4 files changed, 1451 insertions(+), 1 deletion(-) create mode 100644 community/gn/0001-Add-function-expand_directory-to-gn.patch create mode 100644 community/gn/0002-Reject-newlines-in-string-config-values-defines-cfla.patch create mode 100644 community/gn/0003-Add-inputs-parameter-to-tool.patch diff --git a/community/gn/0001-Add-function-expand_directory-to-gn.patch b/community/gn/0001-Add-function-expand_directory-to-gn.patch new file mode 100644 index 00000000000..d9dc7d0cf0d --- /dev/null +++ b/community/gn/0001-Add-function-expand_directory-to-gn.patch @@ -0,0 +1,697 @@ +From 42ace47bb426ca9705175d866bb9bdf168d5535f Mon Sep 17 00:00:00 2001 +From: Matt Stark +Date: Mon, 13 Apr 2026 11:10:13 +1000 +Subject: [PATCH] Add function `expand_directory` to gn. + +This function expands a directory to a list of files. +This will allow us to do a variety of things, such as: +* Removing our `generate_libcxx_headers` script +* Ensuring that our whole sysroot is in the inputs to a rule. + +This is required because the contents of a sysroot are not able to be +stored in the chrome source code because chromeos sysroots can be paths +pointing to the chromeos checkout rather than the chrome checkout. + +It also allows us to significantly improve the quality of our third +party libraries, for which we would, after this change, no longer have +to keep the source file list in sync, but instead simply use those third +party libraries as the source of truth. + +Bug: b:491242305 +Change-Id: Ib52107b2f877021d3e59c54c9a554fac6a6a6964 +Reviewed-on: https://gn-review.googlesource.com/c/gn/+/21860 +Reviewed-by: Takuto Ikuta +Commit-Queue: Matt Stark +--- + build/gen.py | 2 + + docs/reference.md | 35 ++++ + src/gn/build_settings.h | 11 ++ + src/gn/function_exec_script.cc | 14 +- + src/gn/function_expand_directory.cc | 165 ++++++++++++++++++ + src/gn/function_expand_directory_unittest.cc | 168 +++++++++++++++++++ + src/gn/functions.cc | 1 + + src/gn/functions.h | 8 + + src/gn/setup.cc | 69 ++++++-- + src/gn/source_file.cc | 13 ++ + src/gn/source_file.h | 3 + + 11 files changed, 464 insertions(+), 25 deletions(-) + create mode 100644 src/gn/function_expand_directory.cc + create mode 100644 src/gn/function_expand_directory_unittest.cc + +diff --git a/build/gen.py b/build/gen.py +index e40627c8..2a7e7318 100755 +--- a/build/gen.py ++++ b/build/gen.py +@@ -698,6 +698,7 @@ def WriteGNNinja(path, platform, host, options, args_list): + 'src/gn/file_writer.cc', + 'src/gn/frameworks_utils.cc', + 'src/gn/function_exec_script.cc', ++ 'src/gn/function_expand_directory.cc', + 'src/gn/function_filter.cc', + 'src/gn/function_filter_labels.cc', + 'src/gn/function_foreach.cc', +@@ -842,6 +843,7 @@ def WriteGNNinja(path, platform, host, options, args_list): + 'src/gn/filesystem_utils_unittest.cc', + 'src/gn/file_writer_unittest.cc', + 'src/gn/frameworks_utils_unittest.cc', ++ 'src/gn/function_expand_directory_unittest.cc', + 'src/gn/function_filter_unittest.cc', + 'src/gn/function_filter_labels_unittest.cc', + 'src/gn/function_foreach_unittest.cc', +diff --git a/docs/reference.md b/docs/reference.md +index 05329606..27d5af0f 100644 +--- a/docs/reference.md ++++ b/docs/reference.md +@@ -41,6 +41,7 @@ + * [declare_args: Declare build arguments.](#func_declare_args) + * [defined: Returns whether an identifier is defined.](#func_defined) + * [exec_script: Synchronously run a script and return the output.](#func_exec_script) ++ * [expand_directory: Expand a source directory and return files.](#func_expand_directory) + * [filter_exclude: Remove values that match a set of patterns.](#func_filter_exclude) + * [filter_include: Remove values that do not match a set of patterns.](#func_filter_include) + * [filter_labels_exclude: Remove labels that match a set of patterns.](#func_filter_labels_exclude) +@@ -2611,6 +2612,25 @@ + # result. + exec_script("//foo/bar/myscript.py") + ``` ++### **expand_directory**: Expand a source directory and return files. [Back to Top](#gn-reference) ++ ++``` ++ expand_directory(directory, recursive) ++ ++ Returns a list of all files contained within the specified directory. ++ ++ Arguments: ++ directory: A string representing the directory to search, relative to ++ the current BUILD file or source-absolute (starting with "//"). ++ recursive: A boolean indicating whether to search recursively. ++ ++ Returns: ++ A list of source-absolute paths representing the files found, sorted ++ alphabetically. ++ ++ Example: ++ files = expand_directory("src/data", true) ++``` + ### **filter_exclude**: Remove values that match a set of patterns. [Back to Top](#gn-reference) + + ``` +@@ -7358,6 +7378,21 @@ + If both values are set, only the value in "exec_script_allowlist" will + have any effect (so don't set both!). + ++ expand_directory_allowlist [optional] ++ A list of .gn/.gni files (not labels) that have permission to call the ++ expand_directory function. If this list is defined, calls to ++ expand_directory will be checked against this list and GN will fail if ++ the current file isn't in the list. ++ ++ The use of expand_directory is restricted because it encourages ++ monolithic build targets with redundant inputs, which can slow down ++ the build. ++ ++ Example: ++ expand_directory_allowlist = [ ++ "//base/BUILD.gn", ++ ] ++ + export_compile_commands [optional] + A list of label patterns for which to generate a Clang compilation + database (see "gn help label_pattern" for the string format). +diff --git a/src/gn/build_settings.h b/src/gn/build_settings.h +index 6c482845..0d4decb5 100644 +--- a/src/gn/build_settings.h ++++ b/src/gn/build_settings.h +@@ -144,6 +144,15 @@ class BuildSettings { + exec_script_allowlist_ = std::move(list); + } + ++ // A list of files that can call expand_directory(). If the returned pointer ++ // is null, expand_directory may be called from anywhere. ++ const SourceFileSet* expand_directory_allowlist() const { ++ return expand_directory_allowlist_.get(); ++ } ++ void set_expand_directory_allowlist(std::unique_ptr list) { ++ expand_directory_allowlist_ = std::move(list); ++ } ++ + private: + Label root_target_label_; + std::vector root_patterns_; +@@ -167,6 +176,8 @@ class BuildSettings { + PrintCallback print_callback_; + + std::unique_ptr exec_script_allowlist_; ++ std::unique_ptr expand_directory_allowlist_ = ++ std::make_unique(); + + BuildSettings& operator=(const BuildSettings&) = delete; + }; +diff --git a/src/gn/function_exec_script.cc b/src/gn/function_exec_script.cc +index 01cdb661..966dbfcf 100644 +--- a/src/gn/function_exec_script.cc ++++ b/src/gn/function_exec_script.cc +@@ -15,6 +15,7 @@ + #include "gn/input_file.h" + #include "gn/parse_tree.h" + #include "gn/scheduler.h" ++#include "gn/source_file.h" + #include "gn/trace.h" + #include "gn/value.h" + #include "util/build_config.h" +@@ -27,17 +28,8 @@ namespace { + bool CheckExecScriptPermissions(const BuildSettings* build_settings, + const FunctionCallNode* function, + Err* err) { +- const SourceFileSet* allowlist = build_settings->exec_script_allowlist(); +- if (!allowlist) +- return true; // No allowlist specified, don't check. +- +- LocationRange function_range = function->GetRange(); +- if (!function_range.begin().file()) +- return true; // No file, might be some internal thing, implicitly pass. +- +- if (allowlist->find(function_range.begin().file()->name()) != +- allowlist->end()) +- return true; // allowlisted, this is OK. ++ if (InSourceAllowList(function, build_settings->exec_script_allowlist())) ++ return true; + + // Disallowed case. + *err = Err( +diff --git a/src/gn/function_expand_directory.cc b/src/gn/function_expand_directory.cc +new file mode 100644 +index 00000000..4005098a +--- /dev/null ++++ b/src/gn/function_expand_directory.cc +@@ -0,0 +1,165 @@ ++// Copyright 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 ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "base/files/file_enumerator.h" ++#include "base/files/file_util.h" ++#include "base/strings/stringprintf.h" ++#include "gn/err.h" ++#include "gn/filesystem_utils.h" ++#include "gn/functions.h" ++#include "gn/scheduler.h" ++#include "gn/scope.h" ++#include "gn/value.h" ++ ++namespace functions { ++ ++const char kExpandDirectory[] = "expand_directory"; ++const char kExpandDirectory_HelpShort[] = ++ "expand_directory: Expand a source directory and return files."; ++const char kExpandDirectory_Help[] = ++ R"(expand_directory: Expand a source directory and return files. ++ ++ expand_directory(directory, recursive) ++ ++ Returns a list of all files contained within the specified directory. ++ ++ Arguments: ++ directory: A string representing the directory to search, relative to ++ the current BUILD file or source-absolute (starting with "//"). ++ recursive: A boolean indicating whether to search recursively. ++ ++ Returns: ++ A list of source-absolute paths representing the files found, sorted ++ alphabetically. ++ ++ Example: ++ files = expand_directory("src/data", true) ++)"; ++ ++namespace { ++ ++Value ExpandDirectoryInternal(const ParseNode* function, ++ SourceDir source_path, ++ const base::FilePath& disk_path, ++ bool recursive) { ++ auto add_gen_dep = [&](const base::FilePath& path) { ++ g_scheduler->AddGenDependency( ++ path.StripTrailingSeparators().NormalizePathSeparatorsTo( ++ base::FilePath::kSeparators[0])); ++ }; ++ ++ add_gen_dep(disk_path); ++ ++ std::string disk_path_utf8 = FilePathToUTF8(disk_path); ++ Value files(function, Value::LIST); ++ ++ base::FileEnumerator traverser( ++ disk_path, recursive, ++ base::FileEnumerator::FILES | ++ (recursive ? base::FileEnumerator::DIRECTORIES : 0)); ++ for (base::FilePath current = traverser.Next(); !current.empty(); ++ current = traverser.Next()) { ++ if (traverser.GetInfo().IsDirectory()) { ++ add_gen_dep(current); ++ } else { ++ std::string full = source_path.value() + ++ FilePathToUTF8(current).substr(disk_path_utf8.size()); ++ NormalizePath(&full); ++ files.list_value().emplace_back(function, full); ++ } ++ } ++ ++ std::ranges::sort(files.list_value(), [](const auto& lhs, const auto& rhs) { ++ return lhs.string_value() < rhs.string_value(); ++ }); ++ ++ return files; ++} ++ ++} // namespace ++ ++Value RunExpandDirectory(Scope* scope, ++ const FunctionCallNode* function, ++ const std::vector& args, ++ Err* err) { ++ if (args.size() != 2) { ++ *err = Err(function, "Wrong number of arguments.", ++ "expand_directory() takes exactly two arguments"); ++ return Value(); ++ } ++ ++ if (!InSourceAllowList( ++ function, ++ scope->settings()->build_settings()->expand_directory_allowlist())) { ++ *err = Err( ++ function, "Disallowed expand_directory call.", ++ "The use of expand_directory is restricted in this build.\n" ++ "expand_directory is discouraged because it encourages monolithic \n" ++ "build targets with redundant inputs, slowing down the build.\n" ++ "\n" ++ "The allowed callers of expand_directory is maintained in the " ++ "\"//.gn\" file\n" ++ "if you need to modify the allowlist."); ++ return Value(); ++ } ++ ++ if (!args[0].VerifyTypeIs(Value::STRING, err) || ++ !args[1].VerifyTypeIs(Value::BOOLEAN, err)) { ++ return Value(); ++ } ++ ++ std::string root_path = scope->settings()->build_settings()->root_path_utf8(); ++ SourceDir dir = ++ scope->GetSourceDir().ResolveRelativeDir(args[0], err, root_path); ++ if (err->has_error()) ++ return Value(); ++ ++ bool recursive = args[1].boolean_value(); ++ ++ base::FilePath dir_path = ++ scope->settings()->build_settings()->GetFullPath(dir); ++ ++ if (!base::DirectoryExists(dir_path)) { ++ *err = ++ Err(function, "Directory does not exist: " + FilePathToUTF8(dir_path)); ++ return Value(); ++ } ++ ++ // This is highly likely to be called once per toolchain per directory. ++ // Since this involves a file system scan, it's worth caching. ++ struct CacheEntry { ++ std::mutex mutex; ++ Value result; ++ }; ++ ++ static std::map, CacheEntry> cache; ++ static std::mutex cache_mutex; ++ ++ CacheEntry* entry; ++ { ++ std::lock_guard lock(cache_mutex); ++ entry = &cache[{dir_path, recursive}]; ++ } ++ ++ // Now lock the per-entry mutex ++ std::lock_guard lock(entry->mutex); ++ if (entry->result.type() != Value::NONE) { ++ return entry->result; ++ } ++ ++ Value result = ExpandDirectoryInternal(function, dir, dir_path, recursive); ++ ++ entry->result = result; ++ return result; ++} ++ ++} // namespace functions +diff --git a/src/gn/function_expand_directory_unittest.cc b/src/gn/function_expand_directory_unittest.cc +new file mode 100644 +index 00000000..ecd467c6 +--- /dev/null ++++ b/src/gn/function_expand_directory_unittest.cc +@@ -0,0 +1,168 @@ ++// Copyright 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 ++ ++#include "base/files/file_util.h" ++#include "base/files/scoped_temp_dir.h" ++#include "gn/filesystem_utils.h" ++#include "gn/functions.h" ++#include "gn/input_file.h" ++#include "gn/parse_tree.h" ++#include "gn/test_with_scheduler.h" ++#include "gn/test_with_scope.h" ++#include "util/test/test.h" ++ ++class ExpandDirectoryTest : public TestWithScheduler { ++ protected: ++ ExpandDirectoryTest() { ++ CHECK(temp_dir_.CreateUniqueTempDir()); ++ setup.build_settings()->SetRootPath(temp_dir_.GetPath()); ++ } ++ ++ base::ScopedTempDir temp_dir_; ++ TestWithScope setup; ++ ++ std::unique_ptr SetupDefaultDirAndFunction( ++ InputFile* input_file, ++ base::FilePath dir) { ++ auto file1 = dir.AppendASCII("file1.txt"); ++ auto file2 = dir.AppendASCII("file2.txt"); ++ auto sub_dir = dir.AppendASCII("sub"); ++ auto file3 = sub_dir.AppendASCII("file3.txt"); ++ ++ EXPECT_TRUE(base::CreateDirectory(sub_dir)); ++ EXPECT_TRUE(WriteFile(file1, "content1", nullptr)); ++ EXPECT_TRUE(WriteFile(file2, "content2", nullptr)); ++ EXPECT_TRUE(WriteFile(file3, "content3", nullptr)); ++ ++ Location location(input_file, 1, 1); ++ Token token(location, Token::IDENTIFIER, "expand_directory"); ++ auto function = std::make_unique(); ++ function->set_function(token); ++ ++ auto args = std::make_unique(); ++ args->set_begin_token(token); ++ args->set_end(std::make_unique(token)); ++ function->set_args(std::move(args)); ++ ++ auto allowlist = std::make_unique(); ++ allowlist->insert(input_file->name()); ++ setup.build_settings()->set_expand_directory_allowlist( ++ std::move(allowlist)); ++ return function; ++ } ++}; ++ ++TEST_F(ExpandDirectoryTest, Recursive) { ++ auto dir_path = temp_dir_.GetPath().AppendASCII("foo").AppendASCII("bar"); ++ auto input_file = InputFile(SourceFile("//BUILD.gn")); ++ auto function = SetupDefaultDirAndFunction(&input_file, dir_path); ++ ++ Err err; ++ Value result = functions::RunExpandDirectory( ++ setup.scope(), function.get(), ++ {Value(nullptr, "//foo/bar"), Value(nullptr, true)}, &err); ++ ASSERT_FALSE(err.has_error()) << err.message(); ++ ++ ASSERT_EQ(result.type(), Value::LIST); ++ ASSERT_EQ(result.list_value().size(), 3); ++ EXPECT_EQ(result.list_value()[0].string_value(), "//foo/bar/file1.txt"); ++ EXPECT_EQ(result.list_value()[1].string_value(), "//foo/bar/file2.txt"); ++ EXPECT_EQ(result.list_value()[2].string_value(), "//foo/bar/sub/file3.txt"); ++ ++ std::vector deps = scheduler().GetGenDependencies(); ++ EXPECT_TRUE(std::ranges::find(deps, dir_path) != deps.end()) ++ << FilePathToUTF8(dir_path); ++ auto sub = dir_path.AppendASCII("sub"); ++ EXPECT_TRUE(std::ranges::find(deps, sub) != deps.end()) ++ << FilePathToUTF8(sub); ++} ++ ++TEST_F(ExpandDirectoryTest, NonRecursive) { ++ auto dir_path = temp_dir_.GetPath().AppendASCII("foo").AppendASCII("bar"); ++ auto input_file = InputFile(SourceFile("//foo/BUILD.gn")); ++ auto function = SetupDefaultDirAndFunction(&input_file, dir_path); ++ setup.scope()->set_source_dir(SourceDir("//foo/")); ++ ++ Err err; ++ Value result = functions::RunExpandDirectory( ++ setup.scope(), function.get(), ++ {Value(nullptr, "bar"), Value(nullptr, false)}, &err); ++ ASSERT_FALSE(err.has_error()) << err.message(); ++ ASSERT_EQ(result.type(), Value::LIST); ++ ASSERT_EQ(result.list_value().size(), 2); ++ EXPECT_EQ(result.list_value()[0].string_value(), "//foo/bar/file1.txt"); ++ EXPECT_EQ(result.list_value()[1].string_value(), "//foo/bar/file2.txt"); ++ ++ std::vector deps = scheduler().GetGenDependencies(); ++ EXPECT_TRUE(std::ranges::find(deps, dir_path) != deps.end()); ++ EXPECT_TRUE(std::ranges::find(deps, dir_path.AppendASCII("sub")) == ++ deps.end()); ++} ++ ++TEST_F(ExpandDirectoryTest, EmptyDir) { ++ std::string dir_str = FilePathToUTF8(temp_dir_.GetPath()); ++ ++ FunctionCallNode function; ++ Err err; ++ Value result = functions::RunExpandDirectory( ++ setup.scope(), &function, {Value(nullptr, dir_str), Value(nullptr, true)}, ++ &err); ++ ASSERT_FALSE(err.has_error()); ++ ASSERT_EQ(result.type(), Value::LIST); ++ ASSERT_EQ(result.list_value().size(), 0); ++} ++ ++TEST_F(ExpandDirectoryTest, NonExistentDir) { ++ base::FilePath non_existent = temp_dir_.GetPath().AppendASCII("non_existent"); ++ ++ FunctionCallNode function; ++ Err err; ++ Value result = functions::RunExpandDirectory( ++ setup.scope(), &function, ++ {Value(nullptr, FilePathToUTF8(non_existent)), Value(nullptr, true)}, ++ &err); ++ EXPECT_TRUE(err.has_error()); ++} ++ ++TEST_F(ExpandDirectoryTest, Allowlist) { ++ InputFile input_file(SourceFile("//BUILD.gn")); ++ Location location(&input_file, 1, 1); ++ Token token(location, Token::IDENTIFIER, "expand_directory"); ++ FunctionCallNode function; ++ function.set_function(token); ++ ++ auto args = std::make_unique(); ++ args->set_begin_token(token); ++ args->set_end(std::make_unique(token)); ++ function.set_args(std::move(args)); ++ ++ // No allowlist ++ { ++ Err err; ++ Value result = functions::RunExpandDirectory( ++ setup.scope(), &function, ++ {Value(nullptr, FilePathToUTF8(temp_dir_.GetPath())), ++ Value(nullptr, true)}, ++ &err); ++ EXPECT_TRUE(err.has_error()); ++ } ++ ++ // Empty allowlist ++ auto allowlist_owned = std::make_unique(); ++ auto allowlist = allowlist_owned.get(); ++ setup.build_settings()->set_expand_directory_allowlist( ++ std::move(allowlist_owned)); ++ allowlist->insert(SourceFile("//foo.gni")); ++ { ++ Err err; ++ Value result = functions::RunExpandDirectory( ++ setup.scope(), &function, ++ {Value(nullptr, FilePathToUTF8(temp_dir_.GetPath())), ++ Value(nullptr, true)}, ++ &err); ++ EXPECT_TRUE(err.has_error()); ++ } ++} +diff --git a/src/gn/functions.cc b/src/gn/functions.cc +index 830fcee7..d7099650 100644 +--- a/src/gn/functions.cc ++++ b/src/gn/functions.cc +@@ -1524,6 +1524,7 @@ struct FunctionInfoInitializer { + INSERT_FUNCTION(DeclareArgs, false) + INSERT_FUNCTION(Defined, false) + INSERT_FUNCTION(ExecScript, false) ++ INSERT_FUNCTION(ExpandDirectory, false) + INSERT_FUNCTION(FilterExclude, false) + INSERT_FUNCTION(FilterInclude, false) + INSERT_FUNCTION(FilterLabelsInclude, false) +diff --git a/src/gn/functions.h b/src/gn/functions.h +index 787e6ed2..bf97b9e9 100644 +--- a/src/gn/functions.h ++++ b/src/gn/functions.h +@@ -146,6 +146,14 @@ Value RunExecutable(Scope* scope, + BlockNode* block, + Err* err); + ++extern const char kExpandDirectory[]; ++extern const char kExpandDirectory_HelpShort[]; ++extern const char kExpandDirectory_Help[]; ++Value RunExpandDirectory(Scope* scope, ++ const FunctionCallNode* function, ++ const std::vector& args, ++ Err* err); ++ + extern const char kFilterExclude[]; + extern const char kFilterExclude_HelpShort[]; + extern const char kFilterExclude_Help[]; +diff --git a/src/gn/setup.cc b/src/gn/setup.cc +index b29e2ff0..b83a2ea8 100644 +--- a/src/gn/setup.cc ++++ b/src/gn/setup.cc +@@ -127,6 +127,21 @@ Variables + If both values are set, only the value in "exec_script_allowlist" will + have any effect (so don't set both!). + ++ expand_directory_allowlist [optional] ++ A list of .gn/.gni files (not labels) that have permission to call the ++ expand_directory function. If this list is defined, calls to ++ expand_directory will be checked against this list and GN will fail if ++ the current file isn't in the list. ++ ++ The use of expand_directory is restricted because it encourages ++ monolithic build targets with redundant inputs, which can slow down ++ the build. ++ ++ Example: ++ expand_directory_allowlist = [ ++ "//base/BUILD.gn", ++ ] ++ + export_compile_commands [optional] + A list of label patterns for which to generate a Clang compilation + database (see "gn help label_pattern" for the string format). +@@ -257,6 +272,28 @@ base::FilePath FindDotFile(const base::FilePath& current_dir) { + return FindDotFile(up_one_dir); + } + ++std::unique_ptr FillAllowlist(const Value* value, ++ const SourceDir& current_dir, ++ Err* err) { ++ if (!value) ++ return nullptr; ++ ++ if (!value->VerifyTypeIs(Value::LIST, err)) { ++ return nullptr; ++ } ++ auto allowlist = std::make_unique(); ++ for (const auto& item : value->list_value()) { ++ if (!item.VerifyTypeIs(Value::STRING, err)) { ++ return nullptr; ++ } ++ allowlist->insert(current_dir.ResolveRelativeFile(item, err)); ++ if (err->has_error()) { ++ return nullptr; ++ } ++ } ++ return allowlist; ++} ++ + // Called on any thread. Post the item to the builder on the main thread. + void ItemDefinedCallback(MsgLoop* task_runner, + Builder* builder_call_on_main_thread_only, +@@ -1107,24 +1144,28 @@ bool Setup::FillOtherConfig(const base::CommandLine& cmdline, Err* err) { + exec_script_allowlist_value = + dotfile_scope_.GetValue("exec_script_whitelist", true); + } +- + if (exec_script_allowlist_value) { +- // Fill the list of targets to check. +- if (!exec_script_allowlist_value->VerifyTypeIs(Value::LIST, err)) { ++ build_settings_.set_exec_script_allowlist( ++ FillAllowlist(exec_script_allowlist_value, current_dir, err)); ++ if (err->has_error()) { + return false; + } +- std::unique_ptr allowlist = +- std::make_unique(); +- for (const auto& item : exec_script_allowlist_value->list_value()) { +- if (!item.VerifyTypeIs(Value::STRING, err)) { +- return false; +- } +- allowlist->insert(current_dir.ResolveRelativeFile(item, err)); +- if (err->has_error()) { +- return false; +- } ++ } ++ ++ // Fill expand_directory_allowlist. ++ const Value* expand_directory_allowlist_value = ++ dotfile_scope_.GetValue("expand_directory_allowlist", true); ++ ++ if (expand_directory_allowlist_value) { ++ build_settings_.set_expand_directory_allowlist( ++ FillAllowlist(expand_directory_allowlist_value, current_dir, err)); ++ if (err->has_error()) { ++ return false; + } +- build_settings_.set_exec_script_allowlist(std::move(allowlist)); ++ } else { ++ // Treat unspecified as empty. ++ build_settings_.set_expand_directory_allowlist( ++ std::make_unique()); + } + + // Fill optional default_args. +diff --git a/src/gn/source_file.cc b/src/gn/source_file.cc +index 3a9198b3..ecc2d1e9 100644 +--- a/src/gn/source_file.cc ++++ b/src/gn/source_file.cc +@@ -4,6 +4,8 @@ + + #include + ++#include "gn/input_file.h" ++#include "gn/parse_tree.h" + #include "gn/source_file.h" + + #include "base/logging.h" +@@ -222,3 +224,14 @@ bool SourceFileTypeSet::MixedSourceUsed() const { + << static_cast(GoSourceUsed()) + << static_cast(SwiftSourceUsed())) > 2; + } ++ ++bool InSourceAllowList(const ParseNode* node, const SourceFileSet* allowlist) { ++ if (!allowlist) ++ return true; ++ ++ LocationRange range = node->GetRange(); ++ if (!range.begin().file()) ++ return true; ++ ++ return allowlist->find(range.begin().file()->name()) != allowlist->end(); ++} +diff --git a/src/gn/source_file.h b/src/gn/source_file.h +index f682f0d4..f63c5dc6 100644 +--- a/src/gn/source_file.h ++++ b/src/gn/source_file.h +@@ -154,6 +154,9 @@ struct hash { + // overall difference in "gn gen" time is about 10%. + using SourceFileSet = base::flat_set; + ++class ParseNode; ++bool InSourceAllowList(const ParseNode* node, const SourceFileSet* allowlist); ++ + // Represents a set of tool types. + class SourceFileTypeSet { + public: diff --git a/community/gn/0002-Reject-newlines-in-string-config-values-defines-cfla.patch b/community/gn/0002-Reject-newlines-in-string-config-values-defines-cfla.patch new file mode 100644 index 00000000000..312de70430f --- /dev/null +++ b/community/gn/0002-Reject-newlines-in-string-config-values-defines-cfla.patch @@ -0,0 +1,134 @@ +From 46fd0cdb48cf3d42e29e19112eb3f297b3127dfd Mon Sep 17 00:00:00 2001 +From: Nico Weber +Date: Mon, 9 Mar 2026 12:43:52 -0400 +Subject: [PATCH] Reject newlines in string config values (defines, cflags, + etc.) + +A literal newline in a define or flag value would be written verbatim +into ninja files, breaking ninja's line-based parsing. Rather than +trying to escape newlines (which would work on POSIX via $$'\n' but +has no equivalent for cmd.exe on Windows), reject them with a clear +error at GN time. A newline in a compiler flag is almost certainly +a mistake anyway. + +The validation is in GetStringList() so it covers all string config +values: defines, cflags, cflags_c, cflags_cc, cflags_objc, +cflags_objcc, asmflags, arflags, ldflags, rustflags, rustenv, and +swiftflags. + +Before, without the pkg-config.py change in the linked bug, things +failed at build time: + +``` +% autoninja -C out/gnlinux base_unittests +offline mode +ninja: Entering directory `out/gnlinux' + 0.20s load build.ninja failed: + + 1.47s Error: failed to load build.ninja: toolchain.ninja: line:61052: unexpected indent: " include_dirs = +``` + +Now, the fail at `gn gen` time instead: + +``` +% ~/src/gn/out/gn gen out/gnlinux +//build/config/linux/pkg-config.py ["-s", "../../build/linux/debian_bullseye_amd64-sysroot", "-a", "x64"] [] +ERROR at //build/config/linux/atk/BUILD.gn:33:5: Newlines in defines values are not supported. + "ATK_LIB_DIR=\"$atk_lib_dir\"", + ^----------------------------- +The value `ATK_LIB_DIR="[[],[],[],[],[]] +"` contains a newline. +See //build/config/linux/atk/BUILD.gn:19:1: whence it was called. +pkg_config("atk") { +^------------------ +See //ui/accessibility/BUILD.gn:465:20: which caused the file to be included. + configs += [ "//build/config/linux/atk" ] + ^------------------------- +``` + +Bug: 40176116 +Change-Id: I870d625552087430f5a679b8668a1929662e7b4a +Reviewed-on: https://gn-review.googlesource.com/c/gn/+/21460 +Reviewed-by: Takuto Ikuta +Commit-Queue: Nico Weber +Reviewed-by: Nico Weber +--- + src/gn/config_values_extractors_unittest.cc | 36 +++++++++++++++++++++ + src/gn/config_values_generator.cc | 13 ++++++++ + 2 files changed, 49 insertions(+) + +diff --git a/src/gn/config_values_extractors_unittest.cc b/src/gn/config_values_extractors_unittest.cc +index 3db4bff9..d187daba 100644 +--- a/src/gn/config_values_extractors_unittest.cc ++++ b/src/gn/config_values_extractors_unittest.cc +@@ -6,6 +6,7 @@ + + #include "gn/config.h" + #include "gn/config_values_extractors.h" ++#include "gn/config_values_generator.h" + #include "gn/target.h" + #include "gn/test_with_scope.h" + #include "util/test/test.h" +@@ -147,3 +148,38 @@ TEST(ConfigValuesExtractors, IncludeOrdering) { + "//target/ //target/config/ //target/all/ //target/direct/ " + "//dep1/all/ //dep2/all/ //dep1/direct/ "); + } ++ ++TEST(ConfigValuesGenerator, DefinesWithNewlineError) { ++ TestWithScope setup; ++ Err err; ++ ++ // Set up a scope with a defines list containing a newline. ++ Value defines_value(nullptr, Value::LIST); ++ defines_value.list_value().push_back(Value(nullptr, "GOOD")); ++ defines_value.list_value().push_back(Value(nullptr, "BAD=a\nb")); ++ setup.scope()->SetValue("defines", defines_value, nullptr); ++ ++ ConfigValues config_values; ++ ConfigValuesGenerator gen(&config_values, setup.scope(), ++ SourceDir("//foo/"), &err); ++ gen.Run(); ++ EXPECT_TRUE(err.has_error()); ++ EXPECT_EQ(err.message(), "Newlines in defines values are not supported."); ++ EXPECT_EQ(err.help_text(), "The value `BAD=a\nb` contains a newline."); ++} ++ ++TEST(ConfigValuesGenerator, CflagsWithNewlineError) { ++ TestWithScope setup; ++ Err err; ++ ++ Value cflags_value(nullptr, Value::LIST); ++ cflags_value.list_value().push_back(Value(nullptr, "-Dfoo\nbar")); ++ setup.scope()->SetValue("cflags", cflags_value, nullptr); ++ ++ ConfigValues config_values; ++ ConfigValuesGenerator gen(&config_values, setup.scope(), ++ SourceDir("//foo/"), &err); ++ gen.Run(); ++ EXPECT_TRUE(err.has_error()); ++ EXPECT_EQ(err.message(), "Newlines in cflags values are not supported."); ++} +diff --git a/src/gn/config_values_generator.cc b/src/gn/config_values_generator.cc +index cf91a8ce..3d95f466 100644 +--- a/src/gn/config_values_generator.cc ++++ b/src/gn/config_values_generator.cc +@@ -27,6 +27,19 @@ void GetStringList(Scope* scope, + return; // No value, empty input and succeed. + + ExtractListOfStringValues(*value, &(config_values->*accessor)(), err); ++ if (err->has_error()) ++ return; ++ ++ const auto& strings = (config_values->*accessor)(); ++ for (size_t i = 0; i < strings.size(); i++) { ++ if (strings[i].find('\n') != std::string::npos) { ++ *err = Err(value->list_value()[i], ++ "Newlines in " + std::string(var_name) + " values are not " ++ "supported.", ++ "The value `" + strings[i] + "` contains a newline."); ++ return; ++ } ++ } + } + + void GetDirList(Scope* scope, diff --git a/community/gn/0003-Add-inputs-parameter-to-tool.patch b/community/gn/0003-Add-inputs-parameter-to-tool.patch new file mode 100644 index 00000000000..fadfc2633c9 --- /dev/null +++ b/community/gn/0003-Add-inputs-parameter-to-tool.patch @@ -0,0 +1,613 @@ +From d8fc9abd3a572ecce1ac9156eb790d1c1dbca74a Mon Sep 17 00:00:00 2001 +From: Matt Stark +Date: Tue, 10 Mar 2026 00:24:21 +1100 +Subject: [PATCH] Add `inputs` parameter to `tool`. + +This allows tools to specify what files are required for the tools +itself, thus better supporting remote actions. + +Eg. cxx rules might add inputs = [ clang ]. +A rule with command `python3 foo.py` (where foo.py imports bar.py) would +specify both foo.py and bar.py. + +BUG=b:491242305 + +Change-Id: If5381bb944aa616c2a5d2435b6cb8e416a6a6964 +Reviewed-on: https://gn-review.googlesource.com/c/gn/+/21440 +Reviewed-by: Takuto Ikuta +Commit-Queue: Matt Stark +--- + docs/reference.md | 5 ++ + src/gn/config_values_extractors_unittest.cc | 8 +-- + src/gn/config_values_generator.cc | 5 +- + src/gn/function_toolchain.cc | 5 ++ + src/gn/ninja_binary_target_writer.cc | 11 +++- + src/gn/ninja_binary_target_writer.h | 2 +- + src/gn/ninja_c_binary_target_writer.cc | 36 ++++++----- + src/gn/ninja_c_binary_target_writer.h | 6 +- + .../ninja_c_binary_target_writer_unittest.cc | 64 +++++++++++++++++++ + src/gn/ninja_rust_binary_target_writer.cc | 8 ++- + src/gn/ninja_toolchain_writer.cc | 16 +++++ + src/gn/ninja_toolchain_writer.h | 1 + + src/gn/ninja_toolchain_writer_unittest.cc | 25 ++++++++ + src/gn/tool.cc | 33 +++++++++- + src/gn/tool.h | 15 +++++ + 15 files changed, 210 insertions(+), 30 deletions(-) + +diff --git a/docs/reference.md b/docs/reference.md +index 7e848c7a..8fd26a5f 100644 +--- a/docs/reference.md ++++ b/docs/reference.md +@@ -4475,6 +4475,11 @@ + + This concept is somewhat inefficient to express in Ninja (it requires a lot + of duplicate of rules) so should only be used when absolutely necessary. ++ ++ inputs [string list] ++ A list of files needed to execute the tool. ++ For example, if your tool command is "python3 foo.py", and foo.py imports ++ bar.py, you should set inputs to [ "foo.py", "bar.py" ]. + ``` + + #### **Example of defining a toolchain** +diff --git a/src/gn/config_values_extractors_unittest.cc b/src/gn/config_values_extractors_unittest.cc +index d187daba..b1fda895 100644 +--- a/src/gn/config_values_extractors_unittest.cc ++++ b/src/gn/config_values_extractors_unittest.cc +@@ -160,8 +160,8 @@ TEST(ConfigValuesGenerator, DefinesWithNewlineError) { + setup.scope()->SetValue("defines", defines_value, nullptr); + + ConfigValues config_values; +- ConfigValuesGenerator gen(&config_values, setup.scope(), +- SourceDir("//foo/"), &err); ++ ConfigValuesGenerator gen(&config_values, setup.scope(), SourceDir("//foo/"), ++ &err); + gen.Run(); + EXPECT_TRUE(err.has_error()); + EXPECT_EQ(err.message(), "Newlines in defines values are not supported."); +@@ -177,8 +177,8 @@ TEST(ConfigValuesGenerator, CflagsWithNewlineError) { + setup.scope()->SetValue("cflags", cflags_value, nullptr); + + ConfigValues config_values; +- ConfigValuesGenerator gen(&config_values, setup.scope(), +- SourceDir("//foo/"), &err); ++ ConfigValuesGenerator gen(&config_values, setup.scope(), SourceDir("//foo/"), ++ &err); + gen.Run(); + EXPECT_TRUE(err.has_error()); + EXPECT_EQ(err.message(), "Newlines in cflags values are not supported."); +diff --git a/src/gn/config_values_generator.cc b/src/gn/config_values_generator.cc +index 3d95f466..1f0f90c1 100644 +--- a/src/gn/config_values_generator.cc ++++ b/src/gn/config_values_generator.cc +@@ -34,8 +34,9 @@ void GetStringList(Scope* scope, + for (size_t i = 0; i < strings.size(); i++) { + if (strings[i].find('\n') != std::string::npos) { + *err = Err(value->list_value()[i], +- "Newlines in " + std::string(var_name) + " values are not " +- "supported.", ++ "Newlines in " + std::string(var_name) + ++ " values are not " ++ "supported.", + "The value `" + strings[i] + "` contains a newline."); + return; + } +diff --git a/src/gn/function_toolchain.cc b/src/gn/function_toolchain.cc +index 2f9bf578..3fdddaeb 100644 +--- a/src/gn/function_toolchain.cc ++++ b/src/gn/function_toolchain.cc +@@ -134,6 +134,11 @@ Functions and variables + This concept is somewhat inefficient to express in Ninja (it requires a lot + of duplicate of rules) so should only be used when absolutely necessary. + ++ inputs [string list] ++ A list of files needed to execute the tool. ++ For example, if your tool command is "python3 foo.py", and foo.py imports ++ bar.py, you should set inputs to [ "foo.py", "bar.py" ]. ++ + Example of defining a toolchain + + toolchain("32") { +diff --git a/src/gn/ninja_binary_target_writer.cc b/src/gn/ninja_binary_target_writer.cc +index ac45e976..abf5e153 100644 +--- a/src/gn/ninja_binary_target_writer.cc ++++ b/src/gn/ninja_binary_target_writer.cc +@@ -276,19 +276,24 @@ void NinjaBinaryTargetWriter::WriteCompilerBuildLine( + const std::vector& sources, + const std::vector& extra_deps, + const std::vector& order_only_deps, +- const char* tool_name, ++ const Tool* tool, + const std::vector& outputs, + bool can_write_source_info, + bool restat_output_allowed) { + out_ << "build"; + WriteOutputs(outputs); + +- out_ << ": " << rule_prefix_ << tool_name; ++ out_ << ": " << rule_prefix_ << tool->name(); + path_output_.WriteFiles(out_, sources); + +- if (!extra_deps.empty()) { ++ if (!extra_deps.empty() || !tool->inputs().empty()) { + out_ << " |"; + path_output_.WriteFiles(out_, extra_deps); ++ if (auto phony = tool->inputs_phony_or_file(rule_prefix_, ++ *settings_->build_settings())) { ++ out_ << " "; ++ path_output_.WriteFile(out_, *phony); ++ } + } + + if (!order_only_deps.empty()) { +diff --git a/src/gn/ninja_binary_target_writer.h b/src/gn/ninja_binary_target_writer.h +index 29105b47..b64f8843 100644 +--- a/src/gn/ninja_binary_target_writer.h ++++ b/src/gn/ninja_binary_target_writer.h +@@ -56,7 +56,7 @@ class NinjaBinaryTargetWriter : public NinjaTargetWriter { + void WriteCompilerBuildLine(const std::vector& sources, + const std::vector& extra_deps, + const std::vector& order_only_deps, +- const char* tool_name, ++ const Tool* tool, + const std::vector& outputs, + bool can_write_source_info = true, + bool restat_output_allowed = false); +diff --git a/src/gn/ninja_c_binary_target_writer.cc b/src/gn/ninja_c_binary_target_writer.cc +index c864bc09..24852628 100644 +--- a/src/gn/ninja_c_binary_target_writer.cc ++++ b/src/gn/ninja_c_binary_target_writer.cc +@@ -229,14 +229,14 @@ void NinjaCBinaryTargetWriter::WritePCHCommands( + const CTool* tool_c = target_->toolchain()->GetToolAsC(CTool::kCToolCc); + if (tool_c && tool_c->precompiled_header_type() != CTool::PCH_NONE && + target_->source_types_used().Get(SourceFile::SOURCE_C)) { +- WritePCHCommand(&CSubstitutionCFlagsC, CTool::kCToolCc, ++ WritePCHCommand(&CSubstitutionCFlagsC, tool_c, + tool_c->precompiled_header_type(), input_deps, + order_only_deps, object_files, other_files); + } + const CTool* tool_cxx = target_->toolchain()->GetToolAsC(CTool::kCToolCxx); + if (tool_cxx && tool_cxx->precompiled_header_type() != CTool::PCH_NONE && + target_->source_types_used().Get(SourceFile::SOURCE_CPP)) { +- WritePCHCommand(&CSubstitutionCFlagsCc, CTool::kCToolCxx, ++ WritePCHCommand(&CSubstitutionCFlagsCc, tool_cxx, + tool_cxx->precompiled_header_type(), input_deps, + order_only_deps, object_files, other_files); + } +@@ -244,7 +244,7 @@ void NinjaCBinaryTargetWriter::WritePCHCommands( + const CTool* tool_objc = target_->toolchain()->GetToolAsC(CTool::kCToolObjC); + if (tool_objc && tool_objc->precompiled_header_type() == CTool::PCH_GCC && + target_->source_types_used().Get(SourceFile::SOURCE_M)) { +- WritePCHCommand(&CSubstitutionCFlagsObjC, CTool::kCToolObjC, ++ WritePCHCommand(&CSubstitutionCFlagsObjC, tool_objc, + tool_objc->precompiled_header_type(), input_deps, + order_only_deps, object_files, other_files); + } +@@ -253,7 +253,7 @@ void NinjaCBinaryTargetWriter::WritePCHCommands( + target_->toolchain()->GetToolAsC(CTool::kCToolObjCxx); + if (tool_objcxx && tool_objcxx->precompiled_header_type() == CTool::PCH_GCC && + target_->source_types_used().Get(SourceFile::SOURCE_MM)) { +- WritePCHCommand(&CSubstitutionCFlagsObjCc, CTool::kCToolObjCxx, ++ WritePCHCommand(&CSubstitutionCFlagsObjCc, tool_objcxx, + tool_objcxx->precompiled_header_type(), input_deps, + order_only_deps, object_files, other_files); + } +@@ -261,7 +261,7 @@ void NinjaCBinaryTargetWriter::WritePCHCommands( + + void NinjaCBinaryTargetWriter::WritePCHCommand( + const Substitution* flag_type, +- const char* tool_name, ++ const Tool* tool, + CTool::PrecompiledHeaderType header_type, + const std::vector& input_deps, + const std::vector& order_only_deps, +@@ -269,11 +269,11 @@ void NinjaCBinaryTargetWriter::WritePCHCommand( + std::vector* other_files) { + switch (header_type) { + case CTool::PCH_MSVC: +- WriteWindowsPCHCommand(flag_type, tool_name, input_deps, order_only_deps, ++ WriteWindowsPCHCommand(flag_type, tool, input_deps, order_only_deps, + object_files); + break; + case CTool::PCH_GCC: +- WriteGCCPCHCommand(flag_type, tool_name, input_deps, order_only_deps, ++ WriteGCCPCHCommand(flag_type, tool, input_deps, order_only_deps, + other_files); + break; + case CTool::PCH_NONE: +@@ -284,12 +284,13 @@ void NinjaCBinaryTargetWriter::WritePCHCommand( + + void NinjaCBinaryTargetWriter::WriteGCCPCHCommand( + const Substitution* flag_type, +- const char* tool_name, ++ const Tool* tool, + const std::vector& input_deps, + const std::vector& order_only_deps, + std::vector* gch_files) { + // Compute the pch output file (it will be language-specific). + std::vector outputs; ++ auto tool_name = tool->name(); + GetPCHOutputFiles(target_, tool_name, &outputs); + if (outputs.empty()) + return; +@@ -302,7 +303,7 @@ void NinjaCBinaryTargetWriter::WriteGCCPCHCommand( + + // Build line to compile the file. + WriteCompilerBuildLine({target_->config_values().precompiled_source()}, +- extra_deps, order_only_deps, tool_name, outputs); ++ extra_deps, order_only_deps, tool, outputs); + + // This build line needs a custom language-specific flags value. Rule-specific + // variables are just indented underneath the rule line. +@@ -340,13 +341,13 @@ void NinjaCBinaryTargetWriter::WriteGCCPCHCommand( + + void NinjaCBinaryTargetWriter::WriteWindowsPCHCommand( + const Substitution* flag_type, +- const char* tool_name, ++ const Tool* tool, + const std::vector& input_deps, + const std::vector& order_only_deps, + std::vector* object_files) { + // Compute the pch output file (it will be language-specific). + std::vector outputs; +- GetPCHOutputFiles(target_, tool_name, &outputs); ++ GetPCHOutputFiles(target_, tool->name(), &outputs); + if (outputs.empty()) + return; + +@@ -358,7 +359,7 @@ void NinjaCBinaryTargetWriter::WriteWindowsPCHCommand( + + // Build line to compile the file. + WriteCompilerBuildLine({target_->config_values().precompiled_source()}, +- extra_deps, order_only_deps, tool_name, outputs); ++ extra_deps, order_only_deps, tool, outputs); + + // This build line needs a custom language-specific flags value. Rule-specific + // variables are just indented underneath the rule line. +@@ -435,7 +436,7 @@ void NinjaCBinaryTargetWriter::WriteSources( + deps.push_back(module_dep.pcm); + } + +- WriteCompilerBuildLine({source}, deps, order_only_deps, tool_name, ++ WriteCompilerBuildLine({source}, deps, order_only_deps, tool, + tool_outputs); + WritePool(out_); + } +@@ -480,8 +481,7 @@ void NinjaCBinaryTargetWriter::WriteSwiftSources( + + const Tool* tool = target_->swift_values().GetTool(target_); + WriteCompilerBuildLine(target_->sources(), input_deps, +- swift_order_only_deps.vector(), tool->name(), +- *output_files, ++ swift_order_only_deps.vector(), tool, *output_files, + /*can_write_source_info=*/false, + /*restat_output_allowed=*/true); + +@@ -591,6 +591,12 @@ void NinjaCBinaryTargetWriter::WriteLinkerStuff( + std::copy(input_deps.begin(), input_deps.end(), + std::back_inserter(implicit_deps)); + ++ if (auto phony = tool_->inputs_phony_or_file(rule_prefix_, ++ *settings_->build_settings()); ++ phony) { ++ implicit_deps.emplace_back(std::move(*phony)); ++ } ++ + // Any C++ target which depends on a Rust .rlib has to depend on its entire + // tree of transitive rlibs found inside the linking target (which excludes + // rlibs only depended on inside a shared library dependency). +diff --git a/src/gn/ninja_c_binary_target_writer.h b/src/gn/ninja_c_binary_target_writer.h +index 3707f0eb..5436eeec 100644 +--- a/src/gn/ninja_c_binary_target_writer.h ++++ b/src/gn/ninja_c_binary_target_writer.h +@@ -51,7 +51,7 @@ class NinjaCBinaryTargetWriter : public NinjaBinaryTargetWriter { + + // Writes a .pch compile build line for a language type. + void WritePCHCommand(const Substitution* flag_type, +- const char* tool_name, ++ const Tool* tool, + CTool::PrecompiledHeaderType header_type, + const std::vector& input_deps, + const std::vector& order_only_deps, +@@ -59,13 +59,13 @@ class NinjaCBinaryTargetWriter : public NinjaBinaryTargetWriter { + std::vector* other_files); + + void WriteGCCPCHCommand(const Substitution* flag_type, +- const char* tool_name, ++ const Tool* tool, + const std::vector& input_deps, + const std::vector& order_only_deps, + std::vector* gch_files); + + void WriteWindowsPCHCommand(const Substitution* flag_type, +- const char* tool_name, ++ const Tool* tool, + const std::vector& input_deps, + const std::vector& order_only_deps, + std::vector* object_files); +diff --git a/src/gn/ninja_c_binary_target_writer_unittest.cc b/src/gn/ninja_c_binary_target_writer_unittest.cc +index f324d533..73804fde 100644 +--- a/src/gn/ninja_c_binary_target_writer_unittest.cc ++++ b/src/gn/ninja_c_binary_target_writer_unittest.cc +@@ -2847,3 +2847,67 @@ TEST_F(NinjaCBinaryTargetWriterTest, Pool) { + std::string out_str = out.str(); + EXPECT_EQ(expected, out_str) << expected << "\n" << out_str; + } ++ ++TEST_F(NinjaCBinaryTargetWriterTest, ToolInputs) { ++ Err err; ++ TestWithScope setup; ++ ++ Toolchain toolchain_with_inputs( ++ setup.settings(), ++ Label(SourceDir("//toolchain_with_inputs/"), "with_inputs")); ++ ++ std::unique_ptr cxx_tool = Tool::CreateTool(CTool::kCToolCxx); ++ TestWithScope::SetCommandForTool( ++ "c++ {{source}} {{cflags}} {{cflags_cc}} {{defines}} {{include_dirs}} " ++ "-o {{output}}", ++ cxx_tool.get()); ++ cxx_tool->set_outputs(SubstitutionList::MakeForTest( ++ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o")); ++ cxx_tool->set_inputs({SourceFile("//bin/clang++")}); ++ toolchain_with_inputs.SetTool(std::move(cxx_tool)); ++ ++ std::unique_ptr link = Tool::CreateTool(CTool::kCToolLink); ++ TestWithScope::SetCommandForTool( ++ "ld -o {{target_output_name}} {{source}} {{ldflags}} {{libs}}", ++ link.get()); ++ link->set_outputs( ++ SubstitutionList::MakeForTest("{{root_out_dir}}/{{target_output_name}}")); ++ link->set_inputs( ++ {SourceFile("//bin/lld"), SourceFile("//bin/link_wrapper.py")}); ++ toolchain_with_inputs.SetTool(std::move(link)); ++ ++ toolchain_with_inputs.ToolchainSetupComplete(); ++ ++ Target target(setup.settings(), Label(SourceDir("//foo/"), "bar")); ++ target.sources().push_back(SourceFile("//foo/source.cc")); ++ target.set_output_type(Target::EXECUTABLE); ++ target.visibility().SetPublic(); ++ target.SetToolchain(&toolchain_with_inputs); ++ ASSERT_TRUE(target.OnResolved(&err)); ++ ++ std::ostringstream out; ++ NinjaBinaryTargetWriter writer(&target, out); ++ writer.Run(); ++ ++ const char expected[] = ++ "defines =\n" ++ "include_dirs =\n" ++ "root_out_dir = .\n" ++ "target_output_name = bar\n" ++ "\n" ++ "build obj/foo/bar.source.o: cxx ../../foo/source.cc | " ++ "../../bin/clang++\n" ++ " source_file_part = source.cc\n" ++ " source_name_part = source\n" ++ "\n" ++ "build ./bar: link obj/foo/bar.source.o | " ++ "phony/link_inputs\n" ++ " ldflags =\n" ++ " libs =\n" ++ " frameworks =\n" ++ " swiftmodules =\n" ++ " output_extension =\n" ++ " output_dir =\n"; ++ std::string out_str = out.str(); ++ EXPECT_EQ(expected, out_str) << expected << "\n" << out_str; ++} +diff --git a/src/gn/ninja_rust_binary_target_writer.cc b/src/gn/ninja_rust_binary_target_writer.cc +index ba6b4169..cbb5e4ff 100644 +--- a/src/gn/ninja_rust_binary_target_writer.cc ++++ b/src/gn/ninja_rust_binary_target_writer.cc +@@ -139,6 +139,12 @@ void NinjaRustBinaryTargetWriter::Run() { + implicit_deps.Append(classified_deps.extra_object_files.begin(), + classified_deps.extra_object_files.end()); + ++ if (auto phony = tool_->inputs_phony_or_file(rule_prefix_, ++ *settings_->build_settings()); ++ phony) { ++ implicit_deps.emplace_back(std::move(*phony)); ++ } ++ + std::vector rustdeps; + std::vector nonrustdeps; + std::vector swiftmodules; +@@ -213,7 +219,7 @@ void NinjaRustBinaryTargetWriter::Run() { + SubstitutionWriter::ApplyListToLinkerAsOutputFile( + target_, tool_, tool_->outputs(), &tool_outputs); + WriteCompilerBuildLine({target_->rust_values().crate_root()}, +- implicit_deps.vector(), order_only_deps, tool_->name(), ++ implicit_deps.vector(), order_only_deps, tool_, + tool_outputs); + + std::vector extern_deps( +diff --git a/src/gn/ninja_toolchain_writer.cc b/src/gn/ninja_toolchain_writer.cc +index 917d0681..fb04b954 100644 +--- a/src/gn/ninja_toolchain_writer.cc ++++ b/src/gn/ninja_toolchain_writer.cc +@@ -119,6 +119,22 @@ void NinjaToolchainWriter::WriteToolRule(Tool* tool, + + if (tool->restat()) + out_ << kIndent << "restat = 1" << std::endl; ++ ++ // If the size is exactly 1, we don't need a phony rule, since we just write ++ // the input file directly in the build action. ++ if (tool->inputs().size() > 1) { ++ out_ << "build "; ++ path_output_.WriteFile( ++ out_, ++ *tool->inputs_phony_or_file(rule_prefix, *settings_->build_settings())); ++ out_ << ": phony"; ++ for (const auto& input : tool->inputs()) { ++ out_ << " "; ++ path_output_.WriteFile(out_, ++ OutputFile(settings_->build_settings(), input)); ++ } ++ out_ << std::endl; ++ } + } + + void NinjaToolchainWriter::WriteRulePattern(const char* name, +diff --git a/src/gn/ninja_toolchain_writer.h b/src/gn/ninja_toolchain_writer.h +index cbc7c688..f734cfe5 100644 +--- a/src/gn/ninja_toolchain_writer.h ++++ b/src/gn/ninja_toolchain_writer.h +@@ -31,6 +31,7 @@ class NinjaToolchainWriter { + private: + FRIEND_TEST_ALL_PREFIXES(NinjaToolchainWriter, WriteToolRule); + FRIEND_TEST_ALL_PREFIXES(NinjaToolchainWriter, WriteToolRuleWithLauncher); ++ FRIEND_TEST_ALL_PREFIXES(NinjaToolchainWriter, WriteToolRuleWithInputsPhony); + + NinjaToolchainWriter(const Settings* settings, + const Toolchain* toolchain, +diff --git a/src/gn/ninja_toolchain_writer_unittest.cc b/src/gn/ninja_toolchain_writer_unittest.cc +index 863c1744..7c46dcd3 100644 +--- a/src/gn/ninja_toolchain_writer_unittest.cc ++++ b/src/gn/ninja_toolchain_writer_unittest.cc +@@ -38,3 +38,28 @@ TEST(NinjaToolchainWriter, WriteToolRuleWithLauncher) { + "-o ${out}\n", + stream.str()); + } ++ ++TEST(NinjaToolchainWriter, WriteToolRuleWithInputsPhony) { ++ TestWithScope setup; ++ ++ std::unique_ptr tool = Tool::CreateTool(CTool::kCToolCxx); ++ TestWithScope::SetCommandForTool( ++ "launcher c++ {{source}} {{cflags}} {{cflags_cc}} {{defines}} " ++ "{{include_dirs}} -o {{output}}", ++ tool.get()); ++ tool->set_inputs( ++ {SourceFile("//bin/clang++"), SourceFile("//bin/plugin.so")}); ++ ++ std::ostringstream stream; ++ NinjaToolchainWriter writer(setup.settings(), setup.toolchain(), stream); ++ writer.WriteToolRule(tool.get(), std::string("prefix_")); ++ ++ EXPECT_EQ( ++ "rule prefix_cxx\n" ++ " command = launcher c++ ${in} ${cflags} ${cflags_cc} ${defines} " ++ "${include_dirs} " ++ "-o ${out}\n" ++ "build phony/prefix_cxx_inputs: phony ../../bin/clang++ " ++ "../../bin/plugin.so\n", ++ stream.str()); ++} +diff --git a/src/gn/tool.cc b/src/gn/tool.cc +index 684dc3e3..52b6c4ea 100644 +--- a/src/gn/tool.cc ++++ b/src/gn/tool.cc +@@ -4,12 +4,15 @@ + + #include "gn/tool.h" + ++#include "base/strings/stringprintf.h" + #include "gn/builtin_tool.h" + #include "gn/c_tool.h" + #include "gn/general_tool.h" ++#include "gn/output_file.h" + #include "gn/rust_tool.h" + #include "gn/settings.h" + #include "gn/target.h" ++#include "gn/value_extractors.h" + + const char* Tool::kToolNone = ""; + +@@ -199,6 +202,33 @@ bool Tool::ReadOutputExtension(Scope* scope, Err* err) { + return true; + } + ++bool Tool::ReadInputs(Scope* scope, Err* err) { ++ DCHECK(!complete_); ++ const Value* value = scope->GetValue("inputs", true); ++ if (!value) ++ return true; // Not present is fine. ++ ++ std::vector inputs; ++ if (!ExtractListOfRelativeFiles(scope->settings()->build_settings(), *value, ++ scope->GetSourceDir(), &inputs, err)) { ++ return false; ++ } ++ set_inputs(std::move(inputs)); ++ return true; ++} ++ ++std::optional Tool::inputs_phony_or_file( ++ std::string_view rule_prefix, ++ const BuildSettings& build_settings) const { ++ if (inputs_.size() == 1) { ++ return OutputFile(&build_settings, inputs_[0]); ++ } else if (inputs_.size() > 1) { ++ return OutputFile(base::StringPrintf( ++ "phony/%s%s_inputs", std::string(rule_prefix).c_str(), name_)); ++ } ++ return std::nullopt; ++} ++ + bool Tool::InitTool(Scope* scope, Toolchain* toolchain, Err* err) { + if (!ReadPattern(scope, "command", &command_, err) || + !ReadString(scope, "command_launcher", &command_launcher_, err) || +@@ -211,7 +241,8 @@ bool Tool::InitTool(Scope* scope, Toolchain* toolchain, Err* err) { + !ReadBool(scope, "restat", &restat_, err) || + !ReadPattern(scope, "rspfile", &rspfile_, err) || + !ReadPattern(scope, "rspfile_content", &rspfile_content_, err) || +- !ReadLabel(scope, "pool", toolchain->label(), &pool_, err)) { ++ !ReadLabel(scope, "pool", toolchain->label(), &pool_, err) || ++ !ReadInputs(scope, err)) { + return false; + } + const bool command_is_required = name_ != GeneralTool::kGeneralToolAction; +diff --git a/src/gn/tool.h b/src/gn/tool.h +index 68e919cd..d0825416 100644 +--- a/src/gn/tool.h ++++ b/src/gn/tool.h +@@ -5,11 +5,14 @@ + #ifndef TOOLS_GN_TOOL_H_ + #define TOOLS_GN_TOOL_H_ + ++#include + #include ++#include + + #include "base/logging.h" + #include "gn/label.h" + #include "gn/label_ptr.h" ++#include "gn/output_file.h" + #include "gn/scope.h" + #include "gn/source_file.h" + #include "gn/substitution_list.h" +@@ -224,6 +227,16 @@ class Tool { + const LabelPtrPair& pool() const { return pool_; } + void set_pool(LabelPtrPair pool) { pool_ = std::move(pool); } + ++ const std::vector& inputs() const { return inputs_; } ++ void set_inputs(std::vector inputs) { ++ DCHECK(!complete_); ++ inputs_ = std::move(inputs); ++ } ++ ++ std::optional inputs_phony_or_file( ++ std::string_view rule_prefix, ++ const BuildSettings& build_settings) const; ++ + // Other functions ---------------------------------------------------------- + + // Function for the above override to call to complete the tool. +@@ -277,6 +290,7 @@ class Tool { + LabelPtrPair* field, + Err* err); + bool ReadOutputExtension(Scope* scope, Err* err); ++ bool ReadInputs(Scope* scope, Err* err); + + const ParseNode* defined_from_ = nullptr; + const char* name_ = nullptr; +@@ -303,6 +317,7 @@ class Tool { + SubstitutionPattern rspfile_; + SubstitutionPattern rspfile_content_; + LabelPtrPair pool_; ++ std::vector inputs_; + + bool complete_ = false; + diff --git a/community/gn/APKBUILD b/community/gn/APKBUILD index abcf7ace361..019d97b0957 100644 --- a/community/gn/APKBUILD +++ b/community/gn/APKBUILD @@ -2,7 +2,7 @@ maintainer="lauren n. liberda " pkgname=gn pkgver=0_git20251217 -pkgrel=0 +pkgrel=1 _commit=64d35867ca0a1088f13de8f4ccaf1a5687d7f1ce pkgdesc="Meta-build system that generates build files for Ninja" arch="all" @@ -12,6 +12,9 @@ depends="samurai" makedepends="python3 zstd" # gitiles has no clones source="https://ab-sn.lnl.gay/gn-$_commit.tar.zst + 0001-Add-function-expand_directory-to-gn.patch + 0002-Reject-newlines-in-string-config-values-defines-cfla.patch + 0003-Add-inputs-parameter-to-tool.patch " builddir="$srcdir/gn" @@ -56,4 +59,7 @@ package() { sha512sums=" a9377c649fb0b6c88ac409d64c3aa8c8e2ca9230cde80e653a51bd4ac086a91a6a293331b9cfcf9900c8856ca803b5abd81195329d2fa0530382e1572aa4474e gn-64d35867ca0a1088f13de8f4ccaf1a5687d7f1ce.tar.zst +56640817f1e22e6ae067bc6f9b740434b93b3cc24b9786396c8563f20fbff8006c6e2c220966c517a1d26850b7609f5b237109e6d1441d2caa198cfdcdde707c 0001-Add-function-expand_directory-to-gn.patch +30b164da79fe76cf78c6c78879ccb075c0c999c69ca71e52bc7f9b498bf739041110a5d69509fc1fee50714a667ddddb0afcf0d9892e6d5993fd6d803e014503 0002-Reject-newlines-in-string-config-values-defines-cfla.patch +1d4dfa33fbc320a00caf54004c6f20ac026f70ab78dd3873231d51911e8486e203be530d5591a38801da806b0541b25797e95a954d5c173f42212d931b408bf1 0003-Add-inputs-parameter-to-tool.patch "