summaryrefslogtreecommitdiffstats
path: root/tools/gn/function_rebase_path.cc
blob: ef5ef40f984046cab216970f62b9eaaac27ea21c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stddef.h>

#include "tools/gn/build_settings.h"
#include "tools/gn/filesystem_utils.h"
#include "tools/gn/functions.h"
#include "tools/gn/parse_tree.h"
#include "tools/gn/scope.h"
#include "tools/gn/settings.h"
#include "tools/gn/source_dir.h"
#include "tools/gn/source_file.h"
#include "tools/gn/value.h"

namespace functions {

namespace {

// We want the output to match the input in terms of ending in a slash or not.
// Through all the transformations, these can get added or removed in various
// cases.
void MakeSlashEndingMatchInput(const std::string& input, std::string* output) {
  if (EndsWithSlash(input)) {
    if (!EndsWithSlash(*output))  // Preserve same slash type as input.
      output->push_back(input[input.size() - 1]);
  } else {
    if (EndsWithSlash(*output))
      output->resize(output->size() - 1);
  }
}

// Returns true if the given value looks like a directory, otherwise we'll
// assume it's a file.
bool ValueLooksLikeDir(const std::string& value) {
  if (value.empty())
    return true;
  size_t value_size = value.size();

  // Count the number of dots at the end of the string.
  size_t num_dots = 0;
  while (num_dots < value_size && value[value_size - num_dots - 1] == '.')
    num_dots++;

  if (num_dots == value.size())
    return true;  // String is all dots.

  if (IsSlash(value[value_size - num_dots - 1]))
    return true;  // String is a [back]slash followed by 0 or more dots.

  // Anything else.
  return false;
}

Value 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) {
  Value result;  // Ensure return value optimization.

  if (!value.VerifyTypeIs(Value::STRING, err))
    return result;
  const std::string& string_value = value.string_value();

  bool looks_like_dir = ValueLooksLikeDir(string_value);

  // System-absolute output special case.
  if (convert_to_system_absolute) {
    base::FilePath system_path;
    if (looks_like_dir) {
      system_path = scope->settings()->build_settings()->GetFullPath(
          from_dir.ResolveRelativeDir(value, err,
              scope->settings()->build_settings()->root_path_utf8()));
    } else {
      system_path = scope->settings()->build_settings()->GetFullPath(
          from_dir.ResolveRelativeFile(value, err,
              scope->settings()->build_settings()->root_path_utf8()));
    }
    if (err->has_error())
      return Value();

    result = Value(function, FilePathToUTF8(system_path));
    if (looks_like_dir)
      MakeSlashEndingMatchInput(string_value, &result.string_value());
    return result;
  }

  result = Value(function, Value::STRING);
  if (looks_like_dir) {
    result.string_value() = RebasePath(
        from_dir.ResolveRelativeDir(value, err,
            scope->settings()->build_settings()->root_path_utf8()).value(),
        to_dir,
        scope->settings()->build_settings()->root_path_utf8());
    MakeSlashEndingMatchInput(string_value, &result.string_value());
  } else {
    result.string_value() = RebasePath(
        from_dir.ResolveRelativeFile(value, err,
            scope->settings()->build_settings()->root_path_utf8()).value(),
        to_dir,
        scope->settings()->build_settings()->root_path_utf8());
    if (err->has_error())
      return Value();
  }

  return result;
}

}  // namespace

const char kRebasePath[] = "rebase_path";
const char kRebasePath_HelpShort[] =
    "rebase_path: Rebase a file or directory to another location.";
