summaryrefslogtreecommitdiffstats
path: root/platform_utils.py
diff options
context:
space:
mode:
Diffstat (limited to 'platform_utils.py')
-rw-r--r--platform_utils.py388
1 files changed, 203 insertions, 185 deletions
diff --git a/platform_utils.py b/platform_utils.py
index 0203249a..2c48e622 100644
--- a/platform_utils.py
+++ b/platform_utils.py
@@ -20,246 +20,264 @@ import stat
20 20
21 21
22def isWindows(): 22def isWindows():
23 """ Returns True when running with the native port of Python for Windows, 23 """Returns True when running with the native port of Python for Windows,
24 False when running on any other platform (including the Cygwin port of 24 False when running on any other platform (including the Cygwin port of
25 Python). 25 Python).
26 """ 26 """
27 # Note: The cygwin port of Python returns "CYGWIN_NT_xxx" 27 # Note: The cygwin port of Python returns "CYGWIN_NT_xxx"
28 return platform.system() == "Windows" 28 return platform.system() == "Windows"
29 29
30 30
31def symlink(source, link_name): 31def symlink(source, link_name):
32 """Creates a symbolic link pointing to source named link_name. 32 """Creates a symbolic link pointing to source named link_name.
33 Note: On Windows, source must exist on disk, as the implementation needs 33
34 to know whether to create a "File" or a "Directory" symbolic link. 34 Note: On Windows, source must exist on disk, as the implementation needs
35 """ 35 to know whether to create a "File" or a "Directory" symbolic link.
36 if isWindows(): 36 """
37 import platform_utils_win32 37 if isWindows():
38 source = _validate_winpath(source) 38 import platform_utils_win32
39 link_name = _validate_winpath(link_name) 39
40 target = os.path.join(os.path.dirname(link_name), source) 40 source = _validate_winpath(source)
41 if isdir(target): 41 link_name = _validate_winpath(link_name)
42 platform_utils_win32.create_dirsymlink(_makelongpath(source), link_name) 42 target = os.path.join(os.path.dirname(link_name), source)
43 if isdir(target):
44 platform_utils_win32.create_dirsymlink(
45 _makelongpath(source), link_name
46 )
47 else:
48 platform_utils_win32.create_filesymlink(
49 _makelongpath(source), link_name
50 )
43 else: 51 else:
44 platform_utils_win32.create_filesymlink(_makelongpath(source), link_name) 52 return os.symlink(source, link_name)
45 else:
46 return os.symlink(source, link_name)
47 53
48 54
49def _validate_winpath(path): 55def _validate_winpath(path):
50 path = os.path.normpath(path) 56 path = os.path.normpath(path)
51 if _winpath_is_valid(path): 57 if _winpath_is_valid(path):
52 return path 58 return path
53 raise ValueError("Path \"%s\" must be a relative path or an absolute " 59 raise ValueError(
54 "path starting with a drive letter".format(path)) 60 'Path "{}" must be a relative path or an absolute '
61 "path starting with a drive letter".format(path)
62 )
55 63
56 64
57def _winpath_is_valid(path): 65def _winpath_is_valid(path):
58 """Windows only: returns True if path is relative (e.g. ".\\foo") or is 66 """Windows only: returns True if path is relative (e.g. ".\\foo") or is
59 absolute including a drive letter (e.g. "c:\\foo"). Returns False if path 67 absolute including a drive letter (e.g. "c:\\foo"). Returns False if path
60 is ambiguous (e.g. "x:foo" or "\\foo"). 68 is ambiguous (e.g. "x:foo" or "\\foo").
61 """ 69 """
62 assert isWindows() 70 assert isWindows()
63 path = os.path.normpath(path) 71 path = os.path.normpath(path)
64 drive, tail = os.path.splitdrive(path) 72 drive, tail = os.path.splitdrive(path)
65 if tail: 73 if tail:
66 if not drive: 74 if not drive:
67 return tail[0] != os.sep # "\\foo" is invalid 75 return tail[0] != os.sep # "\\foo" is invalid
76 else:
77 return tail[0] == os.sep # "x:foo" is invalid
68 else: 78 else:
69 return tail[0] == os.sep # "x:foo" is invalid 79 return not drive # "x:" is invalid
70 else:
71 return not drive # "x:" is invalid
72 80
73 81
74def _makelongpath(path): 82def _makelongpath(path):
75 """Return the input path normalized to support the Windows long path syntax 83 """Return the input path normalized to support the Windows long path syntax
76 ("\\\\?\\" prefix) if needed, i.e. if the input path is longer than the 84 ("\\\\?\\" prefix) if needed, i.e. if the input path is longer than the
77 MAX_PATH limit. 85 MAX_PATH limit.
78 """ 86 """
79 if isWindows(): 87 if isWindows():
80 # Note: MAX_PATH is 260, but, for directories, the maximum value is actually 246. 88 # Note: MAX_PATH is 260, but, for directories, the maximum value is
81 if len(path) < 246: 89 # actually 246.
82 return path 90 if len(path) < 246:
83 if path.startswith(u"\\\\?\\"): 91 return path
84 return path 92 if path.startswith("\\\\?\\"):
85 if not os.path.isabs(path): 93 return path
86 return path 94 if not os.path.isabs(path):
87 # Append prefix and ensure unicode so that the special longpath syntax 95 return path
88 # is supported by underlying Win32 API calls 96 # Append prefix and ensure unicode so that the special longpath syntax
89 return u"\\\\?\\" + os.path.normpath(path) 97 # is supported by underlying Win32 API calls
90 else: 98 return "\\\\?\\" + os.path.normpath(path)
91 return path 99 else:
100 return path
92 101
93 102
94def rmtree(path, ignore_errors=False): 103def rmtree(path, ignore_errors=False):
95 """shutil.rmtree(path) wrapper with support for long paths on Windows. 104 """shutil.rmtree(path) wrapper with support for long paths on Windows.
96 105
97 Availability: Unix, Windows.""" 106 Availability: Unix, Windows.
98 onerror = None 107 """
99 if isWindows(): 108 onerror = None
100 path = _makelongpath(path) 109 if isWindows():
101 onerror = handle_rmtree_error 110 path = _makelongpath(path)
102 shutil.rmtree(path, ignore_errors=ignore_errors, onerror=onerror) 111 onerror = handle_rmtree_error
112 shutil.rmtree(path, ignore_errors=ignore_errors, onerror=onerror)
103 113
104 114
105def handle_rmtree_error(function, path, excinfo): 115def handle_rmtree_error(function, path, excinfo):
106 # Allow deleting read-only files 116 # Allow deleting read-only files.
107 os.chmod(path, stat.S_IWRITE) 117 os.chmod(path, stat.S_IWRITE)
108 function(path) 118 function(path)
109 119
110 120
111def rename(src, dst): 121def rename(src, dst):
112 """os.rename(src, dst) wrapper with support for long paths on Windows. 122 """os.rename(src, dst) wrapper with support for long paths on Windows.
113 123
114 Availability: Unix, Windows.""" 124 Availability: Unix, Windows.
115 if isWindows(): 125 """
116 # On Windows, rename fails if destination exists, see 126 if isWindows():
117 # https://docs.python.org/2/library/os.html#os.rename 127 # On Windows, rename fails if destination exists, see
118 try: 128 # https://docs.python.org/2/library/os.html#os.rename
119 os.rename(_makelongpath(src), _makelongpath(dst)) 129 try:
120 except OSError as e: 130 os.rename(_makelongpath(src), _makelongpath(dst))
121 if e.errno == errno.EEXIST: 131 except OSError as e:
122 os.remove(_makelongpath(dst)) 132 if e.errno == errno.EEXIST:
123 os.rename(_makelongpath(src), _makelongpath(dst)) 133 os.remove(_makelongpath(dst))
124 else: 134 os.rename(_makelongpath(src), _makelongpath(dst))
125 raise 135 else:
126 else: 136 raise
127 shutil.move(src, dst) 137 else:
138 shutil.move(src, dst)
128 139
129 140
130def remove(path, missing_ok=False): 141def remove(path, missing_ok=False):
131 """Remove (delete) the file path. This is a replacement for os.remove that 142 """Remove (delete) the file path. This is a replacement for os.remove that
132 allows deleting read-only files on Windows, with support for long paths and 143 allows deleting read-only files on Windows, with support for long paths and
133 for deleting directory symbolic links. 144 for deleting directory symbolic links.
134 145
135 Availability: Unix, Windows.""" 146 Availability: Unix, Windows.
136 longpath = _makelongpath(path) if isWindows() else path 147 """
137 try: 148 longpath = _makelongpath(path) if isWindows() else path
138 os.remove(longpath) 149 try:
139 except OSError as e:
140 if e.errno == errno.EACCES:
141 os.chmod(longpath, stat.S_IWRITE)
142 # Directory symbolic links must be deleted with 'rmdir'.
143 if islink(longpath) and isdir(longpath):
144 os.rmdir(longpath)
145 else:
146 os.remove(longpath) 150 os.remove(longpath)
147 elif missing_ok and e.errno == errno.ENOENT: 151 except OSError as e:
148 pass 152 if e.errno == errno.EACCES:
149 else: 153 os.chmod(longpath, stat.S_IWRITE)
150 raise 154 # Directory symbolic links must be deleted with 'rmdir'.
155 if islink(longpath) and isdir(longpath):
156 os.rmdir(longpath)
157 else:
158 os.remove(longpath)
159 elif missing_ok and e.errno == errno.ENOENT:
160 pass
161 else:
162 raise
151 163
152 164
153def walk(top, topdown=True, onerror=None, followlinks=False): 165def walk(top, topdown=True, onerror=None, followlinks=False):
154 """os.walk(path) wrapper with support for long paths on Windows. 166 """os.walk(path) wrapper with support for long paths on Windows.
155 167
156 Availability: Windows, Unix. 168 Availability: Windows, Unix.
157 """ 169 """
158 if isWindows(): 170 if isWindows():
159 return _walk_windows_impl(top, topdown, onerror, followlinks) 171 return _walk_windows_impl(top, topdown, onerror, followlinks)
160 else: 172 else:
161 return os.walk(top, topdown, onerror, followlinks) 173 return os.walk(top, topdown, onerror, followlinks)
162 174
163 175
164def _walk_windows_impl(top, topdown, onerror, followlinks): 176def _walk_windows_impl(top, topdown, onerror, followlinks):
165 try: 177 try:
166 names = listdir(top) 178 names = listdir(top)
167 except Exception as err: 179 except Exception as err:
168 if onerror is not None: 180 if onerror is not None:
169 onerror(err) 181 onerror(err)
170 return 182 return
171 183
172 dirs, nondirs = [], [] 184 dirs, nondirs = [], []
173 for name in names: 185 for name in names:
174 if isdir(os.path.join(top, name)): 186 if isdir(os.path.join(top, name)):
175 dirs.append(name) 187 dirs.append(name)
176 else: 188 else:
177 nondirs.append(name) 189 nondirs.append(name)
178 190
179 if topdown: 191 if topdown:
180 yield top, dirs, nondirs 192 yield top, dirs, nondirs
181 for name in dirs: 193 for name in dirs:
182 new_path = os.path.join(top, name) 194 new_path = os.path.join(top, name)
183 if followlinks or not islink(new_path): 195 if followlinks or not islink(new_path):
184 for x in _walk_windows_impl(new_path, topdown, onerror, followlinks): 196 for x in _walk_windows_impl(
185 yield x 197 new_path, topdown, onerror, followlinks
186 if not topdown: 198 ):
187 yield top, dirs, nondirs 199 yield x
200 if not topdown:
201 yield top, dirs, nondirs
188 202
189 203
190def listdir(path): 204def listdir(path):
191 """os.listdir(path) wrapper with support for long paths on Windows. 205 """os.listdir(path) wrapper with support for long paths on Windows.
192 206
193 Availability: Windows, Unix. 207 Availability: Windows, Unix.
194 """ 208 """
195 return os.listdir(_makelongpath(path)) 209 return os.listdir(_makelongpath(path))
196 210
197 211
198def rmdir(path): 212def rmdir(path):
199 """os.rmdir(path) wrapper with support for long paths on Windows. 213 """os.rmdir(path) wrapper with support for long paths on Windows.
200 214
201 Availability: Windows, Unix. 215 Availability: Windows, Unix.
202 """ 216 """
203 os.rmdir(_makelongpath(path)) 217 os.rmdir(_makelongpath(path))
204 218
205 219
206def isdir(path): 220def isdir(path):
207 """os.path.isdir(path) wrapper with support for long paths on Windows. 221 """os.path.isdir(path) wrapper with support for long paths on Windows.
208 222
209 Availability: Windows, Unix. 223 Availability: Windows, Unix.
210 """ 224 """
211 return os.path.isdir(_makelongpath(path)) 225 return os.path.isdir(_makelongpath(path))
212 226
213 227
214def islink(path): 228def islink(path):
215 """os.path.islink(path) wrapper with support for long paths on Windows. 229 """os.path.islink(path) wrapper with support for long paths on Windows.
216 230
217 Availability: Windows, Unix. 231 Availability: Windows, Unix.
218 """ 232 """
219 if isWindows(): 233 if isWindows():
220 import platform_utils_win32 234 import platform_utils_win32
221 return platform_utils_win32.islink(_makelongpath(path)) 235
222 else: 236 return platform_utils_win32.islink(_makelongpath(path))
223 return os.path.islink(path) 237 else:
238 return os.path.islink(path)
224 239
225 240
226def readlink(path): 241def readlink(path):
227 """Return a string representing the path to which the symbolic link 242 """Return a string representing the path to which the symbolic link
228 points. The result may be either an absolute or relative pathname; 243 points. The result may be either an absolute or relative pathname;
229 if it is relative, it may be converted to an absolute pathname using 244 if it is relative, it may be converted to an absolute pathname using
230 os.path.join(os.path.dirname(path), result). 245 os.path.join(os.path.dirname(path), result).
246
247 Availability: Windows, Unix.
248 """
249 if isWindows():
250 import platform_utils_win32
231 251
232 Availability: Windows, Unix. 252 return platform_utils_win32.readlink(_makelongpath(path))
233 """ 253 else:
234 if isWindows(): 254 return os.readlink(path)
235 import platform_utils_win32
236 return platform_utils_win32.readlink(_makelongpath(path))
237 else:
238 return os.readlink(path)
239 255
240 256
241def realpath(path): 257def realpath(path):
242 """Return the canonical path of the specified filename, eliminating 258 """Return the canonical path of the specified filename, eliminating
243 any symbolic links encountered in the path. 259 any symbolic links encountered in the path.
244 260
245 Availability: Windows, Unix. 261 Availability: Windows, Unix.
246 """ 262 """
247 if isWindows(): 263 if isWindows():
248 current_path = os.path.abspath(path) 264 current_path = os.path.abspath(path)
249 path_tail = [] 265 path_tail = []
250 for c in range(0, 100): # Avoid cycles 266 for c in range(0, 100): # Avoid cycles
251 if islink(current_path): 267 if islink(current_path):
252 target = readlink(current_path) 268 target = readlink(current_path)
253 current_path = os.path.join(os.path.dirname(current_path), target) 269 current_path = os.path.join(
254 else: 270 os.path.dirname(current_path), target
255 basename = os.path.basename(current_path) 271 )
256 if basename == '': 272 else:
257 path_tail.append(current_path) 273 basename = os.path.basename(current_path)
258 break 274 if basename == "":
259 path_tail.append(basename) 275 path_tail.append(current_path)
260 current_path = os.path.dirname(current_path) 276 break
261 path_tail.reverse() 277 path_tail.append(basename)
262 result = os.path.normpath(os.path.join(*path_tail)) 278 current_path = os.path.dirname(current_path)
263 return result 279 path_tail.reverse()
264 else: 280 result = os.path.normpath(os.path.join(*path_tail))
265 return os.path.realpath(path) 281 return result
282 else:
283 return os.path.realpath(path)