/* * Copyright 2017 the original author or authors. * * 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 org.gradle.internal.logging.console; import org.gradle.internal.logging.events.BatchOutputEventListener; import org.gradle.internal.logging.events.EndOutputEvent; import org.gradle.internal.logging.events.OperationIdentifier; import org.gradle.internal.logging.events.OutputEvent; import org.gradle.internal.logging.events.ProgressCompleteEvent; import org.gradle.internal.logging.events.ProgressEvent; import org.gradle.internal.logging.events.ProgressStartEvent; import org.gradle.internal.logging.format.TersePrettyDurationFormatter; import org.gradle.internal.logging.text.Span; import org.gradle.internal.logging.text.Style; import org.gradle.internal.nativeintegration.console.ConsoleMetaData; import org.gradle.internal.time.TimeProvider; import java.util.Arrays; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; public class BuildStatusRenderer extends BatchOutputEventListener { public static final String BUILD_PROGRESS_CATEGORY = "org.gradle.internal.progress.BuildProgressLogger"; private static final long RENDER_NOW_PERIOD_MILLISECONDS = 250; private final BatchOutputEventListener listener; private final StyledLabel buildStatusLabel; private final Console console; private final ConsoleMetaData consoleMetaData; private final TimeProvider timeProvider; private final ScheduledExecutorService executor; private final TersePrettyDurationFormatter elapsedTimeFormatter = new TersePrettyDurationFormatter(); private final Object lock = new Object(); // What actually shows up on the console private String currentBuildStatus; private OperationIdentifier currentPhaseOperationId; // Used to maintain timer private long buildStartTimestamp; private ScheduledFuture future; public BuildStatusRenderer(BatchOutputEventListener listener, StyledLabel buildStatusLabel, Console console, ConsoleMetaData consoleMetaData, TimeProvider timeProvider) { this(listener, buildStatusLabel, console, consoleMetaData, timeProvider, Executors.newSingleThreadScheduledExecutor()); } BuildStatusRenderer(BatchOutputEventListener listener, StyledLabel buildStatusLabel, Console console, ConsoleMetaData consoleMetaData, TimeProvider timeProvider, ScheduledExecutorService executor) { this.listener = listener; this.buildStatusLabel = buildStatusLabel; this.console = console; this.consoleMetaData = consoleMetaData; this.timeProvider = timeProvider; this.executor = executor; this.buildStartTimestamp = timeProvider.getCurrentTime(); } private void buildStarted(ProgressStartEvent progressStartEvent) { currentBuildStatus = progressStartEvent.getShortDescription(); } private void buildProgressed(ProgressEvent progressEvent) { currentBuildStatus = progressEvent.getStatus(); } private void buildFinished() { currentBuildStatus = ""; } @Override public void onOutput(OutputEvent event) { if (event instanceof ProgressStartEvent) { ProgressStartEvent startEvent = (ProgressStartEvent) event; if (currentPhaseOperationId == null && BUILD_PROGRESS_CATEGORY.equals(startEvent.getCategory())) { currentPhaseOperationId = startEvent.getProgressOperationId(); buildStarted(startEvent); } } else if (event instanceof ProgressCompleteEvent) { if (((ProgressCompleteEvent) event).getProgressOperationId().equals(currentPhaseOperationId)) { currentPhaseOperationId = null; } } else if (event instanceof ProgressEvent) { ProgressEvent progressEvent = (ProgressEvent) event; if (progressEvent.getProgressOperationId().equals(currentPhaseOperationId)) { buildProgressed(progressEvent); } } else if (event instanceof EndOutputEvent) { if (future != null && !future.isCancelled()) { future.cancel(false); } executor.shutdown(); buildFinished(); } } @Override public void onOutput(Iterable<OutputEvent> events) { synchronized (lock) { super.onOutput(events); listener.onOutput(events); renderNow(timeProvider.getCurrentTime()); } } private String trimToConsole(String str) { int width = consoleMetaData.getCols() - 1; if (width > 0 && width < str.length()) { return str.substring(0, width); } return str; } private void renderNow(long now) { if (currentBuildStatus != null && !currentBuildStatus.isEmpty()) { if ((future == null || future.isCancelled()) && !executor.isShutdown()) { future = executor.scheduleAtFixedRate(new Runnable() { @Override public void run() { synchronized (lock) { renderNow(timeProvider.getCurrentTime()); } } }, RENDER_NOW_PERIOD_MILLISECONDS, RENDER_NOW_PERIOD_MILLISECONDS, TimeUnit.MILLISECONDS); } String elapsedTime = elapsedTimeFormatter.format(now - buildStartTimestamp); buildStatusLabel.setText(Arrays.asList( new Span(Style.of(Style.Emphasis.BOLD), trimToConsole(format(currentBuildStatus, elapsedTime))))); } console.flush(); } private static String format(String status, String elapsedTime) { return status + " [" + elapsedTime + "]"; } }