<commit_before>/*
 * The MIT License
 *
 * Copyright (c) 2016, CloudBees, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package org.jenkinsci.plugins.pipeline.maven;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.annotation.CheckForNull;

import org.apache.commons.lang.StringUtils;
import org.jenkinsci.lib.configprovider.model.Config;
import org.jenkinsci.plugins.configfiles.maven.MavenSettingsConfig;
import org.jenkinsci.plugins.configfiles.maven.security.CredentialsHelper;
import org.jenkinsci.plugins.configfiles.maven.security.ServerCredentialMapping;
import org.jenkinsci.plugins.workflow.steps.AbstractStepExecutionImpl;
import org.jenkinsci.plugins.workflow.steps.BodyExecution;
import org.jenkinsci.plugins.workflow.steps.BodyExecutionCallback;
import org.jenkinsci.plugins.workflow.steps.EnvironmentExpander;
import org.jenkinsci.plugins.workflow.steps.StepContext;
import org.jenkinsci.plugins.workflow.steps.StepContextParameter;

import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials;
import com.google.inject.Inject;

import hudson.AbortException;
import hudson.EnvVars;
import hudson.FilePath;
import hudson.Launcher;
import hudson.Launcher.ProcStarter;
import hudson.Proc;
import hudson.Util;
import hudson.console.ConsoleLogFilter;
import hudson.model.AbstractBuild;
import hudson.model.Computer;
import hudson.model.JDK;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.slaves.WorkspaceList;
import hudson.tasks.Maven;
import hudson.tasks.Maven.MavenInstallation;
import hudson.tasks._maven.MavenConsoleAnnotator;
import hudson.util.ArgumentListBuilder;
import jenkins.model.Jenkins;

public class WithMavenStepExecution extends AbstractStepExecutionImpl {

    private static final long serialVersionUID = 1L;
    private static final String MAVEN_HOME = "MAVEN_HOME";
    private static final String M2_HOME = "M2_HOME";
    private static final String MAVEN_OPTS = "MAVEN_OPTS";

    private static final Logger LOGGER = Logger.getLogger(WithMavenStepExecution.class.getName());

    @Inject(optional = true)
    private transient WithMavenStep step;
    @StepContextParameter
    private transient TaskListener listener;
    @StepContextParameter
    private transient FilePath ws;
    @StepContextParameter
    private transient Launcher launcher;
    @StepContextParameter
    private transient EnvVars env;
    private transient EnvVars envOverride;
    @StepContextParameter
    private transient Run<?, ?> build;

    private transient Computer computer;
    private transient FilePath tempBinDir;
    private transient BodyExecution body;

    /**
     * Inidicates if running on docker with <tt>docker.image()</tt>
     */
    private boolean withContainer;

    private transient PrintStream console;

    @Override
    public boolean start() throws Exception {
        envOverride = new EnvVars();
        console = listener.getLogger();

        if (LOGGER.isLoggable(Level.FINE)) {
            LOGGER.fine("Maven Installation:" + step.getMavenInstallation());
            LOGGER.fine("Jdk:" + step.getJdk());
            LOGGER.fine("MavenOpts:" + step.getMavenOpts());
            LOGGER.fine("Settings Config:" + step.getMavenSettingsConfig());
            LOGGER.fine("Settings FilePath:" + step.getMavenSettingsFilePath());
        }

        getComputer();

        withContainer = detectWithContainer();

        setupJDK();
        setupMaven();

        MavenConsoleFilter consFilter = new MavenConsoleFilter(computer.getDefaultCharset().name());
        EnvironmentExpander envEx = EnvironmentExpander.merge(getContext().get(EnvironmentExpander.class), new ExpanderImpl(envOverride));

        body = getContext().newBodyInvoker().withContexts(envEx, consFilter).withCallback(new Callback(tempBinDir)).start();

        return false;
    }

    /**
     * Detects if this step is running inside <tt>docker.image()</tt>
     * 
     * This has the following implications:
     * <li>Tool intallers do no work, as they install in the host, see:
     * https://issues.jenkins-ci.org/browse/JENKINS-36159
     * <li>Environment variables do not apply because they belong either to the master or the agent, but not to the
     * container running the <tt>sh</tt> command for maven This is due to the fact that <tt>docker.image()</tt> all it
     * does is decorate the launcher and excute the command with a <tt>docker run</tt> which means that the inherited
     * environment from the OS will be totally different eg: MAVEN_HOME, JAVA_HOME, PATH, etc.
     * 
     * @see <a href=
     * "https://github.com/jenkinsci/docker-workflow-plugin/blob/master/src/main/java/org/jenkinsci/plugins/docker/workflow/WithContainerStep.java#L213">
     * WithContainerStep</a>
     * @return true if running inside docker container with <tt>docker.image()</tt>
     */
    private boolean detectWithContainer() {
        Launcher launcher1 = launcher;
        while (launcher1 instanceof Launcher.DecoratedLauncher) {
            if (launcher1.getClass().getName().contains("WithContainerStep")) {
                LOGGER.fine("Step running within docker.image()");
                return true;
            }
            launcher1 = ((Launcher.DecoratedLauncher) launcher1).getInner();
        }
        return false;
    }

    /**
     * Setup the selected JDK. If none is provided nothing is done.
     */
    private void setupJDK() throws AbortException, IOException, InterruptedException {
        JDK jdk;
        if (!StringUtils.isEmpty(step.getJdk())) {
            if (!withContainer) {
                jdk = Jenkins.getInstance().getJDK(step.getJdk());
                if (jdk == null) {
                    throw new AbortException("Could not find the JDK: " + step.getJdk() + ". Make sure it is configured on the Global Tool Configuration page");
                }
                jdk = jdk.forNode(computer.getNode(), listener).forEnvironment(env);
                jdk.buildEnvVars(envOverride);
            } else { // see #detectWithContainer()
                LOGGER.fine("Ignoring JDK Installation parameter: " + step.getJdk());
                console.println(
                        "WARNING: Step running within docker.image() tool installations are not available see https://issues.jenkins-ci.org/browse/JENKINS-36159. You have specified a JDK installation, which will be ignored.");
            }
        }
    }

    private void setupMaven() throws AbortException, IOException, InterruptedException {
        String mvnExecPath;

        mvnExecPath = obtainMavenExec();

        // Temp dir with the wrapper that will be prepended to the path
        tempBinDir = tempDir(ws).child("withMaven" + Util.getDigestOf(UUID.randomUUID().toString()).substring(0, 8));
        tempBinDir.mkdirs();
        // set the path to our script
        envOverride.put("PATH+MAVEN", tempBinDir.getRemote());

        LOGGER.fine("Using temp dir:" + tempBinDir.getRemote());

        if (mvnExecPath == null) {
            throw new AbortException("Couldn\u2019t find any maven executable");
        }

        FilePath mvnExec = new FilePath(ws.getChannel(), mvnExecPath);
        String content = mavenWrapperContent(mvnExec, setupSettingFile(), setupMavenLocalRepo());

        createWrapperScript(tempBinDir, mvnExec.getName(), content);

        // Maven Ops
        if (!StringUtils.isEmpty(step.getMavenOpts())) {
            String mavenOpts = envOverride.expand(env.expand(step.getMavenOpts()));

            String mavenOpsOriginal = env.get(MAVEN_OPTS);
            if (mavenOpsOriginal != null) {
                mavenOpts = mavenOpts + " " + mavenOpsOriginal;
            }
            envOverride.put(MAVEN_OPTS, mavenOpts.replaceAll("[\t\r\n]+", " "));
        }

        // just as a precaution
        // see http://maven.apache.org/continuum/faqs.html#how-does-continuum-detect-a-successful-build
        envOverride.put("MAVEN_TERMINATE_CMD", "on");
    }

    private String obtainMavenExec() throws AbortException, IOException, InterruptedException {
        MavenInstallation mi = null;
        String mvnExecPath = null;

        LOGGER.fine("Setting up maven");

        String mavenName = step.getMavenInstallation();
        if (withContainer && !StringUtils.isEmpty(mavenName)) {
            console.println(
                    "WARNING: Step running within docker.image() tool installations are not available see https://issues.jenkins-ci.org/browse/JENKINS-36159. You have specified a Maven installation, which will be ignored.");
            LOGGER.fine("Ignoring Maven Installation parameter: " + mavenName);
        }

        if (!withContainer) {
            LOGGER.fine("Maven Installation parameter: " + mavenName);
            if (!StringUtils.isEmpty(mavenName)) {
                for (MavenInstallation i : getMavenInstallations()) {
                    if (mavenName != null && mavenName.equals(i.getName())) {
                        mi = i;
                        LOGGER.fine("Found maven installation on " + mi.getHome());
                        break;
                    }
                }
                if (mi == null) {
                    throw new AbortException("Could not find '" + mavenName + "' maven installation.");
                }
            } else {
                LOGGER.fine("Trying first installation from Maven Installations...");
                MavenInstallation[] installations = getMavenInstallations();
                if (installations.length > 0) {
                    mi = installations[0];
                }
            }
        }

        if (mi != null) {
            console.println("Using Maven Installation " + mi.getName());
            mi = mi.forNode(computer.getNode(), listener).forEnvironment(env);
            mi.buildEnvVars(envOverride);<commit_msg>If no maven install is selected, always fall back to system config.<commit_after>/*
 * The MIT License
 *
 * Copyright (c) 2016, CloudBees, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package org.jenkinsci.plugins.pipeline.maven;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.annotation.CheckForNull;

import org.apache.commons.lang.StringUtils;
import org.jenkinsci.lib.configprovider.model.Config;
import org.jenkinsci.plugins.configfiles.maven.MavenSettingsConfig;
import org.jenkinsci.plugins.configfiles.maven.security.CredentialsHelper;
import org.jenkinsci.plugins.configfiles.maven.security.ServerCredentialMapping;
import org.jenkinsci.plugins.workflow.steps.AbstractStepExecutionImpl;
import org.jenkinsci.plugins.workflow.steps.BodyExecution;
import org.jenkinsci.plugins.workflow.steps.BodyExecutionCallback;
import org.jenkinsci.plugins.workflow.steps.EnvironmentExpander;
import org.jenkinsci.plugins.workflow.steps.StepContext;
import org.jenkinsci.plugins.workflow.steps.StepContextParameter;

import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials;
import com.google.inject.Inject;

import hudson.AbortException;
import hudson.EnvVars;
import hudson.FilePath;
import hudson.Launcher;
import hudson.Launcher.ProcStarter;
import hudson.Proc;
import hudson.Util;
import hudson.console.ConsoleLogFilter;
import hudson.model.AbstractBuild;
import hudson.model.Computer;
import hudson.model.JDK;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.slaves.WorkspaceList;
import hudson.tasks.Maven;
import hudson.tasks.Maven.MavenInstallation;
import hudson.tasks._maven.MavenConsoleAnnotator;
import hudson.util.ArgumentListBuilder;
import jenkins.model.Jenkins;

public class WithMavenStepExecution extends AbstractStepExecutionImpl {

    private static final long serialVersionUID = 1L;
    private static final String MAVEN_HOME = "MAVEN_HOME";
    private static final String M2_HOME = "M2_HOME";
    private static final String MAVEN_OPTS = "MAVEN_OPTS";

    private static final Logger LOGGER = Logger.getLogger(WithMavenStepExecution.class.getName());

    @Inject(optional = true)
    private transient WithMavenStep step;
    @StepContextParameter
    private transient TaskListener listener;
    @StepContextParameter
    private transient FilePath ws;
    @StepContextParameter
    private transient Launcher launcher;
    @StepContextParameter
    private transient EnvVars env;
    private transient EnvVars envOverride;
    @StepContextParameter
    private transient Run<?, ?> build;

    private transient Computer computer;
    private transient FilePath tempBinDir;
    private transient BodyExecution body;

    /**
     * Inidicates if running on docker with <tt>docker.image()</tt>
     */
    private boolean withContainer;

    private transient PrintStream console;

    @Override
    public boolean start() throws Exception {
        envOverride = new EnvVars();
        console = listener.getLogger();

        if (LOGGER.isLoggable(Level.FINE)) {
            LOGGER.fine("Maven Installation:" + step.getMavenInstallation());
            LOGGER.fine("Jdk:" + step.getJdk());
            LOGGER.fine("MavenOpts:" + step.getMavenOpts());
            LOGGER.fine("Settings Config:" + step.getMavenSettingsConfig());
            LOGGER.fine("Settings FilePath:" + step.getMavenSettingsFilePath());
        }

        getComputer();

        withContainer = detectWithContainer();

        setupJDK();
        setupMaven();

        MavenConsoleFilter consFilter = new MavenConsoleFilter(computer.getDefaultCharset().name());
        EnvironmentExpander envEx = EnvironmentExpander.merge(getContext().get(EnvironmentExpander.class), new ExpanderImpl(envOverride));

        body = getContext().newBodyInvoker().withContexts(envEx, consFilter).withCallback(new Callback(tempBinDir)).start();

        return false;
    }

    /**
     * Detects if this step is running inside <tt>docker.image()</tt>
     * 
     * This has the following implications:
     * <li>Tool intallers do no work, as they install in the host, see:
     * https://issues.jenkins-ci.org/browse/JENKINS-36159
     * <li>Environment variables do not apply because they belong either to the master or the agent, but not to the
     * container running the <tt>sh</tt> command for maven This is due to the fact that <tt>docker.image()</tt> all it
     * does is decorate the launcher and excute the command with a <tt>docker run</tt> which means that the inherited
     * environment from the OS will be totally different eg: MAVEN_HOME, JAVA_HOME, PATH, etc.
     * 
     * @see <a href=
     * "https://github.com/jenkinsci/docker-workflow-plugin/blob/master/src/main/java/org/jenkinsci/plugins/docker/workflow/WithContainerStep.java#L213">
     * WithContainerStep</a>
     * @return true if running inside docker container with <tt>docker.image()</tt>
     */
    private boolean detectWithContainer() {
        Launcher launcher1 = launcher;
        while (launcher1 instanceof Launcher.DecoratedLauncher) {
            if (launcher1.getClass().getName().contains("WithContainerStep")) {
                LOGGER.fine("Step running within docker.image()");
                return true;
            }
            launcher1 = ((Launcher.DecoratedLauncher) launcher1).getInner();
        }
        return false;
    }

    /**
     * Setup the selected JDK. If none is provided nothing is done.
     */
    private void setupJDK() throws AbortException, IOException, InterruptedException {
        JDK jdk;
        if (!StringUtils.isEmpty(step.getJdk())) {
            if (!withContainer) {
                jdk = Jenkins.getInstance().getJDK(step.getJdk());
                if (jdk == null) {
                    throw new AbortException("Could not find the JDK: " + step.getJdk() + ". Make sure it is configured on the Global Tool Configuration page");
                }
                jdk = jdk.forNode(computer.getNode(), listener).forEnvironment(env);
                jdk.buildEnvVars(envOverride);
            } else { // see #detectWithContainer()
                LOGGER.fine("Ignoring JDK Installation parameter: " + step.getJdk());
                console.println(
                        "WARNING: Step running within docker.image() tool installations are not available see https://issues.jenkins-ci.org/browse/JENKINS-36159. You have specified a JDK installation, which will be ignored.");
            }
        }
    }

    private void setupMaven() throws AbortException, IOException, InterruptedException {
        String mvnExecPath;

        mvnExecPath = obtainMavenExec();

        // Temp dir with the wrapper that will be prepended to the path
        tempBinDir = tempDir(ws).child("withMaven" + Util.getDigestOf(UUID.randomUUID().toString()).substring(0, 8));
        tempBinDir.mkdirs();
        // set the path to our script
        envOverride.put("PATH+MAVEN", tempBinDir.getRemote());

        LOGGER.fine("Using temp dir:" + tempBinDir.getRemote());

        if (mvnExecPath == null) {
            throw new AbortException("Couldn\u2019t find any maven executable");
        }

        FilePath mvnExec = new FilePath(ws.getChannel(), mvnExecPath);
        String content = mavenWrapperContent(mvnExec, setupSettingFile(), setupMavenLocalRepo());

        createWrapperScript(tempBinDir, mvnExec.getName(), content);

        // Maven Ops
        if (!StringUtils.isEmpty(step.getMavenOpts())) {
            String mavenOpts = envOverride.expand(env.expand(step.getMavenOpts()));

            String mavenOpsOriginal = env.get(MAVEN_OPTS);
            if (mavenOpsOriginal != null) {
                mavenOpts = mavenOpts + " " + mavenOpsOriginal;
            }
            envOverride.put(MAVEN_OPTS, mavenOpts.replaceAll("[\t\r\n]+", " "));
        }

        // just as a precaution
        // see http://maven.apache.org/continuum/faqs.html#how-does-continuum-detect-a-successful-build
        envOverride.put("MAVEN_TERMINATE_CMD", "on");
    }

    private String obtainMavenExec() throws AbortException, IOException, InterruptedException {
        MavenInstallation mi = null;
        String mvnExecPath = null;

        LOGGER.fine("Setting up maven");

        String mavenName = step.getMavenInstallation();

        if (!StringUtils.isEmpty(mavenName)) {
            if (!withContainer) {
                LOGGER.fine("Maven Installation parameter: " + mavenName);
                for (MavenInstallation i : getMavenInstallations()) {
                    if (mavenName != null && mavenName.equals(i.getName())) {
                        mi = i;
                        LOGGER.fine("Found maven installation on " + mi.getHome());
                        break;
                    }
                }
                if (mi == null) {
                    throw new AbortException("Could not find '" + mavenName + "' maven installation.");
                }
            } else {
                console.println(
                        "WARNING: Step running within docker.image() tool installations are not available see https://issues.jenkins-ci.org/browse/JENKINS-36159. You have specified a Maven installation, which will be ignored.");
                LOGGER.fine("Ignoring Maven Installation parameter: " + mavenName);
            }
        }

        if (mi != null) {
            console.println("Using Maven Installation " + mi.getName());
            mi = mi.forNode(computer.getNode(), listener).forEnvironment(env);
            mi.buildEnvVars(envOverride);
{'fraction_non_alphanumeric': 0.09774199352284994, 'fraction_numerical': 0.003328535444404462, 'mean_word_length': 3.017528008673654, 'pattern_counts': {'":': 0, '<': 37, '<?xml version=': 0, '>': 38, 'https://': 8, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '832913', 'n_tokens_mistral': 6331, 'n_tokens_neox': 5970, 'n_words': 1641}