diff options
author | Renaud Paquay <rpaquay@google.com> | 2018-09-27 10:46:58 -0700 |
---|---|---|
committer | Renaud Paquay <rpaquay@google.com> | 2018-10-22 08:16:35 -0700 |
commit | bed8b62345e484b27e048e8f21280c5611f795df (patch) | |
tree | 4efc8203f0a092428377ebc3e3494f01fdb9b90c /platform_utils.py | |
parent | b3133a31642ea88f0e4fe9c382411d43278dc9e4 (diff) | |
download | git-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.py | 126 |
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 | ||
223 | def _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 | |||
223 | def rmtree(path): | 243 | def 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 | ||
236 | def rename(src, dst): | 259 | def 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 | ||
252 | def remove(path): | 278 | def 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 | ||
302 | def 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 | |||
313 | def _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 | |||
339 | def 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 | |||
347 | def 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 | |||
355 | def 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 | |||
269 | def islink(path): | 363 | def 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 | ||