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

Add Windows support in key critical areas. #2663

Merged
merged 3 commits into from
Feb 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 11 additions & 37 deletions pex/atomic_directory.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@
from __future__ import absolute_import

import errno
import fcntl
import hashlib
import os
import threading
from contextlib import contextmanager
from uuid import uuid4

import pex
from pex import pex_warnings
from pex.common import safe_mkdir, safe_rmtree
from pex.enum import Enum
from pex.typing import TYPE_CHECKING, cast
from pex.fs import lock
from pex.fs.lock import FileLockStyle
from pex.typing import TYPE_CHECKING

if TYPE_CHECKING:
from typing import Callable, Dict, Iterator, Optional
Expand Down Expand Up @@ -161,7 +162,7 @@ def finalize(self, source=None):
#
# We have satisfied the single filesystem constraint by arranging the `work_dir` to be a
# sibling of the `target_dir`.
os.rename(source, self._target_dir)
pex.fs.safe_rename(source, self._target_dir)
except OSError as e:
if e.errno not in (errno.EEXIST, errno.ENOTEMPTY):
raise e
Expand All @@ -173,19 +174,8 @@ def cleanup(self):
safe_rmtree(self._work_dir)


class FileLockStyle(Enum["FileLockStyle.Value"]):
class Value(Enum.Value):
pass

BSD = Value("bsd")
POSIX = Value("posix")


FileLockStyle.seal()


def _is_bsd_lock(lock_style=None):
# type: (Optional[FileLockStyle.Value]) -> bool
def _lock_style(lock_style=None):
# type: (Optional[FileLockStyle.Value]) -> FileLockStyle.Value

