Gradle将lib中的java类替换为自己的类,以避免重复

时间:2016-07-14 20:10:44

标签: java android gradle package overwrite

在Android Studio中,我需要从通过Gradle(src/org/luaj/vm2/lib/jse/JavaMethod.java)提取的包中覆盖一个特定文件(dependencies {compile 'org.luaj:luaj-jse:3.0.1'})。

我将文件复制到源目录中并使用完全相同的路径并对其进行了更改。这对于正在使用它的单个JUnit测试用例来说效果很好。它也看起来就像它正在为我的项目的正常编译工作(目前无法轻易确认)。

但是,当我尝试通过ProjectType =" Android Tests"的配置立即运行我的所有测试时,我得到Error:Error converting bytecode to dex: Cause: com.android.dex.DexException: Multiple dex files define Lorg/luaj/vm2/lib/jse/JavaMethod$Overload;

是否需要添加到Gradle文件中的特定任务或命令,以确保项目选择本地源目录中的文件?我尝试了Copy tasksourceSets->main->java->exclude命令,但似乎都没有用(我可能做错了)。我还尝试了"排除模块/组"指令"编译"来自this post

运行/调试确认的非默认设置:

  • Type = Android Tests
  • Module = 我的模块
  • 测试:全部包装
  • 包裹:"测试"

我所有的JUnit测试用例都在"测试"封装

任何可以解决这个问题的答案都可以。如果不是Gradle,可能是android清单或本地源文件本身。

[编辑于2016-07-24] 当我的android模拟器运行较低的API时,错误也发生在正常编译上。 API 16和19出错,但API 23没有。

5 个答案:

答案 0 :(得分:6)

问题:链接您的应用时,链接器会找到两个版本

  • org.luaj:luaj-jse:3.0.1:org.luaj.vm2.lib.jse.JavaMethod and
  • {localProject}:org.luaj.vm2.lib.jse.JavaMethod

如何修复:告诉gradle 排除 org.luaj:luaj-jse:3.0.1:org.luaj.vm2.lib.jse.JavaMethod from building

android {
    packagingOptions {
        exclude '**/JavaMethod.class'
    }
}

我没有用"排除类"但它适用于删除重复的gpl许可证文件a" COPYING"。

如果这"排除"不起作用你可以

  • 将lib org.luaj:luaj-jse:3.0.1下载到本地libs文件夹,
  • 使用zip-app打开jar / aar并手动删除重复的类。
  • 从依赖项中移除org.luaj:luaj-jse:3.0.1,因为现在从lib文件夹加载

答案 1 :(得分:3)

我不完全确定我理解你的问题;但是,它听起来像是一个类路径排序问题,而不是真正的文件覆盖问题。

AFAIK,gradle没有对“依赖”部分的排序做出“保证”,除此之外它是可重复的。在编译要自定义的文件版本时,要使测试/系统使用该文件,它必须在类路径中比复制的jar文件更早。

幸运的是,gradle确实允许一种相当简单的方法“预先”到类路径:

sourceSets.main.compileClasspath = file("path/to/builddir/named/classes") + sourceSets.main.compileClasspath

我对你的系统知之甚少,不能更好地定义。但是,您应该能够轻松地根据您的需求进行定制。也就是说,如果需要,您可以将'compile'更改为其他类路径(runtime,testRuntime等)之一。此外,如果这是更好的解决方案,您可以指定您构建的jar文件而不是classes目录。请记住,它可能不是最佳的,但在类路径定义中指定两次是非常无害的。

答案 2 :(得分:2)

