4

I am trying to run the static main method of a java class from my build.gradle script asp art of the build process. I am using Android Studio 1.0.2 with the Android/Gradle Plugin 'com.android.tools.build:gradle:1.0.0'

The java class whose main method I want to run during the build resides in ...\trunk-gradle\myproject\src\main\java\de\myapp\gradle

package de.myapp.gradle;

public class ConfigureCustomer {

    public static void main(String[] args){
        String server = args[0];
        String customer = args[1];
        System.out.println(String.format("Configuring customer %s with server %s", customer, server));
    }
}

Before I used ANT to call that java method as follows:

<java failonerror="yes" classname="de.myapp.gradle.ConfigureCustomer ">
    <classpath>
        <path location="${base.dir}/bin/classes/"/>
    </classpath>
    <arg line="${customer}"/>
    <arg line="${server }"/>
</java>

But now I am migrating to Groovy, so here is the relevant part of my project's build.gradle file that tries to execute the main method of above class (actual task definition is at the end just before the dependencies):

apply plugin: 'com.android.application'
android {

    project.ext.set("customer", "")
    project.ext.set("server", "")

    dexOptions {
        preDexLibraries = false
    }

    compileSdkVersion 19
    buildToolsVersion "21.1.2"

    defaultConfig {
//Default configuration
    }


    signingConfigs {
        release {
//Configuration for release builds
        }
    }


    buildTypes {

        debug{
            server = "test"
        }

        release {
            server = "release"
        }
    }

    productFlavors {
        customerA{
            customer = "a"
        }
        customerB{
            customer = "b"
        }
        customerC{
            customer = "c"
        }
    }
}

task (configureCustomer,  type: JavaExec) {
    println 'Running customer configuration...'
    main = 'de.myapp.gradle.ConfigureCustomer'
    args customer, server
}

dependencies {
    //Dependency settings
}

So now when I run the following via the command line (windows):

graldew configureCustomer

I get the following error message:

Error: Could not find or load main class de.myapp.gradle.ConfigureCustomer

My questions hence are as follows:

  1. How do I manage to fix the error message above? Do I have to move my java class to another folder? Maybe configure sth in the build scipt?
  2. How can I make sure the java task is executed after the classes have actually been compiled?
  3. If i wanted to execute the task configureCustomer as part of another task, would I simply write the following line in my gradle's task definition?

configureCustomer

I also tried to add the classpath:

task (configureCustomer,  type: JavaExec) {
    println 'Running customer configuration...'
    main = 'de.myapp.gradle.ConfigureCustomer'
    classpath = sourceSets.main.runtimeClasspath
    args customer, server
}

But all that got me was a gradle build error message saying:

Could not find property "main" on SourceSet container

So apparently "sourceSets.main.runtimeClasspath" does not exist in Android Studio's Gradle. Maybe it's named differently. Though I also tried setting the classpath like this:

classpath = '${projectDir.getAbsolutePath()}\\build\\intermediates\\classes\\' + customer + '\\release'

and I also tried this:

classpath = '${projectDir.getAbsolutePath()}\\build\\intermediates\\classes\\' + customer + '\\release\\de\\myapp\\gradle'

None of which worked, the error from above persists:

Error: Could not find or load main class de.myapp.gradle.ConfigureCustomer

AgentKnopf
  • 4,295
  • 7
  • 45
  • 81
  • In general, you're probably going to want to implement this as a separate module built with Gradle's java plugin so it will compile the files before it tries to use them in the build; trying to use Android to build plain Java code can be problematic. I'd discourage you from trying to intermingle the build source with your app's source. Specifically, you're having problems trying to set the classpath because of the execution phase during which the variables you're accessing are being set up, not to mention you're trying to point it to .java files, not .class files. – Scott Barta Jan 26 '15 at 17:50
  • @ScottBarta Thanks a lot for your comment! With Module you mean the gradle Plugin system? I am really new to gradle, and I mostly deal with it, because I need to migrate to Android Studio, so everything is kinda new to me, including the groovy syntax. I read a bunch of tutorials on the net, all suggesting different ways to execute javaexec, none of which worked for me. Though I thought I was pointing to java class files? --> build/intermediate/classes is the folder where the compiled classes end up. I am baffled this is such a tricky thing, since I was used to ant, where it was really easy – AgentKnopf Jan 26 '15 at 21:05
  • It's not clear from your question what you're really trying to do. Implementing the functionality as a Gradle plugin might be a good bet, though again, intermingling that code with your end application code is strange. But I was answering more of your original question, which is how to pull in an individual external class from a Gradle build file. You're being bitten by needing to have that class built by the same build file it's being linked into. You should probably take a step back and have a harder look at what you're trying to do -- very tricky builds are almost never a good idea. – Scott Barta Jan 26 '15 at 21:55
  • @ScottBarta What I am trying to do is, the same I did in the ant build before. As part of the build script I handed one task over to a Java class. It was a really standard thing (at least for ant) and from what I have read gradle should be able to do the same. Alternatively I could have gradle call my ant task which in turn will call my java code but that seems a bit convoluted. The java class is not directly part of my project, its really just executing stuff for the build. I just put it in my project source code because I did not know where else to put it, so that gradle finds it. – AgentKnopf Jan 27 '15 at 08:50
  • @ScottBarta I will add some more info to the question in a moment. Basically the question is: How to do something in gradle, that I have previously done in ANT – AgentKnopf Jan 27 '15 at 08:51
  • Writing your custom logic in Groovy right in the build script will probably be a lot easier than trying to compile and pull in a Java class. – Scott Barta Jan 27 '15 at 16:54
  • @ScottBarta I figured as much : ) . It just irks me, that it is apparently so difficult to integrate a java class into a gradle build, even though it is officially supported. But I might cave after all, though if I do figure out how to integrate that java class, I'll at least post the answer here. – AgentKnopf Jan 29 '15 at 08:05

