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 "tools/gn/ninja_script_target_writer.h"
6
7#include "base/strings/string_util.h"
8#include "tools/gn/err.h"
9#include "tools/gn/file_template.h"
10#include "tools/gn/string_utils.h"
11#include "tools/gn/target.h"
12
13NinjaScriptTargetWriter::NinjaScriptTargetWriter(const Target* target,
14                                                 const Toolchain* toolchain,
15                                                 std::ostream& out)
16    : NinjaTargetWriter(target, toolchain, out),
17      path_output_no_escaping_(
18          target->settings()->build_settings()->build_dir(),
19          ESCAPE_NONE, false) {
20}
21
22NinjaScriptTargetWriter::~NinjaScriptTargetWriter() {
23}
24
25void NinjaScriptTargetWriter::Run() {
26  FileTemplate args_template(target_->script_values().args());
27  std::string custom_rule_name = WriteRuleDefinition(args_template);
28  std::string implicit_deps = GetSourcesImplicitDeps();
29
30  // Collects all output files for writing below.
31  std::vector<OutputFile> output_files;
32
33  if (has_sources()) {
34    // Write separate build lines for each input source file.
35    WriteSourceRules(custom_rule_name, implicit_deps, args_template,
36                     &output_files);
37  } else {
38    // No sources, write a rule that invokes the script once with the
39    // outputs as outputs, and the data as inputs.
40    out_ << "build";
41    if (target_->script_values().has_depfile()) {
42      out_ << " ";
43      WriteDepfile(SourceFile());
44    }
45    const Target::FileList& outputs = target_->script_values().outputs();
46    for (size_t i = 0; i < outputs.size(); i++) {
47      OutputFile output_path(
48          RemovePrefix(outputs[i].value(),
49                       settings_->build_settings()->build_dir().value()));
50      output_files.push_back(output_path);
51      out_ << " ";
52      path_output_.WriteFile(out_, output_path);
53    }
54    out_ << ": " << custom_rule_name << implicit_deps << std::endl;
55    if (target_->script_values().has_depfile()) {
56      out_ << "  depfile = ";
57      WriteDepfile(SourceFile());
58      out_ << std::endl;
59    }
60  }
61  out_ << std::endl;
62
63  WriteStamp(output_files);
64}
65
66std::string NinjaScriptTargetWriter::WriteRuleDefinition(
67    const FileTemplate& args_template) {
68  // Make a unique name for this rule.
69  //
70  // Use a unique name for the response file when there are multiple build
71  // steps so that they don't stomp on each other. When there are no sources,
72  // there will be only one invocation so we can use a simple name.
73  std::string target_label = target_->label().GetUserVisibleName(true);
74  std::string custom_rule_name(target_label);
75  base::ReplaceChars(custom_rule_name, ":/()", "_", &custom_rule_name);
76  custom_rule_name.append("_rule");
77
78  if (settings_->IsWin()) {
79    // Send through gyp-win-tool and use a response file.
80    std::string rspfile = custom_rule_name;
81    if (has_sources())
82      rspfile += ".$unique_name";
83    rspfile += ".rsp";
84
85    out_ << "rule " << custom_rule_name << std::endl;
86    out_ << "  command = ";
87    path_output_.WriteFile(out_, settings_->build_settings()->python_path());
88    // TODO(brettw) this hardcodes "environment.x86" which is something that
89    // the Chrome Windows toolchain writes. We should have a way to invoke
90    // python without requiring this gyp_win_tool thing.
91    out_ << " gyp-win-tool action-wrapper environment.x86 " << rspfile
92         << std::endl;
93    out_ << "  description = CUSTOM " << target_label << std::endl;
94    out_ << "  restat = 1" << std::endl;
95    out_ << "  rspfile = " << rspfile << std::endl;
96
97    // The build command goes in the rsp file.
98    out_ << "  rspfile_content = ";
99    path_output_.WriteFile(out_, settings_->build_settings()->python_path());
100    out_ << " ";
101    path_output_.WriteFile(out_, target_->script_values().script());
102    args_template.WriteWithNinjaExpansions(out_);
103    out_ << std::endl;
104  } else {
105    // Posix can execute Python directly.
106    out_ << "rule " << custom_rule_name << std::endl;
107    out_ << "  command = ";
108    path_output_.WriteFile(out_, settings_->build_settings()->python_path());
109    out_ << " ";
110    path_output_.WriteFile(out_, target_->script_values().script());
111    args_template.WriteWithNinjaExpansions(out_);
112    out_ << std::endl;
113    out_ << "  description = CUSTOM " << target_label << std::endl;
114    out_ << "  restat = 1" << std::endl;
115  }
116
117  out_ << std::endl;
118  return custom_rule_name;
119}
120
121void NinjaScriptTargetWriter::WriteArgsSubstitutions(
122    const SourceFile& source,
123    const FileTemplate& args_template) {
124  std::ostringstream source_file_stream;
125  path_output_no_escaping_.WriteFile(source_file_stream, source);
126
127  EscapeOptions template_escape_options;
128  template_escape_options.mode = ESCAPE_NINJA_SHELL;
129  template_escape_options.inhibit_quoting = true;
130
131  args_template.WriteNinjaVariablesForSubstitution(
132      out_, source_file_stream.str(), template_escape_options);
133}
134
135void NinjaScriptTargetWriter::WriteSourceRules(
136    const std::string& custom_rule_name,
137    const std::string& implicit_deps,
138    const FileTemplate& args_template,
139    std::vector<OutputFile>* output_files) {
140  FileTemplate output_template(GetOutputTemplate());
141
142  const Target::FileList& sources = target_->sources();
143  for (size_t i = 0; i < sources.size(); i++) {
144    out_ << "build";
145    WriteOutputFilesForBuildLine(output_template, sources[i], output_files);
146
147    out_ << ": " << custom_rule_name << " ";
148    path_output_.WriteFile(out_, sources[i]);
149    out_ << implicit_deps << std::endl;
150
151    // Windows needs a unique ID for the response file.
152    if (target_->settings()->IsWin())
153      out_ << "  unique_name = " << i << std::endl;
154
155    if (args_template.has_substitutions())
156      WriteArgsSubstitutions(sources[i], args_template);
157
158    if (target_->script_values().has_depfile()) {
159      out_ << "  depfile = ";
160      WriteDepfile(sources[i]);
161      out_ << std::endl;
162    }
163  }
164}
165
166void NinjaScriptTargetWriter::WriteStamp(
167    const std::vector<OutputFile>& output_files) {
168  out_ << "build ";
169  path_output_.WriteFile(out_, helper_.GetTargetOutputFile(target_));
170  out_ << ": "
171       << helper_.GetRulePrefix(target_->settings())
172       << "stamp";
173  for (size_t i = 0; i < output_files.size(); i++) {
174    out_ << " ";
175    path_output_.WriteFile(out_, output_files[i]);
176  }
177  out_ << std::endl;
178}
179
180void NinjaScriptTargetWriter::WriteOutputFilesForBuildLine(
181    const FileTemplate& output_template,
182    const SourceFile& source,
183    std::vector<OutputFile>* output_files) {
184  // If there is a depfile specified we need to list it as the first output as
185  // that is what ninja will expect the depfile to refer to itself as.
186  if (target_->script_values().has_depfile()) {
187    out_ << " ";
188    WriteDepfile(source);
189  }
190  std::vector<std::string> output_template_result;
191  output_template.ApplyString(source.value(), &output_template_result);
192  for (size_t out_i = 0; out_i < output_template_result.size(); out_i++) {
193    OutputFile output_path(output_template_result[out_i]);
194    output_files->push_back(output_path);
195    out_ << " ";
196    path_output_.WriteFile(out_, output_path);
197  }
198}
199
200void NinjaScriptTargetWriter::WriteDepfile(const SourceFile& source) {
201  std::vector<std::string> result;
202  GetDepfileTemplate().ApplyString(source.value(), &result);
203  path_output_.WriteFile(out_, OutputFile(result[0]));
204}
205
206FileTemplate NinjaScriptTargetWriter::GetDepfileTemplate() const {
207  std::vector<std::string> template_args;
208  std::string depfile_relative_to_build_dir =
209      RemovePrefix(target_->script_values().depfile().value(),
210                   settings_->build_settings()->build_dir().value());
211  template_args.push_back(depfile_relative_to_build_dir);
212  return FileTemplate(template_args);
213}
214