// 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/loader.h" #include "base/bind.h" #include "base/message_loop/message_loop.h" #include "base/stl_util.h" #include "tools/gn/build_settings.h" #include "tools/gn/err.h" #include "tools/gn/filesystem_utils.h" #include "tools/gn/input_file_manager.h" #include "tools/gn/parse_tree.h" #include "tools/gn/scheduler.h" #include "tools/gn/scope_per_file_provider.h" #include "tools/gn/settings.h" #include "tools/gn/source_dir.h" #include "tools/gn/source_file.h" #include "tools/gn/trace.h" namespace { std::string GetOutputSubdirName(const Label& toolchain_label, bool is_default) { // The default toolchain has no subdir. if (is_default) return std::string(); // For now just assume the toolchain name is always a valid dir name. We may // want to clean up the in the future. return toolchain_label.name(); } } // namespace // Identifies one time a file is loaded in a given toolchain so we don't load // it more than once. struct LoaderImpl::LoadID { LoadID() {} LoadID(const SourceFile& f, const Label& tc_name) : file(f), toolchain_name(tc_name) { } bool operator<(const LoadID& other) const { if (file.value() == other.file.value()) return toolchain_name < other.toolchain_name; return file < other.file; } SourceFile file; Label toolchain_name; }; // Our tracking information for a toolchain. struct LoaderImpl::ToolchainRecord { // The default toolchain label can be empty for the first time the default // toolchain is loaded, since we don't know it yet. This will be fixed up // later. It should be valid in all other cases. ToolchainRecord(const BuildSettings* build_settings, const Label& toolchain_label, const Label& default_toolchain_label) : settings(build_settings, GetOutputSubdirName(toolchain_label, toolchain_label == default_toolchain_label)), is_toolchain_loaded(false), is_config_loaded(false) { settings.set_default_toolchain_label(default_toolchain_label); settings.set_toolchain_label(toolchain_label); } Settings settings; bool is_toolchain_loaded; bool is_config_loaded; std::vector waiting_on_me; }; // ----------------------------------------------------------------------------- const void* Loader::kDefaultToolchainKey = &kDefaultToolchainKey; Loader::Loader() { } Loader::~Loader() { } void Loader::Load(const Label& label) { Load(BuildFileForLabel(label), label.GetToolchainLabel()); } // static SourceFile Loader::BuildFileForLabel(const Label& label) { return SourceFile(label.dir().value() + "BUILD.gn"); } // ----------------------------------------------------------------------------- LoaderImpl::LoaderImpl(const BuildSettings* build_settings) : main_loop_(base::MessageLoop::current()), pending_loads_(0), build_settings_(build_settings) { } LoaderImpl::~LoaderImpl() { STLDeleteContainerPairSecondPointers(toolchain_records_.begin(), toolchain_records_.end()); } void LoaderImpl::Load(const SourceFile& file, const Label& in_toolchain_name) { const Label& toolchain_name = in_toolchain_name.is_null() ? default_toolchain_label_ : in_toolchain_name; LoadID load_id(file, toolchain_name); if (!invocations_.insert(load_id).second) return; // Already in set, so this file was already loaded or schedulerd. if (toolchain_records_.empty()) { // Nothing loaded, need to load the default build config. The intial load // should not specify a toolchain. DCHECK(toolchain_name.is_null()); ToolchainRecord* record = new ToolchainRecord(build_settings_, Label(), Label()); toolchain_records_[Label()] = record; // The default build config is no dependent on the toolchain definition, // since we need to load the build config before we know what the default // toolchain name is. record->is_toolchain_loaded = true; record->waiting_on_me.push_back(file); ScheduleLoadBuildConfig(&record->settings, Scope::KeyValueMap()); return; } ToolchainRecord* record; if (toolchain_name.is_null()) record = toolchain_records_[default_toolchain_label_]; else record = toolchain_records_[toolchain_name]; if (!record) { DCHECK(!default_toolchain_label_.is_null()); // No reference to this toolchain found yet, make one. record = new ToolchainRecord(build_settings_, toolchain_name, default_toolchain_label_); toolchain_records_[toolchain_name] = record; // Schedule a load of the toolchain using the default one. Load(BuildFileForLabel(toolchain_name), default_toolchain_label_); } if (record->is_config_loaded) ScheduleLoadFile(&record->settings, file); else record->waiting_on_me.push_back(file); } void LoaderImpl::ToolchainLoaded(const Toolchain* toolchain) { ToolchainRecord* record = toolchain_records_[toolchain->label()]; if (!record) { DCHECK(!default_toolchain_label_.is_null()); record = new ToolchainRecord(build_settings_, toolchain->label(), default_toolchain_label_); toolchain_records_[toolchain->label()] = record; } record->is_toolchain_loaded = true; // The default build config is loaded first, then its toolchain. Secondary // ones are loaded in the opposite order so we can pass toolchain parameters // to the build config. So we may or may not have a config at this point. if (!record->is_config_loaded) { ScheduleLoadBuildConfig(&record->settings, toolchain->args()); } else { // There should be nobody waiting on this if the build config is already // loaded. DCHECK(record->waiting_on_me.empty()); } } Label LoaderImpl::GetDefaultToolchain() const { return default_toolchain_label_; } const Settings* LoaderImpl::GetToolchainSettings(const Label& label) const { ToolchainRecordMap::const_iterator found_toolchain; if (label.is_null()) { if (default_toolchain_label_.is_null()) return NULL; found_toolchain = toolchain_records_.find(default_toolchain_label_); } else { found_toolchain = toolchain_records_.find(label); } if (found_toolchain == toolchain_records_.end()) return NULL; return &found_toolchain->second->settings; } void LoaderImpl::ScheduleLoadFile(const Settings* settings, const SourceFile& file) { Err err; pending_loads_++; if (!AsyncLoadFile(LocationRange(), settings->build_settings(), file, base::Bind(&LoaderImpl::BackgroundLoadFile, this, settings, file), &err)) { g_scheduler->FailWithError(err); DecrementPendingLoads(); } } void LoaderImpl::ScheduleLoadBuildConfig( Settings* settings, const Scope::KeyValueMap& toolchain_overrides) { Err err; pending_loads_++; if (!AsyncLoadFile(LocationRange(), settings->build_settings(), settings->build_settings()->build_config_file(), base::Bind(&LoaderImpl::BackgroundLoadBuildConfig, this, settings, toolchain_overrides), &err)) { g_scheduler->FailWithError(err); DecrementPendingLoads(); } } void LoaderImpl::BackgroundLoadFile(const Settings* settings, const SourceFile& file_name, const ParseNode* root) { if (!root) { main_loop_->PostTask(FROM_HERE, base::Bind(&LoaderImpl::DecrementPendingLoads, this)); return; } if (g_scheduler->verbose_logging()) { g_scheduler->Log("Running", file_name.value() + " with toolchain " + settings->toolchain_label().GetUserVisibleName(false)); } Scope our_scope(settings->base_config()); ScopePerFileProvider per_file_provider(&our_scope); our_scope.set_source_dir(file_name.GetDir()); ScopedTrace trace(TraceItem::TRACE_FILE_EXECUTE, file_name.value()); trace.SetToolchain(settings->toolchain_label()); Err err; root->Execute(&our_scope, &err); if (err.has_error()) g_scheduler->FailWithError(err); trace.Done(); main_loop_->PostTask(FROM_HERE, base::Bind(&LoaderImpl::DidLoadFile, this)); } void LoaderImpl::BackgroundLoadBuildConfig( Settings* settings, const Scope::KeyValueMap& toolchain_overrides, const ParseNode* root) { if (!root) { main_loop_->PostTask(FROM_HERE, base::Bind(&LoaderImpl::DecrementPendingLoads, this)); return; } Scope* base_config = settings->base_config(); base_config->set_source_dir(SourceDir("//")); settings->build_settings()->build_args().SetupRootScope( base_config, toolchain_overrides); base_config->SetProcessingBuildConfig(); // See kDefaultToolchainKey in the header. Label default_toolchain_label; if (settings->is_default()) base_config->SetProperty(kDefaultToolchainKey, &default_toolchain_label); ScopedTrace trace(TraceItem::TRACE_FILE_EXECUTE, settings->build_settings()->build_config_file().value()); trace.SetToolchain(settings->toolchain_label()); const BlockNode* root_block = root->AsBlock(); Err err; root_block->ExecuteBlockInScope(base_config, &err); trace.Done(); if (err.has_error()) g_scheduler->FailWithError(err); base_config->ClearProcessingBuildConfig(); if (settings->is_default()) { // The default toolchain must have been set in the default build config // file. if (default_toolchain_label.is_null()) { g_scheduler->FailWithError(Err(Location(), "The default build config file did not call set_default_toolchain()", "If you don't call this, I can't figure out what toolchain to use\n" "for all of this code.")); } else { DCHECK(settings->toolchain_label().is_null()); settings->set_toolchain_label(default_toolchain_label); } } main_loop_->PostTask(FROM_HERE, base::Bind(&LoaderImpl::DidLoadBuildConfig, this, settings->toolchain_label())); } void LoaderImpl::DidLoadFile() { DecrementPendingLoads(); } void LoaderImpl::DidLoadBuildConfig(const Label& label) { // Do not return early, we must call DecrementPendingLoads() at the bottom. ToolchainRecordMap::iterator found_toolchain = toolchain_records_.find(label); ToolchainRecord* record = NULL; if (found_toolchain == toolchain_records_.end()) { // When loading the default build config, we'll insert it into the record // map with an empty label since we don't yet know what to call it. // // In this case, we should have exactly one entry in the map with an empty // label. We now need to fix up the naming so it refers to the "real" one. CHECK(toolchain_records_.size() == 1); ToolchainRecordMap::iterator empty_label = toolchain_records_.find(Label()); CHECK(empty_label != toolchain_records_.end()); // Fix up the toolchain record. record = empty_label->second; toolchain_records_[label] = record; toolchain_records_.erase(empty_label); // Save the default toolchain label. default_toolchain_label_ = label; DCHECK(record->settings.default_toolchain_label().is_null()); record->settings.set_default_toolchain_label(label); // The settings object should have the toolchain label already set. DCHECK(!record->settings.toolchain_label().is_null()); // Update any stored invocations that refer to the empty toolchain label. // This will normally only be one, for the root build file, so brute-force // is OK. LoadIDSet old_loads; invocations_.swap(old_loads); for (LoadIDSet::iterator i = old_loads.begin(); i != old_loads.end(); ++i) { if (i->toolchain_name.is_null()) { // Fix up toolchain label invocations_.insert(LoadID(i->file, label)); } else { // Can keep the old one. invocations_.insert(*i); } } } else { record = found_toolchain->second; } DCHECK(!record->is_config_loaded); DCHECK(record->is_toolchain_loaded); record->is_config_loaded = true; // Schedule all waiting file loads. for (size_t i = 0; i < record->waiting_on_me.size(); i++) ScheduleLoadFile(&record->settings, record->waiting_on_me[i]); record->waiting_on_me.clear(); DecrementPendingLoads(); } void LoaderImpl::DecrementPendingLoads() { DCHECK(pending_loads_ > 0); pending_loads_--; if (pending_loads_ == 0 && !complete_callback_.is_null()) complete_callback_.Run(); } bool LoaderImpl::AsyncLoadFile( const LocationRange& origin, const BuildSettings* build_settings, const SourceFile& file_name, const base::Callback& callback, Err* err) { if (async_load_file_.is_null()) { return g_scheduler->input_file_manager()->AsyncLoadFile( origin, build_settings, file_name, callback, err); } return async_load_file_.Run( origin, build_settings, file_name, callback, err); }