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 686f0a507ef..a9a6cd61e7a 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 @@ -3,13 +3,19 @@ from __future__ import annotations import json +from pathlib import Path from textwrap import dedent from typing import Callable, Optional import pytest from pants.backend.python.target_types import PexExecutionMode, PexLayout -from pants.testutil.pants_integration_test import PantsResult, run_pants, setup_tmpdir +from pants.base.build_environment import get_buildroot +from pants.testutil.pants_integration_test import ( + PantsResult, + run_pants, + setup_tmpdir, +) def run_generic_test( @@ -309,3 +315,58 @@ def test_pass_extra_pex_cli_subsystem_global_args() -> None: stderr = result.stderr.strip() assert "unrecognized arguments" in stderr assert "non-existing-flag-name" in stderr + + +def test_relocated_resources() -> None: + sources = { + "assets/query.sql": "SELECT 1", + "assets/BUILD": dedent( + """\ + files(name='files', sources=['query.sql']) + """ + ), + "src/py/project/__init__.py": "", + "src/py/project/app.py": dedent( + """\ + from importlib.resources import files + import project + + def main(): + assert files(project).joinpath("query.sql").read_text() == "SELECT 1" + """ + ), + "src/py/project/BUILD": dedent( + """\ + python_sources() + relocated_files( + name='relocated', + files_targets=['assets:files'], + src='assets', + dest='src/py/project', + ) + experimental_wrap_as_resources(name='resources', inputs=[':relocated']) + pex_binary( + name="binary", + dependencies=[':resources'], + entry_point="project.app:main", + ) + """ + ), + "pants.toml": "", + } + + for relpath, content in sources.items(): + path = Path(get_buildroot()).joinpath(relpath) + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(content) + + args = [ + "--backend-packages=pants.backend.python", + "--source-root-patterns=['src/py']", + "--python-interpreter-constraints=['CPython>=3.9']", + "run", + "src/py/project:binary", + ] + result = run_pants(args, use_pantsd=False) + + assert "No such file or directory" not in result.stderr, result.stderr diff --git a/src/python/pants/backend/python/util_rules/python_sources.py b/src/python/pants/backend/python/util_rules/python_sources.py index 65903a8b973..adb4be70629 100644 --- a/src/python/pants/backend/python/util_rules/python_sources.py +++ b/src/python/pants/backend/python/util_rules/python_sources.py @@ -5,6 +5,7 @@ from dataclasses import dataclass from typing import Iterable +import sys from pants.backend.python.target_types import PythonSourceField from pants.backend.python.util_rules import ancestor_files @@ -134,6 +135,7 @@ async def prepare_python_sources( ) for tgt in codegen_targets ) + sys.stderr.write(f"{codegen_sources=}\n") source_root_requests = [ *(SourceRootRequest.for_target(tgt) for tgt in python_and_resources_targets), *( @@ -142,11 +144,14 @@ async def prepare_python_sources( for f in sources.snapshot.files ), ] + sys.stderr.write(f"{source_root_requests=}\n") source_root_objs = await MultiGet( Get(SourceRoot, SourceRootRequest, req) for req in source_root_requests ) source_root_paths = {source_root_obj.path for source_root_obj in source_root_objs} + sys.stderr.write(f"{sources.unrooted_files=}\n") + sys.stderr.write(f"{init_injected=}\n") return PythonSourceFiles( SourceFiles(init_injected, sources.unrooted_files), tuple(sorted(source_root_paths)) ) diff --git a/src/python/pants/core/register.py b/src/python/pants/core/register.py index 7419b10e800..e85e41e0dc3 100644 --- a/src/python/pants/core/register.py +++ b/src/python/pants/core/register.py @@ -5,6 +5,7 @@ These are always activated and cannot be disabled. """ + from pants.backend.codegen import export_codegen_goal from pants.build_graph.build_file_aliases import BuildFileAliases from pants.core.goals import ( @@ -63,7 +64,11 @@ from pants.vcs import git from pants.version import PANTS_SEMVER -wrap_as_resources = wrap_source_rule_and_target(ResourceSourceField, "resources") +wrap_as_resources = wrap_source_rule_and_target( + ResourceSourceField, + "resources", + uses_source_roots=True, +) def rules(): diff --git a/src/python/pants/core/util_rules/source_files.py b/src/python/pants/core/util_rules/source_files.py index 055891ee8a0..f5f5893ed94 100644 --- a/src/python/pants/core/util_rules/source_files.py +++ b/src/python/pants/core/util_rules/source_files.py @@ -1,6 +1,7 @@ # Copyright 2020 Pants project contributors (see CONTRIBUTORS.md). # Licensed under the Apache License, Version 2.0 (see LICENSE). +import sys from dataclasses import dataclass from pathlib import PurePath from typing import Collection, Iterable, Set, Tuple, Type, Union @@ -60,6 +61,8 @@ async def determine_source_files(request: SourceFilesRequest) -> SourceFiles: ) for hydrated_sources, sources_field in zip(all_hydrated_sources, request.sources_fields): + sys.stderr.write(f"{type(sources_field)=}\n") + sys.stderr.write(f"{sources_field.uses_source_roots=}\n") if not sources_field.uses_source_roots: unrooted_files.update(hydrated_sources.snapshot.files) diff --git a/src/python/pants/core/util_rules/wrap_source.py b/src/python/pants/core/util_rules/wrap_source.py index 6b3694789c2..1337a99bdf1 100644 --- a/src/python/pants/core/util_rules/wrap_source.py +++ b/src/python/pants/core/util_rules/wrap_source.py @@ -45,6 +45,12 @@ class ActivateWrapSourceTargetFieldBase(MultipleSourcesField): uses_source_roots = False expected_num_files = 0 +class ActivateWrapSourceTargetFieldBase(MultipleSourcesField): + # We solely register so that codegen can match a fieldset. + # One unique subclass must be defined per target type. + alias = "_sources" + uses_source_roots = False + expected_num_files = 0 class WrapSourceInputsField(SpecialCasedDependencies): alias = "inputs" @@ -99,7 +105,9 @@ async def _wrap_source(wrapper: GenerateSourcesRequest) -> GeneratedSources: def wrap_source_rule_and_target( - source_field_type: type[SourcesField], target_name_suffix: str + source_field_type: type[SourcesField], + target_name_suffix: str, + uses_source_roots: bool = False, ) -> WrapSource: if source_field_type.expected_file_extensions: outputs_help = (