Skip to content
Advertisement

Android Studio, generate Ormlite Configuration File (ormlite.txt), gradle complains “The JavaExec.main property has been deprecated.”

Ormlite is a library to do object-oriented access to sqlite. It requires that we generate an ormlite.txt file which contains the processed configuration of the database objects in my android project.

In addition to my Android run configuration that will actually launch the Android app, I created an Application run configuration to just execute the main() method. This is how I’ve done it for like 5 years. But, after updating gradle I’m getting the following error:

> Configure project :app
The JavaExec.main property has been deprecated. This is scheduled to be removed in Gradle 8.0. 
Please use the mainClass property instead. See 
https://docs.gradle.org/7.3.3/dsl/org.gradle.api.tasks.JavaExec.html#org.gradle.api.tasks.JavaExec:main 
for more details.

Now that gradle 7 won’t let me run my main() method the way I used to, what code am I supposed to use to let me run it and where do I put that code?

I have -no- idea how gradle actually works, I just follow instructions to copy/paste the snippets into my build.gradle that I am told to when I include things like crashlytics, or whatever. So, when I go look at the docs it suggests, I have -no- idea where I’m supposed to be putting the code it’s telling me to add. I’ve tried a million monkeys with a million Android Studios to try to guess at the right place, but I can’t get it to work.

I didn’t previously need to add any build.gradle code, it would just run through the Android Studio Run Configuration which referenced my app: module for the classpath.

The way it worked before… In Android Studio, I just needed to create a simple class with a main() method that would call the proper ormlite function to generate the file:

public class DatabaseConfigUtil extends OrmLiteConfigUtil {

    public static void main(String[] args) throws SQLException, IOException {
        writeConfigFile("ormlite_config.txt"); // NON-NLS
    }
}

And then create a Run Configuration in Android Studio of type “Application” and choose that class. Then when I ran that config, it would add all of the classes in my project to the classpath and then run the main() method. The code could read all of the classes in the classpath and generate the ormlite.txt file.

screenshot of Android Studio run configuration

Now that gradle 7 won’t let me run my main() method the way I used to, what code am I supposed to use to let me run it and where do I put that code?

Here is my app module build.gradle file:

buildscript {
    repositories {
        mavenCentral()
        // jcenter()
        maven {
            url 'https://maven.google.com/'
            name 'Google'
        }
        google()
    }
}

repositories {
    mavenCentral()
    mavenLocal()
    maven {
        url 'https://maven.google.com/'
        name 'Google'
    }
}

apply plugin: 'com.android.application'
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.google.firebase.crashlytics'

dependencies {
    // a bunch of stuff

}


android {
    // API 31 = Android 12.0 Snow Cone
    compileSdkVersion 31

    defaultConfig {
        applicationId "com.mydomain.myapp"
        minSdkVersion 19
        targetSdkVersion 31

        versionCode 459

        versionName "3.9"

        // Enabling multidex support.
        multiDexEnabled true

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

        vectorDrawables.useSupportLibrary = true

        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [
                        "androidManifestFile": "$projectDir/src/main/AndroidManifest.xml".toString()
                ]
            }
        }

    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    buildFeatures {
        viewBinding true
    }


}

Advertisement

Answer

I spent the last day going through an online course on gradle, so that I could try to understand what was going wrong here. And after all of that, I’m convinced this is actually a bug in Android Studio. When I configure a “Run Configuration” in Android Studio, all that is really happening is that it is creating a gradle script in the background which will implement a gradle task for the Run Configuration that I created.

And that generated gradle script is wrong. It is using a deprecated property and so the gradle build is failing.

It is creating a gradle task of type JavaExec and setting the main property equal to the class name that holds my main() method, but the main property has been renamed to mainClass, so the generated code is incorrect and therefore fails.

I haven’t found a workaround yet, but I’m going to try to see if I can just create a gradle task on my own and run it manually.


UPDATE: I’ve finally figure out a workaround and was able to generate my ormlite configuration file. Strap in.

