Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ The full list of changes for this release can be found in https://github.com/dev

Release with new features and bugfixes:

* https://github.com/devonfw/IDEasy/issues/797[#797]: Fix VSCode install on macOS
* https://github.com/devonfw/IDEasy/issues/797[#797]: Fix VSCode install on macOS
* https://github.com/devonfw/IDEasy/issues/1956[#1956]: Merging MAVEN_ARGS buggy
* https://github.com/devonfw/IDEasy/issues/1978[#1978]: Enhanced the quality status documentation
* https://github.com/devonfw/IDEasy/issues/1946[#1946]: Add Spyder Python IDE
Expand All @@ -35,6 +35,7 @@ Release with new features and bugfixes:
* https://github.com/devonfw/IDEasy/issues/1457[#1457]: Improve CLI error messages on invalid args or commandlets not available in current context
* https://github.com/devonfw/IDEasy/issues/2030[#2030]: Fix Plugin install error in Intellij
* https://github.com/devonfw/IDEasy/issues/2032[#2032]: commandlet env bash leads to broken output
* https://github.com/devonfw/IDEasy/issues/2026[#2026]: Create UvRepository and UvBasedCommandlet

The full list of changes for this release can be found in https://github.com/devonfw/IDEasy/milestone/45?closed=1[milestone 2026.06.001].

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
import java.util.Map;
import java.util.NoSuchElementException;

