/* * Copyright 2000-2016 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.lang.ant.config.execution; import com.intellij.concurrency.JobScheduler; import com.intellij.execution.CantRunException; import com.intellij.execution.ExecutionException; import com.intellij.execution.configurations.GeneralCommandLine; import com.intellij.execution.process.*; import com.intellij.execution.testframework.Printable; import com.intellij.execution.testframework.Printer; import com.intellij.execution.util.ExecutionErrorDialog; import com.intellij.history.LocalHistory; import com.intellij.ide.macro.Macro; import com.intellij.lang.ant.AntBundle; import com.intellij.lang.ant.config.AntBuildFile; import com.intellij.lang.ant.config.AntBuildFileBase; import com.intellij.lang.ant.config.AntBuildListener; import com.intellij.lang.ant.config.AntBuildTarget; import com.intellij.lang.ant.config.impl.BuildFileProperty; import com.intellij.lang.ant.segments.OutputPacketProcessor; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.encoding.EncodingProjectManager; import com.intellij.openapi.wm.StatusBar; import com.intellij.openapi.wm.WindowManager; import com.intellij.util.concurrency.FutureResult; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.List; import java.util.concurrent.TimeUnit; public final class ExecutionHandler { private static final Logger LOG = Logger.getInstance("#com.intellij.ant.execution.ExecutionHandler"); @NonNls public static final String PARSER_JAR = "xerces1.jar"; private ExecutionHandler() { } @Nullable public static ProcessHandler executeRunConfiguration(AntRunConfiguration antRunConfiguration, final DataContext dataContext, List<BuildFileProperty> additionalProperties, @NotNull final AntBuildListener antBuildListener) { AntBuildTarget target = antRunConfiguration.getTarget(); if (target == null) { return null; } FutureResult<ProcessHandler> result = runBuildImpl((AntBuildFileBase)target.getModel().getBuildFile(), new String[]{target.getName()}, null, dataContext, additionalProperties, antBuildListener, false); if (result != null) { try { return result.get(); } catch (InterruptedException | java.util.concurrent.ExecutionException e) { LOG.warn(e); } } return null; } /** * @param antBuildListener should not be null. Use {@link com.intellij.lang.ant.config.AntBuildListener#NULL} */ public static void runBuild(final AntBuildFileBase buildFile, String[] targets, @Nullable final AntBuildMessageView buildMessageViewToReuse, final DataContext dataContext, List<BuildFileProperty> additionalProperties, @NotNull final AntBuildListener antBuildListener) { runBuildImpl(buildFile, targets, buildMessageViewToReuse, dataContext, additionalProperties, antBuildListener, true); } /** * @param antBuildListener should not be null. Use {@link com.intellij.lang.ant.config.AntBuildListener#NULL} */ @Nullable private static FutureResult<ProcessHandler> runBuildImpl(final AntBuildFileBase buildFile, String[] targets, @Nullable final AntBuildMessageView buildMessageViewToReuse, final DataContext dataContext, List<BuildFileProperty> additionalProperties, @NotNull final AntBuildListener antBuildListener, final boolean waitFor) { final AntBuildMessageView messageView; final GeneralCommandLine commandLine; final AntBuildListenerWrapper listenerWrapper = new AntBuildListenerWrapper(buildFile, antBuildListener); final Project project = buildFile.getProject(); try { FileDocumentManager.getInstance().saveAllDocuments(); final AntCommandLineBuilder builder = new AntCommandLineBuilder(); builder.setBuildFile(buildFile.getAllOptions(), VfsUtilCore.virtualToIoFile(buildFile.getVirtualFile())); builder.calculateProperties(dataContext, buildFile.getProject(), additionalProperties); builder.addTargets(targets); builder.getCommandLine().setCharset(EncodingProjectManager.getInstance(buildFile.getProject()).getDefaultCharset()); messageView = prepareMessageView(buildMessageViewToReuse, buildFile, targets, additionalProperties); commandLine = builder.getCommandLine().toCommandLine(); messageView.setBuildCommandLine(commandLine.getCommandLineString()); project.getMessageBus().syncPublisher(AntExecutionListener.TOPIC).beforeExecution(new AntBeforeExecutionEvent(buildFile, messageView)); } catch (RunCanceledException e) { e.showMessage(project, AntBundle.message("run.ant.error.dialog.title")); listenerWrapper.buildFinished(AntBuildListener.FAILED_TO_RUN, 0); return null; } catch (CantRunException e) { ExecutionErrorDialog.show(e, AntBundle.message("cant.run.ant.error.dialog.title"), project); listenerWrapper.buildFinished(AntBuildListener.FAILED_TO_RUN, 0); return null; } catch (Macro.ExecutionCancelledException e) { listenerWrapper.buildFinished(AntBuildListener.ABORTED, 0); return null; } catch (Throwable e) { listenerWrapper.buildFinished(AntBuildListener.FAILED_TO_RUN, 0); LOG.error(e); return null; } final FutureResult<ProcessHandler> future = new FutureResult<>(); new Task.Backgroundable(buildFile.getProject(), AntBundle.message("ant.build.progress.dialog.title"), true) { public boolean shouldStartInBackground() { return true; } public void onCancel() { listenerWrapper.buildFinished(AntBuildListener.ABORTED, 0); } public void run(@NotNull final ProgressIndicator indicator) { try { ProcessHandler handler = runBuild(indicator, messageView, buildFile, listenerWrapper, commandLine); future.set(handler); if (waitFor && handler != null) { handler.waitFor(); } } catch (Throwable e) { LOG.error(e); listenerWrapper.buildFinished(AntBuildListener.FAILED_TO_RUN, 0); } } }.queue(); return future; } @Nullable private static ProcessHandler runBuild(final ProgressIndicator progress, @NotNull final AntBuildMessageView errorView, @NotNull final AntBuildFileBase buildFile, @NotNull final AntBuildListener antBuildListener, @NotNull GeneralCommandLine commandLine) { final Project project = buildFile.getProject(); final long startTime = System.currentTimeMillis(); LocalHistory.getInstance().putSystemLabel(project, AntBundle.message("ant.build.local.history.label", buildFile.getName())); final AntProcessHandler handler; try { handler = AntProcessHandler.runCommandLine(commandLine); } catch (final ExecutionException e) { ApplicationManager.getApplication().invokeLater( () -> ExecutionErrorDialog.show(e, AntBundle.message("could.not.start.process.error.dialog.title"), project)); antBuildListener.buildFinished(AntBuildListener.FAILED_TO_RUN, 0); return null; } processRunningAnt(progress, handler, errorView, buildFile, startTime, antBuildListener); return handler; } private static void processRunningAnt(final ProgressIndicator progress, final AntProcessHandler handler, final AntBuildMessageView errorView, final AntBuildFileBase buildFile, final long startTime, final AntBuildListener antBuildListener) { final Project project = buildFile.getProject(); final StatusBar statusbar = WindowManager.getInstance().getStatusBar(project); if (statusbar != null) { statusbar.setInfo(AntBundle.message("ant.build.started.status.message")); } final CheckCancelTask checkCancelTask = new CheckCancelTask(progress, handler); checkCancelTask.start(0); final OutputParser parser = OutputParser2.attachParser(project, handler, errorView, progress, buildFile); handler.addProcessListener(new ProcessAdapter() { private final StringBuilder myUnprocessedStdErr = new StringBuilder(); public void onTextAvailable(ProcessEvent event, Key outputType) { if (outputType == ProcessOutputTypes.STDERR) { final String text = event.getText(); synchronized (myUnprocessedStdErr) { myUnprocessedStdErr.append(text); } } } public void processTerminated(ProcessEvent event) { final long buildTime = System.currentTimeMillis() - startTime; checkCancelTask.cancel(); parser.setStopped(true); final OutputPacketProcessor dispatcher = handler.getErr().getEventsDispatcher(); try { if (event.getExitCode() != 0) { // in case process exits abnormally, provide all unprocessed stderr content final String unprocessed; synchronized (myUnprocessedStdErr) { unprocessed = myUnprocessedStdErr.toString(); myUnprocessedStdErr.setLength(0); } if (!unprocessed.isEmpty()) { dispatcher.processOutput(new Printable() { public void printOn(Printer printer) { errorView.outputError(unprocessed, AntBuildMessageView.PRIORITY_ERR); } }); } } else { synchronized (myUnprocessedStdErr) { myUnprocessedStdErr.setLength(0); } } } finally { errorView.buildFinished(progress != null && progress.isCanceled(), buildTime, antBuildListener, dispatcher); } } }); handler.startNotify(); } static final class CheckCancelTask implements Runnable { private final ProgressIndicator myProgressIndicator; private final OSProcessHandler myProcessHandler; private volatile boolean myCanceled; public CheckCancelTask(ProgressIndicator progressIndicator, OSProcessHandler process) { myProgressIndicator = progressIndicator; myProcessHandler = process; } public void cancel() { myCanceled = true; } public void run() { if (!myCanceled) { try { myProgressIndicator.checkCanceled(); start(50); } catch (ProcessCanceledException e) { myProcessHandler.destroyProcess(); } } } public void start(final long delay) { JobScheduler.getScheduler().schedule(this, delay, TimeUnit.MILLISECONDS); } } private static AntBuildMessageView prepareMessageView(@Nullable AntBuildMessageView buildMessageViewToReuse, AntBuildFileBase buildFile, String[] targets, List<BuildFileProperty> additionalProperties) throws RunCanceledException { AntBuildMessageView messageView; if (buildMessageViewToReuse != null) { messageView = buildMessageViewToReuse; messageView.emptyAll(); } else { messageView = AntBuildMessageView.openBuildMessageView(buildFile.getProject(), buildFile, targets, additionalProperties); if (messageView == null) { throw new RunCanceledException(AntBundle.message("canceled.by.user.error.message")); } } return messageView; } private static class AntBuildListenerWrapper implements AntBuildListener { @NotNull private final AntBuildFile myBuildFile; @NotNull private final AntBuildListener myDelegate; AntBuildListenerWrapper(@NotNull AntBuildFile buildFile, @NotNull AntBuildListener delegate) { myBuildFile = buildFile; myDelegate = delegate; } @Override public void buildFinished(int state, int errorCount) { try { final AntFinishedExecutionEvent.Status status = state == AntBuildListener.ABORTED? AntFinishedExecutionEvent.Status.CANCELED : state == AntBuildListener.FAILED_TO_RUN? AntFinishedExecutionEvent.Status.FAILURE : AntFinishedExecutionEvent.Status.SUCCESS; myBuildFile.getProject().getMessageBus().syncPublisher(AntExecutionListener.TOPIC).buildFinished( new AntFinishedExecutionEvent(myBuildFile, status, errorCount) ); } finally { myDelegate.buildFinished(state, errorCount); } } } }