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

Fix setup_py().with_binaries() to use the default entry point #11021

Merged
merged 3 commits into from
Oct 22, 2020
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
39 changes: 11 additions & 28 deletions src/python/pants/backend/python/goals/package_pex_binary.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Copyright 2019 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

import os
from dataclasses import dataclass
from typing import Tuple, cast

Expand All @@ -15,7 +14,12 @@
PexInheritPathField,
)
from pants.backend.python.target_types import PexPlatformsField as PythonPlatformsField
from pants.backend.python.target_types import PexShebangField, PexZipSafeField
from pants.backend.python.target_types import (
PexShebangField,
PexZipSafeField,
ResolvedPexEntryPoint,
ResolvePexEntryPointRequest,
)
from pants.backend.python.util_rules.pex import PexPlatforms, TwoStepPex
from pants.backend.python.util_rules.pex_from_targets import (
PexFromTargetsRequest,
Expand All @@ -29,12 +33,9 @@
PackageFieldSet,
)
from pants.core.goals.run import RunFieldSet
from pants.engine.fs import PathGlobs, Paths
from pants.engine.rules import Get, collect_rules, rule
from pants.engine.target import InvalidFieldException
from pants.engine.unions import UnionRule
from pants.option.global_options import FilesNotFoundBehavior, GlobalOptions
from pants.source.source_root import SourceRoot, SourceRootRequest
from pants.option.global_options import GlobalOptions
from pants.util.logging import LogLevel


Expand Down Expand Up @@ -77,27 +78,9 @@ async def package_pex_binary(
pex_binary_defaults: PexBinaryDefaults,
global_options: GlobalOptions,
) -> BuiltPackage:
entry_point = field_set.entry_point.value
if entry_point is None:
binary_source_paths = await Get(
Paths, PathGlobs, field_set.sources.path_globs(FilesNotFoundBehavior.error)
)
if len(binary_source_paths.files) != 1:
raise InvalidFieldException(
"No `entry_point` was set for the target "
f"{repr(field_set.address)}, so it must have exactly one source, but it has "
f"{len(binary_source_paths.files)}"
)
entry_point_path = binary_source_paths.files[0]
source_root = await Get(
SourceRoot,
SourceRootRequest,
SourceRootRequest.for_file(entry_point_path),
)
entry_point = PexBinarySources.translate_source_file_to_entry_point(
os.path.relpath(entry_point_path, source_root.path)
)

resolved_entry_point = await Get(
ResolvedPexEntryPoint, ResolvePexEntryPointRequest(field_set.entry_point, field_set.sources)
)
output_filename = field_set.output_path.value_or_default(
field_set.address,
file_ending="pex",
Expand All @@ -109,7 +92,7 @@ async def package_pex_binary(
PexFromTargetsRequest(
addresses=[field_set.address],
internal_only=False,
entry_point=entry_point,
entry_point=resolved_entry_point.val,
platforms=PexPlatforms.create_from_platforms_field(field_set.platforms),
output_filename=output_filename,
additional_args=field_set.generate_additional_args(pex_binary_defaults),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
PythonLibrary,
PythonRequirementLibrary,
PythonTests,
resolve_pex_entry_point,
)
from pants.backend.python.util_rules import pex_from_targets
from pants.core.goals import binary
Expand All @@ -42,6 +43,7 @@ def rule_runner() -> RuleRunner:
*binary.rules(),
*package_pex_binary.rules(),
get_filtered_environment,
resolve_pex_entry_point,
QueryRule(TestResult, (PythonTestFieldSet,)),
QueryRule(TestDebugRequest, (PythonTestFieldSet,)),
],
Expand Down
45 changes: 17 additions & 28 deletions src/python/pants/backend/python/goals/run_pex_binary.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
import os

from pants.backend.python.goals.package_pex_binary import PexBinaryFieldSet
from pants.backend.python.target_types import PexBinaryDefaults, PexBinarySources
from pants.backend.python.target_types import (
PexBinaryDefaults,
ResolvedPexEntryPoint,
ResolvePexEntryPointRequest,
)
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
Expand All @@ -13,12 +17,10 @@
PythonSourceFilesRequest,
)
from pants.core.goals.run import RunFieldSet, RunRequest
from pants.engine.fs import Digest, MergeDigests, PathGlobs, Paths
from pants.engine.fs import Digest, MergeDigests
from pants.engine.rules import Get, MultiGet, collect_rules, rule
from pants.engine.target import InvalidFieldException, TransitiveTargets, TransitiveTargetsRequest
from pants.engine.target import TransitiveTargets, TransitiveTargetsRequest
from pants.engine.unions import UnionRule
from pants.option.global_options import FilesNotFoundBehavior
from pants.source.source_root import SourceRoot, SourceRootRequest
from pants.util.logging import LogLevel


