All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.quarkiverse.quinoa.deployment.packagemanager.PackageManagerInstall Maven / Gradle / Ivy

package io.quarkiverse.quinoa.deployment.packagemanager;

import static io.quarkiverse.quinoa.deployment.packagemanager.types.PackageManagerType.isYarnBerry;
import static io.vertx.core.spi.resolver.ResolverProvider.DISABLE_DNS_RESOLVER_PROP_NAME;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.DirectoryNotEmptyException;
import java.nio.file.Path;
import java.util.Optional;
import java.util.Set;

import org.jboss.logging.Logger;

import com.github.eirslett.maven.plugins.frontend.lib.InstallationException;
import com.github.eirslett.maven.plugins.frontend.lib.PackageManagerInstallFactory;

import io.quarkiverse.quinoa.deployment.config.PackageManagerInstallConfig;
import io.quarkus.deployment.util.FileUtil;
import io.quarkus.runtime.configuration.ConfigurationException;
import io.vertx.core.Vertx;
import io.vertx.core.VertxOptions;

public final class PackageManagerInstall {

    private static final Logger LOG = Logger.getLogger(PackageManagerInstall.class);
    private static final String INSTALL_SUB_PATH = "node";
    public static final String NODE_BINARY = PackageManagerRunner.isWindows() ? "node.exe" : "node";
    public static final String NPM_PATH = INSTALL_SUB_PATH + "/node_modules/npm/bin/npm-cli.js";
    public static final String PNPM_PATH = INSTALL_SUB_PATH + "/node_modules/corepack/dist/pnpm.js";
    public static final String YARN_PATH = INSTALL_SUB_PATH + "/node_modules/corepack/dist/yarn.js";

    private PackageManagerInstall() {

    }

    public static Installation install(PackageManagerInstallConfig config,
            final Path projectDir, final Path uiDir) {
        Path installDir = resolveInstallDir(config, projectDir).normalize();
        if (config.nodeVersion().isEmpty()) {
            throw new ConfigurationException("node-version is required to install package manager",
                    Set.of("quarkus.quinoa.package-manager-install.node-version"));
        }
        if (Integer.parseInt(config.nodeVersion().get().split("[.]")[0]) < 4) {
            throw new ConfigurationException("Quinoa is not compatible with Node prior to v4.0.0",
                    Set.of("quarkus.quinoa.package-manager-install.node-version"));
        }
        int i = 0;
        Exception thrown = null;
        Vertx vertx = null;
        try {
            vertx = createVertxInstance();
            PackageManagerInstallFactory factory = new PackageManagerInstallFactory(vertx, uiDir, installDir,
                    config.packageManagerInstallAuth().username().orElse(null),
                    config.packageManagerInstallAuth().password().orElse(null));
            while (i < 5) {
                try {
                    if (i > 0) {
                        LOG.warnf("An error occurred '%s' during the previous Node.js install, retrying (%s/5)",
                                thrown.getCause().getMessage(), i + 1);
                        FileUtil.deleteDirectory(installDir);
                    }
                    return attemptInstall(config, uiDir, installDir, factory);
                } catch (InstallationException e) {
                    thrown = e;
                    i++;
                } catch (IOException e) {
                    throw new UncheckedIOException(e);
                }
            }
        } finally {
            vertx.close();
        }

        throw new RuntimeException("Error while installing NodeJS", thrown);
    }

    private static Vertx createVertxInstance() {
        String originalValue = System.getProperty(DISABLE_DNS_RESOLVER_PROP_NAME);
        Vertx vertx;
        try {
            System.setProperty(DISABLE_DNS_RESOLVER_PROP_NAME, "true");
            vertx = Vertx.vertx(new VertxOptions());
        } finally {
            // Restore the original value
            if (originalValue == null) {
                System.clearProperty(DISABLE_DNS_RESOLVER_PROP_NAME);
            } else {
                System.setProperty(DISABLE_DNS_RESOLVER_PROP_NAME, originalValue);
            }
        }
        return vertx;
    }

