diff --git a/.mvn/maven.config b/.mvn/maven.config
index 02781b5e5..a40492c0e 100644
--- a/.mvn/maven.config
+++ b/.mvn/maven.config
@@ -1 +1 @@
--Drevision=2025.01.003-beta-SNAPSHOT
+-Drevision=2025.02.001-beta-SNAPSHOT
diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc
index a43466d11..b3bfce973 100644
--- a/CHANGELOG.adoc
+++ b/CHANGELOG.adoc
@@ -2,6 +2,17 @@
This file documents all notable changes to https://github.com/devonfw/IDEasy[IDEasy].
+== 2025.01.003
+
+Release with new features and bugfixes:
+
+* https://github.com/devonfw/IDEasy/issues/993[#993] Creation of start scripts for IDEs
+* https://github.com/devonfw/IDEasy/pull/1003[#1003] graalvm compatibility mode to make x86-64 releases work on arm-64
+* https://github.com/devonfw/IDEasy/issues/954[#954] Improve repository support
+* https://github.com/devonfw/IDEasy/issues/993[#993] Creation of start scripts for IDEs
+
+The full list of changes for this release can be found in https://github.com/devonfw/IDEasy/milestone/20?closed=1[milestone 2025.01.003].
+
== 2025.01.002
Release with important bugfixes:
diff --git a/cli/pom.xml b/cli/pom.xml
index e8f68e93d..68e5bddf4 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -246,6 +246,7 @@
--enable-url-protocols=http,https
--initialize-at-build-time=org.apache.commons
+ -march=compatibility
diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java
index 9a386be81..595fac3c6 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java
@@ -1,21 +1,26 @@
package com.devonfw.tools.ide.commandlet;
+import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
import java.util.Set;
+import java.util.stream.Stream;
import com.devonfw.tools.ide.context.AbstractIdeContext;
import com.devonfw.tools.ide.context.IdeContext;
import com.devonfw.tools.ide.git.GitContext;
import com.devonfw.tools.ide.git.GitUrl;
+import com.devonfw.tools.ide.git.repository.RepositoryCommandlet;
+import com.devonfw.tools.ide.io.FileAccess;
import com.devonfw.tools.ide.property.FlagProperty;
import com.devonfw.tools.ide.property.StringProperty;
-import com.devonfw.tools.ide.repo.CustomToolMetadata;
import com.devonfw.tools.ide.step.Step;
import com.devonfw.tools.ide.tool.CustomToolCommandlet;
import com.devonfw.tools.ide.tool.ToolCommandlet;
+import com.devonfw.tools.ide.tool.repository.CustomToolMetadata;
import com.devonfw.tools.ide.variable.IdeVariables;
/**
@@ -24,10 +29,13 @@
public abstract class AbstractUpdateCommandlet extends Commandlet {
/** {@link StringProperty} for the settings repository URL. */
- protected final StringProperty settingsRepo;
+ public final StringProperty settingsRepo;
- /** {@link FlagProperty} for skipping installation/updating of tools */
- protected final FlagProperty skipTools;
+ /** {@link FlagProperty} for skipping installation/updating of tools. */
+ public final FlagProperty skipTools;
+
+ /** {@link FlagProperty} for skipping the setup of git repositories. */
+ public final FlagProperty skipRepositories;
/**
* The constructor.
@@ -38,7 +46,8 @@ public AbstractUpdateCommandlet(IdeContext context) {
super(context);
addKeyword(getName());
- this.skipTools = add(new FlagProperty("--skip-tools", false, null));
+ this.skipTools = add(new FlagProperty("--skip-tools"));
+ this.skipRepositories = add(new FlagProperty("--skip-repositories"));
this.settingsRepo = new StringProperty("", false, "settingsRepository");
}
@@ -51,11 +60,9 @@ public void run() {
updateConf();
reloadContext();
- if (this.skipTools.isTrue()) {
- this.context.info("Skipping installation/update of tools as specified by the user.");
- } else {
- updateSoftware();
- }
+ updateSoftware();
+ updateRepositories();
+ createStartScripts();
}
private void reloadContext() {
@@ -109,8 +116,8 @@ private void setupConf(Path template, Path conf) {
}
/**
- * Updates the settings repository in IDE_HOME/settings by either cloning if no such repository exists or pulling
- * if the repository exists then saves the latest current commit ID in the file ".commit.id".
+ * Updates the settings repository in IDE_HOME/settings by either cloning if no such repository exists or pulling if the repository exists then saves the
+ * latest current commit ID in the file ".commit.id".
*/
protected void updateSettings() {
@@ -149,9 +156,12 @@ protected void updateSettings() {
private void updateSoftware() {
+ if (this.skipTools.isTrue()) {
+ this.context.info("Skipping installation/update of tools as specified by the user.");
+ return;
+ }
try (Step step = this.context.newStep("Install or update software")) {
Set toolCommandlets = new HashSet<>();
-
// installed tools in IDE_HOME/software
List softwarePaths = this.context.getFileAccess().listChildren(this.context.getSoftwarePath(), Files::isDirectory);
for (Path softwarePath : softwarePaths) {
@@ -183,10 +193,86 @@ private void updateSoftware() {
} catch (Exception e) {
step.error(e, "Installation of {} failed!", toolCommandlet.getName());
}
-
}
step.success();
}
}
+ private void updateRepositories() {
+
+ if (this.skipRepositories.isTrue()) {
+ this.context.info("Skipping setup of repositories as specified by the user.");
+ return;
+ }
+ RepositoryCommandlet repositoryCommandlet = this.context.getCommandletManager().getCommandlet(RepositoryCommandlet.class);
+ repositoryCommandlet.reset();
+ repositoryCommandlet.run();
+ }
+
+ private void createStartScripts() {
+
+ List ides = IdeVariables.CREATE_START_SCRIPTS.get(this.context);
+ if (ides == null) {
+ this.context.info("Variable CREATE_START_SCRIPTS is undefined - skipping start script creation.");
+ return;
+ }
+ for (String ide : ides) {
+ ToolCommandlet tool = this.context.getCommandletManager().getToolCommandlet(ide);
+ if (tool == null) {
+ this.context.error("Undefined IDE '{}' configured in variable CREATE_START_SCRIPTS.");
+ } else {
+ createStartScript(ide);
+ }
+ }
+ }
+
+ private void createStartScript(String ide) {
+
+ this.context.info("Creating start scripts for {}", ide);
+ Path workspaces = this.context.getIdeHome().resolve(IdeContext.FOLDER_WORKSPACES);
+ try (Stream childStream = Files.list(workspaces)) {
+ Iterator iterator = childStream.iterator();
+ while (iterator.hasNext()) {
+ Path child = iterator.next();
+ if (Files.isDirectory(child)) {
+ createStartScript(ide, child.getFileName().toString());
+ }
+ }
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to list children of directory " + workspaces, e);
+ }
+ }
+
+ private void createStartScript(String ide, String workspace) {
+
+ Path ideHome = this.context.getIdeHome();
+ String scriptName = ide + "-" + workspace;
+ boolean windows = this.context.getSystemInfo().isWindows();
+ if (windows) {
+ scriptName = scriptName + ".bat";
+ } else {
+ scriptName = scriptName + ".sh";
+ }
+ Path scriptPath = ideHome.resolve(scriptName);
+ if (Files.exists(scriptPath)) {
+ return;
+ }
+ String scriptContent;
+ if (windows) {
+ scriptContent = "@echo off\r\n"
+ + "pushd %~dp0\r\n"
+ + "cd workspaces/" + workspace + "\r\n"
+ + "call ide " + ide + "\r\n"
+ + "popd\r\n";
+ } else {
+ scriptContent = "#!/usr/bin/env bash\n"
+ + "cd \"$(dirname \"$0\")\"\n"
+ + "cd workspaces/" + workspace + "\n"
+ + "ide " + ide + "\n";
+ }
+ FileAccess fileAccess = this.context.getFileAccess();
+ fileAccess.writeFileContent(scriptContent, scriptPath);
+ fileAccess.makeExecutable(scriptPath);
+ }
+
}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java
index 8103b3ae3..d4b867b60 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java
@@ -12,6 +12,7 @@
import com.devonfw.tools.ide.cli.CliArguments;
import com.devonfw.tools.ide.completion.CompletionCandidateCollector;
import com.devonfw.tools.ide.context.IdeContext;
+import com.devonfw.tools.ide.git.repository.RepositoryCommandlet;
import com.devonfw.tools.ide.property.KeywordProperty;
import com.devonfw.tools.ide.property.Property;
import com.devonfw.tools.ide.tool.androidstudio.AndroidStudio;
diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CreateCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CreateCommandlet.java
index f065d614f..74b8014a3 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CreateCommandlet.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CreateCommandlet.java
@@ -17,9 +17,6 @@ public class CreateCommandlet extends AbstractUpdateCommandlet {
/** {@link StringProperty} for the name of the new project */
public final StringProperty newProject;
- /** {@link FlagProperty} for skipping the setup of git repositories */
- public final FlagProperty skipRepositories;
-
/** {@link FlagProperty} for creating a project with settings inside a code repository */
public final FlagProperty codeRepositoryFlag;
@@ -32,7 +29,6 @@ public CreateCommandlet(IdeContext context) {
super(context);
this.newProject = add(new StringProperty("", true, "project"));
- this.skipRepositories = add(new FlagProperty("--skip-repositories"));
this.codeRepositoryFlag = add(new FlagProperty("--code"));
add(this.settingsRepo);
}
@@ -65,14 +61,7 @@ public void run() {
initializeProject(newProjectPath);
this.context.setIdeHome(newProjectPath);
super.run();
-
- if (this.skipRepositories.isTrue()) {
- this.context.info("Skipping the cloning of project repositories as specified by the user.");
- } else {
- updateRepositories();
- }
this.context.success("Successfully created new project '{}'.", newProjectName);
-
}
private void initializeCodeRepository(String repoUrl) {
@@ -101,23 +90,18 @@ private void initializeProject(Path newInstancePath) {
fileAccess.mkdirs(newInstancePath.resolve(IdeContext.FOLDER_WORKSPACES).resolve(IdeContext.WORKSPACE_MAIN));
}
- private void updateRepositories() {
-
- this.context.getCommandletManager().getCommandlet(RepositoryCommandlet.class).run();
- }
-
@Override
protected void updateSettings() {
- if (codeRepositoryFlag.isTrue()) {
+ if (this.codeRepositoryFlag.isTrue()) {
String codeRepository = this.settingsRepo.getValue();
if (codeRepository == null || codeRepository.isBlank()) {
String message = """
- No code repository was given after '--code'.
- Please give the code repository below that includes your settings folder.
- Further details can be found here: https://github.com/devonfw/IDEasy/blob/main/documentation/settings.adoc
- Code repository URL:
- """;
+ No code repository was given after '--code'.
+ Please give the code repository below that includes your settings folder.
+ Further details can be found here: https://github.com/devonfw/IDEasy/blob/main/documentation/settings.adoc
+ Code repository URL:
+ """;
codeRepository = this.context.askForInput(message);
}
initializeCodeRepository(codeRepository);
diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java
deleted file mode 100644
index 8a46375f9..000000000
--- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java
+++ /dev/null
@@ -1,114 +0,0 @@
-package com.devonfw.tools.ide.commandlet;
-
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.List;
-
-import com.devonfw.tools.ide.context.IdeContext;
-import com.devonfw.tools.ide.git.GitContext;
-import com.devonfw.tools.ide.property.RepositoryProperty;
-import com.devonfw.tools.ide.tool.ToolCommandlet;
-
-/**
- * {@link Commandlet} to setup one or multiple GIT repositories for development.
- */
-public class RepositoryCommandlet extends Commandlet {
-
- /** the repository to setup. */
- public final RepositoryProperty repository;
-
- /**
- * The constructor.
- *
- * @param context the {@link IdeContext}.
- */
- public RepositoryCommandlet(IdeContext context) {
-
- super(context);
- addKeyword(getName());
- addKeyword("setup");
- this.repository = add(new RepositoryProperty("", false, "repository"));
- }
-
- @Override
- public String getName() {
-
- return "repository";
- }
-
- @Override
- public void run() {
-
- Path repositoryFile = this.repository.getValue();
-
- if (repositoryFile != null) {
- // Handle the case when a specific repository is provided
- doImportRepository(repositoryFile, true);
- } else {
- // If no specific repository is provided, check for repositories folder
- Path repositoriesPath = this.context.getRepositoriesPath();
- if (repositoriesPath == null) {
- this.context.warning("Cannot find folder 'repositories' nor 'projects' in your settings.");
- return;
- }
- List propertiesFiles = this.context.getFileAccess()
- .listChildren(repositoriesPath, path -> path.getFileName().toString().endsWith(".properties"));
- boolean forceMode = this.context.isForceMode();
- for (Path propertiesFile : propertiesFiles) {
- doImportRepository(propertiesFile, forceMode);
- }
- }
- }
-
- private void doImportRepository(Path repositoryFile, boolean forceMode) {
-
- this.context.info("Importing repository from {} ...", repositoryFile.getFileName().toString());
- RepositoryConfig repositoryConfig = RepositoryConfig.loadProperties(repositoryFile);
-
- if (!repositoryConfig.active()) {
- this.context.info("Repository is not active by default.");
- if (forceMode) {
- this.context.info("Repository setup is forced, hence proceeding ...");
- } else {
- this.context.info("Skipping repository - use force (-f) to setup all repositories ...");
- return;
- }
- }
-
- String repository = repositoryConfig.path();
- String gitUrl = repositoryConfig.gitUrl();
- if (repository == null || repository.isEmpty() || gitUrl == null || gitUrl.isEmpty()) {
- this.context.warning("Invalid repository configuration {} - both 'path' and 'git-url' have to be defined.", repositoryFile.getFileName().toString());
- return;
- }
-
- this.context.debug(repositoryConfig.toString());
-
- String workspace = repositoryConfig.workspace();
- if (workspace == null) {
- workspace = IdeContext.WORKSPACE_MAIN;
- }
- Path workspacePath = this.context.getIdeHome().resolve(IdeContext.FOLDER_WORKSPACES).resolve(workspace);
- this.context.getFileAccess().mkdirs(workspacePath);
-
- Path repositoryPath = workspacePath.resolve(repository);
- if (!Files.isDirectory(repositoryPath.resolve(GitContext.GIT_FOLDER))) {
- this.context.getGitContext().pullOrClone(repositoryConfig.asGitUrl(), repositoryPath);
- }
-
- String buildCmd = repositoryConfig.buildCmd();
- this.context.debug("Building repository with ide command: {}", buildCmd);
- if (buildCmd != null && !buildCmd.isEmpty()) {
- String[] command = buildCmd.split("\\s+");
- ToolCommandlet commandlet = this.context.getCommandletManager().getRequiredToolCommandlet(command[0]);
-
- for (int i = 1; i < command.length; i++) {
- commandlet.arguments.addValue(command[i]);
- }
- commandlet.run();
- } else {
- this.context.info("Build command not set. Skipping build for repository.");
- }
-
- }
-}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryConfig.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryConfig.java
deleted file mode 100644
index 02b4183a6..000000000
--- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryConfig.java
+++ /dev/null
@@ -1,80 +0,0 @@
-package com.devonfw.tools.ide.commandlet;
-
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.Path;
-import java.util.Collections;
-import java.util.Properties;
-import java.util.Set;
-
-import com.devonfw.tools.ide.git.GitUrl;
-
-/**
- * Represents the configuration of a repository to be used by the {@link RepositoryCommandlet}.
- *
- * @param path Path into which the project is cloned. This path is relative to the workspace.
- * @param workingSets The working sets associated with the repository.
- * @param workspace Workspace to use for checkout and import. Default is main.
- * @param gitUrl Git URL to use for cloning the project.
- * @param gitBranch Git branch to checkout. Git default branch is default.
- * @param buildPath The build path for the repository.
- * @param buildCmd The command to invoke to build the repository after clone or pull. If omitted no build is triggered.
- * @param imports list of IDEs where the repository will be imported to.
- * @param active {@code true} to setup the repository during setup, {@code false} to skip.
- */
-public record RepositoryConfig(
- String path,
- String workingSets,
- String workspace,
- String gitUrl,
- String gitBranch,
- String buildPath,
- String buildCmd,
- Set imports,
- boolean active) {
-
- /**
- * @return the {@link GitUrl} from {@link #gitUrl()} and {@link #gitBranch()}.
- */
- public GitUrl asGitUrl() {
-
- return new GitUrl(this.gitUrl, this.gitBranch);
- }
-
- /**
- * @param filePath the {@link Path} to the {@link Properties} to load.
- * @return the parsed {@link RepositoryConfig}.
- */
- public static RepositoryConfig loadProperties(Path filePath) {
-
- Properties properties = new Properties();
- try (InputStream input = new FileInputStream(filePath.toString())) {
- properties.load(input);
- } catch (IOException e) {
- throw new IllegalStateException("Failed to read file: " + filePath, e);
- }
-
- Set importsSet = getImports(properties);
-
- return new RepositoryConfig(properties.getProperty("path"), properties.getProperty("workingsets"),
- properties.getProperty("workspace"), properties.getProperty("git_url"), properties.getProperty("git_branch"),
- properties.getProperty(("build_path")), properties.getProperty("build_cmd"), importsSet,
- Boolean.parseBoolean(properties.getProperty("active").trim()));
- }
-
- private static Set getImports(Properties properties) {
-
- String importProperty = properties.getProperty("import");
- if (importProperty != null && !importProperty.isEmpty()) {
- return Set.of(importProperty.split("\\s*,\\s*"));
- }
-
- String legacyImportProperty = properties.getProperty("eclipse");
- if ("import".equals(legacyImportProperty)) {
- return Set.of("eclipse");
- } else {
- return Collections.emptySet();
- }
- }
-}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/UpdateCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/UpdateCommandlet.java
index 4fc3a60b7..52dd504d3 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/UpdateCommandlet.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/UpdateCommandlet.java
@@ -22,10 +22,4 @@ public String getName() {
return "update";
}
-
- @Override
- public void run() {
-
- super.run();
- }
}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/UpgradeSettingsCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/UpgradeSettingsCommandlet.java
index 4eed38112..0069b6a4b 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/UpgradeSettingsCommandlet.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/UpgradeSettingsCommandlet.java
@@ -11,9 +11,9 @@
import com.devonfw.tools.ide.environment.EnvironmentVariablesPropertiesFile;
import com.devonfw.tools.ide.environment.EnvironmentVariablesType;
import com.devonfw.tools.ide.merge.DirectoryMerger;
-import com.devonfw.tools.ide.repo.CustomToolsJson;
-import com.devonfw.tools.ide.repo.CustomToolsJsonMapper;
import com.devonfw.tools.ide.tool.mvn.Mvn;
+import com.devonfw.tools.ide.tool.repository.CustomToolsJson;
+import com.devonfw.tools.ide.tool.repository.CustomToolsJsonMapper;
import com.devonfw.tools.ide.variable.IdeVariables;
import com.devonfw.tools.ide.variable.VariableDefinition;
diff --git a/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java b/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java
index 2672da316..7ad9c68d7 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java
@@ -50,12 +50,12 @@
import com.devonfw.tools.ide.process.ProcessContextImpl;
import com.devonfw.tools.ide.process.ProcessResult;
import com.devonfw.tools.ide.property.Property;
-import com.devonfw.tools.ide.repo.CustomToolRepository;
-import com.devonfw.tools.ide.repo.CustomToolRepositoryImpl;
-import com.devonfw.tools.ide.repo.DefaultToolRepository;
-import com.devonfw.tools.ide.repo.ToolRepository;
import com.devonfw.tools.ide.step.Step;
import com.devonfw.tools.ide.step.StepImpl;
+import com.devonfw.tools.ide.tool.repository.CustomToolRepository;
+import com.devonfw.tools.ide.tool.repository.CustomToolRepositoryImpl;
+import com.devonfw.tools.ide.tool.repository.DefaultToolRepository;
+import com.devonfw.tools.ide.tool.repository.ToolRepository;
import com.devonfw.tools.ide.url.model.UrlMetadata;
import com.devonfw.tools.ide.util.DateTimeUtil;
import com.devonfw.tools.ide.validation.ValidationResult;
@@ -78,6 +78,8 @@ public abstract class AbstractIdeContext implements IdeContext {
private final Path ideRoot;
+ private final Path ideInstallationPath;
+
private Path confPath;
protected Path settingsPath;
@@ -190,18 +192,19 @@ public AbstractIdeContext(IdeStartContextImpl startContext, Path workingDirector
setCwd(workingDirectory, workspace, currentDir);
if (this.ideRoot == null) {
+ this.ideInstallationPath = null;
this.toolRepositoryPath = null;
this.urlsPath = null;
this.tempPath = null;
this.tempDownloadPath = null;
this.softwareRepositoryPath = null;
} else {
- Path ideBase = this.ideRoot.resolve(FOLDER_IDE);
- this.toolRepositoryPath = ideBase.resolve("software");
- this.urlsPath = ideBase.resolve("urls");
- this.tempPath = ideBase.resolve("tmp");
+ this.ideInstallationPath = this.ideRoot.resolve(FOLDER_IDE_INSTALLATION);
+ this.toolRepositoryPath = this.ideInstallationPath.resolve("software");
+ this.urlsPath = this.ideInstallationPath.resolve("urls");
+ this.tempPath = this.ideInstallationPath.resolve("tmp");
this.tempDownloadPath = this.tempPath.resolve(FOLDER_DOWNLOADS);
- this.softwareRepositoryPath = ideBase.resolve(FOLDER_SOFTWARE);
+ this.softwareRepositoryPath = this.ideInstallationPath.resolve(FOLDER_SOFTWARE);
if (Files.isDirectory(this.tempPath)) {
// TODO delete all files older than 1 day here...
} else {
@@ -268,7 +271,7 @@ public void setCwd(Path userDir, String workspace, Path ideHome) {
} else {
this.userHome = Path.of(getSystem().getProperty("user.home"));
}
- this.userHomeIde = this.userHome.resolve(".ide");
+ this.userHomeIde = this.userHome.resolve(FOLDER_DOT_IDE);
this.downloadPath = this.userHome.resolve("Downloads/ide");
this.path = computeSystemPath();
@@ -388,6 +391,12 @@ public Path getIdeRoot() {
return this.ideRoot;
}
+ @Override
+ public Path getIdeInstallationPath() {
+
+ return this.ideInstallationPath;
+ }
+
@Override
public Path getCwd() {
@@ -957,7 +966,7 @@ This product (with its included 3rd party components) is open-source software an
""").append(LICENSE_URL);
if (this.ideRoot != null) {
sb.append("\n\nAlso it is included in the documentation that you can find here:\n").
- append(this.ideRoot.resolve(FOLDER_IDE).resolve("IDEasy.pdf").toString()).append("\n");
+ append(this.ideInstallationPath.resolve("IDEasy.pdf").toString()).append("\n");
}
info(sb.toString());
askToContinue("Do you accept these terms of use and all license agreements?");
diff --git a/cli/src/main/java/com/devonfw/tools/ide/context/IdeContext.java b/cli/src/main/java/com/devonfw/tools/ide/context/IdeContext.java
index bc2e00a4b..cf23d5b1c 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/context/IdeContext.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/context/IdeContext.java
@@ -18,10 +18,10 @@
import com.devonfw.tools.ide.os.SystemInfo;
import com.devonfw.tools.ide.os.WindowsPathSyntax;
import com.devonfw.tools.ide.process.ProcessContext;
-import com.devonfw.tools.ide.repo.CustomToolRepository;
-import com.devonfw.tools.ide.repo.ToolRepository;
import com.devonfw.tools.ide.step.Step;
import com.devonfw.tools.ide.tool.mvn.Mvn;
+import com.devonfw.tools.ide.tool.repository.CustomToolRepository;
+import com.devonfw.tools.ide.tool.repository.ToolRepository;
import com.devonfw.tools.ide.url.model.UrlMetadata;
import com.devonfw.tools.ide.variable.IdeVariables;
@@ -52,8 +52,17 @@ public interface IdeContext extends IdeStartContext {
/**
* The base folder name of the IDE inside IDE_ROOT. Intentionally starting with an underscore and not a dot (to prevent effects like OS hiding, maven
* filtering, .gitignore, etc.).
+ *
+ * @see #getIdeInstallationPath()
+ */
+ String FOLDER_IDE_INSTALLATION = "_ide";
+
+ /**
+ * The name of the hidden folder for IDE configuration in the users home directory or status information in the IDE_HOME directory.
+ *
+ * @see #getUserHomeIde()
*/
- String FOLDER_IDE = "_ide";
+ String FOLDER_DOT_IDE = ".ide";
/** The name of the updates folder for temporary data and backup. */
String FOLDER_UPDATES = "updates";
@@ -118,6 +127,11 @@ public interface IdeContext extends IdeStartContext {
*/
String FOLDER_UPDATE = "update";
+ /**
+ * The name of the folder inside {@link #FOLDER_IDE_INSTALLATION _ide} folder containing internal resources and scripts of IDEasy.
+ */
+ String FOLDER_INTERNAL = "internal";
+
/** The file where the installed software version is written to as plain text. */
String FILE_SOFTWARE_VERSION = ".ide.software.version";
@@ -294,6 +308,13 @@ default void requireOnline(String purpose) {
*/
Path getIdeRoot();
+ /**
+ * @return the {@link Path} to the {@link #FOLDER_IDE_INSTALLATION IDE installation}.
+ * @see #getIdeRoot()
+ * @see #FOLDER_IDE_INSTALLATION
+ */
+ Path getIdeInstallationPath();
+
/**
* @return the current working directory ("user.dir"). This is the directory where the user's shell was located when the IDE CLI was invoked.
*/
diff --git a/cli/src/main/java/com/devonfw/tools/ide/git/GitUrl.java b/cli/src/main/java/com/devonfw/tools/ide/git/GitUrl.java
index c77d7f281..4c4f9b5a5 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/git/GitUrl.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/git/GitUrl.java
@@ -44,18 +44,23 @@ public String toString() {
}
/**
- * Extracts the project name from an git URL.
- * For URLs like "https://github.com/devonfw/ide-urls.git" returns "ide-urls"
+ * Extracts the project name from an git URL. For URLs like "https://github.com/devonfw/ide-urls.git" returns "ide-urls"
*
* @return the project name without ".git" extension
*/
public String getProjectName() {
- String path = this.url.substring(this.url.indexOf("://") + 3);
+
+ int lastSlash = this.url.lastIndexOf('/');
+ String path;
+ if (lastSlash >= 0) {
+ path = this.url.substring(lastSlash + 1);
+ } else {
+ path = this.url; // actually invalid URL
+ }
if (path.endsWith(".git")) {
path = path.substring(0, path.length() - 4);
}
- String[] parts = path.split("/");
- return parts[parts.length - 1];
+ return path;
}
/**
diff --git a/cli/src/main/java/com/devonfw/tools/ide/git/repository/RepositoryCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/git/repository/RepositoryCommandlet.java
new file mode 100644
index 000000000..235471bf2
--- /dev/null
+++ b/cli/src/main/java/com/devonfw/tools/ide/git/repository/RepositoryCommandlet.java
@@ -0,0 +1,189 @@
+package com.devonfw.tools.ide.git.repository;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Set;
+
+import com.devonfw.tools.ide.commandlet.Commandlet;
+import com.devonfw.tools.ide.context.IdeContext;
+import com.devonfw.tools.ide.git.GitContext;
+import com.devonfw.tools.ide.git.GitUrl;
+import com.devonfw.tools.ide.io.FileAccess;
+import com.devonfw.tools.ide.property.RepositoryProperty;
+import com.devonfw.tools.ide.step.Step;
+import com.devonfw.tools.ide.tool.ToolCommandlet;
+import com.devonfw.tools.ide.tool.ide.IdeToolCommandlet;
+
+/**
+ * {@link Commandlet} to setup one or multiple GIT repositories for development.
+ */
+public class RepositoryCommandlet extends Commandlet {
+
+ /** the repository to setup. */
+ public final RepositoryProperty repository;
+
+ /**
+ * The constructor.
+ *
+ * @param context the {@link IdeContext}.
+ */
+ public RepositoryCommandlet(IdeContext context) {
+
+ super(context);
+ addKeyword(getName());
+ addKeyword("setup");
+ this.repository = add(new RepositoryProperty("", false, "repository"));
+ }
+
+ @Override
+ public String getName() {
+
+ return "repository";
+ }
+
+ @Override
+ public void run() {
+
+ Path repositoryFile = this.repository.getValue();
+
+ if (repositoryFile != null) {
+ // Handle the case when a specific repository is provided
+ doImportRepository(repositoryFile, true);
+ } else {
+ // If no specific repository is provided, check for repositories folder
+ Path repositoriesPath = this.context.getRepositoriesPath();
+ if (repositoriesPath == null) {
+ this.context.warning("Cannot find folder 'repositories' nor 'projects' in your settings.");
+ return;
+ }
+ List propertiesFiles = this.context.getFileAccess()
+ .listChildren(repositoriesPath, path -> path.getFileName().toString().endsWith(".properties"));
+ boolean forceMode = this.context.isForceMode();
+ for (Path propertiesFile : propertiesFiles) {
+ doImportRepository(propertiesFile, forceMode);
+ }
+ }
+ }
+
+ private void doImportRepository(Path repositoryFile, boolean forceMode) {
+
+ String repositoryFilename = repositoryFile.getFileName().toString();
+ final String repositoryId;
+ if (repositoryFilename.endsWith(IdeContext.EXT_PROPERTIES)) {
+ repositoryId = repositoryFilename.substring(0, repositoryFilename.length() - IdeContext.EXT_PROPERTIES.length());
+ } else {
+ repositoryId = repositoryFilename;
+ }
+ this.context.newStep("Setup of repository " + repositoryId, repositoryFile).run(() -> {
+ doImportRepository(repositoryFile, forceMode, repositoryId);
+ });
+ }
+
+ private void doImportRepository(Path repositoryFile, boolean forceMode, String repositoryId) {
+ RepositoryConfig repositoryConfig = RepositoryConfig.loadProperties(repositoryFile, this.context);
+ if (!repositoryConfig.active()) {
+ if (forceMode) {
+ this.context.info("Setup of repository {} is forced, hence proceeding ...", repositoryId);
+ } else {
+ this.context.info("Skipping repository {} because it is not active - use --force to setup all repositories ...", repositoryId);
+ return;
+ }
+ }
+ GitUrl gitUrl = repositoryConfig.asGitUrl();
+ if (gitUrl == null) {
+ // error was already logged.
+ return;
+ }
+ this.context.debug("Repository configuration: {}", repositoryConfig);
+ Path repositoryPath = getRepositoryPath(repositoryConfig, repositoryId);
+ if (Files.isDirectory(repositoryPath.resolve(GitContext.GIT_FOLDER))) {
+ this.context.info("Repository {} already exists at {}", repositoryId, repositoryPath);
+ if (!this.context.isForceMode()) {
+ this.context.info("Ignoring repository {} - use --force to rerun setup.", repositoryId);
+ return;
+ }
+ }
+ Path ideStatusDir = this.context.getIdeHome().resolve(IdeContext.FOLDER_DOT_IDE);
+ this.context.getFileAccess().mkdirs(ideStatusDir);
+ Path repositoryCreatedStatusFile = ideStatusDir.resolve("repository." + repositoryId);
+ if (Files.exists(repositoryCreatedStatusFile)) {
+ if (!this.context.isForceMode()) {
+ this.context.info("Ignoring repository {} because it was already setup before - use --force for recreation.", repository);
+ return;
+ }
+ }
+ boolean success = cloneOrPullRepository(repositoryPath, gitUrl, repositoryCreatedStatusFile);
+ if (success) {
+ buildRepository(repositoryConfig, repositoryPath);
+ importRepository(repositoryConfig, repositoryPath, repositoryId);
+ }
+ }
+
+ private boolean cloneOrPullRepository(Path repositoryPath, GitUrl gitUrl, Path repositoryCreatedStatusFile) {
+
+ FileAccess fileAccess = this.context.getFileAccess();
+ return this.context.newStep("Clone or pull repository").run(() -> {
+ fileAccess.mkdirs(repositoryPath);
+ this.context.getGitContext().pullOrClone(gitUrl, repositoryPath);
+ fileAccess.touch(repositoryCreatedStatusFile);
+ });
+ }
+
+ private Path getRepositoryPath(RepositoryConfig repositoryConfig, String repositoryId) {
+ String workspace = repositoryConfig.workspace();
+ if (workspace == null) {
+ workspace = IdeContext.WORKSPACE_MAIN;
+ }
+ Path workspacePath = this.context.getIdeHome().resolve(IdeContext.FOLDER_WORKSPACES).resolve(workspace);
+ String repositoryRelativePath = repositoryConfig.path();
+ if (repositoryRelativePath == null) {
+ repositoryRelativePath = repositoryId;
+ }
+ return workspacePath.resolve(repositoryRelativePath);
+ }
+
+ private boolean buildRepository(RepositoryConfig repositoryConfig, Path repositoryPath) {
+ String buildCmd = repositoryConfig.buildCmd();
+ if (buildCmd != null && !buildCmd.isEmpty()) {
+ return this.context.newStep("Build repository via: " + buildCmd).run(() -> {
+ String[] command = buildCmd.split("\\s+");
+ ToolCommandlet commandlet = this.context.getCommandletManager().getRequiredToolCommandlet(command[0]);
+ commandlet.reset();
+ for (int i = 1; i < command.length; i++) {
+ commandlet.arguments.addValue(command[i]);
+ }
+ Path executionDirectory = repositoryPath;
+ String path = repositoryConfig.buildPath();
+ if (path != null) {
+ executionDirectory = executionDirectory.resolve(path);
+ }
+ commandlet.setExecutionDirectory(executionDirectory);
+ commandlet.run();
+ });
+ } else {
+ this.context.debug("Build command not set. Skipping build for repository.");
+ return true;
+ }
+ }
+
+ private void importRepository(RepositoryConfig repositoryConfig, Path repositoryPath, String repositoryId) {
+
+ Set imports = repositoryConfig.imports();
+ if ((imports == null) || imports.isEmpty()) {
+ this.context.debug("Repository {} has no IDE configured for import.", repositoryId);
+ return;
+ }
+ for (String ide : imports) {
+ Step step = this.context.newStep("Importing repository " + repositoryId + " into " + ide);
+ step.run(() -> {
+ ToolCommandlet commandlet = this.context.getCommandletManager().getRequiredToolCommandlet(ide);
+ if (commandlet instanceof IdeToolCommandlet ideCommandlet) {
+ ideCommandlet.importRepository(repositoryPath);
+ } else {
+ step.error("Repository {} has import {} configured that is not an IDE!", repositoryId, ide);
+ }
+ });
+ }
+ }
+}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/git/repository/RepositoryConfig.java b/cli/src/main/java/com/devonfw/tools/ide/git/repository/RepositoryConfig.java
new file mode 100644
index 000000000..23ad5e591
--- /dev/null
+++ b/cli/src/main/java/com/devonfw/tools/ide/git/repository/RepositoryConfig.java
@@ -0,0 +1,96 @@
+package com.devonfw.tools.ide.git.repository;
+
+import java.nio.file.Path;
+import java.util.Properties;
+import java.util.Set;
+
+import com.devonfw.tools.ide.context.IdeContext;
+import com.devonfw.tools.ide.git.GitUrl;
+
+/**
+ * Represents the configuration of a repository to be used by the {@link RepositoryCommandlet}.
+ *
+ * @param path Path into which the project is cloned. This path is relative to the workspace.
+ * @param workingSets The working sets associated with the repository.
+ * @param workspace Workspace to use for checkout and import. Default is main.
+ * @param gitUrl Git URL to use for cloning the project.
+ * @param gitBranch Git branch to checkout. Git default branch is default.
+ * @param buildPath The build path for the repository.
+ * @param buildCmd The command to invoke to build the repository after clone or pull. If omitted no build is triggered.
+ * @param imports list of IDEs where the repository will be imported to.
+ * @param active {@code true} to setup the repository during setup, {@code false} to skip.
+ */
+public record RepositoryConfig(
+ String path,
+ String workingSets,
+ String workspace,
+ String gitUrl,
+ String gitBranch,
+ String buildPath,
+ String buildCmd,
+ Set imports,
+ boolean active) {
+
+ /** {@link RepositoryProperties#getProperty(String) Property name} for {@link #path()}. */
+ public static final String PROPERTY_PATH = "path";
+
+ /** {@link RepositoryProperties#getProperty(String) Property name} for {@link #workingSets()}. */
+ public static final String PROPERTY_WORKING_SETS = "workingsets";
+
+ /** {@link RepositoryProperties#getProperty(String) Property name} for {@link #workspace()}. */
+ public static final String PROPERTY_WORKSPACE = "workspace";
+
+ /** {@link RepositoryProperties#getProperty(String) Property name} for {@link #gitUrl()}. */
+ public static final String PROPERTY_GIT_URL = "git_url";
+
+ /** {@link RepositoryProperties#getProperty(String) Property name} for {@link #buildPath()}. */
+ public static final String PROPERTY_BUILD_PATH = "build_path";
+
+ /** {@link RepositoryProperties#getProperty(String) Property name} for {@link #buildCmd()}. */
+ public static final String PROPERTY_BUILD_CMD = "build_cmd";
+
+ /** {@link RepositoryProperties#getProperty(String) Property name} for {@link #active()}. */
+ public static final String PROPERTY_ACTIVE = "active";
+
+ /** {@link RepositoryProperties#getProperty(String) Property name} for {@link #gitBranch()}. */
+ public static final String PROPERTY_GIT_BRANCH = "git_branch";
+
+ /** {@link RepositoryProperties#getProperty(String) Property name} for {@link #imports()}. */
+ public static final String PROPERTY_IMPORT = "import";
+
+ /** Legacy {@link RepositoryProperties#getProperty(String) property name} for {@link #imports()}. */
+ public static final String PROPERTY_ECLIPSE = "eclipse";
+
+ /**
+ * @return the {@link GitUrl} from {@link #gitUrl()} and {@link #gitBranch()}.
+ */
+ public GitUrl asGitUrl() {
+
+ if (this.gitUrl == null) {
+ return null;
+ }
+ return new GitUrl(this.gitUrl, this.gitBranch);
+ }
+
+ /**
+ * @param filePath the {@link Path} to the {@link Properties} to load.
+ * @param context the {@link IdeContext}.
+ * @return the parsed {@link RepositoryConfig}.
+ */
+ public static RepositoryConfig loadProperties(Path filePath, IdeContext context) {
+
+ RepositoryProperties properties = new RepositoryProperties(filePath, context);
+
+ Set importsSet = properties.getImports();
+
+ return new RepositoryConfig(properties.getProperty(PROPERTY_PATH), properties.getProperty(PROPERTY_WORKING_SETS),
+ properties.getProperty(PROPERTY_WORKSPACE), properties.getProperty(PROPERTY_GIT_URL, true), properties.getProperty(PROPERTY_GIT_BRANCH),
+ properties.getProperty(PROPERTY_BUILD_PATH), properties.getProperty(PROPERTY_BUILD_CMD), importsSet,
+ parseBoolean(properties.getProperty(PROPERTY_ACTIVE).trim()));
+ }
+
+ private static boolean parseBoolean(String value) {
+
+ return "true".equals(value);
+ }
+}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/git/repository/RepositoryProperties.java b/cli/src/main/java/com/devonfw/tools/ide/git/repository/RepositoryProperties.java
new file mode 100644
index 000000000..bbd89c9e2
--- /dev/null
+++ b/cli/src/main/java/com/devonfw/tools/ide/git/repository/RepositoryProperties.java
@@ -0,0 +1,117 @@
+package com.devonfw.tools.ide.git.repository;
+
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Properties;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import com.devonfw.tools.ide.cli.CliException;
+import com.devonfw.tools.ide.context.IdeContext;
+
+/**
+ * {@link Properties} for {@link RepositoryConfig}.
+ */
+final class RepositoryProperties {
+
+ private final Path file;
+
+ private final Properties properties;
+
+ private final IdeContext context;
+
+ /**
+ * The constructor.
+ *
+ * @param file the {@link Path} to the properties file.
+ * @param context the {@link IdeContext}.
+ */
+ public RepositoryProperties(Path file, IdeContext context) {
+ this(file, context, context.getFileAccess().readProperties(file));
+ }
+
+ /**
+ * @param file the {@link Path} to the properties file.
+ * @param context the {@link IdeContext}.
+ * @param properties the actual {@link Properties} loaded from the file.
+ */
+ RepositoryProperties(Path file, IdeContext context, Properties properties) {
+ super();
+ this.file = file;
+ this.properties = properties;
+ this.context = context;
+ }
+
+ /**
+ * @param name the name of the requested property.
+ * @return the value of the requested property or {@code null} if undefined.
+ */
+ public String getProperty(String name) {
+ return getProperty(name, false);
+ }
+
+ /**
+ * @param name the name of the requested property.
+ * @param required - {@code true} if the requested property is required, {@code false} otherwise.
+ * @return the value of the requested property or {@code null} if undefined.
+ */
+ public String getProperty(String name, boolean required) {
+
+ String value = this.properties.getProperty(name);
+ if (value == null) {
+ String legacyName = name.replace("_", ".");
+ if (!legacyName.equals(name)) {
+ value = getLegacyProperty(legacyName, name);
+ if (value == null) {
+ legacyName = name.replace("_", "-");
+ value = getLegacyProperty(legacyName, name);
+ }
+ }
+ }
+ if (isEmpty(value)) {
+ if (required) {
+ throw new CliException("The properties file " + this.file + " must have a non-empty value for the required property " + name);
+ }
+ return null;
+ }
+ return value;
+ }
+
+ private static boolean isEmpty(String value) {
+
+ return (value == null) || value.isBlank();
+ }
+
+ private String getLegacyProperty(String legacyName, String name) {
+
+ String value = this.properties.getProperty(legacyName);
+ if (value != null) {
+ this.context.warning("The properties file {} uses the legacy property {} instead of {}", this.file, legacyName, name);
+ }
+ return value;
+ }
+
+ /**
+ * @return the IDEs where to import the repository.
+ */
+ public Set getImports() {
+
+ String importProperty = this.properties.getProperty(RepositoryConfig.PROPERTY_IMPORT);
+ if (importProperty != null) {
+ if (importProperty.isEmpty()) {
+ return Set.of();
+ }
+ return Arrays.stream(importProperty.split(",")).map(String::trim).collect(Collectors.toUnmodifiableSet());
+ }
+
+ String legacyImportProperty = getLegacyProperty(RepositoryConfig.PROPERTY_ECLIPSE, RepositoryConfig.PROPERTY_IMPORT);
+ if ("import".equals(legacyImportProperty)) {
+ this.context.warning("Property {} is deprecated and should be replaced with {} (invert key and value).", RepositoryConfig.PROPERTY_ECLIPSE,
+ RepositoryConfig.PROPERTY_IMPORT);
+ return Set.of("eclipse");
+ } else {
+ return Set.of();
+ }
+ }
+
+}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/io/FileAccess.java b/cli/src/main/java/com/devonfw/tools/ide/io/FileAccess.java
index 6ecafff03..c438ccd0b 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/io/FileAccess.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/io/FileAccess.java
@@ -1,8 +1,13 @@
package com.devonfw.tools.ide.io;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.PosixFilePermission;
import java.util.List;
+import java.util.Properties;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -306,10 +311,44 @@ default void makeExecutable(Path file) {
*/
String readFileContent(Path file);
+ /**
+ * @param content the {@link String} with the text to write to a file.
+ * @param file the {@link Path} to the file where to save.
+ */
+ void writeFileContent(String content, Path file);
+
/**
* @param path that is checked whether it is a junction or not.
* @return {@code true} if the given {@link Path} is a junction, false otherwise.
*/
boolean isJunction(Path path);
+ /**
+ * @param file the {@link Path} to the {@link Properties} file to read.
+ * @return the parsed {@link Properties}.
+ */
+ default Properties readProperties(Path file) {
+
+ Properties properties = new Properties();
+ try (Reader reader = Files.newBufferedReader(file)) {
+ properties.load(reader);
+ return properties;
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to read properties file: " + file, e);
+ }
+ }
+
+ /**
+ * @param properties the {@link Properties} to save.
+ * @param file the {@link Path} to the file where to save the properties.
+ */
+ default void writeProperties(Properties properties, Path file) {
+
+ try (Writer writer = Files.newBufferedWriter(file)) {
+ properties.store(writer, null);
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to save properties file during tests.", e);
+ }
+ }
+
}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/io/FileAccessImpl.java b/cli/src/main/java/com/devonfw/tools/ide/io/FileAccessImpl.java
index 5ce9c5ad9..790335f7f 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/io/FileAccessImpl.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/io/FileAccessImpl.java
@@ -993,10 +993,28 @@ public String readFileContent(Path file) {
this.context.trace("Reading content of file from {}", file);
try {
String content = Files.readString(file);
- this.context.trace("Read content of file {} as {}", file, content);
+ this.context.trace("Read content of length {} from file {}", content.length(), file);
return content;
} catch (IOException e) {
throw new IllegalStateException("Failed to read file " + file, e);
}
}
+
+ @Override
+ public void writeFileContent(String content, Path file) {
+
+ if (content == null) {
+ content = "";
+ }
+ this.context.trace("Writing content with length {} to file {}", content.length(), file);
+ if (Files.exists(file)) {
+ this.context.info("Overriding content of file {}", file);
+ }
+ try {
+ Files.writeString(file, content);
+ this.context.trace("Wrote content to file {}", file);
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to write file " + file, e);
+ }
+ }
}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/json/JsonMapping.java b/cli/src/main/java/com/devonfw/tools/ide/json/JsonMapping.java
index 9756c680a..92a13a908 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/json/JsonMapping.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/json/JsonMapping.java
@@ -1,11 +1,11 @@
package com.devonfw.tools.ide.json;
-import com.devonfw.tools.ide.repo.CustomToolJson;
-import com.devonfw.tools.ide.repo.CustomToolJsonDeserializer;
-import com.devonfw.tools.ide.repo.CustomToolJsonSerializer;
-import com.devonfw.tools.ide.repo.CustomToolsJson;
-import com.devonfw.tools.ide.repo.CustomToolsJsonDeserializer;
-import com.devonfw.tools.ide.repo.CustomToolsJsonSerializer;
+import com.devonfw.tools.ide.tool.repository.CustomToolJson;
+import com.devonfw.tools.ide.tool.repository.CustomToolJsonDeserializer;
+import com.devonfw.tools.ide.tool.repository.CustomToolJsonSerializer;
+import com.devonfw.tools.ide.tool.repository.CustomToolsJson;
+import com.devonfw.tools.ide.tool.repository.CustomToolsJsonDeserializer;
+import com.devonfw.tools.ide.tool.repository.CustomToolsJsonSerializer;
import com.devonfw.tools.ide.url.model.file.json.ToolDependency;
import com.devonfw.tools.ide.version.VersionIdentifier;
import com.devonfw.tools.ide.version.VersionRange;
diff --git a/cli/src/main/java/com/devonfw/tools/ide/log/AbstractIdeSubLogger.java b/cli/src/main/java/com/devonfw/tools/ide/log/AbstractIdeSubLogger.java
index ac1252a2d..bb8215482 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/log/AbstractIdeSubLogger.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/log/AbstractIdeSubLogger.java
@@ -1,5 +1,7 @@
package com.devonfw.tools.ide.log;
+import com.devonfw.tools.ide.cli.CliException;
+
/**
* Abstract base implementation of {@link IdeSubLogger}.
*/
@@ -137,13 +139,21 @@ public String log(Throwable error, String message, Object... args) {
return message;
}
String actualMessage = message;
+ if (error != null) {
+ if (isOmitStacktrace(error)) {
if (message == null) {
- if (error == null) {
+ actualMessage = error.getMessage();
+ }
+ error = null;
+ } else if (message == null) {
+ actualMessage = error.toString();
+ }
+ }
+ if (actualMessage == null) {
actualMessage = "Internal error: Both message and error is null - nothing to log!";
// fail fast if assertions are enabled, so developers of IDEasy will find the bug immediately but in productive use better log the error and continue
assert false : actualMessage;
- }
- } else if (args != null && args.length > 0) {
+ } else if ((args != null) && (args.length > 0)) {
actualMessage = compose(actualMessage, args);
}
boolean accept = this.listener.onLog(this.level, actualMessage, message, args, error);
@@ -154,6 +164,11 @@ public String log(Throwable error, String message, Object... args) {
return actualMessage;
}
+ private boolean isOmitStacktrace(Throwable error) {
+
+ return (error instanceof CliException);
+ }
+
/**
* @param message the formatted message to log.
* @param error the optional {@link Throwable} to log or {@code null} for no error.
diff --git a/cli/src/main/java/com/devonfw/tools/ide/os/MacOsHelper.java b/cli/src/main/java/com/devonfw/tools/ide/os/MacOsHelper.java
index 7f210b590..08b7a8380 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/os/MacOsHelper.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/os/MacOsHelper.java
@@ -10,8 +10,8 @@
import com.devonfw.tools.ide.context.IdeContext;
import com.devonfw.tools.ide.io.FileAccess;
import com.devonfw.tools.ide.log.IdeLogger;
-import com.devonfw.tools.ide.repo.ToolRepository;
import com.devonfw.tools.ide.tool.ToolCommandlet;
+import com.devonfw.tools.ide.tool.repository.ToolRepository;
/**
* Internal helper class for MacOS workarounds.
diff --git a/cli/src/main/java/com/devonfw/tools/ide/step/Step.java b/cli/src/main/java/com/devonfw/tools/ide/step/Step.java
index 0afffe092..f3b7dcf6d 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/step/Step.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/step/Step.java
@@ -209,17 +209,32 @@ default void error(Throwable error, String message, Object... args) {
/**
* @param stepCode the {@link Runnable} to {@link Runnable#run() execute} for this {@link Step}.
+ * @return {@code true} on success, {@code false} on error.
*/
- default void run(Runnable stepCode) {
+ default boolean run(Runnable stepCode) {
+
+ return run(stepCode, false);
+ }
+
+ /**
+ * @param stepCode the {@link Runnable} to {@link Runnable#run() execute} for this {@link Step}.
+ * @param rethrow - {@code true} to rethrow a potential {@link Throwable error}.
+ * @return {@code true} on success, {@code false} on error (if {@code rethrow} is {@code false}).
+ */
+ default boolean run(Runnable stepCode, boolean rethrow) {
try {
stepCode.run();
if (getSuccess() == null) {
success();
}
+ return true;
} catch (RuntimeException | Error e) {
error(e);
- throw e;
+ if (rethrow) {
+ throw e;
+ }
+ return false;
} finally {
close();
}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/step/StepImpl.java b/cli/src/main/java/com/devonfw/tools/ide/step/StepImpl.java
index cfce221e3..7788e0068 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/step/StepImpl.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/step/StepImpl.java
@@ -169,9 +169,6 @@ private void end(Boolean newSuccess, Throwable error, boolean suppress, String m
this.errorMessage = message;
}
} else {
- if (message == null) {
- message = error.toString();
- }
this.errorMessage = this.context.error().log(error, message, args);
}
logger = this.context.debug();
diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/CustomToolCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/CustomToolCommandlet.java
index 02e44d5b0..5caf871c4 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/tool/CustomToolCommandlet.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/CustomToolCommandlet.java
@@ -2,7 +2,7 @@
import com.devonfw.tools.ide.context.IdeContext;
import com.devonfw.tools.ide.process.EnvironmentContext;
-import com.devonfw.tools.ide.repo.CustomToolMetadata;
+import com.devonfw.tools.ide.tool.repository.CustomToolMetadata;
import com.devonfw.tools.ide.version.GenericVersionRange;
import com.devonfw.tools.ide.version.VersionIdentifier;
diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/GlobalToolCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/GlobalToolCommandlet.java
index bd5716a55..410bb85e6 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/tool/GlobalToolCommandlet.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/GlobalToolCommandlet.java
@@ -14,7 +14,7 @@
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.repo.ToolRepository;
+import com.devonfw.tools.ide.tool.repository.ToolRepository;
import com.devonfw.tools.ide.version.VersionIdentifier;
/**
diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java
index 0f0c8dba5..02a8dfadc 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java
@@ -13,8 +13,8 @@
import com.devonfw.tools.ide.io.FileAccess;
import com.devonfw.tools.ide.io.FileCopyMode;
import com.devonfw.tools.ide.process.EnvironmentContext;
-import com.devonfw.tools.ide.repo.ToolRepository;
import com.devonfw.tools.ide.step.Step;
+import com.devonfw.tools.ide.tool.repository.ToolRepository;
import com.devonfw.tools.ide.url.model.file.json.ToolDependency;
import com.devonfw.tools.ide.version.GenericVersionRange;
import com.devonfw.tools.ide.version.VersionIdentifier;
diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java
index a2aef5d61..f20ab6fd1 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java
@@ -36,6 +36,8 @@ public abstract class ToolCommandlet extends Commandlet implements Tags {
/** The commandline arguments to pass to the tool. */
public final StringProperty arguments;
+ private Path executionDirectory;
+
private MacOsHelper macOsHelper;
/**
@@ -86,6 +88,22 @@ public final Set getTags() {
return this.tags;
}
+ /**
+ * @return the execution directory where the tool will be executed. Will be {@code null} by default leading to execution in the users current working
+ * directory where IDEasy was called.
+ * @see #setExecutionDirectory(Path)
+ */
+ public Path getExecutionDirectory() {
+ return this.executionDirectory;
+ }
+
+ /**
+ * @param executionDirectory the new value of {@link #getExecutionDirectory()}.
+ */
+ public void setExecutionDirectory(Path executionDirectory) {
+ this.executionDirectory = executionDirectory;
+ }
+
/**
* @return the {@link EnvironmentVariables#getToolVersion(String) tool version}.
*/
@@ -116,7 +134,7 @@ protected final String getToolWithEdition() {
* @param edition the edition.
* @return the {@link #getName() tool} with its {@link #getConfiguredEdition() edition}. The edition will be omitted if same as tool.
*/
- protected final static String getToolWithEdition(String tool, String edition) {
+ protected static String getToolWithEdition(String tool, String edition) {
if (tool.equals(edition)) {
return tool;
@@ -164,6 +182,9 @@ public ProcessResult runTool(ProcessMode processMode, GenericVersionRange toolVe
ProcessContext pc = this.context.newProcess().errorHandling(errorHandling);
install(true, pc);
+ if (this.executionDirectory != null) {
+ pc.directory(this.executionDirectory);
+ }
configureToolBinary(pc, processMode, errorHandling);
configureToolArgs(pc, processMode, errorHandling, args);
return pc.run(processMode);
@@ -486,4 +507,9 @@ protected void createStartScript(Path targetDir, String binary, boolean backgrou
context.getFileAccess().makeExecutable(bashFile);
}
+ @Override
+ public void reset() {
+ super.reset();
+ this.executionDirectory = null;
+ }
}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/docker/Docker.java b/cli/src/main/java/com/devonfw/tools/ide/tool/docker/Docker.java
index 9829c4cf3..cac604873 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/tool/docker/Docker.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/docker/Docker.java
@@ -9,10 +9,10 @@
import com.devonfw.tools.ide.context.IdeContext;
import com.devonfw.tools.ide.os.SystemArchitecture;
import com.devonfw.tools.ide.process.EnvironmentContext;
-import com.devonfw.tools.ide.repo.ToolRepository;
import com.devonfw.tools.ide.tool.GlobalToolCommandlet;
import com.devonfw.tools.ide.tool.PackageManager;
import com.devonfw.tools.ide.tool.PackageManagerCommand;
+import com.devonfw.tools.ide.tool.repository.ToolRepository;
import com.devonfw.tools.ide.version.VersionIdentifier;
/**
diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/Eclipse.java b/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/Eclipse.java
index 051099ace..c0f57a264 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/Eclipse.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/Eclipse.java
@@ -18,6 +18,8 @@
import com.devonfw.tools.ide.step.Step;
import com.devonfw.tools.ide.tool.ide.IdeToolCommandlet;
import com.devonfw.tools.ide.tool.java.Java;
+import com.devonfw.tools.ide.tool.mvn.Mvn;
+import com.devonfw.tools.ide.tool.mvn.MvnArtifact;
import com.devonfw.tools.ide.tool.plugin.ToolPluginDescriptor;
/**
@@ -25,6 +27,14 @@
*/
public class Eclipse extends IdeToolCommandlet {
+ // version must correspond to eclipse-import.xml
+ private static final String GROOVY_VERSION = "3.0.23";
+
+ /** Eclipse CLI option for Java virtual machine arguments. */
+ public static final String VMARGS = "-vmargs";
+
+ private boolean groovyInstalled;
+
/**
* The constructor.
*
@@ -38,7 +48,7 @@ public Eclipse(IdeContext context) {
@Override
protected void configureToolBinary(ProcessContext pc, ProcessMode processMode, ProcessErrorHandling errorHandling) {
- if ((processMode == ProcessMode.DEFAULT_CAPTURE) && this.context.getSystemInfo().isWindows()) {
+ if (!processMode.isBackground() && this.context.getSystemInfo().isWindows()) {
pc.executable(Path.of("eclipsec"));
} else {
super.configureToolBinary(pc, processMode, errorHandling);
@@ -48,6 +58,12 @@ protected void configureToolBinary(ProcessContext pc, ProcessMode processMode, P
@Override
protected void configureToolArgs(ProcessContext pc, ProcessMode processMode, ProcessErrorHandling errorHandling, String... args) {
+ if ((args.length > 0) && !VMARGS.equals(args[0])) {
+ String vmArgs = this.context.getVariables().get("ECLIPSE_VMARGS");
+ if ((vmArgs != null) && !vmArgs.isEmpty()) {
+ pc.addArg(VMARGS).addArg(vmArgs);
+ }
+ }
// configure workspace location
pc.addArg("-data").addArg(this.context.getWorkspacePath());
// use keyring from user home to keep secrets and share across projects and workspaces
@@ -57,7 +73,7 @@ protected void configureToolArgs(ProcessContext pc, ProcessMode processMode, Pro
if (processMode == ProcessMode.BACKGROUND) {
// to start eclipse as GUI
pc.addArg("gui").addArg("-showlocation").addArg(this.context.getIdeHome().getFileName());
- } else if (processMode == ProcessMode.DEFAULT_CAPTURE) {
+ } else {
pc.addArg("-consoleLog").addArg("-nosplash");
}
super.configureToolArgs(pc, processMode, errorHandling, args);
@@ -123,4 +139,17 @@ private static boolean isLocked(Path lockfile) {
return false;
}
+ @Override
+ public void importRepository(Path repositoryPath) {
+ if (!this.groovyInstalled) {
+ Mvn maven = this.context.getCommandletManager().getCommandlet(Mvn.class);
+ MvnArtifact groovyAnt = new MvnArtifact("org.codehaus.groovy", "groovy-ant", GROOVY_VERSION);
+ maven.getOrDownloadArtifact(groovyAnt);
+ this.groovyInstalled = true;
+ }
+ // -DdevonImportPath=\"${import_path}\" -DdevonImportWorkingSet=\"${importWorkingSets}\""
+ runTool(ProcessMode.DEFAULT, null, ProcessErrorHandling.THROW_CLI, VMARGS,
+ "-DrepositoryImportPath=\"" + repositoryPath + "\" -DrepositoryImportWorkingSet=\"" + "" + "\"", "-application", "org.eclipse.ant.core.antRunner",
+ "-buildfile", this.context.getIdeInstallationPath().resolve(IdeContext.FOLDER_INTERNAL).resolve("eclipse-import.xml").toString());
+ }
}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/ide/IdeToolCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/ide/IdeToolCommandlet.java
index c1f265337..257a1a24f 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/tool/ide/IdeToolCommandlet.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/ide/IdeToolCommandlet.java
@@ -92,4 +92,14 @@ protected void configureWorkspace() {
}
}
}
+
+ /**
+ * Imports the repository specified by the given {@link Path} into the IDE managed by this {@link IdeToolCommandlet}.
+ *
+ * @param repositoryPath the {@link Path} to the repository directory to import.
+ */
+ public void importRepository(Path repositoryPath) {
+
+ throw new UnsupportedOperationException("Repository import is not yet implemented for IDE " + this.tool);
+ }
}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/Mvn.java b/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/Mvn.java
index b113e20a7..8080d0f55 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/Mvn.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/Mvn.java
@@ -10,8 +10,8 @@
import java.util.regex.Matcher;
import com.devonfw.tools.ide.common.Tag;
-import com.devonfw.tools.ide.git.GitContext;
import com.devonfw.tools.ide.context.IdeContext;
+import com.devonfw.tools.ide.git.GitContext;
import com.devonfw.tools.ide.process.ProcessContext;
import com.devonfw.tools.ide.process.ProcessMode;
import com.devonfw.tools.ide.process.ProcessResult;
@@ -20,6 +20,7 @@
import com.devonfw.tools.ide.tool.java.Java;
import com.devonfw.tools.ide.tool.plugin.PluginBasedCommandlet;
import com.devonfw.tools.ide.tool.plugin.ToolPluginDescriptor;
+import com.devonfw.tools.ide.variable.IdeVariables;
import com.devonfw.tools.ide.variable.VariableSyntax;
/**
@@ -163,7 +164,7 @@ private String getEncryptedPassword(String variable) {
ProcessContext pc = this.context.newProcess().executable("mvn");
pc.addArgs("--encrypt-password", input);
- pc.addArg(getDsettingsSecurityProperty());
+ pc.addArg(getSettingsSecurityProperty());
ProcessResult result = pc.run(ProcessMode.DEFAULT_CAPTURE);
String encryptedPassword = result.getOut().get(0);
@@ -204,6 +205,9 @@ public String getToolHelpArguments() {
return "-h";
}
+ /**
+ * @return the {@link Path} to the folder with the maven configuration templates.
+ */
public Path getMavenTemplatesFolder() {
Path templatesFolder = this.context.getSettingsTemplatePath();
@@ -224,6 +228,10 @@ public Path getMavenTemplatesFolder() {
return templatesConfMvnFolder;
}
+ /**
+ * @param legacy - {@code true} to enforce legacy fallback creation, {@code false} otherwise.
+ * @return the {@link Path} to the maven configuration folder (where "settings.xml" can be found).
+ */
public Path getMavenConfFolder(boolean legacy) {
Path confPath = this.context.getConfPath();
@@ -242,6 +250,9 @@ public Path getMavenConfFolder(boolean legacy) {
return mvnConfigFolder;
}
+ /**
+ * @return the maven arguments (MVN_ARGS).
+ */
public String getMavenArgs() {
Path mavenConfFolder = getMavenConfFolder(false);
Path mvnSettingsFile = mavenConfFolder.resolve(Mvn.SETTINGS_FILE);
@@ -249,10 +260,41 @@ public String getMavenArgs() {
return null;
}
String settingsPath = mvnSettingsFile.toString();
- return "-s " + settingsPath + " " + getDsettingsSecurityProperty();
+ return "-s " + settingsPath + " " + getSettingsSecurityProperty();
}
- private String getDsettingsSecurityProperty() {
+ private String getSettingsSecurityProperty() {
return "-Dsettings.security=" + this.context.getMavenRepository().getParent().resolve(SETTINGS_SECURITY_FILE).toString().replace("\\", "\\\\");
}
+
+ /**
+ * @return the {@link Path} to the local maven repository.
+ */
+ public Path getLocalRepository() {
+ return IdeVariables.M2_REPO.get(this.context);
+ }
+
+ /**
+ * @param artifact the {@link MvnArtifact}.
+ */
+ public void downloadArtifact(MvnArtifact artifact) {
+
+ this.context.newStep("Download artifact " + artifact).run(() -> {
+ runTool("dependency:get", "-Dartifact=" + artifact.getKey());
+ });
+ }
+
+ /**
+ * @param artifact the {@link MvnArtifact}.
+ * @return the {@link Path} to the {@link MvnArtifact} that was downloaded if not already present.
+ */
+ public Path getOrDownloadArtifact(MvnArtifact artifact) {
+
+ Path artifactPath = getLocalRepository().resolve(artifact.getPath());
+ if (!Files.exists(artifactPath)) {
+ downloadArtifact(artifact);
+ assert (Files.exists(artifactPath));
+ }
+ return artifactPath;
+ }
}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/MvnArtifact.java b/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/MvnArtifact.java
new file mode 100644
index 000000000..bd66a9b95
--- /dev/null
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/MvnArtifact.java
@@ -0,0 +1,218 @@
+package com.devonfw.tools.ide.tool.mvn;
+
+import java.util.Objects;
+
+/**
+ * Simple type representing a maven artifact.
+ */
+public final class MvnArtifact {
+
+ /** {@link #getGroupId() Group ID} of IDEasy. */
+ public static final String GROUP_ID_IDEASY = "com.devonfw.tools.IDEasy";
+
+ /** {@link #getArtifactId() Artifact ID} of IDEasy command line interface. */
+ public static final String ARTIFACT_ID_IDEASY_CLI = "ide-cli";
+
+ /** {@link #getClassifier() Classifier} of source code. */
+ public static final String CLASSIFER_SOURCES = "sources";
+
+ /** {@link #getType() Type} of JAR file. */
+ public static final String TYPE_JAR = "jar";
+
+ /** {@link #getType() Type} of POM XML file. */
+ public static final String TYPE_POM = "pom";
+
+ private final String groupId;
+
+ private final String artifactId;
+
+ private final String version;
+
+ private final String classifier;
+
+ private final String type;
+
+ private String path;
+
+ private String key;
+
+ /**
+ * The constructor.
+ *
+ * @param groupId the {@link #getGroupId() group ID}.
+ * @param artifactId the {@link #getArtifactId() artifact ID}.
+ * @param version the {@link #getVersion() version}.
+ */
+ public MvnArtifact(String groupId, String artifactId, String version) {
+ this(groupId, artifactId, version, TYPE_JAR);
+ }
+
+ /**
+ * The constructor.
+ *
+ * @param groupId the {@link #getGroupId() group ID}.
+ * @param artifactId the {@link #getArtifactId() artifact ID}.
+ * @param version the {@link #getVersion() version}.
+ * @param type the {@link #getType() type}.
+ */
+ public MvnArtifact(String groupId, String artifactId, String version, String type) {
+ this(groupId, artifactId, version, type, "");
+ }
+
+ /**
+ * The constructor.
+ *
+ * @param groupId the {@link #getGroupId() group ID}.
+ * @param artifactId the {@link #getArtifactId() artifact ID}.
+ * @param version the {@link #getVersion() version}.
+ * @param type the {@link #getType() type}.
+ * @param classifier the {@link #getClassifier() classifier}.
+ */
+ public MvnArtifact(String groupId, String artifactId, String version, String type, String classifier) {
+ super();
+ this.groupId = requireNotEmpty(groupId, "groupId");
+ this.artifactId = requireNotEmpty(artifactId, "artifactId");
+ this.version = requireNotEmpty(version, "version");
+ this.classifier = notNull(classifier);
+ this.type = requireNotEmpty(type, "type");
+ }
+
+ /**
+ * @return the group ID (e.g. {@link #GROUP_ID_IDEASY}).
+ */
+ public String getGroupId() {
+ return this.groupId;
+ }
+
+ /**
+ * @return the artifact ID (e.g. {@link #ARTIFACT_ID_IDEASY_CLI}).
+ */
+ public String getArtifactId() {
+ return this.artifactId;
+ }
+
+ /**
+ * @return the version.
+ * @see com.devonfw.tools.ide.version.VersionIdentifier
+ */
+ public String getVersion() {
+ return this.version;
+ }
+
+ /**
+ * @return the classifier. Will be the empty {@link String} for no classifier.
+ */
+ public String getClassifier() {
+ return this.classifier;
+ }
+
+ /**
+ * @return the type (e.g. #TYPE_JAR}
+ */
+ public String getType() {
+ return type;
+ }
+
+ /**
+ * @return the {@link String} with the path to the specified artifact relative to the maven repository base path or URL.
+ */
+ public String getPath() {
+ if (this.path == null) {
+ int capacity = this.groupId.length() + 2 * this.artifactId.length() + 2 * this.version.length() + type.length() + classifier.length() + 6;
+ StringBuilder sb = new StringBuilder(capacity);
+ sb.append(this.groupId.replace('.', '/')).append('/')
+ .append(this.artifactId).append('/')
+ .append(this.version).append('/')
+ .append(this.artifactId).append('-').append(this.version);
+ if (!this.classifier.isEmpty()) {
+ sb.append('-').append(this.classifier);
+ }
+ sb.append('.').append(this.type);
+ this.path = sb.toString();
+ assert (this.path.length() <= capacity);
+ }
+ return this.path;
+ }
+
+ /**
+ * @return the artifact key as unique identifier.
+ */
+ String getKey() {
+ if (this.key == null) {
+ int capacity = this.groupId.length() + this.artifactId.length() + this.version.length() + type.length() + classifier.length() + 4;
+ StringBuilder sb = new StringBuilder(capacity);
+ sb.append(this.groupId).append(':').append(this.artifactId).append(':').append(this.version).append(':').append(this.type);
+ if (!this.classifier.isEmpty()) {
+ sb.append(':').append(this.classifier);
+ }
+ this.key = sb.toString();
+ assert (this.key.length() <= capacity);
+ }
+ return this.key;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.groupId, this.artifactId, this.version);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ } else if (obj instanceof MvnArtifact other) {
+ return this.groupId.equals(other.groupId) && this.artifactId.equals(other.artifactId) && this.version.equals(other.version)
+ && this.classifier.equals(other.classifier) && this.type.equals(other.type);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return getKey();
+ }
+
+ private static String notNull(String value) {
+
+ if (value == null) {
+ return "";
+ }
+ return value;
+ }
+
+ private static String requireNotEmpty(String value, String propertyName) {
+
+ if (isEmpty(value)) {
+ throw new IllegalArgumentException("Maven artifact property " + propertyName + " must not be empty");
+ }
+ return value;
+ }
+
+ private static boolean isEmpty(String value) {
+
+ return ((value == null) || value.isEmpty());
+ }
+
+ /**
+ * @param artifactId the {@link #getArtifactId() artifact ID}.
+ * @param version the {@link #getVersion() version}.
+ * @param type the {@link #getType() type}.
+ * @param classifier the {@link #getClassifier() classifier}.
+ * @return the IDEasy {@link MvnArtifact}.
+ */
+ public static MvnArtifact ofIdeasy(String artifactId, String version, String type, String classifier) {
+
+ return new MvnArtifact(GROUP_ID_IDEASY, artifactId, version, type, classifier);
+ }
+
+ /**
+ * @param version the {@link #getVersion() version}.
+ * @param type the {@link #getType() type}.
+ * @param classifier the {@link #getClassifier() classifier}.
+ * @return the IDEasy {@link MvnArtifact}.
+ */
+ public static MvnArtifact ofIdeasyCli(String version, String type, String classifier) {
+
+ return ofIdeasy(ARTIFACT_ID_IDEASY_CLI, version, type, classifier);
+ }
+}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/pgadmin/PgAdmin.java b/cli/src/main/java/com/devonfw/tools/ide/tool/pgadmin/PgAdmin.java
index be969e0fc..137ffb069 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/tool/pgadmin/PgAdmin.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/pgadmin/PgAdmin.java
@@ -8,10 +8,10 @@
import com.devonfw.tools.ide.common.Tag;
import com.devonfw.tools.ide.context.IdeContext;
import com.devonfw.tools.ide.process.EnvironmentContext;
-import com.devonfw.tools.ide.repo.ToolRepository;
import com.devonfw.tools.ide.tool.GlobalToolCommandlet;
import com.devonfw.tools.ide.tool.PackageManager;
import com.devonfw.tools.ide.tool.PackageManagerCommand;
+import com.devonfw.tools.ide.tool.repository.ToolRepository;
import com.devonfw.tools.ide.version.VersionIdentifier;
/**
diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/plugin/ToolPluginDescriptor.java b/cli/src/main/java/com/devonfw/tools/ide/tool/plugin/ToolPluginDescriptor.java
index fbd7968f4..da7fc8d05 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/tool/plugin/ToolPluginDescriptor.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/plugin/ToolPluginDescriptor.java
@@ -1,8 +1,5 @@
package com.devonfw.tools.ide.tool.plugin;
-import java.io.IOException;
-import java.io.Reader;
-import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Locale;
import java.util.Properties;
@@ -32,27 +29,22 @@ public Set getTags() {
/**
* @param propertiesFile the {@link Path} to the plugin {@link Properties} file.
- * @param logger the {@link IdeLogger}.
+ * @param context the {@link IdeContext}.
* @param needUrl - {@code true} if {@link ToolPluginDescriptor#url () URL} needs to be present and a warning shall be logged if missing, {@code false}
* otherwise.
* @return the loaded {@link ToolPluginDescriptor}.
*/
- public static ToolPluginDescriptor of(Path propertiesFile, IdeLogger logger, boolean needUrl) {
+ public static ToolPluginDescriptor of(Path propertiesFile, IdeContext context, boolean needUrl) {
- Properties properties = new Properties();
String name = propertiesFile.getFileName().toString();
name = name.substring(0, name.length() - IdeContext.EXT_PROPERTIES.length());
- try (Reader reader = Files.newBufferedReader(propertiesFile)) {
- properties.load(reader);
- } catch (IOException e) {
- throw new IllegalStateException("Failed to load properties " + propertiesFile, e);
- }
+ Properties properties = context.getFileAccess().readProperties(propertiesFile);
String id = getString(properties, "id", "plugin_id");
String url = getString(properties, "url", "plugin_url");
if (needUrl && ((url == null) || url.isBlank())) {
- logger.warning("Missing plugin URL in {}", propertiesFile);
+ context.warning("Missing plugin URL in {}", propertiesFile);
}
- boolean active = getBoolean(properties, "active", "plugin_active", propertiesFile, logger);
+ boolean active = getBoolean(properties, "active", "plugin_active", propertiesFile, context);
String tagsCsv = getString(properties, "tags", "plugin_tags");
Set tags = Tag.parseCsv(tagsCsv);
return new ToolPluginDescriptor(id, name, url, active, tags);
diff --git a/cli/src/main/java/com/devonfw/tools/ide/repo/AbstractToolRepository.java b/cli/src/main/java/com/devonfw/tools/ide/tool/repository/AbstractToolRepository.java
similarity index 99%
rename from cli/src/main/java/com/devonfw/tools/ide/repo/AbstractToolRepository.java
rename to cli/src/main/java/com/devonfw/tools/ide/tool/repository/AbstractToolRepository.java
index 41c5d4976..ba2986393 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/repo/AbstractToolRepository.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/repository/AbstractToolRepository.java
@@ -1,4 +1,4 @@
-package com.devonfw.tools.ide.repo;
+package com.devonfw.tools.ide.tool.repository;
import java.nio.file.Files;
import java.nio.file.Path;
diff --git a/cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolJson.java b/cli/src/main/java/com/devonfw/tools/ide/tool/repository/CustomToolJson.java
similarity index 97%
rename from cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolJson.java
rename to cli/src/main/java/com/devonfw/tools/ide/tool/repository/CustomToolJson.java
index 92503e962..a7c0e5a51 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolJson.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/repository/CustomToolJson.java
@@ -1,4 +1,4 @@
-package com.devonfw.tools.ide.repo;
+package com.devonfw.tools.ide.tool.repository;
import com.devonfw.tools.ide.os.OperatingSystem;
diff --git a/cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolJsonDeserializer.java b/cli/src/main/java/com/devonfw/tools/ide/tool/repository/CustomToolJsonDeserializer.java
similarity index 98%
rename from cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolJsonDeserializer.java
rename to cli/src/main/java/com/devonfw/tools/ide/tool/repository/CustomToolJsonDeserializer.java
index ee159b20f..d83c03745 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolJsonDeserializer.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/repository/CustomToolJsonDeserializer.java
@@ -1,4 +1,4 @@
-package com.devonfw.tools.ide.repo;
+package com.devonfw.tools.ide.tool.repository;
import java.io.IOException;
diff --git a/cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolJsonSerializer.java b/cli/src/main/java/com/devonfw/tools/ide/tool/repository/CustomToolJsonSerializer.java
similarity index 96%
rename from cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolJsonSerializer.java
rename to cli/src/main/java/com/devonfw/tools/ide/tool/repository/CustomToolJsonSerializer.java
index 56b83f140..274cf4f7c 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolJsonSerializer.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/repository/CustomToolJsonSerializer.java
@@ -1,4 +1,4 @@
-package com.devonfw.tools.ide.repo;
+package com.devonfw.tools.ide.tool.repository;
import java.io.IOException;
diff --git a/cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolMetadata.java b/cli/src/main/java/com/devonfw/tools/ide/tool/repository/CustomToolMetadata.java
similarity index 98%
rename from cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolMetadata.java
rename to cli/src/main/java/com/devonfw/tools/ide/tool/repository/CustomToolMetadata.java
index 085492b27..80c07ba8b 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolMetadata.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/repository/CustomToolMetadata.java
@@ -1,4 +1,4 @@
-package com.devonfw.tools.ide.repo;
+package com.devonfw.tools.ide.tool.repository;
import java.util.Set;
diff --git a/cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolRepository.java b/cli/src/main/java/com/devonfw/tools/ide/tool/repository/CustomToolRepository.java
similarity index 88%
rename from cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolRepository.java
rename to cli/src/main/java/com/devonfw/tools/ide/tool/repository/CustomToolRepository.java
index bac2a6064..a2c320278 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolRepository.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/repository/CustomToolRepository.java
@@ -1,4 +1,4 @@
-package com.devonfw.tools.ide.repo;
+package com.devonfw.tools.ide.tool.repository;
import java.util.Collection;
diff --git a/cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolRepositoryImpl.java b/cli/src/main/java/com/devonfw/tools/ide/tool/repository/CustomToolRepositoryImpl.java
similarity index 99%
rename from cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolRepositoryImpl.java
rename to cli/src/main/java/com/devonfw/tools/ide/tool/repository/CustomToolRepositoryImpl.java
index b6cab89d8..c1dd8dffa 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolRepositoryImpl.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/repository/CustomToolRepositoryImpl.java
@@ -1,4 +1,4 @@
-package com.devonfw.tools.ide.repo;
+package com.devonfw.tools.ide.tool.repository;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
diff --git a/cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolsJson.java b/cli/src/main/java/com/devonfw/tools/ide/tool/repository/CustomToolsJson.java
similarity index 92%
rename from cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolsJson.java
rename to cli/src/main/java/com/devonfw/tools/ide/tool/repository/CustomToolsJson.java
index 9789ce773..afe392fd7 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolsJson.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/repository/CustomToolsJson.java
@@ -1,4 +1,4 @@
-package com.devonfw.tools.ide.repo;
+package com.devonfw.tools.ide.tool.repository;
import java.util.List;
diff --git a/cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolsJsonDeserializer.java b/cli/src/main/java/com/devonfw/tools/ide/tool/repository/CustomToolsJsonDeserializer.java
similarity index 97%
rename from cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolsJsonDeserializer.java
rename to cli/src/main/java/com/devonfw/tools/ide/tool/repository/CustomToolsJsonDeserializer.java
index ee6ba4725..926b7ee24 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolsJsonDeserializer.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/repository/CustomToolsJsonDeserializer.java
@@ -1,4 +1,4 @@
-package com.devonfw.tools.ide.repo;
+package com.devonfw.tools.ide.tool.repository;
import java.io.IOException;
import java.util.ArrayList;
diff --git a/cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolsJsonMapper.java b/cli/src/main/java/com/devonfw/tools/ide/tool/repository/CustomToolsJsonMapper.java
similarity index 99%
rename from cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolsJsonMapper.java
rename to cli/src/main/java/com/devonfw/tools/ide/tool/repository/CustomToolsJsonMapper.java
index e60100876..8e017dd06 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolsJsonMapper.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/repository/CustomToolsJsonMapper.java
@@ -1,4 +1,4 @@
-package com.devonfw.tools.ide.repo;
+package com.devonfw.tools.ide.tool.repository;
import java.io.BufferedReader;
import java.io.BufferedWriter;
diff --git a/cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolsJsonSerializer.java b/cli/src/main/java/com/devonfw/tools/ide/tool/repository/CustomToolsJsonSerializer.java
similarity index 95%
rename from cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolsJsonSerializer.java
rename to cli/src/main/java/com/devonfw/tools/ide/tool/repository/CustomToolsJsonSerializer.java
index 1a77794c9..93d919b1c 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolsJsonSerializer.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/repository/CustomToolsJsonSerializer.java
@@ -1,4 +1,4 @@
-package com.devonfw.tools.ide.repo;
+package com.devonfw.tools.ide.tool.repository;
import java.io.IOException;
import java.util.List;
diff --git a/cli/src/main/java/com/devonfw/tools/ide/repo/DefaultToolRepository.java b/cli/src/main/java/com/devonfw/tools/ide/tool/repository/DefaultToolRepository.java
similarity index 98%
rename from cli/src/main/java/com/devonfw/tools/ide/repo/DefaultToolRepository.java
rename to cli/src/main/java/com/devonfw/tools/ide/tool/repository/DefaultToolRepository.java
index 00ffde30b..7cecc1cce 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/repo/DefaultToolRepository.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/repository/DefaultToolRepository.java
@@ -1,4 +1,4 @@
-package com.devonfw.tools.ide.repo;
+package com.devonfw.tools.ide.tool.repository;
import java.util.Collection;
diff --git a/cli/src/main/java/com/devonfw/tools/ide/repo/ToolRepository.java b/cli/src/main/java/com/devonfw/tools/ide/tool/repository/ToolRepository.java
similarity index 98%
rename from cli/src/main/java/com/devonfw/tools/ide/repo/ToolRepository.java
rename to cli/src/main/java/com/devonfw/tools/ide/tool/repository/ToolRepository.java
index 136084cba..5ac71e8b9 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/repo/ToolRepository.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/repository/ToolRepository.java
@@ -1,4 +1,4 @@
-package com.devonfw.tools.ide.repo;
+package com.devonfw.tools.ide.tool.repository;
import java.nio.file.Path;
import java.util.Collection;
diff --git a/cli/src/main/java/com/devonfw/tools/ide/util/FilenameUtil.java b/cli/src/main/java/com/devonfw/tools/ide/util/FilenameUtil.java
index ffaf36e44..8a83221ef 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/util/FilenameUtil.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/util/FilenameUtil.java
@@ -1,5 +1,6 @@
package com.devonfw.tools.ide.util;
+import java.nio.file.Path;
import java.util.Locale;
/**
@@ -12,6 +13,43 @@ private FilenameUtil() {
// construction forbidden
}
+ /**
+ * @param path the {@link Path} to the file or directory.
+ * @return the filename.
+ */
+ public static String getFilename(Path path) {
+
+ if (path == null) {
+ return null;
+ }
+ Path filename = path.getFileName();
+ if (filename == null) {
+ return null;
+ }
+ return filename.toString();
+ }
+
+ /**
+ * @param path the {@link Path} to the file or directory.
+ * @return the filename excluding a potential {@link #getExtension(String) extension}.
+ */
+ public static String getFilenameWithoutExtension(Path path) {
+
+ String filename = getFilename(path);
+ if (filename == null) {
+ return null;
+ }
+ String result = filename;
+ int lastDot = filename.lastIndexOf('.');
+ if (lastDot > 0) {
+ result = filename.substring(0, lastDot);
+ if (result.endsWith(".tar")) {
+ result = result.substring(0, result.length() - 4);
+ }
+ }
+ return result;
+ }
+
/**
* @param path the file or path name to get the extension from.
* @return the file extension excluding the dot from the given {@code path} or {@code null} if no extension is present.
diff --git a/cli/src/main/package/internal/eclipse-import.groovy b/cli/src/main/package/internal/eclipse-import.groovy
new file mode 100644
index 000000000..b6b2a7137
--- /dev/null
+++ b/cli/src/main/package/internal/eclipse-import.groovy
@@ -0,0 +1,153 @@
+import org.apache.tools.ant.ExitStatusException
+import org.eclipse.core.resources.IProject
+import org.eclipse.core.runtime.IPath
+import org.eclipse.core.runtime.Platform
+import org.eclipse.core.runtime.jobs.IJobManager
+import org.eclipse.core.runtime.jobs.Job
+import org.eclipse.jdt.internal.ui.workingsets.IWorkingSetIDs
+import org.eclipse.ui.IWorkingSet
+import org.eclipse.ui.IWorkingSetManager
+import org.eclipse.ui.PlatformUI
+import org.eclipse.ui.internal.wizards.datatransfer.RecursiveImportListener
+import org.eclipse.ui.internal.wizards.datatransfer.SmartImportJob
+import org.eclipse.ui.wizards.datatransfer.ProjectConfigurator
+
+class SysoutProgressMonitor extends org.eclipse.core.runtime.NullProgressMonitor {
+ private String myName;
+
+ public void setTaskName(String name) {
+ this.myName = name;
+ }
+
+ public void beginTask(String name, int totalWork) {
+ System.out.println(myName + " :Begin Task: " + name + "(" + totalWork + ")");
+ }
+}
+
+class SysoutListener implements RecursiveImportListener {
+ public void projectCreated(IProject project) {
+ System.out.println("Created " + project);
+ }
+
+ public void projectConfigured(IProject project, ProjectConfigurator configurator) {
+ System.out.println("Configured " + project + " using " + configurator);
+ }
+
+ public void errorHappened(IPath location, Exception ex) {
+ System.out.println("Error: " + location);
+ ex.printStackTrace();
+ }
+}
+
+class MyWorkbenchAdvisor extends org.eclipse.ui.application.WorkbenchAdvisor {
+ private Map antProperties;
+
+ public MyWorkbenchAdvisor(Map properties) {
+ this.antProperties = properties;
+ }
+
+ public String getInitialWindowPerspectiveId() {
+ return null;
+ }
+
+ private Set getOrCreateWorkingSets(Collection workingSetNames) {
+ IWorkingSetManager workingSetManager = PlatformUI.getWorkbench().getWorkingSetManager();
+ Set workingSets = new HashSet();
+ for (String workingSetName : workingSetNames) {
+ IWorkingSet workingSet = workingSetManager.getWorkingSet(workingSetName);
+ if (workingSet == null) {
+ workingSet = workingSetManager.createWorkingSet(workingSetName, new IProject[0]);
+ workingSet.setId(IWorkingSetIDs.RESOURCE);
+ workingSetManager.addWorkingSet(workingSet);
+ }
+ workingSets.add(workingSet);
+ }
+ return workingSets;
+ }
+
+ private void doImport(String projectDirectoryName, Collection workingSetNames) {
+ System.out.println("Importing " + projectDirectoryName + " for working sets " + workingSetNames);
+ File projectDirectory = new File(projectDirectoryName);
+ if (!projectDirectory.canRead()) {
+ throw new IllegalStateException("Cannot open project directory " + projectDirectoryName);
+ }
+ SmartImportJob importJob = new SmartImportJob(projectDirectory, getOrCreateWorkingSets(workingSetNames), true, true);
+ Map proposals = importJob.getImportProposals(null);
+ importJob.setDirectoriesToImport(proposals.keySet());
+ importJob.setListener(new SysoutListener());
+ importJob.run(null);
+
+ waitForJobs();
+ }
+
+ private void waitForJobs() {
+ // Class names of jobs to wait for
+ List importantJobClassNames = Arrays.asList(
+ "org.eclipse.m2e.importer.internal.MavenProjectConfigurator\$UpdateMavenConfigurationJob",
+ "org.eclipse.m2e.core.internal.project.registry.ProjectRegistryRefreshJob",
+ "org.eclipse.core.internal.events.AutoBuildJob",
+ );
+ System.out.println("Wating for jobs to finish...");
+ IJobManager jobMan = Job.getJobManager();
+ boolean importantJobsRunning;
+ do {
+ importantJobsRunning = false;
+ System.out.println("Still running:");
+ Job[] jobs = jobMan.find(null);
+ for (Job job : jobs) {
+ importantJobsRunning |= (importantJobClassNames.contains(job.getClass().getName()));
+ System.out.println(job.toString() + ": " + job.getClass().getName());
+ }
+ Thread.sleep(3000);
+ } while (importantJobsRunning);
+ }
+
+ public void preStartup() {
+ try {
+ // Get path from ant properties
+ String path = antProperties.get("devonImportPath");
+ if (path == null || path.equals("")) {
+ throw new IllegalStateException("Parameter repositoryImportPath must be set.");
+ }
+
+ // Get workingsets from ant properties
+ Collection workingSetNames = Collections.EMPTY_LIST;
+ String workingSetParam = antProperties.get("repositoryImportWorkingSet");
+ if (workingSetParam != null && !workingSetParam.equals("")) {
+ workingSetNames = Arrays.asList(workingSetParam.split(","));
+ }
+
+ // Actually start import
+ doImport(path, workingSetNames);
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw e;
+ }
+ }
+
+ public void postStartup() {
+ PlatformUI.getWorkbench().close();
+ }
+}
+// Groovy Script startes here...
+System.out.println("Preparing eclipse instance for import...");
+
+if (Platform.getInstanceLocation().isLocked()) {
+ throw new ExitStatusException("Workspace is locked", PlatformUI.RETURN_UNSTARTABLE);
+}
+
+// We are running in the context of antrunner. Import, Workingset etc. need some
+// parts of the normal IDE infratructure running. So we init it here.
+
+// Register Adapters, e.g. to allow persistence of workingsets work (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=513188)
+org.eclipse.ui.ide.IDE.registerAdapters();
+
+// Create Workbench, this make the eclispe windows appear, but it is necessary for the importer job.
+display = PlatformUI.createDisplay();
+
+// 'properties' will be automatically populated by groovy-ant-task, pass it to the importer
+int rc = PlatformUI.createAndRunWorkbench(display, new MyWorkbenchAdvisor(properties));
+if (rc != PlatformUI.RETURN_OK) {
+ throw new ExitStatusException("Import failed", rc);
+}
+System.out.println("End.");
diff --git a/cli/src/main/package/internal/eclipse-import.xml b/cli/src/main/package/internal/eclipse-import.xml
new file mode 100644
index 000000000..ca102c15c
--- /dev/null
+++ b/cli/src/main/package/internal/eclipse-import.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cli/src/test/java/com/devonfw/tools/ide/commandlet/RepositoryCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/commandlet/RepositoryCommandletTest.java
index 06269ba36..1a1305ca8 100644
--- a/cli/src/test/java/com/devonfw/tools/ide/commandlet/RepositoryCommandletTest.java
+++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/RepositoryCommandletTest.java
@@ -1,7 +1,5 @@
package com.devonfw.tools.ide.commandlet;
-import java.io.IOException;
-import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Properties;
@@ -10,77 +8,100 @@
import com.devonfw.tools.ide.context.AbstractIdeContextTest;
import com.devonfw.tools.ide.context.IdeContext;
import com.devonfw.tools.ide.context.IdeTestContext;
+import com.devonfw.tools.ide.git.repository.RepositoryCommandlet;
+import com.devonfw.tools.ide.io.FileAccess;
+import com.devonfw.tools.ide.log.IdeLogEntry;
+import com.devonfw.tools.ide.log.IdeLogLevel;
public class RepositoryCommandletTest extends AbstractIdeContextTest {
- IdeTestContext context = newContext(PROJECT_BASIC);
-
private static final String PROPERTIES_FILE = "test.properties";
+ private static final String TEST_WORKSPACE = "test-workspace";
+ private static final String TEST_BRANCH = "test-branch";
+ public static final String TEST_REPO = "test-repo";
+ public static final String TEST_GIT_REPO = "test-git-repo";
+
+ /** {@link Properties} of the repository to test. */
+ private final Properties properties;
+
+ private final IdeTestContext context;
+
+ private final Path repositoryTestProperties;
/**
- * Properties object used to write key-value pairs to the properties file "test.properties"
+ * The constructor.
*/
- Properties properties = new Properties();
+ public RepositoryCommandletTest() {
+ super();
+ // actually this should be done in JUnit BeforeAll (setup)
+ // the best way is to avoid such state and just use local variables and private methods in test methods.
+ this.properties = new Properties();
+ this.properties.setProperty("path", TEST_REPO);
+ this.properties.setProperty("workingsets", "test");
+ this.properties.setProperty("workspace", TEST_WORKSPACE);
+ this.properties.setProperty("git_url", "https://github.com/devonfw/" + TEST_GIT_REPO + ".git");
+ this.properties.setProperty("git_branch", TEST_BRANCH);
+ this.properties.setProperty("build_path", ".");
+ this.properties.setProperty("build_cmd", "");
+ this.properties.setProperty("active", "false");
+ this.context = newContext(PROJECT_BASIC);
+ this.repositoryTestProperties = this.context.getSettingsPath().resolve(IdeContext.FOLDER_REPOSITORIES).resolve(PROPERTIES_FILE);
+ }
@Test
- public void testRunWithSpecificRepository() {
+ public void testSetupSpecificRepository() {
// arrange
RepositoryCommandlet rc = this.context.getCommandletManager().getCommandlet(RepositoryCommandlet.class);
- createPropertiesFile();
- rc.repository.setValueAsString(PROPERTIES_FILE, this.context);
+ saveProperties();
+ rc.repository.setValueAsString("test", this.context);
// act
rc.run();
// assert
- assertThat(this.context).logAtInfo().hasMessage("Importing repository from " + PROPERTIES_FILE + " ...");
- assertThat(context.getIdeHome().resolve("workspaces").resolve("test")).exists();
+ assertThat(context.getIdeHome().resolve(IdeContext.FOLDER_WORKSPACES).resolve(TEST_WORKSPACE).resolve(TEST_REPO)).isDirectory();
+ assertThat(this.context).logAtSuccess().hasMessage("Successfully ended step 'Setup of repository test'.");
}
@Test
- public void testRunWithNoSpecificRepositoryAndInactive() {
+ public void testSetupAllRepositoriesInactive() {
// arrange
RepositoryCommandlet rc = this.context.getCommandletManager().getCommandlet(RepositoryCommandlet.class);
- createPropertiesFile();
+ saveProperties();
// act
rc.run();
// assert
- assertThat(this.context).logAtInfo().hasEntries("Importing repository from " + PROPERTIES_FILE + " ...",
- "Skipping repository - use force (-f) to setup all repositories ...");
+ assertThat(this.context).log().hasEntries(new IdeLogEntry(IdeLogLevel.STEP, "Start: Setup of repository test"),
+ new IdeLogEntry(IdeLogLevel.INFO, "Skipping repository test because it is not active - use --force to setup all repositories ..."));
}
@Test
- public void testRunInvalidConfigurationNoPath() {
+ public void testSetupSpecificRepositoryWithoutPath() {
// arrange
RepositoryCommandlet rc = this.context.getCommandletManager().getCommandlet(RepositoryCommandlet.class);
- createPropertiesFile();
this.properties.setProperty("path", "");
- this.properties.setProperty("git_url", "test");
- saveProperties(this.properties);
+ saveProperties();
rc.repository.setValueAsString(PROPERTIES_FILE, this.context);
// act
rc.run();
// assert
- assertThat(this.context).logAtWarning()
- .hasMessage("Invalid repository configuration " + PROPERTIES_FILE + " - both 'path' and 'git-url' have to be defined.");
+ assertThat(this.context.getIdeHome().resolve(IdeContext.FOLDER_WORKSPACES).resolve(TEST_WORKSPACE).resolve("test")).isDirectory();
}
@Test
- public void testRunInvalidConfigurationNoGiturl() {
+ public void testSetupSpecificRepositoryFailsWithoutGitUrl() {
// arrange
RepositoryCommandlet rc = this.context.getCommandletManager().getCommandlet(RepositoryCommandlet.class);
- createPropertiesFile();
- this.properties.setProperty("path", "test");
this.properties.setProperty("git_url", "");
- saveProperties(this.properties);
+ saveProperties();
rc.repository.setValueAsString(PROPERTIES_FILE, this.context);
// act
rc.run();
// assert
- assertThat(this.context).logAtWarning()
- .hasMessage("Invalid repository configuration " + PROPERTIES_FILE + " - both 'path' and 'git-url' have to be defined.");
+ assertThat(this.context).logAtError()
+ .hasMessage("The properties file " + this.repositoryTestProperties + " must have a non-empty value for the required property git_url");
}
@Test
@@ -96,37 +117,10 @@ public void testRunNoRepositoriesOrProjectsFolderFound() {
assertThat(this.context).logAtWarning().hasMessage("Cannot find folder 'repositories' nor 'projects' in your settings.");
}
- private void createPropertiesFile() {
-
- try {
- this.properties.setProperty("path", "test");
- this.properties.setProperty("workingsets", "test");
- this.properties.setProperty("workspace", "test");
- this.properties.setProperty("git_url", "test");
- this.properties.setProperty("git_branch", "test");
- this.properties.setProperty("build_path", "test");
- this.properties.setProperty("build_cmd", "");
- this.properties.setProperty("active", "false");
-
- Path propertiesPath = this.context.getSettingsPath().resolve(IdeContext.FOLDER_REPOSITORIES)
- .resolve(PROPERTIES_FILE);
- this.context.getFileAccess().mkdirs(propertiesPath.getParent());
- Files.createFile(propertiesPath);
- saveProperties(this.properties);
- } catch (IOException e) {
- throw new IllegalStateException("Failed to create properties file during tests.", e);
- }
-
- }
-
- private void saveProperties(Properties properties) {
+ private void saveProperties() {
- Path propertiesPath = this.context.getSettingsPath().resolve(IdeContext.FOLDER_REPOSITORIES)
- .resolve(PROPERTIES_FILE);
- try (var output = Files.newOutputStream(propertiesPath)) {
- properties.store(output, null);
- } catch (IOException e) {
- throw new IllegalStateException("Failed to save properties file during tests.", e);
- }
+ FileAccess fileAccess = this.context.getFileAccess();
+ fileAccess.mkdirs(this.repositoryTestProperties.getParent());
+ fileAccess.writeProperties(this.properties, this.repositoryTestProperties);
}
}
diff --git a/cli/src/test/java/com/devonfw/tools/ide/commandlet/UpdateCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/commandlet/UpdateCommandletTest.java
index ff651acbc..27030b7a3 100644
--- a/cli/src/test/java/com/devonfw/tools/ide/commandlet/UpdateCommandletTest.java
+++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/UpdateCommandletTest.java
@@ -3,12 +3,18 @@
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.Arrays;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
import com.devonfw.tools.ide.context.AbstractIdeContextTest;
import com.devonfw.tools.ide.context.IdeContext;
import com.devonfw.tools.ide.context.IdeTestContext;
+import com.devonfw.tools.ide.environment.EnvironmentVariables;
+import com.devonfw.tools.ide.environment.EnvironmentVariablesType;
+import com.devonfw.tools.ide.variable.IdeVariables;
/**
* Test of {@link UpdateCommandlet}.
@@ -33,6 +39,25 @@ public void testRunPullSettingsAndUpdateSoftware() {
assertThat(context.getSoftwarePath().resolve("mvn")).exists();
}
+ @ParameterizedTest
+ @ValueSource(strings = { "", "eclipse", "intellij", "eclipse,intellij", "intellij , vscode", "eclipse, intellij,vscode" })
+ public void testIdeUpdateCreatesStartScripts(String createStartScripts) {
+
+ // arrange
+ IdeTestContext context = newContext(PROJECT_UPDATE);
+ EnvironmentVariables settings = context.getVariables().getByType(EnvironmentVariablesType.SETTINGS);
+ settings.set(IdeVariables.CREATE_START_SCRIPTS.getName(), createStartScripts);
+ settings.save();
+ UpdateCommandlet uc = context.getCommandletManager().getCommandlet(UpdateCommandlet.class);
+ String[] activeIdes = Arrays.stream(createStartScripts.split(",")).map(String::trim).filter(ide -> !ide.isEmpty()).toArray(String[]::new);
+ // act
+ uc.run();
+
+ // assert
+ assertThat(context).logAtSuccess().hasMessage("Successfully updated settings repository.");
+ verifyStartScriptsForAllWorkspacesAndAllIdes(context, activeIdes);
+ }
+
@Test
public void testRunTemplatesNotFound() throws IOException {
diff --git a/cli/src/test/java/com/devonfw/tools/ide/commandlet/UpgradeSettingsCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/commandlet/UpgradeSettingsCommandletTest.java
index 23b496aa7..da6dafd8e 100644
--- a/cli/src/test/java/com/devonfw/tools/ide/commandlet/UpgradeSettingsCommandletTest.java
+++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/UpgradeSettingsCommandletTest.java
@@ -7,9 +7,9 @@
import com.devonfw.tools.ide.context.AbstractIdeContextTest;
import com.devonfw.tools.ide.context.IdeContext;
import com.devonfw.tools.ide.context.IdeTestContext;
-import com.devonfw.tools.ide.repo.CustomToolJson;
-import com.devonfw.tools.ide.repo.CustomToolsJson;
-import com.devonfw.tools.ide.repo.CustomToolsJsonMapper;
+import com.devonfw.tools.ide.tool.repository.CustomToolJson;
+import com.devonfw.tools.ide.tool.repository.CustomToolsJson;
+import com.devonfw.tools.ide.tool.repository.CustomToolsJsonMapper;
/**
* Integration test of {@link UpgradeSettingsCommandlet} .
@@ -79,7 +79,7 @@ private void verifyUpdateLegacyFolders() {
assertThat(UPGRADE_SETTINGS_PATH.resolve("settings/templates/conf/ide.properties")).exists();
assertThat(UPGRADE_SETTINGS_PATH.resolve("settings/templates/conf/mvn/settings.xml")).exists();
}
-
+
private void verifyUpdateWorkspaceTemplates() {
// arrange
UpgradeSettingsCommandlet upgradeSettingsCommandlet = new UpgradeSettingsCommandlet(context);
diff --git a/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeContextTest.java b/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeContextTest.java
index 943145513..de55f55ed 100644
--- a/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeContextTest.java
+++ b/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeContextTest.java
@@ -2,7 +2,9 @@
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import org.assertj.core.api.Assertions;
@@ -11,13 +13,15 @@
import com.devonfw.tools.ide.io.FileCopyMode;
import com.devonfw.tools.ide.io.IdeProgressBarTestImpl;
import com.devonfw.tools.ide.log.IdeLogLevel;
-import com.devonfw.tools.ide.repo.ToolRepositoryMock;
+import com.devonfw.tools.ide.tool.repository.ToolRepositoryMock;
/**
* Abstract base class for tests that need mocked instances of {@link IdeContext}.
*/
public abstract class AbstractIdeContextTest extends Assertions {
+ private static final Set IDES = Set.of("eclipse", "intellij", "vscode", "android-studio");
+
/** {@link #newContext(String) Name of test project} {@value}. */
protected static final String PROJECT_BASIC = "basic";
@@ -217,4 +221,63 @@ protected static void assertProgressBar(IdeContext context, String taskName, lon
long restSize = maxSize % CHUNK_SIZE;
assertProgressBar(context, taskName, maxSize, CHUNK_SIZE, chunkCount, restSize);
}
+
+ /**
+ * @param context the {@link IdeContext} that was created via the {@link #newContext(String) newContext} method.
+ * @param activeIdes the names of the IDE tools (e.g. "intellij", "eclipse").
+ */
+ protected void verifyStartScriptsForAllWorkspacesAndAllIdes(IdeContext context, String... activeIdes) {
+
+ verifyStartScriptsForAllWorkspaces(context, true, activeIdes);
+ Set inactiveIdes = new HashSet<>(IDES);
+ inactiveIdes.removeAll(Set.of(activeIdes));
+ verifyStartScriptsForAllWorkspaces(context, false, inactiveIdes.toArray(String[]::new));
+ }
+
+ /**
+ * @param context the {@link IdeContext} that was created via the {@link #newContext(String) newContext} method.
+ * @param ides the names of the IDE tools (e.g. "intellij", "eclipse").
+ */
+ protected void verifyStartScriptsForAllWorkspaces(IdeContext context, String... ides) {
+
+ verifyStartScriptsForAllWorkspaces(context, true, ides);
+ }
+
+ /**
+ * @param context the {@link IdeContext} that was created via the {@link #newContext(String) newContext} method.
+ * @param exists - {@code true} to check if the script exists, {@code false} otherwise (check if not exists).
+ * @param ides the names of the IDE tools (e.g. "intellij", "eclipse").
+ */
+ protected void verifyStartScriptsForAllWorkspaces(IdeContext context, boolean exists, String... ides) {
+
+ Path workspaces = context.getIdeHome().resolve(IdeContext.FOLDER_WORKSPACES);
+ for (String ide : ides) {
+ for (Path workspace : context.getFileAccess().listChildren(workspaces, Files::isDirectory)) {
+ verifyStartScript(context, ide, workspace.getFileName().toString(), exists);
+ }
+ }
+ }
+
+ /**
+ * @param context the {@link IdeContext} that was created via the {@link #newContext(String) newContext} method.
+ * @param ide the name of the IDE tool (e.g. "intellij").
+ * @param workspace the name of the workspace (e.g. "main").
+ * @param exists - {@code true} to check if the script exists, {@code false} otherwise (check if not exists).
+ */
+ protected void verifyStartScript(IdeContext context, String ide, String workspace, boolean exists) {
+
+ String scriptExtension;
+ if (context.getSystemInfo().isWindows()) {
+ scriptExtension = ".bat";
+ } else {
+ scriptExtension = ".sh";
+ }
+ Path scriptPath = context.getIdeHome().resolve(ide + "-" + workspace + scriptExtension);
+ if (exists) {
+ assertThat(scriptPath).exists();
+ } else {
+ assertThat(scriptPath).doesNotExist();
+ }
+ }
+
}
diff --git a/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeTestContext.java b/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeTestContext.java
index 35059a819..3d1ac2391 100644
--- a/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeTestContext.java
+++ b/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeTestContext.java
@@ -20,7 +20,7 @@
import com.devonfw.tools.ide.log.IdeLogger;
import com.devonfw.tools.ide.os.SystemInfo;
import com.devonfw.tools.ide.process.ProcessContext;
-import com.devonfw.tools.ide.repo.ToolRepository;
+import com.devonfw.tools.ide.tool.repository.ToolRepository;
import com.devonfw.tools.ide.url.model.UrlMetadata;
import com.devonfw.tools.ide.variable.IdeVariables;
diff --git a/cli/src/test/java/com/devonfw/tools/ide/tool/mvn/MvnArtifactTest.java b/cli/src/test/java/com/devonfw/tools/ide/tool/mvn/MvnArtifactTest.java
new file mode 100644
index 000000000..ee5adf4f6
--- /dev/null
+++ b/cli/src/test/java/com/devonfw/tools/ide/tool/mvn/MvnArtifactTest.java
@@ -0,0 +1,37 @@
+package com.devonfw.tools.ide.tool.mvn;
+
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test of {@link MvnArtifact}.
+ */
+public class MvnArtifactTest extends Assertions {
+
+ /**
+ * Test of {@link MvnArtifact#getPath()}.
+ */
+ @Test
+ public void testIdeasyCli() {
+
+ // arrange
+ String version = "2025.12.001";
+ String type = "tar.gz";
+ String classifier = "windows-arm64";
+ // act
+ MvnArtifact artifact = MvnArtifact.ofIdeasyCli(version, type, classifier);
+ MvnArtifact equal = new MvnArtifact(MvnArtifact.GROUP_ID_IDEASY, MvnArtifact.ARTIFACT_ID_IDEASY_CLI, version, type, classifier);
+ // assert
+ assertThat(artifact.getGroupId()).isEqualTo("com.devonfw.tools.IDEasy");
+ assertThat(artifact.getArtifactId()).isEqualTo("ide-cli");
+ assertThat(artifact.getVersion()).isEqualTo(version);
+ assertThat(artifact.getType()).isEqualTo(type);
+ assertThat(artifact.getClassifier()).isEqualTo(classifier);
+ assertThat(artifact.getPath()).isEqualTo("com/devonfw/tools/IDEasy/ide-cli/2025.12.001/ide-cli-2025.12.001-windows-arm64.tar.gz");
+ assertThat(artifact).hasToString("com.devonfw.tools.IDEasy:ide-cli:2025.12.001:tar.gz:windows-arm64");
+ assertThat(artifact.getKey()).isEqualTo(artifact.toString());
+ assertThat(artifact).isEqualTo(equal);
+ assertThat(artifact.hashCode()).isEqualTo(equal.hashCode());
+ }
+
+}
diff --git a/cli/src/test/java/com/devonfw/tools/ide/repo/CustomToolMetadataTest.java b/cli/src/test/java/com/devonfw/tools/ide/tool/repository/CustomToolMetadataTest.java
similarity index 97%
rename from cli/src/test/java/com/devonfw/tools/ide/repo/CustomToolMetadataTest.java
rename to cli/src/test/java/com/devonfw/tools/ide/tool/repository/CustomToolMetadataTest.java
index d98183157..32668148f 100644
--- a/cli/src/test/java/com/devonfw/tools/ide/repo/CustomToolMetadataTest.java
+++ b/cli/src/test/java/com/devonfw/tools/ide/tool/repository/CustomToolMetadataTest.java
@@ -1,4 +1,4 @@
-package com.devonfw.tools.ide.repo;
+package com.devonfw.tools.ide.tool.repository;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
diff --git a/cli/src/test/java/com/devonfw/tools/ide/repo/CustomToolRepositoryTest.java b/cli/src/test/java/com/devonfw/tools/ide/tool/repository/CustomToolRepositoryTest.java
similarity index 94%
rename from cli/src/test/java/com/devonfw/tools/ide/repo/CustomToolRepositoryTest.java
rename to cli/src/test/java/com/devonfw/tools/ide/tool/repository/CustomToolRepositoryTest.java
index 35dfeb2f6..c1fc34637 100644
--- a/cli/src/test/java/com/devonfw/tools/ide/repo/CustomToolRepositoryTest.java
+++ b/cli/src/test/java/com/devonfw/tools/ide/tool/repository/CustomToolRepositoryTest.java
@@ -1,4 +1,4 @@
-package com.devonfw.tools.ide.repo;
+package com.devonfw.tools.ide.tool.repository;
import org.assertj.core.api.Assertions;
diff --git a/cli/src/test/java/com/devonfw/tools/ide/repo/CustomToolsJsonMapperTest.java b/cli/src/test/java/com/devonfw/tools/ide/tool/repository/CustomToolsJsonMapperTest.java
similarity index 99%
rename from cli/src/test/java/com/devonfw/tools/ide/repo/CustomToolsJsonMapperTest.java
rename to cli/src/test/java/com/devonfw/tools/ide/tool/repository/CustomToolsJsonMapperTest.java
index 97068c515..29a25c867 100644
--- a/cli/src/test/java/com/devonfw/tools/ide/repo/CustomToolsJsonMapperTest.java
+++ b/cli/src/test/java/com/devonfw/tools/ide/tool/repository/CustomToolsJsonMapperTest.java
@@ -1,4 +1,4 @@
-package com.devonfw.tools.ide.repo;
+package com.devonfw.tools.ide.tool.repository;
import java.nio.file.Path;
import java.util.ArrayList;
diff --git a/cli/src/test/java/com/devonfw/tools/ide/repo/ToolRepositoryMock.java b/cli/src/test/java/com/devonfw/tools/ide/tool/repository/ToolRepositoryMock.java
similarity index 98%
rename from cli/src/test/java/com/devonfw/tools/ide/repo/ToolRepositoryMock.java
rename to cli/src/test/java/com/devonfw/tools/ide/tool/repository/ToolRepositoryMock.java
index 76eafa44f..32f0851b1 100644
--- a/cli/src/test/java/com/devonfw/tools/ide/repo/ToolRepositoryMock.java
+++ b/cli/src/test/java/com/devonfw/tools/ide/tool/repository/ToolRepositoryMock.java
@@ -1,4 +1,4 @@
-package com.devonfw.tools.ide.repo;
+package com.devonfw.tools.ide.tool.repository;
import java.io.IOException;
import java.nio.file.Files;
diff --git a/cli/src/test/java/com/devonfw/tools/ide/util/FilenameUtilTest.java b/cli/src/test/java/com/devonfw/tools/ide/util/FilenameUtilTest.java
index 559ca482b..eb6d9b773 100644
--- a/cli/src/test/java/com/devonfw/tools/ide/util/FilenameUtilTest.java
+++ b/cli/src/test/java/com/devonfw/tools/ide/util/FilenameUtilTest.java
@@ -1,5 +1,7 @@
package com.devonfw.tools.ide.util;
+import java.nio.file.Path;
+
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
@@ -29,4 +31,14 @@ public void testGetExtension() {
assertThat(FilenameUtil.getExtension("https://server.com/folder.zip/file.tar.bz2")).isEqualTo("tar.bz2");
}
+ /**
+ * Test of {@link FilenameUtil#getFilenameWithoutExtension(Path)}
+ */
+ @Test
+ public void testGetFilenameWithoutExtension() {
+
+ assertThat(FilenameUtil.getFilenameWithoutExtension(Path.of("foo/bar.some/file.name.extension"))).isEqualTo("file.name");
+ assertThat(FilenameUtil.getFilenameWithoutExtension(Path.of("foo/bar.some/file.name.tar.gz"))).isEqualTo("file.name");
+ }
+
}
diff --git a/documentation/DoD.adoc b/documentation/DoD.adoc
index 86aa996c9..4bb151113 100644
--- a/documentation/DoD.adoc
+++ b/documentation/DoD.adoc
@@ -28,7 +28,7 @@ In case you see `This branch has conflicts that must be resolved` instead, you n
Very simple conflicts may be resolved in the browser on github.
But as a general recommendation you should resolve the conflicts locally with proper merge tool support and rerun tests before you push the merged changes.
* [ ] You followed all link:coding-conventions.adoc[coding conventions]
-* [ ] You have already added the issue implemented by your PR in https://github.com/devonfw/ide/blob/master/CHANGELOG.adoc[CHANGELOG.adoc] to the next open release (see milestones or https://github.com/devonfw/IDEasy/blob/main/.mvn/maven.config[maven.config]).
+* [ ] You have already added the issue implemented by your PR in https://github.com/devonfw/IDEasy/blob/main/CHANGELOG.adoc[CHANGELOG.adoc] to the next open release (see milestones or https://github.com/devonfw/IDEasy/blob/main/.mvn/maven.config[maven.config]).
If there is no issue for your PR consider creating it or directly link the PR itself.
Please note that the CHANGELOG shall only reflect public changes relevant for end-users.
So e.g. if we implement a story and then add another PR as bugfix or improvement to the same story before the bug was ever released, we do not need to document this in the CHANGELOG to avoid spam and confusion.