a167cdad15f43e3de1bbb2731fb80dbc17f9599e
[platform/framework/web/crosswalk.git] / src / tools / gn / function_rebase_path.cc
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 "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"
14
15 namespace functions {
16
17 namespace {
18
19 enum SeparatorConversion {
20   SEP_TO_SLASH,  // All slashes to forward.
21   SEP_TO_SYSTEM,  // Slashes to system ones.
22 };
23
24 // Does the specified path separator conversion in-place.
25 void ConvertSlashes(std::string* str, SeparatorConversion mode) {
26 #if defined(OS_WIN)
27   if (mode == SEP_TO_SYSTEM)
28     std::replace(str->begin(), str->end(), '/', '\\');
29   else
30 #endif
31   if (mode == SEP_TO_SLASH)
32     std::replace(str->begin(), str->end(), '\\', '/');
33 }
34
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
37 // cases.
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]);
42   } else {
43     if (EndsWithSlash(*output))
44       output->resize(output->size() - 1);
45   }
46 }
47
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) {
51   if (value.empty())
52     return true;
53   size_t value_size = value.size();
54
55   // Count the number of dots at the end of the string.
56   size_t num_dots = 0;
57   while (num_dots < value_size && value[value_size - num_dots - 1] == '.')
58     num_dots++;
59
60   if (num_dots == value.size())
61     return true;  // String is all dots.
62
63   if (IsSlash(value[value_size - num_dots - 1]))
64     return true;  // String is a [back]slash followed by 0 or more dots.
65
66   // Anything else.
67   return false;
68 }
69
70 Value ConvertOnePath(const Scope* scope,
71                      const FunctionCallNode* function,
72                      const Value& value,
73                      const SourceDir& from_dir,
74                      const SourceDir& to_dir,
75                      bool convert_to_system_absolute,
76                      SeparatorConversion separator_conversion,
77                      Err* err) {
78   Value result;  // Ensure return value optimization.
79
80   if (!value.VerifyTypeIs(Value::STRING, err))
81     return result;
82   const std::string& string_value = value.string_value();
83
84   bool looks_like_dir = ValueLooksLikeDir(string_value);
85
86   // System-absolute output special case.
87   if (convert_to_system_absolute) {
88     base::FilePath system_path;
89     if (looks_like_dir) {
90       system_path = scope->settings()->build_settings()->GetFullPath(
91           from_dir.ResolveRelativeDir(string_value));
92     } else {
93       system_path = scope->settings()->build_settings()->GetFullPath(
94           from_dir.ResolveRelativeFile(string_value));
95     }
96     result = Value(function, FilePathToUTF8(system_path));
97     if (looks_like_dir)
98       MakeSlashEndingMatchInput(string_value, &result.string_value());
99     ConvertPathToSystem(&result.string_value());
100     return result;
101   }
102
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!");
107     return result;
108   }
109
110   result = Value(function, Value::STRING);
111   if (looks_like_dir) {
112     result.string_value() = RebaseSourceAbsolutePath(
113         from_dir.ResolveRelativeDir(string_value).value(),
114         to_dir);
115     MakeSlashEndingMatchInput(string_value, &result.string_value());
116   } else {
117     result.string_value() = RebaseSourceAbsolutePath(
118         from_dir.ResolveRelativeFile(string_value).value(),
119         to_dir);
120   }
121
122   ConvertSlashes(&result.string_value(), separator_conversion);
123   return result;
124 }
125
126 }  // namespace
127
128 const char kRebasePath[] = "rebase_path";
129 const char kRebasePath_Help[] =
130     "rebase_path: Rebase a file or directory to another location.\n"
131     "\n"
132     "  converted = rebase_path(input,\n"
133     "                          new_base = \"\",\n"
134     "                          current_base = \".\",\n"
135     "                          path_separators = \"to_slash\")\n"
136     "\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"
139     "  directory.\n"
140     "\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"
147     "\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"
151     "\n"
152     "Arguments\n"
153     "\n"
154     "  input\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"
159     "\n"
160     "  new_base\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"
164     "\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"
168     "      programs.\n"
169     "\n"
170     "  current_base\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"
175     "\n"
176     "  path_separators\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"
184     "\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"
188     "      converted,\n"
189     "\n"
190     "Return value\n"
191     "\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"
196     "\n"
197     "Example\n"
198     "\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"
203     "\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"
208     "\n"
209     "  # Convert a file's path separators from forward slashes to system\n"
210     "  # slashes.\n"
211     "  foo = rebase_path(\"source/myfile.txt\", \".\", \".\", \"to_system\")\n"
212     "\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"
219     "\n"
220     "    # Extra file args passed manually need to be explicitly converted\n"
221     "    # to be relative to the build directory:\n"
222     "    args = [\n"
223     "      \"--data\",\n"
224     "      rebase_path(\"//mything/data/input.dat\", root_build_dir),\n"
225     "      \"--rel\",\n"
226     "      rebase_path(\"relative_path.txt\", root_build_dir)\n"
227     "    ] + sources\n"
228     "  }\n";
229
230 Value RunRebasePath(Scope* scope,
231                     const FunctionCallNode* function,
232                     const std::vector<Value>& args,
233                     Err* err) {
234   Value result;
235
236   // Argument indices.
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;
241
242   // Inputs.
243   if (args.size() < 1 || args.size() > 4) {
244     *err = Err(function->function(), "Wrong # of arguments for rebase_path.");
245     return result;
246   }
247   const Value& inputs = args[kArgIndexInputs];
248
249   // To path.
250   bool convert_to_system_absolute = true;
251   SourceDir to_dir;
252   const SourceDir& current_dir = scope->GetSourceDir();
253   if (args.size() > kArgIndexDest) {
254     if (!args[kArgIndexDest].VerifyTypeIs(Value::STRING, err))
255       return result;
256     if (!args[kArgIndexDest].string_value().empty()) {
257       to_dir =
258           current_dir.ResolveRelativeDir(args[kArgIndexDest].string_value());
259       convert_to_system_absolute = false;
260     }
261   }
262
263   // From path.
264   SourceDir from_dir;
265   if (args.size() > kArgIndexFrom) {
266     if (!args[kArgIndexFrom].VerifyTypeIs(Value::STRING, err))
267       return result;
268     from_dir =
269         current_dir.ResolveRelativeDir(args[kArgIndexFrom].string_value());
270   } else {
271     // Default to current directory if unspecified.
272     from_dir = current_dir;
273   }
274
275   // Path conversion.
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.");
283       return result;
284     }
285
286     if (!args[kArgIndexPathConversion].VerifyTypeIs(Value::STRING, err))
287       return result;
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;
294     } else {
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() +
299           "\".");
300       return result;
301     }
302   }
303
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);
308
309   } else if (inputs.type() == Value::LIST) {
310     result = Value(function, Value::LIST);
311     result.list_value().reserve(inputs.list_value().size());
312
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()) {
319         result = Value();
320         return result;
321       }
322     }
323     return result;
324   }
325
326   *err = Err(function->function(),
327              "rebase_path requires a list or a string.");
328   return result;
329 }
330
331 }  // namespace functions