From b06bb6714f49aeccc11141265c630c8a00e4b210 Mon Sep 17 00:00:00 2001 From: Kr328 Date: Fri, 3 Feb 2023 16:27:40 +0800 Subject: [PATCH] Migrate: migrate to the new Instrumentation API (#17) * Migrate: migrate to new agp implement * Chore: add comment marks * Chore: mark final * Improve: add inner class support & fix class name resolving * Improve: let annotation processor compatible with legacy version * Chore: update comments --------- Co-authored-by: RikkaW --- .gitattributes | 3 + annotation-processor/build.gradle.kts | 5 +- .../rikka/tools/refine/RefineProcessor.java | 217 ++++++++-------- annotation/build.gradle.kts | 3 - .../java/dev/rikka/tools/refine/RefineAs.java | 6 + build.gradle.kts | 22 +- gradle-plugin/build.gradle.kts | 6 +- .../dev/rikka/tools/refine/RefineApplier.java | 32 --- .../dev/rikka/tools/refine/RefineCache.java | 15 -- .../rikka/tools/refine/RefineCollector.java | 39 --- .../dev/rikka/tools/refine/RefineFactory.java | 26 ++ .../dev/rikka/tools/refine/RefinePlugin.java | 27 +- .../rikka/tools/refine/RefineRemapper.java | 32 +++ .../rikka/tools/refine/RefineTransform.java | 231 ------------------ .../rikka/tools/refine/utils/FileUtils.java | 17 -- .../rikka/tools/refine/utils/JarUtils.java | 26 -- gradle/wrapper/gradle-wrapper.properties | 2 +- runtime/build.gradle.kts | 3 - .../java/dev/rikka/tools/refine/Refine.java | 23 +- settings.gradle.kts | 16 +- 20 files changed, 236 insertions(+), 515 deletions(-) create mode 100644 .gitattributes delete mode 100644 gradle-plugin/src/main/java/dev/rikka/tools/refine/RefineApplier.java delete mode 100644 gradle-plugin/src/main/java/dev/rikka/tools/refine/RefineCache.java delete mode 100644 gradle-plugin/src/main/java/dev/rikka/tools/refine/RefineCollector.java create mode 100644 gradle-plugin/src/main/java/dev/rikka/tools/refine/RefineFactory.java create mode 100644 gradle-plugin/src/main/java/dev/rikka/tools/refine/RefineRemapper.java delete mode 100644 gradle-plugin/src/main/java/dev/rikka/tools/refine/RefineTransform.java delete mode 100644 gradle-plugin/src/main/java/dev/rikka/tools/refine/utils/FileUtils.java delete mode 100644 gradle-plugin/src/main/java/dev/rikka/tools/refine/utils/JarUtils.java diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..9f95d99 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +* text=auto eol=lf +*.bat text eol=crlf +*.jar binary diff --git a/annotation-processor/build.gradle.kts b/annotation-processor/build.gradle.kts index 306e002..e3fdc1a 100644 --- a/annotation-processor/build.gradle.kts +++ b/annotation-processor/build.gradle.kts @@ -10,16 +10,13 @@ dependencies { annotationProcessor(libs.google.service.compiler) implementation(libs.google.service.annotation) - implementation(libs.javassist) + implementation(libs.asm.all) } publishing { publications { create(project.name, MavenPublication::class) { from(components["java"]) - - artifact(tasks["sourcesJar"]) - artifact(tasks["javadocJar"]) } } } \ No newline at end of file diff --git a/annotation-processor/src/main/java/dev/rikka/tools/refine/RefineProcessor.java b/annotation-processor/src/main/java/dev/rikka/tools/refine/RefineProcessor.java index 6883f2d..0a32a84 100644 --- a/annotation-processor/src/main/java/dev/rikka/tools/refine/RefineProcessor.java +++ b/annotation-processor/src/main/java/dev/rikka/tools/refine/RefineProcessor.java @@ -1,157 +1,164 @@ package dev.rikka.tools.refine; import com.google.auto.service.AutoService; -import javassist.bytecode.AnnotationsAttribute; -import javassist.bytecode.ClassFile; -import javassist.bytecode.annotation.Annotation; -import javassist.bytecode.annotation.ClassMemberValue; +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Processor; import javax.annotation.processing.RoundEnvironment; import javax.lang.model.SourceVersion; -import javax.lang.model.element.Element; -import javax.lang.model.element.PackageElement; -import javax.lang.model.element.TypeElement; +import javax.lang.model.element.*; import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.MirroredTypeException; import javax.tools.FileObject; -import javax.tools.StandardLocation; -import java.io.DataOutputStream; import java.io.IOException; +import java.io.OutputStream; import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; +/** + * Annotation processor that generates classes which store metadata. + */ @AutoService(Processor.class) public class RefineProcessor extends AbstractProcessor { - public @interface Descriptor {} + /** + * Class name suffix. + */ + public static final String REFINE_METADATA_CLASS_NAME = String.valueOf( + 'R' << 16 | 'E' << 16 | 'F' << 8 | 'I' << 8 | 'N' | 'E' + ); + + /** + * Annotation prefix that store refine to. + */ + public static final String REFINE_NS_PACKAGE = "refine"; - public static final String DESCRIPTOR_REFINE_DESCRIPTOR = Descriptor.class.getName(); - public static final String DESCRIPTOR_REFINE_FROM = "from"; - public static final String DESCRIPTOR_REFINE_TO = "to"; + @Override + public Set getSupportedAnnotationTypes() { + return Set.of(RefineAs.class.getName()); + } - private static final String REFINE_DESCRIPTION_SUFFIX = String.valueOf('R' << 16 | 'E' << 16 | 'F' << 8 | 'I' << 8 | 'N' | 'E'); + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.RELEASE_8; + } - private static String resolveInternalName(Element element) { + private static String resolveClassName(final Element element) { final Element enclosing = element.getEnclosingElement(); if (enclosing instanceof TypeElement) { - return resolveInternalName(enclosing) + "$" + element.getSimpleName(); + return resolveClassName(enclosing) + "$" + element.getSimpleName(); } else if (enclosing instanceof PackageElement) { - return ((PackageElement) enclosing).getQualifiedName().toString() - .replace('.', '/') + "/" + element.getSimpleName(); + return ((PackageElement) enclosing).getQualifiedName()+ "." + element.getSimpleName(); } return element.getSimpleName().toString(); } - private static String resolveClassNameFromRefine(RefineAs refine) { - try { - return refine.value().getName(); - } catch (MirroredTypeException e) { - return resolveInternalName(((DeclaredType) e.getTypeMirror()).asElement()); + private void writeRefineMetadata(final String from, final String to, final Element... dependencies) throws IOException { + final String metadataName = from + "$" + REFINE_METADATA_CLASS_NAME; + final String refineToAnnotation = REFINE_NS_PACKAGE + "." + to; + + final ClassWriter metadataWriter = new ClassWriter(0); + metadataWriter.visit( + Opcodes.V1_8, + Opcodes.ACC_FINAL | Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER, + metadataName.replace('.', '/'), + null, + Type.getInternalName(Object.class), + null + ); + + final AnnotationVisitor descriptor = metadataWriter.visitAnnotation(Type.getDescriptor(Descriptor.class), false); + descriptor.visit("from", Type.getType("L" + from.replace('.', '/') + ';')); + descriptor.visit("to", Type.getType("L" + to.replace('.', '/') + ";")); + descriptor.visitEnd(); + + metadataWriter.visitAnnotation("L" + refineToAnnotation.replace('.', '/') + ";", false).visitEnd(); + metadataWriter.visitEnd(); + + final FileObject metadataFile = processingEnv.getFiler().createClassFile(metadataName, dependencies); + try (final OutputStream stream = metadataFile.openOutputStream()) { + stream.write(metadataWriter.toByteArray()); } - } - @Override - public Set getSupportedAnnotationTypes() { - return Set.of(RefineAs.class.getName()); + if (processingEnv.getElementUtils().getTypeElement(to.replace('$', '.')) == null) { + final ClassWriter stubWriter = new ClassWriter(0); + stubWriter.visit( + Opcodes.V1_8, + Opcodes.ACC_FINAL | Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER, + to.replace('.', '/'), + null, + Type.getInternalName(Object.class), + null + ); + stubWriter.visitEnd(); + + final FileObject stubFile = processingEnv.getFiler().createClassFile(to, dependencies); + try (final OutputStream stream = stubFile.openOutputStream()) { + stream.write(stubWriter.toByteArray()); + } + } } - @Override - public SourceVersion getSupportedSourceVersion() { - return SourceVersion.RELEASE_8; + private void processElement(final TypeElement element, final String toClass) throws IOException { + final String fromClass = resolveClassName(element); + + writeRefineMetadata(fromClass, toClass, element); + + for (final Element enclosedElement : element.getEnclosedElements()) { + if (!(enclosedElement instanceof TypeElement)) { + continue; + } + + if (enclosedElement.getAnnotation(RefineAs.class) != null) { + continue; + } + + processElement((TypeElement) enclosedElement, toClass + "$" + enclosedElement.getSimpleName()); + } } @Override - public boolean process(Set annotations, RoundEnvironment roundEnv) { + public boolean process(final Set annotations, final RoundEnvironment roundEnv) { try { - for (final Element element : roundEnv.getElementsAnnotatedWith(RefineAs.class)) { + final TypeElement eRefineAs = processingEnv.getElementUtils().getTypeElement(RefineAs.class.getName()); + final ExecutableElement eRefineAsValue = (ExecutableElement) eRefineAs.getEnclosedElements().stream() + .filter(e -> (e instanceof ExecutableElement) && e.getSimpleName().contentEquals("value")) + .findAny() + .orElseThrow(() -> new IllegalStateException("Invalid @RefineAs annotation.")); + + for (final Element element : roundEnv.getElementsAnnotatedWith(eRefineAs)) { if (!(element instanceof TypeElement)) { continue; } - final RefineAs refine = element.getAnnotation(RefineAs.class); - if (refine == null) { + final AnnotationMirror refineAs = element.getAnnotationMirrors().stream() + .filter(a -> eRefineAs.equals(a.getAnnotationType().asElement())) + .findAny() + .orElse(null); + if (refineAs == null) { continue; } - final String original = resolveInternalName(element); - final String replaced = resolveClassNameFromRefine(refine); + final AnnotationValue refineAsValue = refineAs.getElementValues().get(eRefineAsValue); + if (refineAsValue == null) { + continue; + } - processType((TypeElement) element, element, original, replaced); + processElement((TypeElement) element, resolveClassName(((DeclaredType) refineAsValue.getValue()).asElement())); } - } catch (IOException e) { + } catch (final Exception e) { throw new RuntimeException(e); } return true; } - private void processType(TypeElement type, Element root, String prefix, String replace) throws IOException { - final String sourceInternalName = resolveInternalName(type); - - if (!sourceInternalName.startsWith(prefix)) { - return; - } - - final String[] sourceFragments = sourceInternalName.split("/"); - final String sourcePackageName = Stream.of(sourceFragments) - .limit(sourceFragments.length - 1) - .collect(Collectors.joining(".")); - final String sourceClassName = sourceFragments[sourceFragments.length - 1]; - - final String descriptionName = sourceClassName + "$" + REFINE_DESCRIPTION_SUFFIX; - final String descriptionInternalName = sourcePackageName.replace('.', '/') + "/" + descriptionName; - - final FileObject refineFile = processingEnv.getFiler() - .createResource(StandardLocation.CLASS_OUTPUT, sourcePackageName, descriptionName + ".class", type, root); - - final String targetInternalName = replace + sourceInternalName.substring(prefix.length()); - - try (DataOutputStream stream = new DataOutputStream(refineFile.openOutputStream())) { - final ClassFile cls = new ClassFile(true, descriptionInternalName, null); - final AnnotationsAttribute annotations = new AnnotationsAttribute(cls.getConstPool(), AnnotationsAttribute.invisibleTag); - final Annotation annotation = new Annotation(DESCRIPTOR_REFINE_DESCRIPTOR, cls.getConstPool()); - - annotation.addMemberValue(DESCRIPTOR_REFINE_FROM, new ClassMemberValue(sourceInternalName, cls.getConstPool())); - annotation.addMemberValue(DESCRIPTOR_REFINE_TO, new ClassMemberValue(targetInternalName, cls.getConstPool())); - - annotations.addAnnotation(annotation); - - cls.addAttribute(annotations); - - cls.write(stream); - } - - final String[] targetFragments = targetInternalName.split("/"); - final String targetPackageName = Stream.of(targetFragments) - .limit(sourceFragments.length - 1) - .collect(Collectors.joining(".")); - final String targetClassName = targetFragments[targetFragments.length - 1]; - - final TypeElement target = processingEnv.getElementUtils() - .getTypeElement(targetPackageName + "." + targetClassName.replace('$', '.')); - if (target == null) { - final FileObject classFile = processingEnv.getFiler() - .createResource(StandardLocation.CLASS_OUTPUT, targetPackageName, targetClassName + ".class", type, root); - - try (DataOutputStream stream = new DataOutputStream(classFile.openOutputStream())) { - new ClassFile(type.getKind().isInterface(), targetInternalName, null).write(stream); - } - } - - for (final Element enclosed : type.getEnclosedElements()) { - if (!(enclosed instanceof TypeElement)) { - continue; - } - - if (enclosed.getAnnotation(RefineAs.class) != null || enclosed.getAnnotation(Descriptor.class) != null) { - continue; - } - - processType((TypeElement) enclosed, root, prefix, replace); - } + /** + * Mark class is a metadata class. + */ + public @interface Descriptor { } } diff --git a/annotation/build.gradle.kts b/annotation/build.gradle.kts index 7647350..b905ea7 100644 --- a/annotation/build.gradle.kts +++ b/annotation/build.gradle.kts @@ -8,9 +8,6 @@ publishing { publications { create(project.name, MavenPublication::class) { from(components["java"]) - - artifact(tasks["sourcesJar"]) - artifact(tasks["javadocJar"]) } } } diff --git a/annotation/src/main/java/dev/rikka/tools/refine/RefineAs.java b/annotation/src/main/java/dev/rikka/tools/refine/RefineAs.java index 8febc67..4905e53 100644 --- a/annotation/src/main/java/dev/rikka/tools/refine/RefineAs.java +++ b/annotation/src/main/java/dev/rikka/tools/refine/RefineAs.java @@ -5,8 +5,14 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * Attach this annotation to a class to indicate that this class should be renamed to {@link #value()}. + */ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) public @interface RefineAs { + /** + * Class to rename to. + */ Class value(); } diff --git a/build.gradle.kts b/build.gradle.kts index dba25d5..dfc4c64 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,30 +4,18 @@ task("clean", type = Delete::class) { subprojects { group = "dev.rikka.tools.refine" - version = "3.1.1" + version = "4.0.0" plugins.withId("java") { - println("- Configuring `java`") - extensions.configure { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 - } - tasks.register("sourcesJar", type = Jar::class) { - archiveClassifier.set("sources") - from(project.extensions.getByType().getByName("main").allSource) - } - tasks.register("javadocJar", type = Jar::class) { - archiveClassifier.set("javadoc") - from(tasks["javadoc"]) - } - tasks.withType(Javadoc::class) { - isFailOnError = false + + withSourcesJar() + withJavadocJar() } } plugins.withId("maven-publish") { - println("- Configuring `publishing`") - extensions.configure { publications { withType(MavenPublication::class) { @@ -66,8 +54,6 @@ subprojects { } } plugins.withId("signing") { - println("- Configuring `signing`") - extensions.configure { if (findProperty("signing.gnupg.keyName") != null) { useGpgCmd() diff --git a/gradle-plugin/build.gradle.kts b/gradle-plugin/build.gradle.kts index 776fba9..5cc1a06 100644 --- a/gradle-plugin/build.gradle.kts +++ b/gradle-plugin/build.gradle.kts @@ -12,8 +12,7 @@ dependencies { implementation(project(":annotation-processor")) compileOnly(libs.android.gradle) - implementation(libs.google.gson) - implementation(libs.javassist) + implementation(libs.asm.all) } gradlePlugin { @@ -32,9 +31,6 @@ afterEvaluate { publications { named("pluginMaven", MavenPublication::class) { artifactId = project.name - - artifact(tasks["sourcesJar"]) - artifact(tasks["javadocJar"]) } } } diff --git a/gradle-plugin/src/main/java/dev/rikka/tools/refine/RefineApplier.java b/gradle-plugin/src/main/java/dev/rikka/tools/refine/RefineApplier.java deleted file mode 100644 index d83a76e..0000000 --- a/gradle-plugin/src/main/java/dev/rikka/tools/refine/RefineApplier.java +++ /dev/null @@ -1,32 +0,0 @@ -package dev.rikka.tools.refine; - -import javassist.bytecode.ClassFile; - -import java.io.*; -import java.util.Map; - -public final class RefineApplier { - private final Map refines; - - public RefineApplier(Map refines) { - this.refines = refines; - } - - public void applyFor(InputStream in, OutputStream out) throws IOException { - final DataInputStream input = new DataInputStream(new BufferedInputStream(in)); - final DataOutputStream output = new DataOutputStream(new BufferedOutputStream(out)); - final ClassFile file = new ClassFile(input); - final String self = refines.remove(file.getName()); - - try { - file.renameClass(refines); - } finally { - if (self != null) { - refines.put(file.getName(), self); - } - } - - file.write(output); - output.flush(); - } -} diff --git a/gradle-plugin/src/main/java/dev/rikka/tools/refine/RefineCache.java b/gradle-plugin/src/main/java/dev/rikka/tools/refine/RefineCache.java deleted file mode 100644 index b3ae411..0000000 --- a/gradle-plugin/src/main/java/dev/rikka/tools/refine/RefineCache.java +++ /dev/null @@ -1,15 +0,0 @@ -package dev.rikka.tools.refine; - -import java.util.Map; - -public class RefineCache { - private final Map refines; - - public RefineCache(Map refines) { - this.refines = refines; - } - - public Map getRefines() { - return refines; - } -} diff --git a/gradle-plugin/src/main/java/dev/rikka/tools/refine/RefineCollector.java b/gradle-plugin/src/main/java/dev/rikka/tools/refine/RefineCollector.java deleted file mode 100644 index a18beaa..0000000 --- a/gradle-plugin/src/main/java/dev/rikka/tools/refine/RefineCollector.java +++ /dev/null @@ -1,39 +0,0 @@ -package dev.rikka.tools.refine; - -import javassist.bytecode.AnnotationsAttribute; -import javassist.bytecode.AttributeInfo; -import javassist.bytecode.ClassFile; -import javassist.bytecode.annotation.Annotation; -import javassist.bytecode.annotation.ClassMemberValue; -import javassist.bytecode.annotation.MemberValue; - -import java.io.BufferedInputStream; -import java.io.DataInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.AbstractMap; -import java.util.Map; - -public final class RefineCollector { - public static Map.Entry collect(InputStream stream) throws IOException { - final DataInputStream in = new DataInputStream(new BufferedInputStream(stream)); - final ClassFile file = new ClassFile(in); - - for (AttributeInfo info : file.getAttributes()) { - if (info instanceof AnnotationsAttribute) { - final Annotation annotation = ((AnnotationsAttribute) info).getAnnotation(RefineProcessor.DESCRIPTOR_REFINE_DESCRIPTOR); - if (annotation == null) - continue; - - final MemberValue from = annotation.getMemberValue(RefineProcessor.DESCRIPTOR_REFINE_FROM); - final MemberValue to = annotation.getMemberValue(RefineProcessor.DESCRIPTOR_REFINE_TO); - return new AbstractMap.SimpleEntry<>( - ((ClassMemberValue) from).getValue().replace('.', '/'), - ((ClassMemberValue) to).getValue().replace('.', '/') - ); - } - } - - return null; - } -} diff --git a/gradle-plugin/src/main/java/dev/rikka/tools/refine/RefineFactory.java b/gradle-plugin/src/main/java/dev/rikka/tools/refine/RefineFactory.java new file mode 100644 index 0000000..ed31ebc --- /dev/null +++ b/gradle-plugin/src/main/java/dev/rikka/tools/refine/RefineFactory.java @@ -0,0 +1,26 @@ +package dev.rikka.tools.refine; + +import com.android.build.api.instrumentation.AsmClassVisitorFactory; +import com.android.build.api.instrumentation.ClassContext; +import com.android.build.api.instrumentation.ClassData; +import com.android.build.api.instrumentation.InstrumentationParameters; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.commons.ClassRemapper; + +import javax.annotation.Nonnull; + +/** + * Factory to create classes refine visitor. + */ +public abstract class RefineFactory implements AsmClassVisitorFactory { + @Override + @Nonnull + public ClassVisitor createClassVisitor(final @Nonnull ClassContext classContext, final @Nonnull ClassVisitor classVisitor) { + return new ClassRemapper(classVisitor, new RefineRemapper(classContext)); + } + + @Override + public boolean isInstrumentable(final @Nonnull ClassData classData) { + return true; + } +} diff --git a/gradle-plugin/src/main/java/dev/rikka/tools/refine/RefinePlugin.java b/gradle-plugin/src/main/java/dev/rikka/tools/refine/RefinePlugin.java index 38282c7..41e7a77 100644 --- a/gradle-plugin/src/main/java/dev/rikka/tools/refine/RefinePlugin.java +++ b/gradle-plugin/src/main/java/dev/rikka/tools/refine/RefinePlugin.java @@ -1,17 +1,32 @@ package dev.rikka.tools.refine; -import com.android.build.gradle.BaseExtension; -import com.android.build.gradle.LibraryExtension; +import com.android.build.api.instrumentation.InstrumentationScope; +import com.android.build.api.variant.AndroidComponentsExtension; +import kotlin.Unit; +import org.gradle.api.GradleException; import org.gradle.api.Plugin; import org.gradle.api.Project; +import javax.annotation.Nonnull; + +/** + * Gradle plugin that do the class rename works. + */ @SuppressWarnings("unused") public class RefinePlugin implements Plugin { @Override - public void apply(Project target) { - target.getPlugins().withId("com.android.base", plugin -> { - final BaseExtension base = target.getExtensions().getByType(BaseExtension.class); - base.registerTransform(new RefineTransform(base instanceof LibraryExtension)); + public void apply(@Nonnull final Project target) { + if (!target.getPlugins().hasPlugin("com.android.base")) { + throw new GradleException("This plugin must be applied after `com.android.application` or `com.android.library`."); + } + + final AndroidComponentsExtension components = target.getExtensions().getByType(AndroidComponentsExtension.class); + components.onVariants(components.selector().all(), variant -> { + variant.getInstrumentation().transformClassesWith( + RefineFactory.class, + InstrumentationScope.ALL, + (parameters) -> Unit.INSTANCE + ); }); } } diff --git a/gradle-plugin/src/main/java/dev/rikka/tools/refine/RefineRemapper.java b/gradle-plugin/src/main/java/dev/rikka/tools/refine/RefineRemapper.java new file mode 100644 index 0000000..47f2a71 --- /dev/null +++ b/gradle-plugin/src/main/java/dev/rikka/tools/refine/RefineRemapper.java @@ -0,0 +1,32 @@ +package dev.rikka.tools.refine; + +import com.android.build.api.instrumentation.ClassContext; +import com.android.build.api.instrumentation.ClassData; +import org.objectweb.asm.commons.Remapper; + +class RefineRemapper extends Remapper { + private final ClassContext context; + + public RefineRemapper(final ClassContext context) { + this.context = context; + } + + @Override + public String map(final String typeName) { + final ClassData data = context.loadClassData(typeName.replace('/', '.') + "$" + RefineProcessor.REFINE_METADATA_CLASS_NAME); + if (data == null) { + return typeName; + } + + if (data.getClassAnnotations().contains(RefineProcessor.Descriptor.class.getName())) { + final String to = data.getClassAnnotations().stream() + .filter(a -> a.startsWith(RefineProcessor.REFINE_NS_PACKAGE)) + .findFirst() + .orElseThrow(() -> new UnsupportedOperationException("Use deprecated refine class " + data.getClassName())); + + return to.substring(RefineProcessor.REFINE_NS_PACKAGE.length() + 1).replace('.', '/'); + } + + return typeName; + } +} diff --git a/gradle-plugin/src/main/java/dev/rikka/tools/refine/RefineTransform.java b/gradle-plugin/src/main/java/dev/rikka/tools/refine/RefineTransform.java deleted file mode 100644 index cd2feea..0000000 --- a/gradle-plugin/src/main/java/dev/rikka/tools/refine/RefineTransform.java +++ /dev/null @@ -1,231 +0,0 @@ -package dev.rikka.tools.refine; - -import com.android.build.api.transform.*; -import com.android.build.api.transform.QualifiedContent.Scope; -import com.google.gson.Gson; -import com.google.gson.stream.JsonWriter; -import dev.rikka.tools.refine.utils.FileUtils; -import dev.rikka.tools.refine.utils.JarUtils; -import org.gradle.api.logging.Logger; -import org.gradle.api.logging.Logging; - -import java.io.*; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.*; -import java.util.jar.JarEntry; -import java.util.jar.JarOutputStream; - -@SuppressWarnings({"deprecation", "ResultOfMethodCallIgnored"}) -public class RefineTransform extends Transform { - - private final Set scopes; - - public RefineTransform(boolean isLibrary) { - scopes = isLibrary ? Set.of(Scope.PROJECT) : - Set.of(Scope.PROJECT, Scope.SUB_PROJECTS, Scope.EXTERNAL_LIBRARIES); - } - - @Override - public String getName() { - return "HiddenApiRefine"; - } - - @Override - public Set getInputTypes() { - return Collections.singleton(QualifiedContent.DefaultContentType.CLASSES); - } - - @Override - public Set getScopes() { - return scopes; - } - - @Override - public Set getReferencedScopes() { - return Set.of(Scope.PROVIDED_ONLY); - } - - @Override - public boolean isIncremental() { - return true; - } - - @Override - public boolean isCacheable() { - return true; - } - - private void doTransform(TransformInvocation transform) throws IOException { - final Logger logger = Logging.getLogger(RefineTransform.class); - final HashMap refines = new HashMap<>(); - final Gson gson = new Gson(); - - boolean forceApply = !transform.isIncremental(); - - // collect annotation defines - if (!transform.isIncremental()) { - FileUtils.deleteDirectory(transform.getContext().getTemporaryDir()); - } - - for (TransformInput input : transform.getReferencedInputs()) { - for (JarInput jarInput : input.getJarInputs()) { - final File inputFile = jarInput.getFile(); - final File cacheFile = Paths.get( - transform.getContext().getTemporaryDir().getAbsolutePath(), - jarInput.getName(), - "refine-cache.json" - ).toFile(); - - if (jarInput.getStatus() == Status.REMOVED) { - FileUtils.deleteDirectory(cacheFile.getParentFile()); - continue; - } - - if (jarInput.getStatus() == Status.NOTCHANGED && cacheFile.exists()) { - try { - RefineCache cache = gson.fromJson(new FileReader(cacheFile), RefineCache.class); - refines.putAll(cache.getRefines()); - logger.debug("Refines from " + jarInput.getFile() + " cache matched"); - continue; - } catch (Exception ignore) { - // ignore - } - } - - final HashMap scopedRefines = new HashMap<>(); - - JarUtils.visit(inputFile, (entry, stream) -> { - if (!entry.getName().endsWith(".class")) - return; - - logger.debug("Collecting " + entry.getName()); - - final Map.Entry refine = RefineCollector.collect(stream); - if (refine != null) { - scopedRefines.put(refine.getKey(), refine.getValue()); - } - }); - - cacheFile.getParentFile().mkdirs(); - - try (JsonWriter writer = gson.newJsonWriter(new FileWriter(cacheFile))) { - gson.toJson(new RefineCache(scopedRefines), RefineCache.class, writer); - } - - refines.putAll(scopedRefines); - - forceApply = true; - } - - // TODO: handle DirectoryInput - } - - logger.info("Refines " + refines); - - // Apply refines - if (forceApply) { - transform.getOutputProvider().deleteAll(); - } - - final RefineApplier applier = new RefineApplier(refines); - - for (TransformInput input : transform.getInputs()) { - for (JarInput jarInput : input.getJarInputs()) { - final File inputFile = jarInput.getFile(); - final File outputFile = transform.getOutputProvider() - .getContentLocation( - jarInput.getName(), - jarInput.getContentTypes(), - jarInput.getScopes(), - Format.JAR - ); - - if (!forceApply && jarInput.getStatus() == Status.NOTCHANGED) { - continue; - } - - if (jarInput.getStatus() == Status.REMOVED) { - outputFile.delete(); - continue; - } - - try (JarOutputStream output = new JarOutputStream(new FileOutputStream(outputFile))) { - JarUtils.visit(inputFile, (entry, stream) -> { - output.putNextEntry(new JarEntry(entry.getName())); - - if (entry.getName().endsWith(".class")) { - logger.debug("Transforming " + entry.getName()); - - applier.applyFor(stream, output); - } else { - stream.transferTo(output); - } - }); - } - } - - for (DirectoryInput directoryInput : input.getDirectoryInputs()) { - final Path root = directoryInput.getFile().toPath(); - final HashSet changedFile = new HashSet<>(); - final File outputDir = transform.getOutputProvider() - .getContentLocation( - directoryInput.getName(), - directoryInput.getContentTypes(), - directoryInput.getScopes(), - Format.DIRECTORY - ); - - if (!forceApply) { - directoryInput.getChangedFiles().forEach((file, status) -> { - switch (status) { - case CHANGED: - case ADDED: - case REMOVED: - changedFile.add(root.relativize(file.toPath()).toString()); - } - }); - } else { - Files.walk(root) - .filter((p) -> p.toFile().isFile()) - .forEach((p) -> changedFile.add(root.relativize(p).toString())); - } - - for (String path : changedFile) { - final File inputFile = root.resolve(path).toFile(); - final File outputFile = new File(outputDir, path); - - if (!inputFile.exists()) { - outputFile.delete(); - - continue; - } - - outputFile.getParentFile().mkdirs(); - - try (FileInputStream in = new FileInputStream(inputFile); FileOutputStream out = new FileOutputStream(outputFile)) { - if (inputFile.getName().endsWith(".class")) { - logger.debug("Transforming " + path.replace('\\', '/')); - - applier.applyFor(in, out); - } else { - in.transferTo(out); - } - } - } - } - } - } - - @Override - public void transform(TransformInvocation transformInvocation) throws IOException { - try { - doTransform(transformInvocation); - } catch (Exception e) { - e.printStackTrace(); - - throw e; - } - } -} diff --git a/gradle-plugin/src/main/java/dev/rikka/tools/refine/utils/FileUtils.java b/gradle-plugin/src/main/java/dev/rikka/tools/refine/utils/FileUtils.java deleted file mode 100644 index 961e7db..0000000 --- a/gradle-plugin/src/main/java/dev/rikka/tools/refine/utils/FileUtils.java +++ /dev/null @@ -1,17 +0,0 @@ -package dev.rikka.tools.refine.utils; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Comparator; - -@SuppressWarnings("ResultOfMethodCallIgnored") -public final class FileUtils { - public static void deleteDirectory(File directory) throws IOException { - Files.walk(directory.getAbsoluteFile().toPath()) - .sorted(Comparator.reverseOrder()) - .map(Path::toFile) - .forEach(File::delete); - } -} diff --git a/gradle-plugin/src/main/java/dev/rikka/tools/refine/utils/JarUtils.java b/gradle-plugin/src/main/java/dev/rikka/tools/refine/utils/JarUtils.java deleted file mode 100644 index 0abdbf5..0000000 --- a/gradle-plugin/src/main/java/dev/rikka/tools/refine/utils/JarUtils.java +++ /dev/null @@ -1,26 +0,0 @@ -package dev.rikka.tools.refine.utils; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.jar.JarEntry; -import java.util.jar.JarInputStream; - -public final class JarUtils { - public interface Visitor { - void visit(JarEntry entry, InputStream stream) throws IOException; - } - - public static void visit(File file, Visitor visitor) throws IOException { - try (JarInputStream stream = new JarInputStream(new FileInputStream(file))) { - while (true) { - final JarEntry entry = stream.getNextJarEntry(); - if (entry == null) - break; - - visitor.visit(entry, stream); - } - } - } -} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 00e33ed..070cb70 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/runtime/build.gradle.kts b/runtime/build.gradle.kts index 7647350..b905ea7 100644 --- a/runtime/build.gradle.kts +++ b/runtime/build.gradle.kts @@ -8,9 +8,6 @@ publishing { publications { create(project.name, MavenPublication::class) { from(components["java"]) - - artifact(tasks["sourcesJar"]) - artifact(tasks["javadocJar"]) } } } diff --git a/runtime/src/main/java/dev/rikka/tools/refine/Refine.java b/runtime/src/main/java/dev/rikka/tools/refine/Refine.java index 77587ed..b5fac86 100644 --- a/runtime/src/main/java/dev/rikka/tools/refine/Refine.java +++ b/runtime/src/main/java/dev/rikka/tools/refine/Refine.java @@ -1,8 +1,29 @@ package dev.rikka.tools.refine; +/** + * Util class for using HiddenApiRefine plugin. + */ public final class Refine { + + /** + * Cast an object to {@link T}. + *

+ * A typical usage is cast a "Hidden" class to its real type. + *
+ * For example, {@code android.os.UserHandle} is a public class, but some of its methods are hidden. + * Therefore, you can create a {@code android.os.UserHandleHidden} class annotated with + * {@code @RefineAs(android.os.UserHandle.class)}) to access them. + *
+ * When you have an instance of {@code android.os.UserHandle} and you need to access its hidden methods, + * at this time you can use this method to cast it to {@code android.os.UserHandleHidden}. + * + * @param obj Object + * @param Target type + * @return Object with target type + * @throws java.lang.ClassCastException If HiddenApiRefine plugin is not correctly set or not working + */ @SuppressWarnings("unchecked") - public static T unsafeCast(Object obj) { + public static T unsafeCast(final Object obj) { return (T) obj; } } diff --git a/settings.gradle.kts b/settings.gradle.kts index 291acdb..7535386 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -20,16 +20,14 @@ dependencyResolutionManagement { } versionCatalogs { create("libs") { - val agp = "7.1.2" - val gson = "2.9.0" + val agp = "7.4.1" val service = "1.0.1" - val javassist = "3.28.0-GA" + val asm = "5.2" - library("android-gradle", "com.android.tools.build", "gradle").version(agp) - library("google-gson", "com.google.code.gson", "gson").version(gson) - library("google-service-compiler", "com.google.auto.service", "auto-service").version(service) - library("google-service-annotation", "com.google.auto.service", "auto-service-annotations").version(service) - library("javassist", "org.javassist", "javassist").version(javassist) + library("android-gradle", "com.android.tools.build:gradle:$agp") + library("google-service-compiler", "com.google.auto.service:auto-service:$service") + library("google-service-annotation", "com.google.auto.service:auto-service-annotations:$service") + library("asm-all", "org.ow2.asm:asm-all:$asm") } } -} \ No newline at end of file +}