Skip to content

Commit accfcb3

Browse files
authored
Merge branch 'main' into main
2 parents 104f4bc + ccdfc8a commit accfcb3

4 files changed

Lines changed: 328 additions & 83 deletions

File tree

python/private/pypi/whl_library.bzl

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,11 @@ def _parse_optional_attrs(rctx, args, extra_pip_args = None):
183183
if rctx.attr.add_libdir_to_library_search_path:
184184
if "LDFLAGS" in env:
185185
fail("Can't set both environment LDFLAGS and add_libdir_to_library_search_path")
186-
command = [pypi_repo_utils.resolve_python_interpreter(rctx), "-c", "import sys ; sys.stdout.write('{}/lib'.format(sys.exec_prefix))"]
186+
command = [
187+
pypi_repo_utils.resolve_python_interpreter(rctx),
188+
"-c",
189+
"import sys ; sys.stdout.write('{}/lib'.format(sys.exec_prefix))",
190+
]
187191
result = rctx.execute(command)
188192
if result.return_code != 0:
189193
fail("Failed to get LDFLAGS path: command: {}, exit code: {}, stdout: {}, stderr: {}".format(command, result.return_code, result.stdout, result.stderr))
@@ -292,22 +296,11 @@ def _extract_whl_py(rctx, *, python_interpreter, args, whl_path, environment, lo
292296

293297
def _whl_library_impl(rctx):
294298
logger = repo_utils.logger(rctx)
295-
python_interpreter = pypi_repo_utils.resolve_python_interpreter(
296-
rctx,
297-
python_interpreter = rctx.attr.python_interpreter,
298-
python_interpreter_target = rctx.attr.python_interpreter_target,
299-
)
300-
args = [
301-
"-m",
302-
"python.private.pypi.whl_installer.wheel_installer",
303-
"--requirement",
304-
rctx.attr.requirement,
305-
]
306-
extra_pip_args = []
307-
extra_pip_args.extend(rctx.attr.extra_pip_args)
308299

309300
whl_path = None
310301
sdist_filename = None
302+
extra_pip_args = []
303+
extra_pip_args.extend(rctx.attr.extra_pip_args)
311304
if rctx.attr.whl_file:
312305
rctx.watch(rctx.attr.whl_file)
313306
whl_path = rctx.path(rctx.attr.whl_file)
@@ -352,17 +345,30 @@ def _whl_library_impl(rctx):
352345
# build deps from PyPI (e.g. `flit_core`) if they are missing.
353346
extra_pip_args.extend(["--find-links", "."])
354347

355-
args = _parse_optional_attrs(rctx, args, extra_pip_args)
356-
357348
# also enable pipstar for any whls that are downloaded without `pip`
358349
enable_pipstar = (rp_config.enable_pipstar or whl_path) and rctx.attr.config_load
359350
enable_pipstar_extract = enable_pipstar and rp_config.bazel_8_or_later
360351

361352
# When pipstar is enabled, Python isn't used, so there's no need
362353
# to setup env vars to run Python, unless we need to build an sdist
363-
if enable_pipstar_extract and whl_path:
354+
if enable_pipstar_extract and whl_path and not rctx.attr.whl_patches:
364355
environment = {}
356+
args = []
357+
python_interpreter = None
365358
else:
359+
python_interpreter = pypi_repo_utils.resolve_python_interpreter(
360+
rctx,
361+
python_interpreter = rctx.attr.python_interpreter,
362+
python_interpreter_target = rctx.attr.python_interpreter_target,
363+
)
364+
args = [
365+
"-m",
366+
"python.private.pypi.whl_installer.wheel_installer",
367+
"--requirement",
368+
rctx.attr.requirement,
369+
]
370+
args = _parse_optional_attrs(rctx, args, extra_pip_args)
371+
366372
# Manually construct the PYTHONPATH since we cannot use the toolchain here
367373
environment = _create_repository_execution_environment(rctx, python_interpreter, logger = logger)
368374

python/runfiles/runfiles.py

Lines changed: 175 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,16 @@
2929
import posixpath
3030
import sys
3131
from collections import defaultdict
32-
from typing import Dict, List, Optional, Tuple, Union
32+
from typing import Dict, Generator, Iterable, List, Optional, Tuple, Union
33+
34+
if sys.version_info >= (3, 11):
35+
from typing import Self
36+
elif sys.version_info >= (3, 10):
37+
from typing import TypeAlias
38+
39+
Self: TypeAlias = "Path" # type: ignore
40+
else:
41+
from typing import Any as Self
3342

3443

3544
class _RepositoryMapping:
@@ -143,25 +152,30 @@ def is_empty(self) -> bool:
143152
)
144153

145154

146-
class Path(pathlib.PurePath):
155+
class Path(pathlib.Path):
147156
"""A pathlib-like path object for runfiles.
148157
149-
This class extends `pathlib.PurePath` and resolves paths
158+
This class extends `pathlib.Path` and resolves paths
150159
using the associated `Runfiles` instance when converted to a string.
151160
"""
152161

