Skip to content

Commit

Permalink
Merge branch '100-implement-repository-commandlet' into 102-implement…
Browse files Browse the repository at this point in the history
…-update-commandlet

# Conflicts:
#	cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java
  • Loading branch information
salimbouch committed Jan 17, 2024
2 parents 4c2291a + f847820 commit 60d1e7b
Show file tree
Hide file tree
Showing 37 changed files with 1,482 additions and 69 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/build-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ jobs:
java-version: '17'
- name: Build project with Maven
run: mvn -B -ntp -Dstyle.color=always install
- name: Coveralls GitHub Action
uses: coverallsapp/[email protected]
2 changes: 2 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,5 @@ jobs:
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/[email protected]
1 change: 1 addition & 0 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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://coveralls.io/repos/github/devonfw/IDEasy/badge.svg?branch=main["Coverage Status",link="https://coveralls.io/github/devonfw/IDEasy?branch=main"]

toc::[]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import static com.devonfw.tools.ide.commandlet.RepositoryConfig.loadProperties;

/**
* {@link Commandlet} to setup a repository
* {@link Commandlet} to setup one or multiple GIT repositories for development.
*/
public class RepositoryCommandlet extends Commandlet {

Expand Down Expand Up @@ -46,15 +46,15 @@ public String getName() {
@Override
public void run() {

Path repositoriesPath = this.context.getSettingsPath().resolve(context.FOLDER_REPOSITORIES);
Path legacyRepositoriesPath = this.context.getSettingsPath().resolve(context.FOLDER_LEGACY_REPOSITORIES);
Path repositoryFile = repository.getValueAsPath(context);

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;
Expand Down Expand Up @@ -99,7 +99,6 @@ private void doImportRepository(Path repositoryFile, boolean forceMode) {
}

this.context.debug(repositoryConfig.toString());
this.context.debug("Pull or clone git repository {} ...", repository);

String workspace = repositoryConfig.workspace() != null ? repositoryConfig.workspace() : "main";
Path workspacePath = this.context.getIdeHome().resolve("workspaces").resolve(workspace);
Expand Down Expand Up @@ -127,10 +126,5 @@ private void doImportRepository(Path repositoryFile, boolean forceMode) {
this.context.info("Build command not set. Skipping build for repository.");
}

if (!Files.exists(repositoryPath.resolve(".project"))) {
for (String ideCommandlet : repositoryConfig.imports()) {
//TODO: import repository to ideCommandlet
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public static RepositoryConfig loadProperties(Path filePath) {
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")));
Boolean.parseBoolean(properties.getProperty("active").trim()));
}

private static Set<String> getImports(Properties properties) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,7 @@ public void gitPullOrClone(Path target, String gitRepoUrl) {
if (!gitRepoUrl.startsWith("http")) {
throw new IllegalArgumentException("Invalid git URL '" + gitRepoUrl + "'!");
}
debug("Pull or clone git repository {} ...", gitRepoUrl);
ProcessContext pc = newProcess().directory(target).executable("git");
if (Files.isDirectory(target.resolve(".git"))) {
ProcessResult result = pc.addArg("remote").run(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ public abstract class AbstractEnvironmentVariables implements EnvironmentVariabl
// Variable surrounded with "${" and "}" such as "${JAVA_HOME}" 1......2........
private static final Pattern VARIABLE_SYNTAX = Pattern.compile("(\\$\\{([^}]+)})");

private static final String SELF_REFERENCING_NOT_FOUND = "";

private static final int MAX_RECURSION = 9;

private static final String VARIABLE_PREFIX = "${";
Expand Down Expand Up @@ -161,36 +163,88 @@ public EnvironmentVariables resolved() {
@Override
public String resolve(String string, Object src) {

return resolve(string, src, 0, src, string);
return resolve(string, src, 0, src, string, this);
}

private String resolve(String value, Object src, int recursion, Object rootSrc, String rootValue) {
/**
* This method is called recursively. This allows you to resolve variables that are defined by other variables.
*
* @param value the {@link String} that potentially contains variables in the syntax "${«variable«}". Those will be
* resolved by this method and replaced with their {@link #get(String) value}.
* @param src the source where the {@link String} to resolve originates from. Should have a reasonable
* {@link Object#toString() string representation} that will be used in error or log messages if a variable
* could not be resolved.
* @param recursion the current recursion level. This is used to interrupt endless recursion.
* @param rootSrc the root source where the {@link String} to resolve originates from.
* @param rootValue the root value to resolve.
* @param resolvedVars this is a reference to an object of {@link EnvironmentVariablesResolved} being the lowest level
* in the {@link EnvironmentVariablesType hierarchy} of variables. In case of a self-referencing variable
* {@code x} the resolving has to continue one level higher in the {@link EnvironmentVariablesType hierarchy}
* to avoid endless recursion. The {@link EnvironmentVariablesResolved} is then used if another variable
* {@code y} must be resolved, since resolving this variable has to again start at the lowest level. For
* example: For levels {@code l1, l2} with {@code l1 < l2} and {@code x=${x} foo} and {@code y=bar} defined at
* level {@code l1} and {@code x=test ${y}} defined at level {@code l2}, {@code x} is first resolved at level
* {@code l1} and then up the {@link EnvironmentVariablesType hierarchy} at {@code l2} to avoid endless
* recursion. However, {@code y} must be resolved starting from the lowest level in the
* {@link EnvironmentVariablesType hierarchy} and therefore {@link EnvironmentVariablesResolved} is used.
* @return the given {@link String} with the variables resolved.
*/
private String resolve(String value, Object src, int recursion, Object rootSrc, String rootValue,
AbstractEnvironmentVariables resolvedVars) {

if (value == null) {
return null;
}
if (recursion > MAX_RECURSION) {
throw new IllegalStateException("Reached maximum recursion resolving " + value + " for root valiable " + rootSrc
throw new IllegalStateException("Reached maximum recursion resolving " + value + " for root variable " + rootSrc
+ " with value '" + rootValue + "'.");
}
recursion++;

Matcher matcher = VARIABLE_SYNTAX.matcher(value);
if (!matcher.find()) {
return value;
}
StringBuilder sb = new StringBuilder(value.length() + EXTRA_CAPACITY);
do {
String variableName = matcher.group(2);
String variableValue = getValue(variableName);
String variableValue = resolvedVars.getValue(variableName);
if (variableValue == null) {
this.context.warning("Undefined variable {} in '{}={}' for root '{}={}'", variableName, src, value, rootSrc,
rootValue);
} else {
String replacement = resolve(variableValue, variableName, recursion, rootSrc, rootValue);
continue;
}
EnvironmentVariables lowestFound = findVariable(variableName);
boolean isNotSelfReferencing = lowestFound == null || !lowestFound.getFlat(variableName).equals(value);

if (isNotSelfReferencing) {
// looking for "variableName" starting from resolved upwards the hierarchy
String replacement = resolvedVars.resolve(variableValue, variableName, recursion, rootSrc, rootValue,
resolvedVars);
matcher.appendReplacement(sb, Matcher.quoteReplacement(replacement));
} else { // is self referencing
// finding next occurrence of "variableName" up the hierarchy of EnvironmentVariablesType
EnvironmentVariables next = lowestFound.getParent();
while (next != null) {
if (next.getFlat(variableName) != null) {
break;
}
next = next.getParent();
}
if (next == null) {
matcher.appendReplacement(sb, Matcher.quoteReplacement(SELF_REFERENCING_NOT_FOUND));
continue;
}
// resolving a self referencing variable one level up the hierarchy of EnvironmentVariablesType, i.e. at "next",
// to avoid endless recursion
String replacement = ((AbstractEnvironmentVariables) next).resolve(next.getFlat(variableName), variableName,
recursion, rootSrc, rootValue, resolvedVars);
matcher.appendReplacement(sb, Matcher.quoteReplacement(replacement));

}
} while (matcher.find());
matcher.appendTail(sb);

String resolved = sb.toString();
return resolved;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ default EnvironmentVariables findVariable(String name) {
* @param source the source where the {@link String} to resolve originates from. Should have a reasonable
* {@link Object#toString() string representation} that will be used in error or log messages if a variable
* could not be resolved.
* @return the the given {@link String} with the variables resolved.
* @return the given {@link String} with the variables resolved.
* @see com.devonfw.tools.ide.tool.ide.IdeToolCommandlet
*/
String resolve(String string, Object source);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,9 +222,9 @@ public String set(String name, String value, boolean export) {
String oldValue = this.variables.put(name, value);
boolean flagChanged = export != this.exportedVariables.contains(name);
if (Objects.equals(value, oldValue) && !flagChanged) {
this.context.trace("Set valiable '{}={}' caused no change in {}", name, value, this.propertiesFilePath);
this.context.trace("Set variable '{}={}' caused no change in {}", name, value, this.propertiesFilePath);
} else {
this.context.debug("Set valiable '{}={}' in {}", name, value, this.propertiesFilePath);
this.context.debug("Set variable '{}={}' in {}", name, value, this.propertiesFilePath);
this.modifiedVariables.add(name);
if (export && (value != null)) {
this.exportedVariables.add(name);
Expand Down
31 changes: 29 additions & 2 deletions cli/src/main/java/com/devonfw/tools/ide/io/FileAccess.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,28 @@ public interface FileAccess {
void move(Path source, Path targetDir);

/**
* @param source the source {@link Path} to link to.
* 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);
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.
Expand Down Expand Up @@ -140,4 +158,13 @@ default void copy(Path source, Path target) {
*/
List<Path> getChildrenInDir(Path dir, Predicate<Path> 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<Path> searchDirs);

}
Loading

0 comments on commit 60d1e7b

Please sign in to comment.