diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 0d561169b..922c15a95 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -7,6 +7,7 @@ This file documents all notable changes to https://github.com/devonfw/IDEasy[IDE Release with new features and bugfixes: * https://github.com/devonfw/IDEasy/issues/894[#894]: Fix ide.bat printing for initialization and error output +* https://github.com/devonfw/IDEasy/issues/759[#759]: Add UpgradeSettingsCommandlet for the upgrade of legacy devonfw-ide settings to IDEasy The full list of changes for this release can be found in https://github.com/devonfw/IDEasy/milestone/18?closed=1[milestone 2025.01.001]. diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java index 78df57524..54c985f03 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java @@ -12,7 +12,7 @@ import com.devonfw.tools.ide.git.GitUrl; import com.devonfw.tools.ide.property.FlagProperty; import com.devonfw.tools.ide.property.StringProperty; -import com.devonfw.tools.ide.repo.CustomTool; +import com.devonfw.tools.ide.repo.CustomToolMetadata; import com.devonfw.tools.ide.step.Step; import com.devonfw.tools.ide.tool.CustomToolCommandlet; import com.devonfw.tools.ide.tool.ToolCommandlet; @@ -164,7 +164,7 @@ private void updateSoftware() { } // custom tools in ide-custom-tools.json - for (CustomTool customTool : this.context.getCustomToolRepository().getTools()) { + for (CustomToolMetadata customTool : this.context.getCustomToolRepository().getTools()) { CustomToolCommandlet customToolCommandlet = new CustomToolCommandlet(this.context, customTool); toolCommandlets.add(customToolCommandlet); } 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 7442bcbfc..8103b3ae3 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 @@ -88,6 +88,7 @@ public CommandletManagerImpl(IdeContext context) { add(new RepositoryCommandlet(context)); add(new UninstallCommandlet(context)); add(new UpdateCommandlet(context)); + add(new UpgradeSettingsCommandlet(context)); add(new CreateCommandlet(context)); add(new BuildCommandlet(context)); add(new InstallPluginCommandlet(context)); 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 index b7daa7e17..5b61b7a58 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java @@ -57,7 +57,8 @@ public void run() { return; } - List propertiesFiles = this.context.getFileAccess().listChildren(repositories, path -> path.getFileName().toString().endsWith(".properties")); + List propertiesFiles = this.context.getFileAccess() + .listChildren(repositories, path -> path.getFileName().toString().endsWith(".properties")); boolean forceMode = this.context.isForceMode(); for (Path propertiesFile : propertiesFiles) { diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/UpgradeSettingsCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/UpgradeSettingsCommandlet.java new file mode 100644 index 000000000..4eed38112 --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/UpgradeSettingsCommandlet.java @@ -0,0 +1,183 @@ +package com.devonfw.tools.ide.commandlet; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.function.Function; + +import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.environment.EnvironmentVariables; +import com.devonfw.tools.ide.environment.EnvironmentVariablesPropertiesFile; +import com.devonfw.tools.ide.environment.EnvironmentVariablesType; +import com.devonfw.tools.ide.merge.DirectoryMerger; +import com.devonfw.tools.ide.repo.CustomToolsJson; +import com.devonfw.tools.ide.repo.CustomToolsJsonMapper; +import com.devonfw.tools.ide.tool.mvn.Mvn; +import com.devonfw.tools.ide.variable.IdeVariables; +import com.devonfw.tools.ide.variable.VariableDefinition; + +/** + * {@link Commandlet} to upgrade settings after a migration from devonfw-ide to IDEasy. + */ +public class UpgradeSettingsCommandlet extends Commandlet { + + /** + * The constructor. + * + * @param context the {@link IdeContext}. + */ + public UpgradeSettingsCommandlet(IdeContext context) { + + super(context); + addKeyword(getName()); + } + + @Override + public String getName() { + + return "upgrade-settings"; + } + + @Override + public void run() { + updateLegacyFolders(); + updateProperties(); + updateWorkspaceTemplates(); + } + + private void updateLegacyFolders() { + this.context.info("Updating legacy folders if present..."); + Path settingsPath = context.getSettingsPath(); + updateLegacyFolder(settingsPath, IdeContext.FOLDER_LEGACY_REPOSITORIES, IdeContext.FOLDER_REPOSITORIES); + updateLegacyFolder(settingsPath, IdeContext.FOLDER_LEGACY_TEMPLATES, IdeContext.FOLDER_TEMPLATES); + updateLegacyFolder(settingsPath.resolve(IdeContext.FOLDER_TEMPLATES).resolve(IdeContext.FOLDER_CONF), Mvn.MVN_CONFIG_LEGACY_FOLDER, Mvn.MVN_CONFIG_FOLDER); + } + + private void updateLegacyFolder(Path folder, String legacyName, String newName) { + + Path legacyFolder = folder.resolve(legacyName); + Path newFolder = folder.resolve(newName); + if (Files.isDirectory(legacyFolder)) { + try { + if (!Files.exists(newFolder)) { + Files.move(legacyFolder, newFolder, StandardCopyOption.REPLACE_EXISTING); + this.context.success("Successfully renamed folder '{}' to '{}' in {}.", legacyName, newName, folder); + } + } catch (IOException e) { + this.context.error(e, "Error renaming folder {} to {} in {}", legacyName, newName, folder); + } + } + } + + private void updateWorkspaceTemplates() { + this.context.info("Updating workspace templates (replace legacy variables and change variable syntax)..."); + + DirectoryMerger merger = this.context.getWorkspaceMerger(); + Path settingsDir = this.context.getSettingsPath(); + Path workspaceDir = settingsDir.resolve(IdeContext.FOLDER_WORKSPACE); + if (Files.isDirectory(workspaceDir)) { + merger.upgrade(workspaceDir); + } + this.context.getFileAccess().listChildrenMapped(settingsDir, child -> { + Path childWorkspaceDir = child.resolve(IdeContext.FOLDER_WORKSPACE); + if (Files.isDirectory(childWorkspaceDir)) { + merger.upgrade(childWorkspaceDir); + } + return null; + }); + } + + private void updateProperties() { + // updates DEVON_IDE_CUSTOM_TOOLS to new ide-custom-tools.json + String devonCustomTools = IdeVariables.DEVON_IDE_CUSTOM_TOOLS.get(this.context); + if (devonCustomTools != null) { + CustomToolsJson customToolsJson = CustomToolsJsonMapper.parseCustomToolsFromLegacyConfig(devonCustomTools, context); + if (customToolsJson != null) { + CustomToolsJsonMapper.saveJson(customToolsJson, this.context.getSettingsPath().resolve(IdeContext.FILE_CUSTOM_TOOLS)); + } + } + + // update properties (devon.properties -> ide.properties, convert legacy properties) + EnvironmentVariables environmentVariables = context.getVariables(); + while (environmentVariables != null) { + if (environmentVariables instanceof EnvironmentVariablesPropertiesFile environmentVariablesProperties) { + updateProperties(environmentVariablesProperties); + } + environmentVariables = environmentVariables.getParent(); + } + Path templatePropertiesDir = this.context.getSettingsTemplatePath().resolve(IdeContext.FOLDER_CONF); + if (Files.exists(templatePropertiesDir)) { + EnvironmentVariablesPropertiesFile environmentVariablesProperties = new EnvironmentVariablesPropertiesFile(null, EnvironmentVariablesType.CONF, + templatePropertiesDir, null, this.context); + updateProperties(environmentVariablesProperties); + } + } + + private void updateProperties(EnvironmentVariablesPropertiesFile environmentVariables) { + Path propertiesFilePath = environmentVariables.getPropertiesFilePath(); + if (environmentVariables.getLegacyConfiguration() != null) { + if (environmentVariables.getType() == EnvironmentVariablesType.SETTINGS) { + // adds disabled legacySupportEnabled variable if missing in ide.properties + environmentVariables.set(IdeVariables.IDE_VARIABLE_SYNTAX_LEGACY_SUPPORT_ENABLED.getName(), "false", false); + } + environmentVariables.remove(IdeVariables.DEVON_IDE_CUSTOM_TOOLS.getName()); + for (VariableDefinition var : IdeVariables.VARIABLES) { + String legacyName = var.getLegacyName(); + if (legacyName != null) { + String value = environmentVariables.get(legacyName); + if (value != null) { + String name = var.getName(); + String newValue = environmentVariables.get(name); + if (newValue == null) { + environmentVariables.set(name, value, environmentVariables.isExported(name)); + } + } + environmentVariables.remove(legacyName); + } + } + updatePropertiesLegacyEdition(environmentVariables, "INTELLIJ_EDITION_TYPE", "INTELLIJ_EDITION", this::mapLegacyIntellijEdition); + updatePropertiesLegacyEdition(environmentVariables, "ECLIPSE_EDITION_TYPE", "ECLIPSE_EDITION", this::mapLegacyEclipseEdition); + environmentVariables.save(); + this.context.getFileAccess().backup(environmentVariables.getLegacyPropertiesFilePath()); + } + } + + private String mapLegacyIntellijEdition(String legacyEdition) { + + return switch (legacyEdition) { + case "U" -> "ultimate"; + case "C" -> "intellij"; + default -> { + this.context.warning("Undefined legacy edition {}", legacyEdition); + yield "intellij"; + } + }; + } + + private String mapLegacyEclipseEdition(String legacyEdition) { + + return switch (legacyEdition) { + case "java" -> "eclipse"; + case "jee" -> "jee"; + case "cpp" -> "cpp"; + default -> { + this.context.warning("Undefined legacy edition {}", legacyEdition); + yield "eclipse"; + } + }; + } + + private static void updatePropertiesLegacyEdition(EnvironmentVariablesPropertiesFile environmentVariables, String legacyEditionVariable, + String newEditionVariable, Function editionMapper) { + + String legacyEdition = environmentVariables.get(legacyEditionVariable); + if (legacyEdition != null) { + String newEdition = environmentVariables.get(newEditionVariable); + if (newEdition == null) { + environmentVariables.set(newEditionVariable, editionMapper.apply(legacyEdition), false); + } + environmentVariables.remove(legacyEditionVariable); + } + } +} 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 eb5e16e46..d1fb11cc2 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 @@ -81,7 +81,7 @@ public abstract class AbstractIdeContext implements IdeContext { private Path softwareExtraPath; - private Path softwareRepositoryPath; + private final Path softwareRepositoryPath; protected Path pluginsPath; @@ -91,15 +91,15 @@ public abstract class AbstractIdeContext implements IdeContext { protected Path urlsPath; - private Path tempPath; + private final Path tempPath; - private Path tempDownloadPath; + private final Path tempDownloadPath; private Path cwd; private Path downloadPath; - private Path toolRepositoryPath; + private final Path toolRepositoryPath; protected Path userHome; @@ -308,11 +308,10 @@ private boolean isIdeHome(Path dir) { private EnvironmentVariables createVariables() { AbstractEnvironmentVariables system = createSystemVariables(); - AbstractEnvironmentVariables user = extendVariables(system, this.userHomeIde, EnvironmentVariablesType.USER); - AbstractEnvironmentVariables settings = extendVariables(user, this.settingsPath, EnvironmentVariablesType.SETTINGS); - // TODO should we keep this workspace properties? Was this feature ever used? - AbstractEnvironmentVariables workspace = extendVariables(settings, this.workspacePath, EnvironmentVariablesType.WORKSPACE); - AbstractEnvironmentVariables conf = extendVariables(workspace, this.confPath, EnvironmentVariablesType.CONF); + AbstractEnvironmentVariables user = system.extend(this.userHomeIde, EnvironmentVariablesType.USER); + AbstractEnvironmentVariables settings = user.extend(this.settingsPath, EnvironmentVariablesType.SETTINGS); + AbstractEnvironmentVariables workspace = settings.extend(this.workspacePath, EnvironmentVariablesType.WORKSPACE); + AbstractEnvironmentVariables conf = workspace.extend(this.confPath, EnvironmentVariablesType.CONF); return conf.resolved(); } @@ -321,26 +320,6 @@ protected AbstractEnvironmentVariables createSystemVariables() { return EnvironmentVariables.ofSystem(this); } - protected AbstractEnvironmentVariables extendVariables(AbstractEnvironmentVariables envVariables, Path propertiesPath, EnvironmentVariablesType type) { - - Path propertiesFile = null; - if (propertiesPath == null) { - trace("Configuration directory for type {} does not exist.", type); - } else if (Files.isDirectory(propertiesPath)) { - propertiesFile = propertiesPath.resolve(EnvironmentVariables.DEFAULT_PROPERTIES); - boolean legacySupport = (type != EnvironmentVariablesType.USER); - if (legacySupport && !Files.exists(propertiesFile)) { - Path legacyFile = propertiesPath.resolve(EnvironmentVariables.LEGACY_PROPERTIES); - if (Files.exists(legacyFile)) { - propertiesFile = legacyFile; - } - } - } else { - debug("Configuration directory {} does not exist.", propertiesPath); - } - return envVariables.extend(propertiesFile, type); - } - @Override public SystemInfo getSystemInfo() { 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 0273629a5..9b962655a 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 @@ -136,6 +136,9 @@ public interface IdeContext extends IdeStartContext { /** Legacy folder name used as compatibility fallback if {@link #FOLDER_TEMPLATES} does not exist. */ String FOLDER_LEGACY_TEMPLATES = "devon"; + /** The filename of the configuration file in the settings for this {@link CustomToolRepository}. */ + String FILE_CUSTOM_TOOLS = "ide-custom-tools.json"; + /** * @return {@code true} if {@link #isOfflineMode() offline mode} is active or we are NOT {@link #isOnline() online}, {@code false} otherwise. */ 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 52445bf7b..2caeafab2 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 @@ -95,9 +95,7 @@ public VariableSource getSource() { protected boolean isExported(String name) { if (this.parent != null) { - if (this.parent.isExported(name)) { - return true; - } + return this.parent.isExported(name); } return false; } @@ -144,13 +142,14 @@ protected VariableLine createVariableLine(String name, boolean onlyExported, Abs } /** - * @param propertiesFilePath the {@link #getPropertiesFilePath() propertiesFilePath} of the child {@link EnvironmentVariables}. + * @param propertiesFolderPath the {@link Path} to the folder containing the {@link #getPropertiesFilePath() properties file} of the child + * {@link EnvironmentVariables}. * @param type the {@link #getType() type}. * @return the new {@link EnvironmentVariables}. */ - public AbstractEnvironmentVariables extend(Path propertiesFilePath, EnvironmentVariablesType type) { + public AbstractEnvironmentVariables extend(Path propertiesFolderPath, EnvironmentVariablesType type) { - return new EnvironmentVariablesPropertiesFile(this, type, propertiesFilePath, this.context); + return new EnvironmentVariablesPropertiesFile(this, type, propertiesFolderPath, null, this.context); } /** diff --git a/cli/src/main/java/com/devonfw/tools/ide/environment/EnvironmentVariables.java b/cli/src/main/java/com/devonfw/tools/ide/environment/EnvironmentVariables.java index de52a53e8..dbb45f538 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/environment/EnvironmentVariables.java +++ b/cli/src/main/java/com/devonfw/tools/ide/environment/EnvironmentVariables.java @@ -137,6 +137,16 @@ default EnvironmentVariables getParent() { return null; } + /** + * @param name the {@link com.devonfw.tools.ide.variable.VariableDefinition#getName() name} of the variable to set. + * @param value the new {@link #get(String) value} of the variable to set. May be {@code null} to unset the variable. + * @return the old variable value. + */ + default String set(String name, String value) { + + throw new UnsupportedOperationException(); + } + /** * @param name the {@link com.devonfw.tools.ide.variable.VariableDefinition#getName() name} of the variable to set. * @param value the new {@link #get(String) value} of the variable to set. May be {@code null} to unset the variable. @@ -263,5 +273,4 @@ static String getToolEditionVariable(String tool) { return tool.toUpperCase(Locale.ROOT) + "_EDITION"; } - } diff --git a/cli/src/main/java/com/devonfw/tools/ide/environment/EnvironmentVariablesPropertiesFile.java b/cli/src/main/java/com/devonfw/tools/ide/environment/EnvironmentVariablesPropertiesFile.java index 877b43295..cc78465e2 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/environment/EnvironmentVariablesPropertiesFile.java +++ b/cli/src/main/java/com/devonfw/tools/ide/environment/EnvironmentVariablesPropertiesFile.java @@ -5,7 +5,6 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -27,7 +26,9 @@ public final class EnvironmentVariablesPropertiesFile extends EnvironmentVariabl private final EnvironmentVariablesType type; - private Path propertiesFilePath; + private final Path propertiesFilePath; + + private final Path legacyPropertiesFilePath; private final Map variables; @@ -35,6 +36,8 @@ public final class EnvironmentVariablesPropertiesFile extends EnvironmentVariabl private final Set modifiedVariables; + private Boolean legacyConfiguration; + /** * The constructor. * @@ -46,29 +49,80 @@ public final class EnvironmentVariablesPropertiesFile extends EnvironmentVariabl public EnvironmentVariablesPropertiesFile(AbstractEnvironmentVariables parent, EnvironmentVariablesType type, Path propertiesFilePath, IdeContext context) { + this(parent, type, getParent(propertiesFilePath), propertiesFilePath, context); + } + + /** + * The constructor. + * + * @param parent the parent {@link EnvironmentVariables} to inherit from. + * @param type the {@link #getType() type}. + * @param propertiesFolderPath the {@link Path} to the folder where the properties file is expected. + * @param propertiesFilePath the {@link #getSource() source}. + * @param context the {@link IdeContext}. + */ + public EnvironmentVariablesPropertiesFile(AbstractEnvironmentVariables parent, EnvironmentVariablesType type, Path propertiesFolderPath, + Path propertiesFilePath, IdeContext context) { + super(parent, context); Objects.requireNonNull(type); assert (type != EnvironmentVariablesType.RESOLVED); this.type = type; - this.propertiesFilePath = propertiesFilePath; + if (propertiesFolderPath == null) { + this.propertiesFilePath = null; + this.legacyPropertiesFilePath = null; + } else { + if (propertiesFilePath == null) { + this.propertiesFilePath = propertiesFolderPath.resolve(DEFAULT_PROPERTIES); + } else { + this.propertiesFilePath = propertiesFilePath; + assert (propertiesFilePath.getParent().equals(propertiesFolderPath)); + } + Path legacyPropertiesFolderPath = propertiesFolderPath; + if (type == EnvironmentVariablesType.USER) { + // ~/devon.properties vs. ~/.ide/ide.properties + legacyPropertiesFolderPath = propertiesFolderPath.getParent(); + } + this.legacyPropertiesFilePath = legacyPropertiesFolderPath.resolve(LEGACY_PROPERTIES); + } this.variables = new HashMap<>(); this.exportedVariables = new HashSet<>(); this.modifiedVariables = new HashSet<>(); load(); } + private static Path getParent(Path path) { + + if (path == null) { + return null; + } + return path.getParent(); + } + private void load() { - if (this.propertiesFilePath == null) { - return; + boolean success = load(this.propertiesFilePath); + if (success) { + this.legacyConfiguration = Boolean.FALSE; + } else { + success = load(this.legacyPropertiesFilePath); + if (success) { + this.legacyConfiguration = Boolean.TRUE; + } } - if (!Files.exists(this.propertiesFilePath)) { - this.context.trace("Properties not found at {}", this.propertiesFilePath); - return; + } + + private boolean load(Path file) { + if (file == null) { + return false; } - this.context.trace("Loading properties from {}", this.propertiesFilePath); - boolean legacyProperties = this.propertiesFilePath.getFileName().toString().equals(LEGACY_PROPERTIES); - try (BufferedReader reader = Files.newBufferedReader(this.propertiesFilePath)) { + if (!Files.exists(file)) { + this.context.trace("Properties not found at {}", file); + return false; + } + this.context.trace("Loading properties from {}", file); + boolean legacyProperties = file.getFileName().toString().equals(LEGACY_PROPERTIES); + try (BufferedReader reader = Files.newBufferedReader(file)) { String line; do { line = reader.readLine(); @@ -86,7 +140,7 @@ private void load() { boolean legacyVariable = IdeVariables.isLegacyVariable(name); if (legacyVariable && !legacyProperties) { this.context.warning("Legacy variable name is used to define variable {} in {} - please cleanup your configuration.", variableLine, - this.propertiesFilePath); + file); } String oldValue = this.variables.get(migratedName); if (oldValue != null) { @@ -94,10 +148,10 @@ private void load() { if (legacyVariable) { // if the legacy name was configured we do not want to override the official variable! this.context.warning("Both legacy variable {} and official variable {} are configured in {} - ignoring legacy variable declaration!", - variableDefinition.getLegacyName(), variableDefinition.getName(), this.propertiesFilePath); + variableDefinition.getLegacyName(), variableDefinition.getName(), file); } else { this.context.warning("Duplicate variable definition {} with old value '{}' and new value '{}' in {}", name, oldValue, migratedValue, - this.propertiesFilePath); + file); this.variables.put(migratedName, migratedValue); } } else { @@ -109,58 +163,39 @@ private void load() { } } } while (line != null); + return true; } catch (IOException e) { - throw new IllegalStateException("Failed to load properties from " + this.propertiesFilePath, e); + throw new IllegalStateException("Failed to load properties from " + file, e); } } @Override public void save() { - if (this.modifiedVariables.isEmpty()) { + boolean isLegacy = Boolean.TRUE.equals(this.legacyConfiguration); + if (this.modifiedVariables.isEmpty() && !isLegacy) { this.context.trace("No changes to save in properties file {}", this.propertiesFilePath); return; } - Path newPropertiesFilePath = this.propertiesFilePath; - String propertiesFileName = this.propertiesFilePath.getFileName().toString(); - Path propertiesParentPath = newPropertiesFilePath.getParent(); - if (LEGACY_PROPERTIES.equals(propertiesFileName)) { - newPropertiesFilePath = propertiesParentPath.resolve(DEFAULT_PROPERTIES); - this.context.info("Converting legacy properties to {}", newPropertiesFilePath); + Path file = this.propertiesFilePath; + if (isLegacy) { + this.context.info("Converting legacy properties to {}", this.propertiesFilePath); + file = this.legacyPropertiesFilePath; } - this.context.getFileAccess().mkdirs(propertiesParentPath); - List lines = new ArrayList<>(); + List lines = loadVariableLines(file); - // Skip reading if the file does not exist - if (Files.exists(this.propertiesFilePath)) { - try (BufferedReader reader = Files.newBufferedReader(this.propertiesFilePath)) { - String line; - do { - line = reader.readLine(); - if (line != null) { - VariableLine variableLine = VariableLine.of(line, this.context, getSource()); - lines.add(variableLine); - } - } while (line != null); - } catch (IOException e) { - throw new IllegalStateException("Failed to load existing properties from " + this.propertiesFilePath, e); - } - } else { - this.context.debug("Properties file {} does not exist, skipping read.", newPropertiesFilePath); - } - - try (BufferedWriter writer = Files.newBufferedWriter(newPropertiesFilePath, StandardOpenOption.CREATE, - StandardOpenOption.TRUNCATE_EXISTING)) { + this.context.getFileAccess().mkdirs(this.propertiesFilePath.getParent()); + try (BufferedWriter writer = Files.newBufferedWriter(this.propertiesFilePath)) { // copy and modify original lines from properties file for (VariableLine line : lines) { VariableLine newLine = migrateLine(line, true); if (newLine == null) { - this.context.debug("Removed variable line '{}' from {}", line, newPropertiesFilePath); + this.context.debug("Removed variable line '{}' from {}", line, this.propertiesFilePath); } else { if (newLine != line) { - this.context.debug("Changed variable line from '{}' to '{}' in {}", line, newLine, newPropertiesFilePath); + this.context.debug("Changed variable line from '{}' to '{}' in {}", line, newLine, this.propertiesFilePath); } writer.append(newLine.toString()); writer.append(NEWLINE); @@ -184,9 +219,31 @@ public void save() { } this.modifiedVariables.clear(); } catch (IOException e) { - throw new IllegalStateException("Failed to save properties to " + newPropertiesFilePath, e); + throw new IllegalStateException("Failed to save properties to " + this.propertiesFilePath, e); + } + this.legacyConfiguration = Boolean.FALSE; + } + + private List loadVariableLines(Path file) { + List lines = new ArrayList<>(); + if (!Files.exists(file)) { + // Skip reading if the file does not exist + this.context.debug("Properties file {} does not exist, skipping read.", file); + return lines; + } + try (BufferedReader reader = Files.newBufferedReader(file)) { + String line; + do { + line = reader.readLine(); + if (line != null) { + VariableLine variableLine = VariableLine.of(line, this.context, getSource()); + lines.add(variableLine); + } + } while (line != null); + } catch (IOException e) { + throw new IllegalStateException("Failed to load existing properties from " + file, e); } - this.propertiesFilePath = newPropertiesFilePath; + return lines; } private VariableLine migrateLine(VariableLine line, boolean saveNotLoad) { @@ -232,7 +289,7 @@ protected void collectVariables(Map variables, boolean onl } @Override - protected boolean isExported(String name) { + public boolean isExported(String name) { if (this.exportedVariables.contains(name)) { return true; @@ -255,17 +312,22 @@ public Path getPropertiesFilePath() { @Override public Path getLegacyPropertiesFilePath() { - if (this.propertiesFilePath == null) { - return null; - } - if (this.propertiesFilePath.getFileName().toString().equals(LEGACY_PROPERTIES)) { - return this.propertiesFilePath; - } - Path legacyProperties = this.propertiesFilePath.getParent().resolve(LEGACY_PROPERTIES); - if (Files.exists(legacyProperties)) { - return legacyProperties; - } - return null; + return this.legacyPropertiesFilePath; + } + + /** + * @return {@code Boolean#TRUE} if the current variable state comes from {@link #getLegacyPropertiesFilePath()}, {@code Boolean#FALSE} if state comes from + * {@link #getPropertiesFilePath()}), and {@code null} if neither of these files existed (nothing was loaded). + */ + public Boolean getLegacyConfiguration() { + + return this.legacyConfiguration; + } + + @Override + public String set(String name, String value) { + + return set(name, value, this.exportedVariables.contains(name)); } @Override @@ -287,4 +349,18 @@ public String set(String name, String value, boolean export) { return oldValue; } + /** + * Removes a property. + * + * @param name name of the property to remove. + */ + public void remove(String name) { + String oldValue = this.variables.remove(name); + if (oldValue != null) { + this.modifiedVariables.add(name); + this.exportedVariables.remove(name); + this.context.debug("Removed variable name of '{}' in {}", name, this.propertiesFilePath); + } + } + } diff --git a/cli/src/main/java/com/devonfw/tools/ide/environment/VariableLine.java b/cli/src/main/java/com/devonfw/tools/ide/environment/VariableLine.java index 7d89de937..0314a3efa 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/environment/VariableLine.java +++ b/cli/src/main/java/com/devonfw/tools/ide/environment/VariableLine.java @@ -1,5 +1,8 @@ package com.devonfw.tools.ide.environment; +import java.util.ArrayList; +import java.util.List; + import com.devonfw.tools.ide.log.IdeLogger; /** @@ -314,4 +317,26 @@ public static VariableLine of(boolean export, String name, String value, Variabl return new Variable(export, name, value, null, source); } + /** + * Returns a list of String Variables. + * + * @param value String to parse + * @return List of variables. + */ + public static List parseArray(String value) { + String csv = value; + String separator = ","; + // TODO: refactor with isBashArray method from VariableDefinitionStringList + if (value.startsWith("(") && value.endsWith(")")) { + csv = value.substring(1, value.length() - 1); + separator = " "; + } + String[] items = csv.split(separator); + List list = new ArrayList<>(items.length); + for (String item : items) { + list.add(item.trim()); + } + return list; + } + } 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 a9636c2e3..7b03134e6 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 @@ -5,6 +5,7 @@ import java.util.List; import java.util.Set; import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Predicate; /** @@ -209,6 +210,9 @@ default void extract(Path archiveFile, Path targetDir, Consumer postExtrac /** * Deletes the given {@link Path} idempotent and recursive. + *

