From 96648b9c8944fafd5e29284bda838975e7185568 Mon Sep 17 00:00:00 2001 From: Lorenz Leutgeb Date: Sat, 18 May 2024 18:26:28 +0200 Subject: [PATCH] Add toolchain detection for Nix package manager --- .../DaemonClientToolchainServices.java | 2 + .../src/docs/userguide/jvm/toolchains.adoc | 2 +- .../internal/NixInstallationSupplier.java | 143 ++++++++++++++++++ .../services/ToolchainsJvmServices.java | 2 + 4 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 platforms/jvm/jvm-services/src/main/java/org/gradle/jvm/toolchain/internal/NixInstallationSupplier.java diff --git a/platforms/core-runtime/launcher/src/main/java/org/gradle/launcher/daemon/toolchain/DaemonClientToolchainServices.java b/platforms/core-runtime/launcher/src/main/java/org/gradle/launcher/daemon/toolchain/DaemonClientToolchainServices.java index 4aef6d68f9f65..17b9c412ce135 100644 --- a/platforms/core-runtime/launcher/src/main/java/org/gradle/launcher/daemon/toolchain/DaemonClientToolchainServices.java +++ b/platforms/core-runtime/launcher/src/main/java/org/gradle/launcher/daemon/toolchain/DaemonClientToolchainServices.java @@ -33,6 +33,7 @@ import org.gradle.jvm.toolchain.internal.IntellijInstallationSupplier; import org.gradle.jvm.toolchain.internal.JabbaInstallationSupplier; import org.gradle.jvm.toolchain.internal.JdkCacheDirectory; +import org.gradle.jvm.toolchain.internal.NixInstallationSupplier; import org.gradle.jvm.toolchain.internal.LinuxInstallationSupplier; import org.gradle.jvm.toolchain.internal.OsXInstallationSupplier; import org.gradle.jvm.toolchain.internal.SdkmanInstallationSupplier; @@ -58,6 +59,7 @@ public void configure(ServiceRegistration registration) { registration.add(AsdfInstallationSupplier.class); registration.add(IntellijInstallationSupplier.class); registration.add(JabbaInstallationSupplier.class); + registration.add(NixInstallationSupplier.class); registration.add(SdkmanInstallationSupplier.class); // registration.add(MavenToolchainsInstallationSupplier.class); diff --git a/platforms/documentation/docs/src/docs/userguide/jvm/toolchains.adoc b/platforms/documentation/docs/src/docs/userguide/jvm/toolchains.adoc index d85f176ae75fb..42bd29176d419 100644 --- a/platforms/documentation/docs/src/docs/userguide/jvm/toolchains.adoc +++ b/platforms/documentation/docs/src/docs/userguide/jvm/toolchains.adoc @@ -186,7 +186,7 @@ The following is a list of common package managers, tools, and locations that ar JVM auto-detection knows how to work with: * Operation-system specific locations: Linux, macOS, Windows -* Package Managers: https://asdf-vm.com/#/[Asdf-vm], https://github.com/shyiko/jabba[Jabba], https://sdkman.io/[SDKMAN!] +* Package Managers: https://asdf-vm.com/#/[Asdf-vm], https://github.com/shyiko/jabba[Jabba], https://nixos.org/nix[Nix], https://sdkman.io/[SDKMAN!] * https://maven.apache.org/guides/mini/guide-using-toolchains.html[Maven Toolchain] specifications * https://www.jetbrains.com/help/idea/sdk.html#jdk-from-ide[IntelliJ IDEA] installations diff --git a/platforms/jvm/jvm-services/src/main/java/org/gradle/jvm/toolchain/internal/NixInstallationSupplier.java b/platforms/jvm/jvm-services/src/main/java/org/gradle/jvm/toolchain/internal/NixInstallationSupplier.java new file mode 100644 index 0000000000000..02e5128c8827c --- /dev/null +++ b/platforms/jvm/jvm-services/src/main/java/org/gradle/jvm/toolchain/internal/NixInstallationSupplier.java @@ -0,0 +1,143 @@ +/* + * Copyright 2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.jvm.toolchain.internal; + +import com.google.common.annotations.VisibleForTesting; +import org.gradle.api.logging.Logger; +import org.gradle.api.logging.Logging; +import org.gradle.api.provider.Provider; +import org.gradle.api.provider.ProviderFactory; +import org.gradle.internal.os.OperatingSystem; + +import java.io.File; +import java.io.FileFilter; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.function.Predicate; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import javax.inject.Inject; + +public class NixInstallationSupplier implements InstallationSupplier { + private static final Logger LOGGER = Logging.getLogger(NixInstallationSupplier.class); + + private static final String STORE_PATH_PROPERTY_NAME = "org.gradle.java.installations.nix.store"; + private static final String STORE_PATH_DEFAULT = "/nix/store"; + + // Paths will be of the form "{hash}-{suffix}", for example: + // + // /nix/store/6375rn8kiq9pn4pgdkdiqgvg8b0gdycy-openjdk-19.0.2+7 + // + // We know the the length of Nix store hashes (32), + // and the length of a hyphen (1), and only look at the suffix. + private static final int PREFIX_LENGTH = 33; + + private static final Predicate PATTERN = Pattern.compile("jdk", Pattern.CASE_INSENSITIVE).asPredicate(); + + private static final FileFilter FILTER = (final File file) -> { + final String name = file.getName(); + if (name.length() < PREFIX_LENGTH) { + return false; + } + return PATTERN.test(name.substring(PREFIX_LENGTH)); + }; + + @VisibleForTesting + final File store; + + @Inject + public NixInstallationSupplier(ProviderFactory providerFactory) { + final OperatingSystem os = OperatingSystem.current(); + + // As of 2024-05, Nix is only officially supported on Linux and MacOS. + if (!os.isLinux() && !os.isMacOsX()) { + LOGGER.trace("Operating system not supported."); + store = null; + return; + } + + File store = null; + + final Provider storeProvider = providerFactory.gradleProperty(STORE_PATH_PROPERTY_NAME); + if (storeProvider.isPresent()) { + try { + store = new File(storeProvider.get()); + if (!store.exists()) { + store = null; + LOGGER.warn("Initialization for store path '{}' failed. Path does not exist. Falling back to default '{}'.", store, STORE_PATH_DEFAULT); + } + } catch (Exception e) { + store = null; + final String message = "Initialization for store path '{}' failed. Falling back to default '{}'."; + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(message, store, STORE_PATH_DEFAULT, e); + } else { + LOGGER.warn(message, store, STORE_PATH_DEFAULT); + } + } + if (store != null) { + this.store = store; + return; + } + } + + try { + store = new File(STORE_PATH_DEFAULT); + if (!store.exists()) { + store = null; + LOGGER.debug("Initialization for default store path '{}' failed. Path does not exist.", STORE_PATH_DEFAULT); + } + } catch (Exception e) { + LOGGER.debug("Initialization for default store path '{}' failed.", STORE_PATH_DEFAULT, e); + } + + this.store = store; + } + + @Override + public Set get() { + if (store == null) { + LOGGER.trace("Nothing to do since `store` is `null`."); + return Collections.emptySet(); + } + + final File[] list = store.listFiles(FILTER); + if (list == null) { + LOGGER.debug("Listing files within store path '{}' failed."); + return Collections.emptySet(); + } + + final HashSet locations = new HashSet(list.length); + for (final File file : list) { + locations.add(InstallationLocation.autoDetected(file, getSourceName())); + } + return locations; + } + + @Override + public String getSourceName() { + return "Nix"; + } + + @VisibleForTesting + NixInstallationSupplier(File store) { + this.store = store; + } +} diff --git a/platforms/jvm/toolchains-jvm/src/main/java/org/gradle/jvm/internal/services/ToolchainsJvmServices.java b/platforms/jvm/toolchains-jvm/src/main/java/org/gradle/jvm/internal/services/ToolchainsJvmServices.java index 45d66792fb06c..4d87f996e2235 100644 --- a/platforms/jvm/toolchains-jvm/src/main/java/org/gradle/jvm/internal/services/ToolchainsJvmServices.java +++ b/platforms/jvm/toolchains-jvm/src/main/java/org/gradle/jvm/internal/services/ToolchainsJvmServices.java @@ -47,6 +47,7 @@ import org.gradle.jvm.toolchain.internal.JavaToolchainQueryService; import org.gradle.jvm.toolchain.internal.JdkCacheDirectory; import org.gradle.jvm.toolchain.internal.LinuxInstallationSupplier; +import org.gradle.jvm.toolchain.internal.NixInstallationSupplier; import org.gradle.jvm.toolchain.internal.MavenToolchainsInstallationSupplier; import org.gradle.jvm.toolchain.internal.OsXInstallationSupplier; import org.gradle.jvm.toolchain.internal.SdkmanInstallationSupplier; @@ -95,6 +96,7 @@ public void configure(ServiceRegistration registration) { registration.add(IntellijInstallationSupplier.class); registration.add(JabbaInstallationSupplier.class); registration.add(SdkmanInstallationSupplier.class); + registration.add(NixInstallationSupplier.class); registration.add(MavenToolchainsInstallationSupplier.class); registration.add(LinuxInstallationSupplier.class);