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

#131: Support for tool dependencies #145

Closed
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
a9f80db
#131: Support for tool dependencies
aBega2000 Nov 20, 2023
41d23ef
Merge branch 'main' of https://github.com/aBega2000/IDEasy into featu…
aBega2000 Nov 20, 2023
96f807f
Changes after review
aBega2000 Nov 22, 2023
ecd1df3
Update ToolCommandlet.java
aBega2000 Nov 22, 2023
b222aa5
#131: Changes after reviews
aBega2000 Nov 27, 2023
6080c80
Merge branch 'feature/131-support-for-tool-dependecies' of https://gi…
aBega2000 Nov 27, 2023
f7ff2c2
#131: Changes after review
aBega2000 Dec 1, 2023
e44d547
#131: Changes after review
aBega2000 Dec 13, 2023
4a6c8e9
Merge branch 'devonfw:main' into feature/131-support-for-tool-depende…
aBega2000 Dec 13, 2023
2805f0c
Merge branch 'feature/131-support-for-tool-dependecies' of https://gi…
aBega2000 Dec 13, 2023
67c5c9c
Small Change
aBega2000 Dec 13, 2023
cc8c0cb
#131: Changes after review
aBega2000 Dec 14, 2023
ed0b0f3
Merge branch 'feature/131-support-for-tool-dependecies' of https://gi…
aBega2000 Dec 14, 2023
18884c2
Merge branch 'main' into feature/131-support-for-tool-dependecies
jan-vcapgemini Jan 8, 2024
f2ed771
Merge branch 'devonfw:main' into feature/131-support-for-tool-depende…
aBega2000 Jan 8, 2024
32e4bd1
#131: Changes after review and according to VersionRange with boundar…
aBega2000 Jan 10, 2024
48e3437
Merge branch 'main' into feature/131-support-for-tool-dependecies
aBega2000 Jan 17, 2024
a3d82b9
Merge branch 'main' into feature/131-support-for-tool-dependecies
hohwille Jan 19, 2024
21781f2
#131: Resolved Conflicts
aBega2000 Feb 1, 2024
e4ad57b
Merge commit '43272714a125e23e8b47d4501a16b722cde17ab2' into feature/…
aBega2000 Feb 1, 2024
b9846f8
Merge branch 'feature/131-support-for-tool-dependecies' of https://gi…
aBega2000 Feb 1, 2024
52840dc
Merge branch 'main' of https://github.com/aBega2000/IDEasy into featu…
aBega2000 Feb 1, 2024
fbcbf31
#131: Changes after review
aBega2000 Feb 7, 2024
0a9b2b5
Merge branch 'main' into feature/131-support-for-tool-dependecies
aBega2000 Mar 7, 2024
8f04879
Merge branch 'devonfw:main' into feature/131-support-for-tool-depende…
aBega2000 Mar 12, 2024
584c5f4
Update DependencyJson.java
hohwille Mar 19, 2024
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
145 changes: 144 additions & 1 deletion cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,35 @@
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.json.mapping.JsonMapping;
import com.devonfw.tools.ide.log.IdeLogLevel;
import com.devonfw.tools.ide.repo.ToolRepository;
import com.devonfw.tools.ide.url.model.file.dependencyJson.DependencyInfo;
import com.devonfw.tools.ide.url.model.file.dependencyJson.DependencyJson;
import com.devonfw.tools.ide.version.VersionIdentifier;
import com.devonfw.tools.ide.version.VersionRange;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.BufferedReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Set;
import java.util.List;
import java.util.Map;
import java.util.Iterator;
import java.util.stream.Stream;