+ * ATTENTION: In most cases we want to use {@link #backup(Path)} instead to prevent the user from data loss. + *

* * @param path the {@link Path} to delete. */ @@ -237,7 +241,19 @@ default void extract(Path archiveFile, Path targetDir, Consumer postExtrac * @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); + default List listChildren(Path dir, Predicate filter) { + + return listChildrenMapped(dir, child -> (filter.test(child)) ? child : null); + } + + /** + * @param dir the {@link Path} to the directory where to list the children. + * @param filter the filter {@link Function} used to {@link Function#apply(Object) filter and transform} children to include. If the {@link Function} + * returns {@code null}, the child will be filtered, otherwise the returned {@link Path} will be included in the resulting {@link List}. + * @return all children of the given {@link Path} returned by the given {@link Function}. Will be the empty list if the given {@link Path} is not an existing + * directory. + */ + List listChildrenMapped(Path dir, Function filter); /** * Finds the existing file with the specified name in the given list of directories. 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 ae2f0b605..2100ad327 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 @@ -264,22 +264,32 @@ private boolean isJunction(Path path) { @Override public void backup(Path fileOrFolder) { - if (Files.isSymbolicLink(fileOrFolder) || isJunction(fileOrFolder)) { + if ((fileOrFolder != null) && (Files.isSymbolicLink(fileOrFolder) || isJunction(fileOrFolder))) { delete(fileOrFolder); - } else { - // fileOrFolder is a directory - Path backupPath = this.context.getIdeHome().resolve(IdeContext.FOLDER_UPDATES).resolve(IdeContext.FOLDER_BACKUPS); + } else if ((fileOrFolder != null) && Files.exists(fileOrFolder)) { LocalDateTime now = LocalDateTime.now(); - String date = DateTimeUtil.formatDate(now); + String date = DateTimeUtil.formatDate(now, true); String time = DateTimeUtil.formatTime(now); - Path backupDatePath = backupPath.resolve(date); - mkdirs(backupDatePath); - Path target = backupDatePath.resolve(fileOrFolder.getFileName().toString() + "_" + time); + String filename = fileOrFolder.getFileName().toString(); + Path backupPath = this.context.getIdeHome().resolve(IdeContext.FOLDER_BACKUPS).resolve(date).resolve(time + "_" + filename); + backupPath = appendParentPath(backupPath, fileOrFolder.getParent(), 2); + mkdirs(backupPath); + Path target = backupPath.resolve(filename); this.context.info("Creating backup by moving {} to {}", fileOrFolder, target); move(fileOrFolder, target); + } else { + this.context.trace("Backup of {} skipped as the path does not exist.", fileOrFolder); } } + private static Path appendParentPath(Path path, Path parent, int max) { + + if ((parent == null) || (max <= 0)) { + return path; + } + return appendParentPath(path, parent.getParent(), max - 1).resolve(parent.getFileName()); + } + @Override public void move(Path source, Path targetDir) { @@ -829,7 +839,7 @@ private Path findFirstRecursive(Path dir, Predicate filter, boolean recurs } @Override - public List listChildren(Path dir, Predicate filter) { + public List listChildrenMapped(Path dir, Function filter) { if (!Files.isDirectory(dir)) { return List.of(); @@ -839,9 +849,14 @@ public List listChildren(Path dir, Predicate filter) { Iterator iterator = childStream.iterator(); while (iterator.hasNext()) { Path child = iterator.next(); - if (filter.test(child)) { - this.context.trace("Accepted file {}", child); - children.add(child); + Path filteredChild = filter.apply(child); + if (filteredChild != null) { + if (filteredChild == child) { + this.context.trace("Accepted file {}", child); + } else { + this.context.trace("Accepted file {} and mapped to {}", child, filteredChild); + } + children.add(filteredChild); } else { this.context.trace("Ignoring file {} according to filter", child); } diff --git a/cli/src/main/java/com/devonfw/tools/ide/merge/AbstractWorkspaceMerger.java b/cli/src/main/java/com/devonfw/tools/ide/merge/AbstractWorkspaceMerger.java index 366cbbcdd..0d4378754 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/merge/AbstractWorkspaceMerger.java +++ b/cli/src/main/java/com/devonfw/tools/ide/merge/AbstractWorkspaceMerger.java @@ -8,8 +8,6 @@ /** * {@link WorkspaceMerger} responsible for a single type of {@link Path}. - * - * @since 3.0.0 */ public abstract class AbstractWorkspaceMerger implements WorkspaceMerger { diff --git a/cli/src/main/java/com/devonfw/tools/ide/merge/DirectoryMerger.java b/cli/src/main/java/com/devonfw/tools/ide/merge/DirectoryMerger.java index 27564e8c0..9308ab64c 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/merge/DirectoryMerger.java +++ b/cli/src/main/java/com/devonfw/tools/ide/merge/DirectoryMerger.java @@ -8,6 +8,7 @@ import java.util.Iterator; import java.util.Map; import java.util.Set; +import java.util.stream.Stream; import org.jline.utils.Log; @@ -51,6 +52,7 @@ public DirectoryMerger(IdeContext context) { TextMerger textMerger = new TextMerger(context); this.extension2mergerMap.put("name", textMerger); // intellij specific this.extension2mergerMap.put("editorconfig", textMerger); + this.extension2mergerMap.put("txt", textMerger); this.fallbackMerger = new FallbackMerger(context); } @@ -99,8 +101,8 @@ public void inverseMerge(Path workspace, EnvironmentVariables variables, boolean return; } Log.trace("Traversing directory: {}", update); - try { - Iterator iterator = Files.list(update).iterator(); + try (Stream childStream = Files.list(update)) { + Iterator iterator = childStream.iterator(); while (iterator.hasNext()) { Path updateChild = iterator.next(); Path fileName = updateChild.getFileName(); @@ -125,8 +127,8 @@ private Set addChildren(Path folder, Set children) { if (!Files.isDirectory(folder)) { return children; } - try { - Iterator iterator = Files.list(folder).iterator(); + try (Stream childStream = Files.list(folder)) { + Iterator iterator = childStream.iterator(); while (iterator.hasNext()) { Path child = iterator.next(); if (children == null) { @@ -140,4 +142,23 @@ private Set addChildren(Path folder, Set children) { } } + @Override + public void upgrade(Path folder) { + + try (Stream childStream = Files.list(folder)) { + Iterator iterator = childStream.iterator(); + while (iterator.hasNext()) { + Path child = iterator.next(); + if (Files.isDirectory(child)) { + upgrade(child); + } else { + FileMerger merger = getMerger(child); + merger.upgrade(child); + } + } + } catch (IOException e) { + throw new IllegalStateException("Failed to list children of folder " + folder, e); + } + } + } diff --git a/cli/src/main/java/com/devonfw/tools/ide/merge/FallbackMerger.java b/cli/src/main/java/com/devonfw/tools/ide/merge/FallbackMerger.java index 54bd3bae2..67a78a0cc 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/merge/FallbackMerger.java +++ b/cli/src/main/java/com/devonfw/tools/ide/merge/FallbackMerger.java @@ -8,8 +8,6 @@ /** * Implementation of {@link FileMerger} to use as fallback. It can not actually merge but will simply overwrite the files. - * - * @since 3.0.0 */ public class FallbackMerger extends FileMerger { @@ -39,4 +37,9 @@ public void inverseMerge(Path workspaceFile, EnvironmentVariables resolver, bool // nothing by default, we could copy the workspace file back to the update file if it exists... } + @Override + protected boolean doUpgrade(Path workspaceFile) throws Exception { + + return false; + } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/merge/FileMerger.java b/cli/src/main/java/com/devonfw/tools/ide/merge/FileMerger.java index c931f17df..a1333585a 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/merge/FileMerger.java +++ b/cli/src/main/java/com/devonfw/tools/ide/merge/FileMerger.java @@ -1,12 +1,16 @@ package com.devonfw.tools.ide.merge; +import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; +import java.util.regex.Matcher; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.environment.EnvironmentVariables; import com.devonfw.tools.ide.variable.IdeVariables; +import com.devonfw.tools.ide.variable.VariableDefinition; +import com.devonfw.tools.ide.variable.VariableSyntax; /** * {@link WorkspaceMerger} responsible for a single type of file. @@ -23,7 +27,7 @@ public abstract class FileMerger extends AbstractWorkspaceMerger { public FileMerger(IdeContext context) { super(context); - this.legacySupport = IdeVariables.IDE_VARIABLE_SYNTAX_LEGACY_SUPPORT_ENABLED.get(context).booleanValue(); + this.legacySupport = Boolean.TRUE.equals(IdeVariables.IDE_VARIABLE_SYNTAX_LEGACY_SUPPORT_ENABLED.get(context)); } /** @@ -60,4 +64,80 @@ public final int merge(Path setup, Path update, EnvironmentVariables variables, * @param workspace the workspace {@link Path} to create or update. */ protected abstract void doMerge(Path setup, Path update, EnvironmentVariables variables, Path workspace); + + @Override + public void upgrade(Path workspaceFile) { + + try { + boolean modified = doUpgrade(workspaceFile); + if (modified) { + this.context.debug("Successfully migrated file {}", workspaceFile); + } else { + this.context.trace("Nothing to migrate in file {}", workspaceFile); + } + } catch (Exception e) { + throw new IllegalStateException("Failed to update file " + workspaceFile, e); + } + } + + /** + * @param workspaceFile the {@link Path} to the file to migrate. + * @return {@code true} if the file was migrated (modified), {@code false} otherwise. + * @throws Exception on error. + * @see #upgrade(Path) + */ + protected abstract boolean doUpgrade(Path workspaceFile) throws Exception; + + /** + * Implementation for {@link #doUpgrade(Path)} in case of simple text file format. + * + * @param workspaceFile the {@link Path} to the file to migrate. + * @return {@code true} if the file was migrated (modified), {@code false} otherwise. + * @throws IOException on error. + */ + protected boolean doUpgradeTextContent(Path workspaceFile) throws IOException { + + String content = Files.readString(workspaceFile); + String migratedContent = upgradeWorkspaceContent(content); + boolean modified = !migratedContent.equals(content); + if (modified) { + Files.writeString(workspaceFile, migratedContent); + } + return modified; + } + + /** + * @param content the content from a workspace template file that may contain legacy stuff like old variable syntax. + * @return the given {@link String} with potential legacy constructs being resolved. + */ + protected String upgradeWorkspaceContent(String content) { + + VariableSyntax syntax = VariableSyntax.CURLY; + Matcher matcher = syntax.getPattern().matcher(content); + StringBuilder sb = null; + while (matcher.find()) { + if (sb == null) { + sb = new StringBuilder(content.length() + 8); + } + String variableName = syntax.getVariable(matcher); + String replacement; + VariableDefinition variableDefinition = IdeVariables.get(variableName); + if (variableDefinition != null) { + variableName = variableDefinition.getName(); // ensure legacy name gets replaced with new name + replacement = VariableSyntax.SQUARE.create(variableName); + } else if (variableName.equals("SETTINGS_PATH")) { + replacement = "$[IDE_HOME]/settings"; + } else { + replacement = matcher.group(); // leave ${variableName} untouched + } + matcher.appendReplacement(sb, Matcher.quoteReplacement(replacement)); + } + if (sb == null) { + return content; + } + matcher.appendTail(sb); + return sb.toString().replace("\\conf\\.m2\\", "/conf/.m2/").replace("/conf/.m2/", "/conf/mvn/"); + } + + } diff --git a/cli/src/main/java/com/devonfw/tools/ide/merge/JsonMerger.java b/cli/src/main/java/com/devonfw/tools/ide/merge/JsonMerger.java index 8ea8f5c21..9b9e88392 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/merge/JsonMerger.java +++ b/cli/src/main/java/com/devonfw/tools/ide/merge/JsonMerger.java @@ -1,11 +1,14 @@ package com.devonfw.tools.ide.merge; +import java.io.IOException; import java.io.OutputStream; import java.io.Reader; +import java.io.Writer; import java.nio.file.Files; import java.nio.file.Path; import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; import java.util.Set; import jakarta.json.Json; @@ -23,6 +26,13 @@ import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.environment.EnvironmentVariables; +import com.fasterxml.jackson.core.util.DefaultIndenter; +import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.TextNode; /** * Implementation of {@link FileMerger} for JSON. @@ -225,6 +235,92 @@ private JsonValue mergeAndResolveNativeType(JsonValue json, JsonValue mergeJson, } } + @Override + protected boolean doUpgrade(Path workspaceFile) throws Exception { + + JsonNode jsonNode; + ObjectMapper mapper = new ObjectMapper(); + try (Reader reader = Files.newBufferedReader(workspaceFile)) { + jsonNode = mapper.reader().readTree(reader); + } + JsonNode migratedNode = upgradeJsonNode(jsonNode); + boolean modified = (migratedNode != jsonNode); + if (migratedNode == null) { + migratedNode = jsonNode; + } + if (modified) { + try (Writer writer = Files.newBufferedWriter(workspaceFile)) { + mapper.writer(new JsonPrettyPrinter()).writeValue(writer, migratedNode); + } + } + return modified; + } + + /** + * @param jsonNode the {@link JsonNode} to upgrade. + * @return the given {@link JsonNode} if unmodified after upgrade. Otherwise, a new migrated {@link JsonNode} or {@code null} if the given {@link JsonNode} + * was mutable and the migration could be applied directly. + */ + private JsonNode upgradeJsonNode(JsonNode jsonNode) { + + if (jsonNode instanceof ArrayNode jsonArray) { + return upgradeJsonArray(jsonArray); + } else if (jsonNode instanceof ObjectNode jsonObject) { + return upgradeJsonObject(jsonObject); + } else if (jsonNode instanceof TextNode jsonString) { + return upgradeJsonString(jsonString); + } else { + assert jsonNode.isValueNode(); + return jsonNode; + } + } + + private ObjectNode upgradeJsonObject(ObjectNode jsonObject) { + + ObjectNode result = jsonObject; + Iterator fieldNames = jsonObject.fieldNames(); + while (fieldNames.hasNext()) { + String fieldName = fieldNames.next(); + JsonNode child = jsonObject.get(fieldName); + JsonNode migratedChild = upgradeJsonNode(child); + if (migratedChild != child) { + result = null; + if (migratedChild != null) { + jsonObject.put(fieldName, migratedChild); + } + } + } + return result; + } + + private ArrayNode upgradeJsonArray(ArrayNode jsonArray) { + + ArrayNode result = jsonArray; + int size = jsonArray.size(); + for (int i = 0; i < size; i++) { + JsonNode child = jsonArray.get(i); + JsonNode migratedChild = upgradeJsonNode(child); + if (migratedChild != child) { + result = null; + if (migratedChild != null) { + jsonArray.set(i, migratedChild); + } + } + } + return result; + } + + private JsonNode upgradeJsonString(TextNode jsonString) { + + String text = jsonString.textValue(); + String migratedText = upgradeWorkspaceContent(text); + if (migratedText.equals(text)) { + return jsonString; + } else { + return new TextNode(migratedText); + } + } + private static class Status { /** @@ -264,4 +360,37 @@ private Status(boolean inverse, boolean addNewProperties) { } + /** + * Extends {@link DefaultPrettyPrinter} to get nicely formatted JSON output. + */ + private static class JsonPrettyPrinter extends DefaultPrettyPrinter { + + public JsonPrettyPrinter() { + DefaultPrettyPrinter.Indenter indenter = new DefaultIndenter(" ", "\n"); + indentObjectsWith(indenter); + indentArraysWith(indenter); + _objectFieldValueSeparatorWithSpaces = ": "; + } + + private JsonPrettyPrinter(JsonPrettyPrinter pp) { + super(pp); + } + + @Override + public void writeEndArray(com.fasterxml.jackson.core.JsonGenerator g, int nrOfValues) throws IOException { + + if (!_arrayIndenter.isInline()) { + _nesting--; + } + if (nrOfValues > 0) { + _arrayIndenter.writeIndentation(g, _nesting); + } + g.writeRaw(']'); + } + + @Override + public DefaultPrettyPrinter createInstance() { + return new JsonPrettyPrinter(this); + } + } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/merge/PropertiesMerger.java b/cli/src/main/java/com/devonfw/tools/ide/merge/PropertiesMerger.java index 8fddfc6d4..00a110011 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/merge/PropertiesMerger.java +++ b/cli/src/main/java/com/devonfw/tools/ide/merge/PropertiesMerger.java @@ -8,8 +8,6 @@ import java.util.Properties; import java.util.Set; -import org.jline.utils.Log; - import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.environment.EnvironmentVariables; import com.devonfw.tools.ide.environment.SortedProperties; @@ -37,7 +35,7 @@ protected void doMerge(Path setup, Path update, EnvironmentVariables resolver, P Path template = setup; if (Files.exists(workspace)) { if (!updateFileExists) { - Log.trace("Nothing to do as update file does not exist: {}", update); + this.context.trace("Nothing to do as update file does not exist: {}", update); return; // nothing to do ... } load(properties, workspace); @@ -50,14 +48,14 @@ protected void doMerge(Path setup, Path update, EnvironmentVariables resolver, P } resolve(properties, resolver, template.toString()); save(properties, workspace); - Log.trace("Saved merged properties to: {}", workspace); + this.context.trace("Saved merged properties to: {}", workspace); } /** * @param file the {@link Path} to load. * @return the loaded {@link Properties}. */ - public static Properties load(Path file) { + public Properties load(Path file) { Properties properties = new Properties(); load(properties, file); @@ -68,14 +66,14 @@ public static Properties load(Path file) { * @param file the {@link Path} to load. * @return the loaded {@link Properties}. */ - public static Properties loadIfExists(Path file) { + public Properties loadIfExists(Path file) { Properties properties = new Properties(); if (file != null) { if (Files.exists(file)) { load(properties, file); } else { - Log.trace("Properties file does not exist: {}", file); + this.context.trace("Properties file does not exist: {}", file); } } return properties; @@ -85,9 +83,9 @@ public static Properties loadIfExists(Path file) { * @param properties the existing {@link Properties} instance. * @param file the properties {@link Path} to load. */ - public static void load(Properties properties, Path file) { + public void load(Properties properties, Path file) { - Log.trace("Loading properties file: {}", file); + this.context.trace("Loading properties file: {}", file); try (Reader reader = Files.newBufferedReader(file)) { properties.load(reader); } catch (IOException e) { @@ -108,9 +106,9 @@ private void resolve(Properties properties, EnvironmentVariables variables, Obje * @param properties the {@link Properties} to save. * @param file the {@link Path} to save to. */ - public static void save(Properties properties, Path file) { + public void save(Properties properties, Path file) { - Log.trace("Saving properties file: {}", file); + this.context.trace("Saving properties file: {}", file); ensureParentDirectoryExists(file); try (Writer writer = Files.newBufferedWriter(file)) { properties.store(writer, null); @@ -123,11 +121,11 @@ public static void save(Properties properties, Path file) { public void inverseMerge(Path workspace, EnvironmentVariables variables, boolean addNewProperties, Path update) { if (!Files.exists(workspace)) { - Log.trace("Workspace file does not exist: {}", workspace); + this.context.trace("Workspace file does not exist: {}", workspace); return; } if (!Files.exists(update)) { - Log.trace("Update file does not exist: {}", update); + this.context.trace("Update file does not exist: {}", update); return; } Object src = workspace.getFileName(); @@ -153,10 +151,15 @@ public void inverseMerge(Path workspace, EnvironmentVariables variables, boolean } if (updated) { save(mergedProperties, update); - Log.debug("Saved changes from: {} to: {}", workspace.getFileName(), update); + this.context.debug("Saved changes from: {} to: {}", workspace.getFileName(), update); } else { - Log.trace("No changes for: {}", update); + this.context.trace("No changes for: {}", update); } } + @Override + protected boolean doUpgrade(Path workspaceFile) throws Exception { + + return doUpgradeTextContent(workspaceFile); + } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/merge/TextMerger.java b/cli/src/main/java/com/devonfw/tools/ide/merge/TextMerger.java index e1f71ea73..da4f2b623 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/merge/TextMerger.java +++ b/cli/src/main/java/com/devonfw/tools/ide/merge/TextMerger.java @@ -59,4 +59,10 @@ protected void doMerge(Path setup, Path update, EnvironmentVariables variables, public void inverseMerge(Path workspace, EnvironmentVariables variables, boolean addNewProperties, Path update) { } + + @Override + protected boolean doUpgrade(Path workspaceFile) throws Exception { + + return doUpgradeTextContent(workspaceFile); + } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/merge/WorkspaceMerger.java b/cli/src/main/java/com/devonfw/tools/ide/merge/WorkspaceMerger.java index e09ee7795..dfee5ed1d 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/merge/WorkspaceMerger.java +++ b/cli/src/main/java/com/devonfw/tools/ide/merge/WorkspaceMerger.java @@ -21,9 +21,16 @@ public interface WorkspaceMerger { /** * @param workspace the workspace {@link Path} where to get the changes from. * @param variables the {@link EnvironmentVariables} to {@link EnvironmentVariables#inverseResolve(String, Object) inverse resolve variables}. - * @param addNewProperties - {@code true} to also add new properties to the {@code updateFile}, {@code false} otherwise (to only update existing properties). + * @param addNewProperties - {@code true} to also add new properties to the {@code updateFile}, {@code false} otherwise (to only update existing + * properties). * @param update the update {@link Path} */ void inverseMerge(Path workspace, EnvironmentVariables variables, boolean addNewProperties, Path update); + /** + * @param workspace the {@link Path} to the {@link com.devonfw.tools.ide.context.IdeContext#FOLDER_WORKSPACE workspace} with IDE templates to upgrade + * (migrate and replace legacy constructs). + */ + void upgrade(Path workspace); + } diff --git a/cli/src/main/java/com/devonfw/tools/ide/merge/xmlmerger/XmlMerger.java b/cli/src/main/java/com/devonfw/tools/ide/merge/xmlmerger/XmlMerger.java index 03d4931cb..48579230a 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/merge/xmlmerger/XmlMerger.java +++ b/cli/src/main/java/com/devonfw/tools/ide/merge/xmlmerger/XmlMerger.java @@ -1,5 +1,6 @@ package com.devonfw.tools.ide.merge.xmlmerger; +import java.io.BufferedWriter; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; @@ -35,6 +36,7 @@ public class XmlMerger extends FileMerger { private static final TransformerFactory TRANSFORMER_FACTORY; + /** The namespace URI for this XML merger. */ public static final String MERGE_NS_URI = "https://github.com/devonfw/IDEasy/merge"; static { @@ -255,4 +257,81 @@ private void resolve(NamedNodeMap attributes, EnvironmentVariables variables, bo attribute.setValue(resolvedValue); } } + + @Override + protected boolean doUpgrade(Path workspaceFile) throws Exception { + + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document document = builder.parse(workspaceFile.toFile()); + checkForXmlNamespace(document, workspaceFile); + boolean modified = updateWorkspaceXml(document.getDocumentElement()); + if (modified) { + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + Transformer transformer = transformerFactory.newTransformer(); + DOMSource source = new DOMSource(document); + try (BufferedWriter writer = Files.newBufferedWriter(workspaceFile)) { + StreamResult result = new StreamResult(writer); + transformer.transform(source, result); + } + } + return modified; + } + + private boolean updateWorkspaceXml(Element element) { + + boolean modified = false; + NamedNodeMap attributes = element.getAttributes(); + if (attributes != null) { + for (int i = 0; i < attributes.getLength(); i++) { + Node node = attributes.item(i); + if (node instanceof Attr attribute) { + String value = attribute.getValue(); + String migratedValue = upgradeWorkspaceContent(value); + if (!migratedValue.equals(value)) { + modified = true; + attribute.setValue(migratedValue); + } + } + } + } + + NodeList childNodes = element.getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + Node childNode = childNodes.item(i); + boolean childModified = false; + if (childNode instanceof Element childElement) { + childModified = updateWorkspaceXml(childElement); + } else if (childNode instanceof Text childText) { + String text = childText.getTextContent(); + String migratedText = upgradeWorkspaceContent(text); + childModified = !migratedText.equals(text); + if (childModified) { + childText.setTextContent(migratedText); + } + } + if (childModified) { + modified = true; + } + } + return modified; + } + + private void checkForXmlNamespace(Document document, Path workspaceFile) { + + NamedNodeMap attributes = document.getDocumentElement().getAttributes(); + if (attributes != null) { + for (int i = 0; i < attributes.getLength(); i++) { + Node node = attributes.item(i); + String uri = node.getNamespaceURI(); + if (MERGE_NS_URI.equals(uri)) { + return; + } + } + } + this.context.warning( + "The XML file {} does not contain the XML merge namespace and seems outdated. For details see:\n" + + "https://github.com/devonfw/IDEasy/blob/main/documentation/configurator.adoc#xml-merger", workspaceFile); + } + } diff --git a/cli/src/main/java/com/devonfw/tools/ide/repo/AbstractToolRepository.java b/cli/src/main/java/com/devonfw/tools/ide/repo/AbstractToolRepository.java index 95955aa29..41c5d4976 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/repo/AbstractToolRepository.java +++ b/cli/src/main/java/com/devonfw/tools/ide/repo/AbstractToolRepository.java @@ -164,7 +164,7 @@ protected Path download(String url, Path target, String downloadFilename, Versio this.context.getFileAccess().download(url, tmpDownloadFile); if (resolvedVersion.toString().equals("latest")) { // Some software vendors violate best-practices and provide the latest version only under a fixed URL. - // Therefore if a newer version of that file gets released, the same URL suddenly leads to a different + // Therefore, if a newer version of that file gets released, the same URL suddenly leads to a different // download file with a newer version and a different checksum. // In order to still support such tools we had to implement this workaround so we cannot move the file in the // download cache for later reuse, cannot verify its checksum and also delete the downloaded file on exit diff --git a/cli/src/main/java/com/devonfw/tools/ide/repo/CustomTool.java b/cli/src/main/java/com/devonfw/tools/ide/repo/CustomTool.java deleted file mode 100644 index 47cbb403f..000000000 --- a/cli/src/main/java/com/devonfw/tools/ide/repo/CustomTool.java +++ /dev/null @@ -1,175 +0,0 @@ -package com.devonfw.tools.ide.repo; - -import java.util.Set; - -import com.devonfw.tools.ide.os.OperatingSystem; -import com.devonfw.tools.ide.os.SystemArchitecture; -import com.devonfw.tools.ide.os.SystemInfo; -import com.devonfw.tools.ide.url.model.file.UrlDownloadFileMetadata; -import com.devonfw.tools.ide.version.VersionIdentifier; - -/** - * Representation of a {@link CustomTool} from a {@link CustomToolRepository}. - */ -public final class CustomTool implements UrlDownloadFileMetadata { - - private final String tool; - - private final VersionIdentifier version; - - private final boolean osAgnostic; - - private final boolean archAgnostic; - - private final String repositoryUrl; - - private final String url; - - private final String checksum; - - private final OperatingSystem os; - - private final SystemArchitecture arch; - - /** - * The constructor. - * - * @param tool the {@link #getTool() tool}. - * @param versionIdentifier the {@link #getVersion() version}. - * @param osAgnostic the {@link #isOsAgnostic() OS-agnostic flag}. - * @param archAgnostic the {@link #isArchAgnostic() architecture-agnostic flag}. - * @param repositoryUrl the {@link #getRepositoryUrl() repository URL}. - * @param checksum the {@link #getChecksum() checksum}. - * @param systemInfo the {@link SystemInfo}. - */ - public CustomTool(String tool, VersionIdentifier versionIdentifier, boolean osAgnostic, boolean archAgnostic, - String repositoryUrl, String checksum, SystemInfo systemInfo) { - - super(); - this.tool = tool; - this.version = versionIdentifier; - this.osAgnostic = osAgnostic; - this.archAgnostic = archAgnostic; - this.repositoryUrl = repositoryUrl; - String versionString = versionIdentifier.toString(); - int capacity = repositoryUrl.length() + 2 * tool.length() + 2 * versionString.length() + 7; - if (osAgnostic) { - this.os = null; - } else { - this.os = systemInfo.getOs(); - capacity += this.os.toString().length() + 1; - } - if (archAgnostic) { - this.arch = null; - } else { - this.arch = systemInfo.getArchitecture(); - capacity += this.arch.toString().length() + 1; - } - this.checksum = checksum; - StringBuilder sb = new StringBuilder(capacity); - sb.append(this.repositoryUrl); - char last = this.repositoryUrl.charAt(repositoryUrl.length() - 1); - if ((last != '/') && (last != '\\')) { - sb.append('/'); - } - sb.append(tool); - sb.append('/'); - sb.append(versionString); - sb.append('/'); - sb.append(tool); - sb.append('-'); - sb.append(versionString); - if (this.os != null) { - sb.append('-'); - sb.append(this.os); - } - if (this.arch != null) { - sb.append('-'); - sb.append(this.arch); - } - sb.append(".tgz"); - this.url = sb.toString(); - } - - @Override - public String getTool() { - - return this.tool; - } - - @Override - public String getEdition() { - - return this.tool; - } - - @Override - public VersionIdentifier getVersion() { - - return this.version; - } - - /** - * @return {@code true} if {@link OperatingSystem} agnostic, {@code false} otherwise. - */ - public boolean isOsAgnostic() { - - return this.osAgnostic; - } - - /** - * @return {@code true} if {@link SystemArchitecture} agnostic, {@code false} otherwise. - */ - public boolean isArchAgnostic() { - - return this.archAgnostic; - } - - /** - * @return the repository base URL. This may be a typical URL (e.g. "https://host/path") but may also be a path in your file-system (e.g. to a mounted remote - * network drive). - */ - public String getRepositoryUrl() { - - return this.repositoryUrl; - } - - /** - * @return the URL to the download artifact. - */ - public String getUrl() { - - return this.url; - } - - @Override - public String getChecksum() { - - return this.checksum; - } - - @Override - public OperatingSystem getOs() { - - return this.os; - } - - @Override - public SystemArchitecture getArch() { - - return this.arch; - } - - @Override - public Set getUrls() { - - return Set.of(this.url); - } - - @Override - public String toString() { - - return "CustomTool[" + this.tool + ":" + this.version + "@" + this.repositoryUrl + "]"; - } - -} diff --git a/cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolJson.java b/cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolJson.java new file mode 100644 index 000000000..6bbe9ebc1 --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolJson.java @@ -0,0 +1,26 @@ +package com.devonfw.tools.ide.repo; + +import com.devonfw.tools.ide.os.OperatingSystem; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * JSON representation of a single {@link CustomToolMetadata}. + * + * @param name the {@link CustomToolMetadata#getTool() tool name}. + * @param version the {@link CustomToolMetadata#getVersion() tool version}. + * @param osAgnostic {@code true} if {@link OperatingSystem} agnostic, {@code false} otherwise. + * @param archAgnostic {@code true} if {@link com.devonfw.tools.ide.os.SystemArchitecture} agnostic, {@code false} otherwise. + * @param url the overridden {@link CustomToolsJson#url() repository URL} or {@code null} to inherit. + * @see CustomToolsJson#tools() + */ +public record CustomToolJson(String name, String version, @JsonProperty(value = "os-agnostic") boolean osAgnostic, + @JsonProperty(value = "arch-agnostic") boolean archAgnostic, String url) { + + /** + * @return a new {@link CustomToolsJson} having {@link #url()} set to {@code null}. + */ + public CustomToolJson withoutUrl() { + + return new CustomToolJson(name, version, osAgnostic, archAgnostic, null); + } +} diff --git a/cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolMetadata.java b/cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolMetadata.java new file mode 100644 index 000000000..085492b27 --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolMetadata.java @@ -0,0 +1,119 @@ +package com.devonfw.tools.ide.repo; + +import java.util.Set; + +import com.devonfw.tools.ide.os.OperatingSystem; +import com.devonfw.tools.ide.os.SystemArchitecture; +import com.devonfw.tools.ide.url.model.file.UrlDownloadFileMetadata; +import com.devonfw.tools.ide.version.VersionIdentifier; +import com.fasterxml.jackson.annotation.JsonIgnore; + +/** + * Representation of a {@link CustomToolMetadata} from a {@link CustomToolRepository}. + */ +public final class CustomToolMetadata implements UrlDownloadFileMetadata { + + private final String tool; + + private final VersionIdentifier version; + + private final OperatingSystem os; + + private final SystemArchitecture arch; + + private final String url; + + private final String checksum; + + private final String repositoryUrl; + + /** + * The constructor. + * + * @param tool the {@link #getTool() tool}. + * @param versionString the {@link #getVersion() version} as {@link String}. + * @param os the {@link #getOs() OS}. + * @param arch the {@link #getArch() architecture}. + * @param url the {@link #getUrl() download URL}. + * @param checksum the {@link #getChecksum() checksum}. + * @param repositoryUrl the {@link #getRepositoryUrl() repository URL}. + */ + public CustomToolMetadata(String tool, String versionString, OperatingSystem os, SystemArchitecture arch, + String url, String checksum, String repositoryUrl) { + + super(); + this.tool = tool; + this.version = VersionIdentifier.of(versionString); + this.os = os; + this.arch = arch; + this.url = url; + this.checksum = checksum; + this.repositoryUrl = repositoryUrl; + } + + @Override + public String getTool() { + + return this.tool; + } + + @Override + public String getEdition() { + + return this.tool; + } + + @Override + public VersionIdentifier getVersion() { + + return version; + } + + /** + * @return the URL to the download artifact. + */ + public String getUrl() { + + return this.url; + } + + @Override + public String getChecksum() { + + return this.checksum; + } + + @Override + public OperatingSystem getOs() { + + return this.os; + } + + @Override + public SystemArchitecture getArch() { + + return this.arch; + } + + @Override + public Set getUrls() { + + return Set.of(this.url); + } + + /** + * @return the {@link CustomToolsJson#url() repository base URL}. + */ + @JsonIgnore + public String getRepositoryUrl() { + + return this.repositoryUrl; + } + + @Override + public String toString() { + + return "CustomTool[" + this.tool + ":" + this.version + "@" + this.url + "]"; + } + +} diff --git a/cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolRepository.java b/cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolRepository.java index 5b9ffd13a..bac2a6064 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolRepository.java +++ b/cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolRepository.java @@ -7,12 +7,9 @@ */ public interface CustomToolRepository extends ToolRepository { - /** The filename of the configuration file in the settings for this {@link CustomToolRepository}. */ - String FILE_CUSTOM_TOOLS = "ide-custom-tools.json"; - /** - * @return the {@link Collection} with the {@link CustomTool}s. Will be {@link Collection#isEmpty() empty} if no custom tools are configured. + * @return the {@link Collection} with the {@link CustomToolMetadata}s. Will be {@link Collection#isEmpty() empty} if no custom tools are configured. */ - Collection getTools(); + Collection getTools(); } 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 0d00d466a..b6cab89d8 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 @@ -1,9 +1,5 @@ package com.devonfw.tools.ide.repo; -import java.io.BufferedReader; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -14,14 +10,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import jakarta.json.Json; -import jakarta.json.JsonArray; -import jakarta.json.JsonObject; -import jakarta.json.JsonReader; -import jakarta.json.JsonString; -import jakarta.json.JsonStructure; -import jakarta.json.JsonValue; -import jakarta.json.JsonValue.ValueType; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.url.model.file.UrlDownloadFileMetadata; @@ -36,24 +24,24 @@ public class CustomToolRepositoryImpl extends AbstractToolRepository implements private final String id; - private final Map toolsMap; + private final Map toolsMap; - private final Collection tools; + private final Collection tools; /** * The constructor. * * @param context the owning {@link IdeContext}. - * @param tools the {@link CustomTool}s. + * @param tools the {@link CustomToolMetadata}s. */ - public CustomToolRepositoryImpl(IdeContext context, Collection tools) { + public CustomToolRepositoryImpl(IdeContext context, Collection tools) { super(context); this.toolsMap = new HashMap<>(tools.size()); String repoId = null; - for (CustomTool tool : tools) { + for (CustomToolMetadata tool : tools) { String name = tool.getTool(); - CustomTool duplicate = this.toolsMap.put(name, tool); + CustomToolMetadata duplicate = this.toolsMap.put(name, tool); if (duplicate != null) { throw new IllegalStateException("Duplicate custom tool '" + name + "'!"); } @@ -76,7 +64,7 @@ private static String computeId(String url) { id = id.substring(schemaIndex + 3); // remove schema like "https://" id = URLDecoder.decode(id, StandardCharsets.UTF_8); } - id.replace('\\', '/').replace("//", "/"); // normalize slashes + id = id.replace('\\', '/').replace("//", "/"); // normalize slashes if (id.startsWith("/")) { id = id.substring(1); } @@ -111,7 +99,7 @@ public String getId() { @Override protected UrlDownloadFileMetadata getMetadata(String tool, String edition, VersionIdentifier version) { - CustomTool customTool = getCustomTool(tool); + CustomToolMetadata customTool = getCustomTool(tool); if (!version.equals(customTool.getVersion())) { throw new IllegalArgumentException("Undefined version '" + version + "' for custom tool '" + tool + "' - expected version '" + customTool.getVersion() + "'!"); @@ -122,8 +110,8 @@ protected UrlDownloadFileMetadata getMetadata(String tool, String edition, Versi return customTool; } - private CustomTool getCustomTool(String tool) { - CustomTool customTool = this.toolsMap.get(tool); + private CustomToolMetadata getCustomTool(String tool) { + CustomToolMetadata customTool = this.toolsMap.get(tool); if (customTool == null) { throw new IllegalArgumentException("Undefined custom tool '" + tool + "'!"); } @@ -133,16 +121,16 @@ private CustomTool getCustomTool(String tool) { @Override public VersionIdentifier resolveVersion(String tool, String edition, GenericVersionRange version) { - CustomTool customTool = getCustomTool(tool); - VersionIdentifier customToolVerstion = customTool.getVersion(); - if (!version.contains(customToolVerstion)) { + CustomToolMetadata customTool = getCustomTool(tool); + VersionIdentifier customToolVersion = customTool.getVersion(); + if (!version.contains(customToolVersion)) { throw new IllegalStateException(customTool + " does not satisfy version to install " + version); } - return customToolVerstion; + return customToolVersion; } @Override - public Collection getTools() { + public Collection getTools() { return this.tools; } @@ -160,109 +148,18 @@ public Collection findDependencies(String tool, String edition, public static CustomToolRepository of(IdeContext context) { Path settingsPath = context.getSettingsPath(); - Path customToolsJson = null; + Path customToolsJsonFile = null; if (settingsPath != null) { - customToolsJson = settingsPath.resolve(FILE_CUSTOM_TOOLS); + customToolsJsonFile = settingsPath.resolve(IdeContext.FILE_CUSTOM_TOOLS); } - List tools = new ArrayList<>(); - if ((customToolsJson != null) && Files.exists(customToolsJson)) { - try (InputStream in = Files.newInputStream(customToolsJson); - Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8)) { - - JsonReader jsonReader = Json.createReader(new BufferedReader(reader)); - JsonStructure json = jsonReader.read(); - JsonObject jsonRoot = requireObject(json); - String defaultUrl = getString(jsonRoot, "url", ""); - JsonArray jsonTools = requireArray(jsonRoot.get("tools")); - for (JsonValue jsonTool : jsonTools) { - JsonObject jsonToolObject = requireObject(jsonTool); - String name = getString(jsonToolObject, "name"); - String version = getString(jsonToolObject, "version"); - String url = getString(jsonToolObject, "url", defaultUrl); - boolean osAgnostic = getBoolean(jsonToolObject, "os-agnostic", Boolean.FALSE); - boolean archAgnostic = getBoolean(jsonToolObject, "arch-agnostic", Boolean.TRUE); - if (url.isEmpty()) { - throw new IllegalStateException("Missing 'url' property for tool '" + name + "'!"); - } - // TODO - String checksum = null; - CustomTool customTool = new CustomTool(name, VersionIdentifier.of(version), osAgnostic, archAgnostic, url, - checksum, context.getSystemInfo()); - tools.add(customTool); - } - } catch (Exception e) { - throw new IllegalStateException("Failed to read JSON from " + customToolsJson, e); - } - } - return new CustomToolRepositoryImpl(context, tools); - } - - private static boolean getBoolean(JsonObject json, String property, Boolean defaultValue) { - - JsonValue value = json.get(property); - if (value == null) { - if (defaultValue == null) { - throw new IllegalArgumentException("Missing string property '" + property + "' in JSON: " + json); - } - return defaultValue.booleanValue(); - } - ValueType valueType = value.getValueType(); - if (valueType == ValueType.TRUE) { - return true; - } else if (valueType == ValueType.FALSE) { - return false; + List tools; + if ((customToolsJsonFile != null) && Files.exists(customToolsJsonFile)) { + CustomToolsJson customToolsJson = CustomToolsJsonMapper.loadJson(customToolsJsonFile); + tools = CustomToolsJsonMapper.convert(customToolsJson, context); } else { - throw new IllegalStateException("Expected value type boolean but found " + valueType + " for JSON: " + json); - } - } - - private static String getString(JsonObject json, String property) { - - return getString(json, property, null); - } - - private static String getString(JsonObject json, String property, String defaultValue) { - - JsonValue value = json.get(property); - if (value == null) { - if (defaultValue == null) { - throw new IllegalArgumentException("Missing string property '" + property + "' in JSON: " + json); - } - return defaultValue; - } - require(value, ValueType.STRING); - return ((JsonString) value).getString(); - } - - /** - * @param json the {@link JsonValue} to check. - */ - private static JsonObject requireObject(JsonValue json) { - - require(json, ValueType.OBJECT); - return (JsonObject) json; - } - - /** - * @param json the {@link JsonValue} to check. - */ - private static JsonArray requireArray(JsonValue json) { - - require(json, ValueType.ARRAY); - return (JsonArray) json; - } - - /** - * @param json the {@link JsonValue} to check. - * @param type the expected {@link ValueType}. - */ - private static void require(JsonValue json, ValueType type) { - - ValueType actualType = json.getValueType(); - if (actualType != type) { - throw new IllegalStateException( - "Expected value type " + type + " but found " + actualType + " for JSON: " + json); + tools = new ArrayList<>(); } + return new CustomToolRepositoryImpl(context, tools); } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolsJson.java b/cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolsJson.java new file mode 100644 index 000000000..7f8a8b19d --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolsJson.java @@ -0,0 +1,14 @@ +package com.devonfw.tools.ide.repo; + +import java.util.List; + +/** + * {@link CustomToolsJson} for the ide-custom-tools.json file. + * + * @param url the repository base URL. This may be a typical URL (e.g. "https://host/path") but may also be a path in your file-system (e.g. to a mounted + * remote network drive). + * @param tools the {@link List} of {@link CustomToolJson}. + */ +public record CustomToolsJson(String url, List tools) { + +} diff --git a/cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolsJsonMapper.java b/cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolsJsonMapper.java new file mode 100644 index 000000000..e60100876 --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/repo/CustomToolsJsonMapper.java @@ -0,0 +1,186 @@ +package com.devonfw.tools.ide.repo; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.environment.VariableLine; +import com.devonfw.tools.ide.json.JsonMapping; +import com.devonfw.tools.ide.os.OperatingSystem; +import com.devonfw.tools.ide.os.SystemArchitecture; +import com.devonfw.tools.ide.os.SystemInfo; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Mapper of {@link CustomToolsJson} from/to JSON. + */ +public class CustomToolsJsonMapper { + + private static final ObjectMapper MAPPER = JsonMapping.create(); + + /** + * @param customTools the {@link CustomToolsJson} to save. + * @param path the {@link Path} of the file where to save as JSON. + */ + public static void saveJson(CustomToolsJson customTools, Path path) { + + try (BufferedWriter writer = Files.newBufferedWriter(path)) { + MAPPER.writerWithDefaultPrettyPrinter().writeValue(writer, customTools); + } catch (Exception e) { + throw new IllegalStateException("Failed to save file " + path, e); + } + } + + /** + * @param path the {@link Path} of the JSON file to load. + * @return the parsed {@link CustomToolsJson} or {@code null} if the {@link Path} is not an existing file. + */ + public static CustomToolsJson loadJson(Path path) { + CustomToolsJson customToolsJson = null; + if (Files.isRegularFile(path)) { + try (BufferedReader reader = Files.newBufferedReader(path)) { + customToolsJson = MAPPER.readValue(reader, CustomToolsJson.class); + } catch (Exception e) { + throw new IllegalStateException("Failed to load " + path, e); + } + } + return customToolsJson; + } + + /** + * @param customTools the {@link CustomToolsJson} to convert. + * @param context the {@link IdeContext}. + * @return the converted {@link List} of {@link CustomToolMetadata}. + */ + public static List convert(CustomToolsJson customTools, IdeContext context) { + + String repositoryUrl = customTools.url(); + List tools = customTools.tools(); + List result = new ArrayList<>(tools.size()); + for (CustomToolJson customTool : tools) { + result.add(convert(customTool, repositoryUrl, context)); + } + return result; + } + + private static CustomToolMetadata convert(CustomToolJson customTool, String repositoryUrl, IdeContext context) { + + String tool = customTool.name(); + String version = customTool.version(); + SystemInfo systemInfo = context.getSystemInfo(); + String repoUrl = customTool.url(); + if ((repoUrl == null) || repoUrl.isEmpty()) { + repoUrl = repositoryUrl; + } + int capacity = repoUrl.length() + 2 * tool.length() + 2 * version.length() + 7; + OperatingSystem os; + if (customTool.osAgnostic()) { + os = null; + } else { + os = systemInfo.getOs(); + capacity += os.toString().length() + 1; + } + SystemArchitecture arch; + if (customTool.archAgnostic()) { + arch = null; + } else { + arch = systemInfo.getArchitecture(); + capacity += arch.toString().length() + 1; + } + StringBuilder sb = new StringBuilder(capacity); + sb.append(repoUrl); + char last = repoUrl.charAt(repoUrl.length() - 1); + if ((last != '/') && (last != '\\')) { + sb.append('/'); + } + sb.append(tool); + sb.append('/'); + sb.append(version); + sb.append('/'); + sb.append(tool); + sb.append('-'); + sb.append(version); + if (os != null) { + sb.append('-'); + sb.append(os); + } + if (arch != null) { + sb.append('-'); + sb.append(arch); + } + sb.append(".tgz"); + String url = sb.toString(); + return new CustomToolMetadata(tool, version, os, arch, url, null, repoUrl); + } + + /** + * Retrieves custom tools from a devonfw-ide legacy config. + * + * @param customToolsContent String of custom tools + * @param context the {@link IdeContext}. + * @return {@link CustomToolsJson}. + */ + public static CustomToolsJson parseCustomToolsFromLegacyConfig(String customToolsContent, IdeContext context) { + List customToolEntries = VariableLine.parseArray(customToolsContent); + if (customToolEntries.isEmpty()) { + return null; + } + List customTools = new ArrayList<>(customToolEntries.size()); + String defaultUrl = null; + for (String customToolConfig : customToolEntries) { + CustomToolJson customToolJson = parseCustomToolFromLegacyConfig(customToolConfig); + if (customToolJson == null) { + context.warning("Invalid custom tool entry: {}", customToolConfig); + } else { + String url = customToolJson.url(); + if (defaultUrl == null) { + if ((url == null) || url.isEmpty()) { + context.warning("First custom tool entry has no URL specified: {}", customToolConfig); + } else { + defaultUrl = url; + customToolJson = customToolJson.withoutUrl(); + } + } else if (defaultUrl.equals(url)) { + customToolJson = customToolJson.withoutUrl(); + } + customTools.add(customToolJson); + } + } + if (customTools.isEmpty() || (defaultUrl == null)) { + return null; + } + return new CustomToolsJson(defaultUrl, customTools); + } + + private static CustomToolJson parseCustomToolFromLegacyConfig(String customToolConfig) { + int firstColon = customToolConfig.indexOf(":"); + if (firstColon < 0) { + return null; + } + String toolName = customToolConfig.substring(0, firstColon); + int secondColon = customToolConfig.indexOf(":", firstColon + 1); + if (secondColon < 0) { + return null; + } + String version = customToolConfig.substring(firstColon + 1, secondColon); + int thirdColon = customToolConfig.indexOf(":", secondColon + 1); + boolean osAgnostic = false; + boolean archAgnostic = false; + String url = null; + if (thirdColon > 0) { + if (customToolConfig.substring(secondColon + 1, thirdColon).equals("all")) { + osAgnostic = true; + archAgnostic = true; + url = customToolConfig.substring(thirdColon + 1); + } else { + url = customToolConfig.substring(secondColon + 1); + } + } + return new CustomToolJson(toolName, version, osAgnostic, archAgnostic, url); + } + +} diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/CustomToolCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/CustomToolCommandlet.java index acb4cb1ad..3a0be1d2f 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/CustomToolCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/CustomToolCommandlet.java @@ -1,17 +1,17 @@ package com.devonfw.tools.ide.tool; import com.devonfw.tools.ide.context.IdeContext; -import com.devonfw.tools.ide.repo.CustomTool; +import com.devonfw.tools.ide.repo.CustomToolMetadata; import com.devonfw.tools.ide.version.VersionIdentifier; /** - * {@link LocalToolCommandlet} for a {@link CustomTool}. + * {@link LocalToolCommandlet} for a {@link CustomToolMetadata}. */ public class CustomToolCommandlet extends LocalToolCommandlet { - private CustomTool customTool; + private CustomToolMetadata customTool; - public CustomToolCommandlet(IdeContext context, CustomTool customTool) { + public CustomToolCommandlet(IdeContext context, CustomToolMetadata customTool) { super(context, customTool.getTool(), null); this.customTool = customTool; diff --git a/cli/src/main/java/com/devonfw/tools/ide/util/DateTimeUtil.java b/cli/src/main/java/com/devonfw/tools/ide/util/DateTimeUtil.java index 0fd836d9a..5dc3116fa 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/util/DateTimeUtil.java +++ b/cli/src/main/java/com/devonfw/tools/ide/util/DateTimeUtil.java @@ -11,7 +11,10 @@ */ public final class DateTimeUtil { - private static final DateTimeFormatter DATE_FORMATTER = new DateTimeFormatterBuilder().appendPattern("YYYY-MM-dd") + private static final DateTimeFormatter DATE_FORMATTER_PATH = new DateTimeFormatterBuilder().appendPattern("YYYY/MM/dd") + .toFormatter(); + + private static final DateTimeFormatter DATE_FORMATTER_NAME = new DateTimeFormatterBuilder().appendPattern("YYYY-MM-dd") .toFormatter(); private static final DateTimeFormatter TIME_FORMATTER = new DateTimeFormatterBuilder().appendPattern("HH-mm-ss") @@ -66,11 +69,16 @@ public static Integer compareDuration(Instant start, Instant end, Duration durat /** * @param temporal the {@link LocalDateTime} to format as date. - * @return the {@link LocalDateTime} formatted as date in the format YYYY-MM-dd. + * @param dirs {@code true} to use "/" as separator to create subfolders per year, month, and date, {@code false} otherwise. + * @return the {@link LocalDateTime} formatted as date in the format YYYY-MM-dd or YYYY/MM/dd. */ - public static String formatDate(LocalDateTime temporal) { + public static String formatDate(LocalDateTime temporal, boolean dirs) { - return temporal.format(DATE_FORMATTER); + if (dirs) { + return temporal.format(DATE_FORMATTER_PATH); + } else { + return temporal.format(DATE_FORMATTER_NAME); + } } /** 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 c88080231..73cd6d995 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 @@ -86,6 +86,9 @@ public interface IdeVariables { VariableDefinitionBoolean IDE_VARIABLE_SYNTAX_LEGACY_SUPPORT_ENABLED = new VariableDefinitionBoolean("IDE_VARIABLE_SYNTAX_LEGACY_SUPPORT_ENABLED", null, c -> Boolean.TRUE); + /** {@link VariableDefinition} for {@link com.devonfw.tools.ide.context.IdeContext#getProjectName() DEVON_IDE_CUSTOM_TOOLS}. */ + VariableDefinitionString DEVON_IDE_CUSTOM_TOOLS = new VariableDefinitionString("DEVON_IDE_CUSTOM_TOOLS"); + /** 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, M2_REPO, DOCKER_EDITION, MVN_BUILD_OPTS, NPM_BUILD_OPTS, GRADLE_BUILD_OPTS, YARN_BUILD_OPTS, JASYPT_OPTS, MAVEN_ARGS, diff --git a/cli/src/main/java/com/devonfw/tools/ide/variable/VariableDefinitionStringList.java b/cli/src/main/java/com/devonfw/tools/ide/variable/VariableDefinitionStringList.java index e1b61f6cb..56055cd42 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/variable/VariableDefinitionStringList.java +++ b/cli/src/main/java/com/devonfw/tools/ide/variable/VariableDefinitionStringList.java @@ -1,6 +1,5 @@ package com.devonfw.tools.ide.variable; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.function.Function; @@ -61,7 +60,7 @@ public VariableDefinitionStringList(String name, String legacyName, super(name, legacyName, defaultValueFactory, forceDefaultValue); } - @SuppressWarnings( { "unchecked", "rawtypes" }) + @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public Class> getValueType() { @@ -74,17 +73,7 @@ public List fromString(String value, IdeContext context) { if (value.isEmpty()) { return Collections.emptyList(); } - String csv = value; - String separator = ","; - if (isBashArray(value)) { - csv = value.substring(1, value.length() - 1); - separator = " "; - } - String[] items = csv.split(separator); - List list = new ArrayList<>(items.length); - for (String item : items) { - list.add(item.trim()); - } + List list = VariableLine.parseArray(value); list = Collections.unmodifiableList(list); return list; } diff --git a/cli/src/main/resources/nls/Help.properties b/cli/src/main/resources/nls/Help.properties index 42486803f..043f683e5 100644 --- a/cli/src/main/resources/nls/Help.properties +++ b/cli/src/main/resources/nls/Help.properties @@ -108,6 +108,8 @@ cmd.uninstall-plugin.detail=Plugins can be only installed or uninstalled for too cmd.uninstall.detail=Can be used to uninstall any tool e.g. to uninstall java simply type: 'uninstall java'. cmd.update=Pull your settings and apply updates (software, configuration and repositories). cmd.update.detail=To update your IDE (if instructed by your ide-admin), you only need to run the following command: 'ide update'. +cmd.upgrade-settings=Commandlet to upgrade settings of a devonfw-ide project, to allow migration to IDEasy. +cmd.upgrade-settings.detail=Renames and reconfigures all devon.properties, replaces all legacy variables, updates folder names and points out all xml files that are not compatible for the xml merger. cmd.version=Print the version of IDEasy. cmd.version.detail=To print the current version of IDEasy simply type: 'ide --version'. cmd.vscode=Tool commandlet for Visual Studio Code (IDE). diff --git a/cli/src/main/resources/nls/Help_de.properties b/cli/src/main/resources/nls/Help_de.properties index 3e7af8e8c..35ad45187 100644 --- a/cli/src/main/resources/nls/Help_de.properties +++ b/cli/src/main/resources/nls/Help_de.properties @@ -108,6 +108,8 @@ cmd.uninstall-plugin.detail=Erweiterung können nur für Werkzeuge installiert u cmd.uninstall.detail=Wird dazu verwendet um jedwedes Werkzeug zu deinstallieren. Um z.B. Java zu deinstallieren geben Sie einfach 'uninstall java' in die Konsole ein. cmd.update=Updatet die Settings, Software und Repositories. cmd.update.detail=Um die IDE auf den neuesten Stand zu bringen (falls von Ihrem Admin angewiesen) geben Sie einfach 'ide update' in die Konsole ein. +cmd.upgrade-settings=Kommando zum Aufwerten der Einstellungen eines devonfw-ide Projekts, um den Umstieg zu IDEasy zu ermöglichen. +cmd.upgrade-settings.detail=Benennt alle devon.properties um und konfiguriert sie neu, ersetzt alle legacy Variablen, aktualisiert Ordnernamen und weist auf alle XML-Dateien hin, die nicht für den xml merger kompatibel sind. cmd.version=Gibt die Version von IDEasy aus. cmd.version.detail=Um die aktuelle Version von IDEasy auszugeben, brauchen Sie einfach nur 'ide --version' in die Konsole eingeben. cmd.vscode=Werkzeug Kommando für Visual Studio Code (IDE). diff --git a/cli/src/test/java/com/devonfw/tools/ide/commandlet/UpgradeSettingsCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/commandlet/UpgradeSettingsCommandletTest.java new file mode 100644 index 000000000..23b496aa7 --- /dev/null +++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/UpgradeSettingsCommandletTest.java @@ -0,0 +1,102 @@ +package com.devonfw.tools.ide.commandlet; + +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; +import com.devonfw.tools.ide.context.IdeTestContext; +import com.devonfw.tools.ide.repo.CustomToolJson; +import com.devonfw.tools.ide.repo.CustomToolsJson; +import com.devonfw.tools.ide.repo.CustomToolsJsonMapper; + +/** + * Integration test of {@link UpgradeSettingsCommandlet} . + */ +public class UpgradeSettingsCommandletTest extends AbstractIdeContextTest { + + private static final String PROJECT_UPGRADE_SETTINGS = "upgrade-settings"; + private final IdeTestContext context = newContext(PROJECT_UPGRADE_SETTINGS); + private static final Path UPGRADE_SETTINGS_PATH = TEST_PROJECTS_COPY.resolve(PROJECT_UPGRADE_SETTINGS).resolve("project"); + + /** + * Test of {@link UpgradeSettingsCommandlet}. + * + * @throws Exception on error. + */ + @Test + public void testUpdateSettings() throws Exception { + // arrange + UpgradeSettingsCommandlet upgradeSettingsCommandlet = new UpgradeSettingsCommandlet(context); + // act + upgradeSettingsCommandlet.run(); + // assert + verifyUpdateLegacyFolders(); + verifyUpdateProperties(); + verifyUpdateWorkspaceTemplates(); + } + + /** + * @throws Exception on error. + */ + private void verifyUpdateProperties() throws Exception { + + assertThat(UPGRADE_SETTINGS_PATH.resolve("home/.ide/ide.properties")).exists(); + assertThat(UPGRADE_SETTINGS_PATH.resolve("settings/ide.properties")).exists().content().contains("INTELLIJ_EDITION=ultimate") + .doesNotContain("INTELLIJ_EDITION_TYPE").contains("IDE_VARIABLE_SYNTAX_LEGACY_SUPPORT_ENABLED=false"); + assertThat(UPGRADE_SETTINGS_PATH.resolve("workspaces/main/ide.properties")).exists(); + //assert that file content was changed + assertThat(UPGRADE_SETTINGS_PATH.resolve("conf/ide.properties")).exists().content().contains("MVN_VERSION=test"); + + // devon.properties have been deleted (moved to backup) + assertThat(UPGRADE_SETTINGS_PATH.resolve("home/devon.properties")).doesNotExist(); + assertThat(UPGRADE_SETTINGS_PATH.resolve("settings/devon.properties")).doesNotExist(); + assertThat(UPGRADE_SETTINGS_PATH.resolve("workspaces/main/devon.properties")).doesNotExist(); + assertThat(UPGRADE_SETTINGS_PATH.resolve("conf/devon.properties")).doesNotExist(); + verifyCustomToolsJson(); + } + + private void verifyCustomToolsJson() throws Exception { + // arrange + UpgradeSettingsCommandlet upgradeSettingsCommandlet = new UpgradeSettingsCommandlet(context); + // act + upgradeSettingsCommandlet.run(); + // assert + + Path customToolsJsonFile = UPGRADE_SETTINGS_PATH.resolve("settings").resolve(IdeContext.FILE_CUSTOM_TOOLS); + // assert that ide-custom-tools.json exists + assertThat(customToolsJsonFile).exists(); + CustomToolsJson customToolsJson = CustomToolsJsonMapper.loadJson(customToolsJsonFile); + //assert that ide-custom-tools.json has the correct content + assertThat(customToolsJson.url()).isEqualTo("https://host.tld/projects/my-project"); + assertThat(customToolsJson.tools()).containsExactly(new CustomToolJson("jboss-eap", "7.1.4.GA", true, true, null), + new CustomToolJson("firefox", "70.0.1", false, false, null)); + } + + private void verifyUpdateLegacyFolders() { + assertThat(UPGRADE_SETTINGS_PATH.resolve("settings/repositories/IDEasy.properties")).exists(); + assertThat(UPGRADE_SETTINGS_PATH.resolve("settings/templates/conf/ide.properties")).exists(); + assertThat(UPGRADE_SETTINGS_PATH.resolve("settings/templates/conf/mvn/settings.xml")).exists(); + } + + private void verifyUpdateWorkspaceTemplates() { + // arrange + UpgradeSettingsCommandlet upgradeSettingsCommandlet = new UpgradeSettingsCommandlet(context); + // act + upgradeSettingsCommandlet.run(); + //assert + assertThat(UPGRADE_SETTINGS_PATH.resolve("settings/workspace/testVariableSyntax.txt")).exists().content().contains("$[IDE_HOME]").contains("$[MVN_VERSION]") + .contains("$[IDE_HOME]/conf/mvn/settings.xml").doesNotContain("${IDE_HOME}").doesNotContain("${MVN_VERSION}"); + verifyLoggingOfXmlFiles(); + } + + private void verifyLoggingOfXmlFiles() { + Path workspace = UPGRADE_SETTINGS_PATH.resolve(IdeContext.FOLDER_SETTINGS).resolve("intellij").resolve(IdeContext.FOLDER_WORKSPACE).resolve("TestXml.xml") + .toAbsolutePath(); + //assert + assertThat(context).logAtWarning().hasMessage( + "The XML file " + workspace + " does not contain the XML merge namespace and seems outdated. For details see:\n" + + "https://github.com/devonfw/IDEasy/blob/main/documentation/configurator.adoc#xml-merger"); + } +} 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 39a0f0016..943145513 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 @@ -28,7 +28,7 @@ public abstract class AbstractIdeContextTest extends Assertions { 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 TEST_PROJECTS_COPY = Path.of("target/test-projects"); + protected 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; 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 bdc452214..bc85c9ed9 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 @@ -50,6 +50,7 @@ public void testConfigurator(@TempDir Path workspaceDir) throws Exception { // arrange IdeContext context = newContext(PROJECT_BASIC, null, false); DirectoryMerger merger = context.getWorkspaceMerger(); + PropertiesMerger propertiesMerger = new PropertiesMerger(context); Path templates = Path.of("src/test/resources/templates"); Path setup = templates.resolve(IdeContext.FOLDER_SETUP); Path update = templates.resolve(IdeContext.FOLDER_UPDATE); @@ -62,7 +63,7 @@ public void testConfigurator(@TempDir Path workspaceDir) throws Exception { // assert Path mainPrefsFile = workspaceDir.resolve("main.prefs"); - Properties mainPrefs = PropertiesMerger.load(mainPrefsFile); + Properties mainPrefs = propertiesMerger.load(mainPrefsFile); assertThat(mainPrefs).containsOnly(JAVA_VERSION, JAVA_HOME, THEME, UI); Path jsonFolder = workspaceDir.resolve("json"); assertThat(jsonFolder).isDirectory(); @@ -90,7 +91,7 @@ public void testConfigurator(@TempDir Path workspaceDir) throws Exception { Path configFolder = workspaceDir.resolve("config"); assertThat(configFolder).isDirectory(); Path indentFile = configFolder.resolve("indent.properties"); - Properties indent = PropertiesMerger.load(indentFile); + Properties indent = propertiesMerger.load(indentFile); assertThat(indent).containsOnly(INDENTATION); assertThat(configFolder.resolve("layout.xml")).hasContent(""" @@ -101,7 +102,7 @@ public void testConfigurator(@TempDir Path workspaceDir) throws Exception { console ${IDE_HOME} - """.replace("${IDE_HOME}", IDE_HOME)); + """.replace("${IDE_HOME}", IDE_HOME)); // and arrange EDITOR.apply(mainPrefs); @@ -109,13 +110,13 @@ public void testConfigurator(@TempDir Path workspaceDir) throws Exception { UI_HACKED.apply(mainPrefs); THEME_HACKED.apply(mainPrefs); INDENTATION_HACKED.apply(mainPrefs); - PropertiesMerger.save(mainPrefs, mainPrefsFile); + propertiesMerger.save(mainPrefs, mainPrefsFile); // act merger.merge(setup, update, context.getVariables(), workspaceDir); // assert - mainPrefs = PropertiesMerger.load(mainPrefsFile); + mainPrefs = propertiesMerger.load(mainPrefsFile); assertThat(mainPrefs).containsOnly(JAVA_VERSION, JAVA_HOME, THEME_HACKED, UI_HACKED, EDITOR, INDENTATION_HACKED); assertThat(namePath).hasContent("project - main\ntest"); diff --git a/cli/src/test/java/com/devonfw/tools/ide/repo/CustomToolMetadataTest.java b/cli/src/test/java/com/devonfw/tools/ide/repo/CustomToolMetadataTest.java new file mode 100644 index 000000000..d98183157 --- /dev/null +++ b/cli/src/test/java/com/devonfw/tools/ide/repo/CustomToolMetadataTest.java @@ -0,0 +1,68 @@ +package com.devonfw.tools.ide.repo; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +import com.devonfw.tools.ide.os.OperatingSystem; +import com.devonfw.tools.ide.os.SystemArchitecture; +import com.devonfw.tools.ide.version.VersionIdentifier; + +/** + * Test of {@link CustomToolMetadata}. + */ +public class CustomToolMetadataTest extends Assertions { + + /** + * Test of {@link CustomToolMetadata}. + */ + @Test + public void testAgnostic() { + + // arrange + String name = "jboss-eap"; + String version = "7.4.5.GA"; + String repositoryUrl = "https://host.domain.tld:8443/folder/repo/"; + String url = repositoryUrl + "jboss-eap/7.4.5.GA/jboss-eap-7.4.5.GA.tgz"; + String checksum = "4711"; + OperatingSystem os = null; + SystemArchitecture arch = null; + // act + CustomToolMetadata tool = new CustomToolMetadata(name, version, os, arch, url, checksum, repositoryUrl); + // assert + assertThat(tool.getTool()).isEqualTo(name); + assertThat(tool.getVersion()).isEqualTo(VersionIdentifier.of(version)); + assertThat(tool.getOs()).isEqualTo(os); + assertThat(tool.getArch()).isEqualTo(arch); + assertThat(tool.getUrl()).isEqualTo(url); + assertThat(tool.getChecksum()).isEqualTo(checksum); + assertThat(tool.getRepositoryUrl()).isEqualTo(repositoryUrl); + } + + /** + * Test of {@link CustomToolMetadata}. + */ + @Test + public void testSpecific() { + + // arrange + String name = "firefox"; + String version = "70.0.1"; + String repositoryUrl = "https://host.domain.tld:8443/folder/repo"; + String url = repositoryUrl + "/firefox/70.0.1/firefox-70.0.1-windows.tgz"; + String checksum = "4711"; + OperatingSystem os = OperatingSystem.MAC; + SystemArchitecture arch = SystemArchitecture.ARM64; + // act + CustomToolMetadata tool = new CustomToolMetadata(name, version, os, arch, url, checksum, repositoryUrl); + // assert + assertThat(tool.getTool()).isEqualTo(name); + assertThat(tool.getVersion()).isEqualTo(VersionIdentifier.of(version)); + assertThat(tool.getOs()).isEqualTo(os); + assertThat(tool.getArch()).isEqualTo(arch); + assertThat(tool.getUrl()).isEqualTo(url); + assertThat(tool.getChecksum()).isEqualTo(checksum); + assertThat(tool.getRepositoryUrl()).isEqualTo(repositoryUrl); + } + + +} 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 deleted file mode 100644 index d5ef2df71..000000000 --- a/cli/src/test/java/com/devonfw/tools/ide/repo/CustomToolTest.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.devonfw.tools.ide.repo; - -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.Test; - -import com.devonfw.tools.ide.os.SystemInfoMock; -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, - 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/CustomToolsJsonMapperTest.java b/cli/src/test/java/com/devonfw/tools/ide/repo/CustomToolsJsonMapperTest.java new file mode 100644 index 000000000..97068c515 --- /dev/null +++ b/cli/src/test/java/com/devonfw/tools/ide/repo/CustomToolsJsonMapperTest.java @@ -0,0 +1,124 @@ +package com.devonfw.tools.ide.repo; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +import com.devonfw.tools.ide.context.AbstractIdeTestContext; +import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.context.IdeSlf4jContext; +import com.devonfw.tools.ide.context.IdeTestContext; +import com.devonfw.tools.ide.os.OperatingSystem; +import com.devonfw.tools.ide.os.SystemArchitecture; +import com.devonfw.tools.ide.os.SystemInfoMock; +import com.devonfw.tools.ide.version.VersionIdentifier; + +/** + * Test of {@link CustomToolsJsonMapper}. + */ +public class CustomToolsJsonMapperTest extends Assertions { + + @Test + public void testReadCustomToolsFromJson() { + // arrange + Path testPath = Path.of("src/test/resources/customtools"); + // act + CustomToolsJson customToolsJson = CustomToolsJsonMapper.loadJson(testPath.resolve("ide-custom-tools.json")); + // assert + assertThat(customToolsJson.url()).isEqualTo("https://some-file-server.company.com/projects/my-project"); + // assert that custom tools content matches to json file + assertThat(customToolsJson.tools()).containsExactly(new CustomToolJson("jboss-eap", "7.1.4.GA", true, true, + null), + new CustomToolJson("firefox", "70.0.1", false, false, + "https://some-file-server.company.com/projects/my-project2")); + } + + @Test + public void testReadCustomToolsFromLegacyConfig() { + // arrange + IdeContext context = new IdeTestContext(); + String legacyProperties = "(jboss-eap:7.1.4.GA:all:https://host.tld/projects/my-project firefox:70.0.1:all:)"; + // act + CustomToolsJson customToolsJson = CustomToolsJsonMapper.parseCustomToolsFromLegacyConfig(legacyProperties, context); + // assert + assertThat(customToolsJson.url()).isEqualTo("https://host.tld/projects/my-project"); + assertThat(customToolsJson.tools()).containsExactly(new CustomToolJson("jboss-eap", "7.1.4.GA", true, true, + null), + new CustomToolJson("firefox", "70.0.1", true, true, "")); + } + + @Test + public void testReadEmptyCustomToolsFromLegacyConfig() { + // arrange + IdeContext context = new IdeTestContext(); + String legacyProperties = "()"; + // act + CustomToolsJson customToolsJson = CustomToolsJsonMapper.parseCustomToolsFromLegacyConfig(legacyProperties, context); + // assert + assertThat(customToolsJson).isNull(); + } + + @Test + public void testReadFaultyCustomToolsFromLegacyConfig() { + // arrange + IdeContext context = new IdeTestContext(); + String legacyProperties = "(jboss-eap:7.1.4.GA:all)"; + // act + CustomToolsJson customToolsJson = CustomToolsJsonMapper.parseCustomToolsFromLegacyConfig(legacyProperties, context); + // assert + assertThat(customToolsJson).isNull(); + } + + /** + * Tests the convert of a {@link CustomToolsJson} with different os and arch agnostic settings to a proper {@link CustomToolMetadata}. + */ + @Test + public void testProperConvertFromCustomToolsJsonToCustomToolMetaData() { + + // arrange + AbstractIdeTestContext context = new IdeSlf4jContext(Path.of("")); + context.setSystemInfo(SystemInfoMock.LINUX_X64); + String name = "jboss-eap"; + String version = "7.4.5.GA"; + String repositoryUrl = "https://host.domain.tld:8443/folder/repo/"; + String url = repositoryUrl + "jboss-eap/7.4.5.GA/jboss-eap-7.4.5.GA.tgz"; + OperatingSystem os = null; + SystemArchitecture arch = null; + + String name1 = "firefox"; + String version1 = "70.0.1"; + String repositoryUrl1 = "https://host.domain.tld:8443/folder/repo/"; + String checkOsArchUrl = repositoryUrl1 + "firefox/70.0.1/firefox-70.0.1-linux-x64.tgz"; + OperatingSystem os1 = OperatingSystem.LINUX; + SystemArchitecture arch1 = SystemArchitecture.X64; + + CustomToolJson customToolJson = new CustomToolJson(name, version, true, true, repositoryUrl); + CustomToolJson customToolJsonWithOs = new CustomToolJson(name1, version1, false, false, repositoryUrl1); + List customToolJsonList = new ArrayList<>(); + customToolJsonList.add(customToolJson); + customToolJsonList.add(customToolJsonWithOs); + CustomToolsJson customToolsJson = new CustomToolsJson(repositoryUrl, customToolJsonList); + // act + List customToolMetadata = CustomToolsJsonMapper.convert(customToolsJson, context); + // assert + assertThat(customToolMetadata.get(0).getTool()).isEqualTo(name); + assertThat(customToolMetadata.get(0).getVersion()).isEqualTo(VersionIdentifier.of(version)); + assertThat(customToolMetadata.get(0).getOs()).isEqualTo(os); + assertThat(customToolMetadata.get(0).getArch()).isEqualTo(arch); + assertThat(customToolMetadata.get(0).getUrl()).isEqualTo(url); + assertThat(customToolMetadata.get(0).getChecksum()).isNull(); + assertThat(customToolMetadata.get(0).getRepositoryUrl()).isEqualTo(repositoryUrl); + + assertThat(customToolMetadata.get(1).getTool()).isEqualTo(name1); + assertThat(customToolMetadata.get(1).getVersion()).isEqualTo(VersionIdentifier.of(version1)); + assertThat(customToolMetadata.get(1).getOs()).isEqualTo(os1); + assertThat(customToolMetadata.get(1).getArch()).isEqualTo(arch1); + // assert that url was properly created + assertThat(customToolMetadata.get(1).getUrl()).isEqualTo(checkOsArchUrl); + assertThat(customToolMetadata.get(1).getChecksum()).isNull(); + assertThat(customToolMetadata.get(1).getRepositoryUrl()).isEqualTo(repositoryUrl1); + } +} diff --git a/cli/src/test/resources/customtools/ide-custom-tools.json b/cli/src/test/resources/customtools/ide-custom-tools.json new file mode 100644 index 000000000..b2ad041df --- /dev/null +++ b/cli/src/test/resources/customtools/ide-custom-tools.json @@ -0,0 +1,18 @@ +{ + "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, + "url": "https://some-file-server.company.com/projects/my-project2" + } + ] +} diff --git a/cli/src/test/resources/ide-projects/upgrade-settings/project/conf/devon.properties b/cli/src/test/resources/ide-projects/upgrade-settings/project/conf/devon.properties new file mode 100644 index 000000000..ad6cc14a6 --- /dev/null +++ b/cli/src/test/resources/ide-projects/upgrade-settings/project/conf/devon.properties @@ -0,0 +1,7 @@ +#******************************************************************************** +# This file contains project specific environment variables defined by the user +#******************************************************************************** +DEVON_IDE_HOME=test +DEVON_HOME_DIR=test +DEVON_IDE_CUSTOM_TOOLS=(jboss-eap:7.1.4.GA:all:https://host.tld/projects/my-project firefox:70.0.1:) +JAVA_VERSION=test diff --git a/cli/src/test/resources/ide-projects/upgrade-settings/project/conf/ide.properties b/cli/src/test/resources/ide-projects/upgrade-settings/project/conf/ide.properties new file mode 100644 index 000000000..1b66dd75c --- /dev/null +++ b/cli/src/test/resources/ide-projects/upgrade-settings/project/conf/ide.properties @@ -0,0 +1,5 @@ +#******************************************************************************** +# This file contains project specific environment variables defined by the user +#******************************************************************************** + +MVN_VERSION=test diff --git a/cli/src/test/resources/ide-projects/upgrade-settings/project/home/devon.properties b/cli/src/test/resources/ide-projects/upgrade-settings/project/home/devon.properties new file mode 100644 index 000000000..c532faa1a --- /dev/null +++ b/cli/src/test/resources/ide-projects/upgrade-settings/project/home/devon.properties @@ -0,0 +1,13 @@ +#******************************************************************************** +# This file contains the global configuration from the user HOME directory. +#******************************************************************************** +DOCKER_EDITION=docker +FOO=foo-${BAR} +TEST_ARGS1=${TEST_ARGS1} user1 +TEST_ARGS2=${TEST_ARGS2} user2 +TEST_ARGS3=${TEST_ARGS3} user3 +TEST_ARGS7=user7 +TEST_ARGS10=user10 +TEST_ARGSb=userb +TEST_ARGSc=${TEST_ARGS1} userc +TEST_ARGSd=${TEST_ARGS1} userd diff --git a/cli/src/test/resources/ide-projects/upgrade-settings/project/settings/devon.properties b/cli/src/test/resources/ide-projects/upgrade-settings/project/settings/devon.properties new file mode 100644 index 000000000..db57079d1 --- /dev/null +++ b/cli/src/test/resources/ide-projects/upgrade-settings/project/settings/devon.properties @@ -0,0 +1,9 @@ +#******************************************************************************** +# This file contains project specific environment variables +#******************************************************************************** +JAVA_VERSION=17* +MVN_VERSION=3.9.0 +ECLIPSE_VERSION=2023-03 +INTELLIJ_EDITION_TYPE=U +DEVON_IDE_TOOLS=(mvn eclipse) +DEVON_IDE_CUSTOM_TOOLS=(jboss-eap:7.1.4.GA:all:https://host.tld/projects/my-project firefox:70.0.1:) diff --git a/cli/src/test/resources/ide-projects/upgrade-settings/project/settings/devon/conf/.m2/settings.xml b/cli/src/test/resources/ide-projects/upgrade-settings/project/settings/devon/conf/.m2/settings.xml new file mode 100644 index 000000000..8f10a5555 --- /dev/null +++ b/cli/src/test/resources/ide-projects/upgrade-settings/project/settings/devon/conf/.m2/settings.xml @@ -0,0 +1,87 @@ + + + + ${env.M2_REPO} + + + + + repository + ${env.USERNAME} + $[mavenRepoPassword] + + + + + + + + devonfw-ide + + true + + + + + + + + devonfw-snapshots + + + false + + + + devonfw-snapshots + devonfw SNAPSHOT releases + https://s01.oss.sonatype.org/content/repositories/snapshots/ + + false + never + fail + + + true + never + fail + + + + + + + + + diff --git a/cli/src/test/resources/ide-projects/upgrade-settings/project/settings/devon/conf/devon.properties b/cli/src/test/resources/ide-projects/upgrade-settings/project/settings/devon/conf/devon.properties new file mode 100644 index 000000000..84fbccc23 --- /dev/null +++ b/cli/src/test/resources/ide-projects/upgrade-settings/project/settings/devon/conf/devon.properties @@ -0,0 +1,4 @@ +#******************************************************************************** +# This file contains project specific environment variables defined by the user +#******************************************************************************** +M2_REPO=~/.m2/repository diff --git a/cli/src/test/resources/ide-projects/upgrade-settings/project/settings/intellij/workspace/TestXml.xml b/cli/src/test/resources/ide-projects/upgrade-settings/project/settings/intellij/workspace/TestXml.xml new file mode 100644 index 000000000..7e5ff4176 --- /dev/null +++ b/cli/src/test/resources/ide-projects/upgrade-settings/project/settings/intellij/workspace/TestXml.xml @@ -0,0 +1,17 @@ + + + + + + + + + diff --git a/cli/src/test/resources/ide-projects/upgrade-settings/project/settings/projects/IDEasy.properties b/cli/src/test/resources/ide-projects/upgrade-settings/project/settings/projects/IDEasy.properties new file mode 100644 index 000000000..ca321ca01 --- /dev/null +++ b/cli/src/test/resources/ide-projects/upgrade-settings/project/settings/projects/IDEasy.properties @@ -0,0 +1,5 @@ +path=IDEasy +git_url=https://github.com/devonfw/IDEasy.git +git_branch=main +eclipse=import +active=false diff --git a/cli/src/test/resources/ide-projects/upgrade-settings/project/settings/workspace/testVariableSyntax.txt b/cli/src/test/resources/ide-projects/upgrade-settings/project/settings/workspace/testVariableSyntax.txt new file mode 100644 index 000000000..1b733097a --- /dev/null +++ b/cli/src/test/resources/ide-projects/upgrade-settings/project/settings/workspace/testVariableSyntax.txt @@ -0,0 +1,6 @@ +${DEVON_IDE_HOME} +This is a test text,this is a test text,this is a test text,this is a test text, +this is a test text, +this is a test text,${MAVEN_VERSION}this is a test text,this is a test text,${SETTINGS_PATH} +maven_settings=${DEVON_IDE_HOME}\conf\.m2\settings.xml +this is a test text,this is a test text,this is a test text,this is a test text, diff --git a/cli/src/test/resources/ide-projects/upgrade-settings/project/workspaces/main/devon.properties b/cli/src/test/resources/ide-projects/upgrade-settings/project/workspaces/main/devon.properties new file mode 100644 index 000000000..8fc454ddb --- /dev/null +++ b/cli/src/test/resources/ide-projects/upgrade-settings/project/workspaces/main/devon.properties @@ -0,0 +1,18 @@ +#******************************************************************************** +# This file contains project specific environment variables +#******************************************************************************** +JAVA_VERSION=17* +MVN_VERSION=3.9.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/documentation/migration-from-devonfw.adoc b/documentation/migration-from-devonfw.adoc new file mode 100644 index 000000000..450ec8c01 --- /dev/null +++ b/documentation/migration-from-devonfw.adoc @@ -0,0 +1,16 @@ += Migration to IDEasy + +If you used devonfw-ide for your projects so far, and you want to switch to IDEasy, you should follow these steps in order to do it properly: + +Step 1: Follow the https://github.com/devonfw/IDEasy/blob/main/documentation/setup.adoc[setup] guide to properly install IDEasy + +Step 2: Get rid of all legacy by calling the following command: + +[source] +---- +upgrade-settings +---- + +Step 3: After running the command, you might need to update your xml files for our merger. +Please consider reading our documentation for that topic: +https://github.com/devonfw/IDEasy/blob/main/documentation/configurator.adoc