Merge "Support for -adaptresourcefilecontents"
diff --git a/src/main/java/com/android/tools/r8/DataEntryResource.java b/src/main/java/com/android/tools/r8/DataEntryResource.java
index aa9c73f..b6443a4 100644
--- a/src/main/java/com/android/tools/r8/DataEntryResource.java
+++ b/src/main/java/com/android/tools/r8/DataEntryResource.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.origin.ArchiveEntryOrigin;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.PathOrigin;
+import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@@ -21,6 +22,10 @@
/** Get the bytes of the data entry resource. */
InputStream getByteStream() throws ResourceException;
+ static DataEntryResource fromBytes(byte[] bytes, String name, Origin origin) {
+ return new ByteDataEntryResource(bytes, name, origin);
+ }
+
static DataEntryResource fromFile(Path dir, Path file) {
return new LocalDataEntryResource(dir.resolve(file).toFile(),
file.toString().replace(File.separatorChar, SEPARATOR));
@@ -30,6 +35,34 @@
return new ZipDataEntryResource(zip, entry);
}
+ class ByteDataEntryResource implements DataEntryResource {
+
+ private final byte[] bytes;
+ private final String name;
+ private final Origin origin;
+
+ public ByteDataEntryResource(byte[] bytes, String name, Origin origin) {
+ this.bytes = bytes;
+ this.name = name;
+ this.origin = origin;
+ }
+
+ @Override
+ public InputStream getByteStream() {
+ return new ByteArrayInputStream(bytes);
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public Origin getOrigin() {
+ return origin;
+ }
+ }
+
class ZipDataEntryResource implements DataEntryResource {
private final ZipFile zip;
private final ZipEntry entry;
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 736cb3f..3d18ea3 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -316,21 +316,26 @@
.filter(Objects::nonNull)
.collect(Collectors.toList());
+ ResourceAdapter resourceAdapter =
+ new ResourceAdapter(application.dexItemFactory, graphLense, namingLens, options);
+
for (DataResourceProvider dataResourceProvider : dataResourceProviders) {
try {
- dataResourceProvider.accept(new Visitor() {
- @Override
- public void visit(DataDirectoryResource directory) {
- dataResourceConsumer.accept(directory, options.reporter);
- options.reporter.failIfPendingErrors();
- }
+ dataResourceProvider.accept(
+ new Visitor() {
+ @Override
+ public void visit(DataDirectoryResource directory) {
+ dataResourceConsumer.accept(directory, options.reporter);
+ options.reporter.failIfPendingErrors();
+ }
- @Override
- public void visit(DataEntryResource file) {
- dataResourceConsumer.accept(file, options.reporter);
- options.reporter.failIfPendingErrors();
- }
- });
+ @Override
+ public void visit(DataEntryResource file) {
+ dataResourceConsumer.accept(
+ resourceAdapter.adaptFileContentsIfNeeded(file), options.reporter);
+ options.reporter.failIfPendingErrors();
+ }
+ });
} catch (ResourceException e) {
throw new CompilationError(e.getMessage(), e);
}
diff --git a/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java b/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
new file mode 100644
index 0000000..e325933
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
@@ -0,0 +1,192 @@
+// Copyright (c) 2018, 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.dex;
+
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.ProguardPathFilter;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.io.ByteStreams;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+
+public class ResourceAdapter {
+
+ private final DexItemFactory dexItemFactory;
+ private final GraphLense graphLense;
+ private final NamingLens namingLense;
+ private final InternalOptions options;
+
+ public ResourceAdapter(
+ DexItemFactory dexItemFactory,
+ GraphLense graphLense,
+ NamingLens namingLense,
+ InternalOptions options) {
+ this.dexItemFactory = dexItemFactory;
+ this.graphLense = graphLense;
+ this.namingLense = namingLense;
+ this.options = options;
+ }
+
+ public DataEntryResource adaptFileContentsIfNeeded(DataEntryResource file) {
+ ProguardPathFilter filter = options.proguardConfiguration.getAdaptResourceFileContents();
+ return filter.isEnabled()
+ && !file.getName().toLowerCase().endsWith(FileUtils.CLASS_EXTENSION)
+ && filter.matches(file.getName())
+ ? adaptFileContents(file)
+ : file;
+ }
+
+ // According to the Proguard documentation, the resource files should be parsed and written using
+ // the platform's default character set.
+ private DataEntryResource adaptFileContents(DataEntryResource file) {
+ try (InputStream in = file.getByteStream()) {
+ byte[] bytes = ByteStreams.toByteArray(in);
+ String contents = new String(bytes, Charset.defaultCharset());
+
+ FileContentsAdapter adapter = new FileContentsAdapter(contents);
+ if (adapter.run()) {
+ return DataEntryResource.fromBytes(
+ adapter.getResult().getBytes(Charset.defaultCharset()),
+ file.getName(),
+ file.getOrigin());
+ }
+ } catch (ResourceException e) {
+ options.reporter.error(
+ new StringDiagnostic("Failed to open input: " + e.getMessage(), file.getOrigin()));
+ } catch (Exception e) {
+ options.reporter.error(new ExceptionDiagnostic(e, file.getOrigin()));
+ }
+ return file;
+ }
+
+ private class FileContentsAdapter {
+
+ private final String contents;
+ private final StringBuilder result = new StringBuilder();
+
+ // If any type names in `contents` have been updated. If this flag is still true in the end,
+ // then we can simply use the resource as it was.
+ private boolean changed = false;
+ private int outputFrom = 0;
+ private int position = 0;
+
+ public FileContentsAdapter(String contents) {
+ this.contents = contents;
+ }
+
+ public boolean run() {
+ do {
+ handleMisc();
+ handleJavaType();
+ } while (!eof());
+ if (changed) {
+ // At least one type was renamed. We need to flush all characters in `contents` that follow
+ // the last type that was renamed.
+ outputRangeFromInput(outputFrom, contents.length());
+ } else {
+ // No types were renamed. In this case the adapter should simply have scanned through
+ // `contents`, without outputting anything to `result`.
+ assert outputFrom == 0;
+ assert result.toString().isEmpty();
+ }
+ return changed;
+ }
+
+ public String getResult() {
+ assert changed;
+ return result.toString();
+ }
+
+ // Forwards the cursor until the current character is a Java identifier part.
+ private void handleMisc() {
+ while (!eof() && !Character.isJavaIdentifierPart(contents.charAt(position))) {
+ position++;
+ }
+ }
+
+ // Reads a Java type from the current position in `contents`, and then checks if the given
+ // type has been renamed.
+ private void handleJavaType() {
+ if (eof()) {
+ return;
+ }
+
+ assert Character.isJavaIdentifierPart(contents.charAt(position));
+ int start = position++;
+ while (!eof()) {
+ char currentChar = contents.charAt(position);
+ if (Character.isJavaIdentifierPart(currentChar)) {
+ position++;
+ continue;
+ }
+ if (currentChar == '.'
+ && !eof(position + 1)
+ && Character.isJavaIdentifierPart(contents.charAt(position + 1))) {
+ // Consume the dot and the Java identifier part that follows the dot.
+ position += 2;
+ continue;
+ }
+
+ // Not a valid extension of the type name.
+ break;
+ }
+
+ if ((start > 0 && isDashOrDot(contents.charAt(start - 1)))
+ || (!eof(position) && isDashOrDot(contents.charAt(position)))) {
+ // The Java type starts with '-' or '.', and should not be renamed.
+ return;
+ }
+
+ String javaType = contents.substring(start, position);
+ DexString descriptor =
+ dexItemFactory.lookupString(
+ DescriptorUtils.javaTypeToDescriptorIgnorePrimitives(javaType));
+ DexType dexType = descriptor != null ? dexItemFactory.lookupType(descriptor) : null;
+ if (dexType != null) {
+ DexString renamedDescriptor = namingLense.lookupDescriptor(graphLense.lookupType(dexType));
+ if (!descriptor.equals(renamedDescriptor)) {
+ // Need to flush all changes up to and excluding 'start', and then output the renamed
+ // type.
+ outputRangeFromInput(outputFrom, start);
+ outputString(DescriptorUtils.descriptorToJavaType(renamedDescriptor.toSourceString()));
+ outputFrom = position;
+ changed = true;
+ }
+ }
+ }
+
+ private boolean isDashOrDot(char c) {
+ return c == '.' || c == '-';
+ }
+
+ private void outputRangeFromInput(int from, int toExclusive) {
+ if (from < toExclusive) {
+ result.append(contents.substring(from, toExclusive));
+ }
+ }
+
+ private void outputString(String s) {
+ result.append(s);
+ }
+
+ private boolean eof() {
+ return eof(position);
+ }
+
+ private boolean eof(int position) {
+ return position == contents.length();
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 91b1dc7..3f02009 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -573,6 +573,10 @@
return canonicalize(strings, new DexString(source));
}
+ public DexString lookupString(String source) {
+ return strings.get(new DexString(source));
+ }
+
// TODO(b/67934123) Unify into one method,
public DexItemBasedString createItemBasedString(DexType type) {
assert !sorted;
@@ -635,7 +639,7 @@
return createType(createString(descriptor));
}
- synchronized public DexType lookupType(DexString descriptor) {
+ public DexType lookupType(DexString descriptor) {
return types.get(descriptor);
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index 7526e3f..05539fe 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -57,7 +57,7 @@
private final ProguardClassFilter.Builder adaptClassStrings = ProguardClassFilter.builder();
private final ProguardPathFilter.Builder adaptResourceFilenames = ProguardPathFilter.builder();
private final ProguardPathFilter.Builder adaptResourceFileContents =
- ProguardPathFilter.builder();
+ ProguardPathFilter.builder().disable();
private final ProguardPathFilter.Builder keepDirectories = ProguardPathFilter.builder();
private boolean forceProguardCompatibility = false;
private boolean overloadAggressively;
@@ -235,6 +235,10 @@
adaptResourceFilenames.addPattern(pattern);
}
+ public void enableAdaptResourceFileContents() {
+ adaptResourceFileContents.enable();
+ }
+
public void addAdaptResourceFileContents(ProguardPathList pattern) {
adaptResourceFileContents.addPattern(pattern);
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index b9c3012..855ba98 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -370,10 +370,7 @@
}
parsePathFilter(configurationBuilder::addAdaptResourceFilenames);
} else if (acceptString("adaptresourcefilecontents")) {
- // TODO(36847655): Report an error until it's fully supported.
- if (failOnPartiallyImplementedOptions) {
- failPartiallyImplementedOption("-adaptresourcefilecontents", optionStart);
- }
+ configurationBuilder.enableAdaptResourceFileContents();
parsePathFilter(configurationBuilder::addAdaptResourceFileContents);
} else if (acceptString("identifiernamestring")) {
configurationBuilder.addRule(parseIdentifierNameStringRule(optionStart));
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardPathFilter.java b/src/main/java/com/android/tools/r8/shaking/ProguardPathFilter.java
index cb2e117..b3771ef 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardPathFilter.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardPathFilter.java
@@ -7,6 +7,7 @@
import com.google.common.collect.ImmutableList;
public class ProguardPathFilter {
+ private final boolean enabled;
private final ImmutableList<ProguardPathList> patterns;
public static Builder builder() {
@@ -14,6 +15,7 @@
}
public static class Builder {
+ private boolean enabled = true;
private final ImmutableList.Builder<ProguardPathList> patterns = ImmutableList.builder();
private Builder() {
@@ -24,23 +26,41 @@
return this;
}
+ public Builder disable() {
+ enabled = false;
+ return this;
+ }
+
+ public Builder enable() {
+ enabled = true;
+ return this;
+ }
+
ProguardPathFilter build() {
- return new ProguardPathFilter(patterns.build());
+ return new ProguardPathFilter(patterns.build(), enabled);
}
}
- private ProguardPathFilter(ImmutableList<ProguardPathList> patterns) {
+ private ProguardPathFilter(ImmutableList<ProguardPathList> patterns, boolean enabled) {
+ this.enabled = enabled;
if (patterns.isEmpty()) {
this.patterns = ImmutableList.of(ProguardPathList.emptyList());
} else {
+ assert enabled;
this.patterns = patterns;
}
}
+ public boolean isEnabled() {
+ return enabled;
+ }
+
public boolean matches(String path) {
- for (ProguardPathList pattern : patterns) {
- if (pattern.matches(path)) {
- return true;
+ if (enabled) {
+ for (ProguardPathList pattern : patterns) {
+ if (pattern.matches(path)) {
+ return true;
+ }
}
}
return false;
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 1b026c0..acf9b62 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -10,6 +10,8 @@
import com.android.tools.r8.ArchiveClassFileProvider;
import com.android.tools.r8.ClassFileConsumer;
import com.android.tools.r8.ClassFileResourceProvider;
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.DataResourceProvider;
import com.android.tools.r8.DexFilePerClassFileConsumer;
import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.DirectoryClassFileProvider;
@@ -267,6 +269,7 @@
private final List<ProgramResourceProvider> programResourceProviders = new ArrayList<>();
private final List<ProgramResource> programResources = new ArrayList<>();
+ private final List<DataEntryResource> dataResources = new ArrayList<>();
private final Map<ProgramResource, String> programResourcesMainDescriptor = new HashMap<>();
private final List<ClassFileResourceProvider> classpathResourceProviders = new ArrayList<>();
private final List<ClassFileResourceProvider> libraryResourceProviders = new ArrayList<>();
@@ -464,6 +467,12 @@
return this;
}
+ /** Add resource data. */
+ public Builder addDataResource(byte[] bytes, String name, Origin origin) {
+ addDataResources(DataEntryResource.fromBytes(bytes, name, origin));
+ return this;
+ }
+
/**
* Set proguard-map output data.
*
@@ -529,17 +538,34 @@
* Build final AndroidApp.
*/
public AndroidApp build() {
- if (!programResources.isEmpty()) {
+ if (!programResources.isEmpty() || !dataResources.isEmpty()) {
// If there are individual program resources move them to a dedicated provider.
- final List<ProgramResource> resources = ImmutableList.copyOf(programResources);
+ final List<ProgramResource> finalProgramResources = ImmutableList.copyOf(programResources);
+ final List<DataEntryResource> finalDataResources = ImmutableList.copyOf(dataResources);
programResourceProviders.add(
new ProgramResourceProvider() {
@Override
- public Collection<ProgramResource> getProgramResources() throws ResourceException {
- return resources;
+ public Collection<ProgramResource> getProgramResources() {
+ return finalProgramResources;
+ }
+
+ @Override
+ public DataResourceProvider getDataResourceProvider() {
+ if (!finalDataResources.isEmpty()) {
+ return new DataResourceProvider() {
+ @Override
+ public void accept(Visitor visitor) {
+ for (DataEntryResource dataResource : finalDataResources) {
+ visitor.visit(dataResource);
+ }
+ }
+ };
+ }
+ return null;
}
});
programResources.clear();
+ dataResources.clear();
}
return new AndroidApp(
ImmutableList.copyOf(programResourceProviders),
@@ -576,6 +602,14 @@
programResources.addAll(resources);
}
+ private void addDataResources(DataEntryResource... resources) {
+ addDataResources(Arrays.asList(resources));
+ }
+
+ private void addDataResources(Collection<DataEntryResource> resources) {
+ this.dataResources.addAll(resources);
+ }
+
private void addClasspathOrLibraryProvider(
Path file, List<ClassFileResourceProvider> providerList) throws IOException {
if (!Files.exists(file)) {
diff --git a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
new file mode 100644
index 0000000..7b96286
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
@@ -0,0 +1,298 @@
+// Copyright (c) 2018, 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.naming;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DataDirectoryResource;
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.DataResourceConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class AdaptResourceFileContentsTest extends TestBase {
+
+ private static class CustomDataResourceConsumer implements DataResourceConsumer {
+
+ private final Map<String, ImmutableList<String>> resources = new HashMap<>();
+
+ @Override
+ public void accept(DataDirectoryResource directory, DiagnosticsHandler diagnosticsHandler) {
+ throw new Unreachable();
+ }
+
+ @Override
+ public void accept(DataEntryResource file, DiagnosticsHandler diagnosticsHandler) {
+ try {
+ byte[] bytes = ByteStreams.toByteArray(file.getByteStream());
+ String contents = new String(bytes, Charset.defaultCharset());
+ resources.put(file.getName(), ImmutableList.copyOf(contents.split(System.lineSeparator())));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {}
+
+ public ImmutableList<String> get(String name) {
+ return resources.get(name);
+ }
+ }
+
+ private static final ImmutableList<String> originalAllChangedResource =
+ ImmutableList.of(
+ "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A",
+ "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A<java.lang.String>",
+ "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A<"
+ + "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A>",
+ "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B",
+ // Test property values are rewritten.
+ "property=com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A",
+ // Test XML content is rewritten.
+ "<tag>com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A</tag>",
+ "<tag attr=\"com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A\"></tag>",
+ // Test single-quote literals are rewritten.
+ "'com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A'");
+
+ private static final ImmutableList<String> originalAllPresentResource =
+ ImmutableList.of(
+ "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass",
+ "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A",
+ "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B");
+
+ private static final ImmutableList<String> originalAllUnchangedResource =
+ ImmutableList.of(
+ "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass",
+ // Test there is no renaming for the method on A.
+ "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A.method",
+ "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A.method()",
+ // Test there is no renaming for the methods on B.
+ "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B.method",
+ "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B.method()",
+ // Test various prefixes.
+ "42com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B",
+ "WithIdentifierPrefixcom.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B",
+ "-com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B",
+ "WithDashPrefix-com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B",
+ "$com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B",
+ "WithDollarPrefix$com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B",
+ ".com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B",
+ "WithDotPrefix.com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B",
+ // Test various suffixes.
+ "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B42",
+ "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$BWithIdentifierSuffix",
+ "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B-",
+ "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B-WithDashSuffix",
+ "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B$",
+ "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B$WithDollarSuffix",
+ "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B.",
+ "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B.WithDotSuffix");
+
+ private static String getProguardConfig(
+ boolean enableAdaptResourceFileContents, String adaptResourceFileContentsPathFilter) {
+ String adaptResourceFileContentsRule;
+ if (enableAdaptResourceFileContents) {
+ adaptResourceFileContentsRule = "-adaptresourcefilecontents";
+ if (adaptResourceFileContentsPathFilter != null) {
+ adaptResourceFileContentsRule += " " + adaptResourceFileContentsPathFilter;
+ }
+ } else {
+ adaptResourceFileContentsRule = "";
+ }
+ return String.join(
+ System.lineSeparator(),
+ adaptResourceFileContentsRule,
+ "-keep class " + AdaptResourceFileContentsTestClass.class.getName() + " {",
+ " public static void main(...);",
+ "}",
+ "-neverinline class com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B {",
+ " public void method();",
+ "}");
+ }
+
+ @Test
+ public void testEnabled() throws Exception {
+ CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+ AndroidApp out = compileWithR8(getProguardConfig(true, null), dataResourceConsumer);
+
+ // Check that the data resources have changed as expected.
+ checkAllAreChanged(
+ dataResourceConsumer.get("resource-all-changed.md"), originalAllChangedResource);
+ checkAllAreChanged(
+ dataResourceConsumer.get("resource-all-changed.txt"), originalAllChangedResource);
+
+ // Check that the new names are consistent with the actual application code.
+ checkAllArePresent(
+ dataResourceConsumer.get("resource-all-present.txt"), new CodeInspector(out));
+
+ // Check that the data resources have not changed unexpectedly.
+ checkAllAreUnchanged(
+ dataResourceConsumer.get("resource-all-changed.class"), originalAllChangedResource);
+ checkAllAreUnchanged(
+ dataResourceConsumer.get("resource-all-changed.cLaSs"), originalAllChangedResource);
+ checkAllAreUnchanged(
+ dataResourceConsumer.get("resource-all-unchanged.txt"), originalAllUnchangedResource);
+ }
+
+ @Ignore("b/36847655")
+ @Test
+ public void testProguardBehavior() throws Exception {
+ // TODO(christofferqa): Run Proguard on the example and check that R8 behaves the same way.
+ }
+
+ @Test
+ public void testEnabledWithFilter() throws Exception {
+ CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+ compileWithR8(getProguardConfig(true, "*.md"), dataResourceConsumer);
+
+ // Check that the file matching the filter has changed as expected.
+ checkAllAreChanged(
+ dataResourceConsumer.get("resource-all-changed.md"), originalAllChangedResource);
+
+ // Check that all the other data resources are unchanged.
+ checkAllAreUnchanged(
+ dataResourceConsumer.get("resource-all-changed.class"), originalAllChangedResource);
+ checkAllAreUnchanged(
+ dataResourceConsumer.get("resource-all-changed.cLaSs"), originalAllChangedResource);
+ checkAllAreUnchanged(
+ dataResourceConsumer.get("resource-all-changed.txt"), originalAllChangedResource);
+ checkAllAreUnchanged(
+ dataResourceConsumer.get("resource-all-present.txt"), originalAllPresentResource);
+ checkAllAreUnchanged(
+ dataResourceConsumer.get("resource-all-unchanged.txt"), originalAllUnchangedResource);
+ }
+
+ @Test
+ public void testDisabled() throws Exception {
+ CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+ compileWithR8(getProguardConfig(false, null), dataResourceConsumer);
+
+ // Check that all data resources are unchanged.
+ checkAllAreUnchanged(
+ dataResourceConsumer.get("resource-all-changed.class"), originalAllChangedResource);
+ checkAllAreUnchanged(
+ dataResourceConsumer.get("resource-all-changed.cLaSs"), originalAllChangedResource);
+ checkAllAreUnchanged(
+ dataResourceConsumer.get("resource-all-changed.md"), originalAllChangedResource);
+ checkAllAreUnchanged(
+ dataResourceConsumer.get("resource-all-changed.txt"), originalAllChangedResource);
+ checkAllAreUnchanged(
+ dataResourceConsumer.get("resource-all-present.txt"), originalAllPresentResource);
+ checkAllAreUnchanged(
+ dataResourceConsumer.get("resource-all-unchanged.txt"), originalAllUnchangedResource);
+ }
+
+ private static void checkAllAreChanged(
+ ImmutableList<String> adaptedLines, ImmutableList<String> originalLines) {
+ assertEquals(adaptedLines.size(), originalLines.size());
+ for (int i = 0; i < originalLines.size(); i++) {
+ assertNotEquals(originalLines.get(i), adaptedLines.get(i));
+ }
+ }
+
+ private static void checkAllArePresent(ImmutableList<String> lines, CodeInspector inspector) {
+ for (String line : lines) {
+ assertThat(inspector.clazz(line), isPresent());
+ }
+ }
+
+ private static void checkAllAreUnchanged(
+ ImmutableList<String> adaptedLines, ImmutableList<String> originalLines) {
+ assertEquals(adaptedLines.size(), originalLines.size());
+ for (int i = 0; i < originalLines.size(); i++) {
+ assertEquals(originalLines.get(i), adaptedLines.get(i));
+ }
+ }
+
+ private AndroidApp compileWithR8(String proguardConfig, DataResourceConsumer dataResourceConsumer)
+ throws CompilationFailedException, IOException {
+ AndroidApp app =
+ AndroidApp.builder()
+ .addProgramFiles(
+ ToolHelper.getClassFileForTestClass(AdaptResourceFileContentsTestClass.class),
+ ToolHelper.getClassFileForTestClass(AdaptResourceFileContentsTestClass.A.class),
+ ToolHelper.getClassFileForTestClass(AdaptResourceFileContentsTestClass.B.class))
+ .addDataResource(
+ String.join(System.lineSeparator(), originalAllChangedResource).getBytes(),
+ "resource-all-changed.class",
+ Origin.unknown())
+ .addDataResource(
+ String.join(System.lineSeparator(), originalAllChangedResource).getBytes(),
+ "resource-all-changed.cLaSs",
+ Origin.unknown())
+ .addDataResource(
+ String.join(System.lineSeparator(), originalAllChangedResource).getBytes(),
+ "resource-all-changed.md",
+ Origin.unknown())
+ .addDataResource(
+ String.join(System.lineSeparator(), originalAllChangedResource).getBytes(),
+ "resource-all-changed.txt",
+ Origin.unknown())
+ .addDataResource(
+ String.join(System.lineSeparator(), originalAllPresentResource).getBytes(),
+ "resource-all-present.txt",
+ Origin.unknown())
+ .addDataResource(
+ String.join(System.lineSeparator(), originalAllUnchangedResource).getBytes(),
+ "resource-all-unchanged.txt",
+ Origin.unknown())
+ .build();
+ R8Command command =
+ ToolHelper.allowTestProguardOptions(
+ ToolHelper.prepareR8CommandBuilder(app)
+ .addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown()))
+ .build();
+ return ToolHelper.runR8(
+ command,
+ options -> {
+ // TODO(christofferqa): Class inliner should respect -neverinline.
+ options.enableClassInlining = false;
+ options.enableClassMerging = true;
+ options.dataResourceConsumer = dataResourceConsumer;
+ });
+ }
+}
+
+class AdaptResourceFileContentsTestClass {
+
+ public static void main(String[] args) {
+ B obj = new B();
+ obj.method();
+ }
+
+ static class A {
+
+ public void method() {
+ System.out.println("In A.method()");
+ }
+ }
+
+ static class B extends A {
+
+ public void method() {
+ System.out.println("In B.method()");
+ super.method();
+ }
+ }
+}