Skip to content

Dependencies

Geert Bevin edited this page Aug 30, 2024 · 8 revisions

As mentioned in the Configuring Files section, unlike other build tools, bld doesn't rely on automatic dependency resolution to create the classpaths and modules paths for your Java project. Instead, the jar files inside your project's lib directory will be used to create the classpath and module path for the various scopes:

...
├── lib              // library files creating classpaths for different scopes 
│   ├── bld          // libraries for bld
│   ├── compile      // libraries for compiling your project
│   │   └── modules  // java modules for compiling your project
│   ├── provided     // libraries for compiling, provided by a container
│   │   └── modules  // java modules for compiling, provided by a container
│   ├── runtime      // libraries for running your project
│   │   └── modules  // java modules for running your project
│   ├── standalone   // libraries for running your project standalone
│   │   └── modules  // java modules for running your project standalone
│   └── test         // libraries for testing your project
│       └── modules  // java modules for testing your project
...

NOTE: the Project Creation section contains more details about your project structure.

Defining project dependencies

Putting files inside those lib directories can be done manually, without ever using any automated dependency downloads. For many projects this can be inconvenient, especially since Maven has inspired such a rich ecosystem of readily available artifact repositories.

bld supports the Maven dependency management style and syntax, directly integrating with any Maven repository and resolving dependency trees in the same way, but with a Java API.

For instance:

// ...
public class MyappBuild extends WebProject {
    public MyappBuild() {
        // ...
        repositories = List.of(MAVEN_CENTRAL, RIFE2_RELEASES);
        scope(compile)
            .include(dependency("com.uwyn.rife2", "rife2", version(1,8,0)));
        scope(test)
            .include(dependency("org.jsoup", "jsoup", version(1,18,1)))
            .include(dependency("org.junit.jupiter", "junit-jupiter", version(5,10,3)))
            .include(dependency("org.junit.platform", "junit-platform-console-standalone", version(1,10,3)));
        scope(standalone)
            .include(dependency("org.eclipse.jetty.ee10", "jetty-ee10", version(12,0,11)))
            .include(dependency("org.eclipse.jetty.ee10", "jetty-ee10-servlet", version(12,0,11)))
            .include(dependency("org.slf4j", "slf4j-simple", version(2,0,13)));
    }
    // public static void main ...
}

This sets up Maven Central, and RIFE2's own artifact repository as the repositories your project will use to find dependencies. They will be queried in the order they are specified.

Then, for each scope, a series of dependencies are defined.

If you prefer, a shorter dependency syntax can also be used, which could be convenient if you're copy-pasting dependencies from Maven central by using the Gradle (short) format.

    // ...
    scope(compile)
        .include(dependency("com.uwyn.rife2:rife2:1.8.0"));
    scope(test)
        .include(dependency("org.jsoup:jsoup:1.18.1"))
        .include(dependency("org.junit.jupiter:junit-jupiter:5.10.3"))
        .include(dependency("org.junit.platform:junit-platform-console-standalone:1.10.3"));
    scope(standalone)
        .include(dependency("org.eclipse.jetty.ee10:jetty-ee10:12.0.11"))
        .include(dependency("org.eclipse.jetty.ee10:jetty-ee10-servlet:12.0.11"))
        .include(dependency("org.slf4j:slf4j-simple:2.0.13"));
    // ...

The downside of the shorter syntax is that it can be less convenient to use Java language capabilities for your dependencies, like extracting common groups and versions:

    // ...
    var jetty_group = "org.eclipse.jetty.ee10";
    var jetty_version = version(12,0,11);
    scope(standalone)
        .include(dependency(jetty_group, "jetty-ee10", jetty_version))
        .include(dependency(jetty_group, "jetty-ee10-servlet", jetty_version))
        .include(dependency("org.slf4j", "slf4j-simple", version(2,0,13)));
    // ...

With these dependencies defined, nothing really changed, the jar files are still not in the lib directories. You get them there by telling bld that it should download the dependencies for your project:

