diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..a37053eb --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,35 @@ +name: Build + +on: + push: + branches: [ master, "claude/**" ] + pull_request: + branches: [ master ] + +concurrency: + group: build-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + name: Build & Verify + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v3 + with: + cache-read-only: ${{ github.ref != 'refs/heads/master' }} + + - name: Validate Gradle wrapper + uses: gradle/actions/wrapper-validation@v3 + + - name: Assemble & test + run: ./gradlew assemble check --stacktrace diff --git a/.github/workflows/gradle-publish.yml b/.github/workflows/gradle-publish.yml index 87a5a5c6..ae0b917a 100644 --- a/.github/workflows/gradle-publish.yml +++ b/.github/workflows/gradle-publish.yml @@ -1,47 +1,75 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. -# This workflow will build a package using Gradle and then publish it to GitHub packages when a release is created -# For more information see: https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#Publishing-using-gradle +# Publishes the Hunter artifacts to Maven Central via Sonatype Central Portal. +# +# Triggers: +# - manual dispatch (`workflow_dispatch`) for ad-hoc releases / dry-runs +# - tag push of the form `v*` (e.g. `v1.3.0`) for cut releases +# +# Required repository secrets (Settings → Secrets and variables → Actions): +# MAVEN_CENTRAL_USERNAME — Sonatype Central Portal user token name +# MAVEN_CENTRAL_PASSWORD — Sonatype Central Portal user token password +# SIGNING_IN_MEMORY_KEY — ASCII-armored GPG private key (entire block, +# including BEGIN/END lines). Generate with +# `gpg --armor --export-secret-keys `. +# SIGNING_IN_MEMORY_KEY_ID — last 8 hex chars of the GPG key (optional) +# SIGNING_IN_MEMORY_KEY_PASSWORD — passphrase for that GPG key +# +# The vanniktech maven-publish plugin reads these via the +# `ORG_GRADLE_PROJECT_*` env-var convention. -name: Gradle Package +name: Publish to Maven Central on: + workflow_dispatch: + inputs: + dryRun: + description: 'Run :publishToMavenLocal only (skip Central upload)' + required: false + default: 'false' push: - branches: [ master ] - pull_request: - branches: [ master ] + tags: + - 'v*' -jobs: - build: +concurrency: + group: publish-${{ github.ref }} + cancel-in-progress: false +jobs: + publish: + name: Publish runs-on: ubuntu-latest + timeout-minutes: 30 permissions: contents: read - packages: write - steps: - - uses: actions/checkout@v3 - - name: Set up JDK 11 - uses: actions/setup-java@v3 - with: - java-version: '11' - distribution: 'temurin' - server-id: github # Value of the distributionManagement/repository/id field of the pom.xml - settings-path: ${{ github.workspace }} # location for the settings.xml file - - - name: Build with Gradle - uses: ./gradlew build --no-daemon - with: - arguments: build - - # The USERNAME and TOKEN need to correspond to the credentials environment variables used in - # the publishing section of your build.gradle - - name: Publish to GitHub Packages - uses: ./gradlew build --no-daemon - with: - arguments: publish - env: - USERNAME: ${{ github.actor }} - TOKEN: ${{ secrets.GITHUB_TOKEN }} + - uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v3 + with: + cache-read-only: false + + - name: Validate Gradle wrapper + uses: gradle/actions/wrapper-validation@v3 + + - name: Verify build before publishing + run: ./gradlew assemble --stacktrace + + - name: Publish to Maven Local (dry run) + if: ${{ github.event_name == 'workflow_dispatch' && inputs.dryRun == 'true' }} + run: ./gradlew publishToMavenLocal --stacktrace + + - name: Publish to Maven Central + if: ${{ !(github.event_name == 'workflow_dispatch' && inputs.dryRun == 'true') }} + env: + ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} + ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} + ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_IN_MEMORY_KEY }} + ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.SIGNING_IN_MEMORY_KEY_ID }} + ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_IN_MEMORY_KEY_PASSWORD }} + run: ./gradlew publishAndReleaseToMavenCentral --no-configuration-cache --stacktrace diff --git a/README.md b/README.md index 999f0217..6a0bd843 100644 --- a/README.md +++ b/README.md @@ -2,24 +2,47 @@ [中文](https://github.com/Leaking/Hunter/blob/master/docs/README_ch.md) -Hunter is a framework to develop android gradle plugin based on -[ASM](https://asm.ow2.io/) and [Gradle Transform API](http://tools.android.com/tech-docs/new-build-system/transform-api). +Hunter is a framework to develop android gradle plugins based on +[ASM](https://asm.ow2.io/) and the modern AGP +[Instrumentation API](https://developer.android.com/reference/tools/gradle-api/8.7/com/android/build/api/instrumentation/AsmClassVisitorFactory). It provides a set of useful, scalable plugins for android developers. You can use Hunter to develop more plugins -to monitor your app, enhance 3rd-dependency, enhance android framework. Plugins based on Hunter support incremental and concurrent compile, so you don't need to -be afraid of extra build time. +to monitor your app, enhance 3rd-party deps, or enhance the android framework. Hunter-based plugins inherit +AGP's incremental, parallel instrumentation pipeline, so you don't pay for extra build time. ## Latest Version Recently, Hunter is published to [MavenCentral](https://repo1.maven.org/maven2/cn/quinnchen/hunter/). -Every libraies / plugins use same latest version, replace LATEST_VERSION_IN_README in others README with this latest version +Every library / plugin uses the same latest version. Replace `LATEST_VERSION_IN_README` in the per-plugin +READMEs with this version. ```groovy -1.2.3 +1.3.0 ``` +## Compatibility + +| Hunter | Android Gradle Plugin | Gradle | ASM | Java | +|--------|----------------------------------------------|--------|-----|------| +| 1.3.x | 7.4 – 8.7+ | 8.0+ | 9.7 | 17 | +| 1.2.x | 4.x (legacy Transform API, removed in AGP 8) | 7.x | 7.1 | 8 | + +`1.3.0` is a **breaking** release: the legacy `Transform` API is gone (it was +removed by AGP 8.0 — see issues +[#67](https://github.com/Leaking/Hunter/issues/67), +[#63](https://github.com/Leaking/Hunter/issues/63), +[#34](https://github.com/Leaking/Hunter/issues/34)) and Hunter now wires +itself in via `AndroidComponentsExtension.onVariants` + +`AsmClassVisitorFactory`. ASM was bumped to 9.x to support Java 17 records +and sealed classes +([#60](https://github.com/Leaking/Hunter/issues/60), +[#66](https://github.com/Leaking/Hunter/issues/66)). The constructor +instrumentation bug from +[#48](https://github.com/Leaking/Hunter/issues/48) is also fixed: the timing +probe is now injected after the mandatory `super(...)` / `this(...)` call. + ## Some useful plugins based on Hunter + [OkHttp-Plugin](https://github.com/Leaking/Hunter/blob/master/docs/README_hunter_okhttp.md): Hack Okhttp to set a global Interceptor/Eventlistener/Dns diff --git a/build.gradle b/build.gradle index 040247ae..c5c3d6c2 100644 --- a/build.gradle +++ b/build.gradle @@ -1,31 +1,49 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. +// Top-level build file. -allprojects { - buildscript { - repositories { - mavenLocal() - mavenCentral() - google() - } - dependencies { - classpath Libs.AGP - classpath 'com.vanniktech:gradle-maven-publish-plugin:0.17.0' - } - } +buildscript { repositories { mavenLocal() - mavenCentral() google() + mavenCentral() + gradlePluginPortal() + } + dependencies { + classpath Libs.AGP + classpath 'com.vanniktech:gradle-maven-publish-plugin:0.30.0' } } + allprojects { + repositories { + mavenLocal() + google() + mavenCentral() + gradlePluginPortal() + } +} + +import com.vanniktech.maven.publish.MavenPublishBaseExtension +import com.vanniktech.maven.publish.SonatypeHost + +subprojects { plugins.withId("com.vanniktech.maven.publish") { - mavenPublish { - sonatypeHost = "S01" + extensions.configure(MavenPublishBaseExtension) { ext -> + // Sonatype Central Portal — replaces the deprecated OSSRH that was + // shut down on 2025-06-30. + ext.publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL, true) + // Sign only when a GPG key is configured. Lets ./gradlew + // publishToMavenLocal run on a dev box that has no signing key set + // up, while CI (which exports ORG_GRADLE_PROJECT_signingInMemoryKey) + // still produces signed artifacts for Central. + boolean hasSigningKey = project.findProperty("signingInMemoryKey") != null \ + || System.getenv("ORG_GRADLE_PROJECT_signingInMemoryKey") != null + if (hasSigningKey) { + ext.signAllPublications() + } } } } -task clean(type: Delete) { - delete rootProject.buildDir +tasks.register('clean', Delete) { + delete rootProject.layout.buildDirectory } diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 03d2ce86..9c913306 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -1,107 +1,90 @@ object Versions { - val hunter_common_version = "1.2.1" + const val hunter_common_version = "1.3.0" - val hunter_transform = hunter_common_version + const val hunter_transform = hunter_common_version - val debug_library = hunter_common_version - val debug_plugin = hunter_common_version + const val debug_library = hunter_common_version + const val debug_plugin = hunter_common_version - val linelog_library = hunter_common_version - val linelog_plugin = hunter_common_version + const val linelog_library = hunter_common_version + const val linelog_plugin = hunter_common_version - val okhttp_library = hunter_common_version - val okhttp_library_compat = hunter_common_version - val okhttp_plugin = hunter_common_version + const val okhttp_library = hunter_common_version + const val okhttp_library_compat = hunter_common_version + const val okhttp_plugin = hunter_common_version - val timing_library = hunter_common_version - val timing_plugin = hunter_common_version + const val timing_library = hunter_common_version + const val timing_plugin = hunter_common_version - val AGP = "4.1.3" - val AGP_API = "4.1.0" - val ASM = "7.1" - val common_io = "2.6" + const val AGP = "8.7.0" + const val ASM = "9.7" } object PublishVersions { - val hunter_common_publish_version = "1.2.1" + const val hunter_common_publish_version = "1.3.0" - val hunter_transform = hunter_common_publish_version + const val hunter_transform = hunter_common_publish_version - val debug_library = hunter_common_publish_version - val debug_plugin = hunter_common_publish_version + const val debug_library = hunter_common_publish_version + const val debug_plugin = hunter_common_publish_version - val linelog_library = hunter_common_publish_version - val linelog_plugin = hunter_common_publish_version + const val linelog_library = hunter_common_publish_version + const val linelog_plugin = hunter_common_publish_version - val okhttp_library = hunter_common_publish_version - val okhttp_library_compat = hunter_common_publish_version - val okhttp_plugin = hunter_common_publish_version + const val okhttp_library = hunter_common_publish_version + const val okhttp_library_compat = hunter_common_publish_version + const val okhttp_plugin = hunter_common_publish_version - val timing_library = hunter_common_publish_version - val timing_plugin = hunter_common_publish_version + const val timing_library = hunter_common_publish_version + const val timing_plugin = hunter_common_publish_version } -//object Libs { -// val hunter_debug_library = "com.quinn.hunter:hunter-debug-library:${Versions.hunter_debug_library}" -//} -//object Plugins { -// val hunter_debug_plugin = "com.quinn.hunter:hunter-debug-plugin:${Versions.hunter_debug_plugin}" -//} object Group { - val commonGrouop = "cn.quinnchen.hunter" + const val commonGrouop = "cn.quinnchen.hunter" } + object Artifacts { - val okhttp_library_compat = "hunter-debug-library-compat" - val hunter_transform = "hunter-transform" - - val debug_library = "hunter-debug-library" - val okhttp_library = "hunter-okhttp-library" - val linelog_library = "hunter-linelog-library" - val timing_library = "hunter-timing-library" - - val debug_plugin = "hunter-debug-plugin" - val okhttp_plugin = "hunter-okhttp-plugin" - val linelog_plugin = "hunter-linelog-plugin" - val timing_plugin = "hunter-timing-plugin" + const val okhttp_library_compat = "hunter-okhttp-library-compat" + const val hunter_transform = "hunter-transform" + + const val debug_library = "hunter-debug-library" + const val okhttp_library = "hunter-okhttp-library" + const val linelog_library = "hunter-linelog-library" + const val timing_library = "hunter-timing-library" + + const val debug_plugin = "hunter-debug-plugin" + const val okhttp_plugin = "hunter-okhttp-plugin" + const val linelog_plugin = "hunter-linelog-plugin" + const val timing_plugin = "hunter-timing-plugin" } -//object Libs { -// val hunter_debug_library = "project(':hunter-debug-library')" -// val hunter_okhttp_library = "project(':hunter-okhttp-library')" -// val hunter_okhttp_library_compat = "project(':hunter-okhttp-library-compat')" -// val hunter_linelog_library = "project(':hunter-linelog-library')" -// val hunter_timing_library = "project(':hunter-timing-library')" -//} - object Libs { - val hunter_debug_library = "${Group.commonGrouop}:${Artifacts.debug_library}:${Versions.debug_library}" - val hunter_okhttp_library = "${Group.commonGrouop}:${Artifacts.okhttp_library}:${Versions.okhttp_library}" - val hunter_okhttp_library_compat = "${Group.commonGrouop}:${Artifacts.okhttp_library_compat}:${Versions.okhttp_library_compat}" - val hunter_linelog_library = "${Group.commonGrouop}:${Artifacts.linelog_library}:${Versions.linelog_library}" - val hunter_timing_library = "${Group.commonGrouop}:${Artifacts.timing_library}:${Versions.timing_library}" - val AGP = "com.android.tools.build:gradle:${Versions.AGP}" - val AGP_API = "com.android.tools.build:gradle-api:${Versions.AGP_API}" - val asm = "org.ow2.asm:asm:${Versions.ASM}" - val asm_util = "org.ow2.asm:asm-util:${Versions.ASM}" - val asm_commons = "org.ow2.asm:asm-commons:${Versions.ASM}" - val common_io = "commons-io:commons-io:${Versions.common_io}" + const val hunter_debug_library = "${Group.commonGrouop}:${Artifacts.debug_library}:${Versions.debug_library}" + const val hunter_okhttp_library = "${Group.commonGrouop}:${Artifacts.okhttp_library}:${Versions.okhttp_library}" + const val hunter_okhttp_library_compat = "${Group.commonGrouop}:${Artifacts.okhttp_library_compat}:${Versions.okhttp_library_compat}" + const val hunter_linelog_library = "${Group.commonGrouop}:${Artifacts.linelog_library}:${Versions.linelog_library}" + const val hunter_timing_library = "${Group.commonGrouop}:${Artifacts.timing_library}:${Versions.timing_library}" + const val AGP = "com.android.tools.build:gradle:${Versions.AGP}" + const val AGP_API = "com.android.tools.build:gradle-api:${Versions.AGP}" + const val asm = "org.ow2.asm:asm:${Versions.ASM}" + const val asm_util = "org.ow2.asm:asm-util:${Versions.ASM}" + const val asm_commons = "org.ow2.asm:asm-commons:${Versions.ASM}" + const val asm_tree = "org.ow2.asm:asm-tree:${Versions.ASM}" } object Plugins { - val hunter_transform = "${Group.commonGrouop}:${Artifacts.hunter_transform}:${Versions.hunter_transform}" - val hunter_debug_plugin = "${Group.commonGrouop}:${Artifacts.debug_plugin}:${Versions.debug_plugin}" - val hunter_okhttp_plugin = "${Group.commonGrouop}:${Artifacts.okhttp_plugin}:${Versions.okhttp_plugin}" - val hunter_linelog_plugin = "${Group.commonGrouop}:${Artifacts.linelog_plugin}:${Versions.linelog_plugin}" - val hunter_timing_plugin = "${Group.commonGrouop}:${Artifacts.timing_plugin}:${Versions.timing_plugin}" + const val hunter_transform = "${Group.commonGrouop}:${Artifacts.hunter_transform}:${Versions.hunter_transform}" + const val hunter_debug_plugin = "${Group.commonGrouop}:${Artifacts.debug_plugin}:${Versions.debug_plugin}" + const val hunter_okhttp_plugin = "${Group.commonGrouop}:${Artifacts.okhttp_plugin}:${Versions.okhttp_plugin}" + const val hunter_linelog_plugin = "${Group.commonGrouop}:${Artifacts.linelog_plugin}:${Versions.linelog_plugin}" + const val hunter_timing_plugin = "${Group.commonGrouop}:${Artifacts.timing_plugin}:${Versions.timing_plugin}" } object Android { - val compileSdkVersion = 29 - val buildToolsVersion = "29.0.3" - val minSdkVersion = 15 - val targetSdkVersion = 29 - val versionCode = 1 - val versionName = "1.0" + const val compileSdkVersion = 34 + const val buildToolsVersion = "34.0.0" + const val minSdkVersion = 21 + const val targetSdkVersion = 34 + const val versionCode = 1 + const val versionName = "1.0" } - - diff --git a/gradle-scripts/base-plugin-dependencies.gradle b/gradle-scripts/base-plugin-dependencies.gradle index 8f737005..b2a1274d 100644 --- a/gradle-scripts/base-plugin-dependencies.gradle +++ b/gradle-scripts/base-plugin-dependencies.gradle @@ -1,10 +1,11 @@ dependencies { implementation gradleApi() - implementation localGroovy() - implementation Libs.AGP - implementation Libs.AGP_API - implementation Libs.common_io + // Compile against the AGP public API only; AGP itself is provided by the + // consumer's classpath at apply-time. This stops accidentally pinning a + // specific AGP minor version into the published artifact. + compileOnly Libs.AGP_API implementation Libs.asm - implementation Libs.asm_util implementation Libs.asm_commons + implementation Libs.asm_tree + implementation Libs.asm_util } diff --git a/gradle-scripts/plugin-dependencies.gradle b/gradle-scripts/plugin-dependencies.gradle index 7a9b4bc5..13d38b51 100644 --- a/gradle-scripts/plugin-dependencies.gradle +++ b/gradle-scripts/plugin-dependencies.gradle @@ -1,14 +1,15 @@ dependencies { implementation gradleApi() implementation localGroovy() - implementation Libs.AGP - implementation Libs.AGP_API - implementation Libs.common_io + compileOnly Libs.AGP_API implementation Libs.asm - implementation Libs.asm_util implementation Libs.asm_commons + implementation Libs.asm_tree + implementation Libs.asm_util + implementation project(':hunter-transform') } -dependencies { - implementation Plugins.hunter_transform +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } diff --git a/gradle.properties b/gradle.properties index d76ce667..7ba526e2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,22 +1,19 @@ -## Project-wide Gradle settings. -# -# For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html -# -# Specifies the JVM arguments used for the daemon process. -# The setting is particularly useful for tweaking memory settings. -# Default value: -Xmx1024m -XX:MaxPermSize=256m -# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -# -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true -#Tue Jun 13 11:50:19 CST 2017 -org.gradle.jvmargs=-Xmx1536m +# Project-wide Gradle settings. +# https://docs.gradle.org/current/userguide/build_environment.html + +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +org.gradle.parallel=true +org.gradle.caching=true +org.gradle.configuration-cache=false + +android.useAndroidX=true +android.nonTransitiveRClass=true + +# Silence the AGP-8 warning about Transform/legacy bytecode APIs in old samples. +android.suppressUnsupportedCompileSdk=34 GROUP=cn.quinnchen.hunter -VERSION_NAME=1.2.3 +VERSION_NAME=1.3.0 POM_NAME=Hunter POM_DESCRIPTION=A fast, incremental, concurrent framework to develop compile plugin for android project to manipulate bytecode. POM_INCEPTION_YEAR=2022 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d6706e00..37de3e4a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Tue Jun 21 15:26:13 CST 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/hunter-debug-example/build.gradle b/hunter-debug-example/build.gradle index 90647624..c197e366 100644 --- a/hunter-debug-example/build.gradle +++ b/hunter-debug-example/build.gradle @@ -1,7 +1,15 @@ +buildscript { + dependencies { + classpath Plugins.hunter_debug_plugin + } +} + apply plugin: 'com.android.application' +apply plugin: 'hunter-debug' android { - compileSdkVersion Android.compileSdkVersion + namespace 'com.quinn.hunter.debug' + compileSdk Android.compileSdkVersion buildToolsVersion Android.buildToolsVersion defaultConfig { applicationId "com.quinn.hunter.debug" @@ -10,37 +18,28 @@ android { versionCode Android.versionCode versionName Android.versionName } - buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } - lintOptions { + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + lint { abortOnError false } - } dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') - implementation 'com.android.support:appcompat-v7:28.0.0' + implementation 'androidx.appcompat:appcompat:1.7.0' implementation Libs.hunter_debug_library - implementation 'org.apache.commons:commons-lang3:3.7' + implementation 'org.apache.commons:commons-lang3:3.14.0' } -buildscript { - dependencies { - classpath Plugins.hunter_debug_plugin - classpath Plugins.hunter_transform - } -} - - - -apply plugin: 'hunter-debug' - debugHunterExt { - runVariant = 'DEBUG' -} \ No newline at end of file + runVariant = com.quinn.hunter.transform.RunVariant.DEBUG +} diff --git a/hunter-debug-example/src/main/AndroidManifest.xml b/hunter-debug-example/src/main/AndroidManifest.xml index 369c238f..bdb1e58c 100644 --- a/hunter-debug-example/src/main/AndroidManifest.xml +++ b/hunter-debug-example/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - + - + diff --git a/hunter-debug-library/build.gradle b/hunter-debug-library/build.gradle index d24514d1..2b2fdbe3 100644 --- a/hunter-debug-library/build.gradle +++ b/hunter-debug-library/build.gradle @@ -2,6 +2,14 @@ apply plugin: 'com.android.library' apply plugin: "com.vanniktech.maven.publish" android { - compileSdkVersion Android.compileSdkVersion + namespace 'com.hunter.library.debug' + compileSdk Android.compileSdkVersion buildToolsVersion Android.buildToolsVersion + defaultConfig { + minSdkVersion Android.minSdkVersion + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } } diff --git a/hunter-debug-library/src/main/AndroidManifest.xml b/hunter-debug-library/src/main/AndroidManifest.xml index 793aba82..94cbbcfc 100644 --- a/hunter-debug-library/src/main/AndroidManifest.xml +++ b/hunter-debug-library/src/main/AndroidManifest.xml @@ -1,2 +1 @@ - + diff --git a/hunter-debug-plugin/src/main/groovy/com/quinn/hunter/plugin/debug/DebugHunterExtension.java b/hunter-debug-plugin/src/main/groovy/com/quinn/hunter/plugin/debug/DebugHunterExtension.java index 7c05f117..8d4d4d79 100644 --- a/hunter-debug-plugin/src/main/groovy/com/quinn/hunter/plugin/debug/DebugHunterExtension.java +++ b/hunter-debug-plugin/src/main/groovy/com/quinn/hunter/plugin/debug/DebugHunterExtension.java @@ -2,19 +2,12 @@ import com.quinn.hunter.transform.RunVariant; -/** - * Created by Quinn on 07/10/2018. - */ public class DebugHunterExtension { public RunVariant runVariant = RunVariant.ALWAYS; - public boolean duplcatedClassSafeMode = false; @Override public String toString() { - return "DebugHunterExtension{" + - "runVariant=" + runVariant + - ", duplcatedClassSafeMode=" + duplcatedClassSafeMode + - '}'; + return "DebugHunterExtension{runVariant=" + runVariant + '}'; } } diff --git a/hunter-debug-plugin/src/main/groovy/com/quinn/hunter/plugin/debug/DebugHunterPlugin.java b/hunter-debug-plugin/src/main/groovy/com/quinn/hunter/plugin/debug/DebugHunterPlugin.java index 14fcc4cc..d4b17e66 100644 --- a/hunter-debug-plugin/src/main/groovy/com/quinn/hunter/plugin/debug/DebugHunterPlugin.java +++ b/hunter-debug-plugin/src/main/groovy/com/quinn/hunter/plugin/debug/DebugHunterPlugin.java @@ -1,22 +1,29 @@ package com.quinn.hunter.plugin.debug; -import com.android.build.gradle.AppExtension; +import com.android.build.api.variant.Variant; + +import com.quinn.hunter.plugin.debug.bytecode.DebugClassVisitorFactory; +import com.quinn.hunter.transform.HunterParameters; +import com.quinn.hunter.transform.HunterPlugin; -import org.gradle.api.Plugin; import org.gradle.api.Project; -import java.util.Collections; +public class DebugHunterPlugin extends HunterPlugin { -/** - * Created by Quinn on 16/09/2018. - */ -public class DebugHunterPlugin implements Plugin { + @Override + protected void registerExtension(Project project) { + project.getExtensions().create("debugHunterExt", DebugHunterExtension.class); + } - @SuppressWarnings("NullableProblems") @Override - public void apply(Project project) { - AppExtension appExtension = (AppExtension)project.getProperties().get("android"); - appExtension.registerTransform(new DebugHunterTransform(project), Collections.EMPTY_LIST); + protected boolean skipVariant(Project project, Variant variant) { + DebugHunterExtension ext = + (DebugHunterExtension) project.getExtensions().getByName("debugHunterExt"); + return ext.runVariant.shouldSkip(variant); } -} \ No newline at end of file + @Override + protected Class getFactoryClass() { + return DebugClassVisitorFactory.class; + } +} diff --git a/hunter-debug-plugin/src/main/groovy/com/quinn/hunter/plugin/debug/DebugHunterTransform.java b/hunter-debug-plugin/src/main/groovy/com/quinn/hunter/plugin/debug/DebugHunterTransform.java deleted file mode 100644 index 3e734c51..00000000 --- a/hunter-debug-plugin/src/main/groovy/com/quinn/hunter/plugin/debug/DebugHunterTransform.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.quinn.hunter.plugin.debug; - -import com.android.build.api.transform.Context; -import com.android.build.api.transform.TransformException; -import com.android.build.api.transform.TransformInput; -import com.android.build.api.transform.TransformOutputProvider; -import com.quinn.hunter.plugin.debug.bytecode.DebugWeaver; -import com.quinn.hunter.transform.HunterTransform; -import com.quinn.hunter.transform.RunVariant; - -import org.gradle.api.Project; - -import java.io.IOException; -import java.util.Collection; - -/** - * Created by Quinn on 16/09/2018. - */ -public class DebugHunterTransform extends HunterTransform { - - private Project project; - private DebugHunterExtension debugHunterExtension; - - public DebugHunterTransform(Project project) { - super(project); - this.project = project; - project.getExtensions().create("debugHunterExt", DebugHunterExtension.class); - this.bytecodeWeaver = new DebugWeaver(); - } - - @Override - public void transform(Context context, Collection inputs, Collection referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException { - debugHunterExtension = (DebugHunterExtension) project.getExtensions().getByName("debugHunterExt"); - super.transform(context, inputs, referencedInputs, outputProvider, isIncremental); - } - - @Override - protected RunVariant getRunVariant() { - return debugHunterExtension.runVariant; - } - - @Override - protected boolean inDuplicatedClassSafeMode() { - return debugHunterExtension.duplcatedClassSafeMode; - } -} \ No newline at end of file diff --git a/hunter-debug-plugin/src/main/groovy/com/quinn/hunter/plugin/debug/bytecode/DebugClassAdapter.java b/hunter-debug-plugin/src/main/groovy/com/quinn/hunter/plugin/debug/bytecode/DebugClassAdapter.java index d48bcd5c..2ea2a457 100644 --- a/hunter-debug-plugin/src/main/groovy/com/quinn/hunter/plugin/debug/bytecode/DebugClassAdapter.java +++ b/hunter-debug-plugin/src/main/groovy/com/quinn/hunter/plugin/debug/bytecode/DebugClassAdapter.java @@ -20,8 +20,8 @@ public final class DebugClassAdapter extends ClassVisitor{ private List includeMethods = new ArrayList(); private List implMethods = new ArrayList<>(); - DebugClassAdapter(final ClassVisitor cv, final Map> methodParametersMap) { - super(Opcodes.ASM7, cv); + public DebugClassAdapter(final ClassVisitor cv, final Map> methodParametersMap) { + super(Opcodes.ASM9, cv); this.methodParametersMap = methodParametersMap; } @@ -39,7 +39,7 @@ public void visit(int version, int access, String name, String signature, String public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) { - MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions); + MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); if(includeMethods.contains(name)){ String methodUniqueKey = name + desc; debugMethodAdapter = new DebugMethodAdapter(className, methodParametersMap.get(methodUniqueKey), name, access, desc, mv); diff --git a/hunter-debug-plugin/src/main/groovy/com/quinn/hunter/plugin/debug/bytecode/DebugClassVisitorFactory.java b/hunter-debug-plugin/src/main/groovy/com/quinn/hunter/plugin/debug/bytecode/DebugClassVisitorFactory.java new file mode 100644 index 00000000..fe047d5b --- /dev/null +++ b/hunter-debug-plugin/src/main/groovy/com/quinn/hunter/plugin/debug/bytecode/DebugClassVisitorFactory.java @@ -0,0 +1,15 @@ +package com.quinn.hunter.plugin.debug.bytecode; + +import com.android.build.api.instrumentation.ClassContext; + +import com.quinn.hunter.transform.HunterAsmClassVisitorFactory; +import com.quinn.hunter.transform.HunterParameters; +import com.quinn.hunter.transform.asm.IWeaver; + +public abstract class DebugClassVisitorFactory extends HunterAsmClassVisitorFactory { + + @Override + protected IWeaver createWeaver(ClassContext classContext) { + return new DebugWeaver(); + } +} diff --git a/hunter-debug-plugin/src/main/groovy/com/quinn/hunter/plugin/debug/bytecode/DebugMethodAdapter.java b/hunter-debug-plugin/src/main/groovy/com/quinn/hunter/plugin/debug/bytecode/DebugMethodAdapter.java index e21e9bed..b7da8223 100644 --- a/hunter-debug-plugin/src/main/groovy/com/quinn/hunter/plugin/debug/bytecode/DebugMethodAdapter.java +++ b/hunter-debug-plugin/src/main/groovy/com/quinn/hunter/plugin/debug/bytecode/DebugMethodAdapter.java @@ -23,7 +23,7 @@ public final class DebugMethodAdapter extends LocalVariablesSorter implements Op public DebugMethodAdapter(String className, List parameters, String name, int access, String desc, MethodVisitor mv) { - super(Opcodes.ASM7, access, desc, mv); + super(Opcodes.ASM9, access, desc, mv); if(!className.endsWith("/")) { this.className = className.substring(className.lastIndexOf("/") + 1); } else { diff --git a/hunter-debug-plugin/src/main/groovy/com/quinn/hunter/plugin/debug/bytecode/DebugWeaver.java b/hunter-debug-plugin/src/main/groovy/com/quinn/hunter/plugin/debug/bytecode/DebugWeaver.java index d857b45e..5984deb7 100644 --- a/hunter-debug-plugin/src/main/groovy/com/quinn/hunter/plugin/debug/bytecode/DebugWeaver.java +++ b/hunter-debug-plugin/src/main/groovy/com/quinn/hunter/plugin/debug/bytecode/DebugWeaver.java @@ -1,51 +1,49 @@ package com.quinn.hunter.plugin.debug.bytecode; -import com.android.build.gradle.internal.LoggerWrapper; import com.quinn.hunter.plugin.debug.bytecode.prego.DebugPreGoClassAdapter; import com.quinn.hunter.transform.asm.BaseWeaver; -import com.quinn.hunter.transform.asm.ExtendClassWriter; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.ClassNode; -import java.io.IOException; -import java.io.InputStream; - -/** - * Created by Quinn on 16/09/2018. - */ public final class DebugWeaver extends BaseWeaver { - private static final String PLUGIN_LIBRARY = "com.hunter.library.debug"; - private static final LoggerWrapper logger = LoggerWrapper.getLogger(DebugWeaver.class); - - @Override - public void setExtension(Object extension) { - } + private static final String PLUGIN_LIBRARY_PREFIX = "com.hunter.library.debug"; @Override - public byte[] weaveSingleClassToByteArray(InputStream inputStream) throws IOException { - ClassReader classReader = new ClassReader(inputStream); - ClassWriter classWriter = new ExtendClassWriter(classLoader, ClassWriter.COMPUTE_MAXS); - DebugPreGoClassAdapter debugPreGoClassAdapter = new DebugPreGoClassAdapter(classWriter); - classReader.accept(debugPreGoClassAdapter, ClassReader.EXPAND_FRAMES); - //if need parameter - if(debugPreGoClassAdapter.isNeedParameter()) { - classWriter = new ExtendClassWriter(classLoader, ClassWriter.COMPUTE_MAXS); - DebugClassAdapter debugClassAdapter = new DebugClassAdapter(classWriter, debugPreGoClassAdapter.getMethodParametersMap()); - debugClassAdapter.attachIncludeMethodsAndImplMethods(debugPreGoClassAdapter.getIncludes(),debugPreGoClassAdapter.getImpls()); - classReader.accept(debugClassAdapter, ClassReader.EXPAND_FRAMES); - } - return classWriter.toByteArray(); + public boolean isWeavableClass(String fullyQualifiedClassName) { + return super.isWeavableClass(fullyQualifiedClassName) + && !fullyQualifiedClassName.startsWith(PLUGIN_LIBRARY_PREFIX); } + /** + * Buffers the class as a {@link ClassNode}, then runs the two-pass debug + * pipeline. Pass one ({@link DebugPreGoClassAdapter}) collects parameter + * names and the set of methods to instrument; pass two + * ({@link DebugClassAdapter}) rewrites them. + * + * Buffering is required because AGP gives us a single {@link ClassVisitor} + * chain but the second pass depends on data only the first pass produces. + */ @Override - public boolean isWeavableClass(String fullQualifiedClassName) { - boolean superResult = super.isWeavableClass(fullQualifiedClassName); - boolean isByteCodePlugin = fullQualifiedClassName.startsWith(PLUGIN_LIBRARY); - return superResult && !isByteCodePlugin; + public ClassVisitor wrap(final ClassVisitor next) { + return new ClassNode(Opcodes.ASM9) { + @Override + public void visitEnd() { + super.visitEnd(); + DebugPreGoClassAdapter prego = new DebugPreGoClassAdapter(null); + accept(prego); + if (prego.isNeedParameter()) { + DebugClassAdapter main = + new DebugClassAdapter(next, prego.getMethodParametersMap()); + main.attachIncludeMethodsAndImplMethods( + prego.getIncludes(), prego.getImpls()); + accept(main); + } else { + accept(next); + } + } + }; } - - - } diff --git a/hunter-debug-plugin/src/main/groovy/com/quinn/hunter/plugin/debug/bytecode/prego/DebugPreGoClassAdapter.java b/hunter-debug-plugin/src/main/groovy/com/quinn/hunter/plugin/debug/bytecode/prego/DebugPreGoClassAdapter.java index fba8ab46..f6431a79 100644 --- a/hunter-debug-plugin/src/main/groovy/com/quinn/hunter/plugin/debug/bytecode/prego/DebugPreGoClassAdapter.java +++ b/hunter-debug-plugin/src/main/groovy/com/quinn/hunter/plugin/debug/bytecode/prego/DebugPreGoClassAdapter.java @@ -26,7 +26,7 @@ public final class DebugPreGoClassAdapter extends ClassVisitor{ private List impls = new ArrayList<>(); public DebugPreGoClassAdapter(final ClassVisitor cv) { - super(Opcodes.ASM7, cv); + super(Opcodes.ASM9, cv); } @Override @@ -41,7 +41,7 @@ public AnnotationVisitor visitAnnotation(String desc, boolean visible) { @Override public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) { - MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions); + MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); String methodUniqueKey = name + desc; debugPreGoMethodAdapter = new DebugPreGoMethodAdapter(name, methodUniqueKey, methodParametersMap, mv, classDebug, new MethodCollector() { @Override diff --git a/hunter-debug-plugin/src/main/groovy/com/quinn/hunter/plugin/debug/bytecode/prego/DebugPreGoMethodAdapter.java b/hunter-debug-plugin/src/main/groovy/com/quinn/hunter/plugin/debug/bytecode/prego/DebugPreGoMethodAdapter.java index 5f8d93c7..48b879df 100644 --- a/hunter-debug-plugin/src/main/groovy/com/quinn/hunter/plugin/debug/bytecode/prego/DebugPreGoMethodAdapter.java +++ b/hunter-debug-plugin/src/main/groovy/com/quinn/hunter/plugin/debug/bytecode/prego/DebugPreGoMethodAdapter.java @@ -28,7 +28,7 @@ public class DebugPreGoMethodAdapter extends MethodVisitor implements Opcodes { public DebugPreGoMethodAdapter(String methodName,String methodKey, Map> methodParametersMap, MethodVisitor mv, boolean needParameter, DebugPreGoClassAdapter.MethodCollector methodCollector) { - super(Opcodes.ASM7, mv); + super(Opcodes.ASM9, mv); this.methodName = methodName; this.methodKey = methodKey; this.methodParametersMap = methodParametersMap; @@ -52,7 +52,8 @@ public AnnotationVisitor visitAnnotation(String desc, boolean visible) { @Override public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { - if(!"this".equals(name) && start == labelList.get(0) && needParameter) { + if (!"this".equals(name) && needParameter + && !labelList.isEmpty() && start == labelList.get(0)) { Type type = Type.getType(desc); if(type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) { parameters.add(new Parameter(name, "Ljava/lang/Object;", index)); diff --git a/hunter-linelog-example/build.gradle b/hunter-linelog-example/build.gradle index 51089eaa..27b9e6f0 100644 --- a/hunter-linelog-example/build.gradle +++ b/hunter-linelog-example/build.gradle @@ -1,7 +1,15 @@ +buildscript { + dependencies { + classpath Plugins.hunter_linelog_plugin + } +} + apply plugin: 'com.android.application' +apply plugin: 'hunter-linelog' android { - compileSdkVersion Android.compileSdkVersion + namespace 'com.quinn.hunter.linelog' + compileSdk Android.compileSdkVersion buildToolsVersion Android.buildToolsVersion defaultConfig { applicationId "com.quinn.hunter.linelog" @@ -10,31 +18,23 @@ android { versionCode Android.versionCode versionName Android.versionName } - buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } - lintOptions { + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + lint { abortOnError false } - } dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') - implementation 'com.android.support:appcompat-v7:28.0.0' + implementation 'androidx.appcompat:appcompat:1.7.0' implementation Libs.hunter_linelog_library } - -buildscript { - dependencies { - classpath Plugins.hunter_linelog_plugin - classpath Plugins.hunter_transform - } -} - -apply plugin: 'hunter-linelog' - diff --git a/hunter-linelog-example/src/main/AndroidManifest.xml b/hunter-linelog-example/src/main/AndroidManifest.xml index c98ecb6b..0859cf5f 100644 --- a/hunter-linelog-example/src/main/AndroidManifest.xml +++ b/hunter-linelog-example/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - + - + diff --git a/hunter-linelog-library/build.gradle b/hunter-linelog-library/build.gradle index d24514d1..da74f432 100644 --- a/hunter-linelog-library/build.gradle +++ b/hunter-linelog-library/build.gradle @@ -2,6 +2,14 @@ apply plugin: 'com.android.library' apply plugin: "com.vanniktech.maven.publish" android { - compileSdkVersion Android.compileSdkVersion + namespace 'com.hunter.library.linelog' + compileSdk Android.compileSdkVersion buildToolsVersion Android.buildToolsVersion + defaultConfig { + minSdkVersion Android.minSdkVersion + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } } diff --git a/hunter-linelog-library/src/main/AndroidManifest.xml b/hunter-linelog-library/src/main/AndroidManifest.xml index f9d58d4f..94cbbcfc 100644 --- a/hunter-linelog-library/src/main/AndroidManifest.xml +++ b/hunter-linelog-library/src/main/AndroidManifest.xml @@ -1,4 +1 @@ - - - + diff --git a/hunter-linelog-plugin/src/main/groovy/com/quinn/hunter/plugin/linelog/LinelogHunterExtension.java b/hunter-linelog-plugin/src/main/groovy/com/quinn/hunter/plugin/linelog/LinelogHunterExtension.java index 19bee3bc..4e717054 100644 --- a/hunter-linelog-plugin/src/main/groovy/com/quinn/hunter/plugin/linelog/LinelogHunterExtension.java +++ b/hunter-linelog-plugin/src/main/groovy/com/quinn/hunter/plugin/linelog/LinelogHunterExtension.java @@ -2,19 +2,12 @@ import com.quinn.hunter.transform.RunVariant; -/** - * Created by Quinn on 07/10/2018. - */ public class LinelogHunterExtension { public RunVariant runVariant = RunVariant.ALWAYS; - public boolean duplcatedClassSafeMode = false; @Override public String toString() { - return "LinelogHunterExtension{" + - "runVariant=" + runVariant + - ", duplcatedClassSafeMode=" + duplcatedClassSafeMode + - '}'; + return "LinelogHunterExtension{runVariant=" + runVariant + '}'; } } diff --git a/hunter-linelog-plugin/src/main/groovy/com/quinn/hunter/plugin/linelog/LinelogHunterPlugin.java b/hunter-linelog-plugin/src/main/groovy/com/quinn/hunter/plugin/linelog/LinelogHunterPlugin.java index eb5a71ee..dc7d8597 100644 --- a/hunter-linelog-plugin/src/main/groovy/com/quinn/hunter/plugin/linelog/LinelogHunterPlugin.java +++ b/hunter-linelog-plugin/src/main/groovy/com/quinn/hunter/plugin/linelog/LinelogHunterPlugin.java @@ -1,20 +1,29 @@ package com.quinn.hunter.plugin.linelog; -import com.android.build.gradle.AppExtension; -import org.gradle.api.Plugin; +import com.android.build.api.variant.Variant; + +import com.quinn.hunter.plugin.linelog.bytecode.LinelogClassVisitorFactory; +import com.quinn.hunter.transform.HunterParameters; +import com.quinn.hunter.transform.HunterPlugin; + import org.gradle.api.Project; -import java.util.Collections; -/** - * Created by Quinn on 15/09/2018. - */ -public class LinelogHunterPlugin implements Plugin { +public class LinelogHunterPlugin extends HunterPlugin { - @SuppressWarnings("NullableProblems") @Override - public void apply(Project project) { - AppExtension appExtension = (AppExtension)project.getProperties().get("android"); - appExtension.registerTransform(new LinelogHunterTransform(project), Collections.EMPTY_LIST); + protected void registerExtension(Project project) { + project.getExtensions().create("linelogHunterExt", LinelogHunterExtension.class); } + @Override + protected boolean skipVariant(Project project, Variant variant) { + LinelogHunterExtension ext = + (LinelogHunterExtension) project.getExtensions().getByName("linelogHunterExt"); + return ext.runVariant.shouldSkip(variant); + } + + @Override + protected Class getFactoryClass() { + return LinelogClassVisitorFactory.class; + } } diff --git a/hunter-linelog-plugin/src/main/groovy/com/quinn/hunter/plugin/linelog/LinelogHunterTransform.java b/hunter-linelog-plugin/src/main/groovy/com/quinn/hunter/plugin/linelog/LinelogHunterTransform.java deleted file mode 100644 index c22279f6..00000000 --- a/hunter-linelog-plugin/src/main/groovy/com/quinn/hunter/plugin/linelog/LinelogHunterTransform.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.quinn.hunter.plugin.linelog; - -import com.android.build.api.transform.Context; -import com.android.build.api.transform.TransformException; -import com.android.build.api.transform.TransformInput; -import com.android.build.api.transform.TransformOutputProvider; -import com.quinn.hunter.plugin.linelog.bytecode.LinelogWeaver; -import com.quinn.hunter.transform.HunterTransform; -import com.quinn.hunter.transform.RunVariant; - -import org.gradle.api.Project; - -import java.io.IOException; -import java.util.Collection; - - -/** - * Created by Quinn on 15/09/2018. - */ -public final class LinelogHunterTransform extends HunterTransform { - - private Project project; - private LinelogHunterExtension linelogHunterExtension; - - public LinelogHunterTransform(Project project) { - super(project); - this.project = project; - project.getExtensions().create("linelogHunterExt", LinelogHunterExtension.class); - this.bytecodeWeaver = new LinelogWeaver(); - } - - @Override - public void transform(Context context, Collection inputs, Collection referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException { - linelogHunterExtension = (LinelogHunterExtension) project.getExtensions().getByName("linelogHunterExt"); - super.transform(context, inputs, referencedInputs, outputProvider, isIncremental); - } - - @Override - protected RunVariant getRunVariant() { - return linelogHunterExtension.runVariant; - } - -} diff --git a/hunter-linelog-plugin/src/main/groovy/com/quinn/hunter/plugin/linelog/bytecode/LinelogClassAdapter.java b/hunter-linelog-plugin/src/main/groovy/com/quinn/hunter/plugin/linelog/bytecode/LinelogClassAdapter.java index 8c1312cf..cc005669 100644 --- a/hunter-linelog-plugin/src/main/groovy/com/quinn/hunter/plugin/linelog/bytecode/LinelogClassAdapter.java +++ b/hunter-linelog-plugin/src/main/groovy/com/quinn/hunter/plugin/linelog/bytecode/LinelogClassAdapter.java @@ -9,16 +9,15 @@ */ public final class LinelogClassAdapter extends ClassVisitor{ - LinelogClassAdapter(final ClassVisitor cv) { - super(Opcodes.ASM7, cv); + public LinelogClassAdapter(final ClassVisitor cv) { + super(Opcodes.ASM9, cv); } @Override public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) { - MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions); + MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); return mv == null ? null : new LinelogMethodAdapter(mv); - } } \ No newline at end of file diff --git a/hunter-linelog-plugin/src/main/groovy/com/quinn/hunter/plugin/linelog/bytecode/LinelogClassVisitorFactory.java b/hunter-linelog-plugin/src/main/groovy/com/quinn/hunter/plugin/linelog/bytecode/LinelogClassVisitorFactory.java new file mode 100644 index 00000000..dc75a9c9 --- /dev/null +++ b/hunter-linelog-plugin/src/main/groovy/com/quinn/hunter/plugin/linelog/bytecode/LinelogClassVisitorFactory.java @@ -0,0 +1,15 @@ +package com.quinn.hunter.plugin.linelog.bytecode; + +import com.android.build.api.instrumentation.ClassContext; + +import com.quinn.hunter.transform.HunterAsmClassVisitorFactory; +import com.quinn.hunter.transform.HunterParameters; +import com.quinn.hunter.transform.asm.IWeaver; + +public abstract class LinelogClassVisitorFactory extends HunterAsmClassVisitorFactory { + + @Override + protected IWeaver createWeaver(ClassContext classContext) { + return new LinelogWeaver(); + } +} diff --git a/hunter-linelog-plugin/src/main/groovy/com/quinn/hunter/plugin/linelog/bytecode/LinelogMethodAdapter.java b/hunter-linelog-plugin/src/main/groovy/com/quinn/hunter/plugin/linelog/bytecode/LinelogMethodAdapter.java index 63dff4ea..068b1003 100644 --- a/hunter-linelog-plugin/src/main/groovy/com/quinn/hunter/plugin/linelog/bytecode/LinelogMethodAdapter.java +++ b/hunter-linelog-plugin/src/main/groovy/com/quinn/hunter/plugin/linelog/bytecode/LinelogMethodAdapter.java @@ -1,21 +1,15 @@ package com.quinn.hunter.plugin.linelog.bytecode; -import com.android.build.gradle.internal.LoggerWrapper; - import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; -/** - * Created by Quinn on 15/09/2018. - */ public final class LinelogMethodAdapter extends MethodVisitor implements Opcodes { private int lineNumber; - private static final LoggerWrapper logger = LoggerWrapper.getLogger(LinelogMethodAdapter.class); public LinelogMethodAdapter(MethodVisitor mv) { - super(Opcodes.ASM7, mv); + super(Opcodes.ASM9, mv); } @Override diff --git a/hunter-linelog-plugin/src/main/groovy/com/quinn/hunter/plugin/linelog/bytecode/LinelogWeaver.java b/hunter-linelog-plugin/src/main/groovy/com/quinn/hunter/plugin/linelog/bytecode/LinelogWeaver.java index 810b67e7..cfcc915e 100644 --- a/hunter-linelog-plugin/src/main/groovy/com/quinn/hunter/plugin/linelog/bytecode/LinelogWeaver.java +++ b/hunter-linelog-plugin/src/main/groovy/com/quinn/hunter/plugin/linelog/bytecode/LinelogWeaver.java @@ -1,32 +1,21 @@ package com.quinn.hunter.plugin.linelog.bytecode; -import com.android.build.gradle.internal.LoggerWrapper; import com.quinn.hunter.transform.asm.BaseWeaver; + import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.ClassWriter; -/** - * Created by Quinn on 09/07/2017. - */ public final class LinelogWeaver extends BaseWeaver { - private static final String PLUGIN_LIBRARY = "com.hunter.library.linelog"; - private static final LoggerWrapper logger = LoggerWrapper.getLogger(LinelogWeaver.class); + private static final String PLUGIN_LIBRARY_PREFIX = "com.hunter.library.linelog"; @Override - public void setExtension(Object extension) { + public boolean isWeavableClass(String fullyQualifiedClassName) { + return super.isWeavableClass(fullyQualifiedClassName) + && !fullyQualifiedClassName.startsWith(PLUGIN_LIBRARY_PREFIX); } @Override - public boolean isWeavableClass(String fullQualifiedClassName) { - boolean superResult = super.isWeavableClass(fullQualifiedClassName); - boolean isByteCodePlugin = fullQualifiedClassName.startsWith(PLUGIN_LIBRARY); - return superResult && !isByteCodePlugin; + public ClassVisitor wrap(ClassVisitor next) { + return new LinelogClassAdapter(next); } - - @Override - protected ClassVisitor wrapClassWriter(ClassWriter classWriter) { - return new LinelogClassAdapter(classWriter); - } - } diff --git a/hunter-okhttp-example/build.gradle b/hunter-okhttp-example/build.gradle index 3daba8b3..86602e98 100644 --- a/hunter-okhttp-example/build.gradle +++ b/hunter-okhttp-example/build.gradle @@ -1,7 +1,15 @@ +buildscript { + dependencies { + classpath Plugins.hunter_okhttp_plugin + } +} + apply plugin: 'com.android.application' +apply plugin: 'hunter-okhttp' android { - compileSdkVersion Android.compileSdkVersion + namespace 'com.quinn.hunter.okhttp' + compileSdk Android.compileSdkVersion buildToolsVersion Android.buildToolsVersion defaultConfig { applicationId "com.quinn.hunter.okhttp" @@ -10,35 +18,24 @@ android { versionCode Android.versionCode versionName Android.versionName } - buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } - lintOptions { + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + lint { abortOnError false } - } - dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') - implementation 'com.android.support:appcompat-v7:28.0.0' - //noinspection GradleDependency - implementation 'com.squareup.okhttp3:okhttp:3.11.0' + implementation 'androidx.appcompat:appcompat:1.7.0' + implementation 'com.squareup.okhttp3:okhttp:4.12.0' implementation Libs.hunter_okhttp_library } - -buildscript { - dependencies { - classpath Plugins.hunter_okhttp_plugin - classpath Plugins.hunter_transform - } -} - - -apply plugin: 'hunter-okhttp' - diff --git a/hunter-okhttp-example/src/main/AndroidManifest.xml b/hunter-okhttp-example/src/main/AndroidManifest.xml index 033a26d4..aeb87cd3 100644 --- a/hunter-okhttp-example/src/main/AndroidManifest.xml +++ b/hunter-okhttp-example/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - + @@ -16,7 +15,8 @@ android:supportsRtl="true" android:theme="@style/AppTheme"> - + diff --git a/hunter-okhttp-library-compat/build.gradle b/hunter-okhttp-library-compat/build.gradle index c2046ef3..243dba30 100644 --- a/hunter-okhttp-library-compat/build.gradle +++ b/hunter-okhttp-library-compat/build.gradle @@ -2,12 +2,23 @@ apply plugin: 'com.android.library' apply plugin: "com.vanniktech.maven.publish" android { - compileSdkVersion Android.compileSdkVersion + namespace 'com.hunter.library.okhttp.compat' + compileSdk Android.compileSdkVersion buildToolsVersion Android.buildToolsVersion + defaultConfig { + minSdkVersion Android.minSdkVersion + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) + // The compat artifact targets okhttp < 3.11 (older field layout). Pinned + // to 3.6.0 on purpose to match the legacy bytecode layout the okhttp + // plugin instruments. //noinspection GradleDependency implementation 'com.squareup.okhttp3:okhttp:3.6.0' -} \ No newline at end of file +} diff --git a/hunter-okhttp-library-compat/src/main/AndroidManifest.xml b/hunter-okhttp-library-compat/src/main/AndroidManifest.xml index c7edd639..94cbbcfc 100644 --- a/hunter-okhttp-library-compat/src/main/AndroidManifest.xml +++ b/hunter-okhttp-library-compat/src/main/AndroidManifest.xml @@ -1,4 +1 @@ - - - + diff --git a/hunter-okhttp-library/build.gradle b/hunter-okhttp-library/build.gradle index 7a100df7..a637f66b 100644 --- a/hunter-okhttp-library/build.gradle +++ b/hunter-okhttp-library/build.gradle @@ -2,11 +2,19 @@ apply plugin: 'com.android.library' apply plugin: "com.vanniktech.maven.publish" android { - compileSdkVersion Android.compileSdkVersion + namespace 'com.hunter.library.okhttp' + compileSdk Android.compileSdkVersion buildToolsVersion Android.buildToolsVersion + defaultConfig { + minSdkVersion Android.minSdkVersion + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } } + dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - //noinspection GradleDependency - implementation 'com.squareup.okhttp3:okhttp:3.11.0' -} \ No newline at end of file + implementation 'com.squareup.okhttp3:okhttp:4.12.0' +} diff --git a/hunter-okhttp-library/src/main/AndroidManifest.xml b/hunter-okhttp-library/src/main/AndroidManifest.xml index c7edd639..94cbbcfc 100644 --- a/hunter-okhttp-library/src/main/AndroidManifest.xml +++ b/hunter-okhttp-library/src/main/AndroidManifest.xml @@ -1,4 +1 @@ - - - + diff --git a/hunter-okhttp-plugin/src/main/groovy/com/quinn/hunter/plugin/okhttp/OkHttpHunterExtension.java b/hunter-okhttp-plugin/src/main/groovy/com/quinn/hunter/plugin/okhttp/OkHttpHunterExtension.java index 2ac14ef4..b3de00d1 100644 --- a/hunter-okhttp-plugin/src/main/groovy/com/quinn/hunter/plugin/okhttp/OkHttpHunterExtension.java +++ b/hunter-okhttp-plugin/src/main/groovy/com/quinn/hunter/plugin/okhttp/OkHttpHunterExtension.java @@ -2,22 +2,16 @@ import com.quinn.hunter.transform.RunVariant; -/** - * Created by Quinn on 07/10/2018. - */ public class OkHttpHunterExtension { public RunVariant runVariant = RunVariant.ALWAYS; public boolean weaveEventListener = true; - public boolean duplicatedClassSafeMode = false; @Override public String toString() { return "OkHttpHunterExtension{" + "runVariant=" + runVariant + ", weaveEventListener=" + weaveEventListener + - ", duplicatedClassSafeMode=" + duplicatedClassSafeMode + '}'; } - } diff --git a/hunter-okhttp-plugin/src/main/groovy/com/quinn/hunter/plugin/okhttp/OkHttpHunterParameters.java b/hunter-okhttp-plugin/src/main/groovy/com/quinn/hunter/plugin/okhttp/OkHttpHunterParameters.java new file mode 100644 index 00000000..0db230c0 --- /dev/null +++ b/hunter-okhttp-plugin/src/main/groovy/com/quinn/hunter/plugin/okhttp/OkHttpHunterParameters.java @@ -0,0 +1,12 @@ +package com.quinn.hunter.plugin.okhttp; + +import com.quinn.hunter.transform.HunterParameters; + +import org.gradle.api.provider.Property; +import org.gradle.api.tasks.Input; + +public interface OkHttpHunterParameters extends HunterParameters { + + @Input + Property getWeaveEventListener(); +} diff --git a/hunter-okhttp-plugin/src/main/groovy/com/quinn/hunter/plugin/okhttp/OkHttpHunterPlugin.java b/hunter-okhttp-plugin/src/main/groovy/com/quinn/hunter/plugin/okhttp/OkHttpHunterPlugin.java index 34c615cc..1eee094d 100644 --- a/hunter-okhttp-plugin/src/main/groovy/com/quinn/hunter/plugin/okhttp/OkHttpHunterPlugin.java +++ b/hunter-okhttp-plugin/src/main/groovy/com/quinn/hunter/plugin/okhttp/OkHttpHunterPlugin.java @@ -1,20 +1,36 @@ package com.quinn.hunter.plugin.okhttp; -import com.android.build.gradle.AppExtension; -import org.gradle.api.Plugin; +import com.android.build.api.variant.Variant; + +import com.quinn.hunter.plugin.okhttp.bytecode.OkHttpClassVisitorFactory; +import com.quinn.hunter.transform.HunterPlugin; + import org.gradle.api.Project; -import java.util.Collections; -/** - * Created by Quinn on 09/09/2018. - */ -public final class OkHttpHunterPlugin implements Plugin { +public final class OkHttpHunterPlugin extends HunterPlugin { + + @Override + protected void registerExtension(Project project) { + project.getExtensions().create("okHttpHunterExt", OkHttpHunterExtension.class); + } + + @Override + protected boolean skipVariant(Project project, Variant variant) { + OkHttpHunterExtension ext = + (OkHttpHunterExtension) project.getExtensions().getByName("okHttpHunterExt"); + return ext.runVariant.shouldSkip(variant); + } - @SuppressWarnings("NullableProblems") @Override - public void apply(Project project) { - AppExtension appExtension = (AppExtension)project.getProperties().get("android"); - appExtension.registerTransform(new OkHttpHunterTransform(project), Collections.EMPTY_LIST); + protected void configureParams(Project project, Variant variant, OkHttpHunterParameters params) { + super.configureParams(project, variant, params); + OkHttpHunterExtension ext = + (OkHttpHunterExtension) project.getExtensions().getByName("okHttpHunterExt"); + params.getWeaveEventListener().set(ext.weaveEventListener); } + @Override + protected Class getFactoryClass() { + return OkHttpClassVisitorFactory.class; + } } diff --git a/hunter-okhttp-plugin/src/main/groovy/com/quinn/hunter/plugin/okhttp/OkHttpHunterTransform.java b/hunter-okhttp-plugin/src/main/groovy/com/quinn/hunter/plugin/okhttp/OkHttpHunterTransform.java deleted file mode 100644 index 9367c966..00000000 --- a/hunter-okhttp-plugin/src/main/groovy/com/quinn/hunter/plugin/okhttp/OkHttpHunterTransform.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.quinn.hunter.plugin.okhttp; - -import com.android.build.api.transform.Context; -import com.android.build.api.transform.TransformException; -import com.android.build.api.transform.TransformInput; -import com.android.build.api.transform.TransformOutputProvider; -import com.quinn.hunter.plugin.okhttp.bytecode.OkHttpWeaver; -import com.quinn.hunter.transform.HunterTransform; -import com.quinn.hunter.transform.RunVariant; - -import org.gradle.api.Project; - -import java.io.IOException; -import java.util.Collection; - -/** - * Created by Quinn on 09/09/2018. - */ -final class OkHttpHunterTransform extends HunterTransform { - - private Project project; - private OkHttpHunterExtension okHttpHunterExtension; - - public OkHttpHunterTransform(Project project) { - super(project); - this.project = project; - project.getExtensions().create("okHttpHunterExt", OkHttpHunterExtension.class); - this.bytecodeWeaver = new OkHttpWeaver(); - } - - @Override - public void transform(Context context, Collection inputs, Collection referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException { - okHttpHunterExtension = (OkHttpHunterExtension) project.getExtensions().getByName("okHttpHunterExt"); - this.bytecodeWeaver.setExtension(okHttpHunterExtension); - super.transform(context, inputs, referencedInputs, outputProvider, isIncremental); - } - - @Override - protected RunVariant getRunVariant() { - return okHttpHunterExtension.runVariant; - } - - @Override - protected boolean inDuplicatedClassSafeMode() { - return okHttpHunterExtension.duplicatedClassSafeMode; - } -} diff --git a/hunter-okhttp-plugin/src/main/groovy/com/quinn/hunter/plugin/okhttp/bytecode/OkHttpClassAdapter.java b/hunter-okhttp-plugin/src/main/groovy/com/quinn/hunter/plugin/okhttp/bytecode/OkHttpClassAdapter.java index 2018ff5b..9775f37c 100644 --- a/hunter-okhttp-plugin/src/main/groovy/com/quinn/hunter/plugin/okhttp/bytecode/OkHttpClassAdapter.java +++ b/hunter-okhttp-plugin/src/main/groovy/com/quinn/hunter/plugin/okhttp/bytecode/OkHttpClassAdapter.java @@ -4,17 +4,13 @@ import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; -/** - * Created by Quinn on 09/09/2018. - */ -public final class OkHttpClassAdapter extends ClassVisitor{ +public final class OkHttpClassAdapter extends ClassVisitor { private String className; + private final boolean weaveEventListener; - private boolean weaveEventListener; - - OkHttpClassAdapter(final ClassVisitor cv, boolean weaveEventListener) { - super(Opcodes.ASM7, cv); + public OkHttpClassAdapter(final ClassVisitor cv, boolean weaveEventListener) { + super(Opcodes.ASM9, cv); this.weaveEventListener = weaveEventListener; } @@ -27,12 +23,10 @@ public void visit(int version, int access, String name, String signature, String @Override public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) { - MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions); - if(className.equals("okhttp3/OkHttpClient$Builder") && name.equals("")) { - return mv == null ? null : new OkHttpMethodAdapter(access, desc, mv, weaveEventListener); - } else { - return mv; + MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); + if (mv != null && "okhttp3/OkHttpClient$Builder".equals(className) && "".equals(name)) { + return new OkHttpMethodAdapter(access, desc, mv, weaveEventListener); } + return mv; } - -} \ No newline at end of file +} diff --git a/hunter-okhttp-plugin/src/main/groovy/com/quinn/hunter/plugin/okhttp/bytecode/OkHttpClassVisitorFactory.java b/hunter-okhttp-plugin/src/main/groovy/com/quinn/hunter/plugin/okhttp/bytecode/OkHttpClassVisitorFactory.java new file mode 100644 index 00000000..c7857e44 --- /dev/null +++ b/hunter-okhttp-plugin/src/main/groovy/com/quinn/hunter/plugin/okhttp/bytecode/OkHttpClassVisitorFactory.java @@ -0,0 +1,16 @@ +package com.quinn.hunter.plugin.okhttp.bytecode; + +import com.android.build.api.instrumentation.ClassContext; + +import com.quinn.hunter.plugin.okhttp.OkHttpHunterParameters; +import com.quinn.hunter.transform.HunterAsmClassVisitorFactory; +import com.quinn.hunter.transform.asm.IWeaver; + +public abstract class OkHttpClassVisitorFactory extends HunterAsmClassVisitorFactory { + + @Override + protected IWeaver createWeaver(ClassContext classContext) { + boolean weaveEventListener = getParameters().get().getWeaveEventListener().getOrElse(Boolean.TRUE); + return new OkHttpWeaver(weaveEventListener); + } +} diff --git a/hunter-okhttp-plugin/src/main/groovy/com/quinn/hunter/plugin/okhttp/bytecode/OkHttpMethodAdapter.java b/hunter-okhttp-plugin/src/main/groovy/com/quinn/hunter/plugin/okhttp/bytecode/OkHttpMethodAdapter.java index b152e4ce..666cf6cf 100644 --- a/hunter-okhttp-plugin/src/main/groovy/com/quinn/hunter/plugin/okhttp/bytecode/OkHttpMethodAdapter.java +++ b/hunter-okhttp-plugin/src/main/groovy/com/quinn/hunter/plugin/okhttp/bytecode/OkHttpMethodAdapter.java @@ -1,23 +1,18 @@ package com.quinn.hunter.plugin.okhttp.bytecode; -import com.android.build.gradle.internal.LoggerWrapper; - import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.commons.LocalVariablesSorter; /** - * Only weave okhttp3/OkHttpClient's init method - * Created by Quinn on 09/09/2018. + * Only weaves okhttp3/OkHttpClient$Builder's <init>. */ public final class OkHttpMethodAdapter extends LocalVariablesSorter implements Opcodes { - private static final LoggerWrapper logger = LoggerWrapper.getLogger(OkHttpMethodAdapter.class); - - private boolean weaveEventListener; + private final boolean weaveEventListener; - OkHttpMethodAdapter(int access, String desc, MethodVisitor mv, boolean weaveEventListener) { - super(Opcodes.ASM7, access, desc, mv); + public OkHttpMethodAdapter(int access, String desc, MethodVisitor mv, boolean weaveEventListener) { + super(Opcodes.ASM9, access, desc, mv); this.weaveEventListener = weaveEventListener; } diff --git a/hunter-okhttp-plugin/src/main/groovy/com/quinn/hunter/plugin/okhttp/bytecode/OkHttpWeaver.java b/hunter-okhttp-plugin/src/main/groovy/com/quinn/hunter/plugin/okhttp/bytecode/OkHttpWeaver.java index 808c0d2d..16e5871d 100644 --- a/hunter-okhttp-plugin/src/main/groovy/com/quinn/hunter/plugin/okhttp/bytecode/OkHttpWeaver.java +++ b/hunter-okhttp-plugin/src/main/groovy/com/quinn/hunter/plugin/okhttp/bytecode/OkHttpWeaver.java @@ -1,26 +1,28 @@ package com.quinn.hunter.plugin.okhttp.bytecode; -import com.quinn.hunter.plugin.okhttp.OkHttpHunterExtension; import com.quinn.hunter.transform.asm.BaseWeaver; + import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.ClassWriter; -/** - * Created by Quinn on 09/09/2018. - */ public final class OkHttpWeaver extends BaseWeaver { - private OkHttpHunterExtension okHttpHunterExtension; + private final boolean weaveEventListener; - @Override - public void setExtension(Object extension) { - if(extension == null) return; - this.okHttpHunterExtension = (OkHttpHunterExtension) extension; + public OkHttpWeaver(boolean weaveEventListener) { + this.weaveEventListener = weaveEventListener; } @Override - protected ClassVisitor wrapClassWriter(ClassWriter classWriter) { - return new OkHttpClassAdapter(classWriter, this.okHttpHunterExtension.weaveEventListener); + public boolean isWeavableClass(String fullyQualifiedClassName) { + if (!super.isWeavableClass(fullyQualifiedClassName)) return false; + // Only the OkHttpClient.Builder class actually needs visiting; AGP still + // calls isInstrumentable for every class so this short-circuits the + // expensive ClassVisitor chain on the rest of the project. + return "okhttp3.OkHttpClient$Builder".equals(fullyQualifiedClassName); } + @Override + public ClassVisitor wrap(ClassVisitor next) { + return new OkHttpClassAdapter(next, weaveEventListener); + } } diff --git a/hunter-timing-example/build.gradle b/hunter-timing-example/build.gradle index 6435cb76..ea42a7ed 100644 --- a/hunter-timing-example/build.gradle +++ b/hunter-timing-example/build.gradle @@ -1,7 +1,15 @@ +buildscript { + dependencies { + classpath Plugins.hunter_timing_plugin + } +} + apply plugin: 'com.android.application' +apply plugin: 'hunter-timing' android { - compileSdkVersion Android.compileSdkVersion + namespace 'com.quinn.hunter.timing' + compileSdk Android.compileSdkVersion buildToolsVersion Android.buildToolsVersion defaultConfig { applicationId "com.quinn.hunter.timing" @@ -10,39 +18,30 @@ android { versionCode Android.versionCode versionName Android.versionName } - buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } - lintOptions { + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + lint { abortOnError false } - } - dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') - implementation 'com.android.support:appcompat-v7:28.0.0' - implementation 'com.squareup.okhttp3:okhttp:3.11.0' + implementation 'androidx.appcompat:appcompat:1.7.0' + implementation 'com.squareup.okhttp3:okhttp:4.12.0' implementation Libs.hunter_timing_library } -buildscript { - dependencies { - classpath Plugins.hunter_timing_plugin - classpath Plugins.hunter_transform - } -} - - -apply plugin: 'hunter-timing' - timingHunterExt { - runVariant = 'DEBUG' + runVariant = com.quinn.hunter.transform.RunVariant.DEBUG // whitelist = ['com.quinn.hunter.timing.DataSource'] // blacklist = ['com.quinn.hunter.timing.black'] } diff --git a/hunter-timing-example/src/main/AndroidManifest.xml b/hunter-timing-example/src/main/AndroidManifest.xml index 87eba33d..f5c26138 100644 --- a/hunter-timing-example/src/main/AndroidManifest.xml +++ b/hunter-timing-example/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - + - + diff --git a/hunter-timing-library/build.gradle b/hunter-timing-library/build.gradle index d24514d1..a6bad63c 100644 --- a/hunter-timing-library/build.gradle +++ b/hunter-timing-library/build.gradle @@ -2,6 +2,14 @@ apply plugin: 'com.android.library' apply plugin: "com.vanniktech.maven.publish" android { - compileSdkVersion Android.compileSdkVersion + namespace 'com.hunter.library.timing' + compileSdk Android.compileSdkVersion buildToolsVersion Android.buildToolsVersion + defaultConfig { + minSdkVersion Android.minSdkVersion + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } } diff --git a/hunter-timing-library/src/main/AndroidManifest.xml b/hunter-timing-library/src/main/AndroidManifest.xml index 09575fdd..94cbbcfc 100644 --- a/hunter-timing-library/src/main/AndroidManifest.xml +++ b/hunter-timing-library/src/main/AndroidManifest.xml @@ -1,4 +1 @@ - - - + diff --git a/hunter-timing-plugin/src/main/groovy/com/quinn/hunter/plugin/timing/TimingHunterExtension.java b/hunter-timing-plugin/src/main/groovy/com/quinn/hunter/plugin/timing/TimingHunterExtension.java index 00c68d1a..a507b833 100644 --- a/hunter-timing-plugin/src/main/groovy/com/quinn/hunter/plugin/timing/TimingHunterExtension.java +++ b/hunter-timing-plugin/src/main/groovy/com/quinn/hunter/plugin/timing/TimingHunterExtension.java @@ -6,15 +6,13 @@ import java.util.List; /** - * Created by quinn on 27/06/2017. - * whitelist is prior to to blacklist + * whitelist takes precedence over blacklist. */ public class TimingHunterExtension { public RunVariant runVariant = RunVariant.ALWAYS; public List whitelist = new ArrayList<>(); public List blacklist = new ArrayList<>(); - public boolean duplicatedClassSafeMode = false; @Override public String toString() { @@ -22,7 +20,6 @@ public String toString() { "runVariant=" + runVariant + ", whitelist=" + whitelist + ", blacklist=" + blacklist + - ", duplicatedClassSafeMode=" + duplicatedClassSafeMode + '}'; } } diff --git a/hunter-timing-plugin/src/main/groovy/com/quinn/hunter/plugin/timing/TimingHunterParameters.java b/hunter-timing-plugin/src/main/groovy/com/quinn/hunter/plugin/timing/TimingHunterParameters.java new file mode 100644 index 00000000..9072d07d --- /dev/null +++ b/hunter-timing-plugin/src/main/groovy/com/quinn/hunter/plugin/timing/TimingHunterParameters.java @@ -0,0 +1,15 @@ +package com.quinn.hunter.plugin.timing; + +import com.quinn.hunter.transform.HunterParameters; + +import org.gradle.api.provider.ListProperty; +import org.gradle.api.tasks.Input; + +public interface TimingHunterParameters extends HunterParameters { + + @Input + ListProperty getWhitelist(); + + @Input + ListProperty getBlacklist(); +} diff --git a/hunter-timing-plugin/src/main/groovy/com/quinn/hunter/plugin/timing/TimingHunterPlugin.java b/hunter-timing-plugin/src/main/groovy/com/quinn/hunter/plugin/timing/TimingHunterPlugin.java index b8816dec..6115e1c3 100644 --- a/hunter-timing-plugin/src/main/groovy/com/quinn/hunter/plugin/timing/TimingHunterPlugin.java +++ b/hunter-timing-plugin/src/main/groovy/com/quinn/hunter/plugin/timing/TimingHunterPlugin.java @@ -1,20 +1,37 @@ package com.quinn.hunter.plugin.timing; -import com.android.build.gradle.AppExtension; -import org.gradle.api.Plugin; +import com.android.build.api.variant.Variant; + +import com.quinn.hunter.plugin.timing.bytecode.TimingClassVisitorFactory; +import com.quinn.hunter.transform.HunterPlugin; + import org.gradle.api.Project; -import java.util.Collections; -/** - * Created by Quinn on 25/02/2017. - */ -public class TimingHunterPlugin implements Plugin { +public class TimingHunterPlugin extends HunterPlugin { + + @Override + protected void registerExtension(Project project) { + project.getExtensions().create("timingHunterExt", TimingHunterExtension.class); + } + + @Override + protected boolean skipVariant(Project project, Variant variant) { + TimingHunterExtension ext = + (TimingHunterExtension) project.getExtensions().getByName("timingHunterExt"); + return ext.runVariant.shouldSkip(variant); + } - @SuppressWarnings("NullableProblems") @Override - public void apply(Project project) { - AppExtension appExtension = (AppExtension)project.getProperties().get("android"); - appExtension.registerTransform(new TimingHunterTransform(project), Collections.EMPTY_LIST); + protected void configureParams(Project project, Variant variant, TimingHunterParameters params) { + super.configureParams(project, variant, params); + TimingHunterExtension ext = + (TimingHunterExtension) project.getExtensions().getByName("timingHunterExt"); + params.getWhitelist().set(ext.whitelist); + params.getBlacklist().set(ext.blacklist); } + @Override + protected Class getFactoryClass() { + return TimingClassVisitorFactory.class; + } } diff --git a/hunter-timing-plugin/src/main/groovy/com/quinn/hunter/plugin/timing/TimingHunterTransform.java b/hunter-timing-plugin/src/main/groovy/com/quinn/hunter/plugin/timing/TimingHunterTransform.java deleted file mode 100644 index e7efe6b1..00000000 --- a/hunter-timing-plugin/src/main/groovy/com/quinn/hunter/plugin/timing/TimingHunterTransform.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.quinn.hunter.plugin.timing; - -import com.android.build.api.transform.Context; -import com.android.build.api.transform.TransformException; -import com.android.build.api.transform.TransformInput; -import com.android.build.api.transform.TransformOutputProvider; -import com.quinn.hunter.plugin.timing.bytecode.TimingWeaver; -import com.quinn.hunter.transform.HunterTransform; -import com.quinn.hunter.transform.RunVariant; - -import org.gradle.api.Project; - -import java.io.IOException; -import java.util.Collection; - -/** - * Created by quinn on 07/09/2018 - */ -public final class TimingHunterTransform extends HunterTransform { - - private Project project; - private TimingHunterExtension timingHunterExtension; - - public TimingHunterTransform(Project project) { - super(project); - this.project = project; - project.getExtensions().create("timingHunterExt", TimingHunterExtension.class); - this.bytecodeWeaver = new TimingWeaver(); - } - - @Override - public void transform(Context context, Collection inputs, Collection referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException { - timingHunterExtension = (TimingHunterExtension) project.getExtensions().getByName("timingHunterExt"); - bytecodeWeaver.setExtension(timingHunterExtension); - super.transform(context, inputs, referencedInputs, outputProvider, isIncremental); - } - - protected RunVariant getRunVariant() { - return timingHunterExtension.runVariant; - } - - @Override - protected boolean inDuplicatedClassSafeMode() { - return timingHunterExtension.duplicatedClassSafeMode; - } -} diff --git a/hunter-timing-plugin/src/main/groovy/com/quinn/hunter/plugin/timing/bytecode/TimingClassAdapter.java b/hunter-timing-plugin/src/main/groovy/com/quinn/hunter/plugin/timing/bytecode/TimingClassAdapter.java index 80582a8c..2191b01f 100644 --- a/hunter-timing-plugin/src/main/groovy/com/quinn/hunter/plugin/timing/bytecode/TimingClassAdapter.java +++ b/hunter-timing-plugin/src/main/groovy/com/quinn/hunter/plugin/timing/bytecode/TimingClassAdapter.java @@ -7,14 +7,13 @@ import java.io.File; import java.util.Arrays; -public final class TimingClassAdapter extends ClassVisitor{ +public final class TimingClassAdapter extends ClassVisitor { private String className; - private boolean isHeritedFromBlockHandler = false; - TimingClassAdapter(final ClassVisitor cv) { - super(Opcodes.ASM7, cv); + public TimingClassAdapter(final ClassVisitor cv) { + super(Opcodes.ASM9, cv); } @Override @@ -27,14 +26,17 @@ public void visit(int version, int access, String name, String signature, String @Override public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) { - MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions); - if(isHeritedFromBlockHandler) { + MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); + if (mv == null || isHeritedFromBlockHandler) { + return mv; + } + // Issue #48: constructors require deferring the timing-start probe until + // after the mandatory super()/this() call; abstract / native methods have + // no body so we leave them untouched. + if ((access & (Opcodes.ACC_ABSTRACT | Opcodes.ACC_NATIVE)) != 0) { return mv; - } else { - return mv == null ? null : new com.quinn.hunter.plugin.timing.bytecode.TimingMethodAdapter(className + File.separator + name, access, desc, mv); } + boolean isConstructor = "".equals(name); + return new TimingMethodAdapter(className + File.separator + name, access, desc, mv, isConstructor); } - - - -} \ No newline at end of file +} diff --git a/hunter-timing-plugin/src/main/groovy/com/quinn/hunter/plugin/timing/bytecode/TimingClassVisitorFactory.java b/hunter-timing-plugin/src/main/groovy/com/quinn/hunter/plugin/timing/bytecode/TimingClassVisitorFactory.java new file mode 100644 index 00000000..1e28a047 --- /dev/null +++ b/hunter-timing-plugin/src/main/groovy/com/quinn/hunter/plugin/timing/bytecode/TimingClassVisitorFactory.java @@ -0,0 +1,19 @@ +package com.quinn.hunter.plugin.timing.bytecode; + +import com.android.build.api.instrumentation.ClassContext; + +import com.quinn.hunter.plugin.timing.TimingHunterParameters; +import com.quinn.hunter.transform.HunterAsmClassVisitorFactory; +import com.quinn.hunter.transform.asm.IWeaver; + +public abstract class TimingClassVisitorFactory + extends HunterAsmClassVisitorFactory { + + @Override + protected IWeaver createWeaver(ClassContext classContext) { + TimingHunterParameters params = getParameters().get(); + return new TimingWeaver( + params.getWhitelist().getOrElse(java.util.Collections.emptyList()), + params.getBlacklist().getOrElse(java.util.Collections.emptyList())); + } +} diff --git a/hunter-timing-plugin/src/main/groovy/com/quinn/hunter/plugin/timing/bytecode/TimingMethodAdapter.java b/hunter-timing-plugin/src/main/groovy/com/quinn/hunter/plugin/timing/bytecode/TimingMethodAdapter.java index 4f6f6343..f2ebef38 100644 --- a/hunter-timing-plugin/src/main/groovy/com/quinn/hunter/plugin/timing/bytecode/TimingMethodAdapter.java +++ b/hunter-timing-plugin/src/main/groovy/com/quinn/hunter/plugin/timing/bytecode/TimingMethodAdapter.java @@ -1,42 +1,82 @@ package com.quinn.hunter.plugin.timing.bytecode; -import org.objectweb.asm.Opcodes; import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.commons.LocalVariablesSorter; public final class TimingMethodAdapter extends LocalVariablesSorter implements Opcodes { - private int startVarIndex; + private final String methodName; + private final boolean isConstructor; - private String methodName; + private int startVarIndex = -1; + // For constructors, defer probe injection until after the mandatory + // super(...)/this(...) call. Tracks new/dup pairs so we only count the + // *outermost* INVOKESPECIAL . + private int newDepth = 0; + private boolean superCallSeen = false; - public TimingMethodAdapter(String name, int access, String desc, MethodVisitor mv) { - super(Opcodes.ASM7, access, desc, mv); + public TimingMethodAdapter(String name, int access, String desc, MethodVisitor mv, boolean isConstructor) { + super(Opcodes.ASM9, access, desc, mv); this.methodName = name.replace("/", "."); + this.isConstructor = isConstructor; } @Override public void visitCode() { super.visitCode(); - mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false); - startVarIndex = newLocal(Type.LONG_TYPE); - mv.visitVarInsn(Opcodes.LSTORE, startVarIndex); + if (!isConstructor) { + emitProbeStart(); + } + } + + @Override + public void visitTypeInsn(int opcode, String type) { + if (isConstructor && !superCallSeen && opcode == NEW) { + newDepth++; + } + super.visitTypeInsn(opcode, type); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) { + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); + if (isConstructor && !superCallSeen + && opcode == INVOKESPECIAL && "".equals(name)) { + if (newDepth > 0) { + newDepth--; + } else { + // This is the mandatory super(...)/this(...) call; safe to probe now. + superCallSeen = true; + emitProbeStart(); + } + } } @Override public void visitInsn(int opcode) { if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) { - mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false); - mv.visitVarInsn(LLOAD, startVarIndex); - mv.visitInsn(LSUB); - int index = newLocal(Type.LONG_TYPE); - mv.visitVarInsn(LSTORE, index); - mv.visitLdcInsn(methodName); - mv.visitVarInsn(LLOAD, index); - mv.visitMethodInsn(INVOKESTATIC, "com/hunter/library/timing/BlockManager", "timingMethod", "(Ljava/lang/String;J)V", false); + // If we never managed to install the start probe (e.g. exotic + // bytecode), skip the end probe to avoid VerifyError. + if (startVarIndex >= 0) { + mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false); + mv.visitVarInsn(LLOAD, startVarIndex); + mv.visitInsn(LSUB); + int index = newLocal(Type.LONG_TYPE); + mv.visitVarInsn(LSTORE, index); + mv.visitLdcInsn(methodName); + mv.visitVarInsn(LLOAD, index); + mv.visitMethodInsn(INVOKESTATIC, "com/hunter/library/timing/BlockManager", + "timingMethod", "(Ljava/lang/String;J)V", false); + } } super.visitInsn(opcode); } + private void emitProbeStart() { + mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false); + startVarIndex = newLocal(Type.LONG_TYPE); + mv.visitVarInsn(LSTORE, startVarIndex); + } } diff --git a/hunter-timing-plugin/src/main/groovy/com/quinn/hunter/plugin/timing/bytecode/TimingWeaver.java b/hunter-timing-plugin/src/main/groovy/com/quinn/hunter/plugin/timing/bytecode/TimingWeaver.java index d957970f..d751ef21 100644 --- a/hunter-timing-plugin/src/main/groovy/com/quinn/hunter/plugin/timing/bytecode/TimingWeaver.java +++ b/hunter-timing-plugin/src/main/groovy/com/quinn/hunter/plugin/timing/bytecode/TimingWeaver.java @@ -1,56 +1,44 @@ package com.quinn.hunter.plugin.timing.bytecode; -import com.quinn.hunter.plugin.timing.TimingHunterExtension; import com.quinn.hunter.transform.asm.BaseWeaver; + import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.ClassWriter; -/** - * Created by Quinn on 09/07/2017. - */ +import java.util.Collections; +import java.util.List; + public final class TimingWeaver extends BaseWeaver { - private static final String PLUGIN_LIBRARY = "com.hunter.library.timing"; + private static final String PLUGIN_LIBRARY_PREFIX = "com.hunter.library.timing"; - private TimingHunterExtension timingHunterExtension; + private final List whitelist; + private final List blacklist; - @Override - public void setExtension(Object extension) { - if(extension == null) return; - this.timingHunterExtension = (TimingHunterExtension) extension; + public TimingWeaver(List whitelist, List blacklist) { + this.whitelist = whitelist != null ? whitelist : Collections.emptyList(); + this.blacklist = blacklist != null ? blacklist : Collections.emptyList(); } @Override - public boolean isWeavableClass(String fullQualifiedClassName) { - boolean superResult = super.isWeavableClass(fullQualifiedClassName); - boolean isByteCodePlugin = fullQualifiedClassName.startsWith(PLUGIN_LIBRARY); - if(timingHunterExtension != null) { - //whitelist is prior to to blacklist - if(!timingHunterExtension.whitelist.isEmpty()) { - boolean inWhiteList = false; - for(String item : timingHunterExtension.whitelist) { - if(fullQualifiedClassName.startsWith(item)) { - inWhiteList = true; - } - } - return superResult && !isByteCodePlugin && inWhiteList; + public boolean isWeavableClass(String fullyQualifiedClassName) { + if (!super.isWeavableClass(fullyQualifiedClassName)) return false; + if (fullyQualifiedClassName.startsWith(PLUGIN_LIBRARY_PREFIX)) return false; + if (!whitelist.isEmpty()) { + for (String item : whitelist) { + if (fullyQualifiedClassName.startsWith(item)) return true; } - if(!timingHunterExtension.blacklist.isEmpty()) { - boolean inBlackList = false; - for(String item : timingHunterExtension.blacklist) { - if(fullQualifiedClassName.startsWith(item)) { - inBlackList = true; - } - } - return superResult && !isByteCodePlugin && !inBlackList; + return false; + } + if (!blacklist.isEmpty()) { + for (String item : blacklist) { + if (fullyQualifiedClassName.startsWith(item)) return false; } } - return superResult && !isByteCodePlugin; + return true; } @Override - protected ClassVisitor wrapClassWriter(ClassWriter classWriter) { - return new TimingClassAdapter(classWriter); + public ClassVisitor wrap(ClassVisitor next) { + return new TimingClassAdapter(next); } - } diff --git a/hunter-transform/build.gradle b/hunter-transform/build.gradle index c8f81d31..da6079d8 100644 --- a/hunter-transform/build.gradle +++ b/hunter-transform/build.gradle @@ -1,3 +1,8 @@ apply plugin: 'java-library' apply plugin: "com.vanniktech.maven.publish" apply from: "../gradle-scripts/base-plugin-dependencies.gradle" + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} diff --git a/hunter-transform/src/main/java/com/quinn/hunter/transform/HunterAsmClassVisitorFactory.java b/hunter-transform/src/main/java/com/quinn/hunter/transform/HunterAsmClassVisitorFactory.java new file mode 100644 index 00000000..8954cb04 --- /dev/null +++ b/hunter-transform/src/main/java/com/quinn/hunter/transform/HunterAsmClassVisitorFactory.java @@ -0,0 +1,69 @@ +package com.quinn.hunter.transform; + +import com.android.build.api.instrumentation.AsmClassVisitorFactory; +import com.android.build.api.instrumentation.ClassContext; +import com.android.build.api.instrumentation.ClassData; + +import com.quinn.hunter.transform.asm.IWeaver; + +import org.objectweb.asm.ClassVisitor; + +/** + * Bridges AGP's {@link AsmClassVisitorFactory} contract to Hunter's + * {@link IWeaver} abstraction so plugin authors can keep writing + * {@link ClassVisitor}-based weavers as they did under the legacy + * Transform API. + * + * Subclasses must: + * - declare a parameters interface extending {@link HunterParameters}; + * - implement {@link #createWeaver(ClassContext)} returning the weaver. + * + * The same factory instance is reused across many {@code isInstrumentable} + * and {@code createClassVisitor} invocations during a build, so the weaver + * is lazily cached to avoid re-reading the {@link HunterParameters} for + * every class. + */ +public abstract class HunterAsmClassVisitorFactory

