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