Publishing Kotlin Projects

This page will discuss common topics around publishing your Kotlin projects for others to use.

Basic Publishing Configuration

This example demonstrates how to publish a Mill project to Maven Central. To do so, your module must extend mill.scalalib.PublishModule, and configure the publishing metadata shown below:

build.mill.yaml (download, browse)
extends: [KotlinModule, PublishModule]
kotlinVersion: 2.0.20
publishVersion: 0.0.1
artifactName: example
pomSettings:
  description: Example
  organization: com.lihaoyi
  url: https://github.com/com.lihaoyi/example
  licenses: [MIT]
  versionControl: https://github.com/com.lihaoyi/example
  developers: [{name: Li Haoyi, email: example@example.com}]

You can publish this locally to your ~/.ivy2 filesystem artifact repository via:

> ./mill publishLocal
Publishing Artifact(com.lihaoyi,example...,0.0.1) to ivy repo ...

Apart from publishing locally, you can also publish this project to Sonatype Maven Central via:

> ./mill mill.javalib.SonatypeCentralPublishModule/

For more details on packaging and publishing in Mill, see Kotlin Packaging & Publishing

publishLocal also accepts options like --doc=false and --sources=false, to disable publishing javadoc JARs and source JARs, which are generated and published by default. This can be helpful if you’re not interested in javadoc JARs, as compiling these docjars can slow down the local publishing workflow

publishLocal also accepts --transitive=true, to also publish locally the transitive dependencies of the module being published. This ensures the module can be resolved from the local repository, with no missing dependencies.

Versioning Based on Git Tags

build.mill.yaml (download, browse)
extends: [KotlinModule, PublishModule, mill.util.VcsVersionModule]
kotlinVersion: 2.0.20
artifactName: example
pomSettings:
  description: Example
  organization: com.lihaoyi
  url: https://github.com/com.lihaoyi/example
  licenses: [MIT]
  versionControl: https://github.com/com.lihaoyi/example
  developers: [{name: Li Haoyi, email: example@example.com}]

This example demonstrates how to automatically set your publishVersion based on the latest git tag. This is useful for workflows such as automatic publishing tags on push, such as that discussed in Publishing using Github Actions

> git init .
Initialized empty Git repository in...

> git add -A

> git commit -m "initial commit"
...

> git tag v0.1.0

> ./mill publishLocal
Publishing Artifact(com.lihaoyi,example...,0.1.0...) to ivy repo...

Programmable Publishing Configuration

build.mill (download, browse)
package build

import mill.*, kotlinlib.*, publish.*

object foo extends KotlinModule, PublishModule {

  def kotlinVersion = "1.9.24"

  def publishVersion = "0.0.1"

  def pomSettings = PomSettings(
    description = "Hello",
    organization = "com.lihaoyi",
    url = "https://github.com/lihaoyi/example",
    licenses = Seq(License.MIT),
    versionControl = VersionControl.github("lihaoyi", "example"),
    developers = Seq(Developer("lihaoyi", "Li Haoyi", "https://github.com/lihaoyi"))
  )
}
> ./mill foo.publishLocal
Publishing Artifact(com.lihaoyi,foo,0.0.1) to ivy repo...

This is an example KotlinModule with added publishing capabilities via PublishModule using Mill’s programmable build.mill syntax. It provides publishLocal for local publishing and publishSonatypeCentral (or mill.javalib.SonatypeCentralPublishModule/) for publishing to Maven Central.

Publishing to Maven Central

Once you’ve mixed in PublishModule, apart from publishing locally, you can also publish your project’s modules to maven central

Verifying your Maven Central Namespace

Everyone who publishes to Maven Central needs to register their organization’s namespace (e.g. com.lihaoyi) and verify that they own it as a security measure. To do so, please create an account at:

And register and verify your namespace by following the instructions at:

Setting up GPG

If you’ve never created a keypair before that can be used to sign your artifacts you’ll need to do this. Mill can do this for you via

$ ./mill mill.javalib.SonatypeCentralPublishModule/initGpgKeys

This command requests some basic metadata necessary for generating the GPG keys, generates a key-pair, registers it, and prints out instructions on how to use it from the terminal or in CI.

For most users who have never touched GPG before, using initGpgKeys is the easiest way to get started. But if for some reason initGpgKeys doesn’t work, or you want to fiddle under the hood, you can also set up your GPG keys manually:

