Prepare for "lookup in library classes before program classes"

This reverts commit 7464530e9286ec3b6841208c66eec7bbb2dd54f2, adding
a flag to switch between "lookup in library classes before program
classes" and "lookup in program classes before library classes".

The default is kept as "lookup in program classes before library
classes"

This option for switching is only intended to temporary until
the related definitionFor issues have been resolved, and the
compilation setup for all clients has been fixed.

Bug: 120884788
Change-Id: I861739ffbd18cd5927fd6088a380a24cb8804883
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index 584b4de..562a4e4 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -128,6 +128,7 @@
     private CompilationMode mode;
     private int minApiLevel = 0;
     private boolean disableDesugaring = false;
+    private boolean lookupLibraryBeforeProgram = true;
     private boolean optimizeMultidexForLinearAlloc = false;
 
     abstract CompilationMode defaultCompilationMode();
diff --git a/src/main/java/com/android/tools/r8/DexSplitterHelper.java b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
index 34e67e6..545de8e 100644
--- a/src/main/java/com/android/tools/r8/DexSplitterHelper.java
+++ b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
@@ -132,7 +132,7 @@
       String feature = featureClassMapping.featureForClass(clazzName);
       LazyLoadedDexApplication.Builder featureApplication = applications.get(feature);
       if (featureApplication == null) {
-        featureApplication = DexApplication.builder(app.dexItemFactory, app.timing);
+        featureApplication = DexApplication.builder(app.options, app.timing);
         // If this is the base, we add the main dex list.
         if (feature.equals(featureClassMapping.getBaseName())) {
           featureApplication.addToMainDexList(app.mainDexList);
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 4b53365..3d6fb19 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -603,8 +603,15 @@
       GraphConsumer mainDexKeptGraphConsumer,
       Consumer<List<ProguardConfigurationRule>> syntheticProguardRulesConsumer,
       boolean optimizeMultidexForLinearAlloc) {
-    super(inputApp, mode, programConsumer, mainDexListConsumer, minApiLevel, reporter,
-        enableDesugaring, optimizeMultidexForLinearAlloc);
+    super(
+        inputApp,
+        mode,
+        programConsumer,
+        mainDexListConsumer,
+        minApiLevel,
+        reporter,
+        enableDesugaring,
+        optimizeMultidexForLinearAlloc);
     assert proguardConfiguration != null;
     assert mainDexKeepRules != null;
     this.mainDexKeepRules = mainDexKeepRules;
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 315c217..55978a8 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -102,7 +102,7 @@
     assert verifyMainDexOptionsCompatible(inputApp, options);
     timing.begin("DexApplication.read");
     final LazyLoadedDexApplication.Builder builder =
-        DexApplication.builder(itemFactory, timing, resolver);
+        DexApplication.builder(options, timing, resolver);
     try {
       List<Future<?>> futures = new ArrayList<>();
       // Still preload some of the classes, primarily for two reasons:
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 8602d85..5569d58 100644
--- a/src/main/java/com/android/tools/r8/graph/DexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DexApplication.java
@@ -33,6 +33,7 @@
 
   public final Timing timing;
 
+  public final InternalOptions options;
   public final DexItemFactory dexItemFactory;
 
   // Information on the lexicographically largest string referenced from code.
@@ -44,14 +45,15 @@
       ImmutableList<DataResourceProvider> dataResourceProviders,
       ImmutableSet<DexType> mainDexList,
       String deadCode,
-      DexItemFactory dexItemFactory,
+      InternalOptions options,
       DexString highestSortingString,
       Timing timing) {
     this.proguardMap = proguardMap;
     this.dataResourceProviders = dataResourceProviders;
     this.mainDexList = mainDexList;
     this.deadCode = deadCode;
-    this.dexItemFactory = dexItemFactory;
+    this.options = options;
+    this.dexItemFactory = options.itemFactory;
     this.highestSortingString = highestSortingString;
     this.timing = timing;
   }
@@ -124,6 +126,7 @@
 
     final List<DataResourceProvider> dataResourceProviders = new ArrayList<>();
 
+    public final InternalOptions options;
     public final DexItemFactory dexItemFactory;
     ClassNameMapper proguardMap;
     final Timing timing;
@@ -133,8 +136,9 @@
     final Set<DexType> mainDexList = Sets.newIdentityHashSet();
     private final Collection<DexProgramClass> synthesizedClasses;
 
-    public Builder(DexItemFactory dexItemFactory, Timing timing) {
-      this.dexItemFactory = dexItemFactory;
+    public Builder(InternalOptions options, Timing timing) {
+      this.options = options;
+      this.dexItemFactory = options.itemFactory;
       this.timing = timing;
       this.deadCode = null;
       this.synthesizedClasses = new ArrayList<>();
@@ -148,6 +152,7 @@
       proguardMap = application.getProguardMap();
       timing = application.timing;
       highestSortingString = application.highestSortingString;
+      options = application.options;
       dexItemFactory = application.dexItemFactory;
       mainDexList.addAll(application.mainDexList);
       deadCode = application.deadCode;
@@ -226,13 +231,13 @@
     public abstract DexApplication build();
   }
 
-  public static LazyLoadedDexApplication.Builder builder(DexItemFactory factory, Timing timing) {
-    return builder(factory, timing, ProgramClassCollection::resolveClassConflictImpl);
+  public static LazyLoadedDexApplication.Builder builder(InternalOptions options, Timing timing) {
+    return builder(options, timing, ProgramClassCollection::resolveClassConflictImpl);
   }
 
   public static LazyLoadedDexApplication.Builder builder(
-      DexItemFactory factory, Timing timing, ProgramClassConflictResolver resolver) {
-    return new LazyLoadedDexApplication.Builder(resolver, factory, timing);
+      InternalOptions options, Timing timing, ProgramClassConflictResolver resolver) {
+    return new LazyLoadedDexApplication.Builder(resolver, options, timing);
   }
 
   public DirectMappedDexApplication asDirect() {
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index 9538a2b..533e983 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.DataResourceProvider;
 import com.android.tools.r8.graph.LazyLoadedDexApplication.AllClasses;
 import com.android.tools.r8.naming.ClassNameMapper;
-import com.android.tools.r8.utils.ProgramClassCollection;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -41,7 +41,7 @@
       ImmutableList<DataResourceProvider> dataResourceProviders,
       ImmutableSet<DexType> mainDexList,
       String deadCode,
-      DexItemFactory dexItemFactory,
+      InternalOptions options,
       DexString highestSortingString,
       Timing timing) {
     super(
@@ -49,7 +49,7 @@
         dataResourceProviders,
         mainDexList,
         deadCode,
-        dexItemFactory,
+        options,
         highestSortingString,
         timing);
     this.allClasses = Collections.unmodifiableMap(allClasses);
@@ -166,9 +166,9 @@
       super(application);
       // As a side-effect, this will force-load all classes.
       AllClasses allClasses = application.loadAllClasses();
-      assert application.programClasses().equals(allClasses.getProgramClasses());
       libraryClasses = allClasses.getLibraryClasses();
       classpathClasses = allClasses.getClasspathClasses();
+      replaceProgramClasses(allClasses.getProgramClasses());
     }
 
     private Builder(DirectMappedDexApplication application) {
@@ -185,15 +185,10 @@
     @Override
     public DexApplication build() {
       // Rebuild the map. This will fail if keys are not unique.
-      // TODO(zerny): It seems weird that we have conflict resolution here.
-      ImmutableList<DexProgramClass> newProgramClasses =
-          ImmutableList.copyOf(
-              ProgramClassCollection.create(
-                      programClasses, ProgramClassCollection::resolveClassConflictImpl)
-                  .getAllClasses());
       // TODO(zerny): Consider not rebuilding the map if no program classes are added.
-      Map<DexType, DexClass> allClasses = new IdentityHashMap<>(
-          newProgramClasses.size() + classpathClasses.size() + libraryClasses.size());
+      Map<DexType, DexClass> allClasses =
+          new IdentityHashMap<>(
+              programClasses.size() + classpathClasses.size() + libraryClasses.size());
       // Note: writing classes in reverse priority order, so a duplicate will be correctly ordered.
       // There should never be duplicates and that is asserted in the addAll subroutine.
       addAll(allClasses, libraryClasses);
@@ -202,13 +197,13 @@
       return new DirectMappedDexApplication(
           proguardMap,
           allClasses,
-          newProgramClasses,
+          ImmutableList.copyOf(programClasses),
           classpathClasses,
           libraryClasses,
           ImmutableList.copyOf(dataResourceProviders),
           ImmutableSet.copyOf(mainDexList),
           deadCode,
-          dexItemFactory,
+          options,
           highestSortingString,
           timing);
     }
diff --git a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
index a58b9ac..3e92726 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.dex.ApplicationReader.ProgramClassConflictResolver;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.utils.ClasspathClassCollection;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.LibraryClassCollection;
 import com.android.tools.r8.utils.ProgramClassCollection;
 import com.android.tools.r8.utils.Timing;
@@ -35,7 +36,7 @@
       LibraryClassCollection libraryClasses,
       ImmutableSet<DexType> mainDexList,
       String deadCode,
-      DexItemFactory dexItemFactory,
+      InternalOptions options,
       DexString highestSortingString,
       Timing timing) {
     super(
@@ -43,7 +44,7 @@
         dataResourceProviders,
         mainDexList,
         deadCode,
-        dexItemFactory,
+        options,
         highestSortingString,
         timing);
     this.programClasses = programClasses;
@@ -60,12 +61,25 @@
   @Override
   public DexClass definitionFor(DexType type) {
     assert type.isClassType() : "Cannot lookup definition for type: " + type;
-    DexClass clazz = programClasses.get(type);
-    if (clazz == null && classpathClasses != null) {
-      clazz = classpathClasses.get(type);
-    }
-    if (clazz == null && libraryClasses != null) {
-      clazz = libraryClasses.get(type);
+    DexClass clazz = null;
+    if (options.lookupLibraryBeforeProgram) {
+      if (libraryClasses != null) {
+        clazz = libraryClasses.get(type);
+      }
+      if (clazz == null) {
+        clazz = programClasses.get(type);
+      }
+      if (clazz == null && classpathClasses != null) {
+        clazz = classpathClasses.get(type);
+      }
+    } else {
+      clazz = programClasses.get(type);
+      if (clazz == null && classpathClasses != null) {
+        clazz = classpathClasses.get(type);
+      }
+      if (clazz == null && libraryClasses != null) {
+        clazz = libraryClasses.get(type);
+      }
     }
     return clazz;
   }
@@ -88,24 +102,11 @@
     AllClasses(
         LibraryClassCollection libraryClassesLoader,
         ClasspathClassCollection classpathClassesLoader,
-        ProgramClassCollection programClassesLoader) {
-      // Collect loaded classes in the precedence order program classes, class path classes and
-      // library classes.
-      // TODO(b/120884788): Change library priority.
-      assert programClassesLoader != null;
-      // Program classes are supposed to be loaded, but force-loading them is no-op.
-      programClassesLoader.forceLoad(type -> true);
-      Map<DexType, DexProgramClass> allProgramClasses = programClassesLoader.getAllClassesInMap();
-      int expectedMaxSize = allProgramClasses.size();
-      programClasses = ImmutableList.copyOf(allProgramClasses.values());
+        ProgramClassCollection programClassesLoader,
+        InternalOptions options) {
+      int expectedMaxSize = 0;
 
-      Map<DexType, DexClasspathClass> allClasspathClasses = null;
-      if (classpathClassesLoader != null) {
-        classpathClassesLoader.forceLoad(type -> true);
-        allClasspathClasses = classpathClassesLoader.getAllClassesInMap();
-        expectedMaxSize += allClasspathClasses.size();
-      }
-
+      // Force-load library classes.
       Map<DexType, DexLibraryClass> allLibraryClasses = null;
       if (libraryClassesLoader != null) {
         libraryClassesLoader.forceLoad(type -> true);
@@ -113,38 +114,39 @@
         expectedMaxSize += allLibraryClasses.size();
       }
 
-      // Note: using hash map for building as the immutable builder does not support contains.
+      // Program classes should be fully loaded.
+      assert programClassesLoader != null;
+      assert programClassesLoader.isFullyLoaded();
+      programClassesLoader.forceLoad(type -> true);
+      Map<DexType, DexProgramClass> allProgramClasses = programClassesLoader.getAllClassesInMap();
+      expectedMaxSize += allProgramClasses.size();
+
+      // Force-load classpath classes.
+      Map<DexType, DexClasspathClass> allClasspathClasses = null;
+      if (classpathClassesLoader != null) {
+        classpathClassesLoader.forceLoad(type -> true);
+        allClasspathClasses = classpathClassesLoader.getAllClassesInMap();
+        expectedMaxSize += allClasspathClasses.size();
+      }
+
+      // Collect loaded classes in the precedence order library classes, program classes and
+      // class path classes or program classes, classpath classes and library classes depending
+      // on the configured lookup order.
       Map<DexType, DexClass> prioritizedClasses = new IdentityHashMap<>(expectedMaxSize);
-      prioritizedClasses.putAll(allProgramClasses);
-
-      if (allClasspathClasses != null) {
-        ImmutableList.Builder<DexClasspathClass> builder = ImmutableList.builder();
-        allClasspathClasses.forEach(
-            (type, clazz) -> {
-              if (!prioritizedClasses.containsKey(type)) {
-                prioritizedClasses.put(type, clazz);
-                builder.add(clazz);
-              }
-            });
-        classpathClasses = builder.build();
+      if (options.lookupLibraryBeforeProgram) {
+        libraryClasses = fillPrioritizedClasses(allLibraryClasses, prioritizedClasses);
+        programClasses = fillPrioritizedClasses(allProgramClasses, prioritizedClasses);
+        classpathClasses = fillPrioritizedClasses(allClasspathClasses, prioritizedClasses);
       } else {
-        classpathClasses = ImmutableList.of();
+        programClasses = fillPrioritizedClasses(allProgramClasses, prioritizedClasses);
+        classpathClasses = fillPrioritizedClasses(allClasspathClasses, prioritizedClasses);
+        libraryClasses = fillPrioritizedClasses(allLibraryClasses, prioritizedClasses);
       }
 
-      if (allLibraryClasses != null) {
-        ImmutableList.Builder<DexLibraryClass> builder = ImmutableList.builder();
-        allLibraryClasses.forEach(
-            (type, clazz) -> {
-              if (!prioritizedClasses.containsKey(type)) {
-                prioritizedClasses.put(type, clazz);
-                builder.add(clazz);
-              }
-            });
-        libraryClasses = builder.build();
-      } else {
-        libraryClasses = ImmutableList.of();
-      }
       allClasses = Collections.unmodifiableMap(prioritizedClasses);
+
+      assert prioritizedClasses.size()
+          == libraryClasses.size() + classpathClasses.size() + programClasses.size();
     }
 
     public Map<DexType, DexClass> getAllClasses() {
@@ -164,11 +166,28 @@
     }
   }
 
+  private static <T extends DexClass> ImmutableList<T> fillPrioritizedClasses(
+      Map<DexType, T> classCollection, Map<DexType, DexClass> prioritizedClasses) {
+    if (classCollection != null) {
+      ImmutableList.Builder<T> builder = ImmutableList.builder();
+      classCollection.forEach(
+          (type, clazz) -> {
+            if (!prioritizedClasses.containsKey(type)) {
+              prioritizedClasses.put(type, clazz);
+              builder.add(clazz);
+            }
+          });
+      return builder.build();
+    } else {
+      return ImmutableList.of();
+    }
+  }
+
   /**
    * Force load all classes and return type -> class map containing all the classes.
    */
   public AllClasses loadAllClasses() {
-    return new AllClasses(libraryClasses, classpathClasses, programClasses);
+    return new AllClasses(libraryClasses, classpathClasses, programClasses, options);
   }
 
   public static class Builder extends DexApplication.Builder<Builder> {
@@ -177,8 +196,8 @@
     private LibraryClassCollection libraryClasses;
     private final ProgramClassConflictResolver resolver;
 
-    Builder(ProgramClassConflictResolver resolver, DexItemFactory dexItemFactory, Timing timing) {
-      super(dexItemFactory, timing);
+    Builder(ProgramClassConflictResolver resolver, InternalOptions options, Timing timing) {
+      super(options, timing);
       this.resolver = resolver;
       this.classpathClasses = null;
       this.libraryClasses = null;
@@ -216,7 +235,7 @@
           libraryClasses,
           ImmutableSet.copyOf(mainDexList),
           deadCode,
-          dexItemFactory,
+          options,
           highestSortingString,
           timing);
     }
diff --git a/src/main/java/com/android/tools/r8/utils/ClassMap.java b/src/main/java/com/android/tools/r8/utils/ClassMap.java
index d8366df..416b389 100644
--- a/src/main/java/com/android/tools/r8/utils/ClassMap.java
+++ b/src/main/java/com/android/tools/r8/utils/ClassMap.java
@@ -163,10 +163,10 @@
     ClassProvider<T> classProvider;
 
     // Cache value of class provider, as it might change concurrently.
-    classProvider = this.classProvider.get();
-    if (classProvider == null) {
+    if (isFullyLoaded()) {
       return;
     }
+    classProvider = this.classProvider.get();
 
     // Collects the types which might be represented in fully loaded class map.
     knownClasses = Sets.newIdentityHashSet();
@@ -224,6 +224,10 @@
     }
   }
 
+  public boolean isFullyLoaded() {
+    return this.classProvider.get() == null;
+  }
+
   // Supplier implementing a thread-safe loader for a class loaded from a
   // class provider. Helps avoid synchronizing on the whole class map
   // when loading a class.
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index e93de6a..47a8b7f 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -290,6 +290,12 @@
   // Flag to turn on/off processing of @dalvik.annotation.codegen.CovariantReturnType and
   // @dalvik.annotation.codegen.CovariantReturnType$CovariantReturnTypes.
   public boolean processCovariantReturnTypeAnnotations = true;
+  // Flag to control library/program class lookup order.
+  // TODO(120884788): Enable this flag as the default.
+  public boolean lookupLibraryBeforeProgram = false;
+  // TODO(120884788): Leave this system property as a stop-gap for some time.
+  // public boolean lookupLibraryBeforeProgram =
+  //     System.getProperty("com.android.tools.r8.lookupProgramBeforeLibrary") == null;
 
   // Whether or not to check for valid multi-dex builds.
   //
diff --git a/src/test/java/com/android/tools/r8/JunitAvailabilityInHostArtTest.java b/src/test/java/com/android/tools/r8/JunitAvailabilityInHostArtTest.java
new file mode 100644
index 0000000..9e82042
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/JunitAvailabilityInHostArtTest.java
@@ -0,0 +1,71 @@
+// Copyright (c) 2019, 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 static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class JunitAvailabilityInHostArtTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  public JunitAvailabilityInHostArtTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  // It of the Art host VMs only 5.1.1 and 6.0.1 have junit.framework.Assert present at runtime.
+  private void checkResult(TestRunResult<?> result) {
+    if (parameters.getRuntime().isDex()
+        && ((parameters.getRuntime().asDex().getVm() == DexVm.ART_6_0_1_HOST)
+            || (parameters.getRuntime().asDex().getVm() == DexVm.ART_5_1_1_HOST))) {
+      result.assertSuccessWithOutput(StringUtils.lines("class junit.framework.Assert"));
+    } else {
+      result.assertFailureWithErrorThatMatches(containsString("ClassNotFoundException"));
+    }
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(JunitAvailabilityInHostArtTest.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getRuntime())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .apply(this::checkResult);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue("Only run D8 for Dex backend", parameters.getBackend() == Backend.DEX);
+    testForD8()
+        .addInnerClasses(JunitAvailabilityInHostArtTest.class)
+        .setMinApi(parameters.getRuntime())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .apply(this::checkResult);
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) throws Exception {
+      System.out.println(Class.forName("junit.framework.Assert"));
+    }
+  }
+
+  static class A {}
+}
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 3dfac08..18a6e69 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -771,6 +771,7 @@
           .match(TestCondition
               .runtimes(DexVm.Version.V4_0_4, DexVm.Version.V4_4_4, DexVm.Version.V5_1_1,
                   DexVm.Version.V6_0_1));
+  // TODO(herhut): Change to V8_0_0 once we have a new art VM.
   private static final TestCondition beforeAndroidO =
       TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V7_0_0));
   // TODO(herhut): Change to V8_0_0 once we have a new art VM.
@@ -919,6 +920,32 @@
                           CompilerUnderTest.D8_AFTER_R8CF),
                       TestCondition.runtimes(DexVm.Version.V4_0_4, DexVm.Version.V4_4_4))))
           .put("979-const-method-handle", beforeAndroidP)
