1 /* 2 * Copyright (c) 2014, 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 JdkIdeaAntLogger 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 MATCHES { 73 @Override apply(String s1, String s2)74 boolean apply(String s1, String s2) { 75 return s1.matches(s2); 76 } 77 }; 78 apply(String s1, String s2)79 abstract boolean apply(String s1, String s2); 80 } 81 82 /** 83 * Various kinds of ant messages that we shall intercept 84 */ 85 enum MessageKind { 86 87 /** a make error */ 88 MAKE_ERROR(StringBinaryPredicate.CONTAINS, MSG_ERR, "error:", "compiler.err"), 89 /** a make warning */ 90 MAKE_WARNING(StringBinaryPredicate.CONTAINS, MSG_WARN, "warning:", "compiler.warn"), 91 /** a make note */ 92 MAKE_NOTE(StringBinaryPredicate.CONTAINS, MSG_INFO, "note:", "compiler.note"), 93 /** std make output */ 94 MAKE_OTHER(StringBinaryPredicate.MATCHES, MSG_INFO, ".*"), 95 /** a javac crash */ 96 JAVAC_CRASH(StringBinaryPredicate.STARTS_WITH, MSG_ERR, "An exception has occurred in the compiler"), 97 /** jtreg test success */ 98 JTREG_TEST_PASSED(StringBinaryPredicate.STARTS_WITH, MSG_INFO, "Passed: "), 99 /** jtreg test failure */ 100 JTREG_TEST_FAILED(StringBinaryPredicate.STARTS_WITH, MSG_ERR, "FAILED: "), 101 /** jtreg test error */ 102 JTREG_TEST_ERROR(StringBinaryPredicate.STARTS_WITH, MSG_ERR, "Error: "), 103 /** jtreg report */ 104 JTREG_TEST_REPORT(StringBinaryPredicate.STARTS_WITH, MSG_INFO, "Report written"); 105 106 StringBinaryPredicate sbp; 107 int priority; 108 String[] keys; 109 MessageKind(StringBinaryPredicate sbp, int priority, String... keys)110 MessageKind(StringBinaryPredicate sbp, int priority, String... keys) { 111 this.sbp = sbp; 112 this.priority = priority; 113 this.keys = keys; 114 } 115 116 /** 117 * Does a given message string matches this kind? 118 */ matches(String s)119 boolean matches(String s) { 120 for (String key : keys) { 121 if (sbp.apply(s, key)) { 122 return true; 123 } 124 } 125 return false; 126 } 127 } 128 129 /** 130 * This enum is used to represent the list of tasks we need to keep track of during logging. 131 */ 132 enum Task { 133 /** javac task - invoked during compilation */ 134 MAKE("exec", MessageKind.MAKE_ERROR, MessageKind.MAKE_WARNING, MessageKind.MAKE_NOTE, 135 MessageKind.MAKE_OTHER, MessageKind.JAVAC_CRASH), 136 /** jtreg task - invoked during test execution */ 137 JTREG("jtreg", MessageKind.JTREG_TEST_PASSED, MessageKind.JTREG_TEST_FAILED, MessageKind.JTREG_TEST_ERROR, MessageKind.JTREG_TEST_REPORT), 138 /** initial synthetic task when the logger is created */ 139 ROOT("") { 140 @Override matches(String s)141 boolean matches(String s) { 142 return false; 143 } 144 }, 145 /** synthetic task catching any other tasks not in this list */ 146 ANY("") { 147 @Override matches(String s)148 boolean matches(String s) { 149 return true; 150 } 151 }; 152 153 String taskName; 154 MessageKind[] msgs; 155 Task(String taskName, MessageKind... msgs)156 Task(String taskName, MessageKind... msgs) { 157 this.taskName = taskName; 158 this.msgs = msgs; 159 } 160 matches(String s)161 boolean matches(String s) { 162 return s.equals(taskName); 163 } 164 } 165 166 /** 167 * This enum is used to represent the list of targets we need to keep track of during logging. 168 * A regular expression is used to match a given target name. 169 */ 170 enum Target { 171 /** jtreg target - executed when launching tests */ 172 JTREG("jtreg") { 173 @Override getDisplayMessage(BuildEvent e)174 String getDisplayMessage(BuildEvent e) { 175 return "Running jtreg tests: " + e.getProject().getProperty("jtreg.tests"); 176 } 177 }, 178 /** build selected modules */ 179 BUILD_MODULE("build-module") { 180 @Override getDisplayMessage(BuildEvent e)181 String getDisplayMessage(BuildEvent e) { 182 return "Building modules: " + e.getProject().getProperty("module.name") + "..."; 183 } 184 }, 185 /** build images */ 186 BUILD_IMAGES("images") { 187 @Override getDisplayMessage(BuildEvent e)188 String getDisplayMessage(BuildEvent e) { 189 return "Building images..."; 190 } 191 }, 192 /** build images */ 193 CONFIGURE("-do-configure") { 194 @Override getDisplayMessage(BuildEvent e)195 String getDisplayMessage(BuildEvent e) { 196 return "Configuring build..."; 197 } 198 }, 199 /** synthetic target catching any other target not in this list */ 200 ANY("") { 201 @Override getDisplayMessage(BuildEvent e)202 String getDisplayMessage(BuildEvent e) { 203 return "Executing Ant target(s): " + e.getProject().getProperty("ant.project.invoked-targets"); 204 } 205 @Override matches(String msg)206 boolean matches(String msg) { 207 return true; 208 } 209 }; 210 211 String targetRegex; 212 Target(String targetRegex)213 Target(String targetRegex) { 214 this.targetRegex = targetRegex; 215 } 216 matches(String msg)217 boolean matches(String msg) { 218 return msg.matches(targetRegex); 219 } 220 getDisplayMessage(BuildEvent e)221 abstract String getDisplayMessage(BuildEvent e); 222 } 223 224 /** 225 * A custom build event used to represent status changes which should be notified inside 226 * Intellij 227 */ 228 static class StatusEvent extends BuildEvent { 229 230 /** the target to which the status update refers */ 231 Target target; 232 StatusEvent(BuildEvent e, Target target)233 StatusEvent(BuildEvent e, Target target) { 234 super(new StatusTask(e, target.getDisplayMessage(e))); 235 this.target = target; 236 setMessage(getTask().getTaskName(), 2); 237 } 238 239 /** 240 * A custom task used to channel info regarding a status change 241 */ 242 static class StatusTask extends org.apache.tools.ant.Task { StatusTask(BuildEvent event, String msg)243 StatusTask(BuildEvent event, String msg) { 244 setProject(event.getProject()); 245 setOwningTarget(event.getTarget()); 246 setTaskName(msg); 247 } 248 } 249 } 250 251 /** wrapped ant logger (IntelliJ's own logger) */ 252 DefaultLogger logger; 253 254 /** flag - is this the first target we encounter? */ 255 boolean firstTarget = true; 256 257 /** flag - should subsequenet failures be suppressed ? */ 258 boolean suppressTaskFailures = false; 259 260 /** flag - have we ran into a javac crash ? */ 261 boolean crashFound = false; 262 263 /** stack of status changes associated with pending targets */ 264 Stack<StatusEvent> statusEvents = new Stack<>(); 265 266 /** stack of pending tasks */ 267 Stack<Task> tasks = new Stack<>(); 268 JdkIdeaAntLogger(Project project)269 public JdkIdeaAntLogger(Project project) { 270 for (Object o : project.getBuildListeners()) { 271 if (o instanceof DefaultLogger) { 272 this.logger = (DefaultLogger)o; 273 project.removeBuildListener((BuildListener)o); 274 project.addBuildListener(this); 275 } 276 } 277 tasks.push(Task.ROOT); 278 } 279 280 @Override buildStarted(BuildEvent event)281 public void buildStarted(BuildEvent event) { 282 //do nothing 283 } 284 285 @Override buildFinished(BuildEvent event)286 public void buildFinished(BuildEvent event) { 287 //do nothing 288 } 289 290 @Override targetStarted(BuildEvent event)291 public void targetStarted(BuildEvent event) { 292 EnumSet<Target> statusKinds = firstTarget ? 293 EnumSet.allOf(Target.class) : 294 EnumSet.complementOf(EnumSet.of(Target.ANY)); 295 296 String targetName = event.getTarget().getName(); 297 298 for (Target statusKind : statusKinds) { 299 if (statusKind.matches(targetName)) { 300 StatusEvent statusEvent = new StatusEvent(event, statusKind); 301 statusEvents.push(statusEvent); 302 logger.taskStarted(statusEvent); 303 firstTarget = false; 304 return; 305 } 306 } 307 } 308 309 @Override targetFinished(BuildEvent event)310 public void targetFinished(BuildEvent event) { 311 if (!statusEvents.isEmpty()) { 312 StatusEvent lastEvent = statusEvents.pop(); 313 if (lastEvent.target.matches(event.getTarget().getName())) { 314 logger.taskFinished(lastEvent); 315 } 316 } 317 } 318 319 @Override taskStarted(BuildEvent event)320 public void taskStarted(BuildEvent event) { 321 String taskName = event.getTask().getTaskName(); 322 System.err.println("task started " + taskName); 323 for (Task task : Task.values()) { 324 if (task.matches(taskName)) { 325 tasks.push(task); 326 return; 327 } 328 } 329 } 330 331 @Override taskFinished(BuildEvent event)332 public void taskFinished(BuildEvent event) { 333 if (tasks.peek() == Task.ROOT) { 334 //we need to 'close' the root task to get nicer output 335 logger.taskFinished(event); 336 } else if (!suppressTaskFailures && event.getException() != null) { 337 //the first (innermost) task failure should always be logged 338 event.setMessage(event.getException().toString(), 0); 339 event.setException(null); 340 //note: we turn this into a plain message to avoid stack trace being logged by Idea 341 logger.messageLogged(event); 342 suppressTaskFailures = true; 343 } 344 tasks.pop(); 345 } 346 347 @Override messageLogged(BuildEvent event)348 public void messageLogged(BuildEvent event) { 349 String msg = event.getMessage(); 350 351 boolean processed = false; 352 353 if (!tasks.isEmpty()) { 354 Task task = tasks.peek(); 355 for (MessageKind messageKind : task.msgs) { 356 if (messageKind.matches(msg)) { 357 event.setMessage(msg, messageKind.priority); 358 processed = true; 359 if (messageKind == MessageKind.JAVAC_CRASH) { 360 crashFound = true; 361 } 362 break; 363 } 364 } 365 } 366 367 if (event.getPriority() == MSG_ERR || crashFound) { 368 //we log errors regardless of owning task 369 logger.messageLogged(event); 370 suppressTaskFailures = true; 371 } else if (processed) { 372 logger.messageLogged(event); 373 } 374 } 375 } 376