| // 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 { |
| |
| enum SeparatorConversion { |
| SEP_TO_SLASH, // All slashes to forward. |
| SEP_TO_SYSTEM, // Slashes to system ones. |
| }; |
| |
| // Does the specified path separator conversion in-place. |
| void ConvertSlashes(std::string* str, SeparatorConversion mode) { |
| #if defined(OS_WIN) |
| if (mode == SEP_TO_SYSTEM) |
| std::replace(str->begin(), str->end(), '/', '\\'); |
| else |
| #endif |
| if (mode == SEP_TO_SLASH) |
| std::replace(str->begin(), str->end(), '\\', '/'); |
| } |
| |
| // 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, |
| SeparatorConversion separator_conversion, |
| 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(string_value)); |
| } else { |
| system_path = scope->settings()->build_settings()->GetFullPath( |
| from_dir.ResolveRelativeFile(string_value)); |
| } |
| result = Value(function, FilePathToUTF8(system_path)); |
| if (looks_like_dir) |
| MakeSlashEndingMatchInput(string_value, &result.string_value()); |
| ConvertPathToSystem(&result.string_value()); |
| return result; |
| } |
| |
| if (from_dir.is_system_absolute() || to_dir.is_system_absolute()) { |
| *err = Err(function, "System-absolute directories are not supported for " |
| "the source or dest dir for rebase_path. It would be nice to add this " |
| "if you're so inclined!"); |
| return result; |
| } |
| |
| result = Value(function, Value::STRING); |
| if (looks_like_dir) { |
| result.string_value() = RebaseSourceAbsolutePath( |
| from_dir.ResolveRelativeDir(string_value).value(), |
| to_dir); |
| MakeSlashEndingMatchInput(string_value, &result.string_value()); |
| } else { |
| result.string_value() = RebaseSourceAbsolutePath( |
| from_dir.ResolveRelativeFile(string_value).value(), |
| to_dir); |
| } |
| |
| ConvertSlashes(&result.string_value(), separator_conversion); |
| return result; |
| } |
| |
| } // namespace |
| |
| const char kRebasePath[] = "rebase_path"; |
| 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" |
| " path_separators = \"to_slash\")\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" |
| "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" |
| " path_separators\n" |
| " On Windows systems, indicates whether and how path separators\n" |
| " should be converted as part of the transformation. It can be one\n" |
| " of the following strings:\n" |
| " - \"to_slash\" Normalize all types of slashes to forward slashes.\n" |
| " This is the default if this argument is unspecified.\n" |
| " - \"to_system\" Convert to the system path separators\n" |
| " (backslashes on Windows).\n" |
| "\n" |
| " On Posix systems there are no path separator transformations\n" |
| " applied. If the new_base is empty (specifying absolute output)\n" |
| " this parameter should not be supplied since paths will always be\n" |
| " converted,\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" |
| " # Convert a file's path separators from forward slashes to system\n" |
| " # slashes.\n" |
| " foo = rebase_path(\"source/myfile.txt\", \".\", \".\", \"to_system\")\n" |
| "\n" |
| " # Typical usage for converting to the build directory for a script.\n" |
| " custom(\"myscript\") {\n" |
| " # Don't convert sources, GN will automatically convert these to be\n" |
| " # relative to the build directory when it contructs 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" |
| " ]\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; |
| static const size_t kArgIndexPathConversion = 3; |
| |
| // Inputs. |
| if (args.size() < 1 || args.size() > 4) { |
| *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].string_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].string_value()); |
| } else { |
| // Default to current directory if unspecified. |
| from_dir = current_dir; |
| } |
| |
| // Path conversion. |
| SeparatorConversion sep_conversion = SEP_TO_SLASH; |
| if (args.size() > kArgIndexPathConversion) { |
| if (convert_to_system_absolute) { |
| *err = Err(function, "Can't specify slash conversion.", |
| "You specified absolute system path output by using an empty string " |
| "for the destination directory on rebase_path(). In this case, you " |
| "can't specify slash conversion."); |
| return result; |
| } |
| |
| if (!args[kArgIndexPathConversion].VerifyTypeIs(Value::STRING, err)) |
| return result; |
| const std::string& sep_string = |
| args[kArgIndexPathConversion].string_value(); |
| if (sep_string == "to_slash") { |
| sep_conversion = SEP_TO_SLASH; |
| } else if (sep_string == "to_system") { |
| sep_conversion = SEP_TO_SYSTEM; |
| } else { |
| *err = Err(args[kArgIndexPathConversion], |
| "Invalid path separator conversion mode.", |
| "I was expecting \"to_slash\" or \"to_system\" and\n" |
| "you gave me \"" + args[kArgIndexPathConversion].string_value() + |
| "\"."); |
| return result; |
| } |
| } |
| |
| if (inputs.type() == Value::STRING) { |
| return ConvertOnePath(scope, function, inputs, |
| from_dir, to_dir, convert_to_system_absolute, |
| sep_conversion, err); |
| |
| } else if (inputs.type() == Value::LIST) { |
| result = Value(function, Value::LIST); |
| result.list_value().reserve(inputs.list_value().size()); |
| |
| for (size_t i = 0; i < inputs.list_value().size(); i++) { |
| result.list_value().push_back( |
| ConvertOnePath(scope, function, inputs.list_value()[i], |
| from_dir, to_dir, convert_to_system_absolute, |
| sep_conversion, 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 |