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 <algorithm>
6 #include <iterator>
7 #include <set>
8 #include <vector>
9
10 #include "base/files/file_path.h"
11 #include "base/files/file_util.h"
12 #include "gn/analyzer.h"
13 #include "gn/commands.h"
14 #include "gn/filesystem_utils.h"
15 #include "gn/location.h"
16 #include "gn/setup.h"
17 #include "gn/standard_out.h"
18 #include "gn/string_utils.h"
19
20 namespace commands {
21
22 const char kAnalyze[] = "analyze";
23 const char kAnalyze_HelpShort[] =
24 "analyze: Analyze which targets are affected by a list of files.";
25 const char kAnalyze_Help[] =
26 R"(gn analyze <out_dir> <input_path> <output_path>
27
28 Analyze which targets are affected by a list of files.
29
30 This command takes three arguments:
31
32 out_dir is the path to the build directory.
33
34 input_path is a path to a file containing a JSON object with three fields:
35
36 - "files": A list of the filenames to check.
37
38 - "test_targets": A list of the labels for targets that are needed to run
39 the tests we wish to run.
40
41 - "additional_compile_targets": A list of the labels for targets that we
42 wish to rebuild, but aren't necessarily needed for testing. The important
43 difference between this field and "test_targets" is that if an item in
44 the additional_compile_targets list refers to a group, then any
45 dependencies of that group will be returned if they are out of date, but
46 the group itself does not need to be. If the dependencies themselves are
47 groups, the same filtering is repeated. This filtering can be used to
48 avoid rebuilding dependencies of a group that are unaffected by the input
49 files. The list may also contain the string "all" to refer to a
50 pseudo-group that contains every root target in the build graph.
51
52 This filtering behavior is also known as "pruning" the list of compile
53 targets.
54
55 If input_path is -, input is read from stdin.
56
57 output_path is a path indicating where the results of the command are to be
58 written. The results will be a file containing a JSON object with one or more
59 of following fields:
60
61 - "compile_targets": A list of the labels derived from the input
62 compile_targets list that are affected by the input files. Due to the way
63 the filtering works for compile targets as described above, this list may
64 contain targets that do not appear in the input list.
65
66 - "test_targets": A list of the labels from the input test_targets list that
67 are affected by the input files. This list will be a proper subset of the
68 input list.
69
70 - "invalid_targets": A list of any names from the input that do not exist in
71 the build graph. If this list is non-empty, the "error" field will also be
72 set to "Invalid targets".
73
74 - "status": A string containing one of three values:
75
76 - "Found dependency"
77 - "No dependency"
78 - "Found dependency (all) "
79
80 In the first case, the lists returned in compile_targets and test_targets
81 should be passed to ninja to build. In the second case, nothing was
82 affected and no build is necessary. In the third case, GN could not
83 determine the correct answer and returned the input as the output in order
84 to be safe.
85
86 - "error": This will only be present if an error occurred, and will contain
87 a string describing the error. This includes cases where the input file is
88 not in the right format, or contains invalid targets.
89
90 If output_path is -, output is written to stdout.
91
92 The command returns 1 if it is unable to read the input file or write the
93 output file, or if there is something wrong with the build such that gen
94 would also fail, and 0 otherwise. In particular, it returns 0 even if the
95 "error" key is non-empty and a non-fatal error occurred. In other words, it
96 tries really hard to always write something to the output JSON and convey
97 errors that way rather than via return codes.
98 )";
99
RunAnalyze(const std::vector<std::string> & args)100 int RunAnalyze(const std::vector<std::string>& args) {
101 if (args.size() != 3) {
102 Err(Location(), "You're holding it wrong.",
103 "Usage: \"gn analyze <out_dir> <input_path> <output_path>")
104 .PrintToStdout();
105 return 1;
106 }
107
108 std::string input;
109 if (args[1] == "-") {
110 input = ReadStdin();
111 } else {
112 bool ret = base::ReadFileToString(UTF8ToFilePath(args[1]), &input);
113 if (!ret) {
114 Err(Location(), "Input file " + args[1] + " not found.").PrintToStdout();
115 return 1;
116 }
117 }
118
119 // Deliberately leaked to avoid expensive process teardown.
120 Setup* setup = new Setup;
121 if (!setup->DoSetup(args[0], false) || !setup->Run())
122 return 1;
123
124 Err err;
125 Analyzer analyzer(
126 setup->builder(), setup->build_settings().build_config_file(),
127 setup->GetDotFile(),
128 setup->build_settings().build_args().build_args_dependency_files());
129 std::string output = analyzer.Analyze(input, &err);
130 if (err.has_error()) {
131 err.PrintToStdout();
132 return 1;
133 }
134
135 if (args[2] == "-") {
136 OutputString(output + "\n");
137 } else {
138 WriteFile(UTF8ToFilePath(args[2]), output, &err);
139 if (err.has_error()) {
140 err.PrintToStdout();
141 return 1;
142 }
143 }
144
145 return 0;
146 }
147
148 } // namespace commands
149