sm-commit AT lists.ibiblio.org
Subject: Source Mage code commit list
List archive
[[SM-Commit] ] GIT changes to master grimoire by Ismael Luceno (2ac6f4e42e970e056e90c001f5aa8396051cd535)
- From: Ismael Luceno <scm AT sourcemage.org>
- To: sm-commit AT lists.ibiblio.org, sm-commit AT lists.sourcemage.org
- Subject: [[SM-Commit] ] GIT changes to master grimoire by Ismael Luceno (2ac6f4e42e970e056e90c001f5aa8396051cd535)
- Date: Tue, 6 Oct 2026 16:55:19 +0000
GIT changes to master grimoire by Ismael Luceno <ismael AT sourcemage.org>:
ChangeLog |
2
devel/gn/BUILD |
15
devel/gn/DEPENDS |
4
devel/gn/DETAILS |
18
devel/gn/HISTORY |
5
devel/gn/INSTALL |
1
devel/gn/PRE_BUILD |
3
devel/gn/patches/0001-Add-function-expand_directory-to-gn.patch |
702 ++++++++++
devel/gn/patches/0002-Add-support-for-c_additional_outputs-in-config.patch |
357 +++++
devel/gn/patches/0003-pool-include-cstdint-for-int64_t.patch |
27
10 files changed, 1134 insertions(+)
New commits:
commit 2ac6f4e42e970e056e90c001f5aa8396051cd535
Author: Ismael Luceno <ismael AT sourcemage.org>
Commit: Ismael Luceno <ismael AT sourcemage.org>
gn: new spell, meta-build system that generates build files for Ninja
diff --git a/ChangeLog b/ChangeLog
index f3c4ee0..dbbf05f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -2,6 +2,8 @@
* doc/mandoc: new spell, manpage compiler and manual page viewer
* FUNCTIONS: prepared default_build_configure for BUILD_API 3
* doc/lowdown: new spell, Simple markdown translator
+ * devel/gn: new spell, meta-build system that generates build files
+ for Ninja
2026-10-04 Ismael Luceno <ismael AT sourcemage.org>
* kernels/freedos-lite: new spell, free DOS-compatible operating
diff --git a/devel/gn/BUILD b/devel/gn/BUILD
new file mode 100755
index 0000000..1d6bd56
--- /dev/null
+++ b/devel/gn/BUILD
@@ -0,0 +1,15 @@
+# the project is pure C++, but gen.py consumes both CFLAGS and CXXFLAGS
+unset CFLAGS &&
+
+CXXFLAGS="${CXXFLAGS/-Werror=format-security/}" \
+python3 build/gen.py \
+ --no-last-commit-position \
+ --no-static-libstdc++ \
+ --no-strip \
+ --allow-warnings &&
+
+if is_depends_enabled "$SPELL" samurai; then
+ samu -C out gn
+else
+ ninja -C out gn
+fi
diff --git a/devel/gn/DEPENDS b/devel/gn/DEPENDS
new file mode 100755
index 0000000..d62a983
--- /dev/null
+++ b/devel/gn/DEPENDS
@@ -0,0 +1,4 @@
+depends -sub CXX gcc &&
+depends python3 &&
+depends zstd &&
+depends NINJA-BUILD
diff --git a/devel/gn/DETAILS b/devel/gn/DETAILS
new file mode 100755
index 0000000..eebcb87
--- /dev/null
+++ b/devel/gn/DETAILS
@@ -0,0 +1,18 @@
+ SPELL=gn
+ VERSION=20260401
+ COMMIT=6e8dcdebbadf4f8aa75e6a4b6e0bdf89dce1513a
+ SOURCE="${SPELL}-${COMMIT}.tar.zst"
+ # external snapshot because upstream provides none
+ SOURCE_URL[0]="https://ab-sn.lnl.gay/${SOURCE}"
+
SOURCE_HASH=sha512:fc34debcaa721c0baadd7aa1965412cf63770fbbacb2eb5d9180e659517e4ba30b9cc59144573c97cf9bf772604f501d2512ebf7cce848902eeb15008c87f966
+SOURCE_DIRECTORY="${BUILD_DIRECTORY}/${SPELL}"
+ WEB_SITE="https://gn.googlesource.com/gn"
+ LICENSE[0]="BSD"
+ ENTERED=20260821
+ KEYWORDS="devel"
+ SHORT="meta-build system that generates build files for Ninja"
+cat << EOF
+GN is a meta-build system that generates build files for Ninja. It is the
+build system used by Chromium and by projects derived from it, such as
+Electron and V8.
+EOF
diff --git a/devel/gn/HISTORY b/devel/gn/HISTORY
new file mode 100644
index 0000000..f2c5dcf
--- /dev/null
+++ b/devel/gn/HISTORY
@@ -0,0 +1,5 @@
+2026-10-06 Ismael Luceno <ismael AT sourcemage.org>
+ * BUILD, DEPENDS, DETAILS, INSTALL, PRE_BUILD,
+ patches/0001-Add-function-expand_directory-to-gn.patch,
+ patches/0002-Add-support-for-c_additional_outputs-in-config.patch,
+ patches/0003-pool-include-cstdint-for-int64_t.patch: spell created
diff --git a/devel/gn/INSTALL b/devel/gn/INSTALL
new file mode 100755
index 0000000..742c657
--- /dev/null
+++ b/devel/gn/INSTALL
@@ -0,0 +1 @@
+install -Dm755 out/gn "${INSTALL_ROOT}/usr/bin/gn"
diff --git a/devel/gn/PRE_BUILD b/devel/gn/PRE_BUILD
new file mode 100755
index 0000000..0cab101
--- /dev/null
+++ b/devel/gn/PRE_BUILD
@@ -0,0 +1,3 @@
+default_pre_build &&
+cd "${SOURCE_DIRECTORY}" &&
+apply_patch_dir patches
diff --git a/devel/gn/patches/0001-Add-function-expand_directory-to-gn.patch
b/devel/gn/patches/0001-Add-function-expand_directory-to-gn.patch
new file mode 100644
index 0000000..cb65902
--- /dev/null
+++ b/devel/gn/patches/0001-Add-function-expand_directory-to-gn.patch
@@ -0,0 +1,702 @@
+From 42ace47bb426ca9705175d866bb9bdf168d5535f Mon Sep 17 00:00:00 2001
+From: Matt Stark <msta AT google.com>
+Date: Mon, 13 Apr 2026 11:10:13 +1000
+Subject: [PATCH 1/3] 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 AT google.com>
+Commit-Queue: Matt Stark <msta AT google.com>
+Upstream-Status: Backport
+Origin: Upstream
+Signed-off-by: Ismael Luceno <ismael AT sourcemage.org>
+---
+ build/gen.py | 2 +
+ docs/reference.md | 35 ++++
+ gn/function_expand_directory.cc | 0
+ gn/function_expand_directory_unittest.cc | 0
+ 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 +
+ 13 files changed, 464 insertions(+), 25 deletions(-)
+ create mode 100644 gn/function_expand_directory.cc
+ create mode 100644 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. [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. [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:
diff --git
a/devel/gn/patches/0002-Add-support-for-c_additional_outputs-in-config.patch
b/devel/gn/patches/0002-Add-support-for-c_additional_outputs-in-config.patch
new file mode 100644
index 0000000..5356cba
--- /dev/null
+++
b/devel/gn/patches/0002-Add-support-for-c_additional_outputs-in-config.patch
@@ -0,0 +1,357 @@
+From cf2f1d025f33e922a771e5a73c57c4e0fe911b92 Mon Sep 17 00:00:00 2001
+From: Takuto Ikuta <tikuta AT google.com>
+Date: Wed, 22 Apr 2026 00:11:41 +0900
+Subject: [PATCH 2/3] Add support for c_additional_outputs in config
+
+This feature is needed to support Debug Fission (.dwo files) in
+Chromium. Currently, referencing declare_args variables in a toolchain
+definition evaluates them in the context of the default toolchain,
+causing mismatches for secondary toolchains. Also, we need to control
+outputs based on target configs (e.g., whether -gsplit-dwarf is used or
+not) rather than applying them globally to all targets in a toolchain.
+
+This CL allows defining `c_additional_outputs` in a `config` block. When
+applied to a target, these outputs are expanded and added to the Ninja
+build edge's outputs for each source file compilation.
+
+Bug: 502431091
+Change-Id: I914b4ab220d68d3f9bd8eacd2fe4655138fb636c
+Reviewed-on: https://gn-review.googlesource.com/c/gn/+/21980
+Reviewed-by: Matt Stark <msta AT google.com>
+Reviewed-by: Andrew Grieve <agrieve AT google.com>
+Commit-Queue: Takuto Ikuta <tikuta AT google.com>
+Upstream-Status: Backport
+Origin: Upstream
+Signed-off-by: Ismael Luceno <ismael AT sourcemage.org>
+---
+ docs/reference.md | 20 +++++++
+ src/gn/config_values.cc | 1 +
+ src/gn/config_values.h | 9 ++++
+ src/gn/config_values_extractors_unittest.cc | 19 +++++++
+ src/gn/config_values_generator.cc | 17 ++++++
+ src/gn/ninja_c_binary_target_writer.cc | 37 +++++++++++++
+ .../ninja_c_binary_target_writer_unittest.cc | 53 +++++++++++++++++++
+ src/gn/variables.cc | 24 +++++++++
+ src/gn/variables.h | 4 ++
+ 9 files changed, 184 insertions(+)
+
+diff --git a/docs/reference.md b/docs/reference.md
+index 27d5af0f..41639374 100644
+--- a/docs/reference.md
++++ b/docs/reference.md
+@@ -107,6 +107,7 @@
+ * [bundle_executable_dir: Expansion of {{bundle_executable_dir}} in
create_bundle](#var_bundle_executable_dir)
+ * [bundle_resources_dir: Expansion of {{bundle_resources_dir}} in
create_bundle.](#var_bundle_resources_dir)
+ * [bundle_root_dir: Expansion of {{bundle_root_dir}} in
create_bundle.](#var_bundle_root_dir)
++ * [c_additional_outputs: [string list] Additional outputs for the
compiler.](#var_c_additional_outputs)
+ * [cflags: [string list] Flags passed to all C compiler
variants.](#var_cflags)
+ * [cflags_c: [string list] Flags passed to the C
compiler.](#var_cflags_c)
+ * [cflags_cc: [string list] Flags passed to the C++
compiler.](#var_cflags_cc)
+@@ -5318,6 +5319,25 @@
+ bundle_executable_dir = "${bundle_contents_dir}/MacOS"
+ }
+ ```
++### <a name="var_c_additional_outputs"></a>**c_additional_outputs**:
[string list] Additional outputs for the compiler. [Back to
Top](#gn-reference)
++
++```
++ A list of substitution expressions that will be evaluated in the context
++ of the compiler tool (e.g. "cc") and added to its outputs when this config
++ is applied to a target.
++
++ This is useful for tools that produce side-artifacts like .dwo files
++ when specific flags (like -gsplit-dwarf) are used.
++
++ "c_additional_outputs" are applied to all invocations of the C, C++,
++ Objective C, and Objective C++ compilers.
++
++ Example:
++ config("split_dwarf") {
++ cflags = [ "-gsplit-dwarf" ]
++ c_additional_outputs = [
"{{source_out_dir}}/{{source_name_part}}.dwo" ]
++ }
++```
+ ### <a name="var_cflags"></a>**cflags***: Flags passed to the C
compiler. [Back to Top](#gn-reference)
+
+ ```
+diff --git a/src/gn/config_values.cc b/src/gn/config_values.cc
+index 3bab686a..896e25c6 100644
+--- a/src/gn/config_values.cc
++++ b/src/gn/config_values.cc
+@@ -41,6 +41,7 @@ void ConfigValues::AppendValues(const ConfigValues&
append) {
+ VectorAppend(&rustflags_, append.rustflags_);
+ VectorAppend(&rustenv_, append.rustenv_);
+ VectorAppend(&swiftflags_, append.swiftflags_);
++ VectorAppend(&c_additional_outputs_, append.c_additional_outputs_);
+
+ // Only append precompiled header if there isn't one. It might be nice to
+ // throw an error if there are conflicting precompiled headers, but that
+diff --git a/src/gn/config_values.h b/src/gn/config_values.h
+index 24a235a4..434f8074 100644
+--- a/src/gn/config_values.h
++++ b/src/gn/config_values.h
+@@ -11,6 +11,7 @@
+ #include "gn/lib_file.h"
+ #include "gn/source_dir.h"
+ #include "gn/source_file.h"
++#include "gn/substitution_pattern.h"
+
+ // Holds the values (include_dirs, defines, compiler flags, etc.) for a
given
+ // config or target.
+@@ -62,6 +63,13 @@ class ConfigValues {
+ const std::vector<SourceFile>& inputs() const { return inputs_; }
+ std::vector<SourceFile>& inputs() { return inputs_; }
+
++ const std::vector<SubstitutionPattern>& c_additional_outputs() const {
++ return c_additional_outputs_;
++ }
++ std::vector<SubstitutionPattern>& c_additional_outputs() {
++ return c_additional_outputs_;
++ }
++
+ const std::vector<LibFile>& libs() const { return libs_; }
+ std::vector<LibFile>& libs() { return libs_; }
+
+@@ -100,6 +108,7 @@ class ConfigValues {
+ std::vector<std::string> rustenv_;
+ std::vector<std::string> swiftflags_;
+ std::vector<std::pair<std::string, LibFile>> externs_;
++ std::vector<SubstitutionPattern> c_additional_outputs_;
+ // If you add a new one, be sure to update AppendValues().
+
+ std::string precompiled_header_;
+diff --git a/src/gn/config_values_extractors_unittest.cc
b/src/gn/config_values_extractors_unittest.cc
+index b1fda895..24efa4c8 100644
+--- a/src/gn/config_values_extractors_unittest.cc
++++ b/src/gn/config_values_extractors_unittest.cc
+@@ -183,3 +183,22 @@ TEST(ConfigValuesGenerator, CflagsWithNewlineError) {
+ EXPECT_TRUE(err.has_error());
+ EXPECT_EQ(err.message(), "Newlines in cflags values are not supported.");
+ }
++
++TEST(ConfigValuesGenerator, AdditionalOutputs) {
++ TestWithScope setup;
++ Err err;
++
++ Value outputs_value(nullptr, Value::LIST);
++ outputs_value.list_value().push_back(
++ Value(nullptr, "{{target_out_dir}}/{{source_name_part}}.dwo"));
++ setup.scope()->SetValue("c_additional_outputs", outputs_value, nullptr);
++
++ ConfigValues config_values;
++ ConfigValuesGenerator gen(&config_values, setup.scope(),
SourceDir("//foo/"),
++ &err);
++ gen.Run();
++ EXPECT_FALSE(err.has_error());
++ ASSERT_EQ(config_values.c_additional_outputs().size(), 1u);
++ EXPECT_EQ(config_values.c_additional_outputs()[0].AsString(),
++ "{{target_out_dir}}/{{source_name_part}}.dwo");
++}
+diff --git a/src/gn/config_values_generator.cc
b/src/gn/config_values_generator.cc
+index 1f0f90c1..e2750662 100644
+--- a/src/gn/config_values_generator.cc
++++ b/src/gn/config_values_generator.cc
+@@ -212,4 +212,21 @@ void ConfigValuesGenerator::Run() {
+ if (err_->has_error())
+ return;
+ }
++
++ // C/C++/ObjectiveC/ObjectiveC++ additional outputs.
++ const Value* c_additional_outputs_value =
++ scope_->GetValue(variables::kCAdditionalOutputs, true);
++ if (c_additional_outputs_value) {
++ if (!c_additional_outputs_value->VerifyTypeIs(Value::LIST, err_))
++ return;
++
++ for (const Value& val : c_additional_outputs_value->list_value()) {
++ SubstitutionPattern pattern;
++ if (!pattern.Parse(val, err_))
++ return;
++ if (!pattern.IsInOutputDir(scope_->settings()->build_settings(),
err_))
++ return;
++ config_values_->c_additional_outputs().push_back(std::move(pattern));
++ }
++ }
+ }
+diff --git a/src/gn/ninja_c_binary_target_writer.cc
b/src/gn/ninja_c_binary_target_writer.cc
+index 90413414..d58871a5 100644
+--- a/src/gn/ninja_c_binary_target_writer.cc
++++ b/src/gn/ninja_c_binary_target_writer.cc
+@@ -391,6 +391,19 @@ void NinjaCBinaryTargetWriter::WriteSources(
+
+ std::vector<OutputFile> tool_outputs; // Prevent reallocation in loop.
+ std::vector<OutputFile> deps;
++
++ // Collect unique C-family additional outputs from all recursive configs.
++ std::vector<SubstitutionPattern> c_additional_outputs;
++ std::set<std::string> seen_patterns;
++ for (ConfigValuesIterator iter(target_); !iter.done(); iter.Next()) {
++ for (const auto& pattern : iter.cur().c_additional_outputs()) {
++ std::string pattern_str = pattern.AsString();
++ if (seen_patterns.insert(std::move(pattern_str)).second) {
++ c_additional_outputs.push_back(pattern);
++ }
++ }
++ }
++
+ for (const auto& source : target_->sources()) {
+ DCHECK_NE(source.GetType(), SourceFile::SOURCE_SWIFT);
+
+@@ -440,6 +453,24 @@ void NinjaCBinaryTargetWriter::WriteSources(
+ deps.push_back(*module_dep.pcm);
+ }
+
++ if (tool_name == CTool::kCToolCc || tool_name == CTool::kCToolCxx ||
++ tool_name == CTool::kCToolObjC || tool_name ==
CTool::kCToolObjCxx) {
++ for (const auto& pattern : c_additional_outputs) {
++ // Use ApplyPatternToCompilerAsOutputFile instead of
++ // ApplyPatternToSourceAsOutputFile to support target-level
++ // substitutions (e.g. {{target_out_dir}}) in addition to
source-level
++ // substitutions.
++ OutputFile extra_output =
++ SubstitutionWriter::ApplyPatternToCompilerAsOutputFile(
++ target_, source, pattern);
++ if (!extra_output.value().empty() &&
++ std::find(tool_outputs.begin(), tool_outputs.end(),
extra_output) ==
++ tool_outputs.end()) {
++ tool_outputs.push_back(std::move(extra_output));
++ }
++ }
++ }
++
+ WriteCompilerBuildLine({source}, deps, order_only_deps, tool,
+ tool_outputs);
+ WritePool(out_);
+@@ -452,6 +483,12 @@ void NinjaCBinaryTargetWriter::WriteSources(
+ } else {
+ extra_files->push_back(tool_outputs[0]);
+ }
++
++ // Add additional outputs to extra_files so they are included in the
++ // target's stamp file.
++ for (size_t i = 1; i < tool_outputs.size(); ++i) {
++ extra_files->push_back(tool_outputs[i]);
++ }
+ }
+
+ out_ << std::endl;
+diff --git a/src/gn/ninja_c_binary_target_writer_unittest.cc
b/src/gn/ninja_c_binary_target_writer_unittest.cc
+index bf81b233..585eb534 100644
+--- a/src/gn/ninja_c_binary_target_writer_unittest.cc
++++ b/src/gn/ninja_c_binary_target_writer_unittest.cc
+@@ -165,6 +165,59 @@ TEST_F(NinjaCBinaryTargetWriterTest, SourceSet) {
+ }
+ }
+
++TEST_F(NinjaCBinaryTargetWriterTest, AdditionalOutputs) {
++ Err err;
++ TestWithScope setup;
++
++ Value outputs_value(nullptr, Value::LIST);
++ outputs_value.list_value().push_back(
++ Value(nullptr, "{{target_out_dir}}/{{source_name_part}}.dwo"));
++
++ Config config(setup.settings(), Label(SourceDir("//foo/"),
"split_dwarf"));
++ config.visibility().SetPublic();
++
++ std::vector<SubstitutionPattern> patterns;
++ for (const auto& v : outputs_value.list_value()) {
++ SubstitutionPattern pattern;
++ ASSERT_TRUE(pattern.Parse(v, &err));
++ patterns.push_back(std::move(pattern));
++ }
++ config.own_values().c_additional_outputs() = std::move(patterns);
++ ASSERT_TRUE(config.OnResolved(&err));
++
++ Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
++ target.set_output_type(Target::SOURCE_SET);
++ target.visibility().SetPublic();
++ target.sources().push_back(SourceFile("//foo/input1.cc"));
++ target.source_types_used().Set(SourceFile::SOURCE_CPP);
++ target.configs().push_back(LabelConfigPair(&config));
++ target.SetToolchain(setup.toolchain());
++ ASSERT_TRUE(target.OnResolved(&err));
++
++ std::ostringstream out;
++ NinjaCBinaryTargetWriter writer(&target, out);
++ writer.Run();
++
++ const char expected[] =
++ "defines =\n"
++ "include_dirs =\n"
++ "cflags =\n"
++ "cflags_cc =\n"
++ "root_out_dir = .\n"
++ "target_gen_dir = gen/foo\n"
++ "target_out_dir = obj/foo\n"
++ "target_output_name = bar\n"
++ "\n"
++ "build obj/foo/bar.input1.o obj/foo/input1.dwo: cxx "
++ "../../foo/input1.cc\n"
++ " source_file_part = input1.cc\n"
++ " source_name_part = input1\n"
++ "\n"
++ "build phony/foo/bar: phony obj/foo/bar.input1.o
obj/foo/input1.dwo\n";
++
++ EXPECT_EQ(expected, out.str());
++}
++
+ TEST_F(NinjaCBinaryTargetWriterTest, EscapeDefines) {
+ TestWithScope setup;
+ Err err;
+diff --git a/src/gn/variables.cc b/src/gn/variables.cc
+index ad9657f3..960689b7 100644
+--- a/src/gn/variables.cc
++++ b/src/gn/variables.cc
+@@ -417,6 +417,29 @@ Possible values:
+ All public headers will be marked as textual.
+ )";
+
++const char kCAdditionalOutputs[] = "c_additional_outputs";
++const char kCAdditionalOutputs_HelpShort[] =
++ "c_additional_outputs: [string list] Additional outputs for the
compiler.";
++const char kCAdditionalOutputs_Help[] =
++ R"(c_additional_outputs: [string list] Additional outputs for the
compiler.
++
++ A list of substitution expressions that will be evaluated in the context
++ of the compiler tool (e.g. "cc") and added to its outputs when this config
++ is applied to a target.
++
++ This is useful for tools that produce side-artifacts like .dwo files
++ when specific flags (like -gsplit-dwarf) are used.
++
++ "c_additional_outputs" are applied to all invocations of the C, C++,
++ Objective C, and Objective C++ compilers.
++
++ Example:
++ config("split_dwarf") {
++ cflags = [ "-gsplit-dwarf" ]
++ c_additional_outputs = [
"{{source_out_dir}}/{{source_name_part}}.dwo" ]
++ }
++)";
++
+ // Target variables
------------------------------------------------------------
+
+ #define COMMON_ORDERING_HELP
\
+@@ -2476,6 +2499,7 @@ const VariableInfoMap& GetTargetVariables() {
+ INSERT_VARIABLE(BundleExecutableDir)
+ INSERT_VARIABLE(XcassetCompilerFlags)
+ INSERT_VARIABLE(Transparent)
++ INSERT_VARIABLE(CAdditionalOutputs)
+ INSERT_VARIABLE(Cflags)
+ INSERT_VARIABLE(CflagsC)
+ INSERT_VARIABLE(CflagsCC)
+diff --git a/src/gn/variables.h b/src/gn/variables.h
+index 6820121e..66e0680e 100644
+--- a/src/gn/variables.h
++++ b/src/gn/variables.h
+@@ -86,6 +86,10 @@ extern const char kGenerateModulemap_Help[];
+
+ // Target vars
-----------------------------------------------------------------
+
++extern const char kCAdditionalOutputs[];
++extern const char kCAdditionalOutputs_HelpShort[];
++extern const char kCAdditionalOutputs_Help[];
++
+ extern const char kAllDependentConfigs[];
+ extern const char kAllDependentConfigs_HelpShort[];
+ extern const char kAllDependentConfigs_Help[];
diff --git a/devel/gn/patches/0003-pool-include-cstdint-for-int64_t.patch
b/devel/gn/patches/0003-pool-include-cstdint-for-int64_t.patch
new file mode 100644
index 0000000..b220cd3
--- /dev/null
+++ b/devel/gn/patches/0003-pool-include-cstdint-for-int64_t.patch
@@ -0,0 +1,27 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Ismael Luceno <ismael AT sourcemage.org>
+Date: Tue, 6 Oct 2026 18:47:59 +0200
+Subject: [PATCH 3/3] pool: include <cstdint> for int64_t
+
+pool.h uses int64_t but relies on <string> to drag in <cstdint>, which
+newer libc++ and libstdc++ no longer do. Under clang 22 the class body
+fails to parse, and every later use of the inherited Item::label() is
+reported as "cannot initialize object parameter of type 'const Item'".
+
+Upstream-Status: Pending
+Origin: Source Mage
+Signed-off-by: Ismael Luceno <ismael AT sourcemage.org>
+---
+ src/gn/pool.h | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/src/gn/pool.h
++++ b/src/gn/pool.h
+@@ -5,6 +5,7 @@
+ #ifndef TOOLS_GN_POOL_H_
+ #define TOOLS_GN_POOL_H_
+
++#include <cstdint>
+ #include <string>
+
+ #include "gn/item.h"
- [[SM-Commit] ] GIT changes to master grimoire by Ismael Luceno (2ac6f4e42e970e056e90c001f5aa8396051cd535), Ismael Luceno, 10/06/2026
Archive powered by MHonArc 2.6.24.