diff --git a/.travis.yml b/.travis.yml index 2cc480f39..6ca06e3de 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,9 @@ x-tox-env: &x-tox-env > - TOX_CMD="tox --skip-missing-interpreters=false -v" + TOX_CMD="${TOX_PATH:-tox} --skip-missing-interpreters=false -v" x-tox-install: &x-tox-install | - pip install -U pip - pip install 'tox<=3.14.5' 'virtualenv<20.0.5' + ${PIP_PATH:-pip} install -U pip + ${PIP_PATH:-pip} install 'tox<=3.14.5' 'virtualenv<20.0.5' x-linux-shard: &x-linux-shard os: linux @@ -51,6 +51,9 @@ x-pyenv-install: &x-pyenv-install | "${PYENV}" install --keep --skip-existing ${PYENV_VERSION} "${PYENV}" global ${PYENV_VERSION} +x-pyenv-27-env: &x-pyenv-27-env > + PYENV_VERSION=2.7.18 + x-pyenv-39-env: &x-pyenv-39-env > PYENV_VERSION=3.9.1 @@ -89,7 +92,7 @@ x-osx-27-shard: &x-osx-27-shard env: - *x-tox-env - *x-pyenv-env - - PYENV_VERSION=2.7.18 + - *x-pyenv-27-env x-osx-39-shard: &x-osx-39-shard <<: *x-osx-shard @@ -98,6 +101,33 @@ x-osx-39-shard: &x-osx-39-shard - *x-pyenv-env - *x-pyenv-39-env +x-windows-shard: &x-windows-shard + os: windows + language: shell + cache: + # The default is 3 minutes (180). + timeout: 300 + install: + - *x-tox-install + +x-windows-27-shard: &x-windows-27-shard + <<: *x-windows-shard + before_install: + - choco install python2 + env: + - PIP_PATH="C:\Python27\python.exe -m pip" + - TOX_PATH="C:\Python27\Scripts\tox" + - *x-tox-env + +x-windows-39-shard: &x-windows-39-shard + <<: *x-windows-shard + before_install: + - choco install python --version 3.9.1 + env: + - PIP_PATH="C:\Python39\python.exe -m pip" + - TOX_PATH="C:\Python39\Scripts\tox" + - *x-tox-env + # NB: Travis partitions caches using a combination of os, language amd env vars. As such, we do not # use TOXENV and instead pass the toxenv via -e on the command line. This helps ensure we share # caches as much as possible (eg: all linux python 2.7.15 shards share a cache). @@ -177,3 +207,19 @@ matrix: - <<: *x-osx-39-shard name: TOXENV=py39-integration script: ${TOX_CMD} -e py39-integration + + - <<: *x-windows-27-shard + name: TOXENV=py27 + script: ${TOX_CMD} -e py27 + + - <<: *x-windows-39-shard + name: TOXENV=py39 + script: ${TOX_CMD} -e py39 + + - <<: *x-windows-27-shard + name: TOXENV=py27-integration + script: ${TOX_CMD} -e py27-integration + + - <<: *x-windows-39-shard + name: TOXENV=py39-integration + script: ${TOX_CMD} -e py39-integration diff --git a/pex/common.py b/pex/common.py index d4d8e421e..8b5adc6dd 100644 --- a/pex/common.py +++ b/pex/common.py @@ -6,7 +6,6 @@ import atexit import contextlib import errno -import fcntl import os import re import shutil @@ -21,8 +20,14 @@ from datetime import datetime from uuid import uuid4 +from pex.compatibility import WINDOWS from pex.typing import TYPE_CHECKING +if WINDOWS: + import msvcrt +else: + import fcntl + if TYPE_CHECKING: from typing import Any, DefaultDict, Iterable, Iterator, NoReturn, Optional, Set, Sized @@ -393,7 +398,10 @@ def unlock(): if lock_fd is None: return try: - fcntl.lockf(lock_fd, fcntl.LOCK_UN) + if WINDOWS: + msvcrt.locking(lock_fd, msvcrt.LK_UNLCK, 1) + else: + fcntl.lockf(lock_fd, fcntl.LOCK_UN) finally: os.close(lock_fd) @@ -410,7 +418,21 @@ def unlock(): # N.B.: Since lockf operates on an open file descriptor and these are guaranteed to be # closed by the operating system when the owning process exits, this lock is immune to # staleness. - fcntl.lockf(lock_fd, fcntl.LOCK_EX) # A blocking write lock. + if WINDOWS: + while True: + # Force the non-blocking lock to be blocking. LK_LOCK is msvcrt's implementation of + # a blocking lock, but it only tries 10 times, once per second before rasing an + # OSError. + try: + msvcrt.locking(lock_fd, msvcrt.LK_LOCK, 1) + break + except OSError as ex: + # Deadlock error is raised after failing to lock the file + if ex.errno != errno.EDEADLOCK: + raise + safe_sleep(1) + else: + fcntl.lockf(lock_fd, fcntl.LOCK_EX) # A blocking write lock. if atomic_dir.is_finalized: # We lost the double-checked locking race and our work was done for us by the race # winner so exit early.