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

io.quarkus.bootstrap.resolver.maven.BootstrapMavenContext Maven / Gradle / Ivy

There is a newer version: 3.17.0.CR1
Show newest version
package io.quarkus.bootstrap.resolver.maven;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;

import org.apache.maven.cli.transfer.BatchModeMavenTransferListener;
import org.apache.maven.cli.transfer.ConsoleMavenTransferListener;
import org.apache.maven.cli.transfer.QuietMavenTransferListener;
import org.apache.maven.model.Model;
import org.apache.maven.model.building.ModelBuilder;
import org.apache.maven.model.building.ModelProblemCollector;
import org.apache.maven.model.building.ModelProblemCollectorRequest;
import org.apache.maven.model.path.DefaultPathTranslator;
import org.apache.maven.model.path.ProfileActivationFilePathInterpolator;
import org.apache.maven.model.profile.DefaultProfileActivationContext;
import org.apache.maven.model.profile.DefaultProfileSelector;
import org.apache.maven.model.profile.activation.FileProfileActivator;
import org.apache.maven.model.profile.activation.JdkVersionProfileActivator;
import org.apache.maven.model.profile.activation.OperatingSystemProfileActivator;
import org.apache.maven.model.profile.activation.PropertyProfileActivator;
import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
import org.apache.maven.settings.Mirror;
import org.apache.maven.settings.Profile;
import org.apache.maven.settings.Server;
import org.apache.maven.settings.Settings;
import org.apache.maven.settings.SettingsUtils;
import org.apache.maven.settings.building.DefaultSettingsBuilderFactory;
import org.apache.maven.settings.building.DefaultSettingsBuildingRequest;
import org.apache.maven.settings.building.SettingsBuildingException;
import org.apache.maven.settings.building.SettingsBuildingResult;
import org.apache.maven.settings.building.SettingsProblem;
import org.apache.maven.settings.crypto.DefaultSettingsDecryptionRequest;
import org.apache.maven.settings.crypto.SettingsDecrypter;
import org.apache.maven.settings.crypto.SettingsDecryptionRequest;
import org.apache.maven.settings.crypto.SettingsDecryptionResult;
import org.codehaus.plexus.configuration.PlexusConfiguration;
import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.eclipse.aether.ConfigurationProperties;
import org.eclipse.aether.DefaultRepositoryCache;
import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.impl.RemoteRepositoryManager;
import org.eclipse.aether.repository.ArtifactRepository;
import org.eclipse.aether.repository.Authentication;
import org.eclipse.aether.repository.LocalRepository;
import org.eclipse.aether.repository.Proxy;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.repository.RepositoryPolicy;
import org.eclipse.aether.resolution.ArtifactDescriptorException;
import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
import org.eclipse.aether.transfer.TransferListener;
import org.eclipse.aether.util.repository.AuthenticationBuilder;
import org.eclipse.aether.util.repository.DefaultAuthenticationSelector;
import org.eclipse.aether.util.repository.DefaultMirrorSelector;
import org.eclipse.aether.util.repository.DefaultProxySelector;
import org.jboss.logging.Logger;

import io.quarkus.bootstrap.resolver.maven.options.BootstrapMavenOptions;
import io.quarkus.bootstrap.resolver.maven.workspace.LocalProject;
import io.quarkus.bootstrap.resolver.maven.workspace.LocalWorkspace;
import io.quarkus.bootstrap.resolver.maven.workspace.ModelUtils;
import io.quarkus.bootstrap.util.PropertyUtils;
import io.quarkus.maven.dependency.ArtifactCoords;
import io.smallrye.beanbag.maven.MavenFactory;

public class BootstrapMavenContext {

    private static final Logger log = Logger.getLogger(BootstrapMavenContext.class);

    private static final String BASEDIR = "basedir";
    private static final String DEFAULT_REMOTE_REPO_ID = "central";
    private static final String DEFAULT_REMOTE_REPO_URL = "https://repo.maven.apache.org/maven2";
    private static final String MAVEN_DOT_HOME = "maven.home";
    private static final String MAVEN_HOME = "MAVEN_HOME";
    private static final String MAVEN_SETTINGS = "maven.settings";
    private static final String MAVEN_TOP_LEVEL_PROJECT_BASEDIR = "maven.top-level-basedir";
    private static final String SETTINGS_XML = "settings.xml";
    private static final String SETTINGS_SECURITY = "settings.security";

    private static final String EFFECTIVE_MODEL_BUILDER_PROP = "quarkus.bootstrap.effective-model-builder";
    private static final String WARN_ON_FAILING_WS_MODULES_PROP = "quarkus.bootstrap.warn-on-failing-workspace-modules";

    private static final String MAVEN_RESOLVER_TRANSPORT_KEY = "maven.resolver.transport";
    private static final String MAVEN_RESOLVER_TRANSPORT_DEFAULT = "default";
    private static final String MAVEN_RESOLVER_TRANSPORT_WAGON = "wagon";
    private static final String MAVEN_RESOLVER_TRANSPORT_NATIVE = "native";
    private static final String MAVEN_RESOLVER_TRANSPORT_AUTO = "auto";
    private static final String WAGON_TRANSPORTER_PRIORITY_KEY = "aether.priority.WagonTransporterFactory";
    private static final String NATIVE_HTTP_TRANSPORTER_PRIORITY_KEY = "aether.priority.HttpTransporterFactory";
    private static final String NATIVE_FILE_TRANSPORTER_PRIORITY_KEY = "aether.priority.FileTransporterFactory";
    private static final String RESOLVER_MAX_PRIORITY = String.valueOf(Float.MAX_VALUE);

    private boolean artifactTransferLogging;
    private BootstrapMavenOptions cliOptions;
    private File userSettings;
    private File globalSettings;
    private Boolean offline;

    // Typically, this property will not be enabled in Quarkus application development use-cases
    // It was introduced to support use-cases of using the bootstrap resolver API beyond Quarkus application development
    private Boolean warnOnFailingWorkspaceModules;

    private LocalWorkspace workspace;
    private LocalProject currentProject;
    private Settings settings;
    private List activeSettingsProfiles;
    private RepositorySystem repoSystem;
    private RepositorySystemSession repoSession;
    private List remoteRepos;
    private List remotePluginRepos;
    private RemoteRepositoryManager remoteRepoManager;
    private String localRepo;
    private Path currentPom;
    private Boolean currentProjectExists;
    private String alternatePomName;
    private Path rootProjectDir;
    private boolean preferPomsFromWorkspace;
    private Boolean effectiveModelBuilder;
    private Boolean wsModuleParentHierarchy;
    private SettingsDecrypter settingsDecrypter;
    private final List excludeSisuBeanPackages;
    private final List includeSisuBeanPackages;

    public static BootstrapMavenContextConfig config() {
        return new BootstrapMavenContextConfig<>();
    }

    public BootstrapMavenContext() throws BootstrapMavenException {
        this(new BootstrapMavenContextConfig<>());
    }

    public BootstrapMavenContext(BootstrapMavenContextConfig config)
            throws BootstrapMavenException {
        /*
         * WARNING: this constructor calls instance methods as part of the initialization.
         * This means the values that are available in the config should be set before
         * the instance method invocations.
         */
        this.alternatePomName = config.alternatePomName;
        this.artifactTransferLogging = config.artifactTransferLogging;
        this.localRepo = config.localRepo;
        this.offline = config.offline;
        this.warnOnFailingWorkspaceModules = config.warnOnFailedWorkspaceModules;
        this.repoSystem = config.repoSystem;
        this.repoSession = config.repoSession;
        this.remoteRepos = config.remoteRepos;
        this.remotePluginRepos = config.remotePluginRepos;
        this.remoteRepoManager = config.remoteRepoManager;
        this.settingsDecrypter = config.settingsDecrypter;
        this.cliOptions = config.cliOptions;
        this.excludeSisuBeanPackages = config.getExcludeSisuBeanPackages();
        this.includeSisuBeanPackages = config.getIncludeSisuBeanPackages();
        if (config.rootProjectDir == null) {
            final String topLevelBaseDirStr = PropertyUtils.getProperty(MAVEN_TOP_LEVEL_PROJECT_BASEDIR);
            if (topLevelBaseDirStr != null) {
                final Path tmp = Path.of(topLevelBaseDirStr);
                if (!Files.exists(tmp)) {
                    throw new BootstrapMavenException("Top-level project base directory " + topLevelBaseDirStr
                            + " specified with system property " + MAVEN_TOP_LEVEL_PROJECT_BASEDIR + " does not exist");
                }
                this.rootProjectDir = tmp;
            }
        } else {
            this.rootProjectDir = config.rootProjectDir;
        }
        this.preferPomsFromWorkspace = config.preferPomsFromWorkspace;
        this.effectiveModelBuilder = config.effectiveModelBuilder;
        this.wsModuleParentHierarchy = config.wsModuleParentHierarchy;
        this.userSettings = config.userSettings;
        if (config.currentProject != null) {
            this.currentProject = config.currentProject;
            this.currentPom = currentProject.getRawModel().getPomFile().toPath();
            this.workspace = config.currentProject.getWorkspace();
        } else if (config.workspaceDiscovery) {
            currentProject = resolveCurrentProject(config.modelProvider);
            this.workspace = currentProject == null ? null : currentProject.getWorkspace();
            if (workspace != null) {
                if (config.repoSession == null && repoSession != null && repoSession.getWorkspaceReader() == null) {
                    repoSession = new DefaultRepositorySystemSession(repoSession).setWorkspaceReader(workspace);
                    if (config.remoteRepos == null && remoteRepos != null) {
                        final List rawProjectRepos = resolveRawProjectRepos(remoteRepos);
                        if (!rawProjectRepos.isEmpty()) {
                            remoteRepos = getRemoteRepositoryManager().aggregateRepositories(repoSession, remoteRepos,
                                    rawProjectRepos, true);
                        }
                    }
                }
            }
        }
    }

    public ArtifactCoords getCurrentProjectArtifact(String extension) throws BootstrapMavenException {
        if (currentProject != null) {
            return currentProject.getAppArtifact(extension);
        }
        final Model model = loadCurrentProjectModel();
        if (model == null) {
            return null;
        }
        return ArtifactCoords.of(ModelUtils.getGroupId(model), model.getArtifactId(), ArtifactCoords.DEFAULT_CLASSIFIER,
                extension, ModelUtils.getVersion(model));
    }

    public LocalProject getCurrentProject() {
        return currentProject;
    }

    public LocalWorkspace getWorkspace() {
        return workspace;
    }

    public BootstrapMavenOptions getCliOptions() {
        return cliOptions == null ? cliOptions = BootstrapMavenOptions.newInstance() : cliOptions;
    }

    public File getUserSettings() {
        if (userSettings == null) {
            final String quarkusMavenSettings = getProperty(MAVEN_SETTINGS);
            if (quarkusMavenSettings != null) {
                var f = new File(quarkusMavenSettings);
                return userSettings = f.exists() ? f : null;
            }
            return userSettings = resolveSettingsFile(
                    getCliOptions().getOptionValue(BootstrapMavenOptions.ALTERNATE_USER_SETTINGS),
                    () -> new File(getUserMavenConfigurationHome(), SETTINGS_XML));
        }
        return userSettings;
    }

    private static File getUserMavenConfigurationHome() {
        return new File(PropertyUtils.getUserHome(), ".m2");
    }

    private String getProperty(String name) {
        String value = PropertyUtils.getProperty(name);
        if (value != null) {
            return value;
        }
        final Properties props = getCliOptions().getSystemProperties();
        return props == null ? null : props.getProperty(name);
    }

    public File getGlobalSettings() {
        return globalSettings == null
                ? globalSettings = resolveSettingsFile(
                        getCliOptions().getOptionValue(BootstrapMavenOptions.ALTERNATE_GLOBAL_SETTINGS),
                        () -> {
                            String mavenHome = getProperty(MAVEN_DOT_HOME);
                            if (mavenHome == null) {
                                mavenHome = System.getenv(MAVEN_HOME);
                                if (mavenHome == null) {
                                    mavenHome = "";
                                }
                            }
                            return new File(mavenHome, "conf/settings.xml");
                        })
                : globalSettings;
    }

