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 |