Add Cygwin support.
1 # vim: set ts=8 sts=2 sw=2 tw=99 et:
3 # This file is part of AMBuild.
5 # AMBuild is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
10 # AMBuild is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with AMBuild. If not, see <http://www.gnu.org/licenses/>.
20 from ambuild2 import util
22 handle_t = ctypes.c_void_p
23 handlep_t = ctypes.POINTER(handle_t)
24 if ctypes.sizeof(handle_t) == 4:
25 INVALID_HANDLE_VALUE = 0xffffffff
26 elif ctypes.sizeof(handle_t) == 8:
27 INVALID_HANDLE_VALUE = 0xffffffffffffffff
28 INVALID_HANDLE = handle_t(INVALID_HANDLE_VALUE)
31 SUBLANG_DEFAULT = 0x01
33 def MAKELANGID(primary, sub):
34 return (sub << 10) | primary
36 if sys.platform != 'cygwin':
37 from ctypes import WinError
38 WINDLL = ctypes.windll
47 sys_executable_ = None
49 global sys_executable_
50 if not sys_executable_:
51 output = subprocess.check_output(['cygpath', '-w', sys.executable])
52 sys_executable_ = output.strip() + '.exe'
53 return sys_executable_
55 class WindowsError(Exception):
56 def __init__(self, errno, message):
58 Exception.__init__(self, message)
61 error = GetLastError()
62 buffer = ctypes.create_string_buffer(4096)
64 rval = fnFormatMessage(
65 FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
68 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
69 ctypes.cast(buffer, ctypes.c_void_p),
70 ctypes.sizeof(buffer),
74 raise Exception('system error {0} while reporting system error {1}'.format(GetLastError(), error))
76 raise WindowsError(error, buffer.value)
78 fnCreateNamedPipeA = WINDLL.kernel32.CreateNamedPipeA
79 fnCreateNamedPipeA.argtypes = [
80 ctypes.c_char_p, # LPCTSTR lpName
81 ctypes.c_int, # DWORD dwOpenMode
82 ctypes.c_int, # DWORD dwPipeMode
83 ctypes.c_int, # DWORD nMaxInstances
84 ctypes.c_int, # DWORD nOutBufferSize
85 ctypes.c_int, # DWORD nInBufferSize,
86 ctypes.c_int, # DWORD nDefaultTimeOut
87 ctypes.c_void_p # LPSECURITY_ATTRIBUTES lpSecurityAttributes
89 fnCreateNamedPipeA.restype = handle_t
91 fnCreateFileA = WINDLL.kernel32.CreateFileA
92 fnCreateFileA.argtypes = [
93 ctypes.c_char_p, # LPCTSTR lpFileName
94 ctypes.c_int, # DWORD dwDesiredAccess
95 ctypes.c_int, # DWORD dwShareMode
96 ctypes.c_void_p, # LPSECURITY_ATTRIBUTES lpSecurityAttributes
97 ctypes.c_int, # DWORD dwCreationDisposition
98 ctypes.c_int, # DWORD dwFlagsAndAttributes
99 handle_t # HANDLE hTempateFile
101 fnCreateFileA.restype = handle_t
103 fnCloseHandle = WINDLL.kernel32.CloseHandle
104 fnCloseHandle.argtypes = [ handle_t ]
105 fnCloseHandle.restype = ctypes.c_int
107 class Overlapped(ctypes.Structure):
109 ('Internal', ctypes.c_void_p),
110 ('InternalHigh', ctypes.c_void_p),
112 # This is technically a union of 8 bytes and a pointer, so it's always
113 # 8 bytes on every platform.
114 ('Offset', ctypes.c_int),
115 ('OffsetHigh', ctypes.c_int),
120 def __init__(self, event=handle_t()):
121 super(Overlapped, self).__init__()
125 # This prevents notifying IO completion ports.
126 self.hEvent = ctypes.cast(ctypes.c_void_p(self.hEvent | 1), handle_t)
129 return ctypes.cast(ctypes.c_void_p((self.hEvent.value >> 1) << 1), handle_t)
131 # Given the occasional shitfest that is ctypes, it doesn't work to byref() a
132 # struct. We have to keep the POINTER type around and use from_address.
133 LPOVERLAPPED = ctypes.POINTER(Overlapped)
135 fnCreateIoCompletionPort = WINDLL.kernel32.CreateIoCompletionPort
136 fnCreateIoCompletionPort.argtypes = [
137 handle_t, # HANDLE FileHandle
138 handle_t, # HANDLE ExistingCompletionPort
139 ctypes.c_size_t, # ULONG_PTR CompletionKey
140 ctypes.c_int # DWORD NumberOfConcurrentThreads
142 fnCreateIoCompletionPort.restype = handle_t
144 fnGetQueuedCompletionStatus = WINDLL.kernel32.GetQueuedCompletionStatus
145 fnGetQueuedCompletionStatus.argtypes = [
146 handle_t, # HANDLE CompletionPort
147 ctypes.POINTER(ctypes.c_int), # LPDWORD
148 ctypes.POINTER(ctypes.c_size_t), # PULONG_PTR lpCompletionKey
149 ctypes.POINTER(ctypes.c_void_p), # LPOVERLAPPED *lpOverlapped
152 fnGetQueuedCompletionStatus.restype = ctypes.c_int
154 fnCreateProcessA = WINDLL.kernel32.CreateProcessA
155 fnCreateProcessA.argtypes = [
156 ctypes.c_char_p, # LPCTSTR lpApplicationName
157 ctypes.c_char_p, # LPTSTR lpCommandLine
158 ctypes.c_void_p, # LPSECURITY_ATTRIBUTES lpProcessAttributes
159 ctypes.c_void_p, # LPSECURITY_ATTRIBUTES lpThreadAttributes
160 ctypes.c_int, # BOOL bInheritHandles
161 ctypes.c_int, # DWORD dwCreationFlags
162 ctypes.c_void_p, # lpEnvironment
163 ctypes.c_char_p, # lpCurrentDirectory
164 ctypes.c_void_p, # lpStartupInfo
165 ctypes.c_void_p # LPPROCESS_INFORMATION lpProcessInformation
167 fnCreateProcessA.restype = ctypes.c_int
169 fnDuplicateHandle = WINDLL.kernel32.DuplicateHandle
170 fnDuplicateHandle.argtypes = [
171 handle_t, # hSourceProcessHandle
172 handle_t, # hSourceHandle,
173 handle_t, # hTargetProcessHandle
174 handlep_t, # lpTargetHandle,
175 ctypes.c_int, # dwDesiredAccess
176 ctypes.c_int, # bInheritHandle
177 ctypes.c_int # dwOptions
179 fnDuplicateHandle.restype = ctypes.c_int
181 fnGetCurrentProcess = WINDLL.kernel32.GetCurrentProcess
182 fnGetCurrentProcess.restype = handle_t
184 fnGetStdHandle = WINDLL.kernel32.GetStdHandle
185 fnGetStdHandle.argtypes = [ ctypes.c_int ]
186 fnGetStdHandle.restype = handle_t
188 fnWriteFile = WINDLL.kernel32.WriteFile
189 fnWriteFile.argtypes = [
190 handle_t, # HANDLE hFile,
191 ctypes.c_void_p, # LPCVOID lpBuffer
192 ctypes.c_int, # DWORD nNumberOfBytesToWrite
193 ctypes.POINTER(ctypes.c_int), # LPDWORD lpNumberOfBytesWritten
194 ctypes.c_void_p # LPOVERLAPPED lpOverlapped
196 fnWriteFile.restype = ctypes.c_int
198 fnReadFile = WINDLL.kernel32.ReadFile
199 fnReadFile.argtypes = [
200 handle_t, # HANDLE hFile,
201 ctypes.c_void_p, # LPVOID lpBuffer,
202 ctypes.c_int, # DWORD lpNumberOfBytesRead,
203 ctypes.POINTER(ctypes.c_int), # LPDWORD lpNumberOfBytesWritten,
204 ctypes.c_void_p # LPOVERLAPPED lpOverlapped
206 fnReadFile.restype = ctypes.c_int
208 fnCreateEvent = WINDLL.kernel32.CreateEventA
209 fnCreateEvent.argtypes = [
210 ctypes.c_void_p, # LPSECURITY_ATTRIBUTES lpEventAttributes
211 ctypes.c_int, # BOOL bManualReset
212 ctypes.c_int, # BOOL bInitialState
213 ctypes.c_char_p # LPCTSTR lpName
215 fnCreateEvent.restype = handle_t
217 fnResetEvent = WINDLL.kernel32.ResetEvent
218 fnResetEvent.argtypes = [ handle_t ]
219 fnResetEvent.restype = ctypes.c_int
221 fnGetLastError = WINDLL.kernel32.GetLastError
222 fnGetLastError.restype = ctypes.c_int
224 fnWaitForSingleObject = WINDLL.kernel32.WaitForSingleObject
225 fnWaitForSingleObject.argtypes = [ handle_t, ctypes.c_int ]
226 fnWaitForSingleObject.restype = ctypes.c_int
228 fnWaitForMultipleObjects = WINDLL.kernel32.WaitForMultipleObjects
229 fnWaitForMultipleObjects.argptypes = [
230 ctypes.c_int, # DWORD nCount
231 ctypes.POINTER(handle_t), # const HANDLE *lpHandles
232 ctypes.c_int, # BOOL bWaitAll
233 ctypes.c_int # DWORD dwMilliseconds
235 fnWaitForMultipleObjects.restype = ctypes.c_int
237 fnConnectNamedPipe = WINDLL.kernel32.ConnectNamedPipe
238 fnConnectNamedPipe.argtypes = [ handle_t, ctypes.POINTER(Overlapped) ]
239 fnConnectNamedPipe.restype = ctypes.c_int
241 fnGetExitCodeProcess = WINDLL.kernel32.GetExitCodeProcess
242 fnGetExitCodeProcess.argtypes = [ handle_t, ctypes.POINTER(ctypes.c_int) ]
243 fnGetExitCodeProcess.restype = ctypes.c_int
245 fnFormatMessage = WINDLL.kernel32.FormatMessageA
246 fnFormatMessage.argtypes = [
247 ctypes.c_int, # DWORD dwFlags
248 ctypes.c_void_p, # LPCVOID lpSource
249 ctypes.c_int, # DWORD dwMessageId
250 ctypes.c_int, # DWRD dwLanguageId
251 ctypes.c_void_p, # LPTSTR lpBuffer
252 ctypes.c_int, # DWORD nSize
253 ctypes.c_void_p # va_list *Arguments
255 fnFormatMessage.restype = ctypes.c_int
257 PIPE_ACCESS_DUPLEX = 0x00000003
258 PIPE_TYPE_BYTE = 0x00000000
259 PIPE_TYPE_READMODE_BYTE = 0x00000000
260 FILE_FLAG_FIRST_PIPE_INSTANCE = 0x00080000
261 FILE_FLAG_OVERLAPPED = 0x40000000
262 GENERIC_READ = 0x80000000
263 GENERIC_WRITE = 0x40000000
265 DUPLICATE_SAME_ACCESS = 0x00000002
266 STARTF_USESTDHANDLES = 0x00000100
267 STD_INPUT_HANDLE = -10
268 STD_OUTPUT_HANDLE = -11
269 STD_ERROR_HANDLE = -12
271 WAIT_ABANDONED = 0x00000080
272 WAIT_OBJECT_0 = 0x00000000
273 WAIT_TIMEOUT = 0x00000102
276 FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100
277 FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200
278 FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000
279 ERROR_BROKEN_PIPE = 109
280 ERROR_PIPE_CONNECTED = 535
281 ERROR_IO_PENDING = 997
285 class StartupInfo(ctypes.Structure):
287 super(StartupInfo, self).__init__()
288 self.cb = ctypes.sizeof(StartupInfo)
291 ('cb', ctypes.c_int),
292 ('lpReserved', ctypes.c_char_p),
293 ('lpDesktop', ctypes.c_char_p),
294 ('lpTitle', ctypes.c_char_p),
295 ('dwX', ctypes.c_int),
296 ('dwY', ctypes.c_int),
297 ('dwXSize', ctypes.c_int),
298 ('dwYSize', ctypes.c_int),
299 ('dwXCountChars', ctypes.c_int),
300 ('dwYCountChars', ctypes.c_int),
301 ('dwFillAttribute', ctypes.c_int),
302 ('dwFlags', ctypes.c_int),
303 ('wShowWindow', ctypes.c_short),
304 ('cbReserved2', ctypes.c_short),
305 ('lpReserved2', ctypes.c_void_p),
306 ('hStdInput', handle_t),
307 ('hStdOutput', handle_t),
308 ('hStdError', handle_t)
311 class ProcessInformation(ctypes.Structure):
313 super(ProcessInformation, self).__init__()
316 ('hProcess', handle_t),
317 ('hThread', handle_t),
318 ('dwProcessId', ctypes.c_int),
319 ('dwThreadId', ctypes.c_int)
322 def CreateEvent(manual, initial):
323 rval = fnCreateEvent(None, int(manual), int(initial), None)
327 return handle_t(rval)
329 def ResetEvent(handle):
330 if not fnResetEvent(handle):
333 def CreateNamedPipe():
336 pipe_name = r'\\.\pipe\ambuild-{0}-{1}'.format(os.getpid(), pipe_counter_)
339 flags = PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE
341 rval = fnCreateNamedPipeA(
342 util.str2b(pipe_name),
344 PIPE_TYPE_BYTE | PIPE_TYPE_READMODE_BYTE,
351 if rval == INVALID_HANDLE_VALUE:
353 return handle_t(rval), pipe_name
356 rval = fnCreateFileA(
358 GENERIC_READ | GENERIC_WRITE,
362 FILE_FLAG_OVERLAPPED,
365 if rval == INVALID_HANDLE_VALUE:
367 return handle_t(rval)
369 def CloseHandle(handle):
370 fnCloseHandle(handle)
372 def WaitForSingleObject(handle, wait):
373 result = fnWaitForSingleObject(handle, wait)
374 if result == WAIT_FAILED:
376 return result == WAIT_OBJECT_0
378 def WaitForMultipleObjects(handles, wait_all, wait):
379 nhandles = len(handles)
380 array = (handle_t * nhandles)()
381 for index, handle in enumerate(handles):
382 array[index] = handle
384 result = fnWaitForMultipleObjects(
391 if result == WAIT_FAILED:
396 def CreateIoCompletionPort():
397 rval = fnCreateIoCompletionPort(
405 return handle_t(rval)
407 def RegisterIoCompletion(port, handle, key):
408 rval = fnCreateIoCompletionPort(
416 assert rval == port.value
418 def GetQueuedCompletionStatus(port, wait):
419 nbytes = ctypes.c_int()
420 key = ctypes.c_size_t()
421 poverlapped = ctypes.c_void_p()
423 result = fnGetQueuedCompletionStatus(
425 ctypes.byref(nbytes),
427 ctypes.byref(poverlapped),
431 if not poverlapped.value:
434 overlapped = ctypes.cast(poverlapped.value, LPOVERLAPPED)
435 overlapped = overlapped.contents
439 return False, 0, -1, overlapped
440 return False, 0, key, overlapped
442 return True, nbytes, key, overlapped
445 def GetCurrentProcess():
446 return handle_t(fnGetCurrentProcess())
449 rval = fnGetStdHandle(n)
450 if rval == INVALID_HANDLE_VALUE:
452 return handle_t(rval)
455 return fnGetLastError()
457 def DuplicateHandle(handle):
458 current = GetCurrentProcess()
460 rval = fnDuplicateHandle(
464 ctypes.pointer(target),
465 0, # dwDesiredAccess (ignored)
467 DUPLICATE_SAME_ACCESS
473 class Process(object):
474 def __init__(self, handle, pid):
477 self.returncode = None
480 if self.returncode != None:
483 status = ctypes.c_int()
484 rval = fnGetExitCodeProcess(self.handle, ctypes.byref(status))
488 if status.value == 259:
491 self.returncode = status.value
495 def spawn(cls, channel):
496 eval = 'from ambuild2.ipc.windows import child_main; child_main()'
497 argv = [sys.executable, '-c', eval]
498 argv += ['--name', '{0}'.format(channel.name)]
500 # This is pretty hacky. Maybe we should just pass in the pipe name instead.
501 argv += ['--pipe', '{0}'.format(channel.path)]
503 cmdline = ' '.join(['"{0}"'.format(arg) for arg in argv])
504 cmdline = util.str2b(cmdline)
505 cmdline_buffer = ctypes.create_string_buffer(cmdline)
507 startup_info = StartupInfo()
508 startup_info.dwFlags |= STARTF_USESTDHANDLES
509 startup_info.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE)
510 startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE)
511 startup_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE)
513 proc_info = ProcessInformation()
515 executable = sys_executable()
517 rval = fnCreateProcessA(
518 util.str2b(executable),
524 None, # lpEnvironment
525 None, # lpCurrentDirectory
526 ctypes.pointer(startup_info),
527 ctypes.pointer(proc_info)
532 CloseHandle(proc_info.hThread)
534 return cls(proc_info.hProcess, proc_info.dwProcessId)