Archinamon / android-gradle-aspectj

gradle plug-in adding supports of AspectJ into Android project
Apache License 2.0
363 stars 58 forks source link
android aspectj flavor gradle-plugin groovy jar java kotlin write-aspects

GradleAspectJ-Android

AspectJ Kotlin Download
Android Arsenal contributions welcome GitHub license

A Gradle plugin which enables AspectJ for Android builds. Supports writing code with AspectJ-lang in .aj files and in java-annotation style. Full support of Android product flavors and build types. Support Kotlin, Groovy, Scala and any other languages that compiles into java bytecode.

Actual version supporting of AGP 4.1.+: com.archinamon:android-gradle-aspectj:4.3.0.

Friendly with jRebel for Android!

This plugin is completely friendly with APT (Android Annotation Processing Tools) and Retrolambda project (but Java 8 not supported in .aj files). AndroidAnnotations, Dagger are also supported and works fine.

This plugin has many ideas from the others similar projects, but no one of them grants full pack of features like this one. Nowadays it has been completely re-written using Transform API.

Key features

Augments Java, Kotlin, Groovy bytecode simultaneously!
Works with background mechanics of jvm-based languages out-of-box!
How to teach Android Studio to understand the AspectJ!
May not work properly for AS 3.0 :(

It is easy to isolate your code with aspect classes, that will be simply injected via cross-point functions, named advices, into your core application. The main idea is — code less, do more!

AspectJ-Gradle plugin provides supply of all known JVM-based languages, such as Groovy, Kotlin, etc. That means you can easily write cool stuff which may be inject into any JVM language, not only Java itself! :)

