community/gn: backport changes needed for chromium M148

This commit is contained in:
LN Liberda 2026-04-27 04:40:34 +02:00 committed by omni
parent b27ea208cc
commit 2a2787f018
4 changed files with 1451 additions and 1 deletions

View File

@ -0,0 +1,697 @@
From 42ace47bb426ca9705175d866bb9bdf168d5535f Mon Sep 17 00:00:00 2001
From: Matt Stark <msta@google.com>
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 <tikuta@google.com>
Commit-Queue: Matt Stark <msta@google.com>
---
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")
```
+### <a name="func_expand_directory"></a>**expand_directory**: Expand a source directory and return files.&nbsp;[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)
+```
### <a name="func_filter_exclude"></a>**filter_exclude**: Remove values that match a set of patterns.&nbsp;[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<SourceFileSet> list) {
+ expand_directory_allowlist_ = std::move(list);
+ }
+
private:
Label root_target_label_;
std::vector<LabelPattern> root_patterns_;
@@ -167,6 +176,8 @@ class BuildSettings {
PrintCallback print_callback_;
std::unique_ptr<SourceFileSet> exec_script_allowlist_;
+ std::unique_ptr<SourceFileSet> expand_directory_allowlist_ =
+ std::make_unique<SourceFileSet>();
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 <algorithm>
+#include <map>
+#include <mutex>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#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<Value>& 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<std::pair<base::FilePath, bool>, CacheEntry> cache;
+ static std::mutex cache_mutex;
+
+ CacheEntry* entry;
+ {
+ std::lock_guard<std::mutex> lock(cache_mutex);
+ entry = &cache[{dir_path, recursive}];
+ }
+
+ // Now lock the per-entry mutex
+ std::lock_guard<std::mutex> 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 <algorithm>
+
+#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<FunctionCallNode> 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<FunctionCallNode>();
+ function->set_function(token);
+
+ auto args = std::make_unique<ListNode>();
+ args->set_begin_token(token);
+ args->set_end(std::make_unique<EndNode>(token));
+ function->set_args(std::move(args));
+
+ auto allowlist = std::make_unique<SourceFileSet>();
+ 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<base::FilePath> 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<base::FilePath> 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<ListNode>();
+ args->set_begin_token(token);
+ args->set_end(std::make_unique<EndNode>(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<SourceFileSet>();
+ 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<Value>& 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<SourceFileSet> 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<SourceFileSet>();
+ 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<SourceFileSet> allowlist =
- std::make_unique<SourceFileSet>();
- 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<SourceFileSet>());
}
// 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 <string.h>
+#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<int>(GoSourceUsed())
<< static_cast<int>(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<SourceFile> {
// overall difference in "gn gen" time is about 10%.
using SourceFileSet = base::flat_set<SourceFile, SourceFile::PtrCompare>;
+class ParseNode;
+bool InSourceAllowList(const ParseNode* node, const SourceFileSet* allowlist);
+
// Represents a set of tool types.
class SourceFileTypeSet {
public:

View File

@ -0,0 +1,134 @@
From 46fd0cdb48cf3d42e29e19112eb3f297b3127dfd Mon Sep 17 00:00:00 2001
From: Nico Weber <thakis@chromium.org>
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 <tikuta@google.com>
Commit-Queue: Nico Weber <thakis@chromium.org>
Reviewed-by: Nico Weber <thakis@google.com>
---
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,

View File

@ -0,0 +1,613 @@
From d8fc9abd3a572ecce1ac9156eb790d1c1dbca74a Mon Sep 17 00:00:00 2001
From: Matt Stark <msta@google.com>
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 <tikuta@google.com>
Commit-Queue: Matt Stark <msta@google.com>
---
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<SourceFile>& sources,
const std::vector<OutputFile>& extra_deps,
const std::vector<OutputFile>& order_only_deps,
- const char* tool_name,
+ const Tool* tool,
const std::vector<OutputFile>& 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<SourceFile>& sources,
const std::vector<OutputFile>& extra_deps,
const std::vector<OutputFile>& order_only_deps,
- const char* tool_name,
+ const Tool* tool,
const std::vector<OutputFile>& 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<OutputFile>& input_deps,
const std::vector<OutputFile>& order_only_deps,
@@ -269,11 +269,11 @@ void NinjaCBinaryTargetWriter::WritePCHCommand(
std::vector<OutputFile>* 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<OutputFile>& input_deps,
const std::vector<OutputFile>& order_only_deps,
std::vector<OutputFile>* gch_files) {
// Compute the pch output file (it will be language-specific).
std::vector<OutputFile> 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<OutputFile>& input_deps,
const std::vector<OutputFile>& order_only_deps,
std::vector<OutputFile>* object_files) {
// Compute the pch output file (it will be language-specific).
std::vector<OutputFile> 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<OutputFile>& input_deps,
const std::vector<OutputFile>& order_only_deps,
@@ -59,13 +59,13 @@ class NinjaCBinaryTargetWriter : public NinjaBinaryTargetWriter {
std::vector<OutputFile>* other_files);
void WriteGCCPCHCommand(const Substitution* flag_type,
- const char* tool_name,
+ const Tool* tool,
const std::vector<OutputFile>& input_deps,
const std::vector<OutputFile>& order_only_deps,
std::vector<OutputFile>* gch_files);
void WriteWindowsPCHCommand(const Substitution* flag_type,
- const char* tool_name,
+ const Tool* tool,
const std::vector<OutputFile>& input_deps,
const std::vector<OutputFile>& order_only_deps,
std::vector<OutputFile>* 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<Tool> 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<Tool> 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<OutputFile> rustdeps;
std::vector<OutputFile> nonrustdeps;
std::vector<OutputFile> 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<const Target*> 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 = 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<SourceFile> inputs;
+ if (!ExtractListOfRelativeFiles(scope->settings()->build_settings(), *value,
+ scope->GetSourceDir(), &inputs, err)) {
+ return false;
+ }
+ set_inputs(std::move(inputs));
+ return true;
+}
+
+std::optional<OutputFile> 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 <optional>
#include <string>
+#include <vector>
#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>& pool() const { return pool_; }
void set_pool(LabelPtrPair<Pool> pool) { pool_ = std::move(pool); }
+ const std::vector<SourceFile>& inputs() const { return inputs_; }
+ void set_inputs(std::vector<SourceFile> inputs) {
+ DCHECK(!complete_);
+ inputs_ = std::move(inputs);
+ }
+
+ std::optional<OutputFile> 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<Pool>* 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> pool_;
+ std::vector<SourceFile> inputs_;
bool complete_ = false;

View File

@ -2,7 +2,7 @@
maintainer="lauren n. liberda <lauren@selfisekai.rocks>"
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
"