Beispiel #1
0
async def pylint_lint(request: PylintRequest, pylint: Pylint,
                      python_setup: PythonSetup) -> LintResults:
    if pylint.skip:
        return LintResults([], linter_name="Pylint")

    plugin_target_addresses = await Get(Addresses, UnparsedAddressInputs,
                                        pylint.source_plugins)
    plugin_targets_request = Get(
        TransitiveTargets, TransitiveTargetsRequest(plugin_target_addresses))
    linted_targets_request = Get(
        Targets,
        Addresses(field_set.address for field_set in request.field_sets))
    plugin_targets, linted_targets = await MultiGet(plugin_targets_request,
                                                    linted_targets_request)

    plugin_targets_compatibility_fields = tuple(
        plugin_tgt[InterpreterConstraintsField]
        for plugin_tgt in plugin_targets.closure
        if plugin_tgt.has_field(InterpreterConstraintsField))

    # Pylint needs direct dependencies in the chroot to ensure that imports are valid. However, it
    # doesn't lint those direct dependencies nor does it care about transitive dependencies.
    per_target_dependencies = await MultiGet(
        Get(Targets, DependenciesRequest(field_set.dependencies))
        for field_set in request.field_sets)

    # We batch targets by their interpreter constraints to ensure, for example, that all Python 2
    # targets run together and all Python 3 targets run together.
    # Note that Pylint uses the AST of the interpreter that runs it. So, we include any plugin
    # targets in this interpreter constraints calculation.
    interpreter_constraints_to_target_setup = defaultdict(set)
    for field_set, tgt, dependencies in zip(request.field_sets, linted_targets,
                                            per_target_dependencies):
        target_setup = PylintTargetSetup(field_set,
                                         Targets([tgt, *dependencies]))
        interpreter_constraints = PexInterpreterConstraints.create_from_compatibility_fields(
            (
                *(tgt[InterpreterConstraintsField]
                  for tgt in [tgt, *dependencies]
                  if tgt.has_field(InterpreterConstraintsField)),
                *plugin_targets_compatibility_fields,
            ),
            python_setup,
        )
        interpreter_constraints_to_target_setup[interpreter_constraints].add(
            target_setup)

    partitions = (PylintPartition(
        tuple(
            sorted(target_setups,
                   key=lambda tgt_setup: tgt_setup.field_set.address)),
        interpreter_constraints,
        Targets(plugin_targets.closure),
    ) for interpreter_constraints, target_setups in sorted(
        interpreter_constraints_to_target_setup.items()))
    partitioned_results = await MultiGet(
        Get(LintResult, PylintPartition, partition)
        for partition in partitions)
    return LintResults(partitioned_results, linter_name="Pylint")
Beispiel #2
0
async def mypy_typecheck(request: MyPyRequest, mypy: MyPy,
                         python_setup: PythonSetup) -> TypecheckResults:
    if mypy.skip:
        return TypecheckResults([], typechecker_name="MyPy")

    # We batch targets by their interpreter constraints to ensure, for example, that all Python 2
    # targets run together and all Python 3 targets run together. We can only do this by setting
    # the `--python-version` option, but we allow the user to set it as a safety valve. We warn if
    # they've set the option.
    config_content = await Get(DigestContents, PathGlobs,
                               config_path_globs(mypy))
    python_version_configured = check_and_warn_if_python_version_configured(
        config=next(iter(config_content), None), args=mypy.args)

    # When determining how to batch by interpreter constraints, we must consider the entire
    # transitive closure to get the final resulting constraints.
    # TODO(#10863): Improve the performance of this.
    transitive_targets_per_field_set = await MultiGet(
        Get(TransitiveTargets, TransitiveTargetsRequest([field_set.address]))
        for field_set in request.field_sets)

    interpreter_constraints_to_transitive_targets = defaultdict(set)
    for transitive_targets in transitive_targets_per_field_set:
        interpreter_constraints = (
            PexInterpreterConstraints.create_from_compatibility_fields(
                (tgt[PythonInterpreterCompatibility]
                 for tgt in transitive_targets.closure
                 if tgt.has_field(PythonInterpreterCompatibility)),
                python_setup,
            ) or PexInterpreterConstraints(mypy.interpreter_constraints))
        interpreter_constraints_to_transitive_targets[
            interpreter_constraints].add(transitive_targets)

    partitions = []
    for interpreter_constraints, all_transitive_targets in sorted(
            interpreter_constraints_to_transitive_targets.items()):
        combined_roots: OrderedSet[Address] = OrderedSet()
        combined_closure: OrderedSet[Target] = OrderedSet()
        for transitive_targets in all_transitive_targets:
            combined_roots.update(tgt.address
                                  for tgt in transitive_targets.roots)
            combined_closure.update(transitive_targets.closure)
        partitions.append(
            MyPyPartition(
                FrozenOrderedSet(combined_roots),
                FrozenOrderedSet(combined_closure),
                interpreter_constraints,
                python_version_already_configured=python_version_configured,
            ))

    partitioned_results = await MultiGet(
        Get(TypecheckResult, MyPyPartition, partition)
        for partition in partitions)
    return TypecheckResults(partitioned_results, typechecker_name="MyPy")