To start from you may look at my example project. And also you may find useful to look at reference manual of AspectJ language and simple code snippets. In case aspectj-native not supported by Android Studio (even with IDE-plugin it's using is complicated), you may write a java-classes with aspectj annotations.

Two simple rules you may consider when writing aspect classes.

These rules affects only in case you're writing in native aj-syntax. You may write aspects in java-annotation style and being free from these limitations.

Usage

First add a maven repo link into your repositories block of module build file:

mavenCentral()

Don't forget to add mavenCentral() due to some dependencies inside AspectJ-gradle module.

Add the plugin to your buildscript's dependencies section:

Kotlin ```kotlin classpath("com.archinamon:android-gradle-aspectj:4.3.0") ```
Groovy ```groovy classpath 'com.archinamon:android-gradle-aspectj:4.3.0' ```


Apply the aspectj plugin:

Kotlin ```kotlin plugins { id("com.android.application") id("com.archinamon.aspectj") } ```
Groovy ```groovy plugins { id 'com.android.application' id 'com.archinamon.aspectj' } ```


Now you can write aspects using annotation style or native (even without IntelliJ IDEA Ultimate edition). Let's write simple Application advice:

import android.app.Application;
import android.app.NotificationManager;
import android.content.Context;
import android.support.v4.app.NotificationCompat;

aspect AppStartNotifier {

    pointcut postInit(): within(Application+) && execution(* Application+.onCreate());

    after() returning: postInit() {
        Application app = (Application) thisJoinPoint.getTarget();
        NotificationManager nmng = (NotificationManager) app.getSystemService(Context.NOTIFICATION_SERVICE);
        nmng.notify(9999, new NotificationCompat.Builder(app)
            .setTicker("Hello AspectJ")
            .setContentTitle("Notification from aspectJ")
            .setContentText("privileged aspect AppAdvice")
            .setSmallIcon(R.drawable.ic_launcher)
            .build());
    }
}

Tune extension

Kotlin ```kotlin aspectj { compileTests = true // default value ajc = "1.9.4" // default value java = JavaVersion.VERSION_1_7 // default value /* @see Ext plugin config **/ includeAllJars = false // default value includeJar.addAll(arrayOf("design", "support-v4", "dagger")) // default is empty excludeJar.addAll(arrayOf("support-v7", "joda")) // default is empty extendClasspath = true // default value includeAspectsFromJar.addAll(arrayOf("my-aj-logger-lib", "any-other-libs-with-aspects")) // default is empty ajcArgs.apply { add("-warn:deprecation") add("-referenceInfo") } weaveInfo = true // default value debugInfo = false // default value addSerialVersionUID = false // default value noInlineAround = false // default value ignoreErrors = false // default value breakOnError = true // default value experimental = false // default value buildTimeLog = true // default value transformLogFile = "ajc-transform.log" // default value compilationLogFile = "ajc-compile.log" // default value } ```
Groovy ```groovy aspectj { dryRun false // default value compileTests true // default value ajc '1.9.4' // default value java = JavaVersion.VERSION_1_7 // default value /* @see Ext plugin config **/ includeAllJars false // default value includeJar 'design', 'support-v4', 'dagger' // default is empty excludeJar 'support-v7', 'joda' // default is empty extendClasspath true // default value includeAspectsFromJar 'my-aj-logger-lib', 'any-other-libs-with-aspects' // default is empty ajcArgs << '-referenceInfo' << '-warn:deprecation' weaveInfo true // default value debugInfo false // default value addSerialVersionUID false // default value noInlineAround false // default value ignoreErrors false // default value breakOnError true // default value experimental false // default value buildTimeLog true // default value transformLogFile 'ajc-transform.log' // default value compilationLogFile 'ajc-compile.log' // default value } ```


Note that you may not include all these options!

All the extension parameters are have default values (all of them are described above, except of includeJar/Aspects/ajcArgs options). So no need to define them manually.

Extended plugin config

Kotlin ```kotlin plugins { id("com.android.application") id("com.archinamon.aspectj-ext") } ```
Groovy ```groovy plugins { id 'com.android.application' id 'com.archinamon.aspectj-ext' } ```


Ext config:

Currently it has some limitations:

Provider plugin config

Kotlin ```kotlin plugins { id("com.android.application") id("com.archinamon.aspectj-provides") } ```
Groovy ```groovy plugins { id 'com.android.application' id 'com.archinamon.aspectj-provides' } ```


Plugin-provider may be useful for that cases when you need to extract aspect-sources into separate module and include it on demand to that modules where you only need it. Therefor this behavior will save you build-time due to bypassing aspectj-transformers in provide-only modules.

You ain't limited to describe as much provider-modules as you need and then include them using includeAspectsFromJar parameter in the module which code or dependencies you may want to augment.

With example project you could learn how to write such provider-module.

DryRun plugin config

Kotlin ```kotlin plugins { id("com.android.application") id("com.archinamon.aspectj-dryRun") } ```
Groovy ```groovy plugins { id 'com.android.application' id 'com.archinamon.aspectj-dryRun' } ```


Disables aspectj-compiler and transformation task for the hole project.

Working tests

Kotlin ```kotlin plugins { id("com.android.application") id("com.archinamon.aspectj-junit") } ```
Groovy ```groovy plugins { id 'com.android.application' id 'com.archinamon.aspectj-junit' } ```


Test scope overloads JUnit compilation flow with AJC instead of JavaC. So any aspects has been written within test directory will be compiled with all java sources and aspects will weave them if need.

ProGuard

Correct tuning will depends on your own usage of aspect classes. So if you declares inter-type injections you'll have to predict side-effects and define your annotations/interfaces which you inject into java classes/methods/etc. in proguard config.

Basic rules you'll need to declare for your project:

-adaptclassstrings
-keepattributes InnerClasses, EnclosingMethod, Signature, *Annotation*

-keepnames @org.aspectj.lang.annotation.Aspect class * {
    ajc* <methods>;
}

If you will face problems with lambda factories, you may need to explicitly suppress them. That could happen not in aspect classes but in any arbitrary java-class if you're using Retrolambda. So concrete rule is:

-keep class *$Lambda* { <methods>; }
-keepclassmembernames public class * {
    *** lambda*(...);
}

Changelog

4.2.1 -- Improve jar archives

4.2.0 -- Support AGP 4.0.+

4.1.0 -- Support AGP 3.6.+

4.0.1 -- Fix synchronous run

4.0.0 -- Support AGP 3.5.+

3.4.5 -- Fix for Gradle 6.0

3.4.3 -- Once more fix :(

3.4.2 -- Hotfix provides

3.4.1 -- Fix provides

3.4.0 -- Better DryRun mode

3.3.12 -- Fix 'Dependencies resolution fail'

3.3.11 -- Fix legacy AGP support

3.3.10 -- Update AJC

3.3.9 -- Small fix dryRun

3.3.8 -- Fix unitTest variant

3.3.7 -- Fixes ext plugin

3.3.6 -- Fixes

3.3.5 -- Dry run

3.3.3 -- Support AGP 3.3.+

3.3.0 -- JUnit tests support

3.2.0 -- Gradle 3.0.0 support

3.1.1 -- Useful improvements

3.1.0 -- Provider

3.0.3 -- Minor fixes

3.0.0 -- Grand refactoring in Kotlin

2.4.3 -- Hot-fixed two-step compilation

2.4.2 -- Hot-fix

2.4.0 -- Added aspectj-ext plugin

2.3.1 -- New two-step build mechanic

2.3.0 -- Major fixes

2.2.2 -- Improvements

2.2.1 -- Hot-fix

2.2.0 -- Ajc fixes and improvements

2.1.0 -- Transform api fix

2.0.4 -- Small fix

2.0.3 -- Gradle instant run

2.0.2 -- Fixed filters

2.0.1 -- Hotfix :)

2.0.0 -- Brand new mechanics

1.3.3 -- Rt qualifier

1.3.2 -- One more fix

1.3.1 -- Hot-fixes

1.3.0 -- Merging binary processing and tests

1.2.1 -- Hot-fix of Gradle DSL

1.2.0 -- Binary weaving

1.1.4 -- Experimenting with binary weaving

1.1.2 -- Gradle Instant-run

1.1.1 -- Updating kernel

1.1.0 -- Refactoring

1.0.17 -- Cleanup

1.0.16 -- New plugin routes

1.0.15 -- Full flavor support

1.0.9 -- Basic flavors support

1.0 -- Initial release

Known limitations

All these limits are fighting on and I'll be glad to introduce new build as soon as I solve these problems.

License

Copyright 2015 Eduard "Archinamon" Matsukov.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.