Last active
November 13, 2023 09:37
-
-
Save fintanmm/0fec175b2d8ae4a04f3052ecfcb6def0 to your computer and use it in GitHub Desktop.
Jbang script to clean up sdk's installed by sdkman.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
///usr/bin/env jbang "$0" "$@" ; exit $? | |
/** | |
* <h1>sdk-clean</h1> | |
*/ | |
//DEPS info.picocli:picocli:4.5.0 | |
//DEPS info.picocli:picocli-codegen:4.5.0 | |
//DEPS ch.qos.reload4j:reload4j:1.2.19 | |
import org.apache.log4j.BasicConfigurator; | |
import org.apache.log4j.Logger; | |
import picocli.CommandLine; | |
import picocli.CommandLine.Command; | |
import java.io.BufferedReader; | |
import java.io.File; | |
import java.io.IOException; | |
import java.nio.file.Files; | |
import java.util.ArrayList; | |
import java.util.List; | |
import java.util.Optional; | |
import java.util.regex.Matcher; | |
import java.util.regex.Pattern; | |
import java.util.stream.Stream; | |
@Command(name = "sdk-clean", description = "Cleans up unused SDKs from the SDKMAN installation", mixinStandardHelpOptions = true, version = "sdk-clean 1.0") | |
public class SdkClean implements Runnable { | |
private static final Logger logger = Logger.getLogger(SdkClean.class.getName()); | |
private static final String SDKMANCOMMAND = "source %s/bin/sdkman-init.sh && sdk %s"; | |
private static String sdkmanHome; | |
@CommandLine.Option(names = {"-n", "--keep-last-n-sdks"}, description = "Keep the last n SDKs (default: 3)") | |
private static int keepLastNSdks = 3; | |
@CommandLine.Option(names = {"-sdk"}, description = "The sdk to be removed.", required = true) | |
private static String installedSdk = "quarkus"; | |
public static void main(String[] args) { | |
BasicConfigurator.configure(); | |
// Get the sdkman installation directory | |
sdkmanHome = Optional.ofNullable(System.getenv("SDKMAN_DIR")).orElse("/usr/local/sdkman"); | |
if (isSdkmanInstalled()) { | |
new CommandLine(new SdkClean()).execute(args); | |
} else { | |
logger.debug("sdkman is not installed"); | |
System.exit(0); | |
} | |
} | |
/** | |
* Tests if sdkman is installed. | |
* @return boolean True if sdkman is installed, false otherwise. | |
*/ | |
public static boolean isSdkmanInstalled() { | |
try { | |
Process process = new ProcessBuilder("bash", "-c", SDKMANCOMMAND.formatted(sdkmanHome, "version")).start(); | |
int exitCode = process.waitFor(); | |
return exitCode == process.waitFor(); | |
} catch (IOException | InterruptedException e) { | |
logger.debug("sdk command not found", e); | |
return false; | |
} | |
} | |
@Override | |
public void run() { | |
try { | |
List<String> listOfInstalledSdks = parseInstalledSdk(); | |
listOfInstalledSdks.stream().sorted().forEach(version -> { | |
if (!isInUse()) { | |
try { | |
// if the SDK is not in the list of the last three SDKs, uninstall it | |
if (!keepLastNSdks(listOfInstalledSdks).contains(version)) { | |
uninstall(version); | |
} | |
} catch (IOException | InterruptedException e) { | |
logger.debug("Error uninstalling SDK", e); | |
Thread.currentThread().interrupt(); | |
} | |
} | |
}); | |
} catch (IOException | InterruptedException e) { | |
Thread.currentThread().interrupt(); | |
throw new RuntimeException(e); | |
} | |
} | |
/** | |
* Checks if an SDK is in use by any Java processes. | |
* @return True if the SDK is in use, false otherwise. | |
*/ | |
private static boolean isInUse() { | |
// Check if the SDK is in use by any Java processes | |
boolean isInUse = false; | |
Stream<ProcessHandle> liveProcesses = ProcessHandle.allProcesses(); | |
Optional<ProcessHandle> process = liveProcesses.filter(p -> null != p.info()) | |
.filter(p -> p.info().command().isPresent()) | |
.filter(p -> p.info().command().get().contains(installedSdk)) | |
.findFirst(); | |
if (process.isPresent()) { | |
isInUse = true; | |
} | |
return isInUse; | |
} | |
/** | |
* Keeps the last three SDKs. | |
* | |
* @param installedSdks The list of installed SDKs. | |
* @return List<String> The last three SDKs. | |
*/ | |
public static List<String> keepLastNSdks(List<String> installedSdks) { | |
return installedSdks.stream() | |
.sorted() | |
.skip(Math.max(0, installedSdks.size() - Optional.of(keepLastNSdks).orElse(3))) | |
.toList(); | |
} | |
/** | |
* Uninstalls an SDK. | |
* @param version The version to uninstall. | |
*/ | |
private static void uninstall(String version) throws IOException, InterruptedException { | |
if (!version.isEmpty() && !version.contains("-")){ | |
logger.debug("Uninstalling %s %s".formatted(installedSdk, version)); | |
logger.debug(SDKMANCOMMAND.formatted(sdkmanHome, "uninstall %s %s".formatted(installedSdk, version))); | |
new ProcessBuilder("bash", "-c", SDKMANCOMMAND.formatted(sdkmanHome, "uninstall %s %s".formatted(installedSdk, version))).start().waitFor(); | |
} | |
} | |
/** | |
* Parses the output of the sdk list command. | |
* | |
* @return List<String> The list of installed SDKs. | |
* @throws IOException If an I/O error occurs. | |
* @throws InterruptedException If the process is interrupted. | |
*/ | |
private List<String> parseInstalledSdk() throws IOException, InterruptedException { | |
File sdkList = createTempFile(); | |
List<String> versions = new ArrayList<>(); | |
try (BufferedReader reader = Files.newBufferedReader(sdkList.toPath())) { | |
boolean startParsing = false; | |
String line; | |
while ((line = reader.readLine()) != null) { | |
if (line.contains("Available")) { | |
startParsing = true; | |
continue; | |
} | |
if (startParsing && !line.startsWith("=")) { | |
versions.addAll(extract(line)); | |
} | |
} | |
} | |
Files.delete(sdkList.toPath()); | |
return versions; | |
} | |
/** | |
* Creates a temporary file containing the output of the sdk list command. | |
* @return File The temporary file. | |
*/ | |
private File createTempFile() throws IOException, InterruptedException { | |
File sdkList = File.createTempFile("sdk-list", ".txt"); | |
String command = SDKMANCOMMAND.formatted(sdkmanHome, " list %s > %s".formatted(installedSdk, sdkList.getAbsolutePath())); | |
new ProcessBuilder("bash", "-c", command).start().waitFor(); | |
return sdkList; | |
} | |
/** | |
* Extracts the versions from the line. | |
* @param version The line. | |
* @return List<String> The list of versions. | |
*/ | |
private List<String> extract(String version) { | |
Pattern pattern = Pattern.compile("\\*\\s*(\\S+)"); | |
Matcher matcher = pattern.matcher(version); | |
if (matcher.find()) { | |
return extract(matcher); | |
} else { | |
return extractTokens(version); | |
} | |
} | |
/** | |
* Extracts the matches from the matcher. | |
* @param matcher The matcher. | |
* @return List<String> The list of matches. | |
*/ | |
private List<String> extract(Matcher matcher) { | |
return Stream.generate(matcher::group) | |
.limit(matcher.groupCount()) | |
.map(match -> match.replaceFirst("\\*", "").trim()) | |
.toList(); | |
} | |
/** | |
* Extracts the tokens from the tokens. | |
* @param tokens The tokens. | |
* @return List<String> The list of tokens. | |
*/ | |
private List<String> extractTokens(String tokens) { | |
String[] t = tokens.split("\\s+"); | |
return Stream.of(t) | |
.filter(token -> token.startsWith(">") || token.startsWith("*")) | |
.map(token -> token.replaceFirst("[>*]", "").trim()) | |
.toList(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment