Savant Build

Dependencies

Now, let’s add some dependencies to the project

project(group: "org.example", name: "my-project", version: "1.0", licenses: ["ApacheV2_0"]) {
  dependencies {
    group(name: "compile") {
      dependency(id: "org.apache.commons:commons-collections:3.1.0")
    }
  }
}

This defines a single compile-time dependency on the Commons Collections library version 3.1.0. This isn’t enough information for Savant to download this dependency. You need to tell Savant where to download from. This is done using a workflow definition:

project(group: "org.example", name: "my-project", version: "1.0", licenses: ["ApacheV2_0"]) {
  workflow {
    fetch {
      cache()
      url(url: "http://savant.inversoft.org")
    }
    publish {
      cache()
    }
  }

  dependencies {
    group(name: "compile") {
      dependency(id: "org.apache.commons:commons-collections:3.1.0")
    }
  }
}

This tells Savant to first check the local cache for dependencies. If they aren’t found there, download them from http://savant.inversoft.org and cache them locally (that’s what the publish section is for).

You can simplify this build file like this:

project(group: "org.example", name: "my-project", version: "1.0", licenses: ["ApacheV2_0"]) {
  workflow {
    standard()
  }

  dependencies {
    group(name: "compile") {
      dependency(id: "org.apache.commons:commons-collections:3.1.0")
    }
  }
}

The local cache for Savant is stored at ~/.savant/cache

We now have given Savant enough information to download the dependency and cache it locally.

Dependency Groups

Dependency groups are completely free-form. You can name them whatever you want, but Savant and some of its plugins might use specific dependency groups by default. Here are the default groups:

Exporting

You can select which dependency groups are exported when a project is released. Non-exported dependency groups will not be know to other projects and are effectively hidden. This is a nice way to keep dependency graphs clean. You can define a dependency group as being non-exported like this:

dependencies {
  group(name: "test-compile", export: false) {
    dependency(id: "org.testng:testng:4.8.7")
  }
}

Artifact IDs

Savant uses a shorthand notation for declaring a project’s dependencies (or plugins or any other artifact). The form of this notation is:

<group>:<project>:<version>

This format implies that the artifact is a JAR file that ends with .jar.

Since Savant does not restrict projects from publishing multiple artifacts or the type of the artifact, there are a couple of extended formats as well:

<group>:<project>:<artifact>:<version>
<group>:<project>:<artifact>:<version>:<type>

An example of two dependency declarations from the same project might be something like:

com.mycompany:someproject:first-project-artifact:4.1.2:bin com.mycompany:someproject:second-project-artifact:4.1.2:jar

These definitions might map to these URLs in a Savant repository:

http://mySavantRepository.mycompany.com/com/mycompany/someproject/4.1.2/first-project-artifact-4.1.2.bin
http://mySavantRepository.mycompany.com/com/mycompany/someproject/4.1.2/second-project-artifact-4.1.2.jar

Workflow Processes

There are 4 main workflow processes you can use from Savant. Each process has different parameters you can specify to control its behavior:

Version Compatibility

Savant uses Semantic Versioning to manage version compatibility for individual artifacts. Here is an example:

In this case, Savant needs to determine which version of Foo to include in the classpath at runtime. It can pick either 1.0 or 1.3. SemVer states that 1.0 is runtime compatible with 1.3, therefore, Savant will choose to include version 1.3 in the classpath.

Incompatible Versions

Let’s take the case above but change one thing. Instead of depending on version 1.3, library Bar depends on version 3.2. In this case, SemVer states that these versions are not runtime compatible. In this case, Savant will fail the build and spit out an error message like this:

The artifact [org.example:Foo] has incompatible versions in your dependencies. The versions are [1.0, 3.2]

In this case, Savant is attempting to prevent your application from failing at runtime due to an incompatible library.

Skip Compatibility Check

There are plenty of libraries in the world today that don’t use SemVer or don’t version correctly. Likewise, there are plenty of libraries that correctly use SemVer, but make incompatible changes freely and regularly. In some cases, you don’t really have a choice but to use the latest version of a dependency, even though it might be incompatible. And in some cases you know that the versions are compatible, just have bad versions. Savant solves this problem by allowing your project to define a dependency and skip the compatibility check on that dependency. Here is how you accomplish this:

dependencies {
  group(name: "runtime") {
    dependency(id: "org.example:Foo:3.2", skipCompatibilityCheck: true)
  }
}

By setting the skipCompatibilityCheck flag, Savant will not fail the build when the versions of an artifact are incompatible.

This flag is not transitive. This means that projects that depend on your project will need to specify the flag on the dependency as well. This forces each project to make a informed decision about which version of the library they want to use.

Next, we’ll add some publications in our build file