    public boolean isOffline() throws BootstrapMavenException {
        return offline == null
                ? offline = (getCliOptions().hasOption(BootstrapMavenOptions.OFFLINE) || getEffectiveSettings().isOffline())
                : offline;
    }

    public boolean isWarnOnFailingWorkspaceModules() {
        return warnOnFailingWorkspaceModules == null
                ? warnOnFailingWorkspaceModules = Boolean.getBoolean(WARN_ON_FAILING_WS_MODULES_PROP)
                : warnOnFailingWorkspaceModules;
    }

    public RepositorySystem getRepositorySystem() throws BootstrapMavenException {
        if (repoSystem == null) {
            initRepoSystemAndManager();
        }
        return repoSystem;
    }

    public RemoteRepositoryManager getRemoteRepositoryManager() {
        if (remoteRepoManager == null) {
            initRepoSystemAndManager();
        }
        return remoteRepoManager;
    }

    public RepositorySystemSession getRepositorySystemSession() throws BootstrapMavenException {
        return repoSession == null ? repoSession = newRepositorySystemSession() : repoSession;
    }

    public List getRemoteRepositories() throws BootstrapMavenException {
        return remoteRepos == null ? remoteRepos = resolveRemoteRepos() : remoteRepos;
    }

    public List getRemotePluginRepositories() throws BootstrapMavenException {
        return remotePluginRepos == null ? remotePluginRepos = resolveRemotePluginRepos() : remotePluginRepos;
    }

    public SettingsDecrypter getSettingsDecrypter() {
        if (settingsDecrypter == null) {
            initRepoSystemAndManager();
        }
        return settingsDecrypter;
    }

    public Settings getEffectiveSettings() throws BootstrapMavenException {
        if (settings != null) {
            return settings;
        }

        final DefaultSettingsBuildingRequest settingsRequest = new DefaultSettingsBuildingRequest()
                .setSystemProperties(System.getProperties())
                .setUserSettingsFile(getUserSettings())
                .setGlobalSettingsFile(getGlobalSettings());

        final Properties cmdLineProps = getCliOptions().getSystemProperties();
        if (cmdLineProps != null) {
            settingsRequest.setUserProperties(cmdLineProps);
        }

        final Settings effectiveSettings;
        try {
            final SettingsBuildingResult result = new DefaultSettingsBuilderFactory()
                    .newInstance().build(settingsRequest);
            final List problems = result.getProblems();
            if (!problems.isEmpty()) {
                for (SettingsProblem problem : problems) {
                    switch (problem.getSeverity()) {
                        case ERROR:
                        case FATAL:
                            throw new BootstrapMavenException("Settings problem encountered at " + problem.getLocation(),
                                    problem.getException());
                        default:
                            log.warn("Settings problem encountered at " + problem.getLocation(), problem.getException());
                    }
                }
            }
            effectiveSettings = result.getEffectiveSettings();
        } catch (SettingsBuildingException e) {
            throw new BootstrapMavenException("Failed to initialize Maven repository settings", e);
        }
        return settings = effectiveSettings;
    }

    public String getLocalRepo() throws BootstrapMavenException {
        return localRepo == null ? localRepo = resolveLocalRepo(getEffectiveSettings()) : localRepo;
    }

    private LocalProject resolveCurrentProject(Function modelProvider) throws BootstrapMavenException {
        try {
            return LocalProject.loadWorkspace(this, modelProvider);
        } catch (Exception e) {
            throw new BootstrapMavenException("Failed to load current project at " + getCurrentProjectPomOrNull(), e);
        }
    }

    private String resolveLocalRepo(Settings settings) {
        String localRepo = System.getenv("QUARKUS_LOCAL_REPO");
        if (localRepo != null) {
            return localRepo;
        }
        localRepo = getProperty("maven.repo.local");
        if (localRepo != null) {
            return localRepo;
        }
        localRepo = settings.getLocalRepository();
        return localRepo == null ? new File(getUserMavenConfigurationHome(), "repository").getAbsolutePath() : localRepo;
    }

    private File resolveSettingsFile(String settingsArg, Supplier supplier) {
        File userSettings;
        if (settingsArg != null) {
            userSettings = new File(settingsArg);
            if (userSettings.exists()) {
                return userSettings;
            }
            if (userSettings.isAbsolute()) {
                return null;
            }

            // in case the settings path is a relative one we check whether the pom path is also a relative one
            // in which case we can resolve the settings path relative to the project directory
            // otherwise, we don't have a clue what the settings path is relative to
            String alternatePomDir = getCliOptions().getOptionValue(BootstrapMavenOptions.ALTERNATE_POM_FILE);
            if (alternatePomDir != null) {
                File tmp = new File(alternatePomDir);
                if (tmp.isAbsolute()) {
                    alternatePomDir = null;
                } else {
                    if (!tmp.isDirectory()) {
                        tmp = tmp.getParentFile();
                    }
                    alternatePomDir = tmp == null ? null : tmp.toString();
                }
            }

            // Root project base dir
            userSettings = resolveSettingsFile(settingsArg, alternatePomDir, System.getenv("MAVEN_PROJECTBASEDIR"));
            if (userSettings != null) {
                return userSettings;
            }
            // current module project base dir
            userSettings = resolveSettingsFile(settingsArg, alternatePomDir, PropertyUtils.getProperty(BASEDIR));
            if (userSettings != null) {
                return userSettings;
            }
            userSettings = new File(PropertyUtils.getUserHome(), settingsArg);
            if (userSettings.exists()) {
                return userSettings;
            }
        }
        userSettings = supplier.get();
        return userSettings.exists() ? userSettings : null;
    }

    private File resolveSettingsFile(String settingsArg, String alternatePomDir, String projectBaseDir) {
        if (projectBaseDir == null) {
            return null;
        }
        File userSettings;
        if (alternatePomDir != null && projectBaseDir.endsWith(alternatePomDir)) {
            userSettings = new File(projectBaseDir.substring(0, projectBaseDir.length() - alternatePomDir.length()),
                    settingsArg);
            if (userSettings.exists()) {
                return userSettings;
            }
        }
        userSettings = new File(projectBaseDir, settingsArg);
        if (userSettings.exists()) {
            return userSettings;
        }
        return null;
    }

