Why are only root project properties returned by Gradle properties task run on a multi-project project?

I’ve recently been using the properties task to validate that some changes to my build are having the desired effect on my build properties.

I notice that when I call the properties task on the top-level project of a multi-project build, it only returns the properties for the top-level project. This behavior is surprising to me, since based on Gradle’s documentation, I would expect a task executed by name at the top level to be also called on all subprojects:

The command gradle test will execute the test task in any subprojects, relative to the current working directory, that have that task. If you run the command from the root project directory, you’ll run test in api , shared , services:shared and services:webservice . If you run the command from the services project directory, you’ll only execute the task in services:shared and services:webservice.

The basic rule behind Gradle’s behavior is: execute all tasks down the hierarchy which have this name. Only complain if there is no such task found in any of the subprojects traversed.

Is there something wrong with my understanding of how this should work? Is there something special about the properties task where it doesn’t also apply to the subprojects?

My main goal with this question is to gain a better understanding of how Gradle works. That said, a way to apply the properties task to the subprojects would be welcome as well. I’ve created a similar Stack Overflow question with that focus, rather than the “why”.


Edit: I see that the documentation for the dependencies task states that will only execute in a single project.

The dependencies task will only execute on a single project. If you run the task on the root project, it will show dependencies of the root project and not of any subproject. Be sure to always target the right project when running dependencies.

Is this the same situation/mechanism used with the properties task, just not documented (or not documented where I’ve seen)? Why do these tasks not recurse to subprojects?

Correct.
The task is configured to ignore subproject tasks by the HelpTasksPlugin using an internal API

Then TaskNameResolver does not look at subprojects for the task if impliesSubProjects is true.

Thanks. Using that information, it appears that I can set the “impliesSubProjects” value in my build, which allows me to output all the projects’ properties at once:

properties {
    impliesSubProjects = false
}

You can, but keep in mind that it’s an internal implementation detail not intended to be used by builds and there are no guarantees it will keep working in future Gradle releases.

First, thanks for all the pointers. It’s very helpful in my understanding, and not something I would have necessarily sussed out on my own.

Secondly, thanks for the caution. For my use case of a one-off analysis of all subprojects, this sort of hackery should be fine. But it’s good to be aware that it’s not something that should be relied upon.

That does raise a question for me. It is a public method on DefaultTask, which is a recommended class to extend for creating custom tasks, per Developing Custom Gradle Task Types. I don’t see any documentation indicating that any of the methods in DefaultTask are intended to be internal implementation details. Is there something indicating that this is an internal detail? Or is it more that it’s part of the task API, and not necessarily something builds should be expecting to use?

Unfortunately some parts of Gradle do not hide internals very well, but I feel it is improving over time.

The get/setImpliesSubProjects methods are defined in TaskInternal and implemented by AbstractTask and both are in **.internal.** packages. That’s why I say they are internal (and why they don’t appear in the online javadoc).

As far as DefaultTask goes, ideally stick to methods declared in the Task interface. There is also a section in the user guide on avoiding internal apis.

Thanks. That explains things perfectly, and that is the documentation reference I was missing.

The following packages are listed in the Gradle public API definition, with the exception of any subpackage with internal in the name:

I admit to being a bit confused by the fact that DefaultTask didn’t have anything in it, and extended AbstractTask which was deprecated for removal in 8.0. My (perhaps incorrect) assumption was that the since the AbstractTask class was sticking around for backwards compatibility, everything in it was conceptually part of the public DefaultTask. Sticking to the public interface makes perfect sense, though it is perhaps a bit unfortunate that the internal API is leaking up to subclassers of a public API class designed for subclassing.

Any reason the ability to restrict a task to the level on which it was called is something restricted to the built-in tasks, and not something provided to external task authors?