Lazy class loading with resource providers.
Extending the current lazy loading implementation to allow API
consumer to pass instances implementing LazyResourceProvider
interface as a source of resources that can be queried lazily
for on-demand class searches.
Only class files can be added this way in this implementation.
This functionality is currently implemented as an *extension*
of regular lazy loading which is based on DexClassPromise and
LazyClassFileLoader, and reuses this implementation a lot.
The advantage of using lazy resource providers is that it does
not require actually having all the resources preloaded up front,
but it comes at cost of minor differences of how errors for
conflicting classes are reported in resource-provider based
and regular scenarios.
Currently all the resources added to D8 via --lib or --classpath
are still loaded via regular lazy loading mechanism, resource
providers are only available for API users.
BUG=
Change-Id: I70cba6f3603bfd1e8e33a168d5fd8a3000a6e762
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index a395530..0ad793c 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -39,6 +39,16 @@
super(app, CompilationMode.DEBUG);
}
+ public Builder addClasspathResourceProvider(ResourceProvider provider) {
+ getAppBuilder().addClasspathResourceProvider(provider);
+ return this;
+ }
+
+ public Builder addLibraryResourceProvider(ResourceProvider provider) {
+ getAppBuilder().addLibraryResourceProvider(provider);
+ return this;
+ }
+
@Override
Builder self() {
return this;
diff --git a/src/main/java/com/android/tools/r8/ResourceProvider.java b/src/main/java/com/android/tools/r8/ResourceProvider.java
new file mode 100644
index 0000000..2a5b0dc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ResourceProvider.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2017, 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;
+
+/**
+ * Represents a provider for application resources. All resources returned
+ * via this provider should be class file resources and will be loaded on-demand
+ * or not loaded at all.
+ *
+ * NOTE: currently only resources representing Java class files can be loaded
+ * with lazy resource providers.
+ */
+public interface ResourceProvider {
+ /** Get the class resource associated with the descriptor, or null. */
+ Resource getResource(String descriptor);
+}
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index 4bf5cd9..35f9bfe 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -11,6 +11,7 @@
import static com.android.tools.r8.utils.FileUtils.DEFAULT_DEX_FILENAME;
import com.android.tools.r8.Resource;
+import com.android.tools.r8.ResourceProvider;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexItemFactory;
@@ -22,6 +23,7 @@
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalResource;
+import com.android.tools.r8.utils.LazyClassCollection;
import com.android.tools.r8.utils.MainDexList;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
@@ -68,6 +70,7 @@
readMainDexList(builder, executorService, futures, closer);
readDexSources(builder, executorService, futures, closer);
readClassSources(builder, closer);
+ initializeLazyClassCollection(builder);
ThreadUtils.awaitFutures(futures);
} catch (ExecutionException e) {
// If the reading failed with a valid compilation error, rethrow the unwrapped exception.
@@ -106,6 +109,14 @@
}
}
+ private void initializeLazyClassCollection(DexApplication.Builder builder) {
+ List<ResourceProvider> providers = inputApp.getLazyResourceProviders();
+ if (!providers.isEmpty()) {
+ builder.setLazyClassCollection(
+ new LazyClassCollection(new JarApplicationReader(options), providers));
+ }
+ }
+
private void addLazyLoader(JarApplicationReader application,
DexApplication.Builder builder, InternalResource resource) {
// Generate expected DEX type.
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 63185d1..a558b3e 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -16,19 +16,19 @@
public class AppInfo {
+ public final DexApplication app;
public final DexItemFactory dexItemFactory;
- final ImmutableMap<DexType, DexClassPromise> classMap;
private final ConcurrentHashMap<DexType, Map<Descriptor, KeyedDexItem>> definitions =
new ConcurrentHashMap<>();
public AppInfo(DexApplication application) {
- this.dexItemFactory = application.dexItemFactory;
- this.classMap = application.classMap;
+ this.app = application;
+ this.dexItemFactory = app.dexItemFactory;
}
protected AppInfo(AppInfo previous) {
- this.dexItemFactory = previous.dexItemFactory;
- this.classMap = previous.classMap;
+ this.app = previous.app;
+ this.dexItemFactory = app.dexItemFactory;
this.definitions.putAll(previous.definitions);
}
@@ -40,7 +40,7 @@
private Map<Descriptor, KeyedDexItem> computeDefinitions(DexType type) {
Builder<Descriptor, KeyedDexItem> builder = ImmutableMap.builder();
- DexClassPromise promise = classMap.get(type);
+ DexClassPromise promise = app.definitionFor(type);
if (promise != null) {
DexClass clazz = promise.get();
registerDefinitions(builder, clazz.directMethods());
@@ -59,28 +59,15 @@
}
public Iterable<DexProgramClass> classes() {
- List<DexProgramClass> result = new ArrayList<>();
- for (DexClassPromise promise : classMap.values()) {
- if (promise.isProgramClass()) {
- result.add(promise.get().asProgramClass());
- }
- }
- return result;
+ return app.classes();
}
public Iterable<DexLibraryClass> libraryClasses() {
- List<DexLibraryClass> result = new ArrayList<>();
- for (DexClassPromise promise : classMap.values()) {
- if (promise.isLibraryClass()) {
- result.add(promise.get().asLibraryClass());
- }
- }
- return result;
+ return app.libraryClasses();
}
public DexClass definitionFor(DexType type) {
- DexClassPromise promise = classMap.get(type);
- return promise == null ? null : promise.get();
+ return app.definitionFor(type);
}
public DexEncodedMethod definitionFor(DexMethod method) {
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index 17754cc..9330fdd 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -21,7 +21,7 @@
public AppInfoWithSubtyping(DexApplication application) {
super(application);
- populateSubtypeMap(application.classMap, application.dexItemFactory);
+ populateSubtypeMap(application.getClassMap(), application.dexItemFactory);
}
protected AppInfoWithSubtyping(AppInfoWithSubtyping previous) {
@@ -33,7 +33,7 @@
protected AppInfoWithSubtyping(AppInfoWithSubtyping previous, GraphLense lense) {
super(previous, lense);
// Recompute subtype map if we have modified the graph.
- populateSubtypeMap(previous.classMap, dexItemFactory);
+ populateSubtypeMap(previous.app.getClassMap(), dexItemFactory);
}
public Set<DexType> getMissingClasses() {
diff --git a/src/main/java/com/android/tools/r8/graph/DexApplication.java b/src/main/java/com/android/tools/r8/graph/DexApplication.java
index 1b83e59..bcc845e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DexApplication.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.LazyClassCollection;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableMap;
@@ -33,7 +34,16 @@
public class DexApplication {
// Maps type into class promise, may be used concurrently.
- final ImmutableMap<DexType, DexClassPromise> classMap;
+ private final ImmutableMap<DexType, DexClassPromise> classMap;
+
+ // Lazily loaded classes.
+ //
+ // Note that this collection is autonomous and may be used in several
+ // different applications. Particularly, it is the case when one
+ // application is being build based on another one. Among others,
+ // it will have an important side-effect: class conflict resolution,
+ // generated errors in particular, may be different in lazy scenario.
+ private final LazyClassCollection lazyClassCollection;
public final ImmutableSet<DexType> mainDexList;
@@ -50,11 +60,13 @@
private DexApplication(
ClassNameMapper proguardMap,
ImmutableMap<DexType, DexClassPromise> classMap,
+ LazyClassCollection lazyClassCollection,
ImmutableSet<DexType> mainDexList,
DexItemFactory dexItemFactory,
DexString highestSortingString,
Timing timing) {
this.proguardMap = proguardMap;
+ this.lazyClassCollection = lazyClassCollection;
this.mainDexList = mainDexList;
this.classMap = classMap;
this.dexItemFactory = dexItemFactory;
@@ -62,8 +74,15 @@
this.timing = timing;
}
+ ImmutableMap<DexType, DexClassPromise> getClassMap() {
+ assert lazyClassCollection == null : "Only allowed in non-lazy scenarios.";
+ return classMap;
+ }
+
public Iterable<DexProgramClass> classes() {
List<DexProgramClass> result = new ArrayList<>();
+ // Note: we ignore lazy class collection because it
+ // is never supposed to be used for program classes.
for (DexClassPromise promise : classMap.values()) {
if (promise.isProgramClass()) {
result.add(promise.get().asProgramClass());
@@ -73,6 +92,7 @@
}
public Iterable<DexLibraryClass> libraryClasses() {
+ assert lazyClassCollection == null : "Only allowed in non-lazy scenarios.";
List<DexLibraryClass> result = new ArrayList<>();
for (DexClassPromise promise : classMap.values()) {
if (promise.isLibraryClass()) {
@@ -84,11 +104,17 @@
public DexClass definitionFor(DexType type) {
DexClassPromise promise = classMap.get(type);
+ // In presence of lazy class collection we also reach out to it
+ // as well unless the class found is already a program class.
+ if (lazyClassCollection != null && (promise == null || !promise.isProgramClass())) {
+ promise = lazyClassCollection.get(type, promise);
+ }
return promise == null ? null : promise.get();
}
public DexProgramClass programDefinitionFor(DexType type) {
DexClassPromise promise = classMap.get(type);
+ // Don't bother about lazy class collection, it should never load program classes.
return (promise == null || !promise.isProgramClass()) ? null : promise.get().asProgramClass();
}
@@ -212,7 +238,9 @@
public static class Builder {
- public final Hashtable<DexType, DexClassPromise> classMap = new Hashtable<>();
+ private final Hashtable<DexType, DexClassPromise> classMap = new Hashtable<>();
+ private LazyClassCollection lazyClassCollection;
+
public final Hashtable<DexCode, DexCode> codeItems = new Hashtable<>();
public final DexItemFactory dexItemFactory;
@@ -225,6 +253,7 @@
public Builder(DexItemFactory dexItemFactory, Timing timing) {
this.dexItemFactory = dexItemFactory;
this.timing = timing;
+ this.lazyClassCollection = null;
}
public Builder(DexApplication application) {
@@ -233,6 +262,7 @@
public Builder(DexApplication application, Map<DexType, DexClassPromise> classMap) {
this.classMap.putAll(classMap);
+ this.lazyClassCollection = application.lazyClassCollection;
proguardMap = application.proguardMap;
timing = application.timing;
highestSortingString = application.highestSortingString;
@@ -256,12 +286,18 @@
return this;
}
+ public Builder setLazyClassCollection(LazyClassCollection lazyClassMap) {
+ this.lazyClassCollection = lazyClassMap;
+ return this;
+ }
+
public Builder addClassIgnoringLibraryDuplicates(DexClass clazz) {
addClass(clazz, true);
return this;
}
public Builder addSynthesizedClass(DexProgramClass synthesizedClass, boolean addToMainDexList) {
+ assert synthesizedClass.isProgramClass() : "All synthesized classes must be program classes";
addClassPromise(synthesizedClass);
if (addToMainDexList && !mainDexList.isEmpty()) {
mainDexList.add(synthesizedClass.type);
@@ -269,6 +305,18 @@
return this;
}
+ public List<DexProgramClass> getProgramClasses() {
+ List<DexProgramClass> result = new ArrayList<>();
+ // Note: we ignore lazy class collection because it
+ // is never supposed to be used for program classes.
+ for (DexClassPromise promise : classMap.values()) {
+ if (promise.isProgramClass()) {
+ result.add(promise.get().asProgramClass());
+ }
+ }
+ return result;
+ }
+
// Callback from FileReader when parsing a DexProgramClass (multi-threaded).
private void addClass(DexClass clazz, boolean skipLibDups) {
addClassPromise(clazz, skipLibDups);
@@ -286,83 +334,6 @@
}
}
- private DexClassPromise chooseClass(DexClassPromise a, DexClassPromise b, boolean skipLibDups) {
- // NOTE: We assume that there should not be any conflicting names in user defined
- // classes and/or linked jars. If we ever want to allow 'keep first'-like policy
- // to resolve this kind of conflict between program and/or classpath classes, we'll
- // need to make sure we choose the class we keep deterministically.
- if (a.isProgramClass() && b.isProgramClass()) {
- if (allowProgramClassConflict(a, b)) {
- return a;
- }
- throw new CompilationError(
- "Program type already present: " + a.getType().toSourceString());
- }
- if (a.isProgramClass()) {
- return chooseBetweenProgramAndOtherClass(a, b);
- }
- if (b.isProgramClass()) {
- return chooseBetweenProgramAndOtherClass(b, a);
- }
-
- if (a.isClasspathClass() && b.isClasspathClass()) {
- throw new CompilationError(
- "Classpath type already present: " + a.getType().toSourceString());
- }
- if (a.isClasspathClass()) {
- return chooseBetweenClasspathAndLibraryClass(a, b);
- }
- if (b.isClasspathClass()) {
- return chooseBetweenClasspathAndLibraryClass(b, a);
- }
-
- return chooseBetweenLibraryClasses(b, a, skipLibDups);
- }
-
- private boolean allowProgramClassConflict(DexClassPromise a, DexClassPromise b) {
- // Currently only allow collapsing synthetic lambda classes.
- return a.getOrigin() == DexClass.Origin.Dex
- && b.getOrigin() == DexClass.Origin.Dex
- && a.get().accessFlags.isSynthetic()
- && b.get().accessFlags.isSynthetic()
- && LambdaRewriter.hasLambdaClassPrefix(a.getType())
- && LambdaRewriter.hasLambdaClassPrefix(b.getType());
- }
-
- private DexClassPromise chooseBetweenProgramAndOtherClass(
- DexClassPromise selected, DexClassPromise ignored) {
- assert selected.isProgramClass() && !ignored.isProgramClass();
- if (ignored.isLibraryClass()) {
- logIgnoredClass(ignored, "Class `%s` was specified as library and program type.");
- }
- // We don't log program/classpath class conflict since it is expected case.
- return selected;
- }
-
- private DexClassPromise chooseBetweenClasspathAndLibraryClass(
- DexClassPromise selected, DexClassPromise ignored) {
- assert selected.isClasspathClass() && ignored.isLibraryClass();
- logIgnoredClass(ignored, "Class `%s` was specified as library and classpath type.");
- return selected;
- }
-
- private DexClassPromise chooseBetweenLibraryClasses(
- DexClassPromise selected, DexClassPromise ignored, boolean skipDups) {
- assert selected.isLibraryClass() && ignored.isLibraryClass();
- if (!skipDups) {
- throw new CompilationError(
- "Library type already present: " + selected.getType().toSourceString());
- }
- logIgnoredClass(ignored, "Class `%s` was specified twice as a library type.");
- return selected;
- }
-
- private void logIgnoredClass(DexClassPromise ignored, String message) {
- if (Log.ENABLED) {
- Log.warn(getClass(), message, ignored.getType().toSourceString());
- }
- }
-
public Builder addToMainDexList(Collection<DexType> mainDexList) {
this.mainDexList.addAll(mainDexList);
return this;
@@ -372,10 +343,89 @@
return new DexApplication(
proguardMap,
ImmutableMap.copyOf(classMap),
+ lazyClassCollection,
ImmutableSet.copyOf(mainDexList),
dexItemFactory,
highestSortingString,
timing);
}
}
+
+ public static DexClassPromise chooseClass(
+ DexClassPromise a, DexClassPromise b, boolean skipLibDups) {
+ // NOTE: We assume that there should not be any conflicting names in user defined
+ // classes and/or linked jars. If we ever want to allow 'keep first'-like policy
+ // to resolve this kind of conflict between program and/or classpath classes, we'll
+ // need to make sure we choose the class we keep deterministically.
+ if (a.isProgramClass() && b.isProgramClass()) {
+ if (allowProgramClassConflict(a, b)) {
+ return a;
+ }
+ throw new CompilationError(
+ "Program type already present: " + a.getType().toSourceString());
+ }
+ if (a.isProgramClass()) {
+ return chooseBetweenProgramAndOtherClass(a, b);
+ }
+ if (b.isProgramClass()) {
+ return chooseBetweenProgramAndOtherClass(b, a);
+ }
+
+ if (a.isClasspathClass() && b.isClasspathClass()) {
+ throw new CompilationError(
+ "Classpath type already present: " + a.getType().toSourceString());
+ }
+ if (a.isClasspathClass()) {
+ return chooseBetweenClasspathAndLibraryClass(a, b);
+ }
+ if (b.isClasspathClass()) {
+ return chooseBetweenClasspathAndLibraryClass(b, a);
+ }
+
+ return chooseBetweenLibraryClasses(b, a, skipLibDups);
+ }
+
+ private static boolean allowProgramClassConflict(DexClassPromise a, DexClassPromise b) {
+ // Currently only allow collapsing synthetic lambda classes.
+ return a.getOrigin() == DexClass.Origin.Dex
+ && b.getOrigin() == DexClass.Origin.Dex
+ && a.get().accessFlags.isSynthetic()
+ && b.get().accessFlags.isSynthetic()
+ && LambdaRewriter.hasLambdaClassPrefix(a.getType())
+ && LambdaRewriter.hasLambdaClassPrefix(b.getType());
+ }
+
+ private static DexClassPromise chooseBetweenProgramAndOtherClass(
+ DexClassPromise selected, DexClassPromise ignored) {
+ assert selected.isProgramClass() && !ignored.isProgramClass();
+ if (ignored.isLibraryClass()) {
+ logIgnoredClass(ignored, "Class `%s` was specified as library and program type.");
+ }
+ // We don't log program/classpath class conflict since it is expected case.
+ return selected;
+ }
+
+ private static DexClassPromise chooseBetweenClasspathAndLibraryClass(
+ DexClassPromise selected, DexClassPromise ignored) {
+ assert selected.isClasspathClass() && ignored.isLibraryClass();
+ logIgnoredClass(ignored, "Class `%s` was specified as library and classpath type.");
+ return selected;
+ }
+
+ private static DexClassPromise chooseBetweenLibraryClasses(
+ DexClassPromise selected, DexClassPromise ignored, boolean skipDups) {
+ assert selected.isLibraryClass() && ignored.isLibraryClass();
+ if (!skipDups) {
+ throw new CompilationError(
+ "Library type already present: " + selected.getType().toSourceString());
+ }
+ logIgnoredClass(ignored, "Class `%s` was specified twice as a library type.");
+ return selected;
+ }
+
+ private static void logIgnoredClass(DexClassPromise ignored, String message) {
+ if (Log.ENABLED) {
+ Log.warn(DexApplication.class, message, ignored.getType().toSourceString());
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index d12109e..4aa06d6 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -163,7 +163,7 @@
private void synthesizeLambdaClasses(Builder builder) {
if (lambdaRewriter != null) {
- lambdaRewriter.adjustAccessibility(builder);
+ lambdaRewriter.adjustAccessibility();
lambdaRewriter.synthesizeLambdaClasses(builder);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 81576ec..215cfba 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -216,18 +216,17 @@
}
private static boolean shouldProcess(
- DexClassPromise promise, Flavor flavour, boolean mustBeInterface) {
- return promise.isProgramClass()
- && (promise.getOrigin() != DexClass.Origin.Dex || flavour == Flavor.IncludeAllResources)
- && promise.get().isInterface() == mustBeInterface;
+ DexProgramClass clazz, Flavor flavour, boolean mustBeInterface) {
+ return (clazz.getOrigin() != DexClass.Origin.Dex || flavour == Flavor.IncludeAllResources)
+ && clazz.isInterface() == mustBeInterface;
}
private Map<DexProgramClass, DexProgramClass> processInterfaces(
Builder builder, Flavor flavour) {
InterfaceProcessor processor = new InterfaceProcessor(this);
- for (DexClassPromise promise : builder.classMap.values()) {
- if (shouldProcess(promise, flavour, true)) {
- processor.process(promise.get().asProgramClass());
+ for (DexProgramClass clazz : builder.getProgramClasses()) {
+ if (shouldProcess(clazz, flavour, true)) {
+ processor.process(clazz.get().asProgramClass());
}
}
return processor.companionClasses;
@@ -235,9 +234,9 @@
private Set<DexEncodedMethod> processClasses(Builder builder, Flavor flavour) {
ClassProcessor processor = new ClassProcessor(this);
- for (DexClassPromise promise : builder.classMap.values()) {
- if (shouldProcess(promise, flavour, false)) {
- processor.process(promise.get());
+ for (DexProgramClass clazz : builder.getProgramClasses()) {
+ if (shouldProcess(clazz, flavour, false)) {
+ processor.process(clazz.get());
}
}
return processor.getForwardMethods();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index a4801c9..db28115 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -384,7 +384,11 @@
}
// Ensure access of the referenced symbol(s).
- abstract boolean ensureAccessibility(Builder builder);
+ abstract boolean ensureAccessibility();
+
+ DexClass definitionFor(DexType type) {
+ return rewriter.converter.appInfo.app.definitionFor(type);
+ }
}
// Used for targeting methods referenced directly without creating accessors.
@@ -395,7 +399,7 @@
}
@Override
- boolean ensureAccessibility(Builder builder) {
+ boolean ensureAccessibility() {
return true;
}
}
@@ -408,11 +412,11 @@
}
@Override
- boolean ensureAccessibility(Builder builder) {
+ boolean ensureAccessibility() {
// We already found the static method to be called, just relax its accessibility.
assert descriptor.getAccessibility() != null;
descriptor.getAccessibility().unsetPrivate();
- DexClassPromise promise = builder.classMap.get(descriptor.implHandle.asMethod().holder);
+ DexClassPromise promise = definitionFor(descriptor.implHandle.asMethod().holder);
assert promise != null;
DexClass implMethodHolder = promise.get();
if (implMethodHolder.isInterface()) {
@@ -431,11 +435,11 @@
}
@Override
- boolean ensureAccessibility(Builder builder) {
+ boolean ensureAccessibility() {
// For all instantiation points for which compiler creates lambda$
// methods, it creates these methods in the same class/interface.
DexMethod implMethod = descriptor.implHandle.asMethod();
- DexClassPromise promise = builder.classMap.get(implMethod.holder);
+ DexClassPromise promise = definitionFor(implMethod.holder);
assert promise != null;
DexClass implMethodHolder = promise.get();
@@ -476,9 +480,9 @@
}
@Override
- boolean ensureAccessibility(Builder builder) {
+ boolean ensureAccessibility() {
// Create a static accessor with proper accessibility.
- DexClassPromise promise = builder.classMap.get(callTarget.holder);
+ DexClassPromise promise = definitionFor(callTarget.holder);
assert promise != null && promise.isProgramClass();
DexClass accessorClass = promise.get();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index 34e60d1..10908f0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -193,12 +193,12 @@
* Adjust accessibility of referenced application symbols or
* creates necessary accessors.
*/
- public void adjustAccessibility(Builder builder) {
+ public void adjustAccessibility() {
// For each lambda class perform necessary adjustment of the
// referenced symbols to make them accessible. This can result in
// method access relaxation or creation of accessor method.
for (LambdaClass lambdaClass : knownLambdaClasses.values()) {
- lambdaClass.target.ensureAccessibility(builder);
+ lambdaClass.target.ensureAccessibility();
}
}
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 5c15109..2b78295 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -3,12 +3,12 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils;
-import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
import static com.android.tools.r8.utils.FileUtils.isArchive;
import static com.android.tools.r8.utils.FileUtils.isClassFile;
import static com.android.tools.r8.utils.FileUtils.isDexFile;
import com.android.tools.r8.Resource;
+import com.android.tools.r8.ResourceProvider;
import com.android.tools.r8.errors.CompilationError;
import com.google.common.collect.ImmutableList;
import com.google.common.io.ByteStreams;
@@ -52,6 +52,7 @@
private final ImmutableList<InternalResource> dexSources;
private final ImmutableList<InternalResource> classSources;
+ private final ImmutableList<ResourceProvider> resourceProviders;
private final InternalResource proguardMap;
private final InternalResource proguardSeeds;
private final InternalResource packageDistribution;
@@ -61,12 +62,14 @@
private AndroidApp(
ImmutableList<InternalResource> dexSources,
ImmutableList<InternalResource> classSources,
+ ImmutableList<ResourceProvider> resourceProviders,
InternalResource proguardMap,
InternalResource proguardSeeds,
InternalResource packageDistribution,
InternalResource mainDexList) {
this.dexSources = dexSources;
this.classSources = classSources;
+ this.resourceProviders = resourceProviders;
this.proguardMap = proguardMap;
this.proguardSeeds = proguardSeeds;
this.packageDistribution = packageDistribution;
@@ -166,6 +169,11 @@
return filter(classSources, Resource.Kind.LIBRARY);
}
+ /** Get lazy resource providers. */
+ public List<ResourceProvider> getLazyResourceProviders() {
+ return resourceProviders;
+ }
+
private List<InternalResource> filter(
List<InternalResource> resources, Resource.Kind kind) {
List<InternalResource> out = new ArrayList<>(resources.size());
@@ -362,6 +370,7 @@
private final List<InternalResource> dexSources = new ArrayList<>();
private final List<InternalResource> classSources = new ArrayList<>();
+ private final List<ResourceProvider> resourceProviders = new ArrayList<>();
private InternalResource proguardMap;
private InternalResource proguardSeeds;
private InternalResource packageDistribution;
@@ -375,6 +384,7 @@
private Builder(AndroidApp app) {
dexSources.addAll(app.dexSources);
classSources.addAll(app.classSources);
+ resourceProviders.addAll(app.resourceProviders);
proguardMap = app.proguardMap;
proguardSeeds = app.proguardSeeds;
packageDistribution = app.packageDistribution;
@@ -439,6 +449,14 @@
}
/**
+ * Add classpath resource provider.
+ */
+ public Builder addClasspathResourceProvider(ResourceProvider provider) {
+ resourceProviders.add(provider);
+ return this;
+ }
+
+ /**
* Add library file resources.
*/
public Builder addLibraryFiles(Path... files) throws IOException {
@@ -456,6 +474,14 @@
}
/**
+ * Add library resource provider.
+ */
+ public Builder addLibraryResourceProvider(ResourceProvider provider) {
+ resourceProviders.add(provider);
+ return this;
+ }
+
+ /**
* Add dex program-data with class descriptor.
*/
public Builder addDexProgramData(byte[] data, Set<String> classDescriptors) {
@@ -551,6 +577,7 @@
return new AndroidApp(
ImmutableList.copyOf(dexSources),
ImmutableList.copyOf(classSources),
+ ImmutableList.copyOf(resourceProviders),
proguardMap,
proguardSeeds,
packageDistribution,
@@ -585,7 +612,7 @@
dexSources.add(InternalResource.fromBytes(kind, ByteStreams.toByteArray(stream)));
} else if (isClassFile(name)) {
containsClassData = true;
- String descriptor = guessTypeDescriptor(name);
+ String descriptor = PreloadedResourceProvider.guessTypeDescriptor(name);
classSources.add(InternalResource.fromBytes(
kind, ByteStreams.toByteArray(stream), Collections.singleton(descriptor)));
}
@@ -601,16 +628,4 @@
}
}
}
-
- // Guess class descriptor from location of the class file in the archive.
- private static String guessTypeDescriptor(Path name) {
- String fileName = name.toString();
- assert fileName != null;
- assert fileName.endsWith(CLASS_EXTENSION);
- String descriptor = fileName.substring(0, fileName.length() - CLASS_EXTENSION.length());
- if (descriptor.contains(".")) {
- throw new CompilationError("Unexpected file name in the jar: " + name);
- }
- return 'L' + descriptor + ';';
- }
}
diff --git a/src/main/java/com/android/tools/r8/utils/DirectoryResourceProvider.java b/src/main/java/com/android/tools/r8/utils/DirectoryResourceProvider.java
new file mode 100644
index 0000000..1d2ff81
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/DirectoryResourceProvider.java
@@ -0,0 +1,40 @@
+// Copyright (c) 2017, 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.utils;
+
+import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
+
+import com.android.tools.r8.Resource;
+import com.android.tools.r8.ResourceProvider;
+import java.io.File;
+import java.nio.file.Path;
+
+/** Lazy resource provider based on filesystem directory content. */
+public final class DirectoryResourceProvider implements ResourceProvider {
+ private final Resource.Kind kind;
+ private final Path root;
+
+ private DirectoryResourceProvider(Resource.Kind kind, Path root) {
+ this.kind = kind;
+ this.root = root;
+ }
+
+ @Override
+ public Resource getResource(String descriptor) {
+ assert DescriptorUtils.isClassDescriptor(descriptor);
+
+ // Build expected file path based on type descriptor.
+ String classBinaryName = DescriptorUtils.getClassBinaryNameFromDescriptor(descriptor);
+ Path filePath = root.resolve(classBinaryName + CLASS_EXTENSION);
+ File file = filePath.toFile();
+
+ return (file.exists() && !file.isDirectory())
+ ? InternalResource.fromFile(kind, filePath) : null;
+ }
+
+ /** Create resource provider from directory path. */
+ public static ResourceProvider fromDirectory(Resource.Kind kind, Path dir) {
+ return new DirectoryResourceProvider(kind, dir.toAbsolutePath());
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/LazyClassCollection.java b/src/main/java/com/android/tools/r8/utils/LazyClassCollection.java
new file mode 100644
index 0000000..4f36811
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/LazyClassCollection.java
@@ -0,0 +1,199 @@
+// Copyright (c) 2017, 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.utils;
+
+import com.android.tools.r8.Resource;
+import com.android.tools.r8.ResourceProvider;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassPromise;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.JarApplicationReader;
+import com.android.tools.r8.graph.LazyClassFileLoader;
+import com.google.common.collect.ImmutableList;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Represents a collection of classes loaded lazily from a set of lazy resource
+ * providers. The collection is autonomous, it lazily collects promises but
+ * does not choose the classes in cases of conflicts, delaying it until
+ * the class is asked for.
+ *
+ * NOTE: only java class resources are allowed to be lazy loaded.
+ */
+public final class LazyClassCollection {
+ // Stores promises for types which have ever been asked for before. Special value
+ // EmptyPromise.INSTANCE indicates there were no resources for this type in any of
+ // the resource providers.
+ //
+ // Promises are potentially coming from different providers and chained, but in majority
+ // of the cases there will only be one promise per type. We store promises for all
+ // the resource providers and resolve the classes at the time it is queried.
+ //
+ // Must be synchronized on `classes`.
+ private final Map<DexType, DexClassPromise> classes = new IdentityHashMap<>();
+
+ // Available lazy resource providers.
+ private final List<ResourceProvider> providers;
+
+ // Application reader to be used. Note that the reader may be reused in
+ // many loaders and may be used concurrently, it is considered to be
+ // thread-safe since its only state is internal options which we
+ // consider immutable after they are initialized (barring dex factory
+ // which is thread-safe).
+ private final JarApplicationReader reader;
+
+ public LazyClassCollection(JarApplicationReader reader, List<ResourceProvider> providers) {
+ this.providers = ImmutableList.copyOf(providers);
+ this.reader = reader;
+ }
+
+ /**
+ * Returns a definition for a class or `null` if there is no such class.
+ * Parameter `dominator` represents the class promise that is considered
+ * to be already loaded, it may be null but if specified it may affect
+ * the conflict resolution. For example non-lazy loaded classpath class
+ * provided as `dominator` will conflict with lazy-loaded classpath classes.
+ */
+ public DexClassPromise get(DexType type, DexClassPromise dominator) {
+ DexClassPromise promise;
+
+ // Check if the promise already exists.
+ synchronized (classes) {
+ promise = classes.get(type);
+ }
+
+ if (promise == null) {
+ // Building a promise may be time consuming, we do it outside
+ // the lock so others don't have to wait.
+ promise = buildPromiseChain(type);
+
+ synchronized (classes) {
+ DexClassPromise existing = classes.get(type);
+ if (existing != null) {
+ promise = existing;
+ } else {
+ classes.put(type, promise);
+ }
+ }
+
+ assert promise != EmptyPromise.INSTANCE;
+ }
+
+ return promise == EmptyPromise.INSTANCE ? dominator : chooseClass(dominator, promise);
+ }
+
+ // Build chain of lazy promises or `null` if none of the providers
+ // provided resource for this type.
+ private DexClassPromise buildPromiseChain(DexType type) {
+ String descriptor = type.descriptor.toString();
+ DexClassPromise promise = null;
+ int size = providers.size();
+ for (int i = size - 1; i >= 0; i--) {
+ Resource resource = providers.get(i).getResource(descriptor);
+ if (resource == null) {
+ continue;
+ }
+
+ if (resource.getKind() == Resource.Kind.PROGRAM) {
+ throw new CompilationError("Attempt to load program class " +
+ type.toSourceString() + " via lazy resource provider");
+ }
+
+ assert resource instanceof InternalResource;
+ LazyClassFileLoader loader =
+ new LazyClassFileLoader(type, (InternalResource) resource, reader);
+ promise = (promise == null) ? loader : new DexClassPromiseChain(loader, promise);
+ }
+ return promise == null ? EmptyPromise.INSTANCE : promise;
+ }
+
+ // Chooses the proper promise. Recursion is not expected to be deep.
+ private DexClassPromise chooseClass(DexClassPromise dominator, DexClassPromise candidate) {
+ DexClassPromise best = (dominator == null) ? candidate
+ : DexApplication.chooseClass(dominator, candidate, /* skipLibDups: */ true);
+ return (candidate instanceof DexClassPromiseChain)
+ ? chooseClass(best, ((DexClassPromiseChain) candidate).next) : best;
+ }
+
+ private static final class EmptyPromise implements DexClassPromise {
+ static final EmptyPromise INSTANCE = new EmptyPromise();
+
+ @Override
+ public DexType getType() {
+ throw new Unreachable();
+ }
+
+ @Override
+ public DexClass.Origin getOrigin() {
+ throw new Unreachable();
+ }
+
+ @Override
+ public boolean isProgramClass() {
+ throw new Unreachable();
+ }
+
+ @Override
+ public boolean isClasspathClass() {
+ throw new Unreachable();
+ }
+
+ @Override
+ public boolean isLibraryClass() {
+ throw new Unreachable();
+ }
+
+ @Override
+ public DexClass get() {
+ throw new Unreachable();
+ }
+ }
+
+ private static final class DexClassPromiseChain implements DexClassPromise {
+ final DexClassPromise promise;
+ final DexClassPromise next;
+
+ private DexClassPromiseChain(DexClassPromise promise, DexClassPromise next) {
+ assert promise != null;
+ assert next != null;
+ this.promise = promise;
+ this.next = next;
+ }
+
+ @Override
+ public DexType getType() {
+ return promise.getType();
+ }
+
+ @Override
+ public DexClass.Origin getOrigin() {
+ return promise.getOrigin();
+ }
+
+ @Override
+ public boolean isProgramClass() {
+ return promise.isProgramClass();
+ }
+
+ @Override
+ public boolean isClasspathClass() {
+ return promise.isClasspathClass();
+ }
+
+ @Override
+ public boolean isLibraryClass() {
+ return promise.isLibraryClass();
+ }
+
+ @Override
+ public DexClass get() {
+ return promise.get();
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/PreloadedResourceProvider.java b/src/main/java/com/android/tools/r8/utils/PreloadedResourceProvider.java
new file mode 100644
index 0000000..e3d5652
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/PreloadedResourceProvider.java
@@ -0,0 +1,110 @@
+// Copyright (c) 2017, 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.utils;
+
+import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
+import static com.android.tools.r8.utils.FileUtils.isArchive;
+import static com.android.tools.r8.utils.FileUtils.isClassFile;
+
+import com.android.tools.r8.Resource;
+import com.android.tools.r8.ResourceProvider;
+import com.android.tools.r8.errors.CompilationError;
+import com.google.common.io.ByteStreams;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipInputStream;
+
+/** Lazy resource provider based on preloaded/prebuilt context. */
+public final class PreloadedResourceProvider implements ResourceProvider {
+ private final Resource.Kind kind;
+ private final Map<String, byte[]> content;
+
+ private PreloadedResourceProvider(Resource.Kind kind, Map<String, byte[]> content) {
+ this.kind = kind;
+ this.content = content;
+ }
+
+ @Override
+ public Resource getResource(String descriptor) {
+ byte[] bytes = content.get(descriptor);
+ if (bytes == null) {
+ return null;
+ }
+ return InternalResource.fromBytes(kind, bytes, Collections.singleton(descriptor));
+ }
+
+ /** Create preloaded content resource provider from archive file. */
+ public static ResourceProvider fromArchive(
+ Resource.Kind kind, Path archive) throws IOException {
+ assert isArchive(archive);
+ Builder builder = builder();
+ try (ZipInputStream stream = new ZipInputStream(new FileInputStream(archive.toFile()))) {
+ ZipEntry entry;
+ while ((entry = stream.getNextEntry()) != null) {
+ String name = entry.getName();
+ if (isClassFile(Paths.get(name))) {
+ builder.addResource(guessTypeDescriptor(name), ByteStreams.toByteArray(stream));
+ }
+ }
+ } catch (ZipException e) {
+ throw new CompilationError(
+ "Zip error while reading '" + archive + "': " + e.getMessage(), e);
+ }
+
+ return builder.build(kind);
+ }
+
+ // Guess class descriptor from location of the class file.
+ static String guessTypeDescriptor(Path name) {
+ return guessTypeDescriptor(name.toString());
+ }
+
+ // Guess class descriptor from location of the class file.
+ private static String guessTypeDescriptor(String name) {
+ assert name != null;
+ assert name.endsWith(CLASS_EXTENSION) :
+ "Name " + name + " must have " + CLASS_EXTENSION + " suffix";
+ String descriptor = name.substring(0, name.length() - CLASS_EXTENSION.length());
+ if (descriptor.contains(".")) {
+ throw new CompilationError("Unexpected file name in the archive: " + name);
+ }
+ return 'L' + descriptor + ';';
+ }
+
+ /** Create a new empty builder. */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static final class Builder {
+ private Map<String, byte[]> content = new HashMap<>();
+
+ private Builder() {
+ }
+
+ public Builder addResource(String descriptor, byte[] bytes) {
+ assert content != null;
+ assert descriptor != null;
+ assert bytes != null;
+ assert !content.containsKey(descriptor);
+ content.put(descriptor, bytes);
+ return this;
+ }
+
+ public PreloadedResourceProvider build(Resource.Kind kind) {
+ assert content != null;
+ assert kind != null;
+ PreloadedResourceProvider provider = new PreloadedResourceProvider(kind, content);
+ content = null;
+ return provider;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
index 4fd3276..e2e88e2 100644
--- a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
@@ -32,10 +32,10 @@
import org.junit.Assert;
import org.junit.Test;
-public class D8IncrementalRunExamplesAndroidOTest
+public abstract class D8IncrementalRunExamplesAndroidOTest
extends RunExamplesAndroidOTest<D8Command.Builder> {
- class D8IncrementalTestRunner extends TestRunner {
+ abstract class D8IncrementalTestRunner extends TestRunner {
D8IncrementalTestRunner(String testName, String packageName, String mainClass) {
super(testName, packageName, mainClass);
@@ -58,7 +58,7 @@
List<String> classFiles = collectClassFiles(testJarFile);
for (String classFile : classFiles) {
AndroidApp app = compileClassFiles(
- testJarFile.toString(), Collections.singletonList(classFile), null, OutputMode.Indexed);
+ testJarFile, Collections.singletonList(classFile), null, OutputMode.Indexed);
assert app.getDexProgramResources().size() == 1;
fileToResource.put(
makeRelative(testJarFile, Paths.get(classFile)).toString(),
@@ -73,7 +73,7 @@
TreeMap<String, Resource> fileToResource = new TreeMap<>();
List<String> classFiles = collectClassFiles(testJarFile);
AndroidApp app = compileClassFiles(
- testJarFile.toString(), classFiles, output, OutputMode.FilePerClass);
+ testJarFile, classFiles, output, OutputMode.FilePerClass);
for (InternalResource resource : app.getDexProgramResources()) {
String classDescriptor = resource.getSingleClassDescriptorOrNull();
Assert.assertNotNull("Add resources are expected to have a descriptor", classDescriptor);
@@ -100,18 +100,25 @@
private List<String> collectClassFiles(Path testJarFile) {
List<String> result = new ArrayList<>();
// Collect Java 8 classes.
- Path parent = testJarFile.getParent();
- File packageDir = parent.resolve(Paths.get("classes", packageName)).toFile();
- collectClassFiles(packageDir, result);
+ collectClassFiles(getClassesRoot(testJarFile).toFile(), result);
// Collect legacy classes.
- Path legacyPath = Paths.get("..",
- parent.getFileName().toString() + "Legacy", "classes", packageName);
- packageDir = parent.resolve(legacyPath).toFile();
- collectClassFiles(packageDir, result);
+ collectClassFiles(getLegacyClassesRoot(testJarFile).toFile(), result);
Collections.sort(result);
return result;
}
+ Path getClassesRoot(Path testJarFile) {
+ Path parent = testJarFile.getParent();
+ return parent.resolve(Paths.get("classes", packageName));
+ }
+
+ Path getLegacyClassesRoot(Path testJarFile) {
+ Path parent = testJarFile.getParent();
+ Path legacyPath = Paths.get("..",
+ parent.getFileName().toString() + "Legacy", "classes", packageName);
+ return parent.resolve(legacyPath);
+ }
+
private void collectClassFiles(File dir, List<String> result) {
File[] files = dir.listFiles();
if (files != null) {
@@ -125,10 +132,10 @@
}
}
- AndroidApp compileClassFiles(String classpath,
+ AndroidApp compileClassFiles(Path testJarFile,
List<String> inputFiles, Path output, OutputMode outputMode) throws Throwable {
D8Command.Builder builder = D8Command.builder();
- builder = builder.addClasspathFiles(Paths.get(classpath));
+ addClasspathReference(testJarFile, builder);
for (String inputFile : inputFiles) {
builder = builder.addProgramFiles(Paths.get(inputFile));
}
@@ -141,6 +148,7 @@
if (output != null) {
builder = builder.setOutputPath(output);
}
+ addLibraryReference(builder, Paths.get(ToolHelper.getAndroidJar(builder.getMinApiLevel())));
D8Command command = builder.build();
try {
return ToolHelper.runD8(command, this::combinedOptionConsumer);
@@ -173,6 +181,12 @@
throw re.getCause() == null ? re : re.getCause();
}
}
+
+ abstract void addClasspathReference(
+ Path testJarFile, D8Command.Builder builder) throws IOException;
+
+ abstract void addLibraryReference(
+ D8Command.Builder builder, Path location) throws IOException;
}
@Test
@@ -256,10 +270,7 @@
Assert.assertArrayEquals(expectedFileNames, dexFiles);
}
- @Override
- D8IncrementalTestRunner test(String testName, String packageName, String mainClass) {
- return new D8IncrementalTestRunner(testName, packageName, mainClass);
- }
+ abstract D8IncrementalTestRunner test(String testName, String packageName, String mainClass);
static byte[] readFromResource(Resource resource) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
diff --git a/src/test/java/com/android/tools/r8/D8RegularLazyRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8RegularLazyRunExamplesAndroidOTest.java
new file mode 100644
index 0000000..38acabe
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/D8RegularLazyRunExamplesAndroidOTest.java
@@ -0,0 +1,34 @@
+// Copyright (c) 2017, 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;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+public class D8RegularLazyRunExamplesAndroidOTest
+ extends D8IncrementalRunExamplesAndroidOTest {
+ class D8LazyTestRunner extends D8IncrementalTestRunner {
+
+ D8LazyTestRunner(String testName, String packageName, String mainClass) {
+ super(testName, packageName, mainClass);
+ }
+
+ @Override
+ void addClasspathReference(Path testJarFile, D8Command.Builder builder) throws IOException {
+ builder.addClasspathFiles(testJarFile);
+ }
+
+ @Override
+ void addLibraryReference(D8Command.Builder builder, Path location) throws IOException {
+ builder.addLibraryFiles(Paths.get(ToolHelper.getAndroidJar(builder.getMinApiLevel())));
+ }
+ }
+
+ @Override
+ D8IncrementalTestRunner test(String testName, String packageName, String mainClass) {
+ return new D8LazyTestRunner(testName, packageName, mainClass);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/D8ResourceProviderRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8ResourceProviderRunExamplesAndroidOTest.java
new file mode 100644
index 0000000..b7181b8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/D8ResourceProviderRunExamplesAndroidOTest.java
@@ -0,0 +1,43 @@
+// Copyright (c) 2017, 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;
+
+import com.android.tools.r8.utils.DirectoryResourceProvider;
+import com.android.tools.r8.utils.PreloadedResourceProvider;
+import java.io.IOException;
+import java.nio.file.Path;
+
+public class D8ResourceProviderRunExamplesAndroidOTest
+ extends D8IncrementalRunExamplesAndroidOTest {
+ class D8LazyTestRunner extends D8IncrementalTestRunner {
+
+ D8LazyTestRunner(String testName, String packageName, String mainClass) {
+ super(testName, packageName, mainClass);
+ }
+
+ @Override
+ void addClasspathReference(Path testJarFile, D8Command.Builder builder) {
+ addClasspathPath(getClassesRoot(testJarFile), builder);
+ addClasspathPath(getLegacyClassesRoot(testJarFile), builder);
+ }
+
+ private void addClasspathPath(Path location, D8Command.Builder builder) {
+ builder.addClasspathResourceProvider(
+ DirectoryResourceProvider.fromDirectory(
+ Resource.Kind.CLASSPATH, location.resolve("..")));
+ }
+
+ @Override
+ void addLibraryReference(D8Command.Builder builder, Path location) throws IOException {
+ builder.addLibraryResourceProvider(
+ PreloadedResourceProvider.fromArchive(Resource.Kind.LIBRARY, location));
+ }
+ }
+
+ @Override
+ D8IncrementalTestRunner test(String testName, String packageName, String mainClass) {
+ return new D8LazyTestRunner(testName, packageName, mainClass);
+ }
+}