Skip to content Skip to sidebar Skip to footer

Android New Build System (gradle) And Aspectj

In Google IO the new build system gradle is announced to replace ant. My project is using aspectj and I would like to use it in my project. I couldn't figure out some variables t

Solution 1:

I also wanted to use aspectj with gradle and Android Studio, and I finally got it working, but I still have some hand-written paths that I'd like to replace with more generic gradle options.

Edit: I replaced every hard-coded absolute paths with gradle based alternatives, so this solution does not depend any more on a given platform or user name. However, it still uses relative paths that could change from an IDE to an other or in further releases of Android Studio. Also, I'm not really satisfied with the way I find the android.jar.

I first load aspectj:

configurations {
ajc
aspects
ajInpath
}

ext.aspectjVersion = '1.7.3'

dependencies {
    compile project(":LibTest")

    ajc "org.aspectj:aspectjtools:${aspectjVersion}"
    compile "org.aspectj:aspectjrt:${aspectjVersion}"
    compile 'com.android.support:appcompat-v7:18.0.0'
}

And I then add a task that will run after the JavaCompile task of the current variant:

android.applicationVariants.all { variant ->

    variant.javaCompile.doLast {
        def androidSdk = android.adbExe.parent + "/../platforms/" + android.compileSdkVersion + "/android.jar"

        def iajcClasspath = configurations.compile.asPath + ";" + androidSdk
        configurations.compile.dependencies.each { dep ->
            if(dep.hasProperty("dependencyProject")) {
                iajcClasspath += ":" + dep.dependencyProject.buildDir + "/bundles/release/classes.jar"
            }
        }

        ant.taskdef( resource:"org/aspectj/tools/ant/taskdefs/aspectjTaskdefs.properties", classpath: configurations.ajc.asPath)
        ant.iajc (
                source:sourceCompatibility,
                target:targetCompatibility,
                destDir:"${project.buildDir}/classes/${variant.dirName}",
                maxmem:"512m",
                fork:"true",
                aspectPath:configurations.aspects.asPath,
                inpath:configurations.ajInpath.asPath,
                sourceRootCopyFilter:"**/.svn/*,**/*.java",
                classpath:iajcClasspath
        ){
            sourceroots{
                android.sourceSets.main.java.srcDirs.each{
                    pathelement(location:it.absolutePath)
                }
                pathelement(location:"${project.buildDir}/source/r/${variant.dirName}")
            }
        }
    }
}

Wherever I use ${variant.dirName}, it will be replaced by either "debug" or "release" according to the current build configuration.

Adding android.jar to the classpath is required to compile Android specific classes, and the line pathelement(location:"${project.buildDir}/source/r/${variant.dirName}") is require to use classes from the auto-generated R.java file.

Edit: The iterations over the project dependencies to build iajcClasspath let you use classes from your libraries projects. configurations.compile.asPath already contains a reference to your apklib (aar file), which is actually a zip containing both the jar and the resources of the library. Iajc doesn't recognize these files as it, but there is a bundle directory containing the classes.jar for your library under the build directory. I use a relative path with "release" hard-coded in it, because the library has a different variant than the main project in my situation, so I can't use ${variant.dirName} here.

Here is the complete build.gradle file:

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:0.5.+'
    }
}
apply plugin: 'android'

repositories {
    mavenCentral()
}

android {
    compileSdkVersion 18
    buildToolsVersion "18.1.0"

    defaultConfig {
        minSdkVersion 7
        targetSdkVersion 18
    }
}

configurations {
    ajc
    aspects
    ajInpath
}

ext.aspectjVersion = '1.7.3'

dependencies {
    compile project(":LibTest")

    ajc "org.aspectj:aspectjtools:${aspectjVersion}"
    compile "org.aspectj:aspectjrt:${aspectjVersion}"
    compile 'com.android.support:appcompat-v7:18.0.0'
}

android.applicationVariants.all { variant ->

    variant.javaCompile.doLast {
        def androidSdk = android.adbExe.parent + "/../platforms/" + android.compileSdkVersion + "/android.jar"

        def iajcClasspath = configurations.compile.asPath + ";" + androidSdk
        configurations.compile.dependencies.each { dep ->
            if(dep.hasProperty("dependencyProject")) {
                iajcClasspath += ":" + dep.dependencyProject.buildDir + "/bundles/release/classes.jar"
            }
        }

        ant.taskdef( resource:"org/aspectj/tools/ant/taskdefs/aspectjTaskdefs.properties", classpath: configurations.ajc.asPath)
        ant.iajc (
                source:sourceCompatibility,
                target:targetCompatibility,
                destDir:"${project.buildDir}/classes/${variant.dirName}",
                maxmem:"512m",
                fork:"true",
                aspectPath:configurations.aspects.asPath,
                inpath:configurations.ajInpath.asPath,
                sourceRootCopyFilter:"**/.svn/*,**/*.java",
                classpath:iajcClasspath
        ){
            sourceroots{
                android.sourceSets.main.java.srcDirs.each{
                    pathelement(location:it.absolutePath)
                }
                pathelement(location:"${project.buildDir}/source/r/${variant.dirName}")
            }
        }
    }
}

Solution 2:

I figured out that the AAR can not be used as a jar library in my code. If you are using dependencies like this

compile 'com.android.support:appcompat-v7:18.0.0'

You need to find the jar file and add to the classpath. The following code will do it.

tree = fileTree(dir: "${project.buildDir}/exploded-bundles", include: '**/classes.jar')
tree.each { jarFile ->
    iajcClasspath += ":" + jarFile
}

So the whole section would be:

