Skip to content

Commit

Permalink
Checkpoint 1.
Browse files Browse the repository at this point in the history
Working:
+ `py -mpex cowsay -ccowsay -- -t Moo!`
+ `py -mpex cowsay -ccowsay --venv -- -t Moo!`
+ `py -mpex cowsay -ccowsay -ocowsay.pex`
+ `py -mpex cowsay -ccowsay --venv -ocowsay.pex`
+ `py -mpex cowsay`
+ `py -mpex.cli cache {prune,purge}`

Next up:
+ Figure out tox on Windows.
+ Set up failing CI.

Work towards pex-tool#2658
  • Loading branch information
jsirois committed Feb 5, 2025
1 parent 76c3da6 commit 8f3f59e
Show file tree
Hide file tree
Showing 28 changed files with 592 additions and 89 deletions.
39 changes: 7 additions & 32 deletions pex/atomic_directory.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from __future__ import absolute_import

import errno
import fcntl
import hashlib
import os
import threading
Expand All @@ -13,8 +12,9 @@

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, safe_rename
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 +161,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)
safe_rename(source, self._target_dir)
except OSError as e:
if e.errno not in (errno.EEXIST, errno.ENOTEMPTY):
raise e
Expand All @@ -173,17 +173,6 @@ 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

Expand All @@ -209,29 +198,15 @@ 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, FileLockStyle.BSD if _is_bsd_lock(self._style) else FileLockStyle.POSIX)

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
21 changes: 8 additions & 13 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.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
3 changes: 3 additions & 0 deletions pex/cache/dirs.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,9 @@ def iter_all(cls, pex_root=ENV):
for venv_short_dir_symlink in glob.glob(
CacheDir.VENVS.path("s", "*", cls.SHORT_SYMLINK_NAME, pex_root=pex_root)
):
# TODO(John Sirois): XXX: Explain or limit this Windows hack.
# See: https://github.com/pex-tool/pex/issues/2660#issuecomment-2635441311
venv_short_dir_symlink = os.path.realpath(venv_short_dir_symlink)
if not os.path.isdir(venv_short_dir_symlink):
continue

Expand Down
9 changes: 8 additions & 1 deletion pex/cache/prunable.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from __future__ import absolute_import

import os.path
import sys
from collections import OrderedDict
from datetime import datetime

Expand Down Expand Up @@ -54,7 +55,13 @@ def scan(cls, pex_dirs_by_hash):
# extra requirements installed does not affect cache management.
pip_caches_to_prune = OrderedDict() # type: OrderedDict[PipVersionValue, Pip]
for pip in iter_all_pips(record_access=False):
pex_dir, prunable = pex_dirs_by_hash[pip.pex_hash]
try:
pex_dir, prunable = pex_dirs_by_hash[pip.pex_hash]
except KeyError:
print("pex_dirs_by_hash:", file=sys.stderr)
for hash, pex_dirs in pex_dirs_by_hash.items():
print(f"{hash=} {pex_dirs}", file=sys.stderr)
raise
if prunable:
pips_to_prune[pip] = False
else:
Expand Down
3 changes: 2 additions & 1 deletion pex/cli/commands/lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from pex.executables import is_exe
from pex.interpreter import PythonInterpreter
from pex.orderedset import OrderedSet
from pex.os import 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 +345,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
3 changes: 2 additions & 1 deletion 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_rename
from pex.typing import TYPE_CHECKING, cast

if TYPE_CHECKING:
Expand Down Expand Up @@ -119,7 +120,7 @@ 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.
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
4 changes: 4 additions & 0 deletions pex/dist_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,10 @@ def iter_metadata_files(
):
# type: (...) -> Iterator[MetadataFiles]

# TODO(John Sirois): XXX: Explain or limit this Windows hack.
# See: https://github.com/pex-tool/pex/issues/2660#issuecomment-2635441311
location = os.path.realpath(location)

files = []
for metadata_type in restrict_types_to or MetadataType.values():
key = MetadataKey(metadata_type=metadata_type, location=location)
Expand Down
5 changes: 3 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,14 @@ 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
126 changes: 126 additions & 0 deletions pex/fs/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# Copyright 2025 Pex project contributors.
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import absolute_import

import os
import sys
from typing import Callable

from pex.os import WINDOWS
from pex.typing import TYPE_CHECKING

if TYPE_CHECKING:
from typing import Text, Optional

if WINDOWS and not hasattr(os, "replace"):
_MOVEFILE_REPLACE_EXISTING = 0x1

_MF = None

def safe_rename(
src, # type: Text
dst, # type: Text
):
# type: (...) -> None

import ctypes
from ctypes.wintypes import BOOL, DWORD, LPCWSTR

global _MF
if _MF is None:
mf = ctypes.windll.kernel32.MoveFileExW
mf.argtypes = (
# lpExistingFileName
LPCWSTR,
# lpNewFileName
LPCWSTR,
# dwFlags
DWORD,
)
mf.restype = BOOL
_MF = mf

# See: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-movefileexw
if not _MF(src, dst, _MOVEFILE_REPLACE_EXISTING):
raise ctypes.WinError()

else:
safe_rename = getattr(os, "replace", os.rename)


# N.B.: Python 3.7 has os.symlink on Windows, but the implementation does not pass the
# _SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE flag.
if WINDOWS and (not hasattr(os, "symlink") or sys.version_info[:2] < (3, 8)):
_SYMBOLIC_LINK_FLAG_FILE = 0x0
_SYMBOLIC_LINK_FLAG_DIRECTORY = 0x1
_SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE = 0x2

_CSL = None

def safe_symlink(
src, # type: Text
dst, # type: Text
):
# type: (...) -> None

import ctypes
from ctypes.wintypes import BOOLEAN, DWORD, LPCWSTR

global _CSL
if _CSL is None:
csl = ctypes.windll.kernel32.CreateSymbolicLinkW
csl.argtypes = (
# lpSymlinkFileName
LPCWSTR,
# lpTargetFileName
LPCWSTR,
# dwFlags
DWORD,
)
csl.restype = BOOLEAN
_CSL = csl

# See: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createsymboliclinkw
flags = _SYMBOLIC_LINK_FLAG_DIRECTORY if os.path.isdir(src) else _SYMBOLIC_LINK_FLAG_FILE
flags |= _SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
if not _CSL(dst, src, flags):
raise ctypes.WinError()

else:
safe_realpath = os.path.realpath
safe_symlink = getattr(os, "symlink")


if WINDOWS and not hasattr(os, "link"):
_CHL = None

def safe_link(
src, # type: Text
dst, # type: Text
):
# type: (...) -> None

import ctypes
from ctypes.wintypes import BOOL, LPCWSTR, LPVOID

global _CHL
if _CHL is None:
# See: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createhardlinkw
chl = ctypes.windll.kernel32.CreateHardLinkW
chl.argtypes = (
# lpFileName
LPCWSTR,
# lpExistingFileName
LPCWSTR,
# lpSecurityAttributes (Reserved; must be NULL)
LPVOID,
)
chl.restype = BOOL
_CHL = chl

if not _CHL(dst, src, None):
raise ctypes.WinError()

else:
safe_link = getattr(os, "link")
Loading

0 comments on commit 8f3f59e

Please sign in to comment.