Merge lp:~gz/brz/no_pywin32 into lp:brz

Proposed by Martin Packman
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
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_attr_hidden.

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

Subscribers

People subscribed via source and target branches