blob: 4edf212218ca747c8b648d9eaf62cae3e6825482 [file] [log] [blame]
// 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 <set>
#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"
#include "tools/gn/trace.h"
// How toolchain loading works
// ---------------------------
// When we start loading a build, we'll load the build config file and that
// will call set_default_toolchain. We'll schedule a load of the file
// containing the default toolchain definition, and can do this in parallel
// with all other build files. Targets will have an implicit dependency on the
// toolchain so we won't write out any files until we've loaded the toolchain
// definition.
//
// When we see a reference to a target using a different toolchain, it gets
// more complicated. In this case, the toolchain definition contains arguments
// to pass into the build config file when it is invoked in the context of that
// toolchain. As a result, we have to actually see the definition of the
// toolchain before doing anything else.
//
// So when we see a reference to a non-default toolchain we do the following:
//
// 1. Schedule a load of the file containing the toolchain definition, if it
// isn't loaded already.
// 2. When the toolchain definition is loaded, we schedule a load of the
// build config file in the context of that toolchain. We'll use the
// arguments from the toolchain definition to execute it.
// 3. When the build config is set up, then we can load all of the individual
// buildfiles in the context of that config that we require.
namespace {
enum ToolchainState {
// Toolchain settings have not requested to be loaded. This means we
// haven't seen any targets that require this toolchain yet. This means that
// we have seen a toolchain definition, but no targets that use it. Not
// loading the settings automatically allows you to define a bunch of
// toolchains and potentially not use them without much overhead.
TOOLCHAIN_NOT_LOADED,
// The toolchain definition for non-default toolchains has been scheduled
// to be loaded but has not completed. When this is done, we can load the
// settings file. This is needed to get the arguments out of the toolchain
// definition. This is skipped for the default toolchain which has no
// arguments (see summary above).
TOOLCHAIN_DEFINITION_LOADING,
// The settings have been scheduled to be loaded but have not completed.
TOOLCHAIN_SETTINGS_LOADING,
// The settings are done being loaded.
TOOLCHAIN_SETTINGS_LOADED
};
SourceFile DirToBuildFile(const SourceDir& dir) {
return SourceFile(dir.value() + "BUILD.gn");
}
} // namespace
struct ToolchainManager::Info {
Info(const BuildSettings* build_settings,
const Label& toolchain_name,
const std::string& output_subdir_name)
: state(TOOLCHAIN_NOT_LOADED),
settings(build_settings, output_subdir_name),
toolchain(new Toolchain(&settings, toolchain_name)),
toolchain_set(false),
toolchain_file_loaded(false),
item_node(NULL) {
settings.set_toolchain_label(toolchain_name);
}
~Info() {
if (!item_node) // See toolchain definition for more.
delete toolchain;
}
// 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);
}
}
ToolchainState state;
// The first place in the build that we saw a reference for this toolchain.
// This allows us to report errors if it can't be loaded and blame some
// reasonable place of the code. This will be empty for the default toolchain.
LocationRange specified_from;
// 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;
// When we create an item node, this pointer will be owned by that node
// so it's lifetime is managed by the dependency graph. Before we've created
// the ItemNode, this class has to takre responsibility for this pointer.
Toolchain* toolchain;
bool toolchain_set;
LocationRange toolchain_definition_location;
// Set when the file corresponding to the toolchain definition is loaded.
// This will normally be set right after "toolchain_set". However, if the
// toolchain definition is missing, the file might be marked loaded but the
// toolchain definition could still be unset.
bool toolchain_file_loaded;
// While state == TOOLCHAIN_SETTINGS_LOADING || TOOLCHAIN_DEFINITION_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<SourceFile, LocationRange> 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<SourceFile> 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);
if (!ScheduleBuildConfigLoadLocked(info, true, &err))
g_scheduler->FailWithError(err);
}
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());
// Save the toolchain. We can just overwrite our definition.
*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);
switch (info->state) {
case TOOLCHAIN_NOT_LOADED:
// Toolchain needs to be loaded. Start loading it and push this buildfile
// on the wait queue. The actual toolchain build file will have been
// scheduled to be loaded by LoadNewToolchainLocked() above, so we just
// need to request that the build settings be loaded when that toolchain
// file is done executing (recall toolchain files are executed in the
// context of the default toolchain, which is why we need to do the extra
// Info lookup in the make_pair).
DCHECK(!default_toolchain_.is_null());
pending_build_config_map_[
std::make_pair(DirToBuildFile(toolchain_name.dir()),
toolchains_[default_toolchain_])] =
info;
info->scheduled_invocations[build_file] = specified_from;
// Transition to the loading state.
info->specified_from = specified_from;
info->state = TOOLCHAIN_DEFINITION_LOADING;
return true;
case TOOLCHAIN_DEFINITION_LOADING:
case TOOLCHAIN_SETTINGS_LOADING:
// Toolchain is in the process of loading, push this buildfile on the
// wait queue to run when the config is ready.
info->scheduled_invocations[build_file] = specified_from;
return true;
case TOOLCHAIN_SETTINGS_LOADED:
// Everything is ready, just schedule the build file to load.
return ScheduleBackgroundInvoke(info, specified_from, build_file, err);
default:
NOTREACHED();
return false;
}
}
// 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(&info->settings, default_toolchain_);
info->settings.set_is_default(true);
info->settings.set_toolchain_label(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);
}
bool ToolchainManager::ScheduleBackgroundInvoke(
Info* info,
const LocationRange& specified_from,
const SourceFile& build_file,
Err* err) {
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;
}
return true;
}
bool ToolchainManager::ScheduleBuildConfigLoadLocked(Info* info,
bool is_default,
Err* err) {
GetLock().AssertAcquired();
g_scheduler->IncrementWorkCount();
if (!g_scheduler->input_file_manager()->AsyncLoadFile(
info->specified_from, build_settings_,
build_settings_->build_config_file(),
base::Bind(&ToolchainManager::BackgroundLoadBuildConfig,
base::Unretained(this), info, is_default),
err)) {
g_scheduler->DecrementWorkCount();
return false;
}
info->state = TOOLCHAIN_SETTINGS_LOADING;
return true;
}
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();
base_config->set_source_dir(SourceDir("//"));
info->settings.build_settings()->build_args().SetupRootScope(
base_config, info->toolchain->args());
base_config->SetProcessingBuildConfig();
if (is_default)
base_config->SetProcessingDefaultBuildConfig();
ScopedTrace trace(TraceItem::TRACE_FILE_EXECUTE,
info->settings.build_settings()->build_config_file().value());
trace.SetToolchain(info->settings.toolchain_label());
const BlockNode* root_block = root->AsBlock();
Err err;
root_block->ExecuteBlockInScope(base_config, &err);
trace.Done();
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) {
if (!ScheduleBackgroundInvoke(info, i->second, i->first, &err)) {
g_scheduler->FailWithError(err);
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);
our_scope.set_source_dir(file_name.GetDir());
ScopedTrace trace(TraceItem::TRACE_FILE_EXECUTE, file_name.value());
trace.SetToolchain(info->settings.toolchain_label());
Err err;
root->Execute(&our_scope, &err);
if (err.has_error())
g_scheduler->FailWithError(err);
trace.Done();
{
// Check to see if any build config invocations depend on this file and
// invoke them.
base::AutoLock lock(GetLock());
BuildConfigInvokeMap::iterator found_file =
pending_build_config_map_.find(std::make_pair(file_name, info));
if (found_file != pending_build_config_map_.end()) {
// The toolchain state should be waiting on the definition, which
// should be the thing we just loaded.
Info* info_to_load = found_file->second;
DCHECK(info_to_load->state == TOOLCHAIN_DEFINITION_LOADING);
DCHECK(!info_to_load->toolchain_file_loaded);
info_to_load->toolchain_file_loaded = true;
if (!ScheduleBuildConfigLoadLocked(info_to_load, false, &err))
g_scheduler->FailWithError(err);
pending_build_config_map_.erase(found_file);
}
}
}
g_scheduler->DecrementWorkCount();
}
base::Lock& ToolchainManager::GetLock() const {
return build_settings_->item_tree().lock();
}