• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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