blob: 4005098a2ef683a4960e382bcff25cae7a43d839 [file]
// 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