Publishing

Once you have the various secrets exported as above, you can publish all eligible modules in your Mill project using mill.javalib.SonatypeCentralPublishModule/:

> mill mill.javalib.SonatypeCentralPublishModule/
> mill mill.javalib.SonatypeCentralPublishModule/ --publishArtifacts foo.publishArtifacts

Publishing Using Github Actions

To publish on Github Actions, you can use something like this:

# .github/workflows/publish-artifacts.yml
name: Publish Artifacts
on:
  push:
    tags:
      - '**'
  workflow_dispatch:
jobs:
  publish-artifacts:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: ./mill mill.javalib.SonatypeCentralPublishModule/
        env:
          MILL_PGP_PASSPHRASE: ${{ secrets.MILL_PGP_PASSPHRASE }}
          MILL_PGP_SECRET_BASE64: ${{ secrets.MILL_PGP_SECRET_BASE64 }}
          MILL_SONATYPE_PASSWORD: ${{ secrets.MILL_SONATYPE_PASSWORD }}
          MILL_SONATYPE_USERNAME: ${{ secrets.MILL_SONATYPE_USERNAME }}

Where MILL_PGP_PASSPHRASE, MILL_PGP_SECRET_BASE64, MILL_SONATYPE_PASSWORD, and MILL_SONATYPE_USERNAME configured for the repository’s or organization’s Github Actions workflows. See Using Secrets in Github Actions for more details.

This will set up a Github Actions workflow that will be run automatically when any tag is pushed, and can also be run manually. If you set up Versioning Based on Git Tags, then the publishVersion will be configured to the git tag version.

SNAPSHOT versions

To publish SNAPSHOT versions (that is any version that ends with -SNAPSHOT), you need to enable support for them in your Sonatype Central namespace. To do that, go to the Sonatype Central portal, click on the "three dots" next to the namespace name, and click on "Enable SNAPSHOTs".

SNAPSHOT versions do not have same semantics as regular versions: they are not signed and are not kept forever in the repository.

See Sonatype Central documentation for more information.

Your build file should look like:

extends: JavaModule
publishVersion: 0.0.1-SNAPSHOT

Running any of the publishing tasks will now publish SNAPSHOT versions. When running them you should see a note from Mill that it has detected a SNAPSHOT version.

To consume SNAPSHOT versions in your project, make sure to add the snapshot repository to your build file:

extends: JavaModule
repositories: ["https://central.sonatype.com/repository/maven-snapshots"]
mvnDeps:
- com.example:mymodule:1.0.0-SNAPSHOT

SonatypeCentralPublishModule Argument Reference

The publishAll task can be called from the CLI. If a required value is not provided via the CLI option, it will fall back to an environment variable (if available) or raise an error if missing.

The ./mill mill.javalib.SonatypeCentralPublishModule/publishAll takes the following options:

  • username: The username for calling the Sonatype Central publishing api. Defaults to the MILL_SONATYPE_USERNAME environment variable if unset. If neither the parameter nor the environment variable are set, an error will be thrown.

  • password: The password for calling the Sonatype Central publishing api. Defaults to the MILL_SONATYPE_PASSWORD environment variable if unset. If neither the parameter nor the environment variable are set, an error will be thrown.

  • gpgArgs: Arguments to pass to the gpg package for signing artifacts. Uses the MILL_PGP_PASSPHRASE environment variable if set. Default: [--passphrase=$MILL_PGP_PASSPHRASE], --no-tty, --pinentry-mode, loopback, --batch, --yes, --armor, --detach-sign.

  • publishArtifacts: The command for generating all publishable artifacts (ex. __.publishArtifacts). Required.

  • readTimeout: The timeout for receiving a response from Sonatype Central after the initial connection has occurred. Default: 60000.

  • awaitTimeout: The overall timeout for all retries (including exponential backoff) of the bundle upload. Default: 120 * 1000.

  • connectTimeout: The timeout for the initial connection to Sonatype Central if there is no response. Default: 5000.

  • shouldRelease: Whether the bundle should be automatically released when uploaded to Sonatype Central. If false, the bundle will still be uploaded, but users will need to manually log in to Sonatype Central and publish the bundle from the portal. Default: true

  • bundleName: If set, all packages will be uploaded in a single bundle with the given name. If unset, packages will be uploaded separately. Recommended bundle name syntax: groupName-artifactId-versionNumber. As an example, if publishing the com.lihaoyi requests package, without the bundle name, four different bundles will be uploaded, one for each scala version supported. With a bundle name of com.lihaoyi-requests-<new_version>, a single bundle will be uploaded that contains all packages across scala versions. It is recommended to set the bundle name, so that packages can be verified and deployed together. Default: No bundle name is set and packages will be uploaded separately