这相当令人费解但技术上可行。然而,这不是海报所要求的单一任务:

  1. 从build.gradle中排除所述依赖项,并确保它没有间接包含在另一个jar中(提示:使用./gradlew dependencies进行检查)
  2. 创建一个gradle任务,在已知文件夹中下载所述依赖项
  3. 解压这样的jar,删除违规的.class文件
  4. 将文件夹包含为编译依赖项
  5. 如果可以安全地假设您使用的是Linux / Mac,则可以在第3项上运行一个简单的命令行,它只使用广泛使用的命令:

    mkdir newFolder ; cd newFolder ; jar xf $filename ; rm $offendingFilePath
    

    如果您不关心自动依赖关系管理,可以使用curl下载jar文件,我相信它可以在linux和Mac上广泛使用。

    curl http://somehost.com/some.jar -o some.jar
    

    对于更强大的实现,您可以使用groovy / java代码替换这些简单的命令行。有趣的是,知道gradle可以被视为groovy的超集,这在很多方面可以说是java的超集。这意味着您可以将java / groovy代码放在gradle.build文件中的任何位置。它不干净但有效,而且只是另一种选择。

    对于4,你可以有任何东西

    sourceSets.main.java.srcDirs += ["newFolder/class"]
    

    在build.gradle的根级别,或

    dependencies {
    . . . 
       compile fileTree(dir: 'newFolder', include: ['*.class'])
    . . . 
    

答案 3 :(得分:2)

这是我在Fabio的建议之后最终添加的内容:

//Get LUAJ
buildscript { dependencies { classpath 'de.undercouch:gradle-download-task:3.1.1' }}
apply plugin: 'de.undercouch.download'
task GetLuaJ {
    //Configure
    def JARDownloadURL='http://central.maven.org/maven2/org/luaj/luaj-jse/3.0.1/luaj-jse-3.0.1.jar' //compile 'org.luaj:luaj-jse:3.0.1'
    def BaseDir="$projectDir/luaj"
    def ExtractToDir='class'
    def ConfirmAlreadyDownloadedFile="$BaseDir/$ExtractToDir/lua.class"
    def JarFileName=JARDownloadURL.substring(JARDownloadURL.lastIndexOf('/')+1)
    def ClassesToDeleteDir="$BaseDir/$ExtractToDir/org/luaj/vm2/lib/jse"
    def ClassNamesToDelete=["JavaMethod", "LuajavaLib"]

    //Only run if LuaJ does not already exist
    if (!file(ConfirmAlreadyDownloadedFile).exists()) {
        //Download and extract the source files to /luaj
        println 'Setting up LuaJ' //TODO: For some reason, print statements are not working when the "copy" directive is included below
        mkdir BaseDir
        download {
            src JARDownloadURL
            dest BaseDir
        }
        copy {
            from(zipTree("$BaseDir/$JarFileName"))
            into("$BaseDir/$ExtractToDir")
        }

        //Remove the unneeded class files
        ClassNamesToDelete=ClassNamesToDelete.join("|")
        file(ClassesToDeleteDir).listFiles().each {
            if(it.getPath().replace('\\', '/').matches('^.*?/(?:'+ClassNamesToDelete+')[^/]*\\.class$')) {
                println "Deleting: $it"
                it.delete()
            }
        }
    }
}

我将上传一个直接与jar一起使用的版本。

答案 4 :(得分:0)

如果再得到源jar,则为另一种解决方案:

task downloadAndCopy {
    def downloadDir = "${buildDir}/downloads"
    def generatedSrcDir = "${buildDir}/depSrc"
    copy {
        from(configurations.detachedConfiguration(dependencies.add('implementation', 'xxx:source')))
        file(downloadDir).mkdirs()
        into(downloadDir)
    }

    println("downloading file into ${downloadDir}")

    fileTree(downloadDir).visit { FileVisitDetails details ->
        if (!details.file.name.endsWith("jar")) {
            println("ignore ${details.file.name}")
            return
        }
        println("downloaded ${details.file.name}")
        def srcFiles = zipTree(details.file).matching {
            include "**/*.java"
            exclude "**/NeedEclude*java"
        }
        srcFiles.visit {FileVisitDetails sourceFile ->
            println("include ${sourceFile}")
        }

        copy {
            from(srcFiles)
            into(generatedSrcDir)
        }
    }
}

并记得将depSrc添加到srcDirs

android {
  sourceSets {
    `main.java.srcDirs = ['src/main/java', "${buildDir}/depSrc"] 
  }
}