| // 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 { |
| 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" |
| " Whether an output path will end in a slash will match whether the\n" |
| " corresponding input path ends in a slash. It will return \".\" or\n" |
| " \"./\" (depending on whether the input ends in a slash) to avoid\n" |
| " returning empty strings. This means if you want a root path\n" |
| " (\"//\" or \"/\") not ending in a slash, you can add a dot (\"//.\").\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<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 |