Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

#759: upgrade settings commandlet #820

Open
wants to merge 89 commits into
base: main
Choose a base branch
from

Conversation

leonrohne27
Copy link
Contributor

Fixes: #759

Implements:

  • all devon.properties are renamed to ide.properties. In case both files exists in the same directory, devon.properties gets merged into ide.properties. ide.properties has priority here.
  • duplicated variables in the same ide.properties file are being removed
  • If variable DEVON_IDE_CUSTOM_TOOLS exists, it is converted into a custom-tools.json file in settings
  • if settings/devon folder exists, it is renamed to settings/templates, if settings/projects folder exists, it is renamed to settings/repositories
  • recursively traverses all files in settings/workspace and settings/*/workspace and replacse all known IdeVariables from CURLY into ANGLED syntax and legacy variable names are replaced with their new names.
  • for all .xml files that do not contain the XML namespace of our merger, we print a warning with the filepath saying that this file is most-likely outdated and print a link to a documentation page about this settings migration feature
  • documentation page with small instructions for those who migrate from devonfw-ide to IDEasy
  • if a replacement-patterns.properties is found in a workspace folder a warning will be logged if the file is not empty and the file will be deleted.
  • if settings/ide.properties does not contain IDE_VARIABLE_SYNTAX_LEGACY_SUPPORT_ENABLED=false it will be added.

Kian and others added 30 commits October 2, 2024 16:17
Added the command cd to the shell commandlet that allows the user to switch the directory inside the ide shell
added current working directory path to prompt name
improved existing directory check
adjusted exit codes
removed whitespace from ide prompt
Changed variable name from file_path to filePath
Changed checkForXMLNameSpace() to checkForXmlNamespace()
Comment on lines +260 to +284
try {
List<String> readLines = Files.readAllLines(filePath);
String[] split;
for (String line : readLines) {
if (!line.contains("#") && !line.isEmpty()) {
if (line.contains("DEVON_IDE_CUSTOM_TOOLS")) {
createCustomToolsJson(line);
continue;
}
if (line.contains("DEVON_")) {
line = line.replace("DEVON_", "");
}
split = line.split("[ =]");
if (split.length == 3) {
devonProperties.put(split[1], new String[] { split[0], split[2] });
}
if (split.length == 2) {
devonProperties.put(split[0], split[1]);
}
}
}

} catch (IOException e) {
throw new RuntimeException(e);
}
Copy link
Member

Choose a reason for hiding this comment

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

You are duplicating the code to read a devon.properties and a ide.properties file.

try (BufferedReader reader = Files.newBufferedReader(this.propertiesFilePath)) {
String line;
do {
line = reader.readLine();
if (line != null) {
VariableLine variableLine = VariableLine.of(line, this.context, getSource());
String name = variableLine.getName();
if (name != null) {
VariableLine migratedVariableLine = migrateLine(variableLine, false);
if (migratedVariableLine == null) {
this.context.warning("Illegal variable definition: {}", variableLine);
continue;
}
String migratedName = migratedVariableLine.getName();
String migratedValue = migratedVariableLine.getValue();
boolean legacyVariable = IdeVariables.isLegacyVariable(name);

We should use our existing infrastructure to do that.

Also your implementation contains some problems. E.g. # can also be used inside a value (e.g. to separate branch of a git-url) and I guess you are trying to ignore comments here. Using String "fiddling" with methods such as contains is also dangerous since it will also match in unexpected cases. Surely not a very relevant scenario but rather an odd edge-case but in case a user has DISABLED_DEVON_IDE_CUSTOM_TOOLS=(...) in his config, this will cause undesired effects (and I have seen users who were not aware that you can outcomment a line by adding # at the beginning but were simply adding DISABLED_ as prefix). As you can see in the linked code we already have better infrastructure to parse a line and separate variable name and value. This should be reused.

Copy link
Member

Choose a reason for hiding this comment

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

After re-reading all the code of this method I understand this was an important blind spot.
You tried to reimplement a lot of things that have already been solved in our code but could not find this.
Therefore, you ran into all these challenges how to deal with comments, how to split into property name and value, etc.

Comment on lines +350 to +354
if (set.getValue() instanceof String) {
Files.write(filePath, ("\n" + set.getKey().toString() + "=" + set.getValue().toString()).getBytes(), StandardOpenOption.APPEND);
}
if (set.getValue() instanceof String[]) {
String[] values = (String[]) set.getValue();
Copy link
Member

Choose a reason for hiding this comment

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

Added BufferedReader which now only reads out the first 3 lines of all .xml files. Also, the BufferedReader now searches for a  more consistent String
@hohwille hohwille added this to the release:2024.12.001 milestone Nov 29, 2024
Comment on lines 123 to 124
content = content.replace("{", "[");
content = content.replace("}", "]");
Copy link
Member

Choose a reason for hiding this comment

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

this is wrong. It will destroy all JSON files.
We need to use VariableSyntax, like here:

Matcher matcher = syntax.getPattern().matcher(value);
if (!matcher.find()) {
return value;
}
StringBuilder sb = new StringBuilder(value.length() + EXTRA_CAPACITY);
do {
String variableName = syntax.getVariable(matcher);

jan-vcapgemini and others added 5 commits December 16, 2024 11:04
# Conflicts:
#	CHANGELOG.adoc
#	cli/src/main/resources/nls/Help.properties
#	cli/src/main/resources/nls/Help_de.properties
added more tests
added new CustomToolsJson class
moved constants to IdeContext
changed return type of readCustomToolsFromJson to CustomToolsJson
Copy link
Member

@hohwille hohwille left a comment

Choose a reason for hiding this comment

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

@jan-vcapgemini thanks for your nice improvements 👍

String FILE_CUSTOM_TOOLS = "ide-custom-tools.json";

String DEVON_IDE_CUSTOM_TOOLS = "DEVON_IDE_CUSTOM_TOOLS";
String IDE_CUSTOM_TOOLS = "IDE_CUSTOM_TOOLS";
Copy link
Member

Choose a reason for hiding this comment

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

Please remove pointless constant.

Suggested change
String IDE_CUSTOM_TOOLS = "IDE_CUSTOM_TOOLS";

/** The filename of the configuration file in the settings for this {@link CustomToolRepository}. */
String FILE_CUSTOM_TOOLS = "ide-custom-tools.json";