Beispiel #3
0
async def package_python_dist(
    field_set: PythonDistributionFieldSet,
    python_setup: PythonSetup,
) -> BuiltPackage:
    transitive_targets = await Get(
        TransitiveTargets, TransitiveTargetsRequest([field_set.address]))
    exported_target = ExportedTarget(transitive_targets.roots[0])
    interpreter_constraints = PexInterpreterConstraints.create_from_compatibility_fields(
        (tgt[PythonInterpreterCompatibility]
         for tgt in transitive_targets.dependencies
         if tgt.has_field(PythonInterpreterCompatibility)),
        python_setup,
    )
    chroot = await Get(
        SetupPyChroot,
        SetupPyChrootRequest(exported_target,
                             py2=interpreter_constraints.includes_python2()),
    )

    # If commands were provided, run setup.py with them; Otherwise just dump chroots.
    commands = exported_target.target.get(SetupPyCommandsField).value or ()
    if commands:
        validate_commands(commands)
        setup_py_result = await Get(
            RunSetupPyResult,
            RunSetupPyRequest(exported_target, interpreter_constraints, chroot,
                              commands),
        )
        dist_snapshot = await Get(Snapshot, Digest, setup_py_result.output)
        return BuiltPackage(
            setup_py_result.output,
            tuple(BuiltPackageArtifact(path) for path in dist_snapshot.files),
        )
    else:
        dirname = f"{chroot.setup_kwargs.name}-{chroot.setup_kwargs.version}"
        rel_chroot = await Get(Digest, AddPrefix(chroot.digest, dirname))
        return BuiltPackage(rel_chroot, (BuiltPackageArtifact(dirname), ))