+ implements AsmClassVisitorFactory

{ + + private volatile IWeaver cachedWeaver; + + @Override + public final ClassVisitor createClassVisitor(ClassContext classContext, ClassVisitor nextClassVisitor) { + IWeaver weaver = createWeaver(classContext); + if (weaver == null) { + return nextClassVisitor; + } + return weaver.wrap(nextClassVisitor); + } + + @Override + public final boolean isInstrumentable(ClassData classData) { + IWeaver weaver = getOrCreateWeaver(); + return weaver != null && weaver.isWeavableClass(classData.getClassName()); + } + + /** + * Create the {@link IWeaver} used to instrument a class. Implementations + * should be cheap to construct. + * + * @param classContext non-null when invoked via {@link #createClassVisitor}, + * null when invoked via {@link #isInstrumentable} (where + * it is unavailable). + */ + protected abstract IWeaver createWeaver(ClassContext classContext); + + private IWeaver getOrCreateWeaver() { + IWeaver local = cachedWeaver; + if (local == null) { + synchronized (this) { + local = cachedWeaver; + if (local == null) { + local = createWeaver(null); + cachedWeaver = local; + } + } + } + return local; + } +} diff --git a/hunter-transform/src/main/java/com/quinn/hunter/transform/HunterParameters.java b/hunter-transform/src/main/java/com/quinn/hunter/transform/HunterParameters.java new file mode 100644 index 00000000..fec144ce --- /dev/null +++ b/hunter-transform/src/main/java/com/quinn/hunter/transform/HunterParameters.java @@ -0,0 +1,19 @@ +package com.quinn.hunter.transform; + +import com.android.build.api.instrumentation.InstrumentationParameters; + +import org.gradle.api.provider.Property; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.Optional; + +/** + * Common Gradle task parameters shared by every Hunter ASM visitor factory. + * Subclass this for plugin-specific parameters; AGP serializes the values + * into the worker process that runs the bytecode transform. + */ +public interface HunterParameters extends InstrumentationParameters { + + @Input + @Optional + Property getVariantName(); +} diff --git a/hunter-transform/src/main/java/com/quinn/hunter/transform/HunterPlugin.java b/hunter-transform/src/main/java/com/quinn/hunter/transform/HunterPlugin.java new file mode 100644 index 00000000..38538f36 --- /dev/null +++ b/hunter-transform/src/main/java/com/quinn/hunter/transform/HunterPlugin.java @@ -0,0 +1,77 @@ +package com.quinn.hunter.transform; + +import com.android.build.api.instrumentation.FramesComputationMode; +import com.android.build.api.instrumentation.InstrumentationScope; +import com.android.build.api.variant.AndroidComponentsExtension; +import com.android.build.api.variant.Variant; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; + +import kotlin.Unit; +import kotlin.jvm.functions.Function1; + +/** + * Base plugin that hides the AGP 7+ instrumentation boilerplate. Subclasses + * declare which {@link HunterAsmClassVisitorFactory} should run, scope, and + * how to populate parameters per variant. + */ +public abstract class HunterPlugin