import com.devonfw.tools.ide.tool.inso.Inso;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -37,16 +35,18 @@
import com.devonfw.tools.ide.tool.gradle.Gradle;
import com.devonfw.tools.ide.tool.gui.Gui;
import com.devonfw.tools.ide.tool.helm.Helm;
import com.devonfw.tools.ide.tool.inso.Inso;
import com.devonfw.tools.ide.tool.intellij.Intellij;
import com.devonfw.tools.ide.tool.jasypt.Jasypt;
import com.devonfw.tools.ide.tool.java.Java;
import com.devonfw.tools.ide.tool.jmc.Jmc;
import com.devonfw.tools.ide.tool.just.Just;
import com.devonfw.tools.ide.tool.kotlinc.Kotlinc;
import com.devonfw.tools.ide.tool.kotlinc.KotlincNative;
import com.devonfw.tools.ide.tool.kubectl.KubeCtl;
import com.devonfw.tools.ide.tool.lazydocker.LazyDocker;
import com.devonfw.tools.ide.tool.mvn.Mvn;
import com.devonfw.tools.ide.tool.msvc.Msvc;
import com.devonfw.tools.ide.tool.mvn.Mvn;
import com.devonfw.tools.ide.tool.nest.Nest;
import com.devonfw.tools.ide.tool.ng.Ng;
import com.devonfw.tools.ide.tool.node.Node;
Expand Down Expand Up @@ -172,6 +172,7 @@ public CommandletManagerImpl(IdeContext context) {
add(new Nest(context));
add(new Cdk(context));
add(new Claude(context));
add(new Just(context));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
import com.devonfw.tools.ide.tool.pip.PipRepository;
import com.devonfw.tools.ide.tool.repository.DefaultToolRepository;
import com.devonfw.tools.ide.tool.repository.ToolRepository;
import com.devonfw.tools.ide.tool.uv.UvRepository;
import com.devonfw.tools.ide.url.model.UrlMetadata;
import com.devonfw.tools.ide.util.DateTimeUtil;
import com.devonfw.tools.ide.util.PrivacyUtil;
Expand Down Expand Up @@ -155,6 +156,8 @@ public abstract class AbstractIdeContext implements IdeContext, IdeLogArgFormatt

private PipRepository pipRepository;

private UvRepository uvRepository;

private DirectoryMerger workspaceMerger;

protected UrlMetadata urlMetadata;
Expand Down Expand Up @@ -295,6 +298,13 @@ protected PipRepository createPipRepository() {
return new PipRepository(this);
}

/**
* @return a new {@link UvRepository}
*/
protected UvRepository createUvRepository() {
return new UvRepository(this);
}

private Path findIdeRoot(Path ideHomePath) {

Path ideRootPath = null;
Expand Down Expand Up @@ -514,6 +524,14 @@ public PipRepository getPipRepository() {
return this.pipRepository;
}

@Override
public UvRepository getUvRepository() {
if (this.uvRepository == null) {
this.uvRepository = createUvRepository();
}
return this.uvRepository;
}

@Override
public CustomToolRepository getCustomToolRepository() {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import com.devonfw.tools.ide.tool.npm.NpmRepository;
import com.devonfw.tools.ide.tool.pip.PipRepository;
import com.devonfw.tools.ide.tool.repository.ToolRepository;
import com.devonfw.tools.ide.tool.uv.UvRepository;
import com.devonfw.tools.ide.url.model.UrlMetadata;
import com.devonfw.tools.ide.variable.IdeVariables;
import com.devonfw.tools.ide.version.IdeVersion;
Expand Down Expand Up @@ -354,6 +355,11 @@ default void requireOnline(String purpose, boolean explicitOnlineCheck) {
*/
PipRepository getPipRepository();

/**
* @return the {@link UvRepository}.
*/
UvRepository getUvRepository();

/**
* @return the {@link Path} to the IDE instance directory. You can have as many IDE instances on the same computer as independent tenants for different
* isolated projects.
Expand Down
28 changes: 28 additions & 0 deletions cli/src/main/java/com/devonfw/tools/ide/tool/just/Just.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.devonfw.tools.ide.tool.just;

import java.util.Set;

import com.devonfw.tools.ide.common.Tag;
import com.devonfw.tools.ide.context.IdeContext;
import com.devonfw.tools.ide.tool.uv.UvBasedCommandlet;

/**
* {@link UvBasedCommandlet} for <a href="https://github.com/casey/just">just</a>.
*/
public class Just extends UvBasedCommandlet {

/**
* The constructor.
*
* @param context the {@link com.devonfw.tools.ide.context.IdeContext}.
*/
public Just(IdeContext context) {
super(context, "just", Set.of(Tag.BUILD));
}

@Override
public String getPackageName() {
return "rust-just";
}

}
2 changes: 2 additions & 0 deletions cli/src/main/java/com/devonfw/tools/ide/tool/uv/Uv.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,14 @@ public void installPython(Path installationPath, VersionIdentifier resolvedVersi
ProcessResult result = runTool(processContext, ProcessMode.DEFAULT_CAPTURE, List.of("venv", "--python", resolvedVersion.toString()));
assert result.isSuccessful();
}

@Override
public void setEnvironment(EnvironmentContext environmentContext, ToolInstallation toolInstallation, boolean additionalInstallation) {

super.setEnvironment(environmentContext, toolInstallation, additionalInstallation);
Path pythonPath = this.context.getSoftwarePath().resolve("python");
environmentContext.withEnvVar("UV_TOOL_DIR", pythonPath.resolve("tools").toString());
environmentContext.withEnvVar("UV_TOOL_BIN_DIR", pythonPath.resolve("bin").toString());
environmentContext.withPathEntry(pythonPath.resolve("bin"));
}
}
51 changes: 51 additions & 0 deletions cli/src/main/java/com/devonfw/tools/ide/tool/uv/UvArtifact.java

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missing validation

UvArtifact is missing the requireNotEmpty(name, ...) validation that PipArtifact has. Since getPackageName() currently always returns a hardcoded string this is not an immediate problem, but future UvBasedCommandlet implementations returning null or empty would only fail at the HTTP request rather than immediately with a clear exception.

Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.devonfw.tools.ide.tool.uv;

import java.util.Objects;

import com.devonfw.tools.ide.tool.repository.SoftwareArtifact;

/**
* Simple type representing a uv tool artifact resolved from PyPI.
*/
public final class UvArtifact extends SoftwareArtifact {

private final String name;

/**
* The constructor.
*
* @param name the {@link #getName() name}.
* @param version the {@link #getVersion() version}.
*/
public UvArtifact(String name, String version) {
super(version);
this.name = name;
}

/**
* @return the package name in PyPi.
*/
public String getName() {
return this.name;
}

@Override
protected String computeKey() {
return this.name + "@" + this.version;
}

@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
} else if (obj instanceof UvArtifact other) {
return this.name.equals(other.name) && this.version.equals(other.version);
}
return false;
}

@Override
public int hashCode() {
return Objects.hash(this.name, this.version);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package com.devonfw.tools.ide.tool.uv;

import java.util.Collections;
import java.util.Set;

import com.devonfw.tools.ide.os.OperatingSystem;
import com.devonfw.tools.ide.os.SystemArchitecture;
import com.devonfw.tools.ide.url.model.file.UrlChecksums;
import com.devonfw.tools.ide.url.model.file.UrlChecksumsEmpty;
import com.devonfw.tools.ide.url.model.file.UrlDownloadFileMetadata;
import com.devonfw.tools.ide.version.VersionIdentifier;

/**
* {@link UrlDownloadFileMetadata} representing metadata of a uv tool artifact from PyPI.
*/
public class UvArtifactMetadata implements UrlDownloadFileMetadata {

private final UvArtifact uvArtifact;
private final String tool;
private final String edition;
private final VersionIdentifier version;

/**
* The constructor.
*
* @param uvArtifact the {@link UvArtifact}.
* @param tool the tool name.
* @param edition the edition.
*/
public UvArtifactMetadata(UvArtifact uvArtifact, String tool, String edition) {
this.uvArtifact = uvArtifact;
this.version = VersionIdentifier.of(uvArtifact.getVersion());
this.tool = tool;
this.edition = edition;
}

/**
* @return the {@link UvArtifact}
*/
public UvArtifact getUvArtifact() {
return this.uvArtifact;
}

@Override
public String getTool() {
return this.tool;
}

@Override
public String getEdition() {
return this.edition;
}

@Override
public VersionIdentifier getVersion() {
return this.version;
}

@Override
public Set<String> getUrls() {
return Collections.emptySet();
}

@Override
public OperatingSystem getOs() {
return null;
}

@Override
public SystemArchitecture getArch() {
return null;
}

@Override
public UrlChecksums getChecksums() {
return UrlChecksumsEmpty.of();
}

@Override
public String toString() {
return this.uvArtifact.toString();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package com.devonfw.tools.ide.tool.uv;

import java.nio.file.Files;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.devonfw.tools.ide.common.Tag;
import com.devonfw.tools.ide.context.IdeContext;
import com.devonfw.tools.ide.log.IdeLogLevel;
import com.devonfw.tools.ide.process.ProcessContext;
import com.devonfw.tools.ide.process.ProcessErrorHandling;
import com.devonfw.tools.ide.process.ProcessMode;
import com.devonfw.tools.ide.process.ProcessResult;
import com.devonfw.tools.ide.tool.PackageManagerBasedLocalToolCommandlet;
import com.devonfw.tools.ide.tool.PackageManagerRequest;
import com.devonfw.tools.ide.tool.repository.ToolRepository;
import com.devonfw.tools.ide.version.VersionIdentifier;

/**
* {@link PackageManagerBasedLocalToolCommandlet} for tools that are installed via {@code uv tool install} for PyPI. Concrete tools(e.g. {@code Just}) only need
* a constructor and to override {@link #getPackageName()} if the PyPI package name differs from the IDEasy tool name.
*/
public abstract class UvBasedCommandlet extends PackageManagerBasedLocalToolCommandlet<Uv> {

private static final Logger LOG = LoggerFactory.getLogger(UvBasedCommandlet.class);

/**
* The constructor.
*
* @param context the {@link IdeContext}.
* @param tool the {@link #getName() tool name}.
* @param tags the {@link #getTags() tags} classifying the tool. Should be created via {@link Set#of(Object) Set.of} method.
*/
public UvBasedCommandlet(IdeContext context, String tool, Set<Tag> tags) {
super(context, tool, tags);
}

@Override
protected Class<Uv> getPackageManagerClass() {
return Uv.class;
}

@Override
public ToolRepository getToolRepository() {
return this.context.getUvRepository();
}

@Override
protected Uv getParentTool() {
return this.context.getCommandletManager().getCommandlet(Uv.class);
}

@Override
protected String appendVersion(String tool, VersionIdentifier version) {
return tool + "@" + version;
}

@Override
protected void completeRequestArgs(PackageManagerRequest request) {
request.addArg("tool");
super.completeRequestArgs(request);
}

@Override
protected VersionIdentifier computeInstalledVersion() {
if (!Files.isDirectory(this.context.getSoftwarePath().resolve("uv"))) {
LOG.trace("Since uv is not installed, the tool {} cannot be installed either.", this.tool);
return null;
}
String packageName = getPackageName();
PackageManagerRequest request = new PackageManagerRequest("list", packageName).addArg("tool").addArg("list")
.setProcessMode(ProcessMode.DEFAULT_CAPTURE);
ProcessContext pc = this.context.newProcess().errorHandling(ProcessErrorHandling.NONE);
request.setProcessContext(pc);
ProcessResult result = runPackageManager(request);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Version check can trigger an installation

computeInstalledVersion calls the one-arg runPackageManager(request). The base class @implNote explicitly says to use runPackageManager(request, true) with skipInstallation=true for version checks, since the one-arg form calls pm.install(...) internally.

if (result.isSuccessful()) {
String prefix = packageName + "v";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Wrong prefix when parsing uv tool list

UvBasedCommandlet.java:79

String prefix = packageName + "v";

The real uv tool list output is rust-just v1.37.0 (with a space), so this prefix never matches. As a result the installed version is never detected and getInstalledVersion() always returns null. Should be packageName + " v".

for (String line : result.getOut()) {
if (line.startsWith(prefix)) {
return VersionIdentifier.of(line.substring(prefix.length()).trim());
}
}
}
LOG.debug("Could not determine installed version from 'uv tool list' output.");
result.log(IdeLogLevel.DEBUG);
return null;
}
}
Loading
Loading