153-
# For Python < 3.12 compatibility when subclassing PurePath directly
154-
_flavour = getattr(type(pathlib.PurePath()), "_flavour", None)
162+
# Mypy isn't smart enough to realize `self` in the methods
163+
# refers to our Path class instead of pathlib.Path
164+
_runfiles: Optional["Runfiles"]
165+
_source_repo: Optional[str]
166+
167+
# For Python < 3.12 compatibility when subclassing Path directly
168+
_flavour = getattr(type(pathlib.Path()), "_flavour", None)
155169

156170
def __new__(
157171
cls,
158172
*args: Union[str, os.PathLike],
159173
runfiles: Optional["Runfiles"] = None,
160174
source_repo: Optional[str] = None,
161-
) -> "Path":
175+
) -> Self:
162176
"""Private constructor. Use Runfiles.root() to create instances."""
163177
obj = super().__new__(cls, *args)
164-
# Type checkers might complain about adding attributes to PurePath,
178+
# Type checkers might complain about adding attributes to Path,
165179
# but this is standard for pathlib subclasses.
166180
obj._runfiles = runfiles # type: ignore
167181
obj._source_repo = source_repo # type: ignore
@@ -173,90 +187,205 @@ def __init__(
173187
runfiles: Optional["Runfiles"] = None,
174188
source_repo: Optional[str] = None,
175189
) -> None:
176-
pass
190+
# In Python 3.12+, pathlib was refactored and Path.__init__ now accepts
191+
# *args. Prior to 3.12, Path did not define __init__, so
192+
# super().__init__(*args) would fall through to object.__init__, which
193+
# raises a TypeError because it takes no arguments.
194+
if sys.version_info >= (3, 12):
195+
super().__init__(*args)
196+
else:
197+
super().__init__()
198+
199+
# We override resolve() and absolute() to ensure that in Python < 3.12,
200+
# where pathlib internally uses object.__new__ instead of our custom
201+
# __new__ or with_segments(), the runfiles state is preserved. We delegate
202+
# to self._as_path() because super().resolve() creates intermediate objects
203+
# that would otherwise crash during internal stat() calls.
204+
# override
205+
def resolve(self, strict: bool = False) -> Self:
206+
return type(self)(
207+
self._as_path().resolve(strict=strict),
208+
runfiles=self._runfiles,
209+
source_repo=self._source_repo,
210+
)
177211

178-
def with_segments(self, *pathsegments: Union[str, os.PathLike]) -> "Path":
212+
# override
213+
def absolute(self) -> Self:
214+
return type(self)(
215+
self._as_path().absolute(),
216+
runfiles=self._runfiles,
217+
source_repo=self._source_repo,
218+
)
219+
220+
# override
221+
def with_segments(self, *pathsegments: Union[str, os.PathLike]) -> Self:
179222
"""Used by Python 3.12+ pathlib to create new path objects."""
180223
return type(self)(
181224
*pathsegments,
182-
runfiles=self._runfiles, # type: ignore
183-
source_repo=self._source_repo, # type: ignore
225+
runfiles=self._runfiles,
226+
source_repo=self._source_repo,
184227
)
185228

186229
# For Python < 3.12
187-
@classmethod
188-
def _from_parts(cls, args: Tuple[str, ...]) -> "Path":
189-
obj = super()._from_parts(args) # type: ignore
190-
# These will be set by the calling instance later, or we can't set them here
191-
# properly without context. Usually pathlib calls this from an instance
192-
# method like _make_child, which we also might need to override.
193-
return obj
194-
195-
def _make_child(self, args: Tuple[str, ...]) -> "Path":
230+
# override
231+
def _make_child(self, args: Tuple[str, ...]) -> Self:
196232
obj = super()._make_child(args) # type: ignore
197233
obj._runfiles = self._runfiles # type: ignore
198234
obj._source_repo = self._source_repo # type: ignore
199235
return obj
200236

201-
@classmethod
202-
def _from_parsed_parts(cls, drv: str, root: str, parts: List[str]) -> "Path":
203-
obj = super()._from_parsed_parts(drv, root, parts) # type: ignore
204-
return obj
205-
206-
def _make_child_relpath(self, part: str) -> "Path":
207-
obj = super()._make_child_relpath(part) # type: ignore
208-
obj._runfiles = self._runfiles # type: ignore
209-
obj._source_repo = self._source_repo # type: ignore
210-
return obj
211-
237+
# override
212238
@property
213-
def parents(self) -> Tuple["Path", ...]:
239+
def parents(self) -> Tuple[Self, ...]:
214240
return tuple(
215241
type(self)(
216242
p,
217-
runfiles=getattr(self, "_runfiles", None),
218-
source_repo=getattr(self, "_source_repo", None),
243+
runfiles=self._runfiles,
244+
source_repo=self._source_repo,
219245
)
220246
for p in super().parents
221247
)
222248

249+
# override
223250
@property
224-
def parent(self) -> "Path":
251+
def parent(self) -> Self:
225252
return type(self)(
226253
super().parent,
227-
runfiles=getattr(self, "_runfiles", None),
228-
source_repo=getattr(self, "_source_repo", None),
254+
runfiles=self._runfiles,
255+
source_repo=self._source_repo,
229256
)
230257

