Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Replace token in file before building, but keep token in sources

Tags:

java

gradle

I want to replace a @VERSION@ token in a java source file with a version before building (Gradle is my build system of choice).

In my current script ant.replace(file: 'src/main/java/randers/notenoughvocab/main/Reference.java', token: '@VERSION@', value: version) it replaces the occurrences of @VERSION@ in the actual source file, so after a build all occurrences of the pattern have been replaced by the version and if I change the version the the gradle build file it will no longer find any patterns in there and the version will not update.

I have also seen a task here, but I do not get what values need to be applied for my specific project.

The project layout for my project, if that is needed:
Screenshot of file layout taken from IntelliJ IDEA 14.1.2, Dark theme

like image 799
randers Avatar asked May 04 '15 19:05

randers


5 Answers

You only need to replace @VERSION@ tokens before releasing your software to the public. Here I defined a task compileForRelease that accomplishes it:

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')
}

I don't recommend messing with standard tasks defined by the Java plugin because that would add unnecessary overhead to each and every build.

like image 145
Raffaele Avatar answered Oct 17 '22 14:10

Raffaele


WARNING: As indicated in comments by @Raffaele filtering source code may result in serious problems. This answer assumes that you know well what are you doing and are conscious about potential problems that may occur.

The problem is in the fact that java source files are not copied - they're compiled only - in place. So you need to:

  1. Before compilation - copy the file that contains @VERSION@
  2. Filter the file.
  3. Compile
  4. Restore original file.

Not sure about paths but the following piece of code should be helpful:

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)
    }
}
like image 20
Opal Avatar answered Oct 17 '22 14:10

Opal


I found existing answers somewhat unsatisfying, so here is my solution:

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
}

This addresses various concerns as follows:

  1. Unlike @Opal's answer, the main source remains unmolested; instead it is staged with modifications to $buildDir/src by the processSource task, which mirrors the standard processResources.
  2. Unlike @Gregory Stachowiak's answer, sourceSets.main.java.srcDirs remains the default value and there is no sleight of hand in specifying a location that does not (yet) exist
  3. Unlike @Raffaele's answer, there is no separate task set for release vs other builds. I disagree that separating them is desirable; I think the added complexity is not worth it unless you have measured any performance hit and found it to be unacceptable. Before going with @Raffaele's solution I would even for instance prefer to limit the scope of the filter with include/exclude patterns.
  4. Task dependencies are implicitly defined via outputs.
  5. All locations are taken from defaults and nothing is stringly typed. The only magic value here is src, the directory under $buildDir where the processed source files are put.
  6. (Edit: added 2019/1/12) Other answers do not properly handle situations where only the version has changed. Changing the version should, by itself, invalidate the task output. This is accomplished via inputs.property.
  7. (Edit 2019/5/20) Uses Sync rather than Copy so that files deleted from source are deleted from the filtered source as well (thanks, @Earthcomputer).
like image 36
Aaron Avatar answered Oct 17 '22 15:10

Aaron


To complement other answers, I found this idiom more simple if there's only one value you are looking to change:

task generateSources(type: Copy) {
    from 'src/main/java'
    into 'build/src/main/java'
    filter { line -> line.replaceAll('xxx', 'aaa') }
}
like image 30
Vic Seedoubleyew Avatar answered Oct 17 '22 16:10

Vic Seedoubleyew


I faced the same problem. And I was unable to find working solution. None of the examples I found worked. I believe it might to a fact, that I am new to Gradle and those examples omit some obvious pieces of code. So I looked into Gradle and found my own solution, that I'd like to share:

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
}
like image 30
Gregory Stachowiak Avatar answered Oct 17 '22 15:10

Gregory Stachowiak