| // Copyright 2014 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/err.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/value.h" | |
| namespace functions { | |
| namespace { | |
| // Corresponds to the various values of "what" in the function call. | |
| enum What { | |
| WHAT_FILE, | |
| WHAT_NAME, | |
| WHAT_EXTENSION, | |
| WHAT_DIR, | |
| WHAT_ABSPATH, | |
| WHAT_GEN_DIR, | |
| WHAT_OUT_DIR, | |
| }; | |
| // Returns the directory containing the input (resolving it against the | |
| // |current_dir|), regardless of whether the input is a directory or a file. | |
| SourceDir DirForInput(const Settings* settings, | |
| const SourceDir& current_dir, | |
| const Value& input, | |
| Err* err) { | |
| // Input should already have been validated as a string. | |
| const std::string& input_string = input.string_value(); | |
| if (!input_string.empty() && input_string[input_string.size() - 1] == '/') { | |
| // Input is a directory. | |
| return current_dir.ResolveRelativeDir( | |
| input, err, settings->build_settings()->root_path_utf8()); | |
| } | |
| // Input is a file. | |
| return current_dir | |
| .ResolveRelativeFile(input, err, | |
| settings->build_settings()->root_path_utf8()) | |
| .GetDir(); | |
| } | |
| std::string GetOnePathInfo(const Settings* settings, | |
| const SourceDir& current_dir, | |
| What what, | |
| const Value& input, | |
| Err* err) { | |
| if (!input.VerifyTypeIs(Value::STRING, err)) | |
| return std::string(); | |
| const std::string& input_string = input.string_value(); | |
| if (input_string.empty()) { | |
| *err = Err(input, "Calling get_path_info on an empty string."); | |
| return std::string(); | |
| } | |
| switch (what) { | |
| case WHAT_FILE: { | |
| return FindFilename(&input_string).as_string(); | |
| } | |
| case WHAT_NAME: { | |
| std::string file = FindFilename(&input_string).as_string(); | |
| size_t extension_offset = FindExtensionOffset(file); | |
| if (extension_offset == std::string::npos) | |
| return file; | |
| // Trim extension and dot. | |
| return file.substr(0, extension_offset - 1); | |
| } | |
| case WHAT_EXTENSION: { | |
| return FindExtension(&input_string).as_string(); | |
| } | |
| case WHAT_DIR: { | |
| base::StringPiece dir_incl_slash = FindDir(&input_string); | |
| if (dir_incl_slash.empty()) | |
| return std::string("."); | |
| // Trim slash since this function doesn't return trailing slashes. The | |
| // times we don't do this are if the result is "/" and "//" since those | |
| // slashes can't be trimmed. | |
| if (dir_incl_slash == "/") | |
| return std::string("/."); | |
| if (dir_incl_slash == "//") | |
| return std::string("//."); | |
| return dir_incl_slash.substr(0, dir_incl_slash.size() - 1).as_string(); | |
| } | |
| case WHAT_GEN_DIR: { | |
| return DirectoryWithNoLastSlash(GetSubBuildDirAsSourceDir( | |
| BuildDirContext(settings), | |
| DirForInput(settings, current_dir, input, err), BuildDirType::GEN)); | |
| } | |
| case WHAT_OUT_DIR: { | |
| return DirectoryWithNoLastSlash(GetSubBuildDirAsSourceDir( | |
| BuildDirContext(settings), | |
| DirForInput(settings, current_dir, input, err), BuildDirType::OBJ)); | |
| } | |
| case WHAT_ABSPATH: { | |
| bool as_dir = | |
| !input_string.empty() && input_string[input_string.size() - 1] == '/'; | |
| return current_dir.ResolveRelativeAs( | |
| !as_dir, input, err, settings->build_settings()->root_path_utf8(), | |
| &input_string); | |
| } | |
| default: | |
| NOTREACHED(); | |
| return std::string(); | |
| } | |
| } | |
| } // namespace | |
| const char kGetPathInfo[] = "get_path_info"; | |
| const char kGetPathInfo_HelpShort[] = | |
| "get_path_info: Extract parts of a file or directory name."; | |
| const char kGetPathInfo_Help[] = | |
| R"(get_path_info: Extract parts of a file or directory name. | |
| get_path_info(input, what) | |
| The first argument is either a string representing a file or directory name, | |
| or a list of such strings. If the input is a list the return value will be a | |
| list containing the result of applying the rule to each item in the input. | |
| Possible values for the "what" parameter | |
| "file" | |
| The substring after the last slash in the path, including the name and | |
| extension. If the input ends in a slash, the empty string will be | |
| returned. | |
| "foo/bar.txt" => "bar.txt" | |
| "bar.txt" => "bar.txt" | |
| "foo/" => "" | |
| "" => "" | |
| "name" | |
| The substring of the file name not including the extension. | |
| "foo/bar.txt" => "bar" | |
| "foo/bar" => "bar" | |
| "foo/" => "" | |
| "extension" | |
| The substring following the last period following the last slash, or the | |
| empty string if not found. The period is not included. | |
| "foo/bar.txt" => "txt" | |
| "foo/bar" => "" | |
| "dir" | |
| The directory portion of the name, not including the slash. | |
| "foo/bar.txt" => "foo" | |
| "//foo/bar" => "//foo" | |
| "foo" => "." | |
| The result will never end in a slash, so if the resulting is empty, the | |
| system ("/") or source ("//") roots, a "." will be appended such that it | |
| is always legal to append a slash and a filename and get a valid path. | |
| "out_dir" | |
| The output file directory corresponding to the path of the given file, | |
| not including a trailing slash. | |
| "//foo/bar/baz.txt" => "//out/Default/obj/foo/bar" | |
| "gen_dir" | |
| The generated file directory corresponding to the path of the given file, | |
| not including a trailing slash. | |
| "//foo/bar/baz.txt" => "//out/Default/gen/foo/bar" | |
| "abspath" | |
| The full absolute path name to the file or directory. It will be resolved | |
| relative to the current directory, and then the source- absolute version | |
| will be returned. If the input is system- absolute, the same input will | |
| be returned. | |
| "foo/bar.txt" => "//mydir/foo/bar.txt" | |
| "foo/" => "//mydir/foo/" | |
| "//foo/bar" => "//foo/bar" (already absolute) | |
| "/usr/include" => "/usr/include" (already absolute) | |
| If you want to make the path relative to another directory, or to be | |
| system-absolute, see rebase_path(). | |
| Examples | |
| sources = [ "foo.cc", "foo.h" ] | |
| result = get_path_info(source, "abspath") | |
| # result will be [ "//mydir/foo.cc", "//mydir/foo.h" ] | |
| result = get_path_info("//foo/bar/baz.cc", "dir") | |
| # result will be "//foo/bar" | |
| # Extract the source-absolute directory name, | |
| result = get_path_info(get_path_info(path, "dir"), "abspath") | |
| )"; | |
| Value RunGetPathInfo(Scope* scope, | |
| const FunctionCallNode* function, | |
| const std::vector<Value>& args, | |
| Err* err) { | |
| if (args.size() != 2) { | |
| *err = Err(function, "Expecting two arguments to get_path_info."); | |
| return Value(); | |
| } | |
| // Extract the "what". | |
| if (!args[1].VerifyTypeIs(Value::STRING, err)) | |
| return Value(); | |
| What what; | |
| if (args[1].string_value() == "file") { | |
| what = WHAT_FILE; | |
| } else if (args[1].string_value() == "name") { | |
| what = WHAT_NAME; | |
| } else if (args[1].string_value() == "extension") { | |
| what = WHAT_EXTENSION; | |
| } else if (args[1].string_value() == "dir") { | |
| what = WHAT_DIR; | |
| } else if (args[1].string_value() == "out_dir") { | |
| what = WHAT_OUT_DIR; | |
| } else if (args[1].string_value() == "gen_dir") { | |
| what = WHAT_GEN_DIR; | |
| } else if (args[1].string_value() == "abspath") { | |
| what = WHAT_ABSPATH; | |
| } else { | |
| *err = Err(args[1], "Unknown value for 'what'."); | |
| return Value(); | |
| } | |
| const SourceDir& current_dir = scope->GetSourceDir(); | |
| if (args[0].type() == Value::STRING) { | |
| return Value(function, GetOnePathInfo(scope->settings(), current_dir, what, | |
| args[0], err)); | |
| } else if (args[0].type() == Value::LIST) { | |
| const std::vector<Value>& input_list = args[0].list_value(); | |
| Value result(function, Value::LIST); | |
| for (const auto& cur : input_list) { | |
| result.list_value().push_back(Value( | |
| function, | |
| GetOnePathInfo(scope->settings(), current_dir, what, cur, err))); | |
| if (err->has_error()) | |
| return Value(); | |
| } | |
| return result; | |
| } | |
| *err = Err(args[0], "Path must be a string or a list of strings."); | |
| return Value(); | |
| } | |
| } // namespace functions |