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" (optional): A list of the labels for targets
42 that we wish to rebuild, but aren't necessarily needed for testing. The
43 important difference between this field and "test_targets" is that if an
44 item in 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 "additional_compile_targets" is absent, it defaults to the empty list.
56
57 If input_path is -, input is read from stdin.
58
59 output_path is a path indicating where the results of the command are to be
60 written. The results will be a file containing a JSON object with one or more
61 of following fields:
62
63 - "compile_targets": A list of the labels derived from the input
64 compile_targets list that are affected by the input files. Due to the way
65 the filtering works for compile targets as described above, this list may
66 contain targets that do not appear in the input list.
67
68 - "test_targets": A list of the labels from the input test_targets list that
69 are affected by the input files. This list will be a proper subset of the
70 input list.
71
72 - "invalid_targets": A list of any names from the input that do not exist in
73 the build graph. If this list is non-empty, the "error" field will also be
74 set to "Invalid targets".
75
76 - "status": A string containing one of three values:
77
78 - "Found dependency"
79 - "No dependency"
80 - "Found dependency (all)"
81
82 In the first case, the lists returned in compile_targets and test_targets
83 should be passed to ninja to build. In the second case, nothing was
84 affected and no build is necessary. In the third case, GN could not
85 determine the correct answer and returned the input as the output in order
86 to be safe.
87
88 - "error": This will only be present if an error occurred, and will contain
89 a string describing the error. This includes cases where the input file is
90 not in the right format, or contains invalid targets.
91
92 If output_path is -, output is written to stdout.
93
94 The command returns 1 if it is unable to read the input file or write the
95 output file, or if there is something wrong with the build such that gen
96 would also fail, and 0 otherwise. In particular, it returns 0 even if the
97 "error" key is non-empty and a non-fatal error occurred. In other words, it
98 tries really hard to always write something to the output JSON and convey
99 errors that way rather than via return codes.
100 )*";
101
RunAnalyze(const std::vector<std::string> & args)102 int RunAnalyze(const std::vector<std::string>& args) {
103 if (args.size() != 3) {
104 Err(Location(), "Unknown command format. See \"gn help analyze\"",
105 "Usage: \"gn analyze <out_dir> <input_path> <output_path>")
106 .PrintToStdout();
107 return 1;
108 }
109
110 std::string input;
111 if (args[1] == "-") {
112 input = ReadStdin();
113 } else {
114 bool ret = base::ReadFileToString(UTF8ToFilePath(args[1]), &input);
115 if (!ret) {
116 Err(Location(), "Input file " + args[1] + " not found.").PrintToStdout();
117 return 1;
118 }
119 }
120
121 // Deliberately leaked to avoid expensive process teardown.
122 Setup* setup = new Setup;
123 if (!setup->DoSetup(args[0], false) || !setup->Run())
124 return 1;
125
126 Err err;
127 Analyzer analyzer(
128 setup->builder(), setup->build_settings().build_config_file(),
129 setup->GetDotFile(),
130 setup->build_settings().build_args().build_args_dependency_files());
131 std::string output = analyzer.Analyze(input, &err);
132 if (err.has_error()) {
133 err.PrintToStdout();
134 return 1;
135 }
136
137 if (args[2] == "-") {
138 OutputString(output + "\n");
139 } else {
140 WriteFile(UTF8ToFilePath(args[2]), output, &err);
141 if (err.has_error()) {
142 err.PrintToStdout();
143 return 1;
144 }
145 }
146
147 return 0;
148 }
149
150 } // namespace commands
151