The essential core of my problem was that when I run my main() method, I need all of my android-based classes in my classpath. Normally, if you have a java gradle project, that is trivial because the standard output of a java gradle project is a jar (or some classes). However, the output of an android project is an apk which doesn’t contain your classes, it contains .dex files.

I wasn’t able to figure out a way to build the android project and skip the dexing step (i.e. build all of the classes needed without smashing them down into .dex files.

But I did find a way to un-dex the dex files: https://github.com/pxb1988/dex2jar

I downloaded the dex2jar code and followed the installation instructions to build all of the various code and scripts for the dex2jar tools. I loved the generated dex-tools directory to my user home directory so that I could have it available in a central place. That gave me a script called d2j-dex2jar.sh that would convert a .dex file back into a .jar file filled with .class files, which was exactly what I needed.

Then I added the following to my app/build.gradle file:

def userhome = System.properties['user.home']
def dex2jaroutputdir = "$buildDir/intermediates/fulljar"


task convertdextojars {
    dependsOn('bundleDebug')

    doLast {

        def dexdir = "$buildDir/intermediates/dex/debug/mergeDexDebug"
        def dex2jar = "$userhome/dex-tools/d2j-dex2jar.sh"

        mkdir(dex2jaroutputdir)

        fileTree(include: ['*.dex'], dir: dexdir).each {
            def filename = it

            exec {
                workingDir dex2jaroutputdir
                commandLine dex2jar, '--force', "$filename"
            }
        }
    }

}

This task depends on the android task bundleDebug which would build the app-debug.apk but I didn’t really need to use the apk itself, because the intermediate files used to build the apk were available, so I could just use those. The .dex files that would get added to the apk are stored in $buildDir/intermediates/dex/debug/mergeDexDebug. I had classes.dex, classes2.dex, and classes3.dex, but your mileage may vary.

The convertdextojars task above, would create an output directory to hold my jars, then use fileTree() to get a collection of all of the filenames matching *.dex in the dexdir. Then for each of those files, it would execute a shell command to de-dex the file. I set the exec working directory to my dex2jaroutputdir because the d2j-dex2jar.sh script will produce the output jar to the current working directory. I add the --force flag to the command line so that it will overwrite the jars if they already exist.

This would get me a set of jars that had almost all the code I needed. It would include all of my android code, all of the code for my dependencies, but it wouldn’t include the android.os.* runtime classes.

Next, I added this code to my app/build.gradle file:

task gendbconfig(type: JavaExec) {
    dependsOn('convertdextojars')

    doFirst {
        println "++++++++++++++++++++++++++++++++++++++++"
        fileTree(include: ['*.jar'], dir: dex2jaroutputdir).each {
            println "jar: $it"
        }

    }

    mainClass = 'com.kenny.stuff.DatabaseConfigUtil'
    classpath = fileTree(include: ['*.jar'], dir: dex2jaroutputdir)
    classpath = classpath.plus(files(android.getBootClasspath()[0]))
    workingDir = "$projectDir/src/main"

}

The doFirst() closure isn’t necessary but it was helpful to ensure that my incoming jar files were what I expected.

mainClass is the class containing my main() method.

The first classpath line, builds the collection of those jars that we just generated, which got me almost to what I needed, but it was missing the android runtime classes themselves. And that’s what the second classpath line does. android.getBootClasspath()[0] resolves to /Users/kenny/Library/Android/sdk/platforms/android-31/android.jar on my system.

As a side note, I tried using the notation classpath += instead of classpath = classpath.plus() but the gradle build failed because of some object type issues.

I set the workingDir to $projectDir/src/main (which means app/src/main, because the ormlite code that generates the config file will try to automatically write the config file to your res/raw/ directory if it can find as a subdirectory of the working directory.

Finally, I opened the Terminal window in Android Studio which automatically put me into the top project folder (one folder higher than the app module/project. And I ran the command ./gradlew app:gendbconfig which tells gradle to run the gendbconfig task in the app module.

And voila.

Writing configurations to /path/to/my/android/project/app/src/main/./res/raw/ormlite_config.txt

User contributions licensed under: CC BY-SA
10 People found this is helpful
Advertisement