// 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/toolchain_manager.h" #include #include "base/bind.h" #include "build/build_config.h" #include "tools/gn/err.h" #include "tools/gn/item.h" #include "tools/gn/item_node.h" #include "tools/gn/item_tree.h" #include "tools/gn/parse_tree.h" #include "tools/gn/scheduler.h" #include "tools/gn/scope.h" #include "tools/gn/scope_per_file_provider.h" namespace { SourceFile DirToBuildFile(const SourceDir& dir) { return SourceFile(dir.value() + "BUILD.gn"); } void SetSystemVars(const Settings& settings, Scope* scope) { #if defined(OS_WIN) scope->SetValue("is_win", Value(NULL, 1), NULL); scope->SetValue("is_posix", Value(NULL, 0), NULL); #else scope->SetValue("is_win", Value(NULL, 0), NULL); scope->SetValue("is_posix", Value(NULL, 1), NULL); #endif #if defined(OS_MACOSX) scope->SetValue("is_mac", Value(NULL, 1), NULL); #else scope->SetValue("is_mac", Value(NULL, 0), NULL); #endif #if defined(OS_LINUX) scope->SetValue("is_linux", Value(NULL, 1), NULL); #else scope->SetValue("is_linux", Value(NULL, 0), NULL); #endif } } // namespace struct ToolchainManager::Info { Info(const BuildSettings* build_settings, const Label& toolchain_name, const std::string& output_subdir_name) : state(TOOLCHAIN_SETTINGS_NOT_LOADED), toolchain(toolchain_name), toolchain_set(false), settings(build_settings, &toolchain, output_subdir_name), item_node(NULL) { } // Makes sure that an ItemNode is created for the toolchain, which lets // targets depend on the (potentially future) loading of the toolchain. // // We can't always do this at the beginning since when doing the default // build config, we don't know the toolchain name yet. We also need to go // through some effort to avoid doing this inside the toolchain manager's // lock (to avoid holding two locks at once). void EnsureItemNode() { if (!item_node) { ItemTree& tree = settings.build_settings()->item_tree(); item_node = new ItemNode(&toolchain); tree.AddNodeLocked(item_node); } } SettingsState state; Toolchain toolchain; bool toolchain_set; LocationRange toolchain_definition_location; // When the state is TOOLCHAIN_SETTINGS_LOADED, the settings should be // considered read-only and can be read without locking. Otherwise, they // should not be accessed at all except to load them (which can therefore // also be done outside of the lock). This works as long as the state flag // is only ever read or written inside the lock. Settings settings; // While state == TOOLCHAIN_SETTINGS_LOADING, this will collect all // scheduled invocations using this toolchain. They'll be issued once the // settings file has been interpreted. // // The map maps the source file to "some" location it was invoked from (so // we can give good error messages. It does NOT map to the root of the // file to be invoked (the file still needs loading). This will be NULL // for internally invoked files. typedef std::map ScheduledInvocationMap; ScheduledInvocationMap scheduled_invocations; // Tracks all scheduled and executed invocations for this toolchain. This // is used to avoid invoking a file more than once for a toolchain. std::set all_invocations; // Filled in by EnsureItemNode, see that for more. ItemNode* item_node; }; ToolchainManager::ToolchainManager(const BuildSettings* build_settings) : build_settings_(build_settings) { } ToolchainManager::~ToolchainManager() { for (ToolchainMap::iterator i = toolchains_.begin(); i != toolchains_.end(); ++i) delete i->second; toolchains_.clear(); } void ToolchainManager::StartLoadingUnlocked(const SourceFile& build_file_name) { // How the default build config works: Initially we don't have a toolchain // name to call the settings for the default build config. So we create one // with an empty toolchain name and execute the default build config file. // When that's done, we'll go and fix up the name to the default build config // that the script set. base::AutoLock lock(GetLock()); Err err; Info* info = LoadNewToolchainLocked(LocationRange(), Label(), &err); if (err.has_error()) g_scheduler->FailWithError(err); CHECK(info); info->scheduled_invocations[build_file_name] = LocationRange(); info->all_invocations.insert(build_file_name); g_scheduler->IncrementWorkCount(); if (!g_scheduler->input_file_manager()->AsyncLoadFile( LocationRange(), build_settings_, build_settings_->build_config_file(), base::Bind(&ToolchainManager::BackgroundLoadBuildConfig, base::Unretained(this), info, true), &err)) { g_scheduler->FailWithError(err); g_scheduler->DecrementWorkCount(); } } const Settings* ToolchainManager::GetSettingsForToolchainLocked( const LocationRange& from_here, const Label& toolchain_name, Err* err) { GetLock().AssertAcquired(); ToolchainMap::iterator found = toolchains_.find(toolchain_name); Info* info = NULL; if (found == toolchains_.end()) { info = LoadNewToolchainLocked(from_here, toolchain_name, err); if (!info) return NULL; } else { info = found->second; } info->EnsureItemNode(); return &info->settings; } const Toolchain* ToolchainManager::GetToolchainDefinitionUnlocked( const Label& toolchain_name) { base::AutoLock lock(GetLock()); ToolchainMap::iterator found = toolchains_.find(toolchain_name); if (found == toolchains_.end() || !found->second->toolchain_set) return NULL; // Since we don't allow defining a toolchain more than once, we know that // once it's set it won't be mutated, so we can safely return this pointer // for reading outside the lock. return &found->second->toolchain; } bool ToolchainManager::SetDefaultToolchainUnlocked( const Label& default_toolchain, const LocationRange& defined_here, Err* err) { base::AutoLock lock(GetLock()); if (!default_toolchain_.is_null()) { *err = Err(defined_here, "Default toolchain already set."); err->AppendSubErr(Err(default_toolchain_defined_here_, "Previously defined here.", "You can only set this once.")); return false; } if (default_toolchain.is_null()) { *err = Err(defined_here, "Bad default toolchain name.", "You can't set the default toolchain name to nothing."); return false; } if (!default_toolchain.toolchain_dir().is_null() || !default_toolchain.toolchain_name().empty()) { *err = Err(defined_here, "Toolchain name has toolchain.", "You can't specify a toolchain (inside the parens) for a toolchain " "name. I got:\n" + default_toolchain.GetUserVisibleName(true)); return false; } default_toolchain_ = default_toolchain; default_toolchain_defined_here_ = defined_here; return true; } Label ToolchainManager::GetDefaultToolchainUnlocked() const { base::AutoLock lock(GetLock()); return default_toolchain_; } bool ToolchainManager::SetToolchainDefinitionLocked( const Toolchain& tc, const LocationRange& defined_from, Err* err) { GetLock().AssertAcquired(); ToolchainMap::iterator found = toolchains_.find(tc.label()); Info* info = NULL; if (found == toolchains_.end()) { // New toolchain. info = LoadNewToolchainLocked(defined_from, tc.label(), err); if (!info) return false; } else { // It's important to preserve the exact Toolchain object in our tree since // it will be in the ItemTree and targets may have dependencies on it. info = found->second; } // The labels should match or else we're setting the wrong one! CHECK(info->toolchain.label() == tc.label()); info->toolchain = tc; if (info->toolchain_set) { *err = Err(defined_from, "Duplicate toolchain definition."); err->AppendSubErr(Err( info->toolchain_definition_location, "Previously defined here.", "A toolchain can only be defined once. One tricky way that this could\n" "happen is if your definition is itself in a file that's interpreted\n" "under different toolchains, which would result in multiple\n" "definitions as the file is loaded multiple times. So be sure your\n" "toolchain definitions are in files that either don't define any\n" "targets (probably best) or at least don't contain targets executed\n" "with more than one toolchain.")); return false; } info->EnsureItemNode(); info->toolchain_set = true; info->toolchain_definition_location = defined_from; return true; } bool ToolchainManager::ScheduleInvocationLocked( const LocationRange& specified_from, const Label& toolchain_name, const SourceDir& dir, Err* err) { GetLock().AssertAcquired(); SourceFile build_file(DirToBuildFile(dir)); // If there's no specified toolchain name, use the default. ToolchainMap::iterator found; if (toolchain_name.is_null()) found = toolchains_.find(default_toolchain_); else found = toolchains_.find(toolchain_name); Info* info = NULL; if (found == toolchains_.end()) { // New toolchain. info = LoadNewToolchainLocked(specified_from, toolchain_name, err); if (!info) return false; } else { // Use existing one. info = found->second; if (info->all_invocations.find(build_file) != info->all_invocations.end()) { // We've already seen this source file for this toolchain, don't need // to do anything. return true; } } info->all_invocations.insert(build_file); // True if the settings load needs to be scheduled. bool info_needs_settings_load = false; // True if the settings load has completed. bool info_settings_loaded = false; switch (info->state) { case TOOLCHAIN_SETTINGS_NOT_LOADED: info_needs_settings_load = true; info->scheduled_invocations[build_file] = specified_from; break; case TOOLCHAIN_SETTINGS_LOADING: info->scheduled_invocations[build_file] = specified_from; break; case TOOLCHAIN_SETTINGS_LOADED: info_settings_loaded = true; break; } if (info_needs_settings_load) { // Load the settings file. g_scheduler->IncrementWorkCount(); if (!g_scheduler->input_file_manager()->AsyncLoadFile( specified_from, build_settings_, build_settings_->build_config_file(), base::Bind(&ToolchainManager::BackgroundLoadBuildConfig, base::Unretained(this), info, false), err)) { g_scheduler->DecrementWorkCount(); return false; } } else if (info_settings_loaded) { // Settings are ready to go, load the target file. g_scheduler->IncrementWorkCount(); if (!g_scheduler->input_file_manager()->AsyncLoadFile( specified_from, build_settings_, build_file, base::Bind(&ToolchainManager::BackgroundInvoke, base::Unretained(this), info, build_file), err)) { g_scheduler->DecrementWorkCount(); return false; } } // Else we should have added the infocations to the scheduled_invocations // from within the lock above. return true; } // static std::string ToolchainManager::ToolchainToOutputSubdir( const Label& toolchain_name) { // 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_name.name(); } ToolchainManager::Info* ToolchainManager::LoadNewToolchainLocked( const LocationRange& specified_from, const Label& toolchain_name, Err* err) { GetLock().AssertAcquired(); Info* info = new Info(build_settings_, toolchain_name, ToolchainToOutputSubdir(toolchain_name)); toolchains_[toolchain_name] = info; // Invoke the file containing the toolchain definition so that it gets // defined. The default one (label is empty) will be done spearately. if (!toolchain_name.is_null()) { // The default toolchain should be specified whenever we're requesting // another one. This is how we know under what context we should execute // the invoke for the toolchain file. CHECK(!default_toolchain_.is_null()); ScheduleInvocationLocked(specified_from, default_toolchain_, toolchain_name.dir(), err); } return info; } void ToolchainManager::FixupDefaultToolchainLocked() { // Now that we've run the default build config, we should know the // default toolchain name. Fix up our reference. // See Start() for more. GetLock().AssertAcquired(); if (default_toolchain_.is_null()) { g_scheduler->FailWithError(Err(Location(), "Default toolchain not set.", "Your build config file \"" + build_settings_->build_config_file().value() + "\"\ndid not call set_default_toolchain(). This is needed so " "I know how to actually\ncompile your code.")); return; } ToolchainMap::iterator old_default = toolchains_.find(Label()); CHECK(old_default != toolchains_.end()); Info* info = old_default->second; toolchains_[default_toolchain_] = info; toolchains_.erase(old_default); // Toolchain should not have been loaded in the build config file. CHECK(!info->toolchain_set); // We need to set the toolchain label now that we know it. There's no way // to set the label, but we can assign the toolchain to a new one. Loading // the build config can not change the toolchain, so we won't be overwriting // anything useful. info->toolchain = Toolchain(default_toolchain_); info->EnsureItemNode(); // The default toolchain is loaded in greedy mode so all targets we // encounter are generated. Non-default toolchain settings stay in non-greedy // so we only generate the minimally required set. info->settings.set_greedy_target_generation(true); // Schedule a load of the toolchain build file. Err err; ScheduleInvocationLocked(LocationRange(), default_toolchain_, default_toolchain_.dir(), &err); if (err.has_error()) g_scheduler->FailWithError(err); } void ToolchainManager::BackgroundLoadBuildConfig(Info* info, bool is_default, const ParseNode* root) { // Danger: No early returns without decrementing the work count. if (root && !g_scheduler->is_failed()) { // Nobody should be accessing settings at this point other than us since we // haven't marked it loaded, so we can do it outside the lock. Scope* base_config = info->settings.base_config(); SetSystemVars(info->settings, base_config); base_config->SetProcessingBuildConfig(); if (is_default) base_config->SetProcessingDefaultBuildConfig(); const BlockNode* root_block = root->AsBlock(); Err err; root_block->ExecuteBlockInScope(base_config, &err); base_config->ClearProcessingBuildConfig(); if (is_default) base_config->ClearProcessingDefaultBuildConfig(); if (err.has_error()) { g_scheduler->FailWithError(err); } else { // Base config processing succeeded. Info::ScheduledInvocationMap schedule_these; { base::AutoLock lock(GetLock()); schedule_these.swap(info->scheduled_invocations); info->state = TOOLCHAIN_SETTINGS_LOADED; if (is_default) FixupDefaultToolchainLocked(); } // Schedule build files waiting on this settings. There can be many so we // want to load them in parallel on the pool. for (Info::ScheduledInvocationMap::iterator i = schedule_these.begin(); i != schedule_these.end() && !g_scheduler->is_failed(); ++i) { // Note i->second may be NULL, so don't dereference. g_scheduler->IncrementWorkCount(); if (!g_scheduler->input_file_manager()->AsyncLoadFile( i->second, build_settings_, i->first, base::Bind(&ToolchainManager::BackgroundInvoke, base::Unretained(this), info, i->first), &err)) { g_scheduler->FailWithError(err); g_scheduler->DecrementWorkCount(); break; } } } } g_scheduler->DecrementWorkCount(); } void ToolchainManager::BackgroundInvoke(const Info* info, const SourceFile& file_name, const ParseNode* root) { if (root && !g_scheduler->is_failed()) { if (g_scheduler->verbose_logging()) { g_scheduler->Log("Running", file_name.value() + " with toolchain " + info->toolchain.label().GetUserVisibleName(false)); } Scope our_scope(info->settings.base_config()); ScopePerFileProvider per_file_provider(&our_scope, file_name); Err err; root->Execute(&our_scope, &err); if (err.has_error()) g_scheduler->FailWithError(err); } g_scheduler->DecrementWorkCount(); } base::Lock& ToolchainManager::GetLock() const { return build_settings_->item_tree().lock(); }