#include "gn/setup.h"
#include <stdlib.h>
#include <algorithm>
#include <memory>
#include <sstream>
#include <utility>
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/memory/ref_counted.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "gn/command_format.h"
#include "gn/commands.h"
#include "gn/exec_process.h"
#include "gn/filesystem_utils.h"
#include "gn/graph/include/graph.h"
#include "gn/innerapis_publicinfo_generator.h"
#include "gn/input_file.h"
#include "gn/label_pattern.h"
#include "gn/location.h"
#include "gn/ohos_components_checker.h"
#include "gn/parse_tree.h"
#include "gn/parser.h"
#include "gn/precise/precise.h"
#include "gn/source_dir.h"
#include "gn/source_file.h"
#include "gn/standard_out.h"
#include "gn/switches.h"
#include "gn/tokenizer.h"
#include "gn/trace.h"
#include "gn/value.h"
#include "gn/value_extractors.h"
#include "util/build_config.h"
#if defined(OS_WIN)
#include <windows.h>
#include "base/win/scoped_process_information.h"
#include "base/win/win_util.h"
#endif
const char kDotfile_Help[] =
R"(.gn file
When gn starts, it will search the current directory and parent directories
for a file called ".gn". This indicates the source root. You can override
this detection by using the --root command-line argument
The .gn file in the source root will be executed. The syntax is the same as a
buildfile, but with very limited build setup-specific meaning.
If you specify --root, by default GN will look for the file .gn in that
directory. If you want to specify a different file, you can additionally pass
--dotfile:
gn gen out/Debug --root=/home/build --dotfile=/home/my_gn_file.gn
The system variable `gn_version` is available in the dotfile, but none of
the other variables are, because the dotfile is processed before args.gn
or anything else is processed.
Variables
arg_file_template [optional]
Path to a file containing the text that should be used as the default
args.gn content when you run `gn args`.
buildconfig [required]
Path to the build config file. This file will be used to set up the
build file execution environment for each toolchain.
check_targets [optional]
A list of labels and label patterns that should be checked when running
"gn check" or "gn gen --check". If neither check_targets or
no_check_targets (see below) is specified, all targets will be checked.
It is an error to specify both check_targets and no_check_targets. If it
is the empty list, no targets will be checked. To bypass this list,
request an explicit check of targets, like "//*".
The format of this list is identical to that of "visibility" so see "gn
help visibility" for examples.
no_check_targets [optional]
A list of labels and label patterns that should *not* be checked when
running "gn check" or "gn gen --check". All other targets will be checked.
If neither check_targets (see above) or no_check_targets is specified, all
targets will be checked. It is an error to specify both check_targets and
no_check_targets.
The format of this list is identical to that of "visibility" so see "gn
help visibility" for examples.
check_system_includes [optional]
Boolean to control whether system style includes are checked by default
when running "gn check" or "gn gen --check". System style includes are
includes that use angle brackets <> instead of double quotes "". If this
setting is omitted or set to false, these includes will be ignored by
default. They can be checked explicitly by running
"gn check --check-system" or "gn gen --check=system"
exec_script_allowlist [optional]
A list of .gn/.gni files (not labels) that have permission to call the
exec_script function. If this list is defined, calls to exec_script will
be checked against this list and GN will fail if the current file isn't
in the list.
This is to allow the use of exec_script to be restricted since is easy to
use inappropriately. Wildcards are not supported. Files in the
secondary_source tree (if defined) should be referenced by ignoring the
secondary tree and naming them as if they are in the main tree.
If unspecified, the ability to call exec_script is unrestricted.
Example:
exec_script_allowlist = [
"//base/BUILD.gn",
"//build/my_config.gni",
]
exec_script_whitelist [optional]
A synonym for "exec_script_allowlist" that exists for backwards
compatibility. New code should use "exec_script_allowlist" instead.
If both values are set, only the value in "exec_script_allowlist" will
have any effect (so don't set both!).
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).
When specified, GN will generate a compile_commands.json file in the root
of the build directory containing information on how to compile each
source file reachable from any label matching any pattern in the list.
This is used for Clang-based tooling and some editor integration. See
https://clang.llvm.org/docs/JSONCompilationDatabase.html
The switch --add-export-compile-commands to "gn gen" (see "gn help gen")
appends to this value which provides a per-user way to customize it.
The deprecated switch --export-compile-commands to "gn gen" (see "gn help
gen") adds to the export target list using a different format.
Example:
export_compile_commands = [
"//base/*",
"//tools:doom_melon",
]
root [optional]
Label of the root build target. The GN build will start by loading the
build file containing this target name. This defaults to "//:" which will
cause the file //BUILD.gn to be loaded. Note that build_file_extension
applies to the default case as well.
The command-line switch --root-target will override this value (see "gn
help --root-target").
root_patterns [optional]
A list of label pattern strings. When not defined or empty, the GN build
graph will contain all targets from any BUILD.gn evaluated in the default
toolchain context, and their transitive dependencies.
When set to a non empty list, only the targets in the default toolchain
matching these patterns, and their transitive dependencies, will be defined
instead.
The command-line switch --root-pattern will override this value (see
"gn help --root-pattern")
script_executable [optional]
By default, GN runs the scripts used in action targets and exec_script
calls using the Python interpreter found in PATH. This value specifies the
Python executable or other interpreter to use instead.
If set to the empty string, the scripts will be executed directly.
The command-line switch --script-executable will override this value (see
"gn help --script-executable")
secondary_source [optional]
Label of an alternate directory tree to find input files. When searching
for a BUILD.gn file (or the build config file discussed above), the file
will first be looked for in the source root. If it's not found, the
secondary source root will be checked (which would contain a parallel
directory hierarchy).
This behavior is intended to be used when BUILD.gn files can't be checked
in to certain source directories for whatever reason.
The secondary source root must be inside the main source tree.
default_args [optional]
Scope containing the default overrides for declared arguments. These
overrides take precedence over the default values specified in the
declare_args() block, but can be overridden using --args or the
args.gn file.
This is intended to be used when subprojects declare arguments with
default values that need to be changed for whatever reason.
build_file_extension [optional]
If set to a non-empty string, this is added to the name of all build files
to load.
GN will look for build files named "BUILD.$build_file_extension.gn".
This is intended to be used during migrations or other situations where
there are two independent GN builds in the same directories.
ninja_required_version [optional]
When set specifies the minimum required version of Ninja. The default
required version is 1.7.2. Specifying a higher version might enable the
use of some of newer features that can make the build more efficient.
no_stamp_files [optional]
A boolean flag that can be set to generate Ninja files that use phony
rules instead of stamp files whenever possible. This results in smaller
Ninja build plans, but requires at least Ninja 1.11.
ohos_components_support [optional]
This parameter enable support for OpenHarmony components.
When enabled, gn will load components information from "build_configs/"
directory in the root_out_directory.
The following files will be loaded:
out/build_configs/parts_info/inner_kits_info.json (required):
Required InnerAPI information file for each OHOS component.
out/build_configs/component_override_map.json (optional):
Optional overrided component maps file.
For OpenHarmony system build, this value must be enabled to support
external_deps (see "gn help external_deps") and public_external_deps
(see "gn help public_external_deps").
Example .gn file contents
buildconfig = "//build/config/BUILDCONFIG.gn"
check_targets = [
"//doom_melon/*", # Check everything in this subtree.
"//tools:mind_controlling_ant", # Check this specific target.
]
root = "//:root"
secondary_source = "//build/config/temporary_buildfiles/"
default_args = {
# Default to release builds for this project.
is_debug = false
is_component_build = false
}
)";
namespace {
const base::FilePath::CharType kGnFile[] = FILE_PATH_LITERAL(".gn");
const char kDefaultArgsGn[] =
"# Set build arguments here. See `gn help buildargs`.";
base::FilePath FindDotFile(const base::FilePath& current_dir) {
base::FilePath try_this_file = current_dir.Append(kGnFile);
if (base::PathExists(try_this_file))
return try_this_file;
base::FilePath with_no_slash = current_dir.StripTrailingSeparators();
base::FilePath up_one_dir = with_no_slash.DirName();
if (up_one_dir == current_dir)
return base::FilePath();
return FindDotFile(up_one_dir);
}
void ItemDefinedCallback(MsgLoop* task_runner,
Builder* builder_call_on_main_thread_only,
std::unique_ptr<Item> item) {
DCHECK(item);
g_scheduler->IncrementWorkCount();
auto item_shared = std::make_shared<std::unique_ptr<Item>>(std::move(item));
task_runner->PostTask(
[builder_call_on_main_thread_only, item_shared]() mutable {
builder_call_on_main_thread_only->ItemDefined(std::move(*item_shared));
g_scheduler->DecrementWorkCount();
});
}
void DecrementWorkCount() {
g_scheduler->DecrementWorkCount();
}
#if defined(OS_WIN)
std::u16string SysMultiByteTo16(std::string_view mb) {
if (mb.empty())
return std::u16string();
int mb_length = static_cast<int>(mb.length());
int charcount = MultiByteToWideChar(CP_ACP, 0, mb.data(), mb_length, NULL, 0);
if (charcount == 0)
return std::u16string();
std::u16string wide;
wide.resize(charcount);
MultiByteToWideChar(CP_ACP, 0, mb.data(), mb_length, base::ToWCharT(&wide[0]),
charcount);
return wide;
}
base::FilePath PythonBatToExe(const base::FilePath& bat_path) {
std::u16string command = u"cmd.exe /c \"\"";
command.append(bat_path.value());
command.append(u"\" -c \"import sys; print(sys.executable)\"\"");
std::string python_path;
std::string std_err;
int exit_code;
base::FilePath cwd;
GetCurrentDirectory(&cwd);
if (internal::ExecProcess(command, cwd, &python_path, &std_err, &exit_code) &&
exit_code == 0 && std_err.empty()) {
base::TrimWhitespaceASCII(python_path, base::TRIM_ALL, &python_path);
base::FilePath exe_path(SysMultiByteTo16(python_path));
if (base::PathExists(exe_path))
return exe_path;
}
return base::FilePath();
}
base::FilePath FindWindowsPython(const base::FilePath& python_exe_name,
const base::FilePath& python_bat_name) {
char16_t current_directory[MAX_PATH];
::GetCurrentDirectory(MAX_PATH, reinterpret_cast<LPWSTR>(current_directory));
if (!python_exe_name.empty()) {
CHECK(python_exe_name.FinalExtension() == u".exe");
CHECK_EQ(python_exe_name.IsAbsolute(), false);
base::FilePath cur_dir_candidate_exe =
base::FilePath(current_directory).Append(python_exe_name);
if (base::PathExists(cur_dir_candidate_exe))
return cur_dir_candidate_exe;
}
const char16_t kPathEnvVarName[] = u"Path";
DWORD path_length = ::GetEnvironmentVariable(
reinterpret_cast<LPCWSTR>(kPathEnvVarName), nullptr, 0);
if (path_length == 0)
return base::FilePath();
std::unique_ptr<char16_t[]> full_path(new char16_t[path_length]);
DWORD actual_path_length = ::GetEnvironmentVariable(
reinterpret_cast<LPCWSTR>(kPathEnvVarName),
reinterpret_cast<LPWSTR>(full_path.get()), path_length);
CHECK_EQ(path_length, actual_path_length + 1);
for (const auto& component : base::SplitStringPiece(
std::u16string_view(full_path.get(), path_length), u";",
base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) {
if (!python_exe_name.empty()) {
base::FilePath candidate_exe =
base::FilePath(component).Append(python_exe_name);
if (base::PathExists(candidate_exe))
return candidate_exe;
}
if (!python_bat_name.empty()) {
CHECK(python_bat_name.FinalExtension() == u".bat");
CHECK_EQ(python_bat_name.IsAbsolute(), false);
base::FilePath candidate_bat =
base::FilePath(component).Append(python_bat_name);
if (base::PathExists(candidate_bat)) {
base::FilePath python_exe = PythonBatToExe(candidate_bat);
if (!python_exe.empty())
return python_exe;
}
}
}
return base::FilePath();
}
#endif
}
const char Setup::kBuildArgFileName[] = "args.gn";
Setup::Setup()
: build_settings_(),
loader_(new LoaderImpl(&build_settings_)),
builder_(loader_.get()),
dotfile_settings_(&build_settings_, std::string()),
dotfile_scope_(&dotfile_settings_) {
dotfile_settings_.set_toolchain_label(Label());
dotfile_provider_ =
std::make_unique<ScopePerFileProvider>(&dotfile_scope_, false, true);
build_settings_.set_item_defined_callback(
[task_runner = scheduler_.task_runner(),
builder = &builder_](std::unique_ptr<Item> item) {
ItemDefinedCallback(task_runner, builder, std::move(item));
});
loader_->set_complete_callback(&DecrementWorkCount);
loader_->set_task_runner(scheduler_.task_runner());
}
bool Setup::DoSetup(const std::string& build_dir, bool force_create) {
return DoSetup(build_dir, force_create,
*base::CommandLine::ForCurrentProcess());
}
bool Setup::DoSetup(const std::string& build_dir,
bool force_create,
const base::CommandLine& cmdline) {
Err err;
if (!DoSetupWithErr(build_dir, force_create, cmdline, &err)) {
err.PrintToStdout();
return false;
}
DCHECK(!err.has_error());
return true;
}
bool Setup::FillOhosComponentsInfo(const std::string& build_dir, Err* err)
{
const Value *support =
dotfile_scope_.GetValue("ohos_components_support", true);
build_settings_.set_ohos_components_support(support && support->boolean_value());
const Value *independent = build_settings_.build_args().GetArgOverride("ohos_indep_compiler_enable");
const Value *product = build_settings_.build_args().GetArgOverride("product_name");
const Value* special_parts_scan_switch = build_settings_.build_args().GetArgOverride("special_parts_scan_switch");
bool special_parts_switch = false;
if (special_parts_scan_switch && special_parts_scan_switch->type() == Value::BOOLEAN) {
special_parts_switch = special_parts_scan_switch->boolean_value();
}
if (!ohos_components_.LoadOhosComponents(build_dir, support, independent, product, special_parts_switch, err)) {
return false;
}
if (ohos_components_.isOhosComponentsLoaded()) {
build_settings_.SetOhosComponentsInfo(&ohos_components_);
}
const Value* checkType = build_settings_.build_args().GetArgOverride("ohos_components_checktype");
const Value* ruleSwitch = build_settings_.build_args().GetArgOverride("ohos_interception_rule_switch");
const Value* whitelistDebug = build_settings_.build_args().GetArgOverride("ohos_interception_whitelist_debug");
bool whitelistDebugEnabled = false;
if (whitelistDebug && whitelistDebug->type() == Value::BOOLEAN) {
whitelistDebugEnabled = whitelistDebug->boolean_value();
}
if (ruleSwitch && ruleSwitch->type() == Value::INTEGER) {
if (checkType && checkType->type() == Value::INTEGER) {
ohos_components_.LoadOhosComponentsChecker(build_dir, support, checkType->int_value(), ruleSwitch->int_value(), whitelistDebugEnabled);
} else {
ohos_components_.LoadOhosComponentsChecker(build_dir, support,
OhosComponentChecker::CheckType::INTERCEPT_IGNORE_TEST, ruleSwitch->int_value(), whitelistDebugEnabled);
}
} else {
if (checkType && checkType->type() == Value::INTEGER) {
const unsigned int INTERCEPT_ALL_RULE = (1 << (OhosComponentChecker::BinaryLeftShift::ALL - 1)) - 1;
ohos_components_.LoadOhosComponentsChecker(build_dir, support, checkType->int_value(), INTERCEPT_ALL_RULE, whitelistDebugEnabled);
} else {
ohos_components_.LoadOhosComponentsChecker(build_dir, support, OhosComponentChecker::CheckType::NONE,
OhosComponentChecker::BinaryLeftShift::UNKNOWN, whitelistDebugEnabled);
}
}
if (independent) {
ohos_components_.LoadOhosComponentsMapping(build_dir, support, independent);
}
const Value* preciseEnable = build_settings_.build_args().GetArgOverride("ohos_module_precise_build");
if (preciseEnable && preciseEnable->boolean_value()) {
const Value* preciseConfig = build_settings_.build_args().GetArgOverride("ohos_precise_config");
PreciseManager::Init(build_dir, build_settings_.root_path().value(), preciseConfig);
}
const Value* graphEnable = build_settings_.build_args().GetArgOverride("ohos_graph_enable");
if (graphEnable && graphEnable->boolean_value()) {
Graph::Init(build_dir);
}
return true;
}
bool Setup::DoSetupWithErr(const std::string& build_dir,
bool force_create,
const base::CommandLine& cmdline,
Err* err) {
scheduler_.set_verbose_logging(cmdline.HasSwitch(switches::kVerbose));
if (cmdline.HasSwitch(switches::kTime) ||
cmdline.HasSwitch(switches::kTracelog))
EnableTracing();
ScopedTrace setup_trace(TraceItem::TRACE_SETUP, "DoSetup");
if (!FillSourceDir(cmdline, err))
return false;
if (!RunConfigFile(err))
return false;
if (!FillOtherConfig(cmdline, err))
return false;
if (!FillBuildDir(build_dir, !force_create, err))
return false;
if (default_args_) {
Scope::KeyValueMap overrides;
default_args_->GetCurrentScopeValues(&overrides);
build_settings_.build_args().AddDefaultArgOverrides(overrides);
}
if (fill_arguments_) {
if (!FillArguments(cmdline, err))
return false;
}
if (!FillOhosComponentsInfo(build_dir, err)) {
return false;
}
if (!FillPythonPath(cmdline, err))
return false;
if (!dotfile_scope_.CheckForUnusedVars(err)) {
return false;
}
return true;
}
bool Setup::Run() {
return Run(*base::CommandLine::ForCurrentProcess());
}
bool Setup::Run(const base::CommandLine& cmdline) {
RunPreMessageLoop();
if (!scheduler_.Run())
return false;
return RunPostMessageLoop(cmdline);
}
SourceFile Setup::GetBuildArgFile() const {
return SourceFile(build_settings_.build_dir().value() + kBuildArgFileName);
}
void Setup::RunPreMessageLoop() {
g_scheduler->IncrementWorkCount();
loader_->Load(root_build_file_, LocationRange(), Label());
}
bool Setup::RunPostMessageLoop(const base::CommandLine& cmdline) {
Err err;
if (!builder_.CheckForBadItems(&err)) {
err.PrintToStdout();
return false;
}
if (!build_settings_.build_args().VerifyAllOverridesUsed(&err)) {
if (cmdline.HasSwitch(switches::kFailOnUnusedArgs)) {
err.PrintToStdout();
return false;
}
err.PrintNonfatalToStdout();
OutputString(
"\nThe build continued as if that argument was "
"unspecified.\n\n");
}
if (check_public_headers_) {
std::vector<const Target*> all_targets = builder_.GetAllResolvedTargets();
std::vector<const Target*> to_check;
if (check_patterns()) {
commands::FilterTargetsByPatterns(all_targets, *check_patterns(),
&to_check);
} else if (no_check_patterns()) {
commands::FilterOutTargetsByPatterns(all_targets, *no_check_patterns(),
&to_check);
} else {
to_check = all_targets;
}
if (!commands::CheckPublicHeaders(&build_settings_, all_targets, to_check,
false, false, check_system_includes_)) {
return false;
}
}
if (cmdline.HasSwitch(switches::kTime))
PrintLongHelp(SummarizeTraces());
if (cmdline.HasSwitch(switches::kTracelog))
SaveTraces(cmdline.GetSwitchValuePath(switches::kTracelog));
Err result;
InnerApiPublicInfoGenerator* instance = InnerApiPublicInfoGenerator::getInstance();
if (instance != nullptr) {
if (!instance->GeneratedInnerapiPublicInfo(builder_.GetAllResolvedTargets(), &result)) {
result.PrintToStdout();
return false;
}
}
PreciseManager* preciseManager = PreciseManager::GetInstance();
if (preciseManager != nullptr) {
preciseManager->GeneratPreciseTargets();
}
Graph* graph = Graph::GetInstance();
if (graph != nullptr) {
graph->GenGraph(builder_.GetAllResolvedItems());
}
OhosComponentChecker::WriteInterceptedListIfNeeded();
return true;
}
bool Setup::FillArguments(const base::CommandLine& cmdline, Err* err) {
base::FilePath build_arg_file =
build_settings_.GetFullPath(GetBuildArgFile());
auto switch_value = cmdline.GetSwitchValueString(switches::kArgs);
if (cmdline.HasSwitch(switches::kArgs) ||
(gen_empty_args_ && !PathExists(build_arg_file))) {
if (!FillArgsFromCommandLine(
switch_value.empty() ? kDefaultArgsGn : switch_value, err)) {
return false;
}
SaveArgsToFile();
return true;
}
return FillArgsFromFile(err);
}
bool Setup::FillArgsFromCommandLine(const std::string& args, Err* err) {
args_input_file_ = std::make_unique<InputFile>(SourceFile());
args_input_file_->SetContents(args);
args_input_file_->set_friendly_name("the command-line \"--args\"");
return FillArgsFromArgsInputFile(err);
}
bool Setup::FillArgsFromFile(Err* err) {
ScopedTrace setup_trace(TraceItem::TRACE_SETUP, "Load args file");
SourceFile build_arg_source_file = GetBuildArgFile();
base::FilePath build_arg_file =
build_settings_.GetFullPath(build_arg_source_file);
std::string contents;
if (!base::ReadFileToString(build_arg_file, &contents))
return true;
g_scheduler->AddGenDependency(build_arg_file);
if (contents.empty())
return true;
args_input_file_ = std::make_unique<InputFile>(build_arg_source_file);
args_input_file_->SetContents(contents);
args_input_file_->set_friendly_name(
"build arg file (use \"gn args <out_dir>\" to edit)");
setup_trace.Done();
return FillArgsFromArgsInputFile(err);
}
bool Setup::FillArgsFromArgsInputFile(Err* err) {
ScopedTrace setup_trace(TraceItem::TRACE_SETUP, "Parse args");
args_tokens_ = Tokenizer::Tokenize(args_input_file_.get(), err);
if (err->has_error()) {
return false;
}
args_root_ = Parser::Parse(args_tokens_, err);
if (err->has_error()) {
return false;
}
Scope arg_scope(&dotfile_settings_);
arg_scope.set_source_dir(args_input_file_->dir());
args_root_->Execute(&arg_scope, err);
if (err->has_error()) {
return false;
}
Scope::KeyValueMap overrides;
arg_scope.GetCurrentScopeValues(&overrides);
build_settings_.build_args().AddArgOverrides(overrides);
build_settings_.build_args().set_build_args_dependency_files(
arg_scope.CollectBuildDependencyFiles());
return true;
}
bool Setup::SaveArgsToFile() {
ScopedTrace setup_trace(TraceItem::TRACE_SETUP, "Save args file");
base::FilePath build_arg_file =
build_settings_.GetFullPath(GetBuildArgFile());
base::CreateDirectory(build_arg_file.DirName());
std::string contents = args_input_file_->contents();
commands::FormatStringToString(contents, commands::TreeDumpMode::kInactive,
&contents, nullptr);
#if defined(OS_WIN)
base::ReplaceSubstringsAfterOffset(&contents, 0, "\n", "\r\n");
#endif
if (base::WriteFile(build_arg_file, contents.c_str(),
static_cast<int>(contents.size())) == -1) {
Err(Location(), "Args file could not be written.",
"The file is \"" + FilePathToUTF8(build_arg_file) + "\"")
.PrintToStdout();
return false;
}
g_scheduler->AddGenDependency(build_arg_file);
return true;
}
bool Setup::FillSourceDir(const base::CommandLine& cmdline, Err* err) {
base::FilePath root_path;
base::FilePath relative_root_path =
cmdline.GetSwitchValuePath(switches::kRoot);
if (!relative_root_path.empty()) {
root_path = base::MakeAbsoluteFilePath(relative_root_path);
if (root_path.empty()) {
*err = Err(Location(), "Root source path not found.",
"The path \"" + FilePathToUTF8(relative_root_path) +
"\" doesn't exist.");
return false;
}
base::FilePath dotfile_path =
cmdline.GetSwitchValuePath(switches::kDotfile);
if (dotfile_path.empty()) {
dotfile_name_ = root_path.Append(kGnFile);
} else {
dotfile_name_ = base::MakeAbsoluteFilePath(dotfile_path);
if (dotfile_name_.empty()) {
*err = Err(Location(), "Could not load dotfile.",
"The file \"" + FilePathToUTF8(dotfile_path) +
"\" couldn't be loaded.");
return false;
}
build_settings_.set_dotfile_name(dotfile_name_);
}
} else {
base::FilePath cur_dir;
base::GetCurrentDirectory(&cur_dir);
dotfile_name_ = FindDotFile(cur_dir);
if (dotfile_name_.empty()) {
*err = Err(
Location(), "Can't find source root.",
"I could not find a \".gn\" file in the current directory or any "
"parent,\nand the --root command-line argument was not specified.");
return false;
}
root_path = dotfile_name_.DirName();
}
base::FilePath root_realpath = base::MakeAbsoluteFilePath(root_path);
if (root_realpath.empty()) {
*err = Err(Location(), "Can't get the real root path.",
"I could not get the real path of \"" +
FilePathToUTF8(root_path) + "\".");
return false;
}
if (scheduler_.verbose_logging())
scheduler_.Log("Using source root", FilePathToUTF8(root_realpath));
build_settings_.SetRootPath(root_realpath);
return true;
}
bool Setup::FillBuildDir(const std::string& build_dir,
bool require_exists,
Err* err) {
SourceDir resolved =
SourceDirForCurrentDirectory(build_settings_.root_path())
.ResolveRelativeDir(Value(nullptr, build_dir), err,
build_settings_.root_path_utf8());
if (err->has_error()) {
return false;
}
base::FilePath build_dir_path = build_settings_.GetFullPath(resolved);
if (!base::CreateDirectory(build_dir_path)) {
*err = Err(Location(), "Can't create the build dir.",
"I could not create the build dir \"" +
FilePathToUTF8(build_dir_path) + "\".");
return false;
}
base::FilePath build_dir_realpath =
base::MakeAbsoluteFilePath(build_dir_path);
if (build_dir_realpath.empty()) {
*err = Err(Location(), "Can't get the real build dir path.",
"I could not get the real path of \"" +
FilePathToUTF8(build_dir_path) + "\".");
return false;
}
resolved = SourceDirForPath(build_settings_.root_path(), build_dir_realpath);
if (scheduler_.verbose_logging())
scheduler_.Log("Using build dir", resolved.value());
if (require_exists) {
if (!base::PathExists(
build_dir_path.Append(FILE_PATH_LITERAL("build.ninja")))) {
*err = Err(
Location(), "Not a build directory.",
"This command requires an existing build directory. I interpreted "
"your input\n\"" +
build_dir + "\" as:\n " + FilePathToUTF8(build_dir_path) +
"\nwhich doesn't seem to contain a previously-generated build.");
return false;
}
}
build_settings_.SetBuildDir(resolved);
return true;
}
base::FilePath ProcessFileExtensions(base::FilePath script_executable) {
#if defined(OS_WIN)
auto extension = script_executable.FinalExtension();
if (script_executable.IsAbsolute()) {
if (extension == u".bat")
script_executable = PythonBatToExe(script_executable);
} else {
if (extension == u"") {
script_executable =
FindWindowsPython(script_executable.ReplaceExtension(u".exe"),
script_executable.ReplaceExtension(u".bat"));
} else if (extension == u".bat") {
script_executable =
FindWindowsPython(base::FilePath(), script_executable);
} else if (extension == u".exe") {
script_executable =
FindWindowsPython(script_executable, base::FilePath());
}
}
script_executable = script_executable.NormalizePathSeparatorsTo('/');
#endif
return script_executable;
}
bool Setup::FillPythonPath(const base::CommandLine& cmdline, Err* err) {
ScopedTrace setup_trace(TraceItem::TRACE_SETUP, "Fill Python Path");
const Value* value = dotfile_scope_.GetValue("script_executable", true);
if (cmdline.HasSwitch(switches::kScriptExecutable)) {
auto script_executable =
cmdline.GetSwitchValuePath(switches::kScriptExecutable);
build_settings_.SetPythonPath(ProcessFileExtensions(script_executable));
} else if (value) {
if (!value->VerifyTypeIs(Value::STRING, err)) {
return false;
}
base::FilePath python_path;
if (!value->string_value().empty()) {
python_path =
ProcessFileExtensions(UTF8ToFilePath(value->string_value()));
if (python_path.empty()) {
*err = Err(Location(), "Could not find \"" + value->string_value() +
"\" from dotfile in PATH.");
return false;
}
}
build_settings_.SetPythonPath(std::move(python_path));
} else {
#if defined(OS_WIN)
base::FilePath python_path =
ProcessFileExtensions(base::FilePath(u"python"));
if (!python_path.IsAbsolute()) {
scheduler_.Log("WARNING",
"Could not find python on path, using "
"just \"python.exe\"");
python_path = base::FilePath(u"python.exe");
}
build_settings_.SetPythonPath(std::move(python_path));
#else
build_settings_.SetPythonPath(base::FilePath("python"));
#endif
}
return true;
}
bool Setup::RunConfigFile(Err* err) {
if (scheduler_.verbose_logging())
scheduler_.Log("Got dotfile", FilePathToUTF8(dotfile_name_));
dotfile_input_file_ = std::make_unique<InputFile>(SourceFile("//.gn"));
if (!dotfile_input_file_->Load(dotfile_name_)) {
*err = Err(Location(), "Could not load dotfile.",
"The file \"" + FilePathToUTF8(dotfile_name_) +
"\" couldn't be loaded");
return false;
}
dotfile_tokens_ = Tokenizer::Tokenize(dotfile_input_file_.get(), err);
if (err->has_error()) {
return false;
}
dotfile_root_ = Parser::Parse(dotfile_tokens_, err);
if (err->has_error()) {
return false;
}
g_scheduler->AddGenDependency(dotfile_name_);
dotfile_scope_.AddBuildDependencyFile(SourceFile("//.gn"));
dotfile_root_->Execute(&dotfile_scope_, err);
if (err->has_error()) {
return false;
}
return true;
}
bool Setup::FillOtherConfig(const base::CommandLine& cmdline, Err* err) {
SourceDir current_dir("//");
Label root_target_label(current_dir, "");
std::vector<LabelPattern> root_patterns;
const Value* secondary_value =
dotfile_scope_.GetValue("secondary_source", true);
if (secondary_value) {
if (!secondary_value->VerifyTypeIs(Value::STRING, err)) {
return false;
}
build_settings_.SetSecondarySourcePath(
SourceDir(secondary_value->string_value()));
}
const Value* build_file_extension_value =
dotfile_scope_.GetValue("build_file_extension", true);
if (build_file_extension_value) {
if (!build_file_extension_value->VerifyTypeIs(Value::STRING, err)) {
return false;
}
std::string extension = build_file_extension_value->string_value();
auto normalized_extension = UTF8ToFilePath(extension).value();
if (normalized_extension.find_first_of(base::FilePath::kSeparators) !=
base::FilePath::StringType::npos) {
*err = Err(Location(), "Build file extension '" + extension +
"' cannot " + "contain a path separator");
return false;
}
loader_->set_build_file_extension(extension);
}
const Value* ninja_required_version_value =
dotfile_scope_.GetValue("ninja_required_version", true);
if (ninja_required_version_value) {
if (!ninja_required_version_value->VerifyTypeIs(Value::STRING, err)) {
return false;
}
std::optional<Version> version =
Version::FromString(ninja_required_version_value->string_value());
if (!version) {
Err(Location(), "Invalid Ninja version '" +
ninja_required_version_value->string_value() + "'")
.PrintToStdout();
return false;
}
build_settings_.set_ninja_required_version(*version);
}
const Value* glob_max_results_value =
dotfile_scope_.GetValue("glob_max_results", true);
if (glob_max_results_value) {
if (!glob_max_results_value->VerifyTypeIs(Value::INTEGER, err)) {
return false;
}
int max_results = glob_max_results_value->int_value();
if (max_results <= 0) {
Err(Location(), "Invalid glob_max_results value")
.PrintToStdout();
return false;
}
build_settings_.set_glob_max_results(max_results);
}
if (cmdline.HasSwitch(switches::kRootTarget)) {
auto switch_value = cmdline.GetSwitchValueString(switches::kRootTarget);
Value root_value(nullptr, switch_value);
root_target_label = Label::Resolve(current_dir, std::string_view(), Label(),
root_value, err);
if (err->has_error()) {
return false;
}
if (dotfile_scope_.GetValue("root", true)) {
dotfile_scope_.MarkUsed("root");
}
} else {
const Value* root_value = dotfile_scope_.GetValue("root", true);
if (root_value) {
if (!root_value->VerifyTypeIs(Value::STRING, err)) {
return false;
}
root_target_label = Label::Resolve(current_dir, std::string_view(),
Label(), *root_value, err);
if (err->has_error()) {
return false;
}
}
}
if (cmdline.HasSwitch(switches::kRootPattern)) {
auto& switches = cmdline.GetSwitches();
for (auto it = switches.find(switches::kRootPattern);
it != switches.end() && it->first == switches::kRootPattern; ++it) {
std::string pattern = base::CommandLine::StringTypeToUTF8(it->second);
LabelPattern pat = LabelPattern::GetPattern(
SourceDir("//"), build_settings_.root_path_utf8(),
Value(nullptr, pattern), err);
if (err->has_error()) {
err->AppendSubErr(
Err(Location(),
"for the command-line switch --root-pattern=" + pattern));
return false;
}
if (!pat.toolchain().is_null()) {
*err = Err(Location(),
"Root pattern cannot have toolchain suffix: " + pattern);
return false;
}
root_patterns.push_back(std::move(pat));
}
(void)dotfile_scope_.GetValue("root_patterns", true);
} else {
const Value* root_patterns_value =
dotfile_scope_.GetValue("root_patterns", true);
if (root_patterns_value) {
if (!root_patterns_value->VerifyTypeIs(Value::LIST, err)) {
return false;
}
for (const auto& pattern_value : root_patterns_value->list_value()) {
if (!pattern_value.VerifyTypeIs(Value::STRING, err))
return false;
LabelPattern pat = LabelPattern::GetPattern(
SourceDir("//"), build_settings_.root_path_utf8(), pattern_value,
err);
if (err->has_error())
return false;
if (!pat.toolchain().is_null()) {
*err =
Err(pattern_value, "Root pattern cannot have toolchain suffix: " +
pattern_value.string_value());
return false;
}
root_patterns.push_back(std::move(pat));
}
}
}
root_build_file_ = loader_->BuildFileForLabel(root_target_label);
build_settings_.SetRootTargetLabel(root_target_label);
build_settings_.SetRootPatterns(std::move(root_patterns));
const Value* build_config_value =
dotfile_scope_.GetValue("buildconfig", true);
if (!build_config_value) {
Err(Location(), "No build config file.",
"Your .gn file (\"" + FilePathToUTF8(dotfile_name_) +
"\")\n"
"didn't specify a \"buildconfig\" value.")
.PrintToStdout();
return false;
} else if (!build_config_value->VerifyTypeIs(Value::STRING, err)) {
return false;
}
build_settings_.set_build_config_file(
SourceFile(build_config_value->string_value()));
const Value* check_targets_value =
dotfile_scope_.GetValue("check_targets", true);
if (check_targets_value) {
check_patterns_ = std::make_unique<std::vector<LabelPattern>>();
ExtractListOfLabelPatterns(&build_settings_, *check_targets_value,
current_dir, check_patterns_.get(), err);
if (err->has_error()) {
return false;
}
}
const Value* no_check_targets_value =
dotfile_scope_.GetValue("no_check_targets", true);
if (no_check_targets_value) {
if (check_targets_value) {
Err(Location(), "Conflicting check settings.",
"Your .gn file (\"" + FilePathToUTF8(dotfile_name_) +
"\")\n"
"specified both check_targets and no_check_targets and at most "
"one is allowed.")
.PrintToStdout();
return false;
}
no_check_patterns_ = std::make_unique<std::vector<LabelPattern>>();
ExtractListOfLabelPatterns(&build_settings_, *no_check_targets_value,
current_dir, no_check_patterns_.get(), err);
if (err->has_error()) {
return false;
}
}
const Value* check_system_includes_value =
dotfile_scope_.GetValue("check_system_includes", true);
if (check_system_includes_value) {
if (!check_system_includes_value->VerifyTypeIs(Value::BOOLEAN, err)) {
return false;
}
check_system_includes_ = check_system_includes_value->boolean_value();
}
const Value* exec_script_allowlist_value =
dotfile_scope_.GetValue("exec_script_allowlist", true);
if (!exec_script_allowlist_value) {
exec_script_allowlist_value =
dotfile_scope_.GetValue("exec_script_whitelist", true);
}
if (exec_script_allowlist_value) {
if (!exec_script_allowlist_value->VerifyTypeIs(Value::LIST, err)) {
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;
}
}
build_settings_.set_exec_script_allowlist(std::move(allowlist));
}
const Value* default_args_value =
dotfile_scope_.GetValue("default_args", true);
if (default_args_value) {
if (!default_args_value->VerifyTypeIs(Value::SCOPE, err)) {
return false;
}
default_args_ = default_args_value->scope_value();
}
const Value* arg_file_template_value =
dotfile_scope_.GetValue("arg_file_template", true);
if (arg_file_template_value) {
if (!arg_file_template_value->VerifyTypeIs(Value::STRING, err)) {
return false;
}
SourceFile path(arg_file_template_value->string_value());
build_settings_.set_arg_file_template_path(path);
}
const Value* no_stamp_files_value =
dotfile_scope_.GetValue("no_stamp_files", true);
if (no_stamp_files_value) {
if (!no_stamp_files_value->VerifyTypeIs(Value::BOOLEAN, err)) {
return false;
}
build_settings_.set_no_stamp_files(no_stamp_files_value->boolean_value());
CHECK(!build_settings_.no_stamp_files())
<< "no_stamp_files does not work yet!";
}
const Value* export_cc_value =
dotfile_scope_.GetValue("export_compile_commands", true);
if (export_cc_value) {
if (!ExtractListOfLabelPatterns(&build_settings_, *export_cc_value,
SourceDir("//"), &export_compile_commands_,
err)) {
return false;
}
}
for (const std::string& cur :
cmdline.GetSwitchValueStrings(switches::kAddExportCompileCommands)) {
LabelPattern pat = LabelPattern::GetPattern(
SourceDir("//"), build_settings_.root_path_utf8(), Value(nullptr, cur),
err);
if (err->has_error()) {
err->AppendSubErr(Err(
Location(),
"for the command-line switch --add-export-compile-commands=" + cur));
return false;
}
export_compile_commands_.push_back(std::move(pat));
}
return true;
}