231-
def with_name(self, name: str) -> "Path":
258+
@property
259+
def runfile_path(self) -> str:
260+
"""Returns the runfiles-root relative path."""
261+
path_posix = super().__str__().replace("\\", "/")
262+
if path_posix == ".":
263+
return ""
264+
return path_posix
265+
266+
# override
267+
def with_name(self, name: str) -> Self:
232268
return type(self)(
233269
super().with_name(name),
234-
runfiles=getattr(self, "_runfiles", None),
235-
source_repo=getattr(self, "_source_repo", None),
270+
runfiles=self._runfiles,
271+
source_repo=self._source_repo,
236272
)
237273

238-
def with_suffix(self, suffix: str) -> "Path":
274+
# override
275+
def with_suffix(self, suffix: str) -> Self:
239276
return type(self)(
240277
super().with_suffix(suffix),
241-
runfiles=getattr(self, "_runfiles", None),
242-
source_repo=getattr(self, "_source_repo", None),
278+
runfiles=self._runfiles,
279+
source_repo=self._source_repo,
280+
)
281+
282+
def _as_path(self) -> pathlib.Path:
283+
return pathlib.Path(str(self))
284+
285+
# override
286+
def stat(self, *, follow_symlinks: bool = True) -> os.stat_result:
287+
return self._as_path().stat(follow_symlinks=follow_symlinks)
288+
289+
# override
290+
def lstat(self) -> os.stat_result:
291+
return self._as_path().lstat()
292+
293+
# override
294+
def exists(self) -> bool:
295+
return self._as_path().exists()
296+
297+
# override
298+
def is_dir(self) -> bool:
299+
return self._as_path().is_dir()
300+
301+
# override
302+
def is_file(self) -> bool:
303+
return self._as_path().is_file()
304+
305+
# override
306+
def is_symlink(self) -> bool:
307+
return self._as_path().is_symlink()
308+
309+
# override
310+
def is_block_device(self) -> bool:
311+
return self._as_path().is_block_device()
312+
313+
# override
314+
def is_char_device(self) -> bool:
315+
return self._as_path().is_char_device()
316+
317+
# override
318+
def is_fifo(self) -> bool:
319+
return self._as_path().is_fifo()
320+
321+
# override
322+
def is_socket(self) -> bool:
323+
return self._as_path().is_socket()
324+
325+
# override
326+
def open(
327+
self,
328+
mode: str = "r",
329+
buffering: int = -1,
330+
encoding: Optional[str] = None,
331+
errors: Optional[str] = None,
332+
newline: Optional[str] = None,
333+
):
334+
return self._as_path().open(
335+
mode=mode,
336+
buffering=buffering,
337+
encoding=encoding,
338+
errors=errors,
339+
newline=newline,
243340
)
244341

342+
# override
343+
def read_bytes(self) -> bytes:
344+
return self._as_path().read_bytes()
345+
346+
# override
347+
def read_text(
348+
self, encoding: Optional[str] = None, errors: Optional[str] = None
349+
) -> str:
350+
return self._as_path().read_text(encoding=encoding, errors=errors)
351+
352+
# override
353+
def iterdir(self) -> Generator[Self, None, None]:
354+
resolved = self._as_path()
355+
for p in resolved.iterdir():
356+
yield self / p.name
357+
358+
# override
359+
def glob(self, pattern: str) -> Generator[Self, None, None]:
360+
resolved = self._as_path()
361+
for p in resolved.glob(pattern):
362+
yield self / p.relative_to(resolved)
363+
364+
# override
365+
def rglob(self, pattern: str) -> Generator[Self, None, None]:
366+
resolved = self._as_path()
367+
for p in resolved.rglob(pattern):
368+
yield self / p.relative_to(resolved)
369+
245370
def __repr__(self) -> str:
246-
return 'runfiles.Path({!r})'.format(super().__str__())
371+
return "runfiles.Path({!r})".format(self.runfile_path)
247372

248373
def __str__(self) -> str:
249374
path_posix = super().__str__().replace("\\", "/")
250375
if not path_posix or path_posix == ".":
251376
# pylint: disable=protected-access
252377
return self._runfiles._python_runfiles_root # type: ignore
253378
resolved = self._runfiles.Rlocation(path_posix, source_repo=self._source_repo) # type: ignore
254-
return resolved if resolved is not None else super().__str__()
379+
if resolved is not None:
380+
return resolved
381+
382+
# pylint: disable=protected-access
383+
return posixpath.join(self._runfiles._python_runfiles_root, path_posix) # type: ignore
255384

256385
def __fspath__(self) -> str:
257386
return str(self)
258387

259-
def runfiles_root(self) -> "Path":
388+
def runfiles_root(self) -> Self:
260389
"""Returns a Path object representing the runfiles root."""
261390
return self._runfiles.root(source_repo=self._source_repo) # type: ignore
262391

0 commit comments

Comments
 (0)