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