// Copyright (c) 2013 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 "tools/gn/setup.h" #include #include #include #include "base/bind.h" #include "base/command_line.h" #include "base/file_util.h" #include "base/files/file_path.h" #include "base/process/launch.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "build/build_config.h" #include "tools/gn/filesystem_utils.h" #include "tools/gn/header_checker.h" #include "tools/gn/input_file.h" #include "tools/gn/parse_tree.h" #include "tools/gn/parser.h" #include "tools/gn/source_dir.h" #include "tools/gn/source_file.h" #include "tools/gn/standard_out.h" #include "tools/gn/tokenizer.h" #include "tools/gn/trace.h" #include "tools/gn/value.h" #if defined(OS_WIN) #include #endif extern const char kDotfile_Help[] = ".gn file\n" "\n" " When gn starts, it will search the current directory and parent\n" " directories for a file called \".gn\". This indicates the source root.\n" " You can override this detection by using the --root command-line\n" " argument\n" "\n" " The .gn file in the source root will be executed. The syntax is the\n" " same as a buildfile, but with very limited build setup-specific\n" " meaning.\n" "\n" " If you specify --root, by default GN will look for the file .gn in\n" " that directory. If you want to specify a different file, you can\n" " additionally pass --dotfile:\n" "\n" " gn gen out/Debug --root=/home/build --dotfile=/home/my_gn_file.gn\n" "\n" "Variables\n" "\n" " buildconfig [required]\n" " Label of the build config file. This file will be used to set up\n" " the build file execution environment for each toolchain.\n" "\n" " root [optional]\n" " Label of the root build target. The GN build will start by loading\n" " the build file containing this target name. This defaults to\n" " \"//:\" which will cause the file //BUILD.gn to be loaded.\n" "\n" " secondary_source [optional]\n" " Label of an alternate directory tree to find input files. When\n" " searching for a BUILD.gn file (or the build config file discussed\n" " above), the file fill first be looked for in the source root.\n" " If it's not found, the secondary source root will be checked\n" " (which would contain a parallel directory hierarchy).\n" "\n" " This behavior is intended to be used when BUILD.gn files can't be\n" " checked in to certain source directories for whatever reason.\n" "\n" " The secondary source root must be inside the main source tree.\n" "\n" "Example .gn file contents\n" "\n" " buildconfig = \"//build/config/BUILDCONFIG.gn\"\n" "\n" " root = \"//:root\"\n" "\n" " secondary_source = \"//build/config/temporary_buildfiles/\"\n"; namespace { // More logging. const char kSwitchVerbose[] = "v"; // Set build args. const char kSwitchArgs[] = "args"; // Set root dir. const char kSwitchRoot[] = "root"; // Set dotfile name. const char kSwitchDotfile[] = "dotfile"; // Enable timing. const char kTimeSwitch[] = "time"; const char kTracelogSwitch[] = "tracelog"; const base::FilePath::CharType kGnFile[] = FILE_PATH_LITERAL(".gn"); 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(); // Got to the top. return FindDotFile(up_one_dir); } // Called on any thread. Post the item to the builder on the main thread. void ItemDefinedCallback(base::MessageLoop* main_loop, scoped_refptr builder, scoped_ptr item) { DCHECK(item); main_loop->PostTask(FROM_HERE, base::Bind(&Builder::ItemDefined, builder, base::Passed(&item))); } void DecrementWorkCount() { g_scheduler->DecrementWorkCount(); } } // namespace // CommonSetup ----------------------------------------------------------------- const char CommonSetup::kBuildArgFileName[] = "args.gn"; CommonSetup::CommonSetup() : build_settings_(), loader_(new LoaderImpl(&build_settings_)), builder_(new Builder(loader_.get())), root_build_file_("//BUILD.gn"), check_for_bad_items_(true), check_for_unused_overrides_(true), check_public_headers_(false) { loader_->set_complete_callback(base::Bind(&DecrementWorkCount)); } CommonSetup::CommonSetup(const CommonSetup& other) : build_settings_(other.build_settings_), loader_(new LoaderImpl(&build_settings_)), builder_(new Builder(loader_.get())), root_build_file_(other.root_build_file_), check_for_bad_items_(other.check_for_bad_items_), check_for_unused_overrides_(other.check_for_unused_overrides_), check_public_headers_(other.check_public_headers_) { loader_->set_complete_callback(base::Bind(&DecrementWorkCount)); } CommonSetup::~CommonSetup() { } void CommonSetup::RunPreMessageLoop() { // Load the root build file. loader_->Load(root_build_file_, LocationRange(), Label()); // Will be decremented with the loader is drained. g_scheduler->IncrementWorkCount(); } bool CommonSetup::RunPostMessageLoop() { Err err; if (check_for_bad_items_) { if (!builder_->CheckForBadItems(&err)) { err.PrintToStdout(); return false; } } if (check_for_unused_overrides_) { if (!build_settings_.build_args().VerifyAllOverridesUsed(&err)) { // TODO(brettw) implement a system of warnings. Until we have a better // system, print the error but don't return failure. err.PrintToStdout(); return true; } } if (check_public_headers_) { std::vector targets = builder_->GetAllResolvedTargets(); scoped_refptr header_checker( new HeaderChecker(&build_settings_, targets)); std::vector header_errors; header_checker->Run(&header_errors); for (size_t i = 0; i < header_errors.size(); i++) { if (i > 0) OutputString("___________________\n", DECORATION_YELLOW); header_errors[i].PrintToStdout(); } if (!header_errors.empty()) return false; } // Write out tracing and timing if requested. const CommandLine* cmdline = CommandLine::ForCurrentProcess(); if (cmdline->HasSwitch(kTimeSwitch)) PrintLongHelp(SummarizeTraces()); if (cmdline->HasSwitch(kTracelogSwitch)) SaveTraces(cmdline->GetSwitchValuePath(kTracelogSwitch)); return true; } // Setup ----------------------------------------------------------------------- Setup::Setup() : CommonSetup(), empty_settings_(&empty_build_settings_, std::string()), dotfile_scope_(&empty_settings_), fill_arguments_(true) { empty_settings_.set_toolchain_label(Label()); build_settings_.set_item_defined_callback( base::Bind(&ItemDefinedCallback, scheduler_.main_loop(), builder_)); // The scheduler's main loop wasn't created when the Loader was created, so // we need to set it now. loader_->set_main_loop(scheduler_.main_loop()); } Setup::~Setup() { } bool Setup::DoSetup(const std::string& build_dir) { CommandLine* cmdline = CommandLine::ForCurrentProcess(); scheduler_.set_verbose_logging(cmdline->HasSwitch(kSwitchVerbose)); if (cmdline->HasSwitch(kTimeSwitch) || cmdline->HasSwitch(kTracelogSwitch)) EnableTracing(); ScopedTrace setup_trace(TraceItem::TRACE_SETUP, "DoSetup"); if (!FillSourceDir(*cmdline)) return false; if (!RunConfigFile()) return false; if (!FillOtherConfig(*cmdline)) return false; if (!FillBuildDir(build_dir)) // Must be after FillSourceDir to resolve. return false; if (fill_arguments_) { if (!FillArguments(*cmdline)) return false; } FillPythonPath(); return true; } bool Setup::Run() { RunPreMessageLoop(); if (!scheduler_.Run()) return false; return RunPostMessageLoop(); } Scheduler* Setup::GetScheduler() { return &scheduler_; } SourceFile Setup::GetBuildArgFile() const { return SourceFile(build_settings_.build_dir().value() + kBuildArgFileName); } bool Setup::FillArguments(const CommandLine& cmdline) { // Use the args on the command line if specified, and save them. Do this even // if the list is empty (this means clear any defaults). if (cmdline.HasSwitch(kSwitchArgs)) { if (!FillArgsFromCommandLine(cmdline.GetSwitchValueASCII(kSwitchArgs))) return false; SaveArgsToFile(); return true; } // No command line args given, use the arguments from the build dir (if any). return FillArgsFromFile(); } bool Setup::FillArgsFromCommandLine(const std::string& args) { args_input_file_.reset(new InputFile(SourceFile())); args_input_file_->SetContents(args); args_input_file_->set_friendly_name("the command-line \"--args\""); return FillArgsFromArgsInputFile(); } bool Setup::FillArgsFromFile() { 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; // File doesn't exist, continue with default args. // Add a dependency on the build arguments file. If this changes, we want // to re-generate the build. g_scheduler->AddGenDependency(build_arg_file); if (contents.empty()) return true; // Empty file, do nothing. args_input_file_.reset(new InputFile(build_arg_source_file)); args_input_file_->SetContents(contents); args_input_file_->set_friendly_name( "build arg file (use \"gn args \" to edit)"); setup_trace.Done(); // Only want to count the load as part of the trace. return FillArgsFromArgsInputFile(); } bool Setup::FillArgsFromArgsInputFile() { ScopedTrace setup_trace(TraceItem::TRACE_SETUP, "Parse args"); Err err; args_tokens_ = Tokenizer::Tokenize(args_input_file_.get(), &err); if (err.has_error()) { err.PrintToStdout(); return false; } args_root_ = Parser::Parse(args_tokens_, &err); if (err.has_error()) { err.PrintToStdout(); return false; } Scope arg_scope(&empty_settings_); args_root_->AsBlock()->ExecuteBlockInScope(&arg_scope, &err); if (err.has_error()) { err.PrintToStdout(); return false; } // Save the result of the command args. Scope::KeyValueMap overrides; arg_scope.GetCurrentScopeValues(&overrides); build_settings_.build_args().AddArgOverrides(overrides); return true; } bool Setup::SaveArgsToFile() { ScopedTrace setup_trace(TraceItem::TRACE_SETUP, "Save args file"); Scope::KeyValueMap args = build_settings_.build_args().GetAllOverrides(); std::ostringstream stream; for (Scope::KeyValueMap::const_iterator i = args.begin(); i != args.end(); ++i) { stream << i->first.as_string() << " = " << i->second.ToString(true); stream << std::endl; } // For the first run, the build output dir might not be created yet, so do // that so we can write a file into it. Ignore errors, we'll catch the error // when we try to write a file to it below. base::FilePath build_arg_file = build_settings_.GetFullPath(GetBuildArgFile()); base::CreateDirectory(build_arg_file.DirName()); std::string contents = stream.str(); #if defined(OS_WIN) // Use Windows lineendings for this file since it will often open in // Notepad which can't handle Unix ones. ReplaceSubstringsAfterOffset(&contents, 0, "\n", "\r\n"); #endif if (base::WriteFile(build_arg_file, contents.c_str(), static_cast(contents.size())) == -1) { Err(Location(), "Args file could not be written.", "The file is \"" + FilePathToUTF8(build_arg_file) + "\"").PrintToStdout(); return false; } // Add a dependency on the build arguments file. If this changes, we want // to re-generate the build. g_scheduler->AddGenDependency(build_arg_file); return true; } bool Setup::FillSourceDir(const CommandLine& cmdline) { // Find the .gn file. base::FilePath root_path; // Prefer the command line args to the config file. base::FilePath relative_root_path = cmdline.GetSwitchValuePath(kSwitchRoot); if (!relative_root_path.empty()) { root_path = base::MakeAbsoluteFilePath(relative_root_path); if (root_path.empty()) { Err(Location(), "Root source path not found.", "The path \"" + FilePathToUTF8(relative_root_path) + "\" doesn't exist.").PrintToStdout(); return false; } // When --root is specified, an alternate --dotfile can also be set. // --dotfile should be a real file path and not a "//foo" source-relative // path. base::FilePath dot_file_path = cmdline.GetSwitchValuePath(kSwitchDotfile); if (dot_file_path.empty()) { dotfile_name_ = root_path.Append(kGnFile); } else { dotfile_name_ = base::MakeAbsoluteFilePath(dot_file_path); if (dotfile_name_.empty()) { Err(Location(), "Could not load dotfile.", "The file \"" + FilePathToUTF8(dot_file_path) + "\" cound't be loaded.").PrintToStdout(); return false; } } } else { // In the default case, look for a dotfile and that also tells us where the // source root is. base::FilePath cur_dir; base::GetCurrentDirectory(&cur_dir); dotfile_name_ = FindDotFile(cur_dir); if (dotfile_name_.empty()) { 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.") .PrintToStdout(); return false; } root_path = dotfile_name_.DirName(); } if (scheduler_.verbose_logging()) scheduler_.Log("Using source root", FilePathToUTF8(root_path)); build_settings_.SetRootPath(root_path); return true; } bool Setup::FillBuildDir(const std::string& build_dir) { SourceDir resolved = SourceDirForCurrentDirectory(build_settings_.root_path()). ResolveRelativeDir(build_dir); if (resolved.is_null()) { Err(Location(), "Couldn't resolve build directory.", "The build directory supplied (\"" + build_dir + "\") was not valid."). PrintToStdout(); return false; } if (scheduler_.verbose_logging()) scheduler_.Log("Using build dir", resolved.value()); build_settings_.SetBuildDir(resolved); return true; } void Setup::FillPythonPath() { // Trace this since it tends to be a bit slow on Windows. ScopedTrace setup_trace(TraceItem::TRACE_SETUP, "Fill Python Path"); #if defined(OS_WIN) // Find Python on the path so we can use the absolute path in the build. const base::char16 kGetPython[] = L"cmd.exe /c python -c \"import sys; print sys.executable\""; std::string python_path; if (base::GetAppOutput(kGetPython, &python_path)) { base::TrimWhitespaceASCII(python_path, base::TRIM_ALL, &python_path); if (scheduler_.verbose_logging()) scheduler_.Log("Found python", python_path); } else { scheduler_.Log("WARNING", "Could not find python on path, using " "just \"python.exe\""); python_path = "python.exe"; } build_settings_.set_python_path(base::FilePath(base::UTF8ToUTF16(python_path)) .NormalizePathSeparatorsTo('/')); #else build_settings_.set_python_path(base::FilePath("python")); #endif } bool Setup::RunConfigFile() { if (scheduler_.verbose_logging()) scheduler_.Log("Got dotfile", FilePathToUTF8(dotfile_name_)); dotfile_input_file_.reset(new InputFile(SourceFile("//.gn"))); if (!dotfile_input_file_->Load(dotfile_name_)) { Err(Location(), "Could not load dotfile.", "The file \"" + FilePathToUTF8(dotfile_name_) + "\" cound't be loaded") .PrintToStdout(); return false; } Err err; dotfile_tokens_ = Tokenizer::Tokenize(dotfile_input_file_.get(), &err); if (err.has_error()) { err.PrintToStdout(); return false; } dotfile_root_ = Parser::Parse(dotfile_tokens_, &err); if (err.has_error()) { err.PrintToStdout(); return false; } dotfile_root_->AsBlock()->ExecuteBlockInScope(&dotfile_scope_, &err); if (err.has_error()) { err.PrintToStdout(); return false; } return true; } bool Setup::FillOtherConfig(const CommandLine& cmdline) { Err err; // Secondary source path, read from the config file if present. // Read from the config file if present. const Value* secondary_value = dotfile_scope_.GetValue("secondary_source", true); if (secondary_value) { if (!secondary_value->VerifyTypeIs(Value::STRING, &err)) { err.PrintToStdout(); return false; } build_settings_.SetSecondarySourcePath( SourceDir(secondary_value->string_value())); } // Root build file. const Value* root_value = dotfile_scope_.GetValue("root", true); if (root_value) { if (!root_value->VerifyTypeIs(Value::STRING, &err)) { err.PrintToStdout(); return false; } Label root_target_label = Label::Resolve(SourceDir("//"), Label(), *root_value, &err); if (err.has_error()) { err.PrintToStdout(); return false; } root_build_file_ = Loader::BuildFileForLabel(root_target_label); } // Build config file. 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)) { err.PrintToStdout(); return false; } build_settings_.set_build_config_file( SourceFile(build_config_value->string_value())); return true; } // DependentSetup -------------------------------------------------------------- DependentSetup::DependentSetup(Setup* derive_from) : CommonSetup(*derive_from), scheduler_(derive_from->GetScheduler()) { build_settings_.set_item_defined_callback( base::Bind(&ItemDefinedCallback, scheduler_->main_loop(), builder_)); } DependentSetup::DependentSetup(DependentSetup* derive_from) : CommonSetup(*derive_from), scheduler_(derive_from->GetScheduler()) { build_settings_.set_item_defined_callback( base::Bind(&ItemDefinedCallback, scheduler_->main_loop(), builder_)); } DependentSetup::~DependentSetup() { } Scheduler* DependentSetup::GetScheduler() { return scheduler_; } void DependentSetup::RunPreMessageLoop() { CommonSetup::RunPreMessageLoop(); } bool DependentSetup::RunPostMessageLoop() { return CommonSetup::RunPostMessageLoop(); }