For example, you may call mill.javalib.SonatypeCentralPublishModule/ via:

$ mill \
mill.javalib.SonatypeCentralPublishModule/publishAll \
--username myusername \
--password mypassword \
--gpgArgs --passphrase=$MILL_PGP_PASSPHRASE,--no-tty,--pinentry-mode,loopback,--batch,--yes,--armor,--detach-sign \
--publishArtifacts __.publishArtifacts \
--readTimeout  36000 \
--awaitTimeout 36000 \
--connectTimeout 36000 \
--shouldRelease false \
--bundleName com.lihaoyi-requests:1.0.0

publishSonatypeCentral

The __.publishSonatypeCentral command takes the username and password arguments, documented above.

Manual GPG Setup

If you have never used GPG before, initGpgKeys is probably the easiest way to get started, but you can also follow Sonatype’s GPG Documentation which has manual instructions on how to do this.

Mill uses the following environment variables as a way to pass the necessary secrets for publishing to Maven Central:

# Sonatype Central Portal needs your public key to be uploaded so it can use for verification of artifacts from their end.
#
# Send your public key to ubuntu server so Sonatype Maven Central can use for verification of the artifacts
gpg --keyserver  keyserver.ubuntu.com --send-keys $LONG_ID
#
# Check the server for information about the public key. information will be displayed if found
gpg --keyserver  keyserver.ubuntu.com --recv-keys $LONG_ID
#
# The LHS and RHS of the User Token, accessible through the sonatype
# website `Profile` / `User Token` / `Access User Token`
export MILL_SONATYPE_USERNAME=...
export MILL_SONATYPE_PASSWORD=...

# The base-64 encoded PGP key, which can be encoded in the following way
# for each OS:
#
# MacOS or FreeBSD
# gpg --export-secret-key -a $LONG_ID | base64
#
# Ubuntu (assuming GNU base64)
# gpg --export-secret-key -a $LONG_ID | base64 -w0
#
# Arch
# gpg --export-secret-key -a $LONG_ID | base64 | sed -z 's;\n;;g'
#
# Windows
# gpg --export-secret-key -a %LONG_ID% | openssl base64
export MILL_PGP_SECRET_BASE64=...

# The passphrase associated with your PGP key
export MILL_PGP_PASSPHRASE=...

Publishing to Generic Maven Repositories

Apart from publishing your project’s modules to Maven Central, you may also want to publish them to a generic Maven repository (such as a privately hosted one).

Publishing Secrets

Mill uses the following environment variables as a way to pass the necessary secrets for publishing:

# Credentials for publishing to the repository
export MILL_MAVEN_USERNAME=...
export MILL_MAVEN_PASSWORD=...

Publishing

You can publish all eligible modules in your Mill project using the Default Task of the External Module mill.javalib.MavenPublishModule:

> mill mill.javalib.MavenPublishModule/ \
        --username myusername \
        --password mypassword \
        --releaseUri https://example.company.com/release \
        --snapshotUri https://example.company.com/snapshot

When you do not provide the username and password parameters, Mill will instead look for the MILL_MAVEN_USERNAME and MILL_MAVEN_PASSWORD environment variables. Providing the credentials via environment variables is the recommended approach due to security considerations.

Non-Staging Releases (classic Maven uploads)

If the site does not support staging releases as oss.sonatype.org and s01.oss.sonatype.org do (for example, a self-hosted OSS nexus site), you can pass in the --stagingRelease false option to simply upload release artifacts to corresponding maven path under sonatypeUri instead of staging path.

> mill mill.scalalib.PublishModule/ \
        --publishArtifacts foo.publishArtifacts \
        --sonatypeCreds lihaoyi:$SONATYPE_PASSWORD \
        --sonatypeUri http://example.company.com/release \
        --stagingRelease false

Publishing to other repositories

While Sonatype Maven Central is the default publish repository for JVM ecosystem projects, there are also others that you can use. Mill supports these largely through contrib plugins: