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/setup.h"
6
7 #include <stdlib.h>
8
9 #include <algorithm>
10 #include <memory>
11 #include <sstream>
12 #include <utility>
13
14 #include "base/command_line.h"
15 #include "base/files/file_path.h"
16 #include "base/files/file_util.h"
17 #include "base/memory/ref_counted.h"
18 #include "base/strings/string_split.h"
19 #include "base/strings/string_util.h"
20 #include "base/strings/utf_string_conversions.h"
21 #include "gn/command_format.h"
22 #include "gn/commands.h"
23 #include "gn/exec_process.h"
24 #include "gn/filesystem_utils.h"
25 #include "gn/innerapis_publicinfo_generator.h"
26 #include "gn/input_file.h"
27 #include "gn/label_pattern.h"
28 #include "gn/ohos_components_checker.h"
29 #include "gn/parse_tree.h"
30 #include "gn/parser.h"
31 #include "gn/precise/precise.h"
32 #include "gn/source_dir.h"
33 #include "gn/source_file.h"
34 #include "gn/standard_out.h"
35 #include "gn/switches.h"
36 #include "gn/tokenizer.h"
37 #include "gn/trace.h"
38 #include "gn/value.h"
39 #include "gn/value_extractors.h"
40 #include "util/build_config.h"
41
42 #if defined(OS_WIN)
43 #include <windows.h>
44
45 #include "base/win/scoped_process_information.h"
46 #include "base/win/win_util.h"
47 #endif
48
49 const char kDotfile_Help[] =
50 R"(.gn file
51
52 When gn starts, it will search the current directory and parent directories
53 for a file called ".gn". This indicates the source root. You can override
54 this detection by using the --root command-line argument
55
56 The .gn file in the source root will be executed. The syntax is the same as a
57 buildfile, but with very limited build setup-specific meaning.
58
59 If you specify --root, by default GN will look for the file .gn in that
60 directory. If you want to specify a different file, you can additionally pass
61 --dotfile:
62
63 gn gen out/Debug --root=/home/build --dotfile=/home/my_gn_file.gn
64
65 Variables
66
67 arg_file_template [optional]
68 Path to a file containing the text that should be used as the default
69 args.gn content when you run `gn args`.
70
71 buildconfig [required]
72 Path to the build config file. This file will be used to set up the
73 build file execution environment for each toolchain.
74
75 check_targets [optional]
76 A list of labels and label patterns that should be checked when running
77 "gn check" or "gn gen --check". If neither check_targets or
78 no_check_targets (see below) is specified, all targets will be checked.
79 It is an error to specify both check_targets and no_check_targets. If it
80 is the empty list, no targets will be checked. To bypass this list,
81 request an explicit check of targets, like "//*".
82
83 The format of this list is identical to that of "visibility" so see "gn
84 help visibility" for examples.
85
86 no_check_targets [optional]
87 A list of labels and label patterns that should *not* be checked when
88 running "gn check" or "gn gen --check". All other targets will be checked.
89 If neither check_targets (see above) or no_check_targets is specified, all
90 targets will be checked. It is an error to specify both check_targets and
91 no_check_targets.
92
93 The format of this list is identical to that of "visibility" so see "gn
94 help visibility" for examples.
95
96 check_system_includes [optional]
97 Boolean to control whether system style includes are checked by default
98 when running "gn check" or "gn gen --check". System style includes are
99 includes that use angle brackets <> instead of double quotes "". If this
100 setting is omitted or set to false, these includes will be ignored by
101 default. They can be checked explicitly by running
102 "gn check --check-system" or "gn gen --check=system"
103
104 exec_script_whitelist [optional]
105 A list of .gn/.gni files (not labels) that have permission to call the
106 exec_script function. If this list is defined, calls to exec_script will
107 be checked against this list and GN will fail if the current file isn't
108 in the list.
109
110 This is to allow the use of exec_script to be restricted since is easy to
111 use inappropriately. Wildcards are not supported. Files in the
112 secondary_source tree (if defined) should be referenced by ignoring the
113 secondary tree and naming them as if they are in the main tree.
114
115 If unspecified, the ability to call exec_script is unrestricted.
116
117 Example:
118 exec_script_whitelist = [
119 "//base/BUILD.gn",
120 "//build/my_config.gni",
121 ]
122
123 export_compile_commands [optional]
124 A list of label patterns for which to generate a Clang compilation
125 database (see "gn help label_pattern" for the string format).
126
127 When specified, GN will generate a compile_commands.json file in the root
128 of the build directory containing information on how to compile each
129 source file reachable from any label matching any pattern in the list.
130 This is used for Clang-based tooling and some editor integration. See
131 https://clang.llvm.org/docs/JSONCompilationDatabase.html
132
133 The switch --add-export-compile-commands to "gn gen" (see "gn help gen")
134 appends to this value which provides a per-user way to customize it.
135
136 The deprecated switch --export-compile-commands to "gn gen" (see "gn help
137 gen") adds to the export target list using a different format.
138
139 Example:
140 export_compile_commands = [
141 "//base/*",
142 "//tools:doom_melon",
143 ]
144
145 root [optional]
146 Label of the root build target. The GN build will start by loading the
147 build file containing this target name. This defaults to "//:" which will
148 cause the file //BUILD.gn to be loaded. Note that build_file_extension
149 applies to the default case as well.
150
151 The command-line switch --root-target will override this value (see "gn
152 help --root-target").
153
154 root_patterns [optional]
155 A list of label pattern strings. When not defined or empty, the GN build
156 graph will contain all targets from any BUILD.gn evaluated in the default
157 toolchain context, and their transitive dependencies.
158
159 When set to a non empty list, only the targets in the default toolchain
160 matching these patterns, and their transitive dependencies, will be defined
161 instead.
162
163 The command-line switch --root-pattern will override this value (see
164 "gn help --root-pattern")
165
166 script_executable [optional]
167 By default, GN runs the scripts used in action targets and exec_script
168 calls using the Python interpreter found in PATH. This value specifies the
169 Python executable or other interpreter to use instead.
170
171 If set to the empty string, the scripts will be executed directly.
172
173 The command-line switch --script-executable will override this value (see
174 "gn help --script-executable")
175
176 secondary_source [optional]
177 Label of an alternate directory tree to find input files. When searching
178 for a BUILD.gn file (or the build config file discussed above), the file
179 will first be looked for in the source root. If it's not found, the
180 secondary source root will be checked (which would contain a parallel
181 directory hierarchy).
182
183 This behavior is intended to be used when BUILD.gn files can't be checked
184 in to certain source directories for whatever reason.
185
186 The secondary source root must be inside the main source tree.
187
188 default_args [optional]
189 Scope containing the default overrides for declared arguments. These
190 overrides take precedence over the default values specified in the
191 declare_args() block, but can be overridden using --args or the
192 args.gn file.
193
194 This is intended to be used when subprojects declare arguments with
195 default values that need to be changed for whatever reason.
196
197 build_file_extension [optional]
198 If set to a non-empty string, this is added to the name of all build files
199 to load.
200 GN will look for build files named "BUILD.$build_file_extension.gn".
201 This is intended to be used during migrations or other situations where
202 there are two independent GN builds in the same directories.
203
204 ninja_required_version [optional]
205 When set specifies the minimum required version of Ninja. The default
206 required version is 1.7.2. Specifying a higher version might enable the
207 use of some of newer features that can make the build more efficient.
208
209 ohos_components_support [optional]
210 This parameter enable support for OpenHarmony components.
211 When enabled, gn will load components information from "build_configs/"
212 directory in the root_out_directory.
213
214 The following files will be loaded:
215 out/build_configs/parts_info/inner_kits_info.json (required):
216 Required InnerAPI information file for each OHOS component.
217 out/build_configs/component_override_map.json (optional):
218 Optional overrided component maps file.
219
220 For OpenHarmony system build, this value must be enabled to support
221 external_deps (see "gn help external_deps") and public_external_deps
222 (see "gn help public_external_deps").
223
224 Example .gn file contents
225
226 buildconfig = "//build/config/BUILDCONFIG.gn"
227
228 check_targets = [
229 "//doom_melon/*", # Check everything in this subtree.
230 "//tools:mind_controlling_ant", # Check this specific target.
231 ]
232
233 root = "//:root"
234
235 secondary_source = "//build/config/temporary_buildfiles/"
236
237 default_args = {
238 # Default to release builds for this project.
239 is_debug = false
240 is_component_build = false
241 }
242 )";
243
244 namespace {
245
246 const base::FilePath::CharType kGnFile[] = FILE_PATH_LITERAL(".gn");
247 const char kDefaultArgsGn[] =
248 "# Set build arguments here. See `gn help buildargs`.";
249
FindDotFile(const base::FilePath & current_dir)250 base::FilePath FindDotFile(const base::FilePath& current_dir) {
251 base::FilePath try_this_file = current_dir.Append(kGnFile);
252 if (base::PathExists(try_this_file))
253 return try_this_file;
254
255 base::FilePath with_no_slash = current_dir.StripTrailingSeparators();
256 base::FilePath up_one_dir = with_no_slash.DirName();
257 if (up_one_dir == current_dir)
258 return base::FilePath(); // Got to the top.
259
260 return FindDotFile(up_one_dir);
261 }
262
263 // Called on any thread. Post the item to the builder on the main thread.
264 void ItemDefinedCallback(MsgLoop* task_runner,
265 Builder* builder_call_on_main_thread_only,
266 std::unique_ptr<Item> item) {
267 DCHECK(item);
268
269 // Increment the work count for the duration of defining the item with the
270 // builder. Otherwise finishing this callback will race finishing loading
271 // files. If there is no other pending work at any point in the middle of
272 // this call completing on the main thread, the 'Complete' function will
273 // be signaled and we'll stop running with an incomplete build.
274 g_scheduler->IncrementWorkCount();
275
276 // Work around issue binding a unique_ptr with std::function by moving into a
277 // shared_ptr.
278 auto item_shared = std::make_shared<std::unique_ptr<Item>>(std::move(item));
279 task_runner->PostTask(
__anon062b6b680202() 280 [builder_call_on_main_thread_only, item_shared]() mutable {
281 builder_call_on_main_thread_only->ItemDefined(std::move(*item_shared));
282 g_scheduler->DecrementWorkCount();
283 });
284 }
285
DecrementWorkCount()286 void DecrementWorkCount() {
287 g_scheduler->DecrementWorkCount();
288 }
289
290 #if defined(OS_WIN)
291
SysMultiByteTo16(std::string_view mb)292 std::u16string SysMultiByteTo16(std::string_view mb) {
293 if (mb.empty())
294 return std::u16string();
295
296 int mb_length = static_cast<int>(mb.length());
297 // Compute the length of the buffer.
298 int charcount = MultiByteToWideChar(CP_ACP, 0, mb.data(), mb_length, NULL, 0);
299 if (charcount == 0)
300 return std::u16string();
301
302 std::u16string wide;
303 wide.resize(charcount);
304 MultiByteToWideChar(CP_ACP, 0, mb.data(), mb_length, base::ToWCharT(&wide[0]),
305 charcount);
306
307 return wide;
308 }
309
310 // Given the path to a batch file that runs Python, extracts the name of the
311 // executable actually implementing Python. Generally people write a batch file
312 // to put something named "python" on the path, which then just redirects to
313 // a python.exe somewhere else. This step decodes that setup. On failure,
314 // returns empty path.
PythonBatToExe(const base::FilePath & bat_path)315 base::FilePath PythonBatToExe(const base::FilePath& bat_path) {
316 // Note exciting double-quoting to allow spaces. The /c switch seems to check
317 // for quotes around the whole thing and then deletes them. If you want to
318 // quote the first argument in addition (to allow for spaces in the Python
319 // path, you need *another* set of quotes around that, likewise, we need
320 // two quotes at the end.
321 std::u16string command = u"cmd.exe /c \"\"";
322 command.append(bat_path.value());
323 command.append(u"\" -c \"import sys; print(sys.executable)\"\"");
324
325 std::string python_path;
326 std::string std_err;
327 int exit_code;
328 base::FilePath cwd;
329 GetCurrentDirectory(&cwd);
330 if (internal::ExecProcess(command, cwd, &python_path, &std_err, &exit_code) &&
331 exit_code == 0 && std_err.empty()) {
332 base::TrimWhitespaceASCII(python_path, base::TRIM_ALL, &python_path);
333
334 // Python uses the system multibyte code page for sys.executable.
335 base::FilePath exe_path(SysMultiByteTo16(python_path));
336
337 // Check for reasonable output, cmd may have output an error message.
338 if (base::PathExists(exe_path))
339 return exe_path;
340 }
341 return base::FilePath();
342 }
343
344 // python_exe_name and python_bat_name can be empty but cannot be absolute
345 // paths. They should be "python.exe" or "", etc., and "python.bat" or "", etc.
FindWindowsPython(const base::FilePath & python_exe_name,const base::FilePath & python_bat_name)346 base::FilePath FindWindowsPython(const base::FilePath& python_exe_name,
347 const base::FilePath& python_bat_name) {
348 char16_t current_directory[MAX_PATH];
349 ::GetCurrentDirectory(MAX_PATH, reinterpret_cast<LPWSTR>(current_directory));
350
351 // First search for python.exe in the current directory.
352 if (!python_exe_name.empty()) {
353 CHECK(python_exe_name.FinalExtension() == u".exe");
354 CHECK_EQ(python_exe_name.IsAbsolute(), false);
355 base::FilePath cur_dir_candidate_exe =
356 base::FilePath(current_directory).Append(python_exe_name);
357 if (base::PathExists(cur_dir_candidate_exe))
358 return cur_dir_candidate_exe;
359 }
360
361 // Get the path.
362 const char16_t kPathEnvVarName[] = u"Path";
363 DWORD path_length = ::GetEnvironmentVariable(
364 reinterpret_cast<LPCWSTR>(kPathEnvVarName), nullptr, 0);
365 if (path_length == 0)
366 return base::FilePath();
367 std::unique_ptr<char16_t[]> full_path(new char16_t[path_length]);
368 DWORD actual_path_length = ::GetEnvironmentVariable(
369 reinterpret_cast<LPCWSTR>(kPathEnvVarName),
370 reinterpret_cast<LPWSTR>(full_path.get()), path_length);
371 CHECK_EQ(path_length, actual_path_length + 1);
372
373 // Search for python.exe in the path.
374 for (const auto& component : base::SplitStringPiece(
375 std::u16string_view(full_path.get(), path_length), u";",
376 base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) {
377 if (!python_exe_name.empty()) {
378 base::FilePath candidate_exe =
379 base::FilePath(component).Append(python_exe_name);
380 if (base::PathExists(candidate_exe))
381 return candidate_exe;
382 }
383
384 // Also allow python.bat, but convert into the .exe.
385 if (!python_bat_name.empty()) {
386 CHECK(python_bat_name.FinalExtension() == u".bat");
387 CHECK_EQ(python_bat_name.IsAbsolute(), false);
388 base::FilePath candidate_bat =
389 base::FilePath(component).Append(python_bat_name);
390 if (base::PathExists(candidate_bat)) {
391 base::FilePath python_exe = PythonBatToExe(candidate_bat);
392 if (!python_exe.empty())
393 return python_exe;
394 }
395 }
396 }
397 return base::FilePath();
398 }
399 #endif
400
401 } // namespace
402
403 const char Setup::kBuildArgFileName[] = "args.gn";
404
Setup()405 Setup::Setup()
406 : build_settings_(),
407 loader_(new LoaderImpl(&build_settings_)),
408 builder_(loader_.get()),
409 dotfile_settings_(&build_settings_, std::string()),
410 dotfile_scope_(&dotfile_settings_) {
411 dotfile_settings_.set_toolchain_label(Label());
412
413 build_settings_.set_item_defined_callback(
414 [task_runner = scheduler_.task_runner(),
415 builder = &builder_](std::unique_ptr<Item> item) {
416 ItemDefinedCallback(task_runner, builder, std::move(item));
417 });
418
419 loader_->set_complete_callback(&DecrementWorkCount);
420 // The scheduler's task runner wasn't created when the Loader was created, so
421 // we need to set it now.
422 loader_->set_task_runner(scheduler_.task_runner());
423 }
424
DoSetup(const std::string & build_dir,bool force_create)425 bool Setup::DoSetup(const std::string& build_dir, bool force_create) {
426 return DoSetup(build_dir, force_create,
427 *base::CommandLine::ForCurrentProcess());
428 }
429
DoSetup(const std::string & build_dir,bool force_create,const base::CommandLine & cmdline)430 bool Setup::DoSetup(const std::string& build_dir,
431 bool force_create,
432 const base::CommandLine& cmdline) {
433 Err err;
434 if (!DoSetupWithErr(build_dir, force_create, cmdline, &err)) {
435 err.PrintToStdout();
436 return false;
437 }
438 DCHECK(!err.has_error());
439 return true;
440 }
441
FillOhosComponentsInfo(const std::string & build_dir,Err * err)442 bool Setup::FillOhosComponentsInfo(const std::string& build_dir, Err* err)
443 {
444 // Load OpenHarmony system components definition file.
445 const Value *support =
446 dotfile_scope_.GetValue("ohos_components_support", true);
447 const Value *independent = build_settings_.build_args().GetArgOverride("ohos_indep_compiler_enable");
448 const Value *product = build_settings_.build_args().GetArgOverride("product_name");
449 const Value* special_parts_scan_switch = build_settings_.build_args().GetArgOverride("special_parts_scan_switch");
450 bool special_parts_switch = false;
451 if (special_parts_scan_switch && special_parts_scan_switch->type() == Value::BOOLEAN) {
452 special_parts_switch = special_parts_scan_switch->boolean_value();
453 }
454 if (!ohos_components_.LoadOhosComponents(build_dir, support, independent, product, special_parts_switch, err)) {
455 return false;
456 }
457
458 if (ohos_components_.isOhosComponentsLoaded()) {
459 build_settings_.SetOhosComponentsInfo(&ohos_components_);
460 }
461
462 const Value* checkType = build_settings_.build_args().GetArgOverride("ohos_components_checktype");
463 const Value* ruleSwitch = build_settings_.build_args().GetArgOverride("ohos_interception_rule_switch");
464 if (ruleSwitch && ruleSwitch->type() == Value::INTEGER) {
465 if (checkType && checkType->type() == Value::INTEGER) {
466 ohos_components_.LoadOhosComponentsChecker(build_dir, support, checkType->int_value(), ruleSwitch->int_value());
467 } else {
468 ohos_components_.LoadOhosComponentsChecker(build_dir, support,
469 OhosComponentChecker::CheckType::INTERCEPT_IGNORE_TEST, ruleSwitch->int_value());
470 }
471 } else {
472 if (checkType && checkType->type() == Value::INTEGER) {
473 const unsigned int INTERCEPT_ALL_RULE = (1 << (OhosComponentChecker::BinaryLeftShift::ALL - 1)) - 1;
474 ohos_components_.LoadOhosComponentsChecker(build_dir, support, checkType->int_value(), INTERCEPT_ALL_RULE);
475 } else {
476 ohos_components_.LoadOhosComponentsChecker(build_dir, support, OhosComponentChecker::CheckType::NONE,
477 OhosComponentChecker::BinaryLeftShift::UNKNOWN);
478 }
479 }
480 if (independent) {
481 ohos_components_.LoadOhosComponentsMapping(build_dir, support, independent);
482 }
483
484 const Value* preciseEnable = build_settings_.build_args().GetArgOverride("ohos_module_precise_build");
485 if (preciseEnable && preciseEnable->boolean_value()) {
486 const Value* preciseConfig = build_settings_.build_args().GetArgOverride("ohos_precise_config");
487 PreciseManager::Init(build_dir, preciseConfig);
488 }
489 return true;
490 }
491
DoSetupWithErr(const std::string & build_dir,bool force_create,const base::CommandLine & cmdline,Err * err)492 bool Setup::DoSetupWithErr(const std::string& build_dir,
493 bool force_create,
494 const base::CommandLine& cmdline,
495 Err* err) {
496 scheduler_.set_verbose_logging(cmdline.HasSwitch(switches::kVerbose));
497 if (cmdline.HasSwitch(switches::kTime) ||
498 cmdline.HasSwitch(switches::kTracelog))
499 EnableTracing();
500
501 ScopedTrace setup_trace(TraceItem::TRACE_SETUP, "DoSetup");
502
503 if (!FillSourceDir(cmdline, err))
504 return false;
505 if (!RunConfigFile(err))
506 return false;
507 if (!FillOtherConfig(cmdline, err))
508 return false;
509
510 // Must be after FillSourceDir to resolve.
511 if (!FillBuildDir(build_dir, !force_create, err))
512 return false;
513
514 // Apply project-specific default (if specified).
515 // Must happen before FillArguments().
516 if (default_args_) {
517 Scope::KeyValueMap overrides;
518 default_args_->GetCurrentScopeValues(&overrides);
519 build_settings_.build_args().AddDefaultArgOverrides(overrides);
520 }
521
522 if (fill_arguments_) {
523 if (!FillArguments(cmdline, err))
524 return false;
525 }
526
527 if (!FillOhosComponentsInfo(build_dir, err)) {
528 return false;
529 }
530
531 if (!FillPythonPath(cmdline, err))
532 return false;
533
534 // Check for unused variables in the .gn file.
535 if (!dotfile_scope_.CheckForUnusedVars(err)) {
536 return false;
537 }
538
539 return true;
540 }
541
Run()542 bool Setup::Run() {
543 return Run(*base::CommandLine::ForCurrentProcess());
544 }
545
Run(const base::CommandLine & cmdline)546 bool Setup::Run(const base::CommandLine& cmdline) {
547 RunPreMessageLoop();
548 if (!scheduler_.Run())
549 return false;
550 return RunPostMessageLoop(cmdline);
551 }
552
GetBuildArgFile() const553 SourceFile Setup::GetBuildArgFile() const {
554 return SourceFile(build_settings_.build_dir().value() + kBuildArgFileName);
555 }
556
RunPreMessageLoop()557 void Setup::RunPreMessageLoop() {
558 // Will be decremented with the loader is drained.
559 g_scheduler->IncrementWorkCount();
560
561 // Load the root build file.
562 loader_->Load(root_build_file_, LocationRange(), Label());
563 }
564
RunPostMessageLoop(const base::CommandLine & cmdline)565 bool Setup::RunPostMessageLoop(const base::CommandLine& cmdline) {
566 Err err;
567 if (!builder_.CheckForBadItems(&err)) {
568 err.PrintToStdout();
569 return false;
570 }
571
572 if (!build_settings_.build_args().VerifyAllOverridesUsed(&err)) {
573 if (cmdline.HasSwitch(switches::kFailOnUnusedArgs)) {
574 err.PrintToStdout();
575 return false;
576 }
577 err.PrintNonfatalToStdout();
578 OutputString(
579 "\nThe build continued as if that argument was "
580 "unspecified.\n\n");
581 // Nonfatal error.
582 }
583
584 if (check_public_headers_) {
585 std::vector<const Target*> all_targets = builder_.GetAllResolvedTargets();
586 std::vector<const Target*> to_check;
587 if (check_patterns()) {
588 commands::FilterTargetsByPatterns(all_targets, *check_patterns(),
589 &to_check);
590 } else if (no_check_patterns()) {
591 commands::FilterOutTargetsByPatterns(all_targets, *no_check_patterns(),
592 &to_check);
593 } else {
594 to_check = all_targets;
595 }
596
597 if (!commands::CheckPublicHeaders(&build_settings_, all_targets, to_check,
598 false, false, check_system_includes_)) {
599 return false;
600 }
601 }
602
603 // Write out tracing and timing if requested.
604 if (cmdline.HasSwitch(switches::kTime))
605 PrintLongHelp(SummarizeTraces());
606 if (cmdline.HasSwitch(switches::kTracelog))
607 SaveTraces(cmdline.GetSwitchValuePath(switches::kTracelog));
608
609 Err result;
610 InnerApiPublicInfoGenerator* instance = InnerApiPublicInfoGenerator::getInstance();
611 if (instance != nullptr) {
612 if (!instance->GeneratedInnerapiPublicInfo(builder_.GetAllResolvedTargets(), &result)) {
613 result.PrintToStdout();
614 return false;
615 }
616 }
617
618 PreciseManager* preciseManager = PreciseManager::GetInstance();
619 if (preciseManager != nullptr) {
620 preciseManager->GeneratPreciseTargets();
621 }
622 return true;
623 }
624
FillArguments(const base::CommandLine & cmdline,Err * err)625 bool Setup::FillArguments(const base::CommandLine& cmdline, Err* err) {
626 // Use the args on the command line if specified, and save them. Do this even
627 // if the list is empty (this means clear any defaults).
628 // If --args is not set, args.gn file does not exist and gen_empty_args
629 // is set, generate an empty args.gn file with default comments.
630
631 base::FilePath build_arg_file =
632 build_settings_.GetFullPath(GetBuildArgFile());
633 auto switch_value = cmdline.GetSwitchValueString(switches::kArgs);
634 if (cmdline.HasSwitch(switches::kArgs) ||
635 (gen_empty_args_ && !PathExists(build_arg_file))) {
636 if (!FillArgsFromCommandLine(
637 switch_value.empty() ? kDefaultArgsGn : switch_value, err)) {
638 return false;
639 }
640 SaveArgsToFile();
641 return true;
642 }
643
644 // No command line args given, use the arguments from the build dir (if any).
645 return FillArgsFromFile(err);
646 }
647
FillArgsFromCommandLine(const std::string & args,Err * err)648 bool Setup::FillArgsFromCommandLine(const std::string& args, Err* err) {
649 args_input_file_ = std::make_unique<InputFile>(SourceFile());
650 args_input_file_->SetContents(args);
651 args_input_file_->set_friendly_name("the command-line \"--args\"");
652 return FillArgsFromArgsInputFile(err);
653 }
654
FillArgsFromFile(Err * err)655 bool Setup::FillArgsFromFile(Err* err) {
656 ScopedTrace setup_trace(TraceItem::TRACE_SETUP, "Load args file");
657
658 SourceFile build_arg_source_file = GetBuildArgFile();
659 base::FilePath build_arg_file =
660 build_settings_.GetFullPath(build_arg_source_file);
661
662 std::string contents;
663 if (!base::ReadFileToString(build_arg_file, &contents))
664 return true; // File doesn't exist, continue with default args.
665
666 // Add a dependency on the build arguments file. If this changes, we want
667 // to re-generate the build.
668 g_scheduler->AddGenDependency(build_arg_file);
669
670 if (contents.empty())
671 return true; // Empty file, do nothing.
672
673 args_input_file_ = std::make_unique<InputFile>(build_arg_source_file);
674 args_input_file_->SetContents(contents);
675 args_input_file_->set_friendly_name(
676 "build arg file (use \"gn args <out_dir>\" to edit)");
677
678 setup_trace.Done(); // Only want to count the load as part of the trace.
679 return FillArgsFromArgsInputFile(err);
680 }
681
FillArgsFromArgsInputFile(Err * err)682 bool Setup::FillArgsFromArgsInputFile(Err* err) {
683 ScopedTrace setup_trace(TraceItem::TRACE_SETUP, "Parse args");
684
685 args_tokens_ = Tokenizer::Tokenize(args_input_file_.get(), err);
686 if (err->has_error()) {
687 return false;
688 }
689
690 args_root_ = Parser::Parse(args_tokens_, err);
691 if (err->has_error()) {
692 return false;
693 }
694
695 Scope arg_scope(&dotfile_settings_);
696 // Set soure dir so relative imports in args work.
697 SourceDir root_source_dir =
698 SourceDirForCurrentDirectory(build_settings_.root_path());
699 arg_scope.set_source_dir(root_source_dir);
700 args_root_->Execute(&arg_scope, err);
701 if (err->has_error()) {
702 return false;
703 }
704
705 // Save the result of the command args.
706 Scope::KeyValueMap overrides;
707 arg_scope.GetCurrentScopeValues(&overrides);
708 build_settings_.build_args().AddArgOverrides(overrides);
709 build_settings_.build_args().set_build_args_dependency_files(
710 arg_scope.build_dependency_files());
711 return true;
712 }
713
SaveArgsToFile()714 bool Setup::SaveArgsToFile() {
715 ScopedTrace setup_trace(TraceItem::TRACE_SETUP, "Save args file");
716
717 // For the first run, the build output dir might not be created yet, so do
718 // that so we can write a file into it. Ignore errors, we'll catch the error
719 // when we try to write a file to it below.
720 base::FilePath build_arg_file =
721 build_settings_.GetFullPath(GetBuildArgFile());
722 base::CreateDirectory(build_arg_file.DirName());
723
724 std::string contents = args_input_file_->contents();
725 commands::FormatStringToString(contents, commands::TreeDumpMode::kInactive,
726 &contents, nullptr);
727 #if defined(OS_WIN)
728 // Use Windows lineendings for this file since it will often open in
729 // Notepad which can't handle Unix ones.
730 base::ReplaceSubstringsAfterOffset(&contents, 0, "\n", "\r\n");
731 #endif
732 if (base::WriteFile(build_arg_file, contents.c_str(),
733 static_cast<int>(contents.size())) == -1) {
734 Err(Location(), "Args file could not be written.",
735 "The file is \"" + FilePathToUTF8(build_arg_file) + "\"")
736 .PrintToStdout();
737 return false;
738 }
739
740 // Add a dependency on the build arguments file. If this changes, we want
741 // to re-generate the build.
742 g_scheduler->AddGenDependency(build_arg_file);
743
744 return true;
745 }
746
FillSourceDir(const base::CommandLine & cmdline,Err * err)747 bool Setup::FillSourceDir(const base::CommandLine& cmdline, Err* err) {
748 // Find the .gn file.
749 base::FilePath root_path;
750
751 // Prefer the command line args to the config file.
752 base::FilePath relative_root_path =
753 cmdline.GetSwitchValuePath(switches::kRoot);
754 if (!relative_root_path.empty()) {
755 root_path = base::MakeAbsoluteFilePath(relative_root_path);
756 if (root_path.empty()) {
757 *err = Err(Location(), "Root source path not found.",
758 "The path \"" + FilePathToUTF8(relative_root_path) +
759 "\" doesn't exist.");
760 return false;
761 }
762
763 // When --root is specified, an alternate --dotfile can also be set.
764 // --dotfile should be a real file path and not a "//foo" source-relative
765 // path.
766 base::FilePath dotfile_path =
767 cmdline.GetSwitchValuePath(switches::kDotfile);
768 if (dotfile_path.empty()) {
769 dotfile_name_ = root_path.Append(kGnFile);
770 } else {
771 dotfile_name_ = base::MakeAbsoluteFilePath(dotfile_path);
772 if (dotfile_name_.empty()) {
773 *err = Err(Location(), "Could not load dotfile.",
774 "The file \"" + FilePathToUTF8(dotfile_path) +
775 "\" couldn't be loaded.");
776 return false;
777 }
778 // Only set dotfile_name if it was passed explicitly.
779 build_settings_.set_dotfile_name(dotfile_name_);
780 }
781 } else {
782 // In the default case, look for a dotfile and that also tells us where the
783 // source root is.
784 base::FilePath cur_dir;
785 base::GetCurrentDirectory(&cur_dir);
786 dotfile_name_ = FindDotFile(cur_dir);
787 if (dotfile_name_.empty()) {
788 *err = Err(
789 Location(), "Can't find source root.",
790 "I could not find a \".gn\" file in the current directory or any "
791 "parent,\nand the --root command-line argument was not specified.");
792 return false;
793 }
794 root_path = dotfile_name_.DirName();
795 }
796
797 base::FilePath root_realpath = base::MakeAbsoluteFilePath(root_path);
798 if (root_realpath.empty()) {
799 *err = Err(Location(), "Can't get the real root path.",
800 "I could not get the real path of \"" +
801 FilePathToUTF8(root_path) + "\".");
802 return false;
803 }
804 if (scheduler_.verbose_logging())
805 scheduler_.Log("Using source root", FilePathToUTF8(root_realpath));
806 build_settings_.SetRootPath(root_realpath);
807
808 return true;
809 }
810
FillBuildDir(const std::string & build_dir,bool require_exists,Err * err)811 bool Setup::FillBuildDir(const std::string& build_dir,
812 bool require_exists,
813 Err* err) {
814 SourceDir resolved =
815 SourceDirForCurrentDirectory(build_settings_.root_path())
816 .ResolveRelativeDir(Value(nullptr, build_dir), err,
817 build_settings_.root_path_utf8());
818 if (err->has_error()) {
819 return false;
820 }
821
822 base::FilePath build_dir_path = build_settings_.GetFullPath(resolved);
823 if (!base::CreateDirectory(build_dir_path)) {
824 *err = Err(Location(), "Can't create the build dir.",
825 "I could not create the build dir \"" +
826 FilePathToUTF8(build_dir_path) + "\".");
827 return false;
828 }
829 base::FilePath build_dir_realpath =
830 base::MakeAbsoluteFilePath(build_dir_path);
831 if (build_dir_realpath.empty()) {
832 *err = Err(Location(), "Can't get the real build dir path.",
833 "I could not get the real path of \"" +
834 FilePathToUTF8(build_dir_path) + "\".");
835 return false;
836 }
837 resolved = SourceDirForPath(build_settings_.root_path(), build_dir_realpath);
838
839 if (scheduler_.verbose_logging())
840 scheduler_.Log("Using build dir", resolved.value());
841
842 if (require_exists) {
843 if (!base::PathExists(
844 build_dir_path.Append(FILE_PATH_LITERAL("build.ninja")))) {
845 *err = Err(
846 Location(), "Not a build directory.",
847 "This command requires an existing build directory. I interpreted "
848 "your input\n\"" +
849 build_dir + "\" as:\n " + FilePathToUTF8(build_dir_path) +
850 "\nwhich doesn't seem to contain a previously-generated build.");
851 return false;
852 }
853 }
854
855 build_settings_.SetBuildDir(resolved);
856 return true;
857 }
858
859 // On Chromium repositories on Windows the Python executable can be specified as
860 // python, python.bat, or python.exe (ditto for python3, and with or without a
861 // full path specification). This handles all of these cases and returns a fully
862 // specified path to a .exe file.
863 // This is currently a NOP on other platforms.
ProcessFileExtensions(base::FilePath script_executable)864 base::FilePath ProcessFileExtensions(base::FilePath script_executable) {
865 #if defined(OS_WIN)
866 // If we have a relative path with no extension such as "python" or
867 // "python3" then do a path search on the name with .exe and .bat appended.
868 auto extension = script_executable.FinalExtension();
869 if (script_executable.IsAbsolute()) {
870 // Do translation from .bat to .exe but otherwise just pass through.
871 if (extension == u".bat")
872 script_executable = PythonBatToExe(script_executable);
873 } else {
874 if (extension == u"") {
875 // If no extension is specified then search the path for .exe and .bat
876 // variants.
877 script_executable =
878 FindWindowsPython(script_executable.ReplaceExtension(u".exe"),
879 script_executable.ReplaceExtension(u".bat"));
880 } else if (extension == u".bat") {
881 // Search the path just for the specified .bat.
882 script_executable =
883 FindWindowsPython(base::FilePath(), script_executable);
884 } else if (extension == u".exe") {
885 // Search the path just for the specified .exe.
886 script_executable =
887 FindWindowsPython(script_executable, base::FilePath());
888 }
889 }
890 script_executable = script_executable.NormalizePathSeparatorsTo('/');
891 #endif
892 return script_executable;
893 }
894
FillPythonPath(const base::CommandLine & cmdline,Err * err)895 bool Setup::FillPythonPath(const base::CommandLine& cmdline, Err* err) {
896 // Trace this since it tends to be a bit slow on Windows.
897 ScopedTrace setup_trace(TraceItem::TRACE_SETUP, "Fill Python Path");
898 const Value* value = dotfile_scope_.GetValue("script_executable", true);
899 if (cmdline.HasSwitch(switches::kScriptExecutable)) {
900 auto script_executable =
901 cmdline.GetSwitchValuePath(switches::kScriptExecutable);
902 build_settings_.set_python_path(ProcessFileExtensions(script_executable));
903 } else if (value) {
904 if (!value->VerifyTypeIs(Value::STRING, err)) {
905 return false;
906 }
907 // Note that an empty string value is valid, and means that the scripts
908 // invoked by actions will be run directly.
909 base::FilePath python_path;
910 if (!value->string_value().empty()) {
911 python_path =
912 ProcessFileExtensions(UTF8ToFilePath(value->string_value()));
913 if (python_path.empty()) {
914 *err = Err(Location(), "Could not find \"" + value->string_value() +
915 "\" from dotfile in PATH.");
916 return false;
917 }
918 }
919 build_settings_.set_python_path(python_path);
920 } else {
921 #if defined(OS_WIN)
922 base::FilePath python_path =
923 ProcessFileExtensions(base::FilePath(u"python"));
924 if (!python_path.IsAbsolute()) {
925 scheduler_.Log("WARNING",
926 "Could not find python on path, using "
927 "just \"python.exe\"");
928 python_path = base::FilePath(u"python.exe");
929 }
930 build_settings_.set_python_path(python_path);
931 #else
932 build_settings_.set_python_path(base::FilePath("python"));
933 #endif
934 }
935 return true;
936 }
937
RunConfigFile(Err * err)938 bool Setup::RunConfigFile(Err* err) {
939 if (scheduler_.verbose_logging())
940 scheduler_.Log("Got dotfile", FilePathToUTF8(dotfile_name_));
941
942 dotfile_input_file_ = std::make_unique<InputFile>(SourceFile("//.gn"));
943 if (!dotfile_input_file_->Load(dotfile_name_)) {
944 *err = Err(Location(), "Could not load dotfile.",
945 "The file \"" + FilePathToUTF8(dotfile_name_) +
946 "\" couldn't be loaded");
947 return false;
948 }
949
950 dotfile_tokens_ = Tokenizer::Tokenize(dotfile_input_file_.get(), err);
951 if (err->has_error()) {
952 return false;
953 }
954
955 dotfile_root_ = Parser::Parse(dotfile_tokens_, err);
956 if (err->has_error()) {
957 return false;
958 }
959
960 // Add a dependency on the build arguments file. If this changes, we want
961 // to re-generate the build. This causes the dotfile to make it into
962 // build.ninja.d.
963 g_scheduler->AddGenDependency(dotfile_name_);
964
965 // Also add a build dependency to the scope, which is used by `gn analyze`.
966 dotfile_scope_.AddBuildDependencyFile(SourceFile("//.gn"));
967 dotfile_root_->Execute(&dotfile_scope_, err);
968 if (err->has_error()) {
969 return false;
970 }
971
972 return true;
973 }
974
FillOtherConfig(const base::CommandLine & cmdline,Err * err)975 bool Setup::FillOtherConfig(const base::CommandLine& cmdline, Err* err) {
976 SourceDir current_dir("//");
977 Label root_target_label(current_dir, "");
978 std::vector<LabelPattern> root_patterns;
979
980 // Secondary source path, read from the config file if present.
981 // Read from the config file if present.
982 const Value* secondary_value =
983 dotfile_scope_.GetValue("secondary_source", true);
984 if (secondary_value) {
985 if (!secondary_value->VerifyTypeIs(Value::STRING, err)) {
986 return false;
987 }
988 build_settings_.SetSecondarySourcePath(
989 SourceDir(secondary_value->string_value()));
990 }
991
992 // Build file names.
993 const Value* build_file_extension_value =
994 dotfile_scope_.GetValue("build_file_extension", true);
995 if (build_file_extension_value) {
996 if (!build_file_extension_value->VerifyTypeIs(Value::STRING, err)) {
997 return false;
998 }
999
1000 std::string extension = build_file_extension_value->string_value();
1001 auto normalized_extension = UTF8ToFilePath(extension).value();
1002 if (normalized_extension.find_first_of(base::FilePath::kSeparators) !=
1003 base::FilePath::StringType::npos) {
1004 *err = Err(Location(), "Build file extension '" + extension +
1005 "' cannot " + "contain a path separator");
1006 return false;
1007 }
1008 loader_->set_build_file_extension(extension);
1009 }
1010
1011 // Ninja required version.
1012 const Value* ninja_required_version_value =
1013 dotfile_scope_.GetValue("ninja_required_version", true);
1014 if (ninja_required_version_value) {
1015 if (!ninja_required_version_value->VerifyTypeIs(Value::STRING, err)) {
1016 return false;
1017 }
1018 std::optional<Version> version =
1019 Version::FromString(ninja_required_version_value->string_value());
1020 if (!version) {
1021 Err(Location(), "Invalid Ninja version '" +
1022 ninja_required_version_value->string_value() + "'")
1023 .PrintToStdout();
1024 return false;
1025 }
1026 build_settings_.set_ninja_required_version(*version);
1027 }
1028
1029 // Root build file.
1030 if (cmdline.HasSwitch(switches::kRootTarget)) {
1031 auto switch_value = cmdline.GetSwitchValueString(switches::kRootTarget);
1032 Value root_value(nullptr, switch_value);
1033 root_target_label = Label::Resolve(current_dir, std::string_view(), Label(),
1034 root_value, err);
1035 if (err->has_error()) {
1036 return false;
1037 }
1038 if (dotfile_scope_.GetValue("root", true)) {
1039 // The "kRootTarget" switch overwrites the "root" variable in ".gn".
1040 dotfile_scope_.MarkUsed("root");
1041 }
1042 } else {
1043 const Value* root_value = dotfile_scope_.GetValue("root", true);
1044 if (root_value) {
1045 if (!root_value->VerifyTypeIs(Value::STRING, err)) {
1046 return false;
1047 }
1048
1049 root_target_label = Label::Resolve(current_dir, std::string_view(),
1050 Label(), *root_value, err);
1051 if (err->has_error()) {
1052 return false;
1053 }
1054 }
1055 }
1056
1057 if (cmdline.HasSwitch(switches::kRootPattern)) {
1058 auto& switches = cmdline.GetSwitches();
1059 for (auto it = switches.find(switches::kRootPattern);
1060 it != switches.end() && it->first == switches::kRootPattern; ++it) {
1061 std::string pattern = base::CommandLine::StringTypeToUTF8(it->second);
1062 LabelPattern pat = LabelPattern::GetPattern(
1063 SourceDir("//"), build_settings_.root_path_utf8(),
1064 Value(nullptr, pattern), err);
1065 if (err->has_error()) {
1066 err->AppendSubErr(
1067 Err(Location(),
1068 "for the command-line switch --root-pattern=" + pattern));
1069 return false;
1070 }
1071 if (!pat.toolchain().is_null()) {
1072 *err = Err(Location(),
1073 "Root pattern cannot have toolchain suffix: " + pattern);
1074 return false;
1075 }
1076 root_patterns.push_back(std::move(pat));
1077 }
1078 // Ensure GN does not complain about the .gn root_patterns value being
1079 // ignored if it is set.
1080 (void)dotfile_scope_.GetValue("root_patterns", true);
1081 } else {
1082 const Value* root_patterns_value =
1083 dotfile_scope_.GetValue("root_patterns", true);
1084 if (root_patterns_value) {
1085 if (!root_patterns_value->VerifyTypeIs(Value::LIST, err)) {
1086 return false;
1087 }
1088 for (const auto& pattern_value : root_patterns_value->list_value()) {
1089 if (!pattern_value.VerifyTypeIs(Value::STRING, err))
1090 return false;
1091
1092 LabelPattern pat = LabelPattern::GetPattern(
1093 SourceDir("//"), build_settings_.root_path_utf8(), pattern_value,
1094 err);
1095 if (err->has_error())
1096 return false;
1097 if (!pat.toolchain().is_null()) {
1098 *err =
1099 Err(pattern_value, "Root pattern cannot have toolchain suffix: " +
1100 pattern_value.string_value());
1101 return false;
1102 }
1103 root_patterns.push_back(std::move(pat));
1104 }
1105 }
1106 }
1107
1108 // Set the root build file here in order to take into account the values of
1109 // "build_file_extension" and "root".
1110 root_build_file_ = loader_->BuildFileForLabel(root_target_label);
1111 build_settings_.SetRootTargetLabel(root_target_label);
1112 build_settings_.SetRootPatterns(std::move(root_patterns));
1113
1114 // Build config file.
1115 const Value* build_config_value =
1116 dotfile_scope_.GetValue("buildconfig", true);
1117 if (!build_config_value) {
1118 Err(Location(), "No build config file.",
1119 "Your .gn file (\"" + FilePathToUTF8(dotfile_name_) +
1120 "\")\n"
1121 "didn't specify a \"buildconfig\" value.")
1122 .PrintToStdout();
1123 return false;
1124 } else if (!build_config_value->VerifyTypeIs(Value::STRING, err)) {
1125 return false;
1126 }
1127 build_settings_.set_build_config_file(
1128 SourceFile(build_config_value->string_value()));
1129
1130 // Targets to check.
1131 const Value* check_targets_value =
1132 dotfile_scope_.GetValue("check_targets", true);
1133 if (check_targets_value) {
1134 check_patterns_ = std::make_unique<std::vector<LabelPattern>>();
1135 ExtractListOfLabelPatterns(&build_settings_, *check_targets_value,
1136 current_dir, check_patterns_.get(), err);
1137 if (err->has_error()) {
1138 return false;
1139 }
1140 }
1141
1142 // Targets not to check.
1143 const Value* no_check_targets_value =
1144 dotfile_scope_.GetValue("no_check_targets", true);
1145 if (no_check_targets_value) {
1146 if (check_targets_value) {
1147 Err(Location(), "Conflicting check settings.",
1148 "Your .gn file (\"" + FilePathToUTF8(dotfile_name_) +
1149 "\")\n"
1150 "specified both check_targets and no_check_targets and at most "
1151 "one is allowed.")
1152 .PrintToStdout();
1153 return false;
1154 }
1155 no_check_patterns_ = std::make_unique<std::vector<LabelPattern>>();
1156 ExtractListOfLabelPatterns(&build_settings_, *no_check_targets_value,
1157 current_dir, no_check_patterns_.get(), err);
1158 if (err->has_error()) {
1159 return false;
1160 }
1161 }
1162
1163 const Value* check_system_includes_value =
1164 dotfile_scope_.GetValue("check_system_includes", true);
1165 if (check_system_includes_value) {
1166 if (!check_system_includes_value->VerifyTypeIs(Value::BOOLEAN, err)) {
1167 return false;
1168 }
1169 check_system_includes_ = check_system_includes_value->boolean_value();
1170 }
1171
1172 // Fill exec_script_whitelist.
1173 const Value* exec_script_whitelist_value =
1174 dotfile_scope_.GetValue("exec_script_whitelist", true);
1175 if (exec_script_whitelist_value) {
1176 // Fill the list of targets to check.
1177 if (!exec_script_whitelist_value->VerifyTypeIs(Value::LIST, err)) {
1178 return false;
1179 }
1180 std::unique_ptr<SourceFileSet> whitelist =
1181 std::make_unique<SourceFileSet>();
1182 for (const auto& item : exec_script_whitelist_value->list_value()) {
1183 if (!item.VerifyTypeIs(Value::STRING, err)) {
1184 return false;
1185 }
1186 whitelist->insert(current_dir.ResolveRelativeFile(item, err));
1187 if (err->has_error()) {
1188 return false;
1189 }
1190 }
1191 build_settings_.set_exec_script_whitelist(std::move(whitelist));
1192 }
1193
1194 // Fill optional default_args.
1195 const Value* default_args_value =
1196 dotfile_scope_.GetValue("default_args", true);
1197 if (default_args_value) {
1198 if (!default_args_value->VerifyTypeIs(Value::SCOPE, err)) {
1199 return false;
1200 }
1201
1202 default_args_ = default_args_value->scope_value();
1203 }
1204
1205 const Value* arg_file_template_value =
1206 dotfile_scope_.GetValue("arg_file_template", true);
1207 if (arg_file_template_value) {
1208 if (!arg_file_template_value->VerifyTypeIs(Value::STRING, err)) {
1209 return false;
1210 }
1211 SourceFile path(arg_file_template_value->string_value());
1212 build_settings_.set_arg_file_template_path(path);
1213 }
1214
1215 // No stamp files.
1216 const Value* no_stamp_files_value =
1217 dotfile_scope_.GetValue("no_stamp_files", true);
1218 if (no_stamp_files_value) {
1219 if (!no_stamp_files_value->VerifyTypeIs(Value::BOOLEAN, err)) {
1220 return false;
1221 }
1222 build_settings_.set_no_stamp_files(no_stamp_files_value->boolean_value());
1223 CHECK(!build_settings_.no_stamp_files())
1224 << "no_stamp_files does not work yet!";
1225 }
1226
1227 // Export compile commands.
1228 const Value* export_cc_value =
1229 dotfile_scope_.GetValue("export_compile_commands", true);
1230 if (export_cc_value) {
1231 if (!ExtractListOfLabelPatterns(&build_settings_, *export_cc_value,
1232 SourceDir("//"), &export_compile_commands_,
1233 err)) {
1234 return false;
1235 }
1236 }
1237
1238 // Append any additional export compile command patterns from the cmdline.
1239 for (const std::string& cur :
1240 cmdline.GetSwitchValueStrings(switches::kAddExportCompileCommands)) {
1241 LabelPattern pat = LabelPattern::GetPattern(
1242 SourceDir("//"), build_settings_.root_path_utf8(), Value(nullptr, cur),
1243 err);
1244 if (err->has_error()) {
1245 err->AppendSubErr(Err(
1246 Location(),
1247 "for the command-line switch --add-export-compile-commands=" + cur));
1248 return false;
1249 }
1250 export_compile_commands_.push_back(std::move(pat));
1251 }
1252
1253 return true;
1254 }
1255