Skip to content

fix(ENG-15139): ask Maven, Gradle and sbt where they read the Cloudsmith tools - #595

Merged
cloudsmith-iduffy merged 40 commits into
masterfrom
iduffy/fix-maven-local-repository
Nov 9, 2026
Merged

cloudsmith-iduffy merged 40 commits into
masterfrom
iduffy/fix-maven-local-repository

Conversation

@cloudsmith-iduffy

@cloudsmith-iduffy cloudsmith-iduffy commented Nov 5, 2026 •

Copy link
Copy Markdown
Collaborator

Description

cloudsmith credential-helper install maven, install gradle and install sbt put the Cloudsmith Maven tools in fixed locations: ~/.m2/repository, $GRADLE_USER_HOME or ~/.gradle, and ~/.sbt/1.0/plugins. If you moved one of these, the build tool looked in the other directory and did not find the extension or plugin. The CLI had already reported success, so the first sign of the problem was a failed build.

Before, with <localRepository>/opt/m2</localRepository> in ~/.m2/settings.xml:

$ cloudsmith credential-helper install maven
downloaded cloudsmith-maven-extension-0.2.0.jar to /home/me/.m2/repository/io/cloudsmith/cloudsmith-maven-extension/0.2.0

After:

downloaded cloudsmith-maven-extension-0.2.0.jar to /opt/m2/io/cloudsmith/cloudsmith-maven-extension/0.2.0

The CLI now asks each build tool where it reads its config, and does not parse the tool's config files itself. The tool applies its own settings files, environment variables, system properties and version rules.

Tool The CLI runs The tool reports
Maven mvn -X -o -B cloudsmith-local-repository-probe [DEBUG] Using manager … for <local repository>
Gradle gradle -q --offline --no-daemon -I probe.init.gradle help the Gradle user home, from a one-line init script
sbt sbt -batch -no-colors cloudsmithGlobalPlugins the global plugins directory, from a probe build.sbt (sbt 1 only). If a global plugin does not load, sbt 1 still names the directory in loading global plugins from …, so uninstall can remove a broken plugin

All three use the same code in maven_tools:

  • tool_output runs the tool with a 60-second timeout. It reads stdout and stderr together and removes colors. The tool runs in its own process group. On timeout or Ctrl+C, the CLI stops the tool and the JVM that the launcher script started. On timeout the command fails. It does not fall back to a default path in silence.
  • For a user-wide install, the tool runs in an empty temporary directory, so the config files of the current project do not apply. An empty .mvn and settings.gradle there stop Maven and Gradle from reading a project above the temporary directory. The tool then runs again in a subdirectory. If the two answers differ, the configured path is relative and changes with the directory of each build. The command then stops and tells you to set an absolute path or use --local.
  • If the tool is not on PATH, the CLI uses the old default location. If the tool is on PATH but reports nothing, for example a version-manager shim that works only inside one project, the CLI uses the default location and shows a warning that names it.

Maven also has these rules:

  • The unknown phase makes Maven stop before it runs a plugin, also when the POM has a <defaultGoal> or MAVEN_ARGS has goals.
  • With --local, Maven runs in the current directory, as a build from there does. The project's mvnw (mvnw.cmd on Windows) runs when there is one, else mvn.
  • --mavenrc uses the same repository, so the JAR path in ~/.mavenrc points at it too.

install and uninstall ask the tool. status, and so credential-helper list, keeps the default locations, so list does not start three JVMs.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update
  • Refactoring
  • Other (please describe)

Additional Notes

Each tool has one test that runs the real tool. The test skips when the tool is not installed or does not run. GitHub's Ubuntu and Windows runners have Maven and Gradle, so CI runs those tests. I ran all three locally with Maven 3.6.3, 3.8.8, 3.9.16 and 3.10.0, Gradle 9.8.1 and sbt 1.13.0. Typical time for install: Maven 1 s, Gradle 4 s, sbt 5 s.

The tools do not agree, which is why the CLI asks them and does not parse their files. With -Dmaven.repo.local=${user.home}/repo in .mvn/maven.config, Maven 3.9 and later use ~/repo. Maven 3.8 and earlier use a directory named ${user.home}/repo. sbt prints a relative sbt.global.base as it is, so the sbt probe prints the absolute path.

