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 result.string_value() =
110 RebasePath(resolved_file.value(), to_dir,
111 scope->settings()->build_settings()->root_path_utf8());
112 }
113
114 return result;
115 }
116
117 } // namespace
118
119 const char kRebasePath[] = "rebase_path";
120 const char kRebasePath_HelpShort[] =
121 "rebase_path: Rebase a file or directory to another location.";
122 const char kRebasePath_Help[] =
123 R"(rebase_path: Rebase a file or directory to another location.
124
125 converted = rebase_path(input,
126 new_base = "",
127 current_base = ".")
128
129 Takes a string argument representing a file name, or a list of such strings
130 and converts it/them to be relative to a different base directory.
131
132 When invoking the compiler or scripts, GN will automatically convert sources
133 and include directories to be relative to the build directory. However, if
134 you're passing files directly in the "args" array or doing other manual
135 manipulations where GN doesn't know something is a file name, you will need
136 to convert paths to be relative to what your tool is expecting.
137
138 The common case is to use this to convert paths relative to the current
139 directory to be relative to the build directory (which will be the current
140 directory when executing scripts).
141
142 If you want to convert a file path to be source-absolute (that is, beginning
143 with a double slash like "//foo/bar"), you should use the get_path_info()
144 function. This function won't work because it will always make relative
145 paths, and it needs to support making paths relative to the source root, so
146 it can't also generate source-absolute paths without more special-cases.
147
148 Arguments
149
150 input
151 A string or list of strings representing file or directory names. These
152 can be relative paths ("foo/bar.txt"), system absolute paths
153 ("/foo/bar.txt"), or source absolute paths ("//foo/bar.txt").
154
155 new_base
156 The directory to convert the paths to be relative to. This can be an
157 absolute path or a relative path (which will be treated as being relative
158 to the current BUILD-file's directory).
159
160 As a special case, if new_base is the empty string (the default), all
161 paths will be converted to system-absolute native style paths with system
162 path separators. This is useful for invoking external programs.
163
164 current_base
165 Directory representing the base for relative paths in the input. If this
166 is not an absolute path, it will be treated as being relative to the
167 current build file. Use "." (the default) to convert paths from the
168 current BUILD-file's directory.
169
170 Return value
171
172 The return value will be the same type as the input value (either a string or
173 a list of strings). All relative and source-absolute file names will be
174 converted to be relative to the requested output System-absolute paths will
175 be unchanged.
176
177 Whether an output path will end in a slash will match whether the
178 corresponding input path ends in a slash. It will return "." or "./"
179 (depending on whether the input ends in a slash) to avoid returning empty
180 strings. This means if you want a root path ("//" or "/") not ending in a
181 slash, you can add a dot ("//.").
182
183 Example
184
185 # Convert a file in the current directory to be relative to the build
186 # directory (the current dir when executing compilers and scripts).
187 foo = rebase_path("myfile.txt", root_build_dir)
188 # might produce "../../project/myfile.txt".
189
190 # Convert a file to be system absolute:
191 foo = rebase_path("myfile.txt")
192 # Might produce "D:\\source\\project\\myfile.txt" on Windows or
193 # "/home/you/source/project/myfile.txt" on Linux.
194
195 # Typical usage for converting to the build directory for a script.
196 action("myscript") {
197 # Don't convert sources, GN will automatically convert these to be relative
198 # to the build directory when it constructs the command line for your
199 # script.
200 sources = [ "foo.txt", "bar.txt" ]
201
202 # Extra file args passed manually need to be explicitly converted
203 # to be relative to the build directory:
204 args = [
205 "--data",
206 rebase_path("//mything/data/input.dat", root_build_dir),
207 "--rel",
208 rebase_path("relative_path.txt", root_build_dir)
209 ] + rebase_path(sources, root_build_dir)
210 }
211 )";
212
RunRebasePath(Scope * scope,const FunctionCallNode * function,const std::vector<Value> & args,Err * err)213 Value RunRebasePath(Scope* scope,
214 const FunctionCallNode* function,
215 const std::vector<Value>& args,
216 Err* err) {
217 Value result;
218
219 // Argument indices.
220 static const size_t kArgIndexInputs = 0;
221 static const size_t kArgIndexDest = 1;
222 static const size_t kArgIndexFrom = 2;
223
224 // Inputs.
225 if (args.size() < 1 || args.size() > 3) {
226 *err = Err(function->function(), "Wrong # of arguments for rebase_path.");
227 return result;
228 }
229 const Value& inputs = args[kArgIndexInputs];
230
231 // To path.
232 bool convert_to_system_absolute = true;
233 SourceDir to_dir;
234 const SourceDir& current_dir = scope->GetSourceDir();
235 if (args.size() > kArgIndexDest) {
236 if (!args[kArgIndexDest].VerifyTypeIs(Value::STRING, err))
237 return result;
238 if (!args[kArgIndexDest].string_value().empty()) {
239 to_dir = current_dir.ResolveRelativeDir(
240 args[kArgIndexDest], err,
241 scope->settings()->build_settings()->root_path_utf8());
242 if (err->has_error())
243 return Value();
244 convert_to_system_absolute = false;
245 }
246 }
247
248 // From path.
249 SourceDir from_dir;
250 if (args.size() > kArgIndexFrom) {
251 if (!args[kArgIndexFrom].VerifyTypeIs(Value::STRING, err))
252 return result;
253 from_dir = current_dir.ResolveRelativeDir(
254 args[kArgIndexFrom], err,
255 scope->settings()->build_settings()->root_path_utf8());
256 if (err->has_error())
257 return Value();
258 } else {
259 // Default to current directory if unspecified.
260 from_dir = current_dir;
261 }
262
263 // Path conversion.
264 if (inputs.type() == Value::STRING) {
265 return ConvertOnePath(scope, function, inputs, from_dir, to_dir,
266 convert_to_system_absolute, err);
267
268 } else if (inputs.type() == Value::LIST) {
269 result = Value(function, Value::LIST);
270 result.list_value().reserve(inputs.list_value().size());
271
272 for (const auto& input : inputs.list_value()) {
273 result.list_value().push_back(
274 ConvertOnePath(scope, function, input, from_dir, to_dir,
275 convert_to_system_absolute, err));
276 if (err->has_error()) {
277 result = Value();
278 return result;
279 }
280 }
281 return result;
282 }
283
284 *err = Err(function->function(), "rebase_path requires a list or a string.");
285 return result;
286 }
287
288 } // namespace functions
289