diff options
author | Googler <noreply@google.com> | 2016-06-22 13:33:40 +0000 |
---|---|---|
committer | Lukacs Berki <lberki@google.com> | 2016-06-23 11:01:40 +0000 |
commit | 52d15620851a652efa5c5cae6399bcb3c33105c6 (patch) | |
tree | 0c5c8308d4fdd07cbdbefb1d4fd9acf4eeceeae7 /src/tools/android/java/com/google/devtools/build/android/resources | |
parent | 59c5c8668bd96d5d4c0f1326c23faa526df6e676 (diff) |
Roll forward of commit 1f1f207573c7b9c3e2d3ca1ffb0780a8fd592214: action to write R classes directly
NEW: add check that primary R.txt exists before
trying to load its symbols.
Rollback of commit 32c6c15c8b9bc4e203529f60bedbc5cd8a496a36.
*** Reason for rollback ***
Rollforward with check that primary R.txt exists
*** Original change description ***
Automated [] rollback of commit 1f1f207573c7b9c3e2d3ca1ffb0780a8fd592214.
*** Reason for rollback ***
Doesn't handle aapt that doesn't generate R.txt properly.
--
MOS_MIGRATED_REVID=125559472
Diffstat (limited to 'src/tools/android/java/com/google/devtools/build/android/resources')
-rw-r--r-- | src/tools/android/java/com/google/devtools/build/android/resources/BUILD | 24 | ||||
-rw-r--r-- | src/tools/android/java/com/google/devtools/build/android/resources/RClassWriter.java | 317 |
2 files changed, 341 insertions, 0 deletions
diff --git a/src/tools/android/java/com/google/devtools/build/android/resources/BUILD b/src/tools/android/java/com/google/devtools/build/android/resources/BUILD new file mode 100644 index 0000000000..c1d565e561 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/resources/BUILD @@ -0,0 +1,24 @@ +# Description: +# Tools for android resource processing + +package(default_visibility = [ + "//src/test/java/com/google/devtools/build/android/resources:__pkg__", + "//src/tools/android/java/com/google/devtools/build/android:__pkg__", +]) + +java_library( + name = "resources", + srcs = glob(["*.java"]), + deps = [ + "//third_party:android_common", + "//third_party:asm", + "//third_party:asm-commons", + "//third_party:guava", + ], +) + +filegroup( + name = "srcs", + srcs = glob(["**"]), + visibility = ["//src/tools/android/java/com/google/devtools/build/android:__pkg__"], +) diff --git a/src/tools/android/java/com/google/devtools/build/android/resources/RClassWriter.java b/src/tools/android/java/com/google/devtools/build/android/resources/RClassWriter.java new file mode 100644 index 0000000000..53bb945dc1 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/resources/RClassWriter.java @@ -0,0 +1,317 @@ +// Copyright 2016 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.google.devtools.build.android.resources; + +import com.google.common.base.Preconditions; +import com.google.common.base.Splitter; +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Table; +import com.google.common.io.Files; + +import com.android.SdkConstants; +import com.android.builder.internal.SymbolLoader; +import com.android.builder.internal.SymbolLoader.SymbolEntry; + +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Writes out bytecode for an R.class directly, rather than go through an R.java and compile. This + * avoids re-parsing huge R.java files and other time spent in the java compiler (e.g., plugins like + * ErrorProne). A difference is that this doesn't generate line number tables and other debugging + * information. Also, the order of the constant pool tends to be different. + */ +public class RClassWriter { + + private static final int JAVA_VERSION = Opcodes.V1_7; + private static final String SUPER_CLASS = "java/lang/Object"; + private final File outFolder; + private final String packageName; + private final List<SymbolLoader> symbolTables = new ArrayList<>(); + private final SymbolLoader symbolValues; + private final boolean finalFields; + + public RClassWriter(File outFolder, + String packageName, + SymbolLoader values, + boolean finalFields) { + this.outFolder = outFolder; + this.packageName = packageName; + this.symbolValues = values; + this.finalFields = finalFields; + } + + public void addSymbolsToWrite(SymbolLoader symbols) { + symbolTables.add(symbols); + } + + private Table<String, String, SymbolEntry> getAllSymbols() throws IOException { + Table<String, String, SymbolEntry> symbols = HashBasedTable.create(); + for (SymbolLoader symbolLoader : symbolTables) { + symbols.putAll(getSymbols(symbolLoader)); + } + return symbols; + } + + private Method symbolsMethod; + + private Table<String, String, SymbolEntry> getSymbols(SymbolLoader symbolLoader) + throws IOException { + // TODO(bazel-team): upstream a patch to change the visibility instead of hacking it. + try { + if (symbolsMethod == null) { + Method getSymbols = SymbolLoader.class.getDeclaredMethod("getSymbols"); + getSymbols.setAccessible(true); + symbolsMethod = getSymbols; + } + @SuppressWarnings("unchecked") + Table<String, String, SymbolEntry> result = (Table<String, String, SymbolEntry>) + symbolsMethod.invoke(symbolLoader); + return result; + } catch (ReflectiveOperationException e) { + throw new IOException(e); + } + } + + /** + * Builds the bytecode and writes out the R.class file, and R$inner.class files. + */ + public void write() throws IOException { + Splitter splitter = Splitter.on('.'); + Iterable<String> folders = splitter.split(packageName); + File packageDir = outFolder; + for (String folder : folders) { + packageDir = new File(packageDir, folder); + } + File rClassFile = new File(packageDir, SdkConstants.FN_COMPILED_RESOURCE_CLASS); + Files.createParentDirs(rClassFile); + String packageWithSlashes = packageName.replaceAll("\\.", "/"); + String rClassName = packageWithSlashes + "/R"; + ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); + classWriter + .visit(JAVA_VERSION, Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_SUPER, + rClassName, null, SUPER_CLASS, null); + classWriter.visitSource(SdkConstants.FN_RESOURCE_CLASS, null); + writeConstructor(classWriter); + + Table<String, String, SymbolEntry> symbols = getAllSymbols(); + Table<String, String, SymbolEntry> values = getSymbols(symbolValues); + + Set<String> rowSet = symbols.rowKeySet(); + List<String> rowList = new ArrayList<>(rowSet); + Collections.sort(rowList); + + // Build the R.class w/ the inner classes, then later build the individual R$inner.class. + for (String row : rowList) { + String innerClassName = rClassName + "$" + row; + classWriter.visitInnerClass(innerClassName, rClassName, row, + Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_STATIC); + } + classWriter.visitEnd(); + Files.write(classWriter.toByteArray(), rClassFile); + + // Now generate the R$inner.class files. + for (String row : rowList) { + writeInnerClass(symbols, values, packageDir, rClassName, row); + } + } + + /** + * Represents an int or int[] field and its initializer (where initialization is done via code in + * the static clinit function). + */ + private interface DeferredInitializer { + + /** + * Write the code for the initializer via insts. + * + * @return the number of stack slots needed for the code. + */ + int writeCLInit(String className, InstructionAdapter insts); + } + + private static final class IntArrayDeferredInitializer implements DeferredInitializer { + + private final String fieldName; + private final ImmutableList<Integer> values; + + IntArrayDeferredInitializer(String fieldName, ImmutableList<Integer> values) { + this.fieldName = fieldName; + this.values = values; + } + + public static DeferredInitializer of(String name, String value) { + Preconditions.checkArgument(value.startsWith("{ "), "Expected list starting with { "); + Preconditions.checkArgument(value.endsWith(" }"), "Expected list ending with } "); + // Check for an empty list, which is "{ }". + if (value.length() < 4) { + return new IntArrayDeferredInitializer(name, ImmutableList.<Integer>of()); + } + ImmutableList.Builder<Integer> intValues = ImmutableList.builder(); + String trimmedValue = value.substring(2, value.length() - 2); + Iterable<String> valueStrings = Splitter.on(',') + .trimResults() + .omitEmptyStrings() + .split(trimmedValue); + for (String valueString : valueStrings) { + intValues.add(Integer.decode(valueString)); + } + return new IntArrayDeferredInitializer(name, intValues.build()); + } + + @Override + public int writeCLInit(String className, InstructionAdapter insts) { + insts.iconst(values.size()); + insts.newarray(Type.INT_TYPE); + int curIndex = 0; + for (Integer value : values) { + insts.dup(); + insts.iconst(curIndex); + insts.iconst(value); + insts.astore(Type.INT_TYPE); + ++curIndex; + } + insts.putstatic(className, fieldName, "[I"); + // Needs up to 4 stack slots for: the array ref for the putstatic, the dup of the array ref + // for the store, the index, and the value to store. + return 4; + } + } + + private static final class IntDeferredInitializer implements DeferredInitializer { + + private final String fieldName; + private final Integer value; + + IntDeferredInitializer(String fieldName, Integer value) { + this.fieldName = fieldName; + this.value = value; + } + + public static DeferredInitializer of(String name, String value) { + return new IntDeferredInitializer(name, Integer.decode(value)); + } + + @Override + public int writeCLInit(String className, InstructionAdapter insts) { + insts.iconst(value); + insts.putstatic(className, fieldName, "I"); + // Just needs one stack slot for the iconst. + return 1; + } + } + + private void writeInnerClass( + Table<String, String, SymbolEntry> symbols, + Table<String, String, SymbolEntry> values, + File packageDir, + String fullyQualifiedOuterClass, + String innerClass) throws IOException { + ClassWriter innerClassWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); + String fullyQualifiedInnerClass = fullyQualifiedOuterClass + "$" + innerClass; + innerClassWriter + .visit(JAVA_VERSION, Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_SUPER, + fullyQualifiedInnerClass, null, SUPER_CLASS, null); + innerClassWriter.visitSource("R.java", null); + writeConstructor(innerClassWriter); + innerClassWriter.visitInnerClass( + fullyQualifiedInnerClass, fullyQualifiedOuterClass, innerClass, + Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_STATIC); + + Map<String, SymbolEntry> rowMap = symbols.row(innerClass); + Set<String> symbolSet = rowMap.keySet(); + List<String> symbolList = new ArrayList<>(symbolSet); + Collections.sort(symbolList); + List<DeferredInitializer> deferredInitializers = new ArrayList<>(); + int fieldAccessLevel = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC; + if (finalFields) { + fieldAccessLevel |= Opcodes.ACC_FINAL; + } + for (String symbolName : symbolList) { + // get the matching SymbolEntry from the values Table. + SymbolEntry value = values.get(innerClass, symbolName); + if (value != null) { + String desc; + Object initializer = null; + if (value.getType().equals("int")) { + desc = "I"; + if (finalFields) { + initializer = Integer.decode(value.getValue()); + } else { + deferredInitializers.add(IntDeferredInitializer.of(value.getName(), value.getValue())); + } + } else { + Preconditions.checkArgument(value.getType().equals("int[]")); + desc = "[I"; + deferredInitializers + .add(IntArrayDeferredInitializer.of(value.getName(), value.getValue())); + } + innerClassWriter + .visitField(fieldAccessLevel, value.getName(), desc, null, initializer) + .visitEnd(); + } + } + + if (!deferredInitializers.isEmpty()) { + // build the <clinit> method. + writeStaticClassInit(innerClassWriter, fullyQualifiedInnerClass, deferredInitializers); + } + + innerClassWriter.visitEnd(); + File innerFile = new File(packageDir, "R$" + innerClass + ".class"); + Files.write(innerClassWriter.toByteArray(), innerFile); + } + + private static void writeConstructor(ClassWriter classWriter) { + MethodVisitor constructor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", + null, null); + constructor.visitCode(); + constructor.visitVarInsn(Opcodes.ALOAD, 0); + constructor.visitMethodInsn(Opcodes.INVOKESPECIAL, SUPER_CLASS, "<init>", "()V", false); + constructor.visitInsn(Opcodes.RETURN); + constructor.visitMaxs(1, 1); + constructor.visitEnd(); + } + + private static void writeStaticClassInit( + ClassWriter classWriter, + String className, + List<DeferredInitializer> deferredInitializers) { + MethodVisitor visitor = classWriter.visitMethod(Opcodes.ACC_STATIC, "<clinit>", "()V", + null, null); + visitor.visitCode(); + int stackSlotsNeeded = 0; + InstructionAdapter insts = new InstructionAdapter(visitor); + for (DeferredInitializer fieldInit : deferredInitializers) { + stackSlotsNeeded = Math.max(stackSlotsNeeded, fieldInit.writeCLInit(className, insts)); + } + insts.areturn(Type.VOID_TYPE); + visitor.visitMaxs(stackSlotsNeeded, 0); + visitor.visitEnd(); + } + +} |