aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcushon <cushon@google.com>2017-01-25 22:04:36 -0800
committerLiam Miller-Cushon <cushon@google.com>2017-01-27 15:03:01 -0800
commitd1509927c68b994ecb9eb95a8ae8478da9f04ed4 (patch)
treec76e984a405aeab69641ae8edf82332401e09c8f
parenta64301e54ccc8339879cbcc37f9f7e476a8b50ac (diff)
downloadturbine-d1509927c68b994ecb9eb95a8ae8478da9f04ed4.tar.gz
Repackage supertypes of compiled classes in the output jar
so header compilations can be performed without transitive dependencies on the classpath. MOE_MIGRATED_REVID=145636768
-rw-r--r--java/com/google/turbine/binder/ClassPathBinder.java54
-rw-r--r--java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java5
-rw-r--r--java/com/google/turbine/bytecode/ClassReader.java2
-rw-r--r--java/com/google/turbine/deps/Transitive.java130
-rw-r--r--java/com/google/turbine/main/Main.java32
-rw-r--r--java/com/google/turbine/options/TurbineOptions.java5
-rw-r--r--javatests/com/google/turbine/bytecode/JavapUtils.java5
-rw-r--r--javatests/com/google/turbine/deps/TransitiveTest.java176
-rw-r--r--javatests/com/google/turbine/lower/LowerTest.java2
9 files changed, 383 insertions, 28 deletions
diff --git a/java/com/google/turbine/binder/ClassPathBinder.java b/java/com/google/turbine/binder/ClassPathBinder.java
index 66800a8..af94fed 100644
--- a/java/com/google/turbine/binder/ClassPathBinder.java
+++ b/java/com/google/turbine/binder/ClassPathBinder.java
@@ -16,6 +16,8 @@
package com.google.turbine.binder;
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.ByteStreams;
import com.google.turbine.binder.bytecode.BytecodeBoundClass;
@@ -29,6 +31,7 @@ import java.io.IOException;
import java.nio.file.Path;
import java.util.Enumeration;
import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
@@ -37,6 +40,12 @@ import java.util.jar.JarFile;
public class ClassPathBinder {
/**
+ * The prefix for repackaged transitive dependencies; see {@link
+ * com.google.turbine.deps.Transitive}.
+ */
+ public static final String TRANSITIVE_PREFIX = "META-INF/TRANSITIVE/";
+
+ /**
* Creates an environment containing symbols in the given classpath and bootclasspath, and adds
* them to the top-level index.
*/
@@ -52,6 +61,7 @@ public class ClassPathBinder {
private static Env<ClassSymbol, BytecodeBoundClass> bindClasspath(
TopLevelIndex.Builder tli, Iterable<Path> paths) throws IOException {
+ Map<ClassSymbol, BytecodeBoundClass> transitive = new LinkedHashMap<>();
Map<ClassSymbol, BytecodeBoundClass> map = new HashMap<>();
Env<ClassSymbol, BytecodeBoundClass> benv =
new Env<ClassSymbol, BytecodeBoundClass>() {
@@ -62,11 +72,18 @@ public class ClassPathBinder {
};
for (Path path : paths) {
try {
- bindJar(tli, path, map, benv);
+ bindJar(tli, path, map, benv, transitive);
} catch (IOException e) {
throw new IOException("error reading " + path, e);
}
}
+ for (Map.Entry<ClassSymbol, BytecodeBoundClass> entry : transitive.entrySet()) {
+ ClassSymbol symbol = entry.getKey();
+ if (!map.containsKey(symbol)) {
+ map.put(symbol, entry.getValue());
+ tli.insert(symbol);
+ }
+ }
return new SimpleEnv<>(ImmutableMap.copyOf(map));
}
@@ -74,7 +91,8 @@ public class ClassPathBinder {
TopLevelIndex.Builder tli,
Path path,
Map<ClassSymbol, BytecodeBoundClass> env,
- Env<ClassSymbol, BytecodeBoundClass> benv)
+ Env<ClassSymbol, BytecodeBoundClass> benv,
+ Map<ClassSymbol, BytecodeBoundClass> transitive)
throws IOException {
// TODO(cushon): consider creating a nio-friendly jar reading abstraction for testing,
// that yields something like `Iterable<Pair<String, Supplier<byte[]>>>`
@@ -87,21 +105,35 @@ public class ClassPathBinder {
if (!name.endsWith(".class")) {
continue;
}
+ if (name.startsWith(TRANSITIVE_PREFIX)) {
+ ClassSymbol sym =
+ new ClassSymbol(
+ name.substring(TRANSITIVE_PREFIX.length(), name.length() - ".class".length()));
+ if (!transitive.containsKey(sym)) {
+ transitive.put(
+ sym, new BytecodeBoundClass(sym, toByteArrayOrDie(jf, je), benv, path.toString()));
+ }
+ continue;
+ }
ClassSymbol sym = new ClassSymbol(name.substring(0, name.length() - ".class".length()));
if (!env.containsKey(sym)) {
- env.put(
- sym,
- new BytecodeBoundClass(sym, () -> toByteArrayOrDie(jf, je), benv, path.toString()));
+ env.put(sym, new BytecodeBoundClass(sym, toByteArrayOrDie(jf, je), benv, path.toString()));
tli.insert(sym);
}
}
}
- private static byte[] toByteArrayOrDie(JarFile jf, JarEntry je) {
- try {
- return ByteStreams.toByteArray(jf.getInputStream(je));
- } catch (IOException e) {
- throw new IOError(e);
- }
+ private static Supplier<byte[]> toByteArrayOrDie(JarFile jf, JarEntry je) {
+ return Suppliers.memoize(
+ new Supplier<byte[]>() {
+ @Override
+ public byte[] get() {
+ try {
+ return ByteStreams.toByteArray(jf.getInputStream(je));
+ } catch (IOException e) {
+ throw new IOError(e);
+ }
+ }
+ });
}
}
diff --git a/java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java b/java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java
index 6b53c00..b2ca745 100644
--- a/java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java
+++ b/java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java
@@ -528,4 +528,9 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou
public String jarFile() {
return jarFile;
}
+
+ /** The class file the symbol was loaded from. */
+ public ClassFile classFile() {
+ return classFile.get();
+ }
}
diff --git a/java/com/google/turbine/bytecode/ClassReader.java b/java/com/google/turbine/bytecode/ClassReader.java
index ff55a76..45da3a4 100644
--- a/java/com/google/turbine/bytecode/ClassReader.java
+++ b/java/com/google/turbine/bytecode/ClassReader.java
@@ -149,7 +149,7 @@ public class ClassReader {
List<ClassFile.AnnotationInfo> annotations = new ArrayList<>();
if ((accessFlags & TurbineFlag.ACC_ANNOTATION) == 0) {
reader.skip(reader.u4());
- return null;
+ return ImmutableList.of();
}
reader.u4(); // length
int numAnnotations = reader.u2();
diff --git a/java/com/google/turbine/deps/Transitive.java b/java/com/google/turbine/deps/Transitive.java
new file mode 100644
index 0000000..81747e4
--- /dev/null
+++ b/java/com/google/turbine/deps/Transitive.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2017 Google Inc. 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.turbine.deps;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.turbine.binder.Binder.BindingResult;
+import com.google.turbine.binder.bound.TypeBoundClass;
+import com.google.turbine.binder.bytecode.BytecodeBoundClass;
+import com.google.turbine.binder.env.CompoundEnv;
+import com.google.turbine.binder.env.Env;
+import com.google.turbine.binder.env.SimpleEnv;
+import com.google.turbine.binder.sym.ClassSymbol;
+import com.google.turbine.bytecode.ClassFile;
+import com.google.turbine.bytecode.ClassFile.FieldInfo;
+import com.google.turbine.bytecode.ClassFile.InnerClass;
+import com.google.turbine.bytecode.ClassWriter;
+import com.google.turbine.model.TurbineTyKind;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * Collects the minimal compile-time API for symbols in the supertype closure of compiled classes.
+ * This allows header compilations to be performed against a classpath containing only direct
+ * dependencies and no transitive dependencies.
+ */
+public class Transitive {
+
+ public static ImmutableMap<String, byte[]> collectDeps(
+ ImmutableSet<String> bootClassPath, BindingResult bound) {
+ ImmutableMap.Builder<String, byte[]> transitive = ImmutableMap.builder();
+ for (ClassSymbol sym : superClosure(bound)) {
+ BytecodeBoundClass info = bound.classPathEnv().get(sym);
+ if (info == null) {
+ // the symbol wasn't loaded from the classpath
+ continue;
+ }
+ String jarFile = info.jarFile();
+ if (bootClassPath.contains(jarFile)) {
+ // don't export symbols loaded from the bootclasspath
+ continue;
+ }
+ transitive.put(sym.binaryName(), ClassWriter.writeClass(trimClass(sym, info)));
+ }
+ return transitive.build();
+ }
+
+ /**
+ * Removes information from repackaged classes that will not be needed by upstream compilations.
+ */
+ private static ClassFile trimClass(ClassSymbol sym, BytecodeBoundClass info) {
+ ClassFile cf = info.classFile();
+ // drop non-constant fields
+ Builder<FieldInfo> fields = ImmutableList.builder();
+ for (FieldInfo f : cf.fields()) {
+ if (f.value() != null) {
+ fields.add(f);
+ }
+ }
+ // Remove InnerClass attributes that are unnecessary after pruning the types they refer to.
+ // To do this for javac, we would have to scan all remaining signatures and preserve attributes
+ // for reachable inner classes, but turbine only needs the attributes for the immediate
+ // children or parent of the current class.
+ Builder<InnerClass> innerClasses = ImmutableList.builder();
+ for (InnerClass i : cf.innerClasses()) {
+ if (i.innerClass().equals(sym.binaryName()) || i.outerClass().equals(sym.binaryName())) {
+ innerClasses.add(i);
+ }
+ }
+ return new ClassFile(
+ cf.access(),
+ cf.name(),
+ cf.signature(),
+ cf.superName(),
+ cf.interfaces(),
+ // drop methods, except for annotations where we need to resolve key/value information
+ info.kind() == TurbineTyKind.ANNOTATION ? cf.methods() : ImmutableList.of(),
+ fields.build(),
+ // unnecessary annotations are dropped during class reading, the only remaining ones are
+ // well-known @interface meta-annotations (e.g. @Retention, etc.)
+ cf.annotations(),
+ innerClasses.build(),
+ cf.typeAnnotations());
+ }
+
+ private static Set<ClassSymbol> superClosure(BindingResult bound) {
+ Env<ClassSymbol, TypeBoundClass> env =
+ CompoundEnv.<ClassSymbol, TypeBoundClass>of(new SimpleEnv<>(bound.units()))
+ .append(bound.classPathEnv());
+ Set<ClassSymbol> closure = new LinkedHashSet<>();
+ for (ClassSymbol sym : bound.units().keySet()) {
+ addSuperTypes(closure, env, sym);
+ }
+ return closure;
+ }
+
+ private static void addSuperTypes(
+ Set<ClassSymbol> closure, Env<ClassSymbol, TypeBoundClass> env, ClassSymbol sym) {
+ if (!closure.add(sym)) {
+ return;
+ }
+ TypeBoundClass info = env.get(sym);
+ if (info == null) {
+ return;
+ }
+ closure.addAll(info.children().values());
+ if (info.superclass() != null) {
+ addSuperTypes(closure, env, info.superclass());
+ }
+ for (ClassSymbol i : info.interfaces()) {
+ addSuperTypes(closure, env, i);
+ }
+ }
+}
diff --git a/java/com/google/turbine/main/Main.java b/java/com/google/turbine/main/Main.java
index c82b38e..563bc73 100644
--- a/java/com/google/turbine/main/Main.java
+++ b/java/com/google/turbine/main/Main.java
@@ -25,7 +25,9 @@ import com.google.common.hash.Hashing;
import com.google.common.io.ByteStreams;
import com.google.turbine.binder.Binder;
import com.google.turbine.binder.Binder.BindingResult;
+import com.google.turbine.binder.ClassPathBinder;
import com.google.turbine.deps.Dependencies;
+import com.google.turbine.deps.Transitive;
import com.google.turbine.diag.SourceFile;
import com.google.turbine.lower.Lower;
import com.google.turbine.lower.Lower.Lowered;
@@ -83,6 +85,8 @@ public class Main {
// TODO(cushon): parallelize
Lowered lowered = Lower.lowerAll(bound.units(), bound.classPathEnv());
+ Map<String, byte[]> transitive = Transitive.collectDeps(options.bootClassPath(), bound);
+
if (options.outputDeps().isPresent()) {
DepsProto.Dependencies deps =
Dependencies.collectDeps(options.targetLabel(), options.bootClassPath(), bound, lowered);
@@ -92,7 +96,7 @@ public class Main {
}
}
- writeOutput(Paths.get(options.outputFile()), lowered.bytes());
+ writeOutput(Paths.get(options.outputFile()), lowered.bytes(), transitive);
return true;
}
@@ -121,23 +125,31 @@ public class Main {
}
/** Write bytecode to the output jar. */
- private static void writeOutput(Path path, Map<String, byte[]> lowered) throws IOException {
+ private static void writeOutput(
+ Path path, Map<String, byte[]> lowered, Map<String, byte[]> transitive) throws IOException {
try (OutputStream os = Files.newOutputStream(path);
BufferedOutputStream bos = new BufferedOutputStream(os, BUFFER_SIZE);
JarOutputStream jos = new JarOutputStream(bos)) {
for (Map.Entry<String, byte[]> entry : lowered.entrySet()) {
- JarEntry je = new JarEntry(entry.getKey() + ".class");
- je.setTime(0L); // normalize timestamps to the DOS epoch
- je.setMethod(ZipEntry.STORED);
- byte[] bytes = entry.getValue();
- je.setSize(bytes.length);
- je.setCrc(Hashing.crc32().hashBytes(bytes).padToLong());
- jos.putNextEntry(je);
- jos.write(bytes);
+ addEntry(jos, entry.getKey() + ".class", entry.getValue());
+ }
+ for (Map.Entry<String, byte[]> entry : transitive.entrySet()) {
+ addEntry(
+ jos, ClassPathBinder.TRANSITIVE_PREFIX + entry.getKey() + ".class", entry.getValue());
}
}
}
+ private static void addEntry(JarOutputStream jos, String name, byte[] bytes) throws IOException {
+ JarEntry je = new JarEntry(name);
+ je.setTime(0L); // normalize timestamps to the DOS epoch
+ je.setMethod(ZipEntry.STORED);
+ je.setSize(bytes.length);
+ je.setCrc(Hashing.crc32().hashBytes(bytes).padToLong());
+ jos.putNextEntry(je);
+ jos.write(bytes);
+ }
+
private static final Function<String, Path> TO_PATH =
new Function<String, Path>() {
@Override
diff --git a/java/com/google/turbine/options/TurbineOptions.java b/java/com/google/turbine/options/TurbineOptions.java
index 866318f..9814094 100644
--- a/java/com/google/turbine/options/TurbineOptions.java
+++ b/java/com/google/turbine/options/TurbineOptions.java
@@ -34,7 +34,7 @@ public class TurbineOptions {
private final ImmutableList<String> processorPath;
private final ImmutableSet<String> processors;
private final ImmutableSet<String> blacklistedProcessors;
- private final String tempDir;
+ private final @Nullable String tempDir;
private final ImmutableList<String> sourceJars;
private final Optional<String> outputDeps;
private final ImmutableMap<String, String> directJarsToTargets;
@@ -69,7 +69,7 @@ public class TurbineOptions {
this.processors = checkNotNull(processors, "processors must not be null");
this.blacklistedProcessors =
checkNotNull(blacklistedProcessors, "blacklistedProcessors must not be null");
- this.tempDir = checkNotNull(tempDir, "tempDir must not be null");
+ this.tempDir = tempDir;
this.sourceJars = checkNotNull(sourceJars, "sourceJars must not be null");
this.outputDeps = Optional.fromNullable(outputDeps);
this.directJarsToTargets =
@@ -103,6 +103,7 @@ public class TurbineOptions {
}
/** A temporary directory, e.g. for extracting sourcejar entries to before compilation. */
+ @Nullable
public String tempDir() {
return tempDir;
}
diff --git a/javatests/com/google/turbine/bytecode/JavapUtils.java b/javatests/com/google/turbine/bytecode/JavapUtils.java
index ccaed47..8c711c3 100644
--- a/javatests/com/google/turbine/bytecode/JavapUtils.java
+++ b/javatests/com/google/turbine/bytecode/JavapUtils.java
@@ -32,7 +32,6 @@ import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
-import java.util.List;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaFileObject;
import javax.tools.StandardLocation;
@@ -42,7 +41,8 @@ public class JavapUtils {
static final ImmutableList<Path> BOOTCLASSPATH =
ImmutableList.of(Paths.get(System.getProperty("java.home")).resolve("lib/rt.jar"));
- public static String dump(String className, byte[] bytes) throws IOException {
+ public static String dump(String className, byte[] bytes, ImmutableList<String> options)
+ throws IOException {
FileSystem fs = Jimfs.newFileSystem(Configuration.unix());
Path root = fs.getPath("classes");
Path path = root.resolve(className + ".class");
@@ -54,7 +54,6 @@ public class JavapUtils {
fileManager.setLocationFromPaths(StandardLocation.CLASS_PATH, ImmutableList.of(root));
fileManager.setLocationFromPaths(StandardLocation.PLATFORM_CLASS_PATH, BOOTCLASSPATH);
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
- List<String> options = ImmutableList.of("-s", "-private");
Writer writer = new StringWriter();
JavapTask task =
new JavapTask(writer, fileManager, diagnostics, options, ImmutableList.of(className));
diff --git a/javatests/com/google/turbine/deps/TransitiveTest.java b/javatests/com/google/turbine/deps/TransitiveTest.java
new file mode 100644
index 0000000..c6a6eea
--- /dev/null
+++ b/javatests/com/google/turbine/deps/TransitiveTest.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2016 Google Inc. 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.turbine.deps;
+
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.stream.Collectors.toList;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.io.ByteStreams;
+import com.google.turbine.bytecode.ClassFile;
+import com.google.turbine.bytecode.ClassFile.InnerClass;
+import com.google.turbine.bytecode.ClassReader;
+import com.google.turbine.main.Main;
+import com.google.turbine.options.TurbineOptions;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class TransitiveTest {
+
+ static final ImmutableList<Path> BOOTCLASSPATH =
+ ImmutableList.of(Paths.get(System.getProperty("java.home")).resolve("lib/rt.jar"));
+
+ @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+ class SourceBuilder {
+ private final Path lib;
+ private final ImmutableList.Builder<Path> sources = ImmutableList.builder();
+
+ SourceBuilder() throws IOException {
+ lib = temporaryFolder.newFolder().toPath();
+ }
+
+ SourceBuilder addSourceLines(String name, String... lines) throws IOException {
+ Path path = lib.resolve(name);
+ Files.createDirectories(path.getParent());
+ Files.write(path, Arrays.asList(lines), UTF_8);
+ sources.add(path);
+ return this;
+ }
+
+ ImmutableList<Path> build() {
+ return sources.build();
+ }
+ }
+
+ Path runTurbine(List<Path> sources, List<Path> classpath) throws IOException {
+ Path out = temporaryFolder.newFolder().toPath().resolve("out.jar");
+ boolean ok =
+ Main.compile(
+ TurbineOptions.builder()
+ .addSources(sources.stream().map(Path::toString).collect(toList()))
+ .addClassPathEntries(classpath.stream().map(Path::toString).collect(toList()))
+ .addBootClassPathEntries(Iterables.transform(BOOTCLASSPATH, Path::toString))
+ .setOutput(out.toString())
+ .build());
+ assertThat(ok).isTrue();
+ return out;
+ }
+
+ private Map<String, byte[]> readJar(Path libb) throws IOException {
+ Map<String, byte[]> jarEntries = new LinkedHashMap<>();
+ try (JarFile jf = new JarFile(libb.toFile())) {
+ Enumeration<JarEntry> entries = jf.entries();
+ while (entries.hasMoreElements()) {
+ JarEntry je = entries.nextElement();
+ jarEntries.put(je.getName(), ByteStreams.toByteArray(jf.getInputStream(je)));
+ }
+ }
+ return jarEntries;
+ }
+
+ @Test
+ public void transitive() throws Exception {
+ Path liba =
+ runTurbine(
+ new SourceBuilder()
+ .addSourceLines(
+ "a/A.java",
+ "package a;",
+ "import java.util.Map;",
+ "public class A {",
+ " public @interface Anno {",
+ " int x() default 42;",
+ " }",
+ " public static class Inner {}",
+ " public static final int CONST = 42;",
+ " public int mutable = 42;",
+ " public Map.Entry<String, String> f(Map<String, String> m) {",
+ " return m.entrySet().iterator().next();",
+ " }",
+ "}")
+ .build(),
+ ImmutableList.of());
+
+ Path libb =
+ runTurbine(
+ new SourceBuilder()
+ .addSourceLines("b/B.java", "package b;", "public class B extends a.A {}")
+ .build(),
+ ImmutableList.of(liba));
+
+ // libb repackages A, and any member types
+ assertThat(readJar(libb).keySet())
+ .containsExactly(
+ "b/B.class",
+ "META-INF/TRANSITIVE/a/A.class",
+ "META-INF/TRANSITIVE/a/A$Anno.class",
+ "META-INF/TRANSITIVE/a/A$Inner.class");
+
+ ClassFile a = ClassReader.read(readJar(libb).get("META-INF/TRANSITIVE/a/A.class"));
+ // methods and non-constant fields are removed
+ assertThat(getOnlyElement(a.fields()).name()).isEqualTo("CONST");
+ assertThat(a.methods()).isEmpty();
+ assertThat(Iterables.transform(a.innerClasses(), InnerClass::innerClass))
+ .containsExactly("a/A$Anno", "a/A$Inner");
+
+ // annotation interface methods are preserved
+ assertThat(ClassReader.read(readJar(libb).get("META-INF/TRANSITIVE/a/A$Anno.class")).methods())
+ .hasSize(1);
+
+ // a class that references members of the transitive supertype A by simple name
+ // compiles cleanly against the repackaged version of A
+ Path libc =
+ runTurbine(
+ new SourceBuilder()
+ .addSourceLines(
+ "c/C.java",
+ "package c;",
+ "public class C extends b.B {",
+ " @Anno(x = 2) static final Inner i; // a.A$Inner ",
+ " static final int X = CONST; // a.A#CONST",
+ "}")
+ .build(),
+ ImmutableList.of(libb));
+
+ assertThat(readJar(libc).keySet())
+ .containsExactly(
+ "c/C.class",
+ "META-INF/TRANSITIVE/b/B.class",
+ "META-INF/TRANSITIVE/a/A.class",
+ "META-INF/TRANSITIVE/a/A$Anno.class",
+ "META-INF/TRANSITIVE/a/A$Inner.class");
+ }
+}
diff --git a/javatests/com/google/turbine/lower/LowerTest.java b/javatests/com/google/turbine/lower/LowerTest.java
index 735f69e..ff0391a 100644
--- a/javatests/com/google/turbine/lower/LowerTest.java
+++ b/javatests/com/google/turbine/lower/LowerTest.java
@@ -278,7 +278,7 @@ public class LowerTest {
Map<String, byte[]> actual =
IntegrationTestSupport.runTurbine(input.sources, ImmutableList.of(), BOOTCLASSPATH);
- assertThat(JavapUtils.dump("Test", actual.get("Test")))
+ assertThat(JavapUtils.dump("Test", actual.get("Test"), ImmutableList.of("-s", "-private")))
.isEqualTo(
Joiner.on('\n')
.join(