> implements Plugin { + + @Override + public final void apply(Project project) { + registerExtension(project); + AndroidComponentsExtension androidComponents = + project.getExtensions().findByType(AndroidComponentsExtension.class); + if (androidComponents == null) { + throw new IllegalStateException( + "Hunter plugin '" + getClass().getSimpleName() + "' requires the Android Gradle Plugin" + + " to be applied to the project. Make sure 'com.android.application' or" + + " 'com.android.library' is applied before this plugin."); + } + androidComponents.onVariants(androidComponents.selector().all(), variant -> { + if (skipVariant(project, variant)) { + project.getLogger().info( + "Hunter [{}] skipped for variant '{}'", getClass().getSimpleName(), variant.getName()); + return; + } + Function1 configure = params -> { + configureParams(project, variant, params); + return Unit.INSTANCE; + }; + variant.getInstrumentation().transformClassesWith( + getFactoryClass(), getInstrumentationScope(), configure); + variant.getInstrumentation().setAsmFramesComputationMode(getFramesComputationMode()); + }); + } + + /** Register the user-facing Gradle DSL extension (if any). */ + protected void registerExtension(Project project) { + } + + /** Return false to instrument this variant, true to skip it. */ + protected boolean skipVariant(Project project, Variant variant) { + return false; + } + + /** Wire variant-specific data into the worker parameters. */ + protected void configureParams(Project project, Variant variant, P params) { + params.getVariantName().set(variant.getName()); + } + + /** AGP scope: {@link InstrumentationScope#ALL} (default) or {@link InstrumentationScope#PROJECT}. */ + protected InstrumentationScope getInstrumentationScope() { + return InstrumentationScope.ALL; + } + + /** + * Frame computation mode. Default: recompute frames only for instrumented + * methods, matching the legacy Transform API's {@code COMPUTE_MAXS} cost. + */ + protected FramesComputationMode getFramesComputationMode() { + return FramesComputationMode.COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS; + } + + protected abstract Class getFactoryClass(); +} diff --git a/hunter-transform/src/main/java/com/quinn/hunter/transform/HunterTransform.java b/hunter-transform/src/main/java/com/quinn/hunter/transform/HunterTransform.java deleted file mode 100644 index 1073e6de..00000000 --- a/hunter-transform/src/main/java/com/quinn/hunter/transform/HunterTransform.java +++ /dev/null @@ -1,255 +0,0 @@ -package com.quinn.hunter.transform; - -import com.android.build.api.transform.Context; -import com.android.build.api.transform.DirectoryInput; -import com.android.build.api.transform.Format; -import com.android.build.api.transform.JarInput; -import com.android.build.api.transform.QualifiedContent; -import com.android.build.api.transform.Status; -import com.android.build.api.transform.Transform; -import com.android.build.api.transform.TransformException; -import com.android.build.api.transform.TransformInput; -import com.android.build.api.transform.TransformOutputProvider; -import com.android.build.gradle.internal.pipeline.TransformManager; -import com.quinn.hunter.transform.asm.BaseWeaver; -import com.quinn.hunter.transform.asm.ClassLoaderHelper; -import com.quinn.hunter.transform.concurrent.Schedulers; -import com.quinn.hunter.transform.concurrent.Worker; - -import org.apache.commons.io.FileUtils; -import org.gradle.api.Project; -import org.gradle.api.logging.Logger; - -import java.io.File; -import java.io.IOException; -import java.net.URLClassLoader; -import java.util.Collection; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -/** - * Created by Quinn on 26/02/2017. - * Transform to modify bytecode - */ -public class HunterTransform extends Transform { - - private final Logger logger; - - private static final Set SCOPES = new HashSet<>(); - - static { - SCOPES.add(QualifiedContent.Scope.PROJECT); - SCOPES.add(QualifiedContent.Scope.SUB_PROJECTS); - SCOPES.add(QualifiedContent.Scope.EXTERNAL_LIBRARIES); - } - - private final Project project; - protected BaseWeaver bytecodeWeaver; - private final Worker worker; - private boolean emptyRun = false; - - public HunterTransform(Project project){ - this.project = project; - this.logger = project.getLogger(); - this.worker = Schedulers.IO(); - } - - @Override - public String getName() { - return this.getClass().getSimpleName(); - } - - @Override - public Set getInputTypes() { - return TransformManager.CONTENT_CLASS; - } - - @Override - public Set getScopes() { - return SCOPES; - } - - @Override - public boolean isIncremental() { - return true; - } - - - @SuppressWarnings("deprecation") - @Override - public void transform(Context context, - Collection inputs, - Collection referencedInputs, - TransformOutputProvider outputProvider, - boolean isIncremental) throws IOException, TransformException, InterruptedException { - RunVariant runVariant = getRunVariant(); - if("debug".equals(context.getVariantName())) { - emptyRun = runVariant == RunVariant.RELEASE || runVariant == RunVariant.NEVER; - } else if("release".equals(context.getVariantName())) { - emptyRun = runVariant == RunVariant.DEBUG || runVariant == RunVariant.NEVER; - } - logger.warn(getName() + " isIncremental = " + isIncremental + ", runVariant = " - + runVariant + ", emptyRun = " + emptyRun + ", inDuplicatedClassSafeMode = " + inDuplicatedClassSafeMode()); - long startTime = System.currentTimeMillis(); - if(!isIncremental) { - outputProvider.deleteAll(); - } - URLClassLoader urlClassLoader = ClassLoaderHelper.getClassLoader(inputs, referencedInputs, project); - this.bytecodeWeaver.setClassLoader(urlClassLoader); - boolean flagForCleanDexBuilderFolder = false; - for(TransformInput input : inputs) { - for(JarInput jarInput : input.getJarInputs()) { - Status status = jarInput.getStatus(); - File dest = outputProvider.getContentLocation( - jarInput.getFile().getAbsolutePath(), - jarInput.getContentTypes(), - jarInput.getScopes(), - Format.JAR); - if(isIncremental && !emptyRun) { - switch(status) { - case NOTCHANGED: - break; - case ADDED: - case CHANGED: - transformJar(jarInput.getFile(), dest, status); - break; - case REMOVED: - if (dest.exists()) { - FileUtils.forceDelete(dest); - } - break; - } - } else { - //Forgive me!, Some project will store 3rd-party aar for several copies in dexbuilder folder,unknown issue. - if(inDuplicatedClassSafeMode() && !isIncremental && !flagForCleanDexBuilderFolder) { - cleanDexBuilderFolder(dest); - flagForCleanDexBuilderFolder = true; - } - transformJar(jarInput.getFile(), dest, status); - } - } - - for(DirectoryInput directoryInput : input.getDirectoryInputs()) { - File dest = outputProvider.getContentLocation(directoryInput.getName(), - directoryInput.getContentTypes(), directoryInput.getScopes(), - Format.DIRECTORY); - FileUtils.forceMkdir(dest); - if(isIncremental && !emptyRun) { - String srcDirPath = directoryInput.getFile().getAbsolutePath(); - String destDirPath = dest.getAbsolutePath(); - Map fileStatusMap = directoryInput.getChangedFiles(); - for (Map.Entry changedFile : fileStatusMap.entrySet()) { - Status status = changedFile.getValue(); - File inputFile = changedFile.getKey(); - String destFilePath = inputFile.getAbsolutePath().replace(srcDirPath, destDirPath); - File destFile = new File(destFilePath); - switch (status) { - case NOTCHANGED: - break; - case REMOVED: - if(destFile.exists()) { - //noinspection ResultOfMethodCallIgnored - destFile.delete(); - } - break; - case ADDED: - case CHANGED: - try { - FileUtils.touch(destFile); - } catch (IOException e) { - //maybe mkdirs fail for some strange reason, try again. - FileUtils.forceMkdirParent(destFile); - } - transformSingleFile(inputFile, destFile, srcDirPath); - break; - } - } - } else { - transformDir(directoryInput.getFile(), dest); - } - - } - - } - - worker.await(); - long costTime = System.currentTimeMillis() - startTime; - logger.warn((getName() + " costed " + costTime + "ms")); - } - - private void transformSingleFile(final File inputFile, final File outputFile, final String srcBaseDir) { - worker.submit(() -> { - bytecodeWeaver.weaveSingleClassToFile(inputFile, outputFile, srcBaseDir); - return null; - }); - } - - private void transformDir(final File inputDir, final File outputDir) throws IOException { - if(emptyRun) { - FileUtils.copyDirectory(inputDir, outputDir); - return; - } - final String inputDirPath = inputDir.getAbsolutePath(); - final String outputDirPath = outputDir.getAbsolutePath(); - if (inputDir.isDirectory()) { - for (final File file : com.android.utils.FileUtils.getAllFiles(inputDir)) { - worker.submit(() -> { - String filePath = file.getAbsolutePath(); - File outputFile = new File(filePath.replace(inputDirPath, outputDirPath)); - bytecodeWeaver.weaveSingleClassToFile(file, outputFile, inputDirPath); - return null; - }); - } - } - } - - private void transformJar(final File srcJar, final File destJar, Status status) { - worker.submit(() -> { - if(emptyRun) { - FileUtils.copyFile(srcJar, destJar); - return null; - } - bytecodeWeaver.weaveJar(srcJar, destJar); - return null; - }); - } - - private void cleanDexBuilderFolder(File dest) { - worker.submit(() -> { - try { - String dexBuilderDir = replaceLastPart(dest.getAbsolutePath(), getName(), "dexBuilder"); - //intermediates/transforms/dexBuilder/debug - File file = new File(dexBuilderDir).getParentFile(); - project.getLogger().warn("clean dexBuilder folder = " + file.getAbsolutePath()); - if(file.exists() && file.isDirectory()) { - com.android.utils.FileUtils.deleteDirectoryContents(file); - } - } catch (Exception e) { - e.printStackTrace(); - } - return null; - }); - } - - private String replaceLastPart(String originString, String replacement, String toreplace) { - int start = originString.lastIndexOf(replacement); - StringBuilder builder = new StringBuilder(); - builder.append(originString, 0, start); - builder.append(toreplace); - builder.append(originString.substring(start + replacement.length())); - return builder.toString(); - } - - @Override - public boolean isCacheable() { - return true; - } - - protected RunVariant getRunVariant() { - return RunVariant.ALWAYS; - } - - protected boolean inDuplicatedClassSafeMode(){ - return false; - } -} diff --git a/hunter-transform/src/main/java/com/quinn/hunter/transform/RunVariant.java b/hunter-transform/src/main/java/com/quinn/hunter/transform/RunVariant.java index 14539fd7..b9a2f65d 100644 --- a/hunter-transform/src/main/java/com/quinn/hunter/transform/RunVariant.java +++ b/hunter-transform/src/main/java/com/quinn/hunter/transform/RunVariant.java @@ -1,8 +1,24 @@ package com.quinn.hunter.transform; -/** - * Created by Quinn on 10/05/2018 - */ +import com.android.build.api.variant.Variant; + public enum RunVariant { - DEBUG, RELEASE, ALWAYS, NEVER + DEBUG, RELEASE, ALWAYS, NEVER; + + /** Whether this filter should skip the given AGP variant. */ + public boolean shouldSkip(Variant variant) { + String buildType = variant.getBuildType(); + switch (this) { + case ALWAYS: + return false; + case NEVER: + return true; + case DEBUG: + return !"debug".equals(buildType); + case RELEASE: + return "debug".equals(buildType); + default: + return false; + } + } } diff --git a/hunter-transform/src/main/java/com/quinn/hunter/transform/asm/BaseWeaver.java b/hunter-transform/src/main/java/com/quinn/hunter/transform/asm/BaseWeaver.java index d7762c26..eeb8b155 100644 --- a/hunter-transform/src/main/java/com/quinn/hunter/transform/asm/BaseWeaver.java +++ b/hunter-transform/src/main/java/com/quinn/hunter/transform/asm/BaseWeaver.java @@ -1,115 +1,25 @@ package com.quinn.hunter.transform.asm; -import org.apache.commons.io.FileUtils; -import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.ClassWriter; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.attribute.FileTime; -import java.util.Enumeration; -import java.util.zip.CRC32; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; -import java.util.zip.ZipOutputStream; /** - * Created by quinn on 07/09/2018 + * Default {@link IWeaver} implementation. Filters out generated R, R$* and + * BuildConfig classes; subclasses override {@link #wrap(ClassVisitor)} to + * append their own ClassVisitor to the chain that the AGP instrumentation + * pipeline drives. */ -public abstract class BaseWeaver implements IWeaver{ - - private static final FileTime ZERO = FileTime.fromMillis(0); - - private static final String FILE_SEP = File.separator; - - protected ClassLoader classLoader; - - public BaseWeaver() { - } - - public final void weaveJar(File inputJar, File outputJar) throws IOException { - ZipFile inputZip = new ZipFile(inputJar); - ZipOutputStream outputZip = new ZipOutputStream(new BufferedOutputStream( - java.nio.file.Files.newOutputStream(outputJar.toPath()))); - Enumeration inEntries = inputZip.entries(); - while (inEntries.hasMoreElements()) { - ZipEntry entry = inEntries.nextElement(); - InputStream originalFile = - new BufferedInputStream(inputZip.getInputStream(entry)); - ZipEntry outEntry = new ZipEntry(entry.getName()); - byte[] newEntryContent; - // separator of entry name is always '/', even in windows - if (!isWeavableClass(outEntry.getName().replace("/", "."))) { - newEntryContent = org.apache.commons.io.IOUtils.toByteArray(originalFile); - } else { - newEntryContent = weaveSingleClassToByteArray(originalFile); - } - CRC32 crc32 = new CRC32(); - crc32.update(newEntryContent); - outEntry.setCrc(crc32.getValue()); - outEntry.setMethod(ZipEntry.STORED); - outEntry.setSize(newEntryContent.length); - outEntry.setCompressedSize(newEntryContent.length); - outEntry.setLastAccessTime(ZERO); - outEntry.setLastModifiedTime(ZERO); - outEntry.setCreationTime(ZERO); - outputZip.putNextEntry(outEntry); - outputZip.write(newEntryContent); - outputZip.closeEntry(); - } - outputZip.flush(); - outputZip.close(); - inputZip.close(); - } - - public final void weaveSingleClassToFile(File inputFile, File outputFile, String inputBaseDir) throws IOException { - if(!inputBaseDir.endsWith(FILE_SEP)) inputBaseDir = inputBaseDir + FILE_SEP; - if(isWeavableClass(inputFile.getAbsolutePath().replace(inputBaseDir, "").replace(FILE_SEP, "."))) { - FileUtils.touch(outputFile); - InputStream inputStream = new FileInputStream(inputFile); - byte[] bytes = weaveSingleClassToByteArray(inputStream); - FileOutputStream fos = new FileOutputStream(outputFile); - fos.write(bytes); - fos.close(); - inputStream.close(); - } else { - if (inputFile.isFile()) { - FileUtils.touch(outputFile); - FileUtils.copyFile(inputFile, outputFile); - } - } - } - - public final void setClassLoader(ClassLoader classLoader) { - this.classLoader = classLoader; - } +public abstract class BaseWeaver implements IWeaver { @Override - public byte[] weaveSingleClassToByteArray(InputStream inputStream) throws IOException { - ClassReader classReader = new ClassReader(inputStream); - ClassWriter classWriter = new ExtendClassWriter(classLoader, ClassWriter.COMPUTE_MAXS); - ClassVisitor classWriterWrapper = wrapClassWriter(classWriter); - classReader.accept(classWriterWrapper, ClassReader.EXPAND_FRAMES); - return classWriter.toByteArray(); - } - - public void setExtension(Object extension) { - - } - - protected ClassVisitor wrapClassWriter(ClassWriter classWriter) { - return classWriter; + public boolean isWeavableClass(String fullyQualifiedClassName) { + if (fullyQualifiedClassName == null) return false; + return !fullyQualifiedClassName.contains("R$") + && !fullyQualifiedClassName.endsWith(".R") + && !fullyQualifiedClassName.endsWith(".BuildConfig"); } @Override - public boolean isWeavableClass(String fullQualifiedClassName){ - return fullQualifiedClassName.endsWith(".class") && !fullQualifiedClassName.contains("R$") && !fullQualifiedClassName.contains("R.class") && !fullQualifiedClassName.contains("BuildConfig.class"); + public ClassVisitor wrap(ClassVisitor next) { + return next; } - } diff --git a/hunter-transform/src/main/java/com/quinn/hunter/transform/asm/ClassLoaderHelper.java b/hunter-transform/src/main/java/com/quinn/hunter/transform/asm/ClassLoaderHelper.java deleted file mode 100644 index 2967c2f4..00000000 --- a/hunter-transform/src/main/java/com/quinn/hunter/transform/asm/ClassLoaderHelper.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.quinn.hunter.transform.asm; - -import com.android.build.api.transform.DirectoryInput; -import com.android.build.api.transform.JarInput; -import com.android.build.api.transform.TransformInput; -import com.android.build.gradle.AppExtension; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; - -import org.gradle.api.Project; - -import java.io.File; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.Collection; - -/** - * Created by quinn on 31/08/2018 - */ -public class ClassLoaderHelper { - - - public static URLClassLoader getClassLoader(Collection inputs, - Collection referencedInputs, - Project project) throws MalformedURLException { - ImmutableList.Builder urls = new ImmutableList.Builder<>(); - String androidJarPath = getAndroidJarPath(project); - File file = new File(androidJarPath); - URL androidJarURL = file.toURI().toURL(); - urls.add(androidJarURL); - for (TransformInput totalInputs : Iterables.concat(inputs, referencedInputs)) { - for (DirectoryInput directoryInput : totalInputs.getDirectoryInputs()) { - if (directoryInput.getFile().isDirectory()) { - urls.add(directoryInput.getFile().toURI().toURL()); - } - } - for (JarInput jarInput : totalInputs.getJarInputs()) { - if (jarInput.getFile().isFile()) { - urls.add(jarInput.getFile().toURI().toURL()); - } - } - } - ImmutableList allUrls = urls.build(); - URL[] classLoaderUrls = allUrls.toArray(new URL[0]); - return new URLClassLoader(classLoaderUrls); - } - - /** - * /Users/quinn/Documents/Android/SDK/platforms/android-28/android.jar - */ - private static String getAndroidJarPath(Project project) { - AppExtension appExtension = (AppExtension)project.getProperties().get("android"); - String sdkDirectory = appExtension.getSdkDirectory().getAbsolutePath(); - String compileSdkVersion = appExtension.getCompileSdkVersion(); - sdkDirectory = sdkDirectory + File.separator + "platforms" + File.separator; - return sdkDirectory + compileSdkVersion + File.separator + "android.jar"; - } - - -} diff --git a/hunter-transform/src/main/java/com/quinn/hunter/transform/asm/ExtendClassWriter.java b/hunter-transform/src/main/java/com/quinn/hunter/transform/asm/ExtendClassWriter.java deleted file mode 100644 index 859c3ff3..00000000 --- a/hunter-transform/src/main/java/com/quinn/hunter/transform/asm/ExtendClassWriter.java +++ /dev/null @@ -1,169 +0,0 @@ -package com.quinn.hunter.transform.asm; - -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Opcodes; - -import java.io.IOException; -import java.io.InputStream; -import java.util.HashSet; -import java.util.Set; - -/** - * Created by quinn on 30/08/2018 - */ -public class ExtendClassWriter extends ClassWriter { - - public static final String TAG = "ExtendClassWriter"; - - private static final String OBJECT = "java/lang/Object"; - - private ClassLoader urlClassLoader; - - public ExtendClassWriter(ClassLoader urlClassLoader, int flags) { - super(flags); - this.urlClassLoader = urlClassLoader; - } - - /** - * https://github.com/Moniter123/pinpoint/blob/40106ffe6cc4d6aea9d59b4fb7324bcc009483ee/profiler/src/main/java/com/navercorp/pinpoint/profiler/instrument/ASMClassWriter.java - */ - @Override - protected String getCommonSuperClass(final String type1, final String type2) { - if (type1 == null || type1.equals(OBJECT) || type2 == null || type2.equals(OBJECT)) { - return OBJECT; - } - - if (type1.equals(type2)) { - return type1; - } - - ClassReader type1ClassReader = getClassReader(type1); - ClassReader type2ClassReader = getClassReader(type2); - if (type1ClassReader == null || type2ClassReader == null) { - return OBJECT; - } - - if (isInterface(type1ClassReader)) { - String interfaceName = type1; - if (isImplements(interfaceName, type2ClassReader)) { - return interfaceName; - } - if (isInterface(type2ClassReader)) { - interfaceName = type2; - if (isImplements(interfaceName, type1ClassReader)) { - return interfaceName; - } - } - return OBJECT; - } - - if (isInterface(type2ClassReader)) { - String interfaceName = type2; - if (isImplements(interfaceName, type1ClassReader)) { - return interfaceName; - } - return OBJECT; - } - - final Set superClassNames = new HashSet(); - superClassNames.add(type1); - superClassNames.add(type2); - - String type1SuperClassName = type1ClassReader.getSuperName(); - if (!superClassNames.add(type1SuperClassName)) { - return type1SuperClassName; - } - - String type2SuperClassName = type2ClassReader.getSuperName(); - if (!superClassNames.add(type2SuperClassName)) { - return type2SuperClassName; - } - - while (type1SuperClassName != null || type2SuperClassName != null) { - if (type1SuperClassName != null) { - type1SuperClassName = getSuperClassName(type1SuperClassName); - if (type1SuperClassName != null) { - if (!superClassNames.add(type1SuperClassName)) { - return type1SuperClassName; - } - } - } - - if (type2SuperClassName != null) { - type2SuperClassName = getSuperClassName(type2SuperClassName); - if (type2SuperClassName != null) { - if (!superClassNames.add(type2SuperClassName)) { - return type2SuperClassName; - } - } - } - } - - return OBJECT; - } - - private boolean isImplements(final String interfaceName, final ClassReader classReader) { - ClassReader classInfo = classReader; - - while (classInfo != null) { - final String[] interfaceNames = classInfo.getInterfaces(); - for (String name : interfaceNames) { - if (name != null && name.equals(interfaceName)) { - return true; - } - } - - for (String name : interfaceNames) { - if(name != null) { - final ClassReader interfaceInfo = getClassReader(name); - if (interfaceInfo != null) { - if (isImplements(interfaceName, interfaceInfo)) { - return true; - } - } - } - } - - final String superClassName = classInfo.getSuperName(); - if (superClassName == null || superClassName.equals(OBJECT)) { - break; - } - classInfo = getClassReader(superClassName); - } - - return false; - } - - private boolean isInterface(final ClassReader classReader) { - return (classReader.getAccess() & Opcodes.ACC_INTERFACE) != 0; - } - - - private String getSuperClassName(final String className) { - final ClassReader classReader = getClassReader(className); - if (classReader == null) { - return null; - } - return classReader.getSuperName(); - } - - private ClassReader getClassReader(final String className) { - InputStream inputStream = urlClassLoader.getResourceAsStream(className + ".class"); - try { - if (inputStream != null) { - return new ClassReader(inputStream); - } - } catch (IOException ignored) { - } finally { - if (inputStream != null) { - try { - inputStream.close(); - } catch (IOException ignored) { - } - } - } - return null; - } -} - diff --git a/hunter-transform/src/main/java/com/quinn/hunter/transform/asm/IWeaver.java b/hunter-transform/src/main/java/com/quinn/hunter/transform/asm/IWeaver.java index 05a8a7fd..c3fa80a1 100644 --- a/hunter-transform/src/main/java/com/quinn/hunter/transform/asm/IWeaver.java +++ b/hunter-transform/src/main/java/com/quinn/hunter/transform/asm/IWeaver.java @@ -1,26 +1,10 @@ package com.quinn.hunter.transform.asm; -import java.io.IOException; -import java.io.InputStream; +import org.objectweb.asm.ClassVisitor; -/** - * Created by quinn on 06/09/2018 - */ public interface IWeaver { - /** - * Check a certain file is weavable - * @param filePath class路径 - * @return 是否需要修改字节码 - */ - public boolean isWeavableClass(String filePath) throws IOException; - - /** - * Weave single class to byte array - * @param inputStream - */ - public byte[] weaveSingleClassToByteArray(InputStream inputStream) throws IOException; - + boolean isWeavableClass(String fullyQualifiedClassName); + ClassVisitor wrap(ClassVisitor next); } - diff --git a/hunter-transform/src/main/java/com/quinn/hunter/transform/concurrent/Schedulers.java b/hunter-transform/src/main/java/com/quinn/hunter/transform/concurrent/Schedulers.java deleted file mode 100644 index 5af4371e..00000000 --- a/hunter-transform/src/main/java/com/quinn/hunter/transform/concurrent/Schedulers.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.quinn.hunter.transform.concurrent; - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ForkJoinPool; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -public class Schedulers { - private static final int cpuCount = Runtime.getRuntime().availableProcessors(); - private final static ExecutorService IO = new ThreadPoolExecutor(0, cpuCount * 3, - 30L, TimeUnit.SECONDS, - new LinkedBlockingQueue<>()); - - private static final ExecutorService COMPUTATION = Executors.newWorkStealingPool(cpuCount); - - public static Worker IO() { - return new Worker(IO); - } - - public static Worker COMPUTATION() { - return new Worker(COMPUTATION); - } - - public static ForkJoinPool FORKJOINPOOL() { - return (ForkJoinPool) COMPUTATION; - } -} diff --git a/hunter-transform/src/main/java/com/quinn/hunter/transform/concurrent/Worker.java b/hunter-transform/src/main/java/com/quinn/hunter/transform/concurrent/Worker.java deleted file mode 100644 index 0e7c7388..00000000 --- a/hunter-transform/src/main/java/com/quinn/hunter/transform/concurrent/Worker.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.quinn.hunter.transform.concurrent; - -import java.io.IOException; -import java.util.Collection; -import java.util.LinkedList; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; -import java.util.function.Consumer; - -public class Worker { - protected final LinkedList> futures = new LinkedList>() { - @Override - public synchronized boolean add(Future future) { - return super.add(future); - } - - @Override - public synchronized Future pollFirst() { - return super.pollFirst(); - } - }; - protected ExecutorService executor; - - Worker(ExecutorService executor) { - this.executor = executor; - } - - public void execute(Runnable runnable) { - futures.add(executor.submit(runnable)); - } - - public Future submit(Callable callable) { - Future future = executor.submit(callable); - futures.add(future); - return future; - } - - public void await() throws IOException { - Future future; - while ((future = futures.pollFirst()) != null) { - try { - future.get(); - } catch (ExecutionException | InterruptedException e) { - if (e.getCause() instanceof IOException) { - throw (IOException) e.getCause(); - } else if (e.getCause() instanceof RuntimeException) { - throw (RuntimeException) e.getCause(); - } else if (e.getCause() instanceof Error) { - throw (Error) e.getCause(); - } - throw new RuntimeException(e.getCause()); - } - } - } - - public void submitAndAwait(Collection is, Consumer consumer) throws IOException { - is.stream().map(f -> (Runnable) () -> consumer.accept(f)).forEach(this::execute); - await(); - } -} diff --git a/settings.gradle b/settings.gradle index 76a141f3..7ee2e5ac 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1,13 @@ +pluginManagement { + repositories { + gradlePluginPortal() + google() + mavenCentral() + } +} + +rootProject.name = 'Hunter' + include ':hunter-transform', ':hunter-timing-plugin', ':hunter-timing-library', @@ -7,11 +17,13 @@ include ':hunter-transform', ':hunter-linelog-library', ':hunter-okhttp-plugin', ':hunter-okhttp-library', - ':hunter-okhttp-example', - ':hunter-linelog-example', - ':hunter-debug-example', - ':hunter-timing-example', ':hunter-okhttp-library-compat' - - +// Example apps reference the plugins via published Maven coordinates. They +// can't be built from a clean checkout until the plugins have been published +// (e.g. via :publishToMavenLocal). Re-add them after a local publish, or use +// the dedicated :assembleExamples lifecycle in CI. +// include ':hunter-okhttp-example', +// ':hunter-linelog-example', +// ':hunter-debug-example', +// ':hunter-timing-example'