We will use the Phylonco project as an example, to learn how to setup project dependencies, and understand the difference between consumers and producers.

1. phylonco-lphy

Figure 1: The subproject "phylonco-lphy".

After the version and base name are defined, the second block declares the dependencies. The first dependency is lphy module. The implementation is a Gradle pre-defined configuration to resolve dependencies. The string inside implementation defines GAV coordinate (Group, Artifact, Version), which will map to a unique corresponding version of a software from all releases. It also can be used as a keyword to search in the Maven central repo, for example, the group io.github.linguaphylo.

We use the default model “require” declaring versions, such as io.github.linguaphylo:lphy:1.3.1. This implies that the selected version of lphy cannot be lower than 1.3.1 but could be higher through conflict resolution, when there are multiple versions in the local dependency repository. When the exact version is required, the “strictly” declaring versions can be used. For example, if only lphy 1.2.0 is required, you can use io.github.linguaphylo:lphy:1.2.0!!.

The second dependency is lphy-studio module, because we want to run the studio from this project. But it certainly should not be required by the code, which has no GUI extension. So, the runtimeOnly configuration is used. The third dependency is only used for unit tests.

Using module dependencies, you do not have to worry about any manual process to load the dependency tree of lphy module. They will be automatically downloaded into your Gradle local repository when you first build, according to the dependencies declared in the pom.xml created by publishing task.

Updating your dependencies is important. IntelliJ provides a nice UI for the Gradle project to manage the dependencies.

Figure 2: Dependency manager in IntelliJ.

2. phylonco-beast

The BEAST 2 and its packages are not using the GAV and Maven central repo mechanism, so that we have to use the file dependencies to host their dependencies in Gradle. In order to recognise the version, we rename the released BEAST 2 jars into a format similar with GAV, where the package name follows the version.

Figure 3: The subproject "phylonco-beast".

As you can see in Figure 3, this BEAST 2 extension depends on the jar files of BEAST 2 core and BEASTLabs. fileTree is used to load the jar files from the given directory as file dependencies.

The api configuration is declared for BEAST 2 and BEASTLabs, which means these 2 jars will be exposed to consumers, which means, for instance, the dependencies of subproject “phylonco-lphybeast” has already included these 2 jars, because it depends on this subproject. So, we do not have to replicate jars in the “phylonco-lphybeast” lib folder. But please use api wisely and be respectful of consumers.

The list of available BEAST 2 packages and their package dependencies can be seen from Package Viewer or Package Manager. If you change the drop-down list from the default XML into “packages-extra.xml” in Package Viewer, you can find the webpage to list both “lphybeast” package and “Phylonco” package. Alternatively, you can find the BEAST 2 package dependencies from the version.xml.

3. phylonco-lphybeast

This subproject integrates all LPhy components and BEAST 2 components, so its dependencies must include LPhy and its extension, BEAST 2 and its extensions, LPhyBEAST core, and all of their dependencies.

Figure 4: The subproject "phylonco-lphybeast".

In Figure 4, the first implementation uses GAV to import the LPhy core into the dependencies. The second loads the module “phylonco-lphy”, which is the LPhy extension, as project dependencies. We recommend to always use GAV if possible, but at the time of writing, “phylonco-lphy” has not been published to the Maven central repo yet.

The third implementation loads the module “phylonco-beast”. Referring to the section 2, we defined the BEAST 2’s and BEASTLabs’ dependency configuration type as api. Therefore, this project dependences (project(":phylonco-beast")) include these two jars. Moreover, the last implementation imports the rest of required BEAST 2 libraries from the lib folder.

In the middle, there is a custom configuration zippedConfig to import LPhyBEAST core into the dependencies. It is a zip file, so we have to use another task named as installLPhyBEAST to unzip it, and pass all of jar file names in its lib folder into a Gradle property “lblibs”.
Then, the next implementation loads all jar file names stored in “lblibs”. You can find all these jars from the External Libraries in IntelliJ’s project view.

Understanding your role

Producers and consumers are introduced by the dependency management with Gradle. We strongly recommend you read the linked article to understand the roles.

Please note project dependencies across different repositories are absolutely discouraged by Gradle, due to the encapsulation. This also helps to distinguish the responsibilities of producers and consumers. The producer has the responsibility to provide the reliable version of the software, and use the transitive dependency wisely. It is the consumer’s responsibility to decide what version of the library to use. The consumers should always consume the released or published version, not the latest version of the repository. But a snapshot version built from the latest code can be used only for an emergency situation or testing. This principle allows the developer in a consumer role to focus on one version to develop or extend, so as to produce a stable version of his software.

So the developers cannot load multiple projects (repositories) into the same window of IntelliJ anymore, even though one depends on another project. Therefore, for convenience of consumers, the producer has to provide the source code during the release.

Gradle alos introduces a useful feature, the variant model, which allows a producer creates different releases using the same source code for different kinds of consumers.

Further readings