Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Aspectj with android library

Tags:

I have a lib that use aspects and is available via maven, now I'm trying to use that lib in an android application.

If I include this plug-in in the app gradle file, everything works fine, but my goal is to extract the classpath 'com.uphyca.gradle:gradle-android-aspectj-plugin:0.9.+' and the apply plugin: 'android-aspectj' (required by the plugin) to the my.lib gradle file instead of declaring in my app.

Is that possible?

app gradle file:

classpath 'com.uphyca.gradle:gradle-android-aspectj-plugin:0.9.+'  apply plugin: 'android-aspectj'  dependencies {    compile 'my.lib:example:1.0.0' } 

GOAL:

app gradle file:

dependencies {    compile 'my.lib:example:1.0.0' } 

my.lib gradle file:

classpath 'com.uphyca.gradle:gradle-android-aspectj-plugin:0.9.+'  apply plugin: 'android-aspectj'  dependencies {    compile 'org.aspectj:aspectjrt:1.7.3' } 
like image 274
letz Avatar asked Jun 30 '15 15:06

letz


1 Answers

I had the same problem. This is all I did to solve it.

Root/Main Project

In your root project add the AspectJ tools that contains the ajc compiler that is necessary for weaving your classes. ( You can also add this to your library's build.gradle file but it's better to add it here as the gradle plugin that you will be creating to accommodate your library will be using the ajc.

buildscript {     repositories {         jcenter()       }     dependencies {         classpath 'com.android.tools.build:gradle:1.2.3'         classpath 'org.aspectj:aspectjtools:1.8.5'     } 

Library Project

In your library's build.gradle file ensure that it looks like this similar to this. The main additions are the import statements at the top and the code beneath the android build properties.

import com.android.build.gradle.LibraryPlugin import org.aspectj.bridge.IMessage import org.aspectj.bridge.MessageHandler import org.aspectj.tools.ajc.Main  apply plugin: 'com.android.library'   dependencies {     compile 'org.aspectj:aspectjrt:1.8.5' } android {     compileSdkVersion 22     buildToolsVersion "22.0.1"      defaultConfig {         minSdkVersion 14         targetSdkVersion 22         versionCode 1         versionName "1.0"     }     buildTypes {         release {             minifyEnabled false             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'         }     } }  android.libraryVariants.all { variant ->     LibraryPlugin plugin = project.plugins.getPlugin(LibraryPlugin)     JavaCompile javaCompile = variant.javaCompile     javaCompile.doLast {         String[] args = [                 "-showWeaveInfo",                 "-1.5",                 "-inpath", javaCompile.destinationDir.toString(),                 "-aspectpath", javaCompile.classpath.asPath,                 "-d", javaCompile.destinationDir.toString(),                 "-classpath", javaCompile.classpath.asPath,                 "-bootclasspath", android.bootClasspath.join(File.pathSeparator)         ]          MessageHandler handler = new MessageHandler(true);         new Main().run(args, handler)          def log = project.logger         for (IMessage message : handler.getMessages(null, true)) {             switch (message.getKind()) {                 case IMessage.ABORT:                 case IMessage.ERROR:                 case IMessage.FAIL:                     log.error message.message, message.thrown                     break;                 case IMessage.WARNING:                 case IMessage.INFO:                     log.info message.message, message.thrown                     break;                 case IMessage.DEBUG:                     log.debug message.message, message.thrown                     break;             }         }     } }           

So what's happening is when the project is being compiled the ajc(AspectJ's weaver) command compiles and weaves AspectJ and Java source and .class files, producing .class files compliant with any Java VM.

For this to take place the task needs arguments about your library. That's the reason for creating the args variable.

 String[] args = [                     "-showWeaveInfo",                     "-1.5",                     "-inpath", javaCompile.destinationDir.toString(),                     "-aspectpath", javaCompile.classpath.asPath,                     "-d", javaCompile.destinationDir.toString(),                     "-classpath", javaCompile.classpath.asPath,                     "-bootclasspath", android.bootClasspath.join(File.pathSeparator)             ] 

Then the message handler that's created is simply being passed to ajc to accumulate messages of events that are taking place while ajc is compiling/weaving the classes. Then it's being passed to a project logger that then outputs any important errors or warnings that the ajc produced. For example if a pointcut can't be referenced by an advice it will be detected and shown in the gradle console. enter image description here

So all of what was describe above is basically taking place right here. Where the args and message handler are being passed to the Main function of the ajc(AspectJ compiler).

 MessageHandler handler = new MessageHandler(true);         new Main().run(args, handler)          def log = project.logger         for (IMessage message : handler.getMessages(null, true)) {             switch (message.getKind()) {                 case IMessage.ABORT:                 case IMessage.ERROR:                 case IMessage.FAIL:                     log.error message.message, message.thrown 

Gradle Plugin

The pointcuts/advices of your library weren't being triggered because you were targeting the app module while Aspects were only being woven into your library's module with the com.uphyca.gradle:gradle-android-aspectj-plugin AspectJ plugin. So in order for the Aspects of your library to be woven into your App's module you have to create a gradle plugin for your project. So what you have defined as your goal is your question is not possible, this is the only way it can be done.

This is how the plugin should look. (Plugin is done in groovy).

Plugin's build.gradle

apply plugin: 'groovy'  targetCompatibility = JavaVersion.VERSION_1_7 sourceCompatibility = JavaVersion.VERSION_1_7  dependencies {   compile gradleApi()   compile localGroovy()   compile 'com.android.tools.build:gradle:1.1.0-rc3'   compile 'org.aspectj:aspectjtools:1.8.5'   compile 'org.aspectj:aspectjrt:1.8.5' } 

Then the actual class.

import com.android.build.gradle.AppPlugin import com.android.build.gradle.LibraryPlugin import org.aspectj.bridge.IMessage import org.aspectj.bridge.MessageHandler import org.aspectj.tools.ajc.Main import org.gradle.api.Plugin import org.gradle.api.Project  public class YourPlugin implements Plugin<Project> {     @Override void apply(Project project) {         def hasApp = project.plugins.withType(AppPlugin)         def hasLib = project.plugins.withType(LibraryPlugin)         if (!hasApp && !hasLib) {             throw new IllegalStateException("'android' or 'android-library' plugin required.")         }          final def log = project.logger         final def variants         if (hasApp) {             variants = project.android.applicationVariants         } else {             variants = project.android.libraryVariants         }          project.dependencies {             compile 'com.name:example:1.0'             // TODO this should come transitively             compile 'org.aspectj:aspectjrt:1.8.5'         }          variants.all { variant ->              variant.dex.doFirst {                 String[] args = [                         "-showWeaveInfo",                         "-1.5",                         "-inpath", javaCompile.destinationDir.toString(),                         "-aspectpath", javaCompile.classpath.asPath,                         "-d", javaCompile.destinationDir.toString(),                         "-classpath", javaCompile.classpath.asPath,                         "-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)                 ]                 log.debug "ajc args: " + Arrays.toString(args)                  MessageHandler handler = new MessageHandler(true);                 new Main().run(args, handler);                 for (IMessage message : handler.getMessages(null, true)) {                     switch (message.getKind()) {                         case IMessage.ABORT:                         case IMessage.ERROR:                         case IMessage.FAIL:                             log.error message.message, message.thrown                             break;                         case IMessage.WARNING:                             log.warn message.message, message.thrown                             break;                         case IMessage.INFO:                             log.info message.message, message.thrown                             break;                         case IMessage.DEBUG:                             log.debug message.message, message.thrown                             break;                     }                 }             }         }     } } 

I know this might seem like a lot but it's alot of copy and pasting because the solution remains the same. If you look closely at the class the same things being done in your library module is now being applied to your app's module. The main modification you would do to this is add your library module to the project's dependencies via the plugin that's done here.

 project.dependencies {                 compile 'com.letz:example-library:1.0'                 // TODO this should come transitively                 compile 'org.aspectj:aspectjrt:1.8.5'             } 

For your library to be available to your plugin while developing you have to ensure that it's being deployed to your local maven repository. This can be done by applying this plugin(https://github.com/dcendents/android-maven-gradle-plugin) to your library module and running the gradle install task.

Final Steps

Once all of that is done you can then apply it to a sample app for testing by adding this to it's build.gradle file

buildscript {     repositories {         mavenCentral()          //Only necessary when developing locally.         mavenLocal()     }      dependencies {                       classpath 'com.letz:example-plugin:1.0'     } } apply plugin: 'example-plugin' 

Once that's done your library will be available to the app because it's being added to the project once the plugin has been applied.

If things are still confusing you are in good luck because the project I implemented this solution is on Github so you can fork it, copy the plugin's project and make the necessary changes.

The project is called Flender and it's used to annotate methods that require connectivity checking. Here's the link https://github.com/jd-alexander/flender

Hope this answer helps.

like image 126
Joel Dean Avatar answered Oct 05 '22 11:10

Joel Dean