def test_map_first_party_modules_to_addresses(rule_runner: RuleRunner) -> None: rule_runner.set_options( ["--source-root-patterns=['root1', 'root2', 'root3']"]) # Two proto files belonging to the same target. We should use two file addresses. rule_runner.create_files("root1/protos", ["f1.proto", "f2.proto"]) rule_runner.add_to_build_file("root1/protos", "protobuf_library()") # These protos would result in the same module name, so neither should be used. rule_runner.create_file("root1/two_owners/f.proto") rule_runner.add_to_build_file("root1/two_owners", "protobuf_library()") rule_runner.create_file("root2/two_owners/f.proto") rule_runner.add_to_build_file("root2/two_owners", "protobuf_library()") # A file with grpc. This also uses the `python_source_root` mechanism, which should be # irrelevant to the module mapping because we strip source roots. rule_runner.create_file("root1/tests/f.proto") rule_runner.add_to_build_file( "root1/tests", "protobuf_library(grpc=True, python_source_root='root3')") result = rule_runner.request(FirstPartyPythonMappingImpl, [PythonProtobufMappingMarker()]) assert result == FirstPartyPythonMappingImpl({ "protos.f1_pb2": (Address("root1/protos", relative_file_path="f1.proto"), ), "protos.f2_pb2": (Address("root1/protos", relative_file_path="f2.proto"), ), "tests.f_pb2": (Address("root1/tests", relative_file_path="f.proto"), ), "tests.f_pb2_grpc": (Address("root1/tests", relative_file_path="f.proto"), ), })
def test_file_dependencies(rule_runner: RuleRunner) -> None: # img_A -> files_A # img_A -> img_B -> files_B rule_runner.add_to_build_file( "src/a", dedent("""\ docker_image(name="img_A", dependencies=[":files_A", "src/b:img_B"]) files(name="files_A", sources=["files/**"]) """), ) rule_runner.add_to_build_file( "src/b", dedent("""\ docker_image(name="img_B", dependencies=[":files_B"]) files(name="files_B", sources=["files/**"]) """), ) rule_runner.create_files("src/a", ["Dockerfile"]) rule_runner.create_files("src/a/files", ["a01", "a02"]) rule_runner.create_files("src/b", ["Dockerfile"]) rule_runner.create_files("src/b/files", ["b01", "b02"]) # We want files_B in build context for img_B assert_build_context( rule_runner, Address("src/b", target_name="img_B"), expected_files=[ "src/b/Dockerfile", "src/b/files/b01", "src/b/files/b02" ], ) # We want files_A in build context for img_A, but not files_B assert_build_context( rule_runner, Address("src/a", target_name="img_A"), expected_files=[ "src/a/Dockerfile", "src/a/files/a01", "src/a/files/a02" ], ) # Mixed. rule_runner.add_to_build_file( "src/c", dedent("""\ docker_image(name="img_C", dependencies=["src/a:files_A", "src/b:files_B"]) """), ) rule_runner.create_files("src/c", ["Dockerfile"]) assert_build_context( rule_runner, Address("src/c", target_name="img_C"), expected_files=[ "src/c/Dockerfile", "src/a/files/a01", "src/a/files/a02", "src/b/files/b01", "src/b/files/b02", ], )
def test_inject_handler_dependency(rule_runner: RuleRunner) -> None: rule_runner.add_to_build_file( "", dedent("""\ python_requirement_library( name='ansicolors', requirements=['ansicolors'], module_mapping={'ansicolors': ['colors']}, ) """), ) rule_runner.create_files("project", ["app.py", "self.py"]) rule_runner.add_to_build_file( "project", dedent("""\ python_library() python_awslambda(name='first_party', handler='project.app:func', runtime='python3.7') python_awslambda(name='first_party_shorthand', handler='app.py:func', runtime='python3.7') python_awslambda(name='third_party', handler='colors:func', runtime='python3.7') python_awslambda(name='unrecognized', handler='who_knows.module:func', runtime='python3.7') python_awslambda(name='self', handler='self.py:func', runtime='python3.7') """), ) def assert_injected(address: Address, *, expected: Optional[Address]) -> None: tgt = rule_runner.get_target(address) injected = rule_runner.request( InjectedDependencies, [ InjectPythonLambdaHandlerDependency( tgt[PythonAwsLambdaDependencies]) ], ) assert injected == InjectedDependencies([expected] if expected else []) assert_injected( Address("project", target_name="first_party"), expected=Address("project", relative_file_path="app.py"), ) assert_injected( Address("project", target_name="first_party_shorthand"), expected=Address("project", relative_file_path="app.py"), ) assert_injected( Address("project", target_name="third_party"), expected=Address("", target_name="ansicolors"), ) assert_injected(Address("project", target_name="unrecognized"), expected=None) assert_injected( Address("project", target_name="self"), expected=Address("project", relative_file_path="self.py"), ) # Test that we can turn off the injection. rule_runner.set_options(["--no-python-infer-entry-points"]) assert_injected(Address("project", target_name="first_party"), expected=None)
def create_target( rule_runner: RuleRunner, *, parent_directory: str, files: List[str], target_cls: Type[Target] = PythonTarget, ) -> Target: rule_runner.create_files(parent_directory, files=files) address = Address(spec_path=parent_directory, target_name="target") return target_cls({Sources.alias: files}, address=address)
def test_globs(rule_runner: RuleRunner) -> None: rule_runner.create_files("some/target", ["test1.py", "test2.py"]) rule_runner.add_to_build_file("some/target", target="tgt(sources=['test*.py'])") assert_filedeps( rule_runner, targets=["some/target"], expected={"some/target/BUILD", "some/target/test*.py"}, globs=True, )
def mock_sources_field( rule_runner: RuleRunner, sources: TargetSources, *, include_sources: bool = True, sources_field_cls: Type[SourcesField] = SourcesField, ) -> SourcesField: sources_field = sources_field_cls( sources.source_files if include_sources else [], address=Address.parse(f"{sources.source_root}:lib"), ) rule_runner.create_files(path=sources.source_root, files=sources.source_files) return sources_field
def setup_target( rule_runner: RuleRunner, path: str, *, sources: Optional[List[str]] = None, dependencies: Optional[List[str]] = None, ) -> None: if sources: rule_runner.create_files(path, sources) rule_runner.add_to_build_file( path, f"tgt(sources={sources or []}, dependencies={dependencies or []})", )
def test_invalid_binary(chroot_rule_runner: RuleRunner) -> None: chroot_rule_runner.create_files("src/python/invalid_binary", ["app1.py", "app2.py"]) chroot_rule_runner.add_to_build_file( "src/python/invalid_binary", textwrap.dedent( """ python_library(name='not_a_binary', sources=[]) pex_binary(name='invalid_entrypoint_unowned1', entry_point='app1.py') pex_binary(name='invalid_entrypoint_unowned2', entry_point='invalid_binary.app2') python_distribution( name='invalid_bin1', provides=setup_py( name='invalid_bin1', version='1.1.1' ).with_binaries(foo=':not_a_binary') ) python_distribution( name='invalid_bin2', provides=setup_py( name='invalid_bin2', version='1.1.1' ).with_binaries(foo=':invalid_entrypoint_unowned1') ) python_distribution( name='invalid_bin3', provides=setup_py( name='invalid_bin3', version='1.1.1' ).with_binaries(foo=':invalid_entrypoint_unowned2') ) """ ), ) assert_chroot_error( chroot_rule_runner, Address("src/python/invalid_binary", target_name="invalid_bin1"), InvalidEntryPoint, ) assert_chroot_error( chroot_rule_runner, Address("src/python/invalid_binary", target_name="invalid_bin2"), InvalidEntryPoint, ) assert_chroot_error( chroot_rule_runner, Address("src/python/invalid_binary", target_name="invalid_bin3"), InvalidEntryPoint, )
def test_protobuf_mapping(rule_runner: RuleRunner) -> None: rule_runner.set_options( ["--source-root-patterns=['root1', 'root2', 'root3']"]) # Two proto files belonging to the same target. We should use two file addresses. rule_runner.create_files("root1/protos", ["f1.proto", "f2.proto"]) rule_runner.add_to_build_file("root1/protos", "protobuf_library()") # These protos would result in the same stripped file name, so neither should be used. rule_runner.create_file("root1/two_owners/f.proto") rule_runner.add_to_build_file("root1/two_owners", "protobuf_library()") rule_runner.create_file("root2/two_owners/f.proto") rule_runner.add_to_build_file("root2/two_owners", "protobuf_library()") result = rule_runner.request(ProtobufMapping, []) assert result == ProtobufMapping({ "protos/f1.proto": Address("root1/protos", relative_file_path="f1.proto"), "protos/f2.proto": Address("root1/protos", relative_file_path="f2.proto"), })
def test_files_out_of_tree(rule_runner: RuleRunner) -> None: # src/a:img_A -> res/static:files rule_runner.add_to_build_file( "src/a", dedent("""\ docker_image(name="img_A", dependencies=["res/static:files"]) """), ) rule_runner.add_to_build_file( "res/static", dedent("""\ files(name="files", sources=["!BUILD", "**/*"]) """), ) rule_runner.create_files("src/a", ["Dockerfile"]) rule_runner.create_files("res/static", ["s01", "s02"]) rule_runner.create_files("res/static/sub", ["s03"]) assert_build_context( rule_runner, Address("src/a", target_name="img_A"), expected_files=[ "src/a/Dockerfile", "res/static/s01", "res/static/s02", "res/static/sub/s03", ], )
def test_map_first_party_modules_to_addresses(rule_runner: RuleRunner) -> None: rule_runner.set_options( ["--source-root-patterns=['src/python', 'tests/python', 'build-support']"] ) # Two modules belonging to the same target. We should generate subtargets for each file. rule_runner.create_files("src/python/project/util", ["dirutil.py", "tarutil.py"]) rule_runner.add_to_build_file("src/python/project/util", "python_library()") # A module with two owners, meaning that neither should be resolved. rule_runner.create_file("src/python/two_owners.py") rule_runner.add_to_build_file("src/python", "python_library()") rule_runner.create_file("build-support/two_owners.py") rule_runner.add_to_build_file("build-support", "python_library()") # A package module. Because there's only one source file belonging to the target, we should # not generate subtargets. rule_runner.create_file("tests/python/project_test/demo_test/__init__.py") rule_runner.add_to_build_file("tests/python/project_test/demo_test", "python_library()") # A module with both an implementation and a type stub. rule_runner.create_files("src/python/stubs", ["stub.py", "stub.pyi"]) rule_runner.add_to_build_file("src/python/stubs", "python_library()") # Check that plugin mappings work. Note that we duplicate one of the files with a normal # python_library(), which means neither the Protobuf nor Python targets should be used. rule_runner.create_files("src/python/protos", ["f1.proto", "f2.proto", "f2_pb2.py"]) rule_runner.add_to_build_file( "src/python/protos", dedent( """\ protobuf_library(name='protos') python_library(name='py') """ ), ) result = rule_runner.request(FirstPartyPythonModuleMapping, []) assert result == FirstPartyPythonModuleMapping( { "project.util.dirutil": ( Address("src/python/project/util", relative_file_path="dirutil.py"), ), "project.util.tarutil": ( Address("src/python/project/util", relative_file_path="tarutil.py"), ), "project_test.demo_test": ( Address("tests/python/project_test/demo_test", relative_file_path="__init__.py"), ), "protos.f1_pb2": ( Address("src/python/protos", relative_file_path="f1.proto", target_name="protos"), ), "stubs.stub": ( Address("src/python/stubs", relative_file_path="stub.py"), Address("src/python/stubs", relative_file_path="stub.pyi"), ), } )
def test_map_first_party_modules_to_addresses(rule_runner: RuleRunner) -> None: rule_runner.set_options([ "--source-root-patterns=['src/python', 'tests/python', 'build-support']" ]) # Two modules belonging to the same target. We should generate subtargets for each file. rule_runner.create_files("src/python/project/util", ["dirutil.py", "tarutil.py"]) rule_runner.add_to_build_file("src/python/project/util", "python_library()") # A module with two owners, meaning that neither should be resolved. rule_runner.create_file("src/python/two_owners.py") rule_runner.add_to_build_file("src/python", "python_library()") rule_runner.create_file("build-support/two_owners.py") rule_runner.add_to_build_file("build-support", "python_library()") # A package module. Because there's only one source file belonging to the target, we should # not generate subtargets. rule_runner.create_file("tests/python/project_test/demo_test/__init__.py") rule_runner.add_to_build_file("tests/python/project_test/demo_test", "python_library()") # A module with both an implementation and a type stub. rule_runner.create_files("src/python/stubs", ["stub.py", "stub.pyi"]) rule_runner.add_to_build_file("src/python/stubs", "python_library()") result = rule_runner.request(FirstPartyModuleToAddressMapping, []) assert result.mapping == FrozenDict({ "project.util.dirutil": (Address("src/python/project/util", relative_file_path="dirutil.py"), ), "project.util.tarutil": (Address("src/python/project/util", relative_file_path="tarutil.py"), ), "project_test.demo_test": (Address("tests/python/project_test/demo_test", relative_file_path="__init__.py"), ), "stubs.stub": ( Address("src/python/stubs", relative_file_path="stub.py"), Address("src/python/stubs", relative_file_path="stub.pyi"), ), })
def test_infer_python_imports(caplog) -> None: rule_runner = RuleRunner( rules=[ *import_rules(), *target_types_rules.rules(), QueryRule(InferredDependencies, [InferPythonImportDependencies]), ], target_types=[PythonSourcesGeneratorTarget, PythonRequirementTarget], ) rule_runner.add_to_build_file( "3rdparty/python", dedent("""\ python_requirement( name='Django', requirements=['Django==1.21'], ) """), ) # If there's a `.py` and `.pyi` file for the same module, we should infer a dependency on both. rule_runner.create_file("src/python/str_import/subdir/f.py") rule_runner.create_file("src/python/str_import/subdir/f.pyi") rule_runner.add_to_build_file("src/python/str_import/subdir", "python_sources()") rule_runner.create_file("src/python/util/dep.py") rule_runner.add_to_build_file("src/python/util", "python_sources()") rule_runner.create_file( "src/python/app.py", dedent("""\ import django import unrecognized.module from util.dep import Demo from util import dep """), ) rule_runner.create_file( "src/python/f2.py", dedent("""\ import typing # Import from another file in the same target. from app import main # Dynamic string import. importlib.import_module('str_import.subdir.f') """), ) rule_runner.add_to_build_file("src/python", "python_sources()") def run_dep_inference( address: Address, *, enable_string_imports: bool = False) -> InferredDependencies: args = [ "--backend-packages=pants.backend.python", "--source-root-patterns=src/python" ] if enable_string_imports: args.append("--python-infer-string-imports") rule_runner.set_options(args, env_inherit={"PATH", "PYENV_ROOT", "HOME"}) target = rule_runner.get_target(address) return rule_runner.request( InferredDependencies, [InferPythonImportDependencies(target[PythonSourceField])]) assert run_dep_inference(Address( "src/python", relative_file_path="app.py")) == InferredDependencies([ Address("3rdparty/python", target_name="Django"), Address("src/python/util", relative_file_path="dep.py"), ], ) addr = Address("src/python", relative_file_path="f2.py") assert run_dep_inference(addr) == InferredDependencies( [Address("src/python", relative_file_path="app.py")]) assert run_dep_inference( addr, enable_string_imports=True) == InferredDependencies([ Address("src/python", relative_file_path="app.py"), Address("src/python/str_import/subdir", relative_file_path="f.py"), Address("src/python/str_import/subdir", relative_file_path="f.pyi"), ], ) # Test handling of ambiguous imports. We should warn on the ambiguous dependency, but not warn # on the disambiguated one and should infer a dep. caplog.clear() rule_runner.create_files("src/python/ambiguous", ["dep.py", "disambiguated_via_ignores.py"]) rule_runner.create_file( "src/python/ambiguous/main.py", "import ambiguous.dep\nimport ambiguous.disambiguated_via_ignores\n", ) rule_runner.add_to_build_file( "src/python/ambiguous", dedent("""\ python_sources(name='dep1', sources=['dep.py', 'disambiguated_via_ignores.py']) python_sources(name='dep2', sources=['dep.py', 'disambiguated_via_ignores.py']) python_sources( name='main', sources=['main.py'], dependencies=['!./disambiguated_via_ignores.py:dep2'], ) """), ) assert run_dep_inference( Address("src/python/ambiguous", target_name="main", relative_file_path="main.py")) == InferredDependencies([ Address( "src/python/ambiguous", target_name="dep1", relative_file_path="disambiguated_via_ignores.py", ) ], ) assert len(caplog.records) == 1 assert "The target src/python/ambiguous/main.py:main imports `ambiguous.dep`" in caplog.text assert "['src/python/ambiguous/dep.py:dep1', 'src/python/ambiguous/dep.py:dep2']" in caplog.text assert "disambiguated_via_ignores.py" not in caplog.text
def test_inject_pex_binary_entry_point_dependency() -> None: rule_runner = RuleRunner( rules=[ inject_pex_binary_entry_point_dependency, resolve_pex_entry_point, *import_rules(), QueryRule(InjectedDependencies, [InjectPexBinaryEntryPointDependency]), ], target_types=[PexBinary, PythonRequirementLibrary, PythonLibrary], ) rule_runner.add_to_build_file( "", dedent("""\ python_requirement_library( name='ansicolors', requirements=['ansicolors'], module_mapping={'ansicolors': ['colors']}, ) """), ) rule_runner.create_files("project", ["app.py", "self.py"]) rule_runner.add_to_build_file( "project", dedent("""\ python_library(sources=['app.py']) pex_binary(name='first_party', entry_point='project.app') pex_binary(name='first_party_func', entry_point='project.app:func') pex_binary(name='first_party_shorthand', entry_point='app.py:func') pex_binary(name='first_party_shorthand_func', entry_point='app.py:func') pex_binary(name='third_party', entry_point='colors') pex_binary(name='third_party_func', entry_point='colors:func') pex_binary(name='unrecognized', entry_point='who_knows.module') """), ) def assert_injected(address: Address, *, expected: Optional[Address]) -> None: tgt = rule_runner.get_target(address) injected = rule_runner.request( InjectedDependencies, [InjectPexBinaryEntryPointDependency(tgt[PexBinaryDependencies])], ) assert injected == InjectedDependencies([expected] if expected else []) assert_injected( Address("project", target_name="first_party"), expected=Address("project", relative_file_path="app.py"), ) assert_injected( Address("project", target_name="first_party_func"), expected=Address("project", relative_file_path="app.py"), ) assert_injected( Address("project", target_name="first_party_shorthand"), expected=Address("project", relative_file_path="app.py"), ) assert_injected( Address("project", target_name="first_party_shorthand_func"), expected=Address("project", relative_file_path="app.py"), ) assert_injected( Address("project", target_name="third_party"), expected=Address("", target_name="ansicolors"), ) assert_injected( Address("project", target_name="third_party_func"), expected=Address("", target_name="ansicolors"), ) assert_injected(Address("project", target_name="unrecognized"), expected=None) # Test that we can turn off the injection. rule_runner.set_options(["--no-python-infer-entry-points"]) assert_injected(Address("project", target_name="first_party"), expected=None)
def test_map_module_to_address(rule_runner: RuleRunner) -> None: rule_runner.set_options(["--source-root-patterns=['source_root1', 'source_root2', '/']"]) def assert_owners( module: str, *, expected: list[Address], expected_ambiguous: list[Address] | None = None ) -> None: owners = rule_runner.request(PythonModuleOwners, [PythonModule(module)]) assert list(owners.unambiguous) == expected assert list(owners.ambiguous) == (expected_ambiguous or []) # First check that we can map 3rd-party modules without ambiguity. rule_runner.add_to_build_file( "3rdparty/python", dedent( """\ python_requirement_library( name='ansicolors', requirements=['ansicolors==1.21'], module_mapping={'ansicolors': ['colors']}, ) """ ), ) assert_owners("colors.red", expected=[Address("3rdparty/python", target_name="ansicolors")]) # Now test that we can handle first-party type stubs that go along with that third party # requirement. Note that `colors.pyi` is at the top-level of the source root so that it strips # to the module `colors`. rule_runner.create_file("source_root1/colors.pyi") rule_runner.add_to_build_file("source_root1", "python_library()") assert_owners( "colors.red", expected=[ Address("3rdparty/python", target_name="ansicolors"), Address("source_root1", relative_file_path="colors.pyi"), ], ) # But don't allow a first-party implementation with the same module name. Path(rule_runner.build_root, "source_root1/colors.pyi").unlink() rule_runner.create_file("source_root1/colors.py") assert_owners( "colors.red", expected=[], expected_ambiguous=[ Address("3rdparty/python", target_name="ansicolors"), Address("source_root1", relative_file_path="colors.py"), ], ) # Check a first party module using a module path. rule_runner.create_file("source_root1/project/app.py") rule_runner.create_file("source_root1/project/file2.py") rule_runner.add_to_build_file("source_root1/project", "python_library()") assert_owners( "project.app", expected=[Address("source_root1/project", relative_file_path="app.py")] ) # Now check with a type stub. rule_runner.create_file("source_root1/project/app.pyi") assert_owners( "project.app", expected=[ Address("source_root1/project", relative_file_path="app.py"), Address("source_root1/project", relative_file_path="app.pyi"), ], ) # Check a package path rule_runner.create_file("source_root2/project/subdir/__init__.py") rule_runner.add_to_build_file("source_root2/project/subdir", "python_library()") assert_owners( "project.subdir", expected=[Address("source_root2/project/subdir", relative_file_path="__init__.py")], ) # Test a module with no owner (stdlib). This also smoke tests that we can handle when # there is no parent module. assert_owners("typing", expected=[]) # Test a module with a single owner with a top-level source root of ".". Also confirm we # can handle when the module includes a symbol (like a class name) at the end. rule_runner.create_file("script.py") rule_runner.add_to_build_file("", "python_library(name='script')") assert_owners( "script.Demo", expected=[Address("", relative_file_path="script.py", target_name="script")] ) # Ambiguous modules should be recorded. rule_runner.create_files("source_root1/ambiguous", ["f1.py", "f2.py", "f3.py"]) rule_runner.add_to_build_file( "source_root1/ambiguous", dedent( """\ # Ambiguity purely within third-party deps. python_requirement_library(name='thirdparty1', requirements=['foo']) python_requirement_library(name='thirdparty2', requirements=['foo']) # Ambiguity purely within first-party deps. python_library(name="firstparty1", sources=["f1.py"]) python_library(name="firstparty2", sources=["f1.py"]) # Ambiguity within third-party, which should result in ambiguity for first-party too. # These all share the module `ambiguous.f2`. python_requirement_library( name='thirdparty3', requirements=['bar'], module_mapping={'bar': ['ambiguous.f2']} ) python_requirement_library( name='thirdparty4', requirements=['bar'], module_mapping={'bar': ['ambiguous.f2']} ) python_library(name="firstparty3", sources=["f2.py"]) # Ambiguity within first-party, which should result in ambiguity for third-party too. # These all share the module `ambiguous.f3`. python_library(name="firstparty4", sources=["f3.py"]) python_library(name="firstparty5", sources=["f3.py"]) python_requirement_library( name='thirdparty5', requirements=['baz'], module_mapping={'baz': ['ambiguous.f3']} ) """ ), ) assert_owners( "foo", expected=[], expected_ambiguous=[ Address("source_root1/ambiguous", target_name="thirdparty1"), Address("source_root1/ambiguous", target_name="thirdparty2"), ], ) assert_owners( "ambiguous.f1", expected=[], expected_ambiguous=[ Address( "source_root1/ambiguous", relative_file_path="f1.py", target_name="firstparty1" ), Address( "source_root1/ambiguous", relative_file_path="f1.py", target_name="firstparty2" ), ], ) assert_owners( "ambiguous.f2", expected=[], expected_ambiguous=[ Address("source_root1/ambiguous", target_name="thirdparty3"), Address("source_root1/ambiguous", target_name="thirdparty4"), Address( "source_root1/ambiguous", relative_file_path="f2.py", target_name="firstparty3" ), ], ) assert_owners( "ambiguous.f3", expected=[], expected_ambiguous=[ Address("source_root1/ambiguous", target_name="thirdparty5"), Address( "source_root1/ambiguous", relative_file_path="f3.py", target_name="firstparty4" ), Address( "source_root1/ambiguous", relative_file_path="f3.py", target_name="firstparty5" ), ], )
def test_map_first_party_modules_to_addresses(rule_runner: RuleRunner) -> None: rule_runner.set_options( ["--source-root-patterns=['src/python', 'tests/python', 'build-support']"] ) # Two modules belonging to the same target. We should generate subtargets for each file. rule_runner.create_files("src/python/project/util", ["dirutil.py", "tarutil.py"]) rule_runner.add_to_build_file("src/python/project/util", "python_library()") # A module with two owners, meaning that neither should be resolved. rule_runner.create_file("src/python/two_owners.py") rule_runner.add_to_build_file("src/python", "python_library()") rule_runner.create_file("build-support/two_owners.py") rule_runner.add_to_build_file("build-support", "python_library()") # A package module. Because there's only one source file belonging to the target, we should # not generate subtargets. rule_runner.create_file("tests/python/project_test/demo_test/__init__.py") rule_runner.add_to_build_file("tests/python/project_test/demo_test", "python_library()") # A module with both an implementation and a type stub. Even though the module is the same, we # special-case it to be legal for both file targets to be inferred. rule_runner.create_files("src/python/stubs", ["stub.py", "stub.pyi"]) rule_runner.add_to_build_file("src/python/stubs", "python_library()") # Check that plugin mappings work. Note that we duplicate one of the files with a normal # python_library(), which means neither the Protobuf nor Python targets should be used. rule_runner.create_files("src/python/protos", ["f1.proto", "f2.proto", "f2_pb2.py"]) rule_runner.add_to_build_file( "src/python/protos", dedent( """\ protobuf_library(name='protos') python_library(name='py') """ ), ) # If a module is ambiguous within a particular implementation, which means that it's not used # in that implementation's final mapping, it should still trigger ambiguity with another # implementation. Here, we have ambiguity with the Protobuf targets, but the Python file has # no ambiguity with other Python files; the Protobuf ambiguity needs to result in Python # being ambiguous. rule_runner.create_files("src/python/protos_ambiguous", ["f.proto", "f_pb2.py"]) rule_runner.add_to_build_file( "src/python/protos_ambiguous", dedent( """\ protobuf_library(name='protos1') protobuf_library(name='protos2') python_library(name='py') """ ), ) result = rule_runner.request(FirstPartyPythonModuleMapping, []) assert result == FirstPartyPythonModuleMapping( mapping=FrozenDict( { "project.util.dirutil": ( Address("src/python/project/util", relative_file_path="dirutil.py"), ), "project.util.tarutil": ( Address("src/python/project/util", relative_file_path="tarutil.py"), ), "project_test.demo_test": ( Address( "tests/python/project_test/demo_test", relative_file_path="__init__.py" ), ), "protos.f1_pb2": ( Address( "src/python/protos", relative_file_path="f1.proto", target_name="protos" ), ), "stubs.stub": ( Address("src/python/stubs", relative_file_path="stub.py"), Address("src/python/stubs", relative_file_path="stub.pyi"), ), } ), ambiguous_modules=FrozenDict( { "protos.f2_pb2": ( Address( "src/python/protos", relative_file_path="f2.proto", target_name="protos" ), Address("src/python/protos", relative_file_path="f2_pb2.py", target_name="py"), ), "protos_ambiguous.f_pb2": ( Address( "src/python/protos_ambiguous", relative_file_path="f.proto", target_name="protos1", ), Address( "src/python/protos_ambiguous", relative_file_path="f.proto", target_name="protos2", ), Address( "src/python/protos_ambiguous", relative_file_path="f_pb2.py", target_name="py", ), ), "two_owners": ( Address("build-support", relative_file_path="two_owners.py"), Address("src/python", relative_file_path="two_owners.py"), ), } ), )