Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Windows support #62

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
105 changes: 78 additions & 27 deletions setup.py
Original file line number Diff line number Diff line change
@@ -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')
Expand Down Expand Up @@ -216,36 +214,72 @@ 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.
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)
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:
Expand All @@ -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):
Expand All @@ -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()

Expand Down
3 changes: 2 additions & 1 deletion tests/test_base.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import asyncio
import fcntl
import logging
import os
import sys
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions tests/test_pipes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions tests/test_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions tests/test_signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
DELAY = 0.1


@tb.skip_windows
class _TestSignal:
NEW_LOOP = None

Expand Down
11 changes: 7 additions & 4 deletions tests/test_tcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))

Expand Down Expand Up @@ -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())
Expand Down
2 changes: 2 additions & 0 deletions tests/test_unix.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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')
Expand Down
28 changes: 21 additions & 7 deletions uvloop/_testbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,20 @@
import select
import socket
import ssl
import sys
import tempfile
import threading
import time
import unittest
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))
Expand All @@ -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(
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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():
Expand Down
19 changes: 11 additions & 8 deletions uvloop/errors.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down
5 changes: 3 additions & 2 deletions uvloop/handles/poll.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ cdef class UVPoll(UVHandle):
self._abort_init()
raise MemoryError()

err = uv.uv_poll_init(self._loop.uvloop,
<uv.uv_poll_t *>self._handle, fd)
err = uv.uv_poll_init_socket(self._loop.uvloop,
<uv.uv_poll_t *>self._handle,
<uv.uv_os_sock_t>fd)
if err < 0:
self._abort_init()
raise convert_error(err)
Expand Down
11 changes: 10 additions & 1 deletion uvloop/handles/stream.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (<uv.uv_stream_t*>self._handle).write_queue_size != 0:
raise RuntimeError(
'UVStream._try_write called with data in uv buffers')
Expand Down Expand Up @@ -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
Expand All @@ -423,7 +428,11 @@ cdef class UVStream(UVBaseTransport):
if not buf_len:
return

if (<uv.uv_stream_t*>self._handle).write_queue_size == 0:
IF UNAME_SYSNAME == "Windows":
skip_try = 1

if (not skip_try and
(<uv.uv_stream_t*>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
Expand Down
Loading