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 "gn/loader.h"
6
7 #include <memory>
8
9 #include "gn/build_settings.h"
10 #include "gn/err.h"
11 #include "gn/filesystem_utils.h"
12 #include "gn/input_file_manager.h"
13 #include "gn/parse_tree.h"
14 #include "gn/scheduler.h"
15 #include "gn/scope_per_file_provider.h"
16 #include "gn/settings.h"
17 #include "gn/source_dir.h"
18 #include "gn/source_file.h"
19 #include "gn/trace.h"
20
21 namespace {
22
23 struct SourceFileAndOrigin {
SourceFileAndOrigin__anon53abbbb10111::SourceFileAndOrigin24 SourceFileAndOrigin(const SourceFile& f, const LocationRange& o)
25 : file(f), origin(o) {}
26
27 SourceFile file;
28 LocationRange origin;
29 };
30
31 } // namespace
32
33 // Identifies one time a file is loaded in a given toolchain so we don't load
34 // it more than once.
35 struct LoaderImpl::LoadID {
36 LoadID() = default;
LoadIDLoaderImpl::LoadID37 LoadID(const SourceFile& f, const Label& tc_name)
38 : file(f), toolchain_name(tc_name) {}
39
operator <LoaderImpl::LoadID40 bool operator<(const LoadID& other) const {
41 if (file.value() == other.file.value())
42 return toolchain_name < other.toolchain_name;
43 return file < other.file;
44 }
45
46 SourceFile file;
47 Label toolchain_name;
48 };
49
50 // Our tracking information for a toolchain.
51 struct LoaderImpl::ToolchainRecord {
52 // The default toolchain label can be empty for the first time the default
53 // toolchain is loaded, since we don't know it yet. This will be fixed up
54 // later. It should be valid in all other cases.
ToolchainRecordLoaderImpl::ToolchainRecord55 ToolchainRecord(const BuildSettings* build_settings,
56 const Label& toolchain_label,
57 const Label& default_toolchain_label)
58 : settings(
59 build_settings,
60 GetOutputSubdirName(toolchain_label,
61 toolchain_label == default_toolchain_label)),
62 is_toolchain_loaded(false),
63 is_config_loaded(false) {
64 settings.set_default_toolchain_label(default_toolchain_label);
65 settings.set_toolchain_label(toolchain_label);
66 }
67
68 Settings settings;
69
70 bool is_toolchain_loaded;
71 bool is_config_loaded;
72
73 std::vector<SourceFileAndOrigin> waiting_on_me;
74 };
75
76 // -----------------------------------------------------------------------------
77
78 const void* const Loader::kDefaultToolchainKey = &kDefaultToolchainKey;
79
80 Loader::Loader() = default;
81
82 Loader::~Loader() = default;
83
Load(const Label & label,const LocationRange & origin)84 void Loader::Load(const Label& label, const LocationRange& origin) {
85 Load(BuildFileForLabel(label), origin, label.GetToolchainLabel());
86 }
87
88 // static
BuildFileForLabel(const Label & label)89 SourceFile Loader::BuildFileForLabel(const Label& label) {
90 return SourceFile(label.dir().value() + "BUILD.gn");
91 }
92
93 // -----------------------------------------------------------------------------
94
LoaderImpl(const BuildSettings * build_settings)95 LoaderImpl::LoaderImpl(const BuildSettings* build_settings)
96 : pending_loads_(0), build_settings_(build_settings) {
97 // There may not be an active TaskRunner at this point. When that's the case,
98 // the calling code is expected to call set_task_runner().
99 task_runner_ = MsgLoop::Current();
100 }
101
102 LoaderImpl::~LoaderImpl() = default;
103
Load(const SourceFile & file,const LocationRange & origin,const Label & in_toolchain_name)104 void LoaderImpl::Load(const SourceFile& file,
105 const LocationRange& origin,
106 const Label& in_toolchain_name) {
107 const Label& toolchain_name = in_toolchain_name.is_null()
108 ? default_toolchain_label_
109 : in_toolchain_name;
110 LoadID load_id(file, toolchain_name);
111 if (!invocations_.insert(load_id).second)
112 return; // Already in set, so this file was already loaded or schedulerd.
113
114 if (toolchain_records_.empty()) {
115 // Nothing loaded, need to load the default build config. The initial load
116 // should not specify a toolchain.
117 DCHECK(toolchain_name.is_null());
118
119 std::unique_ptr<ToolchainRecord> new_record =
120 std::make_unique<ToolchainRecord>(build_settings_, Label(), Label());
121 ToolchainRecord* record = new_record.get();
122 Label empty_label; // VS issues spurious warning using ...[Label()].
123 toolchain_records_[empty_label] = std::move(new_record);
124
125 // The default build config is no dependent on the toolchain definition,
126 // since we need to load the build config before we know what the default
127 // toolchain name is.
128 record->is_toolchain_loaded = true;
129
130 record->waiting_on_me.push_back(SourceFileAndOrigin(file, origin));
131 ScheduleLoadBuildConfig(&record->settings, Scope::KeyValueMap());
132
133 return;
134 }
135
136 ToolchainRecord* record;
137 if (toolchain_name.is_null())
138 record = toolchain_records_[default_toolchain_label_].get();
139 else
140 record = toolchain_records_[toolchain_name].get();
141
142 if (!record) {
143 DCHECK(!default_toolchain_label_.is_null());
144
145 // No reference to this toolchain found yet, make one.
146 std::unique_ptr<ToolchainRecord> new_record =
147 std::make_unique<ToolchainRecord>(build_settings_, toolchain_name,
148 default_toolchain_label_);
149 record = new_record.get();
150 toolchain_records_[toolchain_name] = std::move(new_record);
151
152 // Schedule a load of the toolchain using the default one.
153 Load(BuildFileForLabel(toolchain_name), origin, default_toolchain_label_);
154 }
155
156 if (record->is_config_loaded)
157 ScheduleLoadFile(&record->settings, origin, file);
158 else
159 record->waiting_on_me.push_back(SourceFileAndOrigin(file, origin));
160 }
161
ToolchainLoaded(const Toolchain * toolchain)162 void LoaderImpl::ToolchainLoaded(const Toolchain* toolchain) {
163 ToolchainRecord* record = toolchain_records_[toolchain->label()].get();
164 if (!record) {
165 DCHECK(!default_toolchain_label_.is_null());
166 std::unique_ptr<ToolchainRecord> new_record =
167 std::make_unique<ToolchainRecord>(build_settings_, toolchain->label(),
168 default_toolchain_label_);
169 record = new_record.get();
170 toolchain_records_[toolchain->label()] = std::move(new_record);
171 }
172 record->is_toolchain_loaded = true;
173
174 // The default build config is loaded first, then its toolchain. Secondary
175 // ones are loaded in the opposite order so we can pass toolchain parameters
176 // to the build config. So we may or may not have a config at this point.
177 if (!record->is_config_loaded) {
178 ScheduleLoadBuildConfig(&record->settings, toolchain->args());
179 } else {
180 // There should be nobody waiting on this if the build config is already
181 // loaded.
182 DCHECK(record->waiting_on_me.empty());
183 }
184 }
185
GetDefaultToolchain() const186 Label LoaderImpl::GetDefaultToolchain() const {
187 return default_toolchain_label_;
188 }
189
GetToolchainSettings(const Label & label) const190 const Settings* LoaderImpl::GetToolchainSettings(const Label& label) const {
191 ToolchainRecordMap::const_iterator found_toolchain;
192 if (label.is_null()) {
193 if (default_toolchain_label_.is_null())
194 return nullptr;
195 found_toolchain = toolchain_records_.find(default_toolchain_label_);
196 } else {
197 found_toolchain = toolchain_records_.find(label);
198 }
199
200 if (found_toolchain == toolchain_records_.end())
201 return nullptr;
202 return &found_toolchain->second->settings;
203 }
204
ScheduleLoadFile(const Settings * settings,const LocationRange & origin,const SourceFile & file)205 void LoaderImpl::ScheduleLoadFile(const Settings* settings,
206 const LocationRange& origin,
207 const SourceFile& file) {
208 Err err;
209 pending_loads_++;
210 if (!AsyncLoadFile(
211 origin, settings->build_settings(), file,
212 [this, settings, file, origin](const ParseNode* parse_node) {
213 BackgroundLoadFile(settings, file, origin, parse_node);
214 },
215 &err)) {
216 g_scheduler->FailWithError(err);
217 DecrementPendingLoads();
218 }
219 }
220
ScheduleLoadBuildConfig(Settings * settings,const Scope::KeyValueMap & toolchain_overrides)221 void LoaderImpl::ScheduleLoadBuildConfig(
222 Settings* settings,
223 const Scope::KeyValueMap& toolchain_overrides) {
224 Err err;
225 pending_loads_++;
226 if (!AsyncLoadFile(
227 LocationRange(), settings->build_settings(),
228 settings->build_settings()->build_config_file(),
229 [this, settings, toolchain_overrides](const ParseNode* root) {
230 BackgroundLoadBuildConfig(settings, toolchain_overrides, root);
231 },
232 &err)) {
233 g_scheduler->FailWithError(err);
234 DecrementPendingLoads();
235 }
236 }
237
BackgroundLoadFile(const Settings * settings,const SourceFile & file_name,const LocationRange & origin,const ParseNode * root)238 void LoaderImpl::BackgroundLoadFile(const Settings* settings,
239 const SourceFile& file_name,
240 const LocationRange& origin,
241 const ParseNode* root) {
242 if (!root) {
243 task_runner_->PostTask([this]() { DecrementPendingLoads(); });
244 return;
245 }
246
247 if (g_scheduler->verbose_logging()) {
248 g_scheduler->Log("Running",
249 file_name.value() + " with toolchain " +
250 settings->toolchain_label().GetUserVisibleName(false));
251 }
252
253 Scope our_scope(settings->base_config());
254 ScopePerFileProvider per_file_provider(&our_scope, true);
255 our_scope.set_source_dir(file_name.GetDir());
256 our_scope.AddBuildDependencyFile(file_name);
257
258 // Targets, etc. generated as part of running this file will end up here.
259 Scope::ItemVector collected_items;
260 our_scope.set_item_collector(&collected_items);
261
262 ScopedTrace trace(TraceItem::TRACE_FILE_EXECUTE, file_name.value());
263 trace.SetToolchain(settings->toolchain_label());
264
265 Err err;
266 root->Execute(&our_scope, &err);
267 if (!err.has_error())
268 our_scope.CheckForUnusedVars(&err);
269
270 if (err.has_error()) {
271 if (!origin.is_null())
272 err.AppendSubErr(Err(origin, "which caused the file to be included."));
273 g_scheduler->FailWithError(err);
274 }
275
276 // Pass all of the items that were defined off to the builder.
277 for (auto& item : collected_items)
278 settings->build_settings()->ItemDefined(std::move(item));
279
280 trace.Done();
281
282 task_runner_->PostTask([this]() { DidLoadFile(); });
283 }
284
BackgroundLoadBuildConfig(Settings * settings,const Scope::KeyValueMap & toolchain_overrides,const ParseNode * root)285 void LoaderImpl::BackgroundLoadBuildConfig(
286 Settings* settings,
287 const Scope::KeyValueMap& toolchain_overrides,
288 const ParseNode* root) {
289 if (!root) {
290 task_runner_->PostTask([this]() { DecrementPendingLoads(); });
291 return;
292 }
293
294 Scope* base_config = settings->base_config();
295 base_config->set_source_dir(SourceDir("//"));
296 base_config->AddBuildDependencyFile(
297 settings->build_settings()->build_config_file());
298
299 settings->build_settings()->build_args().SetupRootScope(base_config,
300 toolchain_overrides);
301
302 base_config->SetProcessingBuildConfig();
303
304 // See kDefaultToolchainKey in the header.
305 Label default_toolchain_label;
306 if (settings->is_default())
307 base_config->SetProperty(kDefaultToolchainKey, &default_toolchain_label);
308
309 ScopedTrace trace(TraceItem::TRACE_FILE_EXECUTE,
310 settings->build_settings()->build_config_file().value());
311 trace.SetToolchain(settings->toolchain_label());
312
313 // Run the BUILDCONFIG with its directory as the current one. We want
314 // BUILDCONFIG to modify the base_config so can't make a copy or a nested one.
315 base_config->set_source_dir(
316 settings->build_settings()->build_config_file().GetDir());
317
318 Err err;
319 root->Execute(base_config, &err);
320
321 // Put back the root as the default source dir. This probably isn't necessary
322 // as other scopes will set their directories to their own path, but it's a
323 // better default than the build config's directory.
324 base_config->set_source_dir(SourceDir("//"));
325
326 // Clear all private variables left in the scope. We want the root build
327 // config to be like a .gni file in that variables beginning with an
328 // underscore aren't exported.
329 base_config->RemovePrivateIdentifiers();
330
331 trace.Done();
332
333 if (err.has_error())
334 g_scheduler->FailWithError(err);
335
336 base_config->ClearProcessingBuildConfig();
337 if (settings->is_default()) {
338 // The default toolchain must have been set in the default build config
339 // file.
340 if (default_toolchain_label.is_null()) {
341 g_scheduler->FailWithError(Err(
342 Location(),
343 "The default build config file did not call set_default_toolchain()",
344 "If you don't call this, I can't figure out what toolchain to use\n"
345 "for all of this code."));
346 } else {
347 DCHECK(settings->toolchain_label().is_null());
348 settings->set_toolchain_label(default_toolchain_label);
349 }
350 }
351
352 task_runner_->PostTask(
353 [this, toolchain_label = settings->toolchain_label()]() {
354 DidLoadBuildConfig(toolchain_label);
355 });
356 }
357
DidLoadFile()358 void LoaderImpl::DidLoadFile() {
359 DecrementPendingLoads();
360 }
361
DidLoadBuildConfig(const Label & label)362 void LoaderImpl::DidLoadBuildConfig(const Label& label) {
363 // Do not return early, we must call DecrementPendingLoads() at the bottom.
364
365 ToolchainRecordMap::iterator found_toolchain = toolchain_records_.find(label);
366 ToolchainRecord* record = nullptr;
367 if (found_toolchain == toolchain_records_.end()) {
368 // When loading the default build config, we'll insert it into the record
369 // map with an empty label since we don't yet know what to call it.
370 //
371 // In this case, we should have exactly one entry in the map with an empty
372 // label. We now need to fix up the naming so it refers to the "real" one.
373 CHECK_EQ(1U, toolchain_records_.size());
374 ToolchainRecordMap::iterator empty_label = toolchain_records_.find(Label());
375 CHECK(empty_label != toolchain_records_.end());
376
377 // Fix up the toolchain record.
378 std::unique_ptr<ToolchainRecord> moved_record =
379 std::move(empty_label->second);
380 record = moved_record.get();
381 toolchain_records_[label] = std::move(moved_record);
382 toolchain_records_.erase(empty_label);
383
384 // Save the default toolchain label.
385 default_toolchain_label_ = label;
386 DCHECK(record->settings.default_toolchain_label().is_null());
387 record->settings.set_default_toolchain_label(label);
388
389 // The settings object should have the toolchain label already set.
390 DCHECK(!record->settings.toolchain_label().is_null());
391
392 // Update any stored invocations that refer to the empty toolchain label.
393 // This will normally only be one, for the root build file, so brute-force
394 // is OK.
395 LoadIDSet old_loads;
396 invocations_.swap(old_loads);
397 for (const auto& load : old_loads) {
398 if (load.toolchain_name.is_null()) {
399 // Fix up toolchain label
400 invocations_.emplace(load.file, label);
401 } else {
402 // Can keep the old one.
403 invocations_.insert(load);
404 }
405 }
406 } else {
407 record = found_toolchain->second.get();
408 }
409
410 DCHECK(!record->is_config_loaded);
411 DCHECK(record->is_toolchain_loaded);
412 record->is_config_loaded = true;
413
414 // Schedule all waiting file loads.
415 for (const auto& waiting : record->waiting_on_me)
416 ScheduleLoadFile(&record->settings, waiting.origin, waiting.file);
417 record->waiting_on_me.clear();
418
419 DecrementPendingLoads();
420 }
421
DecrementPendingLoads()422 void LoaderImpl::DecrementPendingLoads() {
423 DCHECK_GT(pending_loads_, 0);
424 pending_loads_--;
425 if (pending_loads_ == 0 && complete_callback_)
426 complete_callback_();
427 }
428
AsyncLoadFile(const LocationRange & origin,const BuildSettings * build_settings,const SourceFile & file_name,std::function<void (const ParseNode *)> callback,Err * err)429 bool LoaderImpl::AsyncLoadFile(const LocationRange& origin,
430 const BuildSettings* build_settings,
431 const SourceFile& file_name,
432 std::function<void(const ParseNode*)> callback,
433 Err* err) {
434 if (async_load_file_) {
435 return async_load_file_(origin, build_settings, file_name,
436 std::move(callback), err);
437 }
438 return g_scheduler->input_file_manager()->AsyncLoadFile(
439 origin, build_settings, file_name, std::move(callback), err);
440 }
441