1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <stddef.h>
6
7 #include "gn/err.h"
8 #include "gn/filesystem_utils.h"
9 #include "gn/functions.h"
10 #include "gn/parse_tree.h"
11 #include "gn/scope.h"
12 #include "gn/value.h"
13
14 namespace functions {
15
16 namespace {
17
18 // Corresponds to the various values of "what" in the function call.
19 enum What {
20 WHAT_FILE,
21 WHAT_NAME,
22 WHAT_EXTENSION,
23 WHAT_DIR,
24 WHAT_ABSPATH,
25 WHAT_GEN_DIR,
26 WHAT_OUT_DIR,
27 };
28
29 // Returns the directory containing the input (resolving it against the
30 // |current_dir|), regardless of whether the input is a directory or a file.
DirForInput(const Settings * settings,const SourceDir & current_dir,const Value & input,Err * err)31 SourceDir DirForInput(const Settings* settings,
32 const SourceDir& current_dir,
33 const Value& input,
34 Err* err) {
35 // Input should already have been validated as a string.
36 const std::string& input_string = input.string_value();
37
38 if (!input_string.empty() && input_string[input_string.size() - 1] == '/') {
39 // Input is a directory.
40 return current_dir.ResolveRelativeDir(
41 input, err, settings->build_settings()->root_path_utf8());
42 }
43
44 // Input is a file.
45 return current_dir
46 .ResolveRelativeFile(input, err,
47 settings->build_settings()->root_path_utf8())
48 .GetDir();
49 }
50
GetOnePathInfo(const Settings * settings,const SourceDir & current_dir,What what,const Value & input,Err * err)51 std::string GetOnePathInfo(const Settings* settings,
52 const SourceDir& current_dir,
53 What what,
54 const Value& input,
55 Err* err) {
56 if (!input.VerifyTypeIs(Value::STRING, err))
57 return std::string();
58 const std::string& input_string = input.string_value();
59 if (input_string.empty()) {
60 *err = Err(input, "Calling get_path_info on an empty string.");
61 return std::string();
62 }
63
64 switch (what) {
65 case WHAT_FILE: {
66 return std::string(FindFilename(&input_string));
67 }
68 case WHAT_NAME: {
69 std::string file(FindFilename(&input_string));
70 size_t extension_offset = FindExtensionOffset(file);
71 if (extension_offset == std::string::npos)
72 return file;
73 // Trim extension and dot.
74 return file.substr(0, extension_offset - 1);
75 }
76 case WHAT_EXTENSION: {
77 return std::string(FindExtension(&input_string));
78 }
79 case WHAT_DIR: {
80 std::string_view dir_incl_slash = FindDir(&input_string);
81 if (dir_incl_slash.empty())
82 return std::string(".");
83 // Trim slash since this function doesn't return trailing slashes. The
84 // times we don't do this are if the result is "/" and "//" since those
85 // slashes can't be trimmed.
86 if (dir_incl_slash == "/")
87 return std::string("/.");
88 if (dir_incl_slash == "//")
89 return std::string("//.");
90 return std::string(dir_incl_slash.substr(0, dir_incl_slash.size() - 1));
91 }
92 case WHAT_GEN_DIR: {
93 return DirectoryWithNoLastSlash(GetSubBuildDirAsSourceDir(
94 BuildDirContext(settings),
95 DirForInput(settings, current_dir, input, err), BuildDirType::GEN));
96 }
97 case WHAT_OUT_DIR: {
98 return DirectoryWithNoLastSlash(GetSubBuildDirAsSourceDir(
99 BuildDirContext(settings),
100 DirForInput(settings, current_dir, input, err), BuildDirType::OBJ));
101 }
102 case WHAT_ABSPATH: {
103 bool as_dir =
104 !input_string.empty() && input_string[input_string.size() - 1] == '/';
105
106 return current_dir.ResolveRelativeAs(
107 !as_dir, input, err, settings->build_settings()->root_path_utf8(),
108 &input_string);
109 }
110 default:
111 NOTREACHED();
112 return std::string();
113 }
114 }
115
116 } // namespace
117
118 const char kGetPathInfo[] = "get_path_info";
119 const char kGetPathInfo_HelpShort[] =
120 "get_path_info: Extract parts of a file or directory name.";
121 const char kGetPathInfo_Help[] =
122 R"(get_path_info: Extract parts of a file or directory name.
123
124 get_path_info(input, what)
125
126 The first argument is either a string representing a file or directory name,
127 or a list of such strings. If the input is a list the return value will be a
128 list containing the result of applying the rule to each item in the input.
129
130 Possible values for the "what" parameter
131
132 "file"
133 The substring after the last slash in the path, including the name and
134 extension. If the input ends in a slash, the empty string will be
135 returned.
136 "foo/bar.txt" => "bar.txt"
137 "bar.txt" => "bar.txt"
138 "foo/" => ""
139 "" => ""
140
141 "name"
142 The substring of the file name not including the extension.
143 "foo/bar.txt" => "bar"
144 "foo/bar" => "bar"
145 "foo/" => ""
146
147 "extension"
148 The substring following the last period following the last slash, or the
149 empty string if not found. The period is not included.
150 "foo/bar.txt" => "txt"
151 "foo/bar" => ""
152
153 "dir"
154 The directory portion of the name, not including the slash.
155 "foo/bar.txt" => "foo"
156 "//foo/bar" => "//foo"
157 "foo" => "."
158
159 The result will never end in a slash, so if the resulting is empty, the
160 system ("/") or source ("//") roots, a "." will be appended such that it
161 is always legal to append a slash and a filename and get a valid path.
162
163 "out_dir"
164 The output file directory corresponding to the path of the given file,
165 not including a trailing slash.
166 "//foo/bar/baz.txt" => "//out/Default/obj/foo/bar"
167
168 "gen_dir"
169 The generated file directory corresponding to the path of the given file,
170 not including a trailing slash.
171 "//foo/bar/baz.txt" => "//out/Default/gen/foo/bar"
172
173 "abspath"
174 The full absolute path name to the file or directory. It will be resolved
175 relative to the current directory, and then the source- absolute version
176 will be returned. If the input is system- absolute, the same input will
177 be returned.
178 "foo/bar.txt" => "//mydir/foo/bar.txt"
179 "foo/" => "//mydir/foo/"
180 "//foo/bar" => "//foo/bar" (already absolute)
181 "/usr/include" => "/usr/include" (already absolute)
182
183 If you want to make the path relative to another directory, or to be
184 system-absolute, see rebase_path().
185
186 Examples
187 sources = [ "foo.cc", "foo.h" ]
188 result = get_path_info(source, "abspath")
189 # result will be [ "//mydir/foo.cc", "//mydir/foo.h" ]
190
191 result = get_path_info("//foo/bar/baz.cc", "dir")
192 # result will be "//foo/bar"
193
194 # Extract the source-absolute directory name,
195 result = get_path_info(get_path_info(path, "dir"), "abspath")
196 )";
197
RunGetPathInfo(Scope * scope,const FunctionCallNode * function,const std::vector<Value> & args,Err * err)198 Value RunGetPathInfo(Scope* scope,
199 const FunctionCallNode* function,
200 const std::vector<Value>& args,
201 Err* err) {
202 if (args.size() != 2) {
203 *err = Err(function, "Expecting two arguments to get_path_info.");
204 return Value();
205 }
206
207 // Extract the "what".
208 if (!args[1].VerifyTypeIs(Value::STRING, err))
209 return Value();
210 What what;
211 if (args[1].string_value() == "file") {
212 what = WHAT_FILE;
213 } else if (args[1].string_value() == "name") {
214 what = WHAT_NAME;
215 } else if (args[1].string_value() == "extension") {
216 what = WHAT_EXTENSION;
217 } else if (args[1].string_value() == "dir") {
218 what = WHAT_DIR;
219 } else if (args[1].string_value() == "out_dir") {
220 what = WHAT_OUT_DIR;
221 } else if (args[1].string_value() == "gen_dir") {
222 what = WHAT_GEN_DIR;
223 } else if (args[1].string_value() == "abspath") {
224 what = WHAT_ABSPATH;
225 } else {
226 *err = Err(args[1], "Unknown value for 'what'.");
227 return Value();
228 }
229
230 const SourceDir& current_dir = scope->GetSourceDir();
231 if (args[0].type() == Value::STRING) {
232 return Value(function, GetOnePathInfo(scope->settings(), current_dir, what,
233 args[0], err));
234 } else if (args[0].type() == Value::LIST) {
235 const std::vector<Value>& input_list = args[0].list_value();
236 Value result(function, Value::LIST);
237 for (const auto& cur : input_list) {
238 result.list_value().push_back(Value(
239 function,
240 GetOnePathInfo(scope->settings(), current_dir, what, cur, err)));
241 if (err->has_error())
242 return Value();
243 }
244 return result;
245 }
246
247 *err = Err(args[0], "Path must be a string or a list of strings.");
248 return Value();
249 }
250
251 } // namespace functions
252