diff --git a/setup.py b/setup.py index cb37a878..8ca9d630 100644 --- a/setup.py +++ b/setup.py @@ -1,14 +1,12 @@ import os import os.path +import platform import re import shutil import subprocess import sys -if sys.platform in ('win32', 'cygwin', 'cli'): - raise RuntimeError('uvloop does not support Windows at the moment') - vi = sys.version_info if vi < (3, 5): raise RuntimeError('uvloop requires Python 3.5 or greater') @@ -216,9 +214,10 @@ def _patch_cfile(self, cfile): def build_libuv(self): env = _libuv_build_env() - # Make sure configure and friends are present in case - # we are building from a git checkout. - _libuv_autogen(env) + if sys.platform != 'win32': + # Make sure configure and friends are present in case + # we are building from a git checkout. + _libuv_autogen(env) # Copy the libuv tree to build/ so that its build # products don't pollute sdist accidentally. @@ -226,26 +225,61 @@ def build_libuv(self): shutil.rmtree(LIBUV_BUILD_DIR) shutil.copytree(LIBUV_DIR, LIBUV_BUILD_DIR) - # Sometimes pip fails to preserve the timestamps correctly, - # in which case, make will try to run autotools again. - subprocess.run( - ['touch', 'configure.ac', 'aclocal.m4', 'configure', - 'Makefile.am', 'Makefile.in'], - cwd=LIBUV_BUILD_DIR, env=env, check=True) + if sys.platform == 'win32': + env.pop('VS120COMNTOOLS', None) + env.pop('VS110COMNTOOLS', None) + env.pop('VS100COMNTOOLS', None) + env.pop('VS90COMNTOOLS', None) + env['GYP_MSVS_VERSION'] = '2015' - if 'LIBUV_CONFIGURE_HOST' in env: - cmd = ['./configure', '--host=' + env['LIBUV_CONFIGURE_HOST']] - else: - cmd = ['./configure'] - subprocess.run( - cmd, - cwd=LIBUV_BUILD_DIR, env=env, check=True) + pypath = os.path.expandvars(os.path.join( + '%SYSTEMDRIVE%', 'Python27', 'python.exe')) + if not os.path.exists(pypath): + raise RuntimeError( + 'cannot find Python 2.7 at {!r}'.format(pypath)) + env['PYTHON'] = pypath - j_flag = '-j{}'.format(os.cpu_count() or 1) - c_flag = "CFLAGS={}".format(env['CFLAGS']) - subprocess.run( - ['make', j_flag, c_flag], - cwd=LIBUV_BUILD_DIR, env=env, check=True) + arch = platform.architecture()[0] + libuv_arch = {'32bit': 'x86', '64bit': 'x64'}[arch] + subprocess.run( + ['cmd.exe', '/C', 'vcbuild.bat', libuv_arch, 'release'], + cwd=LIBUV_BUILD_DIR, env=env, check=True) + + else: + if 'LIBUV_CONFIGURE_HOST' in env: + cmd = ['./configure', '--host=' + env['LIBUV_CONFIGURE_HOST']] + else: + cmd = ['./configure'] + subprocess.run( + cmd, + cwd=LIBUV_BUILD_DIR, env=env, check=True) + + # Make sure configure and friends are present in case + # we are building from a git checkout. + _libuv_autogen(env) + + # Copy the libuv tree to build/ so that its build + # products don't pollute sdist accidentally. + if os.path.exists(LIBUV_BUILD_DIR): + shutil.rmtree(LIBUV_BUILD_DIR) + shutil.copytree(LIBUV_DIR, LIBUV_BUILD_DIR) + + # Sometimes pip fails to preserve the timestamps correctly, + # in which case, make will try to run autotools again. + subprocess.run( + ['touch', 'configure.ac', 'aclocal.m4', 'configure', + 'Makefile.am', 'Makefile.in'], + cwd=LIBUV_BUILD_DIR, env=env, check=True) + + subprocess.run( + ['./configure'], + cwd=LIBUV_BUILD_DIR, env=env, check=True) + + j_flag = '-j{}'.format(os.cpu_count() or 1) + c_flag = "CFLAGS={}".format(env['CFLAGS']) + subprocess.run( + ['make', j_flag, c_flag], + cwd=LIBUV_BUILD_DIR, env=env, check=True) def build_extensions(self): if self.use_system_libuv: @@ -256,7 +290,12 @@ def build_extensions(self): # Support macports on Mac OS X. self.compiler.add_include_dir('/opt/local/include') else: - libuv_lib = os.path.join(LIBUV_BUILD_DIR, '.libs', 'libuv.a') + if sys.platform != 'win32': + libuv_lib = os.path.join(LIBUV_BUILD_DIR, '.libs', 'libuv.a') + else: + libuv_lib = os.path.join( + LIBUV_BUILD_DIR, 'Release', 'lib', 'libuv.lib') + if not os.path.exists(libuv_lib): self.build_libuv() if not os.path.exists(libuv_lib): @@ -267,12 +306,24 @@ def build_extensions(self): if sys.platform.startswith('linux'): self.compiler.add_library('rt') + elif sys.platform.startswith(('freebsd', 'dragonfly')): self.compiler.add_library('kvm') + elif sys.platform.startswith('sunos'): self.compiler.add_library('kstat') - - self.compiler.add_library('pthread') + + elif sys.platform.startswith('win'): + self.compiler.add_library('advapi32') + self.compiler.add_library('iphlpapi') + self.compiler.add_library('psapi') + self.compiler.add_library('shell32') + self.compiler.add_library('user32') + self.compiler.add_library('userenv') + self.compiler.add_library('ws2_32') + + if not sys.platform.startswith('win'): + self.compiler.add_library('pthread') super().build_extensions() diff --git a/tests/test_base.py b/tests/test_base.py index 43760891..09d324bd 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -1,5 +1,4 @@ import asyncio -import fcntl import logging import os import sys @@ -683,8 +682,10 @@ def test_loop_call_later_handle_cancelled(self): self.run_loop_briefly(delay=0.05) self.assertFalse(handle.cancelled()) + @unittest.skipIf(sys.platform.startswith('win'), 'posix only test') def test_loop_std_files_cloexec(self): # See https://github.com/MagicStack/uvloop/issues/40 for details. + import fcntl for fd in {0, 1, 2}: flags = fcntl.fcntl(fd, fcntl.F_GETFD) self.assertFalse(flags & fcntl.FD_CLOEXEC) diff --git a/tests/test_pipes.py b/tests/test_pipes.py index 8bec52b3..9e23d7b2 100644 --- a/tests/test_pipes.py +++ b/tests/test_pipes.py @@ -62,6 +62,7 @@ def connection_lost(self, exc): self.done.set_result(None) +@tb.skip_windows class _BasePipeTest: def test_read_pipe(self): proto = MyReadPipeProto(loop=self.loop) diff --git a/tests/test_process.py b/tests/test_process.py index 4d26ba24..22218966 100644 --- a/tests/test_process.py +++ b/tests/test_process.py @@ -527,6 +527,7 @@ def cancel_make_transport(): test_utils.run_briefly(self.loop) +@tb.skip_windows # XXX tests will have to be fixed later class Test_UV_Process(_TestProcess, tb.UVTestCase): def test_process_streams_redirect(self): @@ -563,14 +564,17 @@ async def test(): self.assertEqual(se.read(), b'err\n') +@tb.skip_windows # Some tests fail under asyncio class Test_AIO_Process(_TestProcess, tb.AIOTestCase): pass +@tb.skip_windows # XXX tests will have to be fixed later class TestAsyncio_UV_Process(_AsyncioTests, tb.UVTestCase): pass +@tb.skip_windows # Some tests fail under asyncio class TestAsyncio_AIO_Process(_AsyncioTests, tb.AIOTestCase): pass diff --git a/tests/test_signals.py b/tests/test_signals.py index b92e74fe..517f4df2 100644 --- a/tests/test_signals.py +++ b/tests/test_signals.py @@ -9,6 +9,7 @@ DELAY = 0.1 +@tb.skip_windows class _TestSignal: NEW_LOOP = None diff --git a/tests/test_tcp.py b/tests/test_tcp.py index a71b8f71..e64a5f4f 100644 --- a/tests/test_tcp.py +++ b/tests/test_tcp.py @@ -219,10 +219,10 @@ def test_create_server_4(self): with sock: addr = sock.getsockname() - with self.assertRaisesRegex(OSError, - "error while attempting.*\('127.*: " - "address already in use"): + re = r"(error while attempting.*\('127.*: address already in use)" + re += r"|(error while attempting to bind.*only one usage)" # win + with self.assertRaisesRegex(OSError, re): self.loop.run_until_complete( self.loop.create_server(object, *addr)) @@ -467,7 +467,10 @@ async def client(): loop=self.loop) async def runner(): - with self.assertRaisesRegex(OSError, 'Bad file'): + re = r"(Bad file)" + re += r"|(is not a socket)" # win + + with self.assertRaisesRegex(OSError, re): await client() self.loop.run_until_complete(runner()) diff --git a/tests/test_unix.py b/tests/test_unix.py index 10daf6a4..2985fa5e 100644 --- a/tests/test_unix.py +++ b/tests/test_unix.py @@ -9,6 +9,7 @@ from uvloop import _testbase as tb +@tb.skip_windows class _TestUnix: def test_create_unix_server_1(self): CNT = 0 # number of clients that were successful @@ -453,6 +454,7 @@ class Test_AIO_Unix(_TestUnix, tb.AIOTestCase): pass +@tb.skip_windows class _TestSSL(tb.SSLTestCase): ONLYCERT = tb._cert_fullname(__file__, 'ssl_cert.pem') diff --git a/uvloop/_testbase.py b/uvloop/_testbase.py index 42f40185..4d6f1877 100644 --- a/uvloop/_testbase.py +++ b/uvloop/_testbase.py @@ -12,6 +12,7 @@ import select import socket import ssl +import sys import tempfile import threading import time @@ -19,6 +20,12 @@ import uvloop +def skip_windows(o): + dec = unittest.skipIf(sys.platform in ('win32', 'cygwin', 'cli'), + 'skipped on Windows') + return dec(o) + + class MockPattern(str): def __eq__(self, other): return bool(re.search(str(self), other, re.S)) @@ -29,7 +36,6 @@ class TestCaseDict(collections.UserDict): def __init__(self, name): super().__init__() self.name = name - def __setitem__(self, key, value): if key in self.data: raise RuntimeError('duplicate test {}.{}'.format( @@ -145,7 +151,7 @@ def tcp_server(self, server_prog, *, max_clients=10): if addr is None: - if family == socket.AF_UNIX: + if hasattr(socket, 'AF_UNIX') and family == socket.AF_UNIX: with tempfile.NamedTemporaryFile() as tmp: addr = tmp.name else: @@ -287,16 +293,24 @@ class AIOTestCase(BaseTestCase): def setUp(self): super().setUp() - watcher = asyncio.SafeChildWatcher() - watcher.attach_loop(self.loop) - asyncio.set_child_watcher(watcher) + # No watchers on Windows + if hasattr(asyncio, 'SafeChildWatcher'): + # posix system + watcher = asyncio.SafeChildWatcher() + watcher.attach_loop(self.loop) + asyncio.set_child_watcher(watcher) def tearDown(self): - asyncio.set_child_watcher(None) + if hasattr(asyncio, 'SafeChildWatcher'): + asyncio.set_child_watcher(None) super().tearDown() def new_loop(self): - return asyncio.new_event_loop() + if hasattr(asyncio, 'ProactorEventLoop'): + # On Windows try to use IOCP event loop. + return asyncio.ProactorEventLoop() + else: + return asyncio.new_event_loop() def has_IPv6(): diff --git a/uvloop/errors.pyx b/uvloop/errors.pyx index d810d65e..14c4d891 100644 --- a/uvloop/errors.pyx +++ b/uvloop/errors.pyx @@ -3,13 +3,6 @@ cdef str __strerr(int errno): cdef __convert_python_error(int uverr): - # XXX Won't work for Windows: - # From libuv docs: - # Implementation detail: on Unix error codes are the - # negated errno (or -errno), while on Windows they - # are defined by libuv to arbitrary negative numbers. - cdef int oserr = -uverr - exc = OSError if uverr in (uv.UV_EACCES, uv.UV_EPERM): @@ -48,7 +41,17 @@ cdef __convert_python_error(int uverr): elif uverr == uv.UV_ETIMEDOUT: exc = TimeoutError - return exc(oserr, __strerr(oserr)) + IF UNAME_SYSNAME == "Windows": + # Translate libuv/Windows error to a posix errno + errname = uv.uv_err_name(uverr).decode() + err = getattr(errno, errname, uverr) + return exc(err, uv.uv_strerror(uverr).decode()) + ELSE: + # From libuv docs: + # Implementation detail: on Unix error codes are the + # negated errno (or -errno), while on Windows they + # are defined by libuv to arbitrary negative numbers. + return exc(-uverr, __strerr(-uverr)) cdef int __convert_socket_error(int uverr): diff --git a/uvloop/handles/poll.pyx b/uvloop/handles/poll.pyx index 8640eced..b4db1e3b 100644 --- a/uvloop/handles/poll.pyx +++ b/uvloop/handles/poll.pyx @@ -11,8 +11,9 @@ cdef class UVPoll(UVHandle): self._abort_init() raise MemoryError() - err = uv.uv_poll_init(self._loop.uvloop, - self._handle, fd) + err = uv.uv_poll_init_socket(self._loop.uvloop, + self._handle, + fd) if err < 0: self._abort_init() raise convert_error(err) diff --git a/uvloop/handles/stream.pyx b/uvloop/handles/stream.pyx index 6619b30f..1da79b2d 100644 --- a/uvloop/handles/stream.pyx +++ b/uvloop/handles/stream.pyx @@ -311,6 +311,10 @@ cdef class UVStream(UVBaseTransport): int saved_errno int fd + IF UNAME_SYSNAME == "Windows": + # Don't need this optimization on windows. + raise NotImplementedError + if (self._handle).write_queue_size != 0: raise RuntimeError( 'UVStream._try_write called with data in uv buffers') @@ -413,6 +417,7 @@ cdef class UVStream(UVBaseTransport): int err int buf_len _StreamWriteContext ctx = None + skip_try = 0 if self._closed: # If the handle is closed, just return, it's too @@ -423,7 +428,11 @@ cdef class UVStream(UVBaseTransport): if not buf_len: return - if (self._handle).write_queue_size == 0: + IF UNAME_SYSNAME == "Windows": + skip_try = 1 + + if (not skip_try and + (self._handle).write_queue_size == 0): # libuv internal write buffers for this stream are empty. if buf_len == 1: # If we only have one piece of data to send, let's diff --git a/uvloop/includes/posix.pxi b/uvloop/includes/posix.pxi new file mode 100644 index 00000000..65120081 --- /dev/null +++ b/uvloop/includes/posix.pxi @@ -0,0 +1,74 @@ +cdef extern from "arpa/inet.h" nogil: + + int ntohl(int) + int htonl(int) + int ntohs(int) + + +cdef extern from "sys/socket.h" nogil: + + struct sockaddr: + unsigned short sa_family + char sa_data[14] + + struct addrinfo: + int ai_flags + int ai_family + int ai_socktype + int ai_protocol + size_t ai_addrlen + sockaddr* ai_addr + char* ai_canonname + addrinfo* ai_next + + struct sockaddr_in: + unsigned short sin_family + unsigned short sin_port + # ... + + struct sockaddr_in6: + unsigned short sin6_family + unsigned short sin6_port + unsigned long sin6_flowinfo + # ... + unsigned long sin6_scope_id + + struct sockaddr_storage: + unsigned short ss_family + # ... + + const char *gai_strerror(int errcode) + + int socketpair(int domain, int type, int protocol, int socket_vector[2]) + + int setsockopt(int socket, int level, int option_name, + const void *option_value, int option_len) + + +cdef extern from "unistd.h" nogil: + + ssize_t write(int fd, const void *buf, size_t count) + void _exit(int status) + + +cdef extern from "pthread.h" nogil: + + int pthread_atfork( + void (*prepare)() nogil, + void (*parent)() nogil, + void (*child)() nogil) + + +cdef extern from "includes/compat.h" nogil: + + cdef int EWOULDBLOCK + + cdef int PLATFORM_IS_APPLE + cdef int PLATFORM_IS_LINUX + + struct epoll_event: + # We don't use the fields + pass + + int EPOLL_CTL_DEL + int epoll_ctl(int epfd, int op, int fd, epoll_event *event) diff --git a/uvloop/includes/stdlib.pxi b/uvloop/includes/stdlib.pxi index 01100ee0..cb8b7b62 100644 --- a/uvloop/includes/stdlib.pxi +++ b/uvloop/includes/stdlib.pxi @@ -145,8 +145,8 @@ cdef py_inf = float('inf') # Cython doesn't clean-up imported objects properly in Py3 mode, -# so we delete refs to all modules manually (except sys) -del asyncio, concurrent, collections, errno +# so we delete refs to all modules manually (except sys and errno) +del asyncio, concurrent, collections del functools, inspect, itertools, socket, os, threading del std_signal, subprocess, ssl del time, traceback, warnings, weakref diff --git a/uvloop/includes/system.pxd b/uvloop/includes/system.pxd index 65120081..1da7bf27 100644 --- a/uvloop/includes/system.pxd +++ b/uvloop/includes/system.pxd @@ -1,74 +1,4 @@ -cdef extern from "arpa/inet.h" nogil: - - int ntohl(int) - int htonl(int) - int ntohs(int) - - -cdef extern from "sys/socket.h" nogil: - - struct sockaddr: - unsigned short sa_family - char sa_data[14] - - struct addrinfo: - int ai_flags - int ai_family - int ai_socktype - int ai_protocol - size_t ai_addrlen - sockaddr* ai_addr - char* ai_canonname - addrinfo* ai_next - - struct sockaddr_in: - unsigned short sin_family - unsigned short sin_port - # ... - - struct sockaddr_in6: - unsigned short sin6_family - unsigned short sin6_port - unsigned long sin6_flowinfo - # ... - unsigned long sin6_scope_id - - struct sockaddr_storage: - unsigned short ss_family - # ... - - const char *gai_strerror(int errcode) - - int socketpair(int domain, int type, int protocol, int socket_vector[2]) - - int setsockopt(int socket, int level, int option_name, - const void *option_value, int option_len) - - -cdef extern from "unistd.h" nogil: - - ssize_t write(int fd, const void *buf, size_t count) - void _exit(int status) - - -cdef extern from "pthread.h" nogil: - - int pthread_atfork( - void (*prepare)() nogil, - void (*parent)() nogil, - void (*child)() nogil) - - -cdef extern from "includes/compat.h" nogil: - - cdef int EWOULDBLOCK - - cdef int PLATFORM_IS_APPLE - cdef int PLATFORM_IS_LINUX - - struct epoll_event: - # We don't use the fields - pass - - int EPOLL_CTL_DEL - int epoll_ctl(int epfd, int op, int fd, epoll_event *event) +IF UNAME_SYSNAME == "Windows": + include "windows.pxi" +ELSE: + include "posix.pxi" diff --git a/uvloop/includes/uv.pxd b/uvloop/includes/uv.pxd index 9defb62d..df84d41f 100644 --- a/uvloop/includes/uv.pxd +++ b/uvloop/includes/uv.pxd @@ -4,7 +4,7 @@ from posix.types cimport gid_t, uid_t from . cimport system -cdef extern from "includes/compat.h": +cdef extern from "includes/compat.h" nogil: # Member of uv_poll_event, in compat.h for compatibility # with libuv < v1.9.0 cdef int UV_DISCONNECT @@ -347,7 +347,6 @@ cdef extern from "uv.h" nogil: # Polling - int uv_poll_init(uv_loop_t* loop, uv_poll_t* handle, int fd) int uv_poll_init_socket(uv_loop_t* loop, uv_poll_t* handle, uv_os_sock_t socket) int uv_poll_start(uv_poll_t* handle, int events, uv_poll_cb cb) diff --git a/uvloop/includes/windows.pxi b/uvloop/includes/windows.pxi new file mode 100644 index 00000000..1864bd45 --- /dev/null +++ b/uvloop/includes/windows.pxi @@ -0,0 +1,40 @@ +cdef extern from "Winsock2.h" nogil: + + int ntohl(int) + int htonl(int) + int ntohs(int) + + struct sockaddr: + unsigned short sa_family + char sa_data[14] + + struct addrinfo: + int ai_flags + int ai_family + int ai_socktype + int ai_protocol + size_t ai_addrlen + sockaddr* ai_addr + char* ai_canonname + addrinfo* ai_next + + struct sockaddr_in: + unsigned short sin_family + unsigned short sin_port + # ... + + struct sockaddr_in6: + unsigned short sin6_family + unsigned short sin6_port + unsigned long sin6_flowinfo + # ... + unsigned long sin6_scope_id + + struct sockaddr_storage: + unsigned short ss_family + # ... + + const char *gai_strerror(int errcode) + + int setsockopt(int socket, int level, int option_name, + const void *option_value, int option_len) diff --git a/uvloop/loop.pyx b/uvloop/loop.pyx index ab9588ef..d4a95fd8 100644 --- a/uvloop/loop.pyx +++ b/uvloop/loop.pyx @@ -58,6 +58,17 @@ cdef _is_sock_dgram(sock_type): return (sock_type & 0xF) == uv.SOCK_DGRAM +cdef __dup(sock): + # cross-platform duping + IF UNAME_SYSNAME == "Windows": + dup_sock = sock.dup() + fileno = dup_sock.fileno() + dup_sock.detach() + return fileno + ELSE: + return os_dup(sock.fileno()) + + cdef isfuture(obj): if aio_isfuture is None: return isinstance(obj, aio_Future) @@ -2732,6 +2743,10 @@ cdef void __atfork_child() nogil: cdef __install_atfork(): global __atfork_installed + + IF UNAME_SYSNAME == "Windows": + return + if __atfork_installed: return __atfork_installed = 1