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