blob: 4f07e4c3654720d5c87632103b482e7492ec77e8 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.netbeans.modules.maven.execute;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import javax.swing.AbstractAction;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.model.Prerequisites;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.cli.CommandLineUtils;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.netbeans.api.extexecution.base.ExplicitProcessParameters;
import org.netbeans.api.extexecution.base.Processes;
import org.netbeans.api.extexecution.startup.StartupExtender;
import org.netbeans.api.java.platform.JavaPlatform;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.project.Project;
import org.netbeans.modules.maven.NbMavenProjectImpl;
import org.netbeans.modules.maven.api.Constants;
import org.netbeans.modules.maven.api.FileUtilities;
import org.netbeans.modules.maven.api.NbMavenProject;
import org.netbeans.modules.maven.api.PluginPropertyUtils;
import org.netbeans.modules.maven.api.execute.ActiveJ2SEPlatformProvider;
import org.netbeans.modules.maven.api.execute.ExecutionContext;
import org.netbeans.modules.maven.api.execute.ExecutionResultChecker;
import org.netbeans.modules.maven.api.execute.LateBoundPrerequisitesChecker;
import org.netbeans.modules.maven.api.execute.RunConfig;
import org.netbeans.modules.maven.api.execute.RunUtils;
import org.netbeans.modules.maven.cos.CosChecker;
import org.netbeans.modules.maven.embedder.EmbedderFactory;
import org.netbeans.modules.maven.execute.cmd.Constructor;
import org.netbeans.modules.maven.execute.cmd.ShellConstructor;
import org.netbeans.modules.maven.indexer.api.RepositoryIndexer;
import org.netbeans.modules.maven.indexer.api.RepositoryPreferences;
import org.netbeans.modules.maven.options.MavenSettings;
import org.netbeans.spi.project.ui.support.BuildExecutionSupport;
import org.openide.LifecycleManager;
import org.openide.awt.HtmlBrowser;
import org.openide.execution.ExecutionEngine;
import org.openide.execution.ExecutorTask;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.modules.InstalledFileLocator;
import org.openide.modules.Places;
import org.openide.modules.SpecificationVersion;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
import org.openide.util.RequestProcessor;
import org.openide.util.Task;
import org.openide.util.TaskListener;
import org.openide.util.Utilities;
import org.openide.windows.IOColorLines;
import org.openide.windows.IOColors;
import org.openide.windows.InputOutput;
import org.openide.windows.OutputEvent;
import org.openide.windows.OutputListener;
/**
* support for executing maven, externally on the command line.
* <b>Since 2/1.144</b>, the {@link LateBoundPrerequisitesChecker} registered in Maven projects for JAR packaging by default supports
* {@link ExplicitProcessParameters} API. The caller of the execute-type action can request to append or replace VM or user
* application parameters. The parameters recorded in the POM.xml or NetBeans action mappings are augmented according to that
* instructions:
* <ul>
* <li><b>launcherArgs</b> are mapped to VM arguments (precede main class name)
* <li><b>args</b> are mapped to user application arguments (after main class name)
* </ul>
* VM parameters injected by {@link StartupExtender} API are not affected by this feature.
* <p>
* Example use:
* {@codesnippet MavenExecutionTestBase#samplePassAdditionalVMargs}
* The example will <b>append</b> <code>-DvmArg2=2</code> to VM arguments and <b>replaces</b> all user
* program arguments with <code>"paramY"</code>. Append mode can be controlled using {@link ExplicitProcessParameters.Builder#appendArgs} or
* {@link ExplicitProcessParameters.Builder#appendPriorityArgs}.
*
* @author Milos Kleint (mkleint@codehaus.org)
* @author Svata Dedic (svatopluk.dedic@gmail.com)
*/
public class MavenCommandLineExecutor extends AbstractMavenExecutor {
static final String ENV_PREFIX = "Env."; //NOI18N
static final String INTERNAL_PREFIX = "NbIde."; //NOI18N
static final String ENV_JAVAHOME = "Env.JAVA_HOME"; //NOI18N
private static final String KEY_UUID = "NB_EXEC_MAVEN_PROCESS_UUID"; //NOI18N
private static final String NETBEANS_MAVEN_COMMAND_LINE = "NETBEANS_MAVEN_COMMAND_LINE"; //NOI18N
private Process process;
private String processUUID;
private Process preProcess;
private String preProcessUUID;
private static final SpecificationVersion VER17 = new SpecificationVersion("1.7"); //NOI18N
private static final Logger LOGGER = Logger.getLogger(MavenCommandLineExecutor.class.getName());
private static final RequestProcessor RP = new RequestProcessor(MavenCommandLineExecutor.class.getName(),1);
private final static RequestProcessor UPDATE_INDEX_RP = new RequestProcessor(RunUtils.class.getName(), 5);
/**
* Execute maven build in NetBeans execution engine.
* Most callers should rather use {@link #run} as this variant does no (non-late-bound) prerequisite checks.
* It is mostly suitable for cases where you need full control by the caller over the config, or want to rerun a previous execution.
* @param config
* @param io <code>null</code> or InputOutput to reuse for output of the execution
* @param tc tab context to use or <code>null</code>
* @return
* @since 2.113
*/
public static ExecutorTask executeMaven(final RunConfig config, InputOutput io, TabContext tc) {
ExecuteMaven runner = Lookup.getDefault().lookup(ExecuteMaven.class);
if (runner == null) {
runner = new ExecuteMaven();
}
return runner.execute(config, io, tc);
}
/**
* Hooks for tests to mock the Maven execution.
*/
public static class ExecuteMaven {
public ExecutorTask execute(RunConfig config, InputOutput io, TabContext tc) {
LifecycleManager.getDefault().saveAll();
MavenExecutor exec = new MavenCommandLineExecutor(config, io, tc);
ExecutorTask task = ExecutionEngine.getDefault().execute(config.getTaskDisplayName(), exec, new ProxyNonSelectableInputOutput(exec.getInputOutput()));
exec.setTask(task);
task.addTaskListener(new TaskListener() {
@Override
public void taskFinished(Task t) {
MavenProject mp = config.getMavenProject();
if (mp == null) {
return;
}
final List<Artifact> arts = new ArrayList<Artifact>();
Artifact main = mp.getArtifact();
if (main != null) {
arts.add(main);
}
arts.addAll(mp.getArtifacts());
UPDATE_INDEX_RP.post(new Runnable() {
@Override
public void run() {
RepositoryIndexer.updateIndexWithArtifacts(RepositoryPreferences.getInstance().getLocalRepository(), arts);
}
});
}
});
return task;
}
}
public MavenCommandLineExecutor(RunConfig conf, InputOutput io, TabContext tc) {
super(conf, tc);
this.io = io;
}
/**
* not to be called directly.. use execute();
*/
@Override
public void run() {
synchronized (SEMAPHORE) {
if (task == null) {
try {
SEMAPHORE.wait();
} catch (InterruptedException ex) {
LOGGER.log(Level.FINE, "interrupted", ex);
}
}
}
final BeanRunConfig clonedConfig = new BeanRunConfig(this.config);
clonedConfig.reassignMavenProjectFromParent();
if (clonedConfig.getPreExecution() != null) {
final BeanRunConfig preconf = new BeanRunConfig(clonedConfig.getPreExecution());
preconf.reassignMavenProjectFromParent();
clonedConfig.setPreExecution(preconf);
}
int executionresult = -10;
final InputOutput ioput = getInputOutput();
final ProgressHandle handle = ProgressHandle.createHandle(clonedConfig.getTaskDisplayName(), this, new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
ioput.select();
}
});
actionStatesAtStart();
ExecutionContext exCon = ActionToGoalUtils.ACCESSOR.createContext(ioput, handle);
// check the prerequisites
if (clonedConfig.getProject() != null) {
Lookup.Result<LateBoundPrerequisitesChecker> result = clonedConfig.getProject().getLookup().lookupResult(LateBoundPrerequisitesChecker.class);
for (LateBoundPrerequisitesChecker elem : result.allInstances()) {
if (!elem.checkRunConfig(clonedConfig, exCon)) {
//#238360 when the check says don't execute, we still need to close the output and mark it for reuse
ioput.getOut().close();
ioput.getErr().close();
actionStatesAtFinish(null, null);
markFreeTab();
//#238360 - end
return;
}
if (clonedConfig.getPreExecution() != null) {
if (!elem.checkRunConfig(clonedConfig.getPreExecution(), exCon)) {
//#238360 when the check says don't execute, we still need to close the output and mark it for reuse
ioput.getOut().close();
ioput.getErr().close();
actionStatesAtFinish(null, null);
markFreeTab();
//#238360 - end
return;
}
}
}
}
// final Properties originalProperties = clonedConfig.getProperties();
handle.start();
processInitialMessage();
boolean isMaven3 = !isMaven2();
boolean singlethreaded = !isMultiThreaded(clonedConfig);
boolean eventSpyCompatible = isEventSpyCompatible(clonedConfig);
if (isMaven3 && singlethreaded && eventSpyCompatible) {
injectEventSpy( clonedConfig );
if (clonedConfig.getPreExecution() != null) {
injectEventSpy( (BeanRunConfig) clonedConfig.getPreExecution());
}
}
CommandLineOutputHandler out = new CommandLineOutputHandler(ioput, clonedConfig.getProject(), handle, clonedConfig, isMaven3 && singlethreaded);
try {
BuildExecutionSupport.registerRunningItem(item);
if (MavenSettings.getDefault().isAlwaysShowOutput()) {
ioput.select();
}
if (clonedConfig.getPreExecution() != null) {
ProcessBuilder builder = constructBuilder(clonedConfig.getPreExecution(), ioput);
preProcessUUID = UUID.randomUUID().toString();
builder.environment().put(KEY_UUID, preProcessUUID);
if (executeProcess(out, builder, (p) -> preProcess = p) != 0) {
return;
}
}
//debugging..
// Map<String, String> env = builder.environment();
// for (String key : env.keySet()) {
// ioput.getOut().println(key + ":" + env.get(key));
// }
ProcessBuilder builder = constructBuilder(clonedConfig, ioput);
printCoSWarning(clonedConfig, ioput);
processUUID = UUID.randomUUID().toString();
builder.environment().put(KEY_UUID, processUUID);
executionresult = executeProcess(out, builder, (p) -> process = p);
} catch (IOException x) {
if (Utilities.isWindows()) { //#153101
processIssue153101(x, ioput);
} else {
ioput.getErr().println(x.getMessage());
}
} catch (InterruptedException x) {
cancel();
} catch (ThreadDeath death) {
cancel();
throw death;
} finally {
BuildExecutionSupport.registerFinishedItem(item);
try { //defend against badly written extensions..
out.buildFinished();
if (clonedConfig.getProject() != null) {
Lookup.Result<ExecutionResultChecker> result = clonedConfig.getProject().getLookup().lookupResult(ExecutionResultChecker.class);
for (ExecutionResultChecker elem : result.allInstances()) {
elem.executionResult(clonedConfig, exCon, executionresult);
}
}
}
finally {
//MEVENIDE-623 re add original Properties
// clonedConfig.setProperties(originalProperties);
handle.finish();
ioput.getOut().close();
ioput.getErr().close();
actionStatesAtFinish(out.createResumeFromFinder(), out.getExecutionTree());
markFreeTab();
final Project prj = clonedConfig.getProject();
if (prj != null) {
NbMavenProjectImpl impl = prj.getLookup().lookup(NbMavenProjectImpl.class);
if (impl != null) {
RequestProcessor.Task reloadTask = impl.fireProjectReload();
reloadTask.waitFinished();
}
}
}
}
}
private boolean isEventSpyCompatible(final BeanRunConfig clonedConfig) {
// EventSpy cannot work on jdk < 7
if (clonedConfig.getProject() != null) {
ActiveJ2SEPlatformProvider javaprov = clonedConfig.getProject().getLookup().lookup(ActiveJ2SEPlatformProvider.class);
JavaPlatform platform = javaprov.getJavaPlatform();
return (platform.getSpecification().getVersion().compareTo(VER17) >= 0);
} else {
return true;
}
}
/**
* Overridable by tests.
*/
int executeProcess(CommandLineOutputHandler out, ProcessBuilder builder, Consumer<Process> processSetter) throws IOException, InterruptedException {
Process p = builder.start();
processSetter.accept(p);
out.setStdOut(p.getInputStream());
out.setStdIn(p.getOutputStream());
int executionresult = p.waitFor();
out.waitFor();
return executionresult;
}
private void kill(Process prcs, String uuid) {
Map<String, String> env = new HashMap<String, String>();
env.put(KEY_UUID, uuid);
Processes.killTree(prcs, env);
}
@Override
public boolean cancel() {
final Process pre = preProcess;
preProcess = null;
final Process pro = process;
process = null;
RP.post(new Runnable() {
@Override
public void run() {
if (pre != null) {
kill(pre, preProcessUUID);
}
if (pro != null) {
kill(pro, processUUID);
}
}
});
return true;
}
private static List<String> createMavenExecutionCommand(RunConfig config, Constructor base) {
List<String> toRet = new ArrayList<String>(base.construct());
if (Utilities.isUnix()) { // #198997 - defend against symlinks
File basedir = config.getExecutionDirectory();
try {
if (basedir != null && !basedir.equals(basedir.getCanonicalFile())) {
File pom = new File(basedir, "pom.xml");
if (pom.isFile()) { // #201400
toRet.add("-f");
toRet.add(pom.getAbsolutePath());
}
}
} catch (IOException x) {
LOGGER.log(Level.FINE, "Could not canonicalize " + basedir, x);
}
}
//#164234
//if maven.bat file is in space containing path, we need to quote with simple quotes.
String quote = "\"";
// the command line parameters with space in them need to be quoted and escaped to arrive
// correctly to the java runtime on windows
String escaped = "\\" + quote;
for (Map.Entry<? extends String,? extends String> entry : config.getProperties().entrySet()) {
String k = entry.getKey();
// filter out env vars AND internal properties.
if (k.startsWith(ENV_PREFIX) || k.startsWith(INTERNAL_PREFIX)) {
continue;
}
//skip envs, these get filled in later.
//#228901 since u21 we need to use cmd /c to execute on windows, quotes get escaped and when there is space in value, the value gets wrapped in quotes.
String value = (Utilities.isWindows() ? entry.getValue().replace(quote, escaped) : entry.getValue().replace(quote, "'"));
if (Utilities.isWindows() && value.endsWith("\"")) {
//#201132 property cannot end with 2 double quotes, add a space to the end after our quote to prevent the state
value = value + " ";
}
String s = "-D" + entry.getKey() + "=" + (Utilities.isWindows() && value.contains(" ") ? quote + value + quote : value);
toRet.add(s);
}
//TODO based on a property? or UI option? can this backfire?
//#224526
//execute in encoding that is based on project.build.sourceEncoding to have the output of exec:exec, surefire:test and others properly encoded.
if (config.getMavenProject() != null) {
String enc = config.getMavenProject().getProperties().getProperty(Constants.ENCODING_PROP);
if (enc != null && !enc.equals(Charset.defaultCharset().name())) {
toRet.add("-Dfile.encoding=" + enc);
}
}
if (config.isOffline() != null && config.isOffline().booleanValue()) {
toRet.add("--offline");//NOI18N
}
if (!config.isInteractive()) {
toRet.add("--batch-mode"); //NOI18N
}
if (!config.isRecursive()) {
toRet.add("--non-recursive");//NOI18N
}
if (config.isShowDebug()) {
toRet.add("--debug");//NOI18N
}
if (config.isShowError()) {
toRet.add("--errors");//NOI18N
}
if (config.isUpdateSnapshots()) {
toRet.add("--update-snapshots");//NOI18N
}
if (config.getReactorStyle() != RunConfig.ReactorStyle.NONE) {
File basedir = config.getExecutionDirectory();
MavenProject mp = config.getMavenProject();
File projdir = NbMavenProject.isErrorPlaceholder(mp) ? basedir : mp.getBasedir();
String rel = basedir != null && projdir != null ? FileUtilities.relativizeFile(basedir, projdir) : null;
if (!".".equals(rel)) {
toRet.add(config.getReactorStyle() == RunConfig.ReactorStyle.ALSO_MAKE ? "--also-make" : "--also-make-dependents");
toRet.add("--projects");
toRet.add(rel != null ? rel : mp.getGroupId() + ':' + mp.getArtifactId());
}
}
String opts = MavenSettings.getDefault().getDefaultOptions();
if (opts != null) {
try {
String[] s = CommandLineUtils.translateCommandline(opts);
for (String one : s) {
one = one.trim();
if (one.startsWith("-D")) {
//check against the config.getProperties
} else {
if (!config.isShowDebug() && (one.equals("-X") || one.equals("--debug"))) {
continue;
}
if (!config.isShowError() && (one.equals("-e") || one.equals("--errors"))) {
continue;
}
if (!config.isUpdateSnapshots() && (one.equals("--update-snapshots") || one.equals("-U"))) {
continue;
}
if (config.isInteractive() && (one.equals("--batch-mode") || one.equals("-B"))) {
continue;
}
if ((config.isOffline() != null && !config.isOffline().booleanValue()) && (one.equals("--offline") || one.equals("-o"))) {
continue;
}
}
toRet.add(one);
}
} catch (Exception ex1) {
Logger.getLogger(MavenSettings.class.getName()).log(Level.FINE, "Error parsing global options:{0}", opts);
}
}
String profiles = "";//NOI18N
for (Object profile : config.getActivatedProfiles()) {
profiles = profiles + "," + profile;//NOI18N
}
if (profiles.length() > 0) {
profiles = profiles.substring(1);
toRet.add("-P" + profiles);//NOI18N
}
for (String goal : config.getGoals()) {
toRet.add(goal);
}
return toRet;
}
private ProcessBuilder constructBuilder(final RunConfig clonedConfig, InputOutput ioput) {
File javaHome = null;
Map<String, String> envMap = new LinkedHashMap<String, String>();
for (Map.Entry<? extends String,? extends String> entry : clonedConfig.getProperties().entrySet()) {
if (entry.getKey().startsWith(ENV_PREFIX)) {
String env = entry.getKey().substring(ENV_PREFIX.length());
envMap.put(env, entry.getValue());
if (entry.getKey().equals(ENV_JAVAHOME)) {
javaHome = new File(entry.getValue());
}
}
}
if (javaHome == null) {
if (clonedConfig.getProject() != null) {
//TODO somehow use the config.getMavenProject() call rather than looking up the
// ActiveJ2SEPlatformProvider from lookup. The loaded project can be different from the executed one.
ActiveJ2SEPlatformProvider javaprov = clonedConfig.getProject().getLookup().lookup(ActiveJ2SEPlatformProvider.class);
File path;
FileObject java = javaprov.getJavaPlatform().findTool("java"); //NOI18N
if (java != null) {
Collection<FileObject> objs = javaprov.getJavaPlatform().getInstallFolders();
for (FileObject fo : objs) {
if (FileUtil.isParentOf(fo, java)) {
path = FileUtil.toFile(fo);
if (path != null) {
javaHome = path;
envMap.put(ENV_JAVAHOME.substring(ENV_PREFIX.length()), path.getAbsolutePath());
}
break;
}
}
}
}
//#151559
if (javaHome == null) {
String envJH = System.getenv("JAVA_HOME");
if (envJH != null) {
File f = new File(envJH);
if (!f.exists() || !new File(f, "bin" + File.separator + "java" + (Utilities.isWindows() ? ".exe" : "")).exists()) {
envJH = null; //#233452 ignore non existing JAVA_HOME
}
}
if (envJH == null) {
//NOI18N
javaHome = new File(System.getProperty("java.home"));
envMap.put("JAVA_HOME", javaHome.getAbsolutePath()); //NOI18N
} else {
javaHome = new File(envJH);
envMap.put("JAVA_HOME", javaHome.getAbsolutePath()); //NOI18N
}
}
}
File mavenHome = EmbedderFactory.getEffectiveMavenHome();
if (MavenSettings.getDefault().isUseBestMaven()) {
File n = guessBestMaven(clonedConfig, ioput);
if (n != null) {
mavenHome = n;
}
}
Constructor constructeur = new ShellConstructor(mavenHome);
List<String> cmdLine = createMavenExecutionCommand(clonedConfig, constructeur);
//#228901 on windows, since u21 we must use cmd /c
// the working format is ""C:\Users\mkleint\space in path\apache-maven-3.0.4\bin\mvn.bat"
//-Dexec.executable=java -Dexec.args="-jar
//C:\Users\mkleint\Documents\NetBeansProjects\JavaApplication13\dist\JavaApplication13.jar
//-Dxx=\"space path\" -Dfoo=bar" exec:exec""
if (cmdLine.get(0).equals("cmd")) {
//merge all items after cmd /c into one string and quote it.
StringBuilder sb = new StringBuilder();
Iterator<String> it = cmdLine.iterator();
//sb.append("cmd.exe /c ");
it.next(); //cmd
it.next(); //c
String m = it.next();
sb.append(m);
while (it.hasNext()) {
sb.append(" ").append(it.next());
}
// NETBEANS-3251, NETBEANS-3254:
// JDK-8221858 (non public) / CVE-2019-2958 changed the way cmd
// command lines are verified and made it "difficult" to have embedded
// quotes in it, quotes that are needed for the mvn.bat and some
// parameters of the goals being run (particularly exec:exec).
// Setting the Maven command as an environment variable and
// using the cmd.exe variables extention mechanism when launching
// the command allows to bypass the new JDK check locally without
// resorting to using the global jdk.lang.Process.allowAmbiguousCommands flag
envMap.put(NETBEANS_MAVEN_COMMAND_LINE, sb.toString());
cmdLine = Arrays.asList(new String[] {
"cmd", "/c", "%" + NETBEANS_MAVEN_COMMAND_LINE + "%"
});
}
ProcessBuilder builder = new ProcessBuilder(cmdLine);
builder.redirectErrorStream(true);
builder.directory(clonedConfig.getExecutionDirectory());
StringBuilder display = new StringBuilder("cd ").append(clonedConfig.getExecutionDirectory()).append("; "); // NOI18N
for (Map.Entry<String, String> entry : envMap.entrySet()) {
String env = entry.getKey();
String val = entry.getValue();
if ("M2_HOME".equals(env.toUpperCase(Locale.ENGLISH))) {
continue;// #191374: would prevent bin/mvn from using selected installation
}
// TODO: do we really put *all* the env vars there? maybe filter, M2_HOME and JDK_HOME?
builder.environment().put(env, val);
if (!env.equals(CosChecker.NETBEANS_PROJECT_MAPPINGS)
&& !env.equals(NETBEANS_MAVEN_COMMAND_LINE)) { //don't show to user
display.append(Utilities.escapeParameters(new String[] {env + "=" + val})).append(' '); // NOI18N
}
}
if (mavenHome != null) {
//#195039
builder.environment().put("M2_HOME", mavenHome.getAbsolutePath());
if (!mavenHome.equals(EmbedderFactory.getDefaultMavenHome())) {
//only relevant display when using the non-default maven installation.
display.append(Utilities.escapeParameters(new String[] {"M2_HOME=" + mavenHome.getAbsolutePath()})).append(' '); // NOI18N
}
}
// hide the bypass command and output the command as it used to be (before the bypass command was added)
if (envMap.containsKey(NETBEANS_MAVEN_COMMAND_LINE)) {
display.append(Utilities.escapeParameters(new String[] {"cmd", "/c", envMap.get("NETBEANS_MAVEN_COMMAND_LINE")}));
}
else {
//very hacky here.. have a way to remove
List<String> command = new ArrayList<String>(builder.command());
for (Iterator<String> it = command.iterator(); it.hasNext();) {
String s = it.next();
if (s.startsWith("-D" + CosChecker.MAVENEXTCLASSPATH + "=")) {
it.remove();
}
}
display.append(Utilities.escapeParameters(command.toArray(new String[command.size()])));
}
printGray(ioput, display.toString());
return builder;
}
private static void printGray(InputOutput io, String text) {
if (IOColorLines.isSupported(io)) {
try {
IOColorLines.println(io, text, IOColors.getColor(io, IOColors.OutputType.LOG_DEBUG));
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
} else {
io.getOut().println(text);
}
}
private void processIssue153101(IOException x, InputOutput ioput) {
//#153101
if (x.getMessage() != null && x.getMessage().contains("CreateProcess error=5")) {
System.setProperty("maven.run.cmd", "true");
LOGGER.log(Level.INFO, "Cannot create Process, next time we will run the build with 'cmd /c'", x); //NOI18N
ioput.getErr().println("Cannot execute the mvn.bat executable directly due to wrong access rights, switching to execution via 'cmd.exe /c mvn.bat'."); //NOI18N - in maven output
try {
ioput.getErr().println(" See issue http://www.netbeans.org/issues/show_bug.cgi?id=153101 for details.", new OutputListener() { //NOI18N - in maven output
@Override
public void outputLineSelected(OutputEvent ev) {}
@Override
public void outputLineCleared(OutputEvent ev) {}
@Override
public void outputLineAction(OutputEvent ev) {
try {
HtmlBrowser.URLDisplayer.getDefault().showURL(new URL("http://www.netbeans.org/issues/show_bug.cgi?id=153101")); //NOI18N - in maven output
} catch (MalformedURLException ex) {
Exceptions.printStackTrace(ex);
}
}
});
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
ioput.getErr().println(" This message will show on the next start of the IDE again, to skip it, add -J-Dmaven.run.cmd=true to your etc/netbeans.conf file in your NetBeans installation."); //NOI18N - in maven output
ioput.getErr().println("The detailed exception output is printed to the IDE's log file."); //NOI18N - in maven output
RP.post(new Runnable() {
@Override
public void run() {
RunConfig newConfig = new BeanRunConfig(config);
RunUtils.executeMaven(newConfig);
}
});
} else {
ioput.getErr().println(x.getMessage());
}
}
private void printCoSWarning(BeanRunConfig clonedConfig, InputOutput ioput) {
if (clonedConfig.getProperties().containsKey(CosChecker.ENV_NETBEANS_PROJECT_MAPPINGS)) {
printGray(ioput, "Running NetBeans Compile On Save execution. Phase execution is skipped and output directories of dependency projects (with Compile on Save turned on) will be used instead of their jar artifacts.");
if (isMaven2()) {
printGray(ioput, "WARNING: Using Maven 2.x for execution, NetBeans cannot establish links between current project and output directories of dependency projects with Compile on Save turned on. Only works with Maven 3.0+.");
}
}
if (clonedConfig.getProperties().containsKey(ModelRunConfig.EXEC_MERGED)) {
printGray(ioput, "\nDefault '" + clonedConfig.getActionName() + "' action exec.args merged with maven-exec-plugin arguments declared in pom.xml.");
}
}
boolean isMaven2() {
File mvnHome = EmbedderFactory.getEffectiveMavenHome();
String version = MavenSettings.getCommandLineMavenVersion(mvnHome);
return version != null && version.startsWith("2");
}
private void injectEventSpy(final BeanRunConfig clonedConfig) {
//TEMP
String mavenPath = clonedConfig.getProperties().get(CosChecker.MAVENEXTCLASSPATH);
File jar = InstalledFileLocator.getDefault().locate("maven-nblib/netbeans-eventspy.jar", "org.netbeans.modules.maven", false);
if (jar == null) {
return;
}
if (mavenPath == null) {
mavenPath = "";
} else {
String delimiter = Utilities.isWindows() ? ";" : ":";
if(mavenPath.contains(jar + delimiter)) {
// invoked by output view > rerun? see also issue #249971
return;
}
mavenPath = delimiter + mavenPath;
}
//netbeans-eventspy.jar comes first on classpath
mavenPath = jar.getAbsolutePath() + mavenPath;
clonedConfig.setProperty(CosChecker.MAVENEXTCLASSPATH, mavenPath);
}
private boolean isMultiThreaded(BeanRunConfig clonedConfig) {
String list = MavenSettings.getDefault().getDefaultOptions();
for (String s : clonedConfig.getGoals()) {
list = list + " " + s;
}
if (clonedConfig.getPreExecution() != null) {
for (String s : clonedConfig.getPreExecution().getGoals()) {
list = list + " " + s;
}
}
return list.contains("-T") || list.contains("--threads");
}
private File guessBestMaven(RunConfig clonedConfig, InputOutput ioput) {
MavenProject mp = clonedConfig.getMavenProject();
if (mp != null) {
if (mp.getPrerequisites() != null) {
Prerequisites pp = mp.getPrerequisites();
String ver = pp.getMaven();
if (ver != null) {
return checkAvailability(ver, null, ioput);
}
}
String value = PluginPropertyUtils.getPluginPropertyBuildable(clonedConfig.getMavenProject(), Constants.GROUP_APACHE_PLUGINS, "maven-enforcer-plugin", "enforce", new PluginPropertyUtils.ConfigurationBuilder<String>() {
@Override
public String build(Xpp3Dom configRoot, ExpressionEvaluator eval) {
if(configRoot != null) {
Xpp3Dom rules = configRoot.getChild("rules");
if (rules != null) {
Xpp3Dom rmv = rules.getChild("requireMavenVersion");
if (rmv != null) {
Xpp3Dom v = rmv.getChild("version");
if (v != null) {
return v.getValue();
}
}
}
}
return null;
}
});
if (value != null) {
if (value.contains("[") || value.contains("(")) {
try {
VersionRange vr = VersionRange.createFromVersionSpec(value);
return checkAvailability(null, vr, ioput);
} catch (InvalidVersionSpecificationException ex) {
Exceptions.printStackTrace(ex);
}
} else {
return checkAvailability(value, null, ioput);
}
}
}
return null;
}
private File checkAvailability(String ver, VersionRange vr, InputOutput ioput) {
ArrayList<String> all = new ArrayList(MavenSettings.getDefault().getUserDefinedMavenRuntimes());
//TODO this could be slow? but is it slower than downloading stuff?
//is there a faster way? or can we somehow log the findings after first attempt?
DefaultArtifactVersion candidate = null;
File candidateFile = null;
for (String one : all) {
File f = FileUtil.normalizeFile(new File(one));
String oneVersion = MavenSettings.getCommandLineMavenVersion(f);
if(oneVersion == null) {
continue;
}
if (ver != null && ver.equals(oneVersion)) {
return f;
}
DefaultArtifactVersion dav = new DefaultArtifactVersion(oneVersion);
if (vr != null && vr.containsVersion(dav)) {
if (candidate != null) {
if (candidate.compareTo(dav) < 0) {
candidate = dav;
candidateFile = f;
}
} else {
candidate = new DefaultArtifactVersion(oneVersion);
candidateFile = f;
}
}
}
if (candidateFile != null) {
return candidateFile;
} else if (vr != null) {
ver = vr.getRecommendedVersion() != null ? vr.getRecommendedVersion().toString() : null;
if (ver == null) {
//TODO can we figure out which version to get without hardwiring a list of known versions?
ioput.getOut().println("NetBeans: No match and no recommended version for version range " + vr.toString());
return null;
}
}
if (ver == null) {
return null;
}
File f = getAltMavenLocation();
File child = FileUtil.normalizeFile(new File(f, "apache-maven-" + ver));
if (child.exists()) {
return child;
} else {
f.mkdirs();
ioput.getOut().println("NetBeans: Downloading and unzipping Maven version " + ver);
ZipInputStream str = null;
try {
//this url pattern works for all versions except the last one 3.2.3
//which is only under <mirror>/apache/maven/maven-3/3.2.3/binaries/
URL[] urls = new URL[] {new URL("http://archive.apache.org/dist/maven/binaries/apache-maven-" + ver + "-bin.zip"),
new URL("http://archive.apache.org/dist/maven/maven-3/" + ver + "/binaries/apache-maven-" + ver + "-bin.zip")};
InputStream is = null;
for (URL u : urls) {
try {
is = u.openStream();
break;
} catch (FileNotFoundException e) {
// try next url
}
}
if(is == null) {
LOGGER.log(Level.WARNING, "wasn''t able to download maven binaries, version {0}", ver);
return null;
}
str = new ZipInputStream(is);
ZipEntry entry;
while ((entry = str.getNextEntry()) != null) {
//base it of f not child as the zip contains the maven base folder
File fileOrDir = new File(f, entry.getName());
if (entry.isDirectory()) {
fileOrDir.mkdirs();
} else {
FileOutputStream fos = null;
try {
fos = new FileOutputStream(fileOrDir);
FileUtil.copy(str, fos);
} finally {
IOUtil.close(fos);
}
// correct way to set executable flag?
if ("bin".equals(fileOrDir.getParentFile().getName()) && !fileOrDir.getName().endsWith(".conf")) {
fileOrDir.setExecutable(true);
}
}
}
if (!all.contains(child.getAbsolutePath())) {
all.add(child.getAbsolutePath());
MavenSettings.getDefault().setMavenRuntimes(all);
}
return child;
} catch (MalformedURLException ex) {
Exceptions.printStackTrace(ex);
try {
FileUtils.deleteDirectory(child);
} catch (IOException ex1) {
Exceptions.printStackTrace(ex1);
}
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
try {
FileUtils.deleteDirectory(child);
} catch (IOException ex1) {
Exceptions.printStackTrace(ex1);
}
} finally {
IOUtil.close(str);
}
}
return null;
}
private File getAltMavenLocation() {
if (MavenSettings.getDefault().isUseBestMavenAltLocation()) {
String s = MavenSettings.getDefault().getBestMavenAltLocation();
if (s != null && s.trim().length() > 0) {
return FileUtil.normalizeFile(new File(s));
}
}
return Places.getCacheSubdirectory("downloaded-mavens");
}
}