Replace tokens in java source code

I would like to improve my current ReplaceVersionPlugin (buildSrc/src/main/kotlin/versioning.gradle.kts)

By default the plugin should replace all occurrences of @VERSION@ with project.version in my Java code.

It works, but has 1 big problem:

It injects itself as source (setSource) for every JavaCompile task, even if the task has already another input set. I’ve verified this behaviour by copying the ReplaceVersionPlugin to ReplaceVersionPlugin2 and applying it. The id(...) order becomes important and one of them will be ignored.

My questions are:

  1. How would this be implemented correctly.
  2. Is there an easier way to replace tokens in java code.
open class ReplaceVersionExtension @Inject constructor(
    project: Project
) {
    val tokens: MapProperty<String, String> = project.objects.mapProperty(String::class.java, String::class.java)
        .convention(project.provider { mutableMapOf("VERSION" to "${project.version}") })
}

val replaceVersion = extensions.create<ReplaceVersionExtension>("replaceVersion")

val replaceVersionInSource by tasks.registering(Sync::class) {
    from(project.the<SourceSetContainer>()[SourceSet.MAIN_SOURCE_SET_NAME].java)
    inputs.property("tokens", replaceVersion.tokens)

    // DeferredReplaceTokens has been added as separate code block below
    filter<DeferredReplaceTokens>("tokens" to replaceVersion.tokens)

    into(layout.buildDirectory.dir("src"))
}

// ======= THIS SHOULD PROBABLY BE MODIFIED  ======= 
tasks.withType<JavaCompile>().configureEach {
    setSource(replaceVersionInSource.get().outputs)
}

My code is based on

and

which led to:

class DeferredReplaceTokens(`in`: Reader) : FilterReader(`in`) {

    private var actualReader: FilterReader? = null

    var tokens: MapProperty<String, String>? = null

    private fun reader(): FilterReader {
        actualReader = actualReader ?: ReplaceTokens(this.`in`).also { replaceTokens ->
            val tokens = this.tokens
            if (tokens == null) {
                println("No tokens set.  tokens is null")
            } else {
                tokens.keySet().get()
                    .map { key -> key to tokens.getting(key).get() }
                    .onEach { println("Token: ${it.first} : ${it.second}") }
                    .map { (key, value) -> ReplaceTokens.Token().also { it.key = key; it.value = value } }
                    .forEach(replaceTokens::addConfiguredToken)
            }
        }
        return actualReader!!
    }


    override fun read(cbuf: CharArray, off: Int, len: Int): Int = reader().read(cbuf, off, len)
    override fun read(): Int = reader().read()
    override fun close() = reader().close()
}

I would not replace tokens in Java code at all.
I would have a properties file with placeholders.
The processResources task is prepared to do some filtering or placeholder replacement.
The code would then read the value from the properties file, or from one class that reads the properties file.

I have already started replacing some version tokens with String implementationVersion = MyApplication.class.getPackage().getImplementationVersion();.
It will however take some time until I manage to clean up everything.

The current version is perfectly fine for us, as I know that no other plugin changes the source of the JavaCompile task.

I am currently trying to really understand gradle and was hoping that the answer to my questions would help me.

1 Like