Beispiel #4
0
async def pex_from_targets(request: PexFromTargetsRequest,
                           python_setup: PythonSetup) -> PexRequest:
    if request.direct_deps_only:
        targets = await Get(Targets, Addresses(request.addresses))
        direct_deps = await MultiGet(
            Get(Targets, DependenciesRequest(tgt.get(Dependencies)))
            for tgt in targets)
        all_targets = FrozenOrderedSet(itertools.chain(*direct_deps, targets))
    else:
        transitive_targets = await Get(
            TransitiveTargets, TransitiveTargetsRequest(request.addresses))
        all_targets = transitive_targets.closure

    input_digests = []
    if request.additional_sources:
        input_digests.append(request.additional_sources)
    if request.include_source_files:
        prepared_sources = await Get(StrippedPythonSourceFiles,
                                     PythonSourceFilesRequest(all_targets))
        input_digests.append(
            prepared_sources.stripped_source_files.snapshot.digest)
    merged_input_digest = await Get(Digest, MergeDigests(input_digests))

    if request.hardcoded_interpreter_constraints:
        interpreter_constraints = request.hardcoded_interpreter_constraints
    else:
        calculated_constraints = PexInterpreterConstraints.create_from_compatibility_fields(
            (tgt[PythonInterpreterCompatibility] for tgt in all_targets
             if tgt.has_field(PythonInterpreterCompatibility)),
            python_setup,
        )
        # If there are no targets, we fall back to the global constraints. This is relevant,
        # for example, when running `./pants repl` with no specs.
        interpreter_constraints = calculated_constraints or PexInterpreterConstraints(
            python_setup.interpreter_constraints)

    exact_reqs = PexRequirements.create_from_requirement_fields(
        (tgt[PythonRequirementsField]
         for tgt in all_targets if tgt.has_field(PythonRequirementsField)),
        additional_requirements=request.additional_requirements,
    )

    requirements = exact_reqs
    description = request.description

    if python_setup.requirement_constraints:
        # In requirement strings Foo_-Bar.BAZ and foo-bar-baz refer to the same project. We let
        # packaging canonicalize for us.
        # See: https://www.python.org/dev/peps/pep-0503/#normalized-names

        exact_req_projects = {
            canonicalize_project_name(Requirement.parse(req).project_name)
            for req in exact_reqs
        }
        constraints_file_contents = await Get(
            DigestContents,
            PathGlobs(
                [python_setup.requirement_constraints],
                glob_match_error_behavior=GlobMatchErrorBehavior.error,
                conjunction=GlobExpansionConjunction.all_match,
                description_of_origin=
                "the option `--python-setup-requirement-constraints`",
            ),
        )
        constraints_file_reqs = set(
            parse_requirements(
                next(iter(constraints_file_contents)).content.decode()))
        constraint_file_projects = {
            canonicalize_project_name(req.project_name)
            for req in constraints_file_reqs
        }
        unconstrained_projects = exact_req_projects - constraint_file_projects
        if unconstrained_projects:
            logger.warning(
                f"The constraints file {python_setup.requirement_constraints} does not contain "
                f"entries for the following requirements: {', '.join(unconstrained_projects)}"
            )

        if python_setup.resolve_all_constraints == ResolveAllConstraintsOption.ALWAYS or (
                python_setup.resolve_all_constraints
                == ResolveAllConstraintsOption.NONDEPLOYABLES
                and request.internal_only):
            if unconstrained_projects:
                logger.warning(
                    "Ignoring resolve_all_constraints setting in [python_setup] scope "
                    "because constraints file does not cover all requirements."
                )
            else:
                requirements = PexRequirements(
                    str(req) for req in constraints_file_reqs)
                description = description or f"Resolving {python_setup.requirement_constraints}"
    elif (python_setup.resolve_all_constraints !=
          ResolveAllConstraintsOption.NEVER
          and python_setup.resolve_all_constraints_was_set_explicitly()):
        raise ValueError(
            f"[python-setup].resolve_all_constraints is set to "
            f"{python_setup.resolve_all_constraints.value}, so "
            f"[python-setup].requirement_constraints must also be provided.")

    return PexRequest(
        output_filename=request.output_filename,
        internal_only=request.internal_only,
        requirements=requirements,
        interpreter_constraints=interpreter_constraints,
        platforms=request.platforms,
        entry_point=request.entry_point,
        sources=merged_input_digest,
        additional_inputs=request.additional_inputs,
        additional_args=request.additional_args,
        description=description,
    )
