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 7ee9769f4..d71ddc3bc 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 @@ -132,10 +132,29 @@ public Commandlet getCommandletByFirstKeyword(String keyword) { } /** + * This method gives global access to the {@link CommandletManager} instance. Typically you should have access to + * {@link IdeContext} and use {@link IdeContext#getCommandletManager()} to access the proper instance of + * {@link CommandletManager}. Only in very specific cases where there is no {@link IdeContext} available, you may use + * this method to access it (e.g. from {@link com.devonfw.tools.ide.property.CommandletProperty}) + * + * @return the static instance of this {@link CommandletManager} implementation that has already been initialized. + * @throws IllegalStateException if the instance has not been previously initialized via + * {@link #getOrCreate(IdeContext)}. + */ + public static CommandletManager get() { + + return getOrCreate(null); + } + + /** + * This method has to be called initially from {@link IdeContext} to create the instance of this + * {@link CommandletManager} implementation. It will store that instance internally in a static variable so it can + * later be retrieved with {@link #get()}. + * * @param context the {@link IdeContext}. * @return the {@link CommandletManager}. */ - public static CommandletManager of(IdeContext context) { + public static CommandletManager getOrCreate(IdeContext context) { if (context == null) { if (INSTANCE == null) { 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 929b05515..ac24d3a99 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 @@ -29,6 +29,8 @@ public class SystemPath { private final IdeContext context; + private static final List EXTENSION_PRIORITY = List.of(".exe", ".cmd", ".bat", ".msi", ".ps1", ""); + /** * The constructor. * @@ -89,6 +91,7 @@ private void collectToolPath(Path softwarePath) { if (Files.isDirectory(bin)) { toolPath = bin; } + this.paths.add(0, toolPath); this.tool2pathMap.put(child.getFileName().toString(), toolPath); } } @@ -112,6 +115,41 @@ private static String getTool(Path path, Path softwarePath) { return null; } + private Path findBinaryInOrder(Path path, String tool) { + + for (String extension : EXTENSION_PRIORITY) { + + Path fileToExecute = path.resolve(tool + extension); + + if (Files.exists(fileToExecute)) { + return fileToExecute; + } + } + + return null; + } + + public Path findBinary(Path toolPath) { + Path parent = toolPath.getParent(); + String fileName = toolPath.getFileName().toString(); + + if (parent == null) { + for (Path path : this.paths) { + Path binaryPath = findBinaryInOrder(path, fileName); + if (binaryPath != null) { + return binaryPath; + } + } + } else { + Path binaryPath = findBinaryInOrder(parent, fileName); + if (binaryPath != null) { + return binaryPath; + } + } + + return toolPath; + } + /** * @param tool the name of the tool. * @return the {@link Path} to the directory of the tool where the binaries can be found or {@code null} if the tool 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 d1f99bf49..6265878c6 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 @@ -124,7 +124,7 @@ public AbstractIdeContext(IdeLogLevel minLogLevel, Function tags) { + + super(context, tool, tags); + } + + @Override + protected boolean isExtract() { + // for global tools we usually download installers and do not want to extract them (e.g. installer.msi file shall not be extracted) + return false; + } + + @Override + protected boolean doInstall(boolean silent) { + + Path binaryPath = this.context.getPath().findBinary(Path.of(getBinaryName())); + //if force mode is enabled, go through with the installation even if the tool is already installed + if (binaryPath != null && Files.exists(binaryPath) && !this.context.isForceMode()) { + this.context.debug("{} is already installed at {}", this.tool, binaryPath); + return false; + } + String edition = getEdition(); + ToolRepository toolRepository = this.context.getDefaultToolRepository(); + VersionIdentifier configuredVersion = getConfiguredVersion(); + VersionIdentifier resolvedVersion = toolRepository.resolveVersion(this.tool, edition, configuredVersion); + // 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()) { + downloadBinaryPath = fileAccess.findFirst(downloadBinaryPath, Files::isExecutable, false); + } + ProcessContext pc = this.context.newProcess().errorHandling(ProcessErrorHandling.WARNING).executable(downloadBinaryPath); + int exitCode = pc.run(); + fileAccess.delete(tmpDir); + fileAccess.delete(target); + if (exitCode == 0) { + this.context.success("Successfully installed {} in version {}", this.tool, resolvedVersion); + } else { + this.context.warning("{} in version {} was not successfully installed", this.tool, resolvedVersion); + return false; + } + 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 new file mode 100644 index 000000000..8505dead7 --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java @@ -0,0 +1,185 @@ +package com.devonfw.tools.ide.tool; + +import com.devonfw.tools.ide.commandlet.Commandlet; +import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.io.FileAccess; +import com.devonfw.tools.ide.io.FileCopyMode; +import com.devonfw.tools.ide.log.IdeLogLevel; +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. + */ +public abstract class LocalToolCommandlet extends ToolCommandlet { + + /** + * 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 LocalToolCommandlet(IdeContext context, String tool, Set tags) { + + super(context, tool, tags); + } + + + /** + * @return the {@link Path} where the tool is located (installed). + */ + public Path getToolPath() { + + return this.context.getSoftwarePath().resolve(getName()); + } + + /** + * @return the {@link Path} where the executables of the tool can be found. Typically a "bin" folder inside + * {@link #getToolPath() tool path}. + */ + public Path getToolBinPath() { + + Path toolPath = getToolPath(); + Path binPath = this.context.getFileAccess().findFirst(toolPath, path -> path.getFileName().toString().equals("bin"), + false); + if ((binPath != null) && Files.isDirectory(binPath)) { + return binPath; + } + return toolPath; + } + + @Override + protected boolean doInstall(boolean silent) { + + VersionIdentifier configuredVersion = getConfiguredVersion(); + // install configured version of our tool in the software repository if not already installed + ToolInstallation installation = installInRepo(configuredVersion); + + // check if we already have this version installed (linked) locally in IDE_HOME/software + VersionIdentifier installedVersion = getInstalledVersion(); + VersionIdentifier resolvedVersion = installation.resolvedVersion(); + if (isInstalledVersion(resolvedVersion, installedVersion, silent)) { + return false; + } + // we need to link the version or update the link. + Path toolPath = getToolPath(); + FileAccess fileAccess = this.context.getFileAccess(); + if (Files.exists(toolPath)) { + fileAccess.backup(toolPath); + } + fileAccess.symlink(installation.linkDir(), toolPath); + this.context.getPath().setPath(this.tool, installation.binDir()); + if (installedVersion == null) { + this.context.success("Successfully installed {} in version {}", this.tool, resolvedVersion); + } else { + this.context.success("Successfully installed {} in version {} replacing previous version {]", this.tool, + resolvedVersion, installedVersion); + } + postInstall(); + return true; + } + + /** + * Performs the installation of the {@link #getName() tool} managed by this {@link 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}. + * @return the {@link ToolInstallation} in the central software repository matching the given {@code version}. + */ + public ToolInstallation installInRepo(VersionIdentifier version) { + + return installInRepo(version, getEdition()); + } + + /** + * Performs the installation of the {@link #getName() tool} managed by this {@link 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}. + * @param edition the specific edition to install. + * @return the {@link ToolInstallation} in the central software repository matching the given {@code version}. + */ + public ToolInstallation installInRepo(VersionIdentifier version, String edition) { + + return installInRepo(version, edition, this.context.getDefaultToolRepository()); + } + + /** + * Performs the installation of the {@link #getName() tool} managed by this {@link 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}. + * @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}. + */ + public ToolInstallation installInRepo(VersionIdentifier version, String edition, ToolRepository toolRepository) { + + VersionIdentifier resolvedVersion = toolRepository.resolveVersion(this.tool, edition, version); + Path toolPath = this.context.getSoftwareRepositoryPath().resolve(toolRepository.getId()).resolve(this.tool) + .resolve(edition).resolve(resolvedVersion.toString()); + Path toolVersionFile = toolPath.resolve(IdeContext.FILE_SOFTWARE_VERSION); + FileAccess fileAccess = this.context.getFileAccess(); + if (Files.isDirectory(toolPath)) { + if (Files.exists(toolVersionFile)) { + this.context.debug("Version {} of tool {} is already installed at {}", resolvedVersion, + getToolWithEdition(this.tool, edition), toolPath); + return createToolInstallation(toolPath, resolvedVersion, toolVersionFile); + } + this.context.warning("Deleting corrupted installation at {}", toolPath); + fileAccess.delete(toolPath); + } + Path target = toolRepository.download(this.tool, edition, resolvedVersion); + fileAccess.mkdirs(toolPath.getParent()); + extract(target, toolPath); + try { + Files.writeString(toolVersionFile, resolvedVersion.toString(), StandardOpenOption.CREATE_NEW); + } catch (IOException e) { + throw new IllegalStateException("Failed to write version file " + toolVersionFile, e); + } + return createToolInstallation(toolPath, resolvedVersion, toolVersionFile); + } + + private ToolInstallation createToolInstallation(Path rootDir, VersionIdentifier resolvedVersion, + Path toolVersionFile) { + + Path linkDir = getMacOsHelper().findLinkDir(rootDir); + Path binDir = linkDir; + Path binFolder = binDir.resolve(IdeContext.FOLDER_BIN); + if (Files.isDirectory(binFolder)) { + binDir = binFolder; + } + if (linkDir != rootDir) { + assert (!linkDir.equals(rootDir)); + this.context.getFileAccess().copy(toolVersionFile, linkDir, FileCopyMode.COPY_FILE_OVERRIDE); + } + return new ToolInstallation(rootDir, linkDir, binDir, resolvedVersion); + } + + private boolean isInstalledVersion(VersionIdentifier expectedVersion, VersionIdentifier installedVersion, + boolean silent) { + + if (expectedVersion.equals(installedVersion)) { + IdeLogLevel level = IdeLogLevel.INFO; + if (silent) { + level = IdeLogLevel.DEBUG; + } + this.context.level(level).log("Version {} of tool {} is already installed", installedVersion, + getToolWithEdition()); + return true; + } + return false; + } + +} 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 e3d006c26..c25a7d5bb 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 @@ -3,7 +3,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardOpenOption; +import java.nio.file.Paths; import java.util.List; import java.util.Set; import java.util.stream.Stream; @@ -15,14 +15,11 @@ 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.FileCopyMode; import com.devonfw.tools.ide.io.TarCompression; -import com.devonfw.tools.ide.log.IdeLogLevel; 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.property.StringListProperty; -import com.devonfw.tools.ide.repo.ToolRepository; import com.devonfw.tools.ide.util.FilenameUtil; import com.devonfw.tools.ide.version.VersionIdentifier; @@ -67,6 +64,15 @@ public String getName() { return this.tool; } + /** + * + * @return the name of the binary + */ + protected String getBinaryName() { + + return this.tool; + } + @Override public final Set getTags() { @@ -89,49 +95,19 @@ public void run() { */ public void runTool(VersionIdentifier toolVersion, String... args) { - Path binary; + Path binaryPath; + Path toolPath = Paths.get(getBinaryName()); if (toolVersion == null) { install(true); - binary = getToolBinary(); + binaryPath = toolPath; } else { throw new UnsupportedOperationException("Not yet implemented!"); } - ProcessContext pc = this.context.newProcess().errorHandling(ProcessErrorHandling.WARNING).executable(binary) + ProcessContext pc = this.context.newProcess().errorHandling(ProcessErrorHandling.WARNING).executable(binaryPath) .addArgs(args); pc.run(); } - /** - * @return the {@link Path} where the main executable file of this tool is installed. - */ - public Path getToolBinary() { - - Path binPath = getToolBinPath(); - Path binary = this.context.getFileAccess().findFirst(binPath, this::isBinary, false); - if (binary == null) { - throw new IllegalStateException("Could not find executable binary for " + getName() + " in " + binPath); - } - return binary; - } - - protected boolean isBinary(Path path) { - - String filename = path.getFileName().toString(); - String binaryName = getBinaryName(); - if (filename.equals(binaryName)) { - return true; - } else if (filename.startsWith(binaryName)) { - String suffix = filename.substring(binaryName.length()); - return this.context.getSystemInfo().getOs().isExecutable(suffix); - } - return false; - } - - protected String getBinaryName() { - - return this.tool; - } - /** * @return the {@link EnvironmentVariables#getToolEdition(String) tool edition}. */ @@ -164,29 +140,6 @@ protected final static String getToolWithEdition(String tool, String edition) { return tool + "/" + edition; } - /** - * @return the {@link Path} where the tool is located (installed). - */ - public Path getToolPath() { - - return this.context.getSoftwarePath().resolve(getName()); - } - - /** - * @return the {@link Path} where the executables of the tool can be found. Typically a "bin" folder inside - * {@link #getToolPath() tool path}. - */ - public Path getToolBinPath() { - - Path toolPath = getToolPath(); - Path binPath = this.context.getFileAccess().findFirst(toolPath, path -> path.getFileName().toString().equals("bin"), - false); - if ((binPath != null) && Files.isDirectory(binPath)) { - return binPath; - } - return toolPath; - } - /** * @return the {@link EnvironmentVariables#getToolVersion(String) tool version}. */ @@ -195,35 +148,6 @@ public VersionIdentifier getConfiguredVersion() { return this.context.getVariables().getToolVersion(getName()); } - /** - * @return the currently installed {@link VersionIdentifier version} of this tool or {@code null} if not installed. - */ - public VersionIdentifier getInstalledVersion() { - - Path toolPath = getToolPath(); - if (!Files.isDirectory(toolPath)) { - this.context.trace("Tool {} not installed in {}", getName(), toolPath); - return null; - } - Path toolVersionFile = toolPath.resolve(IdeContext.FILE_SOFTWARE_VERSION); - if (!Files.exists(toolVersionFile)) { - Path legacyToolVersionFile = toolPath.resolve(IdeContext.FILE_LEGACY_SOFTWARE_VERSION); - if (Files.exists(legacyToolVersionFile)) { - toolVersionFile = legacyToolVersionFile; - } else { - this.context.warning("Tool {} is missing version file in {}", getName(), toolVersionFile); - return null; - } - } - try { - String version = Files.readString(toolVersionFile).trim(); - return VersionIdentifier.of(version); - } catch (IOException e) { - throw new IllegalStateException("Failed to read file " + toolVersionFile, e); - } - - } - /** * Method to be called for {@link #install(boolean)} from dependent {@link Commandlet}s. * @@ -254,183 +178,16 @@ public boolean install(boolean silent) { * @return {@code true} if the tool was newly installed, {@code false} if the tool was already installed before and * nothing has changed. */ - protected boolean doInstall(boolean silent) { - - VersionIdentifier configuredVersion = getConfiguredVersion(); - // install configured version of our tool in the software repository if not already installed - ToolInstallation installation = installInRepo(configuredVersion); - - // check if we already have this version installed (linked) locally in IDE_HOME/software - VersionIdentifier installedVersion = getInstalledVersion(); - VersionIdentifier resolvedVersion = installation.resolvedVersion(); - if (isInstalledVersion(resolvedVersion, installedVersion, silent)) { - return false; - } - // we need to link the version or update the link. - Path toolPath = getToolPath(); - FileAccess fileAccess = this.context.getFileAccess(); - if (Files.exists(toolPath)) { - fileAccess.backup(toolPath); - } - fileAccess.symlink(installation.linkDir(), toolPath); - this.context.getPath().setPath(this.tool, installation.binDir()); - if (installedVersion == null) { - this.context.success("Successfully installed {} in version {}", this.tool, resolvedVersion); - } else { - this.context.success("Successfully installed {} in version {} replacing previous version {}", this.tool, - resolvedVersion, installedVersion); - } - postInstall(); - return true; - } + protected abstract boolean doInstall(boolean silent); /** - * This method is called after the tool has been newly installed or updated to a new version. Override it to add - * custom post intallation logic. + * This method is called after the tool has been newly installed or updated to a new version. */ protected void postInstall() { // nothing to do by default } - /** - * Performs the installation of the {@link #getName() tool} managed by this {@link 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}. - * @return the {@link ToolInstallation} in the central software repository matching the given {@code version}. - */ - public ToolInstallation installInRepo(VersionIdentifier version) { - - return installInRepo(version, getEdition()); - } - - /** - * Performs the installation of the {@link #getName() tool} managed by this {@link 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}. - * @param edition the specific edition to install. - * @return the {@link ToolInstallation} in the central software repository matching the given {@code version}. - */ - public ToolInstallation installInRepo(VersionIdentifier version, String edition) { - - return installInRepo(version, edition, this.context.getDefaultToolRepository()); - } - - /** - * Performs the installation of the {@link #getName() tool} managed by this {@link 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}. - * @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}. - */ - public ToolInstallation installInRepo(VersionIdentifier version, String edition, ToolRepository toolRepository) { - - VersionIdentifier resolvedVersion = toolRepository.resolveVersion(this.tool, edition, version); - Path toolPath = this.context.getSoftwareRepositoryPath().resolve(toolRepository.getId()).resolve(this.tool) - .resolve(edition).resolve(resolvedVersion.toString()); - Path toolVersionFile = toolPath.resolve(IdeContext.FILE_SOFTWARE_VERSION); - FileAccess fileAccess = this.context.getFileAccess(); - if (Files.isDirectory(toolPath)) { - if (Files.exists(toolVersionFile)) { - this.context.debug("Version {} of tool {} is already installed at {}", resolvedVersion, - getToolWithEdition(this.tool, edition), toolPath); - return createToolInstallation(toolPath, resolvedVersion, toolVersionFile); - } - this.context.warning("Deleting corrupted installation at {}", toolPath); - fileAccess.delete(toolPath); - } - Path target = toolRepository.download(this.tool, edition, resolvedVersion); - fileAccess.mkdirs(toolPath.getParent()); - extract(target, toolPath); - try { - Files.writeString(toolVersionFile, resolvedVersion.toString(), StandardOpenOption.CREATE_NEW); - } catch (IOException e) { - throw new IllegalStateException("Failed to write version file " + toolVersionFile, e); - } - return createToolInstallation(toolPath, resolvedVersion, toolVersionFile); - } - - private ToolInstallation createToolInstallation(Path rootDir, VersionIdentifier resolvedVersion, - Path toolVersionFile) { - - Path linkDir = getMacOsHelper().findLinkDir(rootDir); - Path binDir = linkDir; - Path binFolder = binDir.resolve(IdeContext.FOLDER_BIN); - if (Files.isDirectory(binFolder)) { - binDir = binFolder; - } - if (linkDir != rootDir) { - assert (!linkDir.equals(rootDir)); - this.context.getFileAccess().copy(toolVersionFile, linkDir, FileCopyMode.COPY_FILE_OVERRIDE); - } - return new ToolInstallation(rootDir, linkDir, binDir, resolvedVersion); - } - - private MacOsHelper getMacOsHelper() { - - if (this.macOsHelper == null) { - this.macOsHelper = new MacOsHelper(this.context); - } - return this.macOsHelper; - } - - /** - * @return {@code true} to extract (unpack) the downloaded binary file, {@code false} otherwise. - */ - protected boolean isExtract() { - - return true; - } - - /** - * @return the currently installed tool version or {@code null} if not found (tool not installed). - */ - protected String getInstalledToolVersion() { - - Path toolPath = getToolPath(); - if (!Files.isDirectory(toolPath)) { - this.context.debug("Tool {} not installed in {}", getName(), toolPath); - return null; - } - Path toolVersionFile = toolPath.resolve(IdeContext.FILE_SOFTWARE_VERSION); - if (!Files.exists(toolVersionFile)) { - Path legacyToolVersionFile = toolPath.resolve(IdeContext.FILE_LEGACY_SOFTWARE_VERSION); - if (Files.exists(legacyToolVersionFile)) { - toolVersionFile = legacyToolVersionFile; - } else { - this.context.warning("Tool {} is missing version file in {}", getName(), toolVersionFile); - return null; - } - } - try { - return Files.readString(toolVersionFile).trim(); - } catch (IOException e) { - throw new IllegalStateException("Failed to read file " + toolVersionFile, e); - } - } - - private boolean isInstalledVersion(VersionIdentifier expectedVersion, VersionIdentifier installedVersion, - boolean silent) { - - if (expectedVersion.equals(installedVersion)) { - IdeLogLevel level = IdeLogLevel.INFO; - if (silent) { - level = IdeLogLevel.DEBUG; - } - this.context.level(level).log("Version {} of tool {} is already installed", installedVersion, - getToolWithEdition()); - return true; - } - return false; - } - /** * @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 @@ -463,9 +220,9 @@ private Path getProperInstallationSubDirOf(Path path) { */ protected void extract(Path file, Path targetDir) { - Path tmpDir = this.context.getFileAccess().createTempDir("extract-" + file.getFileName()); 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); @@ -518,6 +275,57 @@ protected void extract(Path file, Path targetDir) { } } + /** + * @return {@code true} to extract (unpack) the downloaded binary file, {@code false} otherwise. + */ + protected boolean isExtract() { + + return true; + } + + protected MacOsHelper getMacOsHelper() { + + if (this.macOsHelper == null) { + this.macOsHelper = new MacOsHelper(this.context); + } + return this.macOsHelper; + } + + /** + * @return the currently installed {@link VersionIdentifier version} of this tool or {@code null} if not installed. + */ + public VersionIdentifier getInstalledVersion() { + + return getInstalledVersion(this.context.getSoftwarePath().resolve(getName())); + } + + /** + * @return the currently installed {@link VersionIdentifier version} of this tool or {@code null} if not installed. + */ + protected VersionIdentifier getInstalledVersion(Path toolPath) { + + if (!Files.isDirectory(toolPath)) { + this.context.debug("Tool {} not installed in {}", getName(), toolPath); + return null; + } + Path toolVersionFile = toolPath.resolve(IdeContext.FILE_SOFTWARE_VERSION); + if (!Files.exists(toolVersionFile)) { + Path legacyToolVersionFile = toolPath.resolve(IdeContext.FILE_LEGACY_SOFTWARE_VERSION); + if (Files.exists(legacyToolVersionFile)) { + toolVersionFile = legacyToolVersionFile; + } else { + this.context.warning("Tool {} is missing version file in {}", getName(), toolVersionFile); + return null; + } + } + try { + String version = Files.readString(toolVersionFile).trim(); + return VersionIdentifier.of(version); + } catch (IOException e) { + throw new IllegalStateException("Failed to read file " + toolVersionFile, e); + } + } + /** * List the available versions of this tool. */ diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/az/Azure.java b/cli/src/main/java/com/devonfw/tools/ide/tool/az/Azure.java index 772296a59..12937026a 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/az/Azure.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/az/Azure.java @@ -6,13 +6,14 @@ 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.tool.LocalToolCommandlet; import com.devonfw.tools.ide.tool.ToolCommandlet; /** * {@link ToolCommandlet} for azure CLI (azure). */ -public class Azure extends ToolCommandlet { +public class Azure extends LocalToolCommandlet { /** * The constructor. diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/docker/DockerRancherDesktopUrlUpdater.java b/cli/src/main/java/com/devonfw/tools/ide/tool/docker/DockerRancherDesktopUrlUpdater.java index e4c65797c..58cee38b9 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/docker/DockerRancherDesktopUrlUpdater.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/docker/DockerRancherDesktopUrlUpdater.java @@ -42,7 +42,7 @@ protected void addVersion(UrlVersion urlVersion) { String baseUrl = "https://github.com/rancher-sandbox/rancher-desktop/releases/download/v${version}/"; - doAddVersion(urlVersion, baseUrl + "Rancher.Desktop.Setup.${version}.exe", WINDOWS); + doAddVersion(urlVersion, baseUrl + "Rancher.Desktop.Setup.${version}.msi", WINDOWS); doAddVersion(urlVersion, baseUrl + "Rancher.Desktop-${version}.x86_64.dmg", MAC); doAddVersion(urlVersion, baseUrl + "Rancher.Desktop-${version}-mac.aarch64.zip", MAC, ARM64); doAddVersion(urlVersion, baseUrl + "rancher-desktop-linux-v${version}.zip", LINUX); 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 fb4bf37b4..1d0adf78c 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 @@ -1,6 +1,7 @@ package com.devonfw.tools.ide.tool.eclipse; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.List; import java.util.Set; @@ -52,12 +53,12 @@ public boolean install(boolean silent) { */ protected ProcessResult runEclipse(boolean log, String... args) { - Path binary = getToolBinary(); + Path toolPath = Paths.get(getBinaryName()); ProcessContext pc = this.context.newProcess(); if (log) { pc.errorHandling(ProcessErrorHandling.ERROR); } - pc.executable(binary); + pc.executable(toolPath); Path configurationPath = getPluginsInstallationPath().resolve("configuration"); this.context.getFileAccess().mkdirs(configurationPath); if (log) { @@ -69,7 +70,7 @@ protected ProcessResult runEclipse(boolean log, String... args) { } pc.addArg("-configuration").addArg(configurationPath); // TODO ability to use different Java version - Path javaPath = getCommandlet(Java.class).getToolBinary(); + Path javaPath = getCommandlet(Java.class).getToolBinPath(); pc.addArg("-vm").addArg(javaPath); pc.addArgs(args); return pc.run(log); diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/gh/Gh.java b/cli/src/main/java/com/devonfw/tools/ide/tool/gh/Gh.java index bf83c1653..65383ed92 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/gh/Gh.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/gh/Gh.java @@ -3,12 +3,13 @@ import java.util.Set; import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.tool.LocalToolCommandlet; import com.devonfw.tools.ide.tool.ToolCommandlet; /** * {@link ToolCommandlet} for github CLI (gh). */ -public class Gh extends ToolCommandlet { +public class Gh extends LocalToolCommandlet { /** * The constructor. diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/gradle/Gradle.java b/cli/src/main/java/com/devonfw/tools/ide/tool/gradle/Gradle.java index 8329e0182..f0405ce14 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/gradle/Gradle.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/gradle/Gradle.java @@ -3,13 +3,14 @@ import java.util.Set; import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.tool.LocalToolCommandlet; import com.devonfw.tools.ide.tool.ToolCommandlet; import com.devonfw.tools.ide.tool.java.Java; /** * {@link ToolCommandlet} for gradle. */ -public class Gradle extends ToolCommandlet { +public class Gradle extends LocalToolCommandlet { /** * The constructor. diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/helm/Helm.java b/cli/src/main/java/com/devonfw/tools/ide/tool/helm/Helm.java index 83d233d0f..1f4a2a41b 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/helm/Helm.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/helm/Helm.java @@ -1,6 +1,7 @@ package com.devonfw.tools.ide.tool.helm; import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.tool.LocalToolCommandlet; import com.devonfw.tools.ide.tool.ToolCommandlet; import java.util.Set; @@ -8,7 +9,7 @@ /** * {@link ToolCommandlet} for Helm, the package manager for Kubernetes. */ -public class Helm extends ToolCommandlet { +public class Helm extends LocalToolCommandlet { /** * The constructor. * 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 36431b5fc..c517a51f4 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 @@ -14,12 +14,13 @@ import com.devonfw.tools.ide.cli.CliException; 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; /** * {@link ToolCommandlet} for an IDE (integrated development environment). */ -public abstract class IdeToolCommandlet extends ToolCommandlet { +public abstract class IdeToolCommandlet extends LocalToolCommandlet { private Map pluginsMap; diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/java/Java.java b/cli/src/main/java/com/devonfw/tools/ide/tool/java/Java.java index a2add484b..e26c7308a 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/java/Java.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/java/Java.java @@ -3,12 +3,13 @@ import java.util.Set; import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.tool.LocalToolCommandlet; import com.devonfw.tools.ide.tool.ToolCommandlet; /** * {@link ToolCommandlet} for Java (Java Virtual Machine and Java Development Kit). */ -public class Java extends ToolCommandlet { +public class Java extends LocalToolCommandlet { /** * The constructor. diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/kotlinc/Kotlinc.java b/cli/src/main/java/com/devonfw/tools/ide/tool/kotlinc/Kotlinc.java index 8eb8b6ee2..744d051f0 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/kotlinc/Kotlinc.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/kotlinc/Kotlinc.java @@ -3,12 +3,13 @@ import java.util.Set; import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.tool.LocalToolCommandlet; import com.devonfw.tools.ide.tool.ToolCommandlet; /** * {@link ToolCommandlet} for Kotlin command-line compiler (kotlinc). */ -public class Kotlinc extends ToolCommandlet { +public class Kotlinc extends LocalToolCommandlet { /** * The constructor. diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/kotlinc/KotlincNative.java b/cli/src/main/java/com/devonfw/tools/ide/tool/kotlinc/KotlincNative.java index 00cbbdf41..b8ed74b58 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/kotlinc/KotlincNative.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/kotlinc/KotlincNative.java @@ -3,12 +3,13 @@ import java.util.Set; import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.tool.LocalToolCommandlet; import com.devonfw.tools.ide.tool.ToolCommandlet; /** * {@link ToolCommandlet} for Kotlin Native (kotlincnative). */ -public class KotlincNative extends ToolCommandlet { +public class KotlincNative extends LocalToolCommandlet { /** * The constructor. 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 b394bd3db..5ef339f7c 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 @@ -3,13 +3,14 @@ import java.util.Set; import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.tool.LocalToolCommandlet; import com.devonfw.tools.ide.tool.ToolCommandlet; import com.devonfw.tools.ide.tool.java.Java; /** * {@link ToolCommandlet} for maven. */ -public class Mvn extends ToolCommandlet { +public class Mvn extends LocalToolCommandlet { /** * The constructor. diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/node/Node.java b/cli/src/main/java/com/devonfw/tools/ide/tool/node/Node.java index fdd408d38..a3f9c976e 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/node/Node.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/node/Node.java @@ -3,12 +3,13 @@ import java.util.Set; import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.tool.LocalToolCommandlet; import com.devonfw.tools.ide.tool.ToolCommandlet; /** * {@link ToolCommandlet} for node. */ -public class Node extends ToolCommandlet { +public class Node extends LocalToolCommandlet { /** * The constructor. diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/oc/Oc.java b/cli/src/main/java/com/devonfw/tools/ide/tool/oc/Oc.java index f65f1ea54..31a7f1b9f 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/oc/Oc.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/oc/Oc.java @@ -1,6 +1,7 @@ package com.devonfw.tools.ide.tool.oc; import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.tool.LocalToolCommandlet; import com.devonfw.tools.ide.tool.ToolCommandlet; import java.util.Set; @@ -8,7 +9,7 @@ /** * {@link ToolCommandlet} for Openshift CLI. */ -public class Oc extends ToolCommandlet { +public class Oc extends LocalToolCommandlet { /** * The constructor. diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/quarkus/Quarkus.java b/cli/src/main/java/com/devonfw/tools/ide/tool/quarkus/Quarkus.java index d35528415..d135d9abc 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/quarkus/Quarkus.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/quarkus/Quarkus.java @@ -1,6 +1,7 @@ package com.devonfw.tools.ide.tool.quarkus; import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.tool.LocalToolCommandlet; import com.devonfw.tools.ide.tool.ToolCommandlet; import java.util.Set; @@ -8,7 +9,7 @@ /** * {@link ToolCommandlet} for Quarkus. */ -public class Quarkus extends ToolCommandlet { +public class Quarkus extends LocalToolCommandlet { /** * The constructor * 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 3ef684178..56174c0c7 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 @@ -3,12 +3,13 @@ import java.util.Set; import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.tool.LocalToolCommandlet; import com.devonfw.tools.ide.tool.ToolCommandlet; /** * {@link ToolCommandlet} for terraform CLI (terraform). */ -public class Terraform extends ToolCommandlet { +public class Terraform extends LocalToolCommandlet { /** * The constructor. diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/vscode/Vscode.java b/cli/src/main/java/com/devonfw/tools/ide/tool/vscode/Vscode.java index 7963df6fb..a7e85da00 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/vscode/Vscode.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/vscode/Vscode.java @@ -3,12 +3,13 @@ import java.util.Set; import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.tool.LocalToolCommandlet; import com.devonfw.tools.ide.tool.ToolCommandlet; /** * {@link ToolCommandlet} for vscode. */ -public class Vscode extends ToolCommandlet { +public class Vscode extends LocalToolCommandlet { /** * The constructor. @@ -22,6 +23,7 @@ public Vscode (IdeContext context) { @Override protected String getBinaryName() { + return "code"; } diff --git a/documentation/images/LSPAddUser.png b/documentation/images/LSPAddUser.png new file mode 100644 index 000000000..4d12c4f34 Binary files /dev/null and b/documentation/images/LSPAddUser.png differ diff --git a/documentation/images/LSPPoperty.png b/documentation/images/LSPPoperty.png new file mode 100644 index 000000000..35934a4d1 Binary files /dev/null and b/documentation/images/LSPPoperty.png differ diff --git a/documentation/images/LocalSecurityPolicy.png b/documentation/images/LocalSecurityPolicy.png new file mode 100644 index 000000000..2e0a1984f Binary files /dev/null and b/documentation/images/LocalSecurityPolicy.png differ diff --git a/documentation/images/ResourceManager.png b/documentation/images/ResourceManager.png new file mode 100644 index 000000000..6c30af0c0 Binary files /dev/null and b/documentation/images/ResourceManager.png differ diff --git a/documentation/settings.asciidoc b/documentation/settings.asciidoc index 4632e7b7b..8fdc1b2c1 100644 --- a/documentation/settings.asciidoc +++ b/documentation/settings.asciidoc @@ -32,22 +32,21 @@ The settings folder (see `link:variables.asciidoc[SETTINGS_PATH]`) has to follow │ │ └──/ 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/sonarqube[sonarqube] -│ └──/ https://github.com/devonfw/ide-settings/tree/master/sonarqube/profiles[profiles] -│ ├── Devon-C#.xml -│ ├── ... -│ └── Devon-XML.xml +├──/ 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 link:eclipse.asciidoc[eclipse] or link:vscode.asciidoc[vscode], the according folders contain the templates to manage the workspace via our link:configurator.asciidoc[configurator]. +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: @@ -55,7 +54,7 @@ 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 link:eclipse.asciidoc[eclipse], link:intellij..asciidoc[intellij], or link:vscode.asciidoc[vscode] we provide a smart mechanism via our link:configurator.asciidoc[configurator]. +* 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. diff --git a/documentation/symlink.asciidoc b/documentation/symlink.asciidoc new file mode 100644 index 000000000..b4cf563dd --- /dev/null +++ b/documentation/symlink.asciidoc @@ -0,0 +1,21 @@ +:toc: +toc::[] + += Symlink + +The creation of real symbolic links on Windows, requires the user to have according permissions. +In order to grant these permissions to yourself, run `secpol.msc` (`Local Security Policy`) and select `Local Policies/User Rights Assignment` + +1. Right click `Create Symbolic Link`, to open `Properties` and `Add User or Group` ++ +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. + +[cols="3,1a,1a,3", frame=none, grid=none] +|=== +| +| image::images/LSPPoperty.png[] +| image::images/LSPAddUser.png[] +| +|===