Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#47: Support for global tools #113

Merged
merged 29 commits into from
Nov 7, 2023
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
74ef1e3
first changes
salimbouch Oct 19, 2023
baed11e
changes
salimbouch Oct 24, 2023
6bf0f17
tools extend LocalToolCommandlet, globalToolCommand not yet fully imp…
salimbouch Oct 24, 2023
c743813
corrected coding style
salimbouch Oct 24, 2023
fe286c4
corrected coding style
salimbouch Oct 24, 2023
171ba7c
updated TODOs
salimbouch Oct 24, 2023
f1b4f1d
updated TODOs
salimbouch Oct 24, 2023
4bf385d
Merge branch 'main' into 47-support-global-tools
salimbouch Oct 24, 2023
74461da
updated branch
salimbouch Oct 24, 2023
27fc1b8
added -f option
salimbouch Oct 27, 2023
fa3b967
Merge branch 'main' of https://github.com/devonfw/IDEasy into 47-supp…
salimbouch Oct 27, 2023
1580a68
removed unecessary imports
salimbouch Oct 27, 2023
f90d5c9
updated TODOs and JavaDoc
salimbouch Oct 27, 2023
ecd1a13
updated javaDoc
salimbouch Oct 27, 2023
97ae735
merged other PR
salimbouch Oct 28, 2023
886e845
merged other PR
salimbouch Oct 28, 2023
50373eb
removed package-lock.json
salimbouch Oct 29, 2023
ae58363
rearranged local and global TC
salimbouch Oct 30, 2023
e9517f8
Update GlobalToolCommandlet.java
salimbouch Oct 30, 2023
9a8e2f3
removed getToolBinary
salimbouch Oct 30, 2023
bbd224b
updated doInstall
salimbouch Oct 30, 2023
de84209
Merge branch 'main' into 47-support-global-tools
salimbouch Oct 30, 2023
387b7a6
Merge branch 'main' of https://github.com/devonfw/IDEasy into 47-supp…
salimbouch Oct 30, 2023
6cf4682
doInstall in GTC implemented
salimbouch Oct 30, 2023
efd5053
Merge branch '47-support-global-tools' of https://github.com/salimbou…
salimbouch Oct 30, 2023
2b31f10
JavaDocs
salimbouch Oct 30, 2023
830e6d3
updated doInstall
salimbouch Oct 31, 2023
0fa34f3
reviewed changes
salimbouch Nov 6, 2023
d94c0ab
Merge branch 'main' into 47-support-global-tools
hohwille Nov 7, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions cli/src/main/java/com/devonfw/tools/ide/tool/GlobalToolCommandlet.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package com.devonfw.tools.ide.tool;

import com.devonfw.tools.ide.context.IdeContext;
import com.devonfw.tools.ide.process.ProcessContext;
import com.devonfw.tools.ide.process.ProcessErrorHandling;
import com.devonfw.tools.ide.repo.ToolRepository;
import com.devonfw.tools.ide.version.VersionIdentifier;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Set;

public abstract class GlobalToolCommandlet 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 GlobalToolCommandlet(IdeContext context, String tool, Set<String> tags) {

super(context, tool, tags);
}

/**
* @return the {@link Path} where the main executable file of this tool is installed.
*/
@Override
public Path getToolBinary() {

String path = System.getenv("PATH");
String[] pathDirs = path.split(File.pathSeparator);
for (String dir : pathDirs) {
Path toolPath = Paths.get(dir, getName());
if (Files.isExecutable(toolPath)) {
return toolPath;
}
}
return null;
hohwille marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* @return the {@link Path} where the tool is located (installed).
*/
@Override
public Path getToolPath() {

return null;
}


/**
* Override this if the global tool comes with a zip file.
* @return {@code true} to extract (unpack) the downloaded binary file, {@code false} otherwise.
*/
@Override
protected boolean isExtract() {

return false;
}

/**
* Installs {@link #getName() tool}, if force mode is enabled it proceeds with the installation even if the tool
* is already installed
* @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
* nothing has changed.
*/
@Override
protected boolean doInstall(boolean silent) {
hohwille marked this conversation as resolved.
Show resolved Hide resolved

Path binaryPath = getToolBinary();
if (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
Path target = toolRepository.download(this.tool, edition, resolvedVersion);
extract(target, null);
ProcessContext pc = this.context.newProcess().errorHandling(ProcessErrorHandling.WARNING).executable(target);
if (pc.run() == 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;
}



}
246 changes: 246 additions & 0 deletions cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
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.io.TarCompression;
import com.devonfw.tools.ide.log.IdeLogLevel;
import com.devonfw.tools.ide.process.ProcessContext;
import com.devonfw.tools.ide.repo.ToolRepository;
import com.devonfw.tools.ide.util.FilenameUtil;
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;

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<String> tags) {

super(context, tool, tags);
}

/**
* @return the {@link Path} where the main executable file of this tool is installed.
*/

@Override
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 = super.getName();
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;
}

/**
* @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;
}

/**
* Installs or updates the managed {@link #getName() tool}.
*
* @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
* nothing has changed.
*/

@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);
}

/**
* @return the currently installed tool version or {@code null} if not found (tool not installed).
*/
protected String getInstalledToolVersion() {
hohwille marked this conversation as resolved.
Show resolved Hide resolved

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;
}

}
Loading