|  | // 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 <memory> | 
|  |  | 
|  | #include "base/bind.h" | 
|  | #include "base/threading/thread_task_runner_handle.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 { | 
|  |  | 
|  | struct SourceFileAndOrigin { | 
|  | SourceFileAndOrigin(const SourceFile& f, const LocationRange& o) | 
|  | : file(f), origin(o) {} | 
|  |  | 
|  | SourceFile file; | 
|  | LocationRange origin; | 
|  | }; | 
|  |  | 
|  | }  // 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() = default; | 
|  | 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<SourceFileAndOrigin> waiting_on_me; | 
|  | }; | 
|  |  | 
|  | // ----------------------------------------------------------------------------- | 
|  |  | 
|  | const void* const Loader::kDefaultToolchainKey = &kDefaultToolchainKey; | 
|  |  | 
|  | Loader::Loader() = default; | 
|  |  | 
|  | Loader::~Loader() = default; | 
|  |  | 
|  | void Loader::Load(const Label& label, const LocationRange& origin) { | 
|  | Load(BuildFileForLabel(label), origin, label.GetToolchainLabel()); | 
|  | } | 
|  |  | 
|  | // static | 
|  | SourceFile Loader::BuildFileForLabel(const Label& label) { | 
|  | return SourceFile(label.dir().value() + "BUILD.gn"); | 
|  | } | 
|  |  | 
|  | // ----------------------------------------------------------------------------- | 
|  |  | 
|  | LoaderImpl::LoaderImpl(const BuildSettings* build_settings) | 
|  | : pending_loads_(0), build_settings_(build_settings) { | 
|  | // There may not be an active TaskRunner at this point. When that's the case, | 
|  | // the calling code is expected to call set_task_runner(). | 
|  | if (base::ThreadTaskRunnerHandle::IsSet()) | 
|  | task_runner_ = base::ThreadTaskRunnerHandle::Get(); | 
|  | } | 
|  |  | 
|  | LoaderImpl::~LoaderImpl() = default; | 
|  |  | 
|  | void LoaderImpl::Load(const SourceFile& file, | 
|  | const LocationRange& origin, | 
|  | 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 initial load | 
|  | // should not specify a toolchain. | 
|  | DCHECK(toolchain_name.is_null()); | 
|  |  | 
|  | std::unique_ptr<ToolchainRecord> new_record = | 
|  | std::make_unique<ToolchainRecord>(build_settings_, Label(), Label()); | 
|  | ToolchainRecord* record = new_record.get(); | 
|  | toolchain_records_[Label()] = std::move(new_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(SourceFileAndOrigin(file, origin)); | 
|  | ScheduleLoadBuildConfig(&record->settings, Scope::KeyValueMap()); | 
|  |  | 
|  | return; | 
|  | } | 
|  |  | 
|  | ToolchainRecord* record; | 
|  | if (toolchain_name.is_null()) | 
|  | record = toolchain_records_[default_toolchain_label_].get(); | 
|  | else | 
|  | record = toolchain_records_[toolchain_name].get(); | 
|  |  | 
|  | if (!record) { | 
|  | DCHECK(!default_toolchain_label_.is_null()); | 
|  |  | 
|  | // No reference to this toolchain found yet, make one. | 
|  | std::unique_ptr<ToolchainRecord> new_record = | 
|  | std::make_unique<ToolchainRecord>(build_settings_, toolchain_name, | 
|  | default_toolchain_label_); | 
|  | record = new_record.get(); | 
|  | toolchain_records_[toolchain_name] = std::move(new_record); | 
|  |  | 
|  | // Schedule a load of the toolchain using the default one. | 
|  | Load(BuildFileForLabel(toolchain_name), origin, default_toolchain_label_); | 
|  | } | 
|  |  | 
|  | if (record->is_config_loaded) | 
|  | ScheduleLoadFile(&record->settings, origin, file); | 
|  | else | 
|  | record->waiting_on_me.push_back(SourceFileAndOrigin(file, origin)); | 
|  | } | 
|  |  | 
|  | void LoaderImpl::ToolchainLoaded(const Toolchain* toolchain) { | 
|  | ToolchainRecord* record = toolchain_records_[toolchain->label()].get(); | 
|  | if (!record) { | 
|  | DCHECK(!default_toolchain_label_.is_null()); | 
|  | std::unique_ptr<ToolchainRecord> new_record = | 
|  | std::make_unique<ToolchainRecord>(build_settings_, toolchain->label(), | 
|  | default_toolchain_label_); | 
|  | record = new_record.get(); | 
|  | toolchain_records_[toolchain->label()] = std::move(new_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 nullptr; | 
|  | found_toolchain = toolchain_records_.find(default_toolchain_label_); | 
|  | } else { | 
|  | found_toolchain = toolchain_records_.find(label); | 
|  | } | 
|  |  | 
|  | if (found_toolchain == toolchain_records_.end()) | 
|  | return nullptr; | 
|  | return &found_toolchain->second->settings; | 
|  | } | 
|  |  | 
|  | void LoaderImpl::ScheduleLoadFile(const Settings* settings, | 
|  | const LocationRange& origin, | 
|  | const SourceFile& file) { | 
|  | Err err; | 
|  | pending_loads_++; | 
|  | if (!AsyncLoadFile(origin, settings->build_settings(), file, | 
|  | base::Bind(&LoaderImpl::BackgroundLoadFile, this, | 
|  | settings, file, origin), | 
|  | &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 LocationRange& origin, | 
|  | const ParseNode* root) { | 
|  | if (!root) { | 
|  | task_runner_->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, true); | 
|  | our_scope.set_source_dir(file_name.GetDir()); | 
|  | our_scope.AddBuildDependencyFile(file_name); | 
|  |  | 
|  | // Targets, etc. generated as part of running this file will end up here. | 
|  | Scope::ItemVector collected_items; | 
|  | our_scope.set_item_collector(&collected_items); | 
|  |  | 
|  | 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()) | 
|  | our_scope.CheckForUnusedVars(&err); | 
|  |  | 
|  | if (err.has_error()) { | 
|  | if (!origin.is_null()) | 
|  | err.AppendSubErr(Err(origin, "which caused the file to be included.")); | 
|  | g_scheduler->FailWithError(err); | 
|  | } | 
|  |  | 
|  |  | 
|  | // Pass all of the items that were defined off to the builder. | 
|  | for (auto& item : collected_items) | 
|  | settings->build_settings()->ItemDefined(std::move(item)); | 
|  |  | 
|  | trace.Done(); | 
|  |  | 
|  | task_runner_->PostTask(FROM_HERE, base::Bind(&LoaderImpl::DidLoadFile, this)); | 
|  | } | 
|  |  | 
|  | void LoaderImpl::BackgroundLoadBuildConfig( | 
|  | Settings* settings, | 
|  | const Scope::KeyValueMap& toolchain_overrides, | 
|  | const ParseNode* root) { | 
|  | if (!root) { | 
|  | task_runner_->PostTask( | 
|  | FROM_HERE, base::Bind(&LoaderImpl::DecrementPendingLoads, this)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | Scope* base_config = settings->base_config(); | 
|  | base_config->set_source_dir(SourceDir("//")); | 
|  | base_config->AddBuildDependencyFile( | 
|  | settings->build_settings()->build_config_file()); | 
|  |  | 
|  | 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()); | 
|  |  | 
|  | Err err; | 
|  | root->Execute(base_config, &err); | 
|  |  | 
|  | // Clear all private variables left in the scope. We want the root build | 
|  | // config to be like a .gni file in that variables beginning with an | 
|  | // underscore aren't exported. | 
|  | base_config->RemovePrivateIdentifiers(); | 
|  |  | 
|  | 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); | 
|  | } | 
|  | } | 
|  |  | 
|  | task_runner_->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 = nullptr; | 
|  | 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_EQ(1U, toolchain_records_.size()); | 
|  | ToolchainRecordMap::iterator empty_label = toolchain_records_.find(Label()); | 
|  | CHECK(empty_label != toolchain_records_.end()); | 
|  |  | 
|  | // Fix up the toolchain record. | 
|  | std::unique_ptr<ToolchainRecord> moved_record = | 
|  | std::move(empty_label->second); | 
|  | record = moved_record.get(); | 
|  | toolchain_records_[label] = std::move(moved_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 (const auto& load : old_loads) { | 
|  | if (load.toolchain_name.is_null()) { | 
|  | // Fix up toolchain label | 
|  | invocations_.insert(LoadID(load.file, label)); | 
|  | } else { | 
|  | // Can keep the old one. | 
|  | invocations_.insert(load); | 
|  | } | 
|  | } | 
|  | } else { | 
|  | record = found_toolchain->second.get(); | 
|  | } | 
|  |  | 
|  | DCHECK(!record->is_config_loaded); | 
|  | DCHECK(record->is_toolchain_loaded); | 
|  | record->is_config_loaded = true; | 
|  |  | 
|  | // Schedule all waiting file loads. | 
|  | for (const auto& waiting : record->waiting_on_me) | 
|  | ScheduleLoadFile(&record->settings, waiting.origin, waiting.file); | 
|  | record->waiting_on_me.clear(); | 
|  |  | 
|  | DecrementPendingLoads(); | 
|  | } | 
|  |  | 
|  | void LoaderImpl::DecrementPendingLoads() { | 
|  | DCHECK_GT(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<void(const ParseNode*)>& 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); | 
|  | } |