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/import_manager.h"
6
7 #include <memory>
8
9 #include "gn/err.h"
10 #include "gn/parse_tree.h"
11 #include "gn/scheduler.h"
12 #include "gn/scope_per_file_provider.h"
13 #include "gn/trace.h"
14 #include "util/ticks.h"
15
16 namespace {
17
18 // Returns a newly-allocated scope on success, null on failure.
UncachedImport(const Settings * settings,const SourceFile & file,const ParseNode * node_for_err,Err * err)19 std::unique_ptr<Scope> UncachedImport(const Settings* settings,
20 const SourceFile& file,
21 const ParseNode* node_for_err,
22 Err* err) {
23 ScopedTrace load_trace(TraceItem::TRACE_IMPORT_LOAD, file.value());
24 load_trace.SetToolchain(settings->toolchain_label());
25
26 const ParseNode* node = g_scheduler->input_file_manager()->SyncLoadFile(
27 node_for_err->GetRange(), settings->build_settings(), file, err);
28 if (!node)
29 return nullptr;
30
31 std::unique_ptr<Scope> scope =
32 std::make_unique<Scope>(settings->base_config());
33 scope->set_source_dir(file.GetDir());
34
35 // Don't allow ScopePerFileProvider to provide target-related variables.
36 // These will be relative to the imported file, which is probably not what
37 // people mean when they use these.
38 ScopePerFileProvider per_file_provider(scope.get(), false);
39
40 scope->SetProcessingImport();
41 node->Execute(scope.get(), err);
42 if (err->has_error()) {
43 // If there was an error, append the caller location so the error message
44 // displays a why the file was imported (esp. useful for failed asserts).
45 err->AppendSubErr(Err(node_for_err, "whence it was imported."));
46 return nullptr;
47 }
48 scope->ClearProcessingImport();
49
50 return scope;
51 }
52
53 } // namespace
54
55 struct ImportManager::ImportInfo {
56 ImportInfo() = default;
57 ~ImportInfo() = default;
58
59 // This lock protects the unique_ptr. Once the scope is computed,
60 // it is const and can be accessed read-only outside of the lock.
61 std::mutex load_lock;
62
63 std::unique_ptr<const Scope> scope;
64
65 // The result of loading the import. If the load failed, the scope will be
66 // null but this will be set to error. In this case the thread should not
67 // attempt to load the file, even if the scope is null.
68 Err load_result;
69 };
70
71 ImportManager::ImportManager() = default;
72
73 ImportManager::~ImportManager() = default;
74
DoImport(const SourceFile & file,const ParseNode * node_for_err,Scope * scope,Err * err)75 bool ImportManager::DoImport(const SourceFile& file,
76 const ParseNode* node_for_err,
77 Scope* scope,
78 Err* err) {
79 // Key for the current import on the current thread in imports_in_progress_.
80 std::stringstream ss;
81 ss << std::this_thread::get_id() << file.value();
82 std::string key = ss.str();
83
84 // See if we have a cached import, but be careful to actually do the scope
85 // copying outside of the lock.
86 ImportInfo* import_info = nullptr;
87 {
88 std::lock_guard<std::mutex> lock(imports_lock_);
89 std::unique_ptr<ImportInfo>& info_ptr = imports_[file];
90 if (!info_ptr)
91 info_ptr = std::make_unique<ImportInfo>();
92
93 // Promote the ImportInfo to outside of the imports lock.
94 import_info = info_ptr.get();
95
96 if (imports_in_progress_.find(key) != imports_in_progress_.end()) {
97 *err = Err(Location(), file.value() + " is part of an import loop.");
98 return false;
99 }
100 imports_in_progress_.insert(key);
101 }
102
103 // Now use the per-import-file lock to block this thread if another thread
104 // is already processing the import.
105 const Scope* import_scope = nullptr;
106 {
107 Ticks import_block_begin = TicksNow();
108 std::lock_guard<std::mutex> lock(import_info->load_lock);
109
110 if (!import_info->scope) {
111 // Only load if the import hasn't already failed.
112 if (!import_info->load_result.has_error()) {
113 import_info->scope = UncachedImport(
114 scope->settings(), file, node_for_err, &import_info->load_result);
115 }
116 if (import_info->load_result.has_error()) {
117 *err = import_info->load_result;
118 return false;
119 }
120 } else {
121 // Add trace if this thread was blocked for a long period of time and did
122 // not load the import itself.
123 Ticks import_block_end = TicksNow();
124 constexpr auto kImportBlockTraceThresholdMS = 20;
125 if (TracingEnabled() &&
126 TicksDelta(import_block_end, import_block_begin).InMilliseconds() >
127 kImportBlockTraceThresholdMS) {
128 auto import_block_trace = std::make_unique<TraceItem>(
129 TraceItem::TRACE_IMPORT_BLOCK, file.value(),
130 std::this_thread::get_id());
131 import_block_trace->set_begin(import_block_begin);
132 import_block_trace->set_end(import_block_end);
133 import_block_trace->set_toolchain(
134 scope->settings()->toolchain_label().GetUserVisibleName(false));
135 AddTrace(std::move(import_block_trace));
136 }
137 }
138
139 // Promote the now-read-only scope to outside the load lock.
140 import_scope = import_info->scope.get();
141 }
142
143 Scope::MergeOptions options;
144 options.skip_private_vars = true;
145 options.mark_dest_used = true; // Don't require all imported values be used.
146
147 {
148 std::lock_guard<std::mutex> lock(imports_lock_);
149 imports_in_progress_.erase(key);
150 }
151
152 return import_scope->NonRecursiveMergeTo(scope, options, node_for_err,
153 "import", err);
154 }
155
GetImportedFiles() const156 std::vector<SourceFile> ImportManager::GetImportedFiles() const {
157 std::vector<SourceFile> imported_files;
158 imported_files.resize(imports_.size());
159 std::transform(imports_.begin(), imports_.end(), imported_files.begin(),
160 [](const ImportMap::value_type& val) { return val.first; });
161 return imported_files;
162 }
163