| // 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 "gn/build_settings.h" |
| #include "gn/filesystem_utils.h" |
| #include "gn/functions.h" |
| #include "gn/parse_tree.h" |
| #include "gn/scope.h" |
| #include "gn/settings.h" |
| #include "gn/source_dir.h" |
| #include "gn/source_file.h" |
| #include "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(); |
| 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 |
| it 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 |