1 // Copyright (c) 2013 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 "tools/gn/build_settings.h" 6 #include "tools/gn/filesystem_utils.h" 7 #include "tools/gn/functions.h" 8 #include "tools/gn/parse_tree.h" 9 #include "tools/gn/scope.h" 10 #include "tools/gn/settings.h" 11 #include "tools/gn/source_dir.h" 12 #include "tools/gn/source_file.h" 13 #include "tools/gn/value.h" 14 15 namespace functions { 16 17 namespace { 18 19 enum SeparatorConversion { 20 SEP_NO_CHANGE, // Don't change. 21 SEP_TO_SYSTEM, // Slashes to system ones. 22 SEP_FROM_SYSTEM // System ones to slashes. 23 }; 24 25 // Does the specified path separator conversion in-place. 26 void ConvertSlashes(std::string* str, SeparatorConversion mode) { 27 #if defined(OS_WIN) 28 switch (mode) { 29 case SEP_NO_CHANGE: 30 break; 31 case SEP_TO_SYSTEM: 32 for (size_t i = 0; i < str->size(); i++) { 33 if ((*str)[i] == '/') 34 (*str)[i] = '\\'; 35 } 36 break; 37 case SEP_FROM_SYSTEM: 38 for (size_t i = 0; i < str->size(); i++) { 39 if ((*str)[i] == '\\') 40 (*str)[i] = '/'; 41 } 42 break; 43 } 44 #else 45 DCHECK(str->find('\\') == std::string::npos) 46 << "Filename contains a backslash on a non-Windows platform."; 47 #endif 48 } 49 50 bool EndsInSlash(const std::string& s) { 51 return !s.empty() && (s[s.size() - 1] == '/' || s[s.size() - 1] == '\\'); 52 } 53 54 // We want the output to match the input in terms of ending in a slash or not. 55 // Through all the transformations, these can get added or removed in various 56 // cases. 57 void MakeSlashEndingMatchInput(const std::string& input, std::string* output) { 58 if (EndsInSlash(input)) { 59 if (!EndsInSlash(*output)) // Preserve same slash type as input. 60 output->push_back(input[input.size() - 1]); 61 } else { 62 if (EndsInSlash(*output)) 63 output->resize(output->size() - 1); 64 } 65 } 66 67 // Returns true if the given value looks like a directory, otherwise we'll 68 // assume it's a file. 69 bool ValueLooksLikeDir(const std::string& value) { 70 if (value.empty()) 71 return true; 72 size_t value_size = value.size(); 73 74 // Count the number of dots at the end of the string. 75 size_t num_dots = 0; 76 while (num_dots < value_size && value[value_size - num_dots - 1] == '.') 77 num_dots++; 78 79 if (num_dots == value.size()) 80 return true; // String is all dots. 81 82 if (value[value_size - num_dots - 1] == '/' || 83 value[value_size - num_dots - 1] == '\\') 84 return true; // String is a [back]slash followed by 0 or more dots. 85 86 // Anything else. 87 return false; 88 } 89 90 Value ConvertOnePath(const Scope* scope, 91 const FunctionCallNode* function, 92 const Value& value, 93 const SourceDir& from_dir, 94 const SourceDir& to_dir, 95 bool convert_to_system_absolute, 96 SeparatorConversion separator_conversion, 97 Err* err) { 98 Value result; // Ensure return value optimization. 99 100 if (!value.VerifyTypeIs(Value::STRING, err)) 101 return result; 102 const std::string& string_value = value.string_value(); 103 104 bool looks_like_dir = ValueLooksLikeDir(string_value); 105 106 // System-absolute output special case. 107 if (convert_to_system_absolute) { 108 base::FilePath system_path; 109 if (looks_like_dir) { 110 system_path = scope->settings()->build_settings()->GetFullPath( 111 from_dir.ResolveRelativeDir(string_value)); 112 } else { 113 system_path = scope->settings()->build_settings()->GetFullPath( 114 from_dir.ResolveRelativeFile(string_value)); 115 } 116 result = Value(function, FilePathToUTF8(system_path)); 117 if (looks_like_dir) 118 MakeSlashEndingMatchInput(string_value, &result.string_value()); 119 ConvertPathToSystem(&result.string_value()); 120 return result; 121 } 122 123 if (from_dir.is_system_absolute() || to_dir.is_system_absolute()) { 124 *err = Err(function, "System-absolute directories are not supported for " 125 "the source or dest dir for rebase_path. It would be nice to add this " 126 "if you're so inclined!"); 127 return result; 128 } 129 130 result = Value(function, Value::STRING); 131 if (looks_like_dir) { 132 result.string_value() = RebaseSourceAbsolutePath( 133 from_dir.ResolveRelativeDir(string_value).value(), 134 to_dir); 135 MakeSlashEndingMatchInput(string_value, &result.string_value()); 136 } else { 137 result.string_value() = RebaseSourceAbsolutePath( 138 from_dir.ResolveRelativeFile(string_value).value(), 139 to_dir); 140 } 141 142 ConvertSlashes(&result.string_value(), separator_conversion); 143 return result; 144 } 145 146 } // namespace 147 148 const char kRebasePath[] = "rebase_path"; 149 const char kRebasePath_Help[] = 150 "rebase_path: Rebase a file or directory to another location.\n" 151 "\n" 152 " converted = rebase_path(input, current_base, new_base,\n" 153 " [path_separators])\n" 154 "\n" 155 " Takes a string argument representing a file name, or a list of such\n" 156 " strings and converts it/them to be relative to a different base\n" 157 " directory.\n" 158 "\n" 159 " When invoking the compiler or scripts, GN will automatically convert\n" 160 " sources and include directories to be relative to the build directory.\n" 161 " However, if you're passing files directly in the \"args\" array or\n" 162 " doing other manual manipulations where GN doesn't know something is\n" 163 " a file name, you will need to convert paths to be relative to what\n" 164 " your tool is expecting.\n" 165 "\n" 166 " The common case is to use this to convert paths relative to the\n" 167 " current directory to be relative to the build directory (which will\n" 168 " be the current directory when executing scripts).\n" 169 "\n" 170 "Arguments\n" 171 "\n" 172 " input\n" 173 " A string or list of strings representing file or directory names\n" 174 " These can be relative paths (\"foo/bar.txt\", system absolte paths\n" 175 " (\"/foo/bar.txt\"), or source absolute paths (\"//foo/bar.txt\").\n" 176 "\n" 177 " current_base\n" 178 " Directory representing the base for relative paths in the input.\n" 179 " If this is not an absolute path, it will be treated as being\n" 180 " relative to the current build file. Use \".\" to convert paths\n" 181 " from the current BUILD-file's directory.\n" 182 "\n" 183 " new_base\n" 184 " The directory to convert the paths to be relative to. As with the\n" 185 " current_base, this can be a relative path, which will be treated\n" 186 " as being relative to the current BUILD-file's directory.\n" 187 "\n" 188 " As a special case, if new_base is the empty string, all paths\n" 189 " will be converted to system-absolute native style paths with\n" 190 " system path separators. This is useful for invoking external\n" 191 " programs.\n" 192 "\n" 193 " path_separators\n" 194 " On Windows systems, indicates whether and how path separators\n" 195 " should be converted as part of the transformation. It can be one\n" 196 " of the following strings:\n" 197 " - \"none\" Perform no changes on path separators. This is the\n" 198 " default if this argument is unspecified.\n" 199 " - \"to_system\" Convert to the system path separators\n" 200 " (backslashes on Windows).\n" 201 " - \"from_system\" Convert system path separators to forward\n" 202 " slashes.\n" 203 "\n" 204 " On Posix systems there are no path separator transformations\n" 205 " applied. If the new_base is empty (specifying absolute output)\n" 206 " this parameter should not be supplied since paths will always be\n" 207 " converted,\n" 208 "\n" 209 "Return value\n" 210 "\n" 211 " The return value will be the same type as the input value (either a\n" 212 " string or a list of strings). All relative and source-absolute file\n" 213 " names will be converted to be relative to the requested output\n" 214 " System-absolute paths will be unchanged.\n" 215 "\n" 216 "Example\n" 217 "\n" 218 " # Convert a file in the current directory to be relative to the build\n" 219 " # directory (the current dir when executing compilers and scripts).\n" 220 " foo = rebase_path(\"myfile.txt\", \".\", root_build_dir)\n" 221 " # might produce \"../../project/myfile.txt\".\n" 222 "\n" 223 " # Convert a file to be system absolute:\n" 224 " foo = rebase_path(\"myfile.txt\", \".\", \"\")\n" 225 " # Might produce \"D:\\source\\project\\myfile.txt\" on Windows or\n" 226 " # \"/home/you/source/project/myfile.txt\" on Linux.\n" 227 "\n" 228 " # Convert a file's path separators from forward slashes to system\n" 229 " # slashes.\n" 230 " foo = rebase_path(\"source/myfile.txt\", \".\", \".\", \"to_system\")\n" 231 "\n" 232 " # Typical usage for converting to the build directory for a script.\n" 233 " custom(\"myscript\") {\n" 234 " # Don't convert sources, GN will automatically convert these to be\n" 235 " # relative to the build directory when it contructs the command\n" 236 " # line for your script.\n" 237 " sources = [ \"foo.txt\", \"bar.txt\" ]\n" 238 "\n" 239 " # Extra file args passed manually need to be explicitly converted\n" 240 " # to be relative to the build directory:\n" 241 " args = [\n" 242 " \"--data\",\n" 243 " rebase_path(\"//mything/data/input.dat\", \".\", root_build_dir),\n" 244 " \"--rel\",\n" 245 " rebase_path(\"relative_path.txt\", \".\", root_build_dir)\n" 246 " ]\n" 247 " }\n"; 248 249 Value RunRebasePath(Scope* scope, 250 const FunctionCallNode* function, 251 const std::vector<Value>& args, 252 Err* err) { 253 Value result; 254 255 // Inputs. 256 if (args.size() != 3 && args.size() != 4) { 257 *err = Err(function->function(), "rebase_path takes 3 or 4 args."); 258 return result; 259 } 260 const Value& inputs = args[0]; 261 262 // From path. 263 if (!args[1].VerifyTypeIs(Value::STRING, err)) 264 return result; 265 const SourceDir& current_dir = scope->GetSourceDir(); 266 SourceDir from_dir = current_dir.ResolveRelativeDir(args[1].string_value()); 267 268 // To path. 269 if (!args[2].VerifyTypeIs(Value::STRING, err)) 270 return result; 271 bool convert_to_system_absolute = false; 272 SourceDir to_dir; 273 if (args[2].string_value().empty()) { 274 convert_to_system_absolute = true; 275 } else { 276 to_dir = current_dir.ResolveRelativeDir(args[2].string_value()); 277 } 278 279 // Path conversion. 280 SeparatorConversion sep_conversion = SEP_NO_CHANGE; 281 if (args.size() == 4) { 282 if (convert_to_system_absolute) { 283 *err = Err(function, "Can't specify slash conversion.", 284 "You specified absolute system path output by using an empty string " 285 "for the desination directory on rebase_path(). In this case, you " 286 "can't specify slash conversion."); 287 return result; 288 } 289 290 if (!args[3].VerifyTypeIs(Value::STRING, err)) 291 return result; 292 const std::string& sep_string = args[3].string_value(); 293 if (sep_string == "to_system") { 294 sep_conversion = SEP_TO_SYSTEM; 295 } else if (sep_string == "from_system") { 296 sep_conversion = SEP_FROM_SYSTEM; 297 } else if (sep_string != "none") { 298 *err = Err(args[3], "Invalid path separator conversion mode.", 299 "I was expecting \"none\", \"to_system\", or \"from_system\" and\n" 300 "you gave me \"" + args[3].string_value() + "\"."); 301 return result; 302 } 303 } 304 305 if (inputs.type() == Value::STRING) { 306 return ConvertOnePath(scope, function, inputs, 307 from_dir, to_dir, convert_to_system_absolute, 308 sep_conversion, err); 309 310 } else if (inputs.type() == Value::LIST) { 311 result = Value(function, Value::LIST); 312 result.list_value().reserve(inputs.list_value().size()); 313 314 for (size_t i = 0; i < inputs.list_value().size(); i++) { 315 result.list_value().push_back( 316 ConvertOnePath(scope, function, inputs.list_value()[i], 317 from_dir, to_dir, convert_to_system_absolute, 318 sep_conversion, err)); 319 if (err->has_error()) { 320 result = Value(); 321 return result; 322 } 323 } 324 return result; 325 } 326 327 *err = Err(function->function(), 328 "rebase_path requires a list or a string."); 329 return result; 330 } 331 332 } // namespace functions 333