| // 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 "gn/loader.h" |
| |
| #include <memory> |
| |
| #include "gn/build_settings.h" |
| #include "gn/err.h" |
| #include "gn/filesystem_utils.h" |
| #include "gn/input_file_manager.h" |
| #include "gn/parse_tree.h" |
| #include "gn/scheduler.h" |
| #include "gn/scope_per_file_provider.h" |
| #include "gn/settings.h" |
| #include "gn/source_dir.h" |
| #include "gn/source_file.h" |
| #include "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()); |
| } |
| |
| // ----------------------------------------------------------------------------- |
| |
| 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; |
| } |
| |
| SourceFile LoaderImpl::BuildFileForLabel(const Label& label) const { |
| return SourceFile( |
| label.dir().value() + "BUILD" + build_file_extension_ + ".gn"); |
| } |
| |
| void LoaderImpl::ScheduleLoadFile(const Settings* settings, |
| const LocationRange& origin, |
| const SourceFile& file) { |
| Err err; |
| pending_loads_++; |
| if (!AsyncLoadFile( |
| origin, settings->build_settings(), file, |
| [this, settings, file, origin](const ParseNode* parse_node) { |
| BackgroundLoadFile(settings, file, origin, parse_node); |
| }, |
| &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(), |
| [this, settings, toolchain_overrides](const ParseNode* root) { |
| BackgroundLoadBuildConfig(settings, toolchain_overrides, root); |
| }, |
| &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([this]() { DecrementPendingLoads(); }); |
| 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.")); |
| |
| if (!settings->is_default()) |
| err.set_toolchain_label(settings->toolchain_label()); |
| |
| 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([this]() { DidLoadFile(); }); |
| } |
| |
| void LoaderImpl::BackgroundLoadBuildConfig( |
| Settings* settings, |
| const Scope::KeyValueMap& toolchain_overrides, |
| const ParseNode* root) { |
| if (!root) { |
| task_runner_->PostTask([this]() { DecrementPendingLoads(); }); |
| 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()) { |
| if (!settings->is_default()) |
| err.set_toolchain_label(settings->toolchain_label()); |
| |
| 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( |
| [this, toolchain_label = settings->toolchain_label()]() { |
| DidLoadBuildConfig(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_) |
| complete_callback_(); |
| } |
| |
| bool LoaderImpl::AsyncLoadFile(const LocationRange& origin, |
| const BuildSettings* build_settings, |
| const SourceFile& file_name, |
| std::function<void(const ParseNode*)> callback, |
| Err* err) { |
| if (async_load_file_) { |
| return async_load_file_(origin, build_settings, file_name, |
| std::move(callback), err); |
| } |
| return g_scheduler->input_file_manager()->AsyncLoadFile( |
| origin, build_settings, file_name, std::move(callback), err); |
| } |