【问题标题】:Replace token in file before building, but keep token in sources在构建之前替换文件中的令牌,但将令牌保留在源中
【发布时间】:2019-11-01 23:34:51
【问题描述】:

我想用构建之前的版本替换 java 源文件中的 @VERSION@ 标记(Gradle 是我选择的构建系统)。

在我当前的脚本ant.replace(file: 'src/main/java/randers/notenoughvocab/main/Reference.java', token: '@VERSION@', value: version) 中,它替换了实际源文件中出现的@VERSION@,因此在构建后,所有出现的模式都被版本替换,如果我更改版本,gradle 构建文件它将不再在那里找到任何模式,并且版本不会更新。

我还看到了一个任务here,但我不知道我的具体项目需要应用什么值。

我的项目的项目布局,如果需要的话:

【问题讨论】:

  • 我的建议是将版本标记放在属性文件模板中,将属性文件复制到目标目录中替换该过程中的标记,然后在 Java 类中读取属性文件。版本必须嵌入到 .class 文件中是否有特定原因?

标签: java gradle


【解决方案1】:

在向公众发布您的软件之前,您只需替换 @VERSION@ 令牌。这里我定义了一个任务compileForRelease 来完成它:

import org.apache.tools.ant.filters.ReplaceTokens

task sourcesForRelease(type: Copy) {
    from 'src/main/java'
    into 'build/adjustedSrc'
    filter(ReplaceTokens, tokens: [VERSION: '2.3.1'])
}

task compileForRelease(type: JavaCompile, dependsOn: sourcesForRelease) {
    source = sourcesForRelease.destinationDir
    classpath = sourceSets.main.compileClasspath
    destinationDir = file('build/adjustedClasses')
}

我不建议使用 Java 插件定义的标准任务,因为这会给每个构建增加不必要的开销。

【讨论】:

  • @RANders00 这是你的项目——无论如何,Opal 的建议是实现你目标的一种非常奇怪的方式。我希望您尝试过我在此答案中建议的更清洁的替代方案。它更容易理解,你只需要删除十行 Groovy 代码并输入.\gradlew doc
  • @RANders00 实际上 gradlew build 应该 NOT 触摸工作目录(请参阅 cmets 对 Opal 的回答)。目录adjustedSrc 包含替换了标记的源文件,然后被提供给任务文档以生成javadocs(你可以做同样的事情来生成.class 文件的jar)
  • 与标准任务(如 compileJava)混淆的问题是您为每个构建添加替换开销。不好的做法。构建应该尽可能快,并且您只需要在发布时进行替换(例如,稍后您可能会添加一个可能不需要过滤步骤的 CI 服务器 - CPU 周期是稀缺资源!)
  • @Raffaele 发布特定任务的问题是您可能会引入仅在发布版本中出现的错误,我会说这是不好的做法。最好早点失败。它还会增加创建版本的复杂性。
  • 无法理解您的提案如何降低生产错误的可能性,因为它是完全相同的转换。唯一不同的是我的建议保留了源代码
【解决方案2】:

我发现现有的答案有些不满意,所以这是我的解决方案:

import org.apache.tools.ant.filters.ReplaceTokens

task processSource(type: Sync) {
    from sourceSets.main.java
    inputs.property 'version', version
    filter(ReplaceTokens, tokens: [VERSION: version])
    into "$buildDir/src"
}

compileJava {
    source = processSource.outputs
}

这解决了以下各种问题:

  1. 与@Opal 的回答不同,主要来源仍然不受干扰;相反,它通过processSource 任务对$buildDir/src 进行了修改,这反映了标准processResources
  2. 与@Gregory Stachowiak 的回答不同,sourceSets.main.java.srcDirs 仍然是默认值,并且在指定(尚)不存在的位置时没有任何技巧
  3. 与@Raffaele 的回答不同,发布与其他版本没有单独的任务集。我不同意将它们分开是可取的;我认为增加的复杂性是不值得的,除非您测量了任何性能损失并发现它是不可接受的。在使用 @Raffaele 的解决方案之前,我什至更喜欢使用包含/排除模式来限制 filter 的范围。
  4. 任务依赖是通过输出隐式定义的。
  5. 所有位置都取自默认值,没有任何内容是字符串类型的。这里唯一的魔法值是src$buildDir 下的目录,处理过的源文件存放在其中。
  6. (编辑:添加于 2019/1/12)其他答案无法正确处理仅版本更改的情况。更改版本本身应该会使任务输出无效。这是通过inputs.property 完成的。
  7. (编辑 2019/5/20)使用 Sync 而不是 Copy,以便从源中删除的文件也会从过滤后的源中删除(感谢 @Earthcomputer)。

