#include <stddef.h>
#include <algorithm>
#include <cstdlib>
#include <string>
#include <vector>
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "gn/build_settings.h"
#include "gn/err.h"
#include "gn/filesystem_utils.h"
#include "gn/functions.h"
#include "gn/parse_tree.h"
#include "gn/scope.h"
#include "gn/settings.h"
#include "gn/source_dir.h"
#include "gn/source_file.h"
#include "gn/value.h"
namespace functions {
namespace {
bool ParseArguments(const std::vector<Value>& args,
const FunctionCallNode* function,
const Value& root_value,
bool& allow_empty,
Err* err) {
if (args.size() < 1 || args.size() > 2) {
*err = Err(function->function(), "Expecting 1 or 2 arguments.");
return false;
}
if (!root_value.VerifyTypeIs(Value::STRING, err)) {
return false;
}
if (root_value.string_value().empty()) {
*err = Err(function->function(), "Empty path.",
"glob_files: The root path cannot be empty.");
return false;
}
allow_empty = false;
if (args.size() == 2) {
const Value& allow_empty_value = args[1];
if (!allow_empty_value.VerifyTypeIs(Value::BOOLEAN, err)) {
return false;
}
allow_empty = allow_empty_value.boolean_value();
}
return true;
}
bool ResolveAndCheckFile(Scope* scope,
const Value& root_value,
const std::string& source_root_path,
base::FilePath* system_path,
Err* err) {
const std::string& input_string = root_value.string_value();
if (!input_string.empty() && input_string.back() == '/') {
return false;
}
const SourceDir& cur_dir = scope->GetSourceDir();
SourceFile resolved_file =
cur_dir.ResolveRelativeFile(root_value, err, source_root_path);
if (err->has_error()) {
return false;
}
*system_path =
scope->settings()->build_settings()->GetFullPath(resolved_file);
return base::PathExists(*system_path) && !base::DirectoryExists(*system_path);
}
bool ValidateDirectoryPath(Scope* scope,
const FunctionCallNode* function,
const Value& root_value,
const std::string& source_root_path,
SourceDir* resolved_dir,
base::FilePath* system_path,
Err* err) {
const SourceDir& cur_dir = scope->GetSourceDir();
*resolved_dir = cur_dir.ResolveRelativeDir(root_value, err, source_root_path);
if (err->has_error()) {
return false;
}
if (resolved_dir->is_system_absolute()) {
*err =
Err(function->function(), "Invalid directory path.",
"glob_files: The root directory cannot be a system-absolute path. "
"Use a source-absolute path (starting with \"//\") or a path "
"relative to the current file.");
return false;
}
const SourceDir& build_dir = scope->settings()->build_settings()->build_dir();
if (IsStringInOutputDir(build_dir, resolved_dir->value())) {
*err =
Err(function->function(), "Cannot scan output directory.",
"glob_files: The root directory cannot be inside the build output "
"directory (" +
build_dir.value() +
". This function should only "
"be used for source files, not generated files.");
return false;
}
*system_path =
scope->settings()->build_settings()->GetFullPath(*resolved_dir);
return true;
}
Value ReturnSingleFile(const FunctionCallNode* function,
const base::FilePath& system_path,
const std::string& source_root_path,
Err* err) {
std::string file_path_utf8 = FilePathToUTF8(system_path);
std::string source_relative;
if (MakeAbsolutePathRelativeIfPossible(source_root_path, file_path_utf8,
&source_relative)) {
Value result(function, Value::LIST);
result.list_value().push_back(Value(function, source_relative));
return result;
} else {
*err = Err(function->function(), "Invalid file path.",
"glob_files: The specified file path could not be made "
"relative to the source root.");
return Value();
}
}
std::vector<std::string> CollectFilesInDirectory(
const base::FilePath& system_path,
const std::string& source_root_path,
int max_results,
const FunctionCallNode* function,
Err* err) {
std::vector<std::string> results;
base::FileEnumerator enumerator(system_path, true,
base::FileEnumerator::FILES);
for (base::FilePath path = enumerator.Next(); !path.empty();
path = enumerator.Next()) {
if (static_cast<int>(results.size()) >= max_results) {
*err = Err(function->function(), "Too many files found.",
"glob_files: The number of files found exceeds the global "
"limit (glob_max_results = " +
std::to_string(max_results) + ").");
return {};
}
std::string file_path_utf8 = FilePathToUTF8(path);
std::string source_relative;
if (MakeAbsolutePathRelativeIfPossible(source_root_path, file_path_utf8,
&source_relative)) {
results.push_back(source_relative);
}
}
return results;
}
Value BuildResultList(const FunctionCallNode* function,
const std::vector<std::string>& results) {
Value result(function, Value::LIST);
for (const auto& file : results) {
result.list_value().push_back(Value(function, file));
}
return result;
}
bool HandleEmptyDirectory(const FunctionCallNode* function,
bool allow_empty,
const std::vector<std::string>& results,
Err* err) {
if (!allow_empty && results.empty()) {
*err = Err(function->function(), "Empty directory",
"glob_files: The directory is empty and allow_empty is false.");
return false;
}
if (allow_empty && results.empty()) {
Err warning(function->function(), "Empty directory.",
"glob_files: The directory is empty, returning an empty list.");
warning.PrintNonfatalToStdout();
}
return true;
}
}
const char kGlobFiles[] = "glob_files";
const char kGlobFiles_HelpShort[] =
"glob_files: Recursively find all files in a directory or return a single "
"file.";
const char kGlobFiles_Help[] =
R"(glob_files: Recursively find all files in a directory or return a single file.
glob_files(root, allow_empty = false)
Finds all files in the given directory and its subdirectories. If the path
points to a file instead of a directory, return that single file.
arguments:
root: The directory to scan or the file to return. Must be a source-absolute
path (e.g., "//foo/bar") or a path relative to the current file.
allow_empty: If false (default), the function will fail if the directory is
empty or contains no matching files. If true, an empty list will
be returned and a warning will be issued. This parameter has no
effect when a file is specified.
Returns: A list of source-absolute file paths (e.g., ["//foo/bar/file.cc"]).
If a single file is specified, returns a list containing just that file.
Notes:
- Only source files are returned. Files in the output directory are filtered out.
- The root path can be a source-absolute path (starting with "//") or
a path relative to the current file. system-absolute path are not allowed.
- The root path cannot be inside the build output directory.
- The results are sorted and deduplicated to ensure consistency across
different system and file systems.
- The number of files returned is limited by the global setting
glob_max_results (default 5000) to prevent performance issues.
This can be configured in the .gn file in your project root:
glob_max_results = 10000
This is a global limit that cannot be overridden in BUILD.gn files.
- This function should only be used for build inputs. Do not use it to
generated outputs.
- The results can be further filtered using filter_include() and filter_exclude().
)";
Value RunGlobFiles(Scope* scope,
const FunctionCallNode* function,
const std::vector<Value>& args,
Err* err) {
const Value& root_value = args[0];
bool allow_empty;
if (!ParseArguments(args, function, root_value, allow_empty, err)) {
return Value();
}
const std::string& source_root_path =
scope->settings()->build_settings()->root_path_utf8();
base::FilePath system_path;
bool is_file_path = ResolveAndCheckFile(scope, root_value, source_root_path,
&system_path, err);
if (is_file_path) {
return ReturnSingleFile(function, system_path, source_root_path, err);
}
SourceDir resolved_dir;
if (!ValidateDirectoryPath(scope, function, root_value, source_root_path,
&resolved_dir, &system_path, err)) {
return Value();
}
if (!base::DirectoryExists(system_path)) {
*err = Err(function->function(), "Path not found.",
"glob_files: The specified path does not exist.");
return Value();
}
int max_results = scope->settings()->build_settings()->glob_max_results();
std::vector<std::string> results = CollectFilesInDirectory(
system_path, source_root_path, max_results, function, err);
if (err->has_error()) {
return Value();
}
if (!HandleEmptyDirectory(function, allow_empty, results, err)) {
return Value();
}
std::sort(results.begin(), results.end());
results.erase(std::unique(results.begin(), results.end()), results.end());
return BuildResultList(function, results);
}
}