Steve Appling wrote:
Adam Murdoch wrote:
* 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.
The general approach we've taken so far is to sit the DSL on top of a
public API, and I'm pretty sure we'll do the same here (though, often
the public API doesn't get any real coverage in the user guide). To be
more specific:
There are 3 events in a project's life that we're interested in here: 1)
project declaration, where the project is added to the build, 2) build
script compilation, where the class-path is assembled, and 3) project
evaluation, where the build script proper is executed. The approach
which I'm suggesting is really to simply allow a build script to
register an event handler closure for any of these events, just like it
can for afterEvaluate { } or taskGraph.whenReady {}.
So, project { } is simply an event handler for the project declared
event and buildclasspath { } is an event handler for the assemble
classpath (or before-compile) event.
We're going to need some AST magic to extract whichever of these event
handlers need to execute before the script has been compiled. The less
of these, the better, I think. There will not be any AST magic within
the closures themselves. They will just contain statements executed
against a Project (or maybe a ProjectDescriptor/Settings combo thing).
What this means, is that you'll be able to drive the API directly, or
indirectly using your own DSL classes, in these closures. Also, I
imagine this will all sit on top of a ProjectContainer interface,
returned by Build.getProjects() and Project.getSubprojects(). This
container, like TaskContainer, ConfigurationContainer, etc, will allow
you to programatically declare and configure projects.
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'
}
I agree we want to optimise for this case. This is how I want to be able
to compose my own builds :)
* 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?
I don't think so. I want to use the build file name as the default
project name.
Is there a reason to ever change this?
Not sure. There will be :)
Until we find out what that reason is, perhaps we don't need a way for a
build file to specify the project name, other than by naming the build
file appropriately. Which removes (for now) the need to the build file
to register a 'project declared' event handler.
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.
Definitely. The question is where and how should it 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.
If "projects" closure evaluation is not a special event in the life of a
project, ie it is part of the regular build script evaluation, then
everything in the build class-path will be available to the "projects"
closure.
Also, you'll be able to inject configuration into sub projects,
something like:
projects {
... declare some projects
subprojects {
projectDir = file("projects/$name")
buildclasspath {
... inject the classpath config
}
}
}
or
projects {
whenProjectAdded {
buildclasspath {
... inject the classpath config
}
}
... declare some projects
}
An alternative to this configuration injection, would be for a project
classloader to extend their parent's classloader. This might be a simple
starting point for now, as I think the classloader graph will need to be
re-done for whatever modularisation solution we come up with in 0.8.
Thanks for taking the time to think all this through. This is an
important step to help gradle scale up to large projects.
And thank you for the feedback.
Adam
---------------------------------------------------------------------
To unsubscribe from this list, please visit:
http://xircles.codehaus.org/manage_email