Publishing Scala Projects
This page will discuss common topics around publishing your Scala 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:
extends: [ScalaModule, PublishModule]
scalaVersion: 3.8.0
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 Scala Packaging & Publishing
Programmatic Publishing Configuration
package build
import mill.*, scalalib.*, publish.*
object foo extends ScalaModule, PublishModule {
def scalaVersion = "3.8.0"
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"))
)
}
This is an example ScalaModule with added publishing capabilities via
PublishModule. This requires that you define an additional
publishVersion and pomSettings with the relevant metadata, and provides
the .publishLocal and publishSigned tasks for publishing locally to the
machine or to the central maven repository
> ./mill foo.publishLocal # publish specific modules
> ./mill __.publishLocal # publish every eligible module
Publishing Artifact(com.lihaoyi,foo_3,0.0.1) to ivy repo...
publishLocal publishes the artifacts to the ~/.ivy2/local folder on your
machine, allowing them to be resolved by other projects and build tools. This
is useful as a lightweight way of testing out the published artifacts, without
the setup overhead and long latencies of publishing artifacts globally accessible
to anyone in the world.
publishLocal 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,
and javadoc generation fails or takes too much time. When using Scala 2, disabling
javadoc generation can bring large speedups, given it entails compiling your code
a second time.
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.
Publishing to Sonatype Maven Central
This example is configured for publishing to Sonatype Maven Central via
Central Portal. We extend SonatypeCentralPublishModule which provides
simplified publishing tasks without requiring Nexus repository manager.
//| mill-jvm-version: 17
package build
import mill.*, scalalib.*, publish.*
object foo extends ScalaModule, SonatypeCentralPublishModule {
def scalaVersion = "3.8.0"
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"))
)
}
Note that when publishing artifacts to Maven repositories, the JVM version used to build the
artifacts will be the minimum version that downstream projects will need to use in order to
use your artifacts. Thus it is best to set your mill-jvm-version to the lowest version
possible, e.g. mill-jvm-version: 17 in this example which is the lowest version supported
by Mill.
If the publishVersion ends with SNAPSHOT, then the artifacts will NOT be signed and will be
published as a snapshot, which allows overwriting it at a later date. Mill takes care of timestamping
the snapshots and updating the appropriate maven-metadata.xml file to point to the latest snapshot version.
Note that for this to work you MUST enable SNAPSHOTs for your namespace in Sonatype Central.
Instructions for Publishing to Maven Central via Central Portal
Once you’ve mixed in PublishModule, apart from publishing locally, you can also publish
your project’s modules to maven central
GPG
If you’ve never created a keypair before that can be used to sign your artifacts you’ll need to do this. Sonatype’s GPG Documentation has the instructions on how to do this
Publishing Secrets
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
You can publish all eligible modules in your Mill project using
mill.javalib.SonatypeCentralPublishModule/:
> mill mill.javalib.SonatypeCentralPublishModule/
You can also specify individual modules you want to publish in two ways:
> mill foo.publishSonatypeCentral
> mill mill.javalib.SonatypeCentralPublishModule/ --publishArtifacts foo.publishArtifacts
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:
object mymodule extends JavaModule {
def 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:
object myModule extends JavaModule {
def repositories = Seq("https://central.sonatype.com/repository/maven-snapshots")
def mvnDeps = Seq(mvn"com.example:mymodule:1.0.0-SNAPSHOT")
}
Note that if you have downstream modules that depend on this module, they will need to add the snapshot repository to their definition as well, at least until this issue is fixed.
object myDownstreamModule extends JavaModule {
def moduleDeps = Seq(myModule)
// This is also needed.
def repositories = Seq("https://central.sonatype.com/repository/maven-snapshots")
}
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
- uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'
- 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.
Instructions for 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.
Instructions for Publishing to Maven Central via Legacy OSSRH (Deprecated)
|
Publishing via the legacy OSSRH (OSS Repository Hosting) is deprecated and will reach end-of-life on June 30, 2025, due to the retirement of Sonatype’s Nexus Repository Manager v2. Sonatype now recommends using the Central Portal for all new publishing. Migration is strongly encouraged to avoid disruptions. For full details, see the OSSRH Sunset Announcement. |
Just like publishing via the Central Portal requires a GPG key and publish secrets, publishing via the legacy OSSRH (OSS Repository Hosting) also requires them.
Publishing
You can publish all eligible modules in your Mill project using
the Default Task of the
External Module mill.scalalib.PublishModule:
> mill mill.scalalib.PublishModule/
You can also specify individual modules you want to publish via a selector:
> mill mill.scalalib.PublishModule/ --publishArtifacts foo.publishArtifacts
The default URL for publishing to sonatype’s Maven Central is oss.sonatype.org.
Newer projects registered on sonatype may need to publish using s01.oss.sonatype.org.
In that case, you can pass in a --sonatypeUri:
> mill mill.scalalib.PublishModule/ \
--sonatypeUri https://s01.oss.sonatype.org/service/local
This also allows you to publish to your own internal corporate sonatype deployment,
by passing in --sonatypeUri example.company.com instead.
|
Since Feb. 2021 any new Sonatype accounts have been created on
The symptom of using the "wrong" URL for publishing is typically a 403 error code, in response to the publish request. Typically
|
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
- uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'
- run: ./mill mill.scalalib.PublishModule/
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.
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
SonatypeCentralPublishModule Configurations
This module provides settings and a CLI interface for publishing artifacts to Sonatype Maven Central.
You can configure it through your build.mill file or by passing command-line options to it.
Module-Level Settings
You can override default publishing settings in your build.mill like this:
object mymodule extends SonatypeCentralPublishModule {
override def sonatypeCentralGpgArgs: T[String] = "--batch, --yes, -a, -b"
override def sonatypeCentralConnectTimeout: T[Int] = 5000
override def sonatypeCentralReadTimeout: T[Int] = 60000
override def sonatypeCentralAwaitTimeout: T[Int] = 120 * 1000
override def sonatypeCentralShouldRelease: T[Boolean] = true
...
}
Argument Reference
publishAll
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 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 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
Example command
$ mill -i \
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