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