2 Answers2

1

I finally found something that works for Android/Gradle but getting there seemed a lot more complicated, than it should have been.

So for recap - here is the Java class whose main method I'd like to execute:

package de.myapp.gradle;

public class ConfigureCustomer {

    public static void main(String[] args){
        String customer = args[0];
        String versionName = args[1];
        System.out.println(String.format("Configuring customer %s with versionName %s", customer, versionName ));
    }
}

I want to execute the above for each flavor and only for release builds (not debug builds) so here is my task definition (you'd still have to make your task depend on one of the gradle build tasks so it's executed - I am depending on the preBuild task for this purpose):

android {
//Build type setup, signing configuration and other stuff
}

//After the android block my task definition follows:

task buildPrintout(type: JavaExec) {
    android.applicationVariants.all { variant ->
    //Runt he java task for every flavor
        variant.productFlavors.each { flavor ->
            println "Triggering customer configuration for flavor " + flavor.name
            if (variant.buildType.name.equals('release')) {
                //Run the java task only for release builds
                //Cant find the runtime classpath in android/gradle so I'll directly link to my jar file here. The jarfile contains the class I want to run (with the main method)
                classpath += files("libs/my-jarfile.jar")
                //This is the fully qualified name of my class, including package name (de.myapp.gradle) and the classname (ConfigureCustomer)
                main = "de.myapp.gradle.ConfigureCustomer"
                //Pass in arguments - in this case the customer's name and the version name for the app (from AndroidManifest.xml)
                args flavor.name, variant.versionName
            }
        }
    }
}

You'll notice that I dumped the idea of having my Class integrated in the android project I am about to build. Instead I made that one class a separate project, built a jar file and dropped it in the libs folder of the android project i am building.

UPDATE 04.02.2015

I have slightly modified the above to use the javaexec method instead of the JavaExec Task type:

preBuild.doFirst {
    android.applicationVariants.all { variant ->
        variant.productFlavors.each { flavor ->
            if (variant.buildType.name.equals('release')) {
                javaexec {
                    println "Triggering customer build for flavor " + flavor.name
                    classpath += files("libs/my-jarfile.jar")
                    main = "de.myapp.gradle.ConfigureCustomer"
                    args flavor.name, variant.versionName
                }
                println "Done building customer for flavor " + flavor.name
            }
        }
    }
}

And here is yet another variation, where we define the javaexec within a reusable (which is preferred) task, that we then add as a dependency to the preBuild task:

//Define our custom task and add the closures as an action
    task buildCustomer << {
        android.applicationVariants.all { variant ->
            variant.productFlavors.each { flavor ->
                if (variant.buildType.name.equals('release')) {
                        javaexec {
                            println "Triggering customer build for flavor " + flavor.name
                            classpath += files("libs/my-jarfile.jar")
                            main = "de.myapp.gradle.ConfigureCustomer"
                            args flavor.name, variant.versionName
                        }
                        println "Done building customer for flavor " + flavor.name
                }
            }
        }        
    }
    //Make preBuild depend on our task
    preBuild.dependsOn buildCustomer

If you have any questions let me know and I'll try to answer them.

AgentKnopf
  • 4,295
  • 7
  • 45
  • 81
  • 1
    Thank you very much, saved my day! This stuff is not easily searchable on the internet. I still need to execute code after generating an apk. With your approach the code is executed every time there is a build. – Marco Altran Feb 26 '15 at 05:33
  • 1
    nevermind. I just needed to change `preBuild.doFirst` to `assembleRelease.doLast`. Awesome! – Marco Altran Feb 26 '15 at 05:39
  • @MarcoAltran Glad it helped and you made a good point btw - I'll have to check it, but maybe doLast would suffice for my use-case as well, rather than using doFirst. – AgentKnopf Mar 02 '15 at 17:25
0

Change the way of configing classpath

classpath(files('build/intermediates/classes/release',"${android.getSdkDirectory().getAbsolutePath() + '/platforms/' + android.compileSdkVersion + '/android.jar'}"))

It works on android gradle 1.5

Victor Choy
  • 4,006
  • 28
  • 35