• 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     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