Gradle 从库中排除 java class 替换为自己的 class 以避免重复

Gradle exclude java class from lib replaced by own class to avoid duplicate

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

我使用完全相同的路径将文件复制到我的源目录中并对其进行了更改。这对于使用它的单个 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 task and the sourceSets->main->java->exclude command, but neither seemed to work (I may have done them wrong). I also tried the "exclude module/group" directive under "compile" from .

Run/Debug确认的非默认设置:

我所有的 JUnit 测试用例都在 "test" 包中。

任何使它起作用的答案都可以。如果不是 Gradle,可能是 android 清单中的某些内容或本地源文件本身。

[编辑于 2016-07-24] 当我的 android 模拟器 运行ning 更低 APIs 时,normal 编译也会发生错误。 API 16 和 19 错误,但 API 23 没有。

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

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

如何修复:告诉 gradle 排除 org.luaj:luaj-jse:3.0.1:org.luaj.vm2.lib.jse.JavaMethod 来自建筑物

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

我还没有用 "exclude class" 尝试过,但它可以像 "COPYING".

那样删除重复的 gpl 许可文件

如果这个 "exclude" 不起作用你可以

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

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

AFAIK,gradle 不会对 'dependencies' 部分的排序进行 'guarantee',除此之外它将是可重复的。在编译要自定义的文件版本时,要让 test/system 使用该文件,它在类路径中的位置必须早于从中复制的 jar 文件。

幸运的是,gradle 确实允许 'prepending' 到类路径的相当简单的方法:

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

我对你的系统了解不够,无法更好地定义它。但是,您应该能够轻松地根据您的需要进行自定义。也就是说,如果需要,您可以将 'compile' 更改为其他类路径之一(运行时、测试运行时等)。此外,如果更好的解决方案,您可以指定构建的 jarfile 而不是 类 目录。请记住,它可能不是最优的,但在类路径定义中指定两次是相当无害的。

这个比较绕,但是技术上是可行的。然而,这并不是发帖者所要求的单一任务:

  1. 从 build.gradle 中排除所述依赖项并确保它没有被另一个 jar 间接包含(提示:使用 ./gradlew dependencies 检查它)
  2. 创建一个 gradle 任务,将所述依赖项下载到已知文件夹中
  3. 解压此类 jar,删除有问题的 .class 文件
  4. 包含文件夹作为编译依赖项

如果假设您正在使用 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'])
. . . 

这是我根据 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 一起使用的版本。

另一种解决方案,如果我们得到源 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"] 
  }
}