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