variant.javaCompile.doLast {
    // Find the android.jar and add to iajc classpath
    def androidSdk = android.adbExe.parent + "/../platforms/" + android.compileSdkVersion + "/android.jar"
    println 'Android SDK android.jar path: ' + androidSdk

    def iajcClasspath = androidSdk + ":" + configurations.compile.asPath
    configurations.compile.dependencies.each { dep ->
        if(dep.hasProperty("dependencyProject")) {
            iajcClasspath += ":" + dep.dependencyProject.buildDir + "/bundles/release/classes.jar"
        }
    }

    // handle aar dependencies pulled in by gradle (Android support library and etc)
    tree = fileTree(dir: "${project.buildDir}/exploded-bundles", include: '**/classes.jar')
    tree.each { jarFile ->
        iajcClasspath += ":" + jarFile
    }
        println 'Classpath for iajc: ' + iajcClasspath

        ant.taskdef( resource:"org/aspectj/tools/ant/taskdefs/aspectjTaskdefs.properties", classpath: configurations.ajc.asPath)

For the full example please see the build.gradle for AnyMemo project here: https://code.google.com/p/anymemo/source/browse/build.gradle?spec=svnf85aaa4b2d78c62876d0e1f6c3e28252bf03f820&r=f85aaa4b2d78c62876d0e1f6c3e28252bf03f820


Solution 3:

Although previous answers scripts works for most of the situations, they doesn't cover some of the problems of using Android with AspectJ and Gradle.

My test was to create a library project that should be used by anyone via mavenCentral or by me as a reference library project, and a test application project. The library project is the one that has all the aspects and the application test was trying to use those aspects. Giving this as a context, the resulting project structure was:

HEAD-Gradle
---LibraryProject
-------SomeAspects
---TestApplication
-------Uses-SomeAspects

The solutions I found that make it work are:

1- For library projects you must use

libraryVariants.all { variant -> 

instead of

android.applicationVariants.all { variant ->

2- The build dir changed for 19.+ build tools of Android, so as it is suggested in one comment (thanks to "WithoutClass"), you have to use the exploded-aar dir instead of exploded-bundles dir in the tree variable definition. From:

def tree = fileTree(dir: "${project.buildDir}/exploded-bundles", include: '**/classes.jar')

To:

def tree = fileTree(dir: "${project.buildDir}/exploded-aar", include: '**/classes.jar')

3- The final problem I faced when making the integration was that if you have a library project, aspects defined on it were not found on the child project. To solve this you have to add the classes.jar of your custom library to the aspectJ compiler configuration. You can achieve this by adding to the dependencies:

aspects project(":YourLibraryProject")

and it is also needed to make some changes in the script provided in the final of this post.

Right now the best script I can imagine that gives full support for aspectj using even library projects is:

For dependencies:

configurations {
    ajc
    aspects
    ajInpath
}

//Version of aspectj
def aspectjVersion = '1.8.+'
// The dependencies for this project
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    //Your dependencies to a custom library project
    compile project(":YourLibraryProject")
    aspects project(":YourLibraryProject")
    //Aspectj dependencies
    ajc "org.aspectj:aspectjtools:${aspectjVersion}"
    compile "org.aspectj:aspectjrt:${aspectjVersion}"
}

The compiler running script

android.applicationVariants.all { variant ->

variant.javaCompile.doLast {
    // Find the android.jar and add to iajc classpath
    def androidSdk = android.adbExe.parent + "/../platforms/" + android.compileSdkVersion + "/android.jar"

    def iajcClasspath = androidSdk + ":" + configurations.compile.asPath
    //This line and the fordward assignations allow the aspects support in the child project from the library project
    def customAspectsPath = configurations.aspects.asPath
    configurations.compile.dependencies.each { dep ->
        if(dep.hasProperty("dependencyProject")) {
            iajcClasspath += ":" + dep.dependencyProject.buildDir + "/bundles/${variant.buildType.name}/classes.jar"
            customAspectsPath += ":" + dep.dependencyProject.buildDir + "/bundles/${variant.buildType.name}/classes.jar"
        }
    }

    // handle aar dependencies pulled in by gradle (Android support library and etc)

    def tree = fileTree(dir: "${project.buildDir}/exploded-aar", include: '**/classes.jar')
    tree.each { jarFile ->
        iajcClasspath += ":" + jarFile
    }

    ant.taskdef( resource:"org/aspectj/tools/ant/taskdefs/aspectjTaskdefs.properties", classpath: configurations.ajc.asPath)
    ant.iajc (
            source:sourceCompatibility,
            target:targetCompatibility,
            destDir:"${project.buildDir}/classes/${variant.dirName}",
            maxmem:"512m",
            fork:"true",
            aspectPath:customAspectsPath,
            inpath:configurations.ajInpath.asPath,
            sourceRootCopyFilter:"**/.svn/*,**/*.java",
            classpath:iajcClasspath
    ){
        sourceroots{
            android.sourceSets.main.java.srcDirs.each{
                pathelement(location:it.absolutePath)
            }
            pathelement(location:"${project.buildDir}/source/r/${variant.dirName}")
        }
    }
}
}

Remember that if you want to run AspectJ on a library-child project, you must have also this script on the build.gradle of the library.


Solution 4:

In the case of using Android Studio 0.8 or above, it seems that using gradle 0.12.+ is necessary.

In gradle 0.12.+, exploded aar are extracted in the build folder, not in the exploded-aar folder.

Therefore, in order to handle aar dependencies, you must use this code:

tree = fileTree(dir: "${project.buildDir}", include: '**/classes.jar')
tree.each { jarFile ->
    iajcClasspath += ":" + jarFile
}

Solution 5:

Another easier way to set things up is using the android aspectj plugin which is better maintained. https://github.com/uPhyca/gradle-android-aspectj-plugin


Post a Comment for "Android New Build System (gradle) And Aspectj"