Expand All @@ -28,26 +30,13 @@ async def create_pex_binary_run_request(
pex_binary_defaults: PexBinaryDefaults,
pex_env: PexEnvironment,
) -> RunRequest:
entry_point = field_set.entry_point.value
if entry_point is None:
binary_source_paths = await Get(
Paths, PathGlobs, field_set.sources.path_globs(FilesNotFoundBehavior.error)
)
if len(binary_source_paths.files) != 1:
raise InvalidFieldException(
"No `entry_point` was set for the target "
f"{repr(field_set.address)}, so it must have exactly one source, but it has "
f"{len(binary_source_paths.files)}"
)
entry_point_path = binary_source_paths.files[0]
source_root = await Get(
SourceRoot,
SourceRootRequest,
SourceRootRequest.for_file(entry_point_path),
)
entry_point = PexBinarySources.translate_source_file_to_entry_point(
os.path.relpath(entry_point_path, source_root.path)
)
entry_point, transitive_targets = await MultiGet(
Get(
ResolvedPexEntryPoint,
ResolvePexEntryPointRequest(field_set.entry_point, field_set.sources),
),
Get(TransitiveTargets, TransitiveTargetsRequest([field_set.address])),
)
transitive_targets = await Get(TransitiveTargets, TransitiveTargetsRequest([field_set.address]))

# Note that we get an intermediate PexRequest here (instead of going straight to a Pex)
Expand All @@ -72,9 +61,9 @@ async def create_pex_binary_run_request(
interpreter_constraints=requirements_pex_request.interpreter_constraints,
additional_args=field_set.generate_additional_args(pex_binary_defaults),
internal_only=True,
# Note that the entry point file is not in the Pex itself, but on the
# PEX_PATH. This works fine!
entry_point=entry_point,
# Note that the entry point file is not in the PEX itself. It's loaded by setting
# `PEX_EXTRA_SYS_PATH`.
Copy link
Sponsor Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm yes, PEX_EXTRA_SYS_PATH is right.

entry_point=entry_point.val,
),
)

Expand Down
25 changes: 20 additions & 5 deletions src/python/pants/backend/python/goals/setup_py.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,14 @@
from pants.backend.python.macros.python_artifact import PythonArtifact
from pants.backend.python.subsystems.setuptools import Setuptools
from pants.backend.python.target_types import (
PexBinarySources,
PexEntryPointField,
PythonInterpreterCompatibility,
PythonProvidesField,
PythonRequirementsField,
PythonSources,
ResolvedPexEntryPoint,
ResolvePexEntryPointRequest,
SetupPyCommandsField,
)
from pants.backend.python.util_rules.pex import (
Expand Down Expand Up @@ -682,19 +685,31 @@ async def generate_chroot(request: SetupPyChrootRequest) -> SetupPyChroot:
"install_requires": (*requirements, *setup_kwargs.get("install_requires", [])),
}
)

# Add any `pex_binary` targets from `setup_py().with_binaries()` to the dist's entry points.
key_to_binary_spec = exported_target.provides.binaries
binaries = await Get(
Targets, UnparsedAddressInputs(key_to_binary_spec.values(), owning_address=target.address)
)
for key, binary in zip(key_to_binary_spec.keys(), binaries):
binary_entry_point = binary.get(PexEntryPointField).value
if not binary_entry_point:
entry_point_requests = []
for binary in binaries:
if not binary.has_fields([PexEntryPointField, PexBinarySources]):
raise InvalidEntryPoint(
f"The binary {key} exported by {target.address} is not a valid entry point."
"Expected addresses to `pex_binary` targets in `.with_binaries()` for the "
f"`provides` field for {exported_target.target.address}, "
f"but found {binary.address} with target type {binary.alias}."
)
entry_point_requests.append(
ResolvePexEntryPointRequest(binary[PexEntryPointField], binary[PexBinarySources])
)
binary_entry_points = await MultiGet(
Get(ResolvedPexEntryPoint, ResolvePexEntryPointRequest, request)
for request in entry_point_requests
)
for key, binary_entry_point in zip(key_to_binary_spec.keys(), binary_entry_points):
entry_points = setup_kwargs["entry_points"] = setup_kwargs.get("entry_points", {})
console_scripts = entry_points["console_scripts"] = entry_points.get("console_scripts", [])
console_scripts.append(f"{key}={binary_entry_point}")
console_scripts.append(f"{key}={binary_entry_point.val}")

