Home | History | Annotate | Download | only in gn
      1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "tools/gn/toolchain_manager.h"
      6 
      7 #include <set>
      8 
      9 #include "base/bind.h"
     10 #include "build/build_config.h"
     11 #include "tools/gn/err.h"
     12 #include "tools/gn/item.h"
     13 #include "tools/gn/item_node.h"
     14 #include "tools/gn/item_tree.h"
     15 #include "tools/gn/parse_tree.h"
     16 #include "tools/gn/scheduler.h"
     17 #include "tools/gn/scope.h"
     18 #include "tools/gn/scope_per_file_provider.h"
     19 
     20 namespace {
     21 
     22 SourceFile DirToBuildFile(const SourceDir& dir) {
     23   return SourceFile(dir.value() + "BUILD.gn");
     24 }
     25 
     26 void SetSystemVars(const Settings& settings, Scope* scope) {
     27 #if defined(OS_WIN)
     28   scope->SetValue("is_win", Value(NULL, 1), NULL);
     29   scope->SetValue("is_posix", Value(NULL, 0), NULL);
     30 #else
     31   scope->SetValue("is_win", Value(NULL, 0), NULL);
     32   scope->SetValue("is_posix", Value(NULL, 1), NULL);
     33 #endif
     34 
     35 #if defined(OS_MACOSX)
     36   scope->SetValue("is_mac", Value(NULL, 1), NULL);
     37 #else
     38   scope->SetValue("is_mac", Value(NULL, 0), NULL);
     39 #endif
     40 
     41 #if defined(OS_LINUX)
     42   scope->SetValue("is_linux", Value(NULL, 1), NULL);
     43 #else
     44   scope->SetValue("is_linux", Value(NULL, 0), NULL);
     45 #endif
     46 }
     47 
     48 }  // namespace
     49 
     50 struct ToolchainManager::Info {
     51   Info(const BuildSettings* build_settings,
     52        const Label& toolchain_name,
     53        const std::string& output_subdir_name)
     54       : state(TOOLCHAIN_SETTINGS_NOT_LOADED),
     55         toolchain(toolchain_name),
     56         toolchain_set(false),
     57         settings(build_settings, &toolchain, output_subdir_name),
     58         item_node(NULL) {
     59   }
     60 
     61   // Makes sure that an ItemNode is created for the toolchain, which lets
     62   // targets depend on the (potentially future) loading of the toolchain.
     63   //
     64   // We can't always do this at the beginning since when doing the default
     65   // build config, we don't know the toolchain name yet. We also need to go
     66   // through some effort to avoid doing this inside the toolchain manager's
     67   // lock (to avoid holding two locks at once).
     68   void EnsureItemNode() {
     69     if (!item_node) {
     70       ItemTree& tree = settings.build_settings()->item_tree();
     71       item_node = new ItemNode(&toolchain);
     72       tree.AddNodeLocked(item_node);
     73     }
     74   }
     75 
     76   SettingsState state;
     77 
     78   Toolchain toolchain;
     79   bool toolchain_set;
     80   LocationRange toolchain_definition_location;
     81 
     82   // When the state is TOOLCHAIN_SETTINGS_LOADED, the settings should be
     83   // considered read-only and can be read without locking. Otherwise, they
     84   // should not be accessed at all except to load them (which can therefore
     85   // also be done outside of the lock). This works as long as the state flag
     86   // is only ever read or written inside the lock.
     87   Settings settings;
     88 
     89   // While state == TOOLCHAIN_SETTINGS_LOADING, this will collect all
     90   // scheduled invocations using this toolchain. They'll be issued once the
     91   // settings file has been interpreted.
     92   //
     93   // The map maps the source file to "some" location it was invoked from (so
     94   // we can give good error messages. It does NOT map to the root of the
     95   // file to be invoked (the file still needs loading). This will be NULL
     96   // for internally invoked files.
     97   typedef std::map<SourceFile, LocationRange> ScheduledInvocationMap;
     98   ScheduledInvocationMap scheduled_invocations;
     99 
    100   // Tracks all scheduled and executed invocations for this toolchain. This
    101   // is used to avoid invoking a file more than once for a toolchain.
    102   std::set<SourceFile> all_invocations;
    103 
    104   // Filled in by EnsureItemNode, see that for more.
    105   ItemNode* item_node;
    106 };
    107 
    108 ToolchainManager::ToolchainManager(const BuildSettings* build_settings)
    109     : build_settings_(build_settings) {
    110 }
    111 
    112 ToolchainManager::~ToolchainManager() {
    113   for (ToolchainMap::iterator i = toolchains_.begin();
    114        i != toolchains_.end(); ++i)
    115     delete i->second;
    116   toolchains_.clear();
    117 }
    118 
    119 void ToolchainManager::StartLoadingUnlocked(const SourceFile& build_file_name) {
    120   // How the default build config works: Initially we don't have a toolchain
    121   // name to call the settings for the default build config. So we create one
    122   // with an empty toolchain name and execute the default build config file.
    123   // When that's done, we'll go and fix up the name to the default build config
    124   // that the script set.
    125   base::AutoLock lock(GetLock());
    126   Err err;
    127   Info* info = LoadNewToolchainLocked(LocationRange(), Label(), &err);
    128   if (err.has_error())
    129     g_scheduler->FailWithError(err);
    130   CHECK(info);
    131   info->scheduled_invocations[build_file_name] = LocationRange();
    132   info->all_invocations.insert(build_file_name);
    133 
    134   g_scheduler->IncrementWorkCount();
    135   if (!g_scheduler->input_file_manager()->AsyncLoadFile(
    136            LocationRange(), build_settings_,
    137            build_settings_->build_config_file(),
    138            base::Bind(&ToolchainManager::BackgroundLoadBuildConfig,
    139                       base::Unretained(this), info, true),
    140            &err)) {
    141     g_scheduler->FailWithError(err);
    142     g_scheduler->DecrementWorkCount();
    143   }
    144 }
    145 
    146 const Settings* ToolchainManager::GetSettingsForToolchainLocked(
    147     const LocationRange& from_here,
    148     const Label& toolchain_name,
    149     Err* err) {
    150   GetLock().AssertAcquired();
    151   ToolchainMap::iterator found = toolchains_.find(toolchain_name);
    152   Info* info = NULL;
    153   if (found == toolchains_.end()) {
    154     info = LoadNewToolchainLocked(from_here, toolchain_name, err);
    155     if (!info)
    156       return NULL;
    157   } else {
    158     info = found->second;
    159   }
    160   info->EnsureItemNode();
    161 
    162   return &info->settings;
    163 }
    164 
    165 const Toolchain* ToolchainManager::GetToolchainDefinitionUnlocked(
    166     const Label& toolchain_name) {
    167   base::AutoLock lock(GetLock());
    168   ToolchainMap::iterator found = toolchains_.find(toolchain_name);
    169   if (found == toolchains_.end() || !found->second->toolchain_set)
    170     return NULL;
    171 
    172   // Since we don't allow defining a toolchain more than once, we know that
    173   // once it's set it won't be mutated, so we can safely return this pointer
    174   // for reading outside the lock.
    175   return &found->second->toolchain;
    176 }
    177 
    178 bool ToolchainManager::SetDefaultToolchainUnlocked(
    179     const Label& default_toolchain,
    180     const LocationRange& defined_here,
    181     Err* err) {
    182   base::AutoLock lock(GetLock());
    183   if (!default_toolchain_.is_null()) {
    184     *err = Err(defined_here, "Default toolchain already set.");
    185     err->AppendSubErr(Err(default_toolchain_defined_here_,
    186                           "Previously defined here.",
    187                           "You can only set this once."));
    188     return false;
    189   }
    190 
    191   if (default_toolchain.is_null()) {
    192     *err = Err(defined_here, "Bad default toolchain name.",
    193         "You can't set the default toolchain name to nothing.");
    194     return false;
    195   }
    196   if (!default_toolchain.toolchain_dir().is_null() ||
    197       !default_toolchain.toolchain_name().empty()) {
    198     *err = Err(defined_here, "Toolchain name has toolchain.",
    199         "You can't specify a toolchain (inside the parens) for a toolchain "
    200         "name. I got:\n" + default_toolchain.GetUserVisibleName(true));
    201     return false;
    202   }
    203 
    204   default_toolchain_ = default_toolchain;
    205   default_toolchain_defined_here_ = defined_here;
    206   return true;
    207 }
    208 
    209 Label ToolchainManager::GetDefaultToolchainUnlocked() const {
    210   base::AutoLock lock(GetLock());
    211   return default_toolchain_;
    212 }
    213 
    214 bool ToolchainManager::SetToolchainDefinitionLocked(
    215     const Toolchain& tc,
    216     const LocationRange& defined_from,
    217     Err* err) {
    218   GetLock().AssertAcquired();
    219 
    220   ToolchainMap::iterator found = toolchains_.find(tc.label());
    221   Info* info = NULL;
    222   if (found == toolchains_.end()) {
    223     // New toolchain.
    224     info = LoadNewToolchainLocked(defined_from, tc.label(), err);
    225     if (!info)
    226       return false;
    227   } else {
    228     // It's important to preserve the exact Toolchain object in our tree since
    229     // it will be in the ItemTree and targets may have dependencies on it.
    230     info = found->second;
    231   }
    232 
    233   // The labels should match or else we're setting the wrong one!
    234   CHECK(info->toolchain.label() == tc.label());
    235 
    236   info->toolchain = tc;
    237   if (info->toolchain_set) {
    238     *err = Err(defined_from, "Duplicate toolchain definition.");
    239     err->AppendSubErr(Err(
    240         info->toolchain_definition_location,
    241         "Previously defined here.",
    242         "A toolchain can only be defined once. One tricky way that this could\n"
    243         "happen is if your definition is itself in a file that's interpreted\n"
    244         "under different toolchains, which would result in multiple\n"
    245         "definitions as the file is loaded multiple times. So be sure your\n"
    246         "toolchain definitions are in files that either don't define any\n"
    247         "targets (probably best) or at least don't contain targets executed\n"
    248         "with more than one toolchain."));
    249     return false;
    250   }
    251 
    252   info->EnsureItemNode();
    253 
    254   info->toolchain_set = true;
    255   info->toolchain_definition_location = defined_from;
    256   return true;
    257 }
    258 
    259 bool ToolchainManager::ScheduleInvocationLocked(
    260     const LocationRange& specified_from,
    261     const Label& toolchain_name,
    262     const SourceDir& dir,
    263     Err* err) {
    264   GetLock().AssertAcquired();
    265   SourceFile build_file(DirToBuildFile(dir));
    266 
    267   // If there's no specified toolchain name, use the default.
    268   ToolchainMap::iterator found;
    269   if (toolchain_name.is_null())
    270     found = toolchains_.find(default_toolchain_);
    271   else
    272     found = toolchains_.find(toolchain_name);
    273 
    274   Info* info = NULL;
    275   if (found == toolchains_.end()) {
    276     // New toolchain.
    277     info = LoadNewToolchainLocked(specified_from, toolchain_name, err);
    278     if (!info)
    279       return false;
    280   } else {
    281     // Use existing one.
    282     info = found->second;
    283     if (info->all_invocations.find(build_file) !=
    284         info->all_invocations.end()) {
    285       // We've already seen this source file for this toolchain, don't need
    286       // to do anything.
    287       return true;
    288     }
    289   }
    290 
    291   info->all_invocations.insert(build_file);
    292 
    293   // True if the settings load needs to be scheduled.
    294   bool info_needs_settings_load = false;
    295 
    296   // True if the settings load has completed.
    297   bool info_settings_loaded = false;
    298 
    299   switch (info->state) {
    300     case TOOLCHAIN_SETTINGS_NOT_LOADED:
    301       info_needs_settings_load = true;
    302       info->scheduled_invocations[build_file] = specified_from;
    303       break;
    304 
    305     case TOOLCHAIN_SETTINGS_LOADING:
    306       info->scheduled_invocations[build_file] = specified_from;
    307       break;
    308 
    309     case TOOLCHAIN_SETTINGS_LOADED:
    310       info_settings_loaded = true;
    311       break;
    312   }
    313 
    314   if (info_needs_settings_load) {
    315     // Load the settings file.
    316     g_scheduler->IncrementWorkCount();
    317     if (!g_scheduler->input_file_manager()->AsyncLoadFile(
    318              specified_from, build_settings_,
    319              build_settings_->build_config_file(),
    320              base::Bind(&ToolchainManager::BackgroundLoadBuildConfig,
    321                         base::Unretained(this), info, false),
    322              err)) {
    323       g_scheduler->DecrementWorkCount();
    324       return false;
    325     }
    326   } else if (info_settings_loaded) {
    327     // Settings are ready to go, load the target file.
    328     g_scheduler->IncrementWorkCount();
    329     if (!g_scheduler->input_file_manager()->AsyncLoadFile(
    330              specified_from, build_settings_, build_file,
    331              base::Bind(&ToolchainManager::BackgroundInvoke,
    332                         base::Unretained(this), info, build_file),
    333              err)) {
    334       g_scheduler->DecrementWorkCount();
    335       return false;
    336     }
    337   }
    338   // Else we should have added the infocations to the scheduled_invocations
    339   // from within the lock above.
    340   return true;
    341 }
    342 
    343 // static
    344 std::string ToolchainManager::ToolchainToOutputSubdir(
    345     const Label& toolchain_name) {
    346   // For now just assume the toolchain name is always a valid dir name. We may
    347   // want to clean up the in the future.
    348   return toolchain_name.name();
    349 }
    350 
    351 ToolchainManager::Info* ToolchainManager::LoadNewToolchainLocked(
    352     const LocationRange& specified_from,
    353     const Label& toolchain_name,
    354     Err* err) {
    355   GetLock().AssertAcquired();
    356   Info* info = new Info(build_settings_,
    357                         toolchain_name,
    358                         ToolchainToOutputSubdir(toolchain_name));
    359 
    360   toolchains_[toolchain_name] = info;
    361 
    362   // Invoke the file containing the toolchain definition so that it gets
    363   // defined. The default one (label is empty) will be done spearately.
    364   if (!toolchain_name.is_null()) {
    365     // The default toolchain should be specified whenever we're requesting
    366     // another one. This is how we know under what context we should execute
    367     // the invoke for the toolchain file.
    368     CHECK(!default_toolchain_.is_null());
    369     ScheduleInvocationLocked(specified_from, default_toolchain_,
    370                              toolchain_name.dir(), err);
    371   }
    372   return info;
    373 }
    374 
    375 void ToolchainManager::FixupDefaultToolchainLocked() {
    376   // Now that we've run the default build config, we should know the
    377   // default toolchain name. Fix up our reference.
    378   // See Start() for more.
    379   GetLock().AssertAcquired();
    380   if (default_toolchain_.is_null()) {
    381     g_scheduler->FailWithError(Err(Location(),
    382         "Default toolchain not set.",
    383         "Your build config file \"" +
    384         build_settings_->build_config_file().value() +
    385         "\"\ndid not call set_default_toolchain(). This is needed so "
    386         "I know how to actually\ncompile your code."));
    387     return;
    388   }
    389 
    390   ToolchainMap::iterator old_default = toolchains_.find(Label());
    391   CHECK(old_default != toolchains_.end());
    392   Info* info = old_default->second;
    393   toolchains_[default_toolchain_] = info;
    394   toolchains_.erase(old_default);
    395 
    396   // Toolchain should not have been loaded in the build config file.
    397   CHECK(!info->toolchain_set);
    398 
    399   // We need to set the toolchain label now that we know it. There's no way
    400   // to set the label, but we can assign the toolchain to a new one. Loading
    401   // the build config can not change the toolchain, so we won't be overwriting
    402   // anything useful.
    403   info->toolchain = Toolchain(default_toolchain_);
    404   info->EnsureItemNode();
    405 
    406   // The default toolchain is loaded in greedy mode so all targets we
    407   // encounter are generated. Non-default toolchain settings stay in non-greedy
    408   // so we only generate the minimally required set.
    409   info->settings.set_greedy_target_generation(true);
    410 
    411   // Schedule a load of the toolchain build file.
    412   Err err;
    413   ScheduleInvocationLocked(LocationRange(), default_toolchain_,
    414                            default_toolchain_.dir(), &err);
    415   if (err.has_error())
    416     g_scheduler->FailWithError(err);
    417 }
    418 
    419 void ToolchainManager::BackgroundLoadBuildConfig(Info* info,
    420                                                  bool is_default,
    421                                                  const ParseNode* root) {
    422   // Danger: No early returns without decrementing the work count.
    423   if (root && !g_scheduler->is_failed()) {
    424     // Nobody should be accessing settings at this point other than us since we
    425     // haven't marked it loaded, so we can do it outside the lock.
    426     Scope* base_config = info->settings.base_config();
    427     SetSystemVars(info->settings, base_config);
    428     base_config->SetProcessingBuildConfig();
    429     if (is_default)
    430       base_config->SetProcessingDefaultBuildConfig();
    431 
    432     const BlockNode* root_block = root->AsBlock();
    433     Err err;
    434     root_block->ExecuteBlockInScope(base_config, &err);
    435 
    436     base_config->ClearProcessingBuildConfig();
    437     if (is_default)
    438       base_config->ClearProcessingDefaultBuildConfig();
    439 
    440     if (err.has_error()) {
    441       g_scheduler->FailWithError(err);
    442     } else {
    443       // Base config processing succeeded.
    444       Info::ScheduledInvocationMap schedule_these;
    445       {
    446         base::AutoLock lock(GetLock());
    447         schedule_these.swap(info->scheduled_invocations);
    448         info->state = TOOLCHAIN_SETTINGS_LOADED;
    449         if (is_default)
    450           FixupDefaultToolchainLocked();
    451       }
    452 
    453       // Schedule build files waiting on this settings. There can be many so we
    454       // want to load them in parallel on the pool.
    455       for (Info::ScheduledInvocationMap::iterator i = schedule_these.begin();
    456            i != schedule_these.end() && !g_scheduler->is_failed(); ++i) {
    457         // Note i->second may be NULL, so don't dereference.
    458         g_scheduler->IncrementWorkCount();
    459         if (!g_scheduler->input_file_manager()->AsyncLoadFile(
    460                  i->second, build_settings_, i->first,
    461                  base::Bind(&ToolchainManager::BackgroundInvoke,
    462                             base::Unretained(this), info, i->first),
    463                  &err)) {
    464           g_scheduler->FailWithError(err);
    465           g_scheduler->DecrementWorkCount();
    466           break;
    467         }
    468       }
    469     }
    470   }
    471   g_scheduler->DecrementWorkCount();
    472 }
    473 
    474 void ToolchainManager::BackgroundInvoke(const Info* info,
    475                                         const SourceFile& file_name,
    476                                         const ParseNode* root) {
    477   if (root && !g_scheduler->is_failed()) {
    478     if (g_scheduler->verbose_logging()) {
    479       g_scheduler->Log("Running", file_name.value() + " with toolchain " +
    480                        info->toolchain.label().GetUserVisibleName(false));
    481     }
    482 
    483     Scope our_scope(info->settings.base_config());
    484     ScopePerFileProvider per_file_provider(&our_scope, file_name);
    485 
    486     Err err;
    487     root->Execute(&our_scope, &err);
    488     if (err.has_error())
    489       g_scheduler->FailWithError(err);
    490   }
    491 
    492   g_scheduler->DecrementWorkCount();
    493 }
    494 
    495 base::Lock& ToolchainManager::GetLock() const {
    496   return build_settings_->item_tree().lock();
    497 }
    498