mirror of
https://gerrit.googlesource.com/git-repo
synced 2024-12-23 07:16:21 +00:00
b32ccbb66b
- Bump minimum version to Python 3.6. - Use f-strings in a lot of places. Change-Id: I2aa70197230fcec2eff8e7c8eb754f20c08075bb Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/389034 Tested-by: Jason R. Coombs <jaraco@google.com> Reviewed-by: Mike Frysinger <vapier@google.com> Commit-Queue: Jason R. Coombs <jaraco@google.com>
283 lines
8.3 KiB
Python
283 lines
8.3 KiB
Python
# Copyright (C) 2016 The Android Open Source Project
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import errno
|
|
import os
|
|
import platform
|
|
import shutil
|
|
import stat
|
|
|
|
|
|
def isWindows():
|
|
"""Returns True when running with the native port of Python for Windows,
|
|
False when running on any other platform (including the Cygwin port of
|
|
Python).
|
|
"""
|
|
# Note: The cygwin port of Python returns "CYGWIN_NT_xxx"
|
|
return platform.system() == "Windows"
|
|
|
|
|
|
def symlink(source, link_name):
|
|
"""Creates a symbolic link pointing to source named link_name.
|
|
|
|
Note: On Windows, source must exist on disk, as the implementation needs
|
|
to know whether to create a "File" or a "Directory" symbolic link.
|
|
"""
|
|
if isWindows():
|
|
import platform_utils_win32
|
|
|
|
source = _validate_winpath(source)
|
|
link_name = _validate_winpath(link_name)
|
|
target = os.path.join(os.path.dirname(link_name), source)
|
|
if isdir(target):
|
|
platform_utils_win32.create_dirsymlink(
|
|
_makelongpath(source), link_name
|
|
)
|
|
else:
|
|
platform_utils_win32.create_filesymlink(
|
|
_makelongpath(source), link_name
|
|
)
|
|
else:
|
|
return os.symlink(source, link_name)
|
|
|
|
|
|
def _validate_winpath(path):
|
|
path = os.path.normpath(path)
|
|
if _winpath_is_valid(path):
|
|
return path
|
|
raise ValueError(
|
|
f'Path "{path}" must be a relative path or an absolute '
|
|
"path starting with a drive letter"
|
|
)
|
|
|
|
|
|
def _winpath_is_valid(path):
|
|
"""Windows only: returns True if path is relative (e.g. ".\\foo") or is
|
|
absolute including a drive letter (e.g. "c:\\foo"). Returns False if path
|
|
is ambiguous (e.g. "x:foo" or "\\foo").
|
|
"""
|
|
assert isWindows()
|
|
path = os.path.normpath(path)
|
|
drive, tail = os.path.splitdrive(path)
|
|
if tail:
|
|
if not drive:
|
|
return tail[0] != os.sep # "\\foo" is invalid
|
|
else:
|
|
return tail[0] == os.sep # "x:foo" is invalid
|
|
else:
|
|
return not drive # "x:" is invalid
|
|
|
|
|
|
def _makelongpath(path):
|
|
"""Return the input path normalized to support the Windows long path syntax
|
|
("\\\\?\\" prefix) if needed, i.e. if the input path is longer than the
|
|
MAX_PATH limit.
|
|
"""
|
|
if isWindows():
|
|
# Note: MAX_PATH is 260, but, for directories, the maximum value is
|
|
# actually 246.
|
|
if len(path) < 246:
|
|
return path
|
|
if path.startswith("\\\\?\\"):
|
|
return path
|
|
if not os.path.isabs(path):
|
|
return path
|
|
# Append prefix and ensure unicode so that the special longpath syntax
|
|
# is supported by underlying Win32 API calls
|
|
return "\\\\?\\" + os.path.normpath(path)
|
|
else:
|
|
return path
|
|
|
|
|
|
def rmtree(path, ignore_errors=False):
|
|
"""shutil.rmtree(path) wrapper with support for long paths on Windows.
|
|
|
|
Availability: Unix, Windows.
|
|
"""
|
|
onerror = None
|
|
if isWindows():
|
|
path = _makelongpath(path)
|
|
onerror = handle_rmtree_error
|
|
shutil.rmtree(path, ignore_errors=ignore_errors, onerror=onerror)
|
|
|
|
|
|
def handle_rmtree_error(function, path, excinfo):
|
|
# Allow deleting read-only files.
|
|
os.chmod(path, stat.S_IWRITE)
|
|
function(path)
|
|
|
|
|
|
def rename(src, dst):
|
|
"""os.rename(src, dst) wrapper with support for long paths on Windows.
|
|
|
|
Availability: Unix, Windows.
|
|
"""
|
|
if isWindows():
|
|
# On Windows, rename fails if destination exists, see
|
|
# https://docs.python.org/2/library/os.html#os.rename
|
|
try:
|
|
os.rename(_makelongpath(src), _makelongpath(dst))
|
|
except OSError as e:
|
|
if e.errno == errno.EEXIST:
|
|
os.remove(_makelongpath(dst))
|
|
os.rename(_makelongpath(src), _makelongpath(dst))
|
|
else:
|
|
raise
|
|
else:
|
|
shutil.move(src, dst)
|
|
|
|
|
|
def remove(path, missing_ok=False):
|
|
"""Remove (delete) the file path. This is a replacement for os.remove that
|
|
allows deleting read-only files on Windows, with support for long paths and
|
|
for deleting directory symbolic links.
|
|
|
|
Availability: Unix, Windows.
|
|
"""
|
|
longpath = _makelongpath(path) if isWindows() else path
|
|
try:
|
|
os.remove(longpath)
|
|
except OSError as e:
|
|
if e.errno == errno.EACCES:
|
|
os.chmod(longpath, stat.S_IWRITE)
|
|
# Directory symbolic links must be deleted with 'rmdir'.
|
|
if islink(longpath) and isdir(longpath):
|
|
os.rmdir(longpath)
|
|
else:
|
|
os.remove(longpath)
|
|
elif missing_ok and e.errno == errno.ENOENT:
|
|
pass
|
|
else:
|
|
raise
|
|
|
|
|
|
def walk(top, topdown=True, onerror=None, followlinks=False):
|
|
"""os.walk(path) wrapper with support for long paths on Windows.
|
|
|
|
Availability: Windows, Unix.
|
|
"""
|
|
if isWindows():
|
|
return _walk_windows_impl(top, topdown, onerror, followlinks)
|
|
else:
|
|
return os.walk(top, topdown, onerror, followlinks)
|
|
|
|
|
|
def _walk_windows_impl(top, topdown, onerror, followlinks):
|
|
try:
|
|
names = listdir(top)
|
|
except Exception as err:
|
|
if onerror is not None:
|
|
onerror(err)
|
|
return
|
|
|
|
dirs, nondirs = [], []
|
|
for name in names:
|
|
if isdir(os.path.join(top, name)):
|
|
dirs.append(name)
|
|
else:
|
|
nondirs.append(name)
|
|
|
|
if topdown:
|
|
yield top, dirs, nondirs
|
|
for name in dirs:
|
|
new_path = os.path.join(top, name)
|
|
if followlinks or not islink(new_path):
|
|
yield from _walk_windows_impl(
|
|
new_path, topdown, onerror, followlinks
|
|
)
|
|
if not topdown:
|
|
yield top, dirs, nondirs
|
|
|
|
|
|
def listdir(path):
|
|
"""os.listdir(path) wrapper with support for long paths on Windows.
|
|
|
|
Availability: Windows, Unix.
|
|
"""
|
|
return os.listdir(_makelongpath(path))
|
|
|
|
|
|
def rmdir(path):
|
|
"""os.rmdir(path) wrapper with support for long paths on Windows.
|
|
|
|
Availability: Windows, Unix.
|
|
"""
|
|
os.rmdir(_makelongpath(path))
|
|
|
|
|
|
def isdir(path):
|
|
"""os.path.isdir(path) wrapper with support for long paths on Windows.
|
|
|
|
Availability: Windows, Unix.
|
|
"""
|
|
return os.path.isdir(_makelongpath(path))
|
|
|
|
|
|
def islink(path):
|
|
"""os.path.islink(path) wrapper with support for long paths on Windows.
|
|
|
|
Availability: Windows, Unix.
|
|
"""
|
|
if isWindows():
|
|
import platform_utils_win32
|
|
|
|
return platform_utils_win32.islink(_makelongpath(path))
|
|
else:
|
|
return os.path.islink(path)
|
|
|
|
|
|
def readlink(path):
|
|
"""Return a string representing the path to which the symbolic link
|
|
points. The result may be either an absolute or relative pathname;
|
|
if it is relative, it may be converted to an absolute pathname using
|
|
os.path.join(os.path.dirname(path), result).
|
|
|
|
Availability: Windows, Unix.
|
|
"""
|
|
if isWindows():
|
|
import platform_utils_win32
|
|
|
|
return platform_utils_win32.readlink(_makelongpath(path))
|
|
else:
|
|
return os.readlink(path)
|
|
|
|
|
|
def realpath(path):
|
|
"""Return the canonical path of the specified filename, eliminating
|
|
any symbolic links encountered in the path.
|
|
|
|
Availability: Windows, Unix.
|
|
"""
|
|
if isWindows():
|
|
current_path = os.path.abspath(path)
|
|
path_tail = []
|
|
for c in range(0, 100): # Avoid cycles
|
|
if islink(current_path):
|
|
target = readlink(current_path)
|
|
current_path = os.path.join(
|
|
os.path.dirname(current_path), target
|
|
)
|
|
else:
|
|
basename = os.path.basename(current_path)
|
|
if basename == "":
|
|
path_tail.append(current_path)
|
|
break
|
|
path_tail.append(basename)
|
|
current_path = os.path.dirname(current_path)
|
|
path_tail.reverse()
|
|
result = os.path.normpath(os.path.join(*path_tail))
|
|
return result
|
|
else:
|
|
return os.path.realpath(path)
|