To check that the Maven probe runs no goal, I used Maven 3.8.8 with a POM whose <defaultGoal> is process-resources. With no goal, Maven ran maven-resources-plugin. With the unknown phase and MAVEN_ARGS=process-resources, Maven 3.6.3, 3.8.8, 3.9.16 and 3.10.0 stopped before any plugin ran. Maven does not accept goals in .mvn/maven.config.

Maven 3.10 reads user extensions only from ${user.home}/.m2/extensions.xml, also with -Dmaven.user.conf, so that path stays fixed.

An earlier version of this PR parsed the Maven config files in Python. Each review found another case where it did not match Maven, so that code is gone.

🤖 Generated with Claude Code

cloudsmith-iduffy and others added 2 commits November 5, 2026 12:44
The mvn script expands MAVEN_OPTS without quotes, so a JAR path with a
space splits into two JVM arguments and every build fails. mvn.cmd on
Windows does not read ~/.mavenrc. Stop `install maven --mavenrc` in both
cases before it changes a file or downloads the JAR, and tell the user to
use --local or Maven 3.10. The Maven 3.9 warning on Windows now names
only --local.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…itory

Maven loads a build extension from extensions.xml only from Maven Central
or the local repository. The installer always used ~/.m2/repository, so a
custom local repository made Maven miss the extension.

The installer now uses -Dmaven.repo.local from MAVEN_OPTS (and, with
--local, from .mvn/jvm.config and .mvn/maven.config), then
<localRepository> in ~/.m2/settings.xml, then ~/.m2/repository.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cloudsmith-iduffy cloudsmith-iduffy added the v1.31.0-prereq Must merge before the v1.31.0 release label Nov 5, 2026
Copilot AI balanced review requested due to automatic review settings November 5, 2026 12:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The Maven config parser treats commented repository settings as active and truncates valid paths containing spaces, allowing local installation to report success in a repository Maven will not use.

2 open issues
What changed in this PR

Updates the Maven credential-helper installer to place the extension in the configured local repository rather than always using ~/.m2/repository.

Changes:

  • Resolves repository settings from MAVEN_OPTS, project configuration with --local, and user settings.xml.
  • Supports settings placeholders and reports malformed or unresolved settings.
  • Uses the resolved directory for downloads and --mavenrc, with regression tests.
File Description
cloudsmith_cli/​credential_helpers/​maven/​installer.py Resolves the Maven local repository and uses it for extension installation.
cloudsmith_cli/​cli/​tests/​test_credential_helper_maven_tools.py Tests repository selection, precedence, interpolation, and settings errors.

Review effort: Balanced

cloudsmith-iduffy and others added 2 commits November 5, 2026 15:18
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Maven 3.9 reads each line of .mvn/maven.config as one argument and skips
lines that start with #. The installer split the file on whitespace, so a
comment could set the local repository and a path with a space was cut.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The installer misparses Maven 3.8 multi-option configuration lines, placing the extension in the wrong directory and reporting success even though Maven cannot find it.

1 open issue
2 resolved since last review

Review effort: Balanced

Maven 3.8 splits .mvn/maven.config on whitespace, so one line can hold
several options. With --local, the installer now asks mvn -v for the
version when maven.config is not empty, and splits the file on whitespace
for Maven before 3.9.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Whitespace-containing Maven repository paths produce a class-path option that splits into multiple JVM arguments and breaks Maven startup.

No open issues 🎉

1 previously missed issue

In code that hasn't changed since last review

Medium severity Quote whitespace-containing Maven extension paths in MAVEN_OPTS

cloudsmith_cli/​credential_helpers/​maven/​installer.py:273

With <localRepository>/opt/maven cache</localRepository>, install maven --mavenrc now downloads successfully but writes a class-path option that breaks Maven startup. add_block stores the path in MAVEN_OPTS, which the launcher expands unquoted: the generated option becomes two arguments (-Dmaven.ext.class.path=/opt/maven and cache/...jar). Preserve the JAR path as one JVM argument, or reject whitespace-containing paths before downloading or changing activation files and direct the user to an extensions.xml-based installation.

1 resolved since last review

Review effort: Balanced

@cloudsmith-iduffy

