From ed52c0081f983a687645203eee0f54cc4270beff Mon Sep 17 00:00:00 2001 From: Benjy Weinberger Date: Sat, 14 Aug 2021 11:30:42 -0400 Subject: [PATCH] Make local dists available in test, run and repl. [ci skip-rust] [ci skip-build-wheels] --- .../backend/python/goals/pytest_runner.py | 11 ++- .../goals/pytest_runner_integration_test.py | 61 +++++++++++++- src/python/pants/backend/python/goals/repl.py | 61 +++++++++++--- .../backend/python/goals/run_pex_binary.py | 27 +++++- .../goals/run_pex_binary_integration_test.py | 44 ++++++++++ src/python/pants/backend/python/register.py | 2 + .../backend/python/util_rules/local_dists.py | 83 +++++++++++++++++++ .../python/util_rules/local_dists_test.py | 77 +++++++++++++++++ src/python/pants/core/goals/run.py | 2 +- src/python/pants/engine/fs.py | 4 +- src/python/pants/engine/internals/BUILD | 2 +- testprojects/src/python/native/BUILD | 19 ++++- 12 files changed, 368 insertions(+), 25 deletions(-) create mode 100644 src/python/pants/backend/python/util_rules/local_dists.py create mode 100644 src/python/pants/backend/python/util_rules/local_dists_test.py diff --git a/src/python/pants/backend/python/goals/pytest_runner.py b/src/python/pants/backend/python/goals/pytest_runner.py index b8923d55596..12033004aef 100644 --- a/src/python/pants/backend/python/goals/pytest_runner.py +++ b/src/python/pants/backend/python/goals/pytest_runner.py @@ -13,6 +13,7 @@ ) from pants.backend.python.subsystems.pytest import PyTest, PythonTestFieldSet from pants.backend.python.util_rules.interpreter_constraints import InterpreterConstraints +from pants.backend.python.util_rules.local_dists import LocalDistsPex, LocalDistsPexRequest from pants.backend.python.util_rules.pex import Pex, PexRequest, VenvPex, VenvPexProcess from pants.backend.python.util_rules.pex_from_targets import PexFromTargetsRequest from pants.backend.python.util_rules.python_sources import ( @@ -174,6 +175,12 @@ async def setup_pytest_for_target( PexFromTargetsRequest, PexFromTargetsRequest.for_requirements([request.field_set.address], internal_only=True), ) + local_dists_get = Get( + LocalDistsPex, + LocalDistsPexRequest( + [request.field_set.address], interpreter_constraints=interpreter_constraints + ), + ) pytest_pex_get = Get( Pex, PexRequest( @@ -198,12 +205,14 @@ async def setup_pytest_for_target( ( pytest_pex, requirements_pex, + local_dists, prepared_sources, field_set_source_files, extra_output_directory_digest, ) = await MultiGet( pytest_pex_get, requirements_pex_get, + local_dists_get, prepared_sources_get, field_set_source_files_get, extra_output_directory_digest_get, @@ -216,7 +225,7 @@ async def setup_pytest_for_target( interpreter_constraints=interpreter_constraints, main=pytest.main, internal_only=True, - pex_path=[pytest_pex, requirements_pex], + pex_path=[pytest_pex, requirements_pex, local_dists.pex], ), ) config_files_get = Get( diff --git a/src/python/pants/backend/python/goals/pytest_runner_integration_test.py b/src/python/pants/backend/python/goals/pytest_runner_integration_test.py index a40c8ec2db9..0b07b988716 100644 --- a/src/python/pants/backend/python/goals/pytest_runner_integration_test.py +++ b/src/python/pants/backend/python/goals/pytest_runner_integration_test.py @@ -11,17 +11,20 @@ from pants.backend.python import target_types_rules from pants.backend.python.dependency_inference import rules as dependency_inference_rules -from pants.backend.python.goals import package_pex_binary, pytest_runner +from pants.backend.python.goals import package_pex_binary, pytest_runner, setup_py from pants.backend.python.goals.coverage_py import create_or_update_coverage_config from pants.backend.python.goals.pytest_runner import PytestPluginSetup, PytestPluginSetupRequest +from pants.backend.python.macros.python_artifact import PythonArtifact from pants.backend.python.subsystems.pytest import PythonTestFieldSet +from pants.backend.python.subsystems.setuptools import rules as setuptools_rules from pants.backend.python.target_types import ( PexBinary, + PythonDistribution, PythonLibrary, PythonRequirementLibrary, PythonTests, ) -from pants.backend.python.util_rules import pex_from_targets +from pants.backend.python.util_rules import local_dists, pex_from_targets from pants.core.goals.test import ( TestDebugRequest, TestResult, @@ -53,10 +56,20 @@ def rule_runner() -> RuleRunner: *package_pex_binary.rules(), get_filtered_environment, *target_types_rules.rules(), + *local_dists.rules(), + *setup_py.rules(), + *setuptools_rules(), QueryRule(TestResult, (PythonTestFieldSet,)), QueryRule(TestDebugRequest, (PythonTestFieldSet,)), ], - target_types=[PexBinary, PythonLibrary, PythonTests, PythonRequirementLibrary], + target_types=[ + PexBinary, + PythonLibrary, + PythonTests, + PythonRequirementLibrary, + PythonDistribution, + ], + objects={"python_artifact": PythonArtifact}, ) @@ -490,6 +503,48 @@ def test_additional_plugins(): assert result.exit_code == 0 +def test_local_dists(rule_runner: RuleRunner) -> None: + rule_runner.write_files( + { + f"{PACKAGE}/foo/bar.py": "BAR = 'LOCAL DIST'", + f"{PACKAGE}/foo/setup.py": dedent( + """\ + from setuptools import setup + + setup(name="foo", version="9.8.7", packages=["foo"], package_dir={"foo": "."},) + """ + ), + f"{PACKAGE}/foo/bar_test.py": dedent( + """ + from foo.bar import BAR + + def test_bar(): + assert BAR == "LOCAL DIST" + """ + ), + f"{PACKAGE}/foo/BUILD": dedent( + """\ + python_library(name="lib") + + python_distribution( + name="dist", + dependencies=[":lib"], + provides=python_artifact(name="foo", version="9.8.7", setup_script="setup.py"), + setup_py_commands=["bdist_wheel",] + ) + + # Force-exclude any dep on bar.py, so the only way to consume it is via the dist. + python_tests(name="tests", sources=["bar_test.py"], + dependencies=[":dist", "!!:lib"]) + """ + ), + } + ) + tgt = rule_runner.get_target(Address(os.path.join(PACKAGE, "foo"), target_name="tests")) + result = run_pytest(rule_runner, tgt) + assert result.exit_code == 0 + + def test_skip_tests(rule_runner: RuleRunner) -> None: rule_runner.write_files( { diff --git a/src/python/pants/backend/python/goals/repl.py b/src/python/pants/backend/python/goals/repl.py index 8514e0e68f1..22bd8c3e058 100644 --- a/src/python/pants/backend/python/goals/repl.py +++ b/src/python/pants/backend/python/goals/repl.py @@ -1,7 +1,9 @@ # Copyright 2020 Pants project contributors (see CONTRIBUTORS.md). # Licensed under the Apache License, Version 2.0 (see LICENSE). +import os from pants.backend.python.subsystems.ipython import IPython +from pants.backend.python.util_rules.local_dists import LocalDistsPex, LocalDistsPexRequest from pants.backend.python.util_rules.pex import Pex, PexRequest from pants.backend.python.util_rules.pex_environment import PexEnvironment from pants.backend.python.util_rules.pex_from_targets import PexFromTargetsRequest @@ -10,6 +12,7 @@ PythonSourceFilesRequest, ) from pants.core.goals.repl import ReplImplementation, ReplRequest +from pants.engine.addresses import Addresses from pants.engine.fs import Digest, MergeDigests from pants.engine.rules import Get, MultiGet, collect_rules, rule from pants.engine.unions import UnionRule @@ -22,19 +25,38 @@ class PythonRepl(ReplImplementation): @rule(level=LogLevel.DEBUG) async def create_python_repl_request(repl: PythonRepl, pex_env: PexEnvironment) -> ReplRequest: - requirements_request = Get( - Pex, + + # Note that we get an intermediate PexRequest here (instead of going straight to a Pex) so + # that we can get the interpreter constraints for use in local_dists_request. + requirements_pex_request = await Get( + PexRequest, PexFromTargetsRequest, PexFromTargetsRequest.for_requirements( (tgt.address for tgt in repl.targets), internal_only=True ), ) + requirements_request = Get(Pex, PexRequest, requirements_pex_request) + + local_dists_request = Get( + LocalDistsPex, + LocalDistsPexRequest( + Addresses(tgt.address for tgt in repl.targets), + interpreter_constraints=requirements_pex_request.interpreter_constraints, + ), + ) + sources_request = Get( PythonSourceFiles, PythonSourceFilesRequest(repl.targets, include_files=True) ) - requirements_pex, sources = await MultiGet(requirements_request, sources_request) + + requirements_pex, local_dists, sources = await MultiGet( + requirements_request, local_dists_request, sources_request + ) merged_digest = await Get( - Digest, MergeDigests((requirements_pex.digest, sources.source_files.snapshot.digest)) + Digest, + MergeDigests( + (requirements_pex.digest, local_dists.pex.digest, sources.source_files.snapshot.digest) + ), ) complete_pex_env = pex_env.in_workspace() @@ -46,6 +68,7 @@ async def create_python_repl_request(repl: PythonRepl, pex_env: PexEnvironment) extra_env = { **complete_pex_env.environment_dict(python_configured=requirements_pex.python is not None), "PEX_EXTRA_SYS_PATH": ":".join(chrooted_source_roots), + "PEX_PATH": repl.in_chroot(local_dists.pex.name), } return ReplRequest(digest=merged_digest, args=args, extra_env=extra_env) @@ -59,8 +82,8 @@ class IPythonRepl(ReplImplementation): async def create_ipython_repl_request( repl: IPythonRepl, ipython: IPython, pex_env: PexEnvironment ) -> ReplRequest: - # Note that we get an intermediate PexRequest here (instead of going straight to a Pex) - # so that we can get the interpreter constraints for use in ipython_request. + # Note that we get an intermediate PexRequest here (instead of going straight to a Pex) so + # that we can get the interpreter constraints for use in ipython_request/local_dists_request. requirements_pex_request = await Get( PexRequest, PexFromTargetsRequest, @@ -71,6 +94,14 @@ async def create_ipython_repl_request( requirements_request = Get(Pex, PexRequest, requirements_pex_request) + local_dists_request = Get( + LocalDistsPex, + LocalDistsPexRequest( + [tgt.address for tgt in repl.targets], + interpreter_constraints=requirements_pex_request.interpreter_constraints, + ), + ) + sources_request = Get( PythonSourceFiles, PythonSourceFilesRequest(repl.targets, include_files=True) ) @@ -86,13 +117,18 @@ async def create_ipython_repl_request( ), ) - requirements_pex, sources, ipython_pex = await MultiGet( - requirements_request, sources_request, ipython_request + requirements_pex, local_dists, sources, ipython_pex = await MultiGet( + requirements_request, local_dists_request, sources_request, ipython_request ) merged_digest = await Get( Digest, MergeDigests( - (requirements_pex.digest, sources.source_files.snapshot.digest, ipython_pex.digest) + ( + requirements_pex.digest, + local_dists.pex.digest, + sources.source_files.snapshot.digest, + ipython_pex.digest, + ) ), ) @@ -106,7 +142,12 @@ async def create_ipython_repl_request( chrooted_source_roots = [repl.in_chroot(sr) for sr in sources.source_roots] extra_env = { **complete_pex_env.environment_dict(python_configured=ipython_pex.python is not None), - "PEX_PATH": repl.in_chroot(requirements_pex_request.output_filename), + "PEX_PATH": os.pathsep.join( + [ + repl.in_chroot(requirements_pex_request.output_filename), + repl.in_chroot(local_dists.pex.name), + ] + ), "PEX_EXTRA_SYS_PATH": ":".join(chrooted_source_roots), } diff --git a/src/python/pants/backend/python/goals/run_pex_binary.py b/src/python/pants/backend/python/goals/run_pex_binary.py index fb68253ad35..08b89c42b0b 100644 --- a/src/python/pants/backend/python/goals/run_pex_binary.py +++ b/src/python/pants/backend/python/goals/run_pex_binary.py @@ -9,6 +9,7 @@ ResolvedPexEntryPoint, ResolvePexEntryPointRequest, ) +from pants.backend.python.util_rules.local_dists import LocalDistsPex, LocalDistsPexRequest from pants.backend.python.util_rules.pex import Pex, PexRequest from pants.backend.python.util_rules.pex_environment import PexEnvironment from pants.backend.python.util_rules.pex_from_targets import PexFromTargetsRequest @@ -48,6 +49,14 @@ async def create_pex_binary_run_request( requirements_request = Get(Pex, PexRequest, requirements_pex_request) + local_dists_request = Get( + LocalDistsPex, + LocalDistsPexRequest( + [field_set.address], + interpreter_constraints=requirements_pex_request.interpreter_constraints, + ), + ) + sources_request = Get( PythonSourceFiles, PythonSourceFilesRequest(transitive_targets.closure, include_files=True) ) @@ -74,14 +83,19 @@ async def create_pex_binary_run_request( ), ) - requirements, sources, runner_pex = await MultiGet( - requirements_request, sources_request, runner_pex_request + requirements, local_dists, sources, runner_pex = await MultiGet( + requirements_request, local_dists_request, sources_request, runner_pex_request ) merged_digest = await Get( Digest, MergeDigests( - [requirements.digest, sources.source_files.snapshot.digest, runner_pex.digest] + [ + requirements.digest, + local_dists.pex.digest, + sources.source_files.snapshot.digest, + runner_pex.digest, + ] ), ) @@ -94,7 +108,12 @@ def in_chroot(relpath: str) -> str: chrooted_source_roots = [in_chroot(sr) for sr in sources.source_roots] extra_env = { **complete_pex_env.environment_dict(python_configured=runner_pex.python is not None), - "PEX_PATH": in_chroot(requirements_pex_request.output_filename), + "PEX_PATH": os.pathsep.join( + [ + in_chroot(requirements_pex_request.output_filename), + in_chroot(local_dists.pex.name), + ], + ), "PEX_EXTRA_SYS_PATH": ":".join(chrooted_source_roots), } diff --git a/src/python/pants/backend/python/goals/run_pex_binary_integration_test.py b/src/python/pants/backend/python/goals/run_pex_binary_integration_test.py index a5ded9718b6..7de88790027 100644 --- a/src/python/pants/backend/python/goals/run_pex_binary_integration_test.py +++ b/src/python/pants/backend/python/goals/run_pex_binary_integration_test.py @@ -158,3 +158,47 @@ def test_no_leak_pex_root_issues_12055() -> None: result = run_pants(args) result.assert_success() assert os.path.join(named_caches_dir, "pex_root") == result.stdout.strip() + + +def test_local_dist() -> None: + sources = { + "foo/bar.py": "BAR = 'LOCAL DIST'", + "foo/setup.py": dedent( + """\ + from setuptools import setup + + # Double-brace the package_dir to avoid setup_tmpdir treating it as a format. + setup(name="foo", version="9.8.7", packages=["foo"], package_dir={{"foo": "."}},) + """ + ), + "foo/main.py": "from foo.bar import BAR; print(BAR)", + "foo/BUILD": dedent( + """\ + python_library(name="lib", sources=["bar.py", "setup.py"]) + + python_library(name="main_lib", sources=["main.py"]) + + python_distribution( + name="dist", + dependencies=[":lib"], + provides=python_artifact(name="foo", version="9.8.7", setup_script="setup.py"), + setup_py_commands=["bdist_wheel",] + ) + + pex_binary( + name="bin", + entry_point="main.py", + # Force-exclude any dep on bar.py, so the only way to consume it is via the dist. + dependencies=[":main_lib", ":dist", "!!:lib"]) + """ + ), + } + with setup_tmpdir(sources) as tmpdir: + args = [ + "--backend-packages=pants.backend.python", + f"--source-root-patterns=['/{tmpdir}']", + "run", + f"{tmpdir}/foo/main.py", + ] + result = run_pants(args) + assert result.stdout == "LOCAL DIST\n" diff --git a/src/python/pants/backend/python/register.py b/src/python/pants/backend/python/register.py index 1ff17676b87..e2e3824b0e4 100644 --- a/src/python/pants/backend/python/register.py +++ b/src/python/pants/backend/python/register.py @@ -33,6 +33,7 @@ ) from pants.backend.python.util_rules import ( ancestor_files, + local_dists, pex, pex_cli, pex_environment, @@ -59,6 +60,7 @@ def rules(): *coverage_py.rules(), *tailor.rules(), *ancestor_files.rules(), + *local_dists.rules(), *python_sources.rules(), *dependency_inference_rules.rules(), *pex.rules(), diff --git a/src/python/pants/backend/python/util_rules/local_dists.py b/src/python/pants/backend/python/util_rules/local_dists.py new file mode 100644 index 00000000000..6045264b806 --- /dev/null +++ b/src/python/pants/backend/python/util_rules/local_dists.py @@ -0,0 +1,83 @@ +# Copyright 2019 Pants project contributors (see CONTRIBUTORS.md). +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Iterable + +from pants.backend.python.subsystems.setuptools import PythonDistributionFieldSet +from pants.backend.python.util_rules.interpreter_constraints import InterpreterConstraints +from pants.backend.python.util_rules.pex import Pex, PexRequest, PexRequirements +from pants.backend.python.util_rules.pex import rules as pex_rules +from pants.build_graph.address import Address +from pants.core.goals.package import BuiltPackage, PackageFieldSet +from pants.engine.addresses import Addresses +from pants.engine.fs import Digest, MergeDigests +from pants.engine.rules import Get, MultiGet, collect_rules, rule +from pants.engine.target import TransitiveTargets, TransitiveTargetsRequest +from pants.util.meta import frozen_after_init + + +@frozen_after_init +@dataclass(unsafe_hash=True) +class LocalDistsPexRequest: + """Request to build the local dists from the dependency closure of a set of addresses.""" + + addresses: Addresses + interpreter_constraints: InterpreterConstraints + + def __init__( + self, + addresses: Iterable[Address], + *, + interpreter_constraints: InterpreterConstraints = InterpreterConstraints() + ) -> None: + self.addresses = Addresses(addresses) + self.interpreter_constraints = interpreter_constraints + + +@dataclass(frozen=True) +class LocalDistsPex: + """A PEX file containing locally-built dists. + + Can be consumed from another PEX, e.g., by adding to PEX_PATH. + """ + + pex: Pex + + +@rule(desc="Building local distributions") +async def build_local_dists( + request: LocalDistsPexRequest, +) -> LocalDistsPex: + + transitive_targets = await Get(TransitiveTargets, TransitiveTargetsRequest(request.addresses)) + + python_dist_field_sets = [ + PythonDistributionFieldSet.create(target) + for target in transitive_targets.closure + if PythonDistributionFieldSet.is_applicable(target) + ] + + dists = await MultiGet( + [Get(BuiltPackage, PackageFieldSet, field_set) for field_set in python_dist_field_sets] + ) + dists_digest = await Get(Digest, MergeDigests([dist.digest for dist in dists])) + dists_pex = await Get( + Pex, + PexRequest( + output_filename="local_dists.pex", + requirements=PexRequirements( + [art.relpath for dist in dists for art in dist.artifacts if art.relpath] + ), + interpreter_constraints=request.interpreter_constraints, + additional_inputs=dists_digest, + internal_only=True, + ), + ) + return LocalDistsPex(dists_pex) + + +def rules(): + return (*collect_rules(), *pex_rules()) diff --git a/src/python/pants/backend/python/util_rules/local_dists_test.py b/src/python/pants/backend/python/util_rules/local_dists_test.py new file mode 100644 index 00000000000..70f220b9892 --- /dev/null +++ b/src/python/pants/backend/python/util_rules/local_dists_test.py @@ -0,0 +1,77 @@ +# Copyright 2021 Pants project contributors (see CONTRIBUTORS.md). +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +from __future__ import annotations + +import io +import zipfile +from pathlib import PurePath +from textwrap import dedent + +import pytest + +from pants.backend.python import target_types_rules +from pants.backend.python.goals.setup_py import rules as setup_py_rules +from pants.backend.python.macros.python_artifact import PythonArtifact +from pants.backend.python.subsystems.setuptools import rules as setuptools_rules +from pants.backend.python.target_types import PythonDistribution, PythonLibrary +from pants.backend.python.util_rules import local_dists +from pants.backend.python.util_rules.local_dists import LocalDistsPex, LocalDistsPexRequest +from pants.build_graph.address import Address +from pants.engine.fs import DigestContents +from pants.testutil.rule_runner import QueryRule, RuleRunner + + +@pytest.fixture +def rule_runner() -> RuleRunner: + return RuleRunner( + rules=[ + *local_dists.rules(), + *setup_py_rules(), + *setuptools_rules(), + *target_types_rules.rules(), + QueryRule(LocalDistsPex, (LocalDistsPexRequest,)), + ], + target_types=[PythonLibrary, PythonDistribution], + objects={"python_artifact": PythonArtifact}, + ) + + +def test_build_local_dists(rule_runner: RuleRunner) -> None: + foo = PurePath("foo") + rule_runner.write_files( + { + foo + / "BUILD": dedent( + """ + python_library() + + python_distribution( + name = "dist", + dependencies = [":foo"], + provides = python_artifact(name="foo", version="9.8.7", setup_script="foo/setup.py"), + setup_py_commands = ["bdist_wheel",] + ) + """ + ), + foo / "bar.py": "BAR = 42", + foo + / "setup.py": dedent( + """ + from setuptools import setup + + setup(name="foo", version="9.8.7", packages=["foo"], package_dir={"foo": "."},) + """ + ), + } + ) + rule_runner.set_options([], env_inherit={"PATH"}) + request = LocalDistsPexRequest([Address("foo", target_name="dist")]) + result = rule_runner.request(LocalDistsPex, [request]) + + assert result.pex is not None + contents = rule_runner.request(DigestContents, [result.pex.digest]) + assert len(contents) == 1 + with io.BytesIO(contents[0].content) as fp: + with zipfile.ZipFile(fp, "r") as pex: + assert ".deps/foo-9.8.7-py3-none-any.whl/foo/bar.py" in pex.namelist() diff --git a/src/python/pants/core/goals/run.py b/src/python/pants/core/goals/run.py index 4d9a823137d..220d27b54d8 100644 --- a/src/python/pants/core/goals/run.py +++ b/src/python/pants/core/goals/run.py @@ -105,7 +105,7 @@ async def run( field_set = targets_to_valid_field_sets.field_sets[0] request = await Get(RunRequest, RunFieldSet, field_set) - with temporary_dir(root_dir=global_options.options.pants_workdir, cleanup=True) as tmpdir: + with temporary_dir(root_dir=global_options.options.pants_workdir, cleanup=False) as tmpdir: workspace.write_digest( request.digest, path_prefix=PurePath(tmpdir).relative_to(build_root.path).as_posix() ) diff --git a/src/python/pants/engine/fs.py b/src/python/pants/engine/fs.py index f40bf1f07ec..ad1c60e1da6 100644 --- a/src/python/pants/engine/fs.py +++ b/src/python/pants/engine/fs.py @@ -17,7 +17,7 @@ """A Digest is a lightweight reference to a set of files known about by the engine. -You can use `await Get(Snapshot, Digest)` to set the file names referred to, or use `await +You can use `await Get(Snapshot, Digest)` to see the file names referred to, or use `await Get(DigestContents, Digest)` to see the actual file content. """ Digest = PyDigest @@ -217,7 +217,7 @@ class RemovePrefix: """A request to remove the specified prefix path from every file and directory in the digest. This will fail if there are any files or directories in the original input digest without the - specified digest. + specified prefix. Example: diff --git a/src/python/pants/engine/internals/BUILD b/src/python/pants/engine/internals/BUILD index c606abbe5d2..6d3de1a6ae7 100644 --- a/src/python/pants/engine/internals/BUILD +++ b/src/python/pants/engine/internals/BUILD @@ -2,7 +2,7 @@ # Licensed under the Apache License, Version 2.0 (see LICENSE). python_library( - sources=['*.py', '!*_test.py'] + sources=['*.py', '!*_test.py'], ) python_tests( diff --git a/testprojects/src/python/native/BUILD b/testprojects/src/python/native/BUILD index f4485dbf405..0bccedd5e03 100644 --- a/testprojects/src/python/native/BUILD +++ b/testprojects/src/python/native/BUILD @@ -2,18 +2,31 @@ # Licensed under the Apache License, Version 2.0 (see LICENSE). python_library( + name="lib", + sources=["name.py", "setup.py"], ) resources(name="impl", sources=["*.c"]) python_distribution( name = "dist", - dependencies = [":impl", ":native"], - provides = setup_py( + dependencies = [":impl", ":lib"], + provides = python_artifact( name = "native", version = "2.3.4", setup_script='setup.py', ), - setup_py_commands = ["bdist_wheel",] + setup_py_commands = ["bdist_wheel",], ) +python_library( + name="main_lib", + sources=["main.py"], +) + +pex_binary( + name="main", + entry_point='main.py', + # Force-exclude any source dependency on name.py, so that it has to come from the dist. + dependencies=[":dist", ":main_lib", "!!:lib"], +)