    private DefaultRepositorySystemSession newRepositorySystemSession() throws BootstrapMavenException {
        final DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession();
        final Settings settings = getEffectiveSettings();
        final List mirrors = settings.getMirrors();
        if (mirrors != null && !mirrors.isEmpty()) {
            final DefaultMirrorSelector ms = new DefaultMirrorSelector();
            for (Mirror m : mirrors) {
                ms.add(m.getId(), m.getUrl(), m.getLayout(), false, m.isBlocked(), m.getMirrorOf(), m.getMirrorOfLayouts());
            }
            session.setMirrorSelector(ms);
        }
        final String localRepoPath = getLocalRepo();
        session.setLocalRepositoryManager(
                getRepositorySystem().newLocalRepositoryManager(session, new LocalRepository(localRepoPath)));

        session.setOffline(isOffline());

        final BootstrapMavenOptions mvnArgs = getCliOptions();
        if (!mvnArgs.isEmpty()) {
            if (mvnArgs.hasOption(BootstrapMavenOptions.SUPRESS_SNAPSHOT_UPDATES)) {
                session.setUpdatePolicy(RepositoryPolicy.UPDATE_POLICY_NEVER);
            } else if (mvnArgs.hasOption(BootstrapMavenOptions.UPDATE_SNAPSHOTS)) {
                session.setUpdatePolicy(RepositoryPolicy.UPDATE_POLICY_ALWAYS);
            }
            if (mvnArgs.hasOption(BootstrapMavenOptions.CHECKSUM_FAILURE_POLICY)) {
                session.setChecksumPolicy(RepositoryPolicy.CHECKSUM_POLICY_FAIL);
            } else if (mvnArgs.hasOption(BootstrapMavenOptions.CHECKSUM_WARNING_POLICY)) {
                session.setChecksumPolicy(RepositoryPolicy.CHECKSUM_POLICY_WARN);
            }
        }

        final SettingsDecryptionRequest decrypt = new DefaultSettingsDecryptionRequest();
        decrypt.setProxies(settings.getProxies());
        decrypt.setServers(settings.getServers());
        // set settings.security property to ~/.m2/settings-security.xml unless it's already set to some other value
        File settingsSecurityXml = null;
        final boolean setSettingsSecurity = !System.getProperties().contains(SETTINGS_SECURITY)
                && ((settingsSecurityXml = new File(getUserMavenConfigurationHome(), "settings-security.xml")).exists());
        if (setSettingsSecurity) {
            System.setProperty(SETTINGS_SECURITY, settingsSecurityXml.toString());
        }
        final SettingsDecryptionResult decrypted = getSettingsDecrypter().decrypt(decrypt);
        if (setSettingsSecurity) {
            System.clearProperty(SETTINGS_SECURITY);
        }
        if (!decrypted.getProblems().isEmpty() && log.isDebugEnabled()) {
            // this is how maven handles these
            for (SettingsProblem p : decrypted.getProblems()) {
                log.debug(p.getMessage(), p.getException());
            }
        }

        final DefaultProxySelector proxySelector = new DefaultProxySelector();
        for (org.apache.maven.settings.Proxy p : decrypted.getProxies()) {
            if (p.isActive()) {
                proxySelector.add(toAetherProxy(p), p.getNonProxyHosts());
            }
        }
        session.setProxySelector(proxySelector);

        final Map configProps = new LinkedHashMap<>(session.getConfigProperties());
        configProps.put(ConfigurationProperties.USER_AGENT, getUserAgent());
        configProps.put(ConfigurationProperties.INTERACTIVE, settings.isInteractiveMode());

        final DefaultAuthenticationSelector authSelector = new DefaultAuthenticationSelector();
        for (Server server : decrypted.getServers()) {
            AuthenticationBuilder authBuilder = new AuthenticationBuilder();
            authBuilder.addUsername(server.getUsername()).addPassword(server.getPassword());
            authBuilder.addPrivateKey(server.getPrivateKey(), server.getPassphrase());
            authSelector.add(server.getId(), authBuilder.build());

            if (server.getConfiguration() != null) {
                Xpp3Dom dom = (Xpp3Dom) server.getConfiguration();
                for (int i = dom.getChildCount() - 1; i >= 0; i--) {
                    Xpp3Dom child = dom.getChild(i);
                    if ("wagonProvider".equals(child.getName())) {
                        dom.removeChild(i);
                    }
                }
                XmlPlexusConfiguration config = new XmlPlexusConfiguration(dom);
                configProps.put("aether.connector.wagon.config." + server.getId(), config);

                // Translate to proper resolver configuration properties as well (as Plexus XML above is Wagon specific
                // only), but support only configuration/httpConfiguration/all, see
                // https://maven.apache.org/guides/mini/guide-http-settings.html
                Map headers = null;
                Integer connectTimeout = null;
                Integer requestTimeout = null;

                PlexusConfiguration httpHeaders = config.getChild("httpHeaders", false);
                if (httpHeaders != null) {
                    PlexusConfiguration[] properties = httpHeaders.getChildren("property");
                    if (properties != null && properties.length > 0) {
                        headers = new HashMap<>();
                        for (PlexusConfiguration property : properties) {
                            headers.put(
                                    property.getChild("name").getValue(),
                                    property.getChild("value").getValue());
                        }
                    }
                }

                PlexusConfiguration connectTimeoutXml = config.getChild("connectTimeout", false);
                if (connectTimeoutXml != null) {
                    connectTimeout = Integer.parseInt(connectTimeoutXml.getValue());
                } else {
                    // fallback configuration name
                    PlexusConfiguration httpConfiguration = config.getChild("httpConfiguration", false);
                    if (httpConfiguration != null) {
                        PlexusConfiguration httpConfigurationAll = httpConfiguration.getChild("all", false);
                        if (httpConfigurationAll != null) {
                            connectTimeoutXml = httpConfigurationAll.getChild("connectionTimeout", false);
                            if (connectTimeoutXml != null) {
                                connectTimeout = Integer.parseInt(connectTimeoutXml.getValue());
                                log.warn("Settings for server " + server.getId() + " uses legacy format");
                            }
                        }
                    }
                }

                PlexusConfiguration requestTimeoutXml = config.getChild("requestTimeout", false);
                if (requestTimeoutXml != null) {
                    requestTimeout = Integer.parseInt(requestTimeoutXml.getValue());
                } else {
                    // fallback configuration name
                    PlexusConfiguration httpConfiguration = config.getChild("httpConfiguration", false);
                    if (httpConfiguration != null) {
                        PlexusConfiguration httpConfigurationAll = httpConfiguration.getChild("all", false);
                        if (httpConfigurationAll != null) {
                            requestTimeoutXml = httpConfigurationAll.getChild("readTimeout", false);
                            if (requestTimeoutXml != null) {
                                requestTimeout = Integer.parseInt(requestTimeoutXml.getValue());
                                log.warn("Settings for server " + server.getId() + " uses legacy format");
                            }
                        }
                    }
                }

                // org.eclipse.aether.ConfigurationProperties.HTTP_HEADERS => Map
                if (headers != null) {
                    configProps.put(ConfigurationProperties.HTTP_HEADERS + "." + server.getId(), headers);
                }
                // org.eclipse.aether.ConfigurationProperties.CONNECT_TIMEOUT => int
                if (connectTimeout != null) {
                    configProps.put(ConfigurationProperties.CONNECT_TIMEOUT + "." + server.getId(), connectTimeout);
                }
                // org.eclipse.aether.ConfigurationProperties.REQUEST_TIMEOUT => int
                if (requestTimeout != null) {
                    configProps.put(ConfigurationProperties.REQUEST_TIMEOUT + "." + server.getId(), requestTimeout);
                }
            }
            configProps.put("aether.connector.perms.fileMode." + server.getId(), server.getFilePermissions());
            configProps.put("aether.connector.perms.dirMode." + server.getId(), server.getDirectoryPermissions());
        }
        session.setAuthenticationSelector(authSelector);

        Object transport = configProps.getOrDefault(MAVEN_RESOLVER_TRANSPORT_KEY, MAVEN_RESOLVER_TRANSPORT_DEFAULT);
        if (MAVEN_RESOLVER_TRANSPORT_DEFAULT.equals(transport)) {
            // The "default" mode (user did not set anything) from now on defaults to AUTO
        } else if (MAVEN_RESOLVER_TRANSPORT_NATIVE.equals(transport)) {
            // Make sure (whatever extra priority is set) that resolver native is selected
            configProps.put(NATIVE_FILE_TRANSPORTER_PRIORITY_KEY, RESOLVER_MAX_PRIORITY);
            configProps.put(NATIVE_HTTP_TRANSPORTER_PRIORITY_KEY, RESOLVER_MAX_PRIORITY);
        } else if (MAVEN_RESOLVER_TRANSPORT_WAGON.equals(transport)) {
            // Make sure (whatever extra priority is set) that wagon is selected
            configProps.put(WAGON_TRANSPORTER_PRIORITY_KEY, RESOLVER_MAX_PRIORITY);
        } else if (!MAVEN_RESOLVER_TRANSPORT_AUTO.equals(transport)) {
            throw new IllegalArgumentException("Unknown resolver transport '" + transport
                    + "'. Supported transports are: " + MAVEN_RESOLVER_TRANSPORT_WAGON + ", "
                    + MAVEN_RESOLVER_TRANSPORT_NATIVE + ", " + MAVEN_RESOLVER_TRANSPORT_AUTO);
        }

        session.setConfigProperties(configProps);

        if (session.getCache() == null) {
            session.setCache(new DefaultRepositoryCache());
        }

        if (workspace != null) {
            session.setWorkspaceReader(workspace);
        }

        if (session.getTransferListener() == null && artifactTransferLogging) {
            TransferListener transferListener;
            if (mvnArgs.hasOption(BootstrapMavenOptions.NO_TRANSFER_PROGRESS)) {
                transferListener = new QuietMavenTransferListener();
            } else if (mvnArgs.hasOption(BootstrapMavenOptions.BATCH_MODE)) {
                transferListener = new BatchModeMavenTransferListener(System.out);
            } else {
                transferListener = new ConsoleMavenTransferListener(System.out, true);
            }

            session.setTransferListener(transferListener);
        }

        for (var e : System.getProperties().entrySet()) {
            session.setSystemProperty(e.getKey().toString(), e.getValue().toString());
        }

        return session;
    }