+          // Missing class junit.framework.Assert (see JunitAvailabilityInHostArtTest).
+          // TODO(120884788): Add this again.
+          /*
+          .put(
+              "021-string2",
+              TestCondition.or(
+                  TestCondition.match(
+                      TestCondition.compilers(CompilerUnderTest.D8_AFTER_R8CF),
+                      TestCondition.runtimesFrom(DexVm.Version.V7_0_0)),
+                  TestCondition.match(
+                      TestCondition.compilers(CompilerUnderTest.D8_AFTER_R8CF),
+                      TestCondition.runtimes(DexVm.Version.V4_0_4, DexVm.Version.V4_4_4))))
+          */
+          // Missing class junit.framework.Assert (see JunitAvailabilityInHostArtTest).
+          // TODO(120884788): Add this again.
+          /*
+          .put(
+              "082-inline-execute",
+              TestCondition.or(
+                  TestCondition.match(
+                      TestCondition.compilers(CompilerUnderTest.D8_AFTER_R8CF),
+                      TestCondition.runtimesFrom(DexVm.Version.V7_0_0)),
+                  TestCondition.match(
+                      TestCondition.compilers(CompilerUnderTest.D8_AFTER_R8CF),
+                      TestCondition.runtimes(DexVm.Version.V4_0_4, DexVm.Version.V4_4_4))))
+          */
           .build();
 
   // Tests where code generation fails.
