Managing dependencies
Kargo uses Maven coordinates (group:artifact:version) and downloads from Maven Central by default. Search for libraries on central.sonatype.com or klibs.io (for Kotlin Multiplatform libraries).
Adding dependencies
Section titled “Adding dependencies”kargo add com.squareup.okio:okio # latest releasekargo add com.squareup.okio:okio:3.10.2 # an exact versionkargo add io.mockk:mockk --dev # test-onlykargo add org.slf4j:slf4j-simple --scope runtime # only needed at runtimekargo add jakarta.servlet:jakarta.servlet-api --scope providedkargo add com.google.auto.value:auto-value:1.11.0 --scope processor # a javac annotation processorkargo add com.squareup.moshi:moshi-kotlin-codegen:1.15.2 --scope ksp # a KSP processorkargo add com.google.guava:guava -t jvm # only for the JVM targetkargo add org.jetbrains.kotlinx:kotlinx-io-core --pre # allow a pre-release as "latest"Kargo checks that the artifact exists before editing kargo.toml (skip that with --no-verify), and it keeps your file’s comments and formatting.
Without a version, Kargo picks the newest release. Some libraries publish variants next to their plain versions (0.8.0-0.6.x-compat, 33.0.0-jre); Kargo prefers the plain version and prints a note naming the variant it skipped. Pass the full coordinates to pick a variant.
To remove one, name the table it is in the same way as for kargo add:
kargo remove com.squareup.okio:okiokargo rm io.mockk:mockk --devkargo rm com.google.guava:guava -t jvmKeeping dependencies up to date
Section titled “Keeping dependencies up to date”kargo outdated lists the dependencies in kargo.toml that have newer versions, and kargo upgrade raises them:
$ kargo outdatedname current compatible latest kindorg.jetbrains.kotlinx:kotlinx-datetime 0.6.0 0.6.2 0.8.0 dependenciesio.mockk:mockk 1.13.0 1.14.11 1.14.11 dev-dependencieskargo upgrade --dry-run # previewkargo upgrade # newest compatible versionskargo upgrade -i io.mockk:mockk # newest version of one dependency, even a new major versionA compatible version has the same major version as the current one (for 0.x, the same minor version), like Cargo’s default requirements. kargo upgrade edits only the version strings, so your comments and layout stay as they are. Dependencies declared with a range or managed by a BOM are skipped.
kargo upgrade changes what you declared in kargo.toml. To move versions in kargo.lock only, for example a transitive dependency, use kargo update (see The lockfile).
Writing dependencies by hand
Section titled “Writing dependencies by hand”All of these forms are valid in [dependencies], [dev-dependencies] and [target.<name>.dependencies]:
[dependencies]# "group:artifact" = "version""org.jetbrains.kotlinx:kotlinx-datetime" = "0.7.1"
# A short key with the group/artifact spelled outguava = { group = "com.google.guava", version = "33.4.0-jre" }
# Or full coordinates in `coords`logback = { coords = "ch.qos.logback:logback-classic:1.5.32", scope = "runtime" }
# Exclude transitive dependencies"com.example:big-lib" = { version = "2.0", exclude = ["commons-logging:commons-logging"] }
# Don't pull in any transitive dependencies"com.example:standalone" = { version = "1.0", transitive = false }
# A specific file of a module: a classifier and/or an extension (type)"com.example:tool" = { version = "1.0", classifier = "all" }
# Version ranges use Maven syntax"com.example:ranged" = "[1.2,2.0)"| Key | Meaning |
|---|---|
version |
A version or Maven range. Required unless a BOM manages it. |
group, artifact, coords |
Alternatives to writing "group:artifact" as the key. |
scope |
compile (default), runtime, provided, processor or ksp. |
exclude |
group:artifact (or group:*) entries to drop from this dependency’s tree. |
transitive |
false to ignore this dependency’s own dependencies. |
force |
true to pin this version even if something else asks for a newer one. |
classifier, extension |
Select a specific file, such as natives-linux or aar (see Classifiers and native libraries). |
bom |
true to import this POM as a BOM (see below). |
Classifiers and native libraries
Section titled “Classifiers and native libraries”A dependency with a classifier or extension selects one file of a module, such as lwjgl-3.3.6-natives-windows.jar. Libraries with native code publish those files next to their main jar, so you declare the module twice: once for its classes, and once for each native classifier. TOML keys are unique, so give the second declaration a key of your own and spell out the coordinates:
[dependencies]"org.lwjgl:lwjgl" = "3.3.6"lwjgl-natives = { coords = "org.lwjgl:lwjgl:3.3.6", classifier = "natives-windows", scope = "runtime" }Both jars are on the runtime classpath, in the kargo package jar and in kargo.lock. The natives are only needed at runtime, so scope = "runtime" keeps their jar off the compile classpath. Every dependency is identified by group:artifact:classifier:extension; declaring the same one twice is an error.
A module still has one version, however many classifiers you declare. If the declarations, or other libraries, ask for different versions, the highest wins (or the one with force = true), and every classifier uses it.
transitive = false on the classifier declaration only affects that declaration: the plain one still brings in the module’s own dependencies. kargo tree lists the module once, and kargo tree --format json lists every file it contributes.
Scopes
Section titled “Scopes”| Scope | Compile classpath | Runtime classpath | In kargo package jar |
|---|---|---|---|
compile |
yes | yes | yes |
runtime |
no | yes | yes |
provided |
yes | no | no |
processor |
no (javac -processorpath only) |
no | no |
ksp |
no (KSP processor path only) | no | no |
Annotation processors
Section titled “Annotation processors”Java annotation processors such as AutoValue, MapStruct or Immutables generate code while javac compiles your Java sources. Declare the processor with scope = "processor" and its annotations as a normal (usually provided) dependency:
[target.jvm.dependencies]"com.google.auto.value:auto-value-annotations" = { version = "1.11.0", scope = "provided" }"com.google.auto.value:auto-value" = { version = "1.11.0", scope = "processor" }package com.example.app;
import com.google.auto.value.AutoValue;
@AutoValuepublic abstract class Person { public abstract String name();
public static Person create(String name) { return new AutoValue_Person(name); }}Processor dependencies and their own dependencies go on javac’s -processorpath, and Kargo passes -proc:full so they run on every JDK (JDK 23 and newer no longer run processors found on the classpath). They never reach the compile or runtime classpath and are not packaged. Processors declared in [dependencies] or [target.jvm.dependencies] run for the main and test sources; a processor in [dev-dependencies] runs for the tests only. Like everything else, processors are pinned in kargo.lock. When no processor is declared, javac runs with -proc:none.
Pass processor options and other javac flags with [target.jvm] javac-args:
[target.jvm]javac-args = ["-Amapstruct.defaultComponentModel=spring", "-Xlint:all"]Kotlin Symbol Processing (KSP)
Section titled “Kotlin Symbol Processing (KSP)”KSP processors read your Kotlin (and Java) sources and generate code before they compile. Moshi codegen, Room, Dagger and Hilt, Koin annotations and many more ship one. Declare the processor with scope = "ksp" and the library it generates code for as a normal dependency:
[dependencies]"com.squareup.moshi:moshi" = "1.15.2""com.squareup.moshi:moshi-kotlin-codegen" = { version = "1.15.2", scope = "ksp" }package com.example.app
import com.squareup.moshi.JsonClassimport com.squareup.moshi.Moshi
@JsonClass(generateAdapter = true)data class Person(val name: String, val age: Int)
fun main() { val adapter = Moshi.Builder().build().adapter(Person::class.java) // uses the generated PersonJsonAdapter println(adapter.toJson(Person("Ada", 36)))}$ kargo run Processing app v0.1.0 (jvm, ksp) Compiling app v0.1.0 (jvm) Finished `dev` profile [unoptimized + debuginfo] target(s) in 3.57s Running `java com.example.app.MainKt`{"name":"Ada","age":36}On the JVM target, Kargo runs KSP2 standalone (symbol-processing-aa-embeddable) right before the Kotlin compiler, once for the main sources and once for the tests. The Kotlin and Java files it generates under target/<profile>/jvm/generated/ksp/<main|test>/ are compiled with your sources, and generated resources are copied into the output. KSP reruns only when a source, a dependency or a processor changed. It analyzes the sources with your [kotlin] language-version and api-version, which default to the major and minor version of [kotlin] version.
Kargo uses KSP 2.3.12 unless you pick another version in [kotlin]. It applies to the JVM and to the Gradle KSP plugin on Android:
[kotlin]ksp = "2.3.10"Since 2.3.0, KSP versions no longer follow Kotlin versions, so one KSP release works with several Kotlin versions.
As with processor, KSP processors and their dependencies never reach the compile or runtime classpath, are not packaged, and are pinned in kargo.lock (under jvm/ksp, and jvm/test-ksp when you have test-only processors). Processors in [dependencies] or [target.jvm.dependencies] run for the main and test sources; a processor in [dev-dependencies] runs for the tests only. On Android, the scope maps to the Gradle KSP plugin’s ksp(...) and kspTest(...) configurations.
KSP processes JVM and Android code. Wasm, JS and Native ignore ksp dependencies.
BOMs (platforms)
Section titled “BOMs (platforms)”A BOM (“bill of materials”) manages the versions of a family of libraries so you don’t have to. Import one with bom = true, then leave the version out of the libraries it manages:
[dependencies]"software.amazon.awssdk:bom" = { version = "2.31.0", bom = true }"software.amazon.awssdk:s3" = {}"software.amazon.awssdk:dynamodb" = {}Or from the command line:
kargo add software.amazon.awssdk:bom:2.31.0 --bomVersion conflicts
Section titled “Version conflicts”When two libraries depend on different versions of the same module, Kargo does what Gradle does: the highest version wins. Set force = true on a direct dependency to override that.
See what was picked with kargo tree. Conflicts are shown as requested -> selected, and subtrees already printed are marked (*):
kargo tree # runtime classpath of the default targetkargo tree -c compile # compile classpathkargo tree -c test # test classpathkargo tree -t wasm # another targetkargo tree --depth 1 # direct dependencies onlykargo tree --duplicates # only modules requested in more than one versionDependencies of one profile
Section titled “Dependencies of one profile”A dependency that only some builds need goes in the profile table: an HTTP logger in debug builds, or a mock server in a staging build. It is added to the build of every target with that profile, and to no other profile:
[profile.dev.dependencies]"com.squareup.okhttp3:logging-interceptor" = "4.12.0"
[profile.staging]inherits = "release"
[profile.staging.dependencies]"com.squareup.okhttp3:mockwebserver" = "4.12.0"kargo build --profile staging and kargo run --profile staging have the mock server, and kargo build does not. A profile with dependencies of its own resolves them in its own entry of kargo.lock (jvm/runtime@staging), and the others keep their shared entries. A dependency with the same artifact as one of the manifest replaces it for that profile. See Profiles for the keys, and On Android for how the Android target applies them.
Multiplatform libraries
Section titled “Multiplatform libraries”Kotlin Multiplatform libraries publish one Maven module per platform plus Gradle module metadata. Add the common coordinates and Kargo picks the right variant for each target:
[dependencies]"org.jetbrains.kotlinx:kotlinx-datetime" = "0.7.1" # -> .jar on JVM, .klib on Wasm/NativeLibraries that only exist for one platform belong in that target’s section:
[target.jvm.dependencies]"com.zaxxer:HikariCP" = "6.2.1"Local packages
Section titled “Local packages”Another Kargo package on disk can be a dependency too, built from source with your project:
kargo add --path ../mathlib[dependencies]mathlib = { path = "../mathlib" }Its jar (or klib, for Wasm, JS and Native) ends up on your classpaths, with its own Maven dependencies: the ones it marks exported = true to compile against, and the others at runtime only, like Gradle’s api and implementation. To build several packages together with one kargo.lock, make them members of a workspace. See Workspaces and path dependencies.
Repositories
Section titled “Repositories”Maven Central is always used. Google’s Maven repository is added automatically for Android and Compose. Add others in [repositories]:
[repositories]jitpack = "https://jitpack.io"
# Private repository with credentials from environment variablescompany = { url = "https://repo.example.com/maven", username-env = "REPO_USER", password-env = "REPO_PASS" }
# A local directorylibs = "file:///home/me/libs"
# The local Maven repository (~/.m2/repository)local = "maven-local"Credentials are never written in kargo.toml; Kargo reads them from the named environment variables.
To send every repository through a company mirror, or build from a copy of the dependencies kept in the project, see Offline and corporate builds.
maven-local holds what kargo publish --local, mvn install and Gradle’s publishToMavenLocal install, so a library you publish locally can be used by your other projects right away. See Publishing a library.
SNAPSHOT versions
Section titled “SNAPSHOT versions”A version that ends in -SNAPSHOT is a library’s work in progress, redeployed over and over. Declare it like any other version and add the repository that holds it:
[dependencies]"com.acme:lib" = "1.0-SNAPSHOT"
[repositories]snapshots = "https://repo.example.com/snapshots"Repositories such as Nexus, Artifactory and GitHub Packages (and mvn deploy) don’t store a file called lib-1.0-SNAPSHOT.jar. Each deployment is a timestamped build, lib-1.0-20240101.120000-1.jar, and 1.0-SNAPSHOT/maven-metadata.xml says which build is the newest of each file (the jar, the POM, the Gradle module metadata, and each classifier). Kargo reads that file and downloads the newest build. A repository without that metadata, or one that keeps the plain lib-1.0-SNAPSHOT.jar name (what mvn install writes into ~/.m2/repository), is used as it is.
Builds don’t move under you:
-
kargo.lockrecords the build. The locked path is the timestamped file, so every build and every machine uses the same one until you decide otherwise.kargo build --lockedin CI stays reproducible. -
kargo updatemoves to the newest build, and prints which one:Terminal window $ kargo updateUpdating com.acme:lib v1.0-SNAPSHOT (20240101.120000-1) -> v1.0-SNAPSHOT (20240102.080000-2)Updated kargo.lockkargo update com.acme:otherleaves the SNAPSHOTs it wasn’t asked about on their locked builds.kargo build --refreshignores the lockfile for one run and picks the newest build as well. -
Metadata is re-checked daily. Without a lockfile entry (a new dependency, say), Kargo reuses a remote repository’s SNAPSHOT metadata for up to 24 hours.
file:repositories are read every time, so a snapshot you just published to a local directory is seen immediately. A SNAPSHOT stored under its plain name is compared with the repository’s checksum on the same schedule, and downloaded again if it changed.
The lockfile
Section titled “The lockfile”kargo.lock records the exact version of every resolved module and the SHA-256 of every file it downloads. Kargo checks each file it uses against those checksums, cached copies included. Commit it, so everyone builds with the same dependencies.
kargo update # re-resolve everything and rewrite kargo.lockkargo update g:a # re-resolve one module; everything else keeps its locked versionkargo update g:a --precise 1.2.3 # lock one module, even a transitive one, at an exact versionkargo update --dry-run # show what would change without writing kargo.lockkargo fetch # download everything now (useful before going offline)kargo build --locked # fail if kargo.lock would change (use this in CI)kargo build --offline # never touch the networkkargo build --refresh # ignore cached metadata and re-check repositoriesSee the kargo.lock reference for the format.
When the network fails
Section titled “When the network fails”Kargo retries a request up to five times when the server answers HTTP 408, 429, 500, 502, 503 or 504, or the connection times out or drops. DNS, TLS and invalid URL errors fail at once. After such a failure Kargo remembers the host for the rest of the run and every further request to it fails immediately with “cannot reach kargo fetch while online, or use --offline”, instead of retrying for each module.
A failed request for a module’s maven-metadata.xml keeps the cached copy, so kargo add, kargo outdated and kargo upgrade still work from it; Kargo prints using cached metadata from <age> ago. The cached copy is deleted only when the repository says the file does not exist. A 403 for a .sha256, .sha1, .module or maven-metadata.xml file counts as absent, because repositories on object storage answer 403 for missing keys.