| // 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 |