const char kRebasePath_Help[] =
    "rebase_path: Rebase a file or directory to another location.\n"
    "\n"
    "  converted = rebase_path(input,\n"
    "                          new_base = \"\",\n"
    "                          current_base = \".\")\n"
    "\n"
    "  Takes a string argument representing a file name, or a list of such\n"
    "  strings and converts it/them to be relative to a different base\n"
    "  directory.\n"
    "\n"
    "  When invoking the compiler or scripts, GN will automatically convert\n"
    "  sources and include directories to be relative to the build directory.\n"
    "  However, if you're passing files directly in the \"args\" array or\n"
    "  doing other manual manipulations where GN doesn't know something is\n"
    "  a file name, you will need to convert paths to be relative to what\n"
    "  your tool is expecting.\n"
    "\n"
    "  The common case is to use this to convert paths relative to the\n"
    "  current directory to be relative to the build directory (which will\n"
    "  be the current directory when executing scripts).\n"
    "\n"
    "  If you want to convert a file path to be source-absolute (that is,\n"
    "  beginning with a double slash like \"//foo/bar\"), you should use\n"
    "  the get_path_info() function. This function won't work because it will\n"
    "  always make relative paths, and it needs to support making paths\n"
    "  relative to the source root, so can't also generate source-absolute\n"
    "  paths without more special-cases.\n"
    "\n"
    "Arguments\n"
    "\n"
    "  input\n"
    "      A string or list of strings representing file or directory names\n"
    "      These can be relative paths (\"foo/bar.txt\"), system absolute\n"
    "      paths (\"/foo/bar.txt\"), or source absolute paths\n"
    "      (\"//foo/bar.txt\").\n"
    "\n"
    "  new_base\n"
    "      The directory to convert the paths to be relative to. This can be\n"
    "      an absolute path or a relative path (which will be treated\n"
    "      as being relative to the current BUILD-file's directory).\n"
    "\n"
    "      As a special case, if new_base is the empty string (the default),\n"
    "      all paths will be converted to system-absolute native style paths\n"
    "      with system path separators. This is useful for invoking external\n"
    "      programs.\n"
    "\n"
    "  current_base\n"
    "      Directory representing the base for relative paths in the input.\n"
    "      If this is not an absolute path, it will be treated as being\n"
    "      relative to the current build file. Use \".\" (the default) to\n"
    "      convert paths from the current BUILD-file's directory.\n"
    "\n"
    "Return value\n"
    "\n"
    "  The return value will be the same type as the input value (either a\n"
    "  string or a list of strings). All relative and source-absolute file\n"
    "  names will be converted to be relative to the requested output\n"
    "  System-absolute paths will be unchanged.\n"
    "\n"
    "  Whether an output path will end in a slash will match whether the\n"
    "  corresponding input path ends in a slash. It will return \".\" or\n"
    "  \"./\" (depending on whether the input ends in a slash) to avoid\n"
    "  returning empty strings. This means if you want a root path\n"
    "  (\"//\" or \"/\") not ending in a slash, you can add a dot (\"//.\").\n"
    "\n"
    "Example\n"
    "\n"
    "  # Convert a file in the current directory to be relative to the build\n"
    "  # directory (the current dir when executing compilers and scripts).\n"
    "  foo = rebase_path(\"myfile.txt\", root_build_dir)\n"
    "  # might produce \"../../project/myfile.txt\".\n"
    "\n"
    "  # Convert a file to be system absolute:\n"
    "  foo = rebase_path(\"myfile.txt\")\n"
    "  # Might produce \"D:\\source\\project\\myfile.txt\" on Windows or\n"
    "  # \"/home/you/source/project/myfile.txt\" on Linux.\n"
    "\n"
    "  # Typical usage for converting to the build directory for a script.\n"
    "  action(\"myscript\") {\n"
    "    # Don't convert sources, GN will automatically convert these to be\n"
    "    # relative to the build directory when it constructs the command\n"
    "    # line for your script.\n"
    "    sources = [ \"foo.txt\", \"bar.txt\" ]\n"
    "\n"
    "    # Extra file args passed manually need to be explicitly converted\n"
    "    # to be relative to the build directory:\n"
    "    args = [\n"
    "      \"--data\",\n"
    "      rebase_path(\"//mything/data/input.dat\", root_build_dir),\n"
    "      \"--rel\",\n"
    "      rebase_path(\"relative_path.txt\", root_build_dir)\n"
    "    ] + rebase_path(sources, root_build_dir)\n"
    "  }\n";

Value RunRebasePath(Scope* scope,
                    const FunctionCallNode* function,
                    const std::vector<Value>& args,
                    Err* err) {
  Value result;

  // Argument indices.
  static const size_t kArgIndexInputs = 0;
  static const size_t kArgIndexDest = 1;
  static const size_t kArgIndexFrom = 2;

  // Inputs.
  if (args.size() < 1 || args.size() > 3) {
    *err = Err(function->function(), "Wrong # of arguments for rebase_path.");
    return result;
  }
  const Value& inputs = args[kArgIndexInputs];

  // To path.
  bool convert_to_system_absolute = true;
  SourceDir to_dir;
  const SourceDir& current_dir = scope->GetSourceDir();
  if (args.size() > kArgIndexDest) {
    if (!args[kArgIndexDest].VerifyTypeIs(Value::STRING, err))
      return result;
    if (!args[kArgIndexDest].string_value().empty()) {
      to_dir = current_dir.ResolveRelativeDir(
          args[kArgIndexDest], err,
          scope->settings()->build_settings()->root_path_utf8());
      if (err->has_error())
        return Value();
      convert_to_system_absolute = false;
    }
  }

  // From path.
  SourceDir from_dir;
  if (args.size() > kArgIndexFrom) {
    if (!args[kArgIndexFrom].VerifyTypeIs(Value::STRING, err))
      return result;
    from_dir = current_dir.ResolveRelativeDir(
        args[kArgIndexFrom], err,
        scope->settings()->build_settings()->root_path_utf8());
    if (err->has_error())
      return Value();
  } else {
    // Default to current directory if unspecified.
    from_dir = current_dir;
  }

  // Path conversion.
  if (inputs.type() == Value::STRING) {
    return ConvertOnePath(scope, function, inputs,
                          from_dir, to_dir, convert_to_system_absolute, err);

  } else if (inputs.type() == Value::LIST) {
    result = Value(function, Value::LIST);
    result.list_value().reserve(inputs.list_value().size());

    for (const auto& input : inputs.list_value()) {
      result.list_value().push_back(
          ConvertOnePath(scope, function, input,
                         from_dir, to_dir, convert_to_system_absolute, err));
      if (err->has_error()) {
        result = Value();
        return result;
      }
    }
    return result;
  }

  *err = Err(function->function(),
             "rebase_path requires a list or a string.");
  return result;
}

}  // namespace functions