Status: | Merged |
---|---|
Approved by: | Martin Packman |
Approved revision: | no longer in the source branch. |
Merge reported by: | The Breezy Bot |
Merged at revision: | not available |
Proposed branch: | lp:~gz/brz/no_pywin32 |
Merge into: | lp:brz |
Diff against target: |
472 lines (+42/-223) 4 files modified
breezy/lock.py (+1/-77) breezy/tests/test_bzrdir.py (+0/-6) breezy/tests/test_win32utils.py (+6/-22) breezy/win32utils.py (+35/-118) |
To merge this branch: | bzr merge lp:~gz/brz/no_pywin32 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jelmer Vernooij | Approve | ||
Review via email: mp+368881@code.launchpad.net |
Commit message
Remove win32 code using pywin32 library
Description of the change
Remove win32 code using pywin32 library
All Breezy supported Python versions have ctypes, so there's no need for
two implementations of win32 specific logic.
New ctypes implementation of set_file_
Note that not all tests will actually be passing on Python 3 on windows.
To post a comment you must log in.
Revision history for this message
Jelmer Vernooij (jelmer) : | # |
review:
Approve
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote : | # |
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'breezy/lock.py' |
2 | --- breezy/lock.py 2018-11-12 01:41:38 +0000 |
3 | +++ breezy/lock.py 2019-06-16 15:39:01 +0000 |
4 | @@ -143,19 +143,10 @@ |
5 | except ImportError: |
6 | have_fcntl = False |
7 | |
8 | -have_pywin32 = False |
9 | have_ctypes_win32 = False |
10 | if sys.platform == 'win32': |
11 | import msvcrt |
12 | try: |
13 | - import win32file |
14 | - import pywintypes |
15 | - import winerror |
16 | - have_pywin32 = True |
17 | - except ImportError: |
18 | - pass |
19 | - |
20 | - try: |
21 | import ctypes |
22 | have_ctypes_win32 = True |
23 | except ImportError: |
24 | @@ -358,73 +349,6 @@ |
25 | _lock_classes.append(('fcntl', _fcntl_WriteLock, _fcntl_ReadLock)) |
26 | |
27 | |
28 | -if have_pywin32 and sys.platform == 'win32': |
29 | - win32file_CreateFile = win32file.CreateFileW |
30 | - |
31 | - class _w32c_FileLock(_OSLock): |
32 | - |
33 | - def _open(self, filename, access, share, cflags, pymode): |
34 | - self.filename = osutils.realpath(filename) |
35 | - try: |
36 | - self._handle = win32file_CreateFile( |
37 | - filename, access, share, None, win32file.OPEN_ALWAYS, |
38 | - win32file.FILE_ATTRIBUTE_NORMAL, None) |
39 | - except pywintypes.error as e: |
40 | - if e.args[0] == winerror.ERROR_ACCESS_DENIED: |
41 | - raise errors.LockFailed(filename, e) |
42 | - if e.args[0] == winerror.ERROR_SHARING_VIOLATION: |
43 | - raise errors.LockContention(filename, e) |
44 | - raise |
45 | - fd = win32file._open_osfhandle(self._handle, cflags) |
46 | - self.f = os.fdopen(fd, pymode) |
47 | - return self.f |
48 | - |
49 | - def unlock(self): |
50 | - self._clear_f() |
51 | - self._handle = None |
52 | - |
53 | - class _w32c_ReadLock(_w32c_FileLock): |
54 | - def __init__(self, filename): |
55 | - super(_w32c_ReadLock, self).__init__() |
56 | - self._open(filename, win32file.GENERIC_READ, |
57 | - win32file.FILE_SHARE_READ, os.O_RDONLY, "rb") |
58 | - |
59 | - def temporary_write_lock(self): |
60 | - """Try to grab a write lock on the file. |
61 | - |
62 | - On platforms that support it, this will upgrade to a write lock |
63 | - without unlocking the file. |
64 | - Otherwise, this will release the read lock, and try to acquire a |
65 | - write lock. |
66 | - |
67 | - :return: A token which can be used to switch back to a read lock. |
68 | - """ |
69 | - # I can't find a way to upgrade a read lock to a write lock without |
70 | - # unlocking first. So here, we do just that. |
71 | - self.unlock() |
72 | - try: |
73 | - wlock = _w32c_WriteLock(self.filename) |
74 | - except errors.LockError: |
75 | - return False, _w32c_ReadLock(self.filename) |
76 | - return True, wlock |
77 | - |
78 | - class _w32c_WriteLock(_w32c_FileLock): |
79 | - def __init__(self, filename): |
80 | - super(_w32c_WriteLock, self).__init__() |
81 | - self._open(filename, |
82 | - win32file.GENERIC_READ | win32file.GENERIC_WRITE, 0, |
83 | - os.O_RDWR, "rb+") |
84 | - |
85 | - def restore_read_lock(self): |
86 | - """Restore the original ReadLock.""" |
87 | - # For win32 we had to completely let go of the original lock, so we |
88 | - # just unlock and create a new read lock. |
89 | - self.unlock() |
90 | - return _w32c_ReadLock(self.filename) |
91 | - |
92 | - _lock_classes.append(('pywin32', _w32c_WriteLock, _w32c_ReadLock)) |
93 | - |
94 | - |
95 | if have_ctypes_win32: |
96 | from ctypes.wintypes import DWORD, LPWSTR |
97 | LPSECURITY_ATTRIBUTES = ctypes.c_void_p # used as NULL no need to declare |
98 | @@ -517,7 +441,7 @@ |
99 | |
100 | if len(_lock_classes) == 0: |
101 | raise NotImplementedError( |
102 | - "We must have one of fcntl, pywin32, or ctypes available" |
103 | + "We must have one of fcntl or ctypes available" |
104 | " to support OS locking." |
105 | ) |
106 | |
107 | |
108 | === modified file 'breezy/tests/test_bzrdir.py' |
109 | --- breezy/tests/test_bzrdir.py 2019-01-04 21:30:02 +0000 |
110 | +++ breezy/tests/test_bzrdir.py 2019-06-16 15:39:01 +0000 |
111 | @@ -1240,17 +1240,11 @@ |
112 | return out.splitlines() |
113 | |
114 | def test_dot_bzr_hidden(self): |
115 | - if sys.platform == 'win32' and not win32utils.has_win32file: |
116 | - raise TestSkipped( |
117 | - 'unable to make file hidden without pywin32 library') |
118 | b = bzrdir.BzrDir.create('.') |
119 | self.build_tree(['a']) |
120 | self.assertEqual([b'a'], self.get_ls()) |
121 | |
122 | def test_dot_bzr_hidden_with_url(self): |
123 | - if sys.platform == 'win32' and not win32utils.has_win32file: |
124 | - raise TestSkipped( |
125 | - 'unable to make file hidden without pywin32 library') |
126 | b = bzrdir.BzrDir.create(urlutils.local_path_to_url('.')) |
127 | self.build_tree(['a']) |
128 | self.assertEqual([b'a'], self.get_ls()) |
129 | |
130 | === modified file 'breezy/tests/test_win32utils.py' |
131 | --- breezy/tests/test_win32utils.py 2018-11-12 01:41:38 +0000 |
132 | +++ breezy/tests/test_win32utils.py 2019-06-16 15:39:01 +0000 |
133 | @@ -36,13 +36,7 @@ |
134 | |
135 | |
136 | Win32RegistryFeature = features.ModuleAvailableFeature('_winreg') |
137 | -CtypesFeature = features.ModuleAvailableFeature('ctypes') |
138 | -Win32comShellFeature = features.ModuleAvailableFeature('win32com.shell') |
139 | -Win32ApiFeature = features.ModuleAvailableFeature('win32api') |
140 | - |
141 | - |
142 | -# Tests |
143 | -# ----- |
144 | + |
145 | |
146 | class TestWin32UtilsGlobExpand(TestCaseInTempDir): |
147 | |
148 | @@ -187,9 +181,9 @@ |
149 | self.assertEqual('not-existing', p) |
150 | |
151 | |
152 | -class TestLocationsCtypes(TestCase): |
153 | +class TestLocations(TestCase): |
154 | |
155 | - _test_needs_features = [CtypesFeature, features.win32_feature] |
156 | + _test_needs_features = [features.win32_feature] |
157 | |
158 | def assertPathsEqual(self, p1, p2): |
159 | # TODO: The env var values in particular might return the "short" |
160 | @@ -233,20 +227,10 @@ |
161 | self.assertPathsEqual(lad, env.decode(encoding)) |
162 | |
163 | |
164 | -class TestLocationsPywin32(TestLocationsCtypes): |
165 | - |
166 | - _test_needs_features = [Win32comShellFeature] |
167 | - |
168 | - def setUp(self): |
169 | - super(TestLocationsPywin32, self).setUp() |
170 | - # We perform the exact same tests after disabling the use of ctypes. |
171 | - # This causes the implementation to fall back to pywin32. |
172 | - self.overrideAttr(win32utils, 'has_ctypes', False) |
173 | - # FIXME: this should be done by parametrization -- vila 100123 |
174 | - |
175 | - |
176 | class TestSetHidden(TestCaseInTempDir): |
177 | |
178 | + _test_needs_features = [features.win32_feature] |
179 | + |
180 | def test_unicode_dir(self): |
181 | # we should handle unicode paths without errors |
182 | self.requireFeature(features.UnicodeFilenameFeature) |
183 | @@ -335,7 +319,7 @@ |
184 | class TestGetEnvironUnicode(tests.TestCase): |
185 | """Tests for accessing the environment via the windows wide api""" |
186 | |
187 | - _test_needs_features = [CtypesFeature, features.win32_feature] |
188 | + _test_needs_features = [features.win32_feature] |
189 | |
190 | def setUp(self): |
191 | super(TestGetEnvironUnicode, self).setUp() |
192 | |
193 | === modified file 'breezy/win32utils.py' |
194 | --- breezy/win32utils.py 2018-11-12 01:41:38 +0000 |
195 | +++ breezy/win32utils.py 2019-06-16 15:39:01 +0000 |
196 | @@ -22,7 +22,6 @@ |
197 | from __future__ import absolute_import |
198 | |
199 | import glob |
200 | -import operator |
201 | import os |
202 | import struct |
203 | import sys |
204 | @@ -32,37 +31,13 @@ |
205 | ) |
206 | from breezy.i18n import gettext |
207 | |
208 | -# We can cope without it; use a separate variable to help pyflakes |
209 | -try: |
210 | - import ctypes |
211 | - has_ctypes = True |
212 | -except ImportError: |
213 | - has_ctypes = False |
214 | -else: |
215 | - create_buffer = ctypes.create_unicode_buffer |
216 | - extract_buffer = operator.attrgetter("value") |
217 | - suffix = 'W' |
218 | -try: |
219 | - import pywintypes |
220 | - has_pywintypes = True |
221 | -except ImportError: |
222 | - has_pywintypes = has_win32file = has_win32api = False |
223 | -else: |
224 | - try: |
225 | - import win32file |
226 | - has_win32file = True |
227 | - except ImportError: |
228 | - has_win32file = False |
229 | - try: |
230 | - import win32api |
231 | - has_win32api = True |
232 | - except ImportError: |
233 | - has_win32api = False |
234 | +has_ctypes_win32 = False |
235 | +if sys.platform == 'win32': |
236 | + try: |
237 | + import ctypes |
238 | + except ImportError: |
239 | + has_ctypes_win32 = False |
240 | |
241 | -# pulling in win32com.shell is a bit of overhead, and normally we don't need |
242 | -# it as ctypes is preferred and common. lazy_imports and "optional" |
243 | -# modules don't work well, so we do our own lazy thing... |
244 | -has_win32com_shell = None # Set to True or False once we know for sure... |
245 | |
246 | # Special Win32 API constants |
247 | # Handles of std streams |
248 | @@ -89,7 +64,7 @@ |
249 | def debug_memory_win32api(message='', short=True): |
250 | """Use trace.note() to dump the running memory info.""" |
251 | from breezy import trace |
252 | - if has_ctypes: |
253 | + if has_ctypes_win32: |
254 | class PROCESS_MEMORY_COUNTERS_EX(ctypes.Structure): |
255 | """Used by GetProcessMemoryInfo""" |
256 | _fields_ = [('cb', ctypes.c_ulong), |
257 | @@ -123,12 +98,6 @@ |
258 | 'PeakPagefileUsage': mem_struct.PeakPagefileUsage, |
259 | 'PrivateUsage': mem_struct.PrivateUsage, |
260 | } |
261 | - elif has_win32api: |
262 | - import win32process |
263 | - # win32process does not return PrivateUsage, because it doesn't use |
264 | - # PROCESS_MEMORY_COUNTERS_EX (it uses the one without _EX). |
265 | - proc = win32process.GetCurrentProcess() |
266 | - info = win32process.GetProcessMemoryInfo(proc) |
267 | else: |
268 | trace.note(gettext('Cannot debug memory on win32 without ctypes' |
269 | ' or win32process')) |
270 | @@ -163,7 +132,7 @@ |
271 | console window and return tuple (sizex, sizey) if success, |
272 | or default size (defaultx, defaulty) otherwise. |
273 | """ |
274 | - if not has_ctypes: |
275 | + if not has_ctypes_win32: |
276 | # no ctypes is found |
277 | return (defaultx, defaulty) |
278 | |
279 | @@ -189,7 +158,7 @@ |
280 | |
281 | Result is always unicode (or None). |
282 | """ |
283 | - if has_ctypes: |
284 | + if has_ctypes_win32: |
285 | try: |
286 | SHGetSpecialFolderPath = \ |
287 | ctypes.windll.shell32.SHGetSpecialFolderPathW |
288 | @@ -199,23 +168,6 @@ |
289 | buf = ctypes.create_unicode_buffer(MAX_PATH) |
290 | if SHGetSpecialFolderPath(None, buf, csidl, 0): |
291 | return buf.value |
292 | - |
293 | - global has_win32com_shell |
294 | - if has_win32com_shell is None: |
295 | - try: |
296 | - from win32com.shell import shell |
297 | - has_win32com_shell = True |
298 | - except ImportError: |
299 | - has_win32com_shell = False |
300 | - if has_win32com_shell: |
301 | - # still need to bind the name locally, but this is fast. |
302 | - from win32com.shell import shell |
303 | - try: |
304 | - return shell.SHGetSpecialFolderPath(0, csidl, 0) |
305 | - except shell.error: |
306 | - # possibly E_NOTIMPL meaning we can't load the function pointer, |
307 | - # or E_FAIL meaning the function failed - regardless, just ignore it |
308 | - pass |
309 | return None |
310 | |
311 | |
312 | @@ -282,17 +234,17 @@ |
313 | """Return user name as login name. |
314 | If name cannot be obtained return None. |
315 | """ |
316 | - if has_ctypes: |
317 | + if has_ctypes_win32: |
318 | try: |
319 | advapi32 = ctypes.windll.advapi32 |
320 | - GetUserName = getattr(advapi32, 'GetUserName' + suffix) |
321 | + GetUserName = getattr(advapi32, 'GetUserNameW') |
322 | except AttributeError: |
323 | pass |
324 | else: |
325 | - buf = create_buffer(UNLEN + 1) |
326 | + buf = ctypes.create_unicode_buffer(UNLEN + 1) |
327 | n = ctypes.c_int(UNLEN + 1) |
328 | if GetUserName(buf, ctypes.byref(n)): |
329 | - return extract_buffer(buf) |
330 | + return buf.value |
331 | # otherwise try env variables |
332 | return get_environ_unicode('USERNAME') |
333 | |
334 | @@ -308,38 +260,21 @@ |
335 | |
336 | :return: A unicode string representing the host name. |
337 | """ |
338 | - if has_win32api: |
339 | - try: |
340 | - return win32api.GetComputerNameEx(_WIN32_ComputerNameDnsHostname) |
341 | - except (NotImplementedError, win32api.error): |
342 | - # NotImplemented will happen on win9x... |
343 | - pass |
344 | - if has_ctypes: |
345 | + if has_ctypes_win32: |
346 | try: |
347 | kernel32 = ctypes.windll.kernel32 |
348 | except AttributeError: |
349 | pass # Missing the module we need |
350 | else: |
351 | - buf = create_buffer(MAX_COMPUTERNAME_LENGTH + 1) |
352 | + buf = ctypes.create_unicode_buffer(MAX_COMPUTERNAME_LENGTH + 1) |
353 | n = ctypes.c_int(MAX_COMPUTERNAME_LENGTH + 1) |
354 | |
355 | # Try GetComputerNameEx which gives a proper Unicode hostname |
356 | - GetComputerNameEx = getattr(kernel32, 'GetComputerNameEx' + suffix, |
357 | - None) |
358 | + GetComputerNameEx = getattr(kernel32, 'GetComputerNameExW', None) |
359 | if (GetComputerNameEx is not None |
360 | and GetComputerNameEx(_WIN32_ComputerNameDnsHostname, |
361 | buf, ctypes.byref(n))): |
362 | - return extract_buffer(buf) |
363 | - |
364 | - # Try GetComputerName in case GetComputerNameEx wasn't found |
365 | - # It returns the NETBIOS name, which isn't as good, but still ok. |
366 | - # The first GetComputerNameEx might have changed 'n', so reset it |
367 | - n = ctypes.c_int(MAX_COMPUTERNAME_LENGTH + 1) |
368 | - GetComputerName = getattr(kernel32, 'GetComputerName' + suffix, |
369 | - None) |
370 | - if (GetComputerName is not None |
371 | - and GetComputerName(buf, ctypes.byref(n))): |
372 | - return extract_buffer(buf) |
373 | + return buf.value |
374 | return get_environ_unicode('COMPUTERNAME') |
375 | |
376 | |
377 | @@ -440,13 +375,18 @@ |
378 | |
379 | def set_file_attr_hidden(path): |
380 | """Set file attributes to hidden if possible""" |
381 | - if has_win32file: |
382 | - SetFileAttributes = win32file.SetFileAttributesW |
383 | - try: |
384 | - SetFileAttributes(path, win32file.FILE_ATTRIBUTE_HIDDEN) |
385 | - except pywintypes.error as e: |
386 | - from . import trace |
387 | - trace.mutter('Unable to set hidden attribute on %r: %s', path, e) |
388 | + if not has_ctypes_win32: |
389 | + return |
390 | + from ctypes.wintypes import BOOL, DWORD, LPCWSTR |
391 | + _kernel32 = ctypes.windll.kernel32 |
392 | + # <https://docs.microsoft.com/windows/desktop/api/fileapi/nf-fileapi-setfileattributesw> |
393 | + _SetFileAttributesW = ctypes.WINFUNCTYPE(BOOL, LPCWSTR, DWORD)( |
394 | + ("SetFileAttributesW", _kernel32)) |
395 | + FILE_ATTRIBUTE_HIDDEN = 2 |
396 | + if not SetFileAttributes(path, FILE_ATTRIBUTE_HIDDEN): |
397 | + e = ctypes.WinError() |
398 | + from . import trace |
399 | + trace.mutter('Unable to set hidden attribute on %r: %s', path, e) |
400 | |
401 | |
402 | def _command_line_to_argv(command_line, argv, single_quotes_allowed=False): |
403 | @@ -491,7 +431,7 @@ |
404 | return args |
405 | |
406 | |
407 | -if has_ctypes: |
408 | +if has_ctypes_win32: |
409 | def get_unicode_argv(): |
410 | prototype = ctypes.WINFUNCTYPE(ctypes.c_wchar_p) |
411 | GetCommandLineW = prototype(("GetCommandLineW", |
412 | @@ -513,8 +453,6 @@ |
413 | |
414 | A large enough buffer will be allocated to retrieve the value, though |
415 | it may take two calls to the underlying library function. |
416 | - |
417 | - This needs ctypes because pywin32 does not expose the wide version. |
418 | """ |
419 | cfunc = getattr(get_environ_unicode, "_c_function", None) |
420 | if cfunc is None: |
421 | @@ -524,34 +462,19 @@ |
422 | get_environ_unicode._c_function = cfunc |
423 | buffer_size = 256 # heuristic, 256 characters often enough |
424 | while True: |
425 | - buffer = ctypes.create_unicode_buffer(buffer_size) |
426 | - length = cfunc(key, buffer, buffer_size) |
427 | + buf = ctypes.create_unicode_buffer(buffer_size) |
428 | + length = cfunc(key, buf, buffer_size) |
429 | if not length: |
430 | code = ctypes.GetLastError() |
431 | if code == 203: # ERROR_ENVVAR_NOT_FOUND |
432 | return default |
433 | raise ctypes.WinError(code) |
434 | if buffer_size > length: |
435 | - return buffer[:length] |
436 | + return buf[:length] |
437 | buffer_size = length |
438 | |
439 | |
440 | -if has_win32api: |
441 | - def _pywin32_is_local_pid_dead(pid): |
442 | - """True if pid doesn't correspond to live process on this machine""" |
443 | - try: |
444 | - handle = win32api.OpenProcess(1, False, pid) # PROCESS_TERMINATE |
445 | - except pywintypes.error as e: |
446 | - if e[0] == 5: # ERROR_ACCESS_DENIED |
447 | - # Probably something alive we're not allowed to kill |
448 | - return False |
449 | - elif e[0] == 87: # ERROR_INVALID_PARAMETER |
450 | - return True |
451 | - raise |
452 | - handle.close() |
453 | - return False |
454 | - is_local_pid_dead = _pywin32_is_local_pid_dead |
455 | -elif has_ctypes and sys.platform == 'win32': |
456 | +if has_ctypes_win32: |
457 | from ctypes.wintypes import BOOL, DWORD, HANDLE |
458 | _kernel32 = ctypes.windll.kernel32 |
459 | _CloseHandle = ctypes.WINFUNCTYPE(BOOL, HANDLE)( |
460 | @@ -572,11 +495,5 @@ |
461 | raise ctypes.WinError(errorcode) |
462 | _CloseHandle(handle) |
463 | return False |
464 | + |
465 | is_local_pid_dead = _ctypes_is_local_pid_dead |
466 | - |
467 | - |
468 | -def _is_pywintypes_error(evalue): |
469 | - """True if exception instance is an error from pywin32""" |
470 | - if has_pywintypes and isinstance(evalue, pywintypes.error): |
471 | - return True |
472 | - return False |
Running landing tests failed /ci.breezy- vcs.org/ job/brz/ job/brz- land/383/
https:/