Beispiel #5
0
async def run_setup_pys(
    targets_with_origins: TargetsWithOrigins,
    setup_py_subsystem: SetupPySubsystem,
    python_setup: PythonSetup,
    distdir: DistDir,
    workspace: Workspace,
    union_membership: UnionMembership,
) -> SetupPy:
    logger.warning(
        "The `setup_py` goal is deprecated in favor of the `package` goal, which behaves "
        "similarly, except that you specify setup.py commands using the `setup_py_commands` "
        "field on your `python_distribution` targets, instead of on the command line. "
        "`setup_py` will be removed in 2.1.0.dev0.")
    """Run setup.py commands on all exported targets addressed."""
    validate_commands(setup_py_subsystem.args)

    # Get all exported targets, ignoring any non-exported targets that happened to be
    # globbed over, but erroring on any explicitly-requested non-exported targets.

    exported_targets: List[ExportedTarget] = []
    explicit_nonexported_targets: List[Target] = []

    for target_with_origin in targets_with_origins:
        tgt = target_with_origin.target
        if tgt.has_field(PythonProvidesField):
            exported_targets.append(ExportedTarget(tgt))
        elif isinstance(target_with_origin.origin,
                        (AddressLiteralSpec, FilesystemLiteralSpec)):
            explicit_nonexported_targets.append(tgt)
    if explicit_nonexported_targets:
        raise TargetNotExported(
            "Cannot run setup.py on these targets, because they have no `provides=` clause: "
            f'{", ".join(so.address.spec for so in explicit_nonexported_targets)}'
        )

    transitive_targets_per_exported_target = await MultiGet(
        Get(TransitiveTargets, TransitiveTargetsRequest([et.target.address]))
        for et in exported_targets)

    if setup_py_subsystem.transitive:
        closure = FrozenOrderedSet(
            itertools.chain.from_iterable(
                tt.closure for tt in transitive_targets_per_exported_target))
        owners = await MultiGet(
            Get(ExportedTarget, OwnedDependency(tgt)) for tgt in closure
            if is_ownable_target(tgt, union_membership))
        exported_targets = list(FrozenOrderedSet(owners))
        # We must recalculate the transitive targets because it's possible the exported_targets
        # have changed. Any prior results will be memoized.
        transitive_targets_per_exported_target = await MultiGet(
            Get(TransitiveTargets, TransitiveTargetsRequest(
                [et.target.address])) for et in exported_targets)

    interpreter_constraints_per_exported_target = tuple(
        PexInterpreterConstraints.create_from_compatibility_fields(
            (tgt[PythonInterpreterCompatibility]
             for tgt in transitive_targets.dependencies
             if tgt.has_field(PythonInterpreterCompatibility)),
            python_setup,
        ) for transitive_targets in transitive_targets_per_exported_target)

    chroots = await MultiGet(
        Get(
            SetupPyChroot,
            SetupPyChrootRequest(
                exported_target,
                py2=interpreter_constraints.includes_python2()),
        ) for exported_target, interpreter_constraints in zip(
            exported_targets, interpreter_constraints_per_exported_target))

    # If args were provided, run setup.py with them; Otherwise just dump chroots.
    if setup_py_subsystem.args:
        setup_py_results = await MultiGet(
            Get(
                RunSetupPyResult,
                RunSetupPyRequest(exported_target, interpreter_constraints,
                                  chroot, setup_py_subsystem.args),
            ) for exported_target, interpreter_constraints, chroot in zip(
                exported_targets, interpreter_constraints_per_exported_target,
                chroots))

        for exported_target, setup_py_result in zip(exported_targets,
                                                    setup_py_results):
            addr = exported_target.target.address.spec
            logger.info(f"Writing dist for {addr} under {distdir.relpath}/.")
            workspace.write_digest(setup_py_result.output,
                                   path_prefix=str(distdir.relpath))
    else:
        # Just dump the chroot.
        for exported_target, chroot in zip(exported_targets, chroots):
            addr = exported_target.target.address.spec
            setup_py_dir = (
                distdir.relpath /
                f"{chroot.setup_kwargs.name}-{chroot.setup_kwargs.version}")
            logger.info(
                f"Writing setup.py chroot for {addr} to {setup_py_dir}")
            workspace.write_digest(chroot.digest,
                                   path_prefix=str(setup_py_dir))

    return SetupPy(0)
Beispiel #6
0
async def setup_black(
    setup_request: SetupRequest, black: Black, python_setup: PythonSetup
) -> Setup:
    # Black requires 3.6+ but uses the typed-ast library to work with 2.7, 3.4, 3.5, 3.6, and 3.7.
    # However, typed-ast does not understand 3.8+, so instead we must run Black with Python 3.8+
    # when relevant. We only do this if if <3.8 can't be used, as we don't want a loose requirement
    # like `>=3.6` to result in requiring Python 3.8, which would error if 3.8 is not installed on
    # the machine.
    all_interpreter_constraints = PexInterpreterConstraints.create_from_compatibility_fields(
        (field_set.interpreter_constraints for field_set in setup_request.request.field_sets),
        python_setup,
    )
    tool_interpreter_constraints = (
        all_interpreter_constraints
        if (
            all_interpreter_constraints.requires_python38_or_newer()
            and black.options.is_default("interpreter_constraints")
        )
        else PexInterpreterConstraints(black.interpreter_constraints)
    )

    black_pex_get = Get(
        VenvPex,
        PexRequest(
            output_filename="black.pex",
            internal_only=True,
            requirements=PexRequirements(black.all_requirements),
            interpreter_constraints=tool_interpreter_constraints,
            main=black.main,
        ),
    )

    config_files_get = Get(ConfigFiles, ConfigFilesRequest, black.config_request)
    source_files_get = Get(
        SourceFiles,
        SourceFilesRequest(field_set.sources for field_set in setup_request.request.field_sets),
    )

    source_files, black_pex, config_files = await MultiGet(
        source_files_get, black_pex_get, config_files_get
    )
    source_files_snapshot = (
        source_files.snapshot
        if setup_request.request.prior_formatter_result is None
        else setup_request.request.prior_formatter_result
    )

    input_digest = await Get(
        Digest, MergeDigests((source_files_snapshot.digest, config_files.snapshot.digest))
    )

    process = await Get(
        Process,
        VenvPexProcess(
            black_pex,
            argv=generate_args(
                source_files=source_files, black=black, check_only=setup_request.check_only
            ),
            input_digest=input_digest,
            output_files=source_files_snapshot.files,
            description=f"Run Black on {pluralize(len(setup_request.request.field_sets), 'file')}.",
            level=LogLevel.DEBUG,
        ),
    )
    return Setup(process, original_digest=source_files_snapshot.digest)
