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