Repeating full project paths - how to avoid it

I am working on a large complex project with ~170 subprojects.

The particular subproject that builds our complete release package is quite complex - it has to copy bits and pieces of various other subprojects, which in turn means it depends on the tasks that generate these bits and pieces. Since several of these project references are used in multiple places, I decided early on that it would be a good idea to declare them as separate variables to be references later, like so:

def foo_sdk = project(":Java:Foo:Product:SDK")
def foo_server_installer = project(":ReleaseResources:Installers:Foo:Windows:foo-server:")
def foo_client_installer = project(":ReleaseResources:Installers:Foo:Windows:foo-client:")
def bar_sdk = project(":Java:Bar:Product:SDK")
def bar_demo = project(":Java:Bar:Demo:integration-demo")

Then I could have other tasks that would use it like this:

task prepare_stage1_foobar(type: Copy, dependsOn:
        ]) {

    if (OperatingSystem.current().isWindows()) {
        dependsOn foo_server_installer.tasks.installerBuildAip
		dependsOn foo_client_installer.tasks.installerBuildAip        

    destinationDir = new File(stage1CdDir)
    // ----- INSTALLATION -----

	from ("$foo_server_installer.buildDir/installer") {
        include "Foo Server Setup.exe"
        into "Installation/Foo/Server/Windows"
    // ----- TOOLS -----

    from (bar_demo.projectDir) {
        include "Bar Demo Tool.bat"
        into "Tools/Bar Demo Tool/"

    doLast {
        bar_demo.copyReleasable("$stage1CdDir/Tools/Bar Demo Tool")

The above sample is heavily simplified/unrealistic, it’s just to give you a feeling for the sort of things are part of our release packaging process. The real gradle file is over 300 lines and handles database scripts, migration/upgrade tools, documentation, and lots more. Some of the project variables are only used once, and some of them will be used in 4-7 places for different purposes.

Now the problem is using these kind of project references results in problems, which I only have a vague understanding of. Basically the variables force gradle to evaluate the project “early” and this either locks the project for further modification in the future (bad) or it causes it to miss certain things that are only added later (also bad). To fix these issues I have added several evaluationDependsOn statements, but this is really really ugly and only solves the problems sometimes.

My understanding is that I could avoid these issues if I did not use these project variables, but instead write the full project path every time instead.

Is that really the only “right” way to do things? The full paths are hard to read, and if anything changes there are so many places that this change has to be applied, it would really really suck.

So what can I do?

You can use strings instead of task instances in task.dependsOn. So instead of

task foo {
   dependsOn project(':other')

You can do

task foo {
   dependsOn ':other:bar' 

If you have logic (eg shared variables) you want to apply to all projects you can do

allprojects {
   ext {
      bar_demo = ':Java:Bar:Demo:integration'


task foo {
   dependsOn "${bar_demo}:build" 

See Task.dependsOn and dependency types

Thanks for trying to help, though I am fully aware of strings instead of task instances. The problem is, as I tried to explain in end of my post, that these strings tend to be rather unreadable in larger projects, and that any change in the project path/structure will have to be applied in many different places.

But apparently that is just part of life when dealing with gradle, which really sucks.

EDIT: I see now your proposal for a fix that would give the best of both worlds - yes, that would actually be an improvement, thank you!