1 /* 2 * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package idea; 27 28 import org.apache.tools.ant.BuildEvent; 29 import org.apache.tools.ant.BuildListener; 30 import org.apache.tools.ant.DefaultLogger; 31 import org.apache.tools.ant.Project; 32 33 import java.util.EnumSet; 34 import java.util.Stack; 35 36 import static org.apache.tools.ant.Project.*; 37 38 /** 39 * This class is used to wrap the IntelliJ ant logger in order to provide more meaningful 40 * output when building langtools. The basic ant output in IntelliJ can be quite cumbersome to 41 * work with, as it provides two separate views: (i) a tree view, which is good to display build task 42 * in a hierarchical fashion as they are processed; and a (ii) plain text view, which gives you 43 * the full ant output. The main problem is that javac-related messages are buried into the 44 * ant output (which is made very verbose by IntelliJ in order to support the tree view). It is 45 * not easy to figure out which node to expand in order to see the error message; switching 46 * to plain text doesn't help either, as now the output is totally flat. 47 * 48 * This logger class removes a lot of verbosity from the IntelliJ ant logger by not propagating 49 * all the events to the IntelliJ's logger. In addition, certain events are handled in a custom 50 * fashion, to generate better output during the build. 51 */ 52 public final class LangtoolsIdeaAntLogger extends DefaultLogger { 53 54 /** 55 * This is just a way to pass in customized binary string predicates; 56 * 57 * TODO: replace with @code{BiPredicate<String, String>} and method reference when moving to 8 58 */ 59 enum StringBinaryPredicate { CONTAINS()60 CONTAINS() { 61 @Override 62 boolean apply(String s1, String s2) { 63 return s1.contains(s2); 64 } 65 }, 66 STARTS_WITH { 67 @Override apply(String s1, String s2)68 boolean apply(String s1, String s2) { 69 return s1.startsWith(s2); 70 } 71 }; 72 apply(String s1, String s2)73 abstract boolean apply(String s1, String s2); 74 } 75 76 /** 77 * Various kinds of ant messages that we shall intercept 78 */ 79 enum MessageKind { 80 81 /** a javac error */ 82 JAVAC_ERROR(StringBinaryPredicate.CONTAINS, MSG_ERR, "error:", "compiler.err"), 83 /** a javac warning */ 84 JAVAC_WARNING(StringBinaryPredicate.CONTAINS, MSG_WARN, "warning:", "compiler.warn"), 85 /** a javac note */ 86 JAVAC_NOTE(StringBinaryPredicate.CONTAINS, MSG_INFO, "note:", "compiler.note"), 87 /** a javac raw error (these typically come from a build misconfiguration - such as a bad javac flag) */ 88 JAVAC_RAW_ERROR(StringBinaryPredicate.STARTS_WITH, MSG_INFO, "javac: "), 89 /** continuation of some javac error message */ 90 JAVAC_NESTED_DIAG(StringBinaryPredicate.STARTS_WITH, MSG_INFO, " "), 91 /** a javac crash */ 92 JAVAC_CRASH(StringBinaryPredicate.STARTS_WITH, MSG_ERR, "An exception has occurred in the compiler"), 93 /** jtreg test success */ 94 JTREG_TEST_PASSED(StringBinaryPredicate.STARTS_WITH, MSG_INFO, "Passed: "), 95 /** jtreg test failure */ 96 JTREG_TEST_FAILED(StringBinaryPredicate.STARTS_WITH, MSG_ERR, "FAILED: "), 97 /** jtreg test error */ 98 JTREG_TEST_ERROR(StringBinaryPredicate.STARTS_WITH, MSG_ERR, "Error: "), 99 /** jtreg report */ 100 JTREG_TEST_REPORT(StringBinaryPredicate.STARTS_WITH, MSG_INFO, "Report written"); 101 102 StringBinaryPredicate sbp; 103 int priority; 104 String[] keys; 105 MessageKind(StringBinaryPredicate sbp, int priority, String... keys)106 MessageKind(StringBinaryPredicate sbp, int priority, String... keys) { 107 this.sbp = sbp; 108 this.priority = priority; 109 this.keys = keys; 110 } 111 112 /** 113 * Does a given message string matches this kind? 114 */ matches(String s)115 boolean matches(String s) { 116 for (String key : keys) { 117 if (sbp.apply(s, key)) { 118 return true; 119 } 120 } 121 return false; 122 } 123 } 124 125 /** 126 * This enum is used to represent the list of tasks we need to keep track of during logging. 127 */ 128 enum Task { 129 /** exec task - invoked during compilation */ 130 JAVAC("exec", MessageKind.JAVAC_ERROR, MessageKind.JAVAC_WARNING, MessageKind.JAVAC_NOTE, 131 MessageKind.JAVAC_RAW_ERROR, MessageKind.JAVAC_NESTED_DIAG, MessageKind.JAVAC_CRASH), 132 /** jtreg task - invoked during test execution */ 133 JTREG("jtreg", MessageKind.JTREG_TEST_PASSED, MessageKind.JTREG_TEST_FAILED, MessageKind.JTREG_TEST_ERROR, MessageKind.JTREG_TEST_REPORT), 134 /** initial synthetic task when the logger is created */ 135 ROOT("") { 136 @Override matches(String s)137 boolean matches(String s) { 138 return false; 139 } 140 }, 141 /** synthetic task catching any other tasks not in this list */ 142 ANY("") { 143 @Override matches(String s)144 boolean matches(String s) { 145 return true; 146 } 147 }; 148 149 String taskName; 150 MessageKind[] msgs; 151 Task(String taskName, MessageKind... msgs)152 Task(String taskName, MessageKind... msgs) { 153 this.taskName = taskName; 154 this.msgs = msgs; 155 } 156 matches(String s)157 boolean matches(String s) { 158 return s.equals(taskName); 159 } 160 } 161 162 /** 163 * This enum is used to represent the list of targets we need to keep track of during logging. 164 * A regular expression is used to match a given target name. 165 */ 166 enum Target { 167 /** jtreg target - executed when launching tests */ 168 JTREG("jtreg") { 169 @Override getDisplayMessage(BuildEvent e)170 String getDisplayMessage(BuildEvent e) { 171 return "Running jtreg tests: " + e.getProject().getProperty("jtreg.tests"); 172 } 173 }, 174 /** build bootstrap tool target - executed when bootstrapping javac */ 175 BUILD_BOOTSTRAP_JAVAC("build-bootstrap-javac-classes") { 176 @Override getDisplayMessage(BuildEvent e)177 String getDisplayMessage(BuildEvent e) { 178 return "Building bootstrap javac..."; 179 } 180 }, 181 /** build classes target - executed when building classes of given tool */ 182 BUILD_ALL_CLASSES("build-all-classes") { 183 @Override getDisplayMessage(BuildEvent e)184 String getDisplayMessage(BuildEvent e) { 185 return "Building all classes..."; 186 } 187 }, 188 /** synthetic target catching any other target not in this list */ 189 ANY("") { 190 @Override getDisplayMessage(BuildEvent e)191 String getDisplayMessage(BuildEvent e) { 192 return "Executing Ant target(s): " + e.getProject().getProperty("ant.project.invoked-targets"); 193 } 194 @Override matches(String msg)195 boolean matches(String msg) { 196 return true; 197 } 198 }; 199 200 String targetName; 201 Target(String targetName)202 Target(String targetName) { 203 this.targetName = targetName; 204 } 205 matches(String msg)206 boolean matches(String msg) { 207 return msg.equals(targetName); 208 } 209 getDisplayMessage(BuildEvent e)210 abstract String getDisplayMessage(BuildEvent e); 211 } 212 213 /** 214 * A custom build event used to represent status changes which should be notified inside 215 * Intellij 216 */ 217 static class StatusEvent extends BuildEvent { 218 219 /** the target to which the status update refers */ 220 Target target; 221 StatusEvent(BuildEvent e, Target target)222 StatusEvent(BuildEvent e, Target target) { 223 super(new StatusTask(e, target.getDisplayMessage(e))); 224 this.target = target; 225 setMessage(getTask().getTaskName(), 2); 226 } 227 228 /** 229 * A custom task used to channel info regarding a status change 230 */ 231 static class StatusTask extends org.apache.tools.ant.Task { StatusTask(BuildEvent event, String msg)232 StatusTask(BuildEvent event, String msg) { 233 setProject(event.getProject()); 234 setOwningTarget(event.getTarget()); 235 setTaskName(msg); 236 } 237 } 238 } 239 240 /** wrapped ant logger (IntelliJ's own logger) */ 241 DefaultLogger logger; 242 243 /** flag - is this the first target we encounter? */ 244 boolean firstTarget = true; 245 246 /** flag - should subsequenet failures be suppressed ? */ 247 boolean suppressTaskFailures = false; 248 249 /** flag - have we ran into a javac crash ? */ 250 boolean crashFound = false; 251 252 /** stack of status changes associated with pending targets */ 253 Stack<StatusEvent> statusEvents = new Stack<>(); 254 255 /** stack of pending tasks */ 256 Stack<Task> tasks = new Stack<>(); 257 LangtoolsIdeaAntLogger(Project project)258 public LangtoolsIdeaAntLogger(Project project) { 259 for (Object o : project.getBuildListeners()) { 260 if (o instanceof DefaultLogger) { 261 this.logger = (DefaultLogger)o; 262 project.removeBuildListener((BuildListener)o); 263 project.addBuildListener(this); 264 } 265 } 266 logger.setMessageOutputLevel(3); 267 tasks.push(Task.ROOT); 268 } 269 270 @Override buildStarted(BuildEvent event)271 public void buildStarted(BuildEvent event) { 272 //do nothing 273 } 274 275 @Override buildFinished(BuildEvent event)276 public void buildFinished(BuildEvent event) { 277 //do nothing 278 } 279 280 @Override targetStarted(BuildEvent event)281 public void targetStarted(BuildEvent event) { 282 EnumSet<Target> statusKinds = firstTarget ? 283 EnumSet.allOf(Target.class) : 284 EnumSet.complementOf(EnumSet.of(Target.ANY)); 285 286 String targetName = event.getTarget().getName(); 287 288 for (Target statusKind : statusKinds) { 289 if (statusKind.matches(targetName)) { 290 StatusEvent statusEvent = new StatusEvent(event, statusKind); 291 statusEvents.push(statusEvent); 292 logger.taskStarted(statusEvent); 293 firstTarget = false; 294 return; 295 } 296 } 297 } 298 299 @Override targetFinished(BuildEvent event)300 public void targetFinished(BuildEvent event) { 301 if (!statusEvents.isEmpty()) { 302 StatusEvent lastEvent = statusEvents.pop(); 303 if (lastEvent.target.matches(event.getTarget().getName())) { 304 logger.taskFinished(lastEvent); 305 } 306 } 307 } 308 309 @Override taskStarted(BuildEvent event)310 public void taskStarted(BuildEvent event) { 311 String taskName = event.getTask().getTaskName(); 312 for (Task task : Task.values()) { 313 if (task.matches(taskName)) { 314 tasks.push(task); 315 return; 316 } 317 } 318 } 319 320 @Override taskFinished(BuildEvent event)321 public void taskFinished(BuildEvent event) { 322 if (tasks.peek() == Task.ROOT) { 323 //we need to 'close' the root task to get nicer output 324 logger.taskFinished(event); 325 } else if (!suppressTaskFailures && event.getException() != null) { 326 //the first (innermost) task failure should always be logged 327 event.setMessage(event.getException().toString(), 0); 328 event.setException(null); 329 //note: we turn this into a plain message to avoid stack trace being logged by Idea 330 logger.messageLogged(event); 331 suppressTaskFailures = true; 332 } 333 tasks.pop(); 334 } 335 336 @Override messageLogged(BuildEvent event)337 public void messageLogged(BuildEvent event) { 338 String msg = event.getMessage(); 339 340 boolean processed = false; 341 342 if (!tasks.isEmpty()) { 343 Task task = tasks.peek(); 344 for (MessageKind messageKind : task.msgs) { 345 if (messageKind.matches(msg)) { 346 event.setMessage(msg, messageKind.priority); 347 processed = true; 348 if (messageKind == MessageKind.JAVAC_CRASH) { 349 crashFound = true; 350 } 351 break; 352 } 353 } 354 } 355 356 if (event.getPriority() == MSG_ERR || crashFound) { 357 //we log errors regardless of owning task 358 logger.messageLogged(event); 359 suppressTaskFailures = true; 360 } else if (processed) { 361 logger.messageLogged(event); 362 } 363 } 364 } 365