./bld download
Downloading: …/maven2/com/uwyn/rife2/rife2/1.8.0/rife2-1.8.0.jar ... done
Downloading: …/maven2/org/eclipse/jetty/ee10/jetty-ee10/12.0.11/jetty-ee10-12.0.11.jar ... not found
Downloading: …/maven2/org/eclipse/jetty/ee10/jetty-ee10-servlet/12.0.11/jetty-ee10-servlet-12.0.11.jar ... done
Downloading: …/maven2/jakarta/servlet/jakarta.servlet-api/6.0.0/jakarta.servlet-api-6.0.0.jar ... done
Downloading: …/maven2/org/eclipse/jetty/jetty-security/12.0.11/jetty-security-12.0.11.jar ... done
Downloading: …/maven2/org/eclipse/jetty/jetty-server/12.0.11/jetty-server-12.0.11.jar ... done
Downloading: …/maven2/org/eclipse/jetty/jetty-session/12.0.11/jetty-session-12.0.11.jar ... done
Downloading: …/maven2/org/eclipse/jetty/jetty-http/12.0.11/jetty-http-12.0.11.jar ... done
Downloading: …/maven2/org/eclipse/jetty/jetty-io/12.0.11/jetty-io-12.0.11.jar ... done
Downloading: …/maven2/org/eclipse/jetty/jetty-util/12.0.11/jetty-util-12.0.11.jar ... done
Downloading: …/maven2/org/slf4j/slf4j-simple/2.0.13/slf4j-simple-2.0.13.jar ... done
Downloading: …/maven2/org/slf4j/slf4j-api/2.0.13/slf4j-api-2.0.13.jar ... done
Downloading: …/maven2/org/jsoup/jsoup/1.18.1/jsoup-1.18.1.jar ... done
Downloading: …/maven2/org/junit/jupiter/junit-jupiter/5.10.3/junit-jupiter-5.10.3.jar ... done
Downloading: …/maven2/org/junit/jupiter/junit-jupiter-api/5.10.3/junit-jupiter-api-5.10.3.jar ... done
Downloading: …/maven2/org/junit/jupiter/junit-jupiter-params/5.10.3/junit-jupiter-params-5.10.3.jar ... done
Downloading: …/maven2/org/junit/jupiter/junit-jupiter-engine/5.10.3/junit-jupiter-engine-5.10.3.jar ... done
Downloading: …/maven2/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar ... done
Downloading: …/maven2/org/junit/platform/junit-platform-commons/1.10.3/junit-platform-commons-1.10.3.jar ... done
Downloading: …/maven2/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2.jar ... done
Downloading: …/maven2/org/junit/platform/junit-platform-engine/1.10.3/junit-platform-engine-1.10.3.jar ... done
Downloading: …/maven2/org/junit/platform/junit-platform-console-standalone/1.10.3/junit-platform-console-standalone-1.10.3.jar ... done
Downloading finished successfully.

If you now look into the lib directory, you'll see that all the transitive dependencies have been downloaded into the directories of their respective scopes:

lib
├── bld
├── compile
│   │── modules
│   └── rife2-1.8.0.jar
├── provided
│   └── modules
├── runtime
│   └── modules
├── standalone
│   │── modules
│   ├── jakarta.servlet-api-6.0.0.jar
│   ├── jetty-ee10-servlet-12.0.11.jar
│   ├── jetty-http-12.0.11.jar
│   ├── jetty-io-12.0.11.jar
│   ├── jetty-security-12.0.11.jar
│   ├── jetty-server-12.0.11.jar
│   ├── jetty-session-12.0.11.jar
│   ├── jetty-util-12.0.11.jar
│   ├── slf4j-api-2.0.13.jar
│   └── slf4j-simple-2.0.13.jar
└── test
    │── modules
    ├── apiguardian-api-1.1.2.jar
    ├── jsoup-1.18.1.jar
    ├── junit-jupiter-5.10.3.jar
    ├── junit-jupiter-api-5.10.3.jar
    ├── junit-jupiter-engine-5.10.3.jar
    ├── junit-jupiter-params-5.10.3.jar
    ├── junit-platform-commons-1.10.3.jar
    ├── junit-platform-console-standalone-1.10.3.jar
    ├── junit-platform-engine-1.10.3.jar
    └── opentest4j-1.3.0.jar

