| // Copyright (c) 2016, 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.isAarFile; | |
| 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 static com.android.tools.r8.utils.InternalOptions.ASM_VERSION; | |
| import static com.android.tools.r8.utils.ZipUtils.writeToZipStream; | |
| import com.android.tools.r8.ClassFileConsumer; | |
| import com.android.tools.r8.ClassFileResourceProvider; | |
| import com.android.tools.r8.DataDirectoryResource; | |
| import com.android.tools.r8.DataEntryResource; | |
| import com.android.tools.r8.DataResource; | |
| import com.android.tools.r8.DataResourceProvider; | |
| import com.android.tools.r8.DataResourceProvider.Visitor; | |
| import com.android.tools.r8.DexFilePerClassFileConsumer; | |
| import com.android.tools.r8.DexIndexedConsumer; | |
| import com.android.tools.r8.DirectoryClassFileProvider; | |
| import com.android.tools.r8.FeatureSplit; | |
| import com.android.tools.r8.OutputMode; | |
| import com.android.tools.r8.ProgramResource; | |
| import com.android.tools.r8.ProgramResource.Kind; | |
| import com.android.tools.r8.ProgramResourceProvider; | |
| import com.android.tools.r8.Resource; | |
| import com.android.tools.r8.ResourceException; | |
| import com.android.tools.r8.StringResource; | |
| import com.android.tools.r8.Version; | |
| import com.android.tools.r8.dump.DumpOptions; | |
| import com.android.tools.r8.errors.CompilationError; | |
| import com.android.tools.r8.errors.InternalCompilerError; | |
| import com.android.tools.r8.errors.Unreachable; | |
| import com.android.tools.r8.features.ClassToFeatureSplitMap; | |
| import com.android.tools.r8.features.FeatureSplitConfiguration; | |
| import com.android.tools.r8.graph.DexType; | |
| import com.android.tools.r8.origin.ArchiveEntryOrigin; | |
| import com.android.tools.r8.origin.Origin; | |
| import com.android.tools.r8.origin.PathOrigin; | |
| import com.android.tools.r8.profile.art.ArtProfileProvider; | |
| import com.android.tools.r8.profile.art.ArtProfileProviderUtils; | |
| import com.android.tools.r8.profile.startup.StartupProfileProviderUtils; | |
| import com.android.tools.r8.profile.startup.profile.StartupProfile; | |
| import com.android.tools.r8.shaking.FilteredClassPath; | |
| import com.android.tools.r8.startup.StartupProfileProvider; | |
| import com.android.tools.r8.synthesis.SyntheticItems; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.common.collect.ImmutableMap; | |
| import com.google.common.io.ByteStreams; | |
| import com.google.common.io.Closer; | |
| import it.unimi.dsi.fastutil.objects.Object2IntMap; | |
| import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; | |
| import java.io.ByteArrayOutputStream; | |
| import java.io.IOException; | |
| import java.io.InputStream; | |
| import java.io.OutputStream; | |
| import java.nio.charset.StandardCharsets; | |
| import java.nio.file.Files; | |
| import java.nio.file.NoSuchFileException; | |
| import java.nio.file.OpenOption; | |
| import java.nio.file.Path; | |
| import java.nio.file.StandardOpenOption; | |
| import java.util.ArrayList; | |
| import java.util.Arrays; | |
| import java.util.Collection; | |
| import java.util.Collections; | |
| import java.util.Comparator; | |
| import java.util.HashMap; | |
| import java.util.IdentityHashMap; | |
| import java.util.List; | |
| import java.util.Map; | |
| import java.util.Set; | |
| import java.util.TreeSet; | |
| import java.util.function.Consumer; | |
| import java.util.function.Function; | |
| import java.util.zip.ZipEntry; | |
| import java.util.zip.ZipInputStream; | |
| import java.util.zip.ZipOutputStream; | |
| import org.objectweb.asm.ClassReader; | |
| import org.objectweb.asm.ClassVisitor; | |
| import org.objectweb.asm.Opcodes; | |
| import org.objectweb.asm.tree.ClassNode; | |
| import org.objectweb.asm.util.CheckClassAdapter; | |
| /** | |
| * Collection of program files needed for processing. | |
| * | |
| * <p>This abstraction is the main input and output container for a given application. | |
| */ | |
| public class AndroidApp { | |
| // TODO(b/172887664): Move to DumpOptions and capitalize. | |
| private static final String dumpVersionFileName = "r8-version"; | |
| private static final String dumpBuildPropertiesFileName = "build.properties"; | |
| private static final String dumpDesugaredLibraryFileName = "desugared-library.json"; | |
| private static final String dumpMainDexListResourceFileName = "main-dex-list.txt"; | |
| private static final String dumpMainDexRulesResourceFileName = "main-dex-rules.txt"; | |
| private static final String dumpProgramFileName = "program.jar"; | |
| private static final String dumpClasspathFileName = "classpath.jar"; | |
| private static final String dumpLibraryFileName = "library.jar"; | |
| private static final String dumpConfigFileName = "proguard.config"; | |
| private static final String dumpInputConfigFileName = "proguard_input.config"; | |
| private static Map<FeatureSplit, String> dumpFeatureSplitFileNames( | |
| FeatureSplitConfiguration featureSplitConfiguration) { | |
| Map<FeatureSplit, String> featureSplitFileNames = new IdentityHashMap<>(); | |
| if (featureSplitConfiguration != null) { | |
| int i = 1; | |
| for (FeatureSplit featureSplit : featureSplitConfiguration.getFeatureSplits()) { | |
| featureSplitFileNames.put(featureSplit, "feature-" + i + ".jar"); | |
| i++; | |
| } | |
| } | |
| return featureSplitFileNames; | |
| } | |
| private final ImmutableList<ProgramResourceProvider> programResourceProviders; | |
| private final ImmutableMap<Resource, String> programResourcesMainDescriptor; | |
| private final ImmutableList<ClassFileResourceProvider> classpathResourceProviders; | |
| private final ImmutableList<ClassFileResourceProvider> libraryResourceProviders; | |
| // List of internally added archive providers for which we must close their resources. | |
| private final ImmutableList<InternalArchiveClassFileProvider> archiveProvidersToClose; | |
| private final StringResource proguardMapOutputData; | |
| private final StringResource proguardMapInputData; | |
| private final List<StringResource> mainDexListResources; | |
| private final List<String> mainDexClasses; | |
| public void closeInternalArchiveProviders() throws IOException { | |
| for (InternalArchiveClassFileProvider provider : archiveProvidersToClose) { | |
| provider.close(); | |
| } | |
| } | |
| @Override | |
| public String toString() { | |
| StringBuilder builder = new StringBuilder(); | |
| try { | |
| if (!programResourceProviders.isEmpty()) { | |
| builder.append(" Program resources:").append(System.lineSeparator()); | |
| printProgramResourceProviders(builder, programResourceProviders); | |
| } | |
| if (!classpathResourceProviders.isEmpty()) { | |
| builder.append(" Classpath resources:").append(System.lineSeparator()); | |
| printClassFileProviders(builder, classpathResourceProviders); | |
| } | |
| if (!libraryResourceProviders.isEmpty()) { | |
| builder.append(" Library resources:").append(System.lineSeparator()); | |
| printClassFileProviders(builder, libraryResourceProviders); | |
| } | |
| } catch (ResourceException e) { | |
| e.printStackTrace(); | |
| } | |
| return builder.toString(); | |
| } | |
| private static void printProgramResourceProviders( | |
| StringBuilder builder, Collection<ProgramResourceProvider> providers) | |
| throws ResourceException { | |
| for (ProgramResourceProvider provider : providers) { | |
| for (ProgramResource resource : provider.getProgramResources()) { | |
| printProgramResource(builder, resource); | |
| } | |
| } | |
| } | |
| private static void printClassFileProviders( | |
| StringBuilder builder, Collection<ClassFileResourceProvider> providers) { | |
| for (ClassFileResourceProvider provider : providers) { | |
| for (String descriptor : provider.getClassDescriptors()) { | |
| ProgramResource resource = provider.getProgramResource(descriptor); | |
| printProgramResource(builder, resource); | |
| } | |
| } | |
| } | |
| private static void printProgramResource(StringBuilder builder, ProgramResource resource) { | |
| builder.append(" ").append(resource.getOrigin()); | |
| Set<String> descriptors = resource.getClassDescriptors(); | |
| if (descriptors != null && !descriptors.isEmpty()) { | |
| builder.append(" contains "); | |
| StringUtils.append(builder, descriptors); | |
| } | |
| builder.append(System.lineSeparator()); | |
| } | |
| // See factory methods and AndroidApp.Builder below. | |
| private AndroidApp( | |
| ImmutableList<ProgramResourceProvider> programResourceProviders, | |
| ImmutableMap<Resource, String> programResourcesMainDescriptor, | |
| ImmutableList<ClassFileResourceProvider> classpathResourceProviders, | |
| ImmutableList<ClassFileResourceProvider> libraryResourceProviders, | |
| ImmutableList<InternalArchiveClassFileProvider> archiveProvidersToClose, | |
| StringResource proguardMapOutputData, | |
| StringResource proguardMapInputData, | |
| List<StringResource> mainDexListResources, | |
| List<String> mainDexClasses) { | |
| this.programResourceProviders = programResourceProviders; | |
| this.programResourcesMainDescriptor = programResourcesMainDescriptor; | |
| this.classpathResourceProviders = classpathResourceProviders; | |
| this.libraryResourceProviders = libraryResourceProviders; | |
| this.archiveProvidersToClose = archiveProvidersToClose; | |
| this.proguardMapOutputData = proguardMapOutputData; | |
| this.proguardMapInputData = proguardMapInputData; | |
| this.mainDexListResources = mainDexListResources; | |
| this.mainDexClasses = mainDexClasses; | |
| assert verifyInternalProvidersInCloseSet(classpathResourceProviders, archiveProvidersToClose); | |
| assert verifyInternalProvidersInCloseSet(libraryResourceProviders, archiveProvidersToClose); | |
| } | |
| private static boolean verifyInternalProvidersInCloseSet( | |
| ImmutableList<ClassFileResourceProvider> providers, | |
| ImmutableList<InternalArchiveClassFileProvider> providersToClose) { | |
| return providers.stream() | |
| .allMatch( | |
| p -> !(p instanceof InternalArchiveClassFileProvider) || providersToClose.contains(p)); | |
| } | |
| static Reporter defaultReporter() { | |
| return new Reporter(); | |
| } | |
| /** | |
| * Create a new empty builder. | |
| */ | |
| public static Builder builder() { | |
| return builder(defaultReporter()); | |
| } | |
| /** Create a new empty builder. */ | |
| public static Builder builder(Reporter reporter) { | |
| return new Builder(reporter); | |
| } | |
| /** | |
| * Create a new builder initialized with the resources from @code{app}. | |
| */ | |
| public static Builder builder(AndroidApp app) { | |
| return builder(app, defaultReporter()); | |
| } | |
| /** Create a new builder initialized with the resources from @code{app}. */ | |
| public static Builder builder(AndroidApp app, Reporter reporter) { | |
| return new Builder(reporter, app); | |
| } | |
| public int applicationSize() throws IOException, ResourceException { | |
| int bytes = 0; | |
| assert getDexProgramResourcesForTesting().size() == 0 | |
| || getClassProgramResourcesForTesting().size() == 0; | |
| try (Closer closer = Closer.create()) { | |
| for (ProgramResource dex : getDexProgramResourcesForTesting()) { | |
| bytes += ByteStreams.toByteArray(closer.register(dex.getByteStream())).length; | |
| } | |
| for (ProgramResource cf : getClassProgramResourcesForTesting()) { | |
| bytes += ByteStreams.toByteArray(closer.register(cf.getByteStream())).length; | |
| } | |
| } | |
| return bytes; | |
| } | |
| /** Get full collection of all program resources from all program providers. */ | |
| public Collection<ProgramResource> computeAllProgramResources() throws ResourceException { | |
| List<ProgramResource> resources = new ArrayList<>(); | |
| for (ProgramResourceProvider provider : programResourceProviders) { | |
| resources.addAll(provider.getProgramResources()); | |
| } | |
| return resources; | |
| } | |
| // TODO(zerny): Remove this method. | |
| public List<ProgramResource> getDexProgramResourcesForTesting() throws IOException { | |
| try { | |
| return filter(programResourceProviders, Kind.DEX); | |
| } catch (ResourceException e) { | |
| if (e.getCause() instanceof IOException) { | |
| throw (IOException) e.getCause(); | |
| } else { | |
| throw new InternalCompilerError("Unexpected resource error", e); | |
| } | |
| } | |
| } | |
| // TODO(zerny): Remove this method. | |
| public List<ProgramResource> getClassProgramResourcesForTesting() throws IOException { | |
| try { | |
| return filter(programResourceProviders, Kind.CF); | |
| } catch (ResourceException e) { | |
| if (e.getCause() instanceof IOException) { | |
| throw (IOException) e.getCause(); | |
| } else { | |
| throw new InternalCompilerError("Unexpected resource error", e); | |
| } | |
| } | |
| } | |
| public Pair<Set<DataDirectoryResource>, Set<DataEntryResource>> getDataResourcesForTesting() | |
| throws ResourceException { | |
| Set<DataDirectoryResource> dataDirectoryResources = | |
| new TreeSet<>(Comparator.comparing(DataResource::getName)); | |
| Set<DataEntryResource> dataEntryResources = | |
| new TreeSet<>(Comparator.comparing(DataResource::getName)); | |
| for (ProgramResourceProvider programResourceProvider : getProgramResourceProviders()) { | |
| DataResourceProvider dataResourceProvider = programResourceProvider.getDataResourceProvider(); | |
| if (dataResourceProvider != null) { | |
| dataResourceProvider.accept( | |
| new Visitor() { | |
| @Override | |
| public void visit(DataDirectoryResource directory) { | |
| dataDirectoryResources.add( | |
| DataDirectoryResource.fromName(directory.getName(), directory.getOrigin())); | |
| } | |
| @Override | |
| public void visit(DataEntryResource file) { | |
| try { | |
| byte[] bytes = ByteStreams.toByteArray(file.getByteStream()); | |
| DataEntryResource copy = | |
| DataEntryResource.fromBytes(bytes, file.getName(), file.getOrigin()); | |
| dataEntryResources.add(copy); | |
| } catch (IOException | ResourceException e) { | |
| throw new RuntimeException(e); | |
| } | |
| } | |
| }); | |
| } | |
| } | |
| return new Pair<>(dataDirectoryResources, dataEntryResources); | |
| } | |
| /** Get program resource providers. */ | |
| public List<ProgramResourceProvider> getProgramResourceProviders() { | |
| return programResourceProviders; | |
| } | |
| /** Get classpath resource providers. */ | |
| public List<ClassFileResourceProvider> getClasspathResourceProviders() { | |
| return classpathResourceProviders; | |
| } | |
| /** Get library resource providers. */ | |
| public List<ClassFileResourceProvider> getLibraryResourceProviders() { | |
| return libraryResourceProviders; | |
| } | |
| private List<ProgramResource> filter(List<ProgramResourceProvider> providers, Kind kind) | |
| throws ResourceException { | |
| List<ProgramResource> out = new ArrayList<>(); | |
| for (ProgramResourceProvider provider : providers) { | |
| for (ProgramResource code : provider.getProgramResources()) { | |
| if (code.getKind() == kind) { | |
| out.add(code); | |
| } | |
| } | |
| } | |
| return out; | |
| } | |
| /** | |
| * Get the proguard-map associated with an output "app" if it exists. | |
| * | |
| * <p>Note: this should never be used as the input to a compilation. See proguards ApplyMapping | |
| * for such use cases. | |
| */ | |
| public StringResource getProguardMapOutputData() { | |
| return proguardMapOutputData; | |
| } | |
| /** Get the proguard-map associated with an input "app" if it exists. */ | |
| public StringResource getProguardMapInputData() { | |
| return proguardMapInputData; | |
| } | |
| /** | |
| * True if the main dex list resources exists. | |
| */ | |
| public boolean hasMainDexList() { | |
| return !(mainDexListResources.isEmpty() && mainDexClasses.isEmpty()); | |
| } | |
| /** | |
| * True if the main dex list resources exists. | |
| */ | |
| public boolean hasMainDexListResources() { | |
| return !mainDexListResources.isEmpty(); | |
| } | |
| /** | |
| * Get the main dex list resources if any. | |
| */ | |
| public List<StringResource> getMainDexListResources() { | |
| return mainDexListResources; | |
| } | |
| /** | |
| * Get the main dex classes if any. | |
| */ | |
| public List<String> getMainDexClasses() { | |
| return mainDexClasses; | |
| } | |
| /** Returns a copy of this AndroidApp that does not have a main dex list. */ | |
| public AndroidApp withoutMainDexList() { | |
| return new AndroidApp( | |
| programResourceProviders, | |
| programResourcesMainDescriptor, | |
| classpathResourceProviders, | |
| libraryResourceProviders, | |
| archiveProvidersToClose, | |
| proguardMapOutputData, | |
| proguardMapInputData, | |
| ImmutableList.of(), | |
| ImmutableList.of()); | |
| } | |
| /** Write the dex program resources and proguard resource to @code{output}. */ | |
| public void writeForTesting(Path output, OutputMode outputMode) throws IOException { | |
| if (isArchive(output)) { | |
| writeToZipForTesting(output, outputMode); | |
| } else { | |
| writeToDirectory(output, outputMode); | |
| } | |
| } | |
| /** | |
| * Write the dex program resources and proguard resource to @code{directory}. | |
| */ | |
| public void writeToDirectory(Path directory, OutputMode outputMode) throws IOException { | |
| List<ProgramResource> dexProgramSources = getDexProgramResourcesForTesting(); | |
| try { | |
| if (outputMode == OutputMode.DexIndexed) { | |
| DexIndexedConsumer.DirectoryConsumer.writeResources(directory, dexProgramSources); | |
| } else { | |
| DexFilePerClassFileConsumer.DirectoryConsumer.writeResources( | |
| directory, dexProgramSources, programResourcesMainDescriptor); | |
| } | |
| } catch (ResourceException e) { | |
| throw new IOException("Resource Error", e); | |
| } | |
| } | |
| /** Write the dex program resources to @code{archive}. */ | |
| public void writeToZipForTesting(Path archive, OutputMode outputMode) throws IOException { | |
| try { | |
| if (outputMode == OutputMode.DexIndexed) { | |
| Pair<Set<DataDirectoryResource>, Set<DataEntryResource>> dataResourcesForTesting = | |
| getDataResourcesForTesting(); | |
| DexIndexedConsumer.ArchiveConsumer.writeResourcesForTesting( | |
| archive, | |
| getDexProgramResourcesForTesting(), | |
| dataResourcesForTesting.getFirst(), | |
| dataResourcesForTesting.getSecond()); | |
| } else if (outputMode == OutputMode.DexFilePerClassFile | |
| || outputMode == OutputMode.DexFilePerClass) { | |
| List<ProgramResource> resources = getDexProgramResourcesForTesting(); | |
| DexFilePerClassFileConsumer.ArchiveConsumer.writeResourcesForTesting( | |
| archive, resources, programResourcesMainDescriptor); | |
| } else if (outputMode == OutputMode.ClassFile) { | |
| Pair<Set<DataDirectoryResource>, Set<DataEntryResource>> dataResourcesForTesting = | |
| getDataResourcesForTesting(); | |
| ClassFileConsumer.ArchiveConsumer.writeResourcesForTesting( | |
| archive, | |
| getClassProgramResourcesForTesting(), | |
| dataResourcesForTesting.getFirst(), | |
| dataResourcesForTesting.getSecond()); | |
| } else { | |
| throw new Unreachable("Unsupported output-mode for writing: " + outputMode); | |
| } | |
| } catch (ResourceException e) { | |
| throw new IOException("Resource Error", e); | |
| } | |
| } | |
| // Public for testing. | |
| public String getPrimaryClassDescriptor(Resource resource) { | |
| assert resource instanceof ProgramResource; | |
| return programResourcesMainDescriptor.get(resource); | |
| } | |
| public void dump(Path output, DumpOptions dumpOptions, InternalOptions options) { | |
| int nextDexIndex = 0; | |
| OpenOption[] openOptions = | |
| new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING}; | |
| try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(output, openOptions))) { | |
| writeToZipStream( | |
| out, dumpVersionFileName, Version.getVersionString().getBytes(), ZipEntry.DEFLATED); | |
| writeToZipStream( | |
| out, | |
| dumpBuildPropertiesFileName, | |
| dumpOptions.getBuildPropertiesFileContent().getBytes(), | |
| ZipEntry.DEFLATED); | |
| if (dumpOptions.getDesugaredLibraryJsonSource() != null) { | |
| writeToZipStream( | |
| out, | |
| dumpDesugaredLibraryFileName, | |
| dumpOptions.getDesugaredLibraryJsonSource().getBytes(), | |
| ZipEntry.DEFLATED); | |
| if (dumpOptions.dumpInputToFile()) { | |
| options.reporter.warning( | |
| "Dumping a compilation with desugared library on a file may prevent reproduction," | |
| + " use dumpInputToDirectory property instead."); | |
| } | |
| } | |
| if (dumpOptions.getParsedProguardConfiguration() != null) { | |
| String proguardConfig = dumpOptions.getParsedProguardConfiguration(); | |
| writeToZipStream(out, dumpConfigFileName, proguardConfig.getBytes(), ZipEntry.DEFLATED); | |
| } | |
| if (proguardMapInputData != null) { | |
| options.reporter.warning( | |
| "Dumping proguard map input data may have side effects due to I/O on Paths."); | |
| writeToZipStream( | |
| out, | |
| dumpInputConfigFileName, | |
| proguardMapInputData.getString().getBytes(), | |
| ZipEntry.DEFLATED); | |
| } | |
| if (hasMainDexList()) { | |
| List<String> mainDexList = new ArrayList<>(); | |
| if (hasMainDexListResources()) { | |
| options.reporter.warning( | |
| "Dumping main dex list resources may have side effects due to I/O on Paths."); | |
| for (StringResource mainDexListResource : getMainDexListResources()) { | |
| mainDexList.add(mainDexListResource.getString()); | |
| } | |
| } | |
| for (String mainDexClass : getMainDexClasses()) { | |
| mainDexList.add(mainDexClass.replace(".", "/") + CLASS_EXTENSION); | |
| } | |
| String join = StringUtils.join("\n", mainDexList); | |
| writeToZipStream(out, dumpMainDexListResourceFileName, join.getBytes(), ZipEntry.DEFLATED); | |
| } | |
| if (dumpOptions.hasMainDexKeepRules()) { | |
| writeToZipStream( | |
| out, | |
| dumpMainDexRulesResourceFileName, | |
| StringUtils.joinLines(dumpOptions.getMainDexKeepRules()).getBytes(), | |
| ZipEntry.DEFLATED); | |
| } | |
| if (dumpOptions.hasArtProfileProviders()) { | |
| dumpArtProfileProviders(dumpOptions.getArtProfileProviders(), options, out); | |
| } | |
| if (dumpOptions.hasStartupProfileProviders()) { | |
| dumpStartupProfileProviders(dumpOptions.getStartupProfileProviders(), options, out); | |
| } | |
| nextDexIndex = | |
| dumpProgramResources( | |
| dumpProgramFileName, | |
| dumpOptions.getFeatureSplitConfiguration(), | |
| nextDexIndex, | |
| out, | |
| options); | |
| nextDexIndex = dumpClasspathResources(nextDexIndex, out); | |
| nextDexIndex = dumpLibraryResources(nextDexIndex, out); | |
| } catch (IOException | ResourceException e) { | |
| throw options.reporter.fatalError(new ExceptionDiagnostic(e)); | |
| } | |
| } | |
| private int dumpLibraryResources(int nextDexIndex, ZipOutputStream out) | |
| throws IOException, ResourceException { | |
| nextDexIndex = | |
| dumpClassFileResources(dumpLibraryFileName, nextDexIndex, out, libraryResourceProviders); | |
| return nextDexIndex; | |
| } | |
| private int dumpClasspathResources(int nextDexIndex, ZipOutputStream out) | |
| throws IOException, ResourceException { | |
| nextDexIndex = | |
| dumpClassFileResources( | |
| dumpClasspathFileName, nextDexIndex, out, classpathResourceProviders); | |
| return nextDexIndex; | |
| } | |
| private void dumpArtProfileProviders( | |
| Collection<ArtProfileProvider> artProfileProviders, | |
| InternalOptions options, | |
| ZipOutputStream out) | |
| throws IOException { | |
| int artProfileProviderIndex = 1; | |
| for (ArtProfileProvider artProfileProvider : artProfileProviders) { | |
| String artProfileFileName = "art-profile-" + artProfileProviderIndex + ".txt"; | |
| writeToZipStream( | |
| out, | |
| artProfileFileName, | |
| ArtProfileProviderUtils.serializeToString(artProfileProvider).getBytes(), | |
| ZipEntry.DEFLATED); | |
| artProfileProviderIndex++; | |
| } | |
| } | |
| private void dumpStartupProfileProviders( | |
| Collection<StartupProfileProvider> startupProfileProviders, | |
| InternalOptions options, | |
| ZipOutputStream out) | |
| throws IOException { | |
| int startupProfileProviderIndex = 1; | |
| for (StartupProfileProvider startupProfileProvider : startupProfileProviders) { | |
| String startupProfileFileName = "startup-profile-" + startupProfileProviderIndex + ".txt"; | |
| writeToZipStream( | |
| out, | |
| startupProfileFileName, | |
| StartupProfileProviderUtils.serializeToString(options, startupProfileProvider).getBytes(), | |
| ZipEntry.DEFLATED); | |
| startupProfileProviderIndex++; | |
| } | |
| } | |
| private static ClassFileResourceProvider createClassFileResourceProvider( | |
| Map<String, ProgramResource> classPathResources) { | |
| return new ClassFileResourceProvider() { | |
| @Override | |
| public Set<String> getClassDescriptors() { | |
| return classPathResources.keySet(); | |
| } | |
| @Override | |
| public ProgramResource getProgramResource(String descriptor) { | |
| return classPathResources.get(descriptor); | |
| } | |
| }; | |
| } | |
| private int dumpProgramResources( | |
| String archiveName, | |
| FeatureSplitConfiguration featureSplitConfiguration, | |
| int nextDexIndex, | |
| ZipOutputStream out, | |
| InternalOptions options) | |
| throws IOException, ResourceException { | |
| Map<FeatureSplit, String> featureSplitArchiveNames = | |
| dumpFeatureSplitFileNames(featureSplitConfiguration); | |
| Map<FeatureSplit, ByteArrayOutputStream> featureSplitArchiveByteStreams = | |
| new IdentityHashMap<>(); | |
| Map<FeatureSplit, ZipOutputStream> featureSplitArchiveOutputStreams = new IdentityHashMap<>(); | |
| try { | |
| ClassToFeatureSplitMap classToFeatureSplitMap = | |
| ClassToFeatureSplitMap.createInitialClassToFeatureSplitMap( | |
| options.dexItemFactory(), featureSplitConfiguration, options.reporter); | |
| if (featureSplitConfiguration != null) { | |
| for (FeatureSplit featureSplit : featureSplitConfiguration.getFeatureSplits()) { | |
| ByteArrayOutputStream archiveByteStream = new ByteArrayOutputStream(); | |
| featureSplitArchiveByteStreams.put(featureSplit, archiveByteStream); | |
| featureSplitArchiveOutputStreams.put( | |
| featureSplit, new ZipOutputStream(archiveByteStream)); | |
| } | |
| } | |
| try (ByteArrayOutputStream archiveByteStream = new ByteArrayOutputStream()) { | |
| try (ZipOutputStream archiveOutputStream = new ZipOutputStream(archiveByteStream)) { | |
| Object2IntMap<String> seen = new Object2IntOpenHashMap<>(); | |
| Pair<Set<DataDirectoryResource>, Set<DataEntryResource>> dataResources = | |
| getDataResourcesForTesting(); | |
| Set<DataDirectoryResource> dataDirectoryResources = dataResources.getFirst(); | |
| for (DataDirectoryResource dataDirectoryResource : dataDirectoryResources) { | |
| writeToZipStream( | |
| archiveOutputStream, dataDirectoryResource.getName(), new byte[0], ZipEntry.STORED); | |
| } | |
| Set<DataEntryResource> dataEntryResources = dataResources.getSecond(); | |
| for (DataEntryResource dataEntryResource : dataEntryResources) { | |
| String entryName = dataEntryResource.getName(); | |
| try (InputStream dataStream = dataEntryResource.getByteStream()) { | |
| byte[] bytes = ByteStreams.toByteArray(dataStream); | |
| writeToZipStream(archiveOutputStream, entryName, bytes, ZipEntry.DEFLATED); | |
| } | |
| } | |
| for (ProgramResourceProvider provider : programResourceProviders) { | |
| for (ProgramResource programResource : provider.getProgramResources()) { | |
| nextDexIndex = | |
| dumpProgramResource( | |
| seen, | |
| nextDexIndex, | |
| classDescriptor -> { | |
| if (featureSplitConfiguration != null) { | |
| DexType type = options.dexItemFactory().createType(classDescriptor); | |
| SyntheticItems syntheticItems = null; | |
| FeatureSplit featureSplit = | |
| classToFeatureSplitMap.getFeatureSplit( | |
| type, options, StartupProfile.empty(), syntheticItems); | |
| if (featureSplit != null && !featureSplit.isBase()) { | |
| return featureSplitArchiveOutputStreams.get(featureSplit); | |
| } | |
| } | |
| return archiveOutputStream; | |
| }, | |
| archiveOutputStream, | |
| programResource); | |
| } | |
| } | |
| } | |
| writeToZipStream(out, archiveName, archiveByteStream.toByteArray(), ZipEntry.DEFLATED); | |
| if (featureSplitConfiguration != null) { | |
| for (FeatureSplit featureSplit : featureSplitConfiguration.getFeatureSplits()) { | |
| featureSplitArchiveOutputStreams.remove(featureSplit).close(); | |
| writeToZipStream( | |
| out, | |
| featureSplitArchiveNames.get(featureSplit), | |
| featureSplitArchiveByteStreams.get(featureSplit).toByteArray(), | |
| ZipEntry.DEFLATED); | |
| } | |
| } | |
| } | |
| } finally { | |
| closeOutputStreams(featureSplitArchiveOutputStreams.values()); | |
| } | |
| return nextDexIndex; | |
| } | |
| private void closeOutputStreams(Collection<ZipOutputStream> outputStreams) throws IOException { | |
| IOException exception = null; | |
| RuntimeException runtimeException = null; | |
| for (OutputStream outputStream : outputStreams) { | |
| try { | |
| outputStream.close(); | |
| } catch (IOException e) { | |
| exception = e; | |
| } catch (RuntimeException e) { | |
| runtimeException = e; | |
| } | |
| } | |
| if (exception != null) { | |
| throw exception; | |
| } | |
| if (runtimeException != null) { | |
| throw runtimeException; | |
| } | |
| } | |
| private static int dumpClassFileResources( | |
| String archiveName, | |
| int nextDexIndex, | |
| ZipOutputStream out, | |
| ImmutableList<ClassFileResourceProvider> classpathResourceProviders) | |
| throws IOException, ResourceException { | |
| try (ByteArrayOutputStream archiveByteStream = new ByteArrayOutputStream()) { | |
| try (ZipOutputStream archiveOutputStream = new ZipOutputStream(archiveByteStream)) { | |
| Object2IntMap<String> seen = new Object2IntOpenHashMap<>(); | |
| for (ClassFileResourceProvider provider : classpathResourceProviders) { | |
| for (String descriptor : provider.getClassDescriptors()) { | |
| ProgramResource programResource = provider.getProgramResource(descriptor); | |
| int oldDexIndex = nextDexIndex; | |
| nextDexIndex = | |
| dumpProgramResource( | |
| seen, | |
| nextDexIndex, | |
| ignore -> archiveOutputStream, | |
| archiveOutputStream, | |
| programResource); | |
| assert nextDexIndex == oldDexIndex; | |
| } | |
| } | |
| } | |
| writeToZipStream(out, archiveName, archiveByteStream.toByteArray(), ZipEntry.DEFLATED); | |
| } | |
| return nextDexIndex; | |
| } | |
| private static int dumpProgramResource( | |
| Object2IntMap<String> seen, | |
| int nextDexIndex, | |
| Function<String, ZipOutputStream> cfArchiveOutputStream, | |
| ZipOutputStream dexArchiveOutputStream, | |
| ProgramResource programResource) | |
| throws ResourceException, IOException { | |
| byte[] bytes = StreamUtils.streamToByteArrayClose(programResource.getByteStream()); | |
| if (programResource.getKind() == Kind.CF) { | |
| Set<String> classDescriptors = programResource.getClassDescriptors(); | |
| String classDescriptor = | |
| (classDescriptors == null || classDescriptors.size() != 1) | |
| ? extractClassDescriptor(bytes) | |
| : classDescriptors.iterator().next(); | |
| String classFileName = DescriptorUtils.getClassFileName(classDescriptor); | |
| int dupCount = seen.getOrDefault(classDescriptor, 0); | |
| seen.put(classDescriptor, dupCount + 1); | |
| String entryName = dupCount == 0 ? classFileName : (classFileName + "." + dupCount + ".dup"); | |
| writeToZipStream( | |
| cfArchiveOutputStream.apply(classDescriptor), entryName, bytes, ZipEntry.DEFLATED); | |
| } else { | |
| assert programResource.getKind() == Kind.DEX; | |
| String entryName = "classes" + nextDexIndex++ + ".dex"; | |
| writeToZipStream(dexArchiveOutputStream, entryName, bytes, ZipEntry.DEFLATED); | |
| } | |
| return nextDexIndex; | |
| } | |
| private static String extractClassDescriptor(byte[] bytes) { | |
| class ClassNameExtractor extends ClassVisitor { | |
| private String className; | |
| private ClassNameExtractor() { | |
| super(ASM_VERSION); | |
| } | |
| @Override | |
| public void visit( | |
| int version, | |
| int access, | |
| String name, | |
| String signature, | |
| String superName, | |
| String[] interfaces) { | |
| className = name; | |
| } | |
| String getDescriptor() { | |
| return "L" + className + ";"; | |
| } | |
| } | |
| org.objectweb.asm.ClassReader reader = new org.objectweb.asm.ClassReader(bytes); | |
| ClassNameExtractor extractor = new ClassNameExtractor(); | |
| reader.accept( | |
| extractor, | |
| org.objectweb.asm.ClassReader.SKIP_CODE | |
| | org.objectweb.asm.ClassReader.SKIP_DEBUG | |
| | org.objectweb.asm.ClassReader.SKIP_FRAMES); | |
| return extractor.getDescriptor(); | |
| } | |
| public void validateInputs() { | |
| for (ProgramResourceProvider programResourceProvider : getProgramResourceProviders()) { | |
| try { | |
| for (ProgramResource programResource : programResourceProvider.getProgramResources()) { | |
| try { | |
| Kind kind = programResource.getKind(); | |
| if (kind == Kind.DEX) { | |
| continue; | |
| } | |
| byte[] bytes = programResource.getBytes(); | |
| ClassReader classReader = new ClassReader(bytes); | |
| classReader.accept( | |
| new CheckClassAdapter(Opcodes.ASM9, new ClassNode(), true) {}, | |
| ClassReader.EXPAND_FRAMES); | |
| } catch (Throwable e) { | |
| throw new CompilationError("Failed validating " + programResource.getOrigin(), e); | |
| } | |
| } | |
| } catch (ResourceException e) { | |
| throw new CompilationError("Resource exception in validation", e); | |
| } | |
| } | |
| } | |
| public void signalFinishedToProviders(Reporter reporter) throws IOException { | |
| for (ProgramResourceProvider provider : programResourceProviders) { | |
| provider.finished(reporter); | |
| } | |
| for (ClassFileResourceProvider provider : classpathResourceProviders) { | |
| provider.finished(reporter); | |
| } | |
| for (ClassFileResourceProvider provider : libraryResourceProviders) { | |
| provider.finished(reporter); | |
| } | |
| } | |
| /** | |
| * Builder interface for constructing an AndroidApp. | |
| */ | |
| public static class Builder { | |
| private final List<ProgramResourceProvider> programResourceProviders = new ArrayList<>(); | |
| private final List<ProgramResource> programResources = new ArrayList<>(); | |
| private final List<DataResource> dataResources = new ArrayList<>(); | |
| private final Map<ProgramResource, String> programResourcesMainDescriptor = new HashMap<>(); | |
| private final List<ClassFileResourceProvider> classpathResourceProviders = new ArrayList<>(); | |
| private final List<ClassFileResourceProvider> libraryResourceProviders = new ArrayList<>(); | |
| private final List<InternalArchiveClassFileProvider> archiveProvidersToClose = | |
| new ArrayList<>(); | |
| private List<StringResource> mainDexListResources = new ArrayList<>(); | |
| private List<String> mainDexListClasses = new ArrayList<>(); | |
| private boolean ignoreDexInArchive = false; | |
| private StringResource proguardMapOutputData; | |
| private StringResource proguardMapInputData; | |
| private final Reporter reporter; | |
| // See AndroidApp::builder(). | |
| private Builder(Reporter reporter) { | |
| this.reporter = reporter; | |
| } | |
| // See AndroidApp::builder(AndroidApp). | |
| private Builder(Reporter reporter, AndroidApp app) { | |
| this(reporter); | |
| programResourceProviders.addAll(app.programResourceProviders); | |
| classpathResourceProviders.addAll(app.classpathResourceProviders); | |
| libraryResourceProviders.addAll(app.libraryResourceProviders); | |
| archiveProvidersToClose.addAll(app.archiveProvidersToClose); | |
| mainDexListResources = app.mainDexListResources; | |
| mainDexListClasses = app.mainDexClasses; | |
| proguardMapInputData = app.proguardMapInputData; | |
| } | |
| public Reporter getReporter() { | |
| return reporter; | |
| } | |
| public Builder addDump(Path dumpFile) throws IOException { | |
| System.out.println("Reading dump from file: " + dumpFile); | |
| Origin origin = new PathOrigin(dumpFile); | |
| ZipUtils.iter( | |
| dumpFile.toString(), | |
| (entry, input) -> { | |
| String name = entry.getName(); | |
| if (name.equals(dumpVersionFileName)) { | |
| String content = new String(ByteStreams.toByteArray(input), StandardCharsets.UTF_8); | |
| System.out.println("Dump produced by R8 version: " + content); | |
| } else if (name.equals(dumpProgramFileName)) { | |
| readProgramDump(origin, input); | |
| } else if (name.equals(dumpClasspathFileName)) { | |
| readClassFileDump(origin, input, this::addClasspathResourceProvider, "classpath"); | |
| } else if (name.equals(dumpLibraryFileName)) { | |
| readClassFileDump(origin, input, this::addLibraryResourceProvider, "library"); | |
| } else { | |
| System.out.println("WARNING: Unexpected dump file entry: " + entry.getName()); | |
| } | |
| }); | |
| return this; | |
| } | |
| private void readClassFileDump( | |
| Origin origin, | |
| InputStream input, | |
| Consumer<ClassFileResourceProvider> addProvider, | |
| String inputType) | |
| throws IOException { | |
| Map<String, ProgramResource> resources = new HashMap<>(); | |
| try (ZipInputStream stream = new ZipInputStream(input)) { | |
| ZipEntry entry; | |
| while (null != (entry = stream.getNextEntry())) { | |
| String name = entry.getName(); | |
| if (ZipUtils.isClassFile(name)) { | |
| Origin entryOrigin = new ArchiveEntryOrigin(name, origin); | |
| String descriptor = DescriptorUtils.guessTypeDescriptor(name); | |
| ProgramResource resource = | |
| OneShotByteResource.create( | |
| Kind.CF, | |
| entryOrigin, | |
| ByteStreams.toByteArray(stream), | |
| Collections.singleton(descriptor)); | |
| resources.put(descriptor, resource); | |
| } else if (name.endsWith(".dup")) { | |
| System.out.println("WARNING: Duplicate " + inputType + " resource: " + name); | |
| } else { | |
| System.out.println("WARNING: Unexpected " + inputType + " resource: " + name); | |
| } | |
| } | |
| } | |
| if (!resources.isEmpty()) { | |
| addProvider.accept(createClassFileResourceProvider(resources)); | |
| } | |
| } | |
| private void readProgramDump(Origin origin, InputStream input) throws IOException { | |
| List<ProgramResource> programResources = new ArrayList<>(); | |
| List<DataEntryResource> dataResources = new ArrayList<>(); | |
| try (ZipInputStream stream = new ZipInputStream(input)) { | |
| ZipEntry entry; | |
| while (null != (entry = stream.getNextEntry())) { | |
| String name = entry.getName(); | |
| if (ZipUtils.isClassFile(name)) { | |
| Origin entryOrigin = new ArchiveEntryOrigin(name, origin); | |
| String descriptor = DescriptorUtils.guessTypeDescriptor(name); | |
| ProgramResource resource = | |
| OneShotByteResource.create( | |
| Kind.CF, | |
| entryOrigin, | |
| ByteStreams.toByteArray(stream), | |
| Collections.singleton(descriptor)); | |
| programResources.add(resource); | |
| } else if (ZipUtils.isDexFile(name)) { | |
| Origin entryOrigin = new ArchiveEntryOrigin(name, origin); | |
| ProgramResource resource = | |
| OneShotByteResource.create( | |
| Kind.DEX, entryOrigin, ByteStreams.toByteArray(stream), null); | |
| programResources.add(resource); | |
| } else if (name.endsWith(".dup")) { | |
| System.out.println("WARNING: Duplicate program resource: " + name); | |
| } else { | |
| dataResources.add( | |
| DataEntryResource.fromBytes(ByteStreams.toByteArray(stream), name, origin)); | |
| } | |
| } | |
| } | |
| if (!programResources.isEmpty() || !dataResources.isEmpty()) { | |
| addProgramResourceProvider( | |
| new ProgramResourceProvider() { | |
| @Override | |
| public Collection<ProgramResource> getProgramResources() { | |
| return programResources; | |
| } | |
| @Override | |
| public DataResourceProvider getDataResourceProvider() { | |
| return dataResources.isEmpty() | |
| ? null | |
| : new DataResourceProvider() { | |
| @Override | |
| public void accept(Visitor visitor) { | |
| for (DataEntryResource dataResource : dataResources) { | |
| visitor.visit(dataResource); | |
| } | |
| } | |
| }; | |
| } | |
| }); | |
| } | |
| } | |
| /** Add program file resources. */ | |
| public Builder addProgramFiles(Path... files) { | |
| return addProgramFiles(Arrays.asList(files)); | |
| } | |
| /** Add program file resources. */ | |
| public Builder addProgramFiles(Collection<Path> files) { | |
| for (Path file : files) { | |
| addProgramFile(file); | |
| } | |
| return this; | |
| } | |
| public ArchiveResourceProvider createAndAddProvider(FilteredClassPath archive) { | |
| if (isArchive(archive.getPath())) { | |
| ArchiveResourceProvider archiveResourceProvider = | |
| new ArchiveResourceProvider(archive, ignoreDexInArchive); | |
| addProgramResourceProvider(archiveResourceProvider); | |
| return archiveResourceProvider; | |
| } | |
| reporter.error( | |
| new StringDiagnostic( | |
| "Unexpected input type. Only archive types are supported, e.g., .jar, .zip, etc.", | |
| archive.getOrigin(), | |
| archive.getPosition())); | |
| return null; | |
| } | |
| /** Add filtered archives of program resources. */ | |
| public Builder addFilteredProgramArchives(Collection<FilteredClassPath> filteredArchives) { | |
| for (FilteredClassPath archive : filteredArchives) { | |
| createAndAddProvider(archive); | |
| } | |
| return this; | |
| } | |
| public Builder addProgramResourceProvider(ProgramResourceProvider provider) { | |
| assert provider != null; | |
| programResourceProviders.add(provider); | |
| return this; | |
| } | |
| /** Add classpath file resources. */ | |
| public Builder addClasspathFiles(Path... files) { | |
| return addClasspathFiles(Arrays.asList(files)); | |
| } | |
| /** Add classpath file resources. */ | |
| public Builder addClasspathFiles(Collection<Path> files) { | |
| for (Path file : files) { | |
| addClasspathFile(file); | |
| } | |
| return this; | |
| } | |
| /** Add classpath file resources. */ | |
| public Builder addClasspathFile(Path file) { | |
| addClasspathOrLibraryProvider(file, classpathResourceProviders); | |
| return this; | |
| } | |
| /** | |
| * Add classpath resource provider. | |
| */ | |
| public Builder addClasspathResourceProvider(ClassFileResourceProvider provider) { | |
| classpathResourceProviders.add(provider); | |
| return this; | |
| } | |
| /** Add library file resources. */ | |
| public Builder addLibraryFiles(Path... files) { | |
| return addLibraryFiles(Arrays.asList(files)); | |
| } | |
| /** Add library file resources. */ | |
| public Builder addLibraryFiles(Collection<Path> files) { | |
| for (Path file : files) { | |
| addClasspathOrLibraryProvider(file, libraryResourceProviders); | |
| } | |
| return this; | |
| } | |
| /** Add library file resource. */ | |
| public Builder addLibraryFile(Path file) { | |
| addClasspathOrLibraryProvider(file, libraryResourceProviders); | |
| return this; | |
| } | |
| /** Add library file resources. */ | |
| public Builder addFilteredLibraryArchives(Collection<FilteredClassPath> filteredArchives) { | |
| for (FilteredClassPath archive : filteredArchives) { | |
| if (isArchive(archive.getPath())) { | |
| try { | |
| FilteredArchiveClassFileProvider provider = | |
| new FilteredArchiveClassFileProvider(archive); | |
| archiveProvidersToClose.add(provider); | |
| libraryResourceProviders.add(provider); | |
| } catch (IOException e) { | |
| reporter.error(new ExceptionDiagnostic(e, new PathOrigin(archive.getPath()))); | |
| } | |
| } else { | |
| reporter.error( | |
| new StringDiagnostic( | |
| "Unexpected input type. Only archive types are supported, e.g., .jar, .zip, etc.", | |
| archive.getOrigin(), | |
| archive.getPosition())); | |
| } | |
| } | |
| return this; | |
| } | |
| /** | |
| * Add library resource provider. | |
| */ | |
| public Builder addLibraryResourceProvider(ClassFileResourceProvider provider) { | |
| if (provider instanceof InternalArchiveClassFileProvider) { | |
| archiveProvidersToClose.add((InternalArchiveClassFileProvider) provider); | |
| } | |
| libraryResourceProviders.add(provider); | |
| return this; | |
| } | |
| /** | |
| * Add dex program-data with class descriptor. | |
| */ | |
| public Builder addDexProgramData(byte[] data, Set<String> classDescriptors) { | |
| addProgramResources( | |
| ProgramResource.fromBytes(Origin.unknown(), Kind.DEX, data, classDescriptors)); | |
| return this; | |
| } | |
| /** | |
| * Add dex program-data with class descriptor and primary class. | |
| */ | |
| public Builder addDexProgramData( | |
| byte[] data, | |
| Set<String> classDescriptors, | |
| String primaryClassDescriptor) { | |
| ProgramResource resource = ProgramResource.fromBytes( | |
| Origin.unknown(), Kind.DEX, data, classDescriptors); | |
| programResources.add(resource); | |
| programResourcesMainDescriptor.put(resource, primaryClassDescriptor); | |
| return this; | |
| } | |
| /** | |
| * Add dex program-data. | |
| */ | |
| public Builder addDexProgramData(byte[] data, Origin origin) { | |
| addProgramResources(ProgramResource.fromBytes(origin, Kind.DEX, data, null)); | |
| return this; | |
| } | |
| /** | |
| * Add dex program-data. | |
| */ | |
| public Builder addDexProgramData(Collection<byte[]> data) { | |
| for (byte[] datum : data) { | |
| addProgramResources(ProgramResource.fromBytes(Origin.unknown(), Kind.DEX, datum, null)); | |
| } | |
| return this; | |
| } | |
| /** Add Java-bytecode program data. */ | |
| public Builder addClassProgramData(byte[]... data) { | |
| return addClassProgramData(Arrays.asList(data)); | |
| } | |
| /** Add Java-bytecode program data. */ | |
| public Builder addClassProgramData(Collection<byte[]> data) { | |
| for (byte[] datum : data) { | |
| addClassProgramData(datum, Origin.unknown()); | |
| } | |
| return this; | |
| } | |
| /** | |
| * Add Java-bytecode program data. | |
| */ | |
| public Builder addClassProgramData(byte[] data, Origin origin) { | |
| return addClassProgramData(data, origin, null); | |
| } | |
| public Builder addClassProgramData(byte[] data, Origin origin, Set<String> classDescriptors) { | |
| addProgramResources(ProgramResource.fromBytes(origin, Kind.CF, data, classDescriptors)); | |
| return this; | |
| } | |
| /** Add resource data. */ | |
| public Builder addDataResource(DataResource dataResource) { | |
| addDataResources(dataResource); | |
| return this; | |
| } | |
| /** | |
| * Set proguard-map output data. | |
| * | |
| * <p>Note: this should not be used as inputs to compilation! | |
| */ | |
| public Builder setProguardMapOutputData(String content) { | |
| proguardMapOutputData = | |
| content == null ? null : StringResource.fromString(content, Origin.unknown()); | |
| return this; | |
| } | |
| public Builder setProguardMapInputData(Path mapPath) { | |
| this.proguardMapInputData = StringResource.fromFile(mapPath); | |
| return this; | |
| } | |
| /** | |
| * Add a main-dex list file. | |
| */ | |
| public Builder addMainDexListFiles(Path... files) throws NoSuchFileException { | |
| return addMainDexListFiles(Arrays.asList(files)); | |
| } | |
| public Builder addMainDexListFiles(Collection<Path> files) throws NoSuchFileException { | |
| for (Path file : files) { | |
| if (!Files.exists(file)) { | |
| throw new NoSuchFileException(file.toString()); | |
| } | |
| // TODO(sgjesse): Should we just read the file here? This will sacrifice the parallelism | |
| // in ApplicationReader where all input resources are read in parallel. | |
| mainDexListResources.add(StringResource.fromFile(file)); | |
| } | |
| return this; | |
| } | |
| /** | |
| * Add main-dex classes. | |
| */ | |
| public Builder addMainDexClasses(String... classes) { | |
| return addMainDexClasses(Arrays.asList(classes)); | |
| } | |
| /** | |
| * Add main-dex classes. | |
| */ | |
| public Builder addMainDexClasses(Collection<String> classes) { | |
| mainDexListClasses.addAll(classes); | |
| return this; | |
| } | |
| public boolean hasMainDexList() { | |
| return !(mainDexListResources.isEmpty() && mainDexListClasses.isEmpty()); | |
| } | |
| /** | |
| * Ignore dex resources in input archives. | |
| * | |
| * In some situations (e.g. AOSP framework build) the input archives include both class and | |
| * dex resources. Setting this flag ignores the dex resources and reads the class resources | |
| * only. | |
| */ | |
| public Builder setIgnoreDexInArchive(boolean value) { | |
| ignoreDexInArchive = value; | |
| return this; | |
| } | |
| /** | |
| * Build final AndroidApp. | |
| */ | |
| public AndroidApp build() { | |
| ensureAllResourcesAreInProviders(); | |
| return new AndroidApp( | |
| ImmutableList.copyOf(programResourceProviders), | |
| ImmutableMap.copyOf(programResourcesMainDescriptor), | |
| ImmutableList.copyOf(classpathResourceProviders), | |
| ImmutableList.copyOf(libraryResourceProviders), | |
| ImmutableList.copyOf(archiveProvidersToClose), | |
| proguardMapOutputData, | |
| proguardMapInputData, | |
| mainDexListResources, | |
| mainDexListClasses); | |
| } | |
| private void ensureAllResourcesAreInProviders() { | |
| if (programResources.isEmpty() && dataResources.isEmpty()) { | |
| return; | |
| } | |
| final List<ProgramResource> finalProgramResources = ImmutableList.copyOf(programResources); | |
| final List<DataResource> finalDataResources = ImmutableList.copyOf(dataResources); | |
| programResourceProviders.add( | |
| new ProgramResourceProvider() { | |
| @Override | |
| public Collection<ProgramResource> getProgramResources() { | |
| return finalProgramResources; | |
| } | |
| @Override | |
| public DataResourceProvider getDataResourceProvider() { | |
| if (!finalDataResources.isEmpty()) { | |
| return new DataResourceProvider() { | |
| @Override | |
| public void accept(Visitor visitor) { | |
| for (DataResource dataResource : finalDataResources) { | |
| if (dataResource instanceof DataEntryResource) { | |
| visitor.visit((DataEntryResource) dataResource); | |
| } else { | |
| assert dataResource instanceof DataDirectoryResource; | |
| visitor.visit((DataDirectoryResource) dataResource); | |
| } | |
| } | |
| } | |
| }; | |
| } | |
| return null; | |
| } | |
| }); | |
| programResources.clear(); | |
| dataResources.clear(); | |
| } | |
| public Builder addProgramFile(Path file) { | |
| if (!Files.exists(file)) { | |
| PathOrigin pathOrigin = new PathOrigin(file); | |
| NoSuchFileException noSuchFileException = new NoSuchFileException(file.toString()); | |
| reporter.error(new ExceptionDiagnostic(noSuchFileException, pathOrigin)); | |
| } | |
| if (isDexFile(file)) { | |
| addProgramResources(ProgramResource.fromFile(Kind.DEX, file)); | |
| } else if (isClassFile(file)) { | |
| addProgramResources(ProgramResource.fromFile(Kind.CF, file)); | |
| } else if (isAarFile(file)) { | |
| addProgramResourceProvider(AarArchiveResourceProvider.fromArchive(file)); | |
| } else if (isArchive(file)) { | |
| addProgramResourceProvider(ArchiveResourceProvider.fromArchive(file, ignoreDexInArchive)); | |
| } else { | |
| throw new CompilationError("Unsupported source file type", new PathOrigin(file)); | |
| } | |
| return this; | |
| } | |
| private void addProgramResources(ProgramResource... resources) { | |
| addProgramResources(Arrays.asList(resources)); | |
| } | |
| private void addProgramResources(Collection<ProgramResource> resources) { | |
| programResources.addAll(resources); | |
| } | |
| private void addDataResources(DataResource... resources) { | |
| addDataResources(Arrays.asList(resources)); | |
| } | |
| private void addDataResources(Collection<DataResource> resources) { | |
| this.dataResources.addAll(resources); | |
| } | |
| private void addClasspathOrLibraryProvider( | |
| Path file, List<ClassFileResourceProvider> providerList) { | |
| if (!Files.exists(file)) { | |
| reporter.error( | |
| new ExceptionDiagnostic( | |
| new NoSuchFileException(file.toString()), new PathOrigin(file))); | |
| } | |
| if (isArchive(file)) { | |
| try { | |
| InternalArchiveClassFileProvider provider = new InternalArchiveClassFileProvider(file); | |
| archiveProvidersToClose.add(provider); | |
| providerList.add(provider); | |
| } catch (IOException e) { | |
| reporter.error(new ExceptionDiagnostic(e, new PathOrigin(file))); | |
| } | |
| } else if (Files.isDirectory(file) ) { | |
| providerList.add(DirectoryClassFileProvider.fromDirectory(file)); | |
| } else { | |
| throw new CompilationError("Unsupported source file type", new PathOrigin(file)); | |
| } | |
| } | |
| public List<ProgramResourceProvider> getProgramResourceProviders() { | |
| ensureAllResourcesAreInProviders(); | |
| return programResourceProviders; | |
| } | |
| } | |
| } |