summaryrefslogtreecommitdiffstats
path: root/platform_utils.py
diff options
context:
space:
mode:
authorDavid Pursehouse <dpursehouse@collab.net>2017-08-30 10:24:03 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2017-08-30 10:24:03 +0000
commitd1ebc89a083f2966f8c65246bb63e2deaadfab62 (patch)
treecd6bd047ba2c7b83a25ba045a48efa95393f7fbe /platform_utils.py
parent9ead97bb51f5b1ad1d0a45e0c5442f15e1e38fd7 (diff)
parentad1abcb556bfff2744928a8f29d216aee43fdbe3 (diff)
downloadgit-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.py244
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
16import errno
17import os
18import platform
19import select
20import shutil
21import stat
22
23from Queue import Queue
24from threading import Thread
25
26
27def 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
36class 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
83class _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
117class _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
175def 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
193def _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
201def _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
218def rmtree(path):
219 if isWindows():
220 shutil.rmtree(path, onerror=handle_rmtree_error)
221 else:
222 shutil.rmtree(path)
223
224
225def handle_rmtree_error(function, path, excinfo):
226 # Allow deleting read-only files
227 os.chmod(path, stat.S_IWRITE)
228 function(path)
229
230
231def 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)