1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
|
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "tools/gn/ninja_script_target_writer.h"
#include "base/strings/string_util.h"
#include "tools/gn/err.h"
#include "tools/gn/file_template.h"
#include "tools/gn/string_utils.h"
#include "tools/gn/target.h"
NinjaScriptTargetWriter::NinjaScriptTargetWriter(const Target* target,
std::ostream& out)
: NinjaTargetWriter(target, out) {
}
NinjaScriptTargetWriter::~NinjaScriptTargetWriter() {
}
void NinjaScriptTargetWriter::Run() {
WriteEnvironment();
// Run the script from the dir of the BUILD file. This has no trailing
// slash.
const SourceDir& script_cd = target_->label().dir();
std::string script_cd_to_root = InvertDir(script_cd);
if (script_cd_to_root.empty()) {
script_cd_to_root = ".";
} else {
// Remove trailing slash.
DCHECK(script_cd_to_root[script_cd_to_root.size() - 1] == '/');
script_cd_to_root.resize(script_cd_to_root.size() - 1);
}
// Compute the relative script file name. The script string should start with
// 2 slashes, and we trim 1.
DCHECK(target_->script_values().script().is_source_absolute());
std::string script_relative_to_cd = script_cd_to_root;
const std::string& script_string = target_->script_values().script().value();
script_relative_to_cd.append(&script_string[1], script_string.size() - 1);
std::string custom_rule_name = WriteRuleDefinition(script_relative_to_cd);
// Precompute the common dependencies for each step. This includes the
// script itself (changing the script should force a rebuild) and any data
// files.
//
// TODO(brettw) this needs to be re-thought. "data" is supposed to be runtime
// data (i.e. for tests and such) rather than compile-time dependencies for
// each target. If we really need this, we need to have a different way to
// express it.
//
// One idea: add an "inputs" variable to specify this kind of thing. We
// should probably make it an error to specify data but no inputs for a
// script as a way to catch people doing the wrong way.
std::ostringstream common_deps_stream;
path_output_.WriteFile(common_deps_stream, target_->script_values().script());
const Target::FileList& datas = target_->data();
for (size_t i = 0; i < datas.size(); i++) {
common_deps_stream << " ";
path_output_.WriteFile(common_deps_stream, datas[i]);
}
const std::string& common_deps = common_deps_stream.str();
// Collects all output files for writing below.
std::vector<OutputFile> output_files;
if (has_sources()) {
// Write separate rules for each input source file.
WriteSourceRules(custom_rule_name, common_deps, script_cd,
script_cd_to_root, &output_files);
} else {
// No sources, write a rule that invokes the script once with the
// outputs as outputs, and the data as inputs.
out_ << "build";
const Target::FileList& outputs = target_->script_values().outputs();
for (size_t i = 0; i < outputs.size(); i++) {
OutputFile output_path(
RemovePrefix(outputs[i].value(),
settings_->build_settings()->build_dir().value()));
output_files.push_back(output_path);
out_ << " ";
path_output_.WriteFile(out_, output_path);
}
out_ << ": " << custom_rule_name << " " << common_deps << std::endl;
}
out_ << std::endl;
WriteStamp(output_files);
}
std::string NinjaScriptTargetWriter::WriteRuleDefinition(
const std::string& script_relative_to_cd) {
// Make a unique name for this rule.
std::string target_label = target_->label().GetUserVisibleName(true);
std::string custom_rule_name(target_label);
ReplaceChars(custom_rule_name, ":/()", "_", &custom_rule_name);
custom_rule_name.append("_rule");
// Use a unique name for the response file when there are multiple build
// steps so that they don't stomp on each other. When there are no sources,
// there will be only one invocation so we can use a simple name.
if (settings_->IsWin()) {
// Send through gyp-win-tool and use a response file.
std::string rspfile = custom_rule_name;
if (has_sources())
rspfile += ".$unique_name";
rspfile += ".rsp";
out_ << "rule " << custom_rule_name << std::endl;
out_ << " command = $pythonpath gyp-win-tool action-wrapper $arch "
<< rspfile << " ";
path_output_.WriteDir(out_, target_->label().dir(),
PathOutput::DIR_NO_LAST_SLASH);
out_ << std::endl;
out_ << " description = CUSTOM " << target_label << std::endl;
out_ << " restat = 1" << std::endl;
out_ << " rspfile = " << rspfile << std::endl;
// The build command goes in the rsp file.
out_ << " rspfile_content = $pythonpath " << script_relative_to_cd;
for (size_t i = 0; i < target_->script_values().args().size(); i++) {
const std::string& arg = target_->script_values().args()[i];
out_ << " ";
WriteArg(arg);
}
} else {
// Posix can execute Python directly.
out_ << "rule " << custom_rule_name << std::endl;
out_ << " command = cd ";
path_output_.WriteDir(out_, target_->label().dir(),
PathOutput::DIR_NO_LAST_SLASH);
out_ << "; $pythonpath " << script_relative_to_cd;
for (size_t i = 0; i < target_->script_values().args().size(); i++) {
const std::string& arg = target_->script_values().args()[i];
out_ << " ";
WriteArg(arg);
}
out_ << std::endl;
out_ << " description = CUSTOM " << target_label << std::endl;
out_ << " restat = 1" << std::endl;
}
out_ << std::endl;
return custom_rule_name;
}
void NinjaScriptTargetWriter::WriteArg(const std::string& arg) {
// This can be optimized if it's called a lot.
EscapeOptions options;
options.mode = ESCAPE_NINJA;
std::string output_str = EscapeString(arg, options);
// Do this substitution after escaping our our $ will be escaped (which we
// don't want).
ReplaceSubstringsAfterOffset(&output_str, 0, FileTemplate::kSource,
"${source}");
ReplaceSubstringsAfterOffset(&output_str, 0, FileTemplate::kSourceNamePart,
"${source_name_part}");
out_ << output_str;
}
void NinjaScriptTargetWriter::WriteSourceRules(
const std::string& custom_rule_name,
const std::string& common_deps,
const SourceDir& script_cd,
const std::string& script_cd_to_root,
std::vector<OutputFile>* output_files) {
// Construct the template for generating the output files from each source.
const Target::FileList& outputs = target_->script_values().outputs();
std::vector<std::string> output_template_args;
for (size_t i = 0; i < outputs.size(); i++) {
// All outputs should be in the output dir.
output_template_args.push_back(
RemovePrefix(outputs[i].value(),
settings_->build_settings()->build_dir().value()));
}
FileTemplate output_template(output_template_args);
// Prevent re-allocating each time by initializing outside the loop.
std::vector<std::string> output_template_result;
// Path output formatter for wrigin source paths passed to the script.
PathOutput script_source_path_output(script_cd, ESCAPE_SHELL, true);
const Target::FileList& sources = target_->sources();
for (size_t i = 0; i < sources.size(); i++) {
// Write outputs for this source file computed by the template.
out_ << "build";
output_template.ApplyString(sources[i].value(), &output_template_result);
for (size_t out_i = 0; out_i < output_template_result.size(); out_i++) {
OutputFile output_path(output_template_result[out_i]);
output_files->push_back(output_path);
out_ << " ";
path_output_.WriteFile(out_, output_path);
}
out_ << ": " << custom_rule_name
<< " " << common_deps
<< " ";
path_output_.WriteFile(out_, sources[i]);
out_ << std::endl;
out_ << " unique_name = " << i << std::endl;
// The source file here should be relative to the script directory since
// this is the variable passed to the script. Here we slightly abuse the
// OutputFile object by putting a non-output-relative path in it to signal
// that the PathWriter should not prepend directories.
out_ << " source = ";
script_source_path_output.WriteFile(out_, sources[i]);
out_ << std::endl;
out_ << " source_name_part = "
<< FindFilenameNoExtension(&sources[i].value()).as_string()
<< std::endl;
}
}
void NinjaScriptTargetWriter::WriteStamp(
const std::vector<OutputFile>& output_files) {
out_ << "build ";
path_output_.WriteFile(out_, helper_.GetTargetOutputFile(target_));
out_ << ": "
<< helper_.GetRulePrefix(target_->settings()->toolchain())
<< "stamp";
for (size_t i = 0; i < output_files.size(); i++) {
out_ << " ";
path_output_.WriteFile(out_, output_files[i]);
}
out_ << std::endl;
}
|