Beispiel #7
0
async def setup_black(setup_request: SetupRequest, black: Black,
                      python_setup: PythonSetup) -> Setup:
    # Black requires 3.6+ but uses the typed-ast library to work with 2.7, 3.4, 3.5, 3.6, and 3.7.
    # However, typed-ast does not understand 3.8, so instead we must run Black with Python 3.8 when
    # relevant. We only do this if if <3.8 can't be used, as we don't want a loose requirement like
    # `>=3.6` to result in requiring Python 3.8, which would error if 3.8 is not installed on the
    # machine.
    all_interpreter_constraints = PexInterpreterConstraints.create_from_compatibility_fields(
        (field_set.interpreter_constraints
         for field_set in setup_request.request.field_sets),
        python_setup,
    )
    tool_interpreter_constraints = PexInterpreterConstraints((
        "CPython>=3.8", ) if (
            all_interpreter_constraints.requires_python38_or_newer()
            and black.options.is_default("interpreter_constraints")
        ) else black.interpreter_constraints)

    black_pex_request = Get(
        Pex,
        PexRequest(
            output_filename="black.pex",
            internal_only=True,
            requirements=PexRequirements(black.all_requirements),
            interpreter_constraints=tool_interpreter_constraints,
            entry_point=black.entry_point,
        ),
    )

    config_digest_request = Get(
        Digest,
        PathGlobs(
            globs=[black.config] if black.config else [],
            glob_match_error_behavior=GlobMatchErrorBehavior.error,
            description_of_origin="the option `--black-config`",
        ),
    )

    source_files_request = Get(
        SourceFiles,
        SourceFilesRequest(field_set.sources
                           for field_set in setup_request.request.field_sets),
    )

    source_files, black_pex, config_digest = await MultiGet(
        source_files_request, black_pex_request, config_digest_request)
    source_files_snapshot = (
        source_files.snapshot
        if setup_request.request.prior_formatter_result is None else
        setup_request.request.prior_formatter_result)

    input_digest = await Get(
        Digest,
        MergeDigests(
            (source_files_snapshot.digest, black_pex.digest, config_digest)),
    )

    process = await Get(
        Process,
        PexProcess(
            black_pex,
            argv=generate_args(source_files=source_files,
                               black=black,
                               check_only=setup_request.check_only),
            input_digest=input_digest,
            output_files=source_files_snapshot.files,
            description=
            f"Run Black on {pluralize(len(setup_request.request.field_sets), 'file')}.",
            level=LogLevel.DEBUG,
        ),
    )
    return Setup(process, original_digest=source_files_snapshot.digest)
