Gradle 从 lib 中排除 java 类,替换为自己的类以避免重复
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/38383440/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Gradle exclude java class from lib replaced by own class to avoid duplicate
提问by Dakusan
In Android Studio, there is a specific file (src/org/luaj/vm2/lib/jse/JavaMethod.java
) that I need to overwrite from a package that is pulled in via Gradle (dependencies {compile 'org.luaj:luaj-jse:3.0.1'}
).
在 Android Studio 中,src/org/luaj/vm2/lib/jse/JavaMethod.java
我需要从通过 Gradle ( dependencies {compile 'org.luaj:luaj-jse:3.0.1'}
)拉入的包中覆盖一个特定文件( )。
I copied the file into my source directory with the exact same path and made my changes to it. This was working fine for an individual JUnit test case that was using it. It also lookslike it is working for a normal compile of my project (unable to easily confirm at the moment).
我使用完全相同的路径将文件复制到我的源目录中,并对其进行了更改。这对于使用它的单个 JUnit 测试用例工作正常。它看起来也适用于我的项目的正常编译(目前无法轻松确认)。
However, when I try to run all my tests at once via a configuration of ProjectType="Android Tests", I get Error:Error converting bytecode to dex:
Cause: com.android.dex.DexException: Multiple dex files define Lorg/luaj/vm2/lib/jse/JavaMethod$Overload;
.
但是,当我尝试通过 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;
.
Is there a specific task or command that I need to add to my Gradle file to make sure the project selects the file in my local source directory? I tried the Copy taskand 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 this post.
是否需要将特定任务或命令添加到 Gradle 文件中以确保项目选择本地源目录中的文件?我尝试了Copy 任务和sourceSets->main->java->exclude
命令,但似乎都不起作用(我可能做错了)。我还尝试了这篇文章中“编译”下的“排除模块/组”指令。
The non-default settings for the Run/Debug Confirmation:
运行/调试确认的非默认设置:
- Type=Android Tests
- Module=My module
- Test: All in package
- Package: "test"
- 类型=Android 测试
- 模块=我的模块
- 测试:全包
- 包:“测试”
All my JUnit test cases are in the "test" package.
我所有的 JUnit 测试用例都在“test”包中。
Any answer that gets this to work is fine. If not Gradle, perhaps something in the android manifest or the local source file itself.
任何让这个工作的答案都很好。如果不是 Gradle,则可能是 android 清单或本地源文件本身中的某些内容。
[Edit on 2016-07-24] The error is also happening on a normalcompile when my android emulator is running lower APIs. API 16 and 19 error out, but API 23 does not.
[编辑于 2016-07-24]当我的 android 模拟器运行较低的 API 时,正常编译也会发生错误。API 16 和 19 出错,但 API 23 没有。
采纳答案by Dakusan
This is what I ended up adding after Fabio's suggestion:
这是我在 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()
}
}
}
}
I'll upload a version that works directly with the jar later.
稍后我将上传一个直接与 jar 一起使用的版本。
回答by k3b
issue: when linking your app the linker finds two versions
问题:链接您的应用时,链接器发现两个版本
- org.luaj:luaj-jse:3.0.1:org.luaj.vm2.lib.jse.JavaMethod and
- {localProject}:org.luaj.vm2.lib.jse.JavaMethod
- org.luaj:luaj-jse:3.0.1:org.luaj.vm2.lib.jse.JavaMethod 和
- {localProject}:org.luaj.vm2.lib.jse.JavaMethod
howto fix: tell gradle to excludeorg.luaj:luaj-jse:3.0.1:org.luaj.vm2.lib.jse.JavaMethod from building
如何修复:告诉gradle 从构建中排除org.luaj:luaj-jse:3.0.1:org.luaj.vm2.lib.jse.JavaMethod
android {
packagingOptions {
exclude '**/JavaMethod.class'
}
}
I have not tried this with "exclude class" but it works for removing duplicate gpl license files a la "COPYING".
我没有用“排除类”尝试过这个,但它适用于删除重复的 gpl 许可证文件,如“复制”。
If this "exclude" does not work you can
如果这个“排除”不起作用,你可以
- download the lib org.luaj:luaj-jse:3.0.1 to the local libs folder,
- open jar/aar with a zip-app and manually remove the duplicate class.
- remove org.luaj:luaj-jse:3.0.1 from dependencies since this is now loaded from lib folder
- 将 lib org.luaj:luaj-jse:3.0.1 下载到本地 libs 文件夹,
- 使用 zip-app 打开 jar/aar 并手动删除重复的类。
- 从依赖项中删除 org.luaj:luaj-jse:3.0.1 因为它现在是从 lib 文件夹中加载的
回答by JoeG
I am not completely sure I understand your problem; however, it sounds like a classpath ordering issue, not really a file overwrite one.
我不完全确定我理解你的问题;然而,这听起来像是一个类路径排序问题,而不是真正的文件覆盖问题。
AFAIK, gradle does not make a 'guarantee' on the ordering from a 'dependencies' section, save for that it will be repeatable. As you are compiling a version of file that you want to customize, to make your test/system use that file, it must come earlier in the classpath than the jar file it is duplicated from.
AFAIK,gradle 不会对“依赖项”部分的订购做出“保证”,除非它是可重复的。当您编译要自定义的文件版本时,要使您的测试/系统使用该文件,它必须比复制它的 jar 文件更早地出现在类路径中。
Fortunately, gradle does allow a fairly easy method of 'prepending' to the classpath:
幸运的是,gradle 确实允许一种相当简单的“前置”到类路径的方法:
sourceSets.main.compileClasspath = file("path/to/builddir/named/classes") + sourceSets.main.compileClasspath
I don't know enough about your system to define that better. However, you should be able to easily customize to your needs. That is, you can change the 'compile' to one of the other classpath (runtime, testRuntime, etc) if needed. Also, you can specify the jarfile you build rather than the classes directory if that is better solution. Just remember, it may not be optimal, but it is fairly harmless to have something specified twice in the classpath definition.
我对你的系统了解不够,无法更好地定义它。但是,您应该能够轻松地根据您的需要进行自定义。也就是说,如果需要,您可以将“编译”更改为其他类路径之一(运行时、测试运行时等)。此外,如果这是更好的解决方案,您可以指定您构建的 jarfile 而不是 classes 目录。请记住,它可能不是最佳的,但在类路径定义中指定两次是相当无害的。
回答by Fabio
This is rather convoluted but it is technically feasible. However it's not a single task as asked by the poster:
这是相当复杂的,但在技术上是可行的。然而,这不是海报所要求的单一任务:
- Exclude said dependency from build.gradle and make sure it's not indirectly included by another jar (hint: use
./gradlew dependencies
to check it) - create a gradle task that downloads said dependency in a known folder
- unpack such jar, remove offending .class file
- include folder as compile dependency
- 从 build.gradle 中排除所述依赖项并确保它没有被另一个 jar 间接包含(提示:用于
./gradlew dependencies
检查它) - 创建一个 gradle 任务,在已知文件夹中下载所述依赖项
- 解压此类 jar,删除有问题的 .class 文件
- 包含文件夹作为编译依赖项
If it's safe to assume that you're using Linux/Mac you can run a simple command line on item 3, it's only using widely available commands:
如果可以安全地假设您使用的是 Linux/Mac,您可以在第 3 项上运行一个简单的命令行,它只使用广泛可用的命令:
mkdir newFolder ; cd newFolder ; jar xf $filename ; rm $offendingFilePath
If you don't care about automatic dependency management you can download the jar file with curl, which I believe to be widely available on both linux and mac.
如果你不关心自动依赖管理,你可以用 curl 下载 jar 文件,我相信它在 linux 和 mac 上都可以广泛使用。
curl http://somehost.com/some.jar -o some.jar
For a more robust implementation you can substitute such simple command lines with groovy/java code. It's interesting to know that gradle can be seen as a superset of groovy, which is arguable a superset of java in most ways. That means you can put java/groovy code pretty much anywhere into a gradle.build file. It's not clean but it's effective, and it's just another option.
对于更健壮的实现,您可以用 groovy/java 代码替换这样简单的命令行。有趣的是,可以将 gradle 视为 groovy 的超集,在大多数情况下,它可以说是 java 的超集。这意味着您几乎可以将 java/groovy 代码放在 gradle.build 文件中的任何位置。它不干净但很有效,这只是另一种选择。
For 4 you can have something along either
对于 4 你可以有一些东西
sourceSets.main.java.srcDirs += ["newFolder/class"]
at the root level of build.gradle, or
在 build.gradle 的根级别,或
dependencies {
. . .
compile fileTree(dir: 'newFolder', include: ['*.class'])
. . .