Pol
Pol

Reputation: 4008

How to generate on the fly Java source code to build with Gradle in Android Studio?

As part of an Android app that uses the NDK, I need to export some constants from the C/C++ world into Java. For obvious reasons, I'd rather have this automated. I'm using Android Studio with the NDK support.

How can I generate some .java file on the fly from a shell script that would be ran by Gradle whenever the app builds? Ideally this .java file would go in an build intermediaries directory somewhere and wouldn't have to be in the app source directory.

As an example, the shell script would generate this Java source:

public enum Type {
  FOO,
  BAR
}

from this C++ source:

enum class Type {
  FOO,
  BAR
}

For reference here's a simplified version of the Gradle file I'm starting from in Android Studio:

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

model {

    compileOptions.with {
        sourceCompatibility = JavaVersion.VERSION_1_7
        targetCompatibility = JavaVersion.VERSION_1_7
    }

    android {
        compileSdkVersion = 23
        buildToolsVersion = "23.0.2"

        defaultConfig.with {
            applicationId = "..."
            minSdkVersion.apiLevel = 18
            targetSdkVersion.apiLevel = 23
        }

        defaultConfig.multiDexEnabled = true
    }

    android.ndk {
        platformVersion = "18"
        moduleName = "jni"
        ...
    }

    android.buildTypes {
        release {
            minifyEnabled = false
            proguardFiles.add(file('proguard-rules.txt'))
            ndk.with {
                CFlags.add("-Werror")
                cppFlags.add("-Werror")
            }
        }
    }

    android.productFlavors {
        create("arm7") {
            ndk.abiFilters.add("armeabi-v7a")
        }
    }

}

repositories {
    ...
}

dependencies {
    ...
}

apply plugin: 'com.google.gms.google-services'

Upvotes: 2

Views: 2055

Answers (1)

JBirdVegas
JBirdVegas

Reputation: 11405

You could just write the generated file to the $buildDir/generatedJava then add that directory as a source folder. Then make the task javaCompile depend on your task that generates the source. Something like this could be used inside a build.gradle

def outputJavaFile = new File("$buildDir.absolutePath/generatedJava", 'MyEnum.java')
task generateSources(type: Exec) {
    def source = $/
package com.example;
public enum Something {
   One,
   Two
}
/$

    if (!outputJavaFile.parentFile.exists()) {
        outputJavaFile.parentFile.mkdirs()
    }
    outputJavaFile.withWriter {
        it << source
    }
}

compileJava.dependsOn outputJavaFile

sourceSets {
    main {
        java {
            srcDirs 'src/main/java', outputJavaFile.absolutePath
        }
    }
}

// if the goal is to generate the source from a script then just call the script
// inside the Exec closure
task shellScriptToGenerateSources(type: Exec) {
    commandLine 'myShellScript.sh'
}
// then make compileJava depend on the task that runs the script
compileJava.dependsOn shellScriptToGenerateSources

Edit: To apply the same logic to your updated build.gradle file might look something like this

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

// define an output folder for our generated .java files
def GENERATED_JAVA_OUTPUT = "$buildDir/generatedJava"
// if the goal is to generate the source from a script then just call the script
// inside the Exec closure
task shellScriptToGenerateSources(type: Exec) {
    commandLine 'myShellScript.sh'
}
// then make compileJava depend on the task that runs the script
compileJava.dependsOn shellScriptToGenerateSources

model {

    compileOptions.with {
        sourceCompatibility = JavaVersion.VERSION_1_7
        targetCompatibility = JavaVersion.VERSION_1_7
    }

    android {
        compileSdkVersion = 23
        buildToolsVersion = "23.0.2"

        defaultConfig.with {
            applicationId = "..."
            minSdkVersion.apiLevel = 18
            targetSdkVersion.apiLevel = 23
        }

        defaultConfig.multiDexEnabled = true
    }

    android.ndk {
        platformVersion = "18"
        moduleName = "jni"
        ...
    }

    android.buildTypes {
        release {
            minifyEnabled = false
            proguardFiles.add(file('proguard-rules.txt'))
            ndk.with {
                CFlags.add("-Werror")
                cppFlags.add("-Werror")
            }
        }
    }

    android.productFlavors {
        create("arm7") {
            ndk.abiFilters.add("armeabi-v7a")
        }
    }

    // let android know that our java sources shoudl also consider the generated java for the compiler
    android.sourceSet {
        main {
            java {
                srcDir(GENERATED_JAVA_OUTPUT)
            }
        }
    }
}

repositories {
    ...
}

dependencies {
    ...
}

apply plugin: 'com.google.gms.google-services'

Upvotes: 2

Related Questions