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.
5 #include "tools/gn/build_settings.h"
6 #include "tools/gn/filesystem_utils.h"
7 #include "tools/gn/functions.h"
8 #include "tools/gn/parse_tree.h"
9 #include "tools/gn/scope.h"
10 #include "tools/gn/settings.h"
11 #include "tools/gn/source_dir.h"
12 #include "tools/gn/source_file.h"
13 #include "tools/gn/value.h"
19 enum SeparatorConversion
{
20 SEP_NO_CHANGE
, // Don't change.
21 SEP_TO_SYSTEM
, // Slashes to system ones.
22 SEP_FROM_SYSTEM
// System ones to slashes.
25 // Does the specified path separator conversion in-place.
26 void ConvertSlashes(std::string
* str
, SeparatorConversion mode
) {
32 for (size_t i
= 0; i
< str
->size(); i
++) {
38 for (size_t i
= 0; i
< str
->size(); i
++) {
39 if ((*str
)[i
] == '\\')
45 DCHECK(str
->find('\\') == std::string::npos
)
46 << "Filename contains a backslash on a non-Windows platform.";
50 bool EndsInSlash(const std::string
& s
) {
51 return !s
.empty() && (s
[s
.size() - 1] == '/' || s
[s
.size() - 1] == '\\');
54 // We want the output to match the input in terms of ending in a slash or not.
55 // Through all the transformations, these can get added or removed in various
57 void MakeSlashEndingMatchInput(const std::string
& input
, std::string
* output
) {
58 if (EndsInSlash(input
)) {
59 if (!EndsInSlash(*output
)) // Preserve same slash type as input.
60 output
->push_back(input
[input
.size() - 1]);
62 if (EndsInSlash(*output
))
63 output
->resize(output
->size() - 1);
67 // Returns true if the given value looks like a directory, otherwise we'll
68 // assume it's a file.
69 bool ValueLooksLikeDir(const std::string
& value
) {
72 size_t value_size
= value
.size();
74 // Count the number of dots at the end of the string.
76 while (num_dots
< value_size
&& value
[value_size
- num_dots
- 1] == '.')
79 if (num_dots
== value
.size())
80 return true; // String is all dots.
82 if (value
[value_size
- num_dots
- 1] == '/' ||
83 value
[value_size
- num_dots
- 1] == '\\')
84 return true; // String is a [back]slash followed by 0 or more dots.
90 Value
ConvertOnePath(const Scope
* scope
,
91 const FunctionCallNode
* function
,
93 const SourceDir
& from_dir
,
94 const SourceDir
& to_dir
,
95 bool convert_to_system_absolute
,
96 SeparatorConversion separator_conversion
,
98 Value result
; // Ensure return value optimization.
100 if (!value
.VerifyTypeIs(Value::STRING
, err
))
102 const std::string
& string_value
= value
.string_value();
104 bool looks_like_dir
= ValueLooksLikeDir(string_value
);
106 // System-absolute output special case.
107 if (convert_to_system_absolute
) {
108 base::FilePath system_path
;
109 if (looks_like_dir
) {
110 system_path
= scope
->settings()->build_settings()->GetFullPath(
111 from_dir
.ResolveRelativeDir(string_value
));
113 system_path
= scope
->settings()->build_settings()->GetFullPath(
114 from_dir
.ResolveRelativeFile(string_value
));
116 result
= Value(function
, FilePathToUTF8(system_path
));
118 MakeSlashEndingMatchInput(string_value
, &result
.string_value());
119 ConvertPathToSystem(&result
.string_value());
123 if (from_dir
.is_system_absolute() || to_dir
.is_system_absolute()) {
124 *err
= Err(function
, "System-absolute directories are not supported for "
125 "the source or dest dir for rebase_path. It would be nice to add this "
126 "if you're so inclined!");
130 result
= Value(function
, Value::STRING
);
131 if (looks_like_dir
) {
132 result
.string_value() = RebaseSourceAbsolutePath(
133 from_dir
.ResolveRelativeDir(string_value
).value(),
135 MakeSlashEndingMatchInput(string_value
, &result
.string_value());
137 result
.string_value() = RebaseSourceAbsolutePath(
138 from_dir
.ResolveRelativeFile(string_value
).value(),
142 ConvertSlashes(&result
.string_value(), separator_conversion
);
148 const char kRebasePath
[] = "rebase_path";
149 const char kRebasePath_Help
[] =
150 "rebase_path: Rebase a file or directory to another location.\n"
152 " converted = rebase_path(input, current_base, new_base,\n"
153 " [path_separators])\n"
155 " Takes a string argument representing a file name, or a list of such\n"
156 " strings and converts it/them to be relative to a different base\n"
159 " When invoking the compiler or scripts, GN will automatically convert\n"
160 " sources and include directories to be relative to the build directory.\n"
161 " However, if you're passing files directly in the \"args\" array or\n"
162 " doing other manual manipulations where GN doesn't know something is\n"
163 " a file name, you will need to convert paths to be relative to what\n"
164 " your tool is expecting.\n"
166 " The common case is to use this to convert paths relative to the\n"
167 " current directory to be relative to the build directory (which will\n"
168 " be the current directory when executing scripts).\n"
173 " A string or list of strings representing file or directory names\n"
174 " These can be relative paths (\"foo/bar.txt\", system absolte paths\n"
175 " (\"/foo/bar.txt\"), or source absolute paths (\"//foo/bar.txt\").\n"
178 " Directory representing the base for relative paths in the input.\n"
179 " If this is not an absolute path, it will be treated as being\n"
180 " relative to the current build file. Use \".\" to convert paths\n"
181 " from the current BUILD-file's directory.\n"
184 " The directory to convert the paths to be relative to. As with the\n"
185 " current_base, this can be a relative path, which will be treated\n"
186 " as being relative to the current BUILD-file's directory.\n"
188 " As a special case, if new_base is the empty string, all paths\n"
189 " will be converted to system-absolute native style paths with\n"
190 " system path separators. This is useful for invoking external\n"
194 " On Windows systems, indicates whether and how path separators\n"
195 " should be converted as part of the transformation. It can be one\n"
196 " of the following strings:\n"
197 " - \"none\" Perform no changes on path separators. This is the\n"
198 " default if this argument is unspecified.\n"
199 " - \"to_system\" Convert to the system path separators\n"
200 " (backslashes on Windows).\n"
201 " - \"from_system\" Convert system path separators to forward\n"
204 " On Posix systems there are no path separator transformations\n"
205 " applied. If the new_base is empty (specifying absolute output)\n"
206 " this parameter should not be supplied since paths will always be\n"
211 " The return value will be the same type as the input value (either a\n"
212 " string or a list of strings). All relative and source-absolute file\n"
213 " names will be converted to be relative to the requested output\n"
214 " System-absolute paths will be unchanged.\n"
218 " # Convert a file in the current directory to be relative to the build\n"
219 " # directory (the current dir when executing compilers and scripts).\n"
220 " foo = rebase_path(\"myfile.txt\", \".\", root_build_dir)\n"
221 " # might produce \"../../project/myfile.txt\".\n"
223 " # Convert a file to be system absolute:\n"
224 " foo = rebase_path(\"myfile.txt\", \".\", \"\")\n"
225 " # Might produce \"D:\\source\\project\\myfile.txt\" on Windows or\n"
226 " # \"/home/you/source/project/myfile.txt\" on Linux.\n"
228 " # Convert a file's path separators from forward slashes to system\n"
230 " foo = rebase_path(\"source/myfile.txt\", \".\", \".\", \"to_system\")\n"
232 " # Typical usage for converting to the build directory for a script.\n"
233 " custom(\"myscript\") {\n"
234 " # Don't convert sources, GN will automatically convert these to be\n"
235 " # relative to the build directory when it contructs the command\n"
236 " # line for your script.\n"
237 " sources = [ \"foo.txt\", \"bar.txt\" ]\n"
239 " # Extra file args passed manually need to be explicitly converted\n"
240 " # to be relative to the build directory:\n"
243 " rebase_path(\"//mything/data/input.dat\", \".\", root_build_dir),\n"
245 " rebase_path(\"relative_path.txt\", \".\", root_build_dir)\n"
249 Value
RunRebasePath(Scope
* scope
,
250 const FunctionCallNode
* function
,
251 const std::vector
<Value
>& args
,
256 if (args
.size() != 3 && args
.size() != 4) {
257 *err
= Err(function
->function(), "rebase_path takes 3 or 4 args.");
260 const Value
& inputs
= args
[0];
263 if (!args
[1].VerifyTypeIs(Value::STRING
, err
))
265 const SourceDir
& current_dir
= scope
->GetSourceDir();
266 SourceDir from_dir
= current_dir
.ResolveRelativeDir(args
[1].string_value());
269 if (!args
[2].VerifyTypeIs(Value::STRING
, err
))
271 bool convert_to_system_absolute
= false;
273 if (args
[2].string_value().empty()) {
274 convert_to_system_absolute
= true;
276 to_dir
= current_dir
.ResolveRelativeDir(args
[2].string_value());
280 SeparatorConversion sep_conversion
= SEP_NO_CHANGE
;
281 if (args
.size() == 4) {
282 if (convert_to_system_absolute
) {
283 *err
= Err(function
, "Can't specify slash conversion.",
284 "You specified absolute system path output by using an empty string "
285 "for the desination directory on rebase_path(). In this case, you "
286 "can't specify slash conversion.");
290 if (!args
[3].VerifyTypeIs(Value::STRING
, err
))
292 const std::string
& sep_string
= args
[3].string_value();
293 if (sep_string
== "to_system") {
294 sep_conversion
= SEP_TO_SYSTEM
;
295 } else if (sep_string
== "from_system") {
296 sep_conversion
= SEP_FROM_SYSTEM
;
297 } else if (sep_string
!= "none") {
298 *err
= Err(args
[3], "Invalid path separator conversion mode.",
299 "I was expecting \"none\", \"to_system\", or \"from_system\" and\n"
300 "you gave me \"" + args
[3].string_value() + "\".");
305 if (inputs
.type() == Value::STRING
) {
306 return ConvertOnePath(scope
, function
, inputs
,
307 from_dir
, to_dir
, convert_to_system_absolute
,
308 sep_conversion
, err
);
310 } else if (inputs
.type() == Value::LIST
) {
311 result
= Value(function
, Value::LIST
);
312 result
.list_value().reserve(inputs
.list_value().size());
314 for (size_t i
= 0; i
< inputs
.list_value().size(); i
++) {
315 result
.list_value().push_back(
316 ConvertOnePath(scope
, function
, inputs
.list_value()[i
],
317 from_dir
, to_dir
, convert_to_system_absolute
,
318 sep_conversion
, err
));
319 if (err
->has_error()) {
327 *err
= Err(function
->function(),
328 "rebase_path requires a list or a string.");
332 } // namespace functions