Extend Relocator with rewriting of services
Bug: 155175610
Bug: 150829112
Change-Id: I9b0c6b63d551d7ee745a39ee4c6ba3a494f7e37e
diff --git a/src/main/java/com/android/tools/r8/relocator/Relocator.java b/src/main/java/com/android/tools/r8/relocator/Relocator.java
index 5c3d003..2a817ef 100644
--- a/src/main/java/com/android/tools/r8/relocator/Relocator.java
+++ b/src/main/java/com/android/tools/r8/relocator/Relocator.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.dex.Marker.Tool;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppServices;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexString;
@@ -92,6 +93,7 @@
new SimplePackagesRewritingMapper(command.getMapping(), options);
AppView<?> appView = AppView.createForRelocator(appInfo, options, rewritePrefix);
+ appView.setAppServices(AppServices.builder(appView).build());
// Pre-compute all mappings, such that we can use it in the generic signature rewriter.
// TODO(b/129925954, b/124726014): Remove when done.
diff --git a/src/main/java/com/android/tools/r8/relocator/RelocatorCommand.java b/src/main/java/com/android/tools/r8/relocator/RelocatorCommand.java
index 4544cae..4a19677 100644
--- a/src/main/java/com/android/tools/r8/relocator/RelocatorCommand.java
+++ b/src/main/java/com/android/tools/r8/relocator/RelocatorCommand.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.origin.PathOrigin;
import com.android.tools.r8.references.PackageReference;
import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.shaking.ProguardConfiguration;
import com.android.tools.r8.utils.AbortException;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.ExceptionDiagnostic;
@@ -136,11 +137,16 @@
}
public InternalOptions getInternalOptions() {
- InternalOptions options = new InternalOptions(factory, getReporter());
+ // We are using the proguard configuration for adapting resources.
+ InternalOptions options =
+ new InternalOptions(
+ ProguardConfiguration.builder(factory, getReporter()).build(), getReporter());
assert options.threadCount == ThreadUtils.NOT_SPECIFIED;
options.relocatorCompilation = true;
options.threadCount = getThreadCount();
options.programConsumer = consumer;
+ assert consumer != null;
+ options.dataResourceConsumer = consumer.getDataResourceConsumer();
// Set debug to ensure that we are writing all information to the application writer.
options.debug = true;
// We need to read stack maps since we are not processing anything.
@@ -161,7 +167,6 @@
ImmutableMap.builder();
private ClassFileConsumer consumer = null;
private int threadCount = ThreadUtils.NOT_SPECIFIED;
- private Path outputPath = null;
private boolean printVersion;
private boolean printHelp;
@@ -178,8 +183,21 @@
this.reporter = builder.getReporter();
}
+ /**
+ * Setting output to a path.
+ *
+ * <p>Setting the output path will override any previous set consumer or any previous set output
+ * path.
+ *
+ * @param outputPath Output path to write output to. A null argument will clear the program
+ * consumer / output.
+ */
public Builder setOutputPath(Path outputPath) {
- this.outputPath = outputPath;
+ if (outputPath == null) {
+ this.consumer = null;
+ return this;
+ }
+ this.consumer = new ArchiveConsumer(outputPath, true);
return this;
}
@@ -249,21 +267,20 @@
/**
* Set the program consumer.
*
- * <p>Setting the ClassFile consumer will override any previous set consumer or any previous set
- * output path & mode.
+ * <p>Setting the program consumer will override any previous set consumer or any previous set
+ * output path.
*
* @param consumer ClassFile consumer to set as current. A null argument will clear the program
* consumer / output.
*/
public Builder setConsumer(ClassFileConsumer consumer) {
// Setting an explicit program consumer resets any output-path/mode setup.
- outputPath = null;
this.consumer = consumer;
return this;
}
private void validate() {
- if (consumer == null && outputPath == null) {
+ if (consumer == null) {
reporter.error(new StringDiagnostic("No output path or consumer has been specified"));
}
}
@@ -277,9 +294,6 @@
validate();
reporter.failIfPendingErrors();
DexItemFactory factory = new DexItemFactory();
- if (consumer == null) {
- consumer = new ArchiveConsumer(outputPath);
- }
return new RelocatorCommand(
mapping.build(), app.build(), reporter, factory, consumer, threadCount);
} catch (AbortException e) {
@@ -333,7 +347,6 @@
private static Builder parse(String[] args, Origin origin, Builder builder) {
String[] expandedArgs = FlagFile.expandFlagFiles(args, builder::error);
Path outputPath = null;
- Path mapping = null;
for (int i = 0; i < expandedArgs.length; i++) {
String arg = expandedArgs[i].trim();
String nextArg = null;
diff --git a/src/test/java/com/android/tools/r8/relocator/RelocatorServiceLoaderTest.java b/src/test/java/com/android/tools/r8/relocator/RelocatorServiceLoaderTest.java
new file mode 100644
index 0000000..99ac759
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/relocator/RelocatorServiceLoaderTest.java
@@ -0,0 +1,112 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.relocator;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertNotNull;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ZipUtils;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.Scanner;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RelocatorServiceLoaderTest extends TestBase {
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ public RelocatorServiceLoaderTest(TestParameters parameters) {}
+
+ @Test
+ public void testRewritingService()
+ throws IOException, CompilationFailedException, ResourceException {
+ File testJar = temp.newFile("test.jar");
+ Path testJarPath = testJar.toPath();
+ OpenOption[] options =
+ new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
+ try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(testJarPath, options))) {
+ ZipUtils.writeToZipStream(
+ out,
+ "META-INF/services/foo.bar.Baz",
+ StringUtils.lines("foo.bar.BazImpl", "foo.baz.OtherImpl").getBytes(),
+ ZipEntry.STORED);
+ }
+ ZipFile zip = new ZipFile(testJar);
+ assertNotNull(zip.getEntry("META-INF/services/foo.bar.Baz"));
+ Path relocatedJar = temp.newFile("out.jar").toPath();
+ Relocator.run(
+ RelocatorCommand.builder()
+ .addProgramFile(testJarPath)
+ .setOutputPath(relocatedJar)
+ .addPackageMapping(
+ Reference.packageFromString("foo.bar"), Reference.packageFromString("baz.qux"))
+ .build());
+ zip = new ZipFile(relocatedJar.toFile());
+ ZipEntry serviceEntry = zip.getEntry("META-INF/services/baz.qux.Baz");
+ assertNotNull(serviceEntry);
+ InputStream inputStream = zip.getInputStream(serviceEntry);
+ Scanner scanner = new Scanner(inputStream);
+ assertEquals("baz.qux.BazImpl", scanner.next());
+ assertEquals("foo.baz.OtherImpl", scanner.next());
+ assertFalse(scanner.hasNext());
+ }
+
+ @Test
+ public void testRewritingServiceImpl()
+ throws IOException, CompilationFailedException, ResourceException {
+ File testJar = temp.newFile("test.jar");
+ Path testJarPath = testJar.toPath();
+ OpenOption[] options =
+ new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
+ try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(testJarPath, options))) {
+ ZipUtils.writeToZipStream(
+ out,
+ "META-INF/services/foo.bar.Baz",
+ StringUtils.lines("foo.bar.BazImpl", "foo.baz.OtherImpl").getBytes(),
+ ZipEntry.STORED);
+ }
+ ZipFile zip = new ZipFile(testJar);
+ assertNotNull(zip.getEntry("META-INF/services/foo.bar.Baz"));
+ Path relocatedJar = temp.newFile("out.jar").toPath();
+ Relocator.run(
+ RelocatorCommand.builder()
+ .addProgramFile(testJarPath)
+ .setOutputPath(relocatedJar)
+ .addPackageMapping(
+ Reference.packageFromString("foo.baz"), Reference.packageFromString("baz.qux"))
+ .build());
+ zip = new ZipFile(relocatedJar.toFile());
+ ZipEntry serviceEntry = zip.getEntry("META-INF/services/foo.bar.Baz");
+ assertNotNull(serviceEntry);
+ InputStream inputStream = zip.getInputStream(serviceEntry);
+ Scanner scanner = new Scanner(inputStream);
+ assertEquals("foo.bar.BazImpl", scanner.next());
+ assertEquals("baz.qux.OtherImpl", scanner.next());
+ assertFalse(scanner.hasNext());
+ }
+}