// 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] == '.') 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" "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& 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