Copy link
Copy Markdown
Collaborator Author

@copilot resolve the merge conflicts in this pull request

…cal-repository

# Conflicts:
#	cloudsmith_cli/credential_helpers/maven/installer.py

Co-authored-by: cloudsmith-iduffy <178375997+cloudsmith-iduffy@users.noreply.github.com>
auto-merge was automatically disabled November 5, 2026 15:43

Head branch was pushed to by a user without write access

Copilot AI commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

@copilot resolve the merge conflicts in this pull request

Resolved and merged master into the PR branch in merge commit fcf20b6.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The update to install the Maven credential helper in Maven’s configured local repository has no active review issues.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

Gradle reports its user home and sbt 1 reports its global plugins directory,
so -Dgradle.user.home and -Dsbt.global.base now count. Maven, Gradle and sbt
share one way to run the tool and to find a relative path. status keeps the
default locations, so credential-helper list stays fast.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cloudsmith-iduffy cloudsmith-iduffy changed the title fix(ENG-15139): put the Maven extension in the configured local repository fix(ENG-15139): ask Maven, Gradle and sbt where they read the Cloudsmith tools Nov 6, 2026
@cloudsmith-iduffy
cloudsmith-iduffy requested a balanced review from Copilot November 6, 2026 18:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The test asserts an output order that Python buffering does not guarantee, and uninstall leaves relative configuration-path errors uncaught.

2 open issues

Review effort: Balanced

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Uninstall does not catch RelativeConfigPath, so relative Gradle or sbt configuration paths trigger unexpected-error handling and plain-text output instead of structured JSON with -F json.

1 open issue
1 resolved since last review

Review effort: Balanced

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cloudsmith-iduffy
cloudsmith-iduffy requested a balanced review from Copilot November 6, 2026 18:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Human review is needed to assess probe edge cases that can leave custom-path sbt plugins enabled, child JVMs holding cache locks, or installers silently falling back to default paths.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The probes can inherit project-specific Maven configuration, leave child JVMs running after timeout, and silently fall back to a default path when initialization stalls.

3 open issues

Review effort: Balanced

…e probe

A timeout now stops the JVM that the launcher script started and fails the
command, so the CLI never falls back to a default path in silence. An empty
.mvn and settings.gradle stop Maven and Gradle from reading a project above
the temporary directory.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The test uses os.kill(child_pid, 0) to detect a running child, which incorrectly treats an unreaped Linux zombie as alive and can fail despite successful process-group termination.

2 open issues
2 resolved since last review

Review effort: Balanced

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Human review is needed to assess a working-directory dependency that can cause tool detection to fail silently and select the default installation location instead of the configured directory.

1 open issue
1 resolved since last review

Review effort: Balanced

When mvn, gradle or sbt is on PATH but reports no location, the command now
warns and names the default it uses. The executable path is made absolute
before the tool runs in the temporary directory. The empty .mvn and
settings.gradle move into the shared probe, and Gradle and sbt keep only
their probe.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Human assessment is needed because cancellation and timeout cleanup can leave build processes running, while sbt plugin-loading failures can prevent uninstall from finding a custom installation path.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

…emove

The probe runs in its own session, so Ctrl+C now stops it as well. sbt 1
names its global plugins directory before it fails to load a plugin there,
so uninstall can still find a plugin that does not load.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Windows tests fail during cleanup because the patched subprocess method has an incompatible signature, and relative SBT paths can cause install/uninstall to operate in the wrong directory.

2 open issues

Review effort: Balanced

…s test

A relative path that a tool prints now resolves against the directory where
the tool ran, so the two probes see a relative global base of sbt. The
timeout test interrupts only the tool, so taskkill can run on Windows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No active review issues remain for the credential-helper installation changes that use build-tool-reported locations.

No open issues 🎉

2 resolved since last review

Review effort: Balanced

The build tool can take a few seconds to start, so install and uninstall
now print what they ask it before they run it. The list help says that it
checks only the default Gradle and sbt locations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No active review issues remain for the changes to install credential helpers in the build tools’ configured locations.

No open issues 🎉

Review effort: Balanced

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v1.31.0-prereq Must merge before the v1.31.0 release

Development

Successfully merging this pull request may close these issues.

4 participants