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/filesystem_utils.h" 6 7 #include "base/logging.h" 8 #include "base/strings/utf_string_conversions.h" 9 #include "build/build_config.h" 10 #include "tools/gn/location.h" 11 #include "tools/gn/source_dir.h" 12 13 namespace { 14 15 enum DotDisposition { 16 // The given dot is just part of a filename and is not special. 17 NOT_A_DIRECTORY, 18 19 // The given dot is the current directory. 20 DIRECTORY_CUR, 21 22 // The given dot is the first of a double dot that should take us up one. 23 DIRECTORY_UP 24 }; 25 26 // When we find a dot, this function is called with the character following 27 // that dot to see what it is. The return value indicates what type this dot is 28 // (see above). This code handles the case where the dot is at the end of the 29 // input. 30 // 31 // |*consumed_len| will contain the number of characters in the input that 32 // express what we found. 33 DotDisposition ClassifyAfterDot(const std::string& path, 34 size_t after_dot, 35 size_t* consumed_len) { 36 if (after_dot == path.size()) { 37 // Single dot at the end. 38 *consumed_len = 1; 39 return DIRECTORY_CUR; 40 } 41 if (path[after_dot] == '/') { 42 // Single dot followed by a slash. 43 *consumed_len = 2; // Consume the slash 44 return DIRECTORY_CUR; 45 } 46 47 if (path[after_dot] == '.') { 48 // Two dots. 49 if (after_dot + 1 == path.size()) { 50 // Double dot at the end. 51 *consumed_len = 2; 52 return DIRECTORY_UP; 53 } 54 if (path[after_dot + 1] == '/') { 55 // Double dot folowed by a slash. 56 *consumed_len = 3; 57 return DIRECTORY_UP; 58 } 59 } 60 61 // The dots are followed by something else, not a directory. 62 *consumed_len = 1; 63 return NOT_A_DIRECTORY; 64 } 65 66 } // namesapce 67 68 SourceFileType GetSourceFileType(const SourceFile& file, 69 Settings::TargetOS os) { 70 base::StringPiece extension = FindExtension(&file.value()); 71 if (extension == "cc" || extension == "cpp" || extension == "cxx") 72 return SOURCE_CC; 73 if (extension == "h") 74 return SOURCE_H; 75 if (extension == "c") 76 return SOURCE_C; 77 78 switch (os) { 79 case Settings::MAC: 80 if (extension == "m") 81 return SOURCE_M; 82 if (extension == "mm") 83 return SOURCE_MM; 84 break; 85 86 case Settings::WIN: 87 if (extension == "rc") 88 return SOURCE_RC; 89 break; 90 91 default: 92 break; 93 } 94 95 // TODO(brettw) asm files. 96 // TODO(brettw) weird thing with .S on non-Windows platforms. 97 return SOURCE_UNKNOWN; 98 } 99 100 const char* GetExtensionForOutputType(Target::OutputType type, 101 Settings::TargetOS os) { 102 switch (os) { 103 case Settings::MAC: 104 switch (type) { 105 case Target::NONE: 106 NOTREACHED(); 107 return ""; 108 case Target::EXECUTABLE: 109 return ""; 110 case Target::SHARED_LIBRARY: 111 return "dylib"; 112 case Target::STATIC_LIBRARY: 113 return "a"; 114 case Target::LOADABLE_MODULE: 115 return "dylib"; // TODO(brettw) what's this? 116 default: 117 NOTREACHED(); 118 } 119 break; 120 121 case Settings::WIN: 122 switch (type) { 123 case Target::NONE: 124 NOTREACHED(); 125 return ""; 126 case Target::EXECUTABLE: 127 return "exe"; 128 case Target::SHARED_LIBRARY: 129 return "dll.lib"; // Extension of import library. 130 case Target::STATIC_LIBRARY: 131 return "lib"; 132 case Target::LOADABLE_MODULE: 133 return "dll"; // TODO(brettw) what's this? 134 default: 135 NOTREACHED(); 136 } 137 break; 138 139 default: 140 NOTREACHED(); 141 } 142 return ""; 143 } 144 145 std::string FilePathToUTF8(const base::FilePath& path) { 146 #if defined(OS_WIN) 147 return WideToUTF8(path.value()); 148 #else 149 return path.value(); 150 #endif 151 } 152 153 base::FilePath UTF8ToFilePath(const base::StringPiece& sp) { 154 #if defined(OS_WIN) 155 return base::FilePath(UTF8ToWide(sp)); 156 #else 157 return base::FilePath(sp.as_string()); 158 #endif 159 } 160 161 size_t FindExtensionOffset(const std::string& path) { 162 for (int i = static_cast<int>(path.size()); i >= 0; i--) { 163 if (path[i] == '/') 164 break; 165 if (path[i] == '.') 166 return i + 1; 167 } 168 return std::string::npos; 169 } 170 171 base::StringPiece FindExtension(const std::string* path) { 172 size_t extension_offset = FindExtensionOffset(*path); 173 if (extension_offset == std::string::npos) 174 return base::StringPiece(); 175 return base::StringPiece(&path->data()[extension_offset], 176 path->size() - extension_offset); 177 } 178 179 size_t FindFilenameOffset(const std::string& path) { 180 for (int i = static_cast<int>(path.size()) - 1; i >= 0; i--) { 181 if (path[i] == '/') 182 return i + 1; 183 } 184 return 0; // No filename found means everything was the filename. 185 } 186 187 base::StringPiece FindFilename(const std::string* path) { 188 size_t filename_offset = FindFilenameOffset(*path); 189 if (filename_offset == 0) 190 return base::StringPiece(*path); // Everything is the file name. 191 return base::StringPiece(&(*path).data()[filename_offset], 192 path->size() - filename_offset); 193 } 194 195 base::StringPiece FindFilenameNoExtension(const std::string* path) { 196 if (path->empty()) 197 return base::StringPiece(); 198 size_t filename_offset = FindFilenameOffset(*path); 199 size_t extension_offset = FindExtensionOffset(*path); 200 201 size_t name_len; 202 if (extension_offset == std::string::npos) 203 name_len = path->size() - filename_offset; 204 else 205 name_len = extension_offset - filename_offset - 1; 206 207 return base::StringPiece(&(*path).data()[filename_offset], name_len); 208 } 209 210 void RemoveFilename(std::string* path) { 211 path->resize(FindFilenameOffset(*path)); 212 } 213 214 bool EndsWithSlash(const std::string& s) { 215 return !s.empty() && s[s.size() - 1] == '/'; 216 } 217 218 base::StringPiece FindDir(const std::string* path) { 219 size_t filename_offset = FindFilenameOffset(*path); 220 if (filename_offset == 0u) 221 return base::StringPiece(); 222 return base::StringPiece(path->data(), filename_offset); 223 } 224 225 bool EnsureStringIsInOutputDir(const SourceDir& dir, 226 const std::string& str, 227 const Value& originating, 228 Err* err) { 229 // The last char of the dir will be a slash. We don't care if the input ends 230 // in a slash or not, so just compare up until there. 231 // 232 // This check will be wrong for all proper prefixes "e.g. "/output" will 233 // match "/out" but we don't really care since this is just a sanity check. 234 const std::string& dir_str = dir.value(); 235 if (str.compare(0, dir_str.length() - 1, dir_str, 0, dir_str.length() - 1) 236 != 0) { 237 *err = Err(originating, "File not inside output directory.", 238 "The given file should be in the output directory. Normally you would " 239 "specify\n\"$target_output_dir/foo\" or " 240 "\"$target_gen_dir/foo\". I interpreted this as\n\"" 241 + str + "\"."); 242 return false; 243 } 244 return true; 245 } 246 247 std::string InvertDir(const SourceDir& path) { 248 const std::string value = path.value(); 249 if (value.empty()) 250 return std::string(); 251 252 DCHECK(value[0] == '/'); 253 size_t begin_index = 1; 254 255 // If the input begins with two slashes, skip over both (this is a 256 // source-relative dir). 257 if (value.size() > 1 && value[1] == '/') 258 begin_index = 2; 259 260 std::string ret; 261 for (size_t i = begin_index; i < value.size(); i++) { 262 if (value[i] == '/') 263 ret.append("../"); 264 } 265 return ret; 266 } 267 268 void NormalizePath(std::string* path) { 269 char* pathbuf = path->empty() ? NULL : &(*path)[0]; 270 271 // top_index is the first character we can modify in the path. Anything 272 // before this indicates where the path is relative to. 273 size_t top_index = 0; 274 bool is_relative = true; 275 if (!path->empty() && pathbuf[0] == '/') { 276 is_relative = false; 277 278 if (path->size() > 1 && pathbuf[1] == '/') { 279 // Two leading slashes, this is a path into the source dir. 280 top_index = 2; 281 } else { 282 // One leading slash, this is a system-absolute path. 283 top_index = 1; 284 } 285 } 286 287 size_t dest_i = top_index; 288 for (size_t src_i = top_index; src_i < path->size(); /* nothing */) { 289 if (pathbuf[src_i] == '.') { 290 if (src_i == 0 || pathbuf[src_i - 1] == '/') { 291 // Slash followed by a dot, see if it's something special. 292 size_t consumed_len; 293 switch (ClassifyAfterDot(*path, src_i + 1, &consumed_len)) { 294 case NOT_A_DIRECTORY: 295 // Copy the dot to the output, it means nothing special. 296 pathbuf[dest_i++] = pathbuf[src_i++]; 297 break; 298 case DIRECTORY_CUR: 299 // Current directory, just skip the input. 300 src_i += consumed_len; 301 break; 302 case DIRECTORY_UP: 303 // Back up over previous directory component. If we're already 304 // at the top, preserve the "..". 305 if (dest_i > top_index) { 306 // The previous char was a slash, remove it. 307 dest_i--; 308 } 309 310 if (dest_i == top_index) { 311 if (is_relative) { 312 // We're already at the beginning of a relative input, copy the 313 // ".." and continue. We need the trailing slash if there was 314 // one before (otherwise we're at the end of the input). 315 pathbuf[dest_i++] = '.'; 316 pathbuf[dest_i++] = '.'; 317 if (consumed_len == 3) 318 pathbuf[dest_i++] = '/'; 319 320 // This also makes a new "root" that we can't delete by going 321 // up more levels. Otherwise "../.." would collapse to 322 // nothing. 323 top_index = dest_i; 324 } 325 // Otherwise we're at the beginning of an absolute path. Don't 326 // allow ".." to go up another level and just eat it. 327 } else { 328 // Just find the previous slash or the beginning of input. 329 while (dest_i > 0 && pathbuf[dest_i - 1] != '/') 330 dest_i--; 331 } 332 src_i += consumed_len; 333 } 334 } else { 335 // Dot not preceeded by a slash, copy it literally. 336 pathbuf[dest_i++] = pathbuf[src_i++]; 337 } 338 } else if (pathbuf[src_i] == '/') { 339 if (src_i > 0 && pathbuf[src_i - 1] == '/') { 340 // Two slashes in a row, skip over it. 341 src_i++; 342 } else { 343 // Just one slash, copy it. 344 pathbuf[dest_i++] = pathbuf[src_i++]; 345 } 346 } else { 347 // Input nothing special, just copy it. 348 pathbuf[dest_i++] = pathbuf[src_i++]; 349 } 350 } 351 path->resize(dest_i); 352 } 353 354 void ConvertPathToSystem(std::string* path) { 355 #if defined(OS_WIN) 356 for (size_t i = 0; i < path->size(); i++) { 357 if ((*path)[i] == '/') 358 (*path)[i] = '\\'; 359 } 360 #endif 361 } 362 363 std::string PathToSystem(const std::string& path) { 364 std::string ret(path); 365 ConvertPathToSystem(&ret); 366 return ret; 367 } 368 369