Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(63)

Side by Side Diff: tools/gn/toolchain_manager.cc

Issue 21114002: Add initial prototype for the GN meta-buildsystem. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: add owners and readme Created 7 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « tools/gn/toolchain_manager.h ('k') | tools/gn/value.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 }
OLDNEW
« no previous file with comments | « tools/gn/toolchain_manager.h ('k') | tools/gn/value.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698