• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/setup.h"
6 
7 #include <stdlib.h>
8 
9 #include <algorithm>
10 #include <sstream>
11 
12 #include "base/bind.h"
13 #include "base/command_line.h"
14 #include "base/file_util.h"
15 #include "base/files/file_path.h"
16 #include "base/process/launch.h"
17 #include "base/strings/string_split.h"
18 #include "base/strings/string_util.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "build/build_config.h"
21 #include "tools/gn/filesystem_utils.h"
22 #include "tools/gn/header_checker.h"
23 #include "tools/gn/input_file.h"
24 #include "tools/gn/parse_tree.h"
25 #include "tools/gn/parser.h"
26 #include "tools/gn/source_dir.h"
27 #include "tools/gn/source_file.h"
28 #include "tools/gn/standard_out.h"
29 #include "tools/gn/tokenizer.h"
30 #include "tools/gn/trace.h"
31 #include "tools/gn/value.h"
32 
33 #if defined(OS_WIN)
34 #include <windows.h>
35 #endif
36 
37 extern const char kDotfile_Help[] =
38     ".gn file\n"
39     "\n"
40     "  When gn starts, it will search the current directory and parent\n"
41     "  directories for a file called \".gn\". This indicates the source root.\n"
42     "  You can override this detection by using the --root command-line\n"
43     "  argument\n"
44     "\n"
45     "  The .gn file in the source root will be executed. The syntax is the\n"
46     "  same as a buildfile, but with very limited build setup-specific\n"
47     "  meaning.\n"
48     "\n"
49     "  If you specify --root, by default GN will look for the file .gn in\n"
50     "  that directory. If you want to specify a different file, you can\n"
51     "  additionally pass --dotfile:\n"
52     "\n"
53     "    gn gen out/Debug --root=/home/build --dotfile=/home/my_gn_file.gn\n"
54     "\n"
55     "Variables\n"
56     "\n"
57     "  buildconfig [required]\n"
58     "      Label of the build config file. This file will be used to set up\n"
59     "      the build file execution environment for each toolchain.\n"
60     "\n"
61     "  root [optional]\n"
62     "      Label of the root build target. The GN build will start by loading\n"
63     "      the build file containing this target name. This defaults to\n"
64     "      \"//:\" which will cause the file //BUILD.gn to be loaded.\n"
65     "\n"
66     "  secondary_source [optional]\n"
67     "      Label of an alternate directory tree to find input files. When\n"
68     "      searching for a BUILD.gn file (or the build config file discussed\n"
69     "      above), the file fill first be looked for in the source root.\n"
70     "      If it's not found, the secondary source root will be checked\n"
71     "      (which would contain a parallel directory hierarchy).\n"
72     "\n"
73     "      This behavior is intended to be used when BUILD.gn files can't be\n"
74     "      checked in to certain source directories for whatever reason.\n"
75     "\n"
76     "      The secondary source root must be inside the main source tree.\n"
77     "\n"
78     "Example .gn file contents\n"
79     "\n"
80     "  buildconfig = \"//build/config/BUILDCONFIG.gn\"\n"
81     "\n"
82     "  root = \"//:root\"\n"
83     "\n"
84     "  secondary_source = \"//build/config/temporary_buildfiles/\"\n";
85 
86 namespace {
87 
88 // More logging.
89 const char kSwitchVerbose[] = "v";
90 
91 // Set build args.
92 const char kSwitchArgs[] = "args";
93 
94 // Set root dir.
95 const char kSwitchRoot[] = "root";
96 
97 // Set dotfile name.
98 const char kSwitchDotfile[] = "dotfile";
99 
100 // Enable timing.
101 const char kTimeSwitch[] = "time";
102 
103 const char kTracelogSwitch[] = "tracelog";
104 
105 const base::FilePath::CharType kGnFile[] = FILE_PATH_LITERAL(".gn");
106 
FindDotFile(const base::FilePath & current_dir)107 base::FilePath FindDotFile(const base::FilePath& current_dir) {
108   base::FilePath try_this_file = current_dir.Append(kGnFile);
109   if (base::PathExists(try_this_file))
110     return try_this_file;
111 
112   base::FilePath with_no_slash = current_dir.StripTrailingSeparators();
113   base::FilePath up_one_dir = with_no_slash.DirName();
114   if (up_one_dir == current_dir)
115     return base::FilePath();  // Got to the top.
116 
117   return FindDotFile(up_one_dir);
118 }
119 
120 // Called on any thread. Post the item to the builder on the main thread.
ItemDefinedCallback(base::MessageLoop * main_loop,scoped_refptr<Builder> builder,scoped_ptr<Item> item)121 void ItemDefinedCallback(base::MessageLoop* main_loop,
122                          scoped_refptr<Builder> builder,
123                          scoped_ptr<Item> item) {
124   DCHECK(item);
125   main_loop->PostTask(FROM_HERE, base::Bind(&Builder::ItemDefined, builder,
126                                             base::Passed(&item)));
127 }
128 
DecrementWorkCount()129 void DecrementWorkCount() {
130   g_scheduler->DecrementWorkCount();
131 }
132 
133 }  // namespace
134 
135 // CommonSetup -----------------------------------------------------------------
136 
137 const char CommonSetup::kBuildArgFileName[] = "args.gn";
138 
CommonSetup()139 CommonSetup::CommonSetup()
140     : build_settings_(),
141       loader_(new LoaderImpl(&build_settings_)),
142       builder_(new Builder(loader_.get())),
143       root_build_file_("//BUILD.gn"),
144       check_for_bad_items_(true),
145       check_for_unused_overrides_(true),
146       check_public_headers_(false) {
147   loader_->set_complete_callback(base::Bind(&DecrementWorkCount));
148 }
149 
CommonSetup(const CommonSetup & other)150 CommonSetup::CommonSetup(const CommonSetup& other)
151     : build_settings_(other.build_settings_),
152       loader_(new LoaderImpl(&build_settings_)),
153       builder_(new Builder(loader_.get())),
154       root_build_file_(other.root_build_file_),
155       check_for_bad_items_(other.check_for_bad_items_),
156       check_for_unused_overrides_(other.check_for_unused_overrides_),
157       check_public_headers_(other.check_public_headers_) {
158   loader_->set_complete_callback(base::Bind(&DecrementWorkCount));
159 }
160 
~CommonSetup()161 CommonSetup::~CommonSetup() {
162 }
163 
RunPreMessageLoop()164 void CommonSetup::RunPreMessageLoop() {
165   // Load the root build file.
166   loader_->Load(root_build_file_, LocationRange(), Label());
167 
168   // Will be decremented with the loader is drained.
169   g_scheduler->IncrementWorkCount();
170 }
171 
RunPostMessageLoop()172 bool CommonSetup::RunPostMessageLoop() {
173   Err err;
174   if (check_for_bad_items_) {
175     if (!builder_->CheckForBadItems(&err)) {
176       err.PrintToStdout();
177       return false;
178     }
179   }
180 
181   if (check_for_unused_overrides_) {
182     if (!build_settings_.build_args().VerifyAllOverridesUsed(&err)) {
183       // TODO(brettw) implement a system of warnings. Until we have a better
184       // system, print the error but don't return failure.
185       err.PrintToStdout();
186       return true;
187     }
188   }
189 
190   if (check_public_headers_) {
191     std::vector<const Target*> targets = builder_->GetAllResolvedTargets();
192     scoped_refptr<HeaderChecker> header_checker(
193         new HeaderChecker(&build_settings_, targets));
194 
195     std::vector<Err> header_errors;
196     header_checker->Run(&header_errors);
197     for (size_t i = 0; i < header_errors.size(); i++) {
198       if (i > 0)
199         OutputString("___________________\n", DECORATION_YELLOW);
200       header_errors[i].PrintToStdout();
201     }
202     if (!header_errors.empty())
203       return false;
204   }
205 
206   // Write out tracing and timing if requested.
207   const CommandLine* cmdline = CommandLine::ForCurrentProcess();
208   if (cmdline->HasSwitch(kTimeSwitch))
209     PrintLongHelp(SummarizeTraces());
210   if (cmdline->HasSwitch(kTracelogSwitch))
211     SaveTraces(cmdline->GetSwitchValuePath(kTracelogSwitch));
212 
213   return true;
214 }
215 
216 // Setup -----------------------------------------------------------------------
217 
Setup()218 Setup::Setup()
219     : CommonSetup(),
220       empty_settings_(&empty_build_settings_, std::string()),
221       dotfile_scope_(&empty_settings_),
222       fill_arguments_(true) {
223   empty_settings_.set_toolchain_label(Label());
224   build_settings_.set_item_defined_callback(
225       base::Bind(&ItemDefinedCallback, scheduler_.main_loop(), builder_));
226 
227   // The scheduler's main loop wasn't created when the Loader was created, so
228   // we need to set it now.
229   loader_->set_main_loop(scheduler_.main_loop());
230 }
231 
~Setup()232 Setup::~Setup() {
233 }
234 
DoSetup(const std::string & build_dir)235 bool Setup::DoSetup(const std::string& build_dir) {
236   CommandLine* cmdline = CommandLine::ForCurrentProcess();
237 
238   scheduler_.set_verbose_logging(cmdline->HasSwitch(kSwitchVerbose));
239   if (cmdline->HasSwitch(kTimeSwitch) ||
240       cmdline->HasSwitch(kTracelogSwitch))
241     EnableTracing();
242 
243   ScopedTrace setup_trace(TraceItem::TRACE_SETUP, "DoSetup");
244 
245   if (!FillSourceDir(*cmdline))
246     return false;
247   if (!RunConfigFile())
248     return false;
249   if (!FillOtherConfig(*cmdline))
250     return false;
251   if (!FillBuildDir(build_dir))  // Must be after FillSourceDir to resolve.
252     return false;
253   if (fill_arguments_) {
254     if (!FillArguments(*cmdline))
255       return false;
256   }
257   FillPythonPath();
258 
259   return true;
260 }
261 
Run()262 bool Setup::Run() {
263   RunPreMessageLoop();
264   if (!scheduler_.Run())
265     return false;
266   return RunPostMessageLoop();
267 }
268 
GetScheduler()269 Scheduler* Setup::GetScheduler() {
270   return &scheduler_;
271 }
272 
GetBuildArgFile() const273 SourceFile Setup::GetBuildArgFile() const {
274   return SourceFile(build_settings_.build_dir().value() + kBuildArgFileName);
275 }
276 
FillArguments(const CommandLine & cmdline)277 bool Setup::FillArguments(const CommandLine& cmdline) {
278   // Use the args on the command line if specified, and save them. Do this even
279   // if the list is empty (this means clear any defaults).
280   if (cmdline.HasSwitch(kSwitchArgs)) {
281     if (!FillArgsFromCommandLine(cmdline.GetSwitchValueASCII(kSwitchArgs)))
282       return false;
283     SaveArgsToFile();
284     return true;
285   }
286 
287   // No command line args given, use the arguments from the build dir (if any).
288   return FillArgsFromFile();
289 }
290 
FillArgsFromCommandLine(const std::string & args)291 bool Setup::FillArgsFromCommandLine(const std::string& args) {
292   args_input_file_.reset(new InputFile(SourceFile()));
293   args_input_file_->SetContents(args);
294   args_input_file_->set_friendly_name("the command-line \"--args\"");
295   return FillArgsFromArgsInputFile();
296 }
297 
FillArgsFromFile()298 bool Setup::FillArgsFromFile() {
299   ScopedTrace setup_trace(TraceItem::TRACE_SETUP, "Load args file");
300 
301   SourceFile build_arg_source_file = GetBuildArgFile();
302   base::FilePath build_arg_file =
303       build_settings_.GetFullPath(build_arg_source_file);
304 
305   std::string contents;
306   if (!base::ReadFileToString(build_arg_file, &contents))
307     return true;  // File doesn't exist, continue with default args.
308 
309   // Add a dependency on the build arguments file. If this changes, we want
310   // to re-generate the build.
311   g_scheduler->AddGenDependency(build_arg_file);
312 
313   if (contents.empty())
314     return true;  // Empty file, do nothing.
315 
316   args_input_file_.reset(new InputFile(build_arg_source_file));
317   args_input_file_->SetContents(contents);
318   args_input_file_->set_friendly_name(
319       "build arg file (use \"gn args <out_dir>\" to edit)");
320 
321   setup_trace.Done();  // Only want to count the load as part of the trace.
322   return FillArgsFromArgsInputFile();
323 }
324 
FillArgsFromArgsInputFile()325 bool Setup::FillArgsFromArgsInputFile() {
326   ScopedTrace setup_trace(TraceItem::TRACE_SETUP, "Parse args");
327 
328   Err err;
329   args_tokens_ = Tokenizer::Tokenize(args_input_file_.get(), &err);
330   if (err.has_error()) {
331     err.PrintToStdout();
332     return false;
333   }
334 
335   args_root_ = Parser::Parse(args_tokens_, &err);
336   if (err.has_error()) {
337     err.PrintToStdout();
338     return false;
339   }
340 
341   Scope arg_scope(&empty_settings_);
342   args_root_->AsBlock()->ExecuteBlockInScope(&arg_scope, &err);
343   if (err.has_error()) {
344     err.PrintToStdout();
345     return false;
346   }
347 
348   // Save the result of the command args.
349   Scope::KeyValueMap overrides;
350   arg_scope.GetCurrentScopeValues(&overrides);
351   build_settings_.build_args().AddArgOverrides(overrides);
352   return true;
353 }
354 
SaveArgsToFile()355 bool Setup::SaveArgsToFile() {
356   ScopedTrace setup_trace(TraceItem::TRACE_SETUP, "Save args file");
357 
358   Scope::KeyValueMap args = build_settings_.build_args().GetAllOverrides();
359 
360   std::ostringstream stream;
361   for (Scope::KeyValueMap::const_iterator i = args.begin();
362        i != args.end(); ++i) {
363     stream << i->first.as_string() << " = " << i->second.ToString(true);
364     stream << std::endl;
365   }
366 
367   // For the first run, the build output dir might not be created yet, so do
368   // that so we can write a file into it. Ignore errors, we'll catch the error
369   // when we try to write a file to it below.
370   base::FilePath build_arg_file =
371       build_settings_.GetFullPath(GetBuildArgFile());
372   base::CreateDirectory(build_arg_file.DirName());
373 
374   std::string contents = stream.str();
375 #if defined(OS_WIN)
376   // Use Windows lineendings for this file since it will often open in
377   // Notepad which can't handle Unix ones.
378   ReplaceSubstringsAfterOffset(&contents, 0, "\n", "\r\n");
379 #endif
380   if (base::WriteFile(build_arg_file, contents.c_str(),
381       static_cast<int>(contents.size())) == -1) {
382     Err(Location(), "Args file could not be written.",
383       "The file is \"" + FilePathToUTF8(build_arg_file) +
384         "\"").PrintToStdout();
385     return false;
386   }
387 
388   // Add a dependency on the build arguments file. If this changes, we want
389   // to re-generate the build.
390   g_scheduler->AddGenDependency(build_arg_file);
391 
392   return true;
393 }
394 
FillSourceDir(const CommandLine & cmdline)395 bool Setup::FillSourceDir(const CommandLine& cmdline) {
396   // Find the .gn file.
397   base::FilePath root_path;
398 
399   // Prefer the command line args to the config file.
400   base::FilePath relative_root_path = cmdline.GetSwitchValuePath(kSwitchRoot);
401   if (!relative_root_path.empty()) {
402     root_path = base::MakeAbsoluteFilePath(relative_root_path);
403     if (root_path.empty()) {
404       Err(Location(), "Root source path not found.",
405           "The path \"" + FilePathToUTF8(relative_root_path) +
406           "\" doesn't exist.").PrintToStdout();
407       return false;
408     }
409 
410     // When --root is specified, an alternate --dotfile can also be set.
411     // --dotfile should be a real file path and not a "//foo" source-relative
412     // path.
413     base::FilePath dot_file_path = cmdline.GetSwitchValuePath(kSwitchDotfile);
414     if (dot_file_path.empty()) {
415       dotfile_name_ = root_path.Append(kGnFile);
416     } else {
417       dotfile_name_ = base::MakeAbsoluteFilePath(dot_file_path);
418       if (dotfile_name_.empty()) {
419         Err(Location(), "Could not load dotfile.",
420             "The file \"" + FilePathToUTF8(dot_file_path) +
421             "\" cound't be loaded.").PrintToStdout();
422         return false;
423       }
424     }
425   } else {
426     // In the default case, look for a dotfile and that also tells us where the
427     // source root is.
428     base::FilePath cur_dir;
429     base::GetCurrentDirectory(&cur_dir);
430     dotfile_name_ = FindDotFile(cur_dir);
431     if (dotfile_name_.empty()) {
432       Err(Location(), "Can't find source root.",
433           "I could not find a \".gn\" file in the current directory or any "
434           "parent,\nand the --root command-line argument was not specified.")
435           .PrintToStdout();
436       return false;
437     }
438     root_path = dotfile_name_.DirName();
439   }
440 
441   if (scheduler_.verbose_logging())
442     scheduler_.Log("Using source root", FilePathToUTF8(root_path));
443   build_settings_.SetRootPath(root_path);
444 
445   return true;
446 }
447 
FillBuildDir(const std::string & build_dir)448 bool Setup::FillBuildDir(const std::string& build_dir) {
449   SourceDir resolved =
450       SourceDirForCurrentDirectory(build_settings_.root_path()).
451           ResolveRelativeDir(build_dir);
452   if (resolved.is_null()) {
453     Err(Location(), "Couldn't resolve build directory.",
454         "The build directory supplied (\"" + build_dir + "\") was not valid.").
455         PrintToStdout();
456     return false;
457   }
458 
459   if (scheduler_.verbose_logging())
460     scheduler_.Log("Using build dir", resolved.value());
461   build_settings_.SetBuildDir(resolved);
462   return true;
463 }
464 
FillPythonPath()465 void Setup::FillPythonPath() {
466   // Trace this since it tends to be a bit slow on Windows.
467   ScopedTrace setup_trace(TraceItem::TRACE_SETUP, "Fill Python Path");
468 #if defined(OS_WIN)
469   // Find Python on the path so we can use the absolute path in the build.
470   const base::char16 kGetPython[] =
471       L"cmd.exe /c python -c \"import sys; print sys.executable\"";
472   std::string python_path;
473   if (base::GetAppOutput(kGetPython, &python_path)) {
474     base::TrimWhitespaceASCII(python_path, base::TRIM_ALL, &python_path);
475     if (scheduler_.verbose_logging())
476       scheduler_.Log("Found python", python_path);
477   } else {
478     scheduler_.Log("WARNING", "Could not find python on path, using "
479         "just \"python.exe\"");
480     python_path = "python.exe";
481   }
482   build_settings_.set_python_path(base::FilePath(base::UTF8ToUTF16(python_path))
483                                       .NormalizePathSeparatorsTo('/'));
484 #else
485   build_settings_.set_python_path(base::FilePath("python"));
486 #endif
487 }
488 
RunConfigFile()489 bool Setup::RunConfigFile() {
490   if (scheduler_.verbose_logging())
491     scheduler_.Log("Got dotfile", FilePathToUTF8(dotfile_name_));
492 
493   dotfile_input_file_.reset(new InputFile(SourceFile("//.gn")));
494   if (!dotfile_input_file_->Load(dotfile_name_)) {
495     Err(Location(), "Could not load dotfile.",
496         "The file \"" + FilePathToUTF8(dotfile_name_) + "\" cound't be loaded")
497         .PrintToStdout();
498     return false;
499   }
500 
501   Err err;
502   dotfile_tokens_ = Tokenizer::Tokenize(dotfile_input_file_.get(), &err);
503   if (err.has_error()) {
504     err.PrintToStdout();
505     return false;
506   }
507 
508   dotfile_root_ = Parser::Parse(dotfile_tokens_, &err);
509   if (err.has_error()) {
510     err.PrintToStdout();
511     return false;
512   }
513 
514   dotfile_root_->AsBlock()->ExecuteBlockInScope(&dotfile_scope_, &err);
515   if (err.has_error()) {
516     err.PrintToStdout();
517     return false;
518   }
519 
520   return true;
521 }
522 
FillOtherConfig(const CommandLine & cmdline)523 bool Setup::FillOtherConfig(const CommandLine& cmdline) {
524   Err err;
525 
526   // Secondary source path, read from the config file if present.
527   // Read from the config file if present.
528   const Value* secondary_value =
529       dotfile_scope_.GetValue("secondary_source", true);
530   if (secondary_value) {
531     if (!secondary_value->VerifyTypeIs(Value::STRING, &err)) {
532       err.PrintToStdout();
533       return false;
534     }
535     build_settings_.SetSecondarySourcePath(
536         SourceDir(secondary_value->string_value()));
537   }
538 
539   // Root build file.
540   const Value* root_value = dotfile_scope_.GetValue("root", true);
541   if (root_value) {
542     if (!root_value->VerifyTypeIs(Value::STRING, &err)) {
543       err.PrintToStdout();
544       return false;
545     }
546 
547     Label root_target_label =
548         Label::Resolve(SourceDir("//"), Label(), *root_value, &err);
549     if (err.has_error()) {
550       err.PrintToStdout();
551       return false;
552     }
553 
554     root_build_file_ = Loader::BuildFileForLabel(root_target_label);
555   }
556 
557   // Build config file.
558   const Value* build_config_value =
559       dotfile_scope_.GetValue("buildconfig", true);
560   if (!build_config_value) {
561     Err(Location(), "No build config file.",
562         "Your .gn file (\"" + FilePathToUTF8(dotfile_name_) + "\")\n"
563         "didn't specify a \"buildconfig\" value.").PrintToStdout();
564     return false;
565   } else if (!build_config_value->VerifyTypeIs(Value::STRING, &err)) {
566     err.PrintToStdout();
567     return false;
568   }
569   build_settings_.set_build_config_file(
570       SourceFile(build_config_value->string_value()));
571 
572   return true;
573 }
574 
575 // DependentSetup --------------------------------------------------------------
576 
DependentSetup(Setup * derive_from)577 DependentSetup::DependentSetup(Setup* derive_from)
578     : CommonSetup(*derive_from),
579       scheduler_(derive_from->GetScheduler()) {
580   build_settings_.set_item_defined_callback(
581       base::Bind(&ItemDefinedCallback, scheduler_->main_loop(), builder_));
582 }
583 
DependentSetup(DependentSetup * derive_from)584 DependentSetup::DependentSetup(DependentSetup* derive_from)
585     : CommonSetup(*derive_from),
586       scheduler_(derive_from->GetScheduler()) {
587   build_settings_.set_item_defined_callback(
588       base::Bind(&ItemDefinedCallback, scheduler_->main_loop(), builder_));
589 }
590 
~DependentSetup()591 DependentSetup::~DependentSetup() {
592 }
593 
GetScheduler()594 Scheduler* DependentSetup::GetScheduler() {
595   return scheduler_;
596 }
597 
RunPreMessageLoop()598 void DependentSetup::RunPreMessageLoop() {
599   CommonSetup::RunPreMessageLoop();
600 }
601 
RunPostMessageLoop()602 bool DependentSetup::RunPostMessageLoop() {
603   return CommonSetup::RunPostMessageLoop();
604 }
605 
606