blob: 9d3a4e3151657c42284d08d0d056e3961472f21c [file] [log] [blame]
// 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 "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] == '.')
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(
} else {
system_path = scope->settings()->build_settings()->GetFullPath(
result = Value(function, FilePathToUTF8(system_path));
if (looks_like_dir)
MakeSlashEndingMatchInput(string_value, &result.string_value());
return result;
if (from_dir.is_system_absolute() || to_dir.is_system_absolute()) {
*err = Err(function, "System-absolute directories are not supported for "
"the source or dest dir for rebase_path. It would be nice to add this "
"if you're so inclined!");
return result;
result = Value(function, Value::STRING);
if (looks_like_dir) {
result.string_value() = RebaseSourceAbsolutePath(
MakeSlashEndingMatchInput(string_value, &result.string_value());
} else {
result.string_value() = RebaseSourceAbsolutePath(
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"
" converted = rebase_path(input,\n"
" new_base = \"\",\n"
" current_base = \".\")\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"
" 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"
" 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"
" 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"
" 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"
" 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"
" 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"
" 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"
" On Posix systems there are no path separator transformations\n"
" applied. If the new_base is empty (specifying absolute output)\n"
" this parameter should not be supplied since paths will always be\n"
" converted,\n"
"Return value\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"
" # 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"
" # 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"
" # Convert a file's path separators from forward slashes to system\n"
" # slashes.\n"
" foo = rebase_path(\"source/myfile.txt\", \".\", \".\", \"to_system\")\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 contructs the command\n"
" # line for your script.\n"
" sources = [ \"foo.txt\", \"bar.txt\" ]\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"
" ] + sources\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 =
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 =
} else {
// Default to current directory if unspecified.
from_dir = current_dir;
// Path conversion.
if (inputs.type() == Value::STRING) {
if (inputs.string_value() == "//foo")
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);
for (size_t i = 0; i < inputs.list_value().size(); i++) {
ConvertOnePath(scope, function, inputs.list_value()[i],
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