/**
* {@link ToolCommandlet} that is installed locally into the IDE.
*/
public abstract class LocalToolCommandlet extends ToolCommandlet {

private static final ObjectMapper MAPPER = JsonMapping.create();

private static final String dependencyFileName = "dependencies.json";
jan-vcapgemini marked this conversation as resolved.
Show resolved Hide resolved

/**
* The constructor.
*
Expand All @@ -32,7 +46,6 @@ public LocalToolCommandlet(IdeContext context, String tool, Set<String> tags) {
super(context, tool, tags);
}


/**
* @return the {@link Path} where the tool is located (installed).
*/
Expand Down Expand Up @@ -63,6 +76,12 @@ protected boolean doInstall(boolean silent) {
// install configured version of our tool in the software repository if not already installed
ToolInstallation installation = installInRepo(configuredVersion);

if (Files.exists(getDependencyJsonPath())) {
installDependency();
} else {
this.context.info("No Dependencies file found");
jan-vcapgemini marked this conversation as resolved.
Show resolved Hide resolved
}

// check if we already have this version installed (linked) locally in IDE_HOME/software
VersionIdentifier installedVersion = getInstalledVersion();
VersionIdentifier resolvedVersion = installation.resolvedVersion();
Expand Down Expand Up @@ -182,4 +201,128 @@ private boolean isInstalledVersion(VersionIdentifier expectedVersion, VersionIde
return false;
}

/**
* Method to get the Path of the dependencies Json file
*
* @return the {@link Path} of the dependencies file for the tool
*/
private Path getDependencyJsonPath() {

Path URLspath = this.context.getUrlsPath();
Path toolPath = URLspath.resolve(getName()).resolve(getEdition());
jan-vcapgemini marked this conversation as resolved.
Show resolved Hide resolved
return toolPath.resolve(dependencyFileName);
jan-vcapgemini marked this conversation as resolved.
Show resolved Hide resolved
}

private void installDependency() {
hohwille marked this conversation as resolved.
Show resolved Hide resolved

List<DependencyInfo> dependencies = readJson();

try {
for (DependencyInfo dependencyInfo : dependencies) {
VersionRange dependencyVersionRangeFound = VersionRange.of(dependencyInfo.getVersionRange());
String dependencyName = dependencyInfo.getDependency();
ToolCommandlet dependencyTool = this.context.getCommandletManager().getToolCommandlet(dependencyName);
VersionIdentifier dependencyVersionToInstall = findDependencyVersionToInstall(dependencyVersionRangeFound,
dependencyName);
jan-vcapgemini marked this conversation as resolved.
Show resolved Hide resolved

String DefaultToolRepositoryID = this.context.getDefaultToolRepository().getId();
Path dependencyRepository = this.context.getSoftwareRepositoryPath().resolve(DefaultToolRepositoryID)
jan-vcapgemini marked this conversation as resolved.
Show resolved Hide resolved
.resolve(dependencyName).resolve(dependencyTool.getEdition());

if (versionExistsInRepository(dependencyRepository, dependencyVersionRangeFound)) {
this.context.info("Necessary version of the dependency {} is already installed in repository",
dependencyName);
} else {
this.context.info("The version {} of the dependency {} is being installed", dependencyVersionToInstall,
dependencyName);
LocalToolCommandlet dependencyLocal = (LocalToolCommandlet) dependencyTool;
dependencyLocal.installInRepo(dependencyVersionToInstall);
this.context.info("The version {} of the dependency {} was successfully installed",
dependencyVersionToInstall, dependencyName);
}
}
} catch (Exception e) {
jan-vcapgemini marked this conversation as resolved.
Show resolved Hide resolved
this.context.error("Error occurred: {}", e);
}
}

private List<DependencyInfo> readJson() {

Path dependencyJsonPath = getDependencyJsonPath();

try (BufferedReader reader = Files.newBufferedReader(dependencyJsonPath)) {
DependencyJson dependencyJson = MAPPER.readValue(reader, DependencyJson.class);
return findDependenciesFromJson(dependencyJson.getDependencies(), getConfiguredVersion());
} catch (Exception e) {
jan-vcapgemini marked this conversation as resolved.
Show resolved Hide resolved
throw new IllegalStateException("Failed to load " + dependencyJsonPath, e);
}
}

/**
* Method to search the List of versions available in the ide and find the right version to install
*
* @param dependencyVersionRangeFound the {@link VersionRange} of the dependency that was found that needs to be
* installed
* @param dependency the {@link String} of the dependency tool
*
* @return {@link VersionIdentifier} of the dependency that is to be installed
*/
private VersionIdentifier findDependencyVersionToInstall(VersionRange dependencyVersionRangeFound,
String dependency) {
hohwille marked this conversation as resolved.
Show resolved Hide resolved

String dependencyEdition = this.context.getVariables().getToolEdition(dependency);

List<VersionIdentifier> versions = this.context.getUrls().getSortedVersions(dependency, dependencyEdition);

for (VersionIdentifier vi : versions) {
if (dependencyVersionRangeFound.contains(vi)) {
return vi;
}
}
return null;
}

/**
* Method to check if in the repository of the dependency there is a Version greater or equal to the version to be
* installed
*
* @param dependencyRepositoryPath the {@link Path} of the dependency repository
* @param dependencyVersionRangeFound the {@link VersionRange} of the dependency version to be installed
*
* @return the {@code true} if such version exists in repository already, or {@code false} otherwise
*/
private boolean versionExistsInRepository(Path dependencyRepositoryPath,
VersionRange dependencyVersionRangeFound) {

try (Stream<Path> versions = Files.list(dependencyRepositoryPath)) {
Iterator<Path> versionsIterator = versions.iterator();
while (versionsIterator.hasNext()) {
VersionIdentifier versionFound = VersionIdentifier.of(versionsIterator.next().getFileName().toString());
if (dependencyVersionRangeFound.contains(versionFound)) {
return true;
}
}
} catch (IOException e) {
throw new IllegalStateException("Failed to iterate through " + dependencyRepositoryPath, e);
}
return false;
}

private List<DependencyInfo> findDependenciesFromJson(Map<String, List<DependencyInfo>> dependencies,
VersionIdentifier toolVersionToCheck) {

for (Map.Entry<String, List<DependencyInfo>> map : dependencies.entrySet()) {

String versionKey = map.getKey();
VersionIdentifier foundToolVersion = VersionIdentifier.of(versionKey);

// if a newer (greater) version is available, that is not already in the Json file
if (toolVersionToCheck.getStart().compareVersion(foundToolVersion.getStart()).isGreater()) {
hohwille marked this conversation as resolved.
Show resolved Hide resolved
return null;
} else if (foundToolVersion.matches(toolVersionToCheck)) {
return map.getValue();
}
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.devonfw.tools.ide.url.model.file.dependencyJson;

public final class DependencyInfo {
hohwille marked this conversation as resolved.
Show resolved Hide resolved
private String dependency;
private String versionRange;

public String getDependency() {
return dependency;
}
hohwille marked this conversation as resolved.
Show resolved Hide resolved

public String getVersionRange() {
hohwille marked this conversation as resolved.
Show resolved Hide resolved
return versionRange;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.devonfw.tools.ide.url.model.file.dependencyJson;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class DependencyJson {
hohwille marked this conversation as resolved.
Show resolved Hide resolved
private final Map<String, List<DependencyInfo>> dependencies;

public DependencyJson() {

this.dependencies = new LinkedHashMap<>();
}

public Map<String, List<DependencyInfo>> getDependencies() {
Copy link
Member

Choose a reason for hiding this comment

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

In general it is better to keep such structures internally to simplify the JSON mapping with Jackson but for the public API instead provide rather something like public List<DependencyInfo> getDependencies(VersionIdentifier toolVersion) or if you want to keep logic out of the data objects and keep them dump and stupid public List<ToolVersionDependencyInfo> getDependencies(). Looking at this method some new member in the team will not have a clue what the semantic of this map will be. What are the keys of type String? Truth is they are VersionIdentifiers (or are they patterns or may they also be version ranges)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In my Opinion it also makes sense to only change the String to VersionRange, and that's what I've done. Please let me know if this really makes sense.

Copy link
Member

@hohwille hohwille Mar 19, 2024

Choose a reason for hiding this comment

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

Lets agree on this Map<String, List<DependencyInfo>> as it makes the JSON structure simpler.
But we can omit the root object and you can directly deserialize to Map:
https://www.baeldung.com/jackson-map#bd-3-mapltobjectobjectgt-deserialization

I would propose to keep some class like DependencyJson that contains the code to read the JSON and provides the result as DependencyJson wrapping the Map in Java without the root object being in the JSON syntax.
Then DependencyJson should also implement validation logic so that we can render a warning if we hit something like this:

{ "[1.0.0, 2.0.0]": [ ... ],
  "[1.1.0, 1.9.9]": [ ... ],
  ...
}

So in other words valid JSON should not contain overlapping VersionRanges.


return this.dependencies;
}
}
Loading