| /* |
| * 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.apache.easyant.core.ant.listerners; |
| |
| import org.apache.easyant.core.EasyAntMagicNames; |
| import org.apache.easyant.core.ant.ExecutionStatus; |
| import org.apache.tools.ant.BuildEvent; |
| import org.apache.tools.ant.Project; |
| import org.apache.tools.ant.SubBuildListener; |
| import org.apache.tools.ant.listener.TimestampedLogger; |
| |
| import java.io.File; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import static org.apache.tools.ant.util.StringUtils.LINE_SEP; |
| |
| public class MultiModuleLogger extends DefaultEasyAntLogger implements SubBuildListener { |
| |
| |
| /** |
| * Reference key against which build execution results will be stored |
| */ |
| public static final String EXECUTION_TIMER_BUILD_RESULTS = "execution.timer.build.results"; |
| |
| private static final String DEMARKER = "======================================================================"; |
| private volatile boolean subBuildStartedRaised = false; |
| private final Object subBuildLock = new Object(); |
| private long buildStartTime; |
| |
| /** |
| * This is an override point: the message that indicates whether a build failed. Subclasses can change/enhance the |
| * message. |
| * |
| * @return The classic "BUILD FAILED" plus a timestamp |
| */ |
| protected String getBuildFailedMessage() { |
| return super.getBuildFailedMessage() + TimestampedLogger.SPACER + getTimestamp(); |
| } |
| |
| /** |
| * This is an override point: the message that indicates that a build succeeded. Subclasses can change/enhance the |
| * message. |
| * |
| * @return The classic "BUILD SUCCESSFUL" plus a timestamp |
| */ |
| protected String getBuildSuccessfulMessage() { |
| return super.getBuildSuccessfulMessage() + TimestampedLogger.SPACER + getTimestamp(); |
| } |
| |
| public void targetStarted(BuildEvent event) { |
| maybeRaiseSubBuildStarted(event); |
| targetName = extractTargetName(event); |
| } |
| |
| public void taskStarted(BuildEvent event) { |
| maybeRaiseSubBuildStarted(event); |
| super.taskStarted(event); |
| } |
| |
| public void buildFinished(BuildEvent event) { |
| stopTimer(event); |
| printExecutionSubBuildsExecutionTimes(event.getProject()); |
| maybeRaiseSubBuildStarted(event); |
| subBuildFinished(event); |
| super.buildFinished(event); |
| } |
| |
| public void messageLogged(BuildEvent event) { |
| maybeRaiseSubBuildStarted(event); |
| super.messageLogged(event); |
| } |
| |
| public void subBuildStarted(BuildEvent event) { |
| initTimer(event.getProject()); |
| String name = extractNameOrDefault(event); |
| Project project = event.getProject(); |
| |
| File base = project == null ? null : project.getBaseDir(); |
| String path = (base == null) ? "With no base directory" : "In " + base.getAbsolutePath(); |
| printMessage(LINE_SEP + DEMARKER + LINE_SEP + "Entering project " + name |
| + LINE_SEP + path + LINE_SEP + DEMARKER, out, event.getPriority()); |
| } |
| |
| |
| @Override |
| public void buildStarted(BuildEvent event) { |
| initTimer(event.getProject()); |
| super.buildStarted(event); |
| } |
| |
| /** |
| * Get the name of an event |
| * |
| * @param event the event name |
| * @return the name or a default string |
| */ |
| protected String extractNameOrDefault(BuildEvent event) { |
| String name = extractProjectName(event); |
| if (name == null) { |
| name = ""; |
| } else { |
| name = '"' + name + '"'; |
| } |
| return name; |
| } |
| |
| public void subBuildFinished(BuildEvent event) { |
| stopTimer(event); |
| String name = extractNameOrDefault(event); |
| String failed = event.getException() != null ? "failing " : ""; |
| printMessage(LINE_SEP + DEMARKER + LINE_SEP + "Exiting " + failed + "project " |
| + name + LINE_SEP + DEMARKER, out, event.getPriority()); |
| } |
| |
| |
| private void maybeRaiseSubBuildStarted(BuildEvent event) { |
| // double checked locking should be OK since the flag is write-once |
| if (!subBuildStartedRaised) { |
| synchronized (subBuildLock) { |
| if (!subBuildStartedRaised) { |
| subBuildStartedRaised = true; |
| subBuildStarted(event); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Override point, extract the target name |
| * |
| * @param event the event to work on |
| * @return the target name -including the owning project name (if non-null) |
| */ |
| protected String extractTargetName(BuildEvent event) { |
| String targetName = event.getTarget().getName(); |
| String projectName = extractProjectName(event); |
| if (projectName != null && targetName != null) { |
| return projectName + '.' + targetName; |
| } else { |
| return targetName; |
| } |
| } |
| |
| private void initTimer(Project project) { |
| buildStartTime = System.currentTimeMillis(); |
| project.addReference(EXECUTION_TIMER_BUILD_RESULTS, new ArrayList<ExecutionResult>()); |
| } |
| |
| /** |
| * stops the timer and stores the result as a project reference by the key 'referenceName' |
| */ |
| private void stopTimer(BuildEvent event) { |
| List<ExecutionResult> results = event.getProject().getReference(EXECUTION_TIMER_BUILD_RESULTS); |
| ExecutionStatus status = ExecutionStatus.SUCCESS; |
| if (event.getException() != null) { |
| status = ExecutionStatus.FAILED; |
| } else if (event.getProject().getProperty(EasyAntMagicNames.PROJECT_EXECUTED_TARGETS) == null) { |
| status = ExecutionStatus.SKIPPED; |
| } |
| |
| ExecutionResult execResult = new ExecutionResult(event.getProject().getName(), System.currentTimeMillis() |
| - buildStartTime, status); |
| |
| results.add(execResult); |
| |
| } |
| |
| private void printExecutionSubBuildsExecutionTimes(Project project) { |
| List<ExecutionResult> allSubBuildResults = project.getReference(EXECUTION_TIMER_BUILD_RESULTS); |
| if (allSubBuildResults != null && !allSubBuildResults.isEmpty()) { |
| project.log(LINE_SEP + "Project Sub-modules Summary: " + LINE_SEP + formatExecutionResults(allSubBuildResults)); |
| } |
| } |
| |
| private String formatExecutionResults(List<ExecutionResult> results) { |
| String formattedResults; |
| int maxUnitNameLength = 0; |
| int maxExecTimeLength = 0; |
| for (ExecutionResult result : results) { |
| maxUnitNameLength = result.getUnitName().length() > maxUnitNameLength ? result.getUnitName().length() |
| : maxUnitNameLength; |
| maxExecTimeLength = result.getFormattedElapsedTime().length() > maxExecTimeLength ? result |
| .getFormattedElapsedTime().length() : maxExecTimeLength; |
| } |
| StringBuilder sb = new StringBuilder(LINE_SEP); |
| for (ExecutionResult result : results) { |
| String moduleName = padRight(result.getUnitName(), maxUnitNameLength + 10); |
| sb.append(" * ").append(moduleName); |
| // keeping both success and failed strings of equal length |
| String execResult = padRight(result.getStatus().toString(), 7); |
| sb.append(execResult)// |
| .append(" [ took ")// |
| .append(padRight(result.getFormattedElapsedTime(), maxExecTimeLength + 1))// |
| .append("]") |
| .append(LINE_SEP); |
| } |
| |
| return sb.toString(); |
| } |
| |
| private String padRight(String string, int nbSpace) { |
| return String.format("%1$-" + nbSpace + "s", string); |
| } |
| |
| |
| } |