    private List resolveRemoteRepos() throws BootstrapMavenException {
        final List rawRepos = new ArrayList<>();
        readMavenReposFromEnv(rawRepos, System.getenv());
        addReposFromProfiles(rawRepos);

        final boolean centralConfiguredInSettings = includesDefaultRepo(rawRepos);
        if (!centralConfiguredInSettings) {
            rawRepos.add(newDefaultRepository());
        }

        if (workspace == null) {
            return newResolutionRepos(rawRepos);
        }

        final List rawProjectRepos = resolveRawProjectRepos(newResolutionRepos(rawRepos));
        if (!rawProjectRepos.isEmpty()) {
            if (!centralConfiguredInSettings) {
                // if the default repo was added here, we are removing it to add it last
                rawRepos.remove(rawRepos.size() - 1);
            }
            rawRepos.addAll(rawProjectRepos);
            if (!centralConfiguredInSettings && !includesDefaultRepo(rawProjectRepos)) {
                rawRepos.add(newDefaultRepository());
            }
        }

        return newResolutionRepos(rawRepos);
    }

    private List resolveRemotePluginRepos() throws BootstrapMavenException {
        final List rawRepos = new ArrayList<>();
        addReposFromProfiles(rawRepos);
        // central must be there
        if (!includesDefaultRepo(rawRepos)) {
            rawRepos.add(newDefaultRepository());
        }
        return newResolutionRepos(rawRepos);
    }

    private List newResolutionRepos(final List rawRepos)
            throws BootstrapMavenException {
        return getRepositorySystem().newResolutionRepositories(getRepositorySystemSession(), rawRepos);
    }