    private static Installation attemptInstall(PackageManagerInstallConfig config, Path uiDir, Path installDir,
            PackageManagerInstallFactory factory) throws InstallationException {
        try {
            factory.getNodeInstaller()
                    .setNodeVersion("v" + config.nodeVersion().get())
                    .setNodeDownloadRoot(config.nodeDownloadRoot())
                    .setNpmVersion(config.npmVersion())
                    .install();
        } catch (InstallationException e) {
            if (e.getCause() instanceof DirectoryNotEmptyException && e.getCause().getMessage().contains("tmp")) {
                LOG.warnf("Quinoa was not able to delete the Node install temporary directory: %s",
                        e.getCause().getMessage());
            } else {
                throw e;
            }
        }

        // Use npm if npmVersion is different from provided or if no other version is set (then it will use the version provided by nodejs)
        String executionPath = NPM_PATH;
        final String npmVersion = config.npmVersion();
        boolean isNpmProvided = PackageManagerInstallConfig.NPM_PROVIDED.equalsIgnoreCase(npmVersion);
        if (!isNpmProvided) {
            factory.getNPMInstaller()
                    .setNodeVersion("v" + config.nodeVersion().get())
                    .setNpmVersion(npmVersion)
                    .setNpmDownloadRoot(config.npmDownloadRoot())
                    .install();
        }

        // Use yarn if yarnVersion is set (and npm is provided)
        final Optional yarnVersion = config.yarnVersion();
        if (yarnVersion.isPresent() && isNpmProvided) {
            executionPath = YARN_PATH;
            factory.getYarnInstaller()
                    .setIsYarnBerry(isYarnBerry(uiDir))
                    .setYarnVersion("v" + config.yarnVersion().get())
                    .setYarnDownloadRoot(config.yarnDownloadRoot())
                    .setIsYarnBerry(true)
                    .install();
        }

        // Use pnpm if pnpmVersion is set (and npm is provided and yarnVersion is not set)
        final Optional pnpmVersion = config.pnpmVersion();
        if (pnpmVersion.isPresent() && isNpmProvided && yarnVersion.isEmpty()) {
            executionPath = PNPM_PATH;
            factory.getPnpmInstaller()
                    .setNodeVersion("v" + config.nodeVersion().get())
                    .setPnpmVersion(pnpmVersion.get())
                    .setPnpmDownloadRoot(config.pnpmDownloadRoot())
                    .install();
        }

        return resolveInstalledExecutorBinary(installDir, executionPath);

    }

    private static Path resolveInstallDir(PackageManagerInstallConfig config, Path projectDir) {
        final Path installPath = Path.of(config.installDir().trim());
        if (installPath.isAbsolute()) {
            return installPath;
        }
        if (projectDir == null) {
            throw new ConfigurationException(
                    "Use an absolute package-manager-install.install-dir when the project root directory is not standard",
                    Set.of("quarkus.quinoa.package-manager-install.install-dir"));
        }
        return projectDir.resolve(installPath);
    }

    private static Installation resolveInstalledExecutorBinary(Path installDirectory, String executionPath) {
        final Path nodeDirPath = installDirectory.resolve(INSTALL_SUB_PATH)
                .toAbsolutePath();
        final Path executorPath = installDirectory.resolve(executionPath).toAbsolutePath();
        final String platformNodeDirPath = normalizePath(nodeDirPath.toString());
        final String platformExecutorPath = normalizePath(executorPath.toString());
        final String packageManagerBinary = NODE_BINARY + " " + quotePathWithSpaces(platformExecutorPath);
        return new Installation(platformNodeDirPath, packageManagerBinary);
    }

    public static String normalizePath(String path) {
        return PackageManagerRunner.isWindows() ? path.replaceAll("/", "\\\\") : path;
    }

    public static String quotePathWithSpaces(String path) {
        return path.contains(" ") ? "\"".concat(path).concat("\"") : path;
    }

    public static class Installation {
        private final String nodeDirPath;
        private final String packageManagerBinary;

        public Installation(String nodeDirPath, String packageManagerBinary) {
            this.nodeDirPath = nodeDirPath;
            this.packageManagerBinary = packageManagerBinary;
        }

        public String getNodeDirPath() {
            return nodeDirPath;
        }

        public String getPackageManagerBinary() {
            return packageManagerBinary;
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy