Skip to content

Commit

Permalink
Adds aapt2.ResourceCompiler and CompileLibraryResourcesAction.
Browse files Browse the repository at this point in the history
Refactorings:
* Change data binding to have configurable archive generation
* Extract a ZipBuilder class from the ZipBuilderVisitor to provide a general purpose archiving class.
* Small changes to visibility AaptCommandLineBuilder for reuse in the aapt2 code.

RELNOTES: None
PiperOrigin-RevId: 164880571
  • Loading branch information
corbinrsmith-work authored and hlopko committed Aug 11, 2017
1 parent a664a51 commit e7e7ba4
Show file tree
Hide file tree
Showing 9 changed files with 548 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ public void testZipEntryPaths() throws Exception {

try (ZipOutputStream zout = new ZipOutputStream(Files.newOutputStream(output))) {
AndroidResourceOutputs.ZipBuilderVisitor visitor =
new AndroidResourceOutputs.ZipBuilderVisitor(zout, tmp.resolve("foo"), "some/prefix");
new AndroidResourceOutputs.ZipBuilderVisitor(
AndroidResourceOutputs.ZipBuilder.wrap(zout), tmp.resolve("foo"), "some/prefix");
Files.walkFileTree(tmp.resolve("foo"), visitor);
visitor.writeEntries();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
* Builder for AAPT command lines, with support for making flags conditional on build tools version
* and variant type.
*/
class AaptCommandBuilder {
public class AaptCommandBuilder {
private final ImmutableList.Builder<String> flags = new ImmutableList.Builder<>();
private Revision buildToolsVersion;
private VariantType variantType;
Expand All @@ -37,15 +37,15 @@ public AaptCommandBuilder(Path aapt) {
}

/** Sets the build tools version to be used for {@link #whenVersionIsAtLeast}. */
AaptCommandBuilder forBuildToolsVersion(@Nullable Revision buildToolsVersion) {
public AaptCommandBuilder forBuildToolsVersion(@Nullable Revision buildToolsVersion) {
Preconditions.checkState(
this.buildToolsVersion == null, "A build tools version was already specified.");
this.buildToolsVersion = buildToolsVersion;
return this;
}

/** Sets the variant type to be used for {@link #whenVariantIs}. */
AaptCommandBuilder forVariantType(VariantType variantType) {
public AaptCommandBuilder forVariantType(VariantType variantType) {
Preconditions.checkNotNull(variantType);
Preconditions.checkState(this.variantType == null, "A variant type was already specified.");
this.variantType = variantType;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,11 +265,13 @@ public static void main(String[] args) throws Exception {
if (options.resourcesOutput != null) {
Path resourcesDir =
AndroidResourceProcessor.processDataBindings(
tmp.resolve("res_no_binding"),
mergedData.getResourceDir(),
options.dataBindingInfoOut,
packageType,
options.packageForR,
options.primaryManifest);
options.primaryManifest,
true);

// For now, try compressing the library resources that we pass to the validator. This takes
// extra CPU resources to pack and unpack (~2x), but can reduce the zip size (~4x).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@
import static java.nio.charset.StandardCharsets.UTF_8;

import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.Ordering;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.FileVisitResult;
Expand All @@ -29,6 +32,8 @@
import java.nio.file.attribute.FileTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Objects;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
Expand All @@ -42,10 +47,71 @@
/** Collects all the functionationality for an action to create the final output artifacts. */
public class AndroidResourceOutputs {

static class ZipBuilder implements Closeable {
// ZIP timestamps have a resolution of 2 seconds.
// see http://www.info-zip.org/FAQ.html#limits
private static final long MINIMUM_TIMESTAMP_INCREMENT = 2000L;

// The earliest date representable in a zip file, 1-1-1980 (the DOS epoch).
private static final long ZIP_EPOCH =
new GregorianCalendar(1980, 01, 01, 0, 0).getTimeInMillis();

private final ZipOutputStream zip;

private ZipBuilder(ZipOutputStream zip) {
this.zip = zip;
}

public static ZipBuilder createFor(Path archivePath) throws IOException {
return wrap(
new ZipOutputStream(new BufferedOutputStream(Files.newOutputStream(archivePath))));
}

public static ZipBuilder wrap(ZipOutputStream zip) {
return new ZipBuilder(zip);
}

/**
* Normalize timestamps for deterministic builds. Stamp .class files to be a bit newer than
* .java files. See: {@link
* com.google.devtools.build.buildjar.jarhelper.JarHelper#normalizedTimestamp(String)}
*/
protected long normalizeTime(String filename) {
if (filename.endsWith(".class")) {
return ZIP_EPOCH + MINIMUM_TIMESTAMP_INCREMENT;
} else {
return ZIP_EPOCH;
}
}

protected void addEntry(String rawName, byte[] content, int storageMethod) throws IOException {
// Fix the path for windows.
String relativeName = rawName.replace('\\', '/');
// Make sure the zip entry is not absolute.
Preconditions.checkArgument(!relativeName.startsWith("/"));
ZipEntry entry = new ZipEntry(relativeName);
entry.setMethod(storageMethod);
entry.setTime(normalizeTime(relativeName));
entry.setSize(content.length);
CRC32 crc32 = new CRC32();
crc32.update(content);
entry.setCrc(crc32.getValue());

zip.putNextEntry(entry);
zip.write(content);
zip.closeEntry();
}

@Override
public void close() throws IOException {
zip.close();
}
}

/** A FileVisitor that will add all R class files to be stored in a zip archive. */
static final class ClassJarBuildingVisitor extends ZipBuilderVisitor {

ClassJarBuildingVisitor(ZipOutputStream zip, Path root) {
ClassJarBuildingVisitor(ZipBuilder zip, Path root) {
super(zip, root, null);
}

Expand Down Expand Up @@ -89,8 +155,8 @@ static final class SymbolFileSrcJarBuildingVisitor extends ZipBuilderVisitor {

private final boolean staticIds;

private SymbolFileSrcJarBuildingVisitor(ZipOutputStream zip, Path root, boolean staticIds) {
super(zip, root, null);
private SymbolFileSrcJarBuildingVisitor(ZipBuilder zipBuilder, Path root, boolean staticIds) {
super(zipBuilder, root, null);
this.staticIds = staticIds;
}

Expand Down Expand Up @@ -139,51 +205,21 @@ protected void writeFileEntry(Path file) throws IOException {
/** A FileVisitor that will add all files to be stored in a zip archive. */
static class ZipBuilderVisitor extends SimpleFileVisitor<Path> {

// ZIP timestamps have a resolution of 2 seconds.
// see http://www.info-zip.org/FAQ.html#limits
private static final long MINIMUM_TIMESTAMP_INCREMENT = 2000L;
// The earliest date representable in a zip file, 1-1-1980 (the DOS epoch).
private static final long ZIP_EPOCH = 315561600000L;

private final String directoryPrefix;
protected final String directoryPrefix;
private final Collection<Path> paths = new ArrayList<>();
protected final Path root;
private int storageMethod = ZipEntry.STORED;
private final ZipOutputStream zip;
private ZipBuilder zipBuilder;

ZipBuilderVisitor(ZipOutputStream zip, Path root, String directory) {
this.zip = zip;
ZipBuilderVisitor(ZipBuilder zipBuilder, Path root, String directory) {
this.root = root;
this.directoryPrefix = directory;
this.directoryPrefix = directory != null ? (directory + File.separator) : "";
this.zipBuilder = zipBuilder;
}

protected void addEntry(Path file, byte[] content) throws IOException {
String prefix = directoryPrefix != null ? (directoryPrefix + "/") : "";
String relativeName = root.relativize(file).toString();
ZipEntry entry = new ZipEntry((prefix + relativeName).replace('\\', '/'));
entry.setMethod(storageMethod);
entry.setTime(normalizeTime(relativeName));
entry.setSize(content.length);
CRC32 crc32 = new CRC32();
crc32.update(content);
entry.setCrc(crc32.getValue());

zip.putNextEntry(entry);
zip.write(content);
zip.closeEntry();
}

/**
* Normalize timestamps for deterministic builds. Stamp .class files to be a bit newer than
* .java files. See: {@link
* com.google.devtools.build.buildjar.jarhelper.JarHelper#normalizedTimestamp(String)}
*/
protected long normalizeTime(String filename) {
if (filename.endsWith(".class")) {
return ZIP_EPOCH + MINIMUM_TIMESTAMP_INCREMENT;
} else {
return ZIP_EPOCH;
}
Preconditions.checkArgument(file.startsWith(root), "%s does not start with %s", file, root);
zipBuilder.addEntry(directoryPrefix + root.relativize(file), content, storageMethod);
}

public void setCompress(boolean compress) {
Expand Down Expand Up @@ -270,10 +306,8 @@ public static void copyRToOutput(Path generatedSourceRoot, Path rOutput, boolean
public static void createClassJar(Path generatedClassesRoot, Path classJar) {
try {
Files.createDirectories(classJar.getParent());
try (final ZipOutputStream zip =
new ZipOutputStream(new BufferedOutputStream(Files.newOutputStream(classJar)))) {
ClassJarBuildingVisitor visitor =
new ClassJarBuildingVisitor(zip, generatedClassesRoot);
try (final ZipBuilder zip = ZipBuilder.createFor(classJar)) {
ClassJarBuildingVisitor visitor = new ClassJarBuildingVisitor(zip, generatedClassesRoot);
Files.walkFileTree(generatedClassesRoot, visitor);
visitor.writeEntries();
visitor.writeManifestContent();
Expand All @@ -285,6 +319,23 @@ public static void createClassJar(Path generatedClassesRoot, Path classJar) {
}
}

/** Creates a zip archive from all files under the provided root. */
public static void archiveDirectory(Path root, Path archive) {
try {
Files.createDirectories(archive.getParent());
try (final ZipBuilder zip = ZipBuilder.createFor(archive)) {
ZipBuilderVisitor visitor = new ZipBuilderVisitor(zip, root, null);
visitor.setCompress(false);
Files.walkFileTree(root, visitor);
visitor.writeEntries();
}
// Set to the epoch for caching purposes.
Files.setLastModifiedTime(archive, FileTime.fromMillis(0L));
} catch (IOException e) {
throw new RuntimeException(e);
}
}

/**
* Creates a zip file containing the provided android resources and assets.
*
Expand All @@ -296,18 +347,15 @@ public static void createClassJar(Path generatedClassesRoot, Path classJar) {
*/
public static void createResourcesZip(
Path resourcesRoot, Path assetsRoot, Path output, boolean compress) throws IOException {
try (ZipOutputStream zout =
new ZipOutputStream(new BufferedOutputStream(Files.newOutputStream(output)))) {
try (final ZipBuilder zip = ZipBuilder.createFor(output)) {
if (Files.exists(resourcesRoot)) {
ZipBuilderVisitor visitor =
new ZipBuilderVisitor(zout, resourcesRoot, "res");
ZipBuilderVisitor visitor = new ZipBuilderVisitor(zip, resourcesRoot, "res");
visitor.setCompress(compress);
Files.walkFileTree(resourcesRoot, visitor);
visitor.writeEntries();
}
if (Files.exists(assetsRoot)) {
ZipBuilderVisitor visitor =
new ZipBuilderVisitor(zout, assetsRoot, "assets");
ZipBuilderVisitor visitor = new ZipBuilderVisitor(zip, assetsRoot, "assets");
visitor.setCompress(compress);
Files.walkFileTree(assetsRoot, visitor);
visitor.writeEntries();
Expand All @@ -319,11 +367,9 @@ public static void createResourcesZip(
public static void createSrcJar(Path generatedSourcesRoot, Path srcJar, boolean staticIds) {
try {
Files.createDirectories(srcJar.getParent());
try (final ZipOutputStream zip =
new ZipOutputStream(new BufferedOutputStream(Files.newOutputStream(srcJar)))) {
try (final ZipBuilder zip = ZipBuilder.createFor(srcJar)) {
SymbolFileSrcJarBuildingVisitor visitor =
new SymbolFileSrcJarBuildingVisitor(
zip, generatedSourcesRoot, staticIds);
new SymbolFileSrcJarBuildingVisitor(zip, generatedSourcesRoot, staticIds);
Files.walkFileTree(generatedSourcesRoot, visitor);
visitor.writeEntries();
}
Expand All @@ -333,4 +379,33 @@ public static void createSrcJar(Path generatedSourcesRoot, Path srcJar, boolean
throw new RuntimeException(e);
}
}

/** Collects all the compiled resources into an archive, normalizing the paths to the root. */
public static void archiveCompiledResources(
final Path archiveOut,
final Path databindingResourcesRoot,
final Path compiledRoot,
final List<Path> compiledArtifacts)
throws IOException {
final Path relativeDatabindingProcessedResources =
databindingResourcesRoot.getRoot().relativize(databindingResourcesRoot);
try (ZipBuilder builder = ZipBuilder.createFor(archiveOut)) {
for (Path artifact : compiledArtifacts) {
Path relativeName = artifact;
// remove compiled resources prefix
if (artifact.startsWith(compiledRoot)) {
relativeName = compiledRoot.relativize(relativeName);
}
// remove databinding prefix
if (relativeName.startsWith(relativeDatabindingProcessedResources)) {
relativeName =
relativeName.subpath(
relativeDatabindingProcessedResources.getNameCount(),
relativeName.getNameCount());
}

builder.addEntry(relativeName.toString(), Files.readAllBytes(artifact), ZipEntry.STORED);
}
}
}
}
Loading

0 comments on commit e7e7ba4

Please sign in to comment.