    private void addReposFromProfiles(final List rawRepos) throws BootstrapMavenException {
        // reverse the order of the profiles to match the order in which the repos appear in Maven mojos
        final List profiles = getActiveSettingsProfiles();
        for (int i = profiles.size() - 1; i >= 0; --i) {
            addProfileRepos(profiles.get(i).getRepositories(), rawRepos);
        }
    }

    public static RemoteRepository newDefaultRepository() {
        return new RemoteRepository.Builder(DEFAULT_REMOTE_REPO_ID, "default", DEFAULT_REMOTE_REPO_URL)
                .setReleasePolicy(new RepositoryPolicy(true, RepositoryPolicy.UPDATE_POLICY_DAILY,
                        RepositoryPolicy.CHECKSUM_POLICY_WARN))
                .setSnapshotPolicy(new RepositoryPolicy(false, RepositoryPolicy.UPDATE_POLICY_DAILY,
                        RepositoryPolicy.CHECKSUM_POLICY_WARN))
                .build();
    }

    private Model loadCurrentProjectModel() throws BootstrapMavenException {
        final Path pom = getCurrentProjectPomOrNull();
        if (pom == null) {
            return null;
        }
        try {
            return ModelUtils.readModel(pom);
        } catch (IOException e) {
            throw new BootstrapMavenException("Failed to parse " + pom, e);
        }
    }

    private List resolveRawProjectRepos(List repos)
            throws BootstrapMavenException {
        final Artifact projectArtifact;
        if (currentProject == null) {
            final Model model = loadCurrentProjectModel();
            if (model == null) {
                return List.of();
            }
            projectArtifact = new DefaultArtifact(ModelUtils.getGroupId(model), model.getArtifactId(),
                    ArtifactCoords.DEFAULT_CLASSIFIER, ArtifactCoords.TYPE_POM, ModelUtils.getVersion(model));
        } else {
            projectArtifact = new DefaultArtifact(currentProject.getGroupId(), currentProject.getArtifactId(),
                    ArtifactCoords.DEFAULT_CLASSIFIER, ArtifactCoords.TYPE_POM, currentProject.getVersion());
        }

        final RepositorySystem repoSystem = getRepositorySystem();
        final RepositorySystemSession repoSession = getRepositorySystemSession();
        try {
            return repoSystem
                    .readArtifactDescriptor(repoSession, new ArtifactDescriptorRequest()
                            .setArtifact(projectArtifact)
                            .setRepositories(repos))
                    .getRepositories();
        } catch (ArtifactDescriptorException e) {
            throw new BootstrapMavenException("Failed to read artifact descriptor for " + projectArtifact, e);
        }
    }

    public List getActiveSettingsProfiles()
            throws BootstrapMavenException {
        if (activeSettingsProfiles != null) {
            return activeSettingsProfiles;
        }

        final Settings settings = getEffectiveSettings();
        final List allSettingsProfiles = settings.getProfiles();
        if (allSettingsProfiles.isEmpty()) {
            return activeSettingsProfiles = List.of();
        }

        final BootstrapMavenOptions mvnArgs = getCliOptions();
        final Path currentPom = getCurrentProjectPomOrNull();
        final DefaultProfileActivationContext context = new DefaultProfileActivationContext()
                .setActiveProfileIds(mvnArgs.getActiveProfileIds())
                .setInactiveProfileIds(mvnArgs.getInactiveProfileIds())
                .setSystemProperties(System.getProperties())
                .setProjectDirectory(
                        currentPom == null ? getCurrentProjectBaseDir().toFile() : currentPom.getParent().toFile());
        final DefaultProfileSelector profileSelector = new DefaultProfileSelector()
                .addProfileActivator(new PropertyProfileActivator())
                .addProfileActivator(new JdkVersionProfileActivator())
                .addProfileActivator(new OperatingSystemProfileActivator())
                .addProfileActivator(createFileProfileActivator());
        List selectedProfiles = profileSelector
                .getActiveProfiles(toModelProfiles(allSettingsProfiles), context, new ModelProblemCollector() {
                    public void add(ModelProblemCollectorRequest req) {
                        log.error("Failed to activate a Maven profile: " + req.getMessage());
                    }
                });

        if (!settings.getActiveProfiles().isEmpty()) {
            final Set activeProfiles = new HashSet<>(settings.getActiveProfiles());
            // remove already activated to avoid duplicates
            selectedProfiles.forEach(p -> activeProfiles.remove(p.getId()));
            if (!activeProfiles.isEmpty()) {
                final List allActiveProfiles = new ArrayList<>(
                        selectedProfiles.size() + activeProfiles.size());
                allActiveProfiles.addAll(selectedProfiles);
                for (Profile profile : allSettingsProfiles) {
                    if (activeProfiles.contains(profile.getId())) {
                        allActiveProfiles.add(SettingsUtils.convertFromSettingsProfile(profile));
                    }
                }
                selectedProfiles = allActiveProfiles;
            }
        }
        return activeSettingsProfiles = selectedProfiles;
    }

    private static List toModelProfiles(List profiles) {
        final List result = new ArrayList<>(profiles.size());
        for (Profile p : profiles) {
            result.add(SettingsUtils.convertFromSettingsProfile(p));
        }
        return result;
    }

    private static boolean includesDefaultRepo(List repositories) {
        if (repositories.isEmpty()) {
            return false;
        }
        for (ArtifactRepository repository : repositories) {
            if (repository.getId().equals(DEFAULT_REMOTE_REPO_ID)) {
                return true;
            }
        }
        return false;
    }

    private static void addProfileRepos(List repositories,
            final List all) {
        for (org.apache.maven.model.Repository repo : repositories) {
            final RemoteRepository.Builder repoBuilder = new RemoteRepository.Builder(repo.getId(), repo.getLayout(),
                    repo.getUrl());
            org.apache.maven.model.RepositoryPolicy policy = repo.getReleases();
            if (policy != null) {
                repoBuilder.setReleasePolicy(toAetherRepoPolicy(policy));
            }
            policy = repo.getSnapshots();
            if (policy != null) {
                repoBuilder.setSnapshotPolicy(toAetherRepoPolicy(policy));
            }
            all.add(repoBuilder.build());
        }
    }

