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/commands.h"
6
7 #include "base/command_line.h"
8 #include "base/environment.h"
9 #include "base/strings/string_split.h"
10 #include "base/values.h"
11 #include "gn/builder.h"
12 #include "gn/filesystem_utils.h"
13 #include "gn/item.h"
14 #include "gn/label.h"
15 #include "gn/label_pattern.h"
16 #include "gn/setup.h"
17 #include "gn/standard_out.h"
18 #include "gn/target.h"
19 #include "util/build_config.h"
20
21 namespace commands {
22
23 namespace {
24
25 // Like above but the input string can be a pattern that matches multiple
26 // targets. If the input does not parse as a pattern, prints and error and
27 // returns false. If the pattern is valid, fills the vector (which might be
28 // empty if there are no matches) and returns true.
29 //
30 // If all_toolchains is false, a pattern with an unspecified toolchain will
31 // match the default toolchain only. If true, all toolchains will be matched.
ResolveTargetsFromCommandLinePattern(Setup * setup,const std::string & label_pattern,bool all_toolchains,std::vector<const Target * > * matches)32 bool ResolveTargetsFromCommandLinePattern(Setup* setup,
33 const std::string& label_pattern,
34 bool all_toolchains,
35 std::vector<const Target*>* matches) {
36 Value pattern_value(nullptr, label_pattern);
37
38 Err err;
39 LabelPattern pattern = LabelPattern::GetPattern(
40 SourceDirForCurrentDirectory(setup->build_settings().root_path()),
41 setup->build_settings().root_path_utf8(), pattern_value, &err);
42 if (err.has_error()) {
43 err.PrintToStdout();
44 return false;
45 }
46
47 if (!all_toolchains) {
48 // By default a pattern with an empty toolchain will match all toolchains.
49 // If the caller wants to default to the main toolchain only, set it
50 // explicitly.
51 if (pattern.toolchain().is_null()) {
52 // No explicit toolchain set.
53 pattern.set_toolchain(setup->loader()->default_toolchain_label());
54 }
55 }
56
57 std::vector<LabelPattern> pattern_vector;
58 pattern_vector.push_back(pattern);
59 FilterTargetsByPatterns(setup->builder().GetAllResolvedTargets(),
60 pattern_vector, matches);
61 return true;
62 }
63
64 // If there's an error, it will be printed and false will be returned.
ResolveStringFromCommandLineInput(Setup * setup,const SourceDir & current_dir,const std::string & input,bool all_toolchains,UniqueVector<const Target * > * target_matches,UniqueVector<const Config * > * config_matches,UniqueVector<const Toolchain * > * toolchain_matches,UniqueVector<SourceFile> * file_matches)65 bool ResolveStringFromCommandLineInput(
66 Setup* setup,
67 const SourceDir& current_dir,
68 const std::string& input,
69 bool all_toolchains,
70 UniqueVector<const Target*>* target_matches,
71 UniqueVector<const Config*>* config_matches,
72 UniqueVector<const Toolchain*>* toolchain_matches,
73 UniqueVector<SourceFile>* file_matches) {
74 if (LabelPattern::HasWildcard(input)) {
75 // For now, only match patterns against targets. It might be nice in the
76 // future to allow the user to specify which types of things they want to
77 // match, but it should probably only match targets by default.
78 std::vector<const Target*> target_match_vector;
79 if (!ResolveTargetsFromCommandLinePattern(setup, input, all_toolchains,
80 &target_match_vector))
81 return false;
82 for (const Target* target : target_match_vector)
83 target_matches->push_back(target);
84 return true;
85 }
86
87 // Try to figure out what this thing is.
88 Err err;
89 Label label = Label::Resolve(
90 current_dir, setup->build_settings().root_path_utf8(),
91 setup->loader()->default_toolchain_label(), Value(nullptr, input), &err);
92 if (err.has_error()) {
93 // Not a valid label, assume this must be a file.
94 err = Err();
95 file_matches->push_back(current_dir.ResolveRelativeFile(
96 Value(nullptr, input), &err, setup->build_settings().root_path_utf8()));
97 if (err.has_error()) {
98 err.PrintToStdout();
99 return false;
100 }
101 return true;
102 }
103
104 const Item* item = setup->builder().GetItem(label);
105 if (item) {
106 if (const Config* as_config = item->AsConfig())
107 config_matches->push_back(as_config);
108 else if (const Target* as_target = item->AsTarget())
109 target_matches->push_back(as_target);
110 else if (const Toolchain* as_toolchain = item->AsToolchain())
111 toolchain_matches->push_back(as_toolchain);
112 } else {
113 // Not an item, assume this must be a file.
114 file_matches->push_back(current_dir.ResolveRelativeFile(
115 Value(nullptr, input), &err, setup->build_settings().root_path_utf8()));
116 if (err.has_error()) {
117 err.PrintToStdout();
118 return false;
119 }
120 }
121
122 return true;
123 }
124
125 enum TargetPrintingMode {
126 TARGET_PRINT_BUILDFILE,
127 TARGET_PRINT_LABEL,
128 TARGET_PRINT_OUTPUT,
129 };
130
131 // Retrieves the target printing mode based on the command line flags for the
132 // current process. Returns true on success. On error, prints a message to the
133 // console and returns false.
GetTargetPrintingMode(TargetPrintingMode * mode)134 bool GetTargetPrintingMode(TargetPrintingMode* mode) {
135 std::string switch_key = "as";
136 const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
137
138 if (!cmdline->HasSwitch(switch_key)) {
139 // Default to labels.
140 *mode = TARGET_PRINT_LABEL;
141 return true;
142 }
143
144 std::string value = cmdline->GetSwitchValueASCII(switch_key);
145 if (value == "buildfile") {
146 *mode = TARGET_PRINT_BUILDFILE;
147 return true;
148 }
149 if (value == "label") {
150 *mode = TARGET_PRINT_LABEL;
151 return true;
152 }
153 if (value == "output") {
154 *mode = TARGET_PRINT_OUTPUT;
155 return true;
156 }
157
158 Err(Location(), "Invalid value for \"--as\".",
159 "I was expecting \"buildfile\", \"label\", or \"output\" but you\n"
160 "said \"" +
161 value + "\".")
162 .PrintToStdout();
163 return false;
164 }
165
166 // Returns the target type filter based on the command line flags for the
167 // current process. Returns true on success. On error, prints a message to the
168 // console and returns false.
169 //
170 // Target::UNKNOWN will be set if there is no filter. Target::ACTION_FOREACH
171 // will never be returned. Code applying the filters should apply Target::ACTION
172 // to both ACTION and ACTION_FOREACH.
GetTargetTypeFilter(Target::OutputType * type)173 bool GetTargetTypeFilter(Target::OutputType* type) {
174 std::string switch_key = "type";
175 const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
176
177 if (!cmdline->HasSwitch(switch_key)) {
178 // Default to unknown -> no filtering.
179 *type = Target::UNKNOWN;
180 return true;
181 }
182
183 std::string value = cmdline->GetSwitchValueASCII(switch_key);
184 if (value == "group") {
185 *type = Target::GROUP;
186 return true;
187 }
188 if (value == "executable") {
189 *type = Target::EXECUTABLE;
190 return true;
191 }
192 if (value == "shared_library") {
193 *type = Target::SHARED_LIBRARY;
194 return true;
195 }
196 if (value == "loadable_module") {
197 *type = Target::LOADABLE_MODULE;
198 return true;
199 }
200 if (value == "static_library") {
201 *type = Target::STATIC_LIBRARY;
202 return true;
203 }
204 if (value == "source_set") {
205 *type = Target::SOURCE_SET;
206 return true;
207 }
208 if (value == "copy") {
209 *type = Target::COPY_FILES;
210 return true;
211 }
212 if (value == "action") {
213 *type = Target::ACTION;
214 return true;
215 }
216
217 Err(Location(), "Invalid value for \"--type\".").PrintToStdout();
218 return false;
219 }
220
221 // Applies any testonly filtering specified on the command line to the given
222 // target set. On failure, prints an error and returns false.
ApplyTestonlyFilter(std::vector<const Target * > * targets)223 bool ApplyTestonlyFilter(std::vector<const Target*>* targets) {
224 const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
225 std::string testonly_key = "testonly";
226
227 if (targets->empty() || !cmdline->HasSwitch(testonly_key))
228 return true;
229
230 std::string testonly_value = cmdline->GetSwitchValueASCII(testonly_key);
231 bool testonly = false;
232 if (testonly_value == "true") {
233 testonly = true;
234 } else if (testonly_value != "false") {
235 Err(Location(), "Bad value for --testonly.",
236 "I was expecting --testonly=true or --testonly=false.")
237 .PrintToStdout();
238 return false;
239 }
240
241 // Filter into a copy of the vector, then replace the output.
242 std::vector<const Target*> result;
243 result.reserve(targets->size());
244
245 for (const Target* target : *targets) {
246 if (target->testonly() == testonly)
247 result.push_back(target);
248 }
249
250 *targets = std::move(result);
251 return true;
252 }
253
254 // Applies any target type filtering specified on the command line to the given
255 // target set. On failure, prints an error and returns false.
ApplyTypeFilter(std::vector<const Target * > * targets)256 bool ApplyTypeFilter(std::vector<const Target*>* targets) {
257 Target::OutputType type = Target::UNKNOWN;
258 if (!GetTargetTypeFilter(&type))
259 return false;
260 if (targets->empty() || type == Target::UNKNOWN)
261 return true; // Nothing to filter out.
262
263 // Filter into a copy of the vector, then replace the output.
264 std::vector<const Target*> result;
265 result.reserve(targets->size());
266
267 for (const Target* target : *targets) {
268 // Make "action" also apply to ACTION_FOREACH.
269 if (target->output_type() == type ||
270 (type == Target::ACTION &&
271 target->output_type() == Target::ACTION_FOREACH))
272 result.push_back(target);
273 }
274
275 *targets = std::move(result);
276 return true;
277 }
278
279 // Returns the file path generating this item.
BuildFileForItem(const Item * item)280 base::FilePath BuildFileForItem(const Item* item) {
281 return item->defined_from()->GetRange().begin().file()->physical_name();
282 }
283
PrintTargetsAsBuildfiles(const std::vector<const Target * > & targets,base::ListValue * out)284 void PrintTargetsAsBuildfiles(const std::vector<const Target*>& targets,
285 base::ListValue* out) {
286 // Output the set of unique source files.
287 std::set<std::string> unique_files;
288 for (const Target* target : targets)
289 unique_files.insert(FilePathToUTF8(BuildFileForItem(target)));
290
291 for (const std::string& file : unique_files) {
292 out->AppendString(file);
293 }
294 }
295
PrintTargetsAsLabels(const std::vector<const Target * > & targets,base::ListValue * out)296 void PrintTargetsAsLabels(const std::vector<const Target*>& targets,
297 base::ListValue* out) {
298 // Putting the labels into a set automatically sorts them for us.
299 std::set<Label> unique_labels;
300 for (auto* target : targets)
301 unique_labels.insert(target->label());
302
303 // Grab the label of the default toolchain from the first target.
304 Label default_tc_label = targets[0]->settings()->default_toolchain_label();
305
306 for (const Label& label : unique_labels) {
307 // Print toolchain only for ones not in the default toolchain.
308 out->AppendString(label.GetUserVisibleName(label.GetToolchainLabel() !=
309 default_tc_label));
310 }
311 }
312
PrintTargetsAsOutputs(const std::vector<const Target * > & targets,base::ListValue * out)313 void PrintTargetsAsOutputs(const std::vector<const Target*>& targets,
314 base::ListValue* out) {
315 if (targets.empty())
316 return;
317
318 // Grab the build settings from a random target.
319 const BuildSettings* build_settings =
320 targets[0]->settings()->build_settings();
321
322 for (const Target* target : targets) {
323 // Use the link output file if there is one, otherwise fall back to the
324 // dependency output file (for actions, for example).
325 OutputFile output_file = target->link_output_file();
326 if (output_file.value().empty())
327 output_file = target->dependency_output_file();
328
329 SourceFile output_as_source = output_file.AsSourceFile(build_settings);
330 std::string result =
331 RebasePath(output_as_source.value(), build_settings->build_dir(),
332 build_settings->root_path_utf8());
333 out->AppendString(result);
334 }
335 }
336
337 #if defined(OS_WIN)
338 // Git bash will remove the first "/" in "//" paths
339 // This also happens for labels assigned to command line parameters, e.g.
340 // --filters
341 // Fix "//" paths, but not absolute and relative paths
FixGitBashLabelEdit(const std::string & label)342 inline std::string FixGitBashLabelEdit(const std::string& label) {
343 static std::unique_ptr<base::Environment> git_bash_env;
344 if (!git_bash_env)
345 git_bash_env = base::Environment::Create();
346
347 std::string temp_label(label);
348
349 if (git_bash_env->HasVar(
350 "MSYSTEM") && // Only for MinGW based shells like Git Bash
351 temp_label[0] == '/' && // Only fix for //foo paths, not /f:oo paths
352 (temp_label.length() < 2 ||
353 (temp_label[1] != '/' &&
354 (temp_label.length() < 3 || temp_label[1] != ':'))))
355 temp_label.insert(0, "/");
356 return temp_label;
357 }
358 #else
359 // Only repair on Windows
FixGitBashLabelEdit(const std::string & label)360 inline std::string FixGitBashLabelEdit(const std::string& label) {
361 return label;
362 }
363 #endif
364
365 } // namespace
366
CommandInfo()367 CommandInfo::CommandInfo()
368 : help_short(nullptr), help(nullptr), runner(nullptr) {}
369
CommandInfo(const char * in_help_short,const char * in_help,CommandRunner in_runner)370 CommandInfo::CommandInfo(const char* in_help_short,
371 const char* in_help,
372 CommandRunner in_runner)
373 : help_short(in_help_short), help(in_help), runner(in_runner) {}
374
GetCommands()375 const CommandInfoMap& GetCommands() {
376 static CommandInfoMap info_map;
377 if (info_map.empty()) {
378 #define INSERT_COMMAND(cmd) \
379 info_map[k##cmd] = CommandInfo(k##cmd##_HelpShort, k##cmd##_Help, &Run##cmd);
380
381 INSERT_COMMAND(Analyze)
382 INSERT_COMMAND(Args)
383 INSERT_COMMAND(Check)
384 INSERT_COMMAND(Clean)
385 INSERT_COMMAND(Desc)
386 INSERT_COMMAND(Gen)
387 INSERT_COMMAND(Format)
388 INSERT_COMMAND(Help)
389 INSERT_COMMAND(Meta)
390 INSERT_COMMAND(Ls)
391 INSERT_COMMAND(Path)
392 INSERT_COMMAND(Refs)
393
394 #undef INSERT_COMMAND
395 }
396 return info_map;
397 }
398
ResolveTargetFromCommandLineString(Setup * setup,const std::string & label_string)399 const Target* ResolveTargetFromCommandLineString(
400 Setup* setup,
401 const std::string& label_string) {
402 // Need to resolve the label after we know the default toolchain.
403 Label default_toolchain = setup->loader()->default_toolchain_label();
404 Value arg_value(nullptr, FixGitBashLabelEdit(label_string));
405 Err err;
406 Label label = Label::Resolve(
407 SourceDirForCurrentDirectory(setup->build_settings().root_path()),
408 setup->build_settings().root_path_utf8(), default_toolchain, arg_value,
409 &err);
410 if (err.has_error()) {
411 err.PrintToStdout();
412 return nullptr;
413 }
414
415 const Item* item = setup->builder().GetItem(label);
416 if (!item) {
417 Err(Location(), "Label not found.",
418 label.GetUserVisibleName(false) + " not found.")
419 .PrintToStdout();
420 return nullptr;
421 }
422
423 const Target* target = item->AsTarget();
424 if (!target) {
425 Err(Location(), "Not a target.",
426 "The \"" + label.GetUserVisibleName(false) +
427 "\" thing\n"
428 "is not a target. Somebody should probably implement this command "
429 "for "
430 "other\nitem types.")
431 .PrintToStdout();
432 return nullptr;
433 }
434
435 return target;
436 }
437
ResolveFromCommandLineInput(Setup * setup,const std::vector<std::string> & input,bool all_toolchains,UniqueVector<const Target * > * target_matches,UniqueVector<const Config * > * config_matches,UniqueVector<const Toolchain * > * toolchain_matches,UniqueVector<SourceFile> * file_matches)438 bool ResolveFromCommandLineInput(
439 Setup* setup,
440 const std::vector<std::string>& input,
441 bool all_toolchains,
442 UniqueVector<const Target*>* target_matches,
443 UniqueVector<const Config*>* config_matches,
444 UniqueVector<const Toolchain*>* toolchain_matches,
445 UniqueVector<SourceFile>* file_matches) {
446 if (input.empty()) {
447 Err(Location(), "You need to specify a label, file, or pattern.")
448 .PrintToStdout();
449 return false;
450 }
451
452 SourceDir cur_dir =
453 SourceDirForCurrentDirectory(setup->build_settings().root_path());
454 for (const auto& cur : input) {
455 if (!ResolveStringFromCommandLineInput(setup, cur_dir, cur, all_toolchains,
456 target_matches, config_matches,
457 toolchain_matches, file_matches))
458 return false;
459 }
460 return true;
461 }
462
FilterTargetsByPatterns(const std::vector<const Target * > & input,const std::vector<LabelPattern> & filter,std::vector<const Target * > * output)463 void FilterTargetsByPatterns(const std::vector<const Target*>& input,
464 const std::vector<LabelPattern>& filter,
465 std::vector<const Target*>* output) {
466 for (auto* target : input) {
467 for (const auto& pattern : filter) {
468 if (pattern.Matches(target->label())) {
469 output->push_back(target);
470 break;
471 }
472 }
473 }
474 }
475
FilterTargetsByPatterns(const std::vector<const Target * > & input,const std::vector<LabelPattern> & filter,UniqueVector<const Target * > * output)476 void FilterTargetsByPatterns(const std::vector<const Target*>& input,
477 const std::vector<LabelPattern>& filter,
478 UniqueVector<const Target*>* output) {
479 for (auto* target : input) {
480 for (const auto& pattern : filter) {
481 if (pattern.Matches(target->label())) {
482 output->push_back(target);
483 break;
484 }
485 }
486 }
487 }
488
FilterPatternsFromString(const BuildSettings * build_settings,const std::string & label_list_string,std::vector<LabelPattern> * filters,Err * err)489 bool FilterPatternsFromString(const BuildSettings* build_settings,
490 const std::string& label_list_string,
491 std::vector<LabelPattern>* filters,
492 Err* err) {
493 std::vector<std::string> tokens = base::SplitString(
494 label_list_string, ";", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
495 SourceDir root_dir("//");
496
497 filters->reserve(tokens.size());
498 for (const std::string& token : tokens) {
499 LabelPattern pattern = LabelPattern::GetPattern(
500 root_dir, build_settings->root_path_utf8(),
501 Value(nullptr, FixGitBashLabelEdit(token)), err);
502 if (err->has_error())
503 return false;
504 filters->push_back(pattern);
505 }
506
507 return true;
508 }
509
FilterAndPrintTargets(std::vector<const Target * > * targets,base::ListValue * out)510 void FilterAndPrintTargets(std::vector<const Target*>* targets,
511 base::ListValue* out) {
512 if (targets->empty())
513 return;
514
515 if (!ApplyTestonlyFilter(targets))
516 return;
517 if (!ApplyTypeFilter(targets))
518 return;
519
520 TargetPrintingMode printing_mode = TARGET_PRINT_LABEL;
521 if (targets->empty() || !GetTargetPrintingMode(&printing_mode))
522 return;
523 switch (printing_mode) {
524 case TARGET_PRINT_BUILDFILE:
525 PrintTargetsAsBuildfiles(*targets, out);
526 break;
527 case TARGET_PRINT_LABEL:
528 PrintTargetsAsLabels(*targets, out);
529 break;
530 case TARGET_PRINT_OUTPUT:
531 PrintTargetsAsOutputs(*targets, out);
532 break;
533 }
534 }
535
FilterAndPrintTargets(bool indent,std::vector<const Target * > * targets)536 void FilterAndPrintTargets(bool indent, std::vector<const Target*>* targets) {
537 base::ListValue tmp;
538 FilterAndPrintTargets(targets, &tmp);
539 for (const auto& value : tmp) {
540 std::string string;
541 value.GetAsString(&string);
542 if (indent)
543 OutputString(" ");
544 OutputString(string);
545 OutputString("\n");
546 }
547 }
548
FilterAndPrintTargetSet(bool indent,const std::set<const Target * > & targets)549 void FilterAndPrintTargetSet(bool indent,
550 const std::set<const Target*>& targets) {
551 std::vector<const Target*> target_vector(targets.begin(), targets.end());
552 FilterAndPrintTargets(indent, &target_vector);
553 }
554
FilterAndPrintTargetSet(const std::set<const Target * > & targets,base::ListValue * out)555 void FilterAndPrintTargetSet(const std::set<const Target*>& targets,
556 base::ListValue* out) {
557 std::vector<const Target*> target_vector(targets.begin(), targets.end());
558 FilterAndPrintTargets(&target_vector, out);
559 }
560
561 } // namespace commands
562