7

I have a Gradle build script that successfully builds my project and compiles all the artifacts I need.

However, in a couple of cases I'd like to give other developers the option to patch some of the files. For example, in one of the archives there's an xml file with information about database hooks - some of the devs use other versions (or even engines) and need to change these before they can use the build output.

Instead of having them make changes to a version-controlled file, which they might commit by mistake, I'd like to give them the option to have a local, individual patch file which the build script applies.

In an old ant script, we did something like this

<target name="appcontext-patch" if="applicationContext.patch.file">
    <patch patchfile="${applicationContext.patch.file}" originalfile="${dist.dir}/applicationContext.xml"/>
</target>

but I can't figure out how to do the equivalent in Gradle. Is there a better (i.e. more idiomatic) way of doing this than trying to directly convert this into a call to ant.patch?

Some context
This is how the file ends up in the archive in the first place:

into('META-INF') {
    from 'deployment', {
        include 'applicationContext.xml'
        rename { fn ->  "jboss-spring.xml" }
    }
}

It would be fantabulous if I could just do something like

into('META-INF') {
    from 'deployment', {
        include 'applicationContext.xml'
        rename { fn -> "jboss-spring.xml' }
        patch 'local/applicationContext.xml.patch'
    }
}

and have the patch file applied before the file is put in the archive. I don't mind writing some code to make this possible, but I'm quite new to Gradle and I have no idea where to begin.

4

3 回答 3

6

您应该能够直接将您的 ant 调用翻译成 gradle。

关于如何一般地执行此操作的 gradle 文档。基本上属性成为命名参数,子标签成为闭包。该文档有很多很好的例子。

完成翻译后的 ant 任务后,您可以在适当的任务上放入 doFirst 或 doLast 块。

我的第一个猜测是这样的:

apply plugin: 'java'

assemble.doFirst {
    ant.patch(patchfile: applicationContext.patch.file,
              originalFile: "${dist.dir}/applicationContext.xml")
}

这是未经测试的,所以我很确定它会让你走上正确的道路。目的是在 java 插件组装您的存档之前,您希望 gradle 调用一个闭包。在这种情况下,闭包将执行修补您的 xml 的 ant 操作。

或者,您可以使用上面的任务来执行复制和标记。

task myCopyTask(type: Copy) {
    ...
} << {
    ant.patch(patchfile: applicationContext.patch.file,
              originalFile: "${dist.dir}/applicationContext.xml")
}

在这种情况下,您正在自己编写任务,左移运算符 ( <<) 相当于.doLast但要酷得多。我不确定您喜欢哪种方法,但如果您已经有一个复制任务首先将文件获取到那里,我认为 doLast 会尽可能地使相关代码块彼此靠近。

于 2013-06-12T15:39:46.337 回答
2

RFC 5621定义了一种 XML 修补语言,它使用 XPath 来定位文档中要修补的位置。它非常适合调整配置文件。

Java中有一个开源实现(免责声明:我是作者)。它包括一个过滤器,可以在 Gradle 中用于在任何实现 CopySpec 的任务期间修补 XML 文件。例如:

buildscript {
    repositories { jcenter() }
    dependencies { classpath "com.github.dnault:xml-patch:0.3.0" }
}

import com.github.dnault.xmlpatch.filter.XmlPatch

task copyAndPatch(type: Copy) {
    // Patch file in RFC 5621 format
    def patchPath = 'local/applicationContext-patch.xml'

    inputs.file patchPath
    
    into('META-INF') {
        from 'deployment', {
            include 'applicationContext.xml'
            rename { 'jboss-spring.xml' }
            filter(XmlPatch, patch: patchPath)
        }
    }
}
于 2015-02-21T21:03:44.183 回答
1

如果您想在飞行中更多地执行此操作,我可以想到两种主要技术。两者都涉及编写一些代码,但它们可能对你更有吸引力,我非常有信心 gradle 在任何地方都没有内置这种行为。

我个人认为#1 是更好的解决方案,因为您不需要处理 Copy 任务的内部结构。自定义过滤器感觉更干净,更可重复使用。

1)编写您在复制任务中指定的自定义过滤器。我不知道如何编写自定义过滤器的细节,但我会从这里开始。您应该能够将自定义过滤器放在 buildSrc 中(在 gradle.org 上有很多关于此的信息),然后您只需将其导入 gradle 文件的顶部。如果你用 groovy 编写它,我认为你甚至可以ant.patch()再次使用它。

task copyAndPatch() {
    into('META-INF') {
    from 'deployment', {
        include 'applicationContext.xml'
        rename { fn -> "jboss-spring.xml' }
        filter(MyCustomFilterThatDoesAPatch, patchFile: 'local/applicationContext.xml.patch')
    }
}

2)编写自定义任务。同样,我将把细节留给专家,但您可能可以通过子类化 Copy 任务、添加“patch”属性,然后在执行过程中介入以完成脏活。

于 2013-06-13T03:48:31.277 回答