You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat(ENG-15139): set up the Cloudsmith Maven tools for Maven, Gradle, and sbt - #520
The Cloudsmith Maven tools let Maven, Gradle, and sbt builds authenticate to Cloudsmith with no credentials in settings.xml, gradle.properties, or build.sbt. The tools are a Maven extension, a Gradle plugin, and an sbt plugin. Each one gets its credential from the generic credential helper, cloudsmith credential-helper generic, and sends it only to Cloudsmith hosts.
This PR adds three commands that set up the tools:
By default, each command sets up every build of the user. Each command installs the newest release from the public cloudsmith/maven-tools repository and writes that exact version.
Command
Changes
Applies to
install maven
Puts the extension JAR and POM in ~/.m2/repository, and adds the extension to ~/.m2/extensions.xml
Every build with Maven 3.10 or later
install maven --local
Puts the extension in ~/.m2/repository, and adds the extension to .mvn/extensions.xml
The Maven project in the current directory, with Maven 3.8 or later
install maven --mavenrc
Puts the extension in ~/.m2/repository, and adds a block to ~/.mavenrc that sets maven.ext.class.path
Every build that starts with the mvn or mvnw script, with any Maven version
The commands add to existing files. Other extensions in extensions.xml and other lines in ~/.mavenrc stay as they are. uninstall removes only what install added. --dry-run and list work for all three tools.
Prerequisites
Java 17 or later, and Maven, Gradle, or sbt.
A private Cloudsmith Maven repository with Maven Central as an upstream. The examples use WORKSPACE and REPOSITORY as placeholders for its workspace and repository names.
The CLI from this branch, signed in. The plugins also run this CLI to get the credential.
The command downloads the extension and adds it to ~/.m2/extensions.xml:
downloaded cloudsmith-maven-extension-0.1.0.jar to /Users/me/.m2/repository/io/cloudsmith/cloudsmith-maven-extension/0.1.0
downloaded cloudsmith-maven-extension-0.1.0.pom to /Users/me/.m2/repository/io/cloudsmith/cloudsmith-maven-extension/0.1.0
added version 0.1.0 to /Users/me/.m2/extensions.xml
Note: Maven 3.9 and earlier ignore ~/.m2/extensions.xml. When mvn -v reports one of these versions, the command shows a warning. Use --local or --mavenrc instead. See Use Maven 3.9 or earlier.
Step 2: Make a project
Make a directory with this pom.xml. The POM has no credentials, and settings.xml needs no <server> entry.
mvn -X dependency:resolve | grep -E "Using the cloudsmith CLI credential|Downloaded from cloudsmith.*commons-lang3-3.17.0.jar|BUILD"
The extension adds the credential for the Cloudsmith repository, and Maven downloads Commons Lang through it:
[DEBUG] Using the cloudsmith CLI credential for https://dl.cloudsmith.io/basic/WORKSPACE/REPOSITORY/maven/
Downloaded from cloudsmith: https://dl.cloudsmith.io/basic/WORKSPACE/REPOSITORY/maven/org/apache/commons/commons-lang3/3.17.0/commons-lang3-3.17.0.jar (674 kB at 6.9 MB/s)
[INFO] BUILD SUCCESS
Note: Maven downloads only the files that are not in ~/.m2/repository. If the Downloaded from cloudsmith line does not appear, delete ~/.m2/repository/org/apache/commons/commons-lang3/3.17.0 and run the command again.
Use Maven 3.9 or earlier
Choose one of these options:
One project: Run the command in the root directory of the project. The command adds the extension to .mvn/extensions.xml. Commit this file so that the whole team gets the extension. Each machine that builds the project, including CI, must also run the command to get the JAR into its local repository.
Every build: The command adds this block to ~/.mavenrc, after the lines that are already there. The mvn and mvnw scripts read the file. Maven that runs inside an IDE does not read it.
Using the cloudsmith CLI credential for https://dl.cloudsmith.io/basic/WORKSPACE/REPOSITORY/maven/
\--- org.apache.commons:commons-lang3:3.17.0
BUILD SUCCESSFUL in 9s
Note: Gradle has no init scripts for one build, so install gradle has no --local option. To set up one build, apply the io.cloudsmith.gradle-plugin plugin in settings.gradle.kts.
Example: sbt
Step 1: Install the plugin
cloudsmith credential-helper install sbt
wrote /Users/me/.sbt/1.0/plugins/cloudsmith.sbt for version 0.1.0
To set up only the project in the current directory, use --local. The command writes project/cloudsmith.sbt, and the other files of the build stay as they are.
Step 2: Make a project
project/build.properties:
sbt.version=1.13.0
build.sbt:
resolvers +="cloudsmith" at "https://dl.cloudsmith.io/basic/WORKSPACE/REPOSITORY/maven/"
libraryDependencies +="org.apache.commons"%"commons-lang3"%"3.17.0"
Step 3: Resolve the dependency and check the credential
cloudsmith credential-helper uninstall maven # ~/.m2/extensions.xml and ~/.mavenrc
cloudsmith credential-helper uninstall maven --local # .mvn/extensions.xml of the current project
cloudsmith credential-helper uninstall gradle
cloudsmith credential-helper uninstall sbt
cloudsmith credential-helper uninstall sbt --local
uninstall deletes a file only when nothing else is left in it. It keeps the JAR in ~/.m2/repository, because other projects can use it.
Troubleshooting
The build fails with 401 Unauthorized
The build did not get a credential. Run cloudsmith whoami to confirm that the CLI is signed in. Then run cloudsmith domains list to confirm that the repository host is in the list.
The credential line does not appear
A credential in the build takes precedence. Remove a <server> entry with the same id from ~/.m2/settings.xml, credentials from the Gradle repository, or an entry for the host from the sbt credentials.
Type of Change
Bug fix
New feature
Breaking change
Documentation update
Refactoring
Other (please describe)
Additional Notes
Implementation
credential_helpers/maven/: the Maven installer, the extensions.xml edits, and the ~/.mavenrc block. The edits change the text in place, so other content and formatting stay. The extensions.xml edits ignore XML comments.
credential_helpers/gradle/ and credential_helpers/sbt/: small subclasses of ConfigFileInstaller in credential_helpers/maven_tools.py, which also gets the newest version and downloads files.
manage.py: registers the three installers and adds --local (Maven and sbt) and --mavenrc (Maven). Each option is a HelperOption, a click.Option that names the helpers that accept it. For another helper, Click shows a usage error before the installer runs. Click also rejects --local together with --mavenrc.
install maven marks the extension files as installed in _remote.repositories. Maven does not load a build extension that this file records as downloaded from a repository. Without the mark, a copy that Maven downloaded earlier stopped every Maven build in the project.
Tests
Unit tests in cloudsmith_cli/cli/tests/test_credential_helper_maven_tools.py. The full unit suite and pre-commit pass.
End to end in CI: the maven-tools workflow installs this branch and signs in with GitHub OIDC through cloudsmith-cli-action@v3. Each of six jobs runs credential-helper install, downloads a package from a private repository, publishes a new version to it, and runs credential-helper uninstall. The jobs cover maven (Maven 3.10), maven --mavenrc and maven --local (Maven 3.9), gradle, sbt, and sbt --local.
I ran the examples above against a private repository with Maven 3.10.0, Gradle 9.8.1, and sbt 1.13.0, in a separate home directory. The outputs above come from these runs. I also checked these cases:
--mavenrc with Maven 3.10.0: Maven loaded the extension from maven.ext.class.path, and uninstall restored the original ~/.mavenrc.
install sbt --local: sbt added the credential with no global plugin.
A JAR that Maven recorded as downloaded from a repository: after install maven, Maven loaded the extension offline.
Open questions
install maven always uses ~/.m2/repository. It ignores <localRepository> in settings.xml and -Dmaven.repo.local.
--mavenrc writes a POSIX shell block. It does not write %USERPROFILE%\mavenrc_pre.cmd for Windows, and a home directory path with spaces breaks the MAVEN_OPTS line.
sbt 2 is not covered. Its global plugin directory moved to $XDG_CONFIG_HOME/sbt/2/plugins on sbt's development branch, and I did not confirm which release uses it.
list shows the config file in the launcher field, because these tools have no launcher.
--bin-dir and --domain have no effect for these tools.
…, and sbt
`credential-helper install maven|gradle|sbt` sets up the Maven extension
and the Gradle and sbt plugins, which get repository credentials from
`cloudsmith credential-helper generic`. Each install takes the newest
release from the public cloudsmith/maven-tools repository.
- maven: puts the extension in ~/.m2/repository and adds it to
.mvn/extensions.xml of the project in the current directory. Maven 3.9
has no user-wide extensions, and loads a build extension only from
Maven Central or the local repository.
- gradle: writes an init script to ~/.gradle/init.d that applies the
plugin to every build.
- sbt: writes a global plugin to ~/.sbt/1.0/plugins for every sbt 1
build.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`install maven` found the Cloudsmith extension inside an XML comment and
changed the version there. The file then had no active entry, but the
command reported success. A groupId or artifactId with spaces inside the
tag was not found, so the command added a second entry.
The search now replaces comments with spaces before it looks for the
extension, its <version>, and the <extensions> element. The offsets do not
change, so the edit still keeps the rest of the file as it is. The
groupId and artifactId match with spaces around the value.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds installation support for Cloudsmith’s Maven, Gradle, and sbt tools, which obtain repository credentials through the existing generic credential helper.
Changes:
Adds project-level Maven extension installation and global Gradle/sbt plugin configuration.
Resolves and pins release versions, with uninstall, status, and dry-run support.
Registers the installers and adds unit tests and changelog documentation.
… and --mavenrc
`install maven`, `install gradle`, and `install sbt` now set up every build
of the user by default.
- maven: lists the extension in ~/.m2/extensions.xml, which Maven 3.10 and
later read. When `mvn -v` reports an earlier Maven, install shows a
warning that names the two options below.
- maven --local: lists the extension in .mvn/extensions.xml of the project,
as before.
- maven --mavenrc: adds a marked block to ~/.mavenrc that sets
maven.ext.class.path. The mvn and mvnw scripts of every Maven version read
it. The other lines of the file stay, and uninstall removes only the block.
- sbt --local: writes project/cloudsmith.sbt, so the build's own files stay
as they are.
- gradle --local: fails with the settings.gradle.kts plugin block to add.
- Other helpers reject --local and --mavenrc with a usage error.
Install also marks the extension files as installed in
_remote.repositories. Maven does not load a build extension that it
records as downloaded from a repository, so a copy that Maven downloaded
earlier stopped every build in the project.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cloudsmith-iduffy
changed the title
feat(no-ticket): install the Cloudsmith Maven tools for Maven, Gradle, and sbt
feat(no-ticket): set up the Cloudsmith Maven tools for Maven, Gradle, and sbt
Oct 22, 2026
cloudsmith-iduffy
added a commit
to cloudsmith-iduffy/cloudsmith-cli-oidc-test
that referenced
this pull request
Oct 22, 2026
Adds worked examples of the Maven extension and the Gradle and sbt
plugins, set up by `cloudsmith credential-helper install` from
cloudsmith-io/cloudsmith-cli#520. Each example downloads from and
publishes to iduffy-demo/default through GitHub OIDC, and fails unless
the build used the CLI credential for both URLs.
The examples cover every build of the user (global) and one project
(local): Maven through ~/.m2/extensions.xml, ~/.mavenrc, and
.mvn/extensions.xml; Gradle through an init script and the settings
plugin block; and sbt through a global plugin and project/cloudsmith.sbt.
mise installs Java, Maven, Gradle, sbt, and uv, and runs each example as
a task.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… settings
Addresses the review of #520.
- Uninstall deleted .mvn/extensions.xml when the other extensions had
spaces before the > of a tag, such as `<extension >`. The block pattern
now allows these spaces. Before uninstall deletes the file, it parses
what is left, and keeps the file if an <extension> element remains or
if the rest is not valid XML.
- The downloads of the Maven tools used a default session, so
--proxy, --without-ssl-verify, and --without-ssl-verify-strict did not
apply to them. `install` now passes a session with these settings to
the installers that download files. The session does not get the
--api-headers, because the public repository is not the API host.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
--local and --mavenrc now name the helpers that accept them. The install
and uninstall commands show a usage error for another helper before the
installer runs. Click also rejects --local together with --mavenrc.
`install gradle --local` is now a usage error. It no longer gets the
latest version only to show a settings.gradle.kts block.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@copilot resolve the merge conflicts in this pull request
Resolved in 383554c by merging the latest master. Kept helper-option forwarding and both uninstall error handlers. All 1,979 non-integration tests and pre-commit checks for the resolved file passed.
* origin/master:
fix(ENG-15484): accept --color on click versions before 8.2 (#582)
cloudsmith-iduffy
changed the title
feat(no-ticket): set up the Cloudsmith Maven tools for Maven, Gradle, and sbt
feat(ENG-15139): set up the Cloudsmith Maven tools for Maven, Gradle, and sbt
Nov 3, 2026
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
Installation and removal can destroy unrelated Gradle/sbt configuration, while Maven setup overrides existing extension paths and silently disables other extensions.
The JVM uses only the last -Dmaven.ext.class.path value. The ~/.mavenrc
block now adds the JAR to that value instead of a second option.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Install and uninstall now stop with an error when the config file has
content other than the file that install writes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
The duplicate check can leave the Cloudsmith extension unloaded when classpath properties are overridden, while whitespace handling in valid XML can cause installation failures or duplicate entries.
An existing Cloudsmith entry with valid XML such as <version >0.1.0</version > causes installation to report that the version is missing. Spaced groupId or artifactId tags instead hide the entry from install, uninstall, and status, so installation adds a duplicate. Allow whitespace before > on both opening and closing child-element tags.
Allow whitespace before the closing extensions tag
A valid existing file ending in </extensions > or with a newline before > fails installation here with "The file has no element." XML permits that whitespace, but the literal search cannot find the insertion point. Match the closing root tag with optional whitespace and use its start offset for insertion.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Human assessment is needed because requiring build.sbt can reject valid sbt projects and prevent listing or uninstalling local installations after the build file is renamed.
sbt reads any .sbt file in the root and the project/ directory. Maven finds
the project root by its .mvn directory, whatever the POM is called.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Human assessment is needed because Maven option parsing can add the Cloudsmith JAR to a nested argLine value rather than the extension class path, leaving the extension unloaded.
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
MAVEN_OPTS arguments separated by tabs or newlines can cause a duplicate extension-path property that overrides the user's existing extensions, requiring whitespace-aware parsing and regression coverage.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Human assessment is needed because uninstall can delete comments and commented-out third-party extensions from extensions.xml, and the current test expects that content loss.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Human assessment is needed because switching installation modes can leave both Maven activations in place, potentially loading different versions on Maven 3.10+.
Maven 3.10 reads both ~/.m2/extensions.xml and ~/.mavenrc, so only one of
them loads the extension.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reason will be displayed to describe this comment to others. Learn more.
🟢 Approval recommended
The review identifies no active issues in the Maven, Gradle, and sbt credential-helper changes.
No open issues 🎉
Review effort: Balanced
This branch has not been deployed
No deployments
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
The Cloudsmith Maven tools let Maven, Gradle, and sbt builds authenticate to Cloudsmith with no credentials in
settings.xml,gradle.properties, orbuild.sbt. The tools are a Maven extension, a Gradle plugin, and an sbt plugin. Each one gets its credential from the generic credential helper,cloudsmith credential-helper generic, and sends it only to Cloudsmith hosts.This PR adds three commands that set up the tools:
By default, each command sets up every build of the user. Each command installs the newest release from the public
cloudsmith/maven-toolsrepository and writes that exact version.install maven~/.m2/repository, and adds the extension to~/.m2/extensions.xmlinstall maven --local~/.m2/repository, and adds the extension to.mvn/extensions.xmlinstall maven --mavenrc~/.m2/repository, and adds a block to~/.mavenrcthat setsmaven.ext.class.pathmvnormvnwscript, with any Maven versioninstall gradle~/.gradle/init.d/cloudsmith.init.gradle.kts(or$GRADLE_USER_HOME/init.d)install sbt~/.sbt/1.0/plugins/cloudsmith.sbtinstall sbt --localproject/cloudsmith.sbtThe commands add to existing files. Other extensions in
extensions.xmland other lines in~/.mavenrcstay as they are.uninstallremoves only whatinstalladded.--dry-runandlistwork for all three tools.Prerequisites
Java 17 or later, and Maven, Gradle, or sbt.
A private Cloudsmith Maven repository with Maven Central as an upstream. The examples use
WORKSPACEandREPOSITORYas placeholders for its workspace and repository names.The CLI from this branch, signed in. The plugins also run this CLI to get the credential.
gh pr checkout 520 uv sync source .venv/bin/activate cloudsmith whoamiExample: Maven
Step 1: Install the extension
The command downloads the extension and adds it to
~/.m2/extensions.xml:Step 2: Make a project
Make a directory with this
pom.xml. The POM has no credentials, andsettings.xmlneeds no<server>entry.Step 3: Resolve the dependency through Cloudsmith
The extension adds the credential for the Cloudsmith repository, and Maven downloads Commons Lang through it:
Use Maven 3.9 or earlier
Choose one of these options:
One project: Run the command in the root directory of the project. The command adds the extension to
.mvn/extensions.xml. Commit this file so that the whole team gets the extension. Each machine that builds the project, including CI, must also run the command to get the JAR into its local repository.Every build: The command adds this block to
~/.mavenrc, after the lines that are already there. Themvnandmvnwscripts read the file. Maven that runs inside an IDE does not read it.Example: Gradle
Step 1: Install the plugin
The init script applies the plugin to the settings of every Gradle build. The build files need no change.
Step 2: Make a project
settings.gradle.kts:build.gradle.kts:plugins { java } dependencies { implementation("org.apache.commons:commons-lang3:3.17.0") }Step 3: Resolve the dependency through Cloudsmith
Example: sbt
Step 1: Install the plugin
To set up only the project in the current directory, use
--local. The command writesproject/cloudsmith.sbt, and the other files of the build stay as they are.Step 2: Make a project
project/build.properties:build.sbt:Step 3: Resolve the dependency and check the credential
The plugin adds a credential for the Cloudsmith host. sbt does not show the token.
Remove the setup
uninstalldeletes a file only when nothing else is left in it. It keeps the JAR in~/.m2/repository, because other projects can use it.Troubleshooting
The build fails with 401 Unauthorized
The build did not get a credential. Run
cloudsmith whoamito confirm that the CLI is signed in. Then runcloudsmith domains listto confirm that the repository host is in the list.The credential line does not appear
A credential in the build takes precedence. Remove a
<server>entry with the same id from~/.m2/settings.xml,credentialsfrom the Gradle repository, or an entry for the host from the sbtcredentials.Type of Change
Additional Notes
Implementation
credential_helpers/maven/: the Maven installer, theextensions.xmledits, and the~/.mavenrcblock. The edits change the text in place, so other content and formatting stay. Theextensions.xmledits ignore XML comments.credential_helpers/gradle/andcredential_helpers/sbt/: small subclasses ofConfigFileInstallerincredential_helpers/maven_tools.py, which also gets the newest version and downloads files.manage.py: registers the three installers and adds--local(Maven and sbt) and--mavenrc(Maven). Each option is aHelperOption, aclick.Optionthat names the helpers that accept it. For another helper, Click shows a usage error before the installer runs. Click also rejects--localtogether with--mavenrc.install mavenmarks the extension files as installed in_remote.repositories. Maven does not load a build extension that this file records as downloaded from a repository. Without the mark, a copy that Maven downloaded earlier stopped every Maven build in the project.Tests
cloudsmith_cli/cli/tests/test_credential_helper_maven_tools.py. The full unit suite and pre-commit pass.maven-toolsworkflow installs this branch and signs in with GitHub OIDC throughcloudsmith-cli-action@v3. Each of six jobs runscredential-helper install, downloads a package from a private repository, publishes a new version to it, and runscredential-helper uninstall. The jobs covermaven(Maven 3.10),maven --mavenrcandmaven --local(Maven 3.9),gradle,sbt, andsbt --local.--mavenrcwith Maven 3.10.0: Maven loaded the extension frommaven.ext.class.path, anduninstallrestored the original~/.mavenrc.install sbt --local: sbt added the credential with no global plugin.install maven, Maven loaded the extension offline.Open questions
install mavenalways uses~/.m2/repository. It ignores<localRepository>insettings.xmland-Dmaven.repo.local.--mavenrcwrites a POSIX shell block. It does not write%USERPROFILE%\mavenrc_pre.cmdfor Windows, and a home directory path with spaces breaks theMAVEN_OPTSline.$XDG_CONFIG_HOME/sbt/2/pluginson sbt's development branch, and I did not confirm which release uses it.listshows the config file in thelauncherfield, because these tools have no launcher.--bin-dirand--domainhave no effect for these tools.🤖 Generated with Claude Code