Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 8 Current »

I think the biggest challenge for our build system is how to implement multiproject builds. It is likely that most of users will do multiproject builds.

Vision

  • Gant enables DRY for multiproject builds. This is were Ant has massively failed so far.
  • Gant offers an intuitive way to write a multiproject build.
  • Gant offers out of the box functionality for standard Java build tasks, which will grow from release to release.
  • Gant offers maximum flexibility for customization. This is were Maven has massively failed.

Directory structure for multiproject builds

I think it is good to be very flexible regarding the possible directory structures. For Steven's use case it was good enough to assume that all projects are toplevel folders of the root folder. But there are many situations were this structure is not expressive enough and therefore should not be imposed.

In my last project we had a structure like:

  • shared
  • client
  • ...
  • services
    • services-shared
    • service1
    • ...
    • serviceN

And I'm sure there are much more complicated project structures out there which can't be changed or people don't want to change them. I propose to have only one assumption. There is a root folder were we (might) have a top level buildfile and all projects folder must be contained somewhere in this root folder.

Determining the projects to be build

Even if I do not consider pathological cases, automatically determining the projects to be build is not easy or at least not cheap. The rule would be that everything is a project which contains a buildfile. If we look at the projects layout described in the section above (nested projects), we would have to scan many, many folders to look for the buildfiles (e.g. src/main/java/org/codehaus/.....). We could try to be smart and have the policy not to look in folders with name src. But the name src is just a convention and could be changed by the user. We could read the configuration of a found project first and then continue our search. But there might be some folder in a project with many files and subfolder which just sits there (e.g. not in use any longer but people don't want to delete it). For every build that is triggered we would have to scan this folder to look for buildfiles. I think an easy way out of this problem is that people have to declare the projects to be build in a top level build file. This has also the advantage to have a static file containing the projects layout.

Implementation

I've been thinking a lot about this in the last days. I think I have a solution I'm happy with now. It is very much inspired from looking at the code of buildr.

Endresult

After processing all the build scripts, our new Gant will produce one classic Gant build script (im memory). So the complete multiproject build is finally described by exactly one flat list of targets which are related by dependency relations.

How to get there

Every subproject is represented by an instance of a Project object. This Project object is amongst other things a container for target object. For each Project object we create a set of default target instances (e.g. compile, test, ...) which we add to the Project object. Every Project must have a unique name (parent-project-name + project-name). The target instances of a Project object have the name: project-name+target-name.

Customization of the default targets

The default targets are not simple target objects. I think about creating for example a class Compile which extendss the class Target. That means the compile target is a target but also a special compile object, that offers methods for customization and of course the action for actually doing the compile.

In our build script we could write for example target("compile").with classpath. Or even better compile.with classpath.

Adding new behavior

For this feature I guess we have to extend the classic Gant a little bit. We might make it a bit more Rakish. Basically I have two additional methods for a target in mind:

  • target("compile").dependsOn(otherTarget1, otherTarget2, ...)
  • target("compile").addAction { ... }

To add behavior we can now either create a new target in our build script and make a default target dependent on it. Or we can simply add behavior to an existing target, which gets executed after the default behavior is executed.

When I talk about the build script I have in mind that every subproject has its build script. That means the behavior changes described above only apply to the build of this particular subproject. If we want a more general change, we have a couple of possibilities. In the build script of a parent project we can say:

  • something like this.getChildren().each(add behavior)
  • addRecursiveTarget(target)

Interproject dependencies

I think we don't have to explicitly declare them. If there is such a dependency, it means somewhere in the build script of the project that depends on the other project, it refers to this other project anyway. For example compile.with project("otherProject").classpath.

Conclusions

Of course there are many details to hammer out. But I'm confident enough now to start implementing something based on the above ideas if we all think this makes sense.

  • No labels