diff options
author | David Pursehouse <dpursehouse@collab.net> | 2017-08-30 10:24:03 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2017-08-30 10:24:03 +0000 |
commit | d1ebc89a083f2966f8c65246bb63e2deaadfab62 (patch) | |
tree | cd6bd047ba2c7b83a25ba045a48efa95393f7fbe /platform_utils.py | |
parent | 9ead97bb51f5b1ad1d0a45e0c5442f15e1e38fd7 (diff) | |
parent | ad1abcb556bfff2744928a8f29d216aee43fdbe3 (diff) | |
download | git-repo-d1ebc89a083f2966f8c65246bb63e2deaadfab62.tar.gz |
Merge changes from topic "windows-support"
* changes:
Port os.rename calls to work on Windows
Workaround shutil.rmtree limitation on Windows
Add support for creating symbolic links on Windows
Make "git command" and "forall" work on Windows
Diffstat (limited to 'platform_utils.py')
-rw-r--r-- | platform_utils.py | 244 |
1 files changed, 244 insertions, 0 deletions
diff --git a/platform_utils.py b/platform_utils.py new file mode 100644 index 00000000..e0fa9dcc --- /dev/null +++ b/platform_utils.py | |||
@@ -0,0 +1,244 @@ | |||
1 | # | ||
2 | # Copyright (C) 2016 The Android Open Source Project | ||
3 | # | ||
4 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | # you may not use this file except in compliance with the License. | ||
6 | # You may obtain a copy of the License at | ||
7 | # | ||
8 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | # | ||
10 | # Unless required by applicable law or agreed to in writing, software | ||
11 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | # See the License for the specific language governing permissions and | ||
14 | # limitations under the License. | ||
15 | |||
16 | import errno | ||
17 | import os | ||
18 | import platform | ||
19 | import select | ||
20 | import shutil | ||
21 | import stat | ||
22 | |||
23 | from Queue import Queue | ||
24 | from threading import Thread | ||
25 | |||
26 | |||
27 | def isWindows(): | ||
28 | """ Returns True when running with the native port of Python for Windows, | ||
29 | False when running on any other platform (including the Cygwin port of | ||
30 | Python). | ||
31 | """ | ||
32 | # Note: The cygwin port of Python returns "CYGWIN_NT_xxx" | ||
33 | return platform.system() == "Windows" | ||
34 | |||
35 | |||
36 | class FileDescriptorStreams(object): | ||
37 | """ Platform agnostic abstraction enabling non-blocking I/O over a | ||
38 | collection of file descriptors. This abstraction is required because | ||
39 | fctnl(os.O_NONBLOCK) is not supported on Windows. | ||
40 | """ | ||
41 | @classmethod | ||
42 | def create(cls): | ||
43 | """ Factory method: instantiates the concrete class according to the | ||
44 | current platform. | ||
45 | """ | ||
46 | if isWindows(): | ||
47 | return _FileDescriptorStreamsThreads() | ||
48 | else: | ||
49 | return _FileDescriptorStreamsNonBlocking() | ||
50 | |||
51 | def __init__(self): | ||
52 | self.streams = [] | ||
53 | |||
54 | def add(self, fd, dest, std_name): | ||
55 | """ Wraps an existing file descriptor as a stream. | ||
56 | """ | ||
57 | self.streams.append(self._create_stream(fd, dest, std_name)) | ||
58 | |||
59 | def remove(self, stream): | ||
60 | """ Removes a stream, when done with it. | ||
61 | """ | ||
62 | self.streams.remove(stream) | ||
63 | |||
64 | @property | ||
65 | def is_done(self): | ||
66 | """ Returns True when all streams have been processed. | ||
67 | """ | ||
68 | return len(self.streams) == 0 | ||
69 | |||
70 | def select(self): | ||
71 | """ Returns the set of streams that have data available to read. | ||
72 | The returned streams each expose a read() and a close() method. | ||
73 | When done with a stream, call the remove(stream) method. | ||
74 | """ | ||
75 | raise NotImplementedError | ||
76 | |||
77 | def _create_stream(fd, dest, std_name): | ||
78 | """ Creates a new stream wrapping an existing file descriptor. | ||
79 | """ | ||
80 | raise NotImplementedError | ||
81 | |||
82 | |||
83 | class _FileDescriptorStreamsNonBlocking(FileDescriptorStreams): | ||
84 | """ Implementation of FileDescriptorStreams for platforms that support | ||
85 | non blocking I/O. | ||
86 | """ | ||
87 | class Stream(object): | ||
88 | """ Encapsulates a file descriptor """ | ||
89 | def __init__(self, fd, dest, std_name): | ||
90 | self.fd = fd | ||
91 | self.dest = dest | ||
92 | self.std_name = std_name | ||
93 | self.set_non_blocking() | ||
94 | |||
95 | def set_non_blocking(self): | ||
96 | import fcntl | ||
97 | flags = fcntl.fcntl(self.fd, fcntl.F_GETFL) | ||
98 | fcntl.fcntl(self.fd, fcntl.F_SETFL, flags | os.O_NONBLOCK) | ||
99 | |||
100 | def fileno(self): | ||
101 | return self.fd.fileno() | ||
102 | |||
103 | def read(self): | ||
104 | return self.fd.read(4096) | ||
105 | |||
106 | def close(self): | ||
107 | self.fd.close() | ||
108 | |||
109 | def _create_stream(self, fd, dest, std_name): | ||
110 | return self.Stream(fd, dest, std_name) | ||
111 | |||
112 | def select(self): | ||
113 | ready_streams, _, _ = select.select(self.streams, [], []) | ||
114 | return ready_streams | ||
115 | |||
116 | |||
117 | class _FileDescriptorStreamsThreads(FileDescriptorStreams): | ||
118 | """ Implementation of FileDescriptorStreams for platforms that don't support | ||
119 | non blocking I/O. This implementation requires creating threads issuing | ||
120 | blocking read operations on file descriptors. | ||
121 | """ | ||
122 | def __init__(self): | ||
123 | super(_FileDescriptorStreamsThreads, self).__init__() | ||
124 | # The queue is shared accross all threads so we can simulate the | ||
125 | # behavior of the select() function | ||
126 | self.queue = Queue(10) # Limit incoming data from streams | ||
127 | |||
128 | def _create_stream(self, fd, dest, std_name): | ||
129 | return self.Stream(fd, dest, std_name, self.queue) | ||
130 | |||
131 | def select(self): | ||
132 | # Return only one stream at a time, as it is the most straighforward | ||
133 | # thing to do and it is compatible with the select() function. | ||
134 | item = self.queue.get() | ||
135 | stream = item.stream | ||
136 | stream.data = item.data | ||
137 | return [stream] | ||
138 | |||
139 | class QueueItem(object): | ||
140 | """ Item put in the shared queue """ | ||
141 | def __init__(self, stream, data): | ||
142 | self.stream = stream | ||
143 | self.data = data | ||
144 | |||
145 | class Stream(object): | ||
146 | """ Encapsulates a file descriptor """ | ||
147 | def __init__(self, fd, dest, std_name, queue): | ||
148 | self.fd = fd | ||
149 | self.dest = dest | ||
150 | self.std_name = std_name | ||
151 | self.queue = queue | ||
152 | self.data = None | ||
153 | self.thread = Thread(target=self.read_to_queue) | ||
154 | self.thread.daemon = True | ||
155 | self.thread.start() | ||
156 | |||
157 | def close(self): | ||
158 | self.fd.close() | ||
159 | |||
160 | def read(self): | ||
161 | data = self.data | ||
162 | self.data = None | ||
163 | return data | ||
164 | |||
165 | def read_to_queue(self): | ||
166 | """ The thread function: reads everything from the file descriptor into | ||
167 | the shared queue and terminates when reaching EOF. | ||
168 | """ | ||
169 | for line in iter(self.fd.readline, b''): | ||
170 | self.queue.put(_FileDescriptorStreamsThreads.QueueItem(self, line)) | ||
171 | self.fd.close() | ||
172 | self.queue.put(_FileDescriptorStreamsThreads.QueueItem(self, None)) | ||
173 | |||
174 | |||
175 | def symlink(source, link_name): | ||
176 | """Creates a symbolic link pointing to source named link_name. | ||
177 | Note: On Windows, source must exist on disk, as the implementation needs | ||
178 | to know whether to create a "File" or a "Directory" symbolic link. | ||
179 | """ | ||
180 | if isWindows(): | ||
181 | import platform_utils_win32 | ||
182 | source = _validate_winpath(source) | ||
183 | link_name = _validate_winpath(link_name) | ||
184 | target = os.path.join(os.path.dirname(link_name), source) | ||
185 | if os.path.isdir(target): | ||
186 | platform_utils_win32.create_dirsymlink(source, link_name) | ||
187 | else: | ||
188 | platform_utils_win32.create_filesymlink(source, link_name) | ||
189 | else: | ||
190 | return os.symlink(source, link_name) | ||
191 | |||
192 | |||
193 | def _validate_winpath(path): | ||
194 | path = os.path.normpath(path) | ||
195 | if _winpath_is_valid(path): | ||
196 | return path | ||
197 | raise ValueError("Path \"%s\" must be a relative path or an absolute " | ||
198 | "path starting with a drive letter".format(path)) | ||
199 | |||
200 | |||
201 | def _winpath_is_valid(path): | ||
202 | """Windows only: returns True if path is relative (e.g. ".\\foo") or is | ||
203 | absolute including a drive letter (e.g. "c:\\foo"). Returns False if path | ||
204 | is ambiguous (e.g. "x:foo" or "\\foo"). | ||
205 | """ | ||
206 | assert isWindows() | ||
207 | path = os.path.normpath(path) | ||
208 | drive, tail = os.path.splitdrive(path) | ||
209 | if tail: | ||
210 | if not drive: | ||
211 | return tail[0] != os.sep # "\\foo" is invalid | ||
212 | else: | ||
213 | return tail[0] == os.sep # "x:foo" is invalid | ||
214 | else: | ||
215 | return not drive # "x:" is invalid | ||
216 | |||
217 | |||
218 | def rmtree(path): | ||
219 | if isWindows(): | ||
220 | shutil.rmtree(path, onerror=handle_rmtree_error) | ||
221 | else: | ||
222 | shutil.rmtree(path) | ||
223 | |||
224 | |||
225 | def handle_rmtree_error(function, path, excinfo): | ||
226 | # Allow deleting read-only files | ||
227 | os.chmod(path, stat.S_IWRITE) | ||
228 | function(path) | ||
229 | |||
230 | |||
231 | def rename(src, dst): | ||
232 | if isWindows(): | ||
233 | # On Windows, rename fails if destination exists, see | ||
234 | # https://docs.python.org/2/library/os.html#os.rename | ||
235 | try: | ||
236 | os.rename(src, dst) | ||
237 | except OSError as e: | ||
238 | if e.errno == errno.EEXIST: | ||
239 | os.remove(dst) | ||
240 | os.rename(src, dst) | ||
241 | else: | ||
242 | raise | ||
243 | else: | ||
244 | os.rename(src, dst) | ||