【讨论】:

  • 我会使用Sync 任务而不是Copy,以便删除$buildDir/src 中从src/main/java 中删除的文件。除此之外,+1,最佳答案在这里:)
  • 是的,Sync 更好。谢谢!
  • 这似乎只适用于 java 源文件。有没有办法用任意文件做到这一点?
【解决方案3】:

警告:@Raffaele 在 cmets 中指出,过滤源代码可能会导致严重问题。此答案假定您很清楚自己在做什么,并且意识到可能发生的潜在问题。

问题在于 java 源文件没有被复制——它们只是被编译——就地。所以你需要:

  1. 编译前-复制包含@VERSION@的文件
  2. 过滤文件。
  3. 编译
  4. 恢复原始文件。

不确定路径,但以下代码应该会有所帮助:

apply plugin: 'java'

version = '0.0.1'
group = 'randers.notenoughvocab'
archivesBaseName = 'NotEnoughVocab'

def versionFile = 'src/main/java/randers/notenoughvocab/main/Reference.java'
def tempDir = 'build/tmp/sourcesCache'
def versionFileName = 'Reference.java'

compileJava.doFirst {
    copy {
        from(versionFile)
        into(tempDir)
    }
    ant.replace(file: versionFile, token: '@VERSION@', value: version)
}

compileJava.doLast {
    copy {
        from(tempDir + '/' + versionFileName)
        into(project.file(versionFile).parent)
    }
}

【讨论】:

  • @RANders00,是的,我刚刚写了应该如何实现 :)
  • -1:这在概念上是错误的并且非常危险:构建工具永远不应该接触源文件。即使源代码位于某些源代码控制系统中,当构建失败(或构建文件中存在错误)并且工作目录不干净时,您也可能会永远浪费数小时(数天?)的工作。这篇文章对于来自 Google 的其他用户来说也很危险,他们会在他们的构建文件中复制和调整此代码而不了解风险
  • @Raffaele,大体上同意。但是,我不会永远失去这项工作,尤其是在涉及 SCM 工具时。我不会以呈现的方式来做——而是使用属性文件,这是一种常见的做法,有时代码中需要版本号——而不仅仅是在文档中。我只是假设 OP 知道他想要什么和做什么。
  • OP 只想在发布之前替换 @VERSION@ - 替换令牌一次然后提交是没有用的。否则当标记下一个版本并且没有更多@tokens@时你会怎么做?这里的问题是如何。当您在具有本地修改的工作副本中启动构建时,您可能会丢失工作。假设您在过去的三个小时内进行了繁重的重构。在提交之前,您要构建源代码。 buildscript 中有一个错误,它将所有 .java 文件中的“set”替换为“get”。这里没有“撤消”按钮,您只是损失了三个小时的工作
  • 同意,确实有道理。我可能会删除@RANders00 不接受的答案。
【解决方案4】:

我遇到了同样的问题。而且我找不到有效的解决方案。我发现的例子都没有奏效。我相信这可能是一个事实,我是 Gradle 的新手,并且这些示例省略了一些明显的代码片段。所以我研究了 Gradle 并找到了我自己的解决方案,我想分享一下:

import org.apache.tools.ant.filters.ReplaceTokens

// Change compiler source directory
sourceSets {
    main {
        java {
            srcDirs = ['build/src/main/java']
        }
    }
}

// Prepare sources for compilation
task prepareSources(type: Copy) {
    from('src/main/java')
    into('build/src/main/java')
    filter(ReplaceTokens, tokens: [pluginVersion: version])
}

// Prepare sources, before compile
compileJava {
    dependsOn prepareSources
}

【讨论】:

  • 这对我有用。请注意,尽管我的做法略有不同,但我需要“重建”项目。
【解决方案5】:

为了补充其他答案,如果您只想更改一个值,我发现这个习语更简单:

task generateSources(type: Copy) {
    from 'src/main/java'
    into 'build/src/main/java'
    filter { line -> line.replaceAll('xxx', 'aaa') }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-04-11
    • 2015-08-18
    • 2012-05-21
    • 1970-01-01
    • 2017-06-18
    • 2011-04-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多