Now, for as long as you're using the same dependencies, you don't have to issue the download command again. The jar files just remain in these directories and bld uses them to construct the classpaths. Dependency resolution doesn't need to happen anymore until you actually change the dependencies of your project.

This saves a lot on execution time and makes your project directory fully self-contained with the following benefits:

  • You always have everything you need to work, even without network access.
  • IDEs can use the same jars and properly analyze your project, compile and run it.
  • Automated backup system always capture a complete snapshot of your project at any given time.
  • and more ...

Updating dependencies

As time goes on, you'll likely want to check if there's newer versions of your dependencies available and update them. bld comes with a handy updates command that checks if there's any updates for your project's dependencies:

./bld updates
The following dependency updates were found.
standalone:
    org.eclipse.jetty.ee10:jetty-ee10:12.0.12
    org.eclipse.jetty.ee10:jetty-ee10-servlet:12.0.12

You can then decide if this is a version you want to update to, and change your build file accordingly:

    // ...
    var jetty_group = "org.eclipse.jetty.ee10";
    var jetty_version = version(12,0,12);
    scope(standalone)
        .include(dependency(jetty_group, "jetty-ee10", jetty_version))
        .include(dependency(jetty_group, "jetty-ee10-servlet", jetty_version))
        .include(dependency("org.slf4j", "slf4j-simple", version(2,0,13)));
    // ...

Now it's time to run the download command again:

./bld download
Downloading: …/com/uwyn/rife2/rife2/1.8.0/rife2-1.8.0.jar ... exists
Downloading: …/org/eclipse/jetty/ee10/jetty-ee10/12.0.12/jetty-ee10-12.0.12.jar ... not found
Downloading: …/org/eclipse/jetty/ee10/jetty-ee10-servlet/12.0.12/jetty-ee10-servlet-12.0.12.jar ... done
Downloading: …/jakarta/servlet/jakarta.servlet-api/6.0.0/jakarta.servlet-api-6.0.0.jar ... exists
Downloading: …/org/eclipse/jetty/jetty-security/12.0.12/jetty-security-12.0.12.jar ... done
Downloading: …/org/eclipse/jetty/jetty-server/12.0.12/jetty-server-12.0.12.jar ... done
Downloading: …/org/eclipse/jetty/jetty-session/12.0.12/jetty-session-12.0.12.jar ... done
Downloading: …/org/slf4j/slf4j-api/2.0.13/slf4j-api-2.0.13.jar ... exists
Downloading: …/org/eclipse/jetty/jetty-http/12.0.12/jetty-http-12.0.12.jar ... done
Downloading: …/org/eclipse/jetty/jetty-io/12.0.12/jetty-io-12.0.12.jar ... done
Downloading: …/org/eclipse/jetty/jetty-util/12.0.12/jetty-util-12.0.12.jar ... done
Downloading: …/org/slf4j/slf4j-simple/2.0.13/slf4j-simple-2.0.13.jar ... exists
Downloading: …/org/jsoup/jsoup/1.18.1/jsoup-1.18.1.jar ... exists
Downloading: …/org/junit/jupiter/junit-jupiter/5.10.3/junit-jupiter-5.10.3.jar ... exists
Downloading: …/org/junit/jupiter/junit-jupiter-api/5.10.3/junit-jupiter-api-5.10.3.jar ... exists
Downloading: …/org/junit/jupiter/junit-jupiter-params/5.10.3/junit-jupiter-params-5.10.3.jar ... exists
Downloading: …/org/junit/jupiter/junit-jupiter-engine/5.10.3/junit-jupiter-engine-5.10.3.jar ... exists
Downloading: …/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar ... exists
Downloading: …/org/junit/platform/junit-platform-commons/1.10.3/junit-platform-commons-1.10.3.jar ... exists
Downloading: …/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2.jar ... exists
Downloading: …/org/junit/platform/junit-platform-engine/1.10.3/junit-platform-engine-1.10.3.jar ... exists
Downloading: …/org/junit/platform/junit-platform-console-standalone/1.10.3/junit-platform-console-standalone-1.10.3.jar ... exists
Downloading finished successfully.