# Generate the setup script.
setup_py_content = SETUP_BOILERPLATE.format(
Expand Down
6 changes: 4 additions & 2 deletions src/python/pants/backend/python/goals/setup_py_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,15 @@
PythonDistribution,
PythonLibrary,
PythonRequirementLibrary,
resolve_pex_entry_point,
)
from pants.backend.python.util_rules import python_sources
from pants.core.target_types import Files, Resources
from pants.engine.addresses import Address
from pants.engine.fs import Snapshot
from pants.engine.internals.scheduler import ExecutionError
from pants.engine.rules import SubsystemRule, rule
from pants.engine.target import Targets
from pants.engine.target import InvalidFieldException, Targets
from pants.engine.unions import UnionRule
from pants.testutil.rule_runner import QueryRule, RuleRunner

Expand Down Expand Up @@ -95,6 +96,7 @@ def chroot_rule_runner() -> RuleRunner:
get_exporting_owner,
*python_sources.rules(),
setup_kwargs_plugin,
resolve_pex_entry_point,
SubsystemRule(SetupPyGeneration),
UnionRule(SetupKwargsRequest, PluginSetupKwargsRequest),
QueryRule(SetupPyChroot, (SetupPyChrootRequest,)),
Expand Down Expand Up @@ -254,7 +256,7 @@ def test_invalid_binary(chroot_rule_runner: RuleRunner) -> None:
assert_chroot_error(
chroot_rule_runner,
Address("src/python/invalid_binary", target_name="invalid_bin2"),
InvalidEntryPoint,
InvalidFieldException,
)


Expand Down
49 changes: 43 additions & 6 deletions src/python/pants/backend/python/target_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import collections.abc
import logging
import os.path
from dataclasses import dataclass
from textwrap import dedent
from typing import Iterable, Optional, Tuple, Union, cast

Expand All @@ -14,6 +15,7 @@
from pants.base.deprecated import resolve_conflicting_options, warn_or_error
from pants.core.goals.package import OutputPathField
from pants.engine.addresses import Address, Addresses, UnparsedAddressInputs
from pants.engine.fs import PathGlobs, Paths
from pants.engine.rules import Get, collect_rules, rule
from pants.engine.target import (
COMMON_TARGET_FIELDS,
Expand All @@ -36,9 +38,11 @@
Target,
WrappedTarget,
)
from pants.option.global_options import FilesNotFoundBehavior
from pants.option.subsystem import Subsystem
from pants.python.python_requirement import PythonRequirement
from pants.python.python_setup import PythonSetup
from pants.source.source_root import SourceRoot, SourceRootRequest

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -141,18 +145,13 @@ class PexBinarySources(PythonSources):

expected_num_files = range(0, 2)

@staticmethod
def translate_source_file_to_entry_point(stripped_source_path: str) -> str:
module_base, _ = os.path.splitext(stripped_source_path)
return module_base.replace(os.path.sep, ".")


class PexBinaryDependencies(Dependencies):
supports_transitive_excludes = True


class PexEntryPointField(StringField):
"""The default entry point for the binary.
"""The entry point for the binary.

If omitted, Pants will use the module name from the `sources` field, e.g. `project/app.py` will
become the entry point `project.app` .
Expand All @@ -161,6 +160,44 @@ class PexEntryPointField(StringField):
alias = "entry_point"


@dataclass(frozen=True)
class ResolvedPexEntryPoint:
val: str


@dataclass(frozen=True)
class ResolvePexEntryPointRequest:
"""Determine the entry_point by using the `PexEntryPointField` and falling back to the sources
field."""

entry_point_field: PexEntryPointField
sources: PexBinarySources


@rule
async def resolve_pex_entry_point(request: ResolvePexEntryPointRequest) -> ResolvedPexEntryPoint:
if request.entry_point_field.value:
return ResolvedPexEntryPoint(request.entry_point_field.value)
binary_source_paths = await Get(
Paths, PathGlobs, request.sources.path_globs(FilesNotFoundBehavior.error)
)
if len(binary_source_paths.files) != 1:
raise InvalidFieldException(
"No `entry_point` was set for the target "
f"{repr(request.sources.address)}, so it must have exactly one source, but it has "
f"{len(binary_source_paths.files)}."
)
entry_point_path = binary_source_paths.files[0]
source_root = await Get(
SourceRoot,
SourceRootRequest,
SourceRootRequest.for_file(entry_point_path),
)
stripped_source_path = os.path.relpath(entry_point_path, source_root.path)
module_base, _ = os.path.splitext(stripped_source_path)
return ResolvedPexEntryPoint(module_base.replace(os.path.sep, "."))


class PexPlatformsField(StringOrStringSequenceField):
"""The platforms the built PEX should be compatible with.

Expand Down
Loading