JDrupes Builder supports multi-project builds. A multi-project build configuration manages a collection of projects that each provide resources, such as a Java library. In a typical layout, the projects are located in subdirectories of the project’s root directory. The root directory corresponds to a root project in the build configuration, that is, a class implementing RootProject.
The root project provides resources by requesting them from its subprojects, represented in the build configuration by classes that implement Project. The root project can also provide resources itself, typically something like a uber-jar or the combined javadoc for all subprojects.
Examples of multi-project builds can be found in the GitHub repository as demo-project-subprojects and demo-project-library.
Projects in a multi-project build configuration can depend on each other.
Project demo-project-library shows this for an API and its implementation.
public class Api extends AbstractProject implements JavaLibraryProject {
public Api() {
super(name("api"));
}
}
public class Impl extends AbstractProject implements JavaLibraryProject {
public Impl() {
super(name("impl"));
dependency(Expose, project(Api.class));
}
}
The dependency between the implementation and the API project is established
using the Expose intent. The effect of the chosen intent on the handling of
resource requests is described in detail in the documentation for
Project.
Neither Api nor Impl as shown above define any generators for
providing classes (or libraries). Doing this for each project individually
in a multi-project build configuration would be tedious and error-prone.
Common configuration for projects can be defined in the root project using
RootProject.prepareProjects).
This method is automatically executed for each project after the invocation
of super in the project’s constructor.
How a builder project makes use of this mechanism is entirely up to the
project’s author. One approach is to handle only configuration that applies to
all projects in the prepareProject method. Common configuration for
subsets of projects can be defined in a static method that is explicitly
invoked in these project’s constructors.
Another option is to mark projects of a particular type by implementing
a marker interface and use this to control the behavior of prepareProjects.
Although this does not strictly follow classic object oriented design
principles, it is a convenient and pragmatic pattern in this context.
The latter approach is used by
demo-project-library.
Because there are a lot of details to configure, the prepareProjects
method invokes two static helper methods.
@Override
public void prepareProject(Project project) {
setupCommonGenerators(project);
setupEclipseConfigurator(project);
}
The distinctions between the project types can be seen in
setupCommonGenerators:
private static void setupCommonGenerators(Project project) {
if (project instanceof JavaProject) {
if (project instanceof MergedTestProject) {
// ...
} else {
// ...
}
}
if (project instanceof JavaLibraryProject) {
// ...
}
}
At first glance, the complete example on GitHub may appear complex. However, it demonstrates a real-world setup that configures everything required for building and testing artifacts, and for preparing them for publication in a Maven repository.
Keep in mind that no special DSL knowledge is needed. Everything is expressed in plain, easy-to-understand Java code.