bld is smart enough to check the existing files and to detect that they exist and are identical, and downloads the new files for your updated dependencies.

However, since you might be combining automated dependency downloads with manually providing jars, bld doesn't automatically delete the old dependency jars. Since these are still in the lib directories, they will be included in the classpaths. This is most likely not what you want, so you can manually delete them, or if you fully rely on bld for your dependency management, you can use the purge command to get rid of any jar that doesn't correspond to the current dependencies of your project:

./bld purge
Deleting from standalone:
    jetty-io-12.0.11.jar
    jetty-security-12.0.11.jar
    jetty-server-12.0.11.jar
    jetty-http-12.0.11.jar
    jetty-session-12.0.11.jar
    jetty-ee10-servlet-12.0.11.jar
    jetty-util-12.0.11.jar
Purging finished successfully.

In practice, many people will just combine these command in one line:

./bld download purge

or abbreviated:

./bld do pur

Automating download and purge

If you fully want to hand over dependency management to bld and have all jars automatically update as soon as you make changes to your project's dependencies, you can set the autoDownloadPurge option to true:

// ...
public class MyappBuild extends WebProject {
    public MyappBuild() {
        // ...
        autoDownloadPurge = true;
        // ...
    }
    // public static void main ...
}

bld will now calculate and store a signature of your project's dependencies and automatically download and purge them when that signature changes. This will still keep your build running as fast as before, as long your dependencies stay the same.

If you have any SNAPSHOT dependency versions in your dependencies, this will also cause bld to access the internet every time you run it for your project. To work offline, you'll then have to use bld with the --offline argument.

Java modules

bld has direct support for Java modules, which are distributed as regular jars.

The main difference when using Java modules is that alongside the traditional Java classpath, there's now also a module path. Any jar that is specified on the module path will be interpreted as an explicit or an automatic Java module.

