/* * Copyright 2000-2020 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 jetbrains.teamcity.ui.actions; import com.intellij.util.AsynchConsumer; import jetbrains.buildServer.Build; import jetbrains.buildServer.ConnectionStatusListener.ErrorProcessingKind; import jetbrains.buildServer.serverProxy.RemoteBuildServerFacade; import jetbrains.teamcity.Activator; import jetbrains.teamcity.Constants; import jetbrains.teamcity.EclipseIDEAlarm; import jetbrains.teamcity.SharedImages; import jetbrains.teamcity.core.Debug; import jetbrains.teamcity.core.TeamCitySnapshot4Eclipse; import jetbrains.teamcity.ui.views.Console; import org.eclipse.jface.action.Action; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.console.ConsolePlugin; import org.eclipse.ui.console.IConsole; import org.eclipse.ui.console.IConsoleManager; import org.eclipse.ui.console.MessageConsoleStream; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; public class ViewLogAction extends Action { public static final int MESSAGES_PAGE_SIZE = 256; public static enum Mode { LOG, ERROR, MINIMIZE_OUTPUT } public static class ViewErrorAction extends ViewLogAction { private final static String ACTION_NAME = "View Build Errors"; public ViewErrorAction(Build build, Mode mode) { super(build, mode); setText(ACTION_NAME); setImageDescriptor(SharedImages.getImageDescriptorByPath(SharedImages.BUILD_ERR_IMG)); } } private Build myBuild; private Display display; private final Mode myMode; public ViewLogAction(final Build build, final Mode mode) { super(Mode.LOG == mode ? Messages.getString("ViewLogAction.name") : ViewErrorAction.ACTION_NAME); //$NON-NLS-1$ setImageDescriptor(SharedImages.getImageDescriptorByPath(SharedImages.BUILD_LOG_IMG)); setBuild(build); myMode = mode; } public void setBuild(@Nullable final Build build) { myBuild = build; setEnabled(build != null); } private static String getName(final Build build) { return MessageFormat.format("Build #{0}", build.getBuildNumber()); //$NON-NLS-1$ } @Override public void run() { long buildID = myBuild.getBuildId(); Console console = findConsole("TeamCity " + getName(myBuild), buildID); //$NON-NLS-1$ console.setRemoteBuildType(myBuild.getBuildTypeId()); console.activate(); MessageConsoleStream out = console.newMessageStream(); out.setActivateOnWrite(false); display = Display.getCurrent(); console.setTextColor(display.getSystemColor(SWT.COLOR_BLACK)); if (Mode.ERROR == myMode || Mode.MINIMIZE_OUTPUT == myMode && hasCompilationErrors(myBuild)) { showCompilationErrors(console); } else { createFetchJob(console, display, new AtomicReference(myBuild.getBuildTypeId()), new AtomicInteger(0), getName(myBuild), buildID, out); } } private boolean hasCompilationErrors(final Build build) { final boolean[] result = new boolean[] { false }; final TeamCitySnapshot4Eclipse snapshot = Activator.getDefault().getSnapshot(); snapshot.getProcessManager().performAction(new Runnable() { public void run() { result[0] = !build.getCompilationErrorMessages().isEmpty(); } }, "Test compilation errors", ErrorProcessingKind.RETHROW); return result[0]; } private void showCompilationErrors(final Console console) { final MessageConsoleStream out = console.newMessageStream(); final TeamCitySnapshot4Eclipse snapshot = Activator.getDefault().getSnapshot(); final List errors = new ArrayList(); snapshot.getProcessManager().performAction(new Runnable() { public void run() { errors.addAll(myBuild.getCompilationErrorMessages()); final String checkoutDirectory = findDirectory(myBuild.getLogMessages(0, 3)); console.setTarget(checkoutDirectory); } }, "Fetching compilation errors", ErrorProcessingKind.RETHROW); if (!out.isClosed()) { if (!errors.isEmpty()) { console.setTextColor(display.getSystemColor(SWT.COLOR_DARK_RED)); for (final String errLine : errors) { out.println(errLine); } } else { out.println("No compilation errors found"); } } } private String findDirectory(List logMessages) { for (String message : logMessages) { String dirToken = "Checkout directory:"; if (message.contains(dirToken)) { return message.substring(message.indexOf(dirToken) + dirToken.length(), message.length()).trim(); } } return null; } private Console findConsole(String name, long buildID) { String type = "jetbrains.teamcity.ui.Console." + buildID; //$NON-NLS-1$ IConsoleManager manager = ConsolePlugin.getDefault().getConsoleManager(); IConsole[] consoles = manager.getConsoles(); for (int i = 0; consoles != null && i < consoles.length; i++) { if (consoles[i].getType().equals(type)) { manager.removeConsoles(new IConsole[] { consoles[i] }); break; } } Console console = new Console(name, SharedImages.getImageDescriptorByPath(SharedImages.BUILD_LOG_IMG), true, type); manager.addConsoles(new IConsole[] { console }); return console; } private void createFetchJob(@NotNull final Console console, @NotNull final Display d, @NotNull final AtomicReference target, @NotNull final AtomicInteger fetched, @NotNull final String buildName, final long buildID, @NotNull final MessageConsoleStream out) { final EclipseIDEAlarm alarm = (EclipseIDEAlarm) Activator.getDefault().getIDE().createAlarm(); PlatformUI.getWorkbench().getProgressService(); final AsynchConsumer> buildLogPartsConsumer = new AsynchConsumer>() { public void consume(@NotNull final List messages) { if (fetched.compareAndSet(0, 0) && target.get() != null) { for (String line : messages) { if (console.hasTarget()) break; String firstLine = line.trim(); if (firstLine.startsWith("[") && firstLine.indexOf(']') > 0) { //$NON-NLS-1$ target.set(firstLine.substring(1, firstLine.indexOf(']'))); console.setRemoteBuildType(target.get()); firstLine = firstLine.substring(firstLine.indexOf(']') + 1).trim(); if (firstLine.startsWith("Checkout directory: ")) { //$NON-NLS-1$ //noinspection RedundantStringConstructorCall console.setTarget(new String(firstLine.substring("Checkout directory: ".length()).trim())); //$NON-NLS-1$ } } else { target.set(Constants.EMPTY_STRING); } } } for (String line : messages) { if (out.isClosed()) { break; } out.println(line.trim()); } } public void finished() { try { if (out.isClosed()) { out.flush(); out.close(); } } catch (IOException e) { Debug.getInstance().log(Debug.DEBUG_UI, e); } d.asyncExec(new Runnable() { public void run() { console.setName(MessageFormat.format("TeamCity {0} - Completed", buildName)); //$NON-NLS-1$ } }); } }; alarm.addRequest(new Runnable() { public void run() { final RemoteBuildServerFacade facade = Activator.getDefault().getSnapshot().getServerFacade(); final Build build = facade.findBuildInstanceById(buildID); while (true) { final int startFromIdx = fetched.get(); final List messages = build.getLogMessages(startFromIdx, MESSAGES_PAGE_SIZE); // In case of two threads tries to fetch same messages. Only first will pass thru compareAndSet, others will be interrupted. if (!fetched.compareAndSet(startFromIdx, startFromIdx + messages.size())) { return; } buildLogPartsConsumer.consume(Collections.unmodifiableList(messages)); if (out.isClosed()) { // Stop fetching if console closed. (Cannot write into closed console) return; } if (messages.size() < MESSAGES_PAGE_SIZE) { if (build.isFinished()) { // It was last part of messages buildLogPartsConsumer.finished(); } else { // We must continue download messages after some timeout (waiting until build generate something) d.asyncExec(new Runnable() { public void run() { console.setName(MessageFormat.format("TeamCity {0} - Running", buildName)); //$NON-NLS-1$ } }); alarm.addRequest(this, 1000, Messages.getString("ViewLogAction.load.log.task.name"), null, true); //$NON-NLS-1$ } return; } // We must continue download messages right now } } }, 0, Messages.getString("ViewLogAction.load.log.task.name"), null, true); //$NON-NLS-1$ } }