| // 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 |