    private static RepositoryPolicy toAetherRepoPolicy(org.apache.maven.model.RepositoryPolicy modelPolicy) {
        return new RepositoryPolicy(modelPolicy.isEnabled(),
                isEmpty(modelPolicy.getUpdatePolicy()) ? RepositoryPolicy.UPDATE_POLICY_DAILY
                        : modelPolicy.getUpdatePolicy(),
                isEmpty(modelPolicy.getChecksumPolicy()) ? RepositoryPolicy.CHECKSUM_POLICY_WARN
                        : modelPolicy.getChecksumPolicy());
    }

    private static boolean isEmpty(final CharSequence cs) {
        return cs == null || cs.length() == 0;
    }

    /**
     * Convert a {@link org.apache.maven.settings.Proxy} to a {@link Proxy}.
     *
     * @param proxy Maven proxy settings, may be {@code null}.
     * @return Aether repository proxy or {@code null} if given {@link org.apache.maven.settings.Proxy} is {@code null}.
     */
    private static Proxy toAetherProxy(org.apache.maven.settings.Proxy proxy) {
        if (proxy == null) {
            return null;
        }
        Authentication auth = null;
        if (proxy.getUsername() != null) {
            auth = new AuthenticationBuilder()
                    .addUsername(proxy.getUsername())
                    .addPassword(proxy.getPassword())
                    .build();
        }
        return new Proxy(proxy.getProtocol(), proxy.getHost(), proxy.getPort(), auth);
    }

    private void initRepoSystemAndManager() {
        final MavenFactory factory = configureMavenFactory();
        if (repoSystem == null) {
            repoSystem = factory.getRepositorySystem();
        }
        if (remoteRepoManager == null) {
            remoteRepoManager = factory.getContainer().requireBean(RemoteRepositoryManager.class);
        }
        if (settingsDecrypter == null) {
            settingsDecrypter = factory.getContainer().requireBean(SettingsDecrypter.class);
        }
    }

    protected MavenFactory configureMavenFactory() {
        return MavenFactory.create(RepositorySystem.class.getClassLoader(), builder -> {
            for (var pkg : includeSisuBeanPackages) {
                builder.includePackage(pkg);
            }
            for (var pkg : excludeSisuBeanPackages) {
                builder.excludePackage(pkg);
            }
            builder.addBean(ModelBuilder.class)
                    .setSupplier(scope -> new MavenModelBuilder(BootstrapMavenContext.this))
                    .setPriority(100).build();
        });
    }

    private static String getUserAgent() {
        return "Apache-Maven/" + getMavenVersion() + " (Java " + PropertyUtils.getProperty("java.version") + "; "
                + PropertyUtils.getProperty("os.name") + " " + PropertyUtils.getProperty("os.version") + ")";
    }

    private static String getMavenVersion() {
        final String mvnVersion = PropertyUtils.getProperty("maven.version");
        if (mvnVersion != null) {
            return mvnVersion;
        }
        final Properties props = new Properties();
        try (InputStream is = BootstrapMavenContext.class.getResourceAsStream(
                "/META-INF/maven/org.apache.maven/maven-core/pom.properties")) {
            if (is != null) {
                props.load(is);
            }
        } catch (IOException e) {
            log.debug("Failed to read Maven version", e);
        }
        return props.getProperty("version", "unknown-version");
    }

    public boolean isCurrentProjectExists() {
        return currentProjectExists == null
                ? currentProjectExists = getCurrentProjectPomOrNull() != null
                : currentProjectExists;
    }

    public Path getCurrentProjectPomOrNull() {
        if (currentPom != null
                || currentProjectExists != null && !currentProjectExists) {
            return currentPom;
        }
        final Path pom = resolveCurrentPom();
        return currentPom = (currentProjectExists = pom != null) ? pom : null;
    }

    private Path resolveCurrentPom() {
        Path alternatePom = null;
        // explicitly set absolute path has a priority
        if (alternatePomName != null) {
            alternatePom = Paths.get(alternatePomName);
            if (alternatePom.isAbsolute()) {
                return pomXmlOrNull(alternatePom);
            }
        }

        if (alternatePom == null) {
            // check whether an alternate pom was provided as a CLI arg
            final String cliPomName = getCliOptions().getOptionValue(BootstrapMavenOptions.ALTERNATE_POM_FILE);
            if (cliPomName != null) {
                alternatePom = Path.of(cliPomName);
            }
        }

        final String basedirProp = PropertyUtils.getProperty(BASEDIR);
        if (basedirProp != null) {
            // this is the actual current project dir
            return getPomForDirOrNull(Path.of(basedirProp), alternatePom);
        }

        // we are not in the context of a Maven build
        if (alternatePom != null && alternatePom.isAbsolute()) {
            return pomXmlOrNull(alternatePom);
        }

        // trying the current dir as the basedir
        final Path basedir = Path.of("").normalize().toAbsolutePath();
        if (alternatePom != null) {
            return pomXmlOrNull(basedir.resolve(alternatePom));
        }
        final Path pom = basedir.resolve(LocalProject.POM_XML);
        return Files.exists(pom) ? pom : null;
    }

    static Path getPomForDirOrNull(final Path basedir, Path alternatePom) {
        if (alternatePom != null) {
            if (alternatePom.getNameCount() == 1 || basedir.endsWith(alternatePom.getParent())) {
                if (alternatePom.isAbsolute()) {
                    // if the basedir matches the parent of the alternate pom, it's the alternate pom
                    return alternatePom;
                }
                // if the basedir ends with the alternate POM parent relative path, we can try it as the base dir
                final Path pom = basedir.resolve(alternatePom.getFileName());
                if (Files.exists(pom)) {
                    return pom;
                }
            }
        }

        final Path pom = basedir.resolve(LocalProject.POM_XML);
        if (Files.exists(pom)) {
            return pom;
        }

        // give up
        return null;
    }

    private static Path pomXmlOrNull(Path path) {
        if (Files.isDirectory(path)) {
            path = path.resolve(LocalProject.POM_XML);
        }
        return Files.exists(path) ? path.normalize() : null;
    }