bld automatically creates the module path for the various scopes by looking inside the lib/*/modules directories. You can freely use any organization of classpaths and module paths.

In order to have bld download project dependencies inside those modules directories, all that is necessary is to replace dependency(…) with module(…).

For example:

    // ...
    scope(compile)
        .include(module("com.uwyn.rife2:rife2:1.8.0"));
    scope(test)
        .include(dependency("org.jsoup:jsoup:1.18.1"))
        .include(dependency("org.junit.jupiter:junit-jupiter:5.10.3"))
        .include(dependency("org.junit.platform:junit-platform-console-standalone:1.10.3"));
    scope(standalone)
        .include(module("org.eclipse.jetty.ee10:jetty-ee10:12.0.11"))
        .include(module("org.eclipse.jetty.ee10:jetty-ee10-servlet:12.0.11"))
        .include(module("org.slf4j:slf4j-simple:2.0.13"));
    // ...

If you now look into the lib directory, you'll see that the module dependencies have been placed inside the appropriate modules sub-directories:

lib
├── bld
│   ├── bld-wrapper.jar
│   ├── bld-wrapper.properties
│   └── bld.cache
├── compile
│   └── modules
│       └── rife2-1.8.0.jar
├── provided
│   └── modules
├── runtime
│   └── modules
├── standalone
│   └── modules
│       ├── jakarta.servlet-api-6.0.0.jar
│       ├── jetty-ee10-servlet-12.0.12.jar
│       ├── jetty-http-12.0.12.jar
│       ├── jetty-io-12.0.12.jar
│       ├── jetty-security-12.0.12.jar
│       ├── jetty-server-12.0.12.jar
│       ├── jetty-session-12.0.12.jar
│       ├── jetty-util-12.0.12.jar
│       ├── slf4j-api-2.0.13.jar
│       └── slf4j-simple-2.0.13.jar
└── test
    ├── modules
    ├── apiguardian-api-1.1.2.jar
    ├── jsoup-1.18.1.jar
    ├── junit-jupiter-5.11.0.jar
    ├── junit-jupiter-api-5.11.0.jar
    ├── junit-jupiter-engine-5.11.0.jar
    ├── junit-jupiter-params-5.11.0.jar
    ├── junit-platform-commons-1.11.0.jar
    ├── junit-platform-console-standalone-1.11.0.jar
    ├── junit-platform-engine-1.11.0.jar
    └── opentest4j-1.3.0.jar

Version numbers

bld projects use semantic versioning for its projects. Dependencies that also use semantic versioning, use the precedence rules defined by that specification. When a dependency version can't be parsed as a semantic version, bld will fall back to a generic version that uses the exact same complex rules as those that Maven uses in this case. This allows bld to support any existing Maven artifact versioning scheme, while promoting a more standardized approach for its own projects.

When no version number is specified for a dependency, bld will look for the latest version in the specified repositories and use that for the dependency in question.

Version resolution

When dependency trees get bigger, you'll start seeing the same dependencies being pulled in by others. It's also very common that the versions of those common dependencies are not the same. To ensure that only one version of a particular dependency is used in the classpaths of your project, like Maven and Gradle, bld remembers the first version number that was encountered for a particular dependency, and uses that for any later instances of that dependency in the tree.

This has the added benefit that if you want to lock a dependency in the tree to a particular version, all you need to do is to define that dependency version yourself in the relevant scope before other dependencies get to use it.

Version overriding

As detailed in Sensitive and Common Data, bld allows you to use RIFE2's hierarchical properties infrastructure to pass properties into your project. The same hierarchical properties function with bld extensions with the bld-wrapper.properties file being the final property collection.

Here's an overview of how the property collection hierarchies work for your bld project and for the extension bld wrapper. Please refer to the Sensitive and Common Data section for more details:

   Project Properties           Bld Wrapper Properties
           ↓                               ↓
Java System Properties          Java System Properties
           ↓                               ↓
[Local Properties File]         [Local Properties File] 
           ↓                               ↓
 [Bld Properties File]           [Bld Properties File] 
           ↓                               ↓
[Custom Properties File]        [Custom Properties File]
           ↓                               ↓
  System Env Variables            System Env Variables

bld supports a special property format that allows you to override the version of any dependency that is encountered in your project or extensions. All that is required is to use a property key that starts with bld.override somewhere in the property hierarchy, and then to list the dependencies you want to override, seperated by commas.

For instance:

bld.override=org.jsoup:jsoup:1.17.1,org.slf4j:slf4j-simple:2.0.12

For clarity this can also be written as such, as long as each key starts with bld.override:

bld.override-jsoup=org.jsoup:jsoup:1.17.1
bld.override-slf4j=org.slf4j:slf4j-simple:2.0.12

There are so many use-cases for this, like making sure your organization only uses a particular version of a dependency throughout, overriding a transitive dependency in your extensions, trying out new dependency versions before fully committing, and more ...

For example, typing this:

./bld -Dbld.override=org.jsoup:jsoup:1.17.1,org.slf4j:slf4j-simple:2.0.12 \
    download purge compile test

Allows you to quickly check if your project compiles with different dependency versions and if the tests still pass, without having to make a change to the project file itself.

Downloading: …/com/uwyn/rife2/rife2/1.8.0/rife2-1.8.0.jar ... exists
Downloading: …/org/eclipse/jetty/ee10/jetty-ee10/12.0.12/jetty-ee10-12.0.12.jar ... not found
Downloading: …/org/eclipse/jetty/ee10/jetty-ee10-servlet/12.0.12/jetty-ee10-servlet-12.0.12.jar ... exists
Downloading: …/jakarta/servlet/jakarta.servlet-api/6.0.0/jakarta.servlet-api-6.0.0.jar ... exists
Downloading: …/org/eclipse/jetty/jetty-security/12.0.12/jetty-security-12.0.12.jar ... exists
Downloading: …/org/eclipse/jetty/jetty-server/12.0.12/jetty-server-12.0.12.jar ... exists
Downloading: …/org/eclipse/jetty/jetty-session/12.0.12/jetty-session-12.0.12.jar ... exists
Downloading: …/org/slf4j/slf4j-api/2.0.13/slf4j-api-2.0.13.jar ... exists
Downloading: …/org/eclipse/jetty/jetty-http/12.0.12/jetty-http-12.0.12.jar ... exists
Downloading: …/org/eclipse/jetty/jetty-io/12.0.12/jetty-io-12.0.12.jar ... exists
Downloading: …/org/eclipse/jetty/jetty-util/12.0.12/jetty-util-12.0.12.jar ... exists
Downloading: …/org/slf4j/slf4j-simple/2.0.12/slf4j-simple-2.0.12.jar ... done
Downloading: …/org/jsoup/jsoup/1.17.1/jsoup-1.17.1.jar ... done
Downloading: …/org/junit/jupiter/junit-jupiter/5.10.3/junit-jupiter-5.10.3.jar ... exists
Downloading: …/org/junit/jupiter/junit-jupiter-api/5.10.3/junit-jupiter-api-5.10.3.jar ... exists
Downloading: …/org/junit/jupiter/junit-jupiter-params/5.10.3/junit-jupiter-params-5.10.3.jar ... exists
Downloading: …/org/junit/jupiter/junit-jupiter-engine/5.10.3/junit-jupiter-engine-5.10.3.jar ... exists
Downloading: …/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar ... exists
Downloading: …/org/junit/platform/junit-platform-commons/1.10.3/junit-platform-commons-1.10.3.jar ... exists
Downloading: …/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2.jar ... exists
Downloading: …/org/junit/platform/junit-platform-engine/1.10.3/junit-platform-engine-1.10.3.jar ... exists
Downloading: …/org/junit/platform/junit-platform-console-standalone/1.10.3/junit-platform-console-standalone-1.10.3.jar ... exists
Downloading finished successfully.
Deleting from standalone:
    slf4j-simple-2.0.13.jar
Deleting from test:
    jsoup-1.18.1.jar
Purging finished successfully.
Compilation finished successfully.
Test plan execution started. Number of static tests: 2
╷
├─ JUnit Jupiter
│  ├─ MyappTest
│  │  ├─ verifyHello()
│  │  │       tags: []
│  │  │   uniqueId: [engine:junit-jupiter]/[class:com.example.MyappTest]/[method:verifyHello()]
│  │  │     parent: [engine:junit-jupiter]/[class:com.example.MyappTest]
│  │  │     source: MethodSource [className = 'com.example.MyappTest', methodName = 'verifyHello', methodParameterTypes = '']
│  │  │   duration: 99 ms
│  │  │     status: ✔ SUCCESSFUL
│  │  ├─ verifyRoot()
│  │  │       tags: []
│  │  │   uniqueId: [engine:junit-jupiter]/[class:com.example.MyappTest]/[method:verifyRoot()]
│  │  │     parent: [engine:junit-jupiter]/[class:com.example.MyappTest]
│  │  │     source: MethodSource [className = 'com.example.MyappTest', methodName = 'verifyRoot', methodParameterTypes = '']
│  │  │   duration: 2 ms
│  │  │     status: ✔ SUCCESSFUL
│  └─ MyappTest finished after 111 ms.
└─ JUnit Jupiter finished after 118 ms.
Test plan execution finished. Number of all tests: 2

Test run finished after 134 ms
[         2 containers found      ]
[         0 containers skipped    ]
[         2 containers started    ]
[         0 containers aborted    ]
[         2 containers successful ]
[         0 containers failed     ]
[         2 tests found           ]
[         0 tests skipped         ]
[         2 tests started         ]
[         0 tests aborted         ]
[         2 tests successful      ]
[         0 tests failed          ]

Next learn more about Team Setup

Clone this wiki locally