Beispiel #8
0
async def setup_pytest_for_target(
    request: TestSetupRequest,
    pytest: PyTest,
    test_subsystem: TestSubsystem,
    python_setup: PythonSetup,
    coverage_config: CoverageConfig,
    coverage_subsystem: CoverageSubsystem,
    test_extra_env: TestExtraEnv,
    global_options: GlobalOptions,
) -> TestSetup:
    transitive_targets = await Get(
        TransitiveTargets,
        TransitiveTargetsRequest([request.field_set.address]))
    all_targets = transitive_targets.closure

    interpreter_constraints = PexInterpreterConstraints.create_from_compatibility_fields(
        (tgt[PythonInterpreterCompatibility] for tgt in all_targets
         if tgt.has_field(PythonInterpreterCompatibility)),
        python_setup,
    )

    # Defaults to zip_safe=False.
    requirements_pex_request = Get(
        Pex,
        PexFromTargetsRequest,
        PexFromTargetsRequest.for_requirements([request.field_set.address],
                                               internal_only=True),
    )

    pytest_pex_request = Get(
        Pex,
        PexRequest(
            output_filename="pytest.pex",
            requirements=PexRequirements(pytest.get_requirement_strings()),
            interpreter_constraints=interpreter_constraints,
            entry_point="pytest:main",
            internal_only=True,
            additional_args=(
                # NB: We set `--not-zip-safe` because Pytest plugin discovery, which uses
                # `importlib_metadata` and thus `zipp`, does not play nicely when doing import
                # magic directly from zip files. `zipp` has pathologically bad behavior with large
                # zipfiles.
                # TODO: this does have a performance cost as the pex must now be expanded to disk.
                # Long term, it would be better to fix Zipp (whose fix would then need to be used
                # by importlib_metadata and then by Pytest). See
                # https://github.com/jaraco/zipp/pull/26.
                "--not-zip-safe",
                # TODO(John Sirois): Support shading python binaries:
                #   https://github.com/pantsbuild/pants/issues/9206
                "--pex-path",
                requirements_pex_request.input.output_filename,
            ),
        ),
    )

    prepared_sources_request = Get(
        PythonSourceFiles,
        PythonSourceFilesRequest(all_targets, include_files=True))

    # Create any assets that the test depends on through the `runtime_package_dependencies` field.
    assets: Tuple[BuiltPackage, ...] = ()
    unparsed_runtime_packages = (request.field_set.runtime_package_dependencies
                                 .to_unparsed_address_inputs())
    unparsed_runtime_binaries = (request.field_set.runtime_binary_dependencies.
                                 to_unparsed_address_inputs())
    if unparsed_runtime_packages.values or unparsed_runtime_binaries.values:
        runtime_package_targets, runtime_binary_dependencies = await MultiGet(
            Get(Targets, UnparsedAddressInputs, unparsed_runtime_packages),
            Get(Targets, UnparsedAddressInputs, unparsed_runtime_binaries),
        )
        field_sets_per_target = await Get(
            FieldSetsPerTarget,
            FieldSetsPerTargetRequest(
                PackageFieldSet,
                itertools.chain(runtime_package_targets,
                                runtime_binary_dependencies),
            ),
        )
        assets = await MultiGet(
            Get(BuiltPackage, PackageFieldSet, field_set)
            for field_set in field_sets_per_target.field_sets)

    # Get the file names for the test_target so that we can specify to Pytest precisely which files
    # to test, rather than using auto-discovery.
    field_set_source_files_request = Get(
        SourceFiles, SourceFilesRequest([request.field_set.sources]))

    pytest_pex, requirements_pex, prepared_sources, field_set_source_files = await MultiGet(
        pytest_pex_request,
        requirements_pex_request,
        prepared_sources_request,
        field_set_source_files_request,
    )

    input_digest = await Get(
        Digest,
        MergeDigests((
            coverage_config.digest,
            prepared_sources.source_files.snapshot.digest,
            requirements_pex.digest,
            pytest_pex.digest,
            *(binary.digest for binary in assets),
        )),
    )

    add_opts = [f"--color={'yes' if global_options.options.colors else 'no'}"]
    output_files = []

    results_file_name = None
    if pytest.options.junit_xml_dir and not request.is_debug:
        results_file_name = f"{request.field_set.address.path_safe_spec}.xml"
        add_opts.extend((f"--junitxml={results_file_name}", "-o",
                         f"junit_family={pytest.options.junit_family}"))
        output_files.append(results_file_name)

    coverage_args = []
    if test_subsystem.use_coverage and not request.is_debug:
        output_files.append(".coverage")
        cov_paths = coverage_subsystem.filter if coverage_subsystem.filter else (
            ".", )
        coverage_args = [
            "--cov-report=",  # Turn off output.
            *itertools.chain.from_iterable(["--cov", cov_path]
                                           for cov_path in cov_paths),
        ]

    extra_env = {
        "PYTEST_ADDOPTS": " ".join(add_opts),
        "PEX_EXTRA_SYS_PATH": ":".join(prepared_sources.source_roots),
    }

    extra_env.update(test_extra_env.env)

    process = await Get(
        Process,
        PexProcess(
            pytest_pex,
            argv=(*pytest.options.args, *coverage_args,
                  *field_set_source_files.files),
            extra_env=extra_env,
            input_digest=input_digest,
            output_files=output_files,
            timeout_seconds=request.field_set.timeout.
            calculate_from_global_options(pytest),
            execution_slot_variable=pytest.options.execution_slot_var,
            description=f"Run Pytest for {request.field_set.address}",
            level=LogLevel.DEBUG,
            uncacheable=test_subsystem.force and not request.is_debug,
        ),
    )
    return TestSetup(process, results_file_name=results_file_name)
