diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index da7684b4f..1600fd196 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -18,10 +18,5 @@ jobs:
java-version: '17'
- name: Build project with Maven
run: mvn -B -ntp -Dstyle.color=always install
- - name: Deploy to OSSRH nexus
- env:
- SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
- SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
- run: mvn --settings .mvn/settings.xml -DskipTests=true -Darchetype.test.skip=true -Dmaven.install.skip=true -Dstyle.color=always -B -ntp deploy
- name: Coveralls GitHub Action
- uses: coverallsapp/github-action@v2.2.3
\ No newline at end of file
+ uses: coverallsapp/github-action@v2.2.3
diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml
index d696fe129..39d458745 100644
--- a/.github/workflows/nightly-build.yml
+++ b/.github/workflows/nightly-build.yml
@@ -1,5 +1,6 @@
-name: Nightly CI Build and Snapshot Release
-on:
+name: Nightly Build
+on:
+ workflow_dispatch:
schedule:
- cron: '0 2 * * *'
@@ -13,7 +14,7 @@ jobs:
- uses: actions/checkout@v3
- id: verify_commit
name: Verify latest commit is less than 24 hours
- if: ${{ github.event_name == 'schedule' }}
+ if: ${{ (github.event_name == 'schedule') || (github.event_name == 'workflow_dispatch') }}
run: echo '::set-output name=RUN_BUILD::'$(test -n "$(git log --format=%H --since='24 hours ago')" && echo 'true' || echo 'false')
build-natives:
@@ -32,7 +33,7 @@ jobs:
- uses: graalvm/setup-graalvm@v1
with:
- java-version: '17.0.7'
+ java-version: '21.0.2'
distribution: 'graalvm'
github-token: ${{ secrets.GITHUB_TOKEN }}
native-image-job-reports: 'true'
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 97aea20eb..382839581 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -1,4 +1,4 @@
-name: Build and deploy a release including native-images
+name: Release
# Runs manually
on:
@@ -19,7 +19,7 @@ jobs:
- uses: graalvm/setup-graalvm@v1
with:
- java-version: '17.0.7'
+ java-version: '21.0.2'
distribution: 'graalvm'
github-token: ${{ secrets.GITHUB_TOKEN }}
native-image-job-reports: 'true'
@@ -75,8 +75,9 @@ jobs:
git commit -m "set release version to ${next_version}"
git tag -a "release/${next_version}" -m "tagged version ${next_version}"
export GPG_TTY=$TTY
- mvn --settings .mvn/settings.xml -B -ntp deploy -Pdeploy -Dgpg.pin.entry.mode=loopback -Dgpg.passphrase=${{ secrets.GPG_PASSPHRASE }}
+ mkdir -p ./cli/target/
mv ./natives/* ./cli/target/
+ mvn --settings .mvn/settings.xml -B -ntp deploy -Pdeploy -Dgpg.pin.entry.mode=loopback -Dgpg.passphrase=${{ secrets.GPG_PASSPHRASE }}
env:
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
diff --git a/.mvn/maven.config b/.mvn/maven.config
index 0413a95ed..edd4b2730 100644
--- a/.mvn/maven.config
+++ b/.mvn/maven.config
@@ -1 +1 @@
--Drevision=2024.02.001-alpha-SNAPSHOT
+-Drevision=2024.03.001-alpha-SNAPSHOT
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.adoc
similarity index 100%
rename from CHANGELOG.asciidoc
rename to CHANGELOG.adoc
diff --git a/README.adoc b/README.adoc
index de1ef0d62..3810034dc 100644
--- a/README.adoc
+++ b/README.adoc
@@ -10,6 +10,7 @@ image:https://img.shields.io/github/license/devonfw/IDEasy.svg?label=License["Ap
image:https://img.shields.io/maven-central/v/com.devonfw.tools.ide/ide-cli.svg?label=Maven%20Central["Maven Central",link=https://search.maven.org/search?q=g:com.devonfw.tools.ide]
image:https://github.com/devonfw/IDEasy/actions/workflows/build.yml/badge.svg["Build Status",link="https://github.com/devonfw/IDEasy/actions/workflows/build.yml"]
image:https://github.com/devonfw/IDEasy/actions/workflows/update-urls.yml/badge.svg["Update URLS Status",link="https://github.com/devonfw/IDEasy/actions/workflows/update-urls.yml"]
+image:https://github.com/devonfw/IDEasy/actions/workflows/nightly-build.yml/badge.svg["Nightly Release", link="https://github.com/devonfw/IDEasy/actions/workflows/nightly-build.yml"]
image:https://coveralls.io/repos/github/devonfw/IDEasy/badge.svg?branch=main["Coverage Status",link="https://coveralls.io/github/devonfw/IDEasy?branch=main"]
toc::[]
@@ -17,36 +18,26 @@ toc::[]
== Setup
=== Prerequisites
-** Windows
+All you need before installing IDEasy is https://git-scm.com/download/[git].
+** Windows
*** On Windows you need to *download and install* https://git-scm.com/download/win[git for windows].
-
-*** You need a tool to extract `*.tar.gz` files (`tar` and `gzip`).
-+
-For example https://www.7-zip.org/[7-zip] or https://www.win-rar.com/[WinRar].
** Linux
-*** On Linux you need to install `curl` and `git` if you don't have them already
-+
-[source,terminal, .text-center]
-----
-sudo apt-get install git curl
-or
-sudo yum install git-core curl
-----
-
+*** On Linux you only need to install https://git-scm.com/download/linux[git for linux].
** MacOS
*** On MacOS you only need to download and install https://git-scm.com/download/mac[git for mac].
=== Install
-** Download the latest version of `devonfw-ide` from https://github.com/devonfw/IDEasy/releases[here] and extract it to a folder of your choice.
+** Download the latest version of `IDEasy` from https://github.com/devonfw/IDEasy/releases[here] and execute it.
+
(You can find all Releases in https://repo.maven.apache.org/maven2/com/devonfw/tools/IDEasy/ide-cli/[Maven Central])
** Run the `setup` in this folder (On Windows you can double click on `setup.bat`)
+
-A command prompt will open and the setup will start. The setup will ask for a settings URL. In case you do not have a settings URL for your project, you can just press enter to use the default settings.
+A command prompt will open and the setup will start. The setup will ask for a settings URL.
+In case you do not have a settings URL for your project, you can just press enter to use the default settings.
** After the setup is finished, you can close the command prompt and open a new one.
-** *You are done! You can now use the `devon` command in your terminal to use the devonfw-ide.*
+** *You are done! You can now use the `ide` command in your terminal to use the `IDEasy`.*
==== Video Tutorial
ifdef::env-github[]
@@ -63,23 +54,20 @@ See also our latest video https://vimeo.com/808368450/88d4af9d18[devon ide updat
== Documentation
-* link:documentation/features.asciidoc[Features]
-* link:documentation/setup.asciidoc[Download & Setup]
-* link:documentation/usage.asciidoc[Usage]
-* link:documentation/IDEasy-contribution-getting-started.asciidoc[Contribution]
-* link:documentation/configuration.asciidoc[Configuration]
-* link:documentation/structure.asciidoc[Structure]
-* link:documentation/cli.asciidoc[Command Line Interface]
-* link:documentation/variables.asciidoc[Variables]
-* link:documentation/scripts.asciidoc[Scripts]
-* link:documentation/settings.asciidoc[Settings]
-* link:documentation/software.asciidoc[Software Folder]
-* link:documentation/integration.asciidoc[Integration]
-* link:documentation/advanced-tooling.asciidoc[Advanced-tooling]
+* link:documentation/features.adoc[Features]
+* link:documentation/setup.adoc[Download & Setup]
+* link:documentation/usage.adoc[Usage]
+* link:documentation/IDEasy-contribution-getting-started.adoc[Contribution]
+* link:documentation/configuration.adoc[Configuration]
+* link:documentation/structure.adoc[Structure]
+* link:documentation/cli.adoc[Command Line Interface]
+* link:documentation/variables.adoc[Variables]
+* link:documentation/settings.adoc[Settings]
+* link:documentation/advanced-tooling.adoc[Advanced-tooling]
* link:documentation/[Documentation]
== Contribution Guidelines
-*If you want to contribute to `devon-ide` please read our https://github.com/devonfw/ide/blob/master/documentation/devonfw-ide-contribution-getting-started.asciidoc[Contribution Guidelines].*
+*If you want to contribute to `IDEasy` please read our https://github.com/devonfw/IDEasy/blob/main/documentation/IDEasy-contribution-getting-started.adoc[Contribution Guidelines].*
*We use https://github.com/devonfw/IDEasy/issues[GitHub Issues] to track bugs and submit feature requests.*
diff --git a/cli/pom.xml b/cli/pom.xml
index 03f342f93..9d9e62fb7 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -11,6 +11,7 @@
com.devonfw.tools.IDEasy
ide-cli
${revision}
+ ${project.artifactId}
com.devonfw.tools.ide.cli.Ideasy
ideasy
@@ -58,10 +59,14 @@
org.slf4j
slf4j-api
+ 2.0.12
+
ch.qos.logback
logback-classic
+ 1.5.3
+
@@ -103,6 +108,29 @@
+
+
+ maven-resources-plugin
+ 3.3.1
+
+
+ copy-resources
+ validate
+
+ copy-resources
+
+
+ ${project.build.directory}/package
+
+
+ src/main/package
+ true
+
+
+
+
+
+
@@ -125,17 +153,17 @@
- target/${project.artifactId}-linux-x64.tar.gz
+ ${project.build.directory}/${project.artifactId}-linux-x64.tar.gz
tar.gz
linux
- target/${project.artifactId}-windows-x64.tar.gz
+ ${project.build.directory}/${project.artifactId}-windows-x64.tar.gz
tar.gz
windows
- target/${project.artifactId}-mac-x64.tar.gz
+ ${project.build.directory}/${project.artifactId}-mac-x64.tar.gz
tar.gz
mac
@@ -179,9 +207,7 @@
true
-
- ${imageName}
-
+ ${imageName}
--enable-url-protocols=https
-H:IncludeResources="nls/.*"
@@ -189,6 +215,34 @@
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-documentation
+ prepare-package
+
+ copy
+
+
+
+
+
+
+ ${project.groupId}
+ ide-doc
+ ${project.version}
+ pdf
+ IDEasy-documentation.pdf
+
+
+ ${project.build.directory}/package
+ false
+ true
+
+
org.apache.maven.plugins
maven-assembly-plugin
@@ -203,7 +257,7 @@
${releaseName}
false
- /src/main/assembly/exec.xml
+ /src/main/assembly/release.xml
diff --git a/cli/src/main/assembly/exec.xml b/cli/src/main/assembly/release.xml
similarity index 78%
rename from cli/src/main/assembly/exec.xml
rename to cli/src/main/assembly/release.xml
index 7e8727689..f9804e359 100644
--- a/cli/src/main/assembly/exec.xml
+++ b/cli/src/main/assembly/release.xml
@@ -7,15 +7,7 @@
false
-
- ${project.build.directory}
- ./
-
- *
- */**
-
-
-
+
${project.build.directory}
./bin
@@ -23,5 +15,9 @@
${imageName}
+
+ ${project.build.directory}/package
+ .
+
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 15c5cdbe0..122ea7842 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.tool.aws.Aws;
import com.devonfw.tools.ide.tool.az.Azure;
import com.devonfw.tools.ide.tool.cobigen.Cobigen;
+import com.devonfw.tools.ide.tool.docker.Docker;
import com.devonfw.tools.ide.tool.eclipse.Eclipse;
import com.devonfw.tools.ide.tool.gcviewer.GcViewer;
import com.devonfw.tools.ide.tool.gh.Gh;
@@ -23,10 +24,13 @@
import com.devonfw.tools.ide.tool.kotlinc.KotlincNative;
import com.devonfw.tools.ide.tool.mvn.Mvn;
import com.devonfw.tools.ide.tool.node.Node;
+import com.devonfw.tools.ide.tool.npm.Npm;
import com.devonfw.tools.ide.tool.oc.Oc;
import com.devonfw.tools.ide.tool.quarkus.Quarkus;
+import com.devonfw.tools.ide.tool.sonar.Sonar;
import com.devonfw.tools.ide.tool.terraform.Terraform;
import com.devonfw.tools.ide.tool.vscode.Vscode;
+import com.devonfw.tools.ide.tool.jasypt.Jasypt;
/**
* Implementation of {@link CommandletManager}.
@@ -65,10 +69,12 @@ public CommandletManagerImpl(IdeContext context) {
add(new EditionSetCommandlet(context));
add(new EditionListCommandlet(context));
add(new VersionCommandlet(context));
+ add(new RepositoryCommandlet(context));
add(new Gh(context));
add(new Helm(context));
add(new Java(context));
add(new Node(context));
+ add(new Npm(context));
add(new Mvn(context));
add(new GcViewer(context));
add(new Gradle(context));
@@ -83,6 +89,9 @@ public CommandletManagerImpl(IdeContext context) {
add(new Aws(context));
add(new Cobigen(context));
add(new Jmc(context));
+ add(new Jasypt(context));
+ add(new Docker(context));
+ add(new Sonar(context));
}
private void add(Commandlet commandlet) {
diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/EnvironmentCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/EnvironmentCommandlet.java
index c997c7a05..b1bf15c2a 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/EnvironmentCommandlet.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/EnvironmentCommandlet.java
@@ -1,12 +1,12 @@
package com.devonfw.tools.ide.commandlet;
-import java.util.Collection;
-
-import com.devonfw.tools.ide.common.SystemPath;
import com.devonfw.tools.ide.context.IdeContext;
import com.devonfw.tools.ide.environment.VariableLine;
import com.devonfw.tools.ide.os.WindowsPathSyntax;
import com.devonfw.tools.ide.property.FlagProperty;
+import com.devonfw.tools.ide.variable.IdeVariables;
+
+import java.util.Collection;
/**
* {@link Commandlet} to print the environment variables.
@@ -37,7 +37,7 @@ public String getName() {
@Override
public boolean isIdeHomeRequired() {
- return false;
+ return true;
}
@Override
@@ -48,10 +48,14 @@ public void run() {
if (this.context.getSystemInfo().isWindows()) {
line = normalizeWindowsValue(line);
}
+ String lineValue = line.getValue();
+ if (IdeVariables.PATH.getName().equals(line.getName())) {
+ lineValue = this.context.getPath().toString(this.bash.isTrue());
+ }
+ lineValue = "\"" + lineValue + "\"";
+ line = line.withValue(lineValue);
this.context.info(line.toString());
}
- SystemPath path = this.context.getPath();
- this.context.info("export PATH={}", path.toString(this.bash.isTrue()));
}
VariableLine normalizeWindowsValue(VariableLine line) {
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
new file mode 100644
index 000000000..f216099a4
--- /dev/null
+++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java
@@ -0,0 +1,126 @@
+package com.devonfw.tools.ide.commandlet;
+
+import com.devonfw.tools.ide.context.IdeContext;
+import com.devonfw.tools.ide.property.PathProperty;
+import com.devonfw.tools.ide.property.RepositoryProperty;
+import com.devonfw.tools.ide.tool.ToolCommandlet;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+import static com.devonfw.tools.ide.commandlet.RepositoryConfig.loadProperties;
+
+/**
+ * {@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 = 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.getSettingsPath().resolve(IdeContext.FOLDER_REPOSITORIES);
+ Path legacyRepositoriesPath = this.context.getSettingsPath().resolve(IdeContext.FOLDER_LEGACY_REPOSITORIES);
+ Path repositories;
+ if (Files.exists(repositoriesPath)) {
+ repositories = repositoriesPath;
+ } else if (Files.exists(legacyRepositoriesPath)) {
+ repositories = legacyRepositoriesPath;
+ } else {
+ this.context.warning("Cannot find repositories folder nor projects folder.");
+ return;
+ }
+
+ List propertiesFiles = this.context.getFileAccess().listChildren(repositories,
+ 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 = 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 || "".equals(repository) || gitUrl == null || "".equals(gitUrl)) {
+ 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() != null ? repositoryConfig.workspace() : "main";
+ Path workspacePath = this.context.getIdeHome().resolve("workspaces").resolve(workspace);
+ this.context.getFileAccess().mkdirs(workspacePath);
+
+ Path repositoryPath = workspacePath.resolve(repository);
+ this.context.getGitContext().pullOrClone(gitUrl, repositoryConfig.gitBranch(), 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().getToolCommandlet(command[0]);
+ List args = new ArrayList<>(command.length - 1);
+ for (int i = 1; i < command.length; i++) {
+ args.add(command[i]);
+ }
+ commandlet.arguments.setValue(args);
+ 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
new file mode 100644
index 000000000..89dea973e
--- /dev/null
+++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryConfig.java
@@ -0,0 +1,65 @@
+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;
+
+/**
+ * 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) {
+ 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/common/SystemPath.java b/cli/src/main/java/com/devonfw/tools/ide/common/SystemPath.java
index f10c1bca6..b15c0d68a 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/common/SystemPath.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/common/SystemPath.java
@@ -1,5 +1,9 @@
package com.devonfw.tools.ide.common;
+import com.devonfw.tools.ide.context.IdeContext;
+import com.devonfw.tools.ide.os.SystemInfoImpl;
+import com.devonfw.tools.ide.variable.IdeVariables;
+
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
@@ -9,12 +13,20 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.regex.Pattern;
import java.util.stream.Stream;
-import com.devonfw.tools.ide.context.IdeContext;
-
/**
- * Represents the PATH variable in a structured way.
+ * Represents the PATH variable in a structured way. The PATH contains the system path entries together with the entries
+ * for the IDEasy tools. The generic system path entries are stored in a {@link List} ({@code paths}) and the tool
+ * entries are stored in a {@link Map} ({@code tool2pathMap}) as they can change dynamically at runtime (e.g. if a new
+ * tool is installed). As the tools must have priority the actual PATH is build by first the entries for the tools and
+ * then the generic entries from the system PATH. Such tool entries are ignored from the actual PATH of the
+ * {@link System#getenv(String) environment} at construction time and are recomputed from the "software" folder. This is
+ * important as the initial {@link System#getenv(String) environment} PATH entries can come from a different IDEasy
+ * project and the use may have changed projects before calling us again. Recomputing the PATH ensures side-effects from
+ * other projects. However, it also will ensure all the entries to IDEasy locations are automatically managed and
+ * therefore cannot be managed manually be the end-user.
*/
public class SystemPath {
@@ -33,24 +45,47 @@ public class SystemPath {
/**
* The constructor.
*
+ * @param context {@link IdeContext}.
+ */
+ public SystemPath(IdeContext context) {
+
+ this(context, System.getenv(IdeVariables.PATH.getName()));
+ }
+
+ /**
+ * The constructor.
+ *
+ * @param context {@link IdeContext}.
* @param envPath the value of the PATH variable.
- * @param softwarePath the {@link IdeContext#getSoftwarePath() software path}.
+ */
+ public SystemPath(IdeContext context, String envPath) {
+
+ this(context, envPath, context.getIdeRoot(), context.getSoftwarePath());
+ }
+
+ /**
+ * The constructor.
+ *
* @param context {@link IdeContext} for the output of information.
+ * @param envPath the value of the PATH variable.
+ * @param ideRoot the {@link IdeContext#getIdeRoot() IDE_ROOT}.
+ * @param softwarePath the {@link IdeContext#getSoftwarePath() software path}.
*/
- public SystemPath(String envPath, Path softwarePath, IdeContext context) {
+ public SystemPath(IdeContext context, String envPath, Path ideRoot, Path softwarePath) {
- this(envPath, softwarePath, File.pathSeparatorChar, context);
+ this(context, envPath, ideRoot, softwarePath, File.pathSeparatorChar);
}
/**
* The constructor.
*
+ * @param context {@link IdeContext} for the output of information.
* @param envPath the value of the PATH variable.
+ * @param ideRoot the {@link IdeContext#getIdeRoot() IDE_ROOT}.
* @param softwarePath the {@link IdeContext#getSoftwarePath() software path}.
* @param pathSeparator the path separator char (';' for Windows and ':' otherwise).
- * @param context {@link IdeContext} for the output of information.
*/
- public SystemPath(String envPath, Path softwarePath, char pathSeparator, IdeContext context) {
+ public SystemPath(IdeContext context, String envPath, Path ideRoot, Path softwarePath, char pathSeparator) {
super();
this.context = context;
@@ -61,17 +96,11 @@ public SystemPath(String envPath, Path softwarePath, char pathSeparator, IdeCont
String[] envPaths = envPath.split(Character.toString(pathSeparator));
for (String segment : envPaths) {
Path path = Path.of(segment);
- String tool = getTool(path, softwarePath);
+ String tool = getTool(path, ideRoot);
if (tool == null) {
this.paths.add(path);
- } else {
- Path duplicate = this.tool2pathMap.putIfAbsent(tool, path);
- if (duplicate != null) {
- context.warning("Duplicated tool path for tool: {} at path: {} with duplicated path: {}.", tool, path,
- duplicate);
}
}
- }
collectToolPath(softwarePath);
}
@@ -85,14 +114,14 @@ private void collectToolPath(Path softwarePath) {
Iterator iterator = children.iterator();
while (iterator.hasNext()) {
Path child = iterator.next();
- if (Files.isDirectory(child)) {
+ String tool = child.getFileName().toString();
+ if (!"extra".equals(tool) && Files.isDirectory(child)) {
Path toolPath = child;
Path bin = child.resolve("bin");
if (Files.isDirectory(bin)) {
toolPath = bin;
}
- this.paths.add(0, toolPath);
- this.tool2pathMap.put(child.getFileName().toString(), toolPath);
+ this.tool2pathMap.put(tool, toolPath);
}
}
} catch (IOException e) {
@@ -101,13 +130,13 @@ private void collectToolPath(Path softwarePath) {
}
}
- private static String getTool(Path path, Path softwarePath) {
+ private static String getTool(Path path, Path ideRoot) {
- if (softwarePath == null) {
+ if (ideRoot == null) {
return null;
}
- if (path.startsWith(softwarePath)) {
- int i = softwarePath.getNameCount();
+ if (path.startsWith(ideRoot)) {
+ int i = ideRoot.getNameCount();
if (path.getNameCount() > i) {
return path.getName(i).toString();
}
@@ -117,7 +146,11 @@ private static String getTool(Path path, Path softwarePath) {
private Path findBinaryInOrder(Path path, String tool) {
- for (String extension : EXTENSION_PRIORITY) {
+ List extensionPriority = List.of("");
+ if (SystemInfoImpl.INSTANCE.isWindows()) {
+ extensionPriority = EXTENSION_PRIORITY;
+ }
+ for (String extension : extensionPriority) {
Path fileToExecute = path.resolve(tool + extension);
@@ -212,13 +245,26 @@ public String toString(boolean bash) {
return sb.toString();
}
- private void appendPath(Path path, StringBuilder sb, char separator, boolean bash) {
+ private static void appendPath(Path path, StringBuilder sb, char separator, boolean bash) {
if (sb.length() > 0) {
sb.append(separator);
}
String pathString = path.toString();
if (bash && (pathString.length() > 3) && (pathString.charAt(1) == ':')) {
+ pathString = convertWindowsPathToUnixPath(pathString);
+ }
+ sb.append(pathString);
+ }
+
+ /**
+ * Method to convert a valid Windows path string representation to its corresponding one in Unix format.
+ *
+ * @param pathString The Windows path string to convert.
+ * @return The converted Unix path string.
+ */
+ public static String convertWindowsPathToUnixPath(String pathString) {
+
char slash = pathString.charAt(2);
if ((slash == '\\') || (slash == '/')) {
char drive = Character.toLowerCase(pathString.charAt(0));
@@ -226,8 +272,18 @@ private void appendPath(Path path, StringBuilder sb, char separator, boolean bas
pathString = "/" + drive + pathString.substring(2).replace('\\', '/');
}
}
+ return pathString;
}
- sb.append(pathString);
- }
+ /**
+ * Method to validate if a given path string is a Windows path or not
+ *
+ * @param pathString The string to check if it is a Windows path string.
+ * @return {@code true} if it is a valid windows path string, else {@code false}.
+ */
+ public static boolean isValidWindowsPath(String pathString) {
+
+ String windowsFilePathRegEx = "([a-zA-Z]:)?(\\\\[a-zA-Z0-9\\s_.-]+)+\\\\?";
+ return Pattern.matches(windowsFilePathRegEx, pathString);
+ }
}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/common/Tag.java b/cli/src/main/java/com/devonfw/tools/ide/common/Tag.java
index d7ef85640..1eda29dcf 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/common/Tag.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/common/Tag.java
@@ -293,6 +293,7 @@ public final class Tag {
/** {@link #Tag} for github. */
public static final Tag GITHUB = create("github", GIT);
+
/** {@link #Tag} for diff (tools that compare files and determine the difference). */
public static final Tag DIFF = create("diff", CONFIG_MANAGEMENT, false, "patch");
@@ -311,6 +312,12 @@ public final class Tag {
/** {@link #Tag} for Linux. */
public static final Tag LINUX = create("linux", OS, false);
+ /** {@link #getParent() Parent} for cryptography. */
+ public static final Tag CRYPTO = create("cryptography", ROOT, false, "crypto");
+
+ /** {@link #Tag} for encryption. */
+ public static final Tag ENCRYPTION = create("encryption", CRYPTO);
+
private final String id;
private final Tag parent;
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 88c4d6a05..30c995813 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
@@ -1,17 +1,5 @@
package com.devonfw.tools.ide.context;
-import java.io.IOException;
-import java.net.InetAddress;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Objects;
-import java.util.function.Function;
-
import com.devonfw.tools.ide.cli.CliArgument;
import com.devonfw.tools.ide.cli.CliArguments;
import com.devonfw.tools.ide.cli.CliException;
@@ -44,7 +32,18 @@
import com.devonfw.tools.ide.repo.DefaultToolRepository;
import com.devonfw.tools.ide.repo.ToolRepository;
import com.devonfw.tools.ide.url.model.UrlMetadata;
-import com.devonfw.tools.ide.variable.IdeVariables;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
/**
* Abstract base implementation of {@link IdeContext}.
@@ -119,20 +118,25 @@ public abstract class AbstractIdeContext implements IdeContext {
private UrlMetadata urlMetadata;
+ private Path defaultExecutionDirectory;
+
/**
* The constructor.
*
* @param minLogLevel the minimum {@link IdeLogLevel} to enable. Should be {@link IdeLogLevel#INFO} by default.
* @param factory the {@link Function} to create {@link IdeSubLogger} per {@link IdeLogLevel}.
* @param userDir the optional {@link Path} to current working directory.
+ * @param toolRepository @param toolRepository the {@link ToolRepository} of the context. If it is set to {@code null}
+ * {@link DefaultToolRepository} will be used.
*/
- public AbstractIdeContext(IdeLogLevel minLogLevel, Function factory, Path userDir) {
+ public AbstractIdeContext(IdeLogLevel minLogLevel, Function factory, Path userDir,
+ ToolRepository toolRepository) {
super();
this.loggerFactory = factory;
this.loggers = new HashMap<>();
setLogLevel(minLogLevel);
- this.systemInfo = new SystemInfoImpl();
+ this.systemInfo = SystemInfoImpl.INSTANCE;
this.commandletManager = new CommandletManagerImpl(this);
this.fileAccess = new FileAccessImpl(this);
String workspace = WORKSPACE_MAIN;
@@ -232,7 +236,13 @@ public AbstractIdeContext(IdeLogLevel minLogLevel, Function complete(CliArguments arguments, boolean includ
/**
* @param arguments the {@link CliArguments} to apply. Will be {@link CliArguments#next() consumed} as they are
- * matched. Consider passing a {@link CliArguments#copy() copy} as needed.
+ * matched. Consider passing a {@link CliArguments#copy() copy} as needed.
* @param cmd the potential {@link Commandlet} to match.
* @param collector the {@link CompletionCandidateCollector}.
* @return {@code true} if the given {@link Commandlet} matches to the given {@link CliArgument}(s) and those have
- * been applied (set in the {@link Commandlet} and {@link Commandlet#validate() validated}), {@code false}
- * otherwise (the {@link Commandlet} did not match and we have to try a different candidate).
+ * been applied (set in the {@link Commandlet} and {@link Commandlet#validate() validated}), {@code false} otherwise
+ * (the {@link Commandlet} did not match and we have to try a different candidate).
*/
public boolean apply(CliArguments arguments, Commandlet cmd, CompletionCandidateCollector collector) {
diff --git a/cli/src/main/java/com/devonfw/tools/ide/context/GitContext.java b/cli/src/main/java/com/devonfw/tools/ide/context/GitContext.java
index acb7e2b6d..fa3a21bff 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/context/GitContext.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/context/GitContext.java
@@ -13,45 +13,53 @@ public interface GitContext extends IdeLogger {
* Checks if the Git repository in the specified target folder needs an update by inspecting the modification time of
* a magic file.
*
- * @param repoUrl the git remote URL to clone from. May be suffixed with a hash-sign ('#') followed by the branch name
- * to check-out.
+ * @param repoUrl the git remote URL to clone from.
* @param targetRepository the {@link Path} to the target folder where the git repository should be cloned or pulled.
- * It is not the parent directory where git will by default create a sub-folder by default on clone but the *
- * final folder that will contain the ".git" subfolder.
+ * It is not the parent directory where git will by default create a sub-folder by default on clone but the * final
+ * folder that will contain the ".git" subfolder.
*/
- void pullOrCloneIfNeeded(String repoUrl, Path targetRepository);
+ void pullOrCloneIfNeeded(String repoUrl, String branch, Path targetRepository);
/**
* Attempts a git pull and reset if required.
*
- * @param repoUrl the git remote URL to clone from. May be suffixed with a hash-sign ('#') followed by the branch name
- * to check-out.
+ * @param repoUrl the git remote URL to clone from.
+ * @param branch the branch name e.g. master.
* @param targetRepository the {@link Path} to the target folder where the git repository should be cloned or pulled.
- * It is not the parent directory where git will by default create a sub-folder by default on clone but the *
- * final folder that will contain the ".git" subfolder.
+ * It is not the parent directory where git will by default create a sub-folder by default on clone but the * final
+ * folder that will contain the ".git" subfolder.
* @param remoteName the remote name e.g. origin.
- * @param branchName the branch name e.g. master.
*/
- void pullOrFetchAndResetIfNeeded(String repoUrl, Path targetRepository, String remoteName, String branchName);
+ void pullOrFetchAndResetIfNeeded(String repoUrl, String branch, Path targetRepository, String remoteName);
/**
* Runs a git pull or a git clone.
*
- * @param gitRepoUrl the git remote URL to clone from. May be suffixed with a hash-sign ('#') followed by the branch
- * name to check-out.
+ * @param gitRepoUrl the git remote URL to clone from.
* @param targetRepository the {@link Path} to the target folder where the git repository should be cloned or pulled.
- * It is not the parent directory where git will by default create a sub-folder by default on clone but the *
- * final folder that will contain the ".git" subfolder.
+ * It is not the parent directory where git will by default create a sub-folder by default on clone but the * final
+ * folder that will contain the ".git" subfolder.
*/
void pullOrClone(String gitRepoUrl, Path targetRepository);
+ /**
+ * Runs a git pull or a git clone.
+ *
+ * @param gitRepoUrl the git remote URL to clone from.
+ * @param branch the branch name e.g. master.
+ * @param targetRepository the {@link Path} to the target folder where the git repository should be cloned or pulled.
+ * It is not the parent directory where git will by default create a sub-folder by default on clone but the * final
+ * folder that will contain the ".git" subfolder.
+ */
+ void pullOrClone(String gitRepoUrl, String branch, Path targetRepository);
+
/**
* Runs a git clone. Throws a CliException if in offline mode.
*
* @param gitRepoUrl the {@link GitUrl} to use for the repository URL.
* @param targetRepository the {@link Path} to the target folder where the git repository should be cloned or pulled.
- * It is not the parent directory where git will by default create a sub-folder by default on clone but the *
- * final folder that will contain the ".git" subfolder.
+ * It is not the parent directory where git will by default create a sub-folder by default on clone but the * final
+ * folder that will contain the ".git" subfolder.
*/
void clone(GitUrl gitRepoUrl, Path targetRepository);
@@ -59,8 +67,8 @@ public interface GitContext extends IdeLogger {
* Runs a git pull.
*
* @param targetRepository the {@link Path} to the target folder where the git repository should be cloned or pulled.
- * It is not the parent directory where git will by default create a sub-folder by default on clone but the *
- * final folder that will contain the ".git" subfolder.
+ * It is not the parent directory where git will by default create a sub-folder by default on clone but the * final
+ * folder that will contain the ".git" subfolder.
*/
void pull(Path targetRepository);
@@ -68,8 +76,8 @@ public interface GitContext extends IdeLogger {
* Runs a git reset if files were modified.
*
* @param targetRepository the {@link Path} to the target folder where the git repository should be cloned or pulled.
- * It is not the parent directory where git will by default create a sub-folder by default on clone but the *
- * final folder that will contain the ".git" subfolder.
+ * It is not the parent directory where git will by default create a sub-folder by default on clone but the * final
+ * folder that will contain the ".git" subfolder.
* @param remoteName the remote server name.
* @param branchName the name of the branch.
*/
@@ -79,8 +87,8 @@ public interface GitContext extends IdeLogger {
* Runs a git cleanup if untracked files were found.
*
* @param targetRepository the {@link Path} to the target folder where the git repository should be cloned or pulled.
- * It is not the parent directory where git will by default create a sub-folder by default on clone but the *
- * final folder that will contain the ".git" subfolder.
+ * It is not the parent directory where git will by default create a sub-folder by default on clone but the * final
+ * folder that will contain the ".git" subfolder.
*/
void cleanup(Path targetRepository);
diff --git a/cli/src/main/java/com/devonfw/tools/ide/context/GitContextImpl.java b/cli/src/main/java/com/devonfw/tools/ide/context/GitContextImpl.java
index cc2f4568c..1203816dc 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/context/GitContextImpl.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/context/GitContextImpl.java
@@ -17,13 +17,16 @@
import com.devonfw.tools.ide.log.IdeSubLogger;
import com.devonfw.tools.ide.process.ProcessContext;
import com.devonfw.tools.ide.process.ProcessErrorHandling;
+import com.devonfw.tools.ide.process.ProcessMode;
import com.devonfw.tools.ide.process.ProcessResult;
/**
* Implements the {@link GitContext}.
*/
public class GitContextImpl implements GitContext {
- private static final Duration GIT_PULL_CACHE_DELAY_MILLIS = Duration.ofMillis(30 * 60 * 1000);;
+ private static final Duration GIT_PULL_CACHE_DELAY_MILLIS = Duration.ofMillis(30 * 60 * 1000);
+
+ ;
private final IdeContext context;
@@ -39,7 +42,7 @@ public GitContextImpl(IdeContext context) {
}
@Override
- public void pullOrCloneIfNeeded(String repoUrl, Path targetRepository) {
+ public void pullOrCloneIfNeeded(String repoUrl, String branch, Path targetRepository) {
Path gitDirectory = targetRepository.resolve(".git");
@@ -66,13 +69,13 @@ public void pullOrCloneIfNeeded(String repoUrl, Path targetRepository) {
}
} else {
// If the .git directory does not exist, perform git clone
- pullOrClone(repoUrl, targetRepository);
+ pullOrClone(repoUrl, branch, targetRepository);
}
}
- public void pullOrFetchAndResetIfNeeded(String repoUrl, Path targetRepository, String remoteName, String branchName) {
+ public void pullOrFetchAndResetIfNeeded(String repoUrl, String branch, Path targetRepository, String remoteName) {
- pullOrCloneIfNeeded(repoUrl, targetRepository);
+ pullOrCloneIfNeeded(repoUrl, branch, targetRepository);
if (remoteName.isEmpty()) {
reset(targetRepository, "origin", "master");
@@ -86,6 +89,12 @@ public void pullOrFetchAndResetIfNeeded(String repoUrl, Path targetRepository, S
@Override
public void pullOrClone(String gitRepoUrl, Path targetRepository) {
+ pullOrClone(gitRepoUrl, null, targetRepository);
+ }
+
+ @Override
+ public void pullOrClone(String gitRepoUrl, String branch, Path targetRepository) {
+
Objects.requireNonNull(targetRepository);
Objects.requireNonNull(gitRepoUrl);
@@ -96,7 +105,7 @@ public void pullOrClone(String gitRepoUrl, Path targetRepository) {
initializeProcessContext(targetRepository);
if (Files.isDirectory(targetRepository.resolve(".git"))) {
// checks for remotes
- ProcessResult result = this.processContext.addArg("remote").run(true, false);
+ ProcessResult result = this.processContext.addArg("remote").run(ProcessMode.DEFAULT_CAPTURE);
List remotes = result.getOut();
if (remotes.isEmpty()) {
String message = targetRepository
@@ -111,17 +120,7 @@ public void pullOrClone(String gitRepoUrl, Path targetRepository) {
}
}
} else {
- String branch = "";
- int hashIndex = gitRepoUrl.indexOf("#");
- if (hashIndex != -1) {
- branch = gitRepoUrl.substring(hashIndex + 1);
- gitRepoUrl = gitRepoUrl.substring(0, hashIndex);
- }
clone(new GitUrl(gitRepoUrl, branch), targetRepository);
- if (!branch.isEmpty()) {
- this.processContext.addArgs("checkout", branch);
- this.processContext.run();
- }
}
}
@@ -129,8 +128,8 @@ public void pullOrClone(String gitRepoUrl, Path targetRepository) {
* Handles errors which occurred during git pull.
*
* @param targetRepository the {@link Path} to the target folder where the git repository should be cloned or pulled.
- * It is not the parent directory where git will by default create a sub-folder by default on clone but the *
- * final folder that will contain the ".git" subfolder.
+ * It is not the parent directory where git will by default create a sub-folder by default on clone but the * final
+ * folder that will contain the ".git" subfolder.
* @param result the {@link ProcessResult} to evaluate.
*/
private void handleErrors(Path targetRepository, ProcessResult result) {
@@ -143,8 +142,8 @@ private void handleErrors(Path targetRepository, ProcessResult result) {
} else {
this.context.error(message);
if (this.context.isOnline()) {
- this.context
- .error("See above error for details. If you have local changes, please stash or revert and retry.");
+ this.context.error(
+ "See above error for details. If you have local changes, please stash or revert and retry.");
} else {
this.context.error(
"It seems you are offline - please ensure Internet connectivity and retry or activate offline mode (-o or --offline).");
@@ -158,8 +157,8 @@ private void handleErrors(Path targetRepository, ProcessResult result) {
* Lazily initializes the {@link ProcessContext}.
*
* @param targetRepository the {@link Path} to the target folder where the git repository should be cloned or pulled.
- * It is not the parent directory where git will by default create a sub-folder by default on clone but the *
- * final folder that will contain the ".git" subfolder.
+ * It is not the parent directory where git will by default create a sub-folder by default on clone but the * final
+ * folder that will contain the ".git" subfolder.
*/
private void initializeProcessContext(Path targetRepository) {
@@ -182,11 +181,19 @@ public void clone(GitUrl gitRepoUrl, Path targetRepository) {
if (this.context.isQuietMode()) {
this.processContext.addArg("-q");
}
- this.processContext.addArgs("--recursive", parsedUrl, "--config", "core.autocrlf=false", ".");
- result = this.processContext.run(true, false);
+ this.processContext.addArgs("--recursive", gitRepoUrl.url(), "--config", "core.autocrlf=false", ".");
+ result = this.processContext.run(ProcessMode.DEFAULT_CAPTURE);
if (!result.isSuccessful()) {
this.context.warning("Git failed to clone {} into {}.", parsedUrl, targetRepository);
}
+ String branch = gitRepoUrl.branch();
+ if (branch != null) {
+ this.processContext.addArgs("checkout", branch);
+ result = this.processContext.run(ProcessMode.DEFAULT_CAPTURE);
+ if (!result.isSuccessful()) {
+ this.context.warning("Git failed to checkout to branch {}", branch);
+ }
+ }
} else {
throw new CliException("Could not clone " + parsedUrl + " to " + targetRepository + " because you are offline.");
}
@@ -198,7 +205,7 @@ public void pull(Path targetRepository) {
initializeProcessContext(targetRepository);
ProcessResult result;
// pull from remote
- result = this.processContext.addArg("--no-pager").addArg("pull").run(true, false);
+ result = this.processContext.addArg("--no-pager").addArg("pull").run(ProcessMode.DEFAULT_CAPTURE);
if (!result.isSuccessful()) {
Map remoteAndBranchName = retrieveRemoteAndBranchName();
@@ -211,7 +218,7 @@ public void pull(Path targetRepository) {
private Map retrieveRemoteAndBranchName() {
Map remoteAndBranchName = new HashMap<>();
- ProcessResult remoteResult = this.processContext.addArg("branch").addArg("-vv").run(true, false);
+ ProcessResult remoteResult = this.processContext.addArg("branch").addArg("-vv").run(ProcessMode.DEFAULT_CAPTURE);
List remotes = remoteResult.getOut();
if (!remotes.isEmpty()) {
for (String remote : remotes) {
@@ -242,14 +249,14 @@ public void reset(Path targetRepository, String remoteName, String branchName) {
initializeProcessContext(targetRepository);
ProcessResult result;
// check for changed files
- result = this.processContext.addArg("diff-index").addArg("--quiet").addArg("HEAD").run(true, false);
+ result = this.processContext.addArg("diff-index").addArg("--quiet").addArg("HEAD").run(ProcessMode.DEFAULT_CAPTURE);
if (!result.isSuccessful()) {
// reset to origin/master
context.warning("Git has detected modified files -- attempting to reset {} to '{}/{}'.", targetRepository,
remoteName, branchName);
- result = this.processContext.addArg("reset").addArg("--hard").addArg(remoteName + "/" + branchName).run(true,
- false);
+ result = this.processContext.addArg("reset").addArg("--hard").addArg(remoteName + "/" + branchName)
+ .run(ProcessMode.DEFAULT_CAPTURE);
if (!result.isSuccessful()) {
context.warning("Git failed to reset {} to '{}/{}'.", remoteName, branchName, targetRepository);
@@ -265,12 +272,12 @@ public void cleanup(Path targetRepository) {
ProcessResult result;
// check for untracked files
result = this.processContext.addArg("ls-files").addArg("--other").addArg("--directory").addArg("--exclude-standard")
- .run(true, false);
+ .run(ProcessMode.DEFAULT_CAPTURE);
if (!result.getOut().isEmpty()) {
// delete untracked files
context.warning("Git detected untracked files in {} and is attempting a cleanup.", targetRepository);
- result = this.processContext.addArg("clean").addArg("-df").run(true, false);
+ result = this.processContext.addArg("clean").addArg("-df").run(ProcessMode.DEFAULT_CAPTURE);
if (!result.isSuccessful()) {
context.warning("Git failed to clean the repository {}.", targetRepository);
diff --git a/cli/src/main/java/com/devonfw/tools/ide/context/GitUrl.java b/cli/src/main/java/com/devonfw/tools/ide/context/GitUrl.java
index daee4352f..aa678cf2f 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/context/GitUrl.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/context/GitUrl.java
@@ -19,7 +19,7 @@ public record GitUrl(String url, String branch) {
public URL parseUrl() {
String parsedUrl = url;
- if (!branch.isEmpty()) {
+ if (branch != null && !branch.isEmpty()) {
parsedUrl += "#" + branch;
}
URL validUrl;
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 a633a67b0..4090da8bb 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
@@ -58,6 +58,12 @@ public interface IdeContext extends IdeLogger {
/** The name of the bin folder where executable files are found by default. */
String FOLDER_BIN = "bin";
+ /** The name of the repositories folder where properties files are stores for each repository */
+ String FOLDER_REPOSITORIES = "repositories";
+
+ /** The name of the repositories folder where properties files are stores for each repository */
+ String FOLDER_LEGACY_REPOSITORIES = "projects";
+
/** The name of the Contents folder inside a MacOS app. */
String FOLDER_CONTENTS = "Contents";
diff --git a/cli/src/main/java/com/devonfw/tools/ide/context/IdeContextConsole.java b/cli/src/main/java/com/devonfw/tools/ide/context/IdeContextConsole.java
index 53a9c5d84..aa56c36d6 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/context/IdeContextConsole.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/context/IdeContextConsole.java
@@ -26,7 +26,7 @@ public class IdeContextConsole extends AbstractIdeContext {
*/
public IdeContextConsole(IdeLogLevel minLogLevel, Appendable out, boolean colored) {
- super(minLogLevel, level -> new IdeSubLoggerOut(level, out, colored), null);
+ super(minLogLevel, level -> new IdeSubLoggerOut(level, out, colored), null, null);
if (System.console() == null) {
debug("System console not available - using System.in as fallback");
this.scanner = new Scanner(System.in);
@@ -51,12 +51,12 @@ public IdeProgressBar prepareProgressBar(String taskName, long size) {
ProgressBarBuilder pbb = new ProgressBarBuilder();
// default (COLORFUL_UNICODE_BLOCK)
pbb.setStyle(ProgressBarStyle.builder().refreshPrompt("\r").leftBracket("\u001b[33m│").delimitingSequence("")
- .rightBracket("│\u001b[0m").block('█').space(' ').fractionSymbols(" ▏▎▍▌▋▊▉").rightSideFractionSymbol(' ')
- .build());
+ .rightBracket("│\u001b[0m").block('█').space(' ').fractionSymbols(" ▏▎▍▌▋▊▉").rightSideFractionSymbol(' ')
+ .build());
// set different style for Windows systems (ASCII)
if (this.getSystemInfo().isWindows()) {
pbb.setStyle(ProgressBarStyle.builder().refreshPrompt("\r").leftBracket("[").delimitingSequence("")
- .rightBracket("]").block('=').space(' ').fractionSymbols(">").rightSideFractionSymbol(' ').build());
+ .rightBracket("]").block('=').space(' ').fractionSymbols(">").rightSideFractionSymbol(' ').build());
}
pbb.showSpeed();
pbb.setTaskName(taskName);
diff --git a/cli/src/main/java/com/devonfw/tools/ide/environment/AbstractEnvironmentVariables.java b/cli/src/main/java/com/devonfw/tools/ide/environment/AbstractEnvironmentVariables.java
index 4b8bd6551..99bf6d1fe 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/environment/AbstractEnvironmentVariables.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/environment/AbstractEnvironmentVariables.java
@@ -270,7 +270,7 @@ protected String getValue(String name) {
if (!name.equals(key)) {
value = this.parent.get(key);
}
- if (value != null) {
+ if (value == null) {
value = var.getDefaultValueAsString(this.context);
}
}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/environment/EnvironmentVariablesResolved.java b/cli/src/main/java/com/devonfw/tools/ide/environment/EnvironmentVariablesResolved.java
index 9a634a20c..23bb69f4f 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/environment/EnvironmentVariablesResolved.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/environment/EnvironmentVariablesResolved.java
@@ -1,46 +1,71 @@
-package com.devonfw.tools.ide.environment;
-
-/**
- * Implementation of {@link EnvironmentVariables} that resolves variables recursively.
- */
-public class EnvironmentVariablesResolved extends AbstractEnvironmentVariables {
-
- /**
- * The constructor.
- *
- * @param parent the parent {@link EnvironmentVariables} to inherit from.
- */
- EnvironmentVariablesResolved(AbstractEnvironmentVariables parent) {
-
- super(parent, parent.context);
- }
-
- @Override
- public EnvironmentVariablesType getType() {
-
- return EnvironmentVariablesType.RESOLVED;
- }
-
- @Override
- public String getFlat(String name) {
-
- return null;
- }
-
- @Override
- public String get(String name) {
-
- String value = getValue(name);
- if (value != null) {
- value = resolve(value, name);
- }
- return value;
- }
-
- @Override
- public EnvironmentVariables resolved() {
-
- return this;
- }
-
-}
+package com.devonfw.tools.ide.environment;
+
+import com.devonfw.tools.ide.variable.IdeVariables;
+import com.devonfw.tools.ide.variable.VariableDefinition;
+
+import java.util.Set;
+
+/**
+ * Implementation of {@link EnvironmentVariables} that resolves variables recursively.
+ */
+public class EnvironmentVariablesResolved extends AbstractEnvironmentVariables {
+
+ /**
+ * The constructor.
+ *
+ * @param parent the parent {@link EnvironmentVariables} to inherit from.
+ */
+ EnvironmentVariablesResolved(AbstractEnvironmentVariables parent) {
+
+ super(parent, parent.context);
+ }
+
+ @Override
+ public EnvironmentVariablesType getType() {
+
+ return EnvironmentVariablesType.RESOLVED;
+ }
+
+ @Override
+ public String getFlat(String name) {
+
+ return null;
+ }
+
+ @Override
+ public String get(String name) {
+
+ String value = getValue(name);
+ if (value != null) {
+ value = resolve(value, name);
+ }
+ return value;
+ }
+
+ @Override
+ public EnvironmentVariables resolved() {
+
+ return this;
+ }
+
+ @Override
+ protected void collectVariables(Set variables) {
+
+ for (VariableDefinition> var : IdeVariables.VARIABLES) {
+ if (var.isExport() || var.isForceDefaultValue()) {
+ variables.add(var.getName());
+ }
+ }
+ super.collectVariables(variables);
+ }
+
+ @Override
+ protected boolean isExported(String name) {
+
+ VariableDefinition> var = IdeVariables.get(name);
+ if ((var != null) && var.isExport()) {
+ return true;
+ }
+ return super.isExported(name);
+ }
+}
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 5d105d8e0..8b1260083 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,152 +1,235 @@
-package com.devonfw.tools.ide.io;
-
-import java.nio.file.Path;
-import java.util.function.Predicate;
-
-/**
- * Interface that gives access to various operations on files.
- */
-public interface FileAccess {
-
- /**
- * Downloads a file from an arbitrary location.
- *
- * @param url the location of the binary file to download. May also be a local or remote path to copy from.
- * @param targetFile the {@link Path} to the target file to download to. Should not already exists. Missing parent
- * directories will be created automatically.
- */
- void download(String url, Path targetFile);
-
- /**
- * Creates the entire {@link Path} as directories if not already existing.
- *
- * @param directory the {@link Path} to
- * {@link java.nio.file.Files#createDirectories(Path, java.nio.file.attribute.FileAttribute...) create}.
- */
- void mkdirs(Path directory);
-
- /**
- * @param file the {@link Path} to check.
- * @return {@code true} if the given {@code file} points to an existing file, {@code false} otherwise (the given
- * {@link Path} does not exist or is a directory).
- */
- boolean isFile(Path file);
-
- /**
- * @param folder the {@link Path} to check.
- * @return {@code true} if the given {@code folder} points to an existing directory, {@code false} otherwise (a
- * warning is logged in this case).
- */
- boolean isExpectedFolder(Path folder);
-
- /**
- * @param file the {@link Path} to compute the checksum of.
- * @return the computed checksum (SHA-266).
- */
- String checksum(Path file);
-
- /**
- * Moves the given {@link Path} to the backup.
- *
- * @param fileOrFolder the {@link Path} to move to the backup (soft-deletion).
- */
- void backup(Path fileOrFolder);
-
- /**
- * @param source the source {@link Path file or folder} to move.
- * @param targetDir the {@link Path} with the directory to move {@code source} into.
- */
- void move(Path source, Path targetDir);
-
- /**
- * Creates a symbolic link. If the given {@code targetLink} already exists and is a symbolic link or a Windows
- * junction, it will be replaced. In case of missing privileges, Windows Junctions may be used as fallback, which must
- * point to absolute paths. Therefore, the created link will be absolute instead of relative.
- *
- * @param source the source {@link Path} to link to, may be relative or absolute.
- * @param targetLink the {@link Path} where the symbolic link shall be created pointing to {@code source}.
- * @param relative - {@code true} if the symbolic link shall be relative, {@code false} if it shall be absolute.
- */
- void symlink(Path source, Path targetLink, boolean relative);
-
- /**
- * Creates a relative symbolic link. If the given {@code targetLink} already exists and is a symbolic link or a
- * Windows junction, it will be replaced. In case of missing privileges, Windows Junctions may be used as fallback,
- * which must point to absolute paths. Therefore, the created link will be absolute instead of relative.
- *
- * @param source the source {@link Path} to link to, may be relative or absolute.
- * @param targetLink the {@link Path} where the symbolic link shall be created pointing to {@code source}.
- */
- default void symlink(Path source, Path targetLink) {
-
- symlink(source, targetLink, true);
- }
-
- /**
- * @param source the source {@link Path file or folder} to copy.
- * @param target the {@link Path} to copy {@code source} to. See {@link #copy(Path, Path, FileCopyMode)} for details.
- * will always ensure that in the end you will find the same content of {@code source} in {@code target}.
- */
- default void copy(Path source, Path target) {
-
- copy(source, target, FileCopyMode.COPY_TREE_FAIL_IF_EXISTS);
- }
-
- /**
- * @param source the source {@link Path file or folder} to copy.
- * @param target the {@link Path} to copy {@code source} to. Unlike the Linux {@code cp} command this method will not
- * take the filename of {@code source} and copy that to {@code target} in case that is an existing folder.
- * Instead it will always be simple and stupid and just copy from {@code source} to {@code target}. Therefore
- * the result is always clear and easy to predict and understand. Also you can easily rename a file to copy.
- * While {@code cp my-file target} may lead to a different result than {@code cp my-file target/} this method
- * will always ensure that in the end you will find the same content of {@code source} in {@code target}.
- * @param fileOnly - {@code true} if {@code fileOrFolder} is expected to be a file and an exception shall be thrown if
- * it is a directory, {@code false} otherwise (copy recursively).
- */
- void copy(Path source, Path target, FileCopyMode fileOnly);
-
- /**
- * @param file the ZIP file to extract.
- * @param targetDir the {@link Path} with the directory to unzip to.
- */
- void unzip(Path file, Path targetDir);
-
- /**
- * @param file the ZIP file to extract.
- * @param targetDir the {@link Path} with the directory to unzip to.
- * @param compression the {@link TarCompression} to use.
- */
- void untar(Path file, Path targetDir, TarCompression compression);
-
- /**
- * @param path the {@link Path} to convert.
- * @return the absolute and physical {@link Path} (without symbolic links).
- */
- Path toRealPath(Path path);
-
- /**
- * Deletes the given {@link Path} idempotent and recursive.
- *
- * @param path the {@link Path} to delete.
- */
- void delete(Path path);
-
- /**
- * Creates a new temporary directory. ATTENTION: The user of this method is responsible to do house-keeping and
- * {@link #delete(Path) delete} it after the work is done.
- *
- * @param name the default name of the temporary directory to create. A prefix or suffix may be added to ensure
- * uniqueness.
- * @return the {@link Path} to the newly created and unique temporary directory.
- */
- Path createTempDir(String name);
-
- /**
- * @param dir the folder to search.
- * @param filter the {@link Predicate} used to find the {@link Predicate#test(Object) match}.
- * @param recursive - {@code true} to search recursive in all sub-folders, {@code false} otherwise.
- * @return the first child {@link Path} matching the given {@link Predicate} or {@code null} if no match was found.
- */
- Path findFirst(Path dir, Predicate filter, boolean recursive);
-
-}
+package com.devonfw.tools.ide.io;
+
+import java.nio.file.Path;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+/**
+ * Interface that gives access to various operations on files.
+ */
+public interface FileAccess {
+
+ /**
+ * Downloads a file from an arbitrary location.
+ *
+ * @param url the location of the binary file to download. May also be a local or remote path to copy from.
+ * @param targetFile the {@link Path} to the target file to download to. Should not already exists. Missing parent
+ * directories will be created automatically.
+ */
+ void download(String url, Path targetFile);
+
+ /**
+ * Creates the entire {@link Path} as directories if not already existing.
+ *
+ * @param directory the {@link Path} to
+ * {@link java.nio.file.Files#createDirectories(Path, java.nio.file.attribute.FileAttribute...) create}.
+ */
+ void mkdirs(Path directory);
+
+ /**
+ * @param file the {@link Path} to check.
+ * @return {@code true} if the given {@code file} points to an existing file, {@code false} otherwise (the given
+ * {@link Path} does not exist or is a directory).
+ */
+ boolean isFile(Path file);
+
+ /**
+ * @param folder the {@link Path} to check.
+ * @return {@code true} if the given {@code folder} points to an existing directory, {@code false} otherwise (a
+ * warning is logged in this case).
+ */
+ boolean isExpectedFolder(Path folder);
+
+ /**
+ * @param file the {@link Path} to compute the checksum of.
+ * @return the computed checksum (SHA-266).
+ */
+ String checksum(Path file);
+
+ /**
+ * Moves the given {@link Path} to the backup.
+ *
+ * @param fileOrFolder the {@link Path} to move to the backup (soft-deletion).
+ */
+ void backup(Path fileOrFolder);
+
+ /**
+ * @param source the source {@link Path file or folder} to move.
+ * @param targetDir the {@link Path} with the directory to move {@code source} into.
+ */
+ void move(Path source, Path targetDir);
+
+ /**
+ * Creates a symbolic link. If the given {@code targetLink} already exists and is a symbolic link or a Windows
+ * junction, it will be replaced. In case of missing privileges, Windows Junctions may be used as fallback, which must
+ * point to absolute paths. Therefore, the created link will be absolute instead of relative.
+ *
+ * @param source the source {@link Path} to link to, may be relative or absolute.
+ * @param targetLink the {@link Path} where the symbolic link shall be created pointing to {@code source}.
+ * @param relative - {@code true} if the symbolic link shall be relative, {@code false} if it shall be absolute.
+ */
+ void symlink(Path source, Path targetLink, boolean relative);
+
+ /**
+ * Creates a relative symbolic link. If the given {@code targetLink} already exists and is a symbolic link or a
+ * Windows junction, it will be replaced. In case of missing privileges, Windows Junctions may be used as fallback,
+ * which must point to absolute paths. Therefore, the created link will be absolute instead of relative.
+ *
+ * @param source the source {@link Path} to link to, may be relative or absolute.
+ * @param targetLink the {@link Path} where the symbolic link shall be created pointing to {@code source}.
+ */
+ default void symlink(Path source, Path targetLink) {
+
+ symlink(source, targetLink, true);
+ }
+
+ /**
+ * @param source the source {@link Path file or folder} to copy.
+ * @param target the {@link Path} to copy {@code source} to. See {@link #copy(Path, Path, FileCopyMode)} for details.
+ * will always ensure that in the end you will find the same content of {@code source} in {@code target}.
+ */
+ default void copy(Path source, Path target) {
+
+ copy(source, target, FileCopyMode.COPY_TREE_FAIL_IF_EXISTS);
+ }
+
+ /**
+ * @param source the source {@link Path file or folder} to copy.
+ * @param target the {@link Path} to copy {@code source} to. Unlike the Linux {@code cp} command this method will not
+ * take the filename of {@code source} and copy that to {@code target} in case that is an existing folder.
+ * Instead it will always be simple and stupid and just copy from {@code source} to {@code target}. Therefore
+ * the result is always clear and easy to predict and understand. Also you can easily rename a file to copy.
+ * While {@code cp my-file target} may lead to a different result than {@code cp my-file target/} this method
+ * will always ensure that in the end you will find the same content of {@code source} in {@code target}.
+ * @param fileOnly - {@code true} if {@code fileOrFolder} is expected to be a file and an exception shall be thrown if
+ * it is a directory, {@code false} otherwise (copy recursively).
+ */
+ void copy(Path source, Path target, FileCopyMode fileOnly);
+
+ /**
+ * @param archiveFile the {@link Path} to the file to extract.
+ * @param targetDir the {@link Path} to the directory where to extract the {@code archiveFile} to.
+ */
+ default void extract(Path archiveFile, Path targetDir) {
+
+ extract(archiveFile, targetDir, null);
+ }
+
+ /**
+ * @param archiveFile the {@link Path} to the archive file to extract.
+ * @param targetDir the {@link Path} to the directory where to extract the {@code archiveFile}.
+ * @param postExtractHook the {@link Consumer} to be called after the extraction on the final folder before it is
+ * moved to {@code targetDir}.
+ */
+ default void extract(Path archiveFile, Path targetDir, Consumer postExtractHook) {
+
+ extract(archiveFile, targetDir, postExtractHook, true);
+ }
+
+ /**
+ * @param archiveFile the {@link Path} to the archive file to extract.
+ * @param targetDir the {@link Path} to the directory where to extract the {@code archiveFile}.
+ * @param postExtractHook the {@link Consumer} to be called after the extraction on the final folder before it is
+ * moved to {@code targetDir}.
+ * @param extract {@code true} if the {@code archiveFile} should be extracted (default), {@code false} otherwise.
+ */
+ void extract(Path archiveFile, Path targetDir, Consumer postExtractHook, boolean extract);
+
+ /**
+ * Extracts a ZIP file what is the common archive format on Windows. Initially invented by PKZIP for MS-DOS and also
+ * famous from WinZIP software for Windows.
+ *
+ * @param file the ZIP file to extract.
+ * @param targetDir the {@link Path} with the directory to unzip to.
+ */
+ void extractZip(Path file, Path targetDir);
+
+ /**
+ * @param file the ZIP file to extract.
+ * @param targetDir the {@link Path} with the directory to unzip to.
+ * @param compression the {@link TarCompression} to use.
+ */
+ void extractTar(Path file, Path targetDir, TarCompression compression);
+
+ /**
+ * Extracts an Apple DMG (Disk Image) file that is similar to an ISO image. DMG files are commonly used for software
+ * releases on MacOS. Double-clicking such files on MacOS mounts them and show the application together with a
+ * symbolic link to the central applications folder and some help instructions. The user then copies the application
+ * to the applications folder via drag and drop in order to perform the installation.
+ *
+ * @param file the DMG file to extract.
+ * @param targetDir the target directory where to extract the contents to.
+ */
+ void extractDmg(Path file, Path targetDir);
+
+ /**
+ * Extracts an MSI (Microsoft Installer) file. MSI files are commonly used for software releases on Windows that allow
+ * an installation wizard and easy later uninstallation.
+ *
+ * @param file the MSI file to extract.
+ * @param targetDir the target directory where to extract the contents to.
+ */
+ void extractMsi(Path file, Path targetDir);
+
+ /**
+ * Extracts an Apple PKG (Package) file. PKG files are used instead of {@link #extractDmg(Path, Path) DMG files} if
+ * additional changes have to be performed like drivers to be installed. Similar to what
+ * {@link #extractMsi(Path, Path) MSI} is on Windows. PKG files are internally a xar based archive with a specific
+ * structure.
+ *
+ * @param file the PKG file to extract.
+ * @param targetDir the target directory where to extract the contents to.
+ */
+ void extractPkg(Path file, Path targetDir);
+
+ /**
+ * @param path the {@link Path} to convert.
+ * @return the absolute and physical {@link Path} (without symbolic links).
+ */
+ Path toRealPath(Path path);
+
+ /**
+ * Deletes the given {@link Path} idempotent and recursive.
+ *
+ * @param path the {@link Path} to delete.
+ */
+ void delete(Path path);
+
+ /**
+ * Creates a new temporary directory. ATTENTION: The user of this method is responsible to do house-keeping and
+ * {@link #delete(Path) delete} it after the work is done.
+ *
+ * @param name the default name of the temporary directory to create. A prefix or suffix may be added to ensure
+ * uniqueness.
+ * @return the {@link Path} to the newly created and unique temporary directory.
+ */
+ Path createTempDir(String name);
+
+ /**
+ * @param dir the folder to search.
+ * @param filter the {@link Predicate} used to find the {@link Predicate#test(Object) match}.
+ * @param recursive - {@code true} to search recursive in all sub-folders, {@code false} otherwise.
+ * @return the first child {@link Path} matching the given {@link Predicate} or {@code null} if no match was found.
+ */
+ Path findFirst(Path dir, Predicate filter, boolean recursive);
+
+ /**
+ * @param dir the {@link Path} to the directory where to list the children.
+ * @param filter the {@link Predicate} used to {@link Predicate#test(Object) decide} which children to include (if
+ * {@code true} is returned).
+ * @return all children of the given {@link Path} that match the given {@link Predicate}. Will be the empty list of
+ * the given {@link Path} is not an existing directory.
+ */
+ List listChildren(Path dir, Predicate filter);
+
+ /**
+ * Finds the existing file with the specified name in the given list of directories.
+ *
+ * @param fileName The name of the file to find.
+ * @param searchDirs The list of directories to search for the file.
+ * @return The {@code Path} of the existing file, or {@code null} if the file is not found.
+ */
+ Path findExistingFile(String fileName, List searchDirs);
+
+}
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 3906a64ba..7e03e4a76 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
@@ -27,6 +27,7 @@
import java.util.Iterator;
import java.util.List;
import java.util.Set;
+import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
@@ -37,9 +38,13 @@
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
+import com.devonfw.tools.ide.cli.CliException;
import com.devonfw.tools.ide.context.IdeContext;
+import com.devonfw.tools.ide.os.SystemInfoImpl;
+import com.devonfw.tools.ide.process.ProcessContext;
import com.devonfw.tools.ide.url.model.file.UrlChecksum;
import com.devonfw.tools.ide.util.DateTimeUtil;
+import com.devonfw.tools.ide.util.FilenameUtil;
import com.devonfw.tools.ide.util.HexUtil;
/**
@@ -220,22 +225,41 @@ public String checksum(Path file) {
}
}
+ private boolean isJunction(Path path) {
+
+ if (!SystemInfoImpl.INSTANCE.isWindows()) {
+ return false;
+ }
+
+ try {
+ BasicFileAttributes attr = Files.readAttributes(path, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
+ return attr.isOther() && attr.isDirectory();
+ } catch (NoSuchFileException e) {
+ return false; // file doesn't exist
+ } catch (IOException e) {
+ // errors in reading the attributes of the file
+ throw new IllegalStateException(
+ "An unexpected error occurred whilst checking if the file: " + path + " is a junction", e);
+ }
+ }
+
@Override
public void backup(Path fileOrFolder) {
- if (Files.isSymbolicLink(fileOrFolder)) {
+ if (Files.isSymbolicLink(fileOrFolder) || isJunction(fileOrFolder)) {
delete(fileOrFolder);
- return;
+ } else {
+ // fileOrFolder is a directory
+ Path backupPath = this.context.getIdeHome().resolve(IdeContext.FOLDER_UPDATES).resolve(IdeContext.FOLDER_BACKUPS);
+ LocalDateTime now = LocalDateTime.now();
+ String date = DateTimeUtil.formatDate(now);
+ String time = DateTimeUtil.formatTime(now);
+ Path backupDatePath = backupPath.resolve(date);
+ mkdirs(backupDatePath);
+ Path target = backupDatePath.resolve(fileOrFolder.getFileName().toString() + "_" + time);
+ this.context.info("Creating backup by moving {} to {}", fileOrFolder, target);
+ move(fileOrFolder, target);
}
- Path backupPath = this.context.getIdeHome().resolve(IdeContext.FOLDER_UPDATES).resolve(IdeContext.FOLDER_BACKUPS);
- LocalDateTime now = LocalDateTime.now();
- String date = DateTimeUtil.formatDate(now);
- String time = DateTimeUtil.formatTime(now);
- Path backupDatePath = backupPath.resolve(date);
- mkdirs(backupDatePath);
- Path target = backupDatePath.resolve(fileOrFolder.getFileName().toString() + "_" + time);
- this.context.info("Creating backup by moving {} to {}", fileOrFolder, target);
- move(fileOrFolder, target);
}
@Override
@@ -255,6 +279,15 @@ public void copy(Path source, Path target, FileCopyMode mode) {
boolean fileOnly = mode.isFileOnly();
if (fileOnly) {
this.context.debug("Copying file {} to {}", source, target);
+ if (Files.isDirectory(target)) {
+ // if we want to copy "file.txt" to the existing folder "path/to/folder/" in a shell this will copy "file.txt"
+ // into that folder
+ // with Java NIO the raw copy method will fail as we cannot copy the file to the path of the target folder
+ // even worse if FileCopyMode is override the target folder ("path/to/folder/") would be deleted and the result
+ // of our "file.txt" would later appear in "path/to/folder". To prevent such bugs we append the filename to
+ // target
+ target = target.resolve(source.getFileName());
+ }
} else {
this.context.debug("Copying {} recursively to {}", source, target);
}
@@ -287,7 +320,7 @@ private void copyRecursive(Path source, Path target, FileCopyMode mode) throws I
}
}
} else if (Files.exists(source)) {
- if (mode == FileCopyMode.COPY_TREE_OVERRIDE_FILES) {
+ if (mode.isOverrideFile()) {
delete(target);
}
this.context.trace("Copying {} to {}", source, target);
@@ -307,31 +340,14 @@ private void copyRecursive(Path source, Path target, FileCopyMode mode) throws I
*/
private void deleteLinkIfExists(Path path) throws IOException {
- boolean exists = false;
- boolean isJunction = false;
- if (this.context.getSystemInfo().isWindows()) {
- try { // since broken junctions are not detected by Files.exists(brokenJunction)
- BasicFileAttributes attr = Files.readAttributes(path, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
- exists = true;
- isJunction = attr.isOther() && attr.isDirectory();
- } catch (NoSuchFileException e) {
- // ignore, since there is no previous file at the location, so nothing to delete
- return;
- }
- }
- exists = exists || Files.exists(path);
- boolean isSymlink = exists && Files.isSymbolicLink(path);
+ boolean isJunction = isJunction(path); // since broken junctions are not detected by Files.exists()
+ boolean isSymlink = Files.exists(path) && Files.isSymbolicLink(path);
assert !(isSymlink && isJunction);
- if (exists) {
- if (isJunction || isSymlink) {
- this.context.info("Deleting previous " + (isJunction ? "junction" : "symlink") + " at " + path);
- Files.delete(path);
- } else {
- throw new IllegalStateException(
- "The file at " + path + " was not deleted since it is not a symlink or a Windows junction");
- }
+ if (isJunction || isSymlink) {
+ this.context.info("Deleting previous " + (isJunction ? "junction" : "symlink") + " at " + path);
+ Files.delete(path);
}
}
@@ -437,7 +453,7 @@ public void symlink(Path source, Path targetLink, boolean relative) {
try {
Files.createSymbolicLink(targetLink, adaptedSource);
} catch (FileSystemException e) {
- if (this.context.getSystemInfo().isWindows()) {
+ if (SystemInfoImpl.INSTANCE.isWindows()) {
this.context.info("Due to lack of permissions, Microsoft's mklink with junction had to be used to create "
+ "a Symlink. See https://github.com/devonfw/IDEasy/blob/main/documentation/symlinks.asciidoc for "
+ "further details. Error was: " + e.getMessage());
@@ -487,15 +503,104 @@ public Path createTempDir(String name) {
}
@Override
- public void unzip(Path file, Path targetDir) {
+ public void extract(Path archiveFile, Path targetDir, Consumer postExtractHook, boolean extract) {
- unpack(file, targetDir, in -> new ZipArchiveInputStream(in));
+ if (Files.isDirectory(archiveFile)) {
+ Path properInstallDir = archiveFile; // getProperInstallationSubDirOf(archiveFile, archiveFile);
+ if (extract) {
+ this.context.warning("Found directory for download at {} hence copying without extraction!", archiveFile);
+ copy(properInstallDir, targetDir, FileCopyMode.COPY_TREE_OVERRIDE_TREE);
+ } else {
+ move(properInstallDir, targetDir);
+ }
+ postExtractHook(postExtractHook, properInstallDir);
+ return;
+ } else if (!extract) {
+ mkdirs(targetDir);
+ move(archiveFile, targetDir.resolve(archiveFile.getFileName()));
+ return;
+ }
+ Path tmpDir = createTempDir("extract-" + archiveFile.getFileName());
+ this.context.trace("Trying to extract the downloaded file {} to {} and move it to {}.", archiveFile, tmpDir,
+ targetDir);
+ String filename = archiveFile.getFileName().toString();
+ TarCompression tarCompression = TarCompression.of(filename);
+ if (tarCompression != null) {
+ extractTar(archiveFile, tmpDir, tarCompression);
+ } else {
+ String extension = FilenameUtil.getExtension(filename);
+ if (extension == null) {
+ throw new IllegalStateException("Unknown archive format without extension - can not extract " + archiveFile);
+ } else {
+ this.context.trace("Determined file extension {}", extension);
+ }
+ switch (extension) {
+ case "zip", "jar" -> {
+ extractZip(archiveFile, tmpDir);
+ }
+ case "dmg" -> {
+ extractDmg(archiveFile, tmpDir);
+ }
+ case "msi" -> {
+ extractMsi(archiveFile, tmpDir);
+ }
+ case "pkg" -> {
+ extractPkg(archiveFile, tmpDir);
+ }
+ default -> {
+ throw new IllegalStateException("Unknown archive format " + extension + ". Can not extract " + archiveFile);
+ }
+ }
+ }
+ Path properInstallDir = getProperInstallationSubDirOf(tmpDir, archiveFile);
+ postExtractHook(postExtractHook, properInstallDir);
+ move(properInstallDir, targetDir);
+ delete(tmpDir);
+ }
+
+ private void postExtractHook(Consumer postExtractHook, Path properInstallDir) {
+
+ if (postExtractHook != null) {
+ postExtractHook.accept(properInstallDir);
+ }
+ }
+
+ /**
+ * @param path the {@link Path} to start the recursive search from.
+ * @return the deepest subdir {@code s} of the passed path such that all directories between {@code s} and the passed
+ * path (including {@code s}) are the sole item in their respective directory and {@code s} is not named
+ * "bin".
+ */
+ private Path getProperInstallationSubDirOf(Path path, Path archiveFile) {
+
+ try (Stream stream = Files.list(path)) {
+ Path[] subFiles = stream.toArray(Path[]::new);
+ if (subFiles.length == 0) {
+ throw new CliException("The downloaded package " + archiveFile
+ + " seems to be empty as you can check in the extracted folder " + path);
+ } else if (subFiles.length == 1) {
+ String filename = subFiles[0].getFileName().toString();
+ if (!filename.equals(IdeContext.FOLDER_BIN) && !filename.equals(IdeContext.FOLDER_CONTENTS)
+ && !filename.endsWith(".app") && Files.isDirectory(subFiles[0])) {
+ return getProperInstallationSubDirOf(subFiles[0], archiveFile);
+ }
+ }
+ return path;
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to get sub-files of " + path);
+ }
}
@Override
- public void untar(Path file, Path targetDir, TarCompression compression) {
+ public void extractZip(Path file, Path targetDir) {
- unpack(file, targetDir, in -> new TarArchiveInputStream(compression.unpack(in)));
+ extractArchive(file, targetDir, in -> new ZipArchiveInputStream(in));
+ }
+
+ @Override
+ public void extractTar(Path file, Path targetDir, TarCompression compression) {
+
+ extractArchive(file, targetDir, in -> new TarArchiveInputStream(compression.unpack(in)));
}
/**
@@ -519,7 +624,7 @@ public static String generatePermissionString(int permissions) {
return permissionStringBuilder.toString();
}
- private void unpack(Path file, Path targetDir, Function unpacker) {
+ private void extractArchive(Path file, Path targetDir, Function unpacker) {
this.context.trace("Unpacking archive {} to {}", file, targetDir);
try (InputStream is = Files.newInputStream(file); ArchiveInputStream ais = unpacker.apply(is)) {
@@ -555,6 +660,44 @@ private void unpack(Path file, Path targetDir, Function p.getFileName().toString().endsWith(".app"), false);
+ if (appPath == null) {
+ throw new IllegalStateException("Failed to unpack DMG as no MacOS *.app was found in file " + file);
+ }
+ copy(appPath, targetDir);
+ pc.addArgs("detach", "-force", mountPath);
+ pc.run();
+ }
+
+ public void extractMsi(Path file, Path targetDir) {
+
+ this.context.newProcess().executable("msiexec").addArgs("/a", file, "/qn", "TARGETDIR=" + targetDir).run();
+ // msiexec also creates a copy of the MSI
+ Path msiCopy = targetDir.resolve(file.getFileName());
+ delete(msiCopy);
+ }
+
+ public void extractPkg(Path file, Path targetDir) {
+
+ Path tmpDirPkg = createTempDir("ide-pkg-");
+ ProcessContext pc = this.context.newProcess();
+ // we might also be able to use cpio from commons-compression instead of external xar...
+ pc.executable("xar").addArgs("-C", tmpDirPkg, "-xf", file).run();
+ Path contentPath = findFirst(tmpDirPkg, p -> p.getFileName().toString().equals("Payload"), true);
+ extractTar(contentPath, targetDir, TarCompression.GZ);
+ delete(tmpDirPkg);
+ }
+
@Override
public void delete(Path path) {
@@ -627,4 +770,43 @@ private Path findFirstRecursive(Path dir, Predicate filter, boolean recurs
return null;
}
+ @Override
+ public List listChildren(Path dir, Predicate filter) {
+
+ if (!Files.isDirectory(dir)) {
+ return List.of();
+ }
+ List children = new ArrayList<>();
+ try (Stream childStream = Files.list(dir)) {
+ Iterator iterator = childStream.iterator();
+ while (iterator.hasNext()) {
+ Path child = iterator.next();
+ if (filter.test(child)) {
+ this.context.trace("Accepted file {}", child);
+ children.add(child);
+ } else {
+ this.context.trace("Ignoring file {} according to filter", child);
+ }
+ }
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to find children of directory " + dir, e);
+ }
+ return children;
+ }
+
+ @Override
+ public Path findExistingFile(String fileName, List searchDirs) {
+
+ for (Path dir : searchDirs) {
+ Path filePath = dir.resolve(fileName);
+ try {
+ if (Files.exists(filePath)) {
+ return filePath;
+ }
+ } catch (Exception e) {
+ throw new IllegalStateException("Unexpected error while checking existence of file "+filePath+" .", e);
+ }
+ }
+ return null;
+ }
}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/io/FileCopyMode.java b/cli/src/main/java/com/devonfw/tools/ide/io/FileCopyMode.java
index 03287541d..cc07a90cc 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/io/FileCopyMode.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/io/FileCopyMode.java
@@ -1,55 +1,60 @@
-package com.devonfw.tools.ide.io;
-
-/**
- * {@link Enum} with the available modes to {@link FileAccess#copy(java.nio.file.Path, java.nio.file.Path, FileCopyMode)
- * copy} files and folders.
- */
-public enum FileCopyMode {
-
- /**
- * Copy {@link #isFileOnly() only a single file} and {@link #isFailIfExists() fail if the target-file already exists}.
- */
- COPY_FILE_FAIL_IF_EXISTS,
-
- /** Copy {@link #isFileOnly() only a single file} and override the target-file if it already exists. */
- COPY_FILE_OVERRIDE,
-
- /** Copy {@link #isRecursive() recursively} and {@link #isFailIfExists() fail if the target-path already exists}. */
- COPY_TREE_FAIL_IF_EXISTS,
-
- /** Copy {@link #isRecursive() recursively} and override existing files but merge existing folders. */
- COPY_TREE_OVERRIDE_FILES,
-
- /**
- * Copy {@link #isRecursive() recursively} and {@link FileAccess#delete(java.nio.file.Path) delete} the target-file if
- * it exists before copying.
- */
- COPY_TREE_OVERRIDE_TREE;
-
- /**
- * @return {@code true} if only a single file shall be copied. Will fail if a directory is given to copy,
- * {@code false} otherwise (to copy folders recursively).
- */
- public boolean isFileOnly() {
-
- return (this == COPY_FILE_FAIL_IF_EXISTS) || (this == COPY_FILE_OVERRIDE);
- }
-
- /**
- * @return {@code true} if files and folders shall be copied recursively, {@code false} otherwise
- * ({@link #isFileOnly() copy file copy}).
- */
- public boolean isRecursive() {
-
- return !isFileOnly();
- }
-
- /**
- * @return {@code true} to fail if the target file or folder already exists, {@code false} otherwise.
- */
- public boolean isFailIfExists() {
-
- return (this == COPY_FILE_FAIL_IF_EXISTS) || (this == COPY_TREE_FAIL_IF_EXISTS);
- }
-
-}
+package com.devonfw.tools.ide.io;
+
+/**
+ * {@link Enum} with the available modes to
+ * {@link FileAccess#copy(java.nio.file.Path, java.nio.file.Path, FileCopyMode) copy} files and folders.
+ */
+public enum FileCopyMode {
+
+ /**
+ * Copy {@link #isFileOnly() only a single file} and
+ * {@link #isFailIfExists() fail if the target-file already exists}.
+ */
+ COPY_FILE_FAIL_IF_EXISTS,
+
+ /** Copy {@link #isFileOnly() only a single file} and override the target-file if it already exists. */
+ COPY_FILE_OVERRIDE,
+
+ /** Copy {@link #isRecursive() recursively} and {@link #isFailIfExists() fail if the target-path already exists}. */
+ COPY_TREE_FAIL_IF_EXISTS,
+
+ /** Copy {@link #isRecursive() recursively} and override existing files but merge existing folders. */
+ COPY_TREE_OVERRIDE_FILES,
+
+ /**
+ * Copy {@link #isRecursive() recursively} and {@link FileAccess#delete(java.nio.file.Path) delete} the target-file if
+ * it exists before copying.
+ */
+ COPY_TREE_OVERRIDE_TREE;
+
+ /**
+ * @return {@code true} if only a single file shall be copied. Will fail if a directory is given to copy,
+ * {@code false} otherwise (to copy folders recursively).
+ */
+ public boolean isFileOnly() {
+
+ return (this == COPY_FILE_FAIL_IF_EXISTS) || (this == COPY_FILE_OVERRIDE);
+ }
+
+ /**
+ * @return {@code true} if files and folders shall be copied recursively, {@code false} otherwise
+ * ({@link #isFileOnly() copy file copy}).
+ */
+ public boolean isRecursive() {
+
+ return !isFileOnly();
+ }
+
+ /**
+ * @return {@code true} to fail if the target file or folder already exists, {@code false} otherwise.
+ */
+ public boolean isFailIfExists() {
+
+ return (this == COPY_FILE_FAIL_IF_EXISTS) || (this == COPY_TREE_FAIL_IF_EXISTS);
+ }
+
+ public boolean isOverrideFile() {
+
+ return (this == COPY_FILE_OVERRIDE) || (this == COPY_TREE_OVERRIDE_FILES);
+ }
+}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/io/TarCompression.java b/cli/src/main/java/com/devonfw/tools/ide/io/TarCompression.java
index ce626e643..228d56a77 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/io/TarCompression.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/io/TarCompression.java
@@ -1,144 +1,154 @@
-package com.devonfw.tools.ide.io;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
-import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
-
-/**
- * {@link Enum} with the available compression modes of a TAR archive file.
- */
-public enum TarCompression {
-
- /** No compression (uncompressed TAR). */
- NONE("", "tar", null),
-
- /** GNU-Zip compression. */
- GZ("gz", "tgz", "-z") {
-
- @Override
- InputStream unpackRaw(InputStream in) throws IOException {
-
- return new GzipCompressorInputStream(in);
- }
- },
-
- /** BZip2 compression. */
- BZIP2("bz2", "tbz2", "-j", "bzip2") {
-
- @Override
- InputStream unpackRaw(InputStream in) throws IOException {
-
- return new BZip2CompressorInputStream(in);
- }
- };
-
- private final String extension;
-
- private final String combinedExtension;
-
- private final String tarOption;
-
- private final String altExtension;
-
- private TarCompression(String extension, String combinedExtension, String tarOption) {
-
- this(extension, combinedExtension, tarOption, null);
- }
-
- private TarCompression(String extension, String combinedExtension, String tarOption, String altExtension) {
-
- this.extension = extension;
- this.combinedExtension = combinedExtension;
- this.tarOption = tarOption;
- this.altExtension = altExtension;
- }
-
- /**
- * @return the (default) file extension of this compression (excluding the dot). E.g. "gz" for a "tar.gz" or "tgz"
- * file.
- */
- public String getExtension() {
-
- return this.extension;
- }
-
- /**
- * @return the compact file extension of this compression combined with the tar archive information. E.g. "tgz" or
- * "tbz2".
- */
- public String getCombinedExtension() {
-
- return this.combinedExtension;
- }
-
- /**
- * @return the CLI option to enable this compression in the GNU tar command.
- */
- public String getTarOption() {
-
- return this.tarOption;
- }
-
- /**
- * @return altExtension
- */
- public String getAltExtension() {
-
- return this.altExtension;
- }
-
- /**
- * @param in the {@link InputStream} to wrap for unpacking.
- * @return an {@link InputStream} to read the unpacked payload of the given {@link InputStream}.
- */
- public final InputStream unpack(InputStream in) {
-
- try {
- return unpackRaw(in);
- } catch (IOException e) {
- throw new IllegalStateException("Failed to open unpacking stream!", e);
- }
- }
-
- InputStream unpackRaw(InputStream in) throws IOException {
-
- return in;
- }
-
- /**
- * @param extension the file extension (e.g. "tgz", ".tar.gz", "bz2", etc.)
- * @return the {@link TarCompression} detected from the given {@code extension} or {@code null} if none was detected.
- */
- public static TarCompression of(String extension) {
-
- if ((extension == null) || extension.isEmpty()) {
- return null;
- }
- String ext = extension;
- if (ext.charAt(0) == '.') {
- ext = ext.substring(1);
- }
- boolean isTar = false;
- if (ext.startsWith("tar")) {
- isTar = true;
- if ((ext.length() > 3) && (ext.charAt(3) == '.')) {
- ext = ext.substring(4);
- } else {
- return NONE;
- }
- }
- for (TarCompression cmp : values()) {
- if (cmp.extension.equals(ext)) {
- return cmp;
- } else if (!isTar && cmp.combinedExtension.equals(ext)) {
- return cmp;
- } else if ((cmp.altExtension != null) && cmp.altExtension.equals(ext)) {
- return cmp;
- }
- }
- return null;
- }
-
-}
+package com.devonfw.tools.ide.io;
+
+import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
+import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Locale;
+
+/**
+ * {@link Enum} with the available compression modes of a TAR archive file. A GNU Tape ARchive is the standard archive
+ * format on Linux systems. It is similar to ZIP but it allows to represent advanced metadata such as file permissions
+ * (e.g. executable flags). Further, it has no compression and is therefore typically combined with generic file
+ * compressions like {@link #GZ GNU zip} (not to be confused with Windows ZIP) or {@link #BZIP2}.
+ */
+public enum TarCompression {
+
+ /** No compression (uncompressed TAR). */
+ NONE("", "tar", null),
+
+ /** GNU-Zip compression. */
+ GZ("gz", "tgz", "-z") {
+ @Override
+ InputStream unpackRaw(InputStream in) throws IOException {
+
+ return new GzipCompressorInputStream(in);
+ }
+ },
+
+ /** BZip2 compression. */
+ BZIP2("bz2", "tbz2", "-j", "bzip2") {
+ @Override
+ InputStream unpackRaw(InputStream in) throws IOException {
+
+ return new BZip2CompressorInputStream(in);
+ }
+ };
+
+ private final String extension;
+
+ private final String combinedExtension;
+
+ private final String tarOption;
+
+ private final String altExtension;
+
+ private TarCompression(String extension, String combinedExtension, String tarOption) {
+
+ this(extension, combinedExtension, tarOption, null);
+ }
+
+ private TarCompression(String extension, String combinedExtension, String tarOption, String altExtension) {
+
+ this.extension = extension;
+ this.combinedExtension = combinedExtension;
+ this.tarOption = tarOption;
+ this.altExtension = altExtension;
+ }
+
+ /**
+ * @return the (default) file extension of this compression (excluding the dot). E.g. "gz" for a "tar.gz" or "tgz"
+ * file.
+ */
+ public String getExtension() {
+
+ return this.extension;
+ }
+
+ /**
+ * @return the compact file extension of this compression combined with the tar archive information. E.g. "tgz" or
+ * "tbz2".
+ */
+ public String getCombinedExtension() {
+
+ return this.combinedExtension;
+ }
+
+ /**
+ * @return the CLI option to enable this compression in the GNU tar command.
+ */
+ public String getTarOption() {
+
+ return this.tarOption;
+ }
+
+ /**
+ * @return altExtension
+ */
+ public String getAltExtension() {
+
+ return this.altExtension;
+ }
+
+ /**
+ * @param in the {@link InputStream} to wrap for unpacking.
+ * @return an {@link InputStream} to read the unpacked payload of the given {@link InputStream}.
+ */
+ public final InputStream unpack(InputStream in) {
+
+ try {
+ return unpackRaw(in);
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to open unpacking stream!", e);
+ }
+ }
+
+ InputStream unpackRaw(InputStream in) throws IOException {
+
+ return in;
+ }
+
+ /**
+ * @param filename the filename or extension (e.g. "archive.tar.bzip2", "tgz", ".tar.gz", etc.)
+ * @return the {@link TarCompression} detected from the given {@code filename} or {@code null} if none was detected.
+ */
+ public static TarCompression of(String filename) {
+
+ if ((filename == null) || filename.isEmpty()) {
+ return null;
+ }
+ String ext = filename.toLowerCase(Locale.ROOT);
+ int tarIndex = ext.lastIndexOf("tar");
+ if (tarIndex >= 0) {
+ if ((tarIndex == 0) || (ext.charAt(tarIndex - 1) == '.')) {
+ int tarEnd = tarIndex + 3;
+ int rest = ext.length() - tarEnd;
+ if (rest == 0) {
+ return NONE;
+ }
+ if (ext.charAt(tarEnd) == '.') {
+ String compression = ext.substring(tarEnd + 1);
+ for (TarCompression cmp : values()) {
+ if (compression.equals(cmp.extension) || compression.equals(cmp.altExtension)) {
+ return cmp;
+ }
+ }
+ }
+ }
+ return null;
+ }
+ int lastDot = ext.lastIndexOf('.');
+ if (lastDot > 0) {
+ ext = ext.substring(lastDot + 1);
+ }
+ for (TarCompression cmp : values()) {
+ if (ext.equals(cmp.combinedExtension) || (ext.endsWith(cmp.combinedExtension)
+ && ext.charAt(ext.length() - cmp.combinedExtension.length() - 1) == '.')) {
+ return cmp;
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/log/IdeLogLevel.java b/cli/src/main/java/com/devonfw/tools/ide/log/IdeLogLevel.java
index 2758540bc..d3b135562 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/log/IdeLogLevel.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/log/IdeLogLevel.java
@@ -1,67 +1,67 @@
-package com.devonfw.tools.ide.log;
-
-import com.devonfw.tools.ide.context.IdeContext;
-
-/**
- * {@link Enum} with the available log-levels.
- *
- * @see IdeContext#level(IdeLogLevel)
- */
-public enum IdeLogLevel {
-
- /** {@link IdeLogLevel} for tracing (very detailed and verbose logging). */
- TRACE("\033[38;5;240m"),
-
- /** {@link IdeLogLevel} for debugging (more detailed logging). */
- DEBUG("\033[90m"),
-
- /** {@link IdeLogLevel} for general information (regular logging). */
- INFO("\033[39m"),
-
- /**
- * {@link IdeLogLevel} for a step (logs the step name and groups the following log statements until the next step).
- */
- STEP("\033[35m"),
-
- /** {@link IdeLogLevel} for user interaction (e.g. questions or options). */
- INTERACTION("\033[96m"),
-
- /** {@link IdeLogLevel} for success (an important aspect has been completed successfully). */
- SUCCESS("\033[92m"),
-
- /** {@link IdeLogLevel} for a warning (something unexpected or abnormal happened but can be compensated). */
- WARNING("\033[93m"),
-
- /**
- * {@link IdeLogLevel} for an error (something failed and we cannot proceed or the user has to continue with extreme
- * care).
- */
- ERROR("\033[91m");
-
- private final String color;
-
- /**
- * The constructor.
- */
- private IdeLogLevel(String color) {
-
- this.color = color;
- }
-
- /**
- * @return the prefix to append for colored output to set color according to this {@link IdeLogLevel}.
- */
- public String getStartColor() {
-
- return this.color;
- }
-
- /**
- * @return the suffix to append for colored output to reset to default color.
- */
- public String getEndColor() {
-
- return "\033[0m"; // reset color
- }
-
-}
+package com.devonfw.tools.ide.log;
+
+import com.devonfw.tools.ide.context.IdeContext;
+
+/**
+ * {@link Enum} with the available log-levels.
+ *
+ * @see IdeContext#level(IdeLogLevel)
+ */
+public enum IdeLogLevel {
+
+ /** {@link IdeLogLevel} for tracing (very detailed and verbose logging). */
+ TRACE("\033[38;5;240m"),
+
+ /** {@link IdeLogLevel} for debugging (more detailed logging). */
+ DEBUG("\033[90m"),
+
+ /** {@link IdeLogLevel} for general information (regular logging). */
+ INFO(null),
+
+ /**
+ * {@link IdeLogLevel} for a step (logs the step name and groups the following log statements until the next step).
+ */
+ STEP("\033[35m"),
+
+ /** {@link IdeLogLevel} for user interaction (e.g. questions or options). */
+ INTERACTION("\033[96m"),
+
+ /** {@link IdeLogLevel} for success (an important aspect has been completed successfully). */
+ SUCCESS("\033[92m"),
+
+ /** {@link IdeLogLevel} for a warning (something unexpected or abnormal happened but can be compensated). */
+ WARNING("\033[93m"),
+
+ /**
+ * {@link IdeLogLevel} for an error (something failed and we cannot proceed or the user has to continue with extreme
+ * care).
+ */
+ ERROR("\033[91m");
+
+ private final String color;
+
+ /**
+ * The constructor.
+ */
+ private IdeLogLevel(String color) {
+
+ this.color = color;
+ }
+
+ /**
+ * @return the prefix to append for colored output to set color according to this {@link IdeLogLevel}.
+ */
+ public String getStartColor() {
+
+ return this.color;
+ }
+
+ /**
+ * @return the suffix to append for colored output to reset to default color.
+ */
+ public String getEndColor() {
+
+ return "\033[0m"; // reset color
+ }
+
+}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/log/IdeSubLoggerOut.java b/cli/src/main/java/com/devonfw/tools/ide/log/IdeSubLoggerOut.java
index 39b00a16f..734cbea7e 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/log/IdeSubLoggerOut.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/log/IdeSubLoggerOut.java
@@ -1,61 +1,65 @@
-package com.devonfw.tools.ide.log;
-
-import java.io.IOException;
-
-/**
- * Default implementation of {@link IdeSubLogger} that can write to an {@link Appendable} such as {@link System#out} or in
- * case of testing a {@link java.io.StringWriter}.
- */
-public class IdeSubLoggerOut extends AbstractIdeSubLogger {
-
- private final Appendable out;
-
- private final boolean colored;
-
- /**
- * The constructor.
- *
- * @param level the {@link #getLevel() log-level}.
- * @param out the {@link Appendable} to {@link Appendable#append(CharSequence) write} log messages to.
- * @param colored - {@code true} for colored output according to {@link IdeLogLevel}, {@code false} otherwise.
- */
- public IdeSubLoggerOut(IdeLogLevel level, Appendable out, boolean colored) {
-
- super(level);
- if (out == null) {
- // this is on of the very rare excuses where System.out or System.err is allowed to be used!
- if (level == IdeLogLevel.ERROR) {
- this.out = System.err;
- } else {
- this.out = System.out;
- }
- } else {
- this.out = out;
- }
- this.colored = colored;
- }
-
- @Override
- public boolean isEnabled() {
-
- return true;
- }
-
- @Override
- public void log(String message) {
-
- try {
- if (this.colored) {
- this.out.append(this.level.getStartColor());
- }
- this.out.append(message);
- if (this.colored) {
- this.out.append(this.level.getEndColor());
- }
- this.out.append("\n");
- } catch (IOException e) {
- throw new IllegalStateException("Failed to log message: " + message, e);
- }
- }
-
-}
+package com.devonfw.tools.ide.log;
+
+import java.io.IOException;
+
+/**
+ * Default implementation of {@link IdeSubLogger} that can write to an {@link Appendable} such as {@link System#out} or
+ * in case of testing a {@link java.io.StringWriter}.
+ */
+public class IdeSubLoggerOut extends AbstractIdeSubLogger {
+
+ private final Appendable out;
+
+ private final boolean colored;
+
+ /**
+ * The constructor.
+ *
+ * @param level the {@link #getLevel() log-level}.
+ * @param out the {@link Appendable} to {@link Appendable#append(CharSequence) write} log messages to.
+ * @param colored - {@code true} for colored output according to {@link IdeLogLevel}, {@code false} otherwise.
+ */
+ public IdeSubLoggerOut(IdeLogLevel level, Appendable out, boolean colored) {
+
+ super(level);
+ if (out == null) {
+ // this is on of the very rare excuses where System.out or System.err is allowed to be used!
+ if (level == IdeLogLevel.ERROR) {
+ this.out = System.err;
+ } else {
+ this.out = System.out;
+ }
+ } else {
+ this.out = out;
+ }
+ this.colored = colored;
+ }
+
+ @Override
+ public boolean isEnabled() {
+
+ return true;
+ }
+
+ @Override
+ public void log(String message) {
+
+ try {
+ String startColor = null;
+ if (this.colored) {
+ startColor = this.level.getStartColor();
+ if (startColor != null) {
+ this.out.append(startColor);
+ }
+ }
+ this.out.append(message);
+ if (startColor != null) {
+ this.out.append(this.level.getEndColor());
+ }
+ this.out.append("\n");
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to log message: " + message, e);
+ }
+ }
+
+}
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 64a42822b..45396c7fa 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
@@ -1,101 +1,112 @@
-package com.devonfw.tools.ide.os;
-
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.Set;
-
-import com.devonfw.tools.ide.context.IdeContext;
-import com.devonfw.tools.ide.io.FileAccess;
-import com.devonfw.tools.ide.log.IdeLogger;
-
-/**
- * Internal helper class for MacOS workarounds.
- */
-public final class MacOsHelper {
-
- private static final Set INVALID_LINK_FOLDERS = Set.of(IdeContext.FOLDER_CONTENTS,
- IdeContext.FOLDER_RESOURCES, IdeContext.FOLDER_BIN);
-
- private final FileAccess fileAccess;
-
- private final SystemInfo systemInfo;
-
- private final IdeLogger logger;
-
- /**
- * The constructor.
- *
- * @param context the {@link IdeContext} instance.
- */
- public MacOsHelper(IdeContext context) {
-
- this(context.getFileAccess(), context.getSystemInfo(), context);
- }
-
- /**
- * The constructor.
- *
- * @param fileAccess the {@link FileAccess} instance.
- * @param systemInfo the {@link SystemInfo} instance.
- * @param logger the {@link IdeLogger} instance.
- */
- public MacOsHelper(FileAccess fileAccess, SystemInfo systemInfo, IdeLogger logger) {
-
- super();
- this.fileAccess = fileAccess;
- this.systemInfo = systemInfo;
- this.logger = logger;
- }
-
- /**
- * @param rootDir the {@link Path} to the root directory.
- * @return the {@link com.devonfw.tools.ide.tool.ToolInstallation#linkDir() link directory}.
- */
- public Path findLinkDir(Path rootDir) {
-
- if (!this.systemInfo.isMac() || Files.isDirectory(rootDir.resolve(IdeContext.FOLDER_BIN))) {
- return rootDir;
- }
- Path contentsDir = rootDir.resolve(IdeContext.FOLDER_CONTENTS);
- if (Files.isDirectory(contentsDir)) {
- return findLinkDir(contentsDir, rootDir);
- }
- Path appDir = this.fileAccess.findFirst(rootDir,
- p -> p.getFileName().toString().endsWith(".app") && Files.isDirectory(p), false);
- if (appDir != null) {
- contentsDir = appDir.resolve(IdeContext.FOLDER_CONTENTS);
- if (Files.isDirectory(contentsDir)) {
- return findLinkDir(contentsDir, rootDir);
- }
- }
- return rootDir;
- }
-
- private Path findLinkDir(Path contentsDir, Path rootDir) {
-
- this.logger.debug("Found MacOS app in {}", contentsDir);
- Path resourcesAppBin = contentsDir.resolve(IdeContext.FOLDER_RESOURCES).resolve(IdeContext.FOLDER_APP)
- .resolve(IdeContext.FOLDER_BIN);
- if (Files.isDirectory(resourcesAppBin)) {
- return resourcesAppBin.getParent();
- }
- Path linkDir = this.fileAccess.findFirst(contentsDir, this::acceptLinkDir, false);
- if (linkDir != null) {
- return linkDir;
- }
- return rootDir;
- }
-
- private boolean acceptLinkDir(Path path) {
-
- String filename = path.getFileName().toString();
- if (INVALID_LINK_FOLDERS.contains(filename) || filename.startsWith("_")) {
- return false;
- }
- if (Files.isDirectory(path.resolve(IdeContext.FOLDER_BIN))) {
- return true;
- }
- return false;
- }
-
-}
+package com.devonfw.tools.ide.os;
+
+import com.devonfw.tools.ide.context.IdeContext;
+import com.devonfw.tools.ide.io.FileAccess;
+import com.devonfw.tools.ide.log.IdeLogger;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.stream.Stream;
+
+/**
+ * Internal helper class for MacOS workarounds.
+ */
+public final class MacOsHelper {
+
+ private static final Set INVALID_LINK_FOLDERS = Set.of(IdeContext.FOLDER_CONTENTS,
+ IdeContext.FOLDER_RESOURCES, IdeContext.FOLDER_BIN);
+
+ private final FileAccess fileAccess;
+
+ private final SystemInfo systemInfo;
+
+ private final IdeLogger logger;
+
+ /**
+ * The constructor.
+ *
+ * @param context the {@link IdeContext} instance.
+ */
+ public MacOsHelper(IdeContext context) {
+
+ this(context.getFileAccess(), context.getSystemInfo(), context);
+ }
+
+ /**
+ * The constructor.
+ *
+ * @param fileAccess the {@link FileAccess} instance.
+ * @param systemInfo the {@link SystemInfo} instance.
+ * @param logger the {@link IdeLogger} instance.
+ */
+ public MacOsHelper(FileAccess fileAccess, SystemInfo systemInfo, IdeLogger logger) {
+
+ super();
+ this.fileAccess = fileAccess;
+ this.systemInfo = systemInfo;
+ this.logger = logger;
+ }
+
+ /**
+ * @param rootDir the {@link Path} to the root directory.
+ * @param tool the name of the tool to find the link directory for.
+ * @return the {@link com.devonfw.tools.ide.tool.ToolInstallation#linkDir() link directory}.
+ */
+ public Path findLinkDir(Path rootDir, String tool) {
+
+ if (!this.systemInfo.isMac() || Files.isDirectory(rootDir.resolve(IdeContext.FOLDER_BIN))) {
+ return rootDir;
+ }
+ Path contentsDir = rootDir.resolve(IdeContext.FOLDER_CONTENTS);
+ if (Files.isDirectory(contentsDir)) {
+ return findLinkDir(contentsDir, rootDir, tool);
+ }
+ Path appDir = this.fileAccess.findFirst(rootDir,
+ p -> p.getFileName().toString().endsWith(".app") && Files.isDirectory(p), false);
+ if (appDir != null) {
+ contentsDir = appDir.resolve(IdeContext.FOLDER_CONTENTS);
+ if (Files.isDirectory(contentsDir)) {
+ return findLinkDir(contentsDir, rootDir, tool);
+ }
+ }
+ return rootDir;
+ }
+
+ private Path findLinkDir(Path contentsDir, Path rootDir, String tool) {
+
+ this.logger.debug("Found MacOS app in {}", contentsDir);
+ Path resourcesAppBin = contentsDir.resolve(IdeContext.FOLDER_RESOURCES).resolve(IdeContext.FOLDER_APP)
+ .resolve(IdeContext.FOLDER_BIN);
+ if (Files.isDirectory(resourcesAppBin)) {
+ return resourcesAppBin.getParent();
+ }
+ Path linkDir = findContentSubfolder(contentsDir, tool);
+ if (linkDir != null) {
+ return linkDir;
+ }
+ return rootDir;
+ }
+
+ private Path findContentSubfolder(Path dir, String tool) {
+
+ try (Stream childStream = Files.list(dir)) {
+ Iterator iterator = childStream.iterator();
+ while (iterator.hasNext()) {
+ Path child = iterator.next();
+ String filename = child.getFileName().toString();
+ if (INVALID_LINK_FOLDERS.contains(filename) || filename.startsWith("_")) {
+ continue;
+ } else if (Files.isDirectory(child.resolve(IdeContext.FOLDER_BIN)) || Files.exists(child.resolve(tool))) {
+ return child;
+ }
+ }
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to search for file in " + dir, e);
+ }
+ return null;
+ }
+
+}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/os/OperatingSystem.java b/cli/src/main/java/com/devonfw/tools/ide/os/OperatingSystem.java
index c12d0c7ab..6370e4a50 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/os/OperatingSystem.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/os/OperatingSystem.java
@@ -1,78 +1,101 @@
-package com.devonfw.tools.ide.os;
-
-/**
- * Enum with the supported operating systems.
- */
-public enum OperatingSystem {
- /** Microsoft Windows. */
- WINDOWS("windows"),
-
- /** Apple MacOS (not iOS). */
- MAC("mac"),
-
- /** Linux (Ubunutu, Debian, SuSe, etc.) */
- LINUX("linux");
-
- private final String title;
-
- private OperatingSystem(String title) {
-
- this.title = title;
- }
-
- @Override
- public String toString() {
-
- return this.title;
- }
-
- /**
- * @param title the {@link #toString() string representation} of the requested {@link OperatingSystem}.
- * @return the according {@link OperatingSystem} or {@code null} if none matches.
- */
- public static OperatingSystem of(String title) {
-
- for (OperatingSystem os : values()) {
- if (os.toString().equals(title)) {
- return os;
- }
- }
- return null;
- }
-
- /**
- * @param suffix the file extension.
- * @return {@code true} if the given {@code suffix} is an executable file extension of this {@link OperatingSystem},
- * {@code false} otherwise.
- */
- public boolean isExecutable(String suffix) {
-
- if (suffix == null) {
- return false;
- }
- if (suffix.startsWith(".")) {
- suffix = suffix.substring(1);
- }
- if (this == WINDOWS) {
- if (suffix.equals("exe")) {
- return true;
- } else if (suffix.equals("msi")) {
- return true;
- } else if (suffix.equals("cmd")) {
- return true;
- } else if (suffix.equals("bat")) {
- return true;
- } else if (suffix.equals("ps1")) {
- return true;
- }
- } else {
- if (suffix.equals("sh")) {
- return true;
- } else if ((this == MAC) && suffix.equals("pkg")) {
- return true;
- }
- }
- return false;
- }
-
-}
+package com.devonfw.tools.ide.os;
+
+import java.util.Locale;
+
+/**
+ * Enum with the supported operating systems.
+ */
+public enum OperatingSystem {
+ /** Microsoft Windows. */
+ WINDOWS("windows"),
+
+ /** Apple MacOS (not iOS). */
+ MAC("mac"),
+
+ /** Linux (Ubunutu, Debian, SuSe, etc.) */
+ LINUX("linux");
+
+ private final String title;
+
+ private OperatingSystem(String title) {
+
+ this.title = title;
+ }
+
+ @Override
+ public String toString() {
+
+ return this.title;
+ }
+
+ /**
+ * @param title the {@link #toString() string representation} of the requested {@link OperatingSystem}.
+ * @return the according {@link OperatingSystem} or {@code null} if none matches.
+ */
+ public static OperatingSystem of(String title) {
+
+ for (OperatingSystem os : values()) {
+ if (os.title.equals(title)) {
+ return os;
+ }
+ }
+ return null;
+ }
+
+ public static OperatingSystem ofName(String osName) {
+
+ String os = osName.toLowerCase(Locale.ROOT);
+ if (os.startsWith("windows")) {
+ return OperatingSystem.WINDOWS;
+ } else if (os.startsWith("mac") || os.contains("darwin")) {
+ return OperatingSystem.MAC;
+ } else if (os.contains("linux")) {
+ return OperatingSystem.LINUX;
+ } else if (os.contains("bsd")) {
+ return OperatingSystem.LINUX;
+ } else if (os.contains("ix")) {
+ return OperatingSystem.LINUX;
+ } else {
+ System.err.println("ERROR: Unknown operating system '" + osName + "'");
+ // be tolerant: most of our users are working on windows
+ // in case of an odd JVM or virtualization issue let us better continue than failing
+ return OperatingSystem.WINDOWS;
+ }
+ }
+
+ /**
+ * @param suffix the file extension.
+ * @return {@code true} if the given {@code suffix} is an executable file extension of this {@link OperatingSystem},
+ * {@code false} otherwise.
+ */
+ public boolean isExecutable(String suffix) {
+
+ if (suffix == null) {
+ return false;
+ }
+ if (suffix.startsWith(".")) {
+ suffix = suffix.substring(1);
+ }
+ if (this == WINDOWS) {
+ if (suffix.equals("exe")) {
+ return true;
+ } else if (suffix.equals("msi")) {
+ return true;
+ } else if (suffix.equals("cmd")) {
+ return true;
+ } else if (suffix.equals("bat")) {
+ return true;
+ } else if (suffix.equals("ps1")) {
+ return true;
+ }
+ } else {
+ if (suffix.equals("sh")) {
+ return true;
+ } else if ((this == MAC) && suffix.equals("pkg")) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/os/SystemInfoImpl.java b/cli/src/main/java/com/devonfw/tools/ide/os/SystemInfoImpl.java
index 3e9e5a693..b2bcb1736 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/os/SystemInfoImpl.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/os/SystemInfoImpl.java
@@ -1,124 +1,104 @@
-package com.devonfw.tools.ide.os;
-
-import java.util.Locale;
-
-import com.devonfw.tools.ide.version.VersionIdentifier;
-
-/**
- * Implementation of {@link SystemInfo}.
- */
-public class SystemInfoImpl implements SystemInfo {
-
- /** Name of {@link System#getProperty(String) system-property} for {@link #getOsName()}: {@value}. */
- private static final String PROPERTY_OS_NAME = "os.name";
-
- /** Name of {@link System#getProperty(String) system-property} for #getArchitectureName()}: {@value}. */
- private static final String PROPERTY_OS_ARCHITECTURE = "os.arch";
-
- /** Name of {@link System#getProperty(String) system-property} for {@link #getOsVersion()}: {@value}. */
- private static final String PROPERTY_OS_VERSION = "os.version";
-
- private final String osName;
-
- private final VersionIdentifier osVersion;
-
- private final OperatingSystem os;
-
- private final String architectureName;
-
- private final SystemArchitecture architecture;
-
- /**
- * The constructor.
- */
- public SystemInfoImpl() {
-
- this(System.getProperty(PROPERTY_OS_NAME).trim(), System.getProperty(PROPERTY_OS_VERSION).trim(),
- System.getProperty(PROPERTY_OS_ARCHITECTURE).trim());
- }
-
- /**
- * The constructor.
- *
- * @param osName the {@link #getOsName() OS name}
- * @param osVersion the {@link #getOsVersion() OS version}.
- * @param architectureName the {@link #getArchitectureName() architecture name}.
- */
- public SystemInfoImpl(String osName, String osVersion, String architectureName) {
-
- super();
- this.osName = osName;
- this.osVersion = VersionIdentifier.of(osVersion);
- this.architectureName = architectureName;
- this.os = detectOs(this.osName);
- this.architecture = detectArchitecture(this.architectureName);
- }
-
- private static OperatingSystem detectOs(String osName) {
-
- String os = osName.toLowerCase(Locale.US);
- if (os.startsWith("windows")) {
- return OperatingSystem.WINDOWS;
- } else if (os.startsWith("mac") || os.contains("darwin")) {
- return OperatingSystem.MAC;
- } else if (os.contains("linux")) {
- return OperatingSystem.LINUX;
- } else if (os.contains("bsd")) {
- return OperatingSystem.LINUX;
- } else if (os.contains("ix")) {
- return OperatingSystem.LINUX;
- } else {
- System.err.println("ERROR: Unknown operating system '" + osName + "'");
- // be tolerant: most of our users are working on windows
- // in case of an odd JVM or virtualization issue let us better continue than failing
- return OperatingSystem.WINDOWS;
- }
- }
-
- private static SystemArchitecture detectArchitecture(String architectureName) {
-
- if (architectureName.contains("arm") || architectureName.contains("aarch")) {
- return SystemArchitecture.ARM64;
- } else {
- return SystemArchitecture.X64;
- }
- }
-
- @Override
- public OperatingSystem getOs() {
-
- return this.os;
- }
-
- @Override
- public String getOsName() {
-
- return this.osName;
- }
-
- @Override
- public VersionIdentifier getOsVersion() {
-
- return this.osVersion;
- }
-
- @Override
- public String getArchitectureName() {
-
- return this.architectureName;
- }
-
- @Override
- public SystemArchitecture getArchitecture() {
-
- return this.architecture;
- }
-
- @Override
- public String toString() {
-
- return this.os + "@" + this.architecture + "(" + this.osName + "[" + this.osVersion + "]@" + this.architectureName
- + ")";
- }
-
-}
+package com.devonfw.tools.ide.os;
+
+import com.devonfw.tools.ide.version.VersionIdentifier;
+
+/**
+ * Implementation of {@link SystemInfo}.
+ */
+public class SystemInfoImpl implements SystemInfo {
+
+ /** The default {@link SystemInfo} instance for the operating system running this program. */
+ public static final SystemInfo INSTANCE = new SystemInfoImpl();
+
+ /** Name of {@link System#getProperty(String) system-property} for {@link #getOsName()}: {@value}. */
+ private static final String PROPERTY_OS_NAME = "os.name";
+
+ /** Name of {@link System#getProperty(String) system-property} for #getArchitectureName()}: {@value}. */
+ private static final String PROPERTY_OS_ARCHITECTURE = "os.arch";
+
+ /** Name of {@link System#getProperty(String) system-property} for {@link #getOsVersion()}: {@value}. */
+ private static final String PROPERTY_OS_VERSION = "os.version";
+
+ private final String osName;
+
+ private final VersionIdentifier osVersion;
+
+ private final OperatingSystem os;
+
+ private final String architectureName;
+
+ private final SystemArchitecture architecture;
+
+ /**
+ * The constructor.
+ */
+ private SystemInfoImpl() {
+
+ this(System.getProperty(PROPERTY_OS_NAME).trim(), System.getProperty(PROPERTY_OS_VERSION).trim(),
+ System.getProperty(PROPERTY_OS_ARCHITECTURE).trim());
+ }
+
+ /**
+ * The constructor.
+ *
+ * @param osName the {@link #getOsName() OS name}
+ * @param osVersion the {@link #getOsVersion() OS version}.
+ * @param architectureName the {@link #getArchitectureName() architecture name}.
+ */
+ public SystemInfoImpl(String osName, String osVersion, String architectureName) {
+
+ super();
+ this.osName = osName;
+ this.osVersion = VersionIdentifier.of(osVersion);
+ this.architectureName = architectureName;
+ this.os = OperatingSystem.ofName(this.osName);
+ this.architecture = detectArchitecture(this.architectureName);
+ }
+
+ private static SystemArchitecture detectArchitecture(String architectureName) {
+
+ if (architectureName.contains("arm") || architectureName.contains("aarch")) {
+ return SystemArchitecture.ARM64;
+ } else {
+ return SystemArchitecture.X64;
+ }
+ }
+
+ @Override
+ public OperatingSystem getOs() {
+
+ return this.os;
+ }
+
+ @Override
+ public String getOsName() {
+
+ return this.osName;
+ }
+
+ @Override
+ public VersionIdentifier getOsVersion() {
+
+ return this.osVersion;
+ }
+
+ @Override
+ public String getArchitectureName() {
+
+ return this.architectureName;
+ }
+
+ @Override
+ public SystemArchitecture getArchitecture() {
+
+ return this.architecture;
+ }
+
+ @Override
+ public String toString() {
+
+ return this.os + "@" + this.architecture + "(" + this.osName + "[" + this.osVersion + "]@" + this.architectureName
+ + ")";
+ }
+
+}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContext.java b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContext.java
index f2c6cc4a0..5630b4931 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContext.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContext.java
@@ -132,7 +132,7 @@ default ProcessContext addArgs(List>... args) {
*/
default int run() {
- return run(false, false).getExitCode();
+ return run(ProcessMode.DEFAULT).getExitCode();
}
/**
@@ -140,28 +140,9 @@ default int run() {
* arguments}. Will reset the {@link #addArgs(String...) arguments} but not the {@link #executable(Path) command} for
* sub-sequent calls.
*
- * @param capture - {@code true} to capture standard {@link ProcessResult#getOut() out} and
- * {@link ProcessResult#getErr() err} in the {@link ProcessResult}, {@code false} otherwise (to redirect out
- * and err).
+ * @param processMode {@link ProcessMode}
* @return the {@link ProcessResult}.
*/
- default ProcessResult run(boolean capture) {
-
- return run(capture, false);
- }
-
- /**
- * Runs the previously configured {@link #executable(Path) command} with the configured {@link #addArgs(String...)
- * arguments}. Will reset the {@link #addArgs(String...) arguments} but not the {@link #executable(Path) command} for
- * sub-sequent calls.
- *
- * @param capture - {@code true} to capture standard {@link ProcessResult#getOut() out} and
- * {@link ProcessResult#getErr() err} in the {@link ProcessResult}, {@code false} otherwise (to redirect out
- * and err).
- * @param runInBackground {@code true}, the process of the command will be run as background process, {@code false}
- * otherwise (it will be run as foreground process).
- * @return the {@link ProcessResult}.
- */
- ProcessResult run(boolean capture, boolean runInBackground);
+ ProcessResult run(ProcessMode processMode);
}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java
index 4fb1556f9..2afec83ad 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java
@@ -5,6 +5,7 @@
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.ProcessBuilder.Redirect;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
@@ -14,17 +15,21 @@
import java.util.stream.Collectors;
import com.devonfw.tools.ide.cli.CliException;
+import com.devonfw.tools.ide.common.SystemPath;
import com.devonfw.tools.ide.context.IdeContext;
import com.devonfw.tools.ide.environment.VariableLine;
import com.devonfw.tools.ide.log.IdeSubLogger;
+import com.devonfw.tools.ide.os.SystemInfoImpl;
import com.devonfw.tools.ide.util.FilenameUtil;
/**
* Implementation of {@link ProcessContext}.
*/
-public final class ProcessContextImpl implements ProcessContext {
+public class ProcessContextImpl implements ProcessContext {
- private final IdeContext context;
+ private static final String PREFIX_USR_BIN_ENV = "/usr/bin/env ";
+
+ protected final IdeContext context;
private final ProcessBuilder processBuilder;
@@ -44,9 +49,6 @@ public ProcessContextImpl(IdeContext context) {
super();
this.context = context;
this.processBuilder = new ProcessBuilder();
- // TODO needs to be configurable for GUI
- // this.processBuilder.inheritIO();
- this.processBuilder.redirectOutput(Redirect.INHERIT).redirectError(Redirect.INHERIT);
this.errorHandling = ProcessErrorHandling.THROW;
Map environment = this.processBuilder.environment();
for (VariableLine var : this.context.getVariables().collectExportedVariables()) {
@@ -68,7 +70,13 @@ public ProcessContext errorHandling(ProcessErrorHandling handling) {
@Override
public ProcessContext directory(Path directory) {
- this.processBuilder.directory(directory.toFile());
+ if (directory != null) {
+ this.processBuilder.directory(directory.toFile());
+ } else {
+ context.debug(
+ "Could not set the process builder's working directory! Directory of the current java process is used.");
+ }
+
return this;
}
@@ -98,91 +106,85 @@ public ProcessContext withEnvVar(String key, String value) {
}
@Override
- public ProcessResult run(boolean capture, boolean isBackgroundProcess) {
+ public ProcessResult run(ProcessMode processMode) {
- if (isBackgroundProcess) {
- this.context
- .warning("TODO https://github.com/devonfw/IDEasy/issues/9 Implement background process functionality");
+ // TODO ProcessMode needs to be configurable for GUI
+ if (processMode == ProcessMode.DEFAULT) {
+ this.processBuilder.redirectOutput(Redirect.INHERIT).redirectError(Redirect.INHERIT);
}
if (this.executable == null) {
throw new IllegalStateException("Missing executable to run process!");
}
- String executableName = this.executable.toString();
- // pragmatic solution to avoid copying lists/arrays
- this.arguments.add(0, executableName);
- String fileExtension = FilenameUtil.getExtension(executableName);
- boolean isBashScript = "sh".equals(fileExtension) || hasSheBang(this.executable);
- if (isBashScript) {
- String bash = "bash";
- if (this.context.getSystemInfo().isWindows()) {
- String findBashOnWindowsResult = findBashOnWindows();
- if (findBashOnWindowsResult != null) {
- bash = findBashOnWindowsResult;
- }
- }
- this.arguments.add(0, bash);
- }
- this.processBuilder.command(this.arguments);
+ List args = new ArrayList<>(this.arguments.size() + 4);
+ String interpreter = addExecutable(this.executable.toString(), args);
+ args.addAll(this.arguments);
if (this.context.debug().isEnabled()) {
- String message = createCommandMessage(" ...");
+ String message = createCommandMessage(interpreter, " ...");
this.context.debug(message);
}
+
try {
- if (capture) {
+
+ if (processMode == ProcessMode.DEFAULT_CAPTURE) {
this.processBuilder.redirectOutput(Redirect.PIPE).redirectError(Redirect.PIPE);
+ } else if (processMode.isBackground()) {
+ modifyArgumentsOnBackgroundProcess(processMode);
}
+
+ this.processBuilder.command(args);
+
+ Process process = this.processBuilder.start();
+
List out = null;
List err = null;
- Process process = this.processBuilder.start();
- if (capture) {
- try (BufferedReader outReader = new BufferedReader(new InputStreamReader(process.getInputStream()));) {
+
+ if (processMode == ProcessMode.DEFAULT_CAPTURE) {
+ try (BufferedReader outReader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
out = outReader.lines().collect(Collectors.toList());
}
try (BufferedReader errReader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
err = errReader.lines().collect(Collectors.toList());
}
}
- int exitCode = process.waitFor();
- ProcessResult result = new ProcessResultImpl(exitCode, out, err);
- if (!result.isSuccessful() && (this.errorHandling != ProcessErrorHandling.NONE)) {
- String message = createCommandMessage(" failed with exit code " + exitCode + "!");
- if (this.errorHandling == ProcessErrorHandling.THROW) {
- throw new CliException(message, exitCode);
- }
- IdeSubLogger level;
- if (this.errorHandling == ProcessErrorHandling.ERROR) {
- level = this.context.error();
- } else if (this.errorHandling == ProcessErrorHandling.WARNING) {
- level = this.context.warning();
- } else {
- level = this.context.error();
- level.log("Internal error: Undefined error handling {}", this.errorHandling);
- }
- level.log(message);
+
+ int exitCode;
+ if (processMode.isBackground()) {
+ exitCode = ProcessResult.SUCCESS;
+ } else {
+ exitCode = process.waitFor();
}
+
+ ProcessResult result = new ProcessResultImpl(exitCode, out, err);
+ performLogOnError(result, exitCode, interpreter);
+
return result;
+
} catch (Exception e) {
String msg = e.getMessage();
if ((msg == null) || msg.isEmpty()) {
msg = e.getClass().getSimpleName();
}
- throw new IllegalStateException(createCommandMessage(" failed: " + msg), e);
+ throw new IllegalStateException(createCommandMessage(interpreter, " failed: " + msg), e);
} finally {
this.arguments.clear();
}
}
- private String createCommandMessage(String suffix) {
+ private String createCommandMessage(String interpreter, String suffix) {
StringBuilder sb = new StringBuilder();
sb.append("Running command '");
sb.append(this.executable);
sb.append("'");
+ if (interpreter != null) {
+ sb.append(" using ");
+ sb.append(interpreter);
+ }
int size = this.arguments.size();
- if (size > 1) {
+ if (size > 0) {
sb.append(" with arguments");
- for (int i = 1; i < size; i++) {
+ for (int i = 0; i < size; i++) {
String arg = this.arguments.get(i);
sb.append(" '");
sb.append(arg);
@@ -190,22 +192,37 @@ private String createCommandMessage(String suffix) {
}
}
sb.append(suffix);
- String message = sb.toString();
- return message;
+ return sb.toString();
}
- private boolean hasSheBang(Path file) {
+ private String getSheBang(Path file) {
try (InputStream in = Files.newInputStream(file)) {
- byte[] buffer = new byte[2];
+ // "#!/usr/bin/env bash".length() = 19
+ byte[] buffer = new byte[32];
int read = in.read(buffer);
- if ((read == 2) && (buffer[0] == '#') && (buffer[1] == '!')) {
- return true;
+ if ((read > 2) && (buffer[0] == '#') && (buffer[1] == '!')) {
+ int start = 2;
+ int end = 2;
+ while (end < read) {
+ byte c = buffer[end];
+ if ((c == '\n') || (c == '\r') || (c > 127)) {
+ break;
+ } else if ((end == start) && (c == ' ')) {
+ start++;
+ }
+ end++;
+ }
+ String sheBang = new String(buffer, start, end - start, StandardCharsets.US_ASCII).trim();
+ if (sheBang.startsWith(PREFIX_USR_BIN_ENV)) {
+ sheBang = sheBang.substring(PREFIX_USR_BIN_ENV.length());
+ }
+ return sheBang;
}
} catch (IOException e) {
// ignore...
}
- return false;
+ return null;
}
private String findBashOnWindows() {
@@ -259,4 +276,127 @@ private String findBashOnWindows() {
throw new IllegalStateException("Could not find Bash. Please install Git for Windows and rerun.");
}
-}
+ private String addExecutable(String executable, List args) {
+
+ if (!SystemInfoImpl.INSTANCE.isWindows()) {
+ args.add(executable);
+ return null;
+ }
+ String interpreter = null;
+ String fileExtension = FilenameUtil.getExtension(executable);
+ boolean isBashScript = "sh".equals(fileExtension);
+ if (!isBashScript) {
+ String sheBang = getSheBang(this.executable);
+ if (sheBang != null) {
+ String cmd = sheBang;
+ int lastSlash = cmd.lastIndexOf('/');
+ if (lastSlash >= 0) {
+ cmd = cmd.substring(lastSlash + 1);
+ }
+ if (cmd.equals("bash")) {
+ isBashScript = true;
+ } else {
+ // currently we do not support other interpreters...
+ }
+ }
+ }
+ if (isBashScript) {
+ String bash = "bash";
+ interpreter = bash;
+ // here we want to have native OS behavior even if OS is mocked during tests...
+ // if (this.context.getSystemInfo().isWindows()) {
+ if (SystemInfoImpl.INSTANCE.isWindows()) {
+ String findBashOnWindowsResult = findBashOnWindows();
+ if (findBashOnWindowsResult != null) {
+ bash = findBashOnWindowsResult;
+ }
+ }
+ args.add(bash);
+ }
+ if ("msi".equalsIgnoreCase(fileExtension)) {
+ args.add(0, "/i");
+ args.add(0, "msiexec");
+ }
+ args.add(executable);
+ return interpreter;
+ }
+
+ private void performLogOnError(ProcessResult result, int exitCode, String interpreter) {
+
+ if (!result.isSuccessful() && (this.errorHandling != ProcessErrorHandling.NONE)) {
+ String message = createCommandMessage(interpreter, " failed with exit code " + exitCode + "!");
+ if (this.errorHandling == ProcessErrorHandling.THROW) {
+ throw new CliException(message, exitCode);
+ }
+ IdeSubLogger level;
+ if (this.errorHandling == ProcessErrorHandling.ERROR) {
+ level = this.context.error();
+ } else if (this.errorHandling == ProcessErrorHandling.WARNING) {
+ level = this.context.warning();
+ } else {
+ level = this.context.error();
+ level.log("Internal error: Undefined error handling {}", this.errorHandling);
+ }
+ level.log(message);
+ }
+ }
+
+ private void modifyArgumentsOnBackgroundProcess(ProcessMode processMode) {
+
+ if (processMode == ProcessMode.BACKGROUND) {
+ this.processBuilder.redirectOutput(Redirect.INHERIT).redirectError(Redirect.INHERIT);
+ } else if (processMode == ProcessMode.BACKGROUND_SILENT) {
+ this.processBuilder.redirectOutput(Redirect.DISCARD).redirectError(Redirect.DISCARD);
+ } else {
+ throw new IllegalStateException("Cannot handle non background process mode!");
+ }
+
+ String bash = "bash";
+
+ // try to use bash in windows to start the process
+ if (context.getSystemInfo().isWindows()) {
+
+ String findBashOnWindowsResult = findBashOnWindows();
+ if (findBashOnWindowsResult != null) {
+
+ bash = findBashOnWindowsResult;
+
+ } else {
+ context.warning(
+ "Cannot start background process in windows! No bash installation found, output will be discarded.");
+ this.processBuilder.redirectOutput(Redirect.DISCARD).redirectError(Redirect.DISCARD);
+ return;
+ }
+ }
+
+ String commandToRunInBackground = buildCommandToRunInBackground();
+
+ this.arguments.clear();
+ this.arguments.add(bash);
+ this.arguments.add("-c");
+ commandToRunInBackground += " & disown";
+ this.arguments.add(commandToRunInBackground);
+
+ }
+
+ private String buildCommandToRunInBackground() {
+
+ if (context.getSystemInfo().isWindows()) {
+
+ StringBuilder stringBuilder = new StringBuilder();
+
+ for (String argument : this.arguments) {
+
+ if (SystemPath.isValidWindowsPath(argument)) {
+ argument = SystemPath.convertWindowsPathToUnixPath(argument);
+ }
+
+ stringBuilder.append(argument);
+ stringBuilder.append(" ");
+ }
+ return stringBuilder.toString().trim();
+ } else {
+ return this.arguments.stream().map(Object::toString).collect(Collectors.joining(" "));
+ }
+ }
+}
\ No newline at end of file
diff --git a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessMode.java b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessMode.java
new file mode 100644
index 000000000..478b9fba3
--- /dev/null
+++ b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessMode.java
@@ -0,0 +1,51 @@
+package com.devonfw.tools.ide.process;
+
+/**
+ * The ProcessMode defines how to start the command process and how output streams are handled using
+ * {@link ProcessBuilder}. Modes that can be used: {@link #BACKGROUND} {@link #BACKGROUND_SILENT} {@link #DEFAULT}
+ * {@link #DEFAULT_CAPTURE}
+ */
+public enum ProcessMode {
+ /**
+ * The process of the command will be run like a background process. Technically the parent process will simply not
+ * await its child process and a shell is used to start the process. The parent process will get the output of the
+ * child process using {@link ProcessBuilder.Redirect#INHERIT}. In Unix systems the equivalent of appending an '&
+ * disown' is used to detach the subprocess from its parent process. In Unix terms, the shell will not send a SIGHUP
+ * signal but the process remains connected to the terminal so that output is still received. (Only '&' is not used
+ * because it just removes awaiting but not sending of SIGHUP. Using nohup would simply result in redirecting output
+ * to a nohup.out file.)
+ */
+ BACKGROUND,
+ /**
+ * Like {@link #BACKGROUND}. Instead of redirecting the output to the parent process, the output is redirected to the
+ * 'null file' using {@link ProcessBuilder.Redirect#DISCARD}.
+ */
+ BACKGROUND_SILENT,
+ /**
+ * The process will be started according {@link ProcessBuilder.Redirect#INHERIT} without any detaching of parent
+ * process and child process. This setting makes the child process dependant from the parent process! (If you close
+ * the parent process the child process will also be terminated.)
+ */
+ DEFAULT,
+ /**
+ * The process will be started according {@link ProcessBuilder.Redirect#PIPE} and the standard output and standard
+ * error streams will be captured from the parent process. In other words, they will be printed in the console of the
+ * parent process. This setting makes the child process dependant from the parent process! (If you close the parent
+ * process the child process will also be terminated.)
+ */
+ DEFAULT_CAPTURE;
+
+ /**
+ * Method to check if the ProcessMode is a background process.
+ *
+ * @return {@code true} if the {@link ProcessMode} is {@link ProcessMode#BACKGROUND} or
+ * {@link ProcessMode#BACKGROUND_SILENT}, {@code false} if not.
+ */
+ public boolean isBackground() {
+
+ return this == BACKGROUND || this == BACKGROUND_SILENT;
+ }
+
+ // TODO ADD EXTERNAL_WINDOW_MODE IN FUTURE Issue: https://github.com/devonfw/IDEasy/issues/218
+
+}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/EnumProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/EnumProperty.java
new file mode 100644
index 000000000..d1db549e0
--- /dev/null
+++ b/cli/src/main/java/com/devonfw/tools/ide/property/EnumProperty.java
@@ -0,0 +1,59 @@
+package com.devonfw.tools.ide.property;
+
+import java.util.Locale;
+
+import com.devonfw.tools.ide.commandlet.Commandlet;
+import com.devonfw.tools.ide.completion.CompletionCandidateCollector;
+import com.devonfw.tools.ide.context.IdeContext;
+
+/**
+ * {@link Property} with {@link #getValueType() value type} {@link Boolean}.
+ */
+public class EnumProperty> extends Property {
+
+ private final Class valueType;
+
+ /**
+ * The constructor.
+ *
+ * @param name the {@link #getName() property name}.
+ * @param required the {@link #isRequired() required flag}.
+ * @param alias the {@link #getAlias() property alias}.
+ */
+ public EnumProperty(String name, boolean required, String alias, Class valueType) {
+
+ super(name, required, alias, null);
+ this.valueType = valueType;
+ }
+
+ @Override
+ public Class getValueType() {
+
+ return this.valueType;
+ }
+
+ @Override
+ public V parse(String valueAsString, IdeContext context) {
+
+ for (V enumConstant : this.valueType.getEnumConstants()) {
+ String name = enumConstant.name().toLowerCase(Locale.ROOT);
+ if (name.equals(valueAsString)) {
+ return enumConstant;
+ }
+ }
+
+ throw new IllegalArgumentException(String.format("Invalid Enum option: %s", valueAsString));
+ }
+
+ @Override
+ protected void completeValue(String arg, IdeContext context, Commandlet commandlet,
+ CompletionCandidateCollector collector) {
+
+ for (V enumConstant : this.valueType.getEnumConstants()) {
+ String name = enumConstant.name().toLowerCase(Locale.ROOT);
+ if (name.startsWith(arg)) {
+ collector.add(name, null, this, commandlet);
+ }
+ }
+ }
+}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/PasswordProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/PasswordProperty.java
new file mode 100644
index 000000000..a418a12b6
--- /dev/null
+++ b/cli/src/main/java/com/devonfw/tools/ide/property/PasswordProperty.java
@@ -0,0 +1,20 @@
+package com.devonfw.tools.ide.property;
+
+/**
+ * {@link Property} with {@link #getValueType() value type} {@link String} representing a password.
+ */
+public class PasswordProperty extends StringProperty {
+
+ /**
+ * The constructor.
+ *
+ * @param name the {@link #getName() property name}.
+ * @param required the {@link #isRequired() required flag}.
+ * @param alias the {@link #getAlias() property alias}.
+ */
+ public PasswordProperty(String name, boolean required, String alias) {
+
+ super(name, required, alias);
+ }
+
+}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java
new file mode 100644
index 000000000..8059c201e
--- /dev/null
+++ b/cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java
@@ -0,0 +1,60 @@
+package com.devonfw.tools.ide.property;
+
+import com.devonfw.tools.ide.context.IdeContext;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.function.Consumer;
+
+public class RepositoryProperty extends FileProperty {
+
+ /**
+ * The constructor.
+ *
+ * @param name the {@link #getName() property name}.
+ * @param required the {@link #isRequired() required flag}.
+ * @param alias the {@link #getAlias() property alias}.
+ */
+ public RepositoryProperty(String name, boolean required, String alias) {
+
+ super(name, required, alias, true);
+ }
+
+ /**
+ * The constructor.
+ *
+ * @param name the {@link #getName() property name}.
+ * @param required the {@link #isRequired() required flag}.
+ * @param alias the {@link #getAlias() property alias}.
+ * @param validator the {@link Consumer} used to {@link #validate() validate} the {@link #getValue() value}.
+ */
+ public RepositoryProperty(String name, boolean required, String alias, Consumer validator) {
+
+ super(name, required, alias, true, validator);
+ }
+
+ public Path parse(String valueAsString, IdeContext context) {
+
+ if (valueAsString == null) {
+ return null;
+ }
+
+ Path repositoryFile = Path.of(valueAsString);
+ if (!Files.exists(repositoryFile)) {
+ Path repositoriesPath = context.getSettingsPath().resolve(IdeContext.FOLDER_REPOSITORIES);
+ Path legacyRepositoriesPath = context.getSettingsPath().resolve(IdeContext.FOLDER_LEGACY_REPOSITORIES);
+ String propertiesFileName = valueAsString;
+ if (!valueAsString.endsWith(".properties")) {
+ propertiesFileName += ".properties";
+ }
+ repositoryFile = context.getFileAccess().findExistingFile(propertiesFileName,
+ Arrays.asList(repositoriesPath, legacyRepositoriesPath));
+ }
+ if (repositoryFile == null) {
+ throw new IllegalStateException("Could not find properties file: " + valueAsString);
+ }
+ return repositoryFile;
+ }
+
+}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/VersionProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/VersionProperty.java
index 5ceb204aa..a93d2942f 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/property/VersionProperty.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/property/VersionProperty.java
@@ -1,98 +1,98 @@
-package com.devonfw.tools.ide.property;
-
+package com.devonfw.tools.ide.property;
+
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.IntStream;
-import com.devonfw.tools.ide.commandlet.Commandlet;
-import com.devonfw.tools.ide.completion.CompletionCandidate;
-import com.devonfw.tools.ide.completion.CompletionCandidateCollector;
-import com.devonfw.tools.ide.context.IdeContext;
-import com.devonfw.tools.ide.tool.ToolCommandlet;
-import com.devonfw.tools.ide.version.VersionIdentifier;
-import com.devonfw.tools.ide.version.VersionSegment;
-
-/**
- * {@link Property} for {@link VersionIdentifier} as {@link #getValueType() value type}.
- */
-public class VersionProperty extends Property {
-
- /**
- * The constructor.
- *
- * @param name the {@link #getName() property name}.
- * @param required the {@link #isRequired() required flag}.
- * @param alias the {@link #getAlias() property alias}.
- */
- public VersionProperty(String name, boolean required, String alias) {
-
- this(name, required, alias, null);
- }
-
- /**
- * The constructor.
- *
- * @param name the {@link #getName() property name}.
- * @param required the {@link #isRequired() required flag}.
- * @param alias the {@link #getAlias() property alias}.
- * @param validator the {@link Consumer} used to {@link #validate() validate} the {@link #getValue() value}.
- */
- public VersionProperty(String name, boolean required, String alias, Consumer validator) {
-
- super(name, required, alias, validator);
- }
-
- @Override
- public Class getValueType() {
-
- return VersionIdentifier.class;
- }
-
- @Override
- public VersionIdentifier parse(String valueAsString, IdeContext context) {
-
- return VersionIdentifier.of(valueAsString);
- }
-
- @Override
- protected void completeValue(String arg, IdeContext context, Commandlet commandlet,
- CompletionCandidateCollector collector) {
-
- ToolCommandlet tool = commandlet.getToolForVersionCompletion();
- if (tool != null) {
- completeVersion(VersionIdentifier.of(arg), tool, context, commandlet, collector);
- }
- }
+import com.devonfw.tools.ide.commandlet.Commandlet;
+import com.devonfw.tools.ide.completion.CompletionCandidate;
+import com.devonfw.tools.ide.completion.CompletionCandidateCollector;
+import com.devonfw.tools.ide.context.IdeContext;
+import com.devonfw.tools.ide.tool.ToolCommandlet;
+import com.devonfw.tools.ide.version.VersionIdentifier;
+import com.devonfw.tools.ide.version.VersionSegment;
+
+/**
+ * {@link Property} for {@link VersionIdentifier} as {@link #getValueType() value type}.
+ */
+public class VersionProperty extends Property {
+
+ /**
+ * The constructor.
+ *
+ * @param name the {@link #getName() property name}.
+ * @param required the {@link #isRequired() required flag}.
+ * @param alias the {@link #getAlias() property alias}.
+ */
+ public VersionProperty(String name, boolean required, String alias) {
+
+ this(name, required, alias, null);
+ }
+
+ /**
+ * The constructor.
+ *
+ * @param name the {@link #getName() property name}.
+ * @param required the {@link #isRequired() required flag}.
+ * @param alias the {@link #getAlias() property alias}.
+ * @param validator the {@link Consumer} used to {@link #validate() validate} the {@link #getValue() value}.
+ */
+ public VersionProperty(String name, boolean required, String alias, Consumer validator) {
+
+ super(name, required, alias, validator);
+ }
+
+ @Override
+ public Class getValueType() {
+
+ return VersionIdentifier.class;
+ }
+
+ @Override
+ public VersionIdentifier parse(String valueAsString, IdeContext context) {
+
+ return VersionIdentifier.of(valueAsString);
+ }
+
+ @Override
+ protected void completeValue(String arg, IdeContext context, Commandlet commandlet,
+ CompletionCandidateCollector collector) {
+
+ ToolCommandlet tool = commandlet.getToolForVersionCompletion();
+ if (tool != null) {
+ completeVersion(VersionIdentifier.of(arg), tool, context, commandlet, collector);
+ }
+ }
private void completeVersion(VersionIdentifier version2complete, ToolCommandlet tool, IdeContext context, Commandlet commandlet, CompletionCandidateCollector collector) {
-
- collector.disableSorting();
- if (tool != null) {
- String text;
- if (version2complete == null) {
- text = "";
- } else {
- text = version2complete.toString();
- if (version2complete.isPattern()) {
- collector.add(text, "Given version pattern.", this, commandlet);
- return;
- }
- }
- List versions = context.getUrls().getSortedVersions(tool.getName(),
- tool.getEdition());
- int size = versions.size();
- String[] sorderCandidates = IntStream.rangeClosed(1, size).mapToObj(i -> versions.get(size - i).toString())
- .toArray(String[]::new);
- collector.addAllMatches(text, sorderCandidates, this, commandlet);
- List candidates = collector.getCandidates();
- Collections.reverse(candidates);
- CompletionCandidate latest = collector.createCandidate(text + VersionSegment.PATTERN_MATCH_ANY_STABLE_VERSION, "Latest stable matching version", this, commandlet);
- if (candidates.isEmpty()) {
- candidates.add(latest);
- } else {
- candidates.add(1, latest);
- }
+
+ collector.disableSorting();
+ if (tool != null) {
+ String text;
+ if (version2complete == null) {
+ text = "";
+ } else {
+ text = version2complete.toString();
+ if (version2complete.isPattern()) {
+ collector.add(text, "Given version pattern.", this, commandlet);
+ return;
+ }
+ }
+ List versions = context.getUrls().getSortedVersions(tool.getName(), tool.getEdition());
+ int size = versions.size();
+ String[] sortedCandidates = IntStream.rangeClosed(1, size).mapToObj(i -> versions.get(size - i).toString())
+ .toArray(String[]::new);
+ collector.addAllMatches(text, sortedCandidates, this, commandlet);
+ List candidates = collector.getCandidates();
+ Collections.reverse(candidates);
+ CompletionCandidate latest = collector.createCandidate(text + VersionSegment.PATTERN_MATCH_ANY_STABLE_VERSION,
+ "Latest stable matching version", this, commandlet);
+ if (candidates.isEmpty()) {
+ candidates.add(latest);
+ } else {
+ candidates.add(1, latest);
+ }
collector.add(text + VersionSegment.PATTERN_MATCH_ANY_VERSION, "Latest matching version including unstable versions", this, commandlet);
- }
- }
-}
+ }
+ }
+}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolRepositoryImpl.java b/cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolRepositoryImpl.java
index 29fb8172d..584535d7f 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolRepositoryImpl.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolRepositoryImpl.java
@@ -164,7 +164,7 @@ public static CustomToolRepository of(IdeContext context) {
String url = getString(jsonToolObject, "url", defaultUrl);
boolean osAgnostic = getBoolean(jsonToolObject, "os-agnostic", Boolean.FALSE);
boolean archAgnostic = getBoolean(jsonToolObject, "arch-agnostic", Boolean.TRUE);
- if (defaultUrl.isEmpty()) {
+ if (url.isEmpty()) {
throw new IllegalStateException("Missing 'url' property for tool '" + name + "'!");
}
// TODO
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 170f9d36c..be9d13cdb 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
@@ -1,5 +1,11 @@
package com.devonfw.tools.ide.tool;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
import com.devonfw.tools.ide.common.Tag;
import com.devonfw.tools.ide.context.IdeContext;
import com.devonfw.tools.ide.io.FileAccess;
@@ -9,10 +15,6 @@
import com.devonfw.tools.ide.repo.ToolRepository;
import com.devonfw.tools.ide.version.VersionIdentifier;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.Set;
-
/**
* {@link ToolCommandlet} that is installed globally.
*/
@@ -31,6 +33,63 @@ public GlobalToolCommandlet(IdeContext context, String tool, Set tags) {
super(context, tool, tags);
}
+ /**
+ * Performs the installation of the {@link #getName() tool} via a package manager.
+ *
+ * @param silent - {@code true} if called recursively to suppress verbose logging, {@code false} otherwise.
+ * @param commands - A {@link Map} containing the commands used to perform the installation for each package manager.
+ * @return {@code true} if the tool was newly installed, {@code false} if the tool was already installed before and
+ * nothing has changed.
+ */
+ protected boolean installWithPackageManger(Map> commands, boolean silent) {
+
+ Path binaryPath = this.context.getPath().findBinary(Path.of(getBinaryName()));
+
+ if (binaryPath != null && Files.exists(binaryPath) && !this.context.isForceMode()) {
+ IdeLogLevel level = silent ? IdeLogLevel.DEBUG : IdeLogLevel.INFO;
+ this.context.level(level).log("{} is already installed at {}", this.tool, binaryPath);
+ return false;
+ }
+
+ Path bashPath = this.context.getPath().findBinary(Path.of("bash"));
+ if (bashPath == null || !Files.exists(bashPath)) {
+ context.warning("Bash was not found on this machine. Not Proceeding with installation of tool " + this.tool);
+ return false;
+ }
+
+ PackageManager foundPackageManager = null;
+ for (PackageManager pm : commands.keySet()) {
+ if (Files.exists(this.context.getPath().findBinary(Path.of(pm.toString().toLowerCase())))) {
+ foundPackageManager = pm;
+ break;
+ }
+ }
+
+ int finalExitCode = 0;
+ if (foundPackageManager == null) {
+ context.warning("No supported Package Manager found for installation");
+ return false;
+ } else {
+ List commandList = commands.get(foundPackageManager);
+ if (commandList != null) {
+ for (String command : commandList) {
+ ProcessContext pc = this.context.newProcess().errorHandling(ProcessErrorHandling.WARNING).executable(bashPath)
+ .addArgs("-c", command);
+ finalExitCode = pc.run();
+ }
+ }
+ }
+
+ if (finalExitCode == 0) {
+ this.context.success("Successfully installed {}", this.tool);
+ } else {
+ this.context.warning("{} was not successfully installed", this.tool);
+ return false;
+ }
+ postInstall();
+ return true;
+ }
+
@Override
protected boolean isExtract() {
@@ -58,17 +117,20 @@ protected boolean doInstall(boolean silent) {
// download and install the global tool
FileAccess fileAccess = this.context.getFileAccess();
Path target = toolRepository.download(this.tool, edition, resolvedVersion);
- Path tmpDir = fileAccess.createTempDir(getName());
- Path downloadBinaryPath = tmpDir.resolve(target.getFileName());
- extract(target, downloadBinaryPath);
- if (isExtract()) {
+ Path executable = target;
+ Path tmpDir = null;
+ boolean extract = isExtract();
+ if (extract) {
+ tmpDir = fileAccess.createTempDir(getName());
+ Path downloadBinaryPath = tmpDir.resolve(target.getFileName());
+ fileAccess.extract(target, downloadBinaryPath);
downloadBinaryPath = fileAccess.findFirst(downloadBinaryPath, Files::isExecutable, false);
}
- ProcessContext pc = this.context.newProcess().errorHandling(ProcessErrorHandling.WARNING)
- .executable(downloadBinaryPath);
+ ProcessContext pc = this.context.newProcess().errorHandling(ProcessErrorHandling.WARNING).executable(executable);
int exitCode = pc.run();
- fileAccess.delete(tmpDir);
- fileAccess.delete(target);
+ if (tmpDir != null) {
+ fileAccess.delete(tmpDir);
+ }
if (exitCode == 0) {
this.context.success("Successfully installed {} in version {}", this.tool, resolvedVersion);
} else {
@@ -78,5 +140,4 @@ protected boolean doInstall(boolean silent) {
postInstall();
return true;
}
-
}
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 dbbd2bd6d..e09dbf691 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
@@ -1,12 +1,5 @@
package com.devonfw.tools.ide.tool;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
-import java.util.Set;
-
-import com.devonfw.tools.ide.commandlet.Commandlet;
import com.devonfw.tools.ide.common.Tag;
import com.devonfw.tools.ide.context.IdeContext;
import com.devonfw.tools.ide.io.FileAccess;
@@ -15,6 +8,12 @@
import com.devonfw.tools.ide.repo.ToolRepository;
import com.devonfw.tools.ide.version.VersionIdentifier;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.Set;
+
/**
* {@link ToolCommandlet} that is installed locally into the IDE.
*/
@@ -26,7 +25,7 @@ public abstract class LocalToolCommandlet extends ToolCommandlet {
* @param context the {@link IdeContext}.
* @param tool the {@link #getName() tool name}.
* @param tags the {@link #getTags() tags} classifying the tool. Should be created via {@link Set#of(Object) Set.of}
- * method.
+ * method.
*/
public LocalToolCommandlet(IdeContext context, String tool, Set tags) {
@@ -43,13 +42,13 @@ public Path getToolPath() {
/**
* @return the {@link Path} where the executables of the tool can be found. Typically a "bin" folder inside
- * {@link #getToolPath() tool path}.
+ * {@link #getToolPath() tool path}.
*/
public Path getToolBinPath() {
Path toolPath = getToolPath();
- Path binPath = this.context.getFileAccess().findFirst(toolPath, path -> path.getFileName().toString().equals("bin"),
- false);
+ Path binPath = this.context.getFileAccess()
+ .findFirst(toolPath, path -> path.getFileName().toString().equals("bin"), false);
if ((binPath != null) && Files.isDirectory(binPath)) {
return binPath;
}
@@ -70,8 +69,8 @@ protected boolean doInstall(boolean silent) {
VersionIdentifier resolvedVersion = installation.resolvedVersion();
if (resolvedVersion.equals(installedVersion) && !installation.newInstallation()) {
IdeLogLevel level = silent ? IdeLogLevel.DEBUG : IdeLogLevel.INFO;
- this.context.level(level).log("Version {} of tool {} is already installed", installedVersion,
- getToolWithEdition());
+ this.context.level(level)
+ .log("Version {} of tool {} is already installed", installedVersion, getToolWithEdition());
return false;
}
// we need to link the version or update the link.
@@ -80,6 +79,7 @@ protected boolean doInstall(boolean silent) {
if (Files.exists(toolPath)) {
fileAccess.backup(toolPath);
}
+ fileAccess.mkdirs(toolPath.getParent());
fileAccess.symlink(installation.linkDir(), toolPath);
this.context.getPath().setPath(this.tool, installation.binDir());
if (installedVersion == null) {
@@ -93,11 +93,12 @@ protected boolean doInstall(boolean silent) {
}
/**
- * Performs the installation of the {@link #getName() tool} managed by this {@link Commandlet} only in the central
- * software repository without touching the IDE installation.
+ * Performs the installation of the {@link #getName() tool} managed by this
+ * {@link com.devonfw.tools.ide.commandlet.Commandlet} only in the central software repository without touching the
+ * IDE installation.
*
* @param version the {@link VersionIdentifier} requested to be installed. May also be a
- * {@link VersionIdentifier#isPattern() version pattern}.
+ * {@link VersionIdentifier#isPattern() version pattern}.
* @return the {@link ToolInstallation} in the central software repository matching the given {@code version}.
*/
public ToolInstallation installInRepo(VersionIdentifier version) {
@@ -106,11 +107,12 @@ public ToolInstallation installInRepo(VersionIdentifier version) {
}
/**
- * Performs the installation of the {@link #getName() tool} managed by this {@link Commandlet} only in the central
- * software repository without touching the IDE installation.
+ * Performs the installation of the {@link #getName() tool} managed by this
+ * {@link com.devonfw.tools.ide.commandlet.Commandlet} only in the central software repository without touching the
+ * IDE installation.
*
* @param version the {@link VersionIdentifier} requested to be installed. May also be a
- * {@link VersionIdentifier#isPattern() version pattern}.
+ * {@link VersionIdentifier#isPattern() version pattern}.
* @param edition the specific edition to install.
* @return the {@link ToolInstallation} in the central software repository matching the given {@code version}.
*/
@@ -120,11 +122,12 @@ public ToolInstallation installInRepo(VersionIdentifier version, String edition)
}
/**
- * Performs the installation of the {@link #getName() tool} managed by this {@link Commandlet} only in the central
- * software repository without touching the IDE installation.
+ * Performs the installation of the {@link #getName() tool} managed by this
+ * {@link com.devonfw.tools.ide.commandlet.Commandlet} only in the central software repository without touching the
+ * IDE installation.
*
* @param version the {@link VersionIdentifier} requested to be installed. May also be a
- * {@link VersionIdentifier#isPattern() version pattern}.
+ * {@link VersionIdentifier#isPattern() version pattern}.
* @param edition the specific edition to install.
* @param toolRepository the {@link ToolRepository} to use.
* @return the {@link ToolInstallation} in the central software repository matching the given {@code version}.
@@ -152,7 +155,12 @@ public ToolInstallation installInRepo(VersionIdentifier version, String edition,
}
Path target = toolRepository.download(this.tool, edition, resolvedVersion);
fileAccess.mkdirs(toolPath.getParent());
- extract(target, toolPath);
+ boolean extract = isExtract();
+ if (!extract) {
+ this.context.trace("Extraction is disabled for '{}' hence just moving the downloaded file {}.", this.tool,
+ target);
+ }
+ fileAccess.extract(target, toolPath, this::postExtract, extract);
try {
Files.writeString(toolVersionFile, resolvedVersion.toString(), StandardOpenOption.CREATE_NEW);
} catch (IOException e) {
@@ -163,10 +171,20 @@ public ToolInstallation installInRepo(VersionIdentifier version, String edition,
return createToolInstallation(toolPath, resolvedVersion, toolVersionFile, true);
}
+ /**
+ * Post-extraction hook that can be overridden to add custom processing after unpacking and before moving to the final
+ * destination folder.
+ *
+ * @param extractedDir the {@link Path} to the folder with the unpacked tool.
+ */
+ protected void postExtract(Path extractedDir) {
+
+ }
+
private ToolInstallation createToolInstallation(Path rootDir, VersionIdentifier resolvedVersion, Path toolVersionFile,
boolean newInstallation) {
- Path linkDir = getMacOsHelper().findLinkDir(rootDir);
+ Path linkDir = getMacOsHelper().findLinkDir(rootDir, this.tool);
Path binDir = linkDir;
Path binFolder = binDir.resolve(IdeContext.FOLDER_BIN);
if (Files.isDirectory(binFolder)) {
@@ -174,7 +192,8 @@ private ToolInstallation createToolInstallation(Path rootDir, VersionIdentifier
}
if (linkDir != rootDir) {
assert (!linkDir.equals(rootDir));
- this.context.getFileAccess().copy(toolVersionFile, linkDir, FileCopyMode.COPY_FILE_OVERRIDE);
+ this.context.getFileAccess()
+ .copy(toolVersionFile, linkDir.resolve(IdeContext.FILE_SOFTWARE_VERSION), FileCopyMode.COPY_FILE_OVERRIDE);
}
return new ToolInstallation(rootDir, linkDir, binDir, resolvedVersion, newInstallation);
}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/PackageManager.java b/cli/src/main/java/com/devonfw/tools/ide/tool/PackageManager.java
new file mode 100644
index 000000000..9af593534
--- /dev/null
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/PackageManager.java
@@ -0,0 +1,5 @@
+package com.devonfw.tools.ide.tool;
+
+public enum PackageManager {
+ APT, ZYPPER, YUM, DNF
+}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/PluginBasedCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/PluginBasedCommandlet.java
index 898de06bf..cb5c1ad64 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/tool/PluginBasedCommandlet.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/PluginBasedCommandlet.java
@@ -1,200 +1,195 @@
-package com.devonfw.tools.ide.tool;
-
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Stream;
-
-import com.devonfw.tools.ide.cli.CliException;
-import com.devonfw.tools.ide.common.Tag;
-import com.devonfw.tools.ide.context.IdeContext;
-import com.devonfw.tools.ide.io.FileAccess;
-import com.devonfw.tools.ide.tool.ide.IdeToolCommandlet;
-import com.devonfw.tools.ide.tool.ide.PluginDescriptor;
-import com.devonfw.tools.ide.tool.ide.PluginDescriptorImpl;
-
-public abstract class PluginBasedCommandlet extends LocalToolCommandlet {
-
- private Map pluginsMap;
-
- private Collection configuredPlugins;
-
- /**
- * The constructor.
- *
- * @param context the {@link IdeContext}.
- * @param tool the {@link #getName() tool name}.
- * @param tags the {@link #getTags() tags} classifying the tool. Should be created via {@link Set#of(Object) Set.of}
- * method.
- */
- public PluginBasedCommandlet(IdeContext context, String tool, Set tags) {
-
- super(context, tool, tags);
- }
-
- protected Map getPluginsMap() {
-
- if (this.pluginsMap == null) {
- Map map = new HashMap<>();
-
- // Load project-specific plugins
- Path pluginsPath = getPluginsConfigPath();
- loadPluginsFromDirectory(map, pluginsPath);
-
- // Load user-specific plugins
- Path userPluginsPath = getUserHomePluginsConfigPath();
- loadPluginsFromDirectory(map, userPluginsPath);
-
- this.pluginsMap = map;
- }
-
- return this.pluginsMap;
- }
-
- private void loadPluginsFromDirectory(Map map, Path pluginsPath) {
-
- if (Files.isDirectory(pluginsPath)) {
- try (Stream childStream = Files.list(pluginsPath)) {
- Iterator iterator = childStream.iterator();
- while (iterator.hasNext()) {
- Path child = iterator.next();
- String filename = child.getFileName().toString();
- if (filename.endsWith(IdeContext.EXT_PROPERTIES) && Files.exists(child)) {
- PluginDescriptor descriptor = PluginDescriptorImpl.of(child, this.context, isPluginUrlNeeded());
-
- // Priority to user-specific plugins
- map.put(descriptor.getName(), descriptor);
- }
- }
- } catch (IOException e) {
- throw new IllegalStateException("Failed to list children of directory " + pluginsPath, e);
- }
- }
- }
-
- /**
- * @return {@code true} if {@link PluginDescriptor#getUrl() plugin URL} property is needed, {@code false} otherwise.
- */
- protected boolean isPluginUrlNeeded() {
-
- return false;
- }
-
- /**
- * @return the {@link Path} to the folder with the plugin configuration files inside the settings.
- */
- protected Path getPluginsConfigPath() {
-
- return this.context.getSettingsPath().resolve(this.tool).resolve(IdeContext.FOLDER_PLUGINS);
- }
-
- private Path getUserHomePluginsConfigPath() {
-
- return this.context.getUserHome().resolve(Path.of(".ide", "settings", this.tool, IdeContext.FOLDER_PLUGINS));
- }
-
- /**
- * @return the immutable {@link Collection} of {@link PluginDescriptor}s configured for this IDE tool.
- */
- public Collection getConfiguredPlugins() {
-
- if (this.configuredPlugins == null) {
- this.configuredPlugins = Collections.unmodifiableCollection(getPluginsMap().values());
- }
- return this.configuredPlugins;
- }
-
- /**
- * @return the {@link Path} where the plugins of this {@link IdeToolCommandlet} shall be installed.
- */
- public Path getPluginsInstallationPath() {
-
- return this.context.getPluginsPath().resolve(this.tool);
- }
-
- /**
- * @param plugin the {@link PluginDescriptor} to install.
- */
- public abstract void installPlugin(PluginDescriptor plugin);
-
- /**
- * @param plugin the {@link PluginDescriptor} to uninstall.
- */
- public void uninstallPlugin(PluginDescriptor plugin) {
-
- Path pluginsPath = getPluginsInstallationPath();
- if (!Files.isDirectory(pluginsPath)) {
- this.context.debug("Omitting to uninstall plugin {} ({}) as plugins folder does not exist at {}",
- plugin.getName(), plugin.getId(), pluginsPath);
- return;
- }
- FileAccess fileAccess = this.context.getFileAccess();
- Path match = fileAccess.findFirst(pluginsPath, p -> p.getFileName().toString().startsWith(plugin.getId()), false);
- if (match == null) {
- this.context.debug("Omitting to uninstall plugin {} ({}) as plugins folder does not contain a match at {}",
- plugin.getName(), plugin.getId(), pluginsPath);
- return;
- }
- fileAccess.delete(match);
- }
-
- /**
- * @param key the filename of the properties file configuring the requested plugin (typically excluding the
- * ".properties" extension).
- * @return the {@link PluginDescriptor} for the given {@code key}.
- */
- public PluginDescriptor getPlugin(String key) {
-
- if (key == null) {
- return null;
- }
- if (key.endsWith(IdeContext.EXT_PROPERTIES)) {
- key = key.substring(0, key.length() - IdeContext.EXT_PROPERTIES.length());
- }
- PluginDescriptor pluginDescriptor = getPluginsMap().get(key);
- if (pluginDescriptor == null) {
- throw new CliException(
- "Could not find plugin " + key + " at " + getPluginsConfigPath().resolve(key) + ".properties");
- }
- return pluginDescriptor;
- }
-
- @Override
- protected boolean doInstall(boolean silent) {
-
- boolean newlyInstalled = super.doInstall(silent);
- // post installation...
- boolean installPlugins = newlyInstalled;
- Path pluginsInstallationPath = getPluginsInstallationPath();
- if (newlyInstalled) {
- this.context.getFileAccess().delete(pluginsInstallationPath);
- } else if (!Files.isDirectory(pluginsInstallationPath)) {
- installPlugins = true;
- }
- if (installPlugins) {
- for (PluginDescriptor plugin : getPluginsMap().values()) {
- if (plugin.isActive()) {
- installPlugin(plugin);
- } else {
- handleInstall4InactivePlugin(plugin);
- }
- }
- }
- return newlyInstalled;
- }
-
- /**
- * @param plugin the in{@link PluginDescriptor#isActive() active} {@link PluginDescriptor} that is skipped for regular
- * plugin installation.
- */
- protected void handleInstall4InactivePlugin(PluginDescriptor plugin) {
-
- this.context.debug("Omitting installation of inactive plugin {} ({}).", plugin.getName(), plugin.getId());
- }
-}
+package com.devonfw.tools.ide.tool;
+
+import com.devonfw.tools.ide.cli.CliException;
+import com.devonfw.tools.ide.common.Tag;
+import com.devonfw.tools.ide.context.IdeContext;
+import com.devonfw.tools.ide.io.FileAccess;
+import com.devonfw.tools.ide.tool.ide.IdeToolCommandlet;
+import com.devonfw.tools.ide.tool.ide.PluginDescriptor;
+import com.devonfw.tools.ide.tool.ide.PluginDescriptorImpl;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Base class for {@link LocalToolCommandlet}s that support plugins. It can automatically install configured plugins for
+ * the tool managed by this commandlet.
+ */
+public abstract class PluginBasedCommandlet extends LocalToolCommandlet {
+
+ private Map pluginsMap;
+
+ private Collection configuredPlugins;
+
+ /**
+ * The constructor.
+ *
+ * @param context the {@link IdeContext}.
+ * @param tool the {@link #getName() tool name}.
+ * @param tags the {@link #getTags() tags} classifying the tool. Should be created via {@link Set#of(Object) Set.of}
+ * method.
+ */
+ public PluginBasedCommandlet(IdeContext context, String tool, Set tags) {
+
+ super(context, tool, tags);
+ }
+
+ protected Map getPluginsMap() {
+
+ if (this.pluginsMap == null) {
+ Map map = new HashMap<>();
+
+ // Load project-specific plugins
+ Path pluginsPath = getPluginsConfigPath();
+ loadPluginsFromDirectory(map, pluginsPath);
+
+ // Load user-specific plugins, this is done after loading the project-specific plugins so the user can potentially
+ // override plugins (e.g. change active flag).
+ Path userPluginsPath = getUserHomePluginsConfigPath();
+ loadPluginsFromDirectory(map, userPluginsPath);
+
+ this.pluginsMap = map;
+ }
+
+ return this.pluginsMap;
+ }
+
+ private void loadPluginsFromDirectory(Map map, Path pluginsPath) {
+
+ List children = this.context.getFileAccess()
+ .listChildren(pluginsPath, p -> p.getFileName().toString().endsWith(IdeContext.EXT_PROPERTIES));
+ for (Path child : children) {
+ PluginDescriptor descriptor = PluginDescriptorImpl.of(child, this.context, isPluginUrlNeeded());
+ PluginDescriptor duplicate = map.put(descriptor.getName(), descriptor);
+ if (duplicate != null) {
+ this.context.info("Plugin {} from project is overridden by {}", descriptor.getName(), child);
+ }
+ }
+ }
+
+ /**
+ * @return {@code true} if {@link PluginDescriptor#getUrl() plugin URL} property is needed, {@code false} otherwise.
+ */
+ protected boolean isPluginUrlNeeded() {
+
+ return false;
+ }
+
+ /**
+ * @return the {@link Path} to the folder with the plugin configuration files inside the settings.
+ */
+ protected Path getPluginsConfigPath() {
+
+ return this.context.getSettingsPath().resolve(this.tool).resolve(IdeContext.FOLDER_PLUGINS);
+ }
+
+ private Path getUserHomePluginsConfigPath() {
+
+ return this.context.getUserHomeIde().resolve("settings").resolve(this.tool).resolve(IdeContext.FOLDER_PLUGINS);
+ }
+
+ /**
+ * @return the immutable {@link Collection} of {@link PluginDescriptor}s configured for this IDE tool.
+ */
+ public Collection getConfiguredPlugins() {
+
+ if (this.configuredPlugins == null) {
+ this.configuredPlugins = Collections.unmodifiableCollection(getPluginsMap().values());
+ }
+ return this.configuredPlugins;
+ }
+
+ /**
+ * @return the {@link Path} where the plugins of this {@link IdeToolCommandlet} shall be installed.
+ */
+ public Path getPluginsInstallationPath() {
+
+ return this.context.getPluginsPath().resolve(this.tool);
+ }
+
+ /**
+ * @param plugin the {@link PluginDescriptor} to install.
+ */
+ public abstract void installPlugin(PluginDescriptor plugin);
+
+ /**
+ * @param plugin the {@link PluginDescriptor} to uninstall.
+ */
+ public void uninstallPlugin(PluginDescriptor plugin) {
+
+ Path pluginsPath = getPluginsInstallationPath();
+ if (!Files.isDirectory(pluginsPath)) {
+ this.context.debug("Omitting to uninstall plugin {} ({}) as plugins folder does not exist at {}",
+ plugin.getName(), plugin.getId(), pluginsPath);
+ return;
+ }
+ FileAccess fileAccess = this.context.getFileAccess();
+ Path match = fileAccess.findFirst(pluginsPath, p -> p.getFileName().toString().startsWith(plugin.getId()), false);
+ if (match == null) {
+ this.context.debug("Omitting to uninstall plugin {} ({}) as plugins folder does not contain a match at {}",
+ plugin.getName(), plugin.getId(), pluginsPath);
+ return;
+ }
+ fileAccess.delete(match);
+ }
+
+ /**
+ * @param key the filename of the properties file configuring the requested plugin (typically excluding the
+ * ".properties" extension).
+ * @return the {@link PluginDescriptor} for the given {@code key}.
+ */
+ public PluginDescriptor getPlugin(String key) {
+
+ if (key == null) {
+ return null;
+ }
+ if (key.endsWith(IdeContext.EXT_PROPERTIES)) {
+ key = key.substring(0, key.length() - IdeContext.EXT_PROPERTIES.length());
+ }
+ PluginDescriptor pluginDescriptor = getPluginsMap().get(key);
+ if (pluginDescriptor == null) {
+ throw new CliException(
+ "Could not find plugin " + key + " at " + getPluginsConfigPath().resolve(key) + ".properties");
+ }
+ return pluginDescriptor;
+ }
+
+ @Override
+ protected boolean doInstall(boolean silent) {
+
+ boolean newlyInstalled = super.doInstall(silent);
+ // post installation...
+ boolean installPlugins = newlyInstalled;
+ Path pluginsInstallationPath = getPluginsInstallationPath();
+ if (newlyInstalled) {
+ this.context.getFileAccess().delete(pluginsInstallationPath);
+ } else if (!Files.isDirectory(pluginsInstallationPath)) {
+ installPlugins = true;
+ }
+ if (installPlugins) {
+ for (PluginDescriptor plugin : getPluginsMap().values()) {
+ if (plugin.isActive()) {
+ installPlugin(plugin);
+ } else {
+ handleInstall4InactivePlugin(plugin);
+ }
+ }
+ }
+ return newlyInstalled;
+ }
+
+ /**
+ * @param plugin the in{@link PluginDescriptor#isActive() active} {@link PluginDescriptor} that is skipped for regular
+ * plugin installation.
+ */
+ protected void handleInstall4InactivePlugin(PluginDescriptor plugin) {
+
+ this.context.debug("Omitting installation of inactive plugin {} ({}).", plugin.getName(), plugin.getId());
+ }
+}
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 3a5b06255..ebedc8e51 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
@@ -23,11 +23,10 @@
import com.devonfw.tools.ide.context.IdeContext;
import com.devonfw.tools.ide.environment.EnvironmentVariables;
import com.devonfw.tools.ide.environment.EnvironmentVariablesType;
-import com.devonfw.tools.ide.io.FileAccess;
-import com.devonfw.tools.ide.io.TarCompression;
import com.devonfw.tools.ide.os.MacOsHelper;
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.property.StringListProperty;
import com.devonfw.tools.ide.url.model.file.UrlSecurityJsonFile;
import com.devonfw.tools.ide.url.model.file.json.UrlSecurityWarning;
@@ -35,6 +34,12 @@
import com.devonfw.tools.ide.util.Pair;
import com.devonfw.tools.ide.version.VersionIdentifier;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Set;
+
/**
* {@link Commandlet} for a tool integrated into the IDE.
*/
@@ -64,7 +69,16 @@ public ToolCommandlet(IdeContext context, String tool, Set tags) {
this.tool = tool;
this.tags = tags;
addKeyword(tool);
- this.arguments = add(new StringListProperty("", false, "args"));
+ this.arguments = new StringListProperty("", false, "args");
+ initProperties();
+ }
+
+ /**
+ * Add initial Properties to the tool
+ */
+ protected void initProperties() {
+
+ add(this.arguments);
}
/**
@@ -93,20 +107,19 @@ public final Set getTags() {
@Override
public void run() {
- runTool(false, null, this.arguments.asArray());
+ runTool(ProcessMode.DEFAULT, null, this.arguments.asArray());
}
/**
* Ensures the tool is installed and then runs this tool with the given arguments.
*
- * @param runInBackground {@code true}, the process of the command will be run as background process, {@code false}
- * otherwise (it will be run as foreground process).
+ * @param processMode see {@link ProcessMode}
* @param toolVersion the explicit version (pattern) to run. Typically {@code null} to ensure the configured version
* is installed and use that one. Otherwise, the specified version will be installed in the software repository
* without touching and IDE installation and used to run.
* @param args the command-line arguments to run the tool.
*/
- public void runTool(boolean runInBackground, VersionIdentifier toolVersion, String... args) {
+ public void runTool(ProcessMode processMode, VersionIdentifier toolVersion, String... args) {
Path binaryPath;
Path toolPath = Path.of(getBinaryName());
@@ -119,17 +132,17 @@ public void runTool(boolean runInBackground, VersionIdentifier toolVersion, Stri
ProcessContext pc = this.context.newProcess().errorHandling(ProcessErrorHandling.WARNING).executable(binaryPath)
.addArgs(args);
- pc.run(false, runInBackground);
+ pc.run(processMode);
}
/**
* @param toolVersion the explicit {@link VersionIdentifier} of the tool to run.
* @param args the command-line arguments to run the tool.
- * @see ToolCommandlet#runTool(boolean, VersionIdentifier, String...)
+ * @see ToolCommandlet#runTool(ProcessMode, VersionIdentifier, String...)
*/
public void runTool(VersionIdentifier toolVersion, String... args) {
- runTool(false, toolVersion, args);
+ runTool(ProcessMode.DEFAULT, toolVersion, args);
}
/**
@@ -173,7 +186,8 @@ public VersionIdentifier getConfiguredVersion() {
}
/**
- * Method to be called for {@link #install(boolean)} from dependent {@link Commandlet}s.
+ * Method to be called for {@link #install(boolean)} from dependent
+ * {@link com.devonfw.tools.ide.commandlet.Commandlet}s.
*
* @return {@code true} if the tool was newly installed, {@code false} if the tool was already installed before and
* nothing has changed.
@@ -184,7 +198,8 @@ public boolean install() {
}
/**
- * Performs the installation of the {@link #getName() tool} managed by this {@link Commandlet}.
+ * Performs the installation of the {@link #getName() tool} managed by this
+ * {@link com.devonfw.tools.ide.commandlet.Commandlet}.
*
* @param silent - {@code true} if called recursively to suppress verbose logging, {@code false} otherwise.
* @return {@code true} if the tool was newly installed, {@code false} if the tool was already installed before and
@@ -349,111 +364,6 @@ protected void postInstall() {
// nothing to do by default
}
- /**
- * @param path the {@link Path} to start the recursive search from.
- * @return the deepest subdir {@code s} of the passed path such that all directories between {@code s} and the passed
- * path (including {@code s}) are the sole item in their respective directory and {@code s} is not named
- * "bin".
- */
- private Path getProperInstallationSubDirOf(Path path) {
-
- try (Stream stream = Files.list(path)) {
- Path[] subFiles = stream.toArray(Path[]::new);
- if (subFiles.length == 0) {
- throw new CliException("The downloaded package for the tool " + this.tool
- + " seems to be empty as you can check in the extracted folder " + path);
- } else if (subFiles.length == 1) {
- String filename = subFiles[0].getFileName().toString();
- if (!filename.equals(IdeContext.FOLDER_BIN) && !filename.equals(IdeContext.FOLDER_CONTENTS)
- && !filename.endsWith(".app") && Files.isDirectory(subFiles[0])) {
- return getProperInstallationSubDirOf(subFiles[0]);
- }
- }
- return path;
- } catch (IOException e) {
- throw new IllegalStateException("Failed to get sub-files of " + path);
- }
- }
-
- /**
- * @param file the {@link Path} to the file to extract.
- * @param targetDir the {@link Path} to the directory where to extract (or copy) the file.
- */
- protected void extract(Path file, Path targetDir) {
-
- FileAccess fileAccess = this.context.getFileAccess();
- if (isExtract()) {
- Path tmpDir = this.context.getFileAccess().createTempDir("extract-" + file.getFileName());
- this.context.trace("Trying to extract the downloaded file {} to {} and move it to {}.", file, tmpDir, targetDir);
- String extension = FilenameUtil.getExtension(file.getFileName().toString());
- this.context.trace("Determined file extension {}", extension);
- TarCompression tarCompression = TarCompression.of(extension);
- if (tarCompression != null) {
- fileAccess.untar(file, tmpDir, tarCompression);
- } else if ("zip".equals(extension) || "jar".equals(extension)) {
- fileAccess.unzip(file, tmpDir);
- } else if ("dmg".equals(extension)) {
- assert this.context.getSystemInfo().isMac();
- Path mountPath = this.context.getIdeHome().resolve(IdeContext.FOLDER_UPDATES).resolve(IdeContext.FOLDER_VOLUME);
- fileAccess.mkdirs(mountPath);
- ProcessContext pc = this.context.newProcess();
- pc.executable("hdiutil");
- pc.addArgs("attach", "-quiet", "-nobrowse", "-mountpoint", mountPath, file);
- pc.run();
- Path appPath = fileAccess.findFirst(mountPath, p -> p.getFileName().toString().endsWith(".app"), false);
- if (appPath == null) {
- throw new IllegalStateException("Failed to unpack DMG as no MacOS *.app was found in file " + file);
- }
- fileAccess.copy(appPath, tmpDir);
- pc.addArgs("detach", "-force", mountPath);
- pc.run();
- } else if ("msi".equals(extension)) {
- this.context.newProcess().executable("msiexec").addArgs("/a", file, "/qn", "TARGETDIR=" + tmpDir).run();
- // msiexec also creates a copy of the MSI
- Path msiCopy = tmpDir.resolve(file.getFileName());
- fileAccess.delete(msiCopy);
- } else if ("pkg".equals(extension)) {
-
- Path tmpDirPkg = fileAccess.createTempDir("ide-pkg-");
- ProcessContext pc = this.context.newProcess();
- // we might also be able to use cpio from commons-compression instead of external xar...
- pc.executable("xar").addArgs("-C", tmpDirPkg, "-xf", file).run();
- Path contentPath = fileAccess.findFirst(tmpDirPkg, p -> p.getFileName().toString().equals("Payload"), true);
- fileAccess.untar(contentPath, tmpDir, TarCompression.GZ);
- fileAccess.delete(tmpDirPkg);
- } else {
- throw new IllegalStateException("Unknown archive format " + extension + ". Can not extract " + file);
- }
- moveAndProcessExtraction(getProperInstallationSubDirOf(tmpDir), targetDir);
- fileAccess.delete(tmpDir);
- } else {
- this.context.trace("Extraction is disabled for '{}' hence just moving the downloaded file {}.", getName(), file);
-
- if (Files.isDirectory(file)) {
- fileAccess.move(file, targetDir);
- } else {
- try {
- Files.createDirectories(targetDir);
- } catch (IOException e) {
- throw new IllegalStateException("Failed to create folder " + targetDir);
- }
- fileAccess.move(file, targetDir.resolve(file.getFileName()));
- }
- }
- }
-
- /**
- * Moves the extracted content to the final destination {@link Path}. May be overridden to customize the extraction
- * process.
- *
- * @param from the source {@link Path} to move.
- * @param to the target {@link Path} to move to.
- */
- protected void moveAndProcessExtraction(Path from, Path to) {
-
- this.context.getFileAccess().move(from, to);
- }
-
/**
* @return {@code true} to extract (unpack) the downloaded binary file, {@code false} otherwise.
*/
@@ -535,10 +445,11 @@ public String getInstalledEdition(Path toolPath) {
}
return edition;
} catch (IOException e) {
- throw new IllegalStateException("Couldn't determine the edition of " + getName()
- + " from the directory structure of its software path " + toolPath
- + ", assuming the name of the parent directory of the real path of the software path to be the edition "
- + "of the tool.", e);
+ throw new IllegalStateException(
+ "Couldn't determine the edition of " + getName() + " from the directory structure of its software path "
+ + toolPath
+ + ", assuming the name of the parent directory of the real path of the software path to be the edition "
+ + "of the tool.", e);
}
}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/aws/Aws.java b/cli/src/main/java/com/devonfw/tools/ide/tool/aws/Aws.java
index d2257228f..37eaaf362 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/tool/aws/Aws.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/aws/Aws.java
@@ -1,24 +1,21 @@
package com.devonfw.tools.ide.tool.aws;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.attribute.PosixFilePermission;
-import java.util.Set;
-
import com.devonfw.tools.ide.common.Tag;
import com.devonfw.tools.ide.context.IdeContext;
import com.devonfw.tools.ide.environment.EnvironmentVariables;
import com.devonfw.tools.ide.environment.EnvironmentVariablesType;
+import com.devonfw.tools.ide.io.FileAccess;
import com.devonfw.tools.ide.process.ProcessContext;
import com.devonfw.tools.ide.tool.LocalToolCommandlet;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Set;
+
/**
- * {@link LocalToolCommandlet} for AWS CLI (aws).
- *
- * @see AWS CLI homepage
+ * {@link LocalToolCommandlet} for AWS CLI (Amazon Web Services Command
+ * Line Interface).
*/
-
public class Aws extends LocalToolCommandlet {
/**
@@ -31,70 +28,47 @@ public Aws(IdeContext context) {
super(context, "aws", Set.of(Tag.CLOUD));
}
- private void makeExecutable(Path file) {
+ @Override
+ public void postInstall() {
- // TODO this can be removed if issue #132 is fixed. See https://github.com/devonfw/IDEasy/issues/132
- Set permissions = null;
- try {
- permissions = Files.getPosixFilePermissions(file);
- permissions.add(PosixFilePermission.GROUP_EXECUTE);
- permissions.add(PosixFilePermission.OWNER_EXECUTE);
- permissions.add(PosixFilePermission.OTHERS_EXECUTE);
- Files.setPosixFilePermissions(file, permissions);
- } catch (IOException e) {
- throw new RuntimeException("Adding execution permission for Group, Owner and Others did not work for " + file, e);
- }
+ super.postInstall();
+ EnvironmentVariables variables = this.context.getVariables();
+ EnvironmentVariables typeVariables = variables.getByType(EnvironmentVariablesType.CONF);
+ Path awsConfigDir = this.context.getConfPath().resolve("aws");
+ this.context.getFileAccess().mkdirs(awsConfigDir);
+ Path awsConfigFile = awsConfigDir.resolve("config");
+ Path awsCredentialsFile = awsConfigDir.resolve("credentials");
+ typeVariables.set("AWS_CONFIG_FILE", awsConfigFile.toString(), true);
+ typeVariables.set("AWS_SHARED_CREDENTIALS_FILE", awsCredentialsFile.toString(), true);
+ typeVariables.save();
}
- @Override
- protected void moveAndProcessExtraction(Path from, Path to) {
+ protected void postExtract(Path extractedDir) {
if (this.context.getSystemInfo().isLinux()) {
- // make binary executable using java nio because unpacking didn't preserve the file permissions
- // TODO this can be removed if issue #132 is fixed
- Path awsInDistPath = from.resolve("dist").resolve("aws");
- Path awsCompleterInDistPath = from.resolve("dist").resolve("aws_completer");
- makeExecutable(awsInDistPath);
- makeExecutable(awsCompleterInDistPath);
-
// running the install-script that aws shipped
ProcessContext pc = this.context.newProcess();
- Path linuxInstallScript = from.resolve("install");
+ Path linuxInstallScript = extractedDir.resolve("install");
pc.executable(linuxInstallScript);
- pc.addArgs("-i", from.toString(), "-b", from.toString());
+ pc.addArgs("-i", extractedDir.toString(), "-b", extractedDir.toString());
pc.run();
// The install-script that aws ships creates symbolic links to binaries but using absolute paths.
// Since the current process happens in a temporary dir, these links wouldn't be valid after moving the
// installation files to the target dir. So the absolute paths are replaced by relative ones.
- for (String file : new String[] { "aws", "aws_completer", Path.of("v2").resolve("current").toString() }) {
- Path link = from.resolve(file);
+ FileAccess fileAccess = this.context.getFileAccess();
+ for (String file : new String[] { "aws", "aws_completer", "v2/current" }) {
+ Path link = extractedDir.resolve(file);
try {
- this.context.getFileAccess().symlink(link.toRealPath(), link, true);
+ fileAccess.symlink(link.toRealPath(), link, true);
} catch (IOException e) {
throw new RuntimeException(
"Failed to replace absolute link (" + link + ") provided by AWS install script with relative link.", e);
}
}
- this.context.getFileAccess().delete(linuxInstallScript);
- this.context.getFileAccess().delete(from.resolve("dist"));
+ fileAccess.delete(linuxInstallScript);
+ fileAccess.delete(extractedDir.resolve("dist"));
}
- super.moveAndProcessExtraction(from, to);
}
- @Override
- public void postInstall() {
-
- super.postInstall();
-
- EnvironmentVariables variables = this.context.getVariables();
- EnvironmentVariables typeVariables = variables.getByType(EnvironmentVariablesType.CONF);
- Path awsConfigDir = this.context.getConfPath().resolve("aws");
- this.context.getFileAccess().mkdirs(awsConfigDir);
- Path awsConfigFile = awsConfigDir.resolve("config");
- Path awsCredentialsFile = awsConfigDir.resolve("credentials");
- typeVariables.set("AWS_CONFIG_FILE", awsConfigFile.toString(), true);
- typeVariables.set("AWS_SHARED_CREDENTIALS_FILE", awsCredentialsFile.toString(), true);
- typeVariables.save();
- }
}
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
new file mode 100644
index 000000000..0119a3d32
--- /dev/null
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/docker/Docker.java
@@ -0,0 +1,79 @@
+package com.devonfw.tools.ide.tool.docker;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.devonfw.tools.ide.common.Tag;
+import com.devonfw.tools.ide.context.IdeContext;
+import com.devonfw.tools.ide.os.SystemArchitecture;
+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.version.VersionIdentifier;
+
+public class Docker extends GlobalToolCommandlet {
+ /**
+ * The constructor.
+ *
+ * @param context the {@link IdeContext}.
+ */
+ public Docker(IdeContext context) {
+
+ super(context, "docker", Set.of(Tag.DOCKER));
+ }
+
+ @Override
+ public boolean isExtract() {
+
+ return switch (context.getSystemInfo().getOs()) {
+ case WINDOWS -> false;
+ case MAC -> context.getSystemInfo().getArchitecture().equals(SystemArchitecture.ARM64);
+ case LINUX -> true;
+ };
+ }
+
+ @Override
+ protected boolean doInstall(boolean silent) {
+
+ if (context.getSystemInfo().isLinux()) {
+ return installWithPackageManger(getPackageMangerCommands(), silent);
+ } else {
+ return super.doInstall(silent);
+ }
+ }
+
+ private Map> getPackageMangerCommands() {
+
+ Map> commands = new HashMap<>();
+
+ String edition = getEdition();
+ ToolRepository toolRepository = this.context.getDefaultToolRepository();
+ VersionIdentifier configuredVersion = getConfiguredVersion();
+ String resolvedVersion = toolRepository.resolveVersion(this.tool, edition, configuredVersion).toString();
+
+ commands.put(PackageManager.ZYPPER, Arrays.asList(
+ "sudo zypper addrepo https://download.opensuse.org/repositories/isv:/Rancher:/stable/rpm/isv:Rancher:stable.repo",
+ String.format("sudo zypper --no-gpg-checks install rancher-desktop=%s*", resolvedVersion)));
+
+ commands.put(PackageManager.APT, Arrays.asList(
+ "curl -s https://download.opensuse.org/repositories/isv:/Rancher:/stable/deb/Release.key | gpg --dearmor | sudo dd status=none of=/usr/share/keyrings/isv-rancher-stable-archive-keyring.gpg",
+ "echo 'deb [signed-by=/usr/share/keyrings/isv-rancher-stable-archive-keyring.gpg] https://download.opensuse.org/repositories/isv:/Rancher:/stable/deb/ ./' | sudo dd status=none of=/etc/apt/sources.list.d/isv-rancher-stable.list",
+ "sudo apt update",
+ String.format("sudo apt install -y --allow-downgrades rancher-desktop=%s*", resolvedVersion)));
+
+ return commands;
+ }
+
+ @Override
+ protected String getBinaryName() {
+
+ if (context.getSystemInfo().isLinux()) {
+ return "rancher-desktop";
+ } else {
+ return super.getBinaryName();
+ }
+ }
+}
\ No newline at end of file
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 133c65d31..917f55d01 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
@@ -15,6 +15,7 @@
import com.devonfw.tools.ide.log.IdeLogLevel;
import com.devonfw.tools.ide.process.ProcessContext;
import com.devonfw.tools.ide.process.ProcessErrorHandling;
+import com.devonfw.tools.ide.process.ProcessMode;
import com.devonfw.tools.ide.process.ProcessResult;
import com.devonfw.tools.ide.tool.ide.IdeToolCommandlet;
import com.devonfw.tools.ide.tool.ide.PluginDescriptor;
@@ -39,7 +40,7 @@ public Eclipse(IdeContext context) {
protected void runIde(String... args) {
install(true);
- runEclipse(false,
+ runEclipse(ProcessMode.BACKGROUND,
CliArgument.prepend(args, "gui", "-showlocation", this.context.getIdeHome().getFileName().toString()));
}
@@ -53,22 +54,21 @@ public boolean install(boolean silent) {
/**
* Runs eclipse application.
*
- * @param log - {@code true} to run in log mode without opening a window and capture the output, {@code false}
- * otherwise (run in GUI mode).
+ * @param processMode - the {@link ProcessMode}.
* @param args the individual arguments to pass to eclipse.
* @return the {@link ProcessResult}.
*/
- protected ProcessResult runEclipse(boolean log, String... args) {
+ protected ProcessResult runEclipse(ProcessMode processMode, String... args) {
Path toolPath = Path.of(getBinaryName());
ProcessContext pc = this.context.newProcess();
- if (log) {
+ if (processMode == ProcessMode.DEFAULT_CAPTURE) {
pc.errorHandling(ProcessErrorHandling.ERROR);
}
pc.executable(toolPath);
Path configurationPath = getPluginsInstallationPath().resolve("configuration");
this.context.getFileAccess().mkdirs(configurationPath);
- if (log) {
+ if (processMode == ProcessMode.DEFAULT_CAPTURE) {
pc.addArg("-consoleLog").addArg("-nosplash");
} else {
pc.addArg("-clean");
@@ -80,14 +80,16 @@ protected ProcessResult runEclipse(boolean log, String... args) {
Path javaPath = getCommandlet(Java.class).getToolBinPath();
pc.addArg("-vm").addArg(javaPath);
pc.addArgs(args);
- return pc.run(log, false);
+
+ return pc.run(processMode);
+
}
@Override
public void installPlugin(PluginDescriptor plugin) {
- ProcessResult result = runEclipse(true, "org.eclipse.equinox.p2.director", "-repository", plugin.getUrl(),
- "-installIU", plugin.getId());
+ ProcessResult result = runEclipse(ProcessMode.DEFAULT_CAPTURE, "org.eclipse.equinox.p2.director", "-repository",
+ plugin.getUrl(), "-installIU", plugin.getId());
if (result.isSuccessful()) {
for (String line : result.getOut()) {
if (line.contains("Overall install request is satisfiable")) {
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 4bb4800c8..ac7a2a1f6 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
@@ -15,6 +15,7 @@
import com.devonfw.tools.ide.common.Tag;
import com.devonfw.tools.ide.context.IdeContext;
import com.devonfw.tools.ide.io.FileAccess;
+import com.devonfw.tools.ide.process.ProcessMode;
import com.devonfw.tools.ide.tool.LocalToolCommandlet;
import com.devonfw.tools.ide.tool.ToolCommandlet;
import com.devonfw.tools.ide.tool.eclipse.Eclipse;
@@ -207,7 +208,7 @@ public void run() {
*/
protected void runIde(String... args) {
- runTool(false,null, args);
+ runTool(ProcessMode.DEFAULT, null, args);
}
/**
diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/jasypt/Jasypt.java b/cli/src/main/java/com/devonfw/tools/ide/tool/jasypt/Jasypt.java
new file mode 100644
index 000000000..a6f7aff99
--- /dev/null
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/jasypt/Jasypt.java
@@ -0,0 +1,103 @@
+package com.devonfw.tools.ide.tool.jasypt;
+
+import com.devonfw.tools.ide.common.Tag;
+import com.devonfw.tools.ide.context.IdeContext;
+import com.devonfw.tools.ide.property.EnumProperty;
+import com.devonfw.tools.ide.property.PasswordProperty;
+import com.devonfw.tools.ide.tool.LocalToolCommandlet;
+import com.devonfw.tools.ide.tool.ToolCommandlet;
+import com.devonfw.tools.ide.tool.java.Java;
+
+import java.nio.file.Path;
+import java.util.Set;
+
+/**
+ * {@link ToolCommandlet} for Jasypt, The java library which allows to add basic
+ * encryption capabilities with minimum effort.
+ */
+public class Jasypt extends LocalToolCommandlet {
+
+ public final EnumProperty command;
+
+ public final PasswordProperty masterPassword;
+
+ public final PasswordProperty secret;
+
+ private static final String CLASS_NAME_ENCRYPTION = "org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI";
+
+ private static final String CLASS_NAME_DECRYPTION = "org.jasypt.intf.cli.JasyptPBEStringDecryptionCLI";
+
+ /**
+ * The constructor.
+ *
+ * @param context the {@link IdeContext}.
+ */
+ public Jasypt(IdeContext context) {
+
+ super(context, "jasypt", Set.of(Tag.JAVA, Tag.ENCRYPTION));
+
+ this.command = add(new EnumProperty<>("", true, "command", JasyptCommand.class));
+ this.masterPassword = add(new PasswordProperty("", true, "masterPassword"));
+ this.secret = add(new PasswordProperty("", true, "secret"));
+ }
+
+ @Override
+ protected void initProperties() {
+
+ // Empty on purpose
+ }
+
+ @Override
+ public boolean doInstall(boolean silent) {
+
+ getCommandlet(Java.class).install();
+
+ return super.doInstall(silent);
+ }
+
+ @Override
+ protected boolean isExtract() {
+
+ return false;
+ }
+
+ @Override
+ public void run() {
+
+ Path toolPath = getToolPath();
+ if (!toolPath.toFile().exists()) {
+ super.install(true);
+ }
+
+ JasyptCommand command = this.command.getValue();
+ switch (command) {
+ case ENCRYPT:
+ runJasypt(CLASS_NAME_ENCRYPTION);
+ break;
+ case DECRYPT:
+ runJasypt(CLASS_NAME_DECRYPTION);
+ break;
+
+ default:
+ }
+ }
+
+ private void runJasypt(String className) {
+
+ Java java = getCommandlet(Java.class);
+
+ String[] jasyptOptions = this.context.getVariables().get("JASYPT_OPTS").split(" ");
+ String algorithm = jasyptOptions[0];
+ String generatorClassName = jasyptOptions[1];
+
+ java.runTool(null, "-cp", resolveJasyptJarPath().toString(), className, algorithm, generatorClassName,
+ "password=" + this.masterPassword.getValue(), "input=" + this.secret.getValue());
+ }
+
+ private Path resolveJasyptJarPath() {
+
+ Path toolPath = this.getToolPath();
+ String installedVersion = getInstalledVersion().toString();
+ return toolPath.resolve("jasypt-" + installedVersion + ".jar");
+ }
+}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/jasypt/JasyptCommand.java b/cli/src/main/java/com/devonfw/tools/ide/tool/jasypt/JasyptCommand.java
new file mode 100644
index 000000000..9aa914dd5
--- /dev/null
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/jasypt/JasyptCommand.java
@@ -0,0 +1,8 @@
+package com.devonfw.tools.ide.tool.jasypt;
+
+/**
+ * Represents commands for controlling a jasypt operation in The{@link Jasypt} Tool.
+ */
+public enum JasyptCommand {
+ ENCRYPT, DECRYPT
+}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java b/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java
index 0c316cfbf..317910bc6 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java
@@ -1,19 +1,20 @@
package com.devonfw.tools.ide.tool.jmc;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.Iterator;
-import java.util.Set;
-import java.util.stream.Stream;
-
import com.devonfw.tools.ide.common.Tag;
import com.devonfw.tools.ide.context.IdeContext;
import com.devonfw.tools.ide.io.FileAccess;
+import com.devonfw.tools.ide.process.ProcessMode;
import com.devonfw.tools.ide.tool.LocalToolCommandlet;
import com.devonfw.tools.ide.tool.ToolCommandlet;
import com.devonfw.tools.ide.tool.java.Java;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.stream.Stream;
+
/**
* {@link ToolCommandlet} for JDK Mission
* Control, An advanced set of tools for managing, monitoring, profiling, and troubleshooting Java applications.
@@ -27,21 +28,20 @@ public class Jmc extends LocalToolCommandlet {
*/
public Jmc(IdeContext context) {
- super(context, "jmc", Set.of(Tag.JAVA, Tag.QA, Tag.ANALYSE, Tag.JVM));
+ super(context, "jmc", Set.of(Tag.JAVA, Tag.ANALYSE));
}
@Override
public boolean doInstall(boolean silent) {
- // TODO https://github.com/devonfw/IDEasy/issues/209 currently outcommented as this breaks the tests, real fix needed asap
- // getCommandlet(Java.class).install();
+ getCommandlet(Java.class).install();
return super.doInstall(silent);
}
@Override
public void run() {
- runTool(true, null, this.arguments.asArray());
+ runTool(ProcessMode.BACKGROUND, null, this.arguments.asArray());
}
@Override
@@ -57,7 +57,9 @@ public void postInstall() {
moveFilesAndDirs(oldBinaryPath, toolPath);
fileAccess.delete(oldBinaryPath);
} else {
- this.context.info("JMC binary folder not found at {} - ignoring as this legacy problem may be resolved in newer versions.", oldBinaryPath);
+ this.context.info(
+ "JMC binary folder not found at {} - ignoring as this legacy problem may be resolved in newer versions.",
+ oldBinaryPath);
}
}
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 3f1b32299..c24a860fe 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
@@ -36,7 +36,8 @@ public boolean install(boolean silent) {
@Override
public void installPlugin(PluginDescriptor plugin) {
- Path mavenPlugin = this.context.getSoftwarePath().resolve(this.tool).resolve("lib/ext/" + plugin.getName() + ".jar");
+ Path mavenPlugin = this.context.getSoftwarePath().resolve(this.tool)
+ .resolve("lib/ext/" + plugin.getName() + ".jar");
this.context.getFileAccess().download(plugin.getUrl(), mavenPlugin);
if (Files.exists(mavenPlugin)) {
this.context.success("Successfully added {} to {}", plugin.getName(), mavenPlugin.toString());
diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/npm/Npm.java b/cli/src/main/java/com/devonfw/tools/ide/tool/npm/Npm.java
new file mode 100644
index 000000000..5cec9001b
--- /dev/null
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/npm/Npm.java
@@ -0,0 +1,56 @@
+package com.devonfw.tools.ide.tool.npm;
+
+import java.nio.file.Path;
+import java.util.Set;
+
+import com.devonfw.tools.ide.common.Tag;
+import com.devonfw.tools.ide.context.IdeContext;
+import com.devonfw.tools.ide.io.FileAccess;
+import com.devonfw.tools.ide.tool.LocalToolCommandlet;
+import com.devonfw.tools.ide.tool.ToolCommandlet;
+import com.devonfw.tools.ide.tool.node.Node;
+
+/**
+ * {@link ToolCommandlet} for npm.
+ */
+public class Npm extends LocalToolCommandlet {
+
+ /**
+ * The constructor.
+ *
+ * @param context the {@link IdeContext}.
+ */
+ public Npm(IdeContext context) {
+
+ super(context, "npm", Set.of(Tag.JAVA_SCRIPT, Tag.BUILD));
+ }
+
+ @Override
+ public boolean install(boolean silent) {
+
+ getCommandlet(Node.class).install();
+ return super.doInstall(silent);
+ }
+
+ protected void postExtract(Path extractedDir) {
+
+ FileAccess fileAccess = context.getFileAccess();
+ if (context.getSystemInfo().isWindows()) {
+ Path nodeHomePath = this.context.getSoftwarePath().resolve("node/");
+ Path npmBinBath = nodeHomePath.resolve("node_modules/npm/bin/");
+ String npm = "npm";
+ String npx = "npx";
+ String cmd = ".cmd";
+
+ fileAccess.delete(nodeHomePath.resolve(npm));
+ fileAccess.delete(nodeHomePath.resolve(npm + cmd));
+ fileAccess.delete(nodeHomePath.resolve(npx));
+ fileAccess.delete(nodeHomePath.resolve(npx + cmd));
+
+ fileAccess.copy(npmBinBath.resolve(npm), nodeHomePath.resolve(npm));
+ fileAccess.copy(npmBinBath.resolve(npm + cmd), nodeHomePath.resolve(npm + cmd));
+ fileAccess.copy(npmBinBath.resolve(npx), nodeHomePath.resolve(npx));
+ fileAccess.copy(npmBinBath.resolve(npx + cmd), nodeHomePath.resolve(npx + cmd));
+ }
+ }
+}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/sonar/Sonar.java b/cli/src/main/java/com/devonfw/tools/ide/tool/sonar/Sonar.java
new file mode 100644
index 000000000..e3f77051a
--- /dev/null
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/sonar/Sonar.java
@@ -0,0 +1,105 @@
+package com.devonfw.tools.ide.tool.sonar;
+
+import java.nio.file.Path;
+import java.util.Properties;
+import java.util.Set;
+
+import com.devonfw.tools.ide.common.Tag;
+import com.devonfw.tools.ide.context.IdeContext;
+import com.devonfw.tools.ide.property.EnumProperty;
+import com.devonfw.tools.ide.tool.LocalToolCommandlet;
+import com.devonfw.tools.ide.tool.java.Java;
+import com.devonfw.tools.ide.tool.mvn.Mvn;
+import com.devonfw.tools.ide.util.PropertiesFileUtil;
+
+public class Sonar extends LocalToolCommandlet {
+
+ public final EnumProperty command;
+
+ /**
+ * The constructor.
+ *
+ * @param context the {@link IdeContext}. method.
+ */
+ public Sonar(IdeContext context) {
+
+ super(context, "sonar", Set.of(Tag.CODE_QA));
+
+ this.command = add(new EnumProperty<>("", true, "command", SonarCommand.class));
+ add(this.arguments);
+ }
+
+ @Override
+ protected void initProperties() {
+
+ // Empty on purpose
+ }
+
+ @Override
+ public boolean install(boolean silent) {
+
+ getCommandlet(Java.class).install();
+ return super.install(silent);
+ }
+
+ @Override
+ public void run() {
+
+ SonarCommand command = this.command.getValue();
+
+ Path toolPath = getToolPath();
+ if (!toolPath.toFile().exists()) {
+ super.install(true);
+ }
+
+ switch (command) {
+ case ANALYZE:
+ getCommandlet(Mvn.class).runTool(null, "sonar:sonar");
+ break;
+ case START:
+ printSonarWebPort();
+ arguments.setValueAsString("start", context);
+ super.run();
+ break;
+ case STOP:
+ arguments.setValueAsString("stop", context);
+ super.run();
+ break;
+ default:
+ }
+ }
+
+ @Override
+ protected String getBinaryName() {
+
+ SonarCommand command = this.command.getValue();
+
+ Path toolBinPath = getToolBinPath();
+ String sonarLocation = null;
+
+ if (this.context.getSystemInfo().isWindows()) {
+ if (command.equals(SonarCommand.START)) {
+ sonarLocation = "windows-x86-64/StartSonar.bat";
+ } else if (command.equals(SonarCommand.STOP)) {
+ sonarLocation = "windows-x86-64/SonarService.bat";
+ }
+ } else if (this.context.getSystemInfo().isMac()) {
+ sonarLocation = "macosx-universal-64/sonar.sh";
+ } else {
+ sonarLocation = "linux-x86-64/sonar.sh";
+ }
+ return toolBinPath.resolve(sonarLocation).toString();
+ }
+
+ private void printSonarWebPort() {
+
+ this.context.info("SonarQube is running at localhost on the following port (default 9000):");
+ Path sonarPropertiesPath = getToolPath().resolve("conf/sonar.properties");
+
+ Properties sonarProperties = PropertiesFileUtil.loadProperties(sonarPropertiesPath);
+ String sonarWebPort = sonarProperties.getProperty("sonar.web.port");
+ if (sonarWebPort != null) {
+ this.context.info(sonarWebPort);
+ }
+ }
+}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/sonar/SonarCommand.java b/cli/src/main/java/com/devonfw/tools/ide/tool/sonar/SonarCommand.java
new file mode 100644
index 000000000..1ec15148e
--- /dev/null
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/sonar/SonarCommand.java
@@ -0,0 +1,8 @@
+package com.devonfw.tools.ide.tool.sonar;
+
+/**
+ * Represents commands for controlling a sonar operation in The{@link Sonar} Tool.
+ */
+public enum SonarCommand {
+ START, STOP, ANALYZE
+}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/terraform/Terraform.java b/cli/src/main/java/com/devonfw/tools/ide/tool/terraform/Terraform.java
index e99d1264b..33e49e34f 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/tool/terraform/Terraform.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/terraform/Terraform.java
@@ -4,6 +4,7 @@
import com.devonfw.tools.ide.common.Tag;
import com.devonfw.tools.ide.context.IdeContext;
+import com.devonfw.tools.ide.process.ProcessMode;
import com.devonfw.tools.ide.tool.LocalToolCommandlet;
import com.devonfw.tools.ide.tool.ToolCommandlet;
@@ -26,6 +27,6 @@ public Terraform(IdeContext context) {
protected void postInstall() {
super.postInstall();
- runTool(false, null, "-install-autocomplete");
+ runTool(ProcessMode.DEFAULT, null, "-install-autocomplete");
}
}
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 283e35deb..cf9551edf 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,49 +1,49 @@
-package com.devonfw.tools.ide.util;
-
-import java.util.Locale;
-
-/**
- * Utility class for filenames and extensions.
- */
-public final class FilenameUtil {
-
- private FilenameUtil() {
-
- // construction forbidden
- }
-
- /**
- * @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.
- */
- public static String getExtension(String path) {
-
- if (path == null) {
- return null;
- }
- path = path.toLowerCase(Locale.ROOT);
- int lastSlash = path.lastIndexOf('/');
- if (lastSlash < 0) {
- lastSlash = 0;
- }
- int lastDot = path.lastIndexOf('.');
-
- // workaround for sourceforge urls ending with /download like
- // https://sourceforge.net/projects/gcviewer/files/gcviewer-1.36.jar/download
- if (path.startsWith("https://") && path.contains("sourceforge") && path.endsWith("download")) {
- return path.substring(lastDot + 1, lastSlash);
- }
-
- if (lastDot < lastSlash) {
- return null;
- }
- // include previous ".tar" for ".tar.gz" or ".tar.bz2"
- int rawNameLength = lastDot - lastSlash;
- if ((rawNameLength > 4) && path.substring(lastDot - 4, lastDot).equals(".tar")) {
- lastDot = lastDot - 4;
- }
- return path.substring(lastDot + 1);
- }
-
-}
+package com.devonfw.tools.ide.util;
+
+import java.util.Locale;
+
+/**
+ * Utility class for filenames and extensions.
+ */
+public final class FilenameUtil {
+
+ private FilenameUtil() {
+
+ // construction forbidden
+ }
+
+ /**
+ * @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.
+ */
+ public static String getExtension(String path) {
+
+ if (path == null) {
+ return null;
+ }
+ path = path.toLowerCase(Locale.ROOT).replace('\\', '/');
+ int lastSlash = path.lastIndexOf('/');
+ if (lastSlash < 0) {
+ lastSlash = 0;
+ }
+ int lastDot = path.lastIndexOf('.');
+
+ // workaround for sourceforge urls ending with /download like
+ // https://sourceforge.net/projects/gcviewer/files/gcviewer-1.36.jar/download
+ if (path.startsWith("https://") && path.contains("sourceforge") && path.endsWith("download")) {
+ return path.substring(lastDot + 1, lastSlash);
+ }
+
+ if (lastDot < lastSlash) {
+ return null;
+ }
+ // include previous ".tar" for ".tar.gz" or ".tar.bz2"
+ int rawNameLength = lastDot - lastSlash;
+ if ((rawNameLength > 4) && path.substring(lastDot - 4, lastDot).equals(".tar")) {
+ lastDot = lastDot - 4;
+ }
+ return path.substring(lastDot + 1);
+ }
+
+}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/util/PropertiesFileUtil.java b/cli/src/main/java/com/devonfw/tools/ide/util/PropertiesFileUtil.java
new file mode 100644
index 000000000..b0525639b
--- /dev/null
+++ b/cli/src/main/java/com/devonfw/tools/ide/util/PropertiesFileUtil.java
@@ -0,0 +1,27 @@
+package com.devonfw.tools.ide.util;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Properties;
+
+public class PropertiesFileUtil {
+
+ /**
+ * Loads properties from a file at the given path.
+ *
+ * @param path the path to the properties file
+ * @return a Properties object loaded with properties from the file
+ */
+ public static Properties loadProperties(Path path) {
+
+ Properties properties = new Properties();
+
+ try (var inputStream = Files.newInputStream(path)) {
+ properties.load(inputStream);
+ } catch (IOException e) {
+ throw new IllegalArgumentException(String.format("Cannot read Properties File at %s", path.toString()), e);
+ }
+ return properties;
+ }
+}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/variable/AbstractVariableDefinition.java b/cli/src/main/java/com/devonfw/tools/ide/variable/AbstractVariableDefinition.java
index af3ad3315..a83eefccf 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/variable/AbstractVariableDefinition.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/variable/AbstractVariableDefinition.java
@@ -1,134 +1,161 @@
-package com.devonfw.tools.ide.variable;
-
-import java.util.Objects;
-import java.util.function.Function;
-
-import com.devonfw.tools.ide.context.IdeContext;
-
-/**
- * Abstract base implementation of {@link VariableDefinition}.
- *
- * @param the {@link #getValueType() value type}.
- */
-public abstract class AbstractVariableDefinition implements VariableDefinition {
-
- @SuppressWarnings("rawtypes")
- private static final Function NO_DEFAULT_VALUE = context -> null;
-
- private final String name;
-
- private final String legacyName;
-
- private final Function defaultValueFactory;
-
- private final boolean forceDefaultValue;
-
- /**
- * The constructor.
- *
- * @param name the {@link #getName() variable name}.
- */
- public AbstractVariableDefinition(String name) {
-
- this(name, null, NO_DEFAULT_VALUE, false);
- }
-
- /**
- * The constructor.
- *
- * @param name the {@link #getName() variable name}.
- * @param legacyName the {@link #getLegacyName() legacy name}.
- */
- public AbstractVariableDefinition(String name, String legacyName) {
-
- this(name, legacyName, NO_DEFAULT_VALUE, false);
- }
-
- /**
- * The constructor.
- *
- * @param name the {@link #getName() variable name}.
- * @param legacyName the {@link #getLegacyName() legacy name}.
- * @param defaultValueFactory the factory {@link Function} for the {@link #getDefaultValue(IdeContext) default value}.
- */
- public AbstractVariableDefinition(String name, String legacyName, Function defaultValueFactory) {
-
- this(name, legacyName, defaultValueFactory, false);
- }
-
- /**
- * The constructor.
- *
- * @param name the {@link #getName() variable name}.
- * @param legacyName the {@link #getLegacyName() legacy name}.
- * @param defaultValueFactory the factory {@link Function} for the {@link #getDefaultValue(IdeContext) default value}.
- * @param forceDefaultValue the {@link #isForceDefaultValue() forceDefaultValue} flag.
- */
- public AbstractVariableDefinition(String name, String legacyName, Function defaultValueFactory,
- boolean forceDefaultValue) {
-
- super();
- this.name = name;
- this.legacyName = legacyName;
- this.defaultValueFactory = defaultValueFactory;
- this.forceDefaultValue = forceDefaultValue;
- }
-
- @Override
- public boolean isForceDefaultValue() {
-
- return this.forceDefaultValue;
- }
-
- @Override
- public String getName() {
-
- return this.name;
- }
-
- @Override
- public String getLegacyName() {
-
- return this.legacyName;
- }
-
- @Override
- public V getDefaultValue(IdeContext context) {
-
- return this.defaultValueFactory.apply(context);
- }
-
- @Override
- public V get(IdeContext context) {
-
- Objects.requireNonNull(context);
- String valueAsString = null;
- if (!this.forceDefaultValue) {
- valueAsString = context.getVariables().get(this.name);
- if (valueAsString == null) {
- if (this.legacyName != null) {
- valueAsString = context.getVariables().get(this.legacyName);
- }
- }
- }
- V value;
- if (valueAsString == null) {
- value = getDefaultValue(context);
- } else {
- value = fromString(valueAsString, context);
- }
- return value;
- }
-
- @Override
- public String toString() {
-
- Class valueType = getValueType();
- if (valueType == String.class) {
- return this.name;
- } else {
- return this.name + "[" + valueType.getSimpleName() + "]";
- }
- }
-
-}
+package com.devonfw.tools.ide.variable;
+
+import com.devonfw.tools.ide.context.IdeContext;
+
+import java.util.Objects;
+import java.util.function.Function;
+
+/**
+ * Abstract base implementation of {@link VariableDefinition}.
+ *
+ * @param the {@link #getValueType() value type}.
+ */
+public abstract class AbstractVariableDefinition implements VariableDefinition {
+
+ @SuppressWarnings("rawtypes")
+ private static final Function NO_DEFAULT_VALUE = context -> null;
+
+ private final String name;
+
+ private final String legacyName;
+
+ private final Function defaultValueFactory;
+
+ private final boolean forceDefaultValue;
+
+ private final boolean export;
+
+ /**
+ * The constructor.
+ *
+ * @param name the {@link #getName() variable name}.
+ */
+ public AbstractVariableDefinition(String name) {
+
+ this(name, null, NO_DEFAULT_VALUE, false);
+ }
+
+ /**
+ * The constructor.
+ *
+ * @param name the {@link #getName() variable name}.
+ * @param legacyName the {@link #getLegacyName() legacy name}.
+ */
+ public AbstractVariableDefinition(String name, String legacyName) {
+
+ this(name, legacyName, NO_DEFAULT_VALUE, false);
+ }
+
+ /**
+ * The constructor.
+ *
+ * @param name the {@link #getName() variable name}.
+ * @param legacyName the {@link #getLegacyName() legacy name}.
+ * @param defaultValueFactory the factory {@link Function} for the
+ * {@link #getDefaultValue(IdeContext) default value}.
+ * @param forceDefaultValue the {@link #isForceDefaultValue() forceDefaultValue} flag.
+ */
+ public AbstractVariableDefinition(String name, String legacyName, Function defaultValueFactory,
+ boolean forceDefaultValue) {
+
+ this(name, legacyName, defaultValueFactory, forceDefaultValue, false);
+ }
+
+ /**
+ * The constructor.
+ *
+ * @param name the {@link #getName() variable name}.
+ * @param legacyName the {@link #getLegacyName() legacy name}.
+ * @param defaultValueFactory the factory {@link Function} for the
+ * {@link #getDefaultValue(IdeContext) default value}.
+ */
+ public AbstractVariableDefinition(String name, String legacyName, Function defaultValueFactory) {
+
+ this(name, legacyName, defaultValueFactory, false);
+ }
+
+ /**
+ * The constructor.
+ *
+ * @param name the {@link #getName() variable name}.
+ * @param legacyName the {@link #getLegacyName() legacy name}.
+ * @param defaultValueFactory the factory {@link Function} for the
+ * {@link #getDefaultValue(IdeContext) default value}.
+ * @param forceDefaultValue the {@link #isForceDefaultValue() forceDefaultValue} flag.
+ * @param export the {@link #isExport() export} flag.
+ */
+ public AbstractVariableDefinition(String name, String legacyName, Function defaultValueFactory,
+ boolean forceDefaultValue, boolean export) {
+
+ super();
+ this.name = name;
+ this.legacyName = legacyName;
+ this.defaultValueFactory = defaultValueFactory;
+ this.forceDefaultValue = forceDefaultValue;
+ this.export = export;
+ }
+
+ @Override
+ public boolean isForceDefaultValue() {
+
+ return this.forceDefaultValue;
+ }
+
+ @Override
+ public String getName() {
+
+ return this.name;
+ }
+
+ @Override
+ public String getLegacyName() {
+
+ return this.legacyName;
+ }
+
+ @Override
+ public V getDefaultValue(IdeContext context) {
+
+ return this.defaultValueFactory.apply(context);
+ }
+
+ @Override
+ public V get(IdeContext context) {
+
+ Objects.requireNonNull(context);
+ String valueAsString = null;
+ if (!this.forceDefaultValue) {
+ valueAsString = context.getVariables().get(this.name);
+ if (valueAsString == null) {
+ if (this.legacyName != null) {
+ valueAsString = context.getVariables().get(this.legacyName);
+ }
+ }
+ }
+ V value;
+ if (valueAsString == null) {
+ value = getDefaultValue(context);
+ } else {
+ value = fromString(valueAsString, context);
+ }
+ return value;
+ }
+
+ @Override
+ public boolean isExport() {
+
+ return this.export;
+ }
+
+ @Override
+ public String toString() {
+
+ Class valueType = getValueType();
+ if (valueType == String.class) {
+ return this.name;
+ } else {
+ return this.name + "[" + valueType.getSimpleName() + "]";
+ }
+ }
+
+}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/variable/IdeVariables.java b/cli/src/main/java/com/devonfw/tools/ide/variable/IdeVariables.java
index 45ffa5769..4d2f9a48f 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/variable/IdeVariables.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/variable/IdeVariables.java
@@ -1,67 +1,71 @@
-package com.devonfw.tools.ide.variable;
-
-import java.util.Collection;
-import java.util.List;
-
-/**
- * Interface (mis)used to define all the available variables.
- */
-public interface IdeVariables {
-
- /** {@link VariableDefinition} for {@link com.devonfw.tools.ide.context.IdeContext#getIdeHome() IDE_HOME}. */
- VariableDefinitionPath IDE_HOME = new VariableDefinitionPath("IDE_HOME", "DEVON_IDE_HOME", c -> c.getIdeHome(), true);
-
- /** {@link VariableDefinition} for {@link com.devonfw.tools.ide.context.IdeContext#getIdeRoot() IDE_ROOT}. */
- VariableDefinitionPath IDE_ROOT = new VariableDefinitionPath("IDE_ROOT", null, c -> c.getIdeRoot());
-
- /** {@link VariableDefinition} for {@link com.devonfw.tools.ide.context.IdeContext#getUserHome() HOME}. */
- VariableDefinitionPath HOME = new VariableDefinitionPath("HOME", null, c -> c.getUserHome(), true);
-
- /** {@link VariableDefinition} for {@link com.devonfw.tools.ide.context.IdeContext#getWorkspaceName() WORKSPACE}. */
- VariableDefinitionString WORKSPACE = new VariableDefinitionString("WORKSPACE", null, c -> c.getWorkspaceName(), true);
-
- /** {@link VariableDefinition} for {@link com.devonfw.tools.ide.context.IdeContext#getPath() PATH}. */
- VariableDefinitionSystemPath PATH = new VariableDefinitionSystemPath("PATH", null, c -> c.getPath(), true);
-
- /**
- * {@link VariableDefinition} for {@link com.devonfw.tools.ide.context.IdeContext#getWorkspacePath() WORKSPACE_PATH}.
- */
- VariableDefinitionPath WORKSPACE_PATH = new VariableDefinitionPath("WORKSPACE_PATH", null, c -> c.getWorkspacePath(),
- true);
-
- /** {@link VariableDefinition} for list of tools to install by default. */
- VariableDefinitionStringList IDE_TOOLS = new VariableDefinitionStringList("IDE_TOOLS", "DEVON_IDE_TOOLS");
-
- /** {@link VariableDefinition} for list of IDE tools to create start scripts for. */
- VariableDefinitionStringList CREATE_START_SCRIPTS = new VariableDefinitionStringList("CREATE_START_SCRIPTS",
- "DEVON_CREATE_START_SCRIPTS");
-
- /** {@link VariableDefinition} for minimum IDE product version. */
- // TODO define initial IDEasy version as default value
- VariableDefinitionVersion IDE_MIN_VERSION = new VariableDefinitionVersion("IDE_MIN_VERSION", "DEVON_IDE_MIN_VERSION");
-
- /** {@link VariableDefinition} for version of maven (mvn). */
- VariableDefinitionVersion MVN_VERSION = new VariableDefinitionVersion("MVN_VERSION", "MAVEN_VERSION");
-
- /** {@link VariableDefinition} for {@link com.devonfw.tools.ide.context.IdeContext#getWorkspaceName() WORKSPACE}. */
- VariableDefinitionString DOCKER_EDITION = new VariableDefinitionString("DOCKER_EDITION", null, c -> "rancher");
-
- /** {@link VariableDefinition} for {@link com.devonfw.tools.ide.context.IdeContext#getWorkspaceName() WORKSPACE}. */
- VariableDefinitionString GRAALVM_EDITION = new VariableDefinitionString("GRAALVM_EDITION", null, c -> "community");
-
- /** A {@link Collection} with all pre-defined {@link VariableDefinition}s. */
- Collection> VARIABLES = List.of(PATH, HOME, WORKSPACE_PATH, IDE_HOME, IDE_ROOT, WORKSPACE,
- IDE_TOOLS, CREATE_START_SCRIPTS, IDE_MIN_VERSION, MVN_VERSION, DOCKER_EDITION, GRAALVM_EDITION);
-
- /**
- * @param name the name of the requested {@link VariableDefinition}.
- * @return the {@link VariableDefinition} for the given {@code name} or {@code null} if not defined.
- * @see VariableDefinition#getName()
- * @see VariableDefinition#getLegacyName()
- */
- static VariableDefinition> get(String name) {
-
- return IdeVariablesList.get(name);
- }
-
-}
+package com.devonfw.tools.ide.variable;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Interface (mis)used to define all the available variables.
+ */
+public interface IdeVariables {
+
+ /** {@link VariableDefinition} for {@link com.devonfw.tools.ide.context.IdeContext#getIdeHome() IDE_HOME}. */
+ VariableDefinitionPath IDE_HOME = new VariableDefinitionPath("IDE_HOME", "DEVON_IDE_HOME", c -> c.getIdeHome(), true);
+
+ /** {@link VariableDefinition} for {@link com.devonfw.tools.ide.context.IdeContext#getIdeRoot() IDE_ROOT}. */
+ VariableDefinitionPath IDE_ROOT = new VariableDefinitionPath("IDE_ROOT", null, c -> c.getIdeRoot());
+
+ /** {@link VariableDefinition} for {@link com.devonfw.tools.ide.context.IdeContext#getUserHome() HOME}. */
+ VariableDefinitionPath HOME = new VariableDefinitionPath("HOME", null, c -> c.getUserHome(), true);
+
+ /** {@link VariableDefinition} for {@link com.devonfw.tools.ide.context.IdeContext#getWorkspaceName() WORKSPACE}. */
+ VariableDefinitionString WORKSPACE = new VariableDefinitionString("WORKSPACE", null, c -> c.getWorkspaceName(), true);
+
+ /** {@link VariableDefinition} for {@link com.devonfw.tools.ide.context.IdeContext#getPath() PATH}. */
+ VariableDefinitionSystemPath PATH = new VariableDefinitionSystemPath("PATH", null, c -> c.getPath(), true, true);
+
+ /**
+ * {@link VariableDefinition} for {@link com.devonfw.tools.ide.context.IdeContext#getWorkspacePath() WORKSPACE_PATH}.
+ */
+ VariableDefinitionPath WORKSPACE_PATH = new VariableDefinitionPath("WORKSPACE_PATH", null, c -> c.getWorkspacePath(),
+ true);
+
+ /** {@link VariableDefinition} for list of tools to install by default. */
+ VariableDefinitionStringList IDE_TOOLS = new VariableDefinitionStringList("IDE_TOOLS", "DEVON_IDE_TOOLS");
+
+ /** {@link VariableDefinition} for list of IDE tools to create start scripts for. */
+ VariableDefinitionStringList CREATE_START_SCRIPTS = new VariableDefinitionStringList("CREATE_START_SCRIPTS",
+ "DEVON_CREATE_START_SCRIPTS");
+
+ /** {@link VariableDefinition} for minimum IDE product version. */
+ // TODO define initial IDEasy version as default value
+ VariableDefinitionVersion IDE_MIN_VERSION = new VariableDefinitionVersion("IDE_MIN_VERSION", "DEVON_IDE_MIN_VERSION");
+
+ /** {@link VariableDefinition} for version of maven (mvn). */
+ VariableDefinitionVersion MVN_VERSION = new VariableDefinitionVersion("MVN_VERSION", "MAVEN_VERSION");
+
+ /** {@link VariableDefinition} for {@link com.devonfw.tools.ide.context.IdeContext#getWorkspaceName() WORKSPACE}. */
+ VariableDefinitionString DOCKER_EDITION = new VariableDefinitionString("DOCKER_EDITION", null, c -> "rancher");
+
+ /** {@link VariableDefinition} for {@link com.devonfw.tools.ide.context.IdeContext#getWorkspaceName() WORKSPACE}. */
+ VariableDefinitionString GRAALVM_EDITION = new VariableDefinitionString("GRAALVM_EDITION", null, c -> "community");
+
+ /** {@link VariableDefinition} for options of jasypt */
+ VariableDefinitionString JASYPT_OPTS = new VariableDefinitionString("JASYPT_OPTS", null,
+ c -> "algorithm=PBEWITHHMACSHA512ANDAES_256 ivGeneratorClassName=org.jasypt.iv.RandomIvGenerator");
+
+ /** A {@link Collection} with all pre-defined {@link VariableDefinition}s. */
+ Collection> VARIABLES = List.of(PATH, HOME, WORKSPACE_PATH, IDE_HOME, IDE_ROOT, WORKSPACE,
+ IDE_TOOLS, CREATE_START_SCRIPTS, IDE_MIN_VERSION, MVN_VERSION, DOCKER_EDITION, GRAALVM_EDITION, JASYPT_OPTS);
+
+ /**
+ * @param name the name of the requested {@link VariableDefinition}.
+ * @return the {@link VariableDefinition} for the given {@code name} or {@code null} if not defined.
+ * @see VariableDefinition#getName()
+ * @see VariableDefinition#getLegacyName()
+ */
+ static VariableDefinition> get(String name) {
+
+ return IdeVariablesList.get(name);
+ }
+
+}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/variable/VariableDefinition.java b/cli/src/main/java/com/devonfw/tools/ide/variable/VariableDefinition.java
index 162421f23..1124a63e9 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/variable/VariableDefinition.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/variable/VariableDefinition.java
@@ -1,104 +1,108 @@
-package com.devonfw.tools.ide.variable;
-
-import java.nio.file.Path;
-
-import com.devonfw.tools.ide.context.IdeContext;
-import com.devonfw.tools.ide.environment.EnvironmentVariables;
-import com.devonfw.tools.ide.environment.VariableLine;
-
-/**
- * Interface for a definition of a variable.
- *
- * @param the {@link #getValueType() value type}.
- */
-public interface VariableDefinition {
-
- /**
- * @return the name of the variable.
- */
- String getName();
-
- /**
- * @return the optional legacy name that is still supported for downward compatibility. May be {@code null} if
- * undefined (no legacy support).
- */
- String getLegacyName();
-
- /**
- * @return the {@link Class} reflecting the type of the variable value.
- */
- Class getValueType();
-
- /**
- * @param context the {@link IdeContext}.
- * @return the default value. May be {@code null}.
- */
- V getDefaultValue(IdeContext context);
-
- /**
- * @param context the {@link IdeContext}.
- * @return the default value as {@link String}. May be {@code null}.
- *
- * @see #getDefaultValue(IdeContext)
- * @see #toString(Object)
- */
- default String getDefaultValueAsString(IdeContext context) {
-
- V value = getDefaultValue(context);
- if (value == null) {
- return null;
- }
- return toString(value);
- }
-
- /**
- * @return {@code true} if the {@link #getDefaultValue(IdeContext) default value} shall be used without any
- * {@link EnvironmentVariables#get(String) variable lookup} (to prevent odd overriding of build in variables
- * like IDE_HOME), {@code false} otherwise (overriding of default value is allowed and intended).
- */
- boolean isForceDefaultValue();
-
- /**
- * @param value the value as {@link String}. May NOT be {@code null}.
- * @param context TODO
- * @return the value converted to the {@link #getValueType() value type}.
- */
- V fromString(String value, IdeContext context);
-
- /**
- * @param value the typed value.
- * @return the value converted to {@link String}.
- */
- default String toString(V value) {
-
- if (value == null) {
- return "";
- } else if (value instanceof Path) {
- return value.toString().replace('\\', '/');
- }
- return value.toString();
- }
-
- /**
- * @param context the {@link IdeContext}.
- * @return the value of the variable of this {@link VariableDefinition}.
- */
- V get(IdeContext context);
-
- /**
- * @param line the {@link VariableLine} that potentially needs to be migrated.
- * @return the original {@link VariableLine} or a migrated copy of it.
- */
- default VariableLine migrateLine(VariableLine line) {
-
- String name = line.getName();
- if (name != null) {
- String newName = getName();
- if (!name.equals(newName)) {
- return line.withName(newName);
- }
- }
- return line;
- }
-
-}
+package com.devonfw.tools.ide.variable;
+
+import com.devonfw.tools.ide.context.IdeContext;
+import com.devonfw.tools.ide.environment.EnvironmentVariables;
+import com.devonfw.tools.ide.environment.VariableLine;
+
+import java.nio.file.Path;
+
+/**
+ * Interface for a definition of a variable.
+ *
+ * @param the {@link #getValueType() value type}.
+ */
+public interface VariableDefinition {
+
+ /**
+ * @return the name of the variable.
+ */
+ String getName();
+
+ /**
+ * @return the optional legacy name that is still supported for downward compatibility. May be {@code null} if
+ * undefined (no legacy support).
+ */
+ String getLegacyName();
+
+ /**
+ * @return the {@link Class} reflecting the type of the variable value.
+ */
+ Class getValueType();
+
+ /**
+ * @param context the {@link IdeContext}.
+ * @return the default value. May be {@code null}.
+ */
+ V getDefaultValue(IdeContext context);
+
+ /**
+ * @param context the {@link IdeContext}.
+ * @return the default value as {@link String}. May be {@code null}.
+ * @see #getDefaultValue(IdeContext)
+ * @see #toString(Object)
+ */
+ default String getDefaultValueAsString(IdeContext context) {
+
+ V value = getDefaultValue(context);
+ if (value == null) {
+ return null;
+ }
+ return toString(value);
+ }
+
+ /**
+ * @return {@code true} if the {@link #getDefaultValue(IdeContext) default value} shall be used without any
+ * {@link EnvironmentVariables#get(String) variable lookup} (to prevent odd overriding of build in variables like
+ * IDE_HOME), {@code false} otherwise (overriding of default value is allowed and intended).
+ */
+ boolean isForceDefaultValue();
+
+ /**
+ * @param value the value as {@link String}. May NOT be {@code null}.
+ * @param context the {@link IdeContext}.
+ * @return the value converted to the {@link #getValueType() value type}.
+ */
+ V fromString(String value, IdeContext context);
+
+ /**
+ * @return {@code true} if the variable needs to be exported, {@code false} otherwise.
+ */
+ boolean isExport();
+
+ /**
+ * @param value the typed value.
+ * @return the value converted to {@link String}.
+ */
+ default String toString(V value) {
+
+ if (value == null) {
+ return "";
+ } else if (value instanceof Path) {
+ return value.toString().replace('\\', '/');
+ }
+ return value.toString();
+ }
+
+ /**
+ * @param context the {@link IdeContext}.
+ * @return the value of the variable of this {@link VariableDefinition}.
+ */
+ V get(IdeContext context);
+
+ /**
+ * @param line the {@link VariableLine} that potentially needs to be migrated.
+ * @return the original {@link VariableLine} or a migrated copy of it.
+ */
+ default VariableLine migrateLine(VariableLine line) {
+
+ String name = line.getName();
+ if (name != null) {
+ String newName = getName();
+ if (!name.equals(newName)) {
+ return line.withName(newName);
+ }
+ }
+ return line;
+ }
+
+}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/variable/VariableDefinitionSystemPath.java b/cli/src/main/java/com/devonfw/tools/ide/variable/VariableDefinitionSystemPath.java
index a65f54b82..c76a3af23 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/variable/VariableDefinitionSystemPath.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/variable/VariableDefinitionSystemPath.java
@@ -1,73 +1,92 @@
-package com.devonfw.tools.ide.variable;
-
-import java.nio.file.Path;
-import java.util.function.Function;
-
-import com.devonfw.tools.ide.common.SystemPath;
-import com.devonfw.tools.ide.context.IdeContext;
-
-/**
- * Implementation of {@link VariableDefinition} for a variable with the {@link #getValueType() value type} {@link Path}.
- */
-public class VariableDefinitionSystemPath extends AbstractVariableDefinition {
-
- /**
- * The constructor.
- *
- * @param name the {@link #getName() variable name}.
- */
- public VariableDefinitionSystemPath(String name) {
-
- super(name);
- }
-
- /**
- * The constructor.
- *
- * @param name the {@link #getName() variable name}.
- * @param legacyName the {@link #getLegacyName() legacy name}.
- */
- public VariableDefinitionSystemPath(String name, String legacyName) {
-
- super(name, legacyName);
- }
-
- /**
- * The constructor.
- *
- * @param name the {@link #getName() variable name}.
- * @param legacyName the {@link #getLegacyName() legacy name}.
- * @param defaultValueFactory the factory {@link Function} for the {@link #getDefaultValue(IdeContext) default value}.
- */
- public VariableDefinitionSystemPath(String name, String legacyName,
- Function defaultValueFactory) {
-
- super(name, legacyName, defaultValueFactory);
- }
-
- /**
- * The constructor.
- *
- * @param name the {@link #getName() variable name}.
- * @param legacyName the {@link #getLegacyName() legacy name}.
- * @param defaultValueFactory the factory {@link Function} for the {@link #getDefaultValue(IdeContext) default value}.
- * @param forceDefaultValue the {@link #isForceDefaultValue() forceDefaultValue} flag.
- */
- public VariableDefinitionSystemPath(String name, String legacyName,
- Function defaultValueFactory, boolean forceDefaultValue) {
-
- super(name, legacyName, defaultValueFactory, forceDefaultValue);
- }
-
- @Override
- public Class getValueType() {
-
- return SystemPath.class;
- }
-
- @Override
- public SystemPath fromString(String value, IdeContext context) {
-
- return new SystemPath(value, context.getSoftwarePath(), context);
- }
-}
+package com.devonfw.tools.ide.variable;
+
+import com.devonfw.tools.ide.common.SystemPath;
+import com.devonfw.tools.ide.context.IdeContext;
+
+import java.nio.file.Path;
+import java.util.function.Function;
+
+/**
+ * Implementation of {@link VariableDefinition} for a variable with the {@link #getValueType() value type}
+ * {@link Path}.
+ */
+public class VariableDefinitionSystemPath extends AbstractVariableDefinition {
+
+ /**
+ * The constructor.
+ *
+ * @param name the {@link #getName() variable name}.
+ */
+ public VariableDefinitionSystemPath(String name) {
+
+ super(name);
+ }
+
+ /**
+ * The constructor.
+ *
+ * @param name the {@link #getName() variable name}.
+ * @param legacyName the {@link #getLegacyName() legacy name}.
+ */
+ public VariableDefinitionSystemPath(String name, String legacyName) {
+
+ super(name, legacyName);
+ }
+
+ /**
+ * The constructor.
+ *
+ * @param name the {@link #getName() variable name}.
+ * @param legacyName the {@link #getLegacyName() legacy name}.
+ * @param defaultValueFactory the factory {@link Function} for the
+ * {@link #getDefaultValue(IdeContext) default value}.
+ */
+ public VariableDefinitionSystemPath(String name, String legacyName,
+ Function defaultValueFactory) {
+
+ super(name, legacyName, defaultValueFactory);
+ }
+
+ /**
+ * The constructor.
+ *
+ * @param name the {@link #getName() variable name}.
+ * @param legacyName the {@link #getLegacyName() legacy name}.
+ * @param defaultValueFactory the factory {@link Function} for the
+ * {@link #getDefaultValue(IdeContext) default value}.
+ * @param forceDefaultValue the {@link #isForceDefaultValue() forceDefaultValue} flag.
+ */
+ public VariableDefinitionSystemPath(String name, String legacyName,
+ Function defaultValueFactory, boolean forceDefaultValue) {
+
+ super(name, legacyName, defaultValueFactory, forceDefaultValue);
+ }
+
+ /**
+ * The constructor.
+ *
+ * @param name the {@link #getName() variable name}.
+ * @param legacyName the {@link #getLegacyName() legacy name}.
+ * @param defaultValueFactory the factory {@link Function} for the
+ * {@link #getDefaultValue(IdeContext) default value}.
+ * @param forceDefaultValue the {@link #isForceDefaultValue() forceDefaultValue} flag.
+ * @param export the {@link #isExport() export} flag.
+ */
+ public VariableDefinitionSystemPath(String name, String legacyName,
+ Function defaultValueFactory, boolean forceDefaultValue, boolean export) {
+
+ super(name, legacyName, defaultValueFactory, forceDefaultValue, export);
+ }
+
+ @Override
+ public Class getValueType() {
+
+ return SystemPath.class;
+ }
+
+ @Override
+ public SystemPath fromString(String value, IdeContext context) {
+
+ return new SystemPath(context, value);
+ }
+}
diff --git a/cli/src/main/package/bin/ide b/cli/src/main/package/bin/ide
new file mode 100644
index 000000000..7b12b7d00
--- /dev/null
+++ b/cli/src/main/package/bin/ide
@@ -0,0 +1,19 @@
+#!/usr/bin/env bash
+
+# immediately exit on errors
+set -e
+
+# Check if arguments are given
+if [ -n "${1}" ]; then
+ # Call native ideasy with user-provided arguments
+ ideasy "$@"
+fi
+
+ide_env=
+if [ "${OSTYPE}" = "cygwin" ] || [ "${OSTYPE}" = "msys" ]; then
+ ide_env="$(ideasy env --bash)"
+else
+ ide_env="$(ideasy env)"
+fi
+eval "$ide_env"
+unset ide_env
diff --git a/cli/src/main/resources/logback.xml b/cli/src/main/resources/logback.xml
index 537066d47..4c6efeebe 100644
--- a/cli/src/main/resources/logback.xml
+++ b/cli/src/main/resources/logback.xml
@@ -1,16 +1,15 @@
-
-
-
-
-
-
-
-
- %-5level %logger{0} -%kvp- %msg%n
-
-
-
-
-
-
+
+
+
+
+
+ %d{HH:mm:ss.SSS} [%thread] - %-5level - %logger{36} - %msg%n
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cli/src/main/resources/nls/Ide.properties b/cli/src/main/resources/nls/Ide.properties
index ef6b3eda7..1715ce61a 100644
--- a/cli/src/main/resources/nls/Ide.properties
+++ b/cli/src/main/resources/nls/Ide.properties
@@ -11,6 +11,7 @@ cmd-env=Print the environment variables to set and export.
cmd-get-edition=Get the edition of the selected tool.
cmd-get-version=Get the version of the selected tool.
cmd-gh=Tool commandlet for Github CLI.
+cmd-repository=setup the pre-configured git repository.
cmd-gradle=Tool commandlet for Gradle (Build-Tool)
cmd-helm=Tool commandlet for Helm (Kubernetes Package Manager)
cmd-help=Prints this help.
@@ -24,6 +25,7 @@ cmd-list-editions=List the available editions of the selected tool.
cmd-list-versions=List the available versions of the selected tool.
cmd-mvn=Tool commandlet for Maven (Build-Tool)
cmd-node=Tool commandlet for Node.js (JavaScript runtime)
+cmd-npm=Tool commandlet for Npm
cmd-oc=Tool commandlet for Openshift CLI (Kubernetes Management Tool)
cmd-quarkus=Tool commandlet for Quarkus (Framework for cloud-native apps)
cmd-set-edition=Set the edition of the selected tool.
@@ -32,11 +34,18 @@ cmd-shell=Commandlet to start built-in shell with advanced auto-completion.
cmd-terraform=Tool commandlet for Terraform
cmd-vscode=Tool commandlet for Visual Studio Code (IDE)
cmd-cobigen=Tool commandlet for Cobigen
+cmd-jasypt=Tool commandlet for Jasypt
+cmd-sonar=Tool commandlet for SonarQube
val-args=The commandline arguments to pass to the tool.
val-edition=The tool edition.
+val-sonar-command=START|STOP|ANALYZE
+val-jasypt-command=encrypt | decrypt
+val-jasypt-masterPassword=master password
+val-jasypt-secret=The secret to be encrypted or decrypted
val-tool=The tool commandlet to select.
val-version=The tool version
val-set-version-version=The tool version to set.
+val-repository-repository=The name of the properties file of the pre-configured git repository to setup, omit to setup all active repositories.
version-banner=Current version of IDE is {}
opt--batch=enable batch mode (non-interactive)
opt--debug=enable debug logging
diff --git a/cli/src/main/resources/nls/Ide_de.properties b/cli/src/main/resources/nls/Ide_de.properties
index 688bd923a..2d80f673e 100644
--- a/cli/src/main/resources/nls/Ide_de.properties
+++ b/cli/src/main/resources/nls/Ide_de.properties
@@ -10,6 +10,7 @@ cmd-env=Gibt die zu setztenden und exportierenden Umgebungsvariablen aus.
cmd-get-edition=Zeigt die Edition des selektierten Werkzeugs an.
cmd-get-version=Zeigt die Version des selektierten Werkzeugs an.
cmd-gh=Werkzeug Kommando für die Github Kommandoschnittstelle.
+cmd-repository=Richtet das vorkonfigurierte Git Repository ein.
cmd-helm=Werkzeug Kommando für Helm (Kubernetes Package Manager)
cmd-help=Zeigt diese Hilfe an.
cmd-install=Installiert das selektierte Werkzeug.
@@ -22,6 +23,7 @@ cmd-list-editions=Listet die verfügbaren Editionen des selektierten Werkzeugs a
cmd-list-versions=Listet die verfügbaren Versionen des selektierten Werkzeugs auf.
cmd-mvn=Werkzeug Kommando für Maven (Build-Werkzeug)
cmd-node=Werkzeug Kommando für Node.js (JavaScript Laufzeitumgebung)
+cmd-npm=Werkzeug Kommando für Npm
cmd-oc=Werkzeug Kommando für Openshift CLI (Kubernetes Management Tool)
cmd-quarkus=Werkzeug Kommando für Quarkus (Framework für Cloud-native Anwendungen)
cmd-set-edition=Setzt die Edition des selektierten Werkzeugs.
@@ -29,11 +31,14 @@ cmd-set-version=Setzt die Version des selektierten Werkzeugs.
cmd-terraform=Werkzeug Kommando für Terraform.
cmd-vscode=Werkzeug Kommando für Visual Studio Code (IDE)
cmd-cobigen=Werkzeug Kommando für Cobigen.
+cmd-jasypt=Werkzeug Kommando für Jasypt
+cmd-sonar=Werkzeug Kommando für SonarQube.
val-args=Die Kommandozeilen-Argumente zur Übergabe an das Werkzeug.
val-edition=Die Werkzeug Edition.
val-tool=Das zu selektierende Werkzeug Kommando.
val-version=Die Werkzeug Version.
val-set-version-version=Die zu setztende Werkzeug Version.
+val-repository-repository=Der Name der Properties-Datei des vorkonfigurierten Git Repositories zum Einrichten. Falls nicht angegeben, werden alle aktiven Projekte eingerichtet.
version-banner=Die aktuelle Version der IDE ist {}
opt--batch=Aktiviert den Batch-Modus (nicht-interaktive Stapelverarbeitung)
opt--debug=Aktiviert Debug-Ausgaben (Fehleranalyse)
diff --git a/cli/src/test/java/com/devonfw/tools/ide/commandlet/EditionGetCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/commandlet/EditionGetCommandletTest.java
index b4e239b3c..c4881ca9d 100644
--- a/cli/src/test/java/com/devonfw/tools/ide/commandlet/EditionGetCommandletTest.java
+++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/EditionGetCommandletTest.java
@@ -1,67 +1,64 @@
-package com.devonfw.tools.ide.commandlet;
-
-import java.nio.file.Path;
-import java.util.List;
-
-import org.junit.jupiter.api.Test;
-
-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.log.IdeLogLevel;
-
-/** Integration test of {@link EditionGetCommandlet}. */
-
-public class EditionGetCommandletTest extends AbstractIdeContextTest {
-
- /** Test of {@link VersionGetCommandlet} run. */
- @Test
- public void testEditionGetCommandletRun() {
-
- // arrange
- String path = "workspaces/foo-test/my-git-repo";
- String tool = "az";
- IdeTestContext context = newContext("basic", path, true);
- mockInstallTool(context, tool);
- EditionGetCommandlet editionGet = context.getCommandletManager().getCommandlet(EditionGetCommandlet.class);
-
- // act
- editionGet.tool.setValueAsString(tool, context);
- editionGet.run();
-
- // assert
- List logs = context.level(IdeLogLevel.INFO).getMessages();
- assertThat(logs).contains("az");
- }
-
- /**
- * Mocks the installation of a tool, since getEdition depends on symlinks which are not distributed with git
- *
- * @param context the {@link IdeContext} to use.
- * @param tool the tool to mock install.
- */
- private static void mockInstallTool(IdeTestContext context, String tool) {
-
- Path pathToInstallationOfDummyTool = context.getSoftwareRepositoryPath()
- .resolve(context.getDefaultToolRepository().getId()).resolve(tool).resolve("az/testVersion");
- Path pathToLinkedSoftware = context.getSoftwarePath().resolve(tool);
- context.getFileAccess().symlink(pathToInstallationOfDummyTool, pathToLinkedSoftware);
- }
-
- /** Test of {@link VersionGetCommandlet} run, when Installed Version is null. */
- @Test
- public void testVersionGetCommandletRunPrintConfiguredEdition() {
-
- // arrange
- String path = "workspaces/foo-test/my-git-repo";
- IdeTestContext context = newContext("basic", path, false);
- EditionGetCommandlet editionGet = context.getCommandletManager().getCommandlet(EditionGetCommandlet.class);
- editionGet.tool.setValueAsString("java", context);
- // act
- editionGet.run();
- // assert
- assertLogMessage(context, IdeLogLevel.INFO, "The configured edition for tool java is java");
- assertLogMessage(context, IdeLogLevel.INFO, "To install that edition call the following command:");
- assertLogMessage(context, IdeLogLevel.INFO, "ide install java");
- }
-}
+package com.devonfw.tools.ide.commandlet;
+
+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.log.IdeLogLevel;
+import org.junit.jupiter.api.Test;
+
+import java.nio.file.Path;
+import java.util.List;
+
+/** Integration test of {@link EditionGetCommandlet}. */
+
+public class EditionGetCommandletTest extends AbstractIdeContextTest {
+
+ /** Test of {@link VersionGetCommandlet} run. */
+ @Test
+ public void testEditionGetCommandletRun() {
+
+ // arrange
+ String tool = "az";
+ IdeTestContext context = newContext(PROJECT_BASIC);
+ mockInstallTool(context, tool);
+ EditionGetCommandlet editionGet = context.getCommandletManager().getCommandlet(EditionGetCommandlet.class);
+
+ // act
+ editionGet.tool.setValueAsString(tool, context);
+ editionGet.run();
+
+ // assert
+ List logs = context.level(IdeLogLevel.INFO).getMessages();
+ assertThat(logs).contains("az");
+ }
+
+ /**
+ * Mocks the installation of a tool, since getEdition depends on symlinks which are not distributed with git
+ *
+ * @param context the {@link IdeContext} to use.
+ * @param tool the tool to mock install.
+ */
+ private static void mockInstallTool(IdeTestContext context, String tool) {
+
+ Path pathToInstallationOfDummyTool = context.getSoftwareRepositoryPath()
+ .resolve(context.getDefaultToolRepository().getId()).resolve(tool).resolve("az/testVersion");
+ Path pathToLinkedSoftware = context.getSoftwarePath().resolve(tool);
+ context.getFileAccess().symlink(pathToInstallationOfDummyTool, pathToLinkedSoftware);
+ }
+
+ /** Test of {@link VersionGetCommandlet} run, when Installed Version is null. */
+ @Test
+ public void testVersionGetCommandletRunPrintConfiguredEdition() {
+
+ // arrange
+ IdeTestContext context = newContext(PROJECT_BASIC, null, false);
+ EditionGetCommandlet editionGet = context.getCommandletManager().getCommandlet(EditionGetCommandlet.class);
+ editionGet.tool.setValueAsString("java", context);
+ // act
+ editionGet.run();
+ // assert
+ assertLogMessage(context, IdeLogLevel.INFO, "The configured edition for tool java is java");
+ assertLogMessage(context, IdeLogLevel.INFO, "To install that edition call the following command:");
+ assertLogMessage(context, IdeLogLevel.INFO, "ide install java");
+ }
+}
diff --git a/cli/src/test/java/com/devonfw/tools/ide/commandlet/EditionListCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/commandlet/EditionListCommandletTest.java
index 16bde2315..08d3693f4 100644
--- a/cli/src/test/java/com/devonfw/tools/ide/commandlet/EditionListCommandletTest.java
+++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/EditionListCommandletTest.java
@@ -1,28 +1,27 @@
-package com.devonfw.tools.ide.commandlet;
-
-import com.devonfw.tools.ide.context.AbstractIdeContextTest;
-import com.devonfw.tools.ide.context.IdeTestContext;
-import com.devonfw.tools.ide.log.IdeLogLevel;
-import org.junit.jupiter.api.Test;
-
-/** Integration test of {@link EditionListCommandlet}. */
-public class EditionListCommandletTest extends AbstractIdeContextTest {
-
- /** Test of {@link EditionListCommandlet} run. */
- @Test
- public void testEditionListCommandletRun() {
-
- // arrange
- String path = "workspaces/foo-test/my-git-repo";
- IdeTestContext context = newContext("basic", path, false);
- EditionListCommandlet editionList = context.getCommandletManager().getCommandlet(EditionListCommandlet.class);
- editionList.tool.setValueAsString("mvn", context);
-
- // act
- editionList.run();
-
- // assert
- assertLogMessage(context, IdeLogLevel.INFO, "mvn");
- assertLogMessage(context, IdeLogLevel.INFO, "secondMvnEdition");
- }
+package com.devonfw.tools.ide.commandlet;
+
+import com.devonfw.tools.ide.context.AbstractIdeContextTest;
+import com.devonfw.tools.ide.context.IdeTestContext;
+import com.devonfw.tools.ide.log.IdeLogLevel;
+import org.junit.jupiter.api.Test;
+
+/** Integration test of {@link EditionListCommandlet}. */
+public class EditionListCommandletTest extends AbstractIdeContextTest {
+
+ /** Test of {@link EditionListCommandlet} run. */
+ @Test
+ public void testEditionListCommandletRun() {
+
+ // arrange
+ IdeTestContext context = newContext(PROJECT_BASIC, null, false);
+ EditionListCommandlet editionList = context.getCommandletManager().getCommandlet(EditionListCommandlet.class);
+ editionList.tool.setValueAsString("mvn", context);
+
+ // act
+ editionList.run();
+
+ // assert
+ assertLogMessage(context, IdeLogLevel.INFO, "mvn");
+ assertLogMessage(context, IdeLogLevel.INFO, "secondMvnEdition");
+ }
}
\ No newline at end of file
diff --git a/cli/src/test/java/com/devonfw/tools/ide/commandlet/EditionSetCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/commandlet/EditionSetCommandletTest.java
index bc70f23ad..31bc4887d 100644
--- a/cli/src/test/java/com/devonfw/tools/ide/commandlet/EditionSetCommandletTest.java
+++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/EditionSetCommandletTest.java
@@ -1,58 +1,57 @@
-package com.devonfw.tools.ide.commandlet;
-
-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.log.IdeLogLevel;
-import org.junit.jupiter.api.Test;
-
-import java.nio.file.Path;
-import java.util.List;
-
-/** Integration test of {@link EditionSetCommandlet}. */
-public class EditionSetCommandletTest extends AbstractIdeContextTest {
-
- /** Test of {@link VersionSetCommandlet} run. */
- @Test
- public void testEditionSetCommandletRun() {
-
- // arrange
- String path = "workspaces/foo-test/my-git-repo";
- IdeContext context = newContext("basic", path, true);
- EditionSetCommandlet editionSet = context.getCommandletManager().getCommandlet(EditionSetCommandlet.class);
- editionSet.tool.setValueAsString("mvn", context);
- editionSet.edition.setValueAsString("setEdition", context);
-
- // act
- editionSet.run();
-
- // assert
- List logs = ((IdeTestContext) context).level(IdeLogLevel.WARNING).getMessages();
- assertThat(logs).containsExactly("Edition setEdition seems to be invalid");
- Path settingsIdeProperties = context.getSettingsPath().resolve("ide.properties");
- assertThat(settingsIdeProperties).hasContent("""
- #********************************************************************************
- # This file contains project specific environment variables
- #********************************************************************************
-
- JAVA_VERSION=17*
- MVN_VERSION=3.9.*
- ECLIPSE_VERSION=2023-03
- INTELLIJ_EDITION=ultimate
-
- IDE_TOOLS=mvn,eclipse
-
- BAR=bar-${SOME}
-
- TEST_ARGS1=${TEST_ARGS1} settings1
- TEST_ARGS4=${TEST_ARGS4} settings4
- TEST_ARGS5=${TEST_ARGS5} settings5
- TEST_ARGS6=${TEST_ARGS6} settings6
- TEST_ARGS7=${TEST_ARGS7} settings7
- TEST_ARGS8=settings8
- TEST_ARGS9=settings9
- TEST_ARGSb=${TEST_ARGS10} settingsb ${TEST_ARGSa} ${TEST_ARGSb}
- TEST_ARGSc=${TEST_ARGSc} settingsc
- MVN_EDITION=setEdition""");
- }
+package com.devonfw.tools.ide.commandlet;
+
+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.log.IdeLogLevel;
+import org.junit.jupiter.api.Test;
+
+import java.nio.file.Path;
+import java.util.List;
+
+/** Integration test of {@link EditionSetCommandlet}. */
+public class EditionSetCommandletTest extends AbstractIdeContextTest {
+
+ /** Test of {@link VersionSetCommandlet} run. */
+ @Test
+ public void testEditionSetCommandletRun() {
+
+ // arrange
+ IdeContext context = newContext(PROJECT_BASIC);
+ EditionSetCommandlet editionSet = context.getCommandletManager().getCommandlet(EditionSetCommandlet.class);
+ editionSet.tool.setValueAsString("mvn", context);
+ editionSet.edition.setValueAsString("setEdition", context);
+
+ // act
+ editionSet.run();
+
+ // assert
+ List logs = ((IdeTestContext) context).level(IdeLogLevel.WARNING).getMessages();
+ assertThat(logs).containsExactly("Edition setEdition seems to be invalid");
+ Path settingsIdeProperties = context.getSettingsPath().resolve("ide.properties");
+ assertThat(settingsIdeProperties).hasContent("""
+ #********************************************************************************
+ # This file contains project specific environment variables
+ #********************************************************************************
+
+ JAVA_VERSION=17*
+ MVN_VERSION=3.9.*
+ ECLIPSE_VERSION=2023-03
+ INTELLIJ_EDITION=ultimate
+
+ IDE_TOOLS=mvn,eclipse
+
+ BAR=bar-${SOME}
+
+ TEST_ARGS1=${TEST_ARGS1} settings1
+ TEST_ARGS4=${TEST_ARGS4} settings4
+ TEST_ARGS5=${TEST_ARGS5} settings5
+ TEST_ARGS6=${TEST_ARGS6} settings6
+ TEST_ARGS7=${TEST_ARGS7} settings7
+ TEST_ARGS8=settings8
+ TEST_ARGS9=settings9
+ TEST_ARGSb=${TEST_ARGS10} settingsb ${TEST_ARGSa} ${TEST_ARGSb}
+ TEST_ARGSc=${TEST_ARGSc} settingsc
+ MVN_EDITION=setEdition""");
+ }
}
\ No newline at end of file
diff --git a/cli/src/test/java/com/devonfw/tools/ide/commandlet/EnvironmentCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/commandlet/EnvironmentCommandletTest.java
index f52639c8c..5dad97f3e 100644
--- a/cli/src/test/java/com/devonfw/tools/ide/commandlet/EnvironmentCommandletTest.java
+++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/EnvironmentCommandletTest.java
@@ -1,103 +1,104 @@
-package com.devonfw.tools.ide.commandlet;
-
-import org.junit.jupiter.api.Test;
-
-import com.devonfw.tools.ide.context.AbstractIdeContextTest;
-import com.devonfw.tools.ide.context.IdeTestContext;
-import com.devonfw.tools.ide.context.IdeTestContextMock;
-import com.devonfw.tools.ide.environment.VariableLine;
-import com.devonfw.tools.ide.log.IdeLogLevel;
-
-/**
- * Test of {@link EnvironmentCommandlet}.
- */
-public class EnvironmentCommandletTest extends AbstractIdeContextTest {
-
- /**
- * Test of {@link EnvironmentCommandlet#normalizeWindowsValue(String)} for Windows.
- */
- @Test
- public void testNormalizeWindowsValue2Windows() {
-
- EnvironmentCommandlet env = new EnvironmentCommandlet(IdeTestContextMock.get());
- assertThat(env.normalizeWindowsValue("")).isEqualTo("");
- assertThat(env.normalizeWindowsValue("*")).isEqualTo("*");
- assertThat(env.normalizeWindowsValue("$:\\\\{garbage}§")).isEqualTo("$:\\\\{garbage}§");
- assertThat(env.normalizeWindowsValue("/c/Windows/system32/drivers/etc/hosts"))
- .isEqualTo("C:\\Windows\\system32\\drivers\\etc\\hosts");
- assertThat(env.normalizeWindowsValue("C:\\Windows\\system32\\drivers\\etc\\hosts"))
- .isEqualTo("C:\\Windows\\system32\\drivers\\etc\\hosts");
- assertThat(env.normalizeWindowsValue("C:\\Users\\login/.ide/scripts/ide"))
- .isEqualTo("C:\\Users\\login\\.ide\\scripts\\ide");
- assertThat(env.normalizeWindowsValue("\\login/.ide/scripts/ide")).isEqualTo("\\login/.ide/scripts/ide");
- }
-
- /**
- * Test of {@link EnvironmentCommandlet#normalizeWindowsValue(String)} for (Git-)Bash.
- */
- @Test
- public void testNormalizeWindowsValue2Bash() {
-
- EnvironmentCommandlet env = new EnvironmentCommandlet(IdeTestContextMock.get());
- env.bash.setValue(true);
- assertThat(env.normalizeWindowsValue("")).isEqualTo("");
- assertThat(env.normalizeWindowsValue("*")).isEqualTo("*");
- assertThat(env.normalizeWindowsValue("$:\\\\{garbage}§")).isEqualTo("$:\\\\{garbage}§");
- assertThat(env.normalizeWindowsValue("C:\\Windows\\system32\\drivers\\etc\\hosts"))
- .isEqualTo("/c/Windows/system32/drivers/etc/hosts");
- assertThat(env.normalizeWindowsValue("/c/Windows/system32/drivers/etc/hosts"))
- .isEqualTo("/c/Windows/system32/drivers/etc/hosts");
- }
-
- /**
- * Test of {@link EnvironmentCommandlet#normalizeWindowsValue(VariableLine)} for Windows.
- */
- @Test
- public void testNormalizeWindowsLine() {
-
- // arrange
- VariableLine line = VariableLine.of(true, "MAGIC_PATH", "/c/Windows/system32/drivers/etc/hosts");
- EnvironmentCommandlet env = new EnvironmentCommandlet(IdeTestContextMock.get());
- // act
- VariableLine normalized = env.normalizeWindowsValue(line);
- // assert
- assertThat(normalized.getValue()).isEqualTo("C:\\Windows\\system32\\drivers\\etc\\hosts");
- assertThat(normalized.isExport()).isTrue();
- assertThat(normalized.getName()).isEqualTo("MAGIC_PATH");
- }
-
- /**
- * Test of {@link EnvironmentCommandlet} run.
- */
- @Test
- public void testRun() {
-
- // arrange
- String path = "workspaces/foo-test/my-git-repo";
- IdeTestContext context = newContext("basic", path, false);
- EnvironmentCommandlet env = context.getCommandletManager().getCommandlet(EnvironmentCommandlet.class);
- // act
- env.run();
- // assert
- assertLogMessage(context, IdeLogLevel.INFO, "MVN_VERSION=3.9.*");
- assertLogMessage(context, IdeLogLevel.INFO, "SOME=some-${UNDEFINED}");
- assertLogMessage(context, IdeLogLevel.INFO, "BAR=bar-some-${UNDEFINED}");
- assertLogMessage(context, IdeLogLevel.INFO, "IDE_TOOLS=mvn,eclipse");
- assertLogMessage(context, IdeLogLevel.INFO, "ECLIPSE_VERSION=2023-03");
- assertLogMessage(context, IdeLogLevel.INFO, "FOO=foo-bar-some-${UNDEFINED}");
- assertLogMessage(context, IdeLogLevel.INFO, "JAVA_VERSION=17*");
- assertLogMessage(context, IdeLogLevel.INFO, "INTELLIJ_EDITION=ultimate");
- assertLogMessage(context, IdeLogLevel.INFO, "DOCKER_EDITION=docker");
- }
- /**
- * Test of {@link EnvironmentCommandlet} does not require home.
- */
- @Test
- public void testThatHomeIsNotReqired() {
-
- // arrange
- EnvironmentCommandlet env = new EnvironmentCommandlet(IdeTestContextMock.get());
- // act & assert
- assertThat(env.isIdeHomeRequired()).isFalse();
- }
-}
+package com.devonfw.tools.ide.commandlet;
+
+import org.junit.jupiter.api.Test;
+
+import com.devonfw.tools.ide.context.AbstractIdeContextTest;
+import com.devonfw.tools.ide.context.IdeTestContext;
+import com.devonfw.tools.ide.context.IdeTestContextMock;
+import com.devonfw.tools.ide.environment.VariableLine;
+import com.devonfw.tools.ide.log.IdeLogLevel;
+
+/**
+ * Test of {@link EnvironmentCommandlet}.
+ */
+public class EnvironmentCommandletTest extends AbstractIdeContextTest {
+
+ /**
+ * Test of {@link EnvironmentCommandlet#normalizeWindowsValue(String)} for Windows.
+ */
+ @Test
+ public void testNormalizeWindowsValue2Windows() {
+
+ EnvironmentCommandlet env = new EnvironmentCommandlet(IdeTestContextMock.get());
+ assertThat(env.normalizeWindowsValue("")).isEqualTo("");
+ assertThat(env.normalizeWindowsValue("*")).isEqualTo("*");
+ assertThat(env.normalizeWindowsValue("$:\\\\{garbage}§")).isEqualTo("$:\\\\{garbage}§");
+ assertThat(env.normalizeWindowsValue("/c/Windows/system32/drivers/etc/hosts"))
+ .isEqualTo("C:\\Windows\\system32\\drivers\\etc\\hosts");
+ assertThat(env.normalizeWindowsValue("C:\\Windows\\system32\\drivers\\etc\\hosts"))
+ .isEqualTo("C:\\Windows\\system32\\drivers\\etc\\hosts");
+ assertThat(env.normalizeWindowsValue("C:\\Users\\login/.ide/scripts/ide"))
+ .isEqualTo("C:\\Users\\login\\.ide\\scripts\\ide");
+ assertThat(env.normalizeWindowsValue("\\login/.ide/scripts/ide")).isEqualTo("\\login/.ide/scripts/ide");
+ }
+
+ /**
+ * Test of {@link EnvironmentCommandlet#normalizeWindowsValue(String)} for (Git-)Bash.
+ */
+ @Test
+ public void testNormalizeWindowsValue2Bash() {
+
+ EnvironmentCommandlet env = new EnvironmentCommandlet(IdeTestContextMock.get());
+ env.bash.setValue(true);
+ assertThat(env.normalizeWindowsValue("")).isEqualTo("");
+ assertThat(env.normalizeWindowsValue("*")).isEqualTo("*");
+ assertThat(env.normalizeWindowsValue("$:\\\\{garbage}§")).isEqualTo("$:\\\\{garbage}§");
+ assertThat(env.normalizeWindowsValue("C:\\Windows\\system32\\drivers\\etc\\hosts"))
+ .isEqualTo("/c/Windows/system32/drivers/etc/hosts");
+ assertThat(env.normalizeWindowsValue("/c/Windows/system32/drivers/etc/hosts"))
+ .isEqualTo("/c/Windows/system32/drivers/etc/hosts");
+ }
+
+ /**
+ * Test of {@link EnvironmentCommandlet#normalizeWindowsValue(VariableLine)} for Windows.
+ */
+ @Test
+ public void testNormalizeWindowsLine() {
+
+ // arrange
+ VariableLine line = VariableLine.of(true, "MAGIC_PATH", "/c/Windows/system32/drivers/etc/hosts");
+ EnvironmentCommandlet env = new EnvironmentCommandlet(IdeTestContextMock.get());
+ // act
+ VariableLine normalized = env.normalizeWindowsValue(line);
+ // assert
+ assertThat(normalized.getValue()).isEqualTo("C:\\Windows\\system32\\drivers\\etc\\hosts");
+ assertThat(normalized.isExport()).isTrue();
+ assertThat(normalized.getName()).isEqualTo("MAGIC_PATH");
+ }
+
+ /**
+ * Test of {@link EnvironmentCommandlet} run.
+ */
+ @Test
+ public void testRun() {
+
+ // arrange
+ String path = "project/workspaces/foo-test/my-git-repo";
+ IdeTestContext context = newContext(PROJECT_BASIC, path, false);
+ EnvironmentCommandlet env = context.getCommandletManager().getCommandlet(EnvironmentCommandlet.class);
+ // act
+ env.run();
+ // assert
+ assertLogMessage(context, IdeLogLevel.INFO, "MVN_VERSION=\"3.9.*\"");
+ assertLogMessage(context, IdeLogLevel.INFO, "SOME=\"some-${UNDEFINED}\"");
+ assertLogMessage(context, IdeLogLevel.INFO, "BAR=\"bar-some-${UNDEFINED}\"");
+ assertLogMessage(context, IdeLogLevel.INFO, "IDE_TOOLS=\"mvn,eclipse\"");
+ assertLogMessage(context, IdeLogLevel.INFO, "ECLIPSE_VERSION=\"2023-03\"");
+ assertLogMessage(context, IdeLogLevel.INFO, "FOO=\"foo-bar-some-${UNDEFINED}\"");
+ assertLogMessage(context, IdeLogLevel.INFO, "JAVA_VERSION=\"17*\"");
+ assertLogMessage(context, IdeLogLevel.INFO, "INTELLIJ_EDITION=\"ultimate\"");
+ assertLogMessage(context, IdeLogLevel.INFO, "DOCKER_EDITION=\"docker\"");
+ }
+
+ /**
+ * Test that {@link EnvironmentCommandlet} requires home.
+ */
+ @Test
+ public void testThatHomeIsRequired() {
+
+ // arrange
+ EnvironmentCommandlet env = new EnvironmentCommandlet(IdeTestContextMock.get());
+ // act & assert
+ assertThat(env.isIdeHomeRequired()).isTrue();
+ }
+}
diff --git a/cli/src/test/java/com/devonfw/tools/ide/commandlet/HelpCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/commandlet/HelpCommandletTest.java
index 0390891d1..3108b6776 100644
--- a/cli/src/test/java/com/devonfw/tools/ide/commandlet/HelpCommandletTest.java
+++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/HelpCommandletTest.java
@@ -1,90 +1,88 @@
-package com.devonfw.tools.ide.commandlet;
-
-import org.junit.jupiter.api.Test;
-
-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.context.IdeTestContextMock;
-import com.devonfw.tools.ide.log.IdeLogLevel;
-
-/**
- * Integration test of {@link HelpCommandlet}.
- */
-public class HelpCommandletTest extends AbstractIdeContextTest {
-
- /**
- * Test of {@link HelpCommandlet} does not require home.
- */
- @Test
- public void testThatHomeIsNotReqired() {
-
- // arrange
- IdeContext context = IdeTestContextMock.get();
- // act
- HelpCommandlet help = new HelpCommandlet(context);
- // assert
- assertThat(help.isIdeHomeRequired()).isFalse();
- }
-
- /**
- * Test of {@link HelpCommandlet} run.
- */
- @Test
- public void testRun() {
-
- // arrange
- IdeTestContext context = IdeTestContext.of();
- HelpCommandlet help = new HelpCommandlet(context);
- // act
- help.run();
- // assert
- assertLogoMessage(context);
- assertLogMessage(context, IdeLogLevel.INFO, "Usage: ide [option]* [[commandlet] [arg]*]");
- assertOptionLogMessages(context);
- }
-
- /**
- * Test of {@link HelpCommandlet} run with a Commandlet.
- */
- @Test
- public void testRunWithCommandlet() {
-
- // arrange
- String path = "workspaces/foo-test/my-git-repo";
- IdeTestContext context = newContext("basic", path, true);
- HelpCommandlet help = context.getCommandletManager().getCommandlet(HelpCommandlet.class);
- help.commandlet.setValueAsString("mvn", context);
- // act
- help.run();
- // assert
- assertLogoMessage(context);
- assertLogMessage(context, IdeLogLevel.INFO, "Usage: ide [option]* mvn [*]");
- assertLogMessage(context, IdeLogLevel.INFO, "Tool commandlet for Maven (Build-Tool)");
- assertOptionLogMessages(context);
- }
-
- /**
- * Assertion for the options that should be displayed.
- */
- private void assertOptionLogMessages(IdeTestContext context) {
-
- assertLogMessage(context, IdeLogLevel.INFO, "--locale the locale (e.g. 'de' for German language)");
- assertLogMessage(context, IdeLogLevel.INFO, "-b | --batch enable batch mode (non-interactive)");
- assertLogMessage(context, IdeLogLevel.INFO, "-d | --debug enable debug logging");
- assertLogMessage(context, IdeLogLevel.INFO, "-f | --force enable force mode");
- assertLogMessage(context, IdeLogLevel.INFO,
- "-o | --offline enable offline mode (skip updates or git pull, fail downloads or git clone)");
- assertLogMessage(context, IdeLogLevel.INFO,
- "-q | --quiet disable info logging (only log success, warning or error)");
- assertLogMessage(context, IdeLogLevel.INFO, "-t | --trace enable trace logging");
- }
-
- /**
- * Assertion for the IDE-Logo that should be displayed.
- */
- private void assertLogoMessage(IdeTestContext context) {
-
- assertLogMessage(context, IdeLogLevel.INFO, HelpCommandlet.LOGO);
- }
-}
+package com.devonfw.tools.ide.commandlet;
+
+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.context.IdeTestContextMock;
+import com.devonfw.tools.ide.log.IdeLogLevel;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Integration test of {@link HelpCommandlet}.
+ */
+public class HelpCommandletTest extends AbstractIdeContextTest {
+
+ /**
+ * Test of {@link HelpCommandlet} does not require home.
+ */
+ @Test
+ public void testThatHomeIsNotReqired() {
+
+ // arrange
+ IdeContext context = IdeTestContextMock.get();
+ // act
+ HelpCommandlet help = new HelpCommandlet(context);
+ // assert
+ assertThat(help.isIdeHomeRequired()).isFalse();
+ }
+
+ /**
+ * Test of {@link HelpCommandlet} run.
+ */
+ @Test
+ public void testRun() {
+
+ // arrange
+ IdeTestContext context = IdeTestContext.of();
+ HelpCommandlet help = new HelpCommandlet(context);
+ // act
+ help.run();
+ // assert
+ assertLogoMessage(context);
+ assertLogMessage(context, IdeLogLevel.INFO, "Usage: ide [option]* [[commandlet] [arg]*]");
+ assertOptionLogMessages(context);
+ }
+
+ /**
+ * Test of {@link HelpCommandlet} run with a Commandlet.
+ */
+ @Test
+ public void testRunWithCommandlet() {
+
+ // arrange
+ IdeTestContext context = newContext(PROJECT_BASIC);
+ HelpCommandlet help = context.getCommandletManager().getCommandlet(HelpCommandlet.class);
+ help.commandlet.setValueAsString("mvn", context);
+ // act
+ help.run();
+ // assert
+ assertLogoMessage(context);
+ assertLogMessage(context, IdeLogLevel.INFO, "Usage: ide [option]* mvn [*]");
+ assertLogMessage(context, IdeLogLevel.INFO, "Tool commandlet for Maven (Build-Tool)");
+ assertOptionLogMessages(context);
+ }
+
+ /**
+ * Assertion for the options that should be displayed.
+ */
+ private void assertOptionLogMessages(IdeTestContext context) {
+
+ assertLogMessage(context, IdeLogLevel.INFO, "--locale the locale (e.g. 'de' for German language)");
+ assertLogMessage(context, IdeLogLevel.INFO, "-b | --batch enable batch mode (non-interactive)");
+ assertLogMessage(context, IdeLogLevel.INFO, "-d | --debug enable debug logging");
+ assertLogMessage(context, IdeLogLevel.INFO, "-f | --force enable force mode");
+ assertLogMessage(context, IdeLogLevel.INFO,
+ "-o | --offline enable offline mode (skip updates or git pull, fail downloads or git clone)");
+ assertLogMessage(context, IdeLogLevel.INFO,
+ "-q | --quiet disable info logging (only log success, warning or error)");
+ assertLogMessage(context, IdeLogLevel.INFO, "-t | --trace enable trace logging");
+ }
+
+ /**
+ * Assertion for the IDE-Logo that should be displayed.
+ */
+ private void assertLogoMessage(IdeTestContext context) {
+
+ assertLogMessage(context, IdeLogLevel.INFO, HelpCommandlet.LOGO);
+ }
+}
diff --git a/cli/src/test/java/com/devonfw/tools/ide/commandlet/InstallCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/commandlet/InstallCommandletTest.java
index ac21f6f86..67562e69f 100644
--- a/cli/src/test/java/com/devonfw/tools/ide/commandlet/InstallCommandletTest.java
+++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/InstallCommandletTest.java
@@ -1,111 +1,108 @@
-package com.devonfw.tools.ide.commandlet;
-
-import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
-import static com.github.tomakehurst.wiremock.client.WireMock.get;
-import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
-
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-
-import org.junit.jupiter.api.AfterAll;
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.Test;
-
-import com.devonfw.tools.ide.context.AbstractIdeContextTest;
-import com.devonfw.tools.ide.context.IdeContext;
-import com.github.tomakehurst.wiremock.WireMockServer;
-import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
-
-/**
- * Integration test of {@link InstallCommandlet}.
- */
-
-public class InstallCommandletTest extends AbstractIdeContextTest {
-
- private static WireMockServer server;
-
- private static Path resourcePath = Path.of("src/test/resources");
-
- @BeforeAll
- static void setUp() throws IOException {
-
- server = new WireMockServer(WireMockConfiguration.wireMockConfig().port(1111));
- server.start();
- }
-
- @AfterAll
- static void tearDown() throws IOException {
-
- server.shutdownServer();
- }
-
- private void mockWebServer() throws IOException {
-
- Path windowsFilePath = resourcePath.resolve("__files").resolve("java-17.0.6-windows-x64.zip");
- String windowsLength = String.valueOf(Files.size(windowsFilePath));
- server.stubFor(
- get(urlPathEqualTo("/installTest/windows")).willReturn(aResponse().withHeader("Content-Type", "application/zip")
- .withHeader("Content-Length", windowsLength).withStatus(200).withBodyFile("java-17.0.6-windows-x64.zip")));
-
- Path linuxFilePath = resourcePath.resolve("__files").resolve("java-17.0.6-linux-x64.tgz");
- String linuxLength = String.valueOf(Files.size(linuxFilePath));
- server.stubFor(
- get(urlPathEqualTo("/installTest/linux")).willReturn(aResponse().withHeader("Content-Type", "application/tgz")
- .withHeader("Content-Length", linuxLength).withStatus(200).withBodyFile("java-17.0.6-linux-x64.tgz")));
-
- server.stubFor(
- get(urlPathEqualTo("/installTest/macOS")).willReturn(aResponse().withHeader("Content-Type", "application/tgz")
- .withHeader("Content-Length", linuxLength).withStatus(200).withBodyFile("java-17.0.6-linux-x64.tgz")));
- }
-
- /**
- * Test of {@link InstallCommandlet} run, when Installed Version is null.
- */
- @Test
- public void testInstallCommandletRunWithVersion() throws IOException {
-
- // arrange
- String path = "workspaces/foo-test/my-git-repo";
- IdeContext context = newContext("basic", path, true);
- InstallCommandlet install = context.getCommandletManager().getCommandlet(InstallCommandlet.class);
- install.tool.setValueAsString("java", context);
- mockWebServer();
- // act
- install.run();
- // assert
- assertTestInstall(context);
- }
-
- /**
- * Test of {@link InstallCommandlet} run, when Installed Version is set.
- */
- @Test
- public void testInstallCommandletRunWithVersionAndVersionIdentifier() throws IOException {
-
- // arrange
- String path = "workspaces/foo-test/my-git-repo";
- IdeContext context = newContext("basic", path, true);
- InstallCommandlet install = context.getCommandletManager().getCommandlet(InstallCommandlet.class);
- install.tool.setValueAsString("java", context);
- install.version.setValueAsString("17.0.6", context);
- mockWebServer();
-
- // act
- install.run();
- // assert
- assertTestInstall(context);
- }
-
- private void assertTestInstall(IdeContext context) {
-
- assertThat(context.getSoftwarePath().resolve("java")).exists();
- assertThat(context.getSoftwarePath().resolve("java/InstallTest.txt")).hasContent("This is a test file.");
- assertThat(context.getSoftwarePath().resolve("java/bin/HelloWorld.txt")).hasContent("Hello World!");
- if (context.getSystemInfo().isWindows()) {
- assertThat(context.getSoftwarePath().resolve("java/bin/java.cmd")).exists();
- } else if (context.getSystemInfo().isLinux() || context.getSystemInfo().isMac()) {
- assertThat(context.getSoftwarePath().resolve("java/bin/java")).exists();
- }
- }
-}
+package com.devonfw.tools.ide.commandlet;
+
+import com.devonfw.tools.ide.context.AbstractIdeContextTest;
+import com.devonfw.tools.ide.context.IdeContext;
+import com.github.tomakehurst.wiremock.WireMockServer;
+import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
+import static com.github.tomakehurst.wiremock.client.WireMock.get;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
+
+/**
+ * Integration test of {@link InstallCommandlet}.
+ */
+
+public class InstallCommandletTest extends AbstractIdeContextTest {
+
+ private static WireMockServer server;
+
+ private static Path resourcePath = Path.of("src/test/resources");
+
+ @BeforeAll
+ static void setUp() throws IOException {
+
+ server = new WireMockServer(WireMockConfiguration.wireMockConfig().port(1111));
+ server.start();
+ }
+
+ @AfterAll
+ static void tearDown() throws IOException {
+
+ server.shutdownServer();
+ }
+
+ private void mockWebServer() throws IOException {
+
+ Path windowsFilePath = resourcePath.resolve("__files").resolve("java-17.0.6-windows-x64.zip");
+ String windowsLength = String.valueOf(Files.size(windowsFilePath));
+ server.stubFor(get(urlPathEqualTo("/installTest/windows")).willReturn(
+ aResponse().withHeader("Content-Type", "application/zip").withHeader("Content-Length", windowsLength)
+ .withStatus(200).withBodyFile("java-17.0.6-windows-x64.zip")));
+
+ Path linuxFilePath = resourcePath.resolve("__files").resolve("java-17.0.6-linux-x64.tgz");
+ String linuxLength = String.valueOf(Files.size(linuxFilePath));
+ server.stubFor(get(urlPathEqualTo("/installTest/linux")).willReturn(
+ aResponse().withHeader("Content-Type", "application/tgz").withHeader("Content-Length", linuxLength)
+ .withStatus(200).withBodyFile("java-17.0.6-linux-x64.tgz")));
+
+ server.stubFor(get(urlPathEqualTo("/installTest/macOS")).willReturn(
+ aResponse().withHeader("Content-Type", "application/tgz").withHeader("Content-Length", linuxLength)
+ .withStatus(200).withBodyFile("java-17.0.6-linux-x64.tgz")));
+ }
+
+ /**
+ * Test of {@link InstallCommandlet} run, when Installed Version is null.
+ */
+ @Test
+ public void testInstallCommandletRunWithVersion() throws IOException {
+
+ // arrange
+ IdeContext context = newContext(PROJECT_BASIC);
+ InstallCommandlet install = context.getCommandletManager().getCommandlet(InstallCommandlet.class);
+ install.tool.setValueAsString("java", context);
+ mockWebServer();
+ // act
+ install.run();
+ // assert
+ assertTestInstall(context);
+ }
+
+ /**
+ * Test of {@link InstallCommandlet} run, when Installed Version is set.
+ */
+ @Test
+ public void testInstallCommandletRunWithVersionAndVersionIdentifier() throws IOException {
+
+ // arrange
+ IdeContext context = newContext(PROJECT_BASIC);
+ InstallCommandlet install = context.getCommandletManager().getCommandlet(InstallCommandlet.class);
+ install.tool.setValueAsString("java", context);
+ install.version.setValueAsString("17.0.6", context);
+ mockWebServer();
+
+ // act
+ install.run();
+ // assert
+ assertTestInstall(context);
+ }
+
+ private void assertTestInstall(IdeContext context) {
+
+ assertThat(context.getSoftwarePath().resolve("java")).exists();
+ assertThat(context.getSoftwarePath().resolve("java/InstallTest.txt")).hasContent("This is a test file.");
+ assertThat(context.getSoftwarePath().resolve("java/bin/HelloWorld.txt")).hasContent("Hello World!");
+ if (context.getSystemInfo().isWindows()) {
+ assertThat(context.getSoftwarePath().resolve("java/bin/java.cmd")).exists();
+ } else if (context.getSystemInfo().isLinux() || context.getSystemInfo().isMac()) {
+ assertThat(context.getSoftwarePath().resolve("java/bin/java")).exists();
+ }
+ }
+}
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
new file mode 100644
index 000000000..65a67649b
--- /dev/null
+++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/RepositoryCommandletTest.java
@@ -0,0 +1,115 @@
+package com.devonfw.tools.ide.commandlet;
+
+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.log.IdeLogLevel;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Properties;
+
+public class RepositoryCommandletTest extends AbstractIdeContextTest {
+
+ IdeTestContext context = newContext(PROJECT_BASIC);
+
+ private static final String PROPERTIES_FILE = "test.properties";
+
+ /**
+ * Properties object used to write key-value pairs to the properties file "test.properties"
+ */
+ Properties properties = new Properties();
+
+ @Test
+ public void testRunWithSpecificRepository() {
+
+ // arrange
+ RepositoryCommandlet rc = context.getCommandletManager().getCommandlet(RepositoryCommandlet.class);
+ createPropertiesFile();
+ rc.repository.setValueAsString(PROPERTIES_FILE, context);
+ // act
+ rc.run();
+ // assert
+ assertLogMessage(context, IdeLogLevel.INFO, "Importing repository from " + PROPERTIES_FILE + " ...");
+ }
+
+ @Test
+ public void testRunWithNoSpecificRepositoryAndInactive() {
+
+ // arrange
+ RepositoryCommandlet rc = context.getCommandletManager().getCommandlet(RepositoryCommandlet.class);
+ createPropertiesFile();
+ // act
+ rc.run();
+ // assert
+ assertLogMessage(context, IdeLogLevel.INFO, "Importing repository from " + PROPERTIES_FILE + " ...");
+ assertLogMessage(context, IdeLogLevel.INFO, "Skipping repository - use force (-f) to setup all repositories ...");
+ }
+
+ @Test
+ public void testRunInvalidConfiguration() {
+
+ // arrange
+ RepositoryCommandlet rc = context.getCommandletManager().getCommandlet(RepositoryCommandlet.class);
+ createPropertiesFile();
+ properties.setProperty("path", "");
+ properties.setProperty("git_url", "");
+ saveProperties(properties);
+ rc.repository.setValueAsString(PROPERTIES_FILE, context);
+ // act
+ rc.run();
+ // assert
+ assertLogMessage(context, IdeLogLevel.WARNING,
+ "Invalid repository configuration " + PROPERTIES_FILE + " - both 'path' and 'git-url' have to be defined.");
+ }
+
+ @Test
+ public void testRunNoRepositoriesOrProjectsFolderFound() {
+
+ // arrange
+ RepositoryCommandlet rc = context.getCommandletManager().getCommandlet(RepositoryCommandlet.class);
+ Path repositoriesPath = this.context.getSettingsPath().resolve(IdeContext.FOLDER_REPOSITORIES);
+ this.context.getFileAccess().delete(repositoriesPath);
+ // act
+ rc.run();
+ // assert
+ assertLogMessage(context, IdeLogLevel.WARNING, "Cannot find repositories folder nor projects folder.");
+ }
+
+ private void createPropertiesFile() {
+
+ try {
+ properties.setProperty("path", "test");
+ properties.setProperty("workingsets", "test");
+ properties.setProperty("workspace", "test");
+ properties.setProperty("git_url", "test");
+ properties.setProperty("git_branch", "test");
+ properties.setProperty("build_path", "test");
+ properties.setProperty("build_cmd", "");
+ 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(properties);
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to create properties file during tests.", e);
+ }
+
+ }
+
+ private void saveProperties(Properties properties) {
+
+ 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);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/cli/src/test/java/com/devonfw/tools/ide/commandlet/VersionGetCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/commandlet/VersionGetCommandletTest.java
index ffd46dc38..9375a14e8 100644
--- a/cli/src/test/java/com/devonfw/tools/ide/commandlet/VersionGetCommandletTest.java
+++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/VersionGetCommandletTest.java
@@ -1,53 +1,50 @@
-package com.devonfw.tools.ide.commandlet;
-
-import org.junit.jupiter.api.Test;
-
-import com.devonfw.tools.ide.cli.CliException;
-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.log.IdeLogLevel;
-
-/**
- * Integration test of {@link VersionGetCommandlet}.
- */
-public class VersionGetCommandletTest extends AbstractIdeContextTest {
-
- /**
- * Test of {@link VersionGetCommandlet} run, when Installed Version is null.
- */
- @Test
- public void testVersionGetCommandletRunThrowsCliException() {
-
- // arrange
- String path = "workspaces/foo-test/my-git-repo";
- IdeContext context = newContext("basic", path, false);
- VersionGetCommandlet versionGet = context.getCommandletManager().getCommandlet(VersionGetCommandlet.class);
- versionGet.tool.setValueAsString("java", context);
- // act
- try {
- versionGet.run();
- failBecauseExceptionWasNotThrown(CliException.class);
- } catch (CliException e) {
- // assert
- assertThat(e).hasMessageContaining("Tool java is not installed!");
- }
- }
-
- /**
- * Test of {@link VersionGetCommandlet} run.
- */
- @Test
- public void testVersionGetCommandletRun() {
-
- // arrange
- String path = "workspaces/foo-test/my-git-repo";
- IdeTestContext context = newContext("basic", path, false);
- VersionGetCommandlet versionGet = context.getCommandletManager().getCommandlet(VersionGetCommandlet.class);
- // act
- versionGet.tool.setValueAsString("mvn", context);
- versionGet.run();
- // assert
- assertLogMessage(context, IdeLogLevel.INFO, "3.9.4");
- }
-}
+package com.devonfw.tools.ide.commandlet;
+
+import com.devonfw.tools.ide.cli.CliException;
+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.log.IdeLogLevel;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Integration test of {@link VersionGetCommandlet}.
+ */
+public class VersionGetCommandletTest extends AbstractIdeContextTest {
+
+ /**
+ * Test of {@link VersionGetCommandlet} run, when Installed Version is null.
+ */
+ @Test
+ public void testVersionGetCommandletRunThrowsCliException() {
+
+ // arrange
+ IdeContext context = newContext(PROJECT_BASIC, null, false);
+ VersionGetCommandlet versionGet = context.getCommandletManager().getCommandlet(VersionGetCommandlet.class);
+ versionGet.tool.setValueAsString("java", context);
+ // act
+ try {
+ versionGet.run();
+ failBecauseExceptionWasNotThrown(CliException.class);
+ } catch (CliException e) {
+ // assert
+ assertThat(e).hasMessageContaining("Tool java is not installed!");
+ }
+ }
+
+ /**
+ * Test of {@link VersionGetCommandlet} run.
+ */
+ @Test
+ public void testVersionGetCommandletRun() {
+
+ // arrange
+ IdeTestContext context = newContext(PROJECT_BASIC, null, false);
+ VersionGetCommandlet versionGet = context.getCommandletManager().getCommandlet(VersionGetCommandlet.class);
+ // act
+ versionGet.tool.setValueAsString("mvn", context);
+ versionGet.run();
+ // assert
+ assertLogMessage(context, IdeLogLevel.INFO, "3.9.4");
+ }
+}
diff --git a/cli/src/test/java/com/devonfw/tools/ide/commandlet/VersionListCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/commandlet/VersionListCommandletTest.java
index 04bcdd4f0..7dd7eea51 100644
--- a/cli/src/test/java/com/devonfw/tools/ide/commandlet/VersionListCommandletTest.java
+++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/VersionListCommandletTest.java
@@ -1,32 +1,30 @@
-package com.devonfw.tools.ide.commandlet;
-
-import org.junit.jupiter.api.Test;
-
-import com.devonfw.tools.ide.context.AbstractIdeContextTest;
-import com.devonfw.tools.ide.context.IdeTestContext;
-import com.devonfw.tools.ide.log.IdeLogLevel;
-
-/**
- * Integration test of {@link VersionListCommandlet}.
- */
-public class VersionListCommandletTest extends AbstractIdeContextTest {
-
- /**
- * Test of {@link VersionListCommandlet} run.
- */
- @Test
- public void testVersionListCommandletRun() {
-
- // arrange
- String path = "workspaces/foo-test/my-git-repo";
- IdeTestContext context = newContext("basic", path, false);
- VersionListCommandlet versionList = context.getCommandletManager().getCommandlet(VersionListCommandlet.class);
- versionList.tool.setValueAsString("mvn", context);
- // act
- versionList.run();
- // assert
- assertLogMessage(context, IdeLogLevel.INFO, "3.0.5");
- assertLogMessage(context, IdeLogLevel.INFO, "3.1.0");
- assertLogMessage(context, IdeLogLevel.INFO, "3.2.1");
- }
+package com.devonfw.tools.ide.commandlet;
+
+import com.devonfw.tools.ide.context.AbstractIdeContextTest;
+import com.devonfw.tools.ide.context.IdeTestContext;
+import com.devonfw.tools.ide.log.IdeLogLevel;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Integration test of {@link VersionListCommandlet}.
+ */
+public class VersionListCommandletTest extends AbstractIdeContextTest {
+
+ /**
+ * Test of {@link VersionListCommandlet} run.
+ */
+ @Test
+ public void testVersionListCommandletRun() {
+
+ // arrange
+ IdeTestContext context = newContext(PROJECT_BASIC, null, false);
+ VersionListCommandlet versionList = context.getCommandletManager().getCommandlet(VersionListCommandlet.class);
+ versionList.tool.setValueAsString("mvn", context);
+ // act
+ versionList.run();
+ // assert
+ assertLogMessage(context, IdeLogLevel.INFO, "3.0.5");
+ assertLogMessage(context, IdeLogLevel.INFO, "3.1.0");
+ assertLogMessage(context, IdeLogLevel.INFO, "3.2.1");
+ }
}
\ No newline at end of file
diff --git a/cli/src/test/java/com/devonfw/tools/ide/commandlet/VersionSetCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/commandlet/VersionSetCommandletTest.java
index fdfd493d6..df2b650fb 100644
--- a/cli/src/test/java/com/devonfw/tools/ide/commandlet/VersionSetCommandletTest.java
+++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/VersionSetCommandletTest.java
@@ -1,58 +1,56 @@
-package com.devonfw.tools.ide.commandlet;
-
-import java.io.IOException;
-import java.nio.file.Path;
-
-import org.junit.jupiter.api.Test;
-
-import com.devonfw.tools.ide.context.AbstractIdeContextTest;
-import com.devonfw.tools.ide.context.IdeContext;
-
-/**
- * Integration test of {@link VersionSetCommandlet}.
- */
-public class VersionSetCommandletTest extends AbstractIdeContextTest {
-
- /**
- * Test of {@link VersionSetCommandlet} run.
- *
- * @throws IOException on error.
- */
- @Test
- public void testVersionSetCommandletRun() throws IOException {
-
- // arrange
- String path = "workspaces/foo-test/my-git-repo";
- IdeContext context = newContext("basic", path, true);
- VersionSetCommandlet versionSet = context.getCommandletManager().getCommandlet(VersionSetCommandlet.class);
- versionSet.tool.setValueAsString("mvn", context);
- versionSet.version.setValueAsString("3.1.0", context);
- // act
- versionSet.run();
- // assert
- Path settingsIdeProperties = context.getSettingsPath().resolve("ide.properties");
- assertThat(settingsIdeProperties).hasContent("""
- #********************************************************************************
- # This file contains project specific environment variables
- #********************************************************************************
-
- JAVA_VERSION=17*
- MVN_VERSION=3.1.0
- ECLIPSE_VERSION=2023-03
- INTELLIJ_EDITION=ultimate
-
- IDE_TOOLS=mvn,eclipse
-
- BAR=bar-${SOME}
-
- TEST_ARGS1=${TEST_ARGS1} settings1
- TEST_ARGS4=${TEST_ARGS4} settings4
- TEST_ARGS5=${TEST_ARGS5} settings5
- TEST_ARGS6=${TEST_ARGS6} settings6
- TEST_ARGS7=${TEST_ARGS7} settings7
- TEST_ARGS8=settings8
- TEST_ARGS9=settings9
- TEST_ARGSb=${TEST_ARGS10} settingsb ${TEST_ARGSa} ${TEST_ARGSb}
- TEST_ARGSc=${TEST_ARGSc} settingsc""");
- }
-}
+package com.devonfw.tools.ide.commandlet;
+
+import com.devonfw.tools.ide.context.AbstractIdeContextTest;
+import com.devonfw.tools.ide.context.IdeContext;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+/**
+ * Integration test of {@link VersionSetCommandlet}.
+ */
+public class VersionSetCommandletTest extends AbstractIdeContextTest {
+
+ /**
+ * Test of {@link VersionSetCommandlet} run.
+ *
+ * @throws IOException on error.
+ */
+ @Test
+ public void testVersionSetCommandletRun() throws IOException {
+
+ // arrange
+ IdeContext context = newContext(PROJECT_BASIC);
+ VersionSetCommandlet versionSet = context.getCommandletManager().getCommandlet(VersionSetCommandlet.class);
+ versionSet.tool.setValueAsString("mvn", context);
+ versionSet.version.setValueAsString("3.1.0", context);
+ // act
+ versionSet.run();
+ // assert
+ Path settingsIdeProperties = context.getSettingsPath().resolve("ide.properties");
+ assertThat(settingsIdeProperties).hasContent("""
+ #********************************************************************************
+ # This file contains project specific environment variables
+ #********************************************************************************
+
+ JAVA_VERSION=17*
+ MVN_VERSION=3.1.0
+ ECLIPSE_VERSION=2023-03
+ INTELLIJ_EDITION=ultimate
+
+ IDE_TOOLS=mvn,eclipse
+
+ BAR=bar-${SOME}
+
+ TEST_ARGS1=${TEST_ARGS1} settings1
+ TEST_ARGS4=${TEST_ARGS4} settings4
+ TEST_ARGS5=${TEST_ARGS5} settings5
+ TEST_ARGS6=${TEST_ARGS6} settings6
+ TEST_ARGS7=${TEST_ARGS7} settings7
+ TEST_ARGS8=settings8
+ TEST_ARGS9=settings9
+ TEST_ARGSb=${TEST_ARGS10} settingsb ${TEST_ARGSa} ${TEST_ARGSb}
+ TEST_ARGSc=${TEST_ARGSc} settingsc""");
+ }
+}
diff --git a/cli/src/test/java/com/devonfw/tools/ide/common/SystemPathTest.java b/cli/src/test/java/com/devonfw/tools/ide/common/SystemPathTest.java
new file mode 100644
index 000000000..0b8656bee
--- /dev/null
+++ b/cli/src/test/java/com/devonfw/tools/ide/common/SystemPathTest.java
@@ -0,0 +1,49 @@
+package com.devonfw.tools.ide.common;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+/**
+ * Unit tests of {@link SystemPath}.
+ */
+public class SystemPathTest {
+
+ @ParameterizedTest
+ @ValueSource(strings = { "C:\\Users\\User\\Documents\\My Pictures\\photo.jpg",
+ "C:\\Windows\\System32\\drivers\\etc.sys", "D:\\Projects\\ProjectA\\source\\main.py" })
+ public void SystemPathShouldRecognizeWindowsPaths(String pathStringToTest) {
+
+ // act
+ boolean testResult = SystemPath.isValidWindowsPath(pathStringToTest);
+ assertThat(testResult).isTrue();
+
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = { "-kill", "none", "--help", "/usr/local/bin/firefox.exe" })
+ public void SystemPathShouldRecognizeNonWindowsPaths(String pathStringToTest) {
+
+ // act
+ boolean testResult = SystemPath.isValidWindowsPath(pathStringToTest);
+ assertThat(testResult).isFalse();
+
+ }
+
+ @Test
+ public void SystemPathShouldConvertWindowsPathToUnixPath() {
+
+ // arrange
+ String windowsPathString = "C:\\Users\\User\\test.exe";
+ String expectedUnixPathString = "/c/Users/User/test.exe";
+
+ // act
+ String resultPath = SystemPath.convertWindowsPathToUnixPath(windowsPathString);
+
+ // assert
+ assertThat(resultPath).isEqualTo(expectedUnixPathString);
+ }
+
+}
diff --git a/cli/src/test/java/com/devonfw/tools/ide/completion/IdeCompleterTest.java b/cli/src/test/java/com/devonfw/tools/ide/completion/IdeCompleterTest.java
index e2c0a8e23..8f8051b4b 100644
--- a/cli/src/test/java/com/devonfw/tools/ide/completion/IdeCompleterTest.java
+++ b/cli/src/test/java/com/devonfw/tools/ide/completion/IdeCompleterTest.java
@@ -1,138 +1,137 @@
-package com.devonfw.tools.ide.completion;
-
-import java.nio.file.Path;
-
-import org.junit.jupiter.api.Test;
-
-import com.devonfw.tools.ide.cli.AutocompletionReaderTestSupport;
-import com.devonfw.tools.ide.context.IdeTestContext;
-
-/**
- * Integration test of {@link IdeCompleter}.
- */
-public class IdeCompleterTest extends AutocompletionReaderTestSupport {
-
- /**
- * Test of 1st level auto-completion (commandlet name). As suggestions are sorted alphabetically "helm" will be the
- * first match.
- */
- @Test
- public void testIdeCompleterHelp() {
-
- this.reader.setCompleter(newCompleter());
- assertBuffer("helm", new TestBuffer("he").tab().tab());
- }
-
- /**
- * Test of 1st level auto-completion (commandlet name). Here we test the special case of the
- * {@link com.devonfw.tools.ide.commandlet.VersionCommandlet} that has a long-option style.
- */
- @Test
- public void testIdeCompleterVersion() {
-
- this.reader.setCompleter(newCompleter());
- assertBuffer("--version ", new TestBuffer("--vers").tab());
- }
-
- /**
- * Test of 2nd level auto-completion with tool property of {@link com.devonfw.tools.ide.commandlet.InstallCommandlet}.
- */
- @Test
- public void testIdeCompleterInstall() {
-
- this.reader.setCompleter(newCompleter());
- assertBuffer("install mvn ", new TestBuffer("install m").tab());
- }
-
- /**
- * Test of 2nd level auto-completion with commandlet property of
- * {@link com.devonfw.tools.ide.commandlet.HelpCommandlet}.
- */
- @Test
- public void testIdeCompleterHelpWithToolCompletion() {
-
- this.reader.setCompleter(newCompleter());
- assertBuffer("help mvn ", new TestBuffer("help m").tab().tab());
- }
-
- /**
- * Test of second option completion that is already present as short-option.
- */
- @Test
- public void testIdeCompleterDuplicatedOptions() {
-
- this.reader.setCompleter(newCompleter());
- assertBuffer("-t --t", new TestBuffer("-t --t").tab());
-
- }
-
- /**
- * Test of 3rd level completion using version property of {@link com.devonfw.tools.ide.commandlet.InstallCommandlet}
- * contextual to the specified tool. The version "3.2.1" is the latest one from the mocked "basic" project configured
- * for the tool "mvn".
- */
- @Test
- public void testIdeCompleterThirdLayerVersions() {
-
- String path = "workspaces/foo-test/my-git-repo";
- IdeTestContext ideContext = newContext("basic", path, false);
- this.reader.setCompleter(new IdeCompleter(ideContext));
- assertBuffer("install mvn 3.2.1", new TestBuffer("install mvn ").tab().tab());
- }
-
- /**
- * Test that 2nd level completion of undefined commandlet has no effect.
- */
- @Test
- public void testIdeCompleterNonExistentCommand() {
-
- this.reader.setCompleter(newCompleter());
- assertBuffer("cd ", new TestBuffer("cd ").tab().tab().tab());
-
- }
-
- /**
- * Test that no options are completed on 2nd level for {@link com.devonfw.tools.ide.commandlet.VersionGetCommandlet}
- * that has no options.
- */
- @Test
- public void testIdeCompleterPreventsOptionsAfterCommandWithMinus() {
-
- this.reader.setCompleter(newCompleter());
- assertBuffer("get-version -", new TestBuffer("get-version -").tab().tab());
- assertBuffer("get-version - ", new TestBuffer("get-version - ").tab().tab());
-
- }
-
- /**
- * Test that completion with invalid options does not trigger suggestions.
- */
- @Test
- public void testIdeCompleterWithInvalidInputDoesNothing() {
-
- this.reader.setCompleter(newCompleter());
- assertBuffer("get-version -t ", new TestBuffer("get-version -t ").tab().tab());
- assertBuffer("- get-version ", new TestBuffer("- get-version ").tab().tab());
- assertBuffer(" - get-version", new TestBuffer(" - get-version").tab().tab());
- }
-
- /**
- * Test of 2nd level completion of tool property for {@link com.devonfw.tools.ide.commandlet.VersionGetCommandlet}.
- */
- @Test
- public void testIdeCompleterHandlesOptionsBeforeCommand() {
-
- this.reader.setCompleter(newCompleter());
- assertBuffer("get-version mvn ", new TestBuffer("get-version mv").tab().tab());
- }
-
- private IdeCompleter newCompleter() {
-
- return new IdeCompleter(newTestContext());
- }
-
- private IdeTestContext newTestContext() {
-
- return new IdeTestContext(Path.of(""), "");
- }
-}
+package com.devonfw.tools.ide.completion;
+
+import com.devonfw.tools.ide.cli.AutocompletionReaderTestSupport;
+import com.devonfw.tools.ide.context.IdeTestContext;
+import org.junit.jupiter.api.Test;
+
+import java.nio.file.Path;
+
+/**
+ * Integration test of {@link IdeCompleter}.
+ */
+public class IdeCompleterTest extends AutocompletionReaderTestSupport {
+
+ /**
+ * Test of 1st level auto-completion (commandlet name). As suggestions are sorted alphabetically "helm" will be the
+ * first match.
+ */
+ @Test
+ public void testIdeCompleterHelp() {
+
+ this.reader.setCompleter(newCompleter());
+ assertBuffer("helm", new TestBuffer("he").tab().tab());
+ }
+
+ /**
+ * Test of 1st level auto-completion (commandlet name). Here we test the special case of the
+ * {@link com.devonfw.tools.ide.commandlet.VersionCommandlet} that has a long-option style.
+ */
+ @Test
+ public void testIdeCompleterVersion() {
+
+ this.reader.setCompleter(newCompleter());
+ assertBuffer("--version ", new TestBuffer("--vers").tab());
+ }
+
+ /**
+ * Test of 2nd level auto-completion with tool property of
+ * {@link com.devonfw.tools.ide.commandlet.InstallCommandlet}.
+ */
+ @Test
+ public void testIdeCompleterInstall() {
+
+ this.reader.setCompleter(newCompleter());
+ assertBuffer("install mvn ", new TestBuffer("install m").tab());
+ }
+
+ /**
+ * Test of 2nd level auto-completion with commandlet property of
+ * {@link com.devonfw.tools.ide.commandlet.HelpCommandlet}.
+ */
+ @Test
+ public void testIdeCompleterHelpWithToolCompletion() {
+
+ this.reader.setCompleter(newCompleter());
+ assertBuffer("help mvn ", new TestBuffer("help m").tab().tab());
+ }
+
+ /**
+ * Test of second option completion that is already present as short-option.
+ */
+ @Test
+ public void testIdeCompleterDuplicatedOptions() {
+
+ this.reader.setCompleter(newCompleter());
+ assertBuffer("-t --t", new TestBuffer("-t --t").tab());
+
+ }
+
+ /**
+ * Test of 3rd level completion using version property of {@link com.devonfw.tools.ide.commandlet.InstallCommandlet}
+ * contextual to the specified tool. The version "3.2.1" is the latest one from the mocked "basic" project configured
+ * for the tool "mvn".
+ */
+ @Test
+ public void testIdeCompleterThirdLayerVersions() {
+
+ IdeTestContext ideContext = newContext(PROJECT_BASIC, null, false);
+ this.reader.setCompleter(new IdeCompleter(ideContext));
+ assertBuffer("install mvn 3.2.1", new TestBuffer("install mvn ").tab().tab());
+ }
+
+ /**
+ * Test that 2nd level completion of undefined commandlet has no effect.
+ */
+ @Test
+ public void testIdeCompleterNonExistentCommand() {
+
+ this.reader.setCompleter(newCompleter());
+ assertBuffer("cd ", new TestBuffer("cd ").tab().tab().tab());
+
+ }
+
+ /**
+ * Test that no options are completed on 2nd level for {@link com.devonfw.tools.ide.commandlet.VersionGetCommandlet}
+ * that has no options.
+ */
+ @Test
+ public void testIdeCompleterPreventsOptionsAfterCommandWithMinus() {
+
+ this.reader.setCompleter(newCompleter());
+ assertBuffer("get-version -", new TestBuffer("get-version -").tab().tab());
+ assertBuffer("get-version - ", new TestBuffer("get-version - ").tab().tab());
+
+ }
+
+ /**
+ * Test that completion with invalid options does not trigger suggestions.
+ */
+ @Test
+ public void testIdeCompleterWithInvalidInputDoesNothing() {
+
+ this.reader.setCompleter(newCompleter());
+ assertBuffer("get-version -t ", new TestBuffer("get-version -t ").tab().tab());
+ assertBuffer("- get-version ", new TestBuffer("- get-version ").tab().tab());
+ assertBuffer(" - get-version", new TestBuffer(" - get-version").tab().tab());
+ }
+
+ /**
+ * Test of 2nd level completion of tool property for {@link com.devonfw.tools.ide.commandlet.VersionGetCommandlet}.
+ */
+ @Test
+ public void testIdeCompleterHandlesOptionsBeforeCommand() {
+
+ this.reader.setCompleter(newCompleter());
+ assertBuffer("get-version mvn ", new TestBuffer("get-version mv").tab().tab());
+ }
+
+ private IdeCompleter newCompleter() {
+
+ return new IdeCompleter(newTestContext());
+ }
+
+ private IdeTestContext newTestContext() {
+
+ return new IdeTestContext(Path.of(""), "");
+ }
+}
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 12922e97b..9318c6308 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
@@ -1,18 +1,19 @@
package com.devonfw.tools.ide.context;
-import java.nio.file.Path;
-import java.util.List;
-
-import org.assertj.core.api.Assertions;
-import org.assertj.core.api.Condition;
-import org.assertj.core.api.ListAssert;
-
import com.devonfw.tools.ide.io.FileAccess;
import com.devonfw.tools.ide.io.FileAccessImpl;
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.log.IdeTestLogger;
+import com.devonfw.tools.ide.repo.ToolRepositoryMock;
+import org.assertj.core.api.Assertions;
+import org.assertj.core.api.Condition;
+import org.assertj.core.api.ListAssert;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
/**
* Abstract base class for tests that need mocked instances of {@link IdeContext}.
@@ -22,64 +23,74 @@ public abstract class AbstractIdeContextTest extends Assertions {
/** {@link #newContext(String) Name of test project} {@value}. */
protected static final String PROJECT_BASIC = "basic";
+ /** Test- */
+ protected static final Path TEST_RESOURCES = Path.of("src/test/resources");
+
/** The source {@link Path} to the test projects. */
- protected static final Path PATH_PROJECTS = Path.of("src/test/resources/ide-projects");
+ protected static final Path TEST_PROJECTS = TEST_RESOURCES.resolve("ide-projects");
// will not use eclipse-target like done in maven via eclipse profile...
- private static final Path PATH_PROJECTS_COPY = Path.of("target/test-projects/");
+ private static final Path TEST_PROJECTS_COPY = Path.of("target/test-projects");
/** Chunk size to use for progress bars **/
private static final int CHUNK_SIZE = 1024;
/**
- * @param projectTestCaseName the (folder)name of the project test case, in this folder a 'project' folder represents
- * the test project in {@link #PATH_PROJECTS}. E.g. "basic".
+ * @param testProject the (folder)name of the project test case, in this folder a 'project' folder represents the test
+ * project in {@link #TEST_PROJECTS}. E.g. "basic".
* @return the {@link IdeTestContext} pointing to that project.
*/
- protected IdeTestContext newContext(String projectTestCaseName) {
+ protected IdeTestContext newContext(String testProject) {
- return newContext(projectTestCaseName, null, true);
+ return newContext(testProject, null, true);
}
/**
- * @param projectTestCaseName the (folder)name of the project test case, in this folder a 'project' folder represents
- * the test project in {@link #PATH_PROJECTS}. E.g. "basic".
+ * @param testProject the (folder)name of the project test case, in this folder a 'project' folder represents the test
+ * project in {@link #TEST_PROJECTS}. E.g. "basic".
* @param projectPath the relative path inside the test project where to create the context.
* @return the {@link IdeTestContext} pointing to that project.
*/
- protected static IdeTestContext newContext(String projectTestCaseName, String projectPath) {
+ protected static IdeTestContext newContext(String testProject, String projectPath) {
- return newContext(projectTestCaseName, projectPath, true);
+ return newContext(testProject, projectPath, true);
}
/**
- * @param projectTestCaseName the (folder)name of the project test case, in this folder a 'project' folder represents
- * the test project in {@link #PATH_PROJECTS}. E.g. "basic".
+ * @param testProject the (folder)name of the project test case, in this folder a 'project' folder represents the test
+ * project in {@link #TEST_PROJECTS}. E.g. "basic".
* @param projectPath the relative path inside the test project where to create the context.
* @param copyForMutation - {@code true} to create a copy of the project that can be modified by the test,
- * {@code false} otherwise (only to save resources if you are 100% sure that your test never modifies anything
- * in that project.)
+ * {@code false} otherwise (only to save resources if you are 100% sure that your test never modifies anything in that
+ * project.)
* @param answers the answers to use for the {@link IdeTestContext}.
* @return the {@link IdeTestContext} pointing to that project.
*/
- protected static IdeTestContext newContext(String projectTestCaseName, String projectPath, boolean copyForMutation,
- String... answers) {
+ protected static IdeTestContext newContext(String testProject, String projectPath, boolean copyForMutation) {
- Path sourceDir = PATH_PROJECTS.resolve(projectTestCaseName);
- Path userDir = sourceDir.resolve("project");
- IdeTestContext context;
+ Path ideRoot = TEST_PROJECTS.resolve(testProject);
if (copyForMutation) {
- Path projectDir = PATH_PROJECTS_COPY.resolve(projectTestCaseName);
+ Path ideRootCopy = TEST_PROJECTS_COPY.resolve(testProject);
FileAccess fileAccess = new FileAccessImpl(IdeTestContextMock.get());
- fileAccess.delete(projectDir);
- fileAccess.mkdirs(PATH_PROJECTS_COPY);
- fileAccess.copy(sourceDir, projectDir, FileCopyMode.COPY_TREE_OVERRIDE_TREE);
- userDir = projectDir.resolve("project");
+ fileAccess.delete(ideRootCopy);
+ fileAccess.mkdirs(TEST_PROJECTS_COPY);
+ fileAccess.copy(ideRoot, ideRootCopy, FileCopyMode.COPY_TREE_OVERRIDE_TREE);
+ ideRoot = ideRootCopy;
+ }
+ if (projectPath == null) {
+ projectPath = "project";
+ }
+ ToolRepositoryMock mock = null;
+ Path ideHome = ideRoot.resolve(projectPath);
+ ToolRepositoryMock toolRepository = null;
+ Path repositoryFolder = ideRoot.resolve("repository");
+ if (Files.isDirectory(repositoryFolder)) {
+ toolRepository = new ToolRepositoryMock(repositoryFolder);
}
- if (projectPath != null) {
- userDir = userDir.resolve(projectPath);
+ IdeTestContext context = new IdeTestContext(ideHome, toolRepository);
+ if (toolRepository != null) {
+ toolRepository.setContext(context);
}
- context = new IdeTestContext(userDir, answers);
return context;
}
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 9810990e9..0cfa42719 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
@@ -1,14 +1,17 @@
package com.devonfw.tools.ide.context;
-import java.nio.file.Path;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.function.Function;
-
import com.devonfw.tools.ide.io.IdeProgressBar;
import com.devonfw.tools.ide.io.IdeProgressBarTestImpl;
import com.devonfw.tools.ide.log.IdeLogLevel;
import com.devonfw.tools.ide.log.IdeSubLogger;
+import com.devonfw.tools.ide.os.SystemInfo;
+import com.devonfw.tools.ide.repo.DefaultToolRepository;
+import com.devonfw.tools.ide.repo.ToolRepository;
+
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
/**
* Implementation of {@link IdeContext} for testing.
@@ -21,18 +24,24 @@ public class AbstractIdeTestContext extends AbstractIdeContext {
private final Map progressBarMap;
+ private SystemInfo systemInfo;
+
/**
* The constructor.
*
* @param factory the {@link Function} to create {@link IdeSubLogger} per {@link IdeLogLevel}.
* @param userDir the optional {@link Path} to current working directory.
+ * @param toolRepository @param toolRepository the {@link ToolRepository} of the context. If it is set to {@code null}
+ * {@link DefaultToolRepository} will be used.
* @param answers the automatic answers simulating a user in test.
*/
- public AbstractIdeTestContext(Function factory, Path userDir, String... answers) {
+ public AbstractIdeTestContext(Function factory, Path userDir,
+ ToolRepository toolRepository, String... answers) {
- super(IdeLogLevel.TRACE, factory, userDir);
+ super(IdeLogLevel.TRACE, factory, userDir, toolRepository);
this.answers = answers;
this.progressBarMap = new HashMap<>();
+ this.systemInfo = super.getSystemInfo();
}
@Override
@@ -70,4 +79,18 @@ public IdeProgressBar prepareProgressBar(String taskName, long size) {
return progressBar;
}
+ @Override
+ public SystemInfo getSystemInfo() {
+
+ return this.systemInfo;
+ }
+
+ /**
+ * @param systemInfo the {@link SystemInfo} to use for testing.
+ * @see com.devonfw.tools.ide.os.SystemInfoMock
+ */
+ public void setSystemInfo(SystemInfo systemInfo) {
+
+ this.systemInfo = systemInfo;
+ }
}
diff --git a/cli/src/test/java/com/devonfw/tools/ide/context/GitContextMock.java b/cli/src/test/java/com/devonfw/tools/ide/context/GitContextMock.java
index f90fc6f49..7cb12d19d 100644
--- a/cli/src/test/java/com/devonfw/tools/ide/context/GitContextMock.java
+++ b/cli/src/test/java/com/devonfw/tools/ide/context/GitContextMock.java
@@ -7,12 +7,12 @@
public class GitContextMock implements GitContext {
@Override
- public void pullOrCloneIfNeeded(String repoUrl, Path targetRepository) {
+ public void pullOrCloneIfNeeded(String repoUrl, String branch, Path targetRepository) {
}
@Override
- public void pullOrFetchAndResetIfNeeded(String repoUrl, Path targetRepository, String remoteName, String branchName) {
+ public void pullOrFetchAndResetIfNeeded(String repoUrl, String branch, Path targetRepository, String remoteName) {
}
@@ -21,6 +21,11 @@ public void pullOrClone(String gitRepoUrl, Path targetRepository) {
}
+ @Override
+ public void pullOrClone(String gitRepoUrl, String branch, Path targetRepository) {
+
+ }
+
@Override
public void clone(GitUrl gitRepoUrl, Path targetRepository) {
diff --git a/cli/src/test/java/com/devonfw/tools/ide/context/GitContextProcessContextMock.java b/cli/src/test/java/com/devonfw/tools/ide/context/GitContextProcessContextMock.java
index f79a2f6bb..eb257edff 100644
--- a/cli/src/test/java/com/devonfw/tools/ide/context/GitContextProcessContextMock.java
+++ b/cli/src/test/java/com/devonfw/tools/ide/context/GitContextProcessContextMock.java
@@ -10,6 +10,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.process.ProcessResult;
import com.devonfw.tools.ide.process.ProcessResultImpl;
@@ -75,7 +76,7 @@ public ProcessContext withEnvVar(String key, String value) {
}
@Override
- public ProcessResult run(boolean capture, boolean isBackgroundProcess) {
+ public ProcessResult run(ProcessMode processMode) {
Path gitFolderPath = this.directory.resolve(".git");
// deletes a newly added folder
diff --git a/cli/src/test/java/com/devonfw/tools/ide/context/GitContextTest.java b/cli/src/test/java/com/devonfw/tools/ide/context/GitContextTest.java
index 4d773fd3d..527466dbc 100644
--- a/cli/src/test/java/com/devonfw/tools/ide/context/GitContextTest.java
+++ b/cli/src/test/java/com/devonfw/tools/ide/context/GitContextTest.java
@@ -33,7 +33,7 @@ public void testRunGitCloneInOfflineModeThrowsException(@TempDir Path tempDir) {
GitContext gitContext = new GitContextImpl(context);
// act
CliException e1 = assertThrows(CliException.class, () -> {
- gitContext.pullOrClone(gitRepoUrl, tempDir);
+ gitContext.pullOrClone(gitRepoUrl, "", tempDir);
});
// assert
assertThat(e1).hasMessageContaining(gitRepoUrl).hasMessageContaining(tempDir.toString())
@@ -115,7 +115,7 @@ public void testRunGitPullWithForceStartsReset(@TempDir Path tempDir) {
IdeContext context = newGitContext(tempDir, errors, outs, 0, true);
GitContext gitContext = new GitContextImpl(context);
// act
- gitContext.pullOrFetchAndResetIfNeeded(gitRepoUrl, tempDir, "origin", "master");
+ gitContext.pullOrFetchAndResetIfNeeded(gitRepoUrl, "master", tempDir, "origin");
// assert
assertThat(modifiedFile).hasContent("original");
}
@@ -143,7 +143,7 @@ public void testRunGitPullWithForceStartsCleanup(@TempDir Path tempDir) {
throw new RuntimeException(e);
}
// act
- gitContext.pullOrFetchAndResetIfNeeded(gitRepoUrl, tempDir, "origin", "master");
+ gitContext.pullOrFetchAndResetIfNeeded(gitRepoUrl, "master", tempDir, "origin");
// assert
assertThat(tempDir.resolve("new-folder")).doesNotExist();
}
diff --git a/cli/src/test/java/com/devonfw/tools/ide/context/GitContextTestContext.java b/cli/src/test/java/com/devonfw/tools/ide/context/GitContextTestContext.java
index 5a0eafb57..f214d5007 100644
--- a/cli/src/test/java/com/devonfw/tools/ide/context/GitContextTestContext.java
+++ b/cli/src/test/java/com/devonfw/tools/ide/context/GitContextTestContext.java
@@ -33,7 +33,7 @@ public class GitContextTestContext extends AbstractIdeTestContext {
*/
public GitContextTestContext(boolean isOnline, Path userDir, String... answers) {
- super(level -> new IdeTestLogger(level), userDir, answers);
+ super(level -> new IdeTestLogger(level), userDir, null, answers);
testOnlineMode = isOnline;
this.errors = new ArrayList<>();
this.outs = new ArrayList<>();
diff --git a/cli/src/test/java/com/devonfw/tools/ide/context/IdeContextTest.java b/cli/src/test/java/com/devonfw/tools/ide/context/IdeContextTest.java
index dc0d443a7..10dd880be 100644
--- a/cli/src/test/java/com/devonfw/tools/ide/context/IdeContextTest.java
+++ b/cli/src/test/java/com/devonfw/tools/ide/context/IdeContextTest.java
@@ -1,73 +1,72 @@
-package com.devonfw.tools.ide.context;
-
-import java.nio.file.Path;
-
-import org.junit.jupiter.api.Test;
-
-import com.devonfw.tools.ide.common.SystemPath;
-import com.devonfw.tools.ide.environment.EnvironmentVariables;
-import com.devonfw.tools.ide.environment.EnvironmentVariablesType;
-import com.devonfw.tools.ide.log.IdeLogLevel;
-import com.devonfw.tools.ide.variable.IdeVariables;
-
-/**
- * Integration test of {@link IdeContext}.
- */
-public class IdeContextTest extends AbstractIdeContextTest {
-
- /**
- * Test of {@link IdeContext} initialization from basic project.
- */
- @Test
- public void testBasicProjectEnvironment() {
-
- // arrange
- String path = "workspaces/foo-test/my-git-repo";
- // act
- IdeTestContext context = newContext(PROJECT_BASIC, path, false);
- // assert
- assertThat(context.getWorkspaceName()).isEqualTo("foo-test");
- assertThat(IdeVariables.DOCKER_EDITION.get(context)).isEqualTo("docker");
- EnvironmentVariables variables = context.getVariables();
- assertThat(variables.get("FOO")).isEqualTo("foo-bar-some-${UNDEFINED}");
- assertLogMessage(context, IdeLogLevel.WARNING,
- "Undefined variable UNDEFINED in 'SOME=some-${UNDEFINED}' for root 'FOO=foo-${BAR}'");
- assertThat(context.getIdeHome().resolve("readme")).hasContent("this is the IDE_HOME directory");
- assertThat(context.getIdeRoot().resolve("readme")).hasContent("this is the IDE_ROOT directory");
- assertThat(context.getUserHome().resolve("readme")).hasContent("this is the users HOME directory");
- assertThat(variables.getPath("M2_REPO")).isEqualTo(context.getUserHome().resolve(".m2/repository"));
- assertThat(context.getDownloadPath().resolve("readme")).hasContent("this is the download cache");
- assertThat(context.getUrlsPath().resolve("readme")).hasContent("this is the download metadata");
- assertThat(context.getToolRepositoryPath().resolve("readme")).hasContent("this is the tool repository");
- assertThat(context.getWorkspacePath().resolve("readme")).hasContent("this is the foo-test workspace of basic");
- SystemPath systemPath = IdeVariables.PATH.get(context);
- assertThat(systemPath).isSameAs(context.getPath());
- String envPath = System.getenv(IdeVariables.PATH.getName());
- assertThat(systemPath.toString()).isNotEqualTo(envPath).endsWith(envPath);
- Path softwarePath = context.getSoftwarePath();
- Path javaBin = softwarePath.resolve("java/bin");
- assertThat(systemPath.getPath("java")).isEqualTo(javaBin);
- Path mvnBin = softwarePath.resolve("mvn/bin");
- assertThat(systemPath.getPath("mvn")).isEqualTo(mvnBin);
- assertThat(systemPath.toString()).contains(javaBin.toString(), mvnBin.toString());
- assertThat(variables.getType()).isSameAs(EnvironmentVariablesType.RESOLVED);
- assertThat(variables.getByType(EnvironmentVariablesType.RESOLVED)).isSameAs(variables);
- EnvironmentVariables v1 = variables.getParent();
- assertThat(v1.getType()).isSameAs(EnvironmentVariablesType.CONF);
- assertThat(variables.getByType(EnvironmentVariablesType.CONF)).isSameAs(v1);
- EnvironmentVariables v2 = v1.getParent();
- assertThat(v2.getType()).isSameAs(EnvironmentVariablesType.WORKSPACE);
- assertThat(variables.getByType(EnvironmentVariablesType.WORKSPACE)).isSameAs(v2);
- EnvironmentVariables v3 = v2.getParent();
- assertThat(v3.getType()).isSameAs(EnvironmentVariablesType.SETTINGS);
- assertThat(variables.getByType(EnvironmentVariablesType.SETTINGS)).isSameAs(v3);
- EnvironmentVariables v4 = v3.getParent();
- assertThat(v4.getType()).isSameAs(EnvironmentVariablesType.USER);
- assertThat(variables.getByType(EnvironmentVariablesType.USER)).isSameAs(v4);
- EnvironmentVariables v5 = v4.getParent();
- assertThat(v5.getType()).isSameAs(EnvironmentVariablesType.SYSTEM);
- assertThat(variables.getByType(EnvironmentVariablesType.SYSTEM)).isSameAs(v5);
- assertThat(v5.getParent()).isNull();
- }
-
-}
+package com.devonfw.tools.ide.context;
+
+import com.devonfw.tools.ide.common.SystemPath;
+import com.devonfw.tools.ide.environment.EnvironmentVariables;
+import com.devonfw.tools.ide.environment.EnvironmentVariablesType;
+import com.devonfw.tools.ide.log.IdeLogLevel;
+import com.devonfw.tools.ide.variable.IdeVariables;
+import org.junit.jupiter.api.Test;
+
+import java.nio.file.Path;
+
+/**
+ * Integration test of {@link IdeContext}.
+ */
+public class IdeContextTest extends AbstractIdeContextTest {
+
+ /**
+ * Test of {@link IdeContext} initialization from basic project.
+ */
+ @Test
+ public void testBasicProjectEnvironment() {
+
+ // arrange
+ String path = "project/workspaces/foo-test/my-git-repo";
+ // act
+ IdeTestContext context = newContext(PROJECT_BASIC, path, false);
+ // assert
+ assertThat(context.getWorkspaceName()).isEqualTo("foo-test");
+ assertThat(IdeVariables.DOCKER_EDITION.get(context)).isEqualTo("docker");
+ EnvironmentVariables variables = context.getVariables();
+ assertThat(variables.get("FOO")).isEqualTo("foo-bar-some-${UNDEFINED}");
+ assertLogMessage(context, IdeLogLevel.WARNING,
+ "Undefined variable UNDEFINED in 'SOME=some-${UNDEFINED}' for root 'FOO=foo-${BAR}'");
+ assertThat(context.getIdeHome().resolve("readme")).hasContent("this is the IDE_HOME directory");
+ assertThat(context.getIdeRoot().resolve("readme")).hasContent("this is the IDE_ROOT directory");
+ assertThat(context.getUserHome().resolve("readme")).hasContent("this is the users HOME directory");
+ assertThat(variables.getPath("M2_REPO")).isEqualTo(context.getUserHome().resolve(".m2/repository"));
+ assertThat(context.getDownloadPath().resolve("readme")).hasContent("this is the download cache");
+ assertThat(context.getUrlsPath().resolve("readme")).hasContent("this is the download metadata");
+ assertThat(context.getToolRepositoryPath().resolve("readme")).hasContent("this is the tool repository");
+ assertThat(context.getWorkspacePath().resolve("readme")).hasContent("this is the foo-test workspace of basic");
+ SystemPath systemPath = IdeVariables.PATH.get(context);
+ assertThat(systemPath).isSameAs(context.getPath());
+ String envPath = System.getenv(IdeVariables.PATH.getName());
+ assertThat(systemPath.toString()).isNotEqualTo(envPath).endsWith(envPath);
+ Path softwarePath = context.getSoftwarePath();
+ Path javaBin = softwarePath.resolve("java/bin");
+ assertThat(systemPath.getPath("java")).isEqualTo(javaBin);
+ Path mvnBin = softwarePath.resolve("mvn/bin");
+ assertThat(systemPath.getPath("mvn")).isEqualTo(mvnBin);
+ assertThat(systemPath.toString()).contains(javaBin.toString(), mvnBin.toString());
+ assertThat(variables.getType()).isSameAs(EnvironmentVariablesType.RESOLVED);
+ assertThat(variables.getByType(EnvironmentVariablesType.RESOLVED)).isSameAs(variables);
+ EnvironmentVariables v1 = variables.getParent();
+ assertThat(v1.getType()).isSameAs(EnvironmentVariablesType.CONF);
+ assertThat(variables.getByType(EnvironmentVariablesType.CONF)).isSameAs(v1);
+ EnvironmentVariables v2 = v1.getParent();
+ assertThat(v2.getType()).isSameAs(EnvironmentVariablesType.WORKSPACE);
+ assertThat(variables.getByType(EnvironmentVariablesType.WORKSPACE)).isSameAs(v2);
+ EnvironmentVariables v3 = v2.getParent();
+ assertThat(v3.getType()).isSameAs(EnvironmentVariablesType.SETTINGS);
+ assertThat(variables.getByType(EnvironmentVariablesType.SETTINGS)).isSameAs(v3);
+ EnvironmentVariables v4 = v3.getParent();
+ assertThat(v4.getType()).isSameAs(EnvironmentVariablesType.USER);
+ assertThat(variables.getByType(EnvironmentVariablesType.USER)).isSameAs(v4);
+ EnvironmentVariables v5 = v4.getParent();
+ assertThat(v5.getType()).isSameAs(EnvironmentVariablesType.SYSTEM);
+ assertThat(variables.getByType(EnvironmentVariablesType.SYSTEM)).isSameAs(v5);
+ assertThat(v5.getParent()).isNull();
+ }
+
+}
diff --git a/cli/src/test/java/com/devonfw/tools/ide/context/IdeSlf4jContext.java b/cli/src/test/java/com/devonfw/tools/ide/context/IdeSlf4jContext.java
index 94313c290..f3e515d74 100644
--- a/cli/src/test/java/com/devonfw/tools/ide/context/IdeSlf4jContext.java
+++ b/cli/src/test/java/com/devonfw/tools/ide/context/IdeSlf4jContext.java
@@ -17,7 +17,7 @@ public class IdeSlf4jContext extends AbstractIdeTestContext {
*/
public IdeSlf4jContext(Path userDir, String... answers) {
- super(level -> new IdeSlf4jLogger(level), userDir, answers);
+ super(level -> new IdeSlf4jLogger(level), userDir, null, answers);
}
}
diff --git a/cli/src/test/java/com/devonfw/tools/ide/context/IdeTestContext.java b/cli/src/test/java/com/devonfw/tools/ide/context/IdeTestContext.java
index 35a0ad858..b1ed23f43 100644
--- a/cli/src/test/java/com/devonfw/tools/ide/context/IdeTestContext.java
+++ b/cli/src/test/java/com/devonfw/tools/ide/context/IdeTestContext.java
@@ -1,9 +1,11 @@
package com.devonfw.tools.ide.context;
-import java.nio.file.Path;
-
import com.devonfw.tools.ide.log.IdeLogLevel;
import com.devonfw.tools.ide.log.IdeTestLogger;
+import com.devonfw.tools.ide.process.ProcessContext;
+import com.devonfw.tools.ide.repo.ToolRepository;
+
+import java.nio.file.Path;
/**
* Implementation of {@link IdeContext} for testing.
@@ -18,7 +20,20 @@ public class IdeTestContext extends AbstractIdeTestContext {
*/
public IdeTestContext(Path userDir, String... answers) {
- super(level -> new IdeTestLogger(level), userDir, answers);
+ super(level -> new IdeTestLogger(level), userDir, null, answers);
+ }
+
+ /**
+ * The constructor.
+ *
+ * @param userDir the optional {@link Path} to current working directory.
+ * @param toolRepository the {@link ToolRepository} of the context. If it is set to {@code null} *
+ * {@link com.devonfw.tools.ide.repo.DefaultToolRepository} will be used.
+ * @param answers the automatic answers simulating a user in test.
+ */
+ public IdeTestContext(Path userDir, ToolRepository toolRepository, String... answers) {
+
+ super(level -> new IdeTestLogger(level), userDir, toolRepository, answers);
}
@Override
@@ -33,6 +48,12 @@ public GitContext getGitContext() {
return new GitContextMock();
}
+ @Override
+ protected ProcessContext createProcessContext() {
+
+ return new ProcessContextTestImpl(this);
+ }
+
/**
* @return a dummy {@link IdeTestContext}.
*/
diff --git a/cli/src/test/java/com/devonfw/tools/ide/context/ProcessContextTestImpl.java b/cli/src/test/java/com/devonfw/tools/ide/context/ProcessContextTestImpl.java
new file mode 100644
index 000000000..5f53d5069
--- /dev/null
+++ b/cli/src/test/java/com/devonfw/tools/ide/context/ProcessContextTestImpl.java
@@ -0,0 +1,26 @@
+package com.devonfw.tools.ide.context;
+
+import com.devonfw.tools.ide.process.ProcessContextImpl;
+import com.devonfw.tools.ide.process.ProcessMode;
+import com.devonfw.tools.ide.process.ProcessResult;
+
+public class ProcessContextTestImpl extends ProcessContextImpl {
+
+ public ProcessContextTestImpl(IdeContext context) {
+
+ super(context);
+ }
+
+ @Override
+ public ProcessResult run(ProcessMode processMode) {
+
+ ProcessResult result = super.run(ProcessMode.DEFAULT_CAPTURE);
+ for (String out : result.getOut()) {
+ this.context.info(out);
+ }
+ for (String err : result.getErr()) {
+ this.context.error(err);
+ }
+ return result;
+ }
+}
diff --git a/cli/src/test/java/com/devonfw/tools/ide/environment/EnvironmentVariablesTest.java b/cli/src/test/java/com/devonfw/tools/ide/environment/EnvironmentVariablesTest.java
index 7f001c490..0e69e4db9 100644
--- a/cli/src/test/java/com/devonfw/tools/ide/environment/EnvironmentVariablesTest.java
+++ b/cli/src/test/java/com/devonfw/tools/ide/environment/EnvironmentVariablesTest.java
@@ -1,60 +1,60 @@
-package com.devonfw.tools.ide.environment;
-
-import com.devonfw.tools.ide.context.AbstractIdeContextTest;
-import com.devonfw.tools.ide.context.IdeTestContext;
-import org.junit.jupiter.api.Test;
-
-/**
- * Test of {@link EnvironmentVariables}.
- */
-public class EnvironmentVariablesTest extends AbstractIdeContextTest {
-
- /**
- * Test of {@link EnvironmentVariables#resolve(String, Object)} with self referencing variables.
- */
- @Test
- public void testProperEvaluationOfVariables() {
-
- // arrange
- String path = "workspaces/foo-test/my-git-repo";
- IdeTestContext context = newContext(PROJECT_BASIC, path, false);
- EnvironmentVariables variables = context.getVariables();
-
- // act
- String TEST_ARGS1 = variables.get("TEST_ARGS1");
- String TEST_ARGS2 = variables.get("TEST_ARGS2");
- String TEST_ARGS3 = variables.get("TEST_ARGS3");
- String TEST_ARGS4 = variables.get("TEST_ARGS4");
- String TEST_ARGS5 = variables.get("TEST_ARGS5");
- String TEST_ARGS6 = variables.get("TEST_ARGS6");
- String TEST_ARGS7 = variables.get("TEST_ARGS7");
- String TEST_ARGS8 = variables.get("TEST_ARGS8");
- String TEST_ARGS9 = variables.get("TEST_ARGS9");
- String TEST_ARGS10 = variables.get("TEST_ARGS10");
- // some more advanced cases
- String TEST_ARGSa = variables.get("TEST_ARGSa");
- String TEST_ARGSb = variables.get("TEST_ARGSb");
- String TEST_ARGSc = variables.get("TEST_ARGSc");
- String TEST_ARGSd = variables.get("TEST_ARGSd");
-
- // assert
- assertThat(TEST_ARGS1).isEqualTo(" user1 settings1 workspace1 conf1");
- assertThat(TEST_ARGS2).isEqualTo(" user2 conf2");
- assertThat(TEST_ARGS3).isEqualTo(" user3 workspace3");
- assertThat(TEST_ARGS4).isEqualTo(" settings4");
- assertThat(TEST_ARGS5).isEqualTo(" settings5 conf5");
- assertThat(TEST_ARGS6).isEqualTo(" settings6 workspace6 conf6");
-
- assertThat(TEST_ARGS7).isEqualTo("user7 settings7 workspace7 conf7");
- assertThat(TEST_ARGS8).isEqualTo("settings8 workspace8 conf8");
- assertThat(TEST_ARGS9).isEqualTo("settings9 workspace9");
- assertThat(TEST_ARGS10).isEqualTo("user10 workspace10");
-
- assertThat(TEST_ARGSa).isEqualTo(" user1 settings1 workspace1 conf1 user3 workspace3 confa");
- assertThat(TEST_ARGSb)
- .isEqualTo("user10 workspace10 settingsb user1 settings1 workspace1 conf1 user3 workspace3 confa userb");
-
- assertThat(TEST_ARGSc).isEqualTo(" user1 settings1 workspace1 conf1 userc settingsc confc");
- assertThat(TEST_ARGSd).isEqualTo(" user1 settings1 workspace1 conf1 userd workspaced");
- }
-}
+package com.devonfw.tools.ide.environment;
+
+import com.devonfw.tools.ide.context.AbstractIdeContextTest;
+import com.devonfw.tools.ide.context.IdeTestContext;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test of {@link EnvironmentVariables}.
+ */
+public class EnvironmentVariablesTest extends AbstractIdeContextTest {
+
+ /**
+ * Test of {@link EnvironmentVariables#resolve(String, Object)} with self referencing variables.
+ */
+ @Test
+ public void testProperEvaluationOfVariables() {
+
+ // arrange
+ String path = "project/workspaces/foo-test/my-git-repo";
+ IdeTestContext context = newContext(PROJECT_BASIC, path, false);
+ EnvironmentVariables variables = context.getVariables();
+
+ // act
+ String TEST_ARGS1 = variables.get("TEST_ARGS1");
+ String TEST_ARGS2 = variables.get("TEST_ARGS2");
+ String TEST_ARGS3 = variables.get("TEST_ARGS3");
+ String TEST_ARGS4 = variables.get("TEST_ARGS4");
+ String TEST_ARGS5 = variables.get("TEST_ARGS5");
+ String TEST_ARGS6 = variables.get("TEST_ARGS6");
+ String TEST_ARGS7 = variables.get("TEST_ARGS7");
+ String TEST_ARGS8 = variables.get("TEST_ARGS8");
+ String TEST_ARGS9 = variables.get("TEST_ARGS9");
+ String TEST_ARGS10 = variables.get("TEST_ARGS10");
+ // some more advanced cases
+ String TEST_ARGSa = variables.get("TEST_ARGSa");
+ String TEST_ARGSb = variables.get("TEST_ARGSb");
+ String TEST_ARGSc = variables.get("TEST_ARGSc");
+ String TEST_ARGSd = variables.get("TEST_ARGSd");
+
+ // assert
+ assertThat(TEST_ARGS1).isEqualTo(" user1 settings1 workspace1 conf1");
+ assertThat(TEST_ARGS2).isEqualTo(" user2 conf2");
+ assertThat(TEST_ARGS3).isEqualTo(" user3 workspace3");
+ assertThat(TEST_ARGS4).isEqualTo(" settings4");
+ assertThat(TEST_ARGS5).isEqualTo(" settings5 conf5");
+ assertThat(TEST_ARGS6).isEqualTo(" settings6 workspace6 conf6");
+
+ assertThat(TEST_ARGS7).isEqualTo("user7 settings7 workspace7 conf7");
+ assertThat(TEST_ARGS8).isEqualTo("settings8 workspace8 conf8");
+ assertThat(TEST_ARGS9).isEqualTo("settings9 workspace9");
+ assertThat(TEST_ARGS10).isEqualTo("user10 workspace10");
+
+ assertThat(TEST_ARGSa).isEqualTo(" user1 settings1 workspace1 conf1 user3 workspace3 confa");
+ assertThat(TEST_ARGSb).isEqualTo(
+ "user10 workspace10 settingsb user1 settings1 workspace1 conf1 user3 workspace3 confa userb");
+
+ assertThat(TEST_ARGSc).isEqualTo(" user1 settings1 workspace1 conf1 userc settingsc confc");
+ assertThat(TEST_ARGSd).isEqualTo(" user1 settings1 workspace1 conf1 userd workspaced");
+ }
+}
diff --git a/cli/src/test/java/com/devonfw/tools/ide/io/FileAccessImplTest.java b/cli/src/test/java/com/devonfw/tools/ide/io/FileAccessImplTest.java
index 33f2b7183..ccd9f0315 100644
--- a/cli/src/test/java/com/devonfw/tools/ide/io/FileAccessImplTest.java
+++ b/cli/src/test/java/com/devonfw/tools/ide/io/FileAccessImplTest.java
@@ -1,571 +1,570 @@
-package com.devonfw.tools.ide.io;
-
-import static com.devonfw.tools.ide.io.FileAccessImpl.generatePermissionString;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.LinkOption;
-import java.nio.file.NoSuchFileException;
-import java.nio.file.Path;
-import java.nio.file.attribute.PosixFilePermission;
-import java.nio.file.attribute.PosixFilePermissions;
-import java.util.Set;
-
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.io.TempDir;
-
-import com.devonfw.tools.ide.context.AbstractIdeContextTest;
-import com.devonfw.tools.ide.context.IdeContext;
-import com.devonfw.tools.ide.context.IdeTestContextMock;
-
-/**
- * Test of {@link FileAccessImpl}.
- */
-public class FileAccessImplTest extends AbstractIdeContextTest {
-
- /**
- * Checks if Windows junctions are used.
- *
- * @param context the {@link IdeContext} to get system info and file access from.
- * @param dir the {@link Path} to the directory which is used as temp directory.
- * @return {@code true} if Windows junctions are used, {@code false} otherwise.
- */
- private boolean windowsJunctionsAreUsed(IdeContext context, Path dir) {
-
- if (!context.getSystemInfo().isWindows()) {
- return false;
- }
-
- Path source = dir.resolve("checkIfWindowsJunctionsAreUsed");
- Path link = dir.resolve("checkIfWindowsJunctionsAreUsedLink");
- context.getFileAccess().mkdirs(source);
- try {
- Files.createSymbolicLink(link, source);
- return false;
- } catch (IOException e) {
- return true;
- }
- }
-
- /**
- * Test of {@link FileAccessImpl#symlink(Path, Path, boolean)} with "relative = false". Passing absolute paths as
- * source.
- */
- @Test
- public void testSymlinkAbsolute(@TempDir Path tempDir) {
-
- // relative links are checked in testRelativeLinksWorkAfterMoving
-
- // arrange
- IdeContext context = IdeTestContextMock.get();
- FileAccess fileAccess = new FileAccessImpl(context);
- Path dir = tempDir.resolve("parent");
- createDirs(fileAccess, dir);
- boolean readLinks = !windowsJunctionsAreUsed(context, tempDir);
- boolean relative = false;
-
- // act
- createSymlinks(fileAccess, dir, relative);
-
- // assert
- assertSymlinksExist(dir);
- assertSymlinksWork(dir, readLinks);
- }
-
- /**
- * Test of {@link FileAccessImpl#symlink(Path, Path, boolean)} with "relative = false". Passing relative paths as
- * source.
- */
- @Test
- public void testSymlinkAbsolutePassingRelativeSource(@TempDir Path tempDir) {
-
- // arrange
- IdeContext context = IdeTestContextMock.get();
- FileAccess fileAccess = new FileAccessImpl(context);
- Path dir = tempDir.resolve("parent");
- createDirs(fileAccess, dir);
- boolean readLinks = !windowsJunctionsAreUsed(context, tempDir);
- boolean relative = false;
-
- // act
- createSymlinksByPassingRelativeSource(fileAccess, dir, relative);
-
- // assert
- assertSymlinksExist(dir);
- assertSymlinksWork(dir, readLinks);
- }
-
- /**
- * Test of {@link FileAccessImpl#symlink(Path, Path, boolean)} with "relative = true". But Windows junctions are used
- * and therefore the fallback from relative to absolute paths is tested.
- */
- @Test
- public void testSymlinkAbsoluteAsFallback(@TempDir Path tempDir) {
-
- // arrange
- IdeContext context = IdeTestContextMock.get();
- if (!windowsJunctionsAreUsed(context, tempDir)) {
- context.info(
- "Can not check the Test: testSymlinkAbsoluteAsFallback since windows junctions are not used and fallback "
- + "from relative to absolute paths as link target is not used.");
- return;
- }
- FileAccess fileAccess = new FileAccessImpl(context);
- Path dir = tempDir.resolve("parent");
- createDirs(fileAccess, dir);
- boolean readLinks = false; // bc windows junctions are used, which can't be read with Files.readSymbolicLink(link);
- boolean relative = true; // set to true, such that the fallback to absolute paths is used since junctions are used
-
- // act
- createSymlinks(fileAccess, dir, relative);
-
- // assert
- assertSymlinksExist(dir);
- assertSymlinksWork(dir, readLinks);
- }
-
- /**
- * Test of {@link FileAccessImpl#symlink(Path, Path, boolean)} with "relative = false". Furthermore, it is tested that
- * the links are broken after moving them.
- */
- @Test
- public void testSymlinkAbsoluteBreakAfterMoving(@TempDir Path tempDir) throws IOException {
-
- // arrange
- IdeContext context = IdeTestContextMock.get();
- FileAccess fileAccess = new FileAccessImpl(context);
- Path dir = tempDir.resolve("parent");
- createDirs(fileAccess, dir);
- boolean relative = false;
- createSymlinks(fileAccess, dir, relative);
- boolean readLinks = !windowsJunctionsAreUsed(context, tempDir);
-
- // act
- Path sibling = dir.resolveSibling("parent2");
- fileAccess.move(dir, sibling);
-
- // assert
- assertSymlinksExist(sibling);
- assertSymlinksAreBroken(sibling, readLinks);
- }
-
- /**
- * Test of {@link FileAccessImpl#symlink(Path, Path, boolean)} with "relative = true". Furthermore, it is tested that
- * the links still work after moving them. Passing relative paths as source.
- */
- @Test
- public void testSymlinkRelativeWorkAfterMovingPassingRelativeSource(@TempDir Path tempDir) {
-
- // arrange
- IdeContext context = IdeTestContextMock.get();
- if (windowsJunctionsAreUsed(context, tempDir)) {
- context.info("Can not check the Test: testRelativeLinksWorkAfterMoving since windows junctions are used.");
- return;
- }
- FileAccess fileAccess = new FileAccessImpl(context);
- Path dir = tempDir.resolve("parent");
- createDirs(fileAccess, dir);
- boolean relative = true;
- createSymlinksByPassingRelativeSource(fileAccess, dir, relative);
- boolean readLinks = true; // junctions are not used, so links can be read with Files.readSymbolicLink(link);
-
- // act
- Path sibling = dir.resolveSibling("parent2");
- fileAccess.move(dir, sibling);
-
- // assert
- assertSymlinksExist(sibling);
- assertSymlinksWork(sibling, readLinks);
- }
-
- /**
- * Test of {@link FileAccessImpl#symlink(Path, Path, boolean)} with "relative = true". Furthermore, it is tested that
- * the links still work after moving them.
- */
- @Test
- public void testSymlinkRelativeWorkAfterMoving(@TempDir Path tempDir) {
-
- // arrange
- IdeContext context = IdeTestContextMock.get();
- if (windowsJunctionsAreUsed(context, tempDir)) {
- context.info("Can not check the Test: testRelativeLinksWorkAfterMoving since windows junctions are used.");
- return;
- }
- FileAccess fileAccess = new FileAccessImpl(context);
- Path dir = tempDir.resolve("parent");
- createDirs(fileAccess, dir);
- boolean relative = true;
- createSymlinks(fileAccess, dir, relative);
- boolean readLinks = true; // junctions are not used, so links can be read with Files.readSymbolicLink(link);
-
- // act
- Path sibling = dir.resolveSibling("parent2");
- fileAccess.move(dir, sibling);
-
- // assert
- assertSymlinksExist(sibling);
- assertSymlinksWork(sibling, readLinks);
- }
-
- /**
- * Test of {@link FileAccessImpl#symlink(Path, Path, boolean)} when Windows junctions are used and the source is a
- * file.
- */
- @Test
- public void testSymlinkWindowsJunctionsCanNotPointToFiles(@TempDir Path tempDir) throws IOException {
-
- // arrange
- IdeContext context = IdeTestContextMock.get();
- if (!windowsJunctionsAreUsed(context, tempDir)) {
- context
- .info("Can not check the Test: testWindowsJunctionsCanNotPointToFiles since windows junctions are not used.");
- return;
- }
- Path file = tempDir.resolve("file");
- Files.createFile(file);
- FileAccess fileAccess = new FileAccessImpl(context);
-
- // act & assert
- IllegalStateException e1 = assertThrows(IllegalStateException.class, () -> {
- fileAccess.symlink(file, tempDir.resolve("linkToFile"));
- });
- assertThat(e1).hasMessageContaining("These junctions can only point to directories or other junctions");
- }
-
- /**
- * Test of {@link FileAccessImpl#symlink(Path, Path, boolean)} and whether the source paths are simplified correctly
- * by {@link Path#toRealPath(LinkOption...)}.
- */
- @Test
- public void testSymlinkShortcutPaths(@TempDir Path tempDir) {
-
- // arrange
- IdeContext context = IdeTestContextMock.get();
- FileAccess fileAccess = new FileAccessImpl(context);
- Path dir = tempDir.resolve("parent");
- createDirs(fileAccess, dir);
- fileAccess.mkdirs(dir.resolve("d3"));
- boolean readLinks = !windowsJunctionsAreUsed(context, tempDir);
-
- // act
- fileAccess.symlink(dir.resolve("d3/../d1"), dir.resolve("link1"), false);
- fileAccess.symlink(Path.of("d3/../d1"), dir.resolve("link2"), false);
- fileAccess.symlink(dir.resolve("d3/../d1"), dir.resolve("link3"), true);
- fileAccess.symlink(Path.of("d3/../d1"), dir.resolve("link4"), true);
- fileAccess.delete(dir.resolve("d3"));
-
- // assert
- assertSymlinkToRealPath(dir.resolve("link1"), dir.resolve("d1"));
- assertSymlinkToRealPath(dir.resolve("link2"), dir.resolve("d1"));
- assertSymlinkToRealPath(dir.resolve("link3"), dir.resolve("d1"));
- assertSymlinkToRealPath(dir.resolve("link4"), dir.resolve("d1"));
- if (readLinks) {
- assertSymlinkRead(dir.resolve("link1"), dir.resolve("d1"));
- assertSymlinkRead(dir.resolve("link2"), dir.resolve("d1"));
- assertSymlinkRead(dir.resolve("link3"), dir.resolve("d1"));
- assertSymlinkRead(dir.resolve("link4"), dir.resolve("d1"));
- }
- }
-
- private void createDirs(FileAccess fileAccess, Path dir) {
-
- fileAccess.mkdirs(dir.resolve("d1/d11/d111/d1111"));
- fileAccess.mkdirs(dir.resolve("d2/d22/d222"));
- }
-
- /**
- * Creates the symlinks with passing relative paths as source. This is used by the tests of
- * {@link FileAccessImpl#symlink(Path, Path, boolean)}.
- *
- * @param fa the {@link FileAccess} to use.
- * @param dir the {@link Path} to the directory where the symlinks shall be created.
- * @param relative - {@code true} if the symbolic link shall be relative, {@code false} if it shall be absolute.
- */
- private void createSymlinksByPassingRelativeSource(FileAccess fa, Path dir, boolean relative) {
-
- fa.symlink(Path.of("."), dir.resolve("d1/d11/link_to_d1"), relative);
- // test if symbolic links or junctions can be overwritten with symlink()
- fa.symlink(Path.of(".."), dir.resolve("d1/d11/link_to_d1"), relative);
-
- fa.symlink(Path.of("."), dir.resolve("d1/d11/link_to_d11"), relative);
- fa.symlink(Path.of("d111"), dir.resolve("d1/d11/link_to_d111"), relative);
- fa.symlink(Path.of("d111/d1111"), dir.resolve("d1/d11/link_to_d1111"), relative);
- fa.symlink(Path.of("../../d1/../d2"), dir.resolve("d1/d11/link_to_d2"), relative);
- fa.symlink(Path.of("../../d2/d22"), dir.resolve("d1/d11/link_to_d22"), relative);
- fa.symlink(Path.of("../../d2/d22/d222"), dir.resolve("d1/d11/link_to_d222"), relative);
-
- fa.symlink(Path.of("../../d1/d11/link_to_d1"), dir.resolve("d2/d22/link_to_link_to_d1"), relative);
- fa.symlink(Path.of("../d1/d11/link_to_d1"), dir.resolve("d2/another_link_to_link_to_d1"), relative);
- fa.symlink(Path.of("d2/another_link_to_link_to_d1"), dir.resolve("link_to_another_link_to_link_to_d1"), relative);
- }
-
- /**
- * Creates the symlinks with passing absolute paths as source. This is used by the tests of
- * {@link FileAccessImpl#symlink(Path, Path, boolean)}.
- *
- * @param fa the {@link FileAccess} to use.
- * @param dir the {@link Path} to the directory where the symlinks shall be created.
- * @param relative - {@code true} if the symbolic link shall be relative, {@code false} if it shall be absolute.
- */
- private void createSymlinks(FileAccess fa, Path dir, boolean relative) {
-
- fa.symlink(dir.resolve("d1/d11"), dir.resolve("d1/d11/link_to_d1"), relative);
- // test if symbolic links or junctions can be overwritten with symlink()
- fa.symlink(dir.resolve("d1"), dir.resolve("d1/d11/link_to_d1"), relative);
-
- fa.symlink(dir.resolve("d1/d11"), dir.resolve("d1/d11/link_to_d11"), relative);
- fa.symlink(dir.resolve("d1/d11/d111"), dir.resolve("d1/d11/link_to_d111"), relative);
- fa.symlink(dir.resolve("d1/d11/d111/d1111"), dir.resolve("d1/d11/link_to_d1111"), relative);
- fa.symlink(dir.resolve("d1/../d2"), dir.resolve("d1/d11/link_to_d2"), relative);
- fa.symlink(dir.resolve("d2/d22"), dir.resolve("d1/d11/link_to_d22"), relative);
- fa.symlink(dir.resolve("d2/d22/d222"), dir.resolve("d1/d11/link_to_d222"), relative);
-
- fa.symlink(dir.resolve("d1/d11/link_to_d1"), dir.resolve("d2/d22/link_to_link_to_d1"), relative);
- fa.symlink(dir.resolve("d1/d11/link_to_d1"), dir.resolve("d2/another_link_to_link_to_d1"), relative);
- fa.symlink(dir.resolve("d2/another_link_to_link_to_d1"), dir.resolve("link_to_another_link_to_link_to_d1"),
- relative);
- }
-
- /**
- * Checks if the symlinks exist. This is used by the tests of {@link FileAccessImpl#symlink(Path, Path, boolean)}.
- *
- * @param dir the {@link Path} to the directory where the symlinks are expected.
- */
- private void assertSymlinksExist(Path dir) {
-
- assertThat(dir.resolve("d1/d11/link_to_d1")).existsNoFollowLinks();
- assertThat(dir.resolve("d1/d11/link_to_d11")).existsNoFollowLinks();
- assertThat(dir.resolve("d1/d11/link_to_d111")).existsNoFollowLinks();
- assertThat(dir.resolve("d1/d11/link_to_d1111")).existsNoFollowLinks();
- assertThat(dir.resolve("d1/d11/link_to_d2")).existsNoFollowLinks();
- assertThat(dir.resolve("d1/d11/link_to_d22")).existsNoFollowLinks();
- assertThat(dir.resolve("d1/d11/link_to_d222")).existsNoFollowLinks();
- assertThat(dir.resolve("d2/d22/link_to_link_to_d1")).existsNoFollowLinks();
- assertThat(dir.resolve("d2/another_link_to_link_to_d1")).existsNoFollowLinks();
- assertThat(dir.resolve("link_to_another_link_to_link_to_d1")).existsNoFollowLinks();
- }
-
- /**
- * Checks if the symlinks are broken. This is used by the tests of
- * {@link FileAccessImpl#symlink(Path, Path, boolean)}.
- *
- * @param dir the {@link Path} to the directory where the symlinks are expected.
- * @param readLinks - {@code true} if the symbolic link shall be read with {@link Files#readSymbolicLink(Path)}, this
- * does not work for Windows junctions.
- */
- private void assertSymlinksAreBroken(Path dir, boolean readLinks) throws IOException {
-
- assertSymlinkIsBroken(dir.resolve("d1/d11/link_to_d1"), readLinks);
- assertSymlinkIsBroken(dir.resolve("d1/d11/link_to_d11"), readLinks);
- assertSymlinkIsBroken(dir.resolve("d1/d11/link_to_d111"), readLinks);
- assertSymlinkIsBroken(dir.resolve("d1/d11/link_to_d1111"), readLinks);
- assertSymlinkIsBroken(dir.resolve("d1/d11/link_to_d2"), readLinks);
- assertSymlinkIsBroken(dir.resolve("d1/d11/link_to_d22"), readLinks);
- assertSymlinkIsBroken(dir.resolve("d1/d11/link_to_d222"), readLinks);
- assertSymlinkIsBroken(dir.resolve("d2/d22/link_to_link_to_d1"), readLinks);
- assertSymlinkIsBroken(dir.resolve("d2/another_link_to_link_to_d1"), readLinks);
- assertSymlinkIsBroken(dir.resolve("link_to_another_link_to_link_to_d1"), readLinks);
- }
-
- /**
- * Checks if the symlink is broken. This is used by the tests of {@link FileAccessImpl#symlink(Path, Path, boolean)}.
- *
- * @param link the {@link Path} to the link.
- * @param readLinks - {@code true} if the symbolic link shall be read with {@link Files#readSymbolicLink(Path)}, this
- * does not work for Windows junctions.
- */
- private void assertSymlinkIsBroken(Path link, boolean readLinks) throws IOException {
-
- try {
- Path realPath = link.toRealPath();
- if (Files.exists(realPath)) {
- fail("The link target " + realPath + " (from toRealPath) should not exist");
- }
- } catch (IOException e) { // toRealPath() throws exception for junctions
- assertThat(e).isInstanceOf(NoSuchFileException.class);
- }
- if (readLinks) {
- Path readPath = Files.readSymbolicLink(link);
- if (Files.exists(readPath)) {
- fail("The link target " + readPath + " (from readSymbolicLink) should not exist");
- }
- }
- }
-
- /**
- * Checks if the symlinks work. This is used by the tests of {@link FileAccessImpl#symlink(Path, Path, boolean)}.
- *
- * @param dir the {@link Path} to the directory where the symlinks are expected.
- * @param readLinks - {@code true} if the symbolic link shall be read with {@link Files#readSymbolicLink(Path)}, this
- * does not work for Windows junctions.
- */
- private void assertSymlinksWork(Path dir, boolean readLinks) {
-
- assertSymlinkToRealPath(dir.resolve("d1/d11/link_to_d1"), dir.resolve("d1"));
- assertSymlinkToRealPath(dir.resolve("d1/d11/link_to_d11"), dir.resolve("d1/d11"));
- assertSymlinkToRealPath(dir.resolve("d1/d11/link_to_d111"), dir.resolve("d1/d11/d111"));
- assertSymlinkToRealPath(dir.resolve("d1/d11/link_to_d1111"), dir.resolve("d1/d11/d111/d1111"));
- assertSymlinkToRealPath(dir.resolve("d1/d11/link_to_d2"), dir.resolve("d2"));
- assertSymlinkToRealPath(dir.resolve("d1/d11/link_to_d22"), dir.resolve("d2/d22"));
- assertSymlinkToRealPath(dir.resolve("d1/d11/link_to_d222"), dir.resolve("d2/d22/d222"));
- assertSymlinkToRealPath(dir.resolve("d2/d22/link_to_link_to_d1"), dir.resolve("d1"));
- assertSymlinkToRealPath(dir.resolve("d2/another_link_to_link_to_d1"), dir.resolve("d1"));
- assertSymlinkToRealPath(dir.resolve("link_to_another_link_to_link_to_d1"), dir.resolve("d1"));
-
- if (readLinks) {
- assertSymlinkRead(dir.resolve("d1/d11/link_to_d1"), dir.resolve("d1"));
- assertSymlinkRead(dir.resolve("d1/d11/link_to_d11"), dir.resolve("d1/d11"));
- assertSymlinkRead(dir.resolve("d1/d11/link_to_d111"), dir.resolve("d1/d11/d111"));
- assertSymlinkRead(dir.resolve("d1/d11/link_to_d1111"), dir.resolve("d1/d11/d111/d1111"));
- assertSymlinkRead(dir.resolve("d1/d11/link_to_d2"), dir.resolve("d2"));
- assertSymlinkRead(dir.resolve("d1/d11/link_to_d22"), dir.resolve("d2/d22"));
- assertSymlinkRead(dir.resolve("d1/d11/link_to_d222"), dir.resolve("d2/d22/d222"));
- assertSymlinkRead(dir.resolve("d2/d22/link_to_link_to_d1"), dir.resolve("d1/d11/link_to_d1"));
- assertSymlinkRead(dir.resolve("d2/another_link_to_link_to_d1"), dir.resolve("d1/d11/link_to_d1"));
- assertSymlinkRead(dir.resolve("link_to_another_link_to_link_to_d1"),
- dir.resolve("d2/another_link_to_link_to_d1"));
- }
- }
-
- /**
- * Checks if the symlink works by checking {@link Path#toRealPath(LinkOption...)}} against the {@code trueTarget}. .
- * This is used by the tests of {@link FileAccessImpl#symlink(Path, Path, boolean)}.
- *
- * @param link the {@link Path} to the link.
- * @param trueTarget the {@link Path} to the true target.
- */
- private void assertSymlinkToRealPath(Path link, Path trueTarget) {
-
- Path realPath = null;
- try {
- realPath = link.toRealPath();
- } catch (IOException e) {
- fail("In method assertSymlinkToRealPath() could not call toRealPath() on link " + link, e);
- }
- assertThat(realPath).exists();
- assertThat(realPath).existsNoFollowLinks();
- assertThat(realPath).isEqualTo(trueTarget);
- }
-
- /**
- * Checks if the symlink works by checking {@link Files#readSymbolicLink(Path)} against the {@code trueTarget}. This
- * is used by the tests of {@link FileAccessImpl#symlink(Path, Path, boolean)}. Only call this method if junctions are
- * not used, since junctions can not be read with {@link Files#readSymbolicLink(Path)}.
- *
- * @param link the {@link Path} to the link.
- * @param trueTarget the {@link Path} to the true target.
- */
- private void assertSymlinkRead(Path link, Path trueTarget) {
-
- Path readPath = null;
- try {
- readPath = Files.readSymbolicLink(link);
- } catch (IOException e) {
- fail("In method assertSymlinkRead() could not call readSymbolicLink() on link " + link, e);
- }
- assertThat(link.resolveSibling(readPath)).existsNoFollowLinks();
- assertThat(link.resolveSibling(readPath)).exists();
- try {
- assertThat(link.resolveSibling(readPath).toRealPath(LinkOption.NOFOLLOW_LINKS)).isEqualTo(trueTarget);
- } catch (IOException e) {
- fail("In method assertSymlinkRead() could not call toRealPath() on link.resolveSibling(readPath) for link " + link
- + " and readPath " + readPath, e);
- }
- }
-
- /**
- * Test of {@link FileAccessImpl#untar(Path, Path, TarCompression)} with {@link TarCompression#NONE} and checks if
- * file permissions are preserved on Unix.
- */
- @Test
- public void testUntarWithNoneCompressionWithFilePermissions(@TempDir Path tempDir) {
-
- // arrange
- IdeContext context = IdeTestContextMock.get();
- if (context.getSystemInfo().isWindows()) {
- return;
- }
-
- // act
- context.getFileAccess().untar(
- Path.of("src/test/resources/com/devonfw/tools/ide/io").resolve("executable_and_non_executable.tar"), tempDir,
- TarCompression.NONE);
-
- // assert
- assertPosixFilePermissions(tempDir.resolve("executableFile.txt"), "rwxrwxr-x");
- assertPosixFilePermissions(tempDir.resolve("nonExecutableFile.txt"), "rw-rw-r--");
- }
-
- /**
- * Test of {@link FileAccessImpl#untar(Path, Path, TarCompression)} with {@link TarCompression#GZ} and checks if file
- * permissions are preserved on Unix.
- */
- @Test
- public void testUntarWithGzCompressionWithFilePermissions(@TempDir Path tempDir) {
-
- // arrange
- IdeContext context = IdeTestContextMock.get();
- if (context.getSystemInfo().isWindows()) {
- return;
- }
-
- // act
- context.getFileAccess().untar(
- Path.of("src/test/resources/com/devonfw/tools/ide/io").resolve("executable_and_non_executable.tar.gz"), tempDir,
- TarCompression.GZ);
-
- // assert
- assertPosixFilePermissions(tempDir.resolve("executableFile.txt"), "rwxrwxr-x");
- assertPosixFilePermissions(tempDir.resolve("nonExecutableFile.txt"), "rw-rw-r--");
- }
-
- /**
- * Test of {@link FileAccessImpl#untar(Path, Path, TarCompression)} with {@link TarCompression#BZIP2} and checks if
- * file permissions are preserved on Unix.
- */
- @Test
- public void testUntarWithBzip2CompressionWithFilePermissions(@TempDir Path tempDir) {
-
- // arrange
- IdeContext context = IdeTestContextMock.get();
- if (context.getSystemInfo().isWindows()) {
- return;
- }
-
- // act
- context.getFileAccess().untar(
- Path.of("src/test/resources/com/devonfw/tools/ide/io").resolve("executable_and_non_executable.tar.bz2"),
- tempDir, TarCompression.BZIP2);
-
- // assert
- assertPosixFilePermissions(tempDir.resolve("executableFile.txt"), "rwxrwxr-x");
- assertPosixFilePermissions(tempDir.resolve("nonExecutableFile.txt"), "rw-rw-r--");
- }
-
- private void assertPosixFilePermissions(Path file, String permissions) {
-
- try {
- Set posixPermissions = Files.getPosixFilePermissions(file);
- String permissionStr = PosixFilePermissions.toString(posixPermissions);
- assertThat(permissions).isEqualTo(permissionStr);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Test of {@link FileAccessImpl#generatePermissionString(int)}.
- */
- @Test
- public void testGeneratePermissionString() {
-
- assertThat(generatePermissionString(0)).isEqualTo("---------");
- assertThat(generatePermissionString(436)).isEqualTo("rw-rw-r--");
- assertThat(generatePermissionString(948)).isEqualTo("rw-rw-r--");
- assertThat(generatePermissionString(509)).isEqualTo("rwxrwxr-x");
- assertThat(generatePermissionString(511)).isEqualTo("rwxrwxrwx");
-
- }
-
-}
+package com.devonfw.tools.ide.io;
+
+import com.devonfw.tools.ide.context.AbstractIdeContextTest;
+import com.devonfw.tools.ide.context.IdeContext;
+import com.devonfw.tools.ide.context.IdeTestContextMock;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.util.Set;
+
+import static com.devonfw.tools.ide.io.FileAccessImpl.generatePermissionString;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Test of {@link FileAccessImpl}.
+ */
+public class FileAccessImplTest extends AbstractIdeContextTest {
+
+ /**
+ * Checks if Windows junctions are used.
+ *
+ * @param context the {@link IdeContext} to get system info and file access from.
+ * @param dir the {@link Path} to the directory which is used as temp directory.
+ * @return {@code true} if Windows junctions are used, {@code false} otherwise.
+ */
+ private boolean windowsJunctionsAreUsed(IdeContext context, Path dir) {
+
+ if (!context.getSystemInfo().isWindows()) {
+ return false;
+ }
+
+ Path source = dir.resolve("checkIfWindowsJunctionsAreUsed");
+ Path link = dir.resolve("checkIfWindowsJunctionsAreUsedLink");
+ context.getFileAccess().mkdirs(source);
+ try {
+ Files.createSymbolicLink(link, source);
+ return false;
+ } catch (IOException e) {
+ return true;
+ }
+ }
+
+ /**
+ * Test of {@link FileAccessImpl#symlink(Path, Path, boolean)} with "relative = false". Passing absolute paths as
+ * source.
+ */
+ @Test
+ public void testSymlinkAbsolute(@TempDir Path tempDir) {
+
+ // relative links are checked in testRelativeLinksWorkAfterMoving
+
+ // arrange
+ IdeContext context = IdeTestContextMock.get();
+ FileAccess fileAccess = new FileAccessImpl(context);
+ Path dir = tempDir.resolve("parent");
+ createDirs(fileAccess, dir);
+ boolean readLinks = !windowsJunctionsAreUsed(context, tempDir);
+ boolean relative = false;
+
+ // act
+ createSymlinks(fileAccess, dir, relative);
+
+ // assert
+ assertSymlinksExist(dir);
+ assertSymlinksWork(dir, readLinks);
+ }
+
+ /**
+ * Test of {@link FileAccessImpl#symlink(Path, Path, boolean)} with "relative = false". Passing relative paths as
+ * source.
+ */
+ @Test
+ public void testSymlinkAbsolutePassingRelativeSource(@TempDir Path tempDir) {
+
+ // arrange
+ IdeContext context = IdeTestContextMock.get();
+ FileAccess fileAccess = new FileAccessImpl(context);
+ Path dir = tempDir.resolve("parent");
+ createDirs(fileAccess, dir);
+ boolean readLinks = !windowsJunctionsAreUsed(context, tempDir);
+ boolean relative = false;
+
+ // act
+ createSymlinksByPassingRelativeSource(fileAccess, dir, relative);
+
+ // assert
+ assertSymlinksExist(dir);
+ assertSymlinksWork(dir, readLinks);
+ }
+
+ /**
+ * Test of {@link FileAccessImpl#symlink(Path, Path, boolean)} with "relative = true". But Windows junctions are used
+ * and therefore the fallback from relative to absolute paths is tested.
+ */
+ @Test
+ public void testSymlinkAbsoluteAsFallback(@TempDir Path tempDir) {
+
+ // arrange
+ IdeContext context = IdeTestContextMock.get();
+ if (!windowsJunctionsAreUsed(context, tempDir)) {
+ context.info(
+ "Can not check the Test: testSymlinkAbsoluteAsFallback since windows junctions are not used and fallback "
+ + "from relative to absolute paths as link target is not used.");
+ return;
+ }
+ FileAccess fileAccess = new FileAccessImpl(context);
+ Path dir = tempDir.resolve("parent");
+ createDirs(fileAccess, dir);
+ boolean readLinks = false; // bc windows junctions are used, which can't be read with Files.readSymbolicLink(link);
+ boolean relative = true; // set to true, such that the fallback to absolute paths is used since junctions are used
+
+ // act
+ createSymlinks(fileAccess, dir, relative);
+
+ // assert
+ assertSymlinksExist(dir);
+ assertSymlinksWork(dir, readLinks);
+ }
+
+ /**
+ * Test of {@link FileAccessImpl#symlink(Path, Path, boolean)} with "relative = false". Furthermore, it is tested that
+ * the links are broken after moving them.
+ */
+ @Test
+ public void testSymlinkAbsoluteBreakAfterMoving(@TempDir Path tempDir) throws IOException {
+
+ // arrange
+ IdeContext context = IdeTestContextMock.get();
+ FileAccess fileAccess = new FileAccessImpl(context);
+ Path dir = tempDir.resolve("parent");
+ createDirs(fileAccess, dir);
+ boolean relative = false;
+ createSymlinks(fileAccess, dir, relative);
+ boolean readLinks = !windowsJunctionsAreUsed(context, tempDir);
+
+ // act
+ Path sibling = dir.resolveSibling("parent2");
+ fileAccess.move(dir, sibling);
+
+ // assert
+ assertSymlinksExist(sibling);
+ assertSymlinksAreBroken(sibling, readLinks);
+ }
+
+ /**
+ * Test of {@link FileAccessImpl#symlink(Path, Path, boolean)} with "relative = true". Furthermore, it is tested that
+ * the links still work after moving them. Passing relative paths as source.
+ */
+ @Test
+ public void testSymlinkRelativeWorkAfterMovingPassingRelativeSource(@TempDir Path tempDir) {
+
+ // arrange
+ IdeContext context = IdeTestContextMock.get();
+ if (windowsJunctionsAreUsed(context, tempDir)) {
+ context.info("Can not check the Test: testRelativeLinksWorkAfterMoving since windows junctions are used.");
+ return;
+ }
+ FileAccess fileAccess = new FileAccessImpl(context);
+ Path dir = tempDir.resolve("parent");
+ createDirs(fileAccess, dir);
+ boolean relative = true;
+ createSymlinksByPassingRelativeSource(fileAccess, dir, relative);
+ boolean readLinks = true; // junctions are not used, so links can be read with Files.readSymbolicLink(link);
+
+ // act
+ Path sibling = dir.resolveSibling("parent2");
+ fileAccess.move(dir, sibling);
+
+ // assert
+ assertSymlinksExist(sibling);
+ assertSymlinksWork(sibling, readLinks);
+ }
+
+ /**
+ * Test of {@link FileAccessImpl#symlink(Path, Path, boolean)} with "relative = true". Furthermore, it is tested that
+ * the links still work after moving them.
+ */
+ @Test
+ public void testSymlinkRelativeWorkAfterMoving(@TempDir Path tempDir) {
+
+ // arrange
+ IdeContext context = IdeTestContextMock.get();
+ if (windowsJunctionsAreUsed(context, tempDir)) {
+ context.info("Can not check the Test: testRelativeLinksWorkAfterMoving since windows junctions are used.");
+ return;
+ }
+ FileAccess fileAccess = new FileAccessImpl(context);
+ Path dir = tempDir.resolve("parent");
+ createDirs(fileAccess, dir);
+ boolean relative = true;
+ createSymlinks(fileAccess, dir, relative);
+ boolean readLinks = true; // junctions are not used, so links can be read with Files.readSymbolicLink(link);
+
+ // act
+ Path sibling = dir.resolveSibling("parent2");
+ fileAccess.move(dir, sibling);
+
+ // assert
+ assertSymlinksExist(sibling);
+ assertSymlinksWork(sibling, readLinks);
+ }
+
+ /**
+ * Test of {@link FileAccessImpl#symlink(Path, Path, boolean)} when Windows junctions are used and the source is a
+ * file.
+ */
+ @Test
+ public void testSymlinkWindowsJunctionsCanNotPointToFiles(@TempDir Path tempDir) throws IOException {
+
+ // arrange
+ IdeContext context = IdeTestContextMock.get();
+ if (!windowsJunctionsAreUsed(context, tempDir)) {
+ context.info(
+ "Can not check the Test: testWindowsJunctionsCanNotPointToFiles since windows junctions are not used.");
+ return;
+ }
+ Path file = tempDir.resolve("file");
+ Files.createFile(file);
+ FileAccess fileAccess = new FileAccessImpl(context);
+
+ // act & assert
+ IllegalStateException e1 = assertThrows(IllegalStateException.class, () -> {
+ fileAccess.symlink(file, tempDir.resolve("linkToFile"));
+ });
+ assertThat(e1).hasMessageContaining("These junctions can only point to directories or other junctions");
+ }
+
+ /**
+ * Test of {@link FileAccessImpl#symlink(Path, Path, boolean)} and whether the source paths are simplified correctly
+ * by {@link Path#toRealPath(LinkOption...)}.
+ */
+ @Test
+ public void testSymlinkShortcutPaths(@TempDir Path tempDir) {
+
+ // arrange
+ IdeContext context = IdeTestContextMock.get();
+ FileAccess fileAccess = new FileAccessImpl(context);
+ Path dir = tempDir.resolve("parent");
+ createDirs(fileAccess, dir);
+ fileAccess.mkdirs(dir.resolve("d3"));
+ boolean readLinks = !windowsJunctionsAreUsed(context, tempDir);
+
+ // act
+ fileAccess.symlink(dir.resolve("d3/../d1"), dir.resolve("link1"), false);
+ fileAccess.symlink(Path.of("d3/../d1"), dir.resolve("link2"), false);
+ fileAccess.symlink(dir.resolve("d3/../d1"), dir.resolve("link3"), true);
+ fileAccess.symlink(Path.of("d3/../d1"), dir.resolve("link4"), true);
+ fileAccess.delete(dir.resolve("d3"));
+
+ // assert
+ assertSymlinkToRealPath(dir.resolve("link1"), dir.resolve("d1"));
+ assertSymlinkToRealPath(dir.resolve("link2"), dir.resolve("d1"));
+ assertSymlinkToRealPath(dir.resolve("link3"), dir.resolve("d1"));
+ assertSymlinkToRealPath(dir.resolve("link4"), dir.resolve("d1"));
+ if (readLinks) {
+ assertSymlinkRead(dir.resolve("link1"), dir.resolve("d1"));
+ assertSymlinkRead(dir.resolve("link2"), dir.resolve("d1"));
+ assertSymlinkRead(dir.resolve("link3"), dir.resolve("d1"));
+ assertSymlinkRead(dir.resolve("link4"), dir.resolve("d1"));
+ }
+ }
+
+ private void createDirs(FileAccess fileAccess, Path dir) {
+
+ fileAccess.mkdirs(dir.resolve("d1/d11/d111/d1111"));
+ fileAccess.mkdirs(dir.resolve("d2/d22/d222"));
+ }
+
+ /**
+ * Creates the symlinks with passing relative paths as source. This is used by the tests of
+ * {@link FileAccessImpl#symlink(Path, Path, boolean)}.
+ *
+ * @param fa the {@link FileAccess} to use.
+ * @param dir the {@link Path} to the directory where the symlinks shall be created.
+ * @param relative - {@code true} if the symbolic link shall be relative, {@code false} if it shall be absolute.
+ */
+ private void createSymlinksByPassingRelativeSource(FileAccess fa, Path dir, boolean relative) {
+
+ fa.symlink(Path.of("."), dir.resolve("d1/d11/link_to_d1"), relative);
+ // test if symbolic links or junctions can be overwritten with symlink()
+ fa.symlink(Path.of(".."), dir.resolve("d1/d11/link_to_d1"), relative);
+
+ fa.symlink(Path.of("."), dir.resolve("d1/d11/link_to_d11"), relative);
+ fa.symlink(Path.of("d111"), dir.resolve("d1/d11/link_to_d111"), relative);
+ fa.symlink(Path.of("d111/d1111"), dir.resolve("d1/d11/link_to_d1111"), relative);
+ fa.symlink(Path.of("../../d1/../d2"), dir.resolve("d1/d11/link_to_d2"), relative);
+ fa.symlink(Path.of("../../d2/d22"), dir.resolve("d1/d11/link_to_d22"), relative);
+ fa.symlink(Path.of("../../d2/d22/d222"), dir.resolve("d1/d11/link_to_d222"), relative);
+
+ fa.symlink(Path.of("../../d1/d11/link_to_d1"), dir.resolve("d2/d22/link_to_link_to_d1"), relative);
+ fa.symlink(Path.of("../d1/d11/link_to_d1"), dir.resolve("d2/another_link_to_link_to_d1"), relative);
+ fa.symlink(Path.of("d2/another_link_to_link_to_d1"), dir.resolve("link_to_another_link_to_link_to_d1"), relative);
+ }
+
+ /**
+ * Creates the symlinks with passing absolute paths as source. This is used by the tests of
+ * {@link FileAccessImpl#symlink(Path, Path, boolean)}.
+ *
+ * @param fa the {@link FileAccess} to use.
+ * @param dir the {@link Path} to the directory where the symlinks shall be created.
+ * @param relative - {@code true} if the symbolic link shall be relative, {@code false} if it shall be absolute.
+ */
+ private void createSymlinks(FileAccess fa, Path dir, boolean relative) {
+
+ fa.symlink(dir.resolve("d1/d11"), dir.resolve("d1/d11/link_to_d1"), relative);
+ // test if symbolic links or junctions can be overwritten with symlink()
+ fa.symlink(dir.resolve("d1"), dir.resolve("d1/d11/link_to_d1"), relative);
+
+ fa.symlink(dir.resolve("d1/d11"), dir.resolve("d1/d11/link_to_d11"), relative);
+ fa.symlink(dir.resolve("d1/d11/d111"), dir.resolve("d1/d11/link_to_d111"), relative);
+ fa.symlink(dir.resolve("d1/d11/d111/d1111"), dir.resolve("d1/d11/link_to_d1111"), relative);
+ fa.symlink(dir.resolve("d1/../d2"), dir.resolve("d1/d11/link_to_d2"), relative);
+ fa.symlink(dir.resolve("d2/d22"), dir.resolve("d1/d11/link_to_d22"), relative);
+ fa.symlink(dir.resolve("d2/d22/d222"), dir.resolve("d1/d11/link_to_d222"), relative);
+
+ fa.symlink(dir.resolve("d1/d11/link_to_d1"), dir.resolve("d2/d22/link_to_link_to_d1"), relative);
+ fa.symlink(dir.resolve("d1/d11/link_to_d1"), dir.resolve("d2/another_link_to_link_to_d1"), relative);
+ fa.symlink(dir.resolve("d2/another_link_to_link_to_d1"), dir.resolve("link_to_another_link_to_link_to_d1"),
+ relative);
+ }
+
+ /**
+ * Checks if the symlinks exist. This is used by the tests of {@link FileAccessImpl#symlink(Path, Path, boolean)}.
+ *
+ * @param dir the {@link Path} to the directory where the symlinks are expected.
+ */
+ private void assertSymlinksExist(Path dir) {
+
+ assertThat(dir.resolve("d1/d11/link_to_d1")).existsNoFollowLinks();
+ assertThat(dir.resolve("d1/d11/link_to_d11")).existsNoFollowLinks();
+ assertThat(dir.resolve("d1/d11/link_to_d111")).existsNoFollowLinks();
+ assertThat(dir.resolve("d1/d11/link_to_d1111")).existsNoFollowLinks();
+ assertThat(dir.resolve("d1/d11/link_to_d2")).existsNoFollowLinks();
+ assertThat(dir.resolve("d1/d11/link_to_d22")).existsNoFollowLinks();
+ assertThat(dir.resolve("d1/d11/link_to_d222")).existsNoFollowLinks();
+ assertThat(dir.resolve("d2/d22/link_to_link_to_d1")).existsNoFollowLinks();
+ assertThat(dir.resolve("d2/another_link_to_link_to_d1")).existsNoFollowLinks();
+ assertThat(dir.resolve("link_to_another_link_to_link_to_d1")).existsNoFollowLinks();
+ }
+
+ /**
+ * Checks if the symlinks are broken. This is used by the tests of
+ * {@link FileAccessImpl#symlink(Path, Path, boolean)}.
+ *
+ * @param dir the {@link Path} to the directory where the symlinks are expected.
+ * @param readLinks - {@code true} if the symbolic link shall be read with {@link Files#readSymbolicLink(Path)}, this
+ * does not work for Windows junctions.
+ */
+ private void assertSymlinksAreBroken(Path dir, boolean readLinks) throws IOException {
+
+ assertSymlinkIsBroken(dir.resolve("d1/d11/link_to_d1"), readLinks);
+ assertSymlinkIsBroken(dir.resolve("d1/d11/link_to_d11"), readLinks);
+ assertSymlinkIsBroken(dir.resolve("d1/d11/link_to_d111"), readLinks);
+ assertSymlinkIsBroken(dir.resolve("d1/d11/link_to_d1111"), readLinks);
+ assertSymlinkIsBroken(dir.resolve("d1/d11/link_to_d2"), readLinks);
+ assertSymlinkIsBroken(dir.resolve("d1/d11/link_to_d22"), readLinks);
+ assertSymlinkIsBroken(dir.resolve("d1/d11/link_to_d222"), readLinks);
+ assertSymlinkIsBroken(dir.resolve("d2/d22/link_to_link_to_d1"), readLinks);
+ assertSymlinkIsBroken(dir.resolve("d2/another_link_to_link_to_d1"), readLinks);
+ assertSymlinkIsBroken(dir.resolve("link_to_another_link_to_link_to_d1"), readLinks);
+ }
+
+ /**
+ * Checks if the symlink is broken. This is used by the tests of {@link FileAccessImpl#symlink(Path, Path, boolean)}.
+ *
+ * @param link the {@link Path} to the link.
+ * @param readLinks - {@code true} if the symbolic link shall be read with {@link Files#readSymbolicLink(Path)}, this
+ * does not work for Windows junctions.
+ */
+ private void assertSymlinkIsBroken(Path link, boolean readLinks) throws IOException {
+
+ try {
+ Path realPath = link.toRealPath();
+ if (Files.exists(realPath)) {
+ fail("The link target " + realPath + " (from toRealPath) should not exist");
+ }
+ } catch (IOException e) { // toRealPath() throws exception for junctions
+ assertThat(e).isInstanceOf(NoSuchFileException.class);
+ }
+ if (readLinks) {
+ Path readPath = Files.readSymbolicLink(link);
+ if (Files.exists(readPath)) {
+ fail("The link target " + readPath + " (from readSymbolicLink) should not exist");
+ }
+ }
+ }
+
+ /**
+ * Checks if the symlinks work. This is used by the tests of {@link FileAccessImpl#symlink(Path, Path, boolean)}.
+ *
+ * @param dir the {@link Path} to the directory where the symlinks are expected.
+ * @param readLinks - {@code true} if the symbolic link shall be read with {@link Files#readSymbolicLink(Path)}, this
+ * does not work for Windows junctions.
+ */
+ private void assertSymlinksWork(Path dir, boolean readLinks) {
+
+ assertSymlinkToRealPath(dir.resolve("d1/d11/link_to_d1"), dir.resolve("d1"));
+ assertSymlinkToRealPath(dir.resolve("d1/d11/link_to_d11"), dir.resolve("d1/d11"));
+ assertSymlinkToRealPath(dir.resolve("d1/d11/link_to_d111"), dir.resolve("d1/d11/d111"));
+ assertSymlinkToRealPath(dir.resolve("d1/d11/link_to_d1111"), dir.resolve("d1/d11/d111/d1111"));
+ assertSymlinkToRealPath(dir.resolve("d1/d11/link_to_d2"), dir.resolve("d2"));
+ assertSymlinkToRealPath(dir.resolve("d1/d11/link_to_d22"), dir.resolve("d2/d22"));
+ assertSymlinkToRealPath(dir.resolve("d1/d11/link_to_d222"), dir.resolve("d2/d22/d222"));
+ assertSymlinkToRealPath(dir.resolve("d2/d22/link_to_link_to_d1"), dir.resolve("d1"));
+ assertSymlinkToRealPath(dir.resolve("d2/another_link_to_link_to_d1"), dir.resolve("d1"));
+ assertSymlinkToRealPath(dir.resolve("link_to_another_link_to_link_to_d1"), dir.resolve("d1"));
+
+ if (readLinks) {
+ assertSymlinkRead(dir.resolve("d1/d11/link_to_d1"), dir.resolve("d1"));
+ assertSymlinkRead(dir.resolve("d1/d11/link_to_d11"), dir.resolve("d1/d11"));
+ assertSymlinkRead(dir.resolve("d1/d11/link_to_d111"), dir.resolve("d1/d11/d111"));
+ assertSymlinkRead(dir.resolve("d1/d11/link_to_d1111"), dir.resolve("d1/d11/d111/d1111"));
+ assertSymlinkRead(dir.resolve("d1/d11/link_to_d2"), dir.resolve("d2"));
+ assertSymlinkRead(dir.resolve("d1/d11/link_to_d22"), dir.resolve("d2/d22"));
+ assertSymlinkRead(dir.resolve("d1/d11/link_to_d222"), dir.resolve("d2/d22/d222"));
+ assertSymlinkRead(dir.resolve("d2/d22/link_to_link_to_d1"), dir.resolve("d1/d11/link_to_d1"));
+ assertSymlinkRead(dir.resolve("d2/another_link_to_link_to_d1"), dir.resolve("d1/d11/link_to_d1"));
+ assertSymlinkRead(dir.resolve("link_to_another_link_to_link_to_d1"),
+ dir.resolve("d2/another_link_to_link_to_d1"));
+ }
+ }
+
+ /**
+ * Checks if the symlink works by checking {@link Path#toRealPath(LinkOption...)}} against the {@code trueTarget}. .
+ * This is used by the tests of {@link FileAccessImpl#symlink(Path, Path, boolean)}.
+ *
+ * @param link the {@link Path} to the link.
+ * @param trueTarget the {@link Path} to the true target.
+ */
+ private void assertSymlinkToRealPath(Path link, Path trueTarget) {
+
+ Path realPath = null;
+ try {
+ realPath = link.toRealPath();
+ } catch (IOException e) {
+ fail("In method assertSymlinkToRealPath() could not call toRealPath() on link " + link, e);
+ }
+ assertThat(realPath).exists();
+ assertThat(realPath).existsNoFollowLinks();
+ assertThat(realPath).isEqualTo(trueTarget);
+ }
+
+ /**
+ * Checks if the symlink works by checking {@link Files#readSymbolicLink(Path)} against the {@code trueTarget}. This
+ * is used by the tests of {@link FileAccessImpl#symlink(Path, Path, boolean)}. Only call this method if junctions are
+ * not used, since junctions can not be read with {@link Files#readSymbolicLink(Path)}.
+ *
+ * @param link the {@link Path} to the link.
+ * @param trueTarget the {@link Path} to the true target.
+ */
+ private void assertSymlinkRead(Path link, Path trueTarget) {
+
+ Path readPath = null;
+ try {
+ readPath = Files.readSymbolicLink(link);
+ } catch (IOException e) {
+ fail("In method assertSymlinkRead() could not call readSymbolicLink() on link " + link, e);
+ }
+ assertThat(link.resolveSibling(readPath)).existsNoFollowLinks();
+ assertThat(link.resolveSibling(readPath)).exists();
+ try {
+ assertThat(link.resolveSibling(readPath).toRealPath(LinkOption.NOFOLLOW_LINKS)).isEqualTo(trueTarget);
+ } catch (IOException e) {
+ fail("In method assertSymlinkRead() could not call toRealPath() on link.resolveSibling(readPath) for link " + link
+ + " and readPath " + readPath, e);
+ }
+ }
+
+ /**
+ * Test of {@link FileAccessImpl#extractTar(Path, Path, TarCompression)} with {@link TarCompression#NONE} and checks
+ * if file permissions are preserved on Unix.
+ */
+ @Test
+ public void testUntarWithNoneCompressionWithFilePermissions(@TempDir Path tempDir) {
+
+ // arrange
+ IdeContext context = IdeTestContextMock.get();
+ if (context.getSystemInfo().isWindows()) {
+ return;
+ }
+
+ // act
+ context.getFileAccess()
+ .extractTar(Path.of("src/test/resources/com/devonfw/tools/ide/io").resolve("executable_and_non_executable.tar"),
+ tempDir, TarCompression.NONE);
+
+ // assert
+ assertPosixFilePermissions(tempDir.resolve("executableFile.txt"), "rwxrwxr-x");
+ assertPosixFilePermissions(tempDir.resolve("nonExecutableFile.txt"), "rw-rw-r--");
+ }
+
+ /**
+ * Test of {@link FileAccessImpl#extractTar(Path, Path, TarCompression)} with {@link TarCompression#GZ} and checks if
+ * file permissions are preserved on Unix.
+ */
+ @Test
+ public void testUntarWithGzCompressionWithFilePermissions(@TempDir Path tempDir) {
+
+ // arrange
+ IdeContext context = IdeTestContextMock.get();
+ if (context.getSystemInfo().isWindows()) {
+ return;
+ }
+
+ // act
+ context.getFileAccess().extractTar(
+ Path.of("src/test/resources/com/devonfw/tools/ide/io").resolve("executable_and_non_executable.tar.gz"), tempDir,
+ TarCompression.GZ);
+
+ // assert
+ assertPosixFilePermissions(tempDir.resolve("executableFile.txt"), "rwxrwxr-x");
+ assertPosixFilePermissions(tempDir.resolve("nonExecutableFile.txt"), "rw-rw-r--");
+ }
+
+ /**
+ * Test of {@link FileAccessImpl#extractTar(Path, Path, TarCompression)} with {@link TarCompression#BZIP2} and checks
+ * if file permissions are preserved on Unix.
+ */
+ @Test
+ public void testUntarWithBzip2CompressionWithFilePermissions(@TempDir Path tempDir) {
+
+ // arrange
+ IdeContext context = IdeTestContextMock.get();
+ if (context.getSystemInfo().isWindows()) {
+ return;
+ }
+
+ // act
+ context.getFileAccess().extractTar(
+ Path.of("src/test/resources/com/devonfw/tools/ide/io").resolve("executable_and_non_executable.tar.bz2"),
+ tempDir, TarCompression.BZIP2);
+
+ // assert
+ assertPosixFilePermissions(tempDir.resolve("executableFile.txt"), "rwxrwxr-x");
+ assertPosixFilePermissions(tempDir.resolve("nonExecutableFile.txt"), "rw-rw-r--");
+ }
+
+ private void assertPosixFilePermissions(Path file, String permissions) {
+
+ try {
+ Set posixPermissions = Files.getPosixFilePermissions(file);
+ String permissionStr = PosixFilePermissions.toString(posixPermissions);
+ assertThat(permissions).isEqualTo(permissionStr);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Test of {@link FileAccessImpl#generatePermissionString(int)}.
+ */
+ @Test
+ public void testGeneratePermissionString() {
+
+ assertThat(generatePermissionString(0)).isEqualTo("---------");
+ assertThat(generatePermissionString(436)).isEqualTo("rw-rw-r--");
+ assertThat(generatePermissionString(948)).isEqualTo("rw-rw-r--");
+ assertThat(generatePermissionString(509)).isEqualTo("rwxrwxr-x");
+ assertThat(generatePermissionString(511)).isEqualTo("rwxrwxrwx");
+
+ }
+
+}
diff --git a/cli/src/test/java/com/devonfw/tools/ide/io/TarCompressionTest.java b/cli/src/test/java/com/devonfw/tools/ide/io/TarCompressionTest.java
index 4e6e4d574..d7ac8bd3a 100644
--- a/cli/src/test/java/com/devonfw/tools/ide/io/TarCompressionTest.java
+++ b/cli/src/test/java/com/devonfw/tools/ide/io/TarCompressionTest.java
@@ -1,29 +1,32 @@
-package com.devonfw.tools.ide.io;
-
-import org.assertj.core.api.Assertions;
-import org.junit.jupiter.api.Test;
-
-/**
- * Test of {@link TarCompression}.
- */
-public class TarCompressionTest extends Assertions {
-
- /** Test of {@link TarCompression#of(String)}. */
- @Test
- public void testOf() {
-
- assertThat(TarCompression.of(".tar")).isSameAs(TarCompression.NONE);
- assertThat(TarCompression.of("tar")).isSameAs(TarCompression.NONE);
- assertThat(TarCompression.of("tgz")).isSameAs(TarCompression.GZ);
- assertThat(TarCompression.of("gz")).isSameAs(TarCompression.GZ);
- assertThat(TarCompression.of("tar.gz")).isSameAs(TarCompression.GZ);
- assertThat(TarCompression.of("tbz2")).isSameAs(TarCompression.BZIP2);
- assertThat(TarCompression.of("bz2")).isSameAs(TarCompression.BZIP2);
- assertThat(TarCompression.of("bzip2")).isSameAs(TarCompression.BZIP2);
- assertThat(TarCompression.of(".tar.bzip2")).isSameAs(TarCompression.BZIP2);
- assertThat(TarCompression.of(".pkg")).isNull();
- assertThat(TarCompression.of("tfoo")).isNull();
- assertThat(TarCompression.of(".tar.foo")).isNull();
- }
-
-}
+package com.devonfw.tools.ide.io;
+
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test of {@link TarCompression}.
+ */
+public class TarCompressionTest extends Assertions {
+
+ /** Test of {@link TarCompression#of(String)}. */
+ @Test
+ public void testOf() {
+
+ assertThat(TarCompression.of(".tar")).isSameAs(TarCompression.NONE);
+ assertThat(TarCompression.of("tar")).isSameAs(TarCompression.NONE);
+ assertThat(TarCompression.of("file.tgz")).isSameAs(TarCompression.GZ);
+ assertThat(TarCompression.of("tgz")).isSameAs(TarCompression.GZ);
+ assertThat(TarCompression.of("gz")).isNull();
+ assertThat(TarCompression.of("tar.gz")).isSameAs(TarCompression.GZ);
+ assertThat(TarCompression.of("tar.file.tar.gz")).isSameAs(TarCompression.GZ);
+ assertThat(TarCompression.of("tbz2")).isSameAs(TarCompression.BZIP2);
+ assertThat(TarCompression.of("bz2")).isNull();
+ assertThat(TarCompression.of("bzip2")).isNull();
+ assertThat(TarCompression.of("file.tar.bz2")).isSameAs(TarCompression.BZIP2);
+ assertThat(TarCompression.of("file.tar.bzip2")).isSameAs(TarCompression.BZIP2);
+ assertThat(TarCompression.of(".pkg")).isNull();
+ assertThat(TarCompression.of("tfoo")).isNull();
+ assertThat(TarCompression.of("file.tar.foo")).isNull();
+ }
+
+}
diff --git a/cli/src/test/java/com/devonfw/tools/ide/log/IdeTestLogger.java b/cli/src/test/java/com/devonfw/tools/ide/log/IdeTestLogger.java
index 0c9d3cd7d..5b6eb7cc9 100644
--- a/cli/src/test/java/com/devonfw/tools/ide/log/IdeTestLogger.java
+++ b/cli/src/test/java/com/devonfw/tools/ide/log/IdeTestLogger.java
@@ -1,45 +1,46 @@
-package com.devonfw.tools.ide.log;
-
-import java.util.LinkedList;
-import java.util.List;
-
-/**
- * Implementation of {@link IdeSubLogger} for testing that collects all messages and allows to check if an expected
- * message was logged.
- */
-public class IdeTestLogger extends AbstractIdeSubLogger {
-
- private final List messages;
-
- /**
- * The constructor.
- *
- * @param level the {@link #getLevel() log-level}.
- */
- public IdeTestLogger(IdeLogLevel level) {
-
- super(level);
- this.messages = new LinkedList<>();
- }
-
- @Override
- public void log(String message) {
-
- this.messages.add(message);
- }
-
- /**
- * @return the {@link List} of messages that have been logged for test assertions.
- */
- public List getMessages() {
-
- return this.messages;
- }
-
- @Override
- public boolean isEnabled() {
-
- return true;
- }
-
-}
+package com.devonfw.tools.ide.log;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Implementation of {@link IdeSubLogger} for testing that collects all messages and allows to check if an expected
+ * message was logged.
+ */
+public class IdeTestLogger extends IdeSlf4jLogger {
+
+ private final List messages;
+
+ /**
+ * The constructor.
+ *
+ * @param level the {@link #getLevel() log-level}.
+ */
+ public IdeTestLogger(IdeLogLevel level) {
+
+ super(level);
+ this.messages = new LinkedList<>();
+ }
+
+ @Override
+ public void log(String message) {
+
+ super.log(message);
+ this.messages.add(message);
+ }
+
+ /**
+ * @return the {@link List} of messages that have been logged for test assertions.
+ */
+ public List getMessages() {
+
+ return this.messages;
+ }
+
+ @Override
+ public boolean isEnabled() {
+
+ return true;
+ }
+
+}
diff --git a/cli/src/test/java/com/devonfw/tools/ide/merge/DirectoryMergerTest.java b/cli/src/test/java/com/devonfw/tools/ide/merge/DirectoryMergerTest.java
index ce995dd8f..e913f8e22 100644
--- a/cli/src/test/java/com/devonfw/tools/ide/merge/DirectoryMergerTest.java
+++ b/cli/src/test/java/com/devonfw/tools/ide/merge/DirectoryMergerTest.java
@@ -10,14 +10,12 @@
import com.devonfw.tools.ide.context.AbstractIdeContextTest;
import com.devonfw.tools.ide.context.IdeContext;
-import ch.qos.logback.classic.spi.Configurator;
-
/**
* Test of {@link DirectoryMerger}.
*/
public class DirectoryMergerTest extends AbstractIdeContextTest {
- private static final String IDE_HOME = PATH_PROJECTS.resolve(PROJECT_BASIC).resolve("project").toAbsolutePath()
+ private static final String IDE_HOME = TEST_PROJECTS.resolve(PROJECT_BASIC).resolve("project").toAbsolutePath()
.toString().replace('\\', '/');
private static final Prop JAVA_VERSION = new Prop("java.version", "1.11");
@@ -41,7 +39,7 @@ public class DirectoryMergerTest extends AbstractIdeContextTest {
private static final Prop EDITOR = new Prop("editor", "vi");
/**
- * Test of {@link Configurator}.
+ * Test of {@link DirectoryMerger}.
*
* @param workspaceDir the temporary folder to use as workspace for this test.
* @throws Exception on error.
diff --git a/cli/src/test/java/com/devonfw/tools/ide/os/MacOsHelperTest.java b/cli/src/test/java/com/devonfw/tools/ide/os/MacOsHelperTest.java
index 19ffb8237..0bd640c8a 100644
--- a/cli/src/test/java/com/devonfw/tools/ide/os/MacOsHelperTest.java
+++ b/cli/src/test/java/com/devonfw/tools/ide/os/MacOsHelperTest.java
@@ -1,57 +1,74 @@
-package com.devonfw.tools.ide.os;
-
-import java.nio.file.Path;
-
-import org.junit.jupiter.api.Test;
-
-import com.devonfw.tools.ide.context.AbstractIdeContextTest;
-import com.devonfw.tools.ide.context.IdeContext;
-
-/**
- * Test of {@link MacOsHelper}.
- */
-public class MacOsHelperTest extends AbstractIdeContextTest {
-
- private static final IdeContext CONTEXT = newContext("basic", "", false);
-
- private static final Path APPS_DIR = Path.of("src/test/resources/mac-apps");
-
- /** Test "java" structure. */
- @Test
- public void testJava() {
-
- // arrange
- Path rootDir = APPS_DIR.resolve("java");
- MacOsHelper helper = new MacOsHelper(CONTEXT.getFileAccess(), SystemInformationMock.MAC_X64, CONTEXT);
- // act
- Path linkDir = helper.findLinkDir(rootDir);
- // assert
- assertThat(linkDir).isEqualTo(rootDir.resolve("Contents/Resources/app"));
- }
-
- /** Test "special" structure. */
- @Test
- public void testSpecial() {
-
- // arrange
- Path rootDir = APPS_DIR.resolve("special");
- MacOsHelper helper = new MacOsHelper(CONTEXT.getFileAccess(), SystemInformationMock.MAC_X64, CONTEXT);
- // act
- Path linkDir = helper.findLinkDir(rootDir);
- // assert
- assertThat(linkDir).isEqualTo(rootDir.resolve("Special.app/Contents/CorrectFolder"));
- }
-
- /** Test if OS is not Mac. */
- @Test
- public void testNotMac() {
-
- // arrange
- Path rootDir = APPS_DIR.resolve("java");
- MacOsHelper helper = new MacOsHelper(CONTEXT.getFileAccess(), SystemInformationMock.LINUX_X64, CONTEXT);
- // act
- Path linkDir = helper.findLinkDir(rootDir);
- // assert
- assertThat(linkDir).isSameAs(rootDir);
- }
-}
+package com.devonfw.tools.ide.os;
+
+import com.devonfw.tools.ide.context.AbstractIdeContextTest;
+import com.devonfw.tools.ide.context.IdeContext;
+import org.junit.jupiter.api.Test;
+
+import java.nio.file.Path;
+
+/**
+ * Test of {@link MacOsHelper}.
+ */
+public class MacOsHelperTest extends AbstractIdeContextTest {
+
+ private static final IdeContext CONTEXT = newContext(PROJECT_BASIC, null, false);
+
+ private static final Path APPS_DIR = TEST_RESOURCES.resolve("mac-apps");
+
+ /** Test "java" structure. */
+ @Test
+ public void testJava() {
+
+ // arrange
+ String tool = "java";
+ Path rootDir = APPS_DIR.resolve(tool);
+ MacOsHelper helper = new MacOsHelper(CONTEXT.getFileAccess(), SystemInfoMock.MAC_X64, CONTEXT);
+ // act
+ Path linkDir = helper.findLinkDir(rootDir, tool);
+ // assert
+ assertThat(linkDir).isEqualTo(rootDir.resolve("Contents/Resources/app"));
+ }
+
+ /** Test "special" structure. */
+ @Test
+ public void testSpecial() {
+
+ // arrange
+ String tool = "special";
+ Path rootDir = APPS_DIR.resolve(tool);
+ MacOsHelper helper = new MacOsHelper(CONTEXT.getFileAccess(), SystemInfoMock.MAC_X64, CONTEXT);
+ // act
+ Path linkDir = helper.findLinkDir(rootDir, tool);
+ // assert
+ assertThat(linkDir).isEqualTo(rootDir.resolve("Special.app/Contents/CorrectFolder"));
+ }
+
+ /** Test if OS is not Mac. */
+ @Test
+ public void testNotMac() {
+
+ // arrange
+ String tool = "java";
+ Path rootDir = APPS_DIR.resolve(tool);
+ MacOsHelper helper = new MacOsHelper(CONTEXT.getFileAccess(), SystemInfoMock.LINUX_X64, CONTEXT);
+ // act
+ Path linkDir = helper.findLinkDir(rootDir, tool);
+ // assert
+ assertThat(linkDir).isSameAs(rootDir);
+ }
+
+ /** Test "java" structure. */
+ @Test
+ public void testJmc() {
+
+ // arrange
+ String tool = "jmc";
+ Path rootDir = APPS_DIR.resolve(tool);
+ MacOsHelper helper = new MacOsHelper(CONTEXT.getFileAccess(), SystemInfoMock.MAC_X64, CONTEXT);
+ // act
+ Path linkDir = helper.findLinkDir(rootDir, tool);
+ // assert
+ assertThat(linkDir).isEqualTo(rootDir.resolve("Contents/MacOS"));
+ }
+
+}
diff --git a/cli/src/test/java/com/devonfw/tools/ide/os/SystemInformationImplTest.java b/cli/src/test/java/com/devonfw/tools/ide/os/SystemInfoImplTest.java
similarity index 79%
rename from cli/src/test/java/com/devonfw/tools/ide/os/SystemInformationImplTest.java
rename to cli/src/test/java/com/devonfw/tools/ide/os/SystemInfoImplTest.java
index e94a414f1..6699a39ad 100644
--- a/cli/src/test/java/com/devonfw/tools/ide/os/SystemInformationImplTest.java
+++ b/cli/src/test/java/com/devonfw/tools/ide/os/SystemInfoImplTest.java
@@ -1,87 +1,87 @@
-package com.devonfw.tools.ide.os;
-
-import org.assertj.core.api.Assertions;
-import org.junit.jupiter.api.Test;
-
-/**
- * Test of {@link SystemInfoImpl}.
- */
-public class SystemInformationImplTest extends Assertions {
-
- /** Test {@link SystemInfoImpl#SystemInfoImpl()}. */
- @Test
- public void testDefaultConstructor() {
-
- // arrange
- String osName = System.getProperty("os.name");
- String osVersion = System.getProperty("os.version");
- String architectureName = System.getProperty("os.arch");
- // act
- SystemInfo systemInfo = new SystemInfoImpl();
- // assert
- assertThat(systemInfo.getOsName()).isEqualTo(osName).isNotBlank();
- assertThat(systemInfo.getOsVersion().toString()).isEqualTo(osVersion).isNotBlank();
- assertThat(systemInfo.getArchitectureName()).isEqualTo(architectureName).isNotBlank();
- }
-
- /** Test {@link SystemInformationMock#WINDOWS_X64}. */
- @Test
- public void testWindowsDetection() {
-
- // arrange
- OperatingSystem os = OperatingSystem.WINDOWS;
- SystemArchitecture arch = SystemArchitecture.X64;
- // act
- SystemInfo systemInfo = SystemInformationMock.WINDOWS_X64;
- // assert
- assertThat(systemInfo.getOs()).isSameAs(os);
- assertThat(systemInfo.getArchitecture()).isSameAs(arch);
- assertThat(systemInfo.toString()).isEqualTo("windows@x64(Windows 10[10.0]@amd64)");
- }
-
- /** Test {@link SystemInformationMock#MAC_X64}. */
- @Test
- public void testMacDetection() {
-
- // arrange
- OperatingSystem os = OperatingSystem.MAC;
- SystemArchitecture arch = SystemArchitecture.X64;
- // act
- SystemInfo systemInfo = SystemInformationMock.MAC_X64;
- // assert
- assertThat(systemInfo.getOs()).isSameAs(os);
- assertThat(systemInfo.getArchitecture()).isSameAs(arch);
- assertThat(systemInfo.toString()).isEqualTo("mac@x64(Mac OS X[12.6.9]@x86_64)");
- }
-
- /** Test {@link SystemInformationMock#MAC_ARM64}. */
- @Test
- public void testMacArmDetection() {
-
- // arrange
- OperatingSystem os = OperatingSystem.MAC;
- SystemArchitecture arch = SystemArchitecture.ARM64;
- // act
- SystemInfo systemInfo = SystemInformationMock.MAC_ARM64;
- // assert
- assertThat(systemInfo.getOs()).isSameAs(os);
- assertThat(systemInfo.getArchitecture()).isSameAs(arch);
- assertThat(systemInfo.toString()).isEqualTo("mac@arm64(Mac OS X[12.6.9]@aarch64)");
- }
-
- /** Test {@link SystemInformationMock#LINUX_X64}. */
- @Test
- public void testLinuxDetection() {
-
- // arrange
- OperatingSystem os = OperatingSystem.LINUX;
- SystemArchitecture arch = SystemArchitecture.X64;
- // act
- SystemInfo systemInfo = SystemInformationMock.LINUX_X64;
- // assert
- assertThat(systemInfo.getOs()).isSameAs(os);
- assertThat(systemInfo.getArchitecture()).isSameAs(arch);
- assertThat(systemInfo.toString()).isEqualTo("linux@x64(Linux[3.13.0-74-generic]@x64)");
- }
-
-}
+package com.devonfw.tools.ide.os;
+
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test of {@link SystemInfoImpl}.
+ */
+public class SystemInfoImplTest extends Assertions {
+
+ /** Test {@link SystemInfoImpl#SystemInfoImpl()}. */
+ @Test
+ public void testDefaultConstructor() {
+
+ // arrange
+ String osName = System.getProperty("os.name");
+ String osVersion = System.getProperty("os.version");
+ String architectureName = System.getProperty("os.arch");
+ // act
+ SystemInfo systemInfo = SystemInfoImpl.INSTANCE;
+ // assert
+ assertThat(systemInfo.getOsName()).isEqualTo(osName).isNotBlank();
+ assertThat(systemInfo.getOsVersion().toString()).isEqualTo(osVersion).isNotBlank();
+ assertThat(systemInfo.getArchitectureName()).isEqualTo(architectureName).isNotBlank();
+ }
+
+ /** Test {@link SystemInfoMock#WINDOWS_X64}. */
+ @Test
+ public void testWindowsDetection() {
+
+ // arrange
+ OperatingSystem os = OperatingSystem.WINDOWS;
+ SystemArchitecture arch = SystemArchitecture.X64;
+ // act
+ SystemInfo systemInfo = SystemInfoMock.WINDOWS_X64;
+ // assert
+ assertThat(systemInfo.getOs()).isSameAs(os);
+ assertThat(systemInfo.getArchitecture()).isSameAs(arch);
+ assertThat(systemInfo.toString()).isEqualTo("windows@x64(Windows 10[10.0]@amd64)");
+ }
+
+ /** Test {@link SystemInfoMock#MAC_X64}. */
+ @Test
+ public void testMacDetection() {
+
+ // arrange
+ OperatingSystem os = OperatingSystem.MAC;
+ SystemArchitecture arch = SystemArchitecture.X64;
+ // act
+ SystemInfo systemInfo = SystemInfoMock.MAC_X64;
+ // assert
+ assertThat(systemInfo.getOs()).isSameAs(os);
+ assertThat(systemInfo.getArchitecture()).isSameAs(arch);
+ assertThat(systemInfo.toString()).isEqualTo("mac@x64(Mac OS X[12.6.9]@x86_64)");
+ }
+
+ /** Test {@link SystemInfoMock#MAC_ARM64}. */
+ @Test
+ public void testMacArmDetection() {
+
+ // arrange
+ OperatingSystem os = OperatingSystem.MAC;
+ SystemArchitecture arch = SystemArchitecture.ARM64;
+ // act
+ SystemInfo systemInfo = SystemInfoMock.MAC_ARM64;
+ // assert
+ assertThat(systemInfo.getOs()).isSameAs(os);
+ assertThat(systemInfo.getArchitecture()).isSameAs(arch);
+ assertThat(systemInfo.toString()).isEqualTo("mac@arm64(Mac OS X[12.6.9]@aarch64)");
+ }
+
+ /** Test {@link SystemInfoMock#LINUX_X64}. */
+ @Test
+ public void testLinuxDetection() {
+
+ // arrange
+ OperatingSystem os = OperatingSystem.LINUX;
+ SystemArchitecture arch = SystemArchitecture.X64;
+ // act
+ SystemInfo systemInfo = SystemInfoMock.LINUX_X64;
+ // assert
+ assertThat(systemInfo.getOs()).isSameAs(os);
+ assertThat(systemInfo.getArchitecture()).isSameAs(arch);
+ assertThat(systemInfo.toString()).isEqualTo("linux@x64(Linux[3.13.0-74-generic]@x64)");
+ }
+
+}
diff --git a/cli/src/test/java/com/devonfw/tools/ide/os/SystemInformationMock.java b/cli/src/test/java/com/devonfw/tools/ide/os/SystemInfoMock.java
similarity index 53%
rename from cli/src/test/java/com/devonfw/tools/ide/os/SystemInformationMock.java
rename to cli/src/test/java/com/devonfw/tools/ide/os/SystemInfoMock.java
index c7546a4ec..a4dbe09c5 100644
--- a/cli/src/test/java/com/devonfw/tools/ide/os/SystemInformationMock.java
+++ b/cli/src/test/java/com/devonfw/tools/ide/os/SystemInfoMock.java
@@ -1,20 +1,44 @@
-package com.devonfw.tools.ide.os;
-
-/**
- * Mock instances of {@link SystemInfo} to test OS specific behavior independent of the current OS running the test.
- */
-public class SystemInformationMock {
-
- /** {@link OperatingSystem#WINDOWS} with {@link SystemArchitecture#X64}. */
- public static final SystemInfo WINDOWS_X64 = new SystemInfoImpl("Windows 10", "10.0", "amd64");
-
- /** {@link OperatingSystem#MAC} with {@link SystemArchitecture#X64}. */
- public static final SystemInfo MAC_X64 = new SystemInfoImpl("Mac OS X", "12.6.9", "x86_64");
-
- /** {@link OperatingSystem#MAC} with {@link SystemArchitecture#ARM64}. */
- public static final SystemInfo MAC_ARM64 = new SystemInfoImpl("Mac OS X", "12.6.9", "aarch64");
-
- /** {@link OperatingSystem#LINUX} with {@link SystemArchitecture#X64}. */
- public static final SystemInfo LINUX_X64 = new SystemInfoImpl("Linux", "3.13.0-74-generic", "x64");
-
-}
+package com.devonfw.tools.ide.os;
+
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Mock instances of {@link SystemInfo} to test OS specific behavior independent of the current OS running the test.
+ */
+public class SystemInfoMock {
+
+ /** {@link OperatingSystem#WINDOWS} with {@link SystemArchitecture#X64}. */
+ public static final SystemInfo WINDOWS_X64 = new SystemInfoImpl("Windows 10", "10.0", "amd64");
+
+ /** {@link OperatingSystem#MAC} with {@link SystemArchitecture#X64}. */
+ public static final SystemInfo MAC_X64 = new SystemInfoImpl("Mac OS X", "12.6.9", "x86_64");
+
+ /** {@link OperatingSystem#MAC} with {@link SystemArchitecture#ARM64}. */
+ public static final SystemInfo MAC_ARM64 = new SystemInfoImpl("Mac OS X", "12.6.9", "aarch64");
+
+ /** {@link OperatingSystem#LINUX} with {@link SystemArchitecture#X64}. */
+ public static final SystemInfo LINUX_X64 = new SystemInfoImpl("Linux", "3.13.0-74-generic", "x64");
+
+ private static final List MOCKS = List.of(WINDOWS_X64, MAC_X64, MAC_ARM64, LINUX_X64);
+
+ public static SystemInfo of(String osString) {
+
+ osString = osString.toLowerCase(Locale.ROOT);
+ OperatingSystem os = OperatingSystem.of(osString);
+ for (SystemInfo si : MOCKS) {
+ if (os == null) {
+ String osName = si.getOsName().toLowerCase(Locale.ROOT);
+ if (osName.contains(osString)) {
+ return si;
+ }
+ } else {
+ if (si.getOs() == os) {
+ return si;
+ }
+ }
+ }
+ throw new IllegalArgumentException("Unknown operating system: " + osString);
+ }
+
+}
diff --git a/cli/src/test/java/com/devonfw/tools/ide/os/SystemInfoMockTest.java b/cli/src/test/java/com/devonfw/tools/ide/os/SystemInfoMockTest.java
new file mode 100644
index 000000000..53c9033a5
--- /dev/null
+++ b/cli/src/test/java/com/devonfw/tools/ide/os/SystemInfoMockTest.java
@@ -0,0 +1,44 @@
+package com.devonfw.tools.ide.os;
+
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class SystemInfoMockTest extends Assertions {
+
+ /** Test {@link SystemInfoMock#of(String)} for windows. */
+ @Test
+ public void testOfWindows() {
+
+ // arrange
+ String name = "windows";
+ // act
+ SystemInfo systemInfo = SystemInfoMock.of(name);
+ // assert
+ assertThat(systemInfo).isSameAs(SystemInfoMock.WINDOWS_X64);
+ }
+
+ /** Test {@link SystemInfoMock#of(String)} for mac. */
+ @Test
+ public void testOfMac() {
+
+ // arrange
+ String name = "Mac";
+ // act
+ SystemInfo systemInfo = SystemInfoMock.of(name);
+ // assert
+ assertThat(systemInfo).isSameAs(SystemInfoMock.MAC_X64);
+ }
+
+ /** Test {@link SystemInfoMock#of(String)} for linux. */
+ @Test
+ public void testOfLinux() {
+
+ // arrange
+ String name = "linux";
+ // act
+ SystemInfo systemInfo = SystemInfoMock.of(name);
+ // assert
+ assertThat(systemInfo).isSameAs(SystemInfoMock.LINUX_X64);
+ }
+
+}
diff --git a/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java b/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java
new file mode 100644
index 000000000..5d0d4e78a
--- /dev/null
+++ b/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java
@@ -0,0 +1,216 @@
+package com.devonfw.tools.ide.process;
+
+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.log.IdeLogLevel;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.junit.platform.commons.util.ReflectionUtils;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.lang.reflect.Field;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Unit tests of {@link ProcessContextImpl}.
+ */
+public class ProcessContextImplTest extends AbstractIdeContextTest {
+
+ private ProcessContextImpl processConttextUnderTest;
+
+ private Process processMock;
+
+ private ProcessBuilder mockProcessBuilder;
+
+ private IdeContext context;
+
+ @BeforeEach
+ public void setUp() throws Exception {
+
+ mockProcessBuilder = mock(ProcessBuilder.class);
+ context = newContext(PROJECT_BASIC, null, false);
+ processConttextUnderTest = new ProcessContextImpl(context);
+
+ Field field = ReflectionUtils.findFields(ProcessContextImpl.class, f -> f.getName().equals("processBuilder"),
+ ReflectionUtils.HierarchyTraversalMode.TOP_DOWN).get(0);
+
+ field.setAccessible(true);
+ field.set(processConttextUnderTest, mockProcessBuilder);
+ field.setAccessible(false);
+
+ // underTest needs executable
+ Field underTestExecutable = ReflectionUtils.findFields(ProcessContextImpl.class,
+ f -> f.getName().equals("executable"), ReflectionUtils.HierarchyTraversalMode.TOP_DOWN).get(0);
+ underTestExecutable.setAccessible(true);
+ underTestExecutable.set(processConttextUnderTest,
+ TEST_PROJECTS.resolve("_ide/software/nonExistingBinaryForTesting"));
+ underTestExecutable.setAccessible(false);
+
+ processMock = mock(Process.class);
+ when(mockProcessBuilder.start()).thenReturn(processMock);
+
+ when(mockProcessBuilder.redirectOutput(ProcessBuilder.Redirect.PIPE)).thenReturn(mockProcessBuilder);
+ when(mockProcessBuilder.redirectError(ProcessBuilder.Redirect.PIPE)).thenReturn(mockProcessBuilder);
+ when(mockProcessBuilder.redirectOutput(ProcessBuilder.Redirect.DISCARD)).thenReturn(mockProcessBuilder);
+ when(mockProcessBuilder.redirectError(ProcessBuilder.Redirect.DISCARD)).thenReturn(mockProcessBuilder);
+ when(mockProcessBuilder.redirectOutput(ProcessBuilder.Redirect.INHERIT)).thenReturn(mockProcessBuilder);
+ when(mockProcessBuilder.redirectError(ProcessBuilder.Redirect.INHERIT)).thenReturn(mockProcessBuilder);
+
+ }
+
+ @Test
+ public void missingExecutableShouldThrowIllegalState() throws Exception {
+
+ // arrange
+ String expectedMessage = "Missing executable to run process!";
+
+ Field underTestExecutable = ReflectionUtils.findFields(ProcessContextImpl.class,
+ f -> f.getName().equals("executable"), ReflectionUtils.HierarchyTraversalMode.TOP_DOWN).get(0);
+ underTestExecutable.setAccessible(true);
+ underTestExecutable.set(processConttextUnderTest, null);
+ underTestExecutable.setAccessible(false);
+
+ // act & assert
+ Exception exception = assertThrows(IllegalStateException.class, () -> {
+ processConttextUnderTest.run(ProcessMode.DEFAULT);
+ });
+
+ String actualMessage = exception.getMessage();
+
+ assertThat(actualMessage).isEqualTo(expectedMessage);
+ }
+
+ @Test
+ public void onSuccessfulProcessStartReturnSuccessResult() throws Exception {
+
+ // arrange
+
+ when(processMock.waitFor()).thenReturn(ProcessResult.SUCCESS);
+
+ // act
+ ProcessResult result = processConttextUnderTest.run(ProcessMode.DEFAULT);
+
+ // assert
+ verify(mockProcessBuilder).redirectOutput(
+ (ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.INHERIT)));
+
+ verify(mockProcessBuilder).redirectError(
+ (ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.INHERIT)));
+ assertThat(result.isSuccessful()).isTrue();
+
+ }
+
+ @Test
+ public void enablingCaptureShouldRedirectAndCaptureStreamsCorrectly() throws Exception {
+
+ // arrange
+ when(processMock.waitFor()).thenReturn(ProcessResult.SUCCESS);
+ String outputText = "hello world";
+ String errorText = "error";
+
+ try (InputStream outputStream = new ByteArrayInputStream(outputText.getBytes());
+ InputStream errorStream = new ByteArrayInputStream(errorText.getBytes())) {
+
+ when(processMock.getInputStream()).thenReturn(outputStream);
+
+ when(processMock.getErrorStream()).thenReturn(errorStream);
+
+ // act
+ ProcessResult result = processConttextUnderTest.run(ProcessMode.DEFAULT_CAPTURE);
+
+ // assert
+ verify(mockProcessBuilder).redirectOutput(
+ (ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.PIPE)));
+
+ verify(mockProcessBuilder).redirectError(
+ (ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.PIPE)));
+
+ assertThat(outputText).isEqualTo(result.getOut().get(0));
+ assertThat(errorText).isEqualTo(result.getErr().get(0));
+ }
+ }
+
+ @ParameterizedTest
+ @EnumSource(value = ProcessMode.class, names = { "BACKGROUND", "BACKGROUND_SILENT" })
+ public void enablingBackgroundProcessShouldNotBeAwaitedAndShouldNotPassStreams(ProcessMode processMode)
+ throws Exception {
+
+ // arrange
+ when(processMock.waitFor()).thenReturn(ProcessResult.SUCCESS);
+
+ // act
+ ProcessResult result = processConttextUnderTest.run(processMode);
+
+ // assert
+ if (processMode == ProcessMode.BACKGROUND) {
+ verify(mockProcessBuilder).redirectOutput(
+ (ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.INHERIT)));
+
+ verify(mockProcessBuilder).redirectError(
+ (ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.INHERIT)));
+ } else if (processMode == ProcessMode.BACKGROUND_SILENT) {
+ verify(mockProcessBuilder).redirectOutput(
+ (ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.DISCARD)));
+
+ verify(mockProcessBuilder).redirectError(
+ (ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.DISCARD)));
+ }
+
+ verify(processMock, never()).waitFor();
+
+ assertThat(result.getOut()).isNull();
+ assertThat(result.getErr()).isNull();
+
+ }
+
+ @Test
+ public void unsuccessfulProcessShouldThrowIllegalState() throws Exception {
+
+ // arrange
+ when(processMock.waitFor()).thenReturn(ProcessResult.TOOL_NOT_INSTALLED);
+
+ processConttextUnderTest.errorHandling(ProcessErrorHandling.THROW);
+
+ // act & assert
+ assertThrows(IllegalStateException.class, () -> {
+ processConttextUnderTest.run(ProcessMode.DEFAULT);
+ });
+
+ }
+
+ @ParameterizedTest
+ @EnumSource(value = ProcessErrorHandling.class, names = { "WARNING", "ERROR" })
+ public void ProcessWarningAndErrorShouldBeLogged(ProcessErrorHandling processErrorHandling) throws Exception {
+
+ // arrange
+ when(processMock.waitFor()).thenReturn(ProcessResult.TOOL_NOT_INSTALLED);
+ processConttextUnderTest.errorHandling(processErrorHandling);
+ String expectedMessage = "failed with exit code 4!";
+ // act
+ processConttextUnderTest.run(ProcessMode.DEFAULT);
+
+ // assert
+ IdeLogLevel level = convertToIdeLogLevel(processErrorHandling);
+ assertLogMessage((IdeTestContext) context, level, expectedMessage, true);
+ }
+
+ private IdeLogLevel convertToIdeLogLevel(ProcessErrorHandling processErrorHandling) {
+
+ return switch (processErrorHandling) {
+ case NONE, THROW -> null;
+ case WARNING -> IdeLogLevel.WARNING;
+ case ERROR -> IdeLogLevel.ERROR;
+ };
+ }
+
+}
diff --git a/cli/src/test/java/com/devonfw/tools/ide/repo/CustomToolTest.java b/cli/src/test/java/com/devonfw/tools/ide/repo/CustomToolTest.java
index bc1e922f3..4eaa3e42c 100644
--- a/cli/src/test/java/com/devonfw/tools/ide/repo/CustomToolTest.java
+++ b/cli/src/test/java/com/devonfw/tools/ide/repo/CustomToolTest.java
@@ -1,67 +1,66 @@
-package com.devonfw.tools.ide.repo;
-
-import org.assertj.core.api.Assertions;
-import org.junit.jupiter.api.Test;
-
-import com.devonfw.tools.ide.os.SystemInformationMock;
-import com.devonfw.tools.ide.version.VersionIdentifier;
-
-/**
- * Test of {@link CustomTool}.
- */
-public class CustomToolTest extends Assertions {
-
- /**
- * Test of {@link CustomTool}.
- */
- @Test
- public void testAgnostic() {
-
- // arrange
- String name = "jboss-eap";
- VersionIdentifier version = VersionIdentifier.of("7.4.5.GA");
- String repositoryUrl = "https://host.domain.tld:8443/folder/repo";
- String checksum = "4711";
- boolean osAgnostic = true;
- boolean archAgnostic = true;
- // act
- CustomTool tool = new CustomTool(name, version, osAgnostic, archAgnostic, repositoryUrl, checksum, null);
- // assert
- assertThat(tool.getTool()).isEqualTo(name);
- assertThat(tool.getVersion()).isSameAs(version);
- assertThat(tool.isOsAgnostic()).isEqualTo(osAgnostic);
- assertThat(tool.isArchAgnostic()).isEqualTo(archAgnostic);
- assertThat(tool.getRepositoryUrl()).isEqualTo(repositoryUrl);
- assertThat(tool.getUrl())
- .isEqualTo("https://host.domain.tld:8443/folder/repo/jboss-eap/7.4.5.GA/jboss-eap-7.4.5.GA.tgz");
- assertThat(tool.getChecksum()).isEqualTo(checksum);
- }
-
- /**
- * Test of {@link CustomTool}.
- */
- @Test
- public void testSpecific() {
-
- // arrange
- String name = "firefox";
- VersionIdentifier version = VersionIdentifier.of("70.0.1");
- String repositoryUrl = "https://host.domain.tld:8443/folder/repo";
- String checksum = "4711";
- boolean osAgnostic = false;
- boolean archAgnostic = true;
- // act
- CustomTool tool = new CustomTool(name, version, osAgnostic, archAgnostic, repositoryUrl, checksum,
- SystemInformationMock.WINDOWS_X64);
- // assert
- assertThat(tool.getTool()).isEqualTo(name);
- assertThat(tool.getVersion()).isSameAs(version);
- assertThat(tool.isOsAgnostic()).isEqualTo(osAgnostic);
- assertThat(tool.isArchAgnostic()).isEqualTo(archAgnostic);
- assertThat(tool.getRepositoryUrl()).isEqualTo(repositoryUrl);
- assertThat(tool.getUrl())
- .isEqualTo("https://host.domain.tld:8443/folder/repo/firefox/70.0.1/firefox-70.0.1-windows.tgz");
- assertThat(tool.getChecksum()).isEqualTo(checksum);
- }
-
-}
+package com.devonfw.tools.ide.repo;
+
+import com.devonfw.tools.ide.os.SystemInfoMock;
+import com.devonfw.tools.ide.version.VersionIdentifier;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test of {@link CustomTool}.
+ */
+public class CustomToolTest extends Assertions {
+
+ /**
+ * Test of {@link CustomTool}.
+ */
+ @Test
+ public void testAgnostic() {
+
+ // arrange
+ String name = "jboss-eap";
+ VersionIdentifier version = VersionIdentifier.of("7.4.5.GA");
+ String repositoryUrl = "https://host.domain.tld:8443/folder/repo";
+ String checksum = "4711";
+ boolean osAgnostic = true;
+ boolean archAgnostic = true;
+ // act
+ CustomTool tool = new CustomTool(name, version, osAgnostic, archAgnostic, repositoryUrl, checksum, null);
+ // assert
+ assertThat(tool.getTool()).isEqualTo(name);
+ assertThat(tool.getVersion()).isSameAs(version);
+ assertThat(tool.isOsAgnostic()).isEqualTo(osAgnostic);
+ assertThat(tool.isArchAgnostic()).isEqualTo(archAgnostic);
+ assertThat(tool.getRepositoryUrl()).isEqualTo(repositoryUrl);
+ assertThat(tool.getUrl()).isEqualTo(
+ "https://host.domain.tld:8443/folder/repo/jboss-eap/7.4.5.GA/jboss-eap-7.4.5.GA.tgz");
+ assertThat(tool.getChecksum()).isEqualTo(checksum);
+ }
+
+ /**
+ * Test of {@link CustomTool}.
+ */
+ @Test
+ public void testSpecific() {
+
+ // arrange
+ String name = "firefox";
+ VersionIdentifier version = VersionIdentifier.of("70.0.1");
+ String repositoryUrl = "https://host.domain.tld:8443/folder/repo";
+ String checksum = "4711";
+ boolean osAgnostic = false;
+ boolean archAgnostic = true;
+ // act
+ CustomTool tool = new CustomTool(name, version, osAgnostic, archAgnostic, repositoryUrl, checksum,
+ SystemInfoMock.WINDOWS_X64);
+ // assert
+ assertThat(tool.getTool()).isEqualTo(name);
+ assertThat(tool.getVersion()).isSameAs(version);
+ assertThat(tool.isOsAgnostic()).isEqualTo(osAgnostic);
+ assertThat(tool.isArchAgnostic()).isEqualTo(archAgnostic);
+ assertThat(tool.getRepositoryUrl()).isEqualTo(repositoryUrl);
+ assertThat(tool.getUrl()).isEqualTo(
+ "https://host.domain.tld:8443/folder/repo/firefox/70.0.1/firefox-70.0.1-windows.tgz");
+ assertThat(tool.getChecksum()).isEqualTo(checksum);
+ }
+
+}
diff --git a/cli/src/test/java/com/devonfw/tools/ide/repo/ToolRepositoryMock.java b/cli/src/test/java/com/devonfw/tools/ide/repo/ToolRepositoryMock.java
new file mode 100644
index 000000000..b12f203c8
--- /dev/null
+++ b/cli/src/test/java/com/devonfw/tools/ide/repo/ToolRepositoryMock.java
@@ -0,0 +1,96 @@
+package com.devonfw.tools.ide.repo;
+
+import com.devonfw.tools.ide.context.IdeContext;
+import com.devonfw.tools.ide.version.VersionIdentifier;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Iterator;
+import java.util.stream.Stream;
+
+/**
+ * Implementation of {@link ToolRepository} for testing.
+ */
+public class ToolRepositoryMock implements ToolRepository {
+
+ private static final Path PROJECTS_TARGET_PATH = Path.of("target/test-projects");
+
+ private static final String MOCK_DOWNLOAD_FOLDER = "repository";
+
+ private final Path repositoryFolder;
+
+ private IdeContext context;
+
+ /**
+ * The constructor.
+ *
+ * @param repositoryFolder the {@link Path} to the mock repository.
+ */
+ public ToolRepositoryMock(Path repositoryFolder) {
+
+ super();
+ this.repositoryFolder = repositoryFolder;
+ }
+
+ public void setContext(IdeContext context) {
+
+ this.context = context;
+
+ }
+
+ @Override
+ public String getId() {
+
+ return ID_DEFAULT;
+ }
+
+ @Override
+ public VersionIdentifier resolveVersion(String tool, String edition, VersionIdentifier version) {
+
+ return version;
+ }
+
+ @Override
+ public Path download(String tool, String edition, VersionIdentifier version) {
+
+ Path editionFolder = this.repositoryFolder.resolve(tool).resolve(edition);
+ Path versionFolder = editionFolder.resolve(version.toString());
+ if (!Files.isDirectory(versionFolder)) {
+ this.context.debug("Could not find version {} so using 'default' for {}/{}", version, tool, edition);
+ versionFolder = editionFolder.resolve("default");
+ }
+ if (!Files.isDirectory(versionFolder)) {
+ throw new IllegalStateException("Mock download failed - could not find folder " + editionFolder);
+ }
+ Path archiveFolder = versionFolder.resolve(this.context.getSystemInfo().getOs().toString());
+ if (!Files.isDirectory(archiveFolder)) {
+ archiveFolder = versionFolder;
+ }
+ Path contentArchive = null;
+ try (Stream children = Files.list(archiveFolder)) {
+ Iterator iterator = children.iterator();
+ while (iterator.hasNext()) {
+ if (contentArchive == null) {
+ Path child = iterator.next();
+ if (Files.isRegularFile(child) && child.getFileName().startsWith("content.")) {
+ contentArchive = child;
+ this.context.debug("Using compressed archive {} for mock download of {}/{}", child.getFileName(), tool,
+ edition);
+ } else {
+ break;
+ }
+ } else {
+ contentArchive = null;
+ break;
+ }
+ }
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to list children of folder " + archiveFolder);
+ }
+ if (contentArchive != null) {
+ return contentArchive;
+ }
+ return archiveFolder;
+ }
+}
diff --git a/cli/src/test/java/com/devonfw/tools/ide/tool/PluginBasedCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/tool/PluginBasedCommandletTest.java
index 19a48573f..c856b938f 100644
--- a/cli/src/test/java/com/devonfw/tools/ide/tool/PluginBasedCommandletTest.java
+++ b/cli/src/test/java/com/devonfw/tools/ide/tool/PluginBasedCommandletTest.java
@@ -1,45 +1,44 @@
-package com.devonfw.tools.ide.tool;
-
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-
-import java.util.Map;
-import java.util.Set;
-
-import org.junit.jupiter.api.Test;
-
-import com.devonfw.tools.ide.common.Tag;
-import com.devonfw.tools.ide.context.AbstractIdeContextTest;
-import com.devonfw.tools.ide.context.IdeTestContext;
-import com.devonfw.tools.ide.tool.ide.PluginDescriptor;
-
-/**
- * Test of {@link PluginBasedCommandlet}.
- */
-public class PluginBasedCommandletTest extends AbstractIdeContextTest {
-
- @Test
- void testGetPluginsMap() {
-
- IdeTestContext context = newContext(PROJECT_BASIC, "", true);
- String tool = "eclipse";
- Set tags = null;
- ExamplePluginBasedCommandlet pluginBasedCommandlet = new ExamplePluginBasedCommandlet(context, tool, tags);
-
- Map pluginsMap = pluginBasedCommandlet.getPluginsMap();
- assertThat(pluginsMap).isNotNull();
-
- assertThat(pluginsMap.containsKey("checkstyle")).isTrue();
- assertThat(pluginsMap.containsKey("anyedit")).isTrue();
-
- PluginDescriptor plugin1 = pluginsMap.get("checkstyle");
- assertNotNull(plugin1);
- assertThat(plugin1.getName()).isEqualTo("checkstyle");
-
- PluginDescriptor plugin2 = pluginsMap.get("anyedit");
- assertNotNull(plugin2);
- assertThat(plugin2.getName()).isEqualTo("anyedit");
-
- // Check if anyedit plugin has value "false" --> value from user directory
- assertThat(plugin2.isActive()).isFalse();
- }
-}
+package com.devonfw.tools.ide.tool;
+
+import com.devonfw.tools.ide.common.Tag;
+import com.devonfw.tools.ide.context.AbstractIdeContextTest;
+import com.devonfw.tools.ide.context.IdeTestContext;
+import com.devonfw.tools.ide.tool.ide.PluginDescriptor;
+import org.junit.jupiter.api.Test;
+
+import java.util.Map;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+/**
+ * Test of {@link PluginBasedCommandlet}.
+ */
+public class PluginBasedCommandletTest extends AbstractIdeContextTest {
+
+ @Test
+ void testGetPluginsMap() {
+
+ IdeTestContext context = newContext(PROJECT_BASIC, null, false);
+ String tool = "eclipse";
+ Set tags = null;
+ ExamplePluginBasedCommandlet pluginBasedCommandlet = new ExamplePluginBasedCommandlet(context, tool, tags);
+
+ Map pluginsMap = pluginBasedCommandlet.getPluginsMap();
+ assertThat(pluginsMap).isNotNull();
+
+ assertThat(pluginsMap.containsKey("checkstyle")).isTrue();
+ assertThat(pluginsMap.containsKey("anyedit")).isTrue();
+
+ PluginDescriptor plugin1 = pluginsMap.get("checkstyle");
+ assertNotNull(plugin1);
+ assertThat(plugin1.getName()).isEqualTo("checkstyle");
+
+ PluginDescriptor plugin2 = pluginsMap.get("anyedit");
+ assertNotNull(plugin2);
+ assertThat(plugin2.getName()).isEqualTo("anyedit");
+
+ // Check if anyedit plugin has value "false" --> value from user directory
+ assertThat(plugin2.isActive()).isFalse();
+ }
+}
diff --git a/cli/src/test/java/com/devonfw/tools/ide/tool/jasypt/JasyptTest.java b/cli/src/test/java/com/devonfw/tools/ide/tool/jasypt/JasyptTest.java
new file mode 100644
index 000000000..32eb612af
--- /dev/null
+++ b/cli/src/test/java/com/devonfw/tools/ide/tool/jasypt/JasyptTest.java
@@ -0,0 +1,75 @@
+package com.devonfw.tools.ide.tool.jasypt;
+
+import com.devonfw.tools.ide.commandlet.InstallCommandlet;
+import com.devonfw.tools.ide.context.AbstractIdeContextTest;
+import com.devonfw.tools.ide.context.IdeTestContext;
+import com.devonfw.tools.ide.log.IdeLogLevel;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Integration test of {@link Jasypt}.
+ */
+public class JasyptTest extends AbstractIdeContextTest {
+
+ private static final String PROJECT_JASYPT = "jasypt";
+
+ @Test
+ public void testJasyptInstallCommandlet() {
+
+ // arrange
+ IdeTestContext context = newContext(PROJECT_JASYPT);
+ InstallCommandlet install = context.getCommandletManager().getCommandlet(InstallCommandlet.class);
+ install.tool.setValueAsString("jasypt", context);
+ // act
+ install.run();
+
+ // assert
+ checkInstallation(context);
+ }
+
+ @Test
+ public void testJasyptInstall() {
+
+ // arrange
+ IdeTestContext context = newContext(PROJECT_JASYPT);
+
+ Jasypt commandlet = new Jasypt(context);
+
+ // act
+ commandlet.install();
+
+ // assert
+ checkInstallation(context);
+ }
+
+ @Test
+ public void testJasyptRun() {
+
+ // arrange
+ IdeTestContext context = newContext(PROJECT_JASYPT);
+ Jasypt commandlet = new Jasypt(context);
+
+ commandlet.command.setValue(JasyptCommand.ENCRYPT);
+ commandlet.masterPassword.setValue("password");
+ commandlet.secret.setValue("input");
+
+ // act
+ commandlet.run();
+
+ // assert
+ assertLogMessage(context, IdeLogLevel.INFO, "executing java:");
+ assertLogMessage(context, IdeLogLevel.INFO, "This is a jar file.");
+ checkInstallation(context);
+ }
+
+ private void checkInstallation(IdeTestContext context) {
+
+ // install - java
+ assertThat(context.getSoftwarePath().resolve("java/bin/java")).exists();
+
+ // commandlet - jasypt
+ assertThat(context.getSoftwarePath().resolve("jasypt/jasypt-1.9.3.jar")).hasContent("This is a jar file.");
+ assertThat(context.getSoftwarePath().resolve("jasypt/.ide.software.version")).exists().hasContent("1.9.3");
+ assertLogMessage(context, IdeLogLevel.SUCCESS, "Successfully installed jasypt in version 1.9.3");
+ }
+}
diff --git a/cli/src/test/java/com/devonfw/tools/ide/tool/jmc/JmcTest.java b/cli/src/test/java/com/devonfw/tools/ide/tool/jmc/JmcTest.java
index 59477d1de..5de3c5b93 100644
--- a/cli/src/test/java/com/devonfw/tools/ide/tool/jmc/JmcTest.java
+++ b/cli/src/test/java/com/devonfw/tools/ide/tool/jmc/JmcTest.java
@@ -1,115 +1,85 @@
package com.devonfw.tools.ide.tool.jmc;
-import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
-import static com.github.tomakehurst.wiremock.client.WireMock.get;
-import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
-
import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
+import java.util.List;
-import org.junit.jupiter.api.AfterAll;
-import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
import com.devonfw.tools.ide.commandlet.InstallCommandlet;
import com.devonfw.tools.ide.context.AbstractIdeContextTest;
-import com.devonfw.tools.ide.context.IdeContext;
-import com.github.tomakehurst.wiremock.WireMockServer;
-import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
+import com.devonfw.tools.ide.context.IdeTestContext;
+import com.devonfw.tools.ide.log.IdeLogLevel;
+import com.devonfw.tools.ide.os.SystemInfo;
+import com.devonfw.tools.ide.os.SystemInfoMock;
/**
- * Integration test of {@link com.devonfw.tools.ide.tool.jmc.Jmc}.
+ * Integration test of {@link Jmc}.
*/
public class JmcTest extends AbstractIdeContextTest {
- private static WireMockServer server;
-
- private static Path resourcePath = Path.of("src/test/resources");
+ private static final String PROJECT_JMC = "jmc";
- @BeforeAll
- static void setUp() throws IOException {
- server = new WireMockServer(WireMockConfiguration.wireMockConfig().port(1112));
- server.start();
- }
+ @Test
+ public void testJmcInstallCommandlet() throws IOException {
- @AfterAll
- static void tearDown() throws IOException {
+ // arrange
+ IdeTestContext context = newContext(PROJECT_JMC);
+ InstallCommandlet install = context.getCommandletManager().getCommandlet(InstallCommandlet.class);
+ install.tool.setValueAsString("jmc", context);
+ // act
+ install.run();
- server.shutdownServer();
+ // assert
+ checkInstallation(context);
}
- private void mockWebServer() throws IOException {
-
- String windowsFilenameJmc = "org.openjdk.jmc-8.3.0-win32.win32.x86_64.zip";
- String linuxFilenameJmc = "org.openjdk.jmc-8.3.0-linux.gtk.x86_64.tar.gz";
- String macOSFilenameJmc = "org.openjdk.jmc-8.3.0-macosx.cocoa.x86_64.tar.gz";
- String windowsFilenameJava = "java-17.0.6-windows-x64.zip";
- String linuxFilenameJava = "java-17.0.6-linux-x64.tgz";
- String resourceFilesDirName = "__files";
-
- Path windowsFilePathJmc = resourcePath.resolve(resourceFilesDirName).resolve(windowsFilenameJmc);
- String windowsLengthJmc = String.valueOf(Files.size(windowsFilePathJmc));
-
- Path linuxFilePathJmc = resourcePath.resolve(resourceFilesDirName).resolve(linuxFilenameJmc);
- String linuxLengthJmc = String.valueOf(Files.size(linuxFilePathJmc));
-
- Path macOSFilePathJmc = resourcePath.resolve(resourceFilesDirName).resolve(macOSFilenameJmc);
- String maxOSLengthJmc = String.valueOf(Files.size(macOSFilePathJmc));
-
- Path windowsFilePathJava = resourcePath.resolve(resourceFilesDirName).resolve(windowsFilenameJava);
- String windowsLengthJava = String.valueOf(Files.size(windowsFilePathJava));
-
- Path linuxFilePathJava = resourcePath.resolve(resourceFilesDirName).resolve(linuxFilenameJava);
- String linuxLengthJava = String.valueOf(Files.size(linuxFilePathJava));
+ @Test
+ public void testJmcInstall() {
- setupMockServerResponse("/jmcTest/windows", "application/zip", windowsLengthJmc, windowsFilenameJmc);
- setupMockServerResponse("/jmcTest/linux", "application/gz", linuxLengthJmc, linuxFilenameJmc);
- setupMockServerResponse("/jmcTest/macOS", "application/gz", maxOSLengthJmc, macOSFilenameJmc);
- setupMockServerResponse("/installTest/windows", "application/zip", windowsLengthJava, windowsFilenameJava);
- setupMockServerResponse("/installTest/linux", "application/tgz", linuxLengthJava, linuxFilenameJava);
- setupMockServerResponse("/installTest/macOS", "application/tgz", linuxLengthJava, linuxFilenameJava);
+ // arrange
+ IdeTestContext context = newContext(PROJECT_JMC);
- }
+ Jmc commandlet = new Jmc(context);
- private void setupMockServerResponse(String testUrl, String contentType, String contentLength, String bodyFile) {
+ // act
+ commandlet.install();
- server.stubFor(get(urlPathEqualTo(testUrl)).willReturn(aResponse().withHeader("Content-Type", contentType)
- .withHeader("Content-Length", contentLength).withStatus(200).withBodyFile(bodyFile)));
+ // assert
+ checkInstallation(context);
}
- @Test
- public void jmcPostInstallShouldMoveFilesIfRequired() throws IOException {
+ @ParameterizedTest
+ @ValueSource(strings = { "windows", "mac", "linux" })
+ public void testJmcRun(String os) {
// arrange
- String path = "workspaces/foo-test/my-git-repo";
- IdeContext context = newContext("basic", path, true);
- InstallCommandlet install = context.getCommandletManager().getCommandlet(InstallCommandlet.class);
- install.tool.setValueAsString("jmc", context);
- mockWebServer();
+ IdeTestContext context = newContext(PROJECT_JMC);
+ SystemInfo systemInfo = SystemInfoMock.of(os);
+ context.setSystemInfo(systemInfo);
+ Jmc commandlet = new Jmc(context);
+ commandlet.arguments.setValue(List.of("foo", "bar"));
// act
- install.run();
+ commandlet.run();
// assert
- assertThat(context.getSoftwarePath().resolve("jmc")).exists();
- assertThat(context.getSoftwarePath().resolve("jmc/InstallTest.txt")).hasContent("This is a test file.");
+ assertLogMessage(context, IdeLogLevel.INFO, "java jmc");
+ assertLogMessage(context, IdeLogLevel.INFO, "jmc " + os + " foo bar");
+ checkInstallation(context);
+ }
- if (context.getSystemInfo().isWindows()) {
- assertThat(context.getSoftwarePath().resolve("jmc/jmc.cmd")).exists();
- } else if (context.getSystemInfo().isLinux()) {
- assertThat(context.getSoftwarePath().resolve("jmc/jmc")).exists();
- }
+ private void checkInstallation(IdeTestContext context) {
+
+ assertThat(context.getSoftwarePath().resolve("java/bin/java")).exists();
if (context.getSystemInfo().isWindows() || context.getSystemInfo().isLinux()) {
assertThat(context.getSoftwarePath().resolve("jmc/HelloWorld.txt")).hasContent("Hello World!");
assertThat(context.getSoftwarePath().resolve("jmc/JDK Mission Control")).doesNotExist();
+ } else if (context.getSystemInfo().isMac()) {
+ assertThat(context.getSoftwarePath().resolve("jmc/jmc")).exists();
}
-
- if (context.getSystemInfo().isMac()) {
- assertThat(context.getSoftwarePath().resolve("jmc/JDK Mission Control.app")).exists();
- assertThat(context.getSoftwarePath().resolve("jmc/JDK Mission Control.app/Contents")).exists();
- }
-
+ assertThat(context.getSoftwarePath().resolve("jmc/.ide.software.version")).exists().hasContent("8.3.0");
+ assertLogMessage(context, IdeLogLevel.SUCCESS, "Successfully installed jmc in version 8.3.0");
}
-
}
diff --git a/cli/src/test/java/com/devonfw/tools/ide/tool/npm/NpmTest.java b/cli/src/test/java/com/devonfw/tools/ide/tool/npm/NpmTest.java
new file mode 100644
index 000000000..e8e37df20
--- /dev/null
+++ b/cli/src/test/java/com/devonfw/tools/ide/tool/npm/NpmTest.java
@@ -0,0 +1,49 @@
+package com.devonfw.tools.ide.tool.npm;
+
+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.IdeTestContext;
+import com.devonfw.tools.ide.log.IdeLogLevel;
+import com.devonfw.tools.ide.os.SystemInfo;
+import com.devonfw.tools.ide.os.SystemInfoMock;
+
+public class NpmTest extends AbstractIdeContextTest {
+
+ private static final String PROJECT_NPM = "npm";
+
+ @ParameterizedTest
+ @ValueSource(strings = { "windows", "mac", "linux" })
+ public void testNpmInstall(String os) {
+
+ // arrange
+ IdeTestContext context = newContext(PROJECT_NPM);
+ SystemInfo systemInfo = SystemInfoMock.of(os);
+ context.setSystemInfo(systemInfo);
+ Npm commandlet = new Npm(context);
+
+ // act
+ commandlet.install();
+
+ // assert
+ checkInstallation(context);
+ }
+
+ private void checkInstallation(IdeTestContext context) {
+
+ if (context.getSystemInfo().isWindows()) {
+ assertThat(context.getSoftwarePath().resolve("node/npm")).exists()
+ .hasContent("#!/bin/bash\n" + "echo \"npmbin $*\"");
+ assertThat(context.getSoftwarePath().resolve("node/npm.cmd")).exists()
+ .hasContent("@echo off\n" + "echo npmcmdbin %*");
+ assertThat(context.getSoftwarePath().resolve("node/npx")).exists()
+ .hasContent("#!/bin/bash\n" + "echo \"npxbin $*\"");
+ assertThat(context.getSoftwarePath().resolve("node/npx.cmd")).exists()
+ .hasContent("@echo off\n" + "echo npxcmdbin %*");
+ }
+
+ assertThat(context.getSoftwarePath().resolve("npm/.ide.software.version")).exists().hasContent("9.9.2");
+ assertLogMessage(context, IdeLogLevel.SUCCESS, "Successfully installed npm in version 9.9.2");
+ }
+}
\ No newline at end of file
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 a5237708a..9baf603ed 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,31 +1,32 @@
-package com.devonfw.tools.ide.util;
-
-import org.assertj.core.api.Assertions;
-import org.junit.jupiter.api.Test;
-
-/**
- * Test of {@link FilenameUtil}.
- */
-public class FilenameUtilTest extends Assertions {
-
- /**
- * Test of {@link FilenameUtil#getExtension(String)}.
- */
- @Test
- public void testGetExtension() {
-
- assertThat(FilenameUtil.getExtension("foo_zip")).isNull();
- assertThat(FilenameUtil.getExtension("foo.zip")).isEqualTo("zip");
- assertThat(FilenameUtil.getExtension("FOO.ZIP")).isEqualTo("zip");
- assertThat(FilenameUtil.getExtension("file.name.tar.gz")).isEqualTo("tar.gz");
- assertThat(FilenameUtil.getExtension("file.name.Tar.Gz")).isEqualTo("tar.gz");
- assertThat(FilenameUtil.getExtension("FILE.NAME.TAR.GZ")).isEqualTo("tar.gz");
- assertThat(FilenameUtil.getExtension("https://server.com/")).isNull();
- assertThat(FilenameUtil.getExtension("https://server.com/folder.zip")).isEqualTo("zip");
- assertThat(FilenameUtil.getExtension("https://server.com/folder.zip/")).isNull();
- assertThat(FilenameUtil.getExtension("https://server.com/folder.zip/file")).isNull();
- assertThat(FilenameUtil.getExtension("https://server.com/folder.zip/app.dmg")).isEqualTo("dmg");
- assertThat(FilenameUtil.getExtension("https://server.com/folder.zip/file.tar.bz2")).isEqualTo("tar.bz2");
- }
-
-}
+package com.devonfw.tools.ide.util;
+
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test of {@link FilenameUtil}.
+ */
+public class FilenameUtilTest extends Assertions {
+
+ /**
+ * Test of {@link FilenameUtil#getExtension(String)}.
+ */
+ @Test
+ public void testGetExtension() {
+
+ assertThat(FilenameUtil.getExtension("foo_zip")).isNull();
+ assertThat(FilenameUtil.getExtension("foo.zip")).isEqualTo("zip");
+ assertThat(FilenameUtil.getExtension("FOO.ZIP")).isEqualTo("zip");
+ assertThat(FilenameUtil.getExtension("file.name.tar.gz")).isEqualTo("tar.gz");
+ assertThat(FilenameUtil.getExtension("file.name.Tar.Gz")).isEqualTo("tar.gz");
+ assertThat(FilenameUtil.getExtension("FILE.NAME.TAR.GZ")).isEqualTo("tar.gz");
+ assertThat(FilenameUtil.getExtension("windows\\tool\\1.0\\script")).isNull();
+ assertThat(FilenameUtil.getExtension("https://server.com/")).isNull();
+ assertThat(FilenameUtil.getExtension("https://server.com/folder.zip")).isEqualTo("zip");
+ assertThat(FilenameUtil.getExtension("https://server.com/folder.zip/")).isNull();
+ assertThat(FilenameUtil.getExtension("https://server.com/folder.zip/file")).isNull();
+ assertThat(FilenameUtil.getExtension("https://server.com/folder.zip/app.dmg")).isEqualTo("dmg");
+ assertThat(FilenameUtil.getExtension("https://server.com/folder.zip/file.tar.bz2")).isEqualTo("tar.bz2");
+ }
+
+}
diff --git a/cli/src/test/resources/ide-projects/basic/_ide/urls/jmc/jmc/8.3.0/linux_x64.sha256 b/cli/src/test/resources/ide-projects/basic/_ide/urls/jmc/jmc/8.3.0/linux_x64.sha256
deleted file mode 100644
index ba966d272..000000000
--- a/cli/src/test/resources/ide-projects/basic/_ide/urls/jmc/jmc/8.3.0/linux_x64.sha256
+++ /dev/null
@@ -1 +0,0 @@
-cf666655da9bc097a7413af6cc5e9d930bc1f9267410613707f5e4aa724e3bf9
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/basic/_ide/urls/jmc/jmc/8.3.0/linux_x64.urls b/cli/src/test/resources/ide-projects/basic/_ide/urls/jmc/jmc/8.3.0/linux_x64.urls
deleted file mode 100644
index 57920ab67..000000000
--- a/cli/src/test/resources/ide-projects/basic/_ide/urls/jmc/jmc/8.3.0/linux_x64.urls
+++ /dev/null
@@ -1 +0,0 @@
-http://localhost:1112/jmcTest/linux
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/basic/_ide/urls/jmc/jmc/8.3.0/mac_x64.sha256 b/cli/src/test/resources/ide-projects/basic/_ide/urls/jmc/jmc/8.3.0/mac_x64.sha256
deleted file mode 100644
index 628ce170c..000000000
--- a/cli/src/test/resources/ide-projects/basic/_ide/urls/jmc/jmc/8.3.0/mac_x64.sha256
+++ /dev/null
@@ -1 +0,0 @@
-44036f764b9b3ac0e788499ab9f3746bfac47ed09f4c464423a582f5698cabc8
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/basic/_ide/urls/jmc/jmc/8.3.0/mac_x64.urls b/cli/src/test/resources/ide-projects/basic/_ide/urls/jmc/jmc/8.3.0/mac_x64.urls
deleted file mode 100644
index 9d3d0a85c..000000000
--- a/cli/src/test/resources/ide-projects/basic/_ide/urls/jmc/jmc/8.3.0/mac_x64.urls
+++ /dev/null
@@ -1 +0,0 @@
-http://localhost:1112/jmcTest/macOS
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/basic/_ide/urls/jmc/jmc/8.3.0/status.json b/cli/src/test/resources/ide-projects/basic/_ide/urls/jmc/jmc/8.3.0/status.json
deleted file mode 100644
index b58452d90..000000000
--- a/cli/src/test/resources/ide-projects/basic/_ide/urls/jmc/jmc/8.3.0/status.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "manual" : true,
- "urls" : {
- "-680270697" : {
- "success" : {
- "timestamp" : "2023-04-28T16:27:32.819394600Z"
- }
- },
- "-896197542" : {
- "success" : {
- "timestamp" : "2023-04-28T16:27:47.658175400Z"
- }
- },
- "-310367019" : {
- "success" : {
- "timestamp" : "2023-04-28T16:28:02.221367500Z"
- }
- }
- }
-}
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/basic/_ide/urls/jmc/jmc/8.3.0/windows_x64.urls b/cli/src/test/resources/ide-projects/basic/_ide/urls/jmc/jmc/8.3.0/windows_x64.urls
deleted file mode 100644
index 520aaac8d..000000000
--- a/cli/src/test/resources/ide-projects/basic/_ide/urls/jmc/jmc/8.3.0/windows_x64.urls
+++ /dev/null
@@ -1 +0,0 @@
-http://localhost:1112/jmcTest/windows
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/basic/_ide/urls/jmc/jmc/8.3.0/windows_x64.urls.sha256 b/cli/src/test/resources/ide-projects/basic/_ide/urls/jmc/jmc/8.3.0/windows_x64.urls.sha256
deleted file mode 100644
index 83cc5866b..000000000
--- a/cli/src/test/resources/ide-projects/basic/_ide/urls/jmc/jmc/8.3.0/windows_x64.urls.sha256
+++ /dev/null
@@ -1 +0,0 @@
-5cbb836ceb159788f03aed5d2da9debb8fa269139dc0e1f6ffff671ac5367e6b
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/jasypt/_ide/urls/readme b/cli/src/test/resources/ide-projects/jasypt/_ide/urls/readme
new file mode 100644
index 000000000..befcdfa75
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/jasypt/_ide/urls/readme
@@ -0,0 +1 @@
+this is the download metadata
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/jasypt/project/home/.ide/ide.properties b/cli/src/test/resources/ide-projects/jasypt/project/home/.ide/ide.properties
new file mode 100644
index 000000000..e69de29bb
diff --git a/cli/src/test/resources/ide-projects/jasypt/project/home/readme b/cli/src/test/resources/ide-projects/jasypt/project/home/readme
new file mode 100644
index 000000000..5e8bc178c
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/jasypt/project/home/readme
@@ -0,0 +1 @@
+this is the users HOME directory
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/jasypt/project/readme b/cli/src/test/resources/ide-projects/jasypt/project/readme
new file mode 100644
index 000000000..256f5732c
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/jasypt/project/readme
@@ -0,0 +1 @@
+this is the IDE_HOME directory
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/jasypt/project/settings/ide.properties b/cli/src/test/resources/ide-projects/jasypt/project/settings/ide.properties
new file mode 100644
index 000000000..29faea4db
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/jasypt/project/settings/ide.properties
@@ -0,0 +1,2 @@
+JAVA_VERSION=17.0.10_7
+JASYPT_VERSION=1.9.3
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/jasypt/project/workspaces/main/readme b/cli/src/test/resources/ide-projects/jasypt/project/workspaces/main/readme
new file mode 100644
index 000000000..f04b5be39
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/jasypt/project/workspaces/main/readme
@@ -0,0 +1 @@
+this is the main workspace of jmc test case
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/jasypt/readme b/cli/src/test/resources/ide-projects/jasypt/readme
new file mode 100644
index 000000000..15b91829e
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/jasypt/readme
@@ -0,0 +1 @@
+this is the IDE_ROOT directory
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/jasypt/repository/jasypt/jasypt/default/jasypt-1.9.3.jar b/cli/src/test/resources/ide-projects/jasypt/repository/jasypt/jasypt/default/jasypt-1.9.3.jar
new file mode 100644
index 000000000..16f4318e4
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/jasypt/repository/jasypt/jasypt/default/jasypt-1.9.3.jar
@@ -0,0 +1 @@
+This is a jar file.
diff --git a/cli/src/test/resources/ide-projects/jasypt/repository/java/java/default/bin/java b/cli/src/test/resources/ide-projects/jasypt/repository/java/java/default/bin/java
new file mode 100755
index 000000000..655040e33
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/jasypt/repository/java/java/default/bin/java
@@ -0,0 +1,3 @@
+#!/bin/bash
+echo "executing java:"
+cat $2 # .jar file
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/jmc/_ide/urls/readme b/cli/src/test/resources/ide-projects/jmc/_ide/urls/readme
new file mode 100644
index 000000000..befcdfa75
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/jmc/_ide/urls/readme
@@ -0,0 +1 @@
+this is the download metadata
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/jmc/project/home/.ide/ide.properties b/cli/src/test/resources/ide-projects/jmc/project/home/.ide/ide.properties
new file mode 100644
index 000000000..e69de29bb
diff --git a/cli/src/test/resources/ide-projects/jmc/project/home/readme b/cli/src/test/resources/ide-projects/jmc/project/home/readme
new file mode 100644
index 000000000..5e8bc178c
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/jmc/project/home/readme
@@ -0,0 +1 @@
+this is the users HOME directory
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/jmc/project/readme b/cli/src/test/resources/ide-projects/jmc/project/readme
new file mode 100644
index 000000000..256f5732c
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/jmc/project/readme
@@ -0,0 +1 @@
+this is the IDE_HOME directory
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/jmc/project/settings/ide.properties b/cli/src/test/resources/ide-projects/jmc/project/settings/ide.properties
new file mode 100644
index 000000000..31d77e550
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/jmc/project/settings/ide.properties
@@ -0,0 +1,2 @@
+JAVA_VERSION=17.0.10_7
+JMC_VERSION=8.3.0
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/jmc/project/workspaces/main/readme b/cli/src/test/resources/ide-projects/jmc/project/workspaces/main/readme
new file mode 100644
index 000000000..f04b5be39
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/jmc/project/workspaces/main/readme
@@ -0,0 +1 @@
+this is the main workspace of jmc test case
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/jmc/readme b/cli/src/test/resources/ide-projects/jmc/readme
new file mode 100644
index 000000000..15b91829e
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/jmc/readme
@@ -0,0 +1 @@
+this is the IDE_ROOT directory
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/jmc/repository/java/java/default/bin/java b/cli/src/test/resources/ide-projects/jmc/repository/java/java/default/bin/java
new file mode 100755
index 000000000..0aa8af9dc
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/jmc/repository/java/java/default/bin/java
@@ -0,0 +1,2 @@
+#!/bin/bash
+echo "java $*"
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/jmc/repository/java/java/default/bin/java.cmd b/cli/src/test/resources/ide-projects/jmc/repository/java/java/default/bin/java.cmd
new file mode 100644
index 000000000..bbcd683c1
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/jmc/repository/java/java/default/bin/java.cmd
@@ -0,0 +1,2 @@
+@echo off
+echo java %*
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/jmc/repository/jmc/jmc/default/linux/InstallTest.txt b/cli/src/test/resources/ide-projects/jmc/repository/jmc/jmc/default/linux/InstallTest.txt
new file mode 100644
index 000000000..6de7b8c69
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/jmc/repository/jmc/jmc/default/linux/InstallTest.txt
@@ -0,0 +1 @@
+This is a test file.
diff --git a/cli/src/test/resources/ide-projects/jmc/repository/jmc/jmc/default/linux/JDK Mission Control/HelloWorld.txt b/cli/src/test/resources/ide-projects/jmc/repository/jmc/jmc/default/linux/JDK Mission Control/HelloWorld.txt
new file mode 100644
index 000000000..980a0d5f1
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/jmc/repository/jmc/jmc/default/linux/JDK Mission Control/HelloWorld.txt
@@ -0,0 +1 @@
+Hello World!
diff --git a/cli/src/test/resources/ide-projects/jmc/repository/jmc/jmc/default/linux/JDK Mission Control/jmc b/cli/src/test/resources/ide-projects/jmc/repository/jmc/jmc/default/linux/JDK Mission Control/jmc
new file mode 100755
index 000000000..40d7ae899
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/jmc/repository/jmc/jmc/default/linux/JDK Mission Control/jmc
@@ -0,0 +1,3 @@
+#!/bin/bash
+java "jmc"
+echo "jmc linux $*"
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/jmc/repository/jmc/jmc/default/mac/InstallTest.txt b/cli/src/test/resources/ide-projects/jmc/repository/jmc/jmc/default/mac/InstallTest.txt
new file mode 100644
index 000000000..6de7b8c69
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/jmc/repository/jmc/jmc/default/mac/InstallTest.txt
@@ -0,0 +1 @@
+This is a test file.
diff --git a/cli/src/test/resources/ide-projects/jmc/repository/jmc/jmc/default/mac/JDK Mission Control.app/Contents/MacOS/jmc b/cli/src/test/resources/ide-projects/jmc/repository/jmc/jmc/default/mac/JDK Mission Control.app/Contents/MacOS/jmc
new file mode 100755
index 000000000..a9eae8124
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/jmc/repository/jmc/jmc/default/mac/JDK Mission Control.app/Contents/MacOS/jmc
@@ -0,0 +1,3 @@
+#!/bin/bash
+java "jmc"
+echo "jmc mac $*"
diff --git a/cli/src/test/resources/ide-projects/jmc/repository/jmc/jmc/default/mac/JDK Mission Control.app/Contents/Resources/mission_control.icns b/cli/src/test/resources/ide-projects/jmc/repository/jmc/jmc/default/mac/JDK Mission Control.app/Contents/Resources/mission_control.icns
new file mode 100644
index 000000000..e69de29bb
diff --git a/cli/src/test/resources/ide-projects/jmc/repository/jmc/jmc/default/mac/legal/legal.txt b/cli/src/test/resources/ide-projects/jmc/repository/jmc/jmc/default/mac/legal/legal.txt
new file mode 100644
index 000000000..6308c936c
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/jmc/repository/jmc/jmc/default/mac/legal/legal.txt
@@ -0,0 +1 @@
+legal
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/jmc/repository/jmc/jmc/default/windows/InstallTest.txt b/cli/src/test/resources/ide-projects/jmc/repository/jmc/jmc/default/windows/InstallTest.txt
new file mode 100644
index 000000000..6de7b8c69
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/jmc/repository/jmc/jmc/default/windows/InstallTest.txt
@@ -0,0 +1 @@
+This is a test file.
diff --git a/cli/src/test/resources/ide-projects/jmc/repository/jmc/jmc/default/windows/JDK Mission Control/HelloWorld.txt b/cli/src/test/resources/ide-projects/jmc/repository/jmc/jmc/default/windows/JDK Mission Control/HelloWorld.txt
new file mode 100644
index 000000000..980a0d5f1
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/jmc/repository/jmc/jmc/default/windows/JDK Mission Control/HelloWorld.txt
@@ -0,0 +1 @@
+Hello World!
diff --git a/cli/src/test/resources/ide-projects/jmc/repository/jmc/jmc/default/windows/JDK Mission Control/jmc b/cli/src/test/resources/ide-projects/jmc/repository/jmc/jmc/default/windows/JDK Mission Control/jmc
new file mode 100755
index 000000000..4312a5cd6
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/jmc/repository/jmc/jmc/default/windows/JDK Mission Control/jmc
@@ -0,0 +1,3 @@
+#!/bin/bash
+java "jmc"
+echo "jmc windows $*"
diff --git a/cli/src/test/resources/ide-projects/jmc/repository/jmc/jmc/default/windows/JDK Mission Control/jmc.cmd b/cli/src/test/resources/ide-projects/jmc/repository/jmc/jmc/default/windows/JDK Mission Control/jmc.cmd
new file mode 100644
index 000000000..3bd949bb2
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/jmc/repository/jmc/jmc/default/windows/JDK Mission Control/jmc.cmd
@@ -0,0 +1,3 @@
+@echo off
+call java jmc
+echo jmc windows %*
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/npm/_ide/urls/readme b/cli/src/test/resources/ide-projects/npm/_ide/urls/readme
new file mode 100644
index 000000000..befcdfa75
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/npm/_ide/urls/readme
@@ -0,0 +1 @@
+this is the download metadata
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/npm/project/home/.ide/ide.properties b/cli/src/test/resources/ide-projects/npm/project/home/.ide/ide.properties
new file mode 100644
index 000000000..e69de29bb
diff --git a/cli/src/test/resources/ide-projects/npm/project/home/readme b/cli/src/test/resources/ide-projects/npm/project/home/readme
new file mode 100644
index 000000000..5e8bc178c
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/npm/project/home/readme
@@ -0,0 +1 @@
+this is the users HOME directory
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/npm/project/readme b/cli/src/test/resources/ide-projects/npm/project/readme
new file mode 100644
index 000000000..256f5732c
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/npm/project/readme
@@ -0,0 +1 @@
+this is the IDE_HOME directory
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/npm/project/settings/ide.properties b/cli/src/test/resources/ide-projects/npm/project/settings/ide.properties
new file mode 100644
index 000000000..699c73fa4
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/npm/project/settings/ide.properties
@@ -0,0 +1,2 @@
+NODE_VERSION=v18.19.1
+NPM_VERSION=9.9.2
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/npm/project/workspaces/main/readme b/cli/src/test/resources/ide-projects/npm/project/workspaces/main/readme
new file mode 100644
index 000000000..ebbc1b9ec
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/npm/project/workspaces/main/readme
@@ -0,0 +1 @@
+this is the main workspace of npm test case
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/npm/readme b/cli/src/test/resources/ide-projects/npm/readme
new file mode 100644
index 000000000..15b91829e
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/npm/readme
@@ -0,0 +1 @@
+this is the IDE_ROOT directory
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/npm/repository/node/node/default/node_modules/npm/bin/npm b/cli/src/test/resources/ide-projects/npm/repository/node/node/default/node_modules/npm/bin/npm
new file mode 100644
index 000000000..d05692c4c
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/npm/repository/node/node/default/node_modules/npm/bin/npm
@@ -0,0 +1,2 @@
+#!/bin/bash
+echo "npmbin $*"
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/npm/repository/node/node/default/node_modules/npm/bin/npm.cmd b/cli/src/test/resources/ide-projects/npm/repository/node/node/default/node_modules/npm/bin/npm.cmd
new file mode 100644
index 000000000..98265a540
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/npm/repository/node/node/default/node_modules/npm/bin/npm.cmd
@@ -0,0 +1,2 @@
+@echo off
+echo npmcmdbin %*
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/npm/repository/node/node/default/node_modules/npm/bin/npx b/cli/src/test/resources/ide-projects/npm/repository/node/node/default/node_modules/npm/bin/npx
new file mode 100644
index 000000000..3ad24deed
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/npm/repository/node/node/default/node_modules/npm/bin/npx
@@ -0,0 +1,2 @@
+#!/bin/bash
+echo "npxbin $*"
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/npm/repository/node/node/default/node_modules/npm/bin/npx.cmd b/cli/src/test/resources/ide-projects/npm/repository/node/node/default/node_modules/npm/bin/npx.cmd
new file mode 100644
index 000000000..24fdada9e
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/npm/repository/node/node/default/node_modules/npm/bin/npx.cmd
@@ -0,0 +1,2 @@
+@echo off
+echo npxcmdbin %*
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/npm/repository/node/node/default/npm b/cli/src/test/resources/ide-projects/npm/repository/node/node/default/npm
new file mode 100644
index 000000000..9c4874cf0
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/npm/repository/node/node/default/npm
@@ -0,0 +1,2 @@
+#!/bin/bash
+echo "npm $*"
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/npm/repository/node/node/default/npm.cmd b/cli/src/test/resources/ide-projects/npm/repository/node/node/default/npm.cmd
new file mode 100644
index 000000000..be74c3220
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/npm/repository/node/node/default/npm.cmd
@@ -0,0 +1,2 @@
+@echo off
+echo npmcmd %*
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/npm/repository/node/node/default/npx b/cli/src/test/resources/ide-projects/npm/repository/node/node/default/npx
new file mode 100644
index 000000000..2c8c990a2
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/npm/repository/node/node/default/npx
@@ -0,0 +1,2 @@
+#!/bin/bash
+echo "npx $*"
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/npm/repository/node/node/default/npx.cmd b/cli/src/test/resources/ide-projects/npm/repository/node/node/default/npx.cmd
new file mode 100644
index 000000000..7b3ffb529
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/npm/repository/node/node/default/npx.cmd
@@ -0,0 +1,2 @@
+@echo off
+echo npxcmd %*
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/npm/repository/npm/npm/default/linux/TestFile.txt b/cli/src/test/resources/ide-projects/npm/repository/npm/npm/default/linux/TestFile.txt
new file mode 100644
index 000000000..6de7b8c69
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/npm/repository/npm/npm/default/linux/TestFile.txt
@@ -0,0 +1 @@
+This is a test file.
diff --git a/cli/src/test/resources/ide-projects/npm/repository/npm/npm/default/linux/bin/npm b/cli/src/test/resources/ide-projects/npm/repository/npm/npm/default/linux/bin/npm
new file mode 100755
index 000000000..4ceb1a45e
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/npm/repository/npm/npm/default/linux/bin/npm
@@ -0,0 +1,3 @@
+#!/bin/bash
+node "npm"
+echo "npm windows $*"
diff --git a/cli/src/test/resources/ide-projects/npm/repository/npm/npm/default/linux/bin/npm.cmd b/cli/src/test/resources/ide-projects/npm/repository/npm/npm/default/linux/bin/npm.cmd
new file mode 100644
index 000000000..2ed7cd89f
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/npm/repository/npm/npm/default/linux/bin/npm.cmd
@@ -0,0 +1,3 @@
+@echo off
+call node npm
+echo npm windows %*
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/npm/repository/npm/npm/default/mac/TestFile.txt b/cli/src/test/resources/ide-projects/npm/repository/npm/npm/default/mac/TestFile.txt
new file mode 100644
index 000000000..6de7b8c69
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/npm/repository/npm/npm/default/mac/TestFile.txt
@@ -0,0 +1 @@
+This is a test file.
diff --git a/cli/src/test/resources/ide-projects/npm/repository/npm/npm/default/mac/bin/npm b/cli/src/test/resources/ide-projects/npm/repository/npm/npm/default/mac/bin/npm
new file mode 100755
index 000000000..4ceb1a45e
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/npm/repository/npm/npm/default/mac/bin/npm
@@ -0,0 +1,3 @@
+#!/bin/bash
+node "npm"
+echo "npm windows $*"
diff --git a/cli/src/test/resources/ide-projects/npm/repository/npm/npm/default/mac/bin/npm.cmd b/cli/src/test/resources/ide-projects/npm/repository/npm/npm/default/mac/bin/npm.cmd
new file mode 100644
index 000000000..2ed7cd89f
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/npm/repository/npm/npm/default/mac/bin/npm.cmd
@@ -0,0 +1,3 @@
+@echo off
+call node npm
+echo npm windows %*
\ No newline at end of file
diff --git a/cli/src/test/resources/ide-projects/npm/repository/npm/npm/default/windows/TestFile.txt b/cli/src/test/resources/ide-projects/npm/repository/npm/npm/default/windows/TestFile.txt
new file mode 100644
index 000000000..6de7b8c69
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/npm/repository/npm/npm/default/windows/TestFile.txt
@@ -0,0 +1 @@
+This is a test file.
diff --git a/cli/src/test/resources/ide-projects/npm/repository/npm/npm/default/windows/bin/npm b/cli/src/test/resources/ide-projects/npm/repository/npm/npm/default/windows/bin/npm
new file mode 100755
index 000000000..4ceb1a45e
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/npm/repository/npm/npm/default/windows/bin/npm
@@ -0,0 +1,3 @@
+#!/bin/bash
+node "npm"
+echo "npm windows $*"
diff --git a/cli/src/test/resources/ide-projects/npm/repository/npm/npm/default/windows/bin/npm.cmd b/cli/src/test/resources/ide-projects/npm/repository/npm/npm/default/windows/bin/npm.cmd
new file mode 100644
index 000000000..2ed7cd89f
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/npm/repository/npm/npm/default/windows/bin/npm.cmd
@@ -0,0 +1,3 @@
+@echo off
+call node npm
+echo npm windows %*
\ No newline at end of file
diff --git a/cli/src/test/resources/logback-test.xml b/cli/src/test/resources/logback-test.xml
deleted file mode 100644
index 7a043e045..000000000
--- a/cli/src/test/resources/logback-test.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
- %d{HH:mm:ss.SSS} [%thread] [C:%X{correlationId}] - %-5level - %logger{36} - %msg%n
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/cli/src/test/resources/mac-apps/jmc/Contents/MacOS/jmc b/cli/src/test/resources/mac-apps/jmc/Contents/MacOS/jmc
new file mode 100644
index 000000000..a9eae8124
--- /dev/null
+++ b/cli/src/test/resources/mac-apps/jmc/Contents/MacOS/jmc
@@ -0,0 +1,3 @@
+#!/bin/bash
+java "jmc"
+echo "jmc mac $*"
diff --git a/cli/src/test/resources/mac-apps/jmc/Contents/Resources/mission_control.icns b/cli/src/test/resources/mac-apps/jmc/Contents/Resources/mission_control.icns
new file mode 100644
index 000000000..e69de29bb
diff --git a/cli/src/test/resources/mac-apps/jmc/InstallTest.txt b/cli/src/test/resources/mac-apps/jmc/InstallTest.txt
new file mode 100644
index 000000000..6de7b8c69
--- /dev/null
+++ b/cli/src/test/resources/mac-apps/jmc/InstallTest.txt
@@ -0,0 +1 @@
+This is a test file.
diff --git a/cli/src/test/resources/mac-apps/jmc/legal/legal.txt b/cli/src/test/resources/mac-apps/jmc/legal/legal.txt
new file mode 100644
index 000000000..6308c936c
--- /dev/null
+++ b/cli/src/test/resources/mac-apps/jmc/legal/legal.txt
@@ -0,0 +1 @@
+legal
\ No newline at end of file
diff --git a/documentation/DoD.adoc b/documentation/DoD.adoc
new file mode 100644
index 000000000..b246dec25
--- /dev/null
+++ b/documentation/DoD.adoc
@@ -0,0 +1,47 @@
+:toc:
+toc::[]
+
+= Definition of Done
+
+Before a pull request (PR) for devonfw-ide is ready for review, this _definition of done_ (DoD) should be satisfied.
+Please note that external contributors are not strictly required to address all of these points as we love to get contributions and do not want to scare people from contributing with too much constraints.
+However, chances to get your change merged quickly are higher if you address all the following points.
+
+* [ ] Your PR and the issue follows our best-practices:
+** [ ] PR title is of the form `#«issue-id»: «brief summary»` (e.g. `#921: fixed setup.bat`).
+** [ ] PR top-level comment summarizes what has been done and contains link to addressed issue(s).
+** [ ] PR is linked with the issue(s) that it implements and resolves (see sidebar, click on `Development` and enter issue ID) - to archive this add `fixes #«issue-id»` to your PR summary.
+** [ ] PR and issue(s) have suitable labels (OS-specific like `windows`, `macOS`, or `linux` as well as other aspects like `commandlet`, `plugins`, `urls`, or tool specific labels such as `eclipse` or `java`)
+** [ ] Issue is set to `In Progress` and assigned to you (should actually be done before you start your implementation). See also https://github.com/devonfw/IDEasy/blob/main/documentation/IDEasy-contribution-rules-and-guidelines.adoc[here].
+** [ ] Milestones are typically assigned by PO (Jörg) but have to be set before issues and PRs get closed. In case your issue is only addressed by a change in `ide-mirrors`/`ide-urls` or in `ide-settings` repository, please already assign the according milestone (switch to `closed` tab under `Milestone`).
+* [ ] All checks have passed. Otherwise if a check failed (red cross) you need to click the `Details` link, read the logs and fix the problem.
+** [ ] The build and all automated tests succeeded. If failed and you clicked on `Details` add read the logs to find the error.
+** [ ] The contributors license agreement (CLA) is signed by all contributors of the PR.
+** [ ] Git-Gardian did not report any security issue
+* [ ] The feature branch of the PR is up-to-date with the `main` branch.
+If you see `This branch is out-of-date with the base branch` in the PR click the `Update branch` button to fix (or manually merge with the `main` from upstream locally and push your changes).
+In case you see `This branch has conflicts that must be resolved` instead, you need to resolve conflicts.
+Very simple conficts 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]).
+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.
+* [ ] In case your PR adds a new tool `«tool»` as commandlet there are the following additional checks:
+** [ ] A new `UrlUpdater` named `«tool»UrlUpdater` has been added to the according `«tool»` package.
+** [ ] A new commandlet named `«tool»` has been added that allows to install and launch the given software.
+** [ ] The tool can be installed automatically (when creating a new project with `ide create «project-name»` via settings git repo), via `ide install «tool»` or via the commandlet call `ide «tool» «args»`.
+** [ ] The tool is installed locally to `$IDE_HOME/software` (and `$IDE_ROOT/_ide/software`).
+There are very few tools as exception to this rule like `Docker` that extend `GlobalToolCommandlet`.
+** [ ] The tool can be configured locally inside `$IDE_HOME/conf` and not from a global location (e.g. in `$HOME`).
+Note: If a tool reads configuration files from the users home directory this is not given as two IDEasy projects using the same tool then would read the same config so one installation would influence the other.
+** [ ] The help page displays information about the commandlet and its properties (CLI parameters) explaining how to use it properly.
+There are no warnings logged in the help output (like `Cound not find key 'cmd-gcviewer' in ResourceBundle nls.Ide.properties`).
+Therefore add proper help texts for all supported languages https://github.com/devonfw/IDEasy/tree/main/cli/src/main/resources/nls[here].
+** [ ] The new tool is added to the table of tools in https://github.com/devonfw/ide/blob/master/documentation/LICENSE.asciidoc#license[LICENSE.asciidoc] with its according licesne.
+If that license is not yet included, the full license text needs to be added.
+** [ ] The new commandlet installs potential dependencies automatically (e.g. `getCommandlet(«DependentTool».class).install()` in overridden `install` method).
+** [ ] The variables `«TOOL»_VERSION` and `«TOOL»_EDITION` are honored by your commandlet so if present that edition and version will be downloaded and installed (happens by default but important if you implement custom installation logic).
+** [ ] The new commandlet is tested on all plattforms it is availible for.
+Assuming you are using Windows, testing for Linux can be done with WSL or Virtual Box and for MacOS we have a virtual cloud instance.
diff --git a/documentation/IDEasy-advanced.adoc b/documentation/IDEasy-advanced.adoc
new file mode 100644
index 000000000..db76830a7
--- /dev/null
+++ b/documentation/IDEasy-advanced.adoc
@@ -0,0 +1,11 @@
+= Advanced Features
+
+include::advanced-tooling-generic.adoc[leveloffset=2]
+
+include::advanced-tooling-windows.adoc[leveloffset=2]
+
+include::advanced-tooling-mac.adoc[leveloffset=2]
+
+include::advanced-tooling-linux.adoc[leveloffset=2]
+
+include::lombok.adoc[leveloffset=2]
diff --git a/documentation/IDEasy-advanced.asciidoc b/documentation/IDEasy-advanced.asciidoc
deleted file mode 100644
index 9895a59f5..000000000
--- a/documentation/IDEasy-advanced.asciidoc
+++ /dev/null
@@ -1,11 +0,0 @@
-= Advanced Features
-
-include::advanced-tooling-generic.asciidoc[leveloffset=2]
-
-include::advanced-tooling-windows.asciidoc[leveloffset=2]
-
-include::advanced-tooling-mac.asciidoc[leveloffset=2]
-
-include::advanced-tooling-linux.asciidoc[leveloffset=2]
-
-include::lombok.asciidoc[leveloffset=2]
\ No newline at end of file
diff --git a/documentation/IDEasy-contribution-getting-started.asciidoc b/documentation/IDEasy-contribution-getting-started.adoc
similarity index 93%
rename from documentation/IDEasy-contribution-getting-started.asciidoc
rename to documentation/IDEasy-contribution-getting-started.adoc
index ef09b2b48..82a95eff4 100644
--- a/documentation/IDEasy-contribution-getting-started.asciidoc
+++ b/documentation/IDEasy-contribution-getting-started.adoc
@@ -5,12 +5,12 @@ toc::[]
== Installation
To start developing IDEasy, you must first install it on your computer.
-Therefore simply follow the link:setup.asciidoc[setup] guide.
+Therefore simply follow the link:setup.adoc[setup] guide.
It is important to notice that this installation gives you the latest published version of IDEasy with which you can execute commands like eclipse or docker whose scripts are located inside the "${IDE_HOME}$/scripts/command, but for committing code we use a differing folder structure.
This will be addressed under xref:Contribution[Contribution].
== Contribution
-To begin, read through the https://github.com/devonfw/.github/blob/master/CONTRIBUTING.asciidoc[Contribution Guidelines], which you should be sure to follow.
+To begin, read through the https://github.com/devonfw/.github/blob/master/CONTRIBUTING.adoc[Contribution Guidelines], which you should be sure to follow.
First steps regarding forks or cloning of repos and creating branches, as well as some git commands, can be found in the https://github.com/firstcontributions/first-contributions[first contributions guideline].
To contribute code or documentation regarding IDEasy at first you need to fork the https://github.com/devonfw/ideasy[IDEasy] repository and then you can clone your fork into the folder `workspaces/main` in your IDEasy installation.
Open git-bash in the cloned fork at `workspaces/main/IDEasy` and run the following command:
@@ -37,13 +37,13 @@ git push -u origin feature/«issue-id»-«brief-feature-description»
```
Now you can go to https://github.com/devonfw/ideasy/pulls and create the pull-request (PR) from your new feature.
Therefore, please click on the right down arrow of the green button and choose `Create draft pull request` and then click on `Draft pull request`.
-When the checks have completed you can check the link:DoD.asciidoc[Definition-of-Done] (DoD).
+When the checks have completed you can check the link:DoD.adoc[Definition-of-Done] (DoD).
Once, all checks of the DoD are addressed, you can go the the very bottom of the PR and click on `Ready for review` what will take the PR out of the draft mode.
FYI: If you forgot the upstream merge (`git pull upstream/master`) or the upstream changed again while your PR is still open, you will see `This branch is out-of-date with the base branch` at the bottom of your PR and can click on `Update branch`.
For further development and testing, it is recommended to xref:symbolic-links[symlink] the modified scripts into your IDEasy installation.
Alternatively, the scripts can be adapted in the installation folder and then committed to the project.
-For creating a contribution also check the link:DoD.asciidoc[Definition of Done] (DoD).
+For creating a contribution also check the link:DoD.adoc[Definition of Done] (DoD).
=== ShellCheck
@@ -117,21 +117,21 @@ doInstall "-" "«installation-folder»" "«tool-name»" "«tool-version»"
For standard installations omit `"«installation-folder»"` (use the empty string arg `""`) what will install to `${IDE_HOME}/software/«tool-name»`.
=== LICENSE
-Find the license for the new tool and add the tool to the table of `Third party components` of the link:LICENSE.asciidoc[LICENSE].
+Find the license for the new tool and add the tool to the table of `Third party components` of the link:LICENSE.adoc[LICENSE].
If the license itself does not exist, add it to the end of the file as a new section.
== Settings
The https://github.com/devonfw/ide-settings[ide-settings] repository allows project-specific configurations of the IDE tools.
For your own settings, fork the repository, adjust the configuration and specify the repository URL during installation.
-Important notes on configuration and especially configuration files can be found in the link:configuration.asciidoc[configuration].
-In the configuration files many link:variables.asciidoc[variables] can be defined.
+Important notes on configuration and especially configuration files can be found in the link:configuration.adoc[configuration].
+In the configuration files many link:variables.adoc[variables] can be defined.
== Tips and tricks
The following sub-sections give you some tips and tricks to boost your productivity when developing features for `IDEasy`.
== Symbolic Links
-With link:advanced-tooling-windows.asciidoc##create-symbolic-links[symbolic links] you can use one file or folder that is located inside one directory in one or multiple other directories, without copying the whole file or folder into the other directories.
+With link:advanced-tooling-windows.adoc##create-symbolic-links[symbolic links] you can use one file or folder that is located inside one directory in one or multiple other directories, without copying the whole file or folder into the other directories.
The file is still only saved at the initial location, but can be accessed through the created links in a much more comfortable way.
So you are actually just linked to the original file and therefore don't have to worry about differing versions of the file in your directories.
This is very helpful for testing our IDEasy, because e.g. the commandlets folder for testing is not the same as the commandlets folder for git-commits.
@@ -143,7 +143,7 @@ Do the same also for `functions`, `functions-core`, `commandlet-cli`, `environme
Now changes you make in the first mentioned file will directly be available in your testing environment through the created file-link in the testing environment.
=== developer tools
-Have a look at https://github.com/devonfw/ide/blob/master/documentation/advanced-tooling-generic.asciidoc[advanced-tooling-generic.asciidoc] for some helpful developer tools.
+Have a look at https://github.com/devonfw/ide/blob/master/documentation/advanced-tooling-generic.adoc[advanced-tooling-generic.adoc] for some helpful developer tools.
=== Bash (Linux's Borne Again Shell)
**For beginners:**
@@ -157,7 +157,7 @@ That's why it is often enough to implement code for Mac or Linux (at least to so
Let's get to the practical part of the Bash-usage.
-**If you are using Windows**, make sure that you have git-bash installed, so you can execute the Bash commands mentioned in this introduction. [Here you can find git for windows](https://git-scm.com/download/win). In some cases later on you may want or need to use WSL. Our project teams standard way is to install WSL via Rancher Desktop, which is easily installable with the IDEasy by using the command `ide install docker`. If you don't have the IDEasy yet, then you can follow [this guide](https://github.com/devonfw/ide/blob/master/documentation/setup.asciidoc)
+**If you are using Windows**, make sure that you have git-bash installed, so you can execute the Bash commands mentioned in this introduction. [Here you can find git for windows](https://git-scm.com/download/win). In some cases later on you may want or need to use WSL. Our project teams standard way is to install WSL via Rancher Desktop, which is easily installable with the IDEasy by using the command `ide install docker`. If you don't have the IDEasy yet, then you can follow [this guide](https://github.com/devonfw/ide/blob/master/documentation/setup.adoc)
The https://www.youtube.com/watch?v=oxuRxtrO2Ag[following video] gives you some important and helpful basics, still you don't need to know all of these commands directly by heart. Instead under the video you'll find a list with the commands mentioned in the video to make a command return to your mind. Also directly play around with those commands while watching the video to learn faster.
diff --git a/documentation/IDEasy-contribution-rules-and-guidelines.asciidoc b/documentation/IDEasy-contribution-rules-and-guidelines.adoc
similarity index 98%
rename from documentation/IDEasy-contribution-rules-and-guidelines.asciidoc
rename to documentation/IDEasy-contribution-rules-and-guidelines.adoc
index 0e5d26b24..1c6377ace 100644
--- a/documentation/IDEasy-contribution-rules-and-guidelines.asciidoc
+++ b/documentation/IDEasy-contribution-rules-and-guidelines.adoc
@@ -33,7 +33,7 @@ To better organize the board and avoid overload, only pull request are allowed i
Issues remain `in progress` until completed via merge of PR.
General conventions for contributions to devonfw can be found
-https://github.com/devonfw/.github/blob/master/CONTRIBUTING.asciidoc#code-changes[here].
+https://github.com/devonfw/.github/blob/master/CONTRIBUTING.adoc#code-changes[here].
The following conventions are added on top by the IDEasy team from the
learnings & retros for our best way of working together:
diff --git a/documentation/IDEasy-introduction.adoc b/documentation/IDEasy-introduction.adoc
new file mode 100644
index 000000000..16fd2e62c
--- /dev/null
+++ b/documentation/IDEasy-introduction.adoc
@@ -0,0 +1,9 @@
+= Introduction
+
+This document contains the documentation for the tool https://github.com/devonfw/IDEasy[IDEasy].
+It automates the setup and update of local development environments with all the tools including your favorite IDE (integrated development environment).
+
+include::features.adoc[leveloffset=2]
+
+include::setup.adoc[leveloffset=2]
+
diff --git a/documentation/IDEasy-introduction.asciidoc b/documentation/IDEasy-introduction.asciidoc
deleted file mode 100644
index 35a43c394..000000000
--- a/documentation/IDEasy-introduction.asciidoc
+++ /dev/null
@@ -1,12 +0,0 @@
-= Introduction
-
-http://devonfw.com[devonfw] provides a solution to building applications which combine best-in-class frameworks and libraries as well as industry proven practices and code conventions.
-It massively speeds up development, reduces risks and helps deliver better results.
-
-This document contains the documentation for the tool `IDEasy`.
-It automates the setup and update of local development environments with all the tools including your favorite IDE (integrated development environment).
-
-include::features.asciidoc[leveloffset=2]
-
-include::setup.asciidoc[leveloffset=2]
-
diff --git a/documentation/IDEasy-support.asciidoc b/documentation/IDEasy-support.adoc
similarity index 57%
rename from documentation/IDEasy-support.asciidoc
rename to documentation/IDEasy-support.adoc
index 9a024201e..f6cbdd2d3 100644
--- a/documentation/IDEasy-support.asciidoc
+++ b/documentation/IDEasy-support.adoc
@@ -4,5 +4,5 @@ TODO: community, bug-reporting, etc.
<<<<
-include::LICENSE.asciidoc[leveloffset=2]
+include::LICENSE.adoc[leveloffset=2]
diff --git a/documentation/IDEasy-usage.adoc b/documentation/IDEasy-usage.adoc
new file mode 100644
index 000000000..82e720c92
--- /dev/null
+++ b/documentation/IDEasy-usage.adoc
@@ -0,0 +1,39 @@
+= Usage
+
+include::usage.adoc[leveloffset=2]
+
+<<<<
+
+include::configuration.adoc[leveloffset=2]
+
+<<<<
+
+include::variables.adoc[leveloffset=2]
+
+<<<<
+
+include::cli.adoc[leveloffset=2]
+
+include::docker-desktop-alternative.adoc[leveloffset=3]
+
+<<<<
+
+include::structure.adoc[leveloffset=2]
+
+include::conf.adoc[leveloffset=3]
+
+include::log.adoc[leveloffset=3]
+
+include::scripts.adoc[leveloffset=3]
+
+include::settings.adoc[leveloffset=3]
+
+include::software.adoc[leveloffset=3]
+
+include::system.adoc[leveloffset=3]
+
+include::updates.adoc[leveloffset=3]
+
+include::workspaces.adoc[leveloffset=3]
+
+include::projects.adoc[leveloffset=3]
diff --git a/documentation/IDEasy-usage.asciidoc b/documentation/IDEasy-usage.asciidoc
deleted file mode 100644
index 3e49c261c..000000000
--- a/documentation/IDEasy-usage.asciidoc
+++ /dev/null
@@ -1,40 +0,0 @@
-= Usage
-
-include::usage.asciidoc[leveloffset=2]
-
-<<<<
-
-include::configuration.asciidoc[leveloffset=2]
-
-<<<<
-
-include::variables.asciidoc[leveloffset=2]
-
-<<<<
-
-include::cli.asciidoc[leveloffset=2]
-
-include::docker-desktop-alternative.asciidoc[leveloffset=3]
-include::jmc.asciidoc[leveloffset=3]
-
-<<<<
-
-include::structure.asciidoc[leveloffset=2]
-
-include::conf.asciidoc[leveloffset=3]
-
-include::log.asciidoc[leveloffset=3]
-
-include::scripts.asciidoc[leveloffset=3]
-
-include::settings.asciidoc[leveloffset=3]
-
-include::software.asciidoc[leveloffset=3]
-
-include::system.asciidoc[leveloffset=3]
-
-include::updates.asciidoc[leveloffset=3]
-
-include::workspaces.asciidoc[leveloffset=3]
-
-include::projects.asciidoc[leveloffset=3]
diff --git a/documentation/IDEasy.asciidoc b/documentation/IDEasy.adoc
similarity index 67%
rename from documentation/IDEasy.asciidoc
rename to documentation/IDEasy.adoc
index b8dfc0f31..aef49da14 100644
--- a/documentation/IDEasy.asciidoc
+++ b/documentation/IDEasy.adoc
@@ -18,16 +18,16 @@ ${project.version}, ${buildtime}
:toc:
[preface]
-include::IDEasy-introduction.asciidoc[leveloffset=1]
+include::IDEasy-introduction.adoc[leveloffset=1]
<<<<
-include::IDEasy-usage.asciidoc[leveloffset=1]
+include::IDEasy-usage.adoc[leveloffset=1]
<<<<
-include::IDEasy-advanced.asciidoc[leveloffset=1]
+include::IDEasy-advanced.adoc[leveloffset=1]
<<<<
-include::IDEasy-support.asciidoc[leveloffset=1]
\ No newline at end of file
+include::IDEasy-support.adoc[leveloffset=1]
diff --git a/documentation/LICENSE.asciidoc b/documentation/LICENSE.adoc
similarity index 99%
rename from documentation/LICENSE.asciidoc
rename to documentation/LICENSE.adoc
index 49779cf71..2653e8c2e 100644
--- a/documentation/LICENSE.asciidoc
+++ b/documentation/LICENSE.adoc
@@ -21,7 +21,7 @@ The following table shows the components that may be used. The column `inclusion
.Third party components
[options="header"]
-|=======================
+|===
|*Component*|*Inclusion*|*License*
|https://github.com/devonfw/IDEasy[IDEasy] | Directly included |https://github.com/devonfw/IDEasy/blob/master/LICENSE[ASL 2.0]
|https://owasp.org/www-project-dependency-check/[OWASP] | Directly included |https://github.com/devonfw/IDEasy/blob/master/LICENSE[ASL 2.0]
@@ -78,7 +78,7 @@ The following table shows the components that may be used. The column `inclusion
|https://tomcat.apache.org/[tomcat]|Optional|https://www.apache.org/licenses/LICENSE-2.0[ASL 2.0]
|https://www.oracle.com/java/technologies/jdk-mission-control.html[Java Mission Control]|Optional|https://github.com/openjdk/jmc/blob/master/license/LICENSE.txt[UPL 1.0] and https://github.com/openjdk/jmc/blob/master/license/THIRDPARTYREADME.txt[EPL 2.0]
|https://github.com/ctongfei/progressbar[Progressbar]|Optional|https://github.com/ctongfei/progressbar/blob/main/LICENSE[MIT]
-|=======================
+|===
== Apache Software License - Version 2.0
@@ -3572,4 +3572,4 @@ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-```
\ No newline at end of file
+```
diff --git a/documentation/advanced-tooling-generic.asciidoc b/documentation/advanced-tooling-generic.adoc
similarity index 100%
rename from documentation/advanced-tooling-generic.asciidoc
rename to documentation/advanced-tooling-generic.adoc
diff --git a/documentation/advanced-tooling-linux.asciidoc b/documentation/advanced-tooling-linux.adoc
similarity index 61%
rename from documentation/advanced-tooling-linux.asciidoc
rename to documentation/advanced-tooling-linux.adoc
index 66329b23a..bd10f17db 100644
--- a/documentation/advanced-tooling-linux.asciidoc
+++ b/documentation/advanced-tooling-linux.adoc
@@ -3,4 +3,4 @@ toc::[]
= Linux Tooling
-There is nothing in this section so far. If you are a Linux user, please share your experience and provide your valuable hints.
\ No newline at end of file
+There is nothing in this section so far. If you are a Linux user, please share your experience and provide your valuable hints.
diff --git a/documentation/advanced-tooling-mac.asciidoc b/documentation/advanced-tooling-mac.adoc
similarity index 100%
rename from documentation/advanced-tooling-mac.asciidoc
rename to documentation/advanced-tooling-mac.adoc
diff --git a/documentation/advanced-tooling-windows.asciidoc b/documentation/advanced-tooling-windows.adoc
similarity index 98%
rename from documentation/advanced-tooling-windows.asciidoc
rename to documentation/advanced-tooling-windows.adoc
index c04cf546d..6b86318b5 100644
--- a/documentation/advanced-tooling-windows.asciidoc
+++ b/documentation/advanced-tooling-windows.adoc
@@ -14,7 +14,7 @@ Microsoft is also working on a repository for Windows called https://github.com/
== Integration into Windows-Explorer
-After you have link:setup.asciidoc[set up] your `IDEasy` on a windows machine,
+After you have link:setup.adoc[set up] your `IDEasy` on a windows machine,
you already have windows-explorer integration out-of-the-box.
Just right-click on the folder you would like to open in a terminal and choose from the context menu:
diff --git a/documentation/advanced-tooling.adoc b/documentation/advanced-tooling.adoc
new file mode 100644
index 000000000..2068e7f1f
--- /dev/null
+++ b/documentation/advanced-tooling.adoc
@@ -0,0 +1,11 @@
+:toc: macro
+toc::[]
+
+= Advanced Tooling
+
+We have various great hints to offer for advanced tooling:
+
+* link:advanced-tooling-generic.adoc[generic, cross-platform]
+* link:advanced-tooling-windows.adoc[Windows]
+* link:advanced-tooling-mac.adoc[MacOS]
+* link:advanced-tooling-linux.adoc[Linux]
diff --git a/documentation/advanced-tooling.asciidoc b/documentation/advanced-tooling.asciidoc
deleted file mode 100644
index adc2de1fb..000000000
--- a/documentation/advanced-tooling.asciidoc
+++ /dev/null
@@ -1,11 +0,0 @@
-:toc: macro
-toc::[]
-
-= Advanced Tooling
-
-We have various great hints to offer for advanced tooling:
-
-* link:advanced-tooling-generic.asciidoc[generic, cross-platform]
-* link:advanced-tooling-windows.asciidoc[Windows]
-* link:advanced-tooling-mac.asciidoc[MacOS]
-* link:advanced-tooling-linux.asciidoc[Linux]
diff --git a/documentation/cli.asciidoc b/documentation/cli.adoc
similarity index 54%
rename from documentation/cli.asciidoc
rename to documentation/cli.adoc
index d73866633..a8ad7c4b6 100644
--- a/documentation/cli.asciidoc
+++ b/documentation/cli.adoc
@@ -3,10 +3,10 @@ toc::[]
= Ide CLI
-`IDEasy` is shipped with a central command `ide`. The link:setup.asciidoc[setup] will automatically register this command so it is available in any shell on your system. This page describes the Command Line Interface (CLI) of this command.
+`IDEasy` is shipped with a central command `ide`. The link:setup.adoc[setup] will automatically register this command so it is available in any shell on your system. This page describes the Command Line Interface (CLI) of this command.
== Ide
-Without any argument the `ide` command will determine your `IDE_HOME` and setup your link:variables.asciidoc[environment variables] automatically. In case you are not inside of a `IDEasy` folder the command will echo a message and do nothing.
+Without any argument the `ide` command will determine your `IDE_HOME` and setup your link:variables.adoc[environment variables] automatically. In case you are not inside of a `IDEasy` folder the command will echo a message and do nothing.
[source,bash]
--------
@@ -45,49 +45,3 @@ The benefit when using `ide` as wrapper is that it will even work when the comma
We see the main benefit in this for writing portable scripts that you may commit to your git repository and that will then run everywhere and will lazily install the required tools on the fly.
In your daily usage you can and surely should avoid to always type `ide` as prefix to every command.
However, when you automate and want to avoid "command not found" errors, you can simply prefix the command with `ide`.
-
-=== Commandlet overview
-
-The following commandlets are currently available:
-
-* link:android-studio.asciidoc[android-studio]
-* link:aws.asciidoc[aws]
-* link:az.asciidoc[az]
-* link:build.asciidoc[build]
-* link:cobigen.asciidoc[cobigen]
-* link:docker.asciidoc[docker]
-* link:dotnet.asciidoc[dotnet]
-* link:eclipse.asciidoc[eclipse]
-* link:gcloud.asciidoc[gcloud]
-* link:gcviewer.asciidoc[gcviewer]
-* link:gh.asciidoc[gh]
-* link:graalvm.asciidoc[graalvm]
-* link:gradle.asciidoc[gradle]
-* link:helm.asciidoc[helm]
-* link:help.asciidoc[help]
-* link:ide.asciidoc[ide]
-* link:intellij.asciidoc[intellij]
-* link:ionic.asciidoc[ionic]
-* link:jasypt.asciidoc[jasypt]
-* link:java.asciidoc[java]
-* link:jenkins.asciidoc[jenkins]
-* link:jmc.asciidoc[jmc]
-* link:kotlinc.asciidoc[kotlinc]
-* link:kotlinc-native.asciidoc[kotlinc-native]
-* link:kubectl.asciidoc[kubectl]
-* link:lazydocker.asciidoc[lazydocker]
-* link:mvn.asciidoc[mvn]
-* link:ng.asciidoc[ng]
-* link:node.asciidoc[node]
-* link:npm.asciidoc[npm]
-* link:oc.asciidoc[oc]
-* link:python.asciidoc[python]
-* link:pip.asciidoc[pip]
-* link:quarkus.asciidoc[quarkus]
-* link:release.asciidoc[release]
-* link:rewrite.asciidoc[rewrite]
-* link:sonar.asciidoc[sonar]
-* link:terraform.asciidoc[terraform]
-* link:tomcat.asciidoc[tomcat]
-* link:vscode.asciidoc[vscode]
-* link:yarn.asciidoc[yarn]
diff --git a/documentation/coding-conventions.asciidoc b/documentation/coding-conventions.adoc
similarity index 97%
rename from documentation/coding-conventions.asciidoc
rename to documentation/coding-conventions.adoc
index 6feadb37a..703976731 100644
--- a/documentation/coding-conventions.asciidoc
+++ b/documentation/coding-conventions.adoc
@@ -21,6 +21,16 @@ We follow these additional naming rules:
* Names of Generics should be easy to understand. Where suitable follow the common rule `E=Element`, `T=Type`, `K=Key`, `V=Value` but feel free to use longer names for more specific cases such as `ID`, `DTO` or `ENTITY`. The capitalized naming helps to distinguish a generic type from a regular class.
* For `boolean` getter methods use `is` prefix instead of `get` but for `boolean` variable names avoid the `is` prefix (`boolean force = ...` instead of `boolean isForce = ...`) unless the name is a reserved keyword (e.g. `boolean abstract = true` will result in a compile error so consider using `boolean isAbstract = true` instead).
+== Proper IDE configuration
+Ensure your IDE (IntelliJ, Eclipse, VSCode, ...) is properly configured.
+This is what https://github.com/devonfw/IDEasy[IDEasy] is all about.
+A reasonable configuration of formatter, save actions, etc. will ensure:
+
+* no start imports are used (`import java.util.*`)
+* no diff-wars in git (if every developer in the team uses the same formatter settings the diffs in git will only show what really changed)
+* import statements are properly grouped and sorted
+* code has properly indentation and formatting (e.g. newlines after opening curly braces)
+
== Obsolete APIs
Please avoid using the following APIs:
@@ -169,7 +179,7 @@ public class MavenDownloader {
public void download(String url) { ... }
}
----
-Here `url` is used as a constant however it is not declared as such.
+Here `url` is used as a constant however it is not declared as such. Other classes could modify the value (`MavenDownloader.url = "you have been hacked";`).
Instead we should better do this:
[source,java]
----
diff --git a/documentation/conf.adoc b/documentation/conf.adoc
new file mode 100644
index 000000000..0a0c7b422
--- /dev/null
+++ b/documentation/conf.adoc
@@ -0,0 +1,35 @@
+:toc:
+toc::[]
+
+= conf
+This folder contains configurations for your IDE:
+
+.File structure of the conf folder
+[source]
+----
+/ conf
+├──/ .m2
+│ ├──/ repository
+│ │ ├──/ ant
+│ │ ├──/ ...
+│ │ └──/ zw
+│ ├── settings-security.xml
+│ └── settings.xml
+├──/ .sonar
+├──/ ...
+└── variables
+----
+
+The `.m2` folder is used for configurations of https://maven.apache.org/[maven].
+It contains the local `repository` folder used as cache for artifacts downloaded and installed by maven (see also https://maven.apache.org/guides/introduction/introduction-to-repositories.html[maven repositories]).
+Further, there are two configuration files for maven:
+
+* https://maven.apache.org/settings.html[settings.xml] initialized from a template from your `IDEasy` link:settings.adoc[settings].
+You may customize this to your needs (configuring HTTP proxies, credentials, or other user-specific settings).
+Secrets can be specified as `$[«variable.name»]` and will be prompted, encrypted and replaced automatically during the setup (unless in link:cli.adoc#commandlets[batch mode]).
+Please note that this process is skipped in batch mode and also if you use the default settings URL (for simplicity of testing).
+To make use of this feature simply fork or copy the settings to your own git repo.
+In case your credentials have changed or you made a typo, you can simply redo this step by first moving your `${IDE_HOME}/conf/.m2/settings.xml` file to a temporary folder and then calling `ide install mvn`.
+* https://maven.apache.org/guides/mini/guide-encryption.html[settings-security.xml] is auto-generated for you by `IDEasy` with a random password. This should make it easier for `IDEasy` users to use https://maven.apache.org/guides/mini/guide-encryption.html[password encryption] and never add passwords in plain text for better security.
+
+Finally, there is a file `variables` for the user-specific link:configuration.adoc[configuration] of `IDEasy`.
diff --git a/documentation/conf.asciidoc b/documentation/conf.asciidoc
deleted file mode 100644
index 730c665c5..000000000
--- a/documentation/conf.asciidoc
+++ /dev/null
@@ -1,29 +0,0 @@
-:toc:
-toc::[]
-
-= conf
-This folder contains configurations for your IDE:
-
-.File structure of the conf folder
-[source]
-----
-/ conf
-├──/ .m2
-│ ├──/ repository
-│ │ ├──/ ant
-│ │ ├──/ ...
-│ │ └──/ zw
-│ ├── settings-security.xml
-│ └── settings.xml
-├──/ .sonar
-├──/ ...
-└── variables
-----
-
-The `.m2` folder is used for configurations of link:mvn.asciidoc[maven]. It contains the local `repository` folder used as cache for artifacts downloaded and installed by maven (see also https://maven.apache.org/guides/introduction/introduction-to-repositories.html[maven repositories]).
-Further, there are two configuration files for maven:
-
-* https://maven.apache.org/settings.html[settings.xml] initialized from a template from your `IDEasy` xref:settings[]. You may customize this to your needs (configuring HTTP proxies, credentials, or other user-specific settings). Secrets can be specified as `$[«variable.name»]` and will be prompted, encrypted and replaced automatically during the setup (unless in link:cli.asciidoc#commandlets[batch mode]). Please note that this process is skipped in batch mode and also if you use the default settings URL (for simplicity of testing). To make use of this feature simply fork or copy the settings to your own git repo. In case your credentials have changed or you made a typo, you can simply redo this step by first moving your `${IDE_HOME}/conf/.m2/settings.xml` file to a temporary folder and then calling `ide install mvn`.
-* https://maven.apache.org/guides/mini/guide-encryption.html[settings-security.xml] is auto-generated for you by `IDEasy` with a random password. This should make it easier for `IDEasy` users to use https://maven.apache.org/guides/mini/guide-encryption.html[password encryption] and never add passwords in plain text for better security.
-
-Finally, there is a file `variables` for the user-specific link:configuration.asciidoc[configuration] of `IDEasy`.
diff --git a/documentation/configuration.adoc b/documentation/configuration.adoc
new file mode 100644
index 000000000..b2aa40471
--- /dev/null
+++ b/documentation/configuration.adoc
@@ -0,0 +1,47 @@
+:toc:
+toc::[]
+
+= Configuration
+
+`IDEasy` aims to be highly configurable and flexible.
+The configuration of the link:cli.adoc[ide] command and environment variables takes place via `ide.properties` files.
+The following list shows these configuration files in the order they are loaded so files can override variables from files above in the list:
+
+1. build in defaults (for `JAVA_HOME`, `MAVEN_ARGS`, etc.)
+2. `~/ide.properties` - user specific global defaults (on windows in `%USERPROFILE%/ide.properties`)
+3. `https://github.com/devonfw/ide-settings/blob/main/ide.properties[settings/ide.properties]` (`settings/ide.properties`) - project specific configurations from link:settings.adoc[settings].
+4. `workspaces/${WORKSPACE}/ide.properties` - optional workspace specific configurations (especially helpful in projects using docker).
+5. `https://github.com/devonfw/ide-settings/blob/main/template/conf/ide.properties[conf/ide.properties]` - user specific configurations (e.g. `M2_REPO=~/.m2/repository`).
+During setup this file is created by copying a template from `settings/template/conf/ide.properties`.
+
+== ide.properties
+
+The `ide.properties` files allow to define environment variables in a simple and OS independent way:
+
+* `# comments begin with a hash sign (#) and are ignored`
+* `variable_name=variable_value with space etc.`
+* `variable_name=${predefined_variable}/folder_name`
++
+Variable values can refer to other variables that are already defined, which will be resolved to their value.
+You have to used `${...}` syntax to make it work on all platforms (never use `%...%`, `$...`, or `$(...)` syntax in `ide.properties` files.
+* `export exported_variable=this value will be exported in bash, in windows CMD the export prefix is ignored`
+* `variable_name=`
++
+This will unset the specified variable
+* `variable_name=~/some/path/and.file`
++
+Tilde is resolved to your personal home directory on any OS including windows.
+* `array_variable=(value1 value2 value3)`
++
+This will only work properly in bash worlds but as no arrays are used in CMD world of `IDEasy` it does not hurt on windows.
+* Please never surround values with quotes (`var="value"`)
+* This format is similar to Java `*.properties` but does not support advanced features as unicode literals, multi-lined values, etc.
+
+In order to know what to configure, have a look at the available link:variables.adoc[variables].
+
+Please only tweak configurations that you need to change and take according responsibility.
+There is a price to pay for flexibility, which means you have to be careful what you do.
+
+Further, you can configure maven via `conf/.m2/settings.xml`.
+To configure your IDE such as Eclipse, IntelliJ, or VSCode you can tweak the link:settings.adoc[settings].
+
diff --git a/documentation/configuration.asciidoc b/documentation/configuration.asciidoc
deleted file mode 100644
index 914b1a5f7..000000000
--- a/documentation/configuration.asciidoc
+++ /dev/null
@@ -1,43 +0,0 @@
-:toc:
-toc::[]
-
-= Configuration
-
-`IDEasy` aims to be highly configurable and flexible. The configuration of the link:cli.asciidoc[ide] command and environment variables takes place via `ide.properties` files. The following list shows these configuration files in the order they are loaded so files can override variables from files above in the list:
-
-1. build in defaults (for `JAVA_VERSION`, `ECLIPSE_PLUGINS`, etc.)
-2. `~/ide.properties` - user specific global defaults (on windows in `%USERPROFILE%/ide.properties`)
-3. `https://github.com/devonfw/ide/blob/master/scripts/src/main/resources/scripts/devon.properties[scripts/ide.properties]` - defaults provided by `IDEasy`. Never directly modify this file!
-4. `ide.properties` - vendor variables for custom distributions of `link:scripts.asciidoc[IDEasy-scripts]`, may e.g. tweak `SETTINGS_PATH` or predefine `SETTINGS_URL`.
-5. `https://github.com/devonfw/ide-settings/blob/master/devon.properties[settings/ide.properties]` (`${SETTINGS_PATH}/ide.properties`) - project specific configurations from link:settings.asciidoc[settings].
-6. `workspaces/${WORKSPACE}/ide.properties` - optional workspace specific configurations (especially helpful in projects using docker).
-7. `https://github.com/devonfw/ide-settings/blob/master/devon/conf/devon.properties[conf/ide.properties]` - user specific configurations (e.g. `M2_REPO=~/.m2/repository`). During setup this file is created by copying a template from `${SETTINGS_PATH}/devon/conf/ide.properties`.
-
-== ide.properties
-
-The `ide.properties` files allow to define environment variables in a simple and OS independent way:
-
-* `# comments begin with a hash sign (#) and are ignored`
-* `variable_name=variable_value with space etc.`
-* `variable_name=${predefined_variable}/folder_name`
-+
-variable values can refer to other variables that are already defined, which will be resolved to their value. You have to used `${...}` syntax to make it work on all platforms (never use `%...%`, `$...`, or `$(...)` syntax in `ide.properties` files).
-* `export exported_variable=this value will be exported in bash, in windows CMD the export prefix is ignored`
-* `variable_name=`
-+
-this will unset the specified variable
-* `variable_name=~/some/path/and.file`
-+
-tilde is resolved to your personal home directory on any OS including windows.
-* `array_variable=(value1 value2 value3)`
-+
-This will only work properly in bash worlds but as no arrays are used in CMD world of `IDEasy` it does not hurt on windows.
-* Please never surround values with quotes (`var="value"`)
-* This format is similar to Java `*.properties` but does not support advanced features as unicode literals, multi-lined values, etc.
-
-In order to know what to configure, have a look at the available link:variables.asciidoc[variables].
-
-Please only tweak configurations that you need to change and take according responsibility. There is a price to pay for flexibility, which means you have to be careful what you do.
-
-Further, you can configure link:mvn.asciidoc[maven] via `conf/settings.xml`. To configure your IDE such as link:eclipse.asciidoc[eclipse] or link:vscode.asciidoc[vscode] you can tweak the link:settings.asciidoc[settings].
-
diff --git a/documentation/configurator.adoc b/documentation/configurator.adoc
new file mode 100644
index 000000000..b921d5bf8
--- /dev/null
+++ b/documentation/configurator.adoc
@@ -0,0 +1,72 @@
+:toc:
+toc::[]
+
+= Configurator
+
+`IDEasy` has build-in functionality to configure your IDE.
+This allows to merge and manage complex configurations.
+
+== How to use
+The easiest way is that you do not care.
+When you launch the IDE of your choice (e.g. via `ide eclipse`, `ide intellij`, `ide vscode`), this will happen automatically.
+
+== How it works
+For every supported IDE we distinguish the following file structures:
+
+1. _workspace_
++
+The actual configuration location of the tool itself.
+We configure the tool to relocate this to a specific workspace (so by default `workspaces/main/`).
+2. _setup_
++
+A configuration location with the configuration only used during the link:setup.adoc[setup]: `settings/«ide»/workspace/setup`.
+Contains settings to setup a link:workspaces.adoc[workspace].
+After that the user remains control over these settings.
+3. _update_
++
+A configuration location with the configuration used to update and override: `settings/«ide»/workspace/update`.
+Contains settings that are overridden with every update and enforced for every team member.
+If a developer manually changes such configuration setting, it will be reset on the next time the IDE gets started.
+
+The configurator will recursively traverse the directory structure of 2. and 3. together.
+For each located file `«relative-path»/«file»` it will create or update 1. according to the following rules:
+
+* If `«relative-path»/«file»` is present in 1. (workspace) it will be loaded and used as basis.
+* Otherwise if `«relative-path»/«file»` is present in 2. (setup) it will be loaded and used as basis.
+* If `«relative-path»/«file»` is present in 3. (update) it will be loaded and merged with the current basis.
+* Variables in the from `${«variable-name»}` get resolved if `«variable-name»` is defined.
+* If this caused any change the result is written to `«relative-path»/«file»` in 1. (workspace).
+
+In other words this means:
+
+* When your workspace configuration is initially created, 1. is empty. Hence, settings from 2. are used and merged with 3.
+* Settings in 2. are therefore used as initial defaults and suggestions but can be changed by the end-user (developer).
+Hence, use 2. for things such as themes, UI tweaks, etc.
+Once the workspace is configured 2. typically is not relevant anymore.
+* Settings in 3. are applied on every update.
+By default this happens every time you start your IDE, these settings are managed by the `settings` and in control `configurator`.
+If the user modifies such settings and reopens his IDE his changes are reverted.
+Hence, use 3. for things such as code-formatters, compiler options, paths to tools shipped with `IDEasy`, etc. that should be consistent and homogeneous for every team-member.
+
+== How to customize
+
+The settings for your ide are located in `settings/«ide»/workspace` where you find the `setup` (2.) and `update` (3.) sub-folders.
+E.g. for Eclipse the most relevant settings can be found in `settings/eclipse/workspace/update/.metadata/.plugins/org.eclipse.core.runtime/.settings`.
+Of course you could manually edit these settings with a text editor.
+However, this requires a lot of knowledge. As we want to provide a great user-experience with `IDEasy` you can also do the following:
+
+* Launch the IDE to configure (e.g. `ide intellij`).
+* In case of a non-trivial tweak you may first create a backup copy of the IDE configuration in `workspaces/main` (for IntelliJ in `.intelij` and `.idea` subfolders and for eclipse in `.metadata`).
+* Do the desired modification of the configuration via the GUI of your IDE (Preferences dialog).
+* Exit your IDE and wait untill it is shutdown
+* Call `ws-reverse` command for your IDE (e.g. `ide intellij ws-reverse`) - ensure you do this in the same workspace where you launched and tweaked the config (without intermediate `cd` commands).
+* Review the changes to your link:settings.adoc[settings] with a git and diff tool of your choice (e.g. call `git diff`).
+* If all looks as expected commit these changes and push them - consider using a feature branch and ask a colleague to test these changes before you apply this to the main branch.
+* In case you could not find the expected changes, you may have tweaked a property that is not yet managed.
+Therefore, you can try again with `ws-reverse-add` instead of `ws-reverse` (e.g. `ide eclipse ws-reverse-add`) but be aware to revert undesired changes.
+Be sure not to add undesired settings that should not be managed.
+* In case your changes are in an entirely new configuration file that is currently not managed,
+you can simply diff the current workspace folder with the previously created backup copy using a recursive diff tool
+(such as winmerge or maybe just `diff -R`).
+Once you figured out the relevant change from that diff,
+you can manually apply it to the according `«ide»/workspace/update` folder in your `ide-settings` git repository.
diff --git a/documentation/configurator.asciidoc b/documentation/configurator.asciidoc
deleted file mode 100644
index ed22420b4..000000000
--- a/documentation/configurator.asciidoc
+++ /dev/null
@@ -1,45 +0,0 @@
-:toc:
-toc::[]
-
-= Configurator
-
-`IDEasy` maintains and includes a tool called https://github.com/devonfw/ide/tree/master/configurator[IDEasy-configurator]. This allows to synchronize and manage complex configurations. Initially it was written for Eclipse that stores its information in a `.metadata` folder of your workspace. Unfortunately it contains different file-formats (including XML as String value inside properties files), temporary data as well as important configurations with sometimes mixtures of project specific, developer specific, and UI specific settings. To make it short it is a mess. Instead of bashing on Eclipse we want to make this IDE more usable and created a way to manage important parts of such configuration structures.
-
-== How to use
-The easiest way is that you do not care. When you launch the IDE of your choice (e.g. via `ide eclipse`, `ide vscode` or by running `eclipse-main` script), this will happen automatically.
-If you want to explicitly update your workspace without launching the IDE, you can append `ws-update` (e.g. `ide eclipse ws-update`). Instead, if you want to launch your IDE without touching its configuration you can append `run` or `start` (e.g. `ide eclipse run`) what will omit this `configurator`.
-
-== How it works
-For every tool managed with our configurator we distinguish the following file structures:
-
-1. The actual configuration location of the tool itself. We configure the tool to relocate this to a specific workspace (so by default `workspaces/main/`).
-2. A configuration location with the configuration only used during the link:setup.asciidoc[setup]: `$SETTINGS_PATH/«tool»/workspace/setup`. Contains settings to setup a link:workspaces.asciidoc[workspace]. After that the user remains control over these settings.
-3. A configuration location with the configuration used to update and override settings: `$SETTINGS_PATH/«tool»/workspace/update`. Contains settings that are overridden with every update and enforced for every team member.
-
-The configurator will recursively traverse the directory structure of 2. and 3. together. For each located file `«relative-path»/«file»` it will create or update 1. according to the following rules:
-
-* If `«relative-path»/«file»` is present in 1. it will be loaded and used as basis.
-* Otherwise if `«relative-path»/«file»` is present in 2. it will be loaded and used as basis.
-* If `«relative-path»/«file»` is present in 3. it will be loaded and merged with the current basis.
-* Variables in the from `${«variable-name»}` get resolved if `«variable-name»` is defined.
-* If this caused any change the result is written to `«relative-path»/«file»` in 1.
-
-In other words this means:
-
-* When your workspace configuration is initially created, 1. is empty. Hence, settings from 2. are used and merged with 3.
-* Settings in 2. are therefore used as initial defaults and suggestions but can be changed by the end-user (developer). Hence, use 2. for things such as themes, UI tweaks, etc. Once the workspace is configured 2. typically is not relevant anymore.
-* Settings in 3. are applied on every update. By default this happens every time you start your IDE, these settings are managed by the `settings` and in control `configurator`. If the user modifies such settings and reopens his IDE his changes are reverted. Hence, use 3. for things such as code-formatters, compiler options, paths to tools shipped with `IDEasy`, etc. that should be consistent and homogeneous for every team-member.
-
-== How to customize
-
-Many fundamental settings for Eclipse can be found in the sub-folder `https://github.com/devonfw/ide/tree/master/settings/src/main/settings/eclipse/workspace/update/.metadata/.plugins/org.eclipse.core.runtime/.settings[.metadata/.plugins/org.eclipse.core.runtime/.settings]`. Of course you could manually edit these settings with a text editor. However, this requires a lot of knowledge. As we want to provide a great user-experience with `IDEasy` you can also do the following:
-
-* Launch the IDE to configure (e.g. `ide eclipse`).
-* In case of a non-trivial tweak you may first create a backup copy of your workspace folder (for eclipse this would be `workspaces/main/.metadata`) to some temporary location.
-* Do the desired modification of the configuration via the GUI of your IDE (e.g. in Eclipse preferences).
-* Exit your IDE and wait till it is shutdown
-* Call `ws-reverse` command for your IDE (e.g. `ide eclipse ws-reverse`) - ensure you do this in the same workspace where you launched and tweaked the config (without intermediate `cd` commands).
-* Review the changes to your link:settings.asciidoc[settings] with a git and diff tool of your choice (e.g. call `git diff`).
-* If all looks as expected commit these changes and push them - consider using a feature branch and ask a colleague to test these changes before you apply this to the main branch.
-* In case you could not find the expected changes, you may have tweaked a property that is not yet managed. Therefore, you can try again with `ws-reverse-add` instead of `ws-reverse` (e.g. `ide eclipse ws-reverse-add`) but be aware to revert undesired changes. Be sure not to add undesired settings that should not be managed.
-* In case your changes are in an entirely new configuration file that is currently not managed,you can simply diff the current workspace folder with the previously created backup copy using a recursive diff tool (such as winmerge or maybe just `diff -R`). Once you figured out the relevant change from that diff, you can manually apply it to the according `«ide»/workspace/update` folder in your `ide-settings` git repository.
diff --git a/documentation/docker-desktop-alternative.asciidoc b/documentation/docker-desktop-alternative.adoc
similarity index 100%
rename from documentation/docker-desktop-alternative.asciidoc
rename to documentation/docker-desktop-alternative.adoc
diff --git a/documentation/features.asciidoc b/documentation/features.adoc
similarity index 52%
rename from documentation/features.asciidoc
rename to documentation/features.adoc
index 7fb31c47b..8ba0ab91b 100644
--- a/documentation/features.asciidoc
+++ b/documentation/features.adoc
@@ -3,12 +3,15 @@ toc::[]
= Features
-Every developer needs great tools to work efficiently. Setting up these tools manually can be tedious and error-prone.
+Every developer needs great tools to work efficiently.
+Setting up these tools manually can be tedious and error-prone.
Furthermore, some projects may require different versions and configurations of such tools.
-In case you are working for more than one project in parallel, you may have contradicting requirements for your environments (e.g. different versions of Java, Node.js, etc. or different code-styles).
+In case you are working for more than one project in parallel,
+you may have contradicting requirements for your environments (e.g. different versions of Java, Node.js, etc. or different code-styles).
Especially configurations like code-formatters should be consistent within a project to avoid diff-wars.
-`IDEasy` will solve these issues. Here are the features and benefits for you:
+`IDEasy` will solve these issues.
+Here are the features and benefits for you:
* *Efficient* +
Set up your IDE quickly tailored for the requirements of your project.
@@ -17,19 +20,29 @@ Automate the setup and update, avoid manual steps and mistakes.
* *Simple* +
KISS (Keep It Small and Simple), no native installers that globally mess your operating-system or tool-integrations that break with every release.
* *Configurable* +
-Each project can be configured via a simple git repository. These link:settings.asciidoc[settings] allow a project to maintain their configuration tailored to its needs and this way distribute it to its team. A developer can even customize and configure additional fine-tunings for personal preferences.
+Each project can be configured via a simple git repository.
+These link:settings.adoc[settings] allow a project to maintain their configuration tailored to its needs and this way distribute it to its team.
+A developer can even customize and configure additional fine-tunings for personal preferences.
* *Maintainable* +
-For your project you should fork (copy) these link:settings.asciidoc[settings] to an own git repository that can be maintained and updated to manage the tool configurations during the project lifecycle. If you use GitHub, GitLab, or BitBucket every developer can easily suggest changes and improvements to these link:settings.asciidoc[settings] via pull/merge requests. This makes it scale from a single developer up to teams with hundreds of developers.
+For your project you should fork (copy) these link:settings.adoc[settings] to an own git repository that can be maintained and updated to manage the tool configurations during the project lifecycle.
+If you use GitHub, GitLab, or BitBucket every developer can easily suggest changes and improvements to these link:settings.adoc[settings] via pull/merge requests.
+This makes it scale from a single developer up to teams with hundreds of developers.
* *Customizable* +
-You can integrate any tool you need on your local machine. Most development tools are supported out of the box and if you want you get security updates automatically without manual maintenance. You can even add link:software.asciidoc#custom[custom tools] on your own to integrate tools that are not supported out of the box (including closed-source, proprietary or commercial software) as long as you care about the terms and licenses of these tools.
+You can integrate any tool you need on your local machine.
+Most development tools are supported out of the box and if you want you get security updates automatically without manual maintenance.
+You can even add link:software.adoc#custom[custom tools] on your own to integrate tools that are not supported out of the box (including closed-source, proprietary or commercial software) as long as you care about the terms and licenses of these tools.
* *Multi-platform* +
It supports and works on all major platforms: Windows, Mac and Linux.
* *Multi-tenancy* +
-You can have any number of projects "link:setup.asciidoc[installed]" with `IDEasy` on your machine with different tools, tool versions and configurations. Such "installations" do not interfere with each other nor with other software installed on your operating-system.
+You can have any number of projects "link:setup.adoc[installed]" with `IDEasy` on your machine with different tools, tool versions and configurations.
+Such project "installations" do not interfere with each other nor with other software installed on your operating-system.
* *Multiple Workspaces* +
-It supports working with different link:workspaces.asciidoc[workspaces]. You can create and update new workspaces with a few clicks e.g. for different sub-projects or branches. You can see the workspace name in the title-bar of your IDE so you do not get confused and work on the right workspace.
+It supports working with different link:workspaces.adoc[workspaces].
+You can create and update new workspaces with a few clicks e.g. for different sub-projects or branches.
+You can see the workspace name in the title-bar of your IDE so you do not get confused and work on the right workspace.
* *Free* +
-`IDEasy` is free just like everything from https://devonfw.com[devonfw]. See link:license.asciidoc[LICENSE] for details.
+`IDEasy` is free and open-source software.
+See link:license.adoc[LICENSE] for details.
== IDEs
We support the following IDEs:
@@ -41,7 +54,8 @@ We support the following IDEs:
== Motivation
-`TL;DR`? Lets talk to developers a correct language. Here are some examples with `IDEasy`:
+`TL;DR`? Lets talk to developers a correct language.
+Here are some examples with `IDEasy`:
[source,bash]
--------
@@ -49,7 +63,7 @@ We support the following IDEs:
You are not inside an IDEasy project: /
[/]$ cd /projects/devonfw
[devonfw]$ mvn
-zsh: command not found: mvn
+command not found: mvn
[devonfw]$ ide
Environment variables have been set for /projects/devonfw in workspace main
[devonfw]$ mvn -v
@@ -76,7 +90,7 @@ launching Eclipse for workspace test...
--------
This was just a very simple demo of what `IDEasy` can do.
-For further details have a look at our link:cli.asciidoc[CLI documentation].
+For further details have a look at our link:cli.adoc[CLI documentation].
Now you might ask:
@@ -88,4 +102,4 @@ Now you might ask:
* But I use (OhMy)Zsh - it works!
* But ...? - it works!
-Wow! So let's get started with link:setup.asciidoc[download & setup].
+Wow! So let's get started with link:setup.adoc[download & setup].
diff --git a/documentation/graalvm-build-guide.adoc b/documentation/graalvm-build-guide.adoc
new file mode 100644
index 000000000..adaccfe2b
--- /dev/null
+++ b/documentation/graalvm-build-guide.adoc
@@ -0,0 +1,136 @@
+= Graalvm Build Guide
+
+*1. Setup Graalvm*
+
+* Update the file under e.g. C\Projects\IDEasy\settings and add
+
+[width="100%",cols="100%",options="header",]
+|===
+a|
+GRAALVM_EDITION=community
+|===
+
+This is necessary as the community edition of graalvm is delivered with a pre-installed native image.
+
+* Open a command-line interface inside a devon installation e.g. C\Projects\IDEasy\workspaces\main
+
+* Install Graalvm using the devonfw-ide with
+
+[width="100%",cols="100%",options="header",]
+|===
+a|
+devon graalvm setup
+|===
+
+When the installation is complete, the GRAALVM_HOME environment variable is automatically set to the graalvm installation path e.g. C/Projects/IDEasy/software/extra/graalvm
+
+* Restart your cli
+
+* Add *$GRAALVM_HOME/bin* to your *PATH* environment variable
+
+Bash:
+
+[width="100%",cols="100%",options="header",]
+|===
+a|
+export PATH=$GRAALVM_HOME\bin:$PATH
+|===
+
+Powershell (run as Admin):
+
+[width="100%",cols="100%",options="header",]
+|===
+a|
+$path = [System.Environment]::GetEnvironmentVariable("Path",[System.EnvironmentVariableTarget]::Machine) +
+$newPath = "$env:GRAALVM_HOME\bin;" + $path +
+[System.Environment]::SetEnvironmentVariable("Path", $newPath,
+[System.EnvironmentVariableTarget]::Machine)
+|===
+
+*2. Install Dependencies*
+
+*2.1 Windows: Install Visual Studio*
+
+* Download Visual Studio from https://visualstudio.microsoft.com/downloads/
+
+* During the installation, make sure to include the "Desktop development with CPP" workload, as this includes the necessary C/C++ compiler
+
+image:images/cppInstall.png[image]
+* If you already have Visual Studio installed, open the Visual Studio installer and click “modifyˮ on your existing installation.
+Then select the C++ workload and click on install.
+
+*2.2 Linux*
+
+Install the zlib development package on your system:
+
+[width="100%",cols="100%",options="header",]
+|===
+a|
+sudo apt-get update +
+sudo apt-get install zlib1g-dev
+|===
+
+*3. Build Your Application*
+
+Run the following Maven command inside Intellij to compile your application and create an executable:
+
+[width="100%",cols="100%",options="header",]
+|===
+a|
+-B -ntp -Pnative -DskipTests=true package
+|===
+
+image:images/graalvmMvnArgs.png[image]
+
+*Alternatively you can build the project using a cli:*
+
+* Open *PowerShell* and check the java version with
+
+[width="100%",cols="100%",options="header",]
+|===
+a|
+java --version
+|===
+
+If the java version is below 17, you need to specify a JDK with version >=17.
+e.g.
+
+[width="100%",cols="100%",options="header",]
+|===
+a|
+$env:JAVA_HOME = "C:\Projects\IDEasy\software\java\"
+|===
+
+This command sets the *JAVA_HOME* variable temporarily for the session and will be re-set after you close the shell.
+
+* Run the following Maven command in your project directory where the pom.xml is located to compile your application and create an executable:
+
+[width="100%",cols="100%",options="header",]
+|===
+a|
+PATH/TO/MVN/mvn -B -ntp -Pnative -DskipTests=true package
+|===
+
+Example:
+
+[width="100%",cols="100%",options="header",]
+|===
+a|
+C:\Projects\IDEasy\workspaces\main\IDEasy> C:\Projects\IDEasy\software\mvn\bin\mvn -B -ntp -Pnative -DskipTests=true package
+|===
+
+Building the application might take up to 10 minutes depending on your machine.
+
+*4. Run your Application* +
+An ideasy executable (e.g. ideasy.exe under windows) should be created under *../workspace/main/IDEasy/cli/target*.
+
+To run the application open a cli and pass your arguments.
+
+Example:
+
+[width="100%",cols="100%",options="header",]
+|===
+a|
+C:\Projects\IDEasy\workspaces\main\IDEasy\cli\target> .\ideasy.exe mvn --version
+|===
+
diff --git a/documentation/how-to-unlock-files.adoc b/documentation/how-to-unlock-files.adoc
new file mode 100644
index 000000000..1a16fa41f
--- /dev/null
+++ b/documentation/how-to-unlock-files.adoc
@@ -0,0 +1,56 @@
+= Guide: Finding Processes that Block Specific Files
+
+== Introduction
+
+Sometimes, when you try to perform an action on a file on your Windows computer,
+you may encounter an error message that says the file is in use and cannot be accessed.
+This is usually because the file is locked by a process that is currently running on your system.
+In this guide, we will show you how to find the processes that are blocking specific files,
+as well as provide information on Windows file locks and resulting problems and solutions.
+
+== Understanding Windows File Locks
+
+In Windows, when a process opens a file, the operating system creates a file lock to prevent other processes from modifying or deleting the file while it is being used.
+When a file is locked, any attempt to modify or delete the file will fail.
+This can result in errors or unexpected behavior when you try to perform actions on the file.
+
+There are two types of file locks in Windows: shared locks and exclusive locks.
+Shared locks allow multiple processes to access a file at the same time,
+but prevent any one process from modifying or deleting the file while it is in use.
+Exclusive locks, on the other hand, prevent all other processes from accessing the file while it is in use.
+
+== Finding Processes that Block Specific Files
+
+To find the processes that are blocking a specific file, you can use the Windows Resource Monitor or the Command Prompt.
+Here are the steps to do so:
+
+1. Open the Windows Resource Monitor by typing "resmon" in the Start menu search bar, then press Enter.
+
+2. Click on the CPU tab.
+
+3. In the Associated Handles section, type the name of the file that is being blocked in the Search Handles field, then press Enter.
+
+4. The processes that are blocking the file will be listed in the lower pane of the Resource Monitor. Note the name of the process that is blocking the file.
++
+image::images/ResourceManager.png[UnlockFileResourceMonitor]
++
+5. You can right click the process name and terminate it.
+
+== Solutions for File Locking Problems
+
+If you have identified the process that is blocking a file, you can take one of the following actions:
+
+- Close the process: If the process is not necessary, you can close it to release the file lock.
+
+- Wait for the process to complete: If the process is performing a necessary operation on the file, you can wait for it to complete before attempting to perform your action.
+
+- Use a file unlocking tool: There are many third-party tools available that can unlock files and release file locks.
+Some popular tools include Unlocker and ProcessExplorer.
+Another option is IObit Unlocker, which is a free tool that can help you unlock and delete files that are in use.
+You can download IObit Unlocker from the following link: https://www.iobit.com/en/iobit-unlocker.php
+
+== Conclusion
+
+In this guide, we have provided information on Windows file locks and how to find processes that are blocking specific files.
+We have also provided solutions for file locking problems, including using third-party tools like Unlocker, ProcessExplorer, and IObit Unlocker.
+If you encounter file locking problems on your Windows computer, following the steps in this guide should help you resolve them.
diff --git a/documentation/how-to-unlock-files.asciidoc b/documentation/how-to-unlock-files.asciidoc
deleted file mode 100644
index dab3c9631..000000000
--- a/documentation/how-to-unlock-files.asciidoc
+++ /dev/null
@@ -1,41 +0,0 @@
-= Guide: Finding Processes that Block Specific Files
-
-== Introduction
-
-Sometimes, when you try to perform an action on a file on your Windows computer, you may encounter an error message that says the file is in use and cannot be accessed. This is usually because the file is locked by a process that is currently running on your system. In this guide, we will show you how to find the processes that are blocking specific files, as well as provide information on Windows file locks and resulting problems and solutions.
-
-== Understanding Windows File Locks
-
-In Windows, when a process opens a file, the operating system creates a file lock to prevent other processes from modifying or deleting the file while it is being used. When a file is locked, any attempt to modify or delete the file will fail. This can result in errors or unexpected behavior when you try to perform actions on the file.
-
-There are two types of file locks in Windows: shared locks and exclusive locks. Shared locks allow multiple processes to access a file at the same time, but prevent any one process from modifying or deleting the file while it is in use. Exclusive locks, on the other hand, prevent all other processes from accessing the file while it is in use.
-
-== Finding Processes that Block Specific Files
-
-To find the processes that are blocking a specific file, you can use the Windows Resource Monitor or the Command Prompt. Here are the steps to do so:
-
-1. Open the Windows Resource Monitor by typing "resmon" in the Start menu search bar, then press Enter.
-
-2. Click on the CPU tab.
-
-3. In the Associated Handles section, type the name of the file that is being blocked in the Search Handles field, then press Enter.
-
-4. The processes that are blocking the file will be listed in the lower pane of the Resource Monitor. Note the name of the process that is blocking the file.
-+
-image::images/ResourceManager.png[UnlockFileResourceMonitor]
-+
-5. You can right click the process name and terminate it.
-
-== Solutions for File Locking Problems
-
-If you have identified the process that is blocking a file, you can take one of the following actions:
-
-- Close the process: If the process is not necessary, you can close it to release the file lock.
-
-- Wait for the process to complete: If the process is performing a necessary operation on the file, you can wait for it to complete before attempting to perform your action.
-
-- Use a file unlocking tool: There are many third-party tools available that can unlock files and release file locks. Some popular tools include Unlocker and ProcessExplorer. Another option is IObit Unlocker, which is a free tool that can help you unlock and delete files that are in use. You can download IObit Unlocker from the following link: https://www.iobit.com/en/iobit-unlocker.php
-
-== Conclusion
-
-In this guide, we have provided information on Windows file locks and how to find processes that are blocking specific files. We have also provided solutions for file locking problems, including using third-party tools like Unlocker, ProcessExplorer, and IObit Unlocker. If you encounter file locking problems on your Windows computer, following the steps in this guide should help you resolve them.
\ No newline at end of file
diff --git a/documentation/images/cppInstall.png b/documentation/images/cppInstall.png
new file mode 100644
index 000000000..c64d9f290
Binary files /dev/null and b/documentation/images/cppInstall.png differ
diff --git a/documentation/images/graalvmMvnArgs.png b/documentation/images/graalvmMvnArgs.png
new file mode 100644
index 000000000..98b8c29cb
Binary files /dev/null and b/documentation/images/graalvmMvnArgs.png differ
diff --git a/documentation/jmc.asciidoc b/documentation/jmc.asciidoc
deleted file mode 100644
index 75f4918b6..000000000
--- a/documentation/jmc.asciidoc
+++ /dev/null
@@ -1,14 +0,0 @@
-:toc:
-toc::[]
-
-# Java Mission Control
-
-The `jmc` commandlet allows to install and setup https://www.oracle.com/java/technologies/jdk-mission-control.html[Java Mission Control]. To learn more about Java Mission Control, please go https://docs.oracle.com/en/java/java-components/jdk-mission-control/index.html[here].
-
-The arguments (`devon jmc «args»`) are explained by the following table:
-
-[options="header"]
-|=======================
-|*Command* |*Meaning*
-|`install jmc` |install Java Mission Control (or update and verify)
-|`jmc «args»` |run Java Mission Control with the given `«args»`
\ No newline at end of file
diff --git a/documentation/log.adoc b/documentation/log.adoc
new file mode 100644
index 000000000..d446a046f
--- /dev/null
+++ b/documentation/log.adoc
@@ -0,0 +1,5 @@
+:toc:
+toc::[]
+
+= log
+The log directory is used to store log files e.g. for the IDE link:configurator.adoc[configurator]. You may look here for debug information if something goes wrong.
diff --git a/documentation/log.asciidoc b/documentation/log.asciidoc
deleted file mode 100644
index a7dd2ea39..000000000
--- a/documentation/log.asciidoc
+++ /dev/null
@@ -1,5 +0,0 @@
-:toc:
-toc::[]
-
-= log
-The log directory is used to store log files e.g. for the IDE link:configurator.asciidoc[configurator]. You may look here for debug information if something goes wrong.
diff --git a/documentation/lombok.asciidoc b/documentation/lombok.adoc
similarity index 65%
rename from documentation/lombok.asciidoc
rename to documentation/lombok.adoc
index edc7271dc..48610d868 100644
--- a/documentation/lombok.asciidoc
+++ b/documentation/lombok.adoc
@@ -9,20 +9,20 @@ As this requires some tweaks for IDEs we do support you with this guide in case
== Lombok in Eclipse
For eclipse there is a plugin to activate https://projectlombok.org/setup/eclipse[lombok support in eclipse].
-We have this already configured for you in our default link:settings.asciidoc[settings]. So for manual installation after link:setup.asciidoc[setup], you can get it via this command:
+We have this already configured for you in our default link:settings.adoc[settings]. So for manual installation after link:setup.adoc[setup], you can get it via this command:
```
ide eclipse add-plugin lombok
```
-However, to avoid manual extra effort for lombok based projects you only need to activate this plugin in your project specific link:settings.asciidoc[settings] in https://github.com/devonfw/ide-settings/blob/master/eclipse/plugins/lombok.properties#L3[lombok.properties for eclipse] (replace `false` with `true` for `plugin_active`).
+However, to avoid manual extra effort for lombok based projects you only need to activate this plugin in your project specific link:settings.adoc[settings] in https://github.com/devonfw/ide-settings/blob/master/eclipse/plugins/lombok.properties#L3[lombok.properties for eclipse] (replace `false` with `true` for `plugin_active`).
== Lombok for VS-Code
For VisualStudio Code there is an extension to activate https://projectlombok.org/setup/vscode[lombok support in VS-Code].
-We have this already preconfigured for you in our default link:settings.asciidoc[settings]. So for manual installation after link:setup.asciidoc[setup], you can get it via this command:
+We have this already preconfigured for you in our default link:settings.adoc[settings]. So for manual installation after link:setup.adoc[setup], you can get it via this command:
```
ide vscode add-plugin lombok
```
-However, to avoid manual extra effort for lombok based projects you only need to activate this plugin in your project specific link:settings.asciidoc[settings] in https://github.com/devonfw/ide-settings/blob/master/vscode/plugins/lombok.properties#L2[lombok.properties for vscode] (replace `false` with `true` for `plugin_active`).
+However, to avoid manual extra effort for lombok based projects you only need to activate this plugin in your project specific link:settings.adoc[settings] in https://github.com/devonfw/ide-settings/blob/master/vscode/plugins/lombok.properties#L2[lombok.properties for vscode] (replace `false` with `true` for `plugin_active`).
== Lombok for IntelliJ
diff --git a/documentation/pom.xml b/documentation/pom.xml
index bc06310a6..91c39da7b 100644
--- a/documentation/pom.xml
+++ b/documentation/pom.xml
@@ -17,6 +17,7 @@
IDEasy
+ adoc
dev-SNAPSHOT
diff --git a/documentation/projects.asciidoc b/documentation/repository.adoc
similarity index 57%
rename from documentation/projects.asciidoc
rename to documentation/repository.adoc
index eaba11683..dcf054770 100644
--- a/documentation/projects.asciidoc
+++ b/documentation/repository.adoc
@@ -3,7 +3,9 @@ toc::[]
= Project import
-`IDEasy` supports to automatically check out and import required projects into your IDE during link:setup.asciidoc[setup]. To configure this you put a `.properties` file for each desired project into the `projects` sub-folder in your link:settings.asciidoc[settings]. Each `.properties` file describes one "project" which you would like to check out and (potentially) import:
+`IDEasy` supports to automatically check out and import required git repositories into your IDE during link:setupadoc[setup].
+To configure this you put a `.properties` file for each desired project into the `repository` sub-folder in your link:settingsadoc[settings].
+Each `.properties` file describes one "project" which you would like to check out and (potentially) import:
[source, properties]
----
@@ -14,11 +16,11 @@ git_url=http://github.com/someorg/someproject
git_branch=develop
build_path=.
build_cmd=mvn -DskipTests=true -Darchetype.test.skip=true clean install
-eclipse=import
+import=eclipse
active=true
----
- .Variables of project import
+ .Variables of repository import
[options="header"]
|===
|*Variable*|*Value*|*Meaning*
@@ -30,9 +32,8 @@ active=true
|`build_path`|e.g. `.` (default)|(optional) The directory inside `path` where to trigger an initial build after clone or pull (if `build_cmd` is set). For a regular project use `.` to build top-level project.
|`build_cmd`
|e.g. `mvn -D skip Tests=true -Darchetype.test.skip=true clean install`
-|(optional) The _devonfw_ command to invoke to build the project after clone or pull. If omitted no build is triggered.
-|`eclipse`|e.g. `import`|(optional) Desired action for eclipse IDE. If you put `import` here all modules (eclipse projects) in the current project will be imported into eclipse. If you leave this out or put any other value for this parameter, no change in eclipse is done.
-|`active`|`true`|(optional) If set to `false` the project is skipped during the link:setup.asciidoc[setup].
+|(optional) The _IDEasy_ command to build the project after clone or pull.
+If omitted no build is triggered.
+|`import`|e.g. `eclipse`|(optional) Comma separated list of IDEs to import the repository into.
+|`active`|`true`|(optional) If set to `false` the repository is skipped during the link:setup.adoc[setup].
|===
-
-Please note that the `.properties` file is parsed via shell and not via java. So be careful with "advanced" features `.properties` files normally support.
diff --git a/documentation/scripts.asciidoc b/documentation/scripts.asciidoc
deleted file mode 100644
index 4ffa8119b..000000000
--- a/documentation/scripts.asciidoc
+++ /dev/null
@@ -1,68 +0,0 @@
-:toc:
-toc::[]
-
-= scripts
-This directory is the heart of the `devonfw-ide` and contains the required link:https://github.com/devonfw/ide/tree/master/scripts/src/main/resources/scripts[scripts].
-
-.File structure of the conf folder
-[subs=+macros]
-----
-/scripts
-├──/ https://github.com/devonfw/ide/tree/master/scripts/src/main/resources/scripts/command[command]
-│ ├── link:android-studio.asciidoc[android-studio]
-│ ├── link:aws.asciidoc[aws]
-│ ├── link:az.asciidoc[az]
-│ ├── link:build.asciidoc[build]
-│ ├── link:docker.asciidoc[docker]
-│ ├── link:dotnet.asciidoc[dotnet]
-│ ├── link:eclipse.asciidoc[eclipse]
-│ ├── link:gcloud.asciidoc[gcloud]
-│ ├── link:gcviewer.asciidoc[gcviewer]
-│ ├── link:gh.asciidoc[gh]
-│ ├── link:graalvm.asciidoc[graalvm]
-│ ├── link:gradle.asciidoc[gradle]
-│ ├── link:helm.asciidoc[helm]
-│ ├── link:help.asciidoc[help]
-│ ├── link:ide.asciidoc[ide]
-│ ├── link:intellij.asciidoc[intellij]
-│ ├── link:ionic.asciidoc[ionic]
-│ ├── link:jasypt.asciidoc[jasypt]
-│ ├── link:java.asciidoc[java]
-│ ├── link:jenkins.asciidoc[jenkins]
-│ ├── link:jmc.asciidoc[jmc]
-│ ├── link:kotlinc.asciidoc[kotlinc]
-│ ├── link:kotlinc-native.asciidoc[kotlinc-native]
-│ ├── link:kubectl.asciidoc[kubectl]
-│ ├── link:lazydocker.asciidoc[lazydocker]
-│ ├── link:mvn.asciidoc[mvn]
-│ ├── link:ng.asciidoc[ng]
-│ ├── link:node.asciidoc[node]
-│ ├── link:npm.asciidoc[npm]
-│ ├── link:oc.asciidoc[oc]
-│ ├── link:project.asciidoc[project]
-│ ├── link:python.asciidoc[python]
-│ ├── link:pip.asciidoc[pip]
-│ ├── link:quarkus.asciidoc[quarkus]
-│ ├── link:release.asciidoc[release]
-│ ├── link:rewrite.asciidoc[rewrite]
-│ ├── link:sonar.asciidoc[sonar]
-│ ├── link:terraform.asciidoc[terraform]
-│ ├── link:tomcat.asciidoc[tomcat]
-│ ├── link:vscode.asciidoc[vscode]
-│ └── link:yarn.asciidoc[yarn]
-├── link:cli.asciidoc[devon]
-├── link:cli.asciidoc[devon.bat]
-├── link:configuration.asciidoc[environment-project]
-├── link:configuration.asciidoc[environment-project.bat]
-├── link:functions.asciidoc[functions]
-└── link:configuration.asciidoc[devon.properties]
-----
-
-The https://github.com/devonfw/ide/tree/master/scripts/src/main/resources/scripts/command[command] folder contains the link:cli.asciidoc#commandlets[commandlets].
-The https://github.com/devonfw/ide/tree/master/scripts/src/main/resources/scripts/devon[devon] script is the key link:cli.asciidoc[command line interface] for `devonfw-ide`.
-There is also https://github.com/devonfw/ide/tree/master/scripts/src/main/resources/scripts/devon.bat[devon.bat] that can be used in cmd or PowerShell.
-As the `devon` link:cli.asciidoc[CLI] can be used as a global command on your computer from any directory and gets link:setup.asciidoc#install[installed] centrally, it aims to be stable, minimal, and lightweight.
-The key logic to set up the environment variables is therefore in a separate script https://github.com/devonfw/ide/tree/master/scripts/src/main/resources/scripts/environment-project[environment-project] and its Windows variant https://github.com/devonfw/ide/tree/master/scripts/src/main/resources/scripts/environment-project.bat[environment-project.bat] inside this `scripts` folder.
-The file https://github.com/devonfw/ide/tree/master/scripts/src/main/resources/scripts/functions[functions] contains a collection of reusable bash functions.
-These are sourced and used by the link:cli.asciidoc#commandlets[commandlets].
-Finally the `devon.properties` file contains defaults for the general link:configuration.asciidoc[configuration] of `devonfw-ide`.
diff --git a/documentation/settings.adoc b/documentation/settings.adoc
new file mode 100644
index 000000000..6453a3466
--- /dev/null
+++ b/documentation/settings.adoc
@@ -0,0 +1,70 @@
+:toc:
+toc::[]
+
+= settings
+
+`IDEasy` requires `settings` with configuration templates for the arbitrary tools.
+
+To get an initial set of these settings we provide the default https://github.com/devonfw/ide-settings[ide-settings] as an initial package.
+These are also released so you can download the latest stable or any history version at http://search.maven.org/#search|ga|1|a%3A%22devonfw-ide-settings%22[maven central].
+
+To test `IDEasy` or for very small projects you can also use these the latest default settings (just hit return when link:setup.adoc[setup] is asking for the `Settings URL`).
+However, for collaborative projects we strongly encourage you to distribute and maintain the settings via a dedicated and project specific `git` repository.
+This gives you the freedom to control and manage the tools with their versions and configurations during the project lifecycle.
+Therefore simply follow the link:usage.adoc#admin[admin usage guide].
+
+== Structure
+The settings folder (see `link:variables.adoc[SETTINGS_PATH]`) has to follow this file structure:
+
+.File structure of settings
+[subs=+macros]
+----
+/settings
+├──/ https://github.com/devonfw/ide-settings/tree/main/template[template]
+│ ├──/ https://github.com/devonfw/ide-settings/tree/main/template/conf[conf]
+│ │ ├──/ https://github.com/devonfw/ide-settings/tree/main/template/conf/.m2[.m2]
+│ │ │ └── https://github.com/devonfw/ide-settings/blob/main/template/.m2/settings.xml[settings.xml]
+│ │ ├──/ https://github.com/devonfw/ide-settings/tree/main/template/conf/npm[npm]
+│ │ │ └── https://github.com/devonfw/ide-settings/blob/main/template/conf/npm/.npmrc[.npmrc]
+│ │ └── https://github.com/devonfw/ide-settings/blob/main/template/conf/ide.properties[ide.properties]
+├──/ https://github.com/devonfw/ide-settings/tree/main/eclipse[eclipse]
+│ ├──/ https://github.com/devonfw/ide-settings/tree/main/eclipse/workspace[workspace]
+│ │ ├──/ https://github.com/devonfw/ide-settings/tree/main/eclipse/workspace/setup[setup]
+│ │ └──/ https://github.com/devonfw/ide-settings/tree/main/eclipse/workspace/update[update]
+│ ├── https://github.com/devonfw/ide-settings/blob/main/eclipse/lifecycle-mapping-metadata.xml[lifecycle-mapping-metadata.xml]
+│ └── https://github.com/devonfw/ide-settings/blob/main/eclipse/project.dictionary[project.dictionary]
+├──/ https://github.com/devonfw/ide-settings/tree/main/intellij[intellij]
+│ └──/ https://github.com/devonfw/ide-settings/tree/main/intellij/workspace[workspace]
+│ ├──/ https://github.com/devonfw/ide-settings/tree/main/intellij/workspace/setup[setup]
+│ └──/ https://github.com/devonfw/ide-settings/tree/main/intellij/workspace/update[update]
+├──/ https://github.com/devonfw/ide-settings/tree/main/vscode[vscode]
+│ └──/ https://github.com/devonfw/ide-settings/tree/main/vscode/workspace[workspace]
+│ ├──/ https://github.com/devonfw/ide-settings/tree/main/vscode/workspace/setup[setup]
+│ └──/ https://github.com/devonfw/ide-settings/tree/main/vscode/workspace/update[update]
+├──/ ...
+└── https://github.com/devonfw/ide-settings/blob/main/ide.properties[ide.properties]
+----
+
+As you can see, the `settings` folder contains various configurations to customize IDEasy to your project needs.
+The key configuration is the top-level `ide.properties` file.
+The `template` folder contains templates that will be applied to `$IDE_HOME` and therefore allow to define the initial content of the `conf` folder with the defaults for the developer specific link:configuration.adoc[configuration] (e.g. `settings.xml` and `ide.properties`).
+Further, for the IDEs such as https://www.eclipse.org/[Eclipse], https://code.visualstudio.com/[VSCode], or https://www.jetbrains.com/idea/[IntelliJ] the according folders contain the templates to manage the workspace via our link:configurator.adoc[configurator].
+
+== Configuration Philosophy
+Different tools and configuration files require a different handling:
+
+* Where suitable, we directly use these configurations from your `settings` (e.g. for `eclipse/lifecycle-mapping-metadata.xml`, or `eclipse/project.dictionary`).
+* The `template` folder in `settings` contains templates for configuration files.
+They are copied to the `IDEasy` installation during link:setup.adoc[setup] (if no such file already exists).
+In this way the `settings` repository can provide reasonable defaults but allows the user to take over control and customize to his personal needs (e.g. `.m2/settings.xml`).
+* For tools with complex configuration structures like Eclipse, IntelliJ, or VScode we provide a smart mechanism via our link:configurator.adoc[configurator].
+
+== Customize Settings
+You can easily customize these settings for the requirements of your project.
+We suggest that one team member is responsible to ensure that everything stays consistent and works.
+However, every team member can contribute changes as pull-/merge-requests.
+
+You may also create new sub-folders in `settings` and put individual items according to your needs.
+E.g. you could add scripts for https://addons.mozilla.org/de/firefox/addon/greasemonkey[greasemonkey] or https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo[tampermonkey],
+as well as scripts for your database or whatever may be useful and worth to share in your team.
+However, to share and maintain knowledge we recommend to use a wiki.
diff --git a/documentation/settings.asciidoc b/documentation/settings.asciidoc
deleted file mode 100644
index 8fdc1b2c1..000000000
--- a/documentation/settings.asciidoc
+++ /dev/null
@@ -1,62 +0,0 @@
-:toc:
-toc::[]
-
-= settings
-
-`IDEasy` requires `settings` with configuration templates for the arbitrary tools.
-
-To get an initial set of these settings we provide the default https://github.com/devonfw/ide-settings[ide-settings] as an initial package. These are also released so you can download the latest stable or any history version at http://search.maven.org/#search|ga|1|a%3A%22devonfw-ide-settings%22[maven central].
-
-To test `IDEasy` or for very small projects you can also use these the latest default settings (just hit return when link:setup.asciidoc[setup] is asking for the `Settings URL`).
-However, for collaborative projects we strongly encourage you to distribute and maintain the settings via a dedicated and project specific `git` repository.
-This gives you the freedom to control and manage the tools with their versions and configurations during the project lifecycle.
-Therefore simply follow the link:usage.asciidoc#admin[admin usage guide].
-
-== Structure
-The settings folder (see `link:variables.asciidoc[SETTINGS_PATH]`) has to follow this file structure:
-
-.File structure of settings
-[subs=+macros]
-----
-/settings
-├──/ https://github.com/devonfw/ide-settings/tree/master/devon[devon]
-│ ├──/ https://github.com/devonfw/ide-settings/tree/master/devon/conf[conf]
-│ │ ├──/ https://github.com/devonfw/ide-settings/tree/master/devon/conf/.m2[.m2]
-│ │ │ └── https://github.com/devonfw/ide-settings/blob/master/devon/.m2/settings.xml[settings.xml]
-│ │ ├──/ https://github.com/devonfw/ide-settings/tree/master/devon/conf/npm[npm]
-│ │ │ └── https://github.com/devonfw/ide-settings/blob/master/devon/conf/npm/.npmrc[.npmrc]
-│ │ └── https://github.com/devonfw/ide-settings/blob/master/devon/conf/devon.properties[devon.properties]
-├──/ https://github.com/devonfw/ide-settings/tree/master/eclipse[eclipse]
-│ ├──/ https://github.com/devonfw/ide-settings/tree/master/eclipse/workspace[workspace]
-│ │ ├──/ https://github.com/devonfw/ide-settings/tree/master/eclipse/workspace/setup[setup]
-│ │ └──/ https://github.com/devonfw/ide-settings/tree/master/eclipse/workspace/update[update]
-│ ├── https://github.com/devonfw/ide-settings/blob/master/eclipse/lifecycle-mapping-metadata.xml[lifecycle-mapping-metadata.xml]
-│ └── https://github.com/devonfw/ide-settings/blob/master/eclipse/project.dictionary[project.dictionary]
-├──/ https://github.com/devonfw/ide-settings/tree/master/intellij[intellij]
-│ └──/ https://github.com/devonfw/ide-settings/tree/master/intellij/workspace[workspace]
-│ ├──/ https://github.com/devonfw/ide-settings/tree/master/intellij/workspace/setup[setup]
-│ └──/ https://github.com/devonfw/ide-settings/tree/master/intellij/workspace/update[update]
-├──/ https://github.com/devonfw/ide-settings/tree/master/vscode[vscode]
-│ └──/ https://github.com/devonfw/ide-settings/tree/master/vscode/workspace[workspace]
-│ ├──/ https://github.com/devonfw/ide-settings/tree/master/vscode/workspace/setup[setup]
-│ └──/ https://github.com/devonfw/ide-settings/tree/master/vscode/workspace/update[update]
-├──/ ...
-└── https://github.com/devonfw/ide-settings/blob/master/devon.properties[ide.properties]
-----
-
-As you can see, the `settings` folder contains sub-folders for tools of the IDE.
-So the `devon` folder contains `ide.properties` files for the link:configuration.asciidoc[configuration] of your environment.
-Further, for the IDEs such as https://www.eclipse.org/[Eclipse], https://code.visualstudio.com/[VSCode], or https://www.jetbrains.com/idea/[IntelliJ] the according folders contain the templates to manage the workspace via our link:configurator.asciidoc[configurator].
-
-== Configuration Philosophy
-Different tools and configuration files require a different handling:
-
-* Where suitable, we directly use these configurations from your `settings` (e.g. for `eclipse/lifecycle-mapping-metadata.xml`, or `eclipse/project.dictionary`).
-* The `devon` folder in `settings` contains templates for configuration files. There are copied to the `IDEasy` installation during link:setup.asciidoc[setup] (if no such file already exists). In this way the `settings` repository can provide reasonable defaults but allows the user to take over control and customize to his personal needs (e.g. `.m2/settings.xml`).
-* Other configurations need to be imported manually. To avoid manual steps and simplify use we try to automate as much as possible. This currently applies to `sonarqube` profiles but will be automated with https://github.com/devonfw/sonar-devon4j-plugin[sonar-devon4j-plugin] in the future.
-* For tools with complex configuration structures like Eclipse, IntelliJ, or VScode we provide a smart mechanism via our link:configurator.asciidoc[configurator].
-
-== Customize Settings
-You can easily customize these settings for the requirements of your project. We suggest that one team member is responsible to ensure that everything stays consistent and works.
-
-You may also create new sub-folders in `settings` and put individual items according to your needs. E.g. you could add scripts for https://addons.mozilla.org/de/firefox/addon/greasemonkey[greasemonkey] or https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo[tampermonkey], as well as scripts for your database or whatever may be useful and worth to share in your team. However, to share and maintain knowledge we recommend to use a wiki.
diff --git a/documentation/setup.asciidoc b/documentation/setup.adoc
similarity index 98%
rename from documentation/setup.asciidoc
rename to documentation/setup.adoc
index 797be5996..7c10c4fc7 100644
--- a/documentation/setup.asciidoc
+++ b/documentation/setup.adoc
@@ -18,7 +18,7 @@ The latest release of `IDEasy` can be downloaded from https://github.com/devonfw
== Install
Create a central folder `projects` folder (on Windows use `C:\projects` or `D:\projects`, on Linux or Mac use `/projects` or if you do not have such permissions you create it in your home directory as `~/projects`). Inside this folder, create a sub-folder for your new project such as `my-project` and extract the contents of the downloaded archive (`ide-cli-*.tar.gz`) to this new folder. Run the command `setup` in this folder (on windows double clicking on `setup.bat`).
-That's all. To get started read the link:usage.asciidoc[usage].
+That's all. To get started read the link:usage.adoc[usage].
== Uninstall
To "uninstall" your `IDEasy` you only need to call the following command:
diff --git a/documentation/software.adoc b/documentation/software.adoc
new file mode 100644
index 000000000..4e9ab706a
--- /dev/null
+++ b/documentation/software.adoc
@@ -0,0 +1,51 @@
+:doctype: book
+:toc:
+toc::[]
+
+= software
+
+The `software` folder contains the third party tools for your IDE such as maven, npm, java, dotnet, etc.
+However, `IDEasy` only maintains symbolic links here that point to a local xref:repository[].
+
+== Repository
+
+Technically we "install" (extract) all tools into a local repository (in `$IDE_ROOT/_ide/software`) and just place symbolic links to such physical tool version inside the local repository.
+This has the following benefits:
+
+* Switching a tool version forth and back is lightning fast since only a symbolic link needs to be updated.
+* We avoid severe issues with link:how-to-unlock-files.adoc[Windows file-locking].
+* Multiple IDEasy projects can share the same physical tool versions to save disc-space.
+However, we keep previous tool version on updates what can also waste disc-space.
+Therefore, you can run `ide cleanup` to find and release old tool versions and free disc-space.
+
+== Custom Tools
+
+In some cases, a project might need a (proprietary) tools that are not directly supported by `IDEasy`.
+As a solution for this need, `IDEasy` let's you configure custom tools via `settings/ide-custom-tools.json`.
+The following example illustrates how to configure custom tools:
+```json
+{
+ "url": "https://some-file-server.company.com/projects/my-project",
+ "tools": [
+ {
+ "name": "jboss-eap",
+ "version": "7.1.4.GA",
+ "os-agnostic": true,
+ "arch-agnostic": true
+ },
+ {
+ "name": "firefox",
+ "version": "70.0.1",
+ "os-agnostic": false,
+ "arch-agnostic": false
+ }
+ ]
+}
+```
+
+This will download and extract the following content to your `software` folder:
+
+* `https://some-file-server.company.com/projects/my-project/jboss-eap/7.1.4.GA/jboss-eap-7.1.4.GA.tgz`
+* `https://some-file-server.company.com/projects/my-project/firefox/70.0.1/firefox-70.0.1-windows-x64.tgz`
+
+Please note that if you are not using windows, the `-windows` suffix will be `-mac` or `-linux`.
diff --git a/documentation/software.asciidoc b/documentation/software.asciidoc
deleted file mode 100644
index f62126676..000000000
--- a/documentation/software.asciidoc
+++ /dev/null
@@ -1,86 +0,0 @@
-:doctype: book
-:toc:
-toc::[]
-
-= software
-
-The `software` folder contains the third party tools for your IDE such as link:mvn.asciidoc[maven], link:npm.asciidoc[npm], link:java.asciidoc[java], etc.
-With respect to the link:LICENSE.asciidoc[licensing terms] you may create a custom archive containing `IDEasy` together with the required software.
-However, to be platform independent and allow lightweight updates, `IDEasy` is capable to link:setup.asciidoc[download and install] the software automatically for you.
-
-== Repository
-
-By default, software is downloaded via the internet from public download URLs of the according tools.
-However, some projects may need specific tools or tool versions that are not publicly available.
-In such case, they can create their own software repository (e.g. in a `VPN`) and link:configuration.asciidoc[configure] the base URL of it via `SOFTWARE_REPOSITORY` link:variables.asciidoc[variable].
-Then, `IDEasy` will download all software from this repository only instead of the default public download URLs.
-This repository (URL) should be accessible within your network via HTTPS (or HTTP) and without any authentication.
-The repository needs to have the following structure:
-
-```
-${SOFTWARE_REPOSITORY}/«tool»/«version»/«tool»-«version»[-«os»].tgz
-```
-
-So for every tool `«tool»` (link:java.asciidoc[java], link:mvn.asciidoc[maven], link:vscode.asciidoc[vscode], link:eclipse.asciidoc[eclipse], etc.) you need to provide a folder in your repository.
-Within this folder for every supported version `«version»` you need a subfolder.
-This subfolder needs to contain the tool in that version for every operating system `«os»` (`windows`, `linux`, or `mac` - omitted if platform independent, e.g. for `maven`).
-
-== Shared
-
-By default, each installation of `IDEasy` has its own physical installations of the required tools in the desired versions stored in its local `software` folder.
-While this is great for isolation of `IDEasy` installations and to prevent side-effects, it can cause a huge waste of disc resources in case you are having many installations of `IDEasy`.
-If you are a power-user of `IDEasy` with more then ten or even up to hundreds of installations on your machine, you might love to share installations of a software tool in a particular version between multiple `IDEasy` installations.
-In order to do so, you only need to link:configuration.asciidoc[configure] the variable `SOFTWARE_PATH` in your `~/ide.properties` pointing to an existing absolute directory on your disc (e.g. `/projects/software` or `C:\projects\software`).
-
-CAUTION: SOFTWARE_PATH must be an absolute path that is an existing directory.
-On windows it has to be on the same drive as your IDE installations.
-If you use this power-feature you are taking responsibility for side-effects and should not expect support.
-You might also use this link:advanced-tooling-windows.asciidoc#create-symbolic-links[hint] and maintain it manually without enabling the following feature.
-
-Then `IDEasy` will install required software into `${SOFTWARE_PATH}/${software_name}/${software_version}` as needed and create a symbolic link to it in `${IDE_HOME}/software/${software_name}`.
-
-As a benefit, another `IDEasy` installation will using the same software with the same version can re-use the existing installation and only needs to create the symbolic link.
-No more waste of having many identical JDK installations on your disc.
-
-As a drawback, you need to be aware that specific tools may be "manipulated" after installation.
-The most common case is that a tool allows to install plugins or extensions such as all IDEs do.
-Such "manipulations" will cause side-effects between the different `IDEasy` installations sharing the same version of that tool.
-While this can also be a benefit it may also cause trouble.
-If you have a sensitive project that should not be affected by such side-effects, you may again override the `SOFTWARE_PATH` variable to the empty value in your `${IDE_HOME}/conf/ide.properties` of that sensitive installation:
-
-```
-SOFTWARE_PATH=
-```
-
-This will disable this feature particularly for that specific sensitive `IDEasy` installation but let you use it for other ones.
-
-== Custom
-
-In some cases, a project might need a (proprietary) tool(s) that (are) not supported by `IDEasy`.
-A very simple solution is to get a release of `IDEasy` and add the tool(s) to the software folder and then distribute this modified release to your team.
-However, this has several drawbacks as you then have a fork of `IDEasy` all will loose your tool(s) when updating to a new release.
-
-As a solution for this need, `IDEasy` let's you configure custom tools via the `IDE_CUSTOM_TOOLS` link:variables.asciidoc[variable].
-It can be defined in link:configuration.asciidoc[`ide.properties`] of your link:settings.asciidoc[settings] git repository as an array of the custom tools you need to add.
-Each entry applies:
-
-* It needs to have the form `«tool»:«version»[:all]:[«repository-url»]`
-* The first entry must have the `«repository-url»` included which is used as default
-* Further entries will inherit this default if omitted
-* This URL is used in the same way as described above for a software xref:repository[repository].
-* The `SOFTWARE_REPOSITORY` variable is ignored by this feature.
-* The optional infix `:all` is used to indicate that the tool is platform independent.
-Otherwise, an OS specific infix is appended to the URL file to download for your platform (`windows`, `linux`, or `mac`).
-
-As an example, we define it in `${IDE_HOME}/settings/ide.properties`:
-
-```
-IDE_CUSTOM_TOOLS=(jboss-eap:7.1.4.GA:all:https://host.tld/projects/my-project firefox:70.0.1:)
-```
-
-This will download and extract the following content to your `software` folder:
-
-* `https://host.tld/projects/my-project/jboss-eap/7.1.4.GA/jboss-eap-7.1.4.GA.tgz`
-* `https://host.tld/projects/my-project/firefox/70.0.1/firefox-70.0.1-windows.tgz`
-
-Please note that if you are not using windows, the `-windows` suffix will be `-mac` or `-linux`.
diff --git a/documentation/structure.adoc b/documentation/structure.adoc
new file mode 100644
index 000000000..f35420922
--- /dev/null
+++ b/documentation/structure.adoc
@@ -0,0 +1,30 @@
+:toc:
+toc::[]
+
+= Structure
+The directory layout of your `IDEasy` will look like this:
+
+.File structure of your IDEasy
+[subs=+macros]
+----
+/ projects (link:variables.adoc[$IDE_ROOT])
+├──/ _ide/
+| ├──/ bin/
+| | ├──/ ide
+| | └──/ ideasy[.exe]
+| ├──/ link:software.adoc[software]/
+| ├──/ link:system.adoc[system]/
+| ├──/ tmp/
+| └──/ urls/
+└──/ my-project (link:variables.adoc[$IDE_HOME])
+ ├──/ link:conf.adoc[conf]/
+ ├──/ link:log.adoc[log]/
+ ├──/ link:settings.adoc[settings]/
+ ├──/ link:software.adoc[software]/
+ ├──/ link:workspaces.adoc[workspaces]/
+ └── IDEasy-doc.pdf
+----
+
+The elements of the above structure are described in the individual sections.
+As they are hyperlinks you can simply click on them to get more details.
+
diff --git a/documentation/structure.asciidoc b/documentation/structure.asciidoc
deleted file mode 100644
index e83feb9af..000000000
--- a/documentation/structure.asciidoc
+++ /dev/null
@@ -1,26 +0,0 @@
-:toc:
-toc::[]
-
-= Structure
-The directory layout of your `IDEasy` will look like this:
-
-.File structure of your IDEasy
-[subs=+macros]
-----
-/ projects (or C:\Projects, etc.)
-└──/ my-project (link:variables.asciidoc[$IDE_HOME])
- ├──/ link:conf.asciidoc[conf]
- ├──/ link:log.asciidoc[log]
- ├──/ link:scripts.asciidoc[scripts]
- ├──/ link:settings.asciidoc[settings]
- ├──/ link:software.asciidoc[software]
- ├──/ link:system.asciidoc[system]
- ├──/ link:updates.asciidoc[updates]
- ├──/ link:workspaces.asciidoc[workspaces]
- ├── link:setup.asciidoc[setup]
- ├── link:setup.asciidoc[setup.bat]
- └── IDEasy-doc.pdf
-----
-
-The elements of the above structure are described in the individual sections. As they are hyperlinks you can simply click on them to get more details.
-
diff --git a/documentation/symlink.asciidoc b/documentation/symlink.adoc
similarity index 79%
rename from documentation/symlink.asciidoc
rename to documentation/symlink.adoc
index b4cf563dd..738c14c9f 100644
--- a/documentation/symlink.asciidoc
+++ b/documentation/symlink.adoc
@@ -10,7 +10,8 @@ In order to grant these permissions to yourself, run `secpol.msc` (`Local Securi
+
image::images/LocalSecurityPolicy.png[LocalSecurityPolicy]
+
-2. Add your own user account. To do so you might need to be connected to a VPN depending on the company settings.
+2. Add your own user account.
+To do so you might need to be connected to a VPN depending on the company settings.
[cols="3,1a,1a,3", frame=none, grid=none]
|===
diff --git a/documentation/system.adoc b/documentation/system.adoc
new file mode 100644
index 000000000..8856186fe
--- /dev/null
+++ b/documentation/system.adoc
@@ -0,0 +1,8 @@
+:toc:
+toc::[]
+
+= system
+
+The `system` folder contains documentation and solutions for operation system specific integration.
+Please have a look to get the maximum out of `IDEasy` and become a very efficient power user.
+
diff --git a/documentation/system.asciidoc b/documentation/system.asciidoc
deleted file mode 100644
index 13bcccc94..000000000
--- a/documentation/system.asciidoc
+++ /dev/null
@@ -1,7 +0,0 @@
-:toc:
-toc::[]
-
-= system
-
-The link:https://github.com/devonfw/ide/tree/master/scripts/src/main/resources/system[system] folder contains documentation and solutions for operation system specific link:integration.asciidoc[integration]. Please have a look to get the maximum out of `IDEasy` and become a very efficient power user.
-
diff --git a/documentation/updates.asciidoc b/documentation/updates.asciidoc
deleted file mode 100644
index dc22b8f7e..000000000
--- a/documentation/updates.asciidoc
+++ /dev/null
@@ -1,11 +0,0 @@
-:toc:
-toc::[]
-
-= updates
-
-The `updates` folder is used for temporary data. This includes:
-
-* extracted archives for installation and updates
-* backups of old content on updates to prevent data loss
-
-If all works fine you may clean this folder to save some kilo- or mega-bytes. Otherwise, you can ignore it unless you are looking for a backup after a failed or unplanned upgrade.
\ No newline at end of file
diff --git a/documentation/usage.asciidoc b/documentation/usage.adoc
similarity index 77%
rename from documentation/usage.asciidoc
rename to documentation/usage.adoc
index 98a8e7db9..6d235bcf2 100644
--- a/documentation/usage.asciidoc
+++ b/documentation/usage.adoc
@@ -9,7 +9,7 @@ This section explains the usage of `IDEasy` according to your role:
= Usage
== Developer
-As a developer you are supported to link:setup.asciidoc[setup] your IDE automated and fast while you can have a nice cup of coffee (after you provided `settings-URL` and accepted the license).
+As a developer you are supported to link:setup.adoc[setup] your IDE automated and fast while you can have a nice cup of coffee (after you provided `settings-URL` and accepted the license).
You only need the settings URL from your xref:admin[ide-admin].
Experienced developers can directly call `setup «settings-URL»`.
Otherwise if you just call `setup` (e.g. by double-clicking it), you can enter it when you are prompted for `Settings URL` (using copy&paste to avoid typos).
@@ -30,16 +30,16 @@ You can still delete the according installation from your `software` folder and
=== Working with multiple workspaces
If you are working on different branches in parallel you typically want to use multiple workspaces.
-. Go to the link:workspaces.asciidoc[workspaces] folder in your link:variables.asciidoc[${IDE_HOME}] and create a new folder with the name of your choice (e.g. `release2.1`).
+. Go to the link:workspaces.adoc[workspaces] folder in your link:variables.adoc[${IDE_HOME}] and create a new folder with the name of your choice (e.g. `release2.1`).
. Check out (`git clone ...`) the according projects and branch into that workspace folder.
-. Open a shell in that new workspace folder (`cd` to it) and according to your IDE run e.g. link:eclipse.asciidoc[eclipse], link:vscode.asciidoc[vscode], or link:intellij.asciidoc[intellij] to create your workspace and launch the IDE. You can also add the parameter `create-script` to the IDE link:cli.asciidoc#commandlets[commandlet] in order to create a launch-script for your IDE.
+. Open a shell in that new workspace folder (`cd` to it) and according to your IDE run e.g. link:eclipse.adoc[eclipse], link:vscode.adoc[vscode], or link:intellij.adoc[intellij] to create your workspace and launch the IDE. You can also add the parameter `create-script` to the IDE link:cli.adoc#commandlets[commandlet] in order to create a launch-script for your IDE.
You can have multiple instances of eclipse running for each workspace in parallel. To distinguish these instances you will find the workspace name in the title of eclipse.
== Admin
-You can easily customize and link:configuration.asciidoc[configure] `IDEasy` for the requirements of your project.
-In order to do so, you need to create your own project-specific settings git repository and provide the URL to all developers for the link:setup.asciidoc[setup].
+You can easily customize and link:configuration.adoc[configure] `IDEasy` for the requirements of your project.
+In order to do so, you need to create your own project-specific settings git repository and provide the URL to all developers for the link:setup.adoc[setup].
With tools such as gitlab, bitbucket or github every developer can easily propose changes and improvements.
However, we suggest that one team member is responsible to ensure that everything stays consistent and works.
We will call this person the _ide-admin_ of your project.
@@ -56,8 +56,8 @@ git push
```
+
Now you should have a full fork as a copy of the `settings` git repo with all its history that is ready for upstream merges.
-. Study the link:settings.asciidoc#structure[structure] of this git repository to understand where to find which configuration.
-. Study the link:configuration.asciidoc[configuration] and understand that general settings can be tweaked in the toplevel `ide.properties` file of your settings git repository.
+. Study the link:settings.adoc#structure[structure] of this git repository to understand where to find which configuration.
+. Study the link:configuration.adoc[configuration] and understand that general settings can be tweaked in the toplevel `ide.properties` file of your settings git repository.
. Configure the tools and their versions for your project. Here is an example:
+
```
@@ -70,14 +70,14 @@ MAVEN_VERSION=3.6.2
```
+
This way you will take over control of the tools and their versions for every developer in your project team and ensure that things get reproducible.
-. In case you need a proprietary or unsupported tool, you can study link:software.asciidoc#custom[how to include custom tools].
-. In case you have very restrictive policies about downloading tools from the internet, you can create and configure a link:software.asciidoc#repository[software repository] for your project or company.
-. Some of the tools (especially the actual IDEs) allow extensions via plugins. You can customize them to your needs for link:eclipse.asciidoc#plugins[eclipse], link:vscode.asciidoc#plugins[VS code], or link:intellij.asciidoc#plugins[intelliJ].
-. In your `settings` git repository you will find a `projects` folder. Here you will find configurations files for every git project relevant for your actual project. Feel free to create new projects for your needs and delete the `devonfw` specific default projects. The link:projects.asciidoc[projects] documentation will explain you how to do this.
+. In case you need a proprietary or unsupported tool, you can study link:software.adoc#custom[how to include custom tools].
+. In case you have very restrictive policies about downloading tools from the internet, you can create and configure a link:software.adoc#repository[software repository] for your project or company.
+. Some of the tools (especially the actual IDEs) allow extensions via plugins. You can customize them to your needs for link:eclipse.adoc#plugins[eclipse], link:vscode.adoc#plugins[VS code], or link:intellij.adoc#plugins[intelliJ].
+. In your `settings` git repository you will find a `projects` folder. Here you will find configurations files for every git project relevant for your actual project. Feel free to create new projects for your needs and delete the `devonfw` specific default projects. The link:projects.adoc[projects] documentation will explain you how to do this.
. For every IDE you will also find an according folder in your `settings` git repository. Here are the individual configuration settings for that IDE. You can change them by directly editing the according configuration files directly with a text-editor in your `settings` git repository. However, this is a really complex way and will take you a lot of time to find the right file and property to tweak for your actual need. Instead we suggest to study
-link:configurator.asciidoc#how-to-customize[how to customize IDE specific settings].
+link:configurator.adoc#how-to-customize[how to customize IDE specific settings].
. You may also create new sub-folders in your `settings` git repository and put individual things according to your needs. E.g. you could add scripts for https://addons.mozilla.org/de/firefox/addon/greasemonkey[greasemonkey] or https://www.tampermonkey.net/[tampermonkey], as well as scripts for your database or whatever may be useful and worth to share in your team. However, to share and maintain knowledge we recommend to use a wiki instead.
-. You may want to customize the link:eclipse.asciidoc#dictionary[Eclipse spellchecker dictionary] for your project and your language.
+. You may want to customize the link:eclipse.adoc#dictionary[Eclipse spellchecker dictionary] for your project and your language.
All described in the above steps (except the first one) can be used to manage and update the configuration during the project lifecycle.
However, when you have done changes especially in a larger project, please consider the following best-practices to avoid that a large teams gets blocked by a non-functional IDE:
@@ -88,7 +88,8 @@ However, when you have done changes especially in a larger project, please consi
* Only after that works well for a couple of days, inform the entire team to update.
=== Announce changes to your team
-In order to roll out the perfectly configured `IDEasy` to your project initially or when new members join, you only have to provide the `Settings URL` to the xref:developer[developers] of your team.
+In order to roll out the perfectly configured `IDEasy` to your project initially or when new members join,
+you only have to provide the `Settings URL` to the xref:developer[developers] of your team.
You can also provide a specific branch with `Settings URL#branch` to use variations of common settings or to test new settings before making them public to the team.
After you changed and tested your `settings` git repository (main branch), you only need to announce this to your xref:developer[developers] (e.g. via email or some communication tool) so that they will call `ide update` and automatically get up-to-date with the latest changes (see xref:update[update]).
diff --git a/documentation/variables.adoc b/documentation/variables.adoc
new file mode 100644
index 000000000..341c9fe0a
--- /dev/null
+++ b/documentation/variables.adoc
@@ -0,0 +1,35 @@
+:toc:
+toc::[]
+
+= Variables
+
+`IDEasy` defines a set of standard variables to your environment for link:configuration.adoc[configuration].
+These environment variables are described by the following table.
+Those variables printed *bold* are also exported in your shell (except for windows CMD that does not have such concept).
+Variables with the value `-` are not set by default but may be set via link:configuration.adoc[configuration] to override defaults.
+Please note that we are trying to minimize any potential side-effect from `IDEasy` to the outside world by reducing the number of variables and only exporting those that are required.
+
+.Variables of IDEasy
+[options="header"]
+|=======================
+|*Variable*|*Value*|*Meaning*
+|`IDE_ROOT`|e.g. `/projects/` or `C:\projects`|The installation root directory of `IDEasy` - see link:structure.adoc[structure] for details.
+|`IDE_HOME`|e.g. `/projects/my-project`|The top level directory of your `IDEasy` project.
+|`PATH`|`$IDE_HOME/software/java:...:$PATH`|You system path is adjusted by `ide` link:cli.adoc[command].
+|`HOME_DIR`|`~`|The platform independent home directory of the current user. In some edge-cases (e.g. in cygwin) this differs from `~` to ensure a central home directory for the user on a single machine in any context or environment.
+|`IDE_TOOLS`|`(java mvn node npm)`|List of tools that should be installed by default on project creation.
+|`CREATE_START_SCRIPTS`|`(eclipse vscode)`|List of IDEs that shall be used by developers in the project and therefore start-scripts are created on setup.
+|*`WORKSPACE`*|`main`|The link:workspaces.adoc[workspace] you are currently in. Defaults to `main` if you are not inside a link:workspaces.adoc[workspace]. Never set this variable in any `ide.properties` file.
+|`WORKSPACE_PATH`|`$IDE_HOME/workspaces/$WORKSPACE`|Absolute path to current link:workspaces.adoc[workspace]. Never set this variable in any `ide.properties` file.
+|*`JAVA_HOME`*|`$IDE_HOME/software/java`|Path to JDK
+|*`MVN_REPO`*|`$IDE_HOME/conf/mvn/repository`|Path to your local maven repository. For projects without high security demands, you may change this to the maven default `~/.m2/repository` and share your repository among multiple projects.
+|*`MVN_HOME`*|`$IDE_HOME/software/mvn`|Path to Maven
+|*`MAVEN_OPTS`*|`-Xmx512m -s $IDE_HOME/conf/mvn/settings.xml`|Maven options
+|*`DOCKER_EDITION`*|e.g. `rancher`| If set as `docker` the command `ide install docker` will setup Docker Desktop globally at the users computer what requires a subscription/license for professional usage. If set to `rancher` or undefined it will install Rancher Desktop instead.
+|*`GRAALVM_HOME`*|`$IDE_HOME/software/extra/graalvm`|Path to GraalVM
+|`ECLIPSE_VMARGS`|`-Xms128M -Xmx768M -XX:MaxPermSize=256M`|JVM options for Eclipse
+|`«TOOL»_EDITION`|`-`|The edition of the tool `«TOOL»` to install and use (e.g. `ECLIPSE_EDITION`, `INTELLIJ_EDITION` or `DOCKER_EDITION`)
+|`«TOOL»_VERSION`|`-`|The version of the tool `«TOOL»` to install and use (e.g. `ECLIPSE_VERSION` or `MVN_VERSION`).
+|`«TOOL»_BUILD_OPTS`|e.g.`clean install`|The arguments provided to the build-tool `«TOOL»` in order to run a build.
+|`«TOOL»_RELEASE_OPTS`|e.g.`clean deploy -Dchangelist= -Pdeploy`|The arguments provided to the build-tool `«TOOL»` in order to perform a release build.
+|=======================
diff --git a/documentation/variables.asciidoc b/documentation/variables.asciidoc
deleted file mode 100644
index d61c3641c..000000000
--- a/documentation/variables.asciidoc
+++ /dev/null
@@ -1,41 +0,0 @@
-:toc:
-toc::[]
-
-= Variables
-
-`IDEasy` defines a set of standard variables to your environment for link:configuration.asciidoc[configuration] via `variables[.bat]` files.
-These environment variables are described by the following table.
-Those variables printed *bold* are also exported in your shell (except for windows CMD that does not have such concept). Variables with the value `-` are not set by default but may be set via link:configuration.asciidoc[configuration] to override defaults.
-Please note that we are trying to minimize any potential side-effect from `IDEasy` to the outside world by reducing the number of variables and only exporting those that are required.
-
-.Variables of IDEasy
-[options="header"]
-|=======================
-|*Variable*|*Value*|*Meaning*
-|`IDE_HOME`|e.g. `/projects/my-project`|The top level directory of your `IDEasy` link:structure.asciidoc[structure].
-|`PATH`|`$PATH:$IDE_HOME/software/java:...`|You system path is adjusted by `ide` link:cli.asciidoc[command].
-|`HOME_DIR`|`~`|The platform independent home directory of the current user. In some edge-cases (e.g. in cygwin) this differs from `~` to ensure a central home directory for the user on a single machine in any context or environment.
-|`IDE_TOOLS`|`(java mvn node npm)`|List of tools that should be installed and upgraded by default for your current IDE.
-|`IDE_CUSTOM_TOOLS`|`-`|List of custom tools that should be installed additionally. See link:software.asciidoc#custom[software] for further details.
-|`CREATE_START_SCRIPTS`|`(eclipse vscode)`|List of IDEs that shall be used by developers in the project and therefore start-scripts are created on setup.
-|*`IDE_OLD_PATH`*|`...`|A "backup" of `PATH` before it was extended by `ide` to allow recovering it. Internal variable that should never be set or tweaked.
-|*`WORKSPACE`*|`main`|The link:workspaces.asciidoc[workspace] you are currently in. Defaults to `main` if you are not inside a link:workspaces.asciidoc[workspace]. Never touch this variable in any `variables` file.
-|`WORKSPACE_PATH`|`$IDE_HOME/workspaces/$WORKSPACE`|Absolute path to current link:workspaces.asciidoc[workspace]. Never touch this variable in any `variables` file.
-|*`JAVA_HOME`*|`$IDE_HOME/software/java`|Path to JDK
-|`SETTINGS_PATH`|`$IDE_HOME/settings`|Path to your link:settings.asciidoc[settings]. To keep `oasp4j-ide` legacy behaviour set this to `$IDE_HOME/workspaces/main/development/settings`.
-|*`M2_REPO`*|`$IDE_HOME/conf/.m2/repository`|Path to your local maven repository. For projects without high security demands, you may change this to the maven default `~/.m2/repository` and share your repository among multiple projects.
-|*`MVN_HOME`*|`$IDE_HOME/software/mvn`|Path to Maven
-|*`MAVEN_OPTS`*|`-Xmx512m -Duser.home=$IDE_HOME/conf`|Maven options
-|*`DOCKER_EDITION`*|e.g. `docker`| If set as `docker` the command `ide install docker` will setup Docker Desktop globally at the users computer what requires a subscription/license for professional usage. If set to `rancher` or undefined it will install Rancher Desktop instead.
-|*`GRAALVM_HOME`*|`$IDE_HOME/software/extra/graalvm`|Path to GraalVM
-|`SOFTWARE_REPOSITORY`|`-`|Project specific or custom link:software.asciidoc#repository[software-repository].
-|`SOFTWARE_PATH`|`-`|Globally shared user-specific link:software.asciidoc#shared[local software installation location].
-|`ECLIPSE_VMARGS`|`-Xms128M -Xmx768M -XX:MaxPermSize=256M`|JVM options for Eclipse
-|deprecated: `ECLIPSE_PLUGINS`|`-`|Array with "feature groups" and "update site URLs" to customize required link:eclipse.asciidoc#plugins[eclipse plugins]. Deprecated - see link:eclipse.asciidoc#plugins[Eclipse plugins].
-|`«TOOL»_EDITION`|`-`|The edition of the tool `«TOOL»` to install and use (e.g. `ECLIPSE_EDITION`, `INTELLIJ_EDITION` or `DOCKER_EDITION`)
-|`«TOOL»_VERSION`|`-`|The version of the tool `«TOOL»` to install and use (e.g. `ECLIPSE_VERSION` or `MVN_VERSION`).
-|`EXTRA_JAVA_VERSION`|`-`|An additional (newer) version of link:java.asciidoc[java] that will be used to run java-based IDEs (e.g. link:eclipse.asciidoc[eclipse] or link:intellij.asciidoc[intellij]).
-|`«TOOL»_BUILD_OPTS`|e.g.`clean install`|The arguments provided to the build-tool `«TOOL»` in order to run a build.
-|`«TOOL»_RELEASE_OPTS`|e.g.`clean deploy -Dchangelist= -Pdeploy`|The arguments provided to the build-tool `«TOOL»` in order to perform a release build.
-|`IDE_TRACE`||If value is not an empty string, the `IDEasy` scripts will trace each script line executed. For bash two lines output: before and again after expansion. *ATTENTION:* This is not a regular variable working via `ide.properties`. Instead manually do `export IDE_TRACE=true` in bash or `set IDE_TRACE=true` in windows CMD before running an ide command to get a trace log that you can provide to experts in order to trace down a bug and see what went wrong.
-|=======================
diff --git a/documentation/workspaces.adoc b/documentation/workspaces.adoc
new file mode 100644
index 000000000..2eb35531b
--- /dev/null
+++ b/documentation/workspaces.adoc
@@ -0,0 +1,35 @@
+:toc:
+toc::[]
+
+= workspaces
+
+The `workspaces` folder contains folders for your active work.
+There is a workspace folder `main` dedicated for your primary work.
+You may do all your work inside the `main` workspace.
+Also, you are free to create any number of additional workspace folders named as you like (e.g. `test`, `release`, `testing`, `my-sub-project`, etc.).
+Using multiple workspaces allows to run multiple IDE runtime instances with separate configurations within the same IDEasy project.
+
+Within the workspace folder (e.g. `workspaces/main`) you are again free to create sub-folders for (sub-)projects according to your needs.
+We assume that in most cases you clone link:repository.adoc[git repositories] here.
+The following structure shows an example layout:
+
+.File structure of workspaces
+[subs=+macros]
+----
+/ workspaces/
+├──/ main/
+│ ├──/ link:configurator.adoc[.metadata/]
+│ ├──/ IDEasy/
+│ ├──/ ide-settings/
+│ └──/ test-project/
+└──/ stable/
+ ├──/ link:configurator.adoc[.metadata/]
+ └──/ IDEasy/
+----
+
+In the `main` workspace you may find the cloned forks for regular work as a base to create pull-requests while in the `stable` workspace there is a clone of the official repository.
+Or you have checked out the `main` branch of your project-repository in `main` while in `stable` there is the latest production release branch.
+However, this is just an example.
+Some people like to create separate workspaces for development and maintenance branches with git.
+Other people just switch between those via `git checkout`.
+IDEasy offers you great flexibility and you are free to do what makes sense for you.
diff --git a/documentation/workspaces.asciidoc b/documentation/workspaces.asciidoc
deleted file mode 100644
index 178d84ffb..000000000
--- a/documentation/workspaces.asciidoc
+++ /dev/null
@@ -1,26 +0,0 @@
-:toc:
-toc::[]
-
-= workspaces
-
-The `workspaces` folder contains folders for your active work. There is a workspace folder `main` dedicated for your primary work. You may do all your work inside the `main` workspace. Also, you are free to create any number of additional workspace folders named as you like (e.g. `test`, `release`, `testing`, `my-sub-project`, etc.). Using multiple workspaces is especially relevant for Eclipse as each workspace has its own Eclipse runtime instance and configuration.
-
-Within the workspace folder (e.g. `workspaces/main`) you are again free to create sub-folders for (sub-)projects according to your needs. We assume that in most cases you clone git repositories here. The following structure shows an example layout for devonfw:
-
-.File structure of workspaces
-[subs=+macros]
-----
-/ workspaces
-├──/ main
-│ ├──/ link:configurator.asciidoc[.metadata]
-│ ├──/ https://github.com/devonfw/ide[ide]
-│ ├──/ https://github.com/devonfw/devon4j[devon4j]
-│ └──/ https://github.com/devonfw/my-thai-star[my-thai-star]
-└──/ stable
- ├──/ link:configurator.asciidoc[.metadata]
- ├──/ https://github.com/devonfw/ide[ide]
- └──/ https://github.com/devonfw/devon4j[devon4j]
-----
-
-In the `main` workspace you may find the cloned forks for regular work (in the example e.g. `devon4j`) as a base to create pull-requests while in the `stable` workspace there is a clone of `devon4j` from the official https://github.com/devonfw/devon4j/[devon4j].
-However, this is just an example. Some people like to create separate workspaces for development and maintenance branches with git. Other people just switch between those via `git checkout`.
diff --git a/pom.xml b/pom.xml
index 7322df841..fef77ed7a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -62,6 +62,12 @@
3.24.2
test
+
+ org.mockito
+ mockito-core
+ 5.10.0
+ test
+