On Aug 26, 2008, at 12:47 PM, Adam Murdoch wrote:
Adam Murdoch wrote:
Hans Dockter wrote:
On Aug 7, 2008, at 1:22 PM, Adam Murdoch wrote:
Hans Dockter wrote:
On Aug 6, 2008, at 12:12 PM, Adam Murdoch wrote:
Hans Dockter wrote:
What about doing things differently?
We could introduce a Configuration class which implements
Task. One can add dependencies to a configuration. Executing
such a task means resolving its dependencies. Such a task
would offer also methods to get a path, list of files,
etc ... The compile task for example would depends on its
configuration task(s). A project dependency would establish a
depends relation between the configuration it belongs to and
the configuration of the other project. The artifact
producing configuration of the other project would depend on
the corresponding artifact task(s).
This would remove unnecessary elements from our API. It
decreases the learning curve and simplifies the design. Last
but not least we have finally our Configuration object to
express this important domain concept.
I think this is an excellent idea. We already do something
similar (conceptually) for bundles: A task is added to the
project for each bundle produced by the project, and I can ask
that a bundle be built from the command-line, add dependencies
on it, query it for its location, etc. We could probably come
up with a common approach for configurations and bundles.
A bundle is a task and at the same time a container for archive
tasks on which it depends. Your analogy is that a configuration
is a container for dependencies, right?
Sorry, I meant to say archives instead of bundles, ie we do
something similar for archives (make them available as tasks
which other tasks can depend on). Hopefully that makes more sense.
I'm not sure if Configuration should implement Task, or
whether adding a Configuration would trigger the adding of a
Task that resolves it. The problem with implementing Task is
that there are (at least) 2 interpretations of 'executing' a
configuration: resolving it, and producing/publishing it. By
adding a task instead, we have the option of adding both a
resolve task and a publish task for a configuration. I guess
another option would be to have 2 types of Configuration: one
for incoming dependencies and one for produced artifacts.
In Ivy itself all configurations are equals. They may contain
only external dependencies or only artifacts produced by the
project or both (e.g. a configuration that exposes the
artifacts of a projects plus its external dependencies). I'm
not sure if Ivy misses to model an important concept.
Our configurations could take the same approach as Ivy. The
fact that a configuration contains artifacts to be produced by
the project can be expressed by the fact that this
configuration depends on the respective archive task. A resolve
could be simply defined by calling an Ivy resolve for the
underlying Ivy ocnfiguration.
This makes sense. So, for example, if I have a project that
produces an artifact and includes it in a configuration, I add
an Archive (task) to produce the artifact, then add a
Configuration (task) with a dependency on the archive task.
Adding this dependency declares that the archive is a
publication included in the configuration.
If I want to use the configuration in my project, I add another
task with a dependency on the configuration task. Adding this
dependency declares that the task uses the configuration. Before
my task is executes, the archive is built, the configuration is
resolved, and my task can query the Configuration object for the
files that make up the configuration.
If I want to include artifacts from another project, I can add a
dependency from the configuration task to a configuration task
in the other project.
Right. With the current design we would use an intermediary for
doing this. A project dependency would establish the dependency
between the two configurations of the respective projects. The
project dependency has the additional job to translate this
dependency into ivy language.
I can add more artifacts to the configuration by adding more
dependencies on archive tasks (or any file producing task,
really). I can add external dependencies (log4j, say) by adding
them directly to the configuration task.
Where do you think publishing would happen in all this?
Publishing in the sense of adding an artifact to a repository
happens in the uploadLibs and uploadDists tasks.
I'm interested in how this happens generically, so assume I'm not
using the java plugin. In my example above, then, I would add an
upload task which depends on the configuration it uploads. Adding
the dependency declares that the upload task publishes the config
to a repository. So, the dependency graph ends up like: upload ->
config -> archive. The upload task could potentially be
automatically added when the configuration is added to the project.
If I want to use the config from another project in the same multi-
project build, I really want to depend on the config rather than
the upload task (ie I want to depend on the thing I use, rather
than the step that happens to produce it). So, then ideally I have
a dependency graph like a:task -> a:config -> b:config ->
b:archive. Would we do an implicit publish when b:config is
executed, or would a:config reach in to project b and resolve the
archive from there?
I'm not sure if it was a good idea to introduce a new term
'upload' for this instead of using the term 'publish'.
The remaining open issue is how to deal with cleaning. Building a
dependency without cleaning is not that reliable. This is a
(modified) quote from one of my earlier emails:
One more point we need to think about. If we do a partial build
of project A which has a project dependency on project B. Let's
say we execute 'gradle clean libs'. Project A is cleaned before
its libs are created, not so project B. Only the artifact
producing task is executed. We could declare an additional
dependsOn('projectB') in project A. Such a dependsOn establishes
task dependencies between tasks with similar names of both
projects. Now the clean is done for both projects but also the
libs task is executed for project B which would neutralize our
effort to become more fine-grained regarding project
dependencies artifacts.
Not sure yet. When you execute 'gradle clean libs', you're really
saying 'rebuild the libs and its dependencies', so I think a good
solution is going to allow me to 1. ask gradle to do this from the
command-line, and 2. declare in the build scripts how to do this.
Thinking about it, there are a two things you might want gradle to
do when you execute 'gradle clean libs':
- rebuild the libs and all their dependencies
- rebuild this lib only.
It would be good to handle both these cases.
Definitely. Specially in large builds people want to be able to say:
I know the project dependencies haven't change. So please rebuild
this project only to save time.
Right now the jars of the project dependencies are copied to
the .gradle/build-resolver dir, which is always removed before and
after a build execution. To make the above work I guess we must not
remove this directory and think about whether this can lead to
problems with stale jar's.
Given that cleaning and rebuilding are concepts that pretty much
every build has, it is tempting to bake this concept into the
build tool's domain model. Something like (I haven't thought this
through, its just an example), a project can declare which task
should be run before doing a rebuild of its artifacts (eg a Clean
task). Then, using the configuration dependencies, gradle can
decorate the dependency graph to add in the clean tasks if a
rebuild is being done.
Some other options:
- We add some way to specify a dependency like: "this task depends
on the 'clean' task of each project which this project uses
artifacts from". You can then attach such a dependency to your
project's 'clean' task. Or 'rebuild' task.
- We change the Clean task so that it automatically has such a
dependency, so that you just use the Clean task in your project and
it figures it all out. Alternatively we could add a Rebuild task
which does this.
- We add a command-line option (--rebuild, say) which executes the
'clean' task for all projects whose artifacts are going to be used
during the build.
Yet another approach would be to add a property to the
dependencyManager, let's call it cleanTask. The default behavior of a
ProjectDependency would be to create a dependency from the
configuration (future implementation!) to the cleanTask of the
dependencyProject's dependency manager. This approach would confine
the notion of cleaning a dependency project to the domain model of
dependency management.
- Hans
--
Hans Dockter
Gradle Project lead
http://www.gradle.org
---------------------------------------------------------------------
To unsubscribe from this list, please visit:
http://xircles.codehaus.org/manage_email