diff --git a/src/test/java/com/android/tools/r8/classlookup/LibraryClassExtendsProgramClassTest.java b/src/test/java/com/android/tools/r8/classlookup/LibraryClassExtendsProgramClassTest.java
index a68358a..79a084d 100644
--- a/src/test/java/com/android/tools/r8/classlookup/LibraryClassExtendsProgramClassTest.java
+++ b/src/test/java/com/android/tools/r8/classlookup/LibraryClassExtendsProgramClassTest.java
@@ -4,18 +4,36 @@
 
 package com.android.tools.r8.classlookup;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.jasmin.JasminBuilder;
-import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.util.List;
 import org.junit.BeforeClass;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
+// This test used to test R8 errors/warnings when library class extends program class. Before
+// the change fixing b/120884788, that could easily happen as lookup would lookup in program
+// classes before library classes, and the Android library included parts of JUnit, which could
+// also easily end up as program classes when JUnit was used by a program.
+//
+// Now that library classes are looked up before program classes these JUnit classes will be
+// found in the library and the ones in program will be ignored and not end up in the output.
+//
+// For a D8 compilation any class passed as input will end up in the output.
+@RunWith(Parameterized.class)
 public class LibraryClassExtendsProgramClassTest extends TestBase {
 
   private static List<byte[]> junitClasses;
@@ -27,14 +45,86 @@
     junitClasses = builder.buildClasses();
   }
 
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  public LibraryClassExtendsProgramClassTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private void checkClassesInResult(CodeInspector inspector) {
+    if (parameters.isDexRuntime()) {
+      noClassesInResult(inspector);
+    } else {
+      testCaseClassInResult(inspector);
+    }
+  }
+
+  private void noClassesInResult(CodeInspector inspector) {
+    assertEquals(1, inspector.allClasses().size());
+    assertThat(inspector.clazz(TestClass.class), isPresent());
+  }
+
+  private void testCaseClassInResult(CodeInspector inspector) {
+    assertEquals(2, inspector.allClasses().size());
+    assertThat(inspector.clazz("junit.framework.TestCase"), isPresent());
+    assertThat(inspector.clazz(TestClass.class), isPresent());
+  }
+
+  @Test
+  public void testFullMode() throws Exception {
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters.getApiLevel())
+        .addProgramClasses(TestClass.class)
+        .addProgramClassFileData(junitClasses)
+        .addKeepAllClassesRule()
+        // TODO(120884788): Remove when this is the default.
+        .addOptionsModification(options -> options.lookupLibraryBeforeProgram = true)
+        .compile()
+        .inspect(this::checkClassesInResult)
+        .assertNoMessages();
+  }
+
+  @Test
+  public void testCompatibilityMode() throws Exception {
+    testForR8Compat(parameters.getBackend())
+        .setMinApi(parameters.getApiLevel())
+        .addProgramClasses(TestClass.class)
+        .addProgramClassFileData(junitClasses)
+        .addKeepAllClassesRule()
+        // TODO(120884788): Remove when this is the default.
+        .addOptionsModification(options -> options.lookupLibraryBeforeProgram = true)
+        .compile()
+        .inspect(this::checkClassesInResult)
+        .assertNoMessages();
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue("Only run D8 for Dex backend", parameters.isDexRuntime());
+    testForD8()
+        .setMinApi(parameters.getApiLevel())
+        .addProgramClasses(TestClass.class)
+        .addProgramClassFileData(junitClasses)
+        .compile()
+        .inspect(this::testCaseClassInResult)
+        .assertNoMessages();
+  }
+
   @Test
   public void testFullModeError() {
+    assumeTrue("Only run for Dex backend", parameters.isDexRuntime());
     try {
-      testForR8(Backend.DEX)
-          .setMinApi(AndroidApiLevel.O)
+      testForR8(parameters.getBackend())
+          .setMinApi(parameters.getApiLevel())
           .addProgramClasses(TestClass.class)
           .addProgramClassFileData(junitClasses)
           .addKeepAllClassesRule()
+          .addOptionsModification(options -> options.lookupLibraryBeforeProgram = false)
           .compile();
       fail("Succeeded in full mode");
     } catch (CompilationFailedException e) {
@@ -44,12 +134,14 @@
 
   @Test
   public void testCompatibilityModeWarning() throws Exception {
+    assumeTrue("Only run for Dex backend", parameters.isDexRuntime());
     R8TestCompileResult result =
-        testForR8Compat(Backend.DEX)
-            .setMinApi(AndroidApiLevel.O)
+        testForR8Compat(parameters.getBackend())
+            .setMinApi(parameters.getApiLevel())
             .addProgramClasses(TestClass.class)
             .addProgramClassFileData(junitClasses)
             .addKeepAllClassesRule()
+            .addOptionsModification(options -> options.lookupLibraryBeforeProgram = false)
             .compile()
             .assertOnlyWarnings();
 
@@ -69,12 +161,13 @@
 
   @Test
   public void testWithDontWarn() throws Exception {
-    testForR8(Backend.DEX)
-        .setMinApi(AndroidApiLevel.O)
-        .addProgramClasses(TestClass.class)
+    assumeTrue("Only run for Dex backend", parameters.isDexRuntime());
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters.getApiLevel())
         .addProgramClassFileData(junitClasses)
         .addKeepAllClassesRule()
         .addKeepRules("-dontwarn android.test.**")
+        .addOptionsModification(options -> options.lookupLibraryBeforeProgram = false)
         .compile()
         .assertNoMessages();
   }
diff --git a/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java b/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java
index 9060e55..75ffea7 100644
--- a/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java
+++ b/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java
@@ -9,6 +9,8 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Reporter;
 import java.util.Collections;
 import org.junit.Assert;
 import org.junit.Test;
@@ -17,7 +19,8 @@
 
   private ObjectToOffsetMapping emptyObjectTObjectMapping() {
     return new ObjectToOffsetMapping(
-        DexApplication.builder(new DexItemFactory(), null).build(),
+        DexApplication.builder(new InternalOptions(new DexItemFactory(), new Reporter()), null)
+            .build(),
         Collections.emptyList(),
         Collections.emptyList(),
         Collections.emptyList(),
diff --git a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
index a6ace5b..2186d02 100644
--- a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
+++ b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
@@ -129,14 +129,14 @@
         Constants.MAX_NON_JUMBO_INDEX - 1,
         classes);
 
-    DexApplication.Builder builder = DirectMappedDexApplication
-        .builder(dexItemFactory, new Timing("SharedClassWritingTest"));
+    InternalOptions options = new InternalOptions(dexItemFactory, new Reporter());
+    DexApplication.Builder builder =
+        DirectMappedDexApplication.builder(options, new Timing("SharedClassWritingTest"));
     builder.addSynthesizedClass(sharedSynthesizedClass, false);
     classes.forEach(builder::addProgramClass);
     DexApplication application = builder.build();
 
     CollectInfoConsumer consumer = new CollectInfoConsumer();
-    InternalOptions options = new InternalOptions(dexItemFactory, new Reporter());
     options.programConsumer = consumer;
     ApplicationWriter writer =
         new ApplicationWriter(
diff --git a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
index 9f25230..9377410 100644
--- a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
+++ b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
 import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -35,6 +36,7 @@
 import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
 import com.android.tools.r8.utils.codeinspector.FoundFieldSubject;
 import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.google.common.collect.ImmutableList;
 import com.google.common.io.ByteStreams;
 import com.google.common.io.Closer;
 import java.io.File;
@@ -137,6 +139,19 @@
       R8Command.Builder builder = R8Command.builder(reporter);
       builder.addProgramFiles(ListUtils.map(inputs, Paths::get));
       if (pgConfs != null) {
+        // Sanitize libraries for apps relying on the Proguard behaviour of lookup in program
+        // classes before library classes. See tools/sanitize_libraries.py for more information.
+        Path sanitizedLibrary = temp.getRoot().toPath().resolve("sanitized_lib.jar");
+        Path sanitizedPgConf = temp.getRoot().toPath().resolve("sanitized.config");
+        List<String> command =
+            new ImmutableList.Builder<String>()
+                .add("tools/sanitize_libraries.py")
+                .add(sanitizedLibrary.toString())
+                .add(sanitizedPgConf.toString())
+                .addAll(pgConfs)
+                .build();
+        ProcessResult result = ToolHelper.runProcess(new ProcessBuilder(command));
+        assert result.exitCode == 0;
         builder.addProguardConfigurationFiles(
             pgConfs.stream().map(Paths::get).collect(Collectors.toList()));
       } else {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
index 97cb2b2..d32606d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
@@ -130,7 +130,7 @@
 
     InternalOptions options = new InternalOptions();
     options.debug = true;
-    AppInfo appInfo = new AppInfo(DexApplication.builder(options.itemFactory, null).build());
+    AppInfo appInfo = new AppInfo(DexApplication.builder(options, null).build());
     AppView<?> appView = AppView.createForD8(appInfo, options);
     IRCode code =
         new IRCode(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
index 059501b..3cd2b53 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.Argument;
@@ -93,7 +92,7 @@
   @Test
   public void trivialGotoLoopAsFallthrough() {
     InternalOptions options = new InternalOptions();
-    DexApplication app = DexApplication.builder(new DexItemFactory(), new Timing("")).build();
+    DexApplication app = DexApplication.builder(new InternalOptions(), new Timing("")).build();
     AppView<AppInfo> appView = AppView.createForD8(new AppInfo(app), options);
     // Setup block structure:
     // block0:
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
index b251214..020551e 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
@@ -346,8 +346,7 @@
   public void multipleLiveTempRegisters() {
     InternalOptions options = new InternalOptions();
     AppView<AppInfo> appInfo =
-        AppView.createForD8(
-            new AppInfo(DexApplication.builder(options.itemFactory, null).build()), options);
+        AppView.createForD8(new AppInfo(DexApplication.builder(options, null).build()), options);
     TypeLatticeElement objectType =
         TypeLatticeElement.fromDexType(
             options.itemFactory.objectType, Nullability.maybeNull(), appInfo);
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java b/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
index ef517d0..a5183d3 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
@@ -56,7 +56,7 @@
   @Test
   public void splitOverlappingInactiveIntervalWithNoNextUse() {
     InternalOptions options = new InternalOptions();
-    AppInfo appInfo = new AppInfo(DexApplication.builder(options.itemFactory, null).build());
+    AppInfo appInfo = new AppInfo(DexApplication.builder(options, null).build());
     AppView<?> appView = AppView.createForD8(appInfo, options);
     IRCode code = simpleCode();
     MyRegisterAllocator allocator = new MyRegisterAllocator(appView, code);
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index bea4ea0..7d8db39 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -744,8 +744,8 @@
     options.minApiLevel = minApi;
     options.intermediate = intermediate;
     DexItemFactory factory = options.itemFactory;
-    AppInfo appInfo = new AppInfo(DexApplication.builder(factory, timing).build());
-    DexApplication.Builder builder = DexApplication.builder(factory, timing);
+    AppInfo appInfo = new AppInfo(DexApplication.builder(options, timing).build());
+    DexApplication.Builder builder = DexApplication.builder(options, timing);
     for (String clazz : classes) {
       DexString desc = factory.createString(DescriptorUtils.javaTypeToDescriptor(clazz));
       DexType type = factory.createType(desc);
diff --git a/src/test/java/com/android/tools/r8/maindexlist/b72312389/B72312389.java b/src/test/java/com/android/tools/r8/maindexlist/b72312389/B72312389.java
index c1741c9..f2f4e46 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/b72312389/B72312389.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/b72312389/B72312389.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.maindexlist.b72312389;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -33,6 +34,9 @@
 
 @RunWith(VmTestRunner.class)
 public class B72312389 extends TestBase {
+  // TODO(120884788): Remove this when default is true.
+  private static boolean lookupLibraryBeforeProgram = false;
+
   // Build a app with a class extending InstrumentationTestCase and including both the junit
   // and the Android library.
   private void buildInstrumentationTestCaseApplication(BaseCommand.Builder builder) {
@@ -58,7 +62,11 @@
         .addMainDexRules(keepInstrumentationTestCaseRules, Origin.unknown())
         .build();
     List<String> mainDexList = GenerateMainDexList.run(command);
-    assertTrue(mainDexList.contains("junit/framework/TestCase.class"));
+    if (lookupLibraryBeforeProgram) {
+      assertFalse(mainDexList.contains("junit/framework/TestCase.class"));
+    } else {
+      assertTrue(mainDexList.contains("junit/framework/TestCase.class"));
+    }
     diagnostics.assertEmpty();
   }
 
@@ -86,9 +94,11 @@
         .build();
     CodeInspector inspector = new CodeInspector(ToolHelper.runR8(command));
     assertTrue(inspector.clazz("instrumentationtest.InstrumentationTest").isPresent());
-    assertTrue(mainDexList.content.contains("junit/framework/TestCase.class"));
-    // TODO(72794301): Two copies of this message is a bit over the top.
-    assertEquals(2,
+    assertEquals(
+        !lookupLibraryBeforeProgram,
+        mainDexList.content.contains("junit/framework/TestCase.class"));
+    assertEquals(
+        lookupLibraryBeforeProgram ? 0 : 2,
         diagnostics.countLibraryClassExtensdProgramClassWarnings(
             "android.test.InstrumentationTestCase", "junit.framework.TestCase"));
   }
@@ -102,11 +112,16 @@
         .addMainDexRules(keepInstrumentationTestCaseRules, Origin.unknown())
         .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
         .build();
-    try {
+    if (lookupLibraryBeforeProgram) {
       R8.run(command);
-      fail();
-    } catch (CompilationFailedException e) {
-      // Expected, as library class extending program class is an error for R8.
+      diagnostics.assertEmpty();
+    } else {
+      try {
+        R8.run(command);
+        fail();
+      } catch (CompilationFailedException e) {
+        // Expected, as library class extending program class is an error for R8.
+      }
     }
   }
 
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index a480214..b8bd0ed 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -15,6 +15,7 @@
 import gmscore_data
 import golem
 import nest_data
+from sanitize_libraries import SanitizeLibraries
 import toolhelper
 import utils
 import youtube_data
@@ -301,9 +302,14 @@
 
   if options.compiler == 'r8':
     if 'pgconf' in values and not options.k:
-      for pgconf in values['pgconf']:
-        args.extend(['--pg-conf', pgconf])
-        app_provided_pg_conf = True
+      sanitized_lib_path = os.path.join(
+          os.path.abspath(outdir), 'sanitized_lib.jar')
+      sanitized_pgconf_path = os.path.join(
+          os.path.abspath(outdir), 'sanitized.config')
+      SanitizeLibraries(
+          sanitized_lib_path, sanitized_pgconf_path, values['pgconf'])
+      args.extend(['--pg-conf', sanitized_pgconf_path])
+      app_provided_pg_conf = True
     if options.k:
       args.extend(['--pg-conf', options.k])
     if 'maindexrules' in values:
diff --git a/tools/sanitize_libraries.py b/tools/sanitize_libraries.py
new file mode 100755
index 0000000..e67a06e
--- /dev/null
+++ b/tools/sanitize_libraries.py
@@ -0,0 +1,76 @@
+#!/usr/bin/env python
+# Copyright (c) 2019, 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.
+from __future__ import print_function
+import os
+import sys
+import zipfile
+
+# Proguard lookup program classes before library classes. In R8 this is
+# not the behaviour (it used to be) R8 will check library classes before
+# program classes. Some apps have duplicate classes in the library and program.
+# To make these apps work with R8 simulate program classes before library
+# classes by creating a new library jar which have all the provided library
+# classes which are not also in program classes.
+def SanitizeLibraries(sanitized_lib_path, sanitized_pgconf_path, pgconfs):
+
+  injars = []
+  libraryjars = []
+
+  with open(sanitized_pgconf_path, 'w') as sanitized_pgconf:
+    for pgconf in pgconfs:
+      pgconf_dirname = os.path.abspath(os.path.dirname(pgconf))
+      first_library_jar = True
+      with open(pgconf) as pgconf_file:
+        for line in pgconf_file:
+          trimmed = line.strip()
+          if trimmed.startswith('-injars'):
+            # Collect -injars and leave them in the configuration.
+            injar = os.path.join(
+                pgconf_dirname, trimmed[len('-injars'):].strip())
+            injars.append(injar)
+            sanitized_pgconf.write('-injars {}\n'.format(injar))
+          elif trimmed.startswith('-libraryjars'):
+            # Collect -libraryjars and replace them with the sanitized library.
+            libraryjar = os.path.join(
+                pgconf_dirname, trimmed[len('-libraryjars'):].strip())
+            libraryjars.append(libraryjar)
+            if first_library_jar:
+              sanitized_pgconf.write(
+                  '-libraryjars {}\n'.format(sanitized_lib_path))
+              first_library_jar = False
+            sanitized_pgconf.write('# {}'.format(line))
+          else:
+            sanitized_pgconf.write(line)
+
+  program_entries = set()
+  library_entries = set()
+
+  for injar in injars:
+    with zipfile.ZipFile(injar, 'r') as injar_zf:
+      for zipinfo in injar_zf.infolist():
+        program_entries.add(zipinfo.filename)
+
+  with zipfile.ZipFile(sanitized_lib_path, 'w') as output_zf:
+    for libraryjar in libraryjars:
+      with zipfile.ZipFile(libraryjar, 'r') as input_zf:
+       for zipinfo in input_zf.infolist():
+         if (not zipinfo.filename in program_entries
+             and not zipinfo.filename in library_entries):
+           library_entries.add(zipinfo.filename)
+           output_zf.writestr(zipinfo, input_zf.read(zipinfo))
+
+  return sanitized_pgconf_path
+
+def main(argv):
+  if (len(argv) < 3):
+    print("Wrong number of arguments!")
+    print("Usage: sanitize_libraries.py " +
+          "<sanitized_lib> <sanitized_pgconf> (<existing_pgconf)+")
+    return 1
+  else:
+    SanitizeLibraries(argv[0], argv[1], argv[2:])
+
+if __name__ == '__main__':
+  sys.exit(main(sys.argv[1:]))
diff --git a/tools/utils.py b/tools/utils.py
index f8c1afe..7931c52 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -339,16 +339,18 @@
   return exit_code == 0
 
 class TempDir(object):
- def __init__(self, prefix=''):
+ def __init__(self, prefix='', delete=True):
    self._temp_dir = None
    self._prefix = prefix
+   self._delete = delete
 
  def __enter__(self):
    self._temp_dir = tempfile.mkdtemp(self._prefix)
    return self._temp_dir
 
  def __exit__(self, *_):
-   shutil.rmtree(self._temp_dir, ignore_errors=True)
+   if self._delete:
+     shutil.rmtree(self._temp_dir, ignore_errors=True)
 
 class ChangedWorkingDirectory(object):
  def __init__(self, working_directory, quiet=False):