summaryrefslogtreecommitdiffstats
path: root/platform_utils.py
diff options
context:
space:
mode:
authorRenaud Paquay <rpaquay@google.com>2018-09-27 10:46:58 -0700
committerRenaud Paquay <rpaquay@google.com>2018-10-22 08:16:35 -0700
commitbed8b62345e484b27e048e8f21280c5611f795df (patch)
tree4efc8203f0a092428377ebc3e3494f01fdb9b90c /platform_utils.py
parentb3133a31642ea88f0e4fe9c382411d43278dc9e4 (diff)
downloadgit-repo-bed8b62345e484b27e048e8f21280c5611f795df.tar.gz
Add support for long paths
* Add more file i/o wrappers in platform_utils to allow using long paths (length > MAX_PATH) on Windows. * Paths using the long path syntax ("\\?\" prefix) should never escape the platform_utils API surface area, so that this specific syntax is not visible to the rest of the repo code base. * Forward many calls from os.xxx to platform_utils.xxx in various place to ensure long paths support, specifically when repo decides to delete obsolete directories. * There are more places that need to be converted to support long paths, this commit is an initial effort to unblock a few common use cases. * Also, fix remove function to handle directory symlinks Change-Id: If82ccc408e516e96ff7260be25f8fd2fe3f9571a
Diffstat (limited to 'platform_utils.py')
-rw-r--r--platform_utils.py126
1 files changed, 110 insertions, 16 deletions
diff --git a/platform_utils.py b/platform_utils.py
index a3e96531..b2cc2459 100644
--- a/platform_utils.py
+++ b/platform_utils.py
@@ -187,10 +187,10 @@ def symlink(source, link_name):
187 source = _validate_winpath(source) 187 source = _validate_winpath(source)
188 link_name = _validate_winpath(link_name) 188 link_name = _validate_winpath(link_name)
189 target = os.path.join(os.path.dirname(link_name), source) 189 target = os.path.join(os.path.dirname(link_name), source)
190 if os.path.isdir(target): 190 if isdir(target):
191 platform_utils_win32.create_dirsymlink(source, link_name) 191 platform_utils_win32.create_dirsymlink(_makelongpath(source), link_name)
192 else: 192 else:
193 platform_utils_win32.create_filesymlink(source, link_name) 193 platform_utils_win32.create_filesymlink(_makelongpath(source), link_name)
194 else: 194 else:
195 return os.symlink(source, link_name) 195 return os.symlink(source, link_name)
196 196
@@ -220,9 +220,32 @@ def _winpath_is_valid(path):
220 return not drive # "x:" is invalid 220 return not drive # "x:" is invalid
221 221
222 222
223def _makelongpath(path):
224 """Return the input path normalized to support the Windows long path syntax
225 ("\\\\?\\" prefix) if needed, i.e. if the input path is longer than the
226 MAX_PATH limit.
227 """
228 if isWindows():
229 # Note: MAX_PATH is 260, but, for directories, the maximum value is actually 246.
230 if len(path) < 246:
231 return path
232 if path.startswith(u"\\\\?\\"):
233 return path
234 if not os.path.isabs(path):
235 return path
236 # Append prefix and ensure unicode so that the special longpath syntax
237 # is supported by underlying Win32 API calls
238 return u"\\\\?\\" + os.path.normpath(path)
239 else:
240 return path
241
242
223def rmtree(path): 243def rmtree(path):
244 """shutil.rmtree(path) wrapper with support for long paths on Windows.
245
246 Availability: Unix, Windows."""
224 if isWindows(): 247 if isWindows():
225 shutil.rmtree(path, onerror=handle_rmtree_error) 248 shutil.rmtree(_makelongpath(path), onerror=handle_rmtree_error)
226 else: 249 else:
227 shutil.rmtree(path) 250 shutil.rmtree(path)
228 251
@@ -234,15 +257,18 @@ def handle_rmtree_error(function, path, excinfo):
234 257
235 258
236def rename(src, dst): 259def rename(src, dst):
260 """os.rename(src, dst) wrapper with support for long paths on Windows.
261
262 Availability: Unix, Windows."""
237 if isWindows(): 263 if isWindows():
238 # On Windows, rename fails if destination exists, see 264 # On Windows, rename fails if destination exists, see
239 # https://docs.python.org/2/library/os.html#os.rename 265 # https://docs.python.org/2/library/os.html#os.rename
240 try: 266 try:
241 os.rename(src, dst) 267 os.rename(_makelongpath(src), _makelongpath(dst))
242 except OSError as e: 268 except OSError as e:
243 if e.errno == errno.EEXIST: 269 if e.errno == errno.EEXIST:
244 os.remove(dst) 270 os.remove(_makelongpath(dst))
245 os.rename(src, dst) 271 os.rename(_makelongpath(src), _makelongpath(dst))
246 else: 272 else:
247 raise 273 raise
248 else: 274 else:
@@ -250,30 +276,98 @@ def rename(src, dst):
250 276
251 277
252def remove(path): 278def remove(path):
253 """Remove (delete) the file path. This is a replacement for os.remove, but 279 """Remove (delete) the file path. This is a replacement for os.remove that
254 allows deleting read-only files on Windows. 280 allows deleting read-only files on Windows, with support for long paths and
255 """ 281 for deleting directory symbolic links.
282
283 Availability: Unix, Windows."""
256 if isWindows(): 284 if isWindows():
285 longpath = _makelongpath(path)
257 try: 286 try:
258 os.remove(path) 287 os.remove(longpath)
259 except OSError as e: 288 except OSError as e:
260 if e.errno == errno.EACCES: 289 if e.errno == errno.EACCES:
261 os.chmod(path, stat.S_IWRITE) 290 os.chmod(longpath, stat.S_IWRITE)
262 os.remove(path) 291 # Directory symbolic links must be deleted with 'rmdir'.
292 if islink(longpath) and isdir(longpath):
293 os.rmdir(longpath)
294 else:
295 os.remove(longpath)
263 else: 296 else:
264 raise 297 raise
265 else: 298 else:
266 os.remove(path) 299 os.remove(path)
267 300
268 301
302def walk(top, topdown=True, onerror=None, followlinks=False):
303 """os.walk(path) wrapper with support for long paths on Windows.
304
305 Availability: Windows, Unix.
306 """
307 if isWindows():
308 return _walk_windows_impl(top, topdown, onerror, followlinks)
309 else:
310 return os.walk(top, topdown, onerror, followlinks)
311
312
313def _walk_windows_impl(top, topdown, onerror, followlinks):
314 try:
315 names = listdir(top)
316 except error, err:
317 if onerror is not None:
318 onerror(err)
319 return
320
321 dirs, nondirs = [], []
322 for name in names:
323 if isdir(os.path.join(top, name)):
324 dirs.append(name)
325 else:
326 nondirs.append(name)
327
328 if topdown:
329 yield top, dirs, nondirs
330 for name in dirs:
331 new_path = os.path.join(top, name)
332 if followlinks or not islink(new_path):
333 for x in _walk_windows_impl(new_path, topdown, onerror, followlinks):
334 yield x
335 if not topdown:
336 yield top, dirs, nondirs
337
338
339def listdir(path):
340 """os.listdir(path) wrapper with support for long paths on Windows.
341
342 Availability: Windows, Unix.
343 """
344 return os.listdir(_makelongpath(path))
345
346
347def rmdir(path):
348 """os.rmdir(path) wrapper with support for long paths on Windows.
349
350 Availability: Windows, Unix.
351 """
352 os.rmdir(_makelongpath(path))
353
354
355def isdir(path):
356 """os.path.isdir(path) wrapper with support for long paths on Windows.
357
358 Availability: Windows, Unix.
359 """
360 return os.path.isdir(_makelongpath(path))
361
362
269def islink(path): 363def islink(path):
270 """Test whether a path is a symbolic link. 364 """os.path.islink(path) wrapper with support for long paths on Windows.
271 365
272 Availability: Windows, Unix. 366 Availability: Windows, Unix.
273 """ 367 """
274 if isWindows(): 368 if isWindows():
275 import platform_utils_win32 369 import platform_utils_win32
276 return platform_utils_win32.islink(path) 370 return platform_utils_win32.islink(_makelongpath(path))
277 else: 371 else:
278 return os.path.islink(path) 372 return os.path.islink(path)
279 373
@@ -288,7 +382,7 @@ def readlink(path):
288 """ 382 """
289 if isWindows(): 383 if isWindows():
290 import platform_utils_win32 384 import platform_utils_win32
291 return platform_utils_win32.readlink(path) 385 return platform_utils_win32.readlink(_makelongpath(path))
292 else: 386 else:
293 return os.readlink(path) 387 return os.readlink(path)
294 388