Hack to pass $APP_HOME as system property in start scripts no longer working

I’m using the application plugin and am creating custom start scripts for my main class. At runtime, that main class needs to be aware of the application home directory where it’s being installed to via installDist as I’m bundling some files and directories with the distribution by adding some custom content to the distributions {} stanza. Hence I’m using the defaultJvmOpts parameter to pass the $APP_HOME variable which is defined in the start script template and the resulting script generated from it: defaultJvmOpts = ['-Dfoo=$APP_HOME']. The $ sign gets escaped in the process, so I have been using a hack that worked for years to remove the escaping character using a doLast action. See this minimal build script for a working example: gradle-scripts-tests/build.gradle at master · sebkur/gradle-scripts-tests · GitHub

I think I originally came across the idea on either of these sources:

This approach worked until Gradle 6.9.1 and seems to stop working starting with Gradle 6.9.2.

The script template changed significantly across the two versions, so I’m not sure what exactly is causing this.

I came across two related issues:

I have set up a testing project that exhibits the behavior both for some older versions of Gradle where this was still working and some more recent versions where it does no longer work: GitHub - sebkur/gradle-scripts-tests

I realize this is a hack and maybe it was never supposed to work like this as I’m kind of exploiting some internal information from the CreateStartScripts task’s script template (the variable name of $APP_HOME). Also it seems passing variables is not something supported as by default the $ gets escaped.

Could this be fixed somehow? Or is there a better way to make the main class aware of the application home directory at runtime?

This is the commit that changed the behavior: Fix arg handling to avoid unguarded 'eval' · gradle/gradle@8e6d38e · GitHub

Apparently there’s even an integration test def "can use APP_HOME in DEFAULT_JVM_OPTS with custom start script"() that is supposed to make sure APP_HOME can be used in DEFAULT_JVM_OPTS: gradle/ApplicationPluginIntegrationTest.groovy at 27736f787ed3005acc2c777eb8f0c85e66dcc860 · gradle/gradle · GitHub

Turns out the integration test does work and also gave insight into what was going wrong in my own build files. I was using something like:

    defaultJvmOpts = ['-Dfoo=$APP_HOME']
    doLast {
        unixScript.text = unixScript.text.replaceAll('\\\\\\\$APP_HOME', '\\$APP_HOME')

while the script in the integration test does something simpler:

    defaultJvmOpts = ['-Dfoo=APP_HOME_PLACEHOLDER']
    doLast {
        unixScript.text = unixScript.text.replace('APP_HOME_PLACEHOLDER', "'\$APP_HOME'")

Apparently my first approach relied on the way the script escaped the $ while the second one doesn’t.