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 <stddef.h>
6
7 #include "gn/build_settings.h"
8 #include "gn/filesystem_utils.h"
9 #include "gn/functions.h"
10 #include "gn/parse_tree.h"
11 #include "gn/scope.h"
12 #include "gn/settings.h"
13 #include "gn/source_dir.h"
14 #include "gn/source_file.h"
15 #include "gn/value.h"
16
17 namespace functions {
18
19 namespace {
20
21 // We want the output to match the input in terms of ending in a slash or not.
22 // Through all the transformations, these can get added or removed in various
23 // cases.
MakeSlashEndingMatchInput(const std::string & input,std::string * output)24 void MakeSlashEndingMatchInput(const std::string& input, std::string* output) {
25 if (EndsWithSlash(input)) {
26 if (!EndsWithSlash(*output)) // Preserve same slash type as input.
27 output->push_back(input[input.size() - 1]);
28 } else {
29 if (EndsWithSlash(*output))
30 output->resize(output->size() - 1);
31 }
32 }
33
34 // Returns true if the given value looks like a directory, otherwise we'll
35 // assume it's a file.
ValueLooksLikeDir(const std::string & value)36 bool ValueLooksLikeDir(const std::string& value) {
37 if (value.empty())
38 return true;
39 size_t value_size = value.size();
40
41 // Count the number of dots at the end of the string.
42 size_t num_dots = 0;
43 while (num_dots < value_size && value[value_size - num_dots - 1] == '.')
44 num_dots++;
45
46 if (num_dots == value.size())
47 return true; // String is all dots.
48
49 if (IsSlash(value[value_size - num_dots - 1]))
50 return true; // String is a [back]slash followed by 0 or more dots.
51
52 // Anything else.
53 return false;
54 }
55
ConvertOnePath(const Scope * scope,const FunctionCallNode * function,const Value & value,const SourceDir & from_dir,const SourceDir & to_dir,bool convert_to_system_absolute,Err * err)56 Value ConvertOnePath(const Scope* scope,
57 const FunctionCallNode* function,
58 const Value& value,
59 const SourceDir& from_dir,
60 const SourceDir& to_dir,
61 bool convert_to_system_absolute,
62 Err* err) {
63 Value result; // Ensure return value optimization.
64
65 if (!value.VerifyTypeIs(Value::STRING, err))
66 return result;
67 const std::string& string_value = value.string_value();
68
69 bool looks_like_dir = ValueLooksLikeDir(string_value);
70
71 // System-absolute output special case.
72 if (convert_to_system_absolute) {
73 base::FilePath system_path;
74 if (looks_like_dir) {
75 system_path = scope->settings()->build_settings()->GetFullPath(
76 from_dir.ResolveRelativeDir(
77 value, err,
78 scope->settings()->build_settings()->root_path_utf8()));
79 } else {
80 system_path = scope->settings()->build_settings()->GetFullPath(
81 from_dir.ResolveRelativeFile(
82 value, err,
83 scope->settings()->build_settings()->root_path_utf8()));
84 }
85 if (err->has_error())
86 return Value();
87
88 result = Value(function, FilePathToUTF8(system_path));
89 if (looks_like_dir)
90 MakeSlashEndingMatchInput(string_value, &result.string_value());
91 return result;
92 }
93
94 result = Value(function, Value::STRING);
95 if (looks_like_dir) {
96 result.string_value() = RebasePath(
97 from_dir
98 .ResolveRelativeDir(
99 value, err,
100 scope->settings()->build_settings()->root_path_utf8())
101 .value(),
102 to_dir, scope->settings()->build_settings()->root_path_utf8());
103 MakeSlashEndingMatchInput(string_value, &result.string_value());
104 } else {
105 SourceFile resolved_file = from_dir.ResolveRelativeFile(
106 value, err, scope->settings()->build_settings()->root_path_utf8());
107 if (err->has_error())
108 return Value();
109 // Special case:
110 // rebase_path("//foo", "//bar") ==> "../foo"
111 // rebase_path("//foo", "//foo") ==> "." and not "../foo"
112 if (resolved_file.value() ==
113 to_dir.value().substr(0, to_dir.value().size() - 1)) {
114 result.string_value() = ".";
115 } else {
116 result.string_value() =
117 RebasePath(resolved_file.value(), to_dir,
118 scope->settings()->build_settings()->root_path_utf8());
119 }
120 }
121
122 return result;
123 }
124
125 } // namespace
126
127 const char kRebasePath[] = "rebase_path";
128 const char kRebasePath_HelpShort[] =
129 "rebase_path: Rebase a file or directory to another location.";
130 const char kRebasePath_Help[] =
131 R"(rebase_path: Rebase a file or directory to another location.
132
133 converted = rebase_path(input,
134 new_base = "",
135 current_base = ".")
136
137 Takes a string argument representing a file name, or a list of such strings
138 and converts it/them to be relative to a different base directory.
139
140 When invoking the compiler or scripts, GN will automatically convert sources
141 and include directories to be relative to the build directory. However, if
142 you're passing files directly in the "args" array or doing other manual
143 manipulations where GN doesn't know something is a file name, you will need
144 to convert paths to be relative to what your tool is expecting.
145
146 The common case is to use this to convert paths relative to the current
147 directory to be relative to the build directory (which will be the current
148 directory when executing scripts).
149
150 If you want to convert a file path to be source-absolute (that is, beginning
151 with a double slash like "//foo/bar"), you should use the get_path_info()
152 function. This function won't work because it will always make relative
153 paths, and it needs to support making paths relative to the source root, so
154 can't also generate source-absolute paths without more special-cases.
155
156 Arguments
157
158 input
159 A string or list of strings representing file or directory names These
160 can be relative paths ("foo/bar.txt"), system absolute paths
161 ("/foo/bar.txt"), or source absolute paths ("//foo/bar.txt").
162
163 new_base
164 The directory to convert the paths to be relative to. This can be an
165 absolute path or a relative path (which will be treated as being relative
166 to the current BUILD-file's directory).
167
168 As a special case, if new_base is the empty string (the default), all
169 paths will be converted to system-absolute native style paths with system
170 path separators. This is useful for invoking external programs.
171
172 current_base
173 Directory representing the base for relative paths in the input. If this
174 is not an absolute path, it will be treated as being relative to the
175 current build file. Use "." (the default) to convert paths from the
176 current BUILD-file's directory.
177
178 Return value
179
180 The return value will be the same type as the input value (either a string or
181 a list of strings). All relative and source-absolute file names will be
182 converted to be relative to the requested output System-absolute paths will
183 be unchanged.
184
185 Whether an output path will end in a slash will match whether the
186 corresponding input path ends in a slash. It will return "." or "./"
187 (depending on whether the input ends in a slash) to avoid returning empty
188 strings. This means if you want a root path ("//" or "/") not ending in a
189 slash, you can add a dot ("//.").
190
191 Example
192
193 # Convert a file in the current directory to be relative to the build
194 # directory (the current dir when executing compilers and scripts).
195 foo = rebase_path("myfile.txt", root_build_dir)
196 # might produce "../../project/myfile.txt".
197
198 # Convert a file to be system absolute:
199 foo = rebase_path("myfile.txt")
200 # Might produce "D:\\source\\project\\myfile.txt" on Windows or
201 # "/home/you/source/project/myfile.txt" on Linux.
202
203 # Typical usage for converting to the build directory for a script.
204 action("myscript") {
205 # Don't convert sources, GN will automatically convert these to be relative
206 # to the build directory when it constructs the command line for your
207 # script.
208 sources = [ "foo.txt", "bar.txt" ]
209
210 # Extra file args passed manually need to be explicitly converted
211 # to be relative to the build directory:
212 args = [
213 "--data",
214 rebase_path("//mything/data/input.dat", root_build_dir),
215 "--rel",
216 rebase_path("relative_path.txt", root_build_dir)
217 ] + rebase_path(sources, root_build_dir)
218 }
219 )";
220
RunRebasePath(Scope * scope,const FunctionCallNode * function,const std::vector<Value> & args,Err * err)221 Value RunRebasePath(Scope* scope,
222 const FunctionCallNode* function,
223 const std::vector<Value>& args,
224 Err* err) {
225 Value result;
226
227 // Argument indices.
228 static const size_t kArgIndexInputs = 0;
229 static const size_t kArgIndexDest = 1;
230 static const size_t kArgIndexFrom = 2;
231
232 // Inputs.
233 if (args.size() < 1 || args.size() > 3) {
234 *err = Err(function->function(), "Wrong # of arguments for rebase_path.");
235 return result;
236 }
237 const Value& inputs = args[kArgIndexInputs];
238
239 // To path.
240 bool convert_to_system_absolute = true;
241 SourceDir to_dir;
242 const SourceDir& current_dir = scope->GetSourceDir();
243 if (args.size() > kArgIndexDest) {
244 if (!args[kArgIndexDest].VerifyTypeIs(Value::STRING, err))
245 return result;
246 if (!args[kArgIndexDest].string_value().empty()) {
247 to_dir = current_dir.ResolveRelativeDir(
248 args[kArgIndexDest], err,
249 scope->settings()->build_settings()->root_path_utf8());
250 if (err->has_error())
251 return Value();
252 convert_to_system_absolute = false;
253 }
254 }
255
256 // From path.
257 SourceDir from_dir;
258 if (args.size() > kArgIndexFrom) {
259 if (!args[kArgIndexFrom].VerifyTypeIs(Value::STRING, err))
260 return result;
261 from_dir = current_dir.ResolveRelativeDir(
262 args[kArgIndexFrom], err,
263 scope->settings()->build_settings()->root_path_utf8());
264 if (err->has_error())
265 return Value();
266 } else {
267 // Default to current directory if unspecified.
268 from_dir = current_dir;
269 }
270
271 // Path conversion.
272 if (inputs.type() == Value::STRING) {
273 return ConvertOnePath(scope, function, inputs, from_dir, to_dir,
274 convert_to_system_absolute, err);
275
276 } else if (inputs.type() == Value::LIST) {
277 result = Value(function, Value::LIST);
278 result.list_value().reserve(inputs.list_value().size());
279
280 for (const auto& input : inputs.list_value()) {
281 result.list_value().push_back(
282 ConvertOnePath(scope, function, input, from_dir, to_dir,
283 convert_to_system_absolute, err));
284 if (err->has_error()) {
285 result = Value();
286 return result;
287 }
288 }
289 return result;
290 }
291
292 *err = Err(function->function(), "rebase_path requires a list or a string.");
293 return result;
294 }
295
296 } // namespace functions
297