| // 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 "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(). | 
 |   task_runner_ = MsgLoop::Current(); | 
 | } | 
 |  | 
 | 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(); | 
 |     Label empty_label;  // VS issues spurious warning using ...[Label()]. | 
 |     toolchain_records_[empty_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( | 
 |         base::BindOnce(&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(base::BindOnce(&LoaderImpl::DidLoadFile, this)); | 
 | } | 
 |  | 
 | void LoaderImpl::BackgroundLoadBuildConfig( | 
 |     Settings* settings, | 
 |     const Scope::KeyValueMap& toolchain_overrides, | 
 |     const ParseNode* root) { | 
 |   if (!root) { | 
 |     task_runner_->PostTask( | 
 |         base::BindOnce(&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()); | 
 |  | 
 |   // Run the BUILDCONFIG with its directory as the current one. We want | 
 |   // BUILDCONFIG to modify the base_config so can't make a copy or a nested one. | 
 |   base_config->set_source_dir( | 
 |       settings->build_settings()->build_config_file().GetDir()); | 
 |  | 
 |   Err err; | 
 |   root->Execute(base_config, &err); | 
 |  | 
 |   // Put back the root as the default source dir. This probably isn't necessary | 
 |   // as other scopes will set their directories to their own path, but it's a | 
 |   // better default than the build config's directory. | 
 |   base_config->set_source_dir(SourceDir("//")); | 
 |  | 
 |   // 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(base::BindOnce(&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_.emplace(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); | 
 | } |