Skip to content

Commit 4c41a58

Browse files
authored
Prefix the entire setup.py chroot. (#12372)
More specifically: Add a prefix to the chroot. This prefix only exists at build time, and does not affect the built dist, but allows us to capture setuptools output from input dirs without capturing the entire sandbox. No longer prefix just the sources within the chroot. This was never particularly necessary, and I originally implemented that somewhat arbitrarily. Note that this means that from now on dists will have a different internal structure, but one that is more usual and simpler, so probably better. Run setup.py with its directory as the cwd. Custom setup.pys will expect that. This will be useful when we run the setuptools develop command on setup.py scripts that build native code and write the .so or other output files into the input dirs. This is a replay of #12359 with a fix for the issue that caused it to be reverted. [ci skip-rust]
1 parent 40469dc commit 4c41a58

File tree

4 files changed

+55
-29
lines changed

4 files changed

+55
-29
lines changed

src/python/pants/BUILD

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ python_distribution(
2828
# used when creating the wheel, which is a good thing. We should be setting this ABI to ensure
2929
# consumers of pantsbuild.pants are using a compatible interpreter.
3030
# TODO(7344): the tuple syntax for ext_modules is deprecated. Use Extension once we support it.
31-
ext_modules=[('native_engine', {'sources': ['src/pants/dummy.c']})],
31+
ext_modules=[('native_engine', {'sources': ['pants/dummy.c']})],
3232
).with_binaries(
3333
pants='src/python/pants/bin:pants',
3434
)

src/python/pants/backend/python/goals/setup_py.py

+24-16
Original file line numberDiff line numberDiff line change
@@ -405,10 +405,6 @@ async def package_python_dist(
405405
return BuiltPackage(rel_chroot, (BuiltPackageArtifact(dirname),))
406406

407407

408-
# We write .py sources into the chroot under this dir.
409-
CHROOT_SOURCE_ROOT = "src"
410-
411-
412408
SETUP_BOILERPLATE = """
413409
# DO NOT EDIT THIS FILE -- AUTOGENERATED BY PANTS
414410
# Target: {target_address_spec}
@@ -433,22 +429,40 @@ async def run_setup_py(req: RunSetupPyRequest, setuptools: Setuptools) -> RunSet
433429
interpreter_constraints=req.interpreter_constraints,
434430
),
435431
)
432+
433+
# We prefix the entire chroot, and run with this prefix as the cwd, so that we can capture any
434+
# changes setup made within it (e.g., when running 'develop') without also capturing other
435+
# artifacts of the pex process invocation.
436+
chroot_prefix = "chroot"
437+
436438
# The setuptools dist dir, created by it under the chroot (not to be confused with
437439
# pants's own dist dir, at the buildroot).
438-
dist_dir = "dist/"
440+
dist_dir = "dist"
441+
442+
prefixed_chroot = await Get(Digest, AddPrefix(req.chroot.digest, chroot_prefix))
443+
444+
# setup.py basically always expects to be run with the cwd as its own directory
445+
# (e.g., paths in it are relative to that directory). This is true of the setup.py
446+
# we generate and is overwhelmingly likely to be true for existing setup.py files,
447+
# as there is no robust way to run them otherwise.
448+
setup_script_reldir, setup_script_name = os.path.split(req.chroot.setup_script)
449+
working_directory = os.path.join(chroot_prefix, setup_script_reldir)
450+
439451
result = await Get(
440452
ProcessResult,
441453
VenvPexProcess(
442454
setuptools_pex,
443-
argv=(req.chroot.setup_script, *req.args),
444-
input_digest=req.chroot.digest,
455+
argv=(setup_script_name, *req.args),
456+
input_digest=prefixed_chroot,
457+
working_directory=working_directory,
445458
# setuptools commands that create dists write them to the distdir.
446459
# TODO: Could there be other useful files to capture?
447-
output_directories=(dist_dir,),
460+
output_directories=(dist_dir,), # Relative to the working_directory.
448461
description=f"Run setuptools for {req.exported_target.target.address}",
449462
level=LogLevel.DEBUG,
450463
),
451464
)
465+
# Note that output_digest paths are relative to the working_directory.
452466
output_digest = await Get(Digest, RemovePrefix(result.output_digest, dist_dir))
453467
return RunSetupPyResult(output_digest)
454468

@@ -535,7 +549,6 @@ async def generate_chroot(request: SetupPyChrootRequest) -> SetupPyChroot:
535549
# specified `SetupKwargs(_allow_banned_keys=True)`.
536550
setup_kwargs.update(
537551
{
538-
"package_dir": {"": CHROOT_SOURCE_ROOT, **setup_kwargs.get("package_dir", {})},
539552
"packages": (*sources.packages, *(setup_kwargs.get("packages", []))),
540553
"namespace_packages": (
541554
*sources.namespace_packages,
@@ -595,13 +608,8 @@ async def generate_chroot(request: SetupPyChrootRequest) -> SetupPyChroot:
595608
FileContent("setup.py", setup_py_content),
596609
FileContent("MANIFEST.in", "include *.py".encode()),
597610
]
598-
extra_files_digest, src_digest = await MultiGet(
599-
Get(Digest, CreateDigest(files_to_create)),
600-
# Nest the sources under the src/ prefix.
601-
Get(Digest, AddPrefix(sources.digest, CHROOT_SOURCE_ROOT)),
602-
)
603-
604-
chroot_digest = await Get(Digest, MergeDigests((src_digest, extra_files_digest)))
611+
extra_files_digest = await Get(Digest, CreateDigest(files_to_create))
612+
chroot_digest = await Get(Digest, MergeDigests((sources.digest, extra_files_digest)))
605613
return SetupPyChroot(
606614
chroot_digest, "setup.py", FinalizedSetupKwargs(setup_kwargs, address=target.address)
607615
)

src/python/pants/backend/python/goals/setup_py_test.py

+8-10
Original file line numberDiff line numberDiff line change
@@ -285,13 +285,13 @@ def test_generate_chroot(chroot_rule_runner: RuleRunner) -> None:
285285
assert_chroot(
286286
chroot_rule_runner,
287287
[
288-
"src/files/README.txt",
289-
"src/foo/qux/__init__.py",
290-
"src/foo/qux/qux.py",
291-
"src/foo/qux/qux.pyi",
292-
"src/foo/resources/js/code.js",
293-
"src/foo/__init__.py",
294-
"src/foo/foo.py",
288+
"files/README.txt",
289+
"foo/qux/__init__.py",
290+
"foo/qux/qux.py",
291+
"foo/qux/qux.pyi",
292+
"foo/resources/js/code.js",
293+
"foo/__init__.py",
294+
"foo/foo.py",
295295
"setup.py",
296296
"MANIFEST.in",
297297
],
@@ -300,7 +300,6 @@ def test_generate_chroot(chroot_rule_runner: RuleRunner) -> None:
300300
"name": "foo",
301301
"version": "1.2.3",
302302
"plugin_demo": "hello world",
303-
"package_dir": {"": "src"},
304303
"packages": ("foo", "foo.qux"),
305304
"namespace_packages": ("foo",),
306305
"package_data": {"foo": ("resources/js/code.js",)},
@@ -378,13 +377,12 @@ def test_binary_shorthand(chroot_rule_runner: RuleRunner) -> None:
378377
)
379378
assert_chroot(
380379
chroot_rule_runner,
381-
["src/project/app.py", "setup.py", "MANIFEST.in"],
380+
["project/app.py", "setup.py", "MANIFEST.in"],
382381
"setup.py",
383382
{
384383
"name": "bin",
385384
"version": "1.1.1",
386385
"plugin_demo": "hello world",
387-
"package_dir": {"": "src"},
388386
"packages": ("project",),
389387
"namespace_packages": (),
390388
"install_requires": (),

src/python/pants/engine/process_test.py

+22-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,14 @@
55

66
import pytest
77

8-
from pants.engine.fs import EMPTY_DIGEST, CreateDigest, Digest, DigestContents, FileContent
8+
from pants.engine.fs import (
9+
EMPTY_DIGEST,
10+
CreateDigest,
11+
Digest,
12+
DigestContents,
13+
Directory,
14+
FileContent,
15+
)
916
from pants.engine.internals.scheduler import ExecutionError
1017
from pants.engine.process import (
1118
BinaryPathRequest,
@@ -74,11 +81,24 @@ def test_env(rule_runner: RuleRunner) -> None:
7481
assert b"VAR2=VAL" in result.stdout
7582

7683

77-
def test_output_digest(rule_runner: RuleRunner) -> None:
84+
@pytest.mark.parametrize("working_directory", ["", "subdir"])
85+
def test_output_digest(rule_runner: RuleRunner, working_directory) -> None:
86+
# Test that the output files are relative to the working directory, both in how
87+
# they're specified, and their paths in the output_digest.
88+
input_digest = (
89+
rule_runner.request(
90+
Digest,
91+
[CreateDigest([Directory(working_directory)])],
92+
)
93+
if working_directory
94+
else EMPTY_DIGEST
95+
)
7896
process = Process(
97+
input_digest=input_digest,
7998
argv=("/bin/bash", "-c", "echo -n 'European Burmese' > roland"),
8099
description="echo roland",
81100
output_files=("roland",),
101+
working_directory=working_directory,
82102
)
83103
result = rule_runner.request(ProcessResult, [process])
84104
assert result.output_digest == Digest(

0 commit comments

Comments
 (0)