# The atomic_directory file locking has used POSIX locks since inception. These have maximum
# compatibility across OSes and stand a decent chance of working over modern NFS. With the
Expand All @@ -194,10 +184,9 @@ def _is_bsd_lock(lock_style=None):
# `lock_style` to atomic_directory. In order to allow experimenting with / debugging possible
# file locking bugs, we allow a `_PEX_FILE_LOCK_STYLE` back door private ~API to upgrade all
# locks to BSD style locks. This back door can be removed at any time.
file_lock_style = lock_style or FileLockStyle.for_value(
return lock_style or FileLockStyle.for_value(
os.environ.get("_PEX_FILE_LOCK_STYLE", FileLockStyle.POSIX.value)
)
return file_lock_style is FileLockStyle.BSD


@attr.s(frozen=True)
Expand All @@ -209,29 +198,14 @@ class _FileLock(object):
def acquire(self):
# type: () -> Callable[[], None]
self._in_process_lock.acquire()

# N.B.: We don't actually write anything to the lock file but the fcntl file locking
# operations only work on files opened for at least write.
safe_mkdir(os.path.dirname(self._path))
lock_fd = os.open(self._path, os.O_CREAT | os.O_WRONLY)

lock_api = cast(
"Callable[[int, int], None]",
fcntl.flock if _is_bsd_lock(self._style) else fcntl.lockf,
)

# N.B.: Since lockf and flock operate 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.
lock_api(lock_fd, fcntl.LOCK_EX) # A blocking write lock.
file_lock = lock.acquire(self._path, exclusive=True, style=_lock_style(self._style))

def release():
# type: () -> None
try:
lock_api(lock_fd, fcntl.LOCK_UN)
file_lock.release()
finally:
os.close(lock_fd)
self._in_process_lock.release()
self._in_process_lock.release()

return release

Expand Down
23 changes: 9 additions & 14 deletions pex/cache/access.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@

from __future__ import absolute_import, print_function

import fcntl
import itertools
import os
from contextlib import contextmanager

from pex.common import safe_mkdir, touch
from pex.common import touch
from pex.fs import lock
from pex.fs.lock import FileLockStyle
from pex.os import WINDOWS
from pex.typing import TYPE_CHECKING
from pex.variables import ENV

Expand Down Expand Up @@ -64,21 +66,14 @@ def _lock(exclusive):
existing_exclusive, lock_fd, existing_lock_file = _LOCK
if existing_exclusive == exclusive:
return existing_lock_file
elif WINDOWS:
# Windows shared locks are not re-entrant; so this is the best we can do.
lock.release(lock_fd)

lock_file = os.path.join(ENV.PEX_ROOT, "access.lck")

if lock_fd is None:
# N.B.: We don't actually write anything to the lock file but the fcntl file locking
# operations only work on files opened for at least write.
safe_mkdir(os.path.dirname(lock_file))
lock_fd = os.open(lock_file, os.O_CREAT | os.O_WRONLY)

# N.B.: Since flock 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.flock(lock_fd, fcntl.LOCK_EX if exclusive else fcntl.LOCK_SH)

_LOCK = exclusive, lock_fd, lock_file
file_lock = lock.acquire(lock_file, exclusive=exclusive, style=FileLockStyle.BSD, fd=lock_fd)
_LOCK = exclusive, file_lock.fd, lock_file
return lock_file


Expand Down
2 changes: 1 addition & 1 deletion pex/cache/dirs.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
from pex.compatibility import commonpath
from pex.enum import Enum
from pex.exceptions import production_assert
from pex.executables import is_exe
from pex.orderedset import OrderedSet
from pex.os import is_exe
from pex.typing import TYPE_CHECKING, cast
from pex.variables import ENV, Variables

Expand Down
4 changes: 2 additions & 2 deletions pex/cli/commands/lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@
)
from pex.enum import Enum
from pex.exceptions import production_assert
from pex.executables import is_exe
from pex.interpreter import PythonInterpreter
from pex.orderedset import OrderedSet
from pex.os import is_exe, safe_execv
from pex.pep_376 import InstalledWheel, Record
from pex.pep_427 import InstallableType
from pex.pep_440 import Version
Expand Down Expand Up @@ -344,7 +344,7 @@ def sync(

if self.command:
try:
os.execv(self.command[0], self.command)
safe_execv(self.command)
except OSError as e:
return Error("Failed to execute {exe}: {err}".format(exe=self.command[0], err=e))

Expand Down
11 changes: 6 additions & 5 deletions pex/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

from pex.enum import Enum
from pex.executables import chmod_plus_x
from pex.fs import safe_link, safe_rename, safe_symlink
from pex.typing import TYPE_CHECKING, cast

if TYPE_CHECKING:
Expand Down Expand Up @@ -119,13 +120,13 @@ def do_copy():
# type: () -> None
temp_dest = dest + uuid4().hex
shutil.copy(source, temp_dest)
os.rename(temp_dest, dest)
safe_rename(temp_dest, dest)

# If the platform supports hard-linking, use that and fall back to copying.
# Windows does not support hard-linking.
if hasattr(os, "link"):
try:
os.link(source, dest)
safe_link(source, dest)
except OSError as e:
if e.errno == errno.EEXIST:
# File already exists. If overwrite=True, write otherwise skip.
Expand Down Expand Up @@ -618,7 +619,7 @@ def symlink(
self._ensure_parent(dst)
abs_src = os.path.realpath(src)
abs_dst = os.path.realpath(os.path.join(self.chroot, dst))
os.symlink(os.path.relpath(abs_src, os.path.dirname(abs_dst)), abs_dst)
safe_symlink(os.path.relpath(abs_src, os.path.dirname(abs_dst)), abs_dst)

def write(
self,
Expand Down Expand Up @@ -779,7 +780,7 @@ def relative_symlink(
"""
dst_parent = os.path.dirname(dst)
rel_src = os.path.relpath(src, dst_parent)
os.symlink(rel_src, dst)
safe_symlink(rel_src, dst)


class CopyMode(Enum["CopyMode.Value"]):
Expand Down Expand Up @@ -839,7 +840,7 @@ def iter_copytree(
# later go missing leaving the dst_entry dangling.
if link and not os.path.islink(src_entry):
try:
os.link(src_entry, dst_entry)
safe_link(src_entry, dst_entry)
continue
except OSError as e:
if e.errno != errno.EXDEV:
Expand Down
2 changes: 0 additions & 2 deletions pex/compatibility.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,6 @@ def cpu_count():

from Queue import Queue as Queue

WINDOWS = os.name == "nt"


# Universal newlines is the default in Python 3.
MODE_READ_UNIVERSAL_NEWLINES = "rU" if PY2 else "r"
Expand Down
11 changes: 1 addition & 10 deletions pex/executables.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import stat
from textwrap import dedent

from pex.os import is_exe
from pex.typing import TYPE_CHECKING

if TYPE_CHECKING:
Expand All @@ -28,16 +29,6 @@ def chmod_plus_x(path):
os.chmod(path, path_mode)


def is_exe(path):
# type: (Text) -> bool
"""Determines if the given path is a file executable by the current user.

:param path: The path to check.
:return: True if the given path is a file executable by the current user.
"""
return os.path.isfile(path) and os.access(path, os.R_OK | os.X_OK)


def is_script(
path, # type: Text
pattern=None, # type: Optional[bytes]
Expand Down
7 changes: 5 additions & 2 deletions pex/finders.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from pex.executables import is_python_script
from pex.pep_376 import InstalledWheel
from pex.pep_503 import ProjectName
from pex.sysconfig import SCRIPT_DIR, script_name
from pex.typing import TYPE_CHECKING, cast
from pex.wheel import Wheel

Expand All @@ -38,14 +39,16 @@ def find(
):
# type: (...) -> Optional[DistributionScript]
if dist.type is DistributionType.WHEEL:
script_path = Wheel.load(dist.location).data_path("scripts", name)
script_path = Wheel.load(dist.location).data_path("scripts", script_name(name))
with open_zip(dist.location) as zfp:
try:
zfp.getinfo(script_path)
except KeyError:
return None
elif dist.type is DistributionType.INSTALLED:
script_path = InstalledWheel.load(dist.location).stashed_path("bin", name)
script_path = InstalledWheel.load(dist.location).stashed_path(
SCRIPT_DIR, script_name(name)
)
if not os.path.isfile(script_path):
return None
else:
Expand Down
Loading