diff --git a/build/allowed_deps.txt b/build/allowed_deps.txt index d58838d..c2dfc5c 100644 --- a/build/allowed_deps.txt +++ b/build/allowed_deps.txt @@ -15,6 +15,7 @@ android.hardware.audio.common-V1-ndk(minSdkVersion:31) android.hardware.audio.common@5.0(minSdkVersion:30) +android.hardware.bluetooth-V1-ndk(minSdkVersion:33) android.hardware.bluetooth.a2dp@1.0(minSdkVersion:30) android.hardware.bluetooth.audio-V1-ndk(minSdkVersion:31) android.hardware.bluetooth.audio-V2-ndk(minSdkVersion:31) @@ -63,6 +64,7 @@ android.hardware.neuralnetworks@1.1(minSdkVersion:30) android.hardware.neuralnetworks@1.2(minSdkVersion:30) android.hardware.neuralnetworks@1.3(minSdkVersion:30) android.hardware.radio-V1.0-java(minSdkVersion:current) +android.hardware.radio.sap-V1-java(minSdkVersion:33) android.hardware.security.rkp-V3-java(minSdkVersion:33) android.hardware.tetheroffload.config-V1.0-java(minSdkVersion:current) android.hardware.tetheroffload.control-V1.0-java(minSdkVersion:current) @@ -130,6 +132,7 @@ androidx.collection_collection(minSdkVersion:24) androidx.collection_collection-jvm(minSdkVersion:24) androidx.collection_collection-ktx(minSdkVersion:24) androidx.concurrent_concurrent-futures(minSdkVersion:24) +androidx.constraintlayout_constraintlayout-core(minSdkVersion:24) androidx.coordinatorlayout_coordinatorlayout(minSdkVersion:14) androidx.core_core(minSdkVersion:14) androidx.core_core-ktx(minSdkVersion:14) @@ -544,6 +547,8 @@ libhidlmemory(minSdkVersion:29) libhwbinder-impl-internal(minSdkVersion:29) libhwbinder_headers(minSdkVersion:29) libidna(minSdkVersion:29) +libimapper_providerutils(minSdkVersion:29) +libimapper_stablec(minSdkVersion:29) libion(minSdkVersion:29) libip_checksum(minSdkVersion:30) libjni(minSdkVersion:29) @@ -747,7 +752,9 @@ libunwindstack(minSdkVersion:29) liburl(minSdkVersion:29) libutf(minSdkVersion:(no version)) libutf(minSdkVersion:14) +libutils(minSdkVersion:29) libutils(minSdkVersion:apex_inherit) +libutils_headers(minSdkVersion:29) libutils_headers(minSdkVersion:apex_inherit) libuwb_uci_packets(minSdkVersion:Tiramisu) libvorbisidec(minSdkVersion:29) @@ -839,6 +846,7 @@ networkstack-aidl-interfaces-V12-java(minSdkVersion:29) networkstack-aidl-interfaces-V13-java(minSdkVersion:29) networkstack-aidl-interfaces-V14-java(minSdkVersion:29) networkstack-aidl-interfaces-V15-java(minSdkVersion:29) +networkstack-aidl-interfaces-V16-java(minSdkVersion:29) networkstack-aidl-latest(minSdkVersion:29) networkstack-client(minSdkVersion:29) NetworkStackApi29Shims(minSdkVersion:29) @@ -864,6 +872,11 @@ note_memtag_heap_async(minSdkVersion:16) note_memtag_heap_sync(minSdkVersion:16) offlinelocationtimezoneprovider(minSdkVersion:31) okhttp(minSdkVersion:31) +okio-lib(minSdkVersion:30) +opencensus-java-api(minSdkVersion:33) +opencensus-java-api(minSdkVersion:current) +opencensus-java-contrib-grpc-metrics(minSdkVersion:33) +opencensus-java-contrib-grpc-metrics(minSdkVersion:current) OsuLoginGoogle(minSdkVersion:30) perfetto_trace_protos(minSdkVersion:S) PermissionController(minSdkVersion:28) @@ -911,6 +924,7 @@ prebuilt_androidx.collection_collection-nodeps(minSdkVersion:24) prebuilt_androidx.collection_collection-nodeps(minSdkVersion:30) prebuilt_androidx.collection_collection-nodeps(minSdkVersion:current) prebuilt_androidx.concurrent_concurrent-futures-nodeps(minSdkVersion:24) +prebuilt_androidx.constraintlayout_constraintlayout-core-nodeps(minSdkVersion:24) prebuilt_androidx.coordinatorlayout_coordinatorlayout-nodeps(minSdkVersion:(no version)) prebuilt_androidx.coordinatorlayout_coordinatorlayout-nodeps(minSdkVersion:14) prebuilt_androidx.core_core-ktx-nodeps(minSdkVersion:(no version)) diff --git a/java/com/android/modules/targetprep/Android.bp b/java/com/android/modules/targetprep/Android.bp index 0e4858e..87b0651 100644 --- a/java/com/android/modules/targetprep/Android.bp +++ b/java/com/android/modules/targetprep/Android.bp @@ -24,4 +24,12 @@ java_library_host { "compatibility-host-util", "androidx.annotation_annotation", ], + static_libs: [ + "compat-classpaths-testing", + "classpath_classes_proto_java" + ], + visibility: [ + "//packages/modules/common:__subpackages__", + "//packages/modules/SdkExtensions", + ], } \ No newline at end of file diff --git a/java/com/android/modules/targetprep/ClasspathFetcher.java b/java/com/android/modules/targetprep/ClasspathFetcher.java index c4de7a3..7838343 100644 --- a/java/com/android/modules/targetprep/ClasspathFetcher.java +++ b/java/com/android/modules/targetprep/ClasspathFetcher.java @@ -16,19 +16,37 @@ package com.android.modules.targetprep; +import static android.compat.testing.Classpaths.ClasspathType.BOOTCLASSPATH; +import static android.compat.testing.Classpaths.ClasspathType.SYSTEMSERVERCLASSPATH; + +import android.compat.testing.Classpaths; +import android.compat.testing.Classpaths.ClasspathType; + +import com.android.modules.proto.ClasspathClasses.Classpath; +import com.android.modules.proto.ClasspathClasses.ClasspathClassesDump; +import com.android.modules.proto.ClasspathClasses.ClasspathEntry; +import com.android.modules.proto.ClasspathClasses.Jar; + import com.android.tradefed.config.Option; import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.INativeDevice; import com.android.tradefed.device.ITestDevice; import com.android.tradefed.invoker.TestInformation; import com.android.tradefed.log.LogUtil.CLog; import com.android.tradefed.targetprep.BaseTargetPreparer; import com.android.tradefed.targetprep.TargetSetupError; import com.android.tradefed.util.RunUtil; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Objects; + +import org.jf.dexlib2.iface.ClassDef; /* * Target preparer that fetches classpath relevant artifacts for a test in a 'reentrant' manner. @@ -54,20 +72,43 @@ import java.nio.file.Path; public class ClasspathFetcher extends BaseTargetPreparer { public static final String DEVICE_JAR_ARTIFACTS_TAG = "device-jar-artifacts"; + public static final String BCP_CLASSES_FILE = "bcp.pb"; + public static final String SSCP_CLASSES_FILE = "sscp.pb"; + + // TODO(andreionea): also fetch classes for standalone system server jars, apk-in-apex and + // shared libraries. They require more mocking on the test side. + + public static final String APEX_PKG_TAG = "apex-package"; + // Special case for fetching only non-updatable platform. + public static final String PLATFORM_PACKAGE = "platform"; + + @Option(name = "apex-package", + description = "The package name of the apex under test.") + private String mApexPackage; private boolean mFetchedArtifacts = false; @Override public void setUp(TestInformation testInfo) throws TargetSetupError, DeviceNotAvailableException { + Objects.requireNonNull(testInfo.getDevice()); + if (mApexPackage != null) { + testInfo.properties().put(APEX_PKG_TAG, mApexPackage); + } // The artifacts have been fetched already, no need to do anything else. if (testInfo.properties().containsKey(DEVICE_JAR_ARTIFACTS_TAG)) { return; } try { final Path tmpDir = Files.createTempDirectory("device_artifacts"); - testInfo.properties().put(DEVICE_JAR_ARTIFACTS_TAG, tmpDir.toAbsolutePath().toString()); - // TODO(b/254647172): Fetch data + testInfo.properties().put(DEVICE_JAR_ARTIFACTS_TAG, + tmpDir.toAbsolutePath().toString()); + + getClassesInClasspath(testInfo.getDevice(), BOOTCLASSPATH) + .writeTo(new FileOutputStream(new File(tmpDir.toFile(), BCP_CLASSES_FILE))); + getClassesInClasspath(testInfo.getDevice(), SYSTEMSERVERCLASSPATH) + .writeTo(new FileOutputStream(new File(tmpDir.toFile(), SSCP_CLASSES_FILE))); + mFetchedArtifacts = true; } catch(IOException e) { throw new RuntimeException("Could not create temp artifacts dir!", e); @@ -84,13 +125,70 @@ public class ClasspathFetcher extends BaseTargetPreparer { + " artifacts, but the DEVICE_JAR_ARTIFACTS_TAG property was removed"); } final File jarArtifactsDir = new File(path); - if (!jarArtifactsDir.delete()) { - throw new RuntimeException("Failed to remove jar artifacts dir!"); - } + deleteDirectory(jarArtifactsDir); } finally { testInfo.properties().remove(DEVICE_JAR_ARTIFACTS_TAG); } } } + private Classpath classpathTypeToClasspathEnum(ClasspathType t) { + switch(t) { + case BOOTCLASSPATH: + return Classpath.valueOf(Classpath.BOOTCLASSPATH_VALUE); + case SYSTEMSERVERCLASSPATH: + return Classpath.valueOf(Classpath.SYSTEMSERVERCLASSPATH_VALUE); + default: + throw new RuntimeException("Unknown classpath type " + t); + } + } + + private ImmutableSet getClassesInFile(INativeDevice device, String file) + throws DeviceNotAvailableException, IOException { + final File jar = device.pullFile(file); + if (jar == null) { + throw new IllegalStateException("could not pull remote file " + file); + } + return Classpaths.getClassDefsFromJar(jar) + .stream() + .map(ClassDef::getType) + .collect(ImmutableSet.toImmutableSet()); + } + + private ClasspathClassesDump getClassesInClasspath(INativeDevice device, ClasspathType type) + throws DeviceNotAvailableException, IOException { + ClasspathClassesDump.Builder builder = ClasspathClassesDump.newBuilder(); + final ImmutableList jars = Classpaths.getJarsOnClasspath(device, type); + for (String jar : jars) { + ClasspathEntry.Builder entryBuilder = ClasspathEntry.newBuilder(); + Jar.Builder jarBuilder = Jar.newBuilder(); + jarBuilder.setClasspath(classpathTypeToClasspathEnum(type)); + jarBuilder.setPath(jar); + entryBuilder.setJar(jarBuilder.build()); + entryBuilder.addAllClasses(getClassesInFile(device, jar)); + builder.addEntries(entryBuilder.build()); + } + return builder.build(); + } + + + /** + * Deletes a directory and its contents recursively + * + * @param directory to delete + */ + private static void deleteDirectory(File directory) { + File[] files = directory.listFiles(); + if (files != null) { + for (File file : files) { + if (!file.isDirectory()) { + file.delete(); + } else { + deleteDirectory(file); + } + } + } + directory.delete(); + } + } \ No newline at end of file diff --git a/javatests/com/android/modules/conformanceframework/Android.bp b/javatests/com/android/modules/conformanceframework/Android.bp new file mode 100644 index 0000000..c8c2e6b --- /dev/null +++ b/javatests/com/android/modules/conformanceframework/Android.bp @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +java_test_host { + name: "ConformanceFrameworkTests", + srcs: ["*.java"], + static_libs: [ + "junit", + "ClasspathFetcher", + "truth-prebuilt", + "objenesis", + ], + libs: [ + "cts-tradefed", + "tradefed", + "compatibility-host-util", + ], + test_suites: [ + "general-tests", + ], +} diff --git a/javatests/com/android/modules/conformanceframework/DuplicateClassesTest.java b/javatests/com/android/modules/conformanceframework/DuplicateClassesTest.java new file mode 100644 index 0000000..f10cc52 --- /dev/null +++ b/javatests/com/android/modules/conformanceframework/DuplicateClassesTest.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.modules.conformanceframework; + + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assume.assumeTrue; + +import com.android.modules.proto.ClasspathClasses.ClasspathClassesDump; +import com.android.modules.proto.ClasspathClasses.ClasspathEntry; +import com.android.modules.proto.ClasspathClasses.Jar; +import com.android.modules.targetprep.ClasspathFetcher; +import com.android.modules.utils.build.testing.DeviceSdkLevel; +import com.android.tradefed.config.Option; +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.invoker.TestInformation; +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; +import com.android.tradefed.testtype.junit4.BeforeClassWithInfo; +import com.android.tradefed.testtype.junit4.DeviceTestRunOptions; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; + +import org.jf.dexlib2.iface.ClassDef; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + + + +/** + * Tests for detecting no duplicate class files are present on BOOTCLASSPATH and + * SYSTEMSERVERCLASSPATH. + * + *

Duplicate class files are not safe as some of the jars on *CLASSPATH are updated outside of + * the main dessert release cycle; they also contribute to unnecessary disk space usage. + */ +@RunWith(DeviceJUnit4ClassRunner.class) +public class DuplicateClassesTest extends BaseHostJUnit4Test { + private static ImmutableSet sBootclasspathJars; + private static ImmutableSet sSystemserverclasspathJars; + + private static ImmutableMultimap sJarsToClasses; + private static String sApexPackage; + + private DeviceSdkLevel mDeviceSdkLevel; + + /** + * Fetch all classpath info extracted by ClasspathFetcher. + * + */ + @BeforeClassWithInfo + public static void setupOnce(TestInformation testInfo) throws Exception { + final String dctArtifactsPath = Objects.requireNonNull( + testInfo.properties().get(ClasspathFetcher.DEVICE_JAR_ARTIFACTS_TAG)); + sApexPackage = testInfo.properties().get(ClasspathFetcher.APEX_PKG_TAG); + final ImmutableMultimap.Builder jarsToClasses = + new ImmutableMultimap.Builder<>(); + final File bcpDumpFile = new File(dctArtifactsPath, ClasspathFetcher.BCP_CLASSES_FILE); + final ClasspathClassesDump bcpDump = + ClasspathClassesDump.parseFrom(new FileInputStream(bcpDumpFile)); + sBootclasspathJars = bcpDump.getEntriesList().stream() + .map(entry -> entry.getJar().getPath()) + .collect(ImmutableSet.toImmutableSet()); + bcpDump.getEntriesList().stream() + .forEach(entry -> { + jarsToClasses.putAll(entry.getJar().getPath(), entry.getClassesList()); + }); + final File sscpDumpFile = new File(dctArtifactsPath, ClasspathFetcher.SSCP_CLASSES_FILE); + final ClasspathClassesDump sscpDump = + ClasspathClassesDump.parseFrom(new FileInputStream(sscpDumpFile)); + sSystemserverclasspathJars = sscpDump.getEntriesList().stream() + .map(entry -> entry.getJar().getPath()) + .collect(ImmutableSet.toImmutableSet()); + sscpDump.getEntriesList().stream() + .forEach(entry -> { + jarsToClasses.putAll(entry.getJar().getPath(), entry.getClassesList()); + }); + sJarsToClasses = jarsToClasses.build(); + } + + @Before + public void setup() { + mDeviceSdkLevel = new DeviceSdkLevel(getDevice()); + } + + /** + * Ensure that there are no duplicate classes among jars listed in BOOTCLASSPATH. + */ + @Test + public void testBootclasspath_nonDuplicateClasses() throws Exception { + assumeTrue(mDeviceSdkLevel.isDeviceAtLeastR()); + assertThat(getDuplicateClasses(sBootclasspathJars)).isEmpty(); + } + + /** + * Ensure that there are no duplicate classes among jars listed in SYSTEMSERVERCLASSPATH. + */ + @Test + public void testSystemserverClasspath_nonDuplicateClasses() throws Exception { + assumeTrue(mDeviceSdkLevel.isDeviceAtLeastR()); + assertThat(getDuplicateClasses(sSystemserverclasspathJars)).isEmpty(); + } + + /** + * Ensure that there are no duplicate classes among jars listed in BOOTCLASSPATH and + * SYSTEMSERVERCLASSPATH. + */ + @Test + public void testSystemserverAndBootClasspath_nonDuplicateClasses() throws Exception { + assumeTrue(mDeviceSdkLevel.isDeviceAtLeastR()); + final ImmutableSet.Builder jars = new ImmutableSet.Builder<>(); + jars.addAll(sBootclasspathJars); + jars.addAll(sSystemserverclasspathJars); + assertThat(getDuplicateClasses(jars.build())).isEmpty(); + } + + /** + * Gets the duplicate classes within a list of jar files. + * + * @param jars a list of jar files. + * @return a multimap with the class name as a key and the jar files as a value. + */ + private Multimap getDuplicateClasses(ImmutableCollection jars) { + final HashMultimap allClasses = HashMultimap.create(); + Multimaps.invertFrom(Multimaps.filterKeys(sJarsToClasses, jars::contains), allClasses); + return Multimaps.filterKeys(allClasses, key -> validDuplicates(allClasses.get(key))); + } + + /** + * Filtering function for excluding invalid / uninteresting duplicates. + * + * This will filter out classes that are in only 1 jar, or duplicates that + * do not include jars in the apex under test. + */ + + private boolean validDuplicates(Collection duplicateJars) { + if (duplicateJars.size() <= 1) { + return false; + } + if (sApexPackage.equals(ClasspathFetcher.PLATFORM_PACKAGE)) { + return duplicateJars.stream() + .anyMatch(jar -> !jar.startsWith("/apex")); + } + final String apexPrefix = "/apex/" + sApexPackage; + return duplicateJars.stream() + .anyMatch(jar -> jar.startsWith(apexPrefix)); + + } +} diff --git a/javatests/com/android/modules/targetprep/Android.bp b/javatests/com/android/modules/targetprep/Android.bp index 827cdb3..baa0219 100644 --- a/javatests/com/android/modules/targetprep/Android.bp +++ b/javatests/com/android/modules/targetprep/Android.bp @@ -34,6 +34,10 @@ java_test_host { "junit", "tradefed", ], + java_resources: [ + ":LibraryA", + ":LibraryB", + ], test_suites: [ "general-tests", ], diff --git a/javatests/com/android/modules/targetprep/ClasspathFetcherTest.java b/javatests/com/android/modules/targetprep/ClasspathFetcherTest.java index 28e4957..586a3c6 100644 --- a/javatests/com/android/modules/targetprep/ClasspathFetcherTest.java +++ b/javatests/com/android/modules/targetprep/ClasspathFetcherTest.java @@ -22,17 +22,30 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; import static org.junit.Assume.assumeTrue; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import com.android.modules.proto.ClasspathClasses.ClasspathClassesDump; +import com.android.modules.proto.ClasspathClasses.ClasspathEntry; import com.android.tradefed.build.IDeviceBuildInfo; import com.android.tradefed.device.ITestDevice; import com.android.tradefed.invoker.IInvocationContext; import com.android.tradefed.invoker.InvocationContext; import com.android.tradefed.invoker.TestInformation; +import com.android.tradefed.util.CommandResult; +import com.android.tradefed.util.CommandStatus; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.List; import org.junit.Assert; import org.junit.Before; @@ -46,6 +59,7 @@ import org.junit.runners.JUnit4; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import org.mockito.stubbing.Answer; @RunWith(JUnit4.class) public class ClasspathFetcherTest { @@ -57,6 +71,9 @@ public class ClasspathFetcherTest { private TestInformation mTestInfo; + private String mBootclasspathJarNames = ""; + private String mSystemServerclasspathJarNames = ""; + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); @@ -64,6 +81,28 @@ public class ClasspathFetcherTest { when(mMockTestDevice.getSerialNumber()).thenReturn(SERIAL); when(mMockTestDevice.getDeviceDescriptor()).thenReturn(null); when(mMockTestDevice.isAppEnumerationSupported()).thenReturn(false); + when(mMockTestDevice.executeShellV2Command(eq("echo $BOOTCLASSPATH"))).then( + invocation -> { + return successfulCommandResult(mBootclasspathJarNames, ""); + } + ); + when(mMockTestDevice.executeShellV2Command(eq("echo $SYSTEMSERVERCLASSPATH"))).then( + invocation -> { + return successfulCommandResult(mSystemServerclasspathJarNames, ""); + } + ); + when(mMockTestDevice.pullFile(anyString())).then( + invocation -> { + final String path = invocation.getArgument(0); + final File tempFile = File.createTempFile(path, null); + + try (InputStream is = + ClasspathFetcherTest.class.getClassLoader().getResourceAsStream(path)) { + Files.copy(is, tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + } + return tempFile; + } + ); IInvocationContext context = new InvocationContext(); context.addAllocatedDevice("device", mMockTestDevice); context.addDeviceBuildInfo("device", mMockBuildInfo); @@ -72,6 +111,8 @@ public class ClasspathFetcherTest { @Test public void testSingleArtifactFetcher() throws Exception { + mBootclasspathJarNames = "LibraryA.jar"; + mSystemServerclasspathJarNames = "LibraryB.jar"; final ClasspathFetcher fetcher = new ClasspathFetcher(); fetcher.setUp(mTestInfo); assertThat(mTestInfo.properties().containsKey(DEVICE_JAR_ARTIFACTS_TAG)).isTrue(); @@ -81,6 +122,8 @@ public class ClasspathFetcherTest { @Test public void testMultipleArtifactFetchers() throws Exception { + mBootclasspathJarNames = "LibraryA.jar"; + mSystemServerclasspathJarNames = "LibraryB.jar"; final ClasspathFetcher fetcher1 = new ClasspathFetcher(); final ClasspathFetcher fetcher2 = new ClasspathFetcher(); @@ -92,4 +135,68 @@ public class ClasspathFetcherTest { fetcher1.tearDown(mTestInfo, null); assertThat(mTestInfo.properties().containsKey(DEVICE_JAR_ARTIFACTS_TAG)).isFalse(); } + + @Test + public void testFetchCorrectBcpClasses() throws Exception { + mBootclasspathJarNames = "LibraryA.jar"; + mSystemServerclasspathJarNames = "LibraryB.jar"; + final ClasspathFetcher fetcher = new ClasspathFetcher(); + + try { + fetcher.setUp(mTestInfo); + + final File bcpProto = new File(mTestInfo.properties().get(DEVICE_JAR_ARTIFACTS_TAG), + ClasspathFetcher.BCP_CLASSES_FILE); + assertThat(bcpProto.exists()).isTrue(); + ClasspathClassesDump dump = + ClasspathClassesDump.parseFrom(new FileInputStream(bcpProto)); + List entries = dump.getEntriesList(); + assertThat(entries.size()).isEqualTo(1); + ClasspathEntry entry = entries.get(0); + assertThat(entry.hasJar()).isTrue(); + assertThat(entry.getJar().getPath()).isEqualTo("LibraryA.jar"); + assertThat(entry.getClassesList().size()).isEqualTo(1); + assertThat(entry.getClassesList().get(0)) + .isEqualTo("Lcom/android/modules/targetprep/android/A;"); + } finally { + fetcher.tearDown(mTestInfo, null); + } + } + + @Test + public void testFetchCorrectSscpClasses() throws Exception { + mBootclasspathJarNames = "LibraryA.jar"; + mSystemServerclasspathJarNames = "LibraryB.jar"; + final ClasspathFetcher fetcher = new ClasspathFetcher(); + + try { + fetcher.setUp(mTestInfo); + + final File sscpProto = new File(mTestInfo.properties().get(DEVICE_JAR_ARTIFACTS_TAG), + ClasspathFetcher.SSCP_CLASSES_FILE); + assertThat(sscpProto.exists()).isTrue(); + ClasspathClassesDump dump = + ClasspathClassesDump.parseFrom(new FileInputStream(sscpProto)); + List entries = dump.getEntriesList(); + assertThat(entries.size()).isEqualTo(1); + ClasspathEntry entry = entries.get(0); + assertThat(entry.hasJar()).isTrue(); + assertThat(entry.getJar().getPath()).isEqualTo("LibraryB.jar"); + assertThat(entry.getClassesList().size()).isEqualTo(1); + assertThat(entry.getClassesList().get(0)) + .isEqualTo("Lcom/android/modules/targetprep/android/B;"); + } finally { + fetcher.tearDown(mTestInfo, null); + } + } + + private static CommandResult successfulCommandResult(String stdout, String stderr) { + final CommandResult result = new CommandResult(); + result.setStatus(CommandStatus.SUCCESS); + result.setExitCode(0); + result.setStdout(stdout); + result.setStderr(stderr); + return result; + } + } diff --git a/javatests/com/android/modules/targetprep/android/A.java b/javatests/com/android/modules/targetprep/android/A.java new file mode 100644 index 0000000..5ab967f --- /dev/null +++ b/javatests/com/android/modules/targetprep/android/A.java @@ -0,0 +1,3 @@ +package com.android.modules.targetprep.android; + +public class A {} \ No newline at end of file diff --git a/javatests/com/android/modules/targetprep/android/Android.bp b/javatests/com/android/modules/targetprep/android/Android.bp new file mode 100644 index 0000000..e5607dc --- /dev/null +++ b/javatests/com/android/modules/targetprep/android/Android.bp @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +java_library { + name: "LibraryA", + srcs: ["A.java"], + installable: true, +} + +java_library { + name: "LibraryB", + srcs: ["B.java"], + installable: true, +} diff --git a/javatests/com/android/modules/targetprep/android/B.java b/javatests/com/android/modules/targetprep/android/B.java new file mode 100644 index 0000000..80daba6 --- /dev/null +++ b/javatests/com/android/modules/targetprep/android/B.java @@ -0,0 +1,3 @@ +package com.android.modules.targetprep.android; + +public class B {} \ No newline at end of file diff --git a/javatests/com/android/modules/updatablesharedlibs/apps/targetS/Android.bp b/javatests/com/android/modules/updatablesharedlibs/apps/targetS/Android.bp index 24c495b..c714267 100644 --- a/javatests/com/android/modules/updatablesharedlibs/apps/targetS/Android.bp +++ b/javatests/com/android/modules/updatablesharedlibs/apps/targetS/Android.bp @@ -14,6 +14,7 @@ package { default_applicable_licenses: ["Android-Apache-2.0"], + default_visibility: ["//packages/modules/common/javatests:__subpackages__"], } android_test_helper_app { @@ -25,5 +26,6 @@ android_test_helper_app { "truth-prebuilt", ], sdk_version: "current", + min_sdk_version: "31", target_sdk_version: "31", } diff --git a/javatests/com/android/modules/updatablesharedlibs/apps/targetT/Android.bp b/javatests/com/android/modules/updatablesharedlibs/apps/targetT/Android.bp index 70a549e..2649a95 100644 --- a/javatests/com/android/modules/updatablesharedlibs/apps/targetT/Android.bp +++ b/javatests/com/android/modules/updatablesharedlibs/apps/targetT/Android.bp @@ -25,5 +25,6 @@ android_test_helper_app { "truth-prebuilt", ], sdk_version: "current", + min_sdk_version: "Tiramisu", target_sdk_version: "Tiramisu", } diff --git a/javatests/com/android/modules/updatablesharedlibs/apps/targetTWithLib/Android.bp b/javatests/com/android/modules/updatablesharedlibs/apps/targetTWithLib/Android.bp index 890af11..f3e7029 100644 --- a/javatests/com/android/modules/updatablesharedlibs/apps/targetTWithLib/Android.bp +++ b/javatests/com/android/modules/updatablesharedlibs/apps/targetTWithLib/Android.bp @@ -29,5 +29,6 @@ android_test_helper_app { "androidx.test.core", ], sdk_version: "current", + min_sdk_version: "Tiramisu", target_sdk_version: "Tiramisu", } diff --git a/sdk/ModuleDefaults.bp b/sdk/ModuleDefaults.bp index d83fcc8..34ccf4b 100644 --- a/sdk/ModuleDefaults.bp +++ b/sdk/ModuleDefaults.bp @@ -240,7 +240,10 @@ DCLA_MIN_SDK_VERSION = "31" soong_config_module_type_import { from: "system/apex/Android.bp", - module_types: ["library_linking_strategy_apex_defaults"], + module_types: [ + "library_linking_strategy_apex_defaults", + "library_linking_strategy_cc_defaults", + ], } library_linking_strategy_apex_defaults { @@ -322,3 +325,20 @@ apex_defaults { compressible: true, defaults_visibility: ["//packages/modules:__subpackages__"], } + +APEX_LOWEST_MIN_SDK_VERSION = "29" + +library_linking_strategy_cc_defaults { + name: "apex-lowest-min-sdk-version", + defaults_visibility: [ + "//system/core/libutils", + ], + min_sdk_version: APEX_LOWEST_MIN_SDK_VERSION, + soong_config_variables: { + library_linking_strategy: { + prefer_static: { + min_sdk_version: "apex_inherit", + }, + }, + }, +} diff --git a/tools/finalize_sdk.py b/tools/finalize_sdk.py index b9edc01..87a2b16 100755 --- a/tools/finalize_sdk.py +++ b/tools/finalize_sdk.py @@ -57,7 +57,9 @@ def fetch_artifacts(target, build_id, artifact_path): def repo_for_sdk(filename): module = filename.split('-')[0] target_dir = '' + if module == 'btservices': return Path('prebuilts/module_sdk/Bluetooth') if module == 'media': return Path('prebuilts/module_sdk/Media') + if module == 'rkpd': return Path('prebuilts/module_sdk/RemoteKeyProvisioning') if module == 'tethering': return Path('prebuilts/module_sdk/Connectivity') for dir in os.listdir('prebuilts/module_sdk/'): if module.lower() in dir.lower():