    public Path getCurrentProjectBaseDir() {
        if (currentProject != null) {
            return currentProject.getDir();
        }
        final String basedirProp = PropertyUtils.getProperty(BASEDIR);
        return basedirProp == null ? Path.of("").normalize().toAbsolutePath() : Paths.get(basedirProp);
    }

    public Path getRootProjectBaseDir() {
        // originally we checked for MAVEN_PROJECTBASEDIR which is set by the mvn script
        // and points to the first parent containing '.mvn' dir but it's not consistent
        // with how Maven discovers the workspace and also created issues testing the Quarkus platform
        // due to its specific FS layout
        return rootProjectDir;
    }

    public boolean isPreferPomsFromWorkspace() {
        return preferPomsFromWorkspace;
    }

    public boolean isEffectiveModelBuilder() {
        if (effectiveModelBuilder == null) {
            final String s = PropertyUtils.getProperty(EFFECTIVE_MODEL_BUILDER_PROP);
            effectiveModelBuilder = s == null ? false : Boolean.parseBoolean(s);
        }
        return effectiveModelBuilder;
    }

    public boolean isWorkspaceModuleParentHierarchy() {
        return wsModuleParentHierarchy == null ? false : wsModuleParentHierarchy;
    }

    static final String BOOTSTRAP_MAVEN_REPOS = "BOOTSTRAP_MAVEN_REPOS";
    static final String BOOTSTRAP_MAVEN_REPO_PREFIX = "BOOTSTRAP_MAVEN_REPO_";
    static final String URL_SUFFIX = "_URL";
    static final String SNAPSHOT_SUFFIX = "_SNAPSHOT";
    static final String RELEASE_SUFFIX = "_RELEASE";

    static void readMavenReposFromEnv(List repos, Map env) {
        final String envRepos = env.get(BOOTSTRAP_MAVEN_REPOS);
        if (envRepos == null || envRepos.isBlank()) {
            return;
        }
        final StringBuilder buf = new StringBuilder();
        for (int i = 0; i < envRepos.length(); ++i) {
            final char c = envRepos.charAt(i);
            if (c == ',') {
                initMavenRepoFromEnv(envRepos, buf.toString(), env, repos);
                buf.setLength(0);
            } else {
                buf.append(c);
            }
        }
        if (buf.length() > 0) {
            initMavenRepoFromEnv(envRepos, buf.toString(), env, repos);
        }
    }

    private static void initMavenRepoFromEnv(String envRepos, String repoId, Map env,
            List repos) {
        final String envRepoId = toEnvVarPart(repoId);
        String repoUrl = null;
        boolean snapshot = true;
        boolean release = true;
        for (Map.Entry envvar : env.entrySet()) {
            final String varName = envvar.getKey();
            if (varName.startsWith(BOOTSTRAP_MAVEN_REPO_PREFIX)
                    && varName.regionMatches(BOOTSTRAP_MAVEN_REPO_PREFIX.length(), envRepoId, 0, envRepoId.length())) {
                if (isMavenRepoEnvVarOption(varName, repoId, URL_SUFFIX)) {
                    repoUrl = envvar.getValue();
                } else if (isMavenRepoEnvVarOption(varName, repoId, SNAPSHOT_SUFFIX)) {
                    snapshot = Boolean.parseBoolean(envvar.getValue());
                } else if (isMavenRepoEnvVarOption(varName, repoId, RELEASE_SUFFIX)) {
                    release = Boolean.parseBoolean(envvar.getValue());
                }
            }
        }
        if (repoUrl == null || repoUrl.isBlank()) {
            log.warn("Maven repository " + repoId + " listed in " + BOOTSTRAP_MAVEN_REPOS + "=" + envRepos
                    + " was ignored because the corresponding " + BOOTSTRAP_MAVEN_REPO_PREFIX + envRepoId + URL_SUFFIX
                    + " is missing");
        } else {
            final RemoteRepository.Builder repoBuilder = new RemoteRepository.Builder(repoId, "default", repoUrl);
            if (!release) {
                repoBuilder.setReleasePolicy(new RepositoryPolicy(false, RepositoryPolicy.UPDATE_POLICY_DAILY,
                        RepositoryPolicy.CHECKSUM_POLICY_WARN));
            }
            if (!snapshot) {
                repoBuilder.setSnapshotPolicy(new RepositoryPolicy(false, RepositoryPolicy.UPDATE_POLICY_DAILY,
                        RepositoryPolicy.CHECKSUM_POLICY_WARN));
            }
            repos.add(repoBuilder.build());
        }
    }

    private static String toEnvVarPart(String s) {
        final StringBuilder buf = new StringBuilder(s.length());
        for (int i = 0; i < s.length(); ++i) {
            final char c = s.charAt(i);
            if (c == '.' || c == '-') {
                buf.append('_');
            } else {
                buf.append(Character.toUpperCase(c));
            }
        }
        return buf.toString();
    }

    private static boolean isMavenRepoEnvVarOption(String varName, String repoId, String option) {
        return varName.length() == BOOTSTRAP_MAVEN_REPO_PREFIX.length() + repoId.length() + option.length()
                && varName.endsWith(option);
    }

    private static FileProfileActivator createFileProfileActivator() throws BootstrapMavenException {
        var activator = new FileProfileActivator();
        var translator = new DefaultPathTranslator();
        try {
            var pathInterpolator = new ProfileActivationFilePathInterpolator();
            pathInterpolator.setPathTranslator(translator);
            activator.setProfileActivationFilePathInterpolator(pathInterpolator);
        } catch (NoClassDefFoundError e) {
            // ProfileActivationFilePathInterpolator not found; Maven < 3.8.5 (https://github.com/apache/maven/pull/649)
            try {
                activator.getClass().getMethod("setPathTranslator", org.apache.maven.model.path.PathTranslator.class)
                        .invoke(activator, translator);
            } catch (ReflectiveOperationException reflectionExc) {
                throw new BootstrapMavenException(
                        "Failed to set up DefaultPathTranslator for Maven < 3.8.5 DefaultPathTranslator",
                        reflectionExc);
            }
        }
        return activator;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy