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.
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.
Adam
---------------------------------------------------------------------
To unsubscribe from this list, please visit:
http://xircles.codehaus.org/manage_email