def test_multiple_resolves(rule_runner: RuleRunner) -> None: rule_runner.write_files({ "BUILD": dedent("""\ jvm_artifact( name='hamcrest', group='org.hamcrest', artifact='hamcrest-core', version="1.3", resolve=parametrize("a", "b"), ) jvm_artifact( name='opentest4j', group='org.opentest4j', artifact='opentest4j', version='1.2.0', resolve="a", ) jvm_artifact( name='apiguardian-api', group='org.apiguardian', artifact='apiguardian-api', version='1.1.0', resolve="b", ) """), }) rule_runner.set_options(["--jvm-resolves={'a': 'a.lock', 'b': 'b.lock'}"], env_inherit={"PATH"}) result = rule_runner.request(UserGenerateLockfiles, [RequestedJVMUserResolveNames(["a", "b"])]) hamcrest_core = ArtifactRequirement( Coordinate("org.hamcrest", "hamcrest-core", "1.3")) assert set(result) == { GenerateJvmLockfile( artifacts=ArtifactRequirements([ hamcrest_core, ArtifactRequirement( Coordinate("org.opentest4j", "opentest4j", "1.2.0")), ]), resolve_name="a", lockfile_dest="a.lock", ), GenerateJvmLockfile( artifacts=ArtifactRequirements([ ArtifactRequirement( Coordinate("org.apiguardian", "apiguardian-api", "1.1.0")), hamcrest_core, ]), resolve_name="b", lockfile_dest="b.lock", ), }
async def setup_user_lockfile_requests( requested: RequestedJVMUserResolveNames, all_targets: AllTargets, jvm_subsystem: JvmSubsystem, ) -> UserGenerateLockfiles: resolve_to_artifacts: Mapping[ str, OrderedSet[ArtifactRequirement]] = defaultdict(OrderedSet) for tgt in sorted(all_targets, key=lambda t: t.address): if not tgt.has_field(JvmArtifactResolveField): continue artifact = ArtifactRequirement.from_jvm_artifact_target(tgt) resolve = tgt[JvmResolveField].normalized_value(jvm_subsystem) resolve_to_artifacts[resolve].add(artifact) # Generate a JVM lockfile request for each requested resolve. This step also allows other backends to # validate the proposed set of artifact requirements for each resolve. jvm_lockfile_requests = await MultiGet( Get( GenerateJvmLockfile, _ValidateJvmArtifactsRequest( artifacts=ArtifactRequirements( resolve_to_artifacts.get(resolve, ())), resolve_name=resolve, ), ) for resolve in requested) return UserGenerateLockfiles(jvm_lockfile_requests)
def jvm_lockfile(self, request) -> JVMLockfileFixture: mark = request.node.get_closest_marker("jvm_lockfile") definition = JVMLockfileFixtureDefinition.from_kwargs(mark.kwargs) # Load the lockfile. lockfile_path = request.node.path.parent / definition.lockfile_rel_path lockfile_contents = lockfile_path.read_bytes() lockfile = CoursierResolvedLockfile.from_serialized(lockfile_contents) # Check the lockfile's requirements against the requirements in the lockfile. # Fail the test if the lockfile needs to be regenerated. artifact_reqs = ArtifactRequirements([ ArtifactRequirement(coordinate) for coordinate in definition.coordinates ]) if not lockfile.metadata: raise ValueError( f"Expected JVM lockfile {definition.lockfile_rel_path} to have metadata." ) if not lockfile.metadata.is_valid_for(artifact_reqs, LockfileContext.TOOL): raise ValueError( f"Lockfile fixture {definition.lockfile_rel_path} is not valid. " "Please re-generate it using: " f"{bin_name()} internal-generate-test-lockfile-fixtures ::") return JVMLockfileFixture(lockfile, lockfile_contents.decode(), artifact_reqs)
async def resolve_scala_library_for_resolve( request: ScalaRuntimeForResolveRequest, jvm_artifact_targets: AllJvmArtifactTargets, jvm: JvmSubsystem, scala_subsystem: ScalaSubsystem, ) -> ScalaRuntimeForResolve: scala_version = scala_subsystem.version_for_resolve(request.resolve_name) for tgt in jvm_artifact_targets: if tgt[JvmResolveField].normalized_value(jvm) != request.resolve_name: continue artifact = ArtifactRequirement.from_jvm_artifact_target(tgt) if ( artifact.coordinate.group != SCALA_LIBRARY_GROUP or artifact.coordinate.artifact != SCALA_LIBRARY_ARTIFACT ): continue if artifact.coordinate.version != scala_version: raise ConflictingScalaLibraryVersionInResolveError( request.resolve_name, scala_version, artifact.coordinate ) return ScalaRuntimeForResolve(tgt.address) raise MissingScalaLibraryInResolveError(request.resolve_name, scala_version)
async def gather_lockfile_fixtures() -> RenderedJVMLockfileFixtures: configs = await Get(CollectedJVMLockfileFixtureConfigs, CollectFixtureConfigsRequest()) rendered_fixtures = [] for config in configs: artifact_reqs = ArtifactRequirements([ ArtifactRequirement(coordinate) for coordinate in config.definition.coordinates ]) lockfile = await Get(CoursierResolvedLockfile, ArtifactRequirements, artifact_reqs) serialized_lockfile = JVMLockfileMetadata.new( artifact_reqs ).add_header_to_lockfile( lockfile.to_serialized(), regenerate_command= f"{bin_name()} {InternalGenerateTestLockfileFixturesSubsystem.name} ::", delimeter="#", ) lockfile_path = os.path.join(os.path.dirname(config.test_file_path), config.definition.lockfile_rel_path) rendered_fixtures.append( RenderedJVMLockfileFixture( content=serialized_lockfile, path=lockfile_path, )) return RenderedJVMLockfileFixtures(rendered_fixtures)
def test_generate_lockfile(rule_runner: RuleRunner) -> None: artifacts = ArtifactRequirements([ ArtifactRequirement(Coordinate("org.hamcrest", "hamcrest-core", "1.3")) ]) result = rule_runner.request( GenerateLockfileResult, [ GenerateJvmLockfile(artifacts=artifacts, resolve_name="test", lockfile_dest="lock.txt") ], ) digest_contents = rule_runner.request(DigestContents, [result.digest]) assert len(digest_contents) == 1 expected = CoursierResolvedLockfile( entries=(CoursierLockfileEntry( coord=Coordinate( group="org.hamcrest", artifact="hamcrest-core", version="1.3", ), file_name="org.hamcrest_hamcrest-core_1.3.jar", direct_dependencies=Coordinates([]), dependencies=Coordinates([]), file_digest=FileDigest( fingerprint= "66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9", serialized_bytes_length=45024, ), ), ), metadata=JVMLockfileMetadata.new(artifacts), ) assert CoursierResolvedLockfile.from_serialized( digest_contents[0].content) == expected
def test_vintage_success_with_dep(rule_runner: RuleRunner) -> None: rule_runner.write_files({ "3rdparty/jvm/default.lock": JUNIT4_RESOLVED_LOCKFILE.serialize( [ArtifactRequirement(coordinate=JUNIT_COORD)]), "BUILD": dedent(f"""\ jvm_artifact( name = 'junit_junit', group = '{JUNIT_COORD.group}', artifact = '{JUNIT_COORD.artifact}', version = '{JUNIT_COORD.version}', ) java_sources( name='example-lib', ) junit_tests( name = 'example-test', dependencies = [ ':junit_junit', '//:example-lib', ], ) """), "ExampleLib.java": dedent(""" package org.pantsbuild.example.lib; public class ExampleLib { public static String hello() { return "Hello!"; } } """), "ExampleTest.java": dedent(""" package org.pantsbuild.example; import org.pantsbuild.example.lib.ExampleLib; import junit.framework.TestCase; public class ExampleTest extends TestCase { public void testHello(){ assertTrue(ExampleLib.hello() == "Hello!"); } } """), }) test_result = run_junit_test(rule_runner, "example-test", "ExampleTest.java") assert test_result.exit_code == 0 assert re.search(r"Finished:\s+testHello", test_result.stdout) is not None assert re.search(r"1 tests successful", test_result.stdout) is not None assert re.search(r"1 tests found", test_result.stdout) is not None
def _write_file_dependencies(rule_runner: RuleRunner, junit_deps: Iterable[str], path_to_read: str): junit_deps_str = ", ".join(f"'{i}'" for i in junit_deps) rule_runner.write_files( { "3rdparty/jvm/default.lock": JUNIT4_RESOLVED_LOCKFILE.serialize( [ArtifactRequirement(coordinate=JUNIT_COORD)] ), "BUILD": dedent( f"""\ jvm_artifact( name = 'junit_junit', group = '{JUNIT_COORD.group}', artifact = '{JUNIT_COORD.artifact}', version = '{JUNIT_COORD.version}', ) junit_tests( name='example-test', dependencies= [ ':junit_junit', {junit_deps_str} ], ) file( name="duck", source="ducks.txt", ) files( name="ducks", sources=["*.txt"], ) relocated_files( name="relocated_ducks", files_targets=[":duck"], src="", dest="ducks", ) """ ), "SimpleTest.java": dedent( f""" package org.pantsbuild.example; import junit.framework.TestCase; import java.nio.file.Files; import java.nio.file.Path; public class SimpleTest extends TestCase {{ public void testHello() throws Exception {{ assertEquals("lol ducks", Files.readString(Path.of("{path_to_read}"))); }} }} """ ), "ducks.txt": "lol ducks", } )
def test_resolve_with_a_jar(rule_runner: RuleRunner) -> None: rule_runner.write_files({ "BUILD": textwrap.dedent("""\ jvm_artifact( name="jeremy", group="jeremy", artifact="jeremy", version="4.13.2", jar="jeremy.jar", ) """), "jeremy.jar": "hello dave", }) targets = rule_runner.request(Targets, [ RawSpecs(recursive_globs=(RecursiveGlobSpec(""), ), description_of_origin="tests") ]) jeremy_target = targets[0] jar_field = jeremy_target[JvmArtifactJarSourceField] requirement = ArtifactRequirement( coordinate=Coordinate( group="jeremy", artifact="jeremy", version="4.13.2", ), jar=jar_field, ) resolved_lockfile = rule_runner.request( CoursierResolvedLockfile, [ArtifactRequirements([requirement])], ) coordinate = requirement.coordinate assert resolved_lockfile == CoursierResolvedLockfile( entries=(CoursierLockfileEntry( coord=Coordinate(group=coordinate.group, artifact=coordinate.artifact, version=coordinate.version), file_name= f"{coordinate.group}_{coordinate.artifact}_{coordinate.version}.jar", direct_dependencies=Coordinates([]), dependencies=Coordinates([]), file_digest=FileDigest( fingerprint= "55b9afa8d7776cd6c318eec51f506e9c7f66c247dcec343d4667f5f269714f86", serialized_bytes_length=10, ), pants_address=jar_field.address.spec, ), ))
def test_jupiter_simple_failure(rule_runner: RuleRunner) -> None: rule_runner.write_files( { "3rdparty/jvm/default.lock": JUNIT5_RESOLVED_LOCKFILE.serialize( [ArtifactRequirement(coordinate=JUPITER_COORD)] ), "BUILD": dedent( f"""\ jvm_artifact( name = 'org.junit.jupiter_junit-jupiter-api', group = '{JUPITER_COORD.group}', artifact = '{JUPITER_COORD.artifact}', version = '{JUPITER_COORD.version}', ) junit_tests( name='example-test', dependencies= [ ':org.junit.jupiter_junit-jupiter-api', ], ) """ ), "SimpleTest.java": dedent( """ package org.pantsbuild.example; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; class SimpleTest { @Test void testHello(){ assertEquals("Goodbye!", "Hello!"); } } """ ), } ) test_result = run_junit_test(rule_runner, "example-test", "SimpleTest.java") assert test_result.exit_code == 1 assert test_result.xml_results and test_result.xml_results.files assert ( re.search( r"Finished:.*?testHello.*?Exception: org.opentest4j.AssertionFailedError: expected: <Goodbye!> but was: <Hello!>", test_result.stdout, re.DOTALL, ) is not None ) assert re.search(r"1 tests failed", test_result.stdout) is not None assert re.search(r"1 tests found", test_result.stdout) is not None
def test_vintage_simple_failure(rule_runner: RuleRunner) -> None: rule_runner.write_files( { "3rdparty/jvm/default.lock": JUNIT4_RESOLVED_LOCKFILE.serialize( [ArtifactRequirement(coordinate=JUNIT_COORD)] ), "BUILD": dedent( f"""\ jvm_artifact( name = 'junit_junit', group = '{JUNIT_COORD.group}', artifact = '{JUNIT_COORD.artifact}', version = '{JUNIT_COORD.version}', ) junit_tests( name='example-test', dependencies= [ ':junit_junit', ], ) """ ), "SimpleTest.java": dedent( """ package org.pantsbuild.example; import org.junit.Test; import static org.junit.Assert.*; public class SimpleTest { @Test public void helloTest(){ assertTrue("Goodbye!" == "Hello!"); } } """ ), } ) test_result = run_junit_test(rule_runner, "example-test", "SimpleTest.java") assert test_result.exit_code == 1 assert ( re.search( r"Finished:.*?helloTest.*?Exception: java.lang.AssertionError", test_result.stdout, re.DOTALL, ) is not None ) assert re.search(r"1 tests failed", test_result.stdout) is not None assert re.search(r"1 tests found", test_result.stdout) is not None
async def gather_coordinates_for_jvm_lockfile( request: GatherJvmCoordinatesRequest, ) -> ArtifactRequirements: # Separate `artifact_inputs` by whether the strings parse as an `Address` or not. requirements: set[ArtifactRequirement] = set() candidate_address_inputs: set[AddressInput] = set() bad_artifact_inputs = [] for artifact_input in request.artifact_inputs: # Try parsing as a `Coordinate` first since otherwise `AddressInput.parse` will try to see if the # group name is a file on disk. if 2 <= artifact_input.count(":") <= 3: try: maybe_coord = Coordinate.from_coord_str( artifact_input).as_requirement() requirements.add(maybe_coord) continue except Exception: pass try: address_input = AddressInput.parse( artifact_input, description_of_origin=f"the option `{request.option_name}`") candidate_address_inputs.add(address_input) except Exception: bad_artifact_inputs.append(artifact_input) if bad_artifact_inputs: raise ValueError( "The following values could not be parsed as an address nor as a JVM coordinate string. " f"The problematic inputs supplied to the `{request.option_name}` option were: " f"{', '.join(bad_artifact_inputs)}.") # Gather coordinates from the provided addresses. addresses = await MultiGet( Get(Address, AddressInput, ai) for ai in candidate_address_inputs) all_supplied_targets = await Get(Targets, Addresses(addresses)) other_targets = [] for tgt in all_supplied_targets: if JvmArtifactFieldSet.is_applicable(tgt): requirements.add(ArtifactRequirement.from_jvm_artifact_target(tgt)) else: other_targets.append(tgt) if other_targets: raise ValueError( softwrap(f""" The following addresses reference targets that are not `jvm_artifact` targets. Please only supply the addresses of `jvm_artifact` for the `{request.option_name}` option. The problematic addresses are: {', '.join(str(tgt.address) for tgt in other_targets)}. """)) return ArtifactRequirements(requirements)
def test_vintage_scala_simple_success(rule_runner: RuleRunner) -> None: rule_runner.write_files( { "3rdparty/jvm/default.lock": JUNIT4_RESOLVED_LOCKFILE.serialize( [ArtifactRequirement(coordinate=JUNIT_COORD)] ), "BUILD": dedent( f"""\ jvm_artifact( name = 'junit_junit', group = '{JUNIT_COORD.group}', artifact = '{JUNIT_COORD.artifact}', version = '{JUNIT_COORD.version}', ) scala_junit_tests( name='example-test', dependencies= [ ':junit_junit', ], ) """ ), "SimpleTest.scala": dedent( """ package org.pantsbuild.example import junit.framework.TestCase import junit.framework.Assert._ class SimpleTest extends TestCase { def testHello(): Unit = { assertTrue("Hello!" == "Hello!") } } """ ), } ) test_result = run_junit_test(rule_runner, "example-test", "SimpleTest.scala") assert test_result.exit_code == 0 assert re.search(r"Finished:\s+testHello", test_result.stdout) is not None assert re.search(r"1 tests successful", test_result.stdout) is not None assert re.search(r"1 tests found", test_result.stdout) is not None
async def collect_thirdparty_modules( request: ThirdpartyModulesRequest, classpath_entry_request: ClasspathEntryRequestFactory, ) -> ThirdpartyModules: coarsened_targets = await Get(CoarsenedTargets, Addresses, request.addresses) resolve = await Get(CoursierResolveKey, CoarsenedTargets, coarsened_targets) lockfile = await Get(CoursierResolvedLockfile, CoursierResolveKey, resolve) applicable_lockfile_entries: dict[CoursierLockfileEntry, CoarsenedTarget] = {} for ct in coarsened_targets.coarsened_closure(): for tgt in ct.members: if not JvmArtifactFieldSet.is_applicable(tgt): continue artifact_requirement = ArtifactRequirement.from_jvm_artifact_target( tgt) entry = get_entry_for_coord(lockfile, artifact_requirement.coordinate) if not entry: _logger.warning( f"No lockfile entry for {artifact_requirement.coordinate} in resolve {resolve.name}." ) continue applicable_lockfile_entries[entry] = ct classpath_entries = await MultiGet( Get( ClasspathEntry, ClasspathEntryRequest, classpath_entry_request.for_targets(component=target, resolve=resolve), ) for target in applicable_lockfile_entries.values()) resolve_digest = await Get( Digest, MergeDigests(cpe.digest for cpe in classpath_entries)) return ThirdpartyModules( resolve, dict(zip(applicable_lockfile_entries, classpath_entries)), resolve_digest, )
def test_resolve_with_broken_url(rule_runner: RuleRunner) -> None: coordinate = ArtifactRequirement( coordinate=Coordinate( group="org.hamcrest", artifact="hamcrest-core", version= "1.3_inexplicably_wrong", # if the group/artifact/version is real, coursier will fallback ), url="https://this_url_does_not_work", ) expected_exception_msg = r".*this_url_does_not_work not found under https.*" with pytest.raises(ExecutionError, match=expected_exception_msg): rule_runner.request( CoursierResolvedLockfile, [ArtifactRequirements([coordinate])], )
def find_jvm_artifacts_or_raise( required_coordinates: Iterable[Coordinate | UnversionedCoordinate], resolve: str, jvm_artifact_targets: AllJvmArtifactTargets, jvm: JvmSubsystem, ) -> frozenset[Address]: remaining_coordinates: set[Coordinate | UnversionedCoordinate] = set( required_coordinates) addresses: set[Address] = set() for tgt in jvm_artifact_targets: if tgt[JvmResolveField].normalized_value(jvm) != resolve: continue artifact = ArtifactRequirement.from_jvm_artifact_target(tgt) found_coordinates: set[Coordinate | UnversionedCoordinate] = set() for coordinate in remaining_coordinates: if isinstance(coordinate, Coordinate): if (artifact.coordinate.group != coordinate.group or artifact.coordinate.artifact != coordinate.artifact): continue if artifact.coordinate.version != coordinate.version: raise ConflictingJvmArtifactVersion( group=coordinate.group, artifact=coordinate.artifact, required_version=coordinate.version, found_coordinate=artifact.coordinate, ) elif isinstance(coordinate, UnversionedCoordinate): if (artifact.coordinate.group != coordinate.group or artifact.coordinate.artifact != coordinate.artifact): continue found_coordinates.add(coordinate) if found_coordinates: remaining_coordinates.difference_update(found_coordinates) addresses.add(tgt.address) if remaining_coordinates: raise MissingJvmArtifacts(remaining_coordinates) return frozenset(addresses)
async def fetch_with_coursier( request: CoursierFetchRequest) -> FallibleClasspathEntry: # TODO: Loading this per JvmArtifact. lockfile = await Get(CoursierResolvedLockfile, CoursierResolveKey, request.resolve) requirement = ArtifactRequirement.from_jvm_artifact_target( request.component.representative) if lockfile.metadata and not lockfile.metadata.is_valid_for( [requirement], LockfileContext.USER): raise ValueError( f"Requirement `{requirement.to_coord_arg_str()}` has changed since the lockfile " f"for {request.resolve.path} was generated. Run `{bin_name()} generate-lockfiles` to update your " "lockfile based on the new requirements.") # All of the transitive dependencies are exported. # TODO: Expose an option to control whether this exports only the root, direct dependencies, # transitive dependencies, etc. assert len(request.component.members ) == 1, "JvmArtifact does not have dependencies." root_entry, transitive_entries = lockfile.dependencies( request.resolve, requirement.coordinate, ) classpath_entries = await MultiGet( Get(ClasspathEntry, CoursierLockfileEntry, entry) for entry in (root_entry, *transitive_entries)) exported_digest = await Get( Digest, MergeDigests(cpe.digest for cpe in classpath_entries)) return FallibleClasspathEntry( description=str(request.component), result=CompileResult.SUCCEEDED, output=ClasspathEntry.merge(exported_digest, classpath_entries), exit_code=0, )
def test_resolve_with_working_url(rule_runner: RuleRunner) -> None: requirement = ArtifactRequirement( coordinate=Coordinate( group="apache-commons-local", artifact="commons-collections", version="1.0.0_JAR_LOCAL", ), url= "https://repo1.maven.org/maven2/org/apache/commons/commons-collections4/4.2/commons-collections4-4.2.jar", ) resolved_lockfile = rule_runner.request( CoursierResolvedLockfile, [ArtifactRequirements([requirement])], ) coordinate = requirement.coordinate assert resolved_lockfile == CoursierResolvedLockfile( entries=(CoursierLockfileEntry( coord=Coordinate(group=coordinate.group, artifact=coordinate.artifact, version=coordinate.version), file_name= f"{coordinate.group}_{coordinate.artifact}_{coordinate.version}.jar", direct_dependencies=Coordinates([]), dependencies=Coordinates([]), file_digest=FileDigest( fingerprint= "6a594721d51444fd97b3eaefc998a77f606dedb03def494f74755aead3c9df3e", serialized_bytes_length=752798, ), remote_url=requirement.url, pants_address=None, ), ))
def test_compile_with_maven_deps(rule_runner: RuleRunner) -> None: joda_coord = Coordinate(group="joda-time", artifact="joda-time", version="2.10.10") scala_library_coord = Coordinate(group="org.scala-lang", artifact="scala-library", version="2.13.6") resolved_joda_lockfile = TestCoursierWrapper.new(entries=( CoursierLockfileEntry( coord=joda_coord, file_name="joda-time-2.10.10.jar", direct_dependencies=Coordinates([]), dependencies=Coordinates([]), file_digest=FileDigest( fingerprint= "dd8e7c92185a678d1b7b933f31209b6203c8ffa91e9880475a1be0346b9617e3", serialized_bytes_length=644419, ), ), CoursierLockfileEntry( coord=scala_library_coord, file_name="org.scala-lang_scala-library_2.13.6.jar", direct_dependencies=Coordinates([]), dependencies=Coordinates([]), file_digest=FileDigest( "f19ed732e150d3537794fd3fe42ee18470a3f707efd499ecd05a99e727ff6c8a", 5955737, ), ), )) rule_runner.write_files({ "BUILD": dedent(f"""\ jvm_artifact( name = "joda-time_joda-time", group = "{joda_coord.group}", artifact = "{joda_coord.artifact}", version = "{joda_coord.version}", ) scala_sources( name = 'main', dependencies = [":joda-time_joda-time"], ) """), "3rdparty/jvm/BUILD": DEFAULT_SCALA_LIBRARY_TARGET, "3rdparty/jvm/default.lock": resolved_joda_lockfile.serialize([ ArtifactRequirement(joda_coord), ArtifactRequirement(scala_library_coord) ]), "Example.scala": dedent(""" package org.pantsbuild.example import org.joda.time.DateTime object Main { def main(args: Array[String]): Unit = { val dt = new DateTime() println(dt.getYear) } } """), }) classpath = rule_runner.request( RenderedClasspath, [ CompileScalaSourceRequest( component=expect_single_expanded_coarsened_target( rule_runner, Address(spec_path="", target_name="main")), resolve=make_resolve(rule_runner), ) ], ) assert classpath.content == { ".Example.scala.main.scalac.jar": { "META-INF/MANIFEST.MF", "org/pantsbuild/example/Main$.class", "org/pantsbuild/example/Main.class", } }
DEFAULT_LOCKFILE = TestCoursierWrapper( CoursierResolvedLockfile((CoursierLockfileEntry( coord=Coordinate(group="org.scala-lang", artifact="scala-library", version="2.13.6"), file_name="org.scala-lang_scala-library_2.13.6.jar", direct_dependencies=Coordinates(), dependencies=Coordinates(), file_digest=FileDigest( "f19ed732e150d3537794fd3fe42ee18470a3f707efd499ecd05a99e727ff6c8a", 5955737), ), ))).serialize([ ArtifactRequirement(coordinate=Coordinate(group="org.scala-lang", artifact="scala-library", version="2.13.6")) ]) DEFAULT_SCALA_LIBRARY_TARGET = textwrap.dedent("""\ jvm_artifact( name="org.scala-lang_scala-library_2.13.6", group="org.scala-lang", artifact="scala-library", version="2.13.6", ) """) SCALA_LIB_SOURCE = dedent(""" package org.pantsbuild.example.lib
coord=Coordinate( group="com.thesamet.scalapb", artifact="scalapb-runtime_2.13", version="0.11.6", ), file_name="scalapb-runtime_2.13-0.11.6.jar", direct_dependencies=Coordinates([]), dependencies=Coordinates([]), file_digest=FileDigest( "439b613f40b9ac43db4d68de5bef36befc56393d9c9cd002e2b965ce94f6f793", 2426575, ), ), ), ).serialize([ ArtifactRequirement( Coordinate( group="com.thesamet.scalapb", artifact="scalapb-runtime_2.13", version="0.11.6", )) ]) @pytest.fixture def rule_runner() -> RuleRunner: rule_runner = RuleRunner( rules=[ *config_files.rules(), *classpath.rules(), *coursier_fetch_rules(), *coursier_setup_rules(), *external_tool_rules(), *source_files.rules(),
def test_jupiter_success_with_dep(rule_runner: RuleRunner) -> None: rule_runner.write_files({ "3rdparty/jvm/default.lock": JUNIT5_RESOLVED_LOCKFILE.serialize( [ArtifactRequirement(coordinate=JUPITER_COORD)]), "BUILD": dedent(f"""\ jvm_artifact( name = 'org.junit.jupiter_junit-jupiter-api', group = '{JUPITER_COORD.group}', artifact = '{JUPITER_COORD.artifact}', version = '{JUPITER_COORD.version}', ) java_sources( name='example-lib', ) junit_tests( name = 'example-test', dependencies = [ ':org.junit.jupiter_junit-jupiter-api', '//:example-lib', ], ) """), "ExampleLib.java": dedent(""" package org.pantsbuild.example.lib; public class ExampleLib { public static String hello() { return "Hello!"; } } """), "SimpleTest.java": dedent(""" package org.pantsbuild.example; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; import org.pantsbuild.example.lib.ExampleLib; class SimpleTest { @Test void testHello(){ assertEquals(ExampleLib.hello(), "Hello!"); } } """), }) test_result = run_junit_test(rule_runner, "example-test", "SimpleTest.java") assert test_result.exit_code == 0 assert re.search(r"Finished:\s+testHello", test_result.stdout) is not None assert re.search(r"1 tests successful", test_result.stdout) is not None assert re.search(r"1 tests found", test_result.stdout) is not None
def test_basic_war_packaging(rule_runner: RuleRunner) -> None: servlet_coordinate = Coordinate(group="javax.servlet", artifact="servlet-api", version="2.5") rule_runner.write_files( { "war-test/BUILD": textwrap.dedent( """\ jvm_artifact( name="javax.servlet_servlet-api", group="javax.servlet", artifact="servlet-api", version="2.5", ) jvm_war( name="war", dependencies=[":javax.servlet_servlet-api"], descriptor="web.xml", content=[":html"], ) files(name="orig_html", sources=["*.html"]) relocated_files( name="html", files_targets=[":orig_html"], src="war-test", dest="", ) """ ), "war-test/web.xml": textwrap.dedent( """\ <web-app> </web-app> """ ), "war-test/index.html": textwrap.dedent( """\ <html> <body> <p>This is the home page.</p> </html> """ ), "3rdparty/jvm/default.lock": TestCoursierWrapper.new( ( CoursierLockfileEntry( coord=servlet_coordinate, file_name="javax.servlet_servlet-api_2.5.jar", direct_dependencies=Coordinates(), dependencies=Coordinates(), file_digest=FileDigest( "c658ea360a70faeeadb66fb3c90a702e4142a0ab7768f9ae9828678e0d9ad4dc", 105112, ), ), ), ).serialize([ArtifactRequirement(servlet_coordinate)]), } ) war_tgt = rule_runner.get_target(Address("war-test", target_name="war")) built_package = rule_runner.request(BuiltPackage, [PackageWarFileFieldSet.create(war_tgt)]) assert built_package.digest != EMPTY_DIGEST assert len(built_package.artifacts) == 1 package = built_package.artifacts[0] assert package.relpath == "war-test/war.war" contents = rule_runner.request(DigestContents, [built_package.digest]) assert len(contents) == 1 zip_bytes = BytesIO(contents[0].content) with zipfile.ZipFile(zip_bytes, "r") as zf: files = zf.filelist filenames = [f.filename for f in files] assert sorted(filenames) == [ "WEB-INF/", "WEB-INF/classes/", "WEB-INF/lib/", "WEB-INF/lib/javax.servlet_servlet-api_2.5.jar", "WEB-INF/web.xml", "index.html", ]
async def scala_bsp_dependency_modules( request: ScalaBSPDependencyModulesRequest, build_root: BuildRoot, ) -> BSPDependencyModulesResult: coarsened_targets = await Get( CoarsenedTargets, Addresses([fs.address for fs in request.field_sets])) resolve = await Get(CoursierResolveKey, CoarsenedTargets, coarsened_targets) lockfile = await Get(CoursierResolvedLockfile, CoursierResolveKey, resolve) # TODO: Can this use ClasspathEntryRequest? transitive_targets = await Get( TransitiveTargets, TransitiveTargetsRequest(roots=[ tgt.address for coarsened_target in coarsened_targets for tgt in coarsened_target.members ]), ) artifact_requirements = [ ArtifactRequirement.from_jvm_artifact_target(tgt) for tgt in transitive_targets.closure if JvmArtifactFieldSet.is_applicable(tgt) ] applicable_lockfile_entries: set[CoursierLockfileEntry] = set() for artifact_requirement in artifact_requirements: entry = get_entry_for_coord(lockfile, artifact_requirement.coordinate) if not entry: _logger.warning( f"No lockfile entry for {artifact_requirement.coordinate} in resolve {resolve.name}." ) continue applicable_lockfile_entries.add(entry) resolve_digest = await Get( Digest, CreateDigest([ FileEntry(entry.file_name, entry.file_digest) for entry in applicable_lockfile_entries ]), ) resolve_digest = await Get( Digest, AddPrefix(resolve_digest, f"jvm/resolves/{resolve.name}/lib")) modules = [ DependencyModule( name=f"{entry.coord.group}:{entry.coord.artifact}", version=entry.coord.version, data=MavenDependencyModule( organization=entry.coord.group, name=entry.coord.artifact, version=entry.coord.version, scope=None, artifacts=(MavenDependencyModuleArtifact( uri=build_root.pathlib_path.joinpath( f".pants.d/bsp/jvm/resolves/{resolve.name}/lib/{entry.file_name}" ).as_uri()), ), ), ) for entry in applicable_lockfile_entries ] return BSPDependencyModulesResult( modules=tuple(modules), digest=resolve_digest, )
def test_compile_with_local_scalac_plugin(rule_runner: RuleRunner) -> None: acyclic_coord = Coordinate(group="com.lihaoyi", artifact="acyclic_2.13", version="0.2.1") scala_library_coord = Coordinate(group="org.scala-lang", artifact="scala-library", version="2.13.6") rule_runner.write_files({ "lib/BUILD": dedent(f"""\ jvm_artifact( name = "acyclic_lib", group = "{acyclic_coord.group}", artifact = "{acyclic_coord.artifact}", version = "{acyclic_coord.version}", packages=["acyclic.**"], ) scalac_plugin( name = "acyclic", # TODO: Support relative addresses. artifact = "lib:acyclic_lib", ) scala_sources( scalac_plugins=["acyclic"], ) """), "3rdparty/jvm/BUILD": DEFAULT_SCALA_LIBRARY_TARGET, "3rdparty/jvm/default.lock": TestCoursierWrapper.new(entries=( CoursierLockfileEntry( coord=acyclic_coord, file_name="acyclic_2.13-0.2.1.jar", direct_dependencies=Coordinates([]), dependencies=Coordinates([]), file_digest=FileDigest( "4bc4656140ad5e4802fedcdbe920ec7c92dbebf5e76d1c60d35676a314481944", 62534, ), ), CoursierLockfileEntry( coord=scala_library_coord, file_name="org.scala-lang_scala-library_2.13.6.jar", direct_dependencies=Coordinates([]), dependencies=Coordinates([]), file_digest=FileDigest( "f19ed732e150d3537794fd3fe42ee18470a3f707efd499ecd05a99e727ff6c8a", 5955737, ), ), )).serialize([ ArtifactRequirement(coordinate=acyclic_coord), ArtifactRequirement(scala_library_coord), ]), "3rdparty/jvm/scalac-plugins.lock": TestCoursierWrapper.new(entries=( CoursierLockfileEntry( coord=acyclic_coord, file_name="acyclic_2.13-0.2.1.jar", direct_dependencies=Coordinates([]), dependencies=Coordinates([]), file_digest=FileDigest( "4bc4656140ad5e4802fedcdbe920ec7c92dbebf5e76d1c60d35676a314481944", 62534, ), ), CoursierLockfileEntry( coord=scala_library_coord, file_name="org.scala-lang_scala-library_2.13.6.jar", direct_dependencies=Coordinates([]), dependencies=Coordinates([]), file_digest=FileDigest( "f19ed732e150d3537794fd3fe42ee18470a3f707efd499ecd05a99e727ff6c8a", 5955737, ), ), )).serialize([ ArtifactRequirement(coordinate=acyclic_coord), ]), "lib/A.scala": dedent(""" package lib import acyclic.file class A { val b: B = null } """), "lib/B.scala": dedent(""" package lib class B { val a: A = null } """), }) rule_runner.set_options( args=[], env_inherit=PYTHON_BOOTSTRAP_ENV, ) request = CompileScalaSourceRequest( component=expect_single_expanded_coarsened_target( rule_runner, Address(spec_path="lib", relative_file_path="A.scala")), resolve=make_resolve(rule_runner), ) fallible_result = rule_runner.request(FallibleClasspathEntry, [request]) assert fallible_result.result == CompileResult.FAILED and fallible_result.stderr assert "error: Unwanted cyclic dependency" in fallible_result.stderr
def test_compile_with_multiple_scala_versions(rule_runner: RuleRunner) -> None: scala_library_coord_2_12 = Coordinate(group="org.scala-lang", artifact="scala-library", version="2.12.15") scala_library_coord_2_13 = Coordinate(group="org.scala-lang", artifact="scala-library", version="2.13.8") rule_runner.write_files({ "BUILD": dedent("""\ scala_sources( name = 'main_2.12', resolve = "scala2.12", ) scala_sources( name = 'main_2.13', resolve = "scala2.13", ) jvm_artifact( name="org.scala-lang_scala-library_2.12.15", group="org.scala-lang", artifact="scala-library", version="2.12.15", resolve="scala2.12", ) jvm_artifact( name="org.scala-lang_scala-library_2.13.8", group="org.scala-lang", artifact="scala-library", version="2.13.8", resolve="scala2.13", ) """), "Example.scala": SCALA_LIB_SOURCE, "3rdparty/jvm/scala2.12.lock": TestCoursierWrapper.new(entries=(CoursierLockfileEntry( coord=scala_library_coord_2_12, file_name="org.scala-lang_scala-library_2.12.15.jar", direct_dependencies=Coordinates([]), dependencies=Coordinates([]), file_digest=FileDigest( "e518bb640e2175de5cb1f8e326679b8d975376221f1b547757de429bbf4563f0", 5443542, ), ), ), ).serialize([ArtifactRequirement(scala_library_coord_2_12)]), "3rdparty/jvm/scala2.13.lock": TestCoursierWrapper.new(entries=(CoursierLockfileEntry( coord=scala_library_coord_2_13, file_name="org.scala-lang_scala-library_2.13.8.jar", direct_dependencies=Coordinates([]), dependencies=Coordinates([]), file_digest=FileDigest( "a0882b82514190c2bac7d1a459872a75f005fc0f3e88b2bc0390367146e35db7", 6003601, ), ), ), ).serialize([ArtifactRequirement(scala_library_coord_2_13)]), }) rule_runner.set_options( [ '--scala-version-for-resolve={"scala2.12":"2.12.15","scala2.13":"2.13.8"}', '--jvm-resolves={"scala2.12":"3rdparty/jvm/scala2.12.lock","scala2.13":"3rdparty/jvm/scala2.13.lock"}', ], env_inherit=PYTHON_BOOTSTRAP_ENV, ) classpath_2_12 = rule_runner.request( ClasspathEntry, [ CompileScalaSourceRequest( component=expect_single_expanded_coarsened_target( rule_runner, Address(spec_path="", target_name="main_2.12")), resolve=make_resolve(rule_runner, "scala2.12", "3rdparty/jvm/scala2.12.lock"), ) ], ) entries_2_12 = list(ClasspathEntry.closure([classpath_2_12])) filenames_2_12 = sorted( itertools.chain.from_iterable(entry.filenames for entry in entries_2_12)) assert filenames_2_12 == [ ".Example.scala.main_2.12.scalac.jar", "org.scala-lang_scala-library_2.12.15.jar", ] classpath_2_13 = rule_runner.request( ClasspathEntry, [ CompileScalaSourceRequest( component=expect_single_expanded_coarsened_target( rule_runner, Address(spec_path="", target_name="main_2.13")), resolve=make_resolve(rule_runner, "scala2.13", "3rdparty/jvm/scala2.13.lock"), ) ], ) entries_2_13 = list(ClasspathEntry.closure([classpath_2_13])) filenames_2_13 = sorted( itertools.chain.from_iterable(entry.filenames for entry in entries_2_13)) assert filenames_2_13 == [ ".Example.scala.main_2.13.scalac.jar", "org.scala-lang_scala-library_2.13.8.jar", ]
def test_compile_with_multiple_scalac_plugins(rule_runner: RuleRunner) -> None: better_monadic_coord = Coordinate(group="com.olegpy", artifact="better-monadic-for_2.13", version="0.3.1") kind_projector_coord = Coordinate(group="org.typelevel", artifact="kind-projector_2.13.6", version="0.13.2") scala_compiler_coord = Coordinate(group="org.scala-lang", artifact="scala-compiler", version="2.13.6") scala_library_coord = Coordinate(group="org.scala-lang", artifact="scala-library", version="2.13.6") scala_reflect_coord = Coordinate(group="org.scala-lang", artifact="scala-reflect", version="2.13.6") jna_coord = Coordinate(group="net.java.dev.jna", artifact="jna", version="5.3.1") jline_coord = Coordinate(group="org.jline", artifact="jline", version="3.19.0") rule_runner.write_files({ "lib/BUILD": dedent(f"""\ scala_sources() jvm_artifact( name="kind-projector-lib", group="{kind_projector_coord.group}", artifact="{kind_projector_coord.artifact}", version="{kind_projector_coord.version}", ) scalac_plugin( name="kind-projector", plugin_name="kind-projector", # TODO: Support relative addresses. artifact="lib:kind-projector-lib", ) jvm_artifact( name="better-monadic-for-lib", group="{better_monadic_coord.group}", artifact="{better_monadic_coord.artifact}", version="{better_monadic_coord.version}", ) scalac_plugin( name="better-monadic-for", plugin_name="bm4", # TODO: Support relative addresses. artifact="lib:better-monadic-for-lib", ) """), "3rdparty/jvm/BUILD": DEFAULT_SCALA_LIBRARY_TARGET, "3rdparty/jvm/default.lock": DEFAULT_LOCKFILE, "3rdparty/jvm/scalac-plugins.lock": TestCoursierWrapper.new(entries=( CoursierLockfileEntry( coord=better_monadic_coord, file_name="com.olegpy_better-monadic-for_2.13_0.3.1.jar", direct_dependencies=Coordinates( [scala_compiler_coord, scala_library_coord]), dependencies=Coordinates( [scala_compiler_coord, scala_library_coord]), file_digest=FileDigest( "fac649fa7de697d1f98d3f814c4b70f5372c547fa41778383e22cee6c16084f5", 130370, ), ), CoursierLockfileEntry( coord=jna_coord, file_name="net.java.dev.jna_jna_5.3.1.jar", direct_dependencies=Coordinates([]), dependencies=Coordinates([]), file_digest=FileDigest( "01cb505c0698d0f7acf3524c7e73acb7dc424a5bae5e9c86ce44075ab32bc4ee", 1505196, ), ), CoursierLockfileEntry( coord=jline_coord, file_name="org.jline_jline_3.19.0.jar", direct_dependencies=Coordinates([]), dependencies=Coordinates([]), file_digest=FileDigest( "c99ddcfa5431cab88d1cd40fd63bec6ab5a3fe2e83877051198539af66592a46", 987021, ), ), CoursierLockfileEntry( coord=scala_compiler_coord, file_name="org.scala-lang_scala-compiler_2.13.6.jar", direct_dependencies=Coordinates([ jna_coord, jline_coord, scala_library_coord, scala_reflect_coord ]), dependencies=Coordinates([ jna_coord, jline_coord, scala_library_coord, scala_reflect_coord ]), file_digest=FileDigest( "310d263d622a3d016913e94ee00b119d270573a5ceaa6b21312d69637fd9eec1", 12010571, ), ), CoursierLockfileEntry( coord=scala_library_coord, file_name="org.scala-lang_scala-library_2.13.6.jar", direct_dependencies=Coordinates([]), dependencies=Coordinates([]), file_digest=FileDigest( "f19ed732e150d3537794fd3fe42ee18470a3f707efd499ecd05a99e727ff6c8a", 5955737, ), ), CoursierLockfileEntry( coord=scala_reflect_coord, file_name="org.scala-lang_scala-reflect_2.13.6.jar", direct_dependencies=Coordinates([scala_library_coord]), dependencies=Coordinates([scala_library_coord]), file_digest=FileDigest( "f713593809b387c60935bb9a940dfcea53bd0dbf8fdc8d10739a2896f8ac56fa", 3769997, ), ), CoursierLockfileEntry( coord=kind_projector_coord, file_name="org.typelevel_kind-projector_2.13.6_0.13.2.jar", direct_dependencies=Coordinates( [scala_compiler_coord, scala_library_coord]), dependencies=Coordinates([ scala_compiler_coord, scala_reflect_coord, scala_library_coord, jna_coord, jline_coord, ]), file_digest=FileDigest( "3d713d02bbe0d52b01c22ac11a50970460114f32b339f3ea429d52461d6c39ff", 44257, ), ), )).serialize([ ArtifactRequirement(better_monadic_coord), ArtifactRequirement(kind_projector_coord), ]), "lib/A.scala": dedent("""\ trait Functor[F[_]] { def map[A, B](fa: F[A])(f: A => B): F[B] } object KindProjectorTest { implicit def eitherFunctor[E]: Functor[Either[E, *]] = new Functor[Either[E, *]] { def map[A, B](fa: Either[E, A])(f: A => B): Either[E, B] = { fa match { case Left(e) => Left(e) case Right(a) => Right(f(a)) } } } } object BetterMonadicForTest { def example: Option[String] = { case class ImplicitTest(id: String) for { x <- Option(42) implicit0(it: ImplicitTest) <- Option(ImplicitTest("eggs")) _ <- Option("dummy") _ = "dummy" _ = assert(implicitly[ImplicitTest] eq it) } yield "ok" } } """), }) rule_runner.set_options( args=[ "--scalac-plugins-global=['lib:better-monadic-for', 'lib:kind-projector']", "--scalac-plugins-global-lockfile=3rdparty/jvm/scalac-plugins.lock", ], env_inherit=PYTHON_BOOTSTRAP_ENV, ) request = CompileScalaSourceRequest( component=expect_single_expanded_coarsened_target( rule_runner, Address(spec_path="lib", relative_file_path="A.scala")), resolve=make_resolve(rule_runner), ) rule_runner.request(RenderedClasspath, [request])
artifact="libthrift", version="0.15.0", ), file_name="libthrift-0.15.0.jar", direct_dependencies=Coordinates([]), dependencies=Coordinates([]), file_digest=FileDigest( "e9c47420147cbb87a6df08bc36da04e2be1561967b5ef82d2f3ef9ec090d85d0", 305670, ), ), # Note: The transitive dependencies of libthrift have been intentionally omitted from this resolve. ), ).serialize([ ArtifactRequirement( Coordinate( group="org.apache.thrift", artifact="libthrift", version="0.15.0", )) ]) @pytest.fixture def rule_runner() -> RuleRunner: return RuleRunner( rules=[ *thrift_rules(), *apache_thrift_rules(), *apache_thrift_java_rules(), *source_files.rules(), *source_root.rules(), *graph.rules(),
async def coursier_fetch_one_coord( request: CoursierLockfileEntry, ) -> ClasspathEntry: """Run `coursier fetch --intransitive` to fetch a single artifact. This rule exists to permit efficient subsetting of a "global" classpath in the form of a lockfile. Callers can determine what subset of dependencies from the lockfile are needed for a given target, then request those lockfile entries individually. By fetching only one entry at a time, we maximize our cache efficiency. If instead we fetched the entire subset that the caller wanted, there would be a different cache key for every possible subset. This rule also guarantees exact reproducibility. If all caches have been removed, `coursier fetch` will re-download the artifact, and this rule will confirm that what was downloaded matches exactly (by content digest) what was specified in the lockfile (what Coursier originally downloaded). """ # Prepare any URL- or JAR-specifying entries for use with Coursier req: ArtifactRequirement if request.pants_address: targets = await Get( Targets, UnparsedAddressInputs([request.pants_address], owning_address=None, description_of_origin="TODO(#14468)"), ) req = ArtifactRequirement(request.coord, jar=targets[0][JvmArtifactJarSourceField]) else: req = ArtifactRequirement(request.coord, url=request.remote_url) coursier_resolve_info = await Get( CoursierResolveInfo, ArtifactRequirements([req]), ) coursier_report_file_name = "coursier_report.json" process_result = await Get( ProcessResult, CoursierFetchProcess( args=( coursier_report_file_name, "--intransitive", *coursier_resolve_info.argv, ), input_digest=coursier_resolve_info.digest, output_directories=("classpath", ), output_files=(coursier_report_file_name, ), description= f"Fetching with coursier: {request.coord.to_coord_str()}", ), ) report_digest = await Get( Digest, DigestSubset(process_result.output_digest, PathGlobs([coursier_report_file_name]))) report_contents = await Get(DigestContents, Digest, report_digest) report = json.loads(report_contents[0].content) report_deps = report["dependencies"] if len(report_deps) == 0: raise CoursierError( "Coursier fetch report has no dependencies (i.e. nothing was fetched)." ) elif len(report_deps) > 1: raise CoursierError( "Coursier fetch report has multiple dependencies, but exactly 1 was expected." ) dep = report_deps[0] resolved_coord = Coordinate.from_coord_str(dep["coord"]) if resolved_coord != request.coord: raise CoursierError( f'Coursier resolved coord "{resolved_coord.to_coord_str()}" does not match requested coord "{request.coord.to_coord_str()}".' ) classpath_dest_name = classpath_dest_filename(dep["coord"], dep["file"]) classpath_dest = f"classpath/{classpath_dest_name}" resolved_file_digest = await Get( Digest, DigestSubset(process_result.output_digest, PathGlobs([classpath_dest]))) stripped_digest = await Get( Digest, RemovePrefix(resolved_file_digest, "classpath")) file_digest = await Get( FileDigest, ExtractFileDigest(stripped_digest, classpath_dest_name), ) if file_digest != request.file_digest: raise CoursierError( f"Coursier fetch for '{resolved_coord}' succeeded, but fetched artifact {file_digest} did not match the expected artifact: {request.file_digest}." ) return ClasspathEntry(digest=stripped_digest, filenames=(classpath_dest_name, ))
def test_compile_with_maven_deps(rule_runner: RuleRunner) -> None: joda_coord = Coordinate(group="joda-time", artifact="joda-time", version="2.10.10") resolved_joda_lockfile = TestCoursierWrapper.new( entries=(CoursierLockfileEntry( coord=joda_coord, file_name="joda-time-2.10.10.jar", direct_dependencies=Coordinates([]), dependencies=Coordinates([]), file_digest=FileDigest( fingerprint= "dd8e7c92185a678d1b7b933f31209b6203c8ffa91e9880475a1be0346b9617e3", serialized_bytes_length=644419, ), ), )) rule_runner.write_files({ "BUILD": dedent("""\ jvm_artifact( name = "joda-time_joda-time", group = "joda-time", artifact = "joda-time", version = "2.10.10", ) java_sources( name = 'main', dependencies = [ ':joda-time_joda-time', ] ) """), "3rdparty/jvm/default.lock": resolved_joda_lockfile.serialize( [ArtifactRequirement(coordinate=joda_coord)]), "Example.java": dedent(""" package org.pantsbuild.example; import org.joda.time.DateTime; public class Example { public static void main(String[] args) { DateTime dt = new DateTime(); System.out.println(dt.getYear()); } } """), }) request = CompileJavaSourceRequest( component=expect_single_expanded_coarsened_target( rule_runner, Address(spec_path="", target_name="main")), resolve=make_resolve(rule_runner), ) classpath = rule_runner.request(RenderedClasspath, [request]) assert classpath.content == { ".Example.java.main.javac.jar": {"org/pantsbuild/example/Example.class"} }