Beispiel #9
0
async def run_setup_pys(
    targets_with_origins: TargetsWithOrigins,
    setup_py_subsystem: SetupPySubsystem,
    python_setup: PythonSetup,
    distdir: DistDir,
    workspace: Workspace,
    union_membership: UnionMembership,
) -> SetupPy:
    """Run setup.py commands on all exported targets addressed."""
    validate_args(setup_py_subsystem.args)

    # Get all exported targets, ignoring any non-exported targets that happened to be
    # globbed over, but erroring on any explicitly-requested non-exported targets.

    exported_targets: List[ExportedTarget] = []
    explicit_nonexported_targets: List[Target] = []

    for target_with_origin in targets_with_origins:
        tgt = target_with_origin.target
        if tgt.has_field(PythonProvidesField):
            exported_targets.append(ExportedTarget(tgt))
        elif isinstance(target_with_origin.origin, (AddressLiteralSpec, FilesystemLiteralSpec)):
            explicit_nonexported_targets.append(tgt)
    if explicit_nonexported_targets:
        raise TargetNotExported(
            "Cannot run setup.py on these targets, because they have no `provides=` clause: "
            f'{", ".join(so.address.spec for so in explicit_nonexported_targets)}'
        )

    if setup_py_subsystem.transitive:
        # Expand out to all owners of the entire dep closure.
        transitive_targets = await Get(
            TransitiveTargets, Addresses(et.target.address for et in exported_targets)
        )
        owners = await MultiGet(
            Get(ExportedTarget, OwnedDependency(tgt))
            for tgt in transitive_targets.closure
            if is_ownable_target(tgt, union_membership)
        )
        exported_targets = list(FrozenOrderedSet(owners))

    interpreter_constraints = PexInterpreterConstraints.create_from_compatibility_fields(
        (
            target_with_origin.target[PythonInterpreterCompatibility]
            for target_with_origin in targets_with_origins
            if target_with_origin.target.has_field(PythonInterpreterCompatibility)
        ),
        python_setup,
    )
    chroots = await MultiGet(
        Get(
            SetupPyChroot,
            SetupPyChrootRequest(exported_target, py2=interpreter_constraints.includes_python2()),
        )
        for exported_target in exported_targets
    )

    # If args were provided, run setup.py with them; Otherwise just dump chroots.
    if setup_py_subsystem.args:
        setup_py_results = await MultiGet(
            Get(
                RunSetupPyResult,
                RunSetupPyRequest(exported_target, chroot, setup_py_subsystem.args),
            )
            for exported_target, chroot in zip(exported_targets, chroots)
        )

        for exported_target, setup_py_result in zip(exported_targets, setup_py_results):
            addr = exported_target.target.address.spec
            logger.info(f"Writing dist for {addr} under {distdir.relpath}/.")
            workspace.write_digest(setup_py_result.output, path_prefix=str(distdir.relpath))
    else:
        # Just dump the chroot.
        for exported_target, chroot in zip(exported_targets, chroots):
            addr = exported_target.target.address.spec
            setup_py_dir = (
                distdir.relpath / f"{chroot.setup_kwargs.name}-{chroot.setup_kwargs.version}"
            )
            logger.info(f"Writing setup.py chroot for {addr} to {setup_py_dir}")
            workspace.write_digest(chroot.digest, path_prefix=str(setup_py_dir))

    return SetupPy(0)