Gradle ANTLR4 plugin in Android app: build fails

2019-09-18 00:16发布

I spent few days spotting this issue, and I'd like to post my finding here.

I'm building an Android app with several modules, one of them using ANTLR plugin. While the module with ANTLR builds OK, as it is a Java module, the android module fails in transformClassesWithDexForDebug task:

* What went wrong:
Execution failed for task ':Android:transformClassesWithDexForDebug'.
> com.android.build.api.transform.TransformException: com.android.ide.common.process.ProcessException: java.util.concurrent.ExecutionException: java.lang.RuntimeException: Translation has been interrupted

If I try to enable multiDexEnabled in build.gradle I get a different message:

* What went wrong:
Execution failed for task ':Android:transformClassesWithJarMergingForDebug'.
> com.android.build.api.transform.TransformException: java.util.zip.ZipException: duplicate entry: org/abego/treelayout/Configuration$AlignmentInLevel.class

Some answers here suggests increasing Java RAM for dexing, reducing dependencies such as google-play library - I did that but no joy.

Here go build.gradle samples used:

ANTLR4 module:

apply plugin: 'antlr'
apply plugin: 'java'
apply plugin: 'maven'

sourceCompatibility = 1.7
targetCompatibility = 1.7

final GENERATED_MAIN = "src-gen/main/java"
final GENERATED_TEST = "src-gen/test/java"

sourceSets {
    main {
        java { srcDirs += [GENERATED_MAIN] }
    }
    main {
        java { srcDirs += [GENERATED_TEST] }
    }
}

repositories {
     mavenCentral()   
}

dependencies {
   antlr("org.antlr:antlr4:4.5") {
       exclude group: 'org.antlr', module:'antlr-runtime'
//       exclude group: 'org.antlr', module:'antlr4-runtime'
       exclude group: 'org.antlr', module:'ST4'
   }
    compile ('com.yuvalshavit:antlr-denter:1.0') {
       exclude group: 'org.antlr', module:'antlr4-runtime'
    }
    testCompile group: 'junit', name: 'junit', version:'4.11'
}

generateGrammarSource.doFirst {
    outputDirectory = new File(GENERATED_MAIN)
}
generateGrammarSource.doLast {
    moveAntlrGeneratedFilesToTheirPackages(source, GENERATED_MAIN)
}
generateTestGrammarSource.doFirst {
    outputDirectory = new File(GENERATED_TEST)
}
generateTestGrammarSource.doLast {
    moveAntlrGeneratedFilesToTheirPackages(source, GENERATED_TEST)
}

def moveAntlrGeneratedFilesToTheirPackages(FileTree grammarFiles, generatedFolder) {
    grammarFiles.each {File file ->
        final grammarName = file.name.lastIndexOf('.')>=0 ? file.name[0 .. file.name.lastIndexOf('.')-1] : file.name
        final grammarPackage = extractPackageNameFromGrammerFile(file)
        copy {
            from generatedFolder
            include "${grammarName}*.*"
            into generatedFolder + "/" + grammarPackage.replaceAll("\\.", "/")
        }
    }
    project.delete fileTree(generatedFolder).include('*.*')
}

def extractPackageNameFromGrammerFile(File grammarFile) {
    def grammarPackage = "unknown.package"
    def packageRegex = ~/[ ]*package[ ]*([a-zA-Z]+[a-zA-Z0-9.-_]*)[ ]*;/
    grammarFile.eachLine { line ->
        def matcher = packageRegex.matcher(line)
        if (matcher.find()){
            grammarPackage = matcher.group(1)
        }
    }
    return grammarPackage
}

android module:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.2"

    defaultConfig {
        applicationId "<some package name here>"
        minSdkVersion 14
        targetSdkVersion 22
//        multiDexEnabled true
    }


    dexOptions {
//        javaMaxHeapSize "4g"
    preDexLibraries = false
    }

    lintOptions {
          abortOnError false
    }

    sourceSets {
        main {
            manifest.srcFile 'AndroidManifest.xml'
            java.srcDirs = ['src']
            resources.srcDirs = ['src']
            aidl.srcDirs = ['iadl']
            renderscript.srcDirs = ['src']
            res.srcDirs = ['res']
            assets.srcDirs = ['assets']
        }
    }

}

dependencies {
    compile project(':texas-board')   // this references the ANTLR module mentioned above
    ...
}

root build.gradle:

buildscript {
    repositories {
        jcenter()
    mavenCentral()
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:2.1.2'
    }
}

analyzing dependencies (gradle dependencies) shows that the problem is that antlr gradle plugin includes org.abego.treelayout classes both via antlr4 library used for compilation, and antlr4-runtime:

+--- project :texas-board
|    \--- project :rules_engine
|         +--- org.antlr:antlr4:4.5
|         |    +--- org.antlr:antlr4-runtime:4.5
|         |    |    \--- org.abego.treelayout:org.abego.treelayout.core:1.0.1
|         |    \--- org.antlr:antlr-runtime:3.5.2
|         \--- com.yuvalshavit:antlr-denter:1.0

once again - the matter is that the org.abego.treelayout classes are present both in org.antlr:antlr4:4.5, org.antlr:antlr4-runtime:4.5 and probably even in org.abego.treelayout:org.abego.treelayout.core:1.0.1. I guess that it is a bug with antlr4 plugin for gradle - they wrongly added those classes in main package while they are supposed to be in dependencies only. Probably I should submit a bug to the plugin tracker.

Moreover, antlr4 plugin even adds antlr3-runtime dependency which may redefine org.abego.treelayout classes as well (so I also excluded it).

While Java apps accept extra copies of java classes (they take the first one from classpath I believe), android plugin fails in dex stage reporting errors mentioned above.

1条回答
老娘就宠你
2楼-- · 2019-09-18 01:01

My solution was to remove duplicate dependencies:

apply plugin: 'antlr'
apply plugin: 'java'
apply plugin: 'maven'

sourceCompatibility = 1.7
targetCompatibility = 1.7

final GENERATED_MAIN = "src-gen/main/java"
final GENERATED_TEST = "src-gen/test/java"

sourceSets {
    main {
        java { srcDirs += [GENERATED_MAIN] }
    }
    main {
        java { srcDirs += [GENERATED_TEST] }
    }
}

repositories {
     mavenCentral()   
}

dependencies {
   antlr("org.antlr:antlr4:4.5") {
       exclude group: 'org.antlr', module:'antlr-runtime'
       exclude group: 'org.antlr', module:'antlr4-runtime'
       exclude group: 'org.antlr', module:'ST4' // just in case :-)
   }
    compile ('com.yuvalshavit:antlr-denter:1.0') {
       // denter library also has dependency on antlr4-runtime ...
       exclude group: 'org.antlr', module:'antlr4-runtime' 
    }
    testCompile group: 'junit', name: 'junit', version:'4.11'
}
...

I'm open for any better solution you can suggest - I'm not good with gradle.

查看更多
登录 后发表回答