1 // Copyright 2020 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 <stddef.h>
6 
7 #include <algorithm>
8 
9 #include "base/command_line.h"
10 #include "base/strings/stringprintf.h"
11 #include "gn/commands.h"
12 #include "gn/setup.h"
13 #include "gn/standard_out.h"
14 
15 namespace commands {
16 
17 const char kOutputs[] = "outputs";
18 const char kOutputs_HelpShort[] = "outputs: Which files a source/target make.";
19 const char kOutputs_Help[] =
20     R"(gn outputs <out_dir> <list of target or file names...>
21 
22   Lists the output files corresponding to the given target(s) or file name(s).
23   There can be multiple outputs because there can be more than one output
24   generated by a build step, and there can be more than one toolchain matched.
25   You can also list multiple inputs which will generate a union of all the
26   outputs from those inputs.
27 
28    - The input target/file names are relative to the current directory.
29 
30    - The output file names are relative to the root build directory.
31 
32    This command is useful for finding a ninja command that will build only a
33    portion of the build.
34 
35 Target outputs
36 
37   If the parameter is a target name that includes a toolchain, it will match
38   only that target in that toolchain. If no toolchain is specified, it will
39   match all targets with that name in any toolchain.
40 
41   The result will be the outputs specified by that target which could be a
42   library, executable, output of an action, a stamp file, etc.
43 
44 File outputs
45 
46   If the parameter is a file name it will compute the output for that compile
47   step for all targets in all toolchains that contain that file as a source
48   file.
49 
50   If the source is not compiled (e.g. a header or text file), the command will
51   produce no output.
52 
53   If the source is listed as an "input" to a binary target or action will
54   resolve to that target's outputs.
55 
56 Example
57 
58   gn outputs out/debug some/directory:some_target
59       Find the outputs of a given target.
60 
61   gn outputs out/debug src/project/my_file.cc | xargs ninja -C out/debug
62       Compiles just the given source file in all toolchains it's referenced in.
63 
64   git diff --name-only | xargs gn outputs out/x64 | xargs ninja -C out/x64
65       Compiles all files changed in git.
66 )";
67 
RunOutputs(const std::vector<std::string> & args)68 int RunOutputs(const std::vector<std::string>& args) {
69   if (args.size() < 2) {
70     Err(Location(),
71         "Expected a build dir and one or more input files or targets.\n"
72         "Usage: \"gn outputs <out_dir> <target-or-file>*\"")
73         .PrintToStdout();
74     return 1;
75   }
76 
77   // Deliberately leaked to avoid expensive process teardown.
78   Setup* setup = new Setup;
79   if (!setup->DoSetup(args[0], false))
80     return 1;
81   if (!setup->Run())
82     return 1;
83 
84   std::vector<std::string> inputs(args.begin() + 1, args.end());
85 
86   UniqueVector<const Target*> target_matches;
87   UniqueVector<const Config*> config_matches;
88   UniqueVector<const Toolchain*> toolchain_matches;
89   UniqueVector<SourceFile> file_matches;
90   if (!ResolveFromCommandLineInput(setup, inputs, false, &target_matches,
91                                    &config_matches, &toolchain_matches,
92                                    &file_matches))
93     return 1;
94 
95   // We only care about targets and files.
96   if (target_matches.empty() && file_matches.empty()) {
97     Err(Location(), "The input matched no targets or files.").PrintToStdout();
98     return 1;
99   }
100 
101   // Resulting outputs.
102   std::vector<OutputFile> outputs;
103 
104   // Files. This must go first because it may add to the "targets" list.
105   std::vector<const Target*> all_targets =
106       setup->builder().GetAllResolvedTargets();
107   for (const SourceFile& file : file_matches) {
108     std::vector<TargetContainingFile> targets;
109     GetTargetsContainingFile(setup, all_targets, file, false, &targets);
110     if (targets.empty()) {
111       Err(Location(), base::StringPrintf("No targets reference the file '%s'.",
112                                          file.value().c_str()))
113           .PrintToStdout();
114       return 1;
115     }
116 
117     // There can be more than one target that references this file, evaluate the
118     // output name in all of them.
119     for (const TargetContainingFile& pair : targets) {
120       if (pair.second == HowTargetContainsFile::kInputs) {
121         // Inputs maps to the target itself. This will be evaluated below.
122         target_matches.push_back(pair.first);
123       } else if (pair.second == HowTargetContainsFile::kSources) {
124         // Source file, check it.
125         const char* computed_tool = nullptr;
126         std::vector<OutputFile> file_outputs;
127         pair.first->GetOutputFilesForSource(file, &computed_tool,
128                                             &file_outputs);
129         outputs.insert(outputs.end(), file_outputs.begin(), file_outputs.end());
130       }
131     }
132   }
133 
134   // Targets.
135   for (const Target* target : target_matches) {
136     std::vector<SourceFile> output_files;
137     Err err;
138     if (!target->GetOutputsAsSourceFiles(LocationRange(), true, &output_files,
139                                          &err)) {
140       err.PrintToStdout();
141       return 1;
142     }
143 
144     // Convert to OutputFiles.
145     for (const SourceFile& file : output_files)
146       outputs.emplace_back(&setup->build_settings(), file);
147   }
148 
149   // Print.
150   for (const OutputFile& output_file : outputs)
151     printf("%s\n", output_file.value().c_str());
152   return 0;
153 }
154 
155 }  // namespace commands
156