Adam Murdoch wrote:
Hi,

I'd like to come up with a plan for removing the settings.gradle file.

(apologies for the essay - I think this stuff needs to be considered as a whole)

There's 5 things we use settings.gradle for, which we will need replacements for:

1. Defining the project heirarchy. That is, which projects are included in thie build?

2. Defining the project descriptor for each project in the build. That is, what is the project name, build file, project dir and build dir for each project?

3. Defining the build script classpath(s). That is, which classes are available at compilation time and execution time for each build script.

4. Defining the classpath(s) for custom plugins to be used in the build script. Currently we use the build script classpath for the custom plugin classpath, but I think they really are separate use cases.

5. Locating the root project of the build when running from the command-line.

Plus, the solution should allow us to later add better ways of composing the build and plugin classpaths, such as by using project dependencies.

I have some suggestions, but they're really just a starting point for discussion.


* Defining the heirarchy

It would be useful, I think, for any project to be able to define its own subprojects. This allows for better build encapsulation, and would allow small builds to be more easily composed into multiple larger builds. Perhaps something like this in the build file:

projects {
  subProject {
     buildFile = 'sub-project.gradle'
  }
  anotherSubProjectUsingDefaults
aSubProjectConfiguredUsingAMap(projectDir: 'sub', buildFile: 'sub.gradle')
}

This could be nested to allow a project tree to be specified:

projects {
   subProject {
       anotherProject { ... }
   }
}

I think we should do away with the processing stage which figures out which projects are included in the build, and simply allow them to be added at any point during the evaluation stage. That is, projects are just regular domain objects, not anything special. I think this consistency important. In addition, this would mean you can use plugins or classes in buildSrc to define or influence the project hierarchy without us doing anything special to support it. The same would be true of any future capability we add to let you compose the build logic. To me, this approach seems more flexible and more likely to handle use-cases we haven't anticipated, than would special-casing assembly of the project hierarchy.

There are some down-sides, but I think we can come up with simple solutions to those (or already have, in the Task model).


One of the reasons that we have our own fork of Gradle currently is that we needed to be able to programmatically construct the set of projects used in a build. We would like to to accomplish the same goals with this, but it looks like you may be planning to use AST transformations or another compile time technique to implement your scheme. If so, please provide an API oriented alternative.

A common situation may be a single root project with a large number of child projects where all you want to specify in the root is the location of all the children's gradle files. In this case, all of the project configuration may be done in the individual subproject gradle files. A simplified syntax for this case might be:

projects  {
   include 'sub/sub.gradle', 'sub2/gradle/sub2.gradle'
}


* Project Descriptor

It makes sense that a build script should be able to define the name of the project, something like:

project {
   name = 'myProject'
   // maybe some other properties as well
}

Given that the name is fundamental to the identity of the project, this closure would have to be executed as soon as the project is included in the build (regardless of how that might happen). We would need to do some magic to extract just this closure. I think it should be possible.

It's a closure so that 1) we can find it easily, 2) you can use statements to configure the descriptor, and 3) so we can add more properties or methods to the descriptor later.

Some issues:

- There is the potential for 2 different names to be specified for the project: one in the ancestor project, and one in the project itself. Not sure how to resolve this.

- Which properties should be available for configuring in the descriptor? For example, do we let you specify the project or build dirs? Let you apply plugins? Define tasks/configurations/repositories? Set arbitrary project properties? Are you simply configuring a Project object, where the name property is mutable while the closure executes?

- Which properties can only be specified in the descriptor? Certainly the name is one. If we let you specify the project or build dirs, should we make them immutable after the descriptor has been configured?

- What about the ancestor parent which includes the project? What can it configure?

Names:
In our Gradle fork, some of the changes we made were to allow us to debug .gradle files in IDEA. We found it necessary to have distinct names for the gradle files in each project, so for the project "reportengine", the gradle file would just be "reportengine.gradle". This is also much nicer to deal with in an IDE's editor where you often just see the file name on a editor's tab, not the full path. It was confusing to see 6 different "build.gradle" files. Is there a problem with just using the base name of the .gradle file as the project name? Is there a reason to ever change this?

Other configured properties:
Currently we need to store the gradle files in a subdirectory of the project directory to avoid some problems with the location of IDEA's source directories. Please allow both the project and build dirs to be configured.


* Build Script Classpath

Again, it makes sense that each project can specify its own classpath. Perhaps, something like:

buildclasspath {
   repositories { .... }     dependencies {
       build name: 'somelib', version: '..'
       build group: '..' ...
   }
}

That is, a mini project which allows you specify repositories and dependencies (but maybe not configurations). Like the project descriptor, this closure would be executed as a special step before compiling the build script proper.

Some issues:

- Do we configure the project descriptor before the build classpath? Or the other way around? That is, can you use the project name in the buildclasspath { } closure, or can you use the classpath in the project { } closure?


We would like to be able to use classes from the buildclasspath inside the project closure. It would be nice to be able to share some of this from the root project. It will be verbose to redefine things like the company repo in every build file. Currently we inject the company repo from the root project for the configuration phase so it is only specified in one place. Syntactically it might be nice to nest the "buildclasspath" section inside of the root projects "projects" section, but this may conflict with my goal of using the buildclasspath in the evaluation of this section.

- Do we continue with a single classloader for all projects? I'd be tempted to create a classloader per project.

A classloader per project helps avoid conflicts as projects age and diverge. Over time older subprojects may have different (and possibly conflicting) buildclasspath needs from a newer subproject.


* Custom plugins

Some possibilities:

- Continue to use the build script classpath for locating plugins.
- Use a separate configuration for plugins in the buildclasspath { } closure. - Allow dependencies for a plugin to be declared closer to where the plugin is applied, something like:

usePlugin(SomeClass) {
dependencies group: 'myOrg', name: 'myBuildInfrastructureProject', version: '1.0+'
}

By teasing apart the build script and plugin classpaths, particularly if we end up with a classpath per plugin, we are taking a big step towards Gradle modularisation. In addition, if we add file dependencies and project dependencies to the mix, we end up with a really flexible solution for composing build logic - in particular sharing this stuff across the organisation.


Having a separate configuration sounds the most flexible even though we don't currently have a need for this.


* Finding the root project.

When settings.gradle disappears, we lose the marker file which helps us find the root project.

A simple replacement is to scan up the directory hierarchy and treat the highest *.gradle file we find as the root project's build file. We would replace the --settings-file command-line option with a --root-project command-line option in those cases where this doesn't work.

Alternatively, we could allow the project descriptor to point to the parent project. Or combine these two options.

You can scan up the hierarchy by default, but please allow some way to specify the parent in the project descriptor. Currently in our system, the root project is actually a peer in the directory structure.



Adam


---------------------------------------------------------------------
To unsubscribe from this list, please visit:

   http://xircles.codehaus.org/manage_email



Thanks for taking the time to think all this through. This is an important step to help gradle scale up to large projects.

--
Steve Appling
Automated Logic Research Team

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

   http://xircles.codehaus.org/manage_email


Reply via email to