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
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
|
// 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/build_settings.h"
#include "tools/gn/filesystem_utils.h"
#include "tools/gn/functions.h"
#include "tools/gn/parse_tree.h"
#include "tools/gn/scope.h"
#include "tools/gn/settings.h"
#include "tools/gn/source_dir.h"
#include "tools/gn/source_file.h"
#include "tools/gn/value.h"
namespace functions {
namespace {
enum SeparatorConversion {
SEP_TO_SLASH, // All slashes to forward.
SEP_TO_SYSTEM, // Slashes to system ones.
};
// Does the specified path separator conversion in-place.
void ConvertSlashes(std::string* str, SeparatorConversion mode) {
#if defined(OS_WIN)
if (mode == SEP_TO_SYSTEM)
std::replace(str->begin(), str->end(), '/', '\\');
else
#endif
if (mode == SEP_TO_SLASH)
std::replace(str->begin(), str->end(), '\\', '/');
}
// We want the output to match the input in terms of ending in a slash or not.
// Through all the transformations, these can get added or removed in various
// cases.
void MakeSlashEndingMatchInput(const std::string& input, std::string* output) {
if (EndsWithSlash(input)) {
if (!EndsWithSlash(*output)) // Preserve same slash type as input.
output->push_back(input[input.size() - 1]);
} else {
if (EndsWithSlash(*output))
output->resize(output->size() - 1);
}
}
// Returns true if the given value looks like a directory, otherwise we'll
// assume it's a file.
bool ValueLooksLikeDir(const std::string& value) {
if (value.empty())
return true;
size_t value_size = value.size();
// Count the number of dots at the end of the string.
size_t num_dots = 0;
while (num_dots < value_size && value[value_size - num_dots - 1] == '.')
num_dots++;
if (num_dots == value.size())
return true; // String is all dots.
if (IsSlash(value[value_size - num_dots - 1]))
return true; // String is a [back]slash followed by 0 or more dots.
// Anything else.
return false;
}
Value ConvertOnePath(const Scope* scope,
const FunctionCallNode* function,
const Value& value,
const SourceDir& from_dir,
const SourceDir& to_dir,
bool convert_to_system_absolute,
SeparatorConversion separator_conversion,
Err* err) {
Value result; // Ensure return value optimization.
if (!value.VerifyTypeIs(Value::STRING, err))
return result;
const std::string& string_value = value.string_value();
bool looks_like_dir = ValueLooksLikeDir(string_value);
// System-absolute output special case.
if (convert_to_system_absolute) {
base::FilePath system_path;
if (looks_like_dir) {
system_path = scope->settings()->build_settings()->GetFullPath(
from_dir.ResolveRelativeDir(string_value));
} else {
system_path = scope->settings()->build_settings()->GetFullPath(
from_dir.ResolveRelativeFile(string_value));
}
result = Value(function, FilePathToUTF8(system_path));
if (looks_like_dir)
MakeSlashEndingMatchInput(string_value, &result.string_value());
ConvertPathToSystem(&result.string_value());
return result;
}
if (from_dir.is_system_absolute() || to_dir.is_system_absolute()) {
*err = Err(function, "System-absolute directories are not supported for "
"the source or dest dir for rebase_path. It would be nice to add this "
"if you're so inclined!");
return result;
}
result = Value(function, Value::STRING);
if (looks_like_dir) {
result.string_value() = RebaseSourceAbsolutePath(
from_dir.ResolveRelativeDir(string_value).value(),
to_dir);
MakeSlashEndingMatchInput(string_value, &result.string_value());
} else {
result.string_value() = RebaseSourceAbsolutePath(
from_dir.ResolveRelativeFile(string_value).value(),
to_dir);
}
ConvertSlashes(&result.string_value(), separator_conversion);
return result;
}
} // namespace
const char kRebasePath[] = "rebase_path";
const char kRebasePath_Help[] =
"rebase_path: Rebase a file or directory to another location.\n"
"\n"
" converted = rebase_path(input,\n"
" new_base = \"\",\n"
" current_base = \".\",\n"
" path_separators = \"to_slash\")\n"
"\n"
" Takes a string argument representing a file name, or a list of such\n"
" strings and converts it/them to be relative to a different base\n"
" directory.\n"
"\n"
" When invoking the compiler or scripts, GN will automatically convert\n"
" sources and include directories to be relative to the build directory.\n"
" However, if you're passing files directly in the \"args\" array or\n"
" doing other manual manipulations where GN doesn't know something is\n"
" a file name, you will need to convert paths to be relative to what\n"
" your tool is expecting.\n"
"\n"
" The common case is to use this to convert paths relative to the\n"
" current directory to be relative to the build directory (which will\n"
" be the current directory when executing scripts).\n"
"\n"
"Arguments\n"
"\n"
" input\n"
" A string or list of strings representing file or directory names\n"
" These can be relative paths (\"foo/bar.txt\"), system absolute\n"
" paths (\"/foo/bar.txt\"), or source absolute paths\n"
" (\"//foo/bar.txt\").\n"
"\n"
" new_base\n"
" The directory to convert the paths to be relative to. This can be\n"
" an absolute path or a relative path (which will be treated\n"
" as being relative to the current BUILD-file's directory).\n"
"\n"
" As a special case, if new_base is the empty string (the default),\n"
" all paths will be converted to system-absolute native style paths\n"
" with system path separators. This is useful for invoking external\n"
" programs.\n"
"\n"
" current_base\n"
" Directory representing the base for relative paths in the input.\n"
" If this is not an absolute path, it will be treated as being\n"
" relative to the current build file. Use \".\" (the default) to\n"
" convert paths from the current BUILD-file's directory.\n"
"\n"
" path_separators\n"
" On Windows systems, indicates whether and how path separators\n"
" should be converted as part of the transformation. It can be one\n"
" of the following strings:\n"
" - \"to_slash\" Normalize all types of slashes to forward slashes.\n"
" This is the default if this argument is unspecified.\n"
" - \"to_system\" Convert to the system path separators\n"
" (backslashes on Windows).\n"
"\n"
" On Posix systems there are no path separator transformations\n"
" applied. If the new_base is empty (specifying absolute output)\n"
" this parameter should not be supplied since paths will always be\n"
" converted,\n"
"\n"
"Return value\n"
"\n"
" The return value will be the same type as the input value (either a\n"
" string or a list of strings). All relative and source-absolute file\n"
" names will be converted to be relative to the requested output\n"
" System-absolute paths will be unchanged.\n"
"\n"
"Example\n"
"\n"
" # Convert a file in the current directory to be relative to the build\n"
" # directory (the current dir when executing compilers and scripts).\n"
" foo = rebase_path(\"myfile.txt\", root_build_dir)\n"
" # might produce \"../../project/myfile.txt\".\n"
"\n"
" # Convert a file to be system absolute:\n"
" foo = rebase_path(\"myfile.txt\")\n"
" # Might produce \"D:\\source\\project\\myfile.txt\" on Windows or\n"
" # \"/home/you/source/project/myfile.txt\" on Linux.\n"
"\n"
" # Convert a file's path separators from forward slashes to system\n"
" # slashes.\n"
" foo = rebase_path(\"source/myfile.txt\", \".\", \".\", \"to_system\")\n"
"\n"
" # Typical usage for converting to the build directory for a script.\n"
" custom(\"myscript\") {\n"
" # Don't convert sources, GN will automatically convert these to be\n"
" # relative to the build directory when it contructs the command\n"
" # line for your script.\n"
" sources = [ \"foo.txt\", \"bar.txt\" ]\n"
"\n"
" # Extra file args passed manually need to be explicitly converted\n"
" # to be relative to the build directory:\n"
" args = [\n"
" \"--data\",\n"
" rebase_path(\"//mything/data/input.dat\", root_build_dir),\n"
" \"--rel\",\n"
" rebase_path(\"relative_path.txt\", root_build_dir)\n"
" ]\n"
" }\n";
Value RunRebasePath(Scope* scope,
const FunctionCallNode* function,
const std::vector<Value>& args,
Err* err) {
Value result;
// Argument indices.
static const size_t kArgIndexInputs = 0;
static const size_t kArgIndexDest = 1;
static const size_t kArgIndexFrom = 2;
static const size_t kArgIndexPathConversion = 3;
// Inputs.
if (args.size() < 1 || args.size() > 4) {
*err = Err(function->function(), "Wrong # of arguments for rebase_path.");
return result;
}
const Value& inputs = args[kArgIndexInputs];
// To path.
bool convert_to_system_absolute = true;
SourceDir to_dir;
const SourceDir& current_dir = scope->GetSourceDir();
if (args.size() > kArgIndexDest) {
if (!args[kArgIndexDest].VerifyTypeIs(Value::STRING, err))
return result;
if (!args[kArgIndexDest].string_value().empty()) {
to_dir =
current_dir.ResolveRelativeDir(args[kArgIndexDest].string_value());
convert_to_system_absolute = false;
}
}
// From path.
SourceDir from_dir;
if (args.size() > kArgIndexFrom) {
if (!args[kArgIndexFrom].VerifyTypeIs(Value::STRING, err))
return result;
from_dir =
current_dir.ResolveRelativeDir(args[kArgIndexFrom].string_value());
} else {
// Default to current directory if unspecified.
from_dir = current_dir;
}
// Path conversion.
SeparatorConversion sep_conversion = SEP_TO_SLASH;
if (args.size() > kArgIndexPathConversion) {
if (convert_to_system_absolute) {
*err = Err(function, "Can't specify slash conversion.",
"You specified absolute system path output by using an empty string "
"for the destination directory on rebase_path(). In this case, you "
"can't specify slash conversion.");
return result;
}
if (!args[kArgIndexPathConversion].VerifyTypeIs(Value::STRING, err))
return result;
const std::string& sep_string =
args[kArgIndexPathConversion].string_value();
if (sep_string == "to_slash") {
sep_conversion = SEP_TO_SLASH;
} else if (sep_string == "to_system") {
sep_conversion = SEP_TO_SYSTEM;
} else {
*err = Err(args[kArgIndexPathConversion],
"Invalid path separator conversion mode.",
"I was expecting \"to_slash\" or \"to_system\" and\n"
"you gave me \"" + args[kArgIndexPathConversion].string_value() +
"\".");
return result;
}
}
if (inputs.type() == Value::STRING) {
return ConvertOnePath(scope, function, inputs,
from_dir, to_dir, convert_to_system_absolute,
sep_conversion, err);
} else if (inputs.type() == Value::LIST) {
result = Value(function, Value::LIST);
result.list_value().reserve(inputs.list_value().size());
for (size_t i = 0; i < inputs.list_value().size(); i++) {
result.list_value().push_back(
ConvertOnePath(scope, function, inputs.list_value()[i],
from_dir, to_dir, convert_to_system_absolute,
sep_conversion, err));
if (err->has_error()) {
result = Value();
return result;
}
}
return result;
}
*err = Err(function->function(),
"rebase_path requires a list or a string.");
return result;
}
} // namespace functions
|