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_TO_SLASH, // All slashes to forward.
21 SEP_TO_SYSTEM, // Slashes to system ones.
24 // Does the specified path separator conversion in-place.
25 void ConvertSlashes(std::string* str, SeparatorConversion mode) {
27 if (mode == SEP_TO_SYSTEM)
28 std::replace(str->begin(), str->end(), '/', '\\');
31 if (mode == SEP_TO_SLASH)
32 std::replace(str->begin(), str->end(), '\\', '/');
35 // We want the output to match the input in terms of ending in a slash or not.
36 // Through all the transformations, these can get added or removed in various
38 void MakeSlashEndingMatchInput(const std::string& input, std::string* output) {
39 if (EndsWithSlash(input)) {
40 if (!EndsWithSlash(*output)) // Preserve same slash type as input.
41 output->push_back(input[input.size() - 1]);
43 if (EndsWithSlash(*output))
44 output->resize(output->size() - 1);
48 // Returns true if the given value looks like a directory, otherwise we'll
49 // assume it's a file.
50 bool ValueLooksLikeDir(const std::string& value) {
53 size_t value_size = value.size();
55 // Count the number of dots at the end of the string.
57 while (num_dots < value_size && value[value_size - num_dots - 1] == '.')
60 if (num_dots == value.size())
61 return true; // String is all dots.
63 if (IsSlash(value[value_size - num_dots - 1]))
64 return true; // String is a [back]slash followed by 0 or more dots.
70 Value ConvertOnePath(const Scope* scope,
71 const FunctionCallNode* function,
73 const SourceDir& from_dir,
74 const SourceDir& to_dir,
75 bool convert_to_system_absolute,
76 SeparatorConversion separator_conversion,
78 Value result; // Ensure return value optimization.
80 if (!value.VerifyTypeIs(Value::STRING, err))
82 const std::string& string_value = value.string_value();
84 bool looks_like_dir = ValueLooksLikeDir(string_value);
86 // System-absolute output special case.
87 if (convert_to_system_absolute) {
88 base::FilePath system_path;
90 system_path = scope->settings()->build_settings()->GetFullPath(
91 from_dir.ResolveRelativeDir(string_value));
93 system_path = scope->settings()->build_settings()->GetFullPath(
94 from_dir.ResolveRelativeFile(string_value));
96 result = Value(function, FilePathToUTF8(system_path));
98 MakeSlashEndingMatchInput(string_value, &result.string_value());
99 ConvertPathToSystem(&result.string_value());
103 if (from_dir.is_system_absolute() || to_dir.is_system_absolute()) {
104 *err = Err(function, "System-absolute directories are not supported for "
105 "the source or dest dir for rebase_path. It would be nice to add this "
106 "if you're so inclined!");
110 result = Value(function, Value::STRING);
111 if (looks_like_dir) {
112 result.string_value() = RebaseSourceAbsolutePath(
113 from_dir.ResolveRelativeDir(string_value).value(),
115 MakeSlashEndingMatchInput(string_value, &result.string_value());
117 result.string_value() = RebaseSourceAbsolutePath(
118 from_dir.ResolveRelativeFile(string_value).value(),
122 ConvertSlashes(&result.string_value(), separator_conversion);
128 const char kRebasePath[] = "rebase_path";
129 const char kRebasePath_Help[] =
130 "rebase_path: Rebase a file or directory to another location.\n"
132 " converted = rebase_path(input,\n"
133 " new_base = \"\",\n"
134 " current_base = \".\",\n"
135 " path_separators = \"to_slash\")\n"
137 " Takes a string argument representing a file name, or a list of such\n"
138 " strings and converts it/them to be relative to a different base\n"
141 " When invoking the compiler or scripts, GN will automatically convert\n"
142 " sources and include directories to be relative to the build directory.\n"
143 " However, if you're passing files directly in the \"args\" array or\n"
144 " doing other manual manipulations where GN doesn't know something is\n"
145 " a file name, you will need to convert paths to be relative to what\n"
146 " your tool is expecting.\n"
148 " The common case is to use this to convert paths relative to the\n"
149 " current directory to be relative to the build directory (which will\n"
150 " be the current directory when executing scripts).\n"
155 " A string or list of strings representing file or directory names\n"
156 " These can be relative paths (\"foo/bar.txt\"), system absolute\n"
157 " paths (\"/foo/bar.txt\"), or source absolute paths\n"
158 " (\"//foo/bar.txt\").\n"
161 " The directory to convert the paths to be relative to. This can be\n"
162 " an absolute path or a relative path (which will be treated\n"
163 " as being relative to the current BUILD-file's directory).\n"
165 " As a special case, if new_base is the empty string (the default),\n"
166 " all paths will be converted to system-absolute native style paths\n"
167 " with system path separators. This is useful for invoking external\n"
171 " Directory representing the base for relative paths in the input.\n"
172 " If this is not an absolute path, it will be treated as being\n"
173 " relative to the current build file. Use \".\" (the default) to\n"
174 " convert paths from the current BUILD-file's directory.\n"
177 " On Windows systems, indicates whether and how path separators\n"
178 " should be converted as part of the transformation. It can be one\n"
179 " of the following strings:\n"
180 " - \"to_slash\" Normalize all types of slashes to forward slashes.\n"
181 " This is the default if this argument is unspecified.\n"
182 " - \"to_system\" Convert to the system path separators\n"
183 " (backslashes on Windows).\n"
185 " On Posix systems there are no path separator transformations\n"
186 " applied. If the new_base is empty (specifying absolute output)\n"
187 " this parameter should not be supplied since paths will always be\n"
192 " The return value will be the same type as the input value (either a\n"
193 " string or a list of strings). All relative and source-absolute file\n"
194 " names will be converted to be relative to the requested output\n"
195 " System-absolute paths will be unchanged.\n"
199 " # Convert a file in the current directory to be relative to the build\n"
200 " # directory (the current dir when executing compilers and scripts).\n"
201 " foo = rebase_path(\"myfile.txt\", root_build_dir)\n"
202 " # might produce \"../../project/myfile.txt\".\n"
204 " # Convert a file to be system absolute:\n"
205 " foo = rebase_path(\"myfile.txt\")\n"
206 " # Might produce \"D:\\source\\project\\myfile.txt\" on Windows or\n"
207 " # \"/home/you/source/project/myfile.txt\" on Linux.\n"
209 " # Convert a file's path separators from forward slashes to system\n"
211 " foo = rebase_path(\"source/myfile.txt\", \".\", \".\", \"to_system\")\n"
213 " # Typical usage for converting to the build directory for a script.\n"
214 " action(\"myscript\") {\n"
215 " # Don't convert sources, GN will automatically convert these to be\n"
216 " # relative to the build directory when it contructs the command\n"
217 " # line for your script.\n"
218 " sources = [ \"foo.txt\", \"bar.txt\" ]\n"
220 " # Extra file args passed manually need to be explicitly converted\n"
221 " # to be relative to the build directory:\n"
224 " rebase_path(\"//mything/data/input.dat\", root_build_dir),\n"
226 " rebase_path(\"relative_path.txt\", root_build_dir)\n"
230 Value RunRebasePath(Scope* scope,
231 const FunctionCallNode* function,
232 const std::vector<Value>& args,
237 static const size_t kArgIndexInputs = 0;
238 static const size_t kArgIndexDest = 1;
239 static const size_t kArgIndexFrom = 2;
240 static const size_t kArgIndexPathConversion = 3;
243 if (args.size() < 1 || args.size() > 4) {
244 *err = Err(function->function(), "Wrong # of arguments for rebase_path.");
247 const Value& inputs = args[kArgIndexInputs];
250 bool convert_to_system_absolute = true;
252 const SourceDir& current_dir = scope->GetSourceDir();
253 if (args.size() > kArgIndexDest) {
254 if (!args[kArgIndexDest].VerifyTypeIs(Value::STRING, err))
256 if (!args[kArgIndexDest].string_value().empty()) {
258 current_dir.ResolveRelativeDir(args[kArgIndexDest].string_value());
259 convert_to_system_absolute = false;
265 if (args.size() > kArgIndexFrom) {
266 if (!args[kArgIndexFrom].VerifyTypeIs(Value::STRING, err))
269 current_dir.ResolveRelativeDir(args[kArgIndexFrom].string_value());
271 // Default to current directory if unspecified.
272 from_dir = current_dir;
276 SeparatorConversion sep_conversion = SEP_TO_SLASH;
277 if (args.size() > kArgIndexPathConversion) {
278 if (convert_to_system_absolute) {
279 *err = Err(function, "Can't specify slash conversion.",
280 "You specified absolute system path output by using an empty string "
281 "for the destination directory on rebase_path(). In this case, you "
282 "can't specify slash conversion.");
286 if (!args[kArgIndexPathConversion].VerifyTypeIs(Value::STRING, err))
288 const std::string& sep_string =
289 args[kArgIndexPathConversion].string_value();
290 if (sep_string == "to_slash") {
291 sep_conversion = SEP_TO_SLASH;
292 } else if (sep_string == "to_system") {
293 sep_conversion = SEP_TO_SYSTEM;
295 *err = Err(args[kArgIndexPathConversion],
296 "Invalid path separator conversion mode.",
297 "I was expecting \"to_slash\" or \"to_system\" and\n"
298 "you gave me \"" + args[kArgIndexPathConversion].string_value() +
304 if (inputs.type() == Value::STRING) {
305 return ConvertOnePath(scope, function, inputs,
306 from_dir, to_dir, convert_to_system_absolute,
307 sep_conversion, err);
309 } else if (inputs.type() == Value::LIST) {
310 result = Value(function, Value::LIST);
311 result.list_value().reserve(inputs.list_value().size());
313 for (size_t i = 0; i < inputs.list_value().size(); i++) {
314 result.list_value().push_back(
315 ConvertOnePath(scope, function, inputs.list_value()[i],
316 from_dir, to_dir, convert_to_system_absolute,
317 sep_conversion, err));
318 if (err->has_error()) {
326 *err = Err(function->function(),
327 "rebase_path requires a list or a string.");
331 } // namespace functions