Skip to content

Implement Windows support #62

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

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')
@@ -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:
@@ -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()

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
@@ -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)
1 change: 1 addition & 0 deletions tests/test_pipes.py
Original file line number Diff line number Diff line change
@@ -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)
4 changes: 4 additions & 0 deletions tests/test_process.py
Original file line number Diff line number Diff line change
@@ -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

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


@tb.skip_windows
class _TestSignal:
NEW_LOOP = None

11 changes: 7 additions & 4 deletions tests/test_tcp.py
Original file line number Diff line number Diff line change
@@ -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())
2 changes: 2 additions & 0 deletions tests/test_unix.py
Original file line number Diff line number Diff line change
@@ -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')
28 changes: 21 additions & 7 deletions uvloop/_testbase.py
Original file line number Diff line number Diff line change
@@ -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))
@@ -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():
19 changes: 11 additions & 8 deletions uvloop/errors.pyx
Original file line number Diff line number Diff line change
@@ -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):
5 changes: 3 additions & 2 deletions uvloop/handles/poll.pyx
Original file line number Diff line number Diff line change
@@ -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)
11 changes: 10 additions & 1 deletion uvloop/handles/stream.pyx
Original file line number Diff line number Diff line change
@@ -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')
@@ -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 (<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
Loading