String DEVON_IDE_CUSTOM_TOOLS = "DEVON_IDE_CUSTOM_TOOLS";
Copy link
Member

Choose a reason for hiding this comment

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

Create in IdeVariables or give a name that indicates that this is a variable name?
Please also add JavaDoc.

Comment on lines 174 to 205
public static CustomToolsJson readCustomToolsFromJson(IdeContext context, Path customToolsJsonPath) {
try (InputStream in = Files.newInputStream(customToolsJsonPath);
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"));
List<CustomTool> customTools = new ArrayList<>();

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 + "'!");
}
} catch (Exception e) {
throw new IllegalStateException("Failed to read JSON from " + customToolsJson, e);
// TODO
String checksum = null;
CustomTool customTool = new CustomTool(name, VersionIdentifier.of(version), osAgnostic, archAgnostic, url,
checksum, context.getSystemInfo());
customTools.add(customTool);
}
return new CustomToolsJson("tools", defaultUrl, customTools);
} catch (Exception e) {
throw new IllegalStateException("Failed to read JSON from " + customToolsJsonPath, e);
}
return new CustomToolRepositoryImpl(context, tools);
}
Copy link
Member

Choose a reason for hiding this comment

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

This method and logic belong to CustomToolsJson class. Furhter, we can use Jackson for the mapping instead of writing the mapping code manually. We can add an internal method for post-processing that will set the url in all CustomTool instances to the parent one from CustomToolsJson in case it is null. This method can then be called both from the JSON parsing as well as from the String parsing method.

@@ -0,0 +1,18 @@
{
Copy link
Member

Choose a reason for hiding this comment

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

Please rename this file to ide-custom-tools.json to keep our own contract.

context.setSystemInfo(systemInfo);
Path testPath = Path.of("src/test/resources/customtools");
// act
CustomToolsJson customToolsJson = CustomToolRepositoryImpl.readCustomToolsFromJson(context, testPath.resolve("custom-tools.json"));
Copy link
Member

Choose a reason for hiding this comment

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

Great that you added new test methods to increase coverage. 👍
As I already commented on the main code please follow SoC and put the code into the classes responsible for what is done by the according method.
Then do the same for JUnits:
If you test a class from src/main/java/my/package/MyClass.java then the unit tests shall be in src/test/java/my/package/MyClassTest.java.
Hence you should create CustomToolsJsonTest instead of extending this test.

public void testReadCustomToolsFromLegacyConfig() {
// arrange
IdeContext context = new IdeTestContext();
String legacyProperties = "DEVON_IDE_CUSTOM_TOOLS=(jboss-eap:7.1.4.GA:all:https://host.tld/projects/my-project firefox:70.0.1:all:https://host.tld/projects/my-project2)";
Copy link
Member

Choose a reason for hiding this comment

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

I already left review comments to Kian and Leon that the context of that method is kind of odd and also we need to reuse our existing infrastructure to parse ide.properties and devon.properties files.
Then we can just get the value of a variable and only need to pass that value to this method instead of fiddling the value from the variable inside a class that is not responsible for that and code gets more redundant.
BTW: We also have existing code to parse variable array syntax into a regular Java List. This is currently in VariableDefinitionStringList e.g. used by IdeVariables.IDE_TOOLS to parse things like IDE_TOOLS=(java mvn node npm) (see https://github.com/devonfw/IDEasy/blob/main/documentation/variables.adoc).
We could even extract that parsing code to VariableLine for easier reuse here.

From the test perspective I would then only pass the value and not the entire variable declaration line.

@@ -0,0 +1 @@
c2de7dfbd9f8faaa21b4cdd8518f826dd558c9ab24a0616b3ed28437a674a97b
Copy link
Member

Choose a reason for hiding this comment

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

since when do we add checksums for test projects? Are they actually used? I can not see how this could be used since the java installation in our tests is not yet downloaded as archive but only copied as directory tree.
If these *.sha256 files are all not needed or used, please remove them.

jan-vcapgemini and others added 3 commits December 20, 2024 12:00
replaced regex parsing with indexOf
added JsonIgnores and JsonProperties to CustomTool
converted version from versionIdentifier to String
added Tool record to CustomToolsJson
moved fromString into VariableLine
replaced parenthesis parsing with VariableLine.fromString
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
commandlet enhancement New feature or request
Projects
Status: Team Review
Development

Successfully merging this pull request may close these issues.

Create UpgradeSettingsCommandlet
5 participants