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' }
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With