#1953: Implement base functionality of Cleanup Commandlet#1957
#1953: Implement base functionality of Cleanup Commandlet#1957areinicke wants to merge 17 commits into
Conversation
Coverage Report for CI Build 26655164025Coverage decreased (-0.03%) to 71.061%Details
Uncovered ChangesNo uncovered changes found. Coverage Regressions10 previously-covered lines in 3 files lost coverage.
Coverage Stats💛 - Coveralls |
Caylipp
left a comment
There was a problem hiding this comment.
I tested the cleanup commandlet locally.
For my manual test, I installed additional versions of gh and java and then ran ide cleanup. Only the extra unused versions were deleted, while the versions still referenced by a project were kept. So the basic deletion functionality worked for me.
I noticed an issue in ide shell where duplicate entries appeared on a second run. However, this issue has already been fixed.
Overall, the implementation looks good to me and my manual test worked. Very good!
hohwille
left a comment
There was a problem hiding this comment.
@areinicke thanks for your PR. Great that you divided the very large story and created a very reasonable starting point as first implementation that totally makes sense to me.
Also the logging as preview what will be deleted and the user confirmation looks good to me. Great job 👍
I left quite some review comments due to some conventions and design issues.
I hope you consider it as appreciation to learn some things to further advance your Java coding style and not as nasty picky pointless extra rework ;)
|
|
||
| super(context); | ||
| addKeyword(getName()); | ||
| this.forceDelete = add(new FlagProperty("--fd")); // Force-Delete flag: Skips confirmation prompts if provided |
There was a problem hiding this comment.
POSIX standard defines options in two forms:
- short option have only a single character
-f,-dand are typically used by experts who know the CLI very well and are lazy and want to save typing work. These options can also be combined:-fdis the same as-f -d. - long options start with two dashes and should be long and self explanatory. This is used e.g. in scripts to make it easier to understand for the reader what is going on here so instead of
-fdwe can also write--force --debugand newbie users will be happy since they can way easier understand what is going on.
| this.forceDelete = add(new FlagProperty("--fd")); // Force-Delete flag: Skips confirmation prompts if provided | |
| this.forceDelete = add(new FlagProperty("--force-delete")); // Skips confirmation prompts if provided |
Please note that obviously you also need to update the help texts again when applying this change.
There was a problem hiding this comment.
BTW: are you aware that you can already do the same without adding this property?
ide -f cleanup or ide --force cleanup will run the cleanup in force mode and the askToContinue method will be automatically confirmed then... So if you want to follow KISS, you could even omit this property.
| /** | ||
| * Class which represents individual IDE tools. Contains multiple parameter such as the name of the tool, the path where it is installed, | ||
| * a list of projects which use this tool and a boolean whether the tool is marked for deletion or not. | ||
| * Furthermore, it contains a list of editions of the edition (e.g. for intellij, we could have the editions "intellij" or "ultimate"). | ||
| */ | ||
| private static class IdeToolEditionVersion { | ||
| String versionName; | ||
| private final Path path; | ||
| private final List<String> usedBy = new java.util.ArrayList<>(); | ||
| private boolean delete = false; | ||
|
|
||
| IdeToolEditionVersion(String versionName, Path path) { | ||
| this.versionName = versionName; | ||
| this.path = path; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Class which represents individual editions of an IDE tool. Contains multiple parameter such as the name of the edition, the path where it is installed, | ||
| * a list of projects which use this edition and a boolean whether the edition is marked for deletion or not. | ||
| * Furthermore, it contains a list of versions of the edition (e.g. for intellij ultimate edition, we could have version 2022.3 and 2023.1 installed). | ||
| */ | ||
| private static class IdeToolEdition { | ||
| String editionName; | ||
| private final Path path; | ||
| private final List<String> usedBy = new java.util.ArrayList<>(); | ||
| private boolean delete = false; | ||
| private final List<IdeToolEditionVersion> versions = new java.util.ArrayList<>(); | ||
|
|
||
| IdeToolEdition(String editionName, Path path) { | ||
| this.editionName = editionName; | ||
| this.path = path; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Class which represents individual versions of an edition of an IDE tool. Contains multiple parameter such as the name of the version, the path where it is installed, | ||
| * a list of projects which use this version and a boolean whether the version is marked for deletion or not. | ||
| */ | ||
| private static class IdeTool { | ||
| String toolName; | ||
| private final Path path; | ||
| private final List<String> usedBy = new java.util.ArrayList<>(); | ||
| private boolean delete = false; | ||
| private final List<IdeToolEdition> editions = new java.util.ArrayList<>(); | ||
|
|
||
| IdeTool(String toolName, Path path) { | ||
| this.toolName = toolName; | ||
| this.path = path; | ||
| } | ||
| } |
There was a problem hiding this comment.
I would recommend to move this to top-level classes (allowing them to become public API that we can later reuse in the GUI so the user can see a Popup with a tree and tick or untick checkboxes.
Anyway if you keep inner classes follow the general conventions for Java types:
public [class|interface|record|enum|@interface] <name> ... {
// constants
// fields / member variables
// constructors
// methods while generic Object methods like equals/hashCode/toString usually go to the end
// inner types
}
So move them to the end what makes the code easier to read for typical Java coders used to that convention.
|
|
||
| // Identify and remove unused tools. | ||
| Step step = context.newStep("Identify and remove unused software"); | ||
| step.run(() -> {discoverAndDeleteUnusedSoftware();}); |
There was a problem hiding this comment.
| step.run(() -> {discoverAndDeleteUnusedSoftware();}); | |
| step.run(this::discoverAndDeleteUnusedSoftware); |
| */ | ||
| private void discoverAndDeleteUnusedSoftware() { | ||
| // Iterate over software in $IDE_ROOT/_ide/software folder and save installed software to a list | ||
| discoverInstalledSoftware(this.context.getIdeRoot().resolve("_ide/software/default")); |
There was a problem hiding this comment.
Can be improved by reusing more dedicated method and constants (may require import statement to apply suggestion):
| discoverInstalledSoftware(this.context.getIdeRoot().resolve("_ide/software/default")); | |
| discoverInstalledSoftware(this.context.getSoftwareRepositoryPath().resolve(ToolRepository.ID_DEFAULT)); |
| discoverUsedSoftware(ideasyProject.resolve("software"), ideasyProject.getFileName().toString()); | ||
| discoverUsedSoftware(ideasyProject.resolve("software/extra"), ideasyProject.getFileName().toString()); |
There was a problem hiding this comment.
Reuse constants (this also allows to find usages in the workspace via IDE to see where in the code we are accessing this folder):
| discoverUsedSoftware(ideasyProject.resolve("software"), ideasyProject.getFileName().toString()); | |
| discoverUsedSoftware(ideasyProject.resolve("software/extra"), ideasyProject.getFileName().toString()); | |
| Path ideasyProjectSoftware = ideasyProject.resolve(IdeContext.FOLDER_SOFTWARE); | |
| discoverUsedSoftware(ideasyProjectSoftware, ideasyProject.getFileName().toString()); | |
| discoverUsedSoftware(ideasyProjectSoftware.resolve(IdeContext.FOLDER_EXTRA), ideasyProject.getFileName().toString()); |
| private static class IdeTool { | ||
| String toolName; | ||
| private final Path path; | ||
| private final List<String> usedBy = new java.util.ArrayList<>(); |
There was a problem hiding this comment.
I am slightly confused why we have this usedBy in all 3 inner classes.
If we really want to do that, we could even use OOP and derive the 3 classes from an abstract class with a getUsedBy() and a nice centralised JavaDoc that is then inherited and reused by all 3 classes.
But IMHO we only need to track what tool+edition+version combination is used.
If all unused ones are deleted and we end up with en empty edition folder this does not really waste disc-space and we can still check that after deletion we also delete the parent folder if it became empty (and if we delete the edition folder we can also delete the version folder if that is now also empty).
There was a problem hiding this comment.
BTW: Generic types like String or Number always need explanation. So if you see List<String> you do not know what this is unless you have further JavaDoc or other hints.
If you instead see List<IdeProject> and IdeProject is a class or record with JavaDoc then things immediately get clear. Such IdeProject type could wrap the project name as String but also maybe contain the Path to the project and later could even have methods like getWorkspaces() or whatever - see what @laim2003 is currently implementing for the GUI.
| // Check if directory contains a software version file. If so, read version and add to list of used software. | ||
| if (Files.exists(current_folder.resolve(IdeContext.FILE_SOFTWARE_VERSION)) | ||
| || Files.exists(current_folder.resolve(IdeContext.FILE_LEGACY_SOFTWARE_VERSION))) { | ||
| if (!current_folder.startsWith(this.context.getIdeRoot().resolve("_ide/software/default"))) { |
| * @param software_folder The software folder of the IDEasy project to scan for used software | ||
| * @param project_name The name of the project we are currently scanning | ||
| */ | ||
| private void discoverUsedSoftware(Path software_folder, String project_name) { |
There was a problem hiding this comment.
please get used to camlCase for all Java variables (except for constants in UPPER_CASE).
| String tool_name = current_folder.getParent().getParent().getFileName().toString(); | ||
| String tool_edition = current_folder.getParent().getFileName().toString(); | ||
| String tool_version = current_folder.getFileName().toString(); | ||
| // Check if software exists in global IdeTool list. If so, mark as used |
There was a problem hiding this comment.
nice to have but would be easier to read when extracting local variables.
So something like this:
| // Check if software exists in global IdeTool list. If so, mark as used | |
| // currentFolder has the structure «repo-path»/«tool»/«edition»/«version» | |
| String toolVersion = currentFolder.getFileName().toString(); | |
| Path toolEditionFolder = currentFolder.getParent(); | |
| String toolEdition = toolEditionFolder.getFileName().toString(); | |
| String toolName = toolEditionFolder.getParent().getFileName().toString(); |
There was a problem hiding this comment.
Somehow GitHub messed the line markers. I wanted to suggest this for 196-199.
|
|
||
| // assert | ||
| assertThat(context.getIdeRoot().resolve("_ide/software/default/az")).doesNotExist(); | ||
| assertThat(context).logAtSuccess().hasMessage("Unused tools have been deleted successfully."); |
There was a problem hiding this comment.
nice to have: you are just asserting that we logged this message and have deleted something/anything.
We should rather assert that something that is still used was not deleted and something that was unused has been deleted. Otherwise your implementation can be entirely broken but the test may still be green.
This PR fixes #1953
Implemented changes:
cleanupcommandlet. Users can runide cleanupto scan for unused tool installations and automatically remove them. A tool is considered as unused if no project links to the tool installation from its software folder at$IDE_ROOT/<project>/softwareor$IDE_ROOT/<project>/software/extra.--fd.ide helpdocumentation for the new commandletTesting instructions
Please add conscise, understandable instructions on how a reviewer can test/verify the functionality of your contribution here:
set-editionandset-versioncommandlet repeatedly.AND
ide helpandide help cleanupto verify that the help documentation works as expected.Checklist for this PR
Make sure everything is checked before merging this PR. For further info please also see
our DoD.
mvn clean testlocally all tests pass and build is successful#«issue-id»: «brief summary»(e.g.#921: fixed setup.bat). If no issue ID exists, title only.In Progressand assigned to you or there is no issue (might happen for very small PRs)with
internal