LangtoolsIdeaAntLogger.java revision 3294:9adfb22ff08f
1139823Simp/*
244165Sjulian * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
344165Sjulian * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
444165Sjulian *
544165Sjulian * This code is free software; you can redistribute it and/or modify it
644165Sjulian * under the terms of the GNU General Public License version 2 only, as
744165Sjulian * published by the Free Software Foundation.  Oracle designates this
844165Sjulian * particular file as subject to the "Classpath" exception as provided
944165Sjulian * by Oracle in the LICENSE file that accompanied this code.
1044165Sjulian *
1144165Sjulian * This code is distributed in the hope that it will be useful, but WITHOUT
1244165Sjulian * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
1344165Sjulian * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
1444165Sjulian * version 2 for more details (a copy is included in the LICENSE file that
1544165Sjulian * accompanied this code).
1644165Sjulian *
1744165Sjulian * You should have received a copy of the GNU General Public License version
1844165Sjulian * 2 along with this work; if not, write to the Free Software Foundation,
1944165Sjulian * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
2044165Sjulian *
2144165Sjulian * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2244165Sjulian * or visit www.oracle.com if you need additional information or have any
2344165Sjulian * questions.
2444165Sjulian */
2544165Sjulian
2644165Sjulianpackage idea;
2744165Sjulian
2844165Sjulianimport org.apache.tools.ant.BuildEvent;
2944165Sjulianimport org.apache.tools.ant.BuildListener;
3044165Sjulianimport org.apache.tools.ant.DefaultLogger;
3144165Sjulianimport org.apache.tools.ant.Project;
3244165Sjulian
3350477Speterimport java.util.EnumSet;
3444165Sjulianimport java.util.Stack;
3544165Sjulian
3644165Sjulianimport static org.apache.tools.ant.Project.*;
3744165Sjulian
3844165Sjulian/**
3944165Sjulian * This class is used to wrap the IntelliJ ant logger in order to provide more meaningful
4044165Sjulian * output when building langtools. The basic ant output in IntelliJ can be quite cumbersome to
4144165Sjulian * work with, as it provides two separate views: (i) a tree view, which is good to display build task
4244165Sjulian * in a hierarchical fashion as they are processed; and a (ii) plain text view, which gives you
4344165Sjulian * the full ant output. The main problem is that javac-related messages are buried into the
4474408Smdodd * ant output (which is made very verbose by IntelliJ in order to support the tree view). It is
4544165Sjulian * not easy to figure out which node to expand in order to see the error message; switching
4644165Sjulian * to plain text doesn't help either, as now the output is totally flat.
4744165Sjulian *
48112271Smdodd * This logger class removes a lot of verbosity from the IntelliJ ant logger by not propagating
49112271Smdodd * all the events to the IntelliJ's logger. In addition, certain events are handled in a custom
5044165Sjulian * fashion, to generate better output during the build.
51112271Smdodd */
5244165Sjulianpublic final class LangtoolsIdeaAntLogger extends DefaultLogger {
5344165Sjulian
5444165Sjulian    /**
5544165Sjulian     * This is just a way to pass in customized binary string predicates;
56257176Sglebius     *
57184710Sbz     * TODO: replace with @code{BiPredicate<String, String>} and method reference when moving to 8
58112271Smdodd     */
5944165Sjulian    enum StringBinaryPredicate {
6044165Sjulian        CONTAINS() {
61186119Sqingli            @Override
6244165Sjulian            boolean apply(String s1, String s2) {
63184710Sbz                return s1.contains(s2);
64112271Smdodd            }
65112271Smdodd        },
66112271Smdodd        STARTS_WITH {
6744165Sjulian            @Override
6844165Sjulian            boolean apply(String s1, String s2) {
6974408Smdodd                return s1.startsWith(s2);
7044165Sjulian            }
7144165Sjulian        };
7244165Sjulian
7344165Sjulian        abstract boolean apply(String s1, String s2);
7474408Smdodd    }
7574408Smdodd
7674408Smdodd    /**
7744165Sjulian     * Various kinds of ant messages that we shall intercept
78163606Srwatson     */
79163606Srwatson    enum MessageKind {
80126907Srwatson
81112277Smdodd        /** a javac error */
82112277Smdodd        JAVAC_ERROR(StringBinaryPredicate.CONTAINS, MSG_ERR, "error:", "compiler.err"),
83112273Smdodd        /** a javac warning */
84112294Smdodd        JAVAC_WARNING(StringBinaryPredicate.CONTAINS, MSG_WARN, "warning:", "compiler.warn"),
85112273Smdodd        /** a javac note */
86112276Smdodd        JAVAC_NOTE(StringBinaryPredicate.CONTAINS, MSG_INFO, "note:", "compiler.note"),
8774408Smdodd        /** continuation of some javac error message */
88112297Smdodd        JAVAC_NESTED_DIAG(StringBinaryPredicate.STARTS_WITH, MSG_INFO, "  "),
89112297Smdodd        /** a javac crash */
90112297Smdodd        JAVAC_CRASH(StringBinaryPredicate.STARTS_WITH, MSG_ERR, "An exception has occurred in the compiler"),
9144165Sjulian        /** jtreg test success */
92152296Sru        JTREG_TEST_PASSED(StringBinaryPredicate.STARTS_WITH, MSG_INFO, "Passed: "),
9344165Sjulian        /** jtreg test failure */
94112296Smdodd        JTREG_TEST_FAILED(StringBinaryPredicate.STARTS_WITH, MSG_ERR, "FAILED: "),
95111774Smdodd        /** jtreg test error */
9644165Sjulian        JTREG_TEST_ERROR(StringBinaryPredicate.STARTS_WITH, MSG_ERR, "Error: "),
97112296Smdodd        /** jtreg report */
98112296Smdodd        JTREG_TEST_REPORT(StringBinaryPredicate.STARTS_WITH, MSG_INFO, "Report written");
9944165Sjulian
10058313Slile        StringBinaryPredicate sbp;
10158313Slile        int priority;
102112297Smdodd        String[] keys;
103112297Smdodd
104112297Smdodd        MessageKind(StringBinaryPredicate sbp, int priority, String... keys) {
105112297Smdodd            this.sbp = sbp;
106112297Smdodd            this.priority = priority;
107112297Smdodd            this.keys = keys;
108112297Smdodd        }
109112297Smdodd
11044165Sjulian        /**
11158313Slile         * Does a given message string matches this kind?
11244165Sjulian         */
11344165Sjulian        boolean matches(String s) {
11444165Sjulian            for (String key : keys) {
115152315Sru                if (sbp.apply(s, key)) {
116152315Sru                    return true;
117112297Smdodd                }
118112272Smdodd            }
119112272Smdodd            return false;
120112272Smdodd        }
121152296Sru    }
122112297Smdodd
123112297Smdodd    /**
124112297Smdodd     * This enum is used to represent the list of tasks we need to keep track of during logging.
125112297Smdodd     */
126112297Smdodd    enum Task {
12744165Sjulian        /** exec task - invoked during compilation */
12844165Sjulian        JAVAC("exec", MessageKind.JAVAC_ERROR, MessageKind.JAVAC_WARNING, MessageKind.JAVAC_NOTE,
12974408Smdodd                       MessageKind.JAVAC_NESTED_DIAG, MessageKind.JAVAC_CRASH),
13074408Smdodd        /** jtreg task - invoked during test execution */
13174408Smdodd        JTREG("jtreg", MessageKind.JTREG_TEST_PASSED, MessageKind.JTREG_TEST_FAILED, MessageKind.JTREG_TEST_ERROR, MessageKind.JTREG_TEST_REPORT),
13274408Smdodd        /** initial synthetic task when the logger is created */
13374408Smdodd        ROOT("") {
13474408Smdodd            @Override
13574408Smdodd            boolean matches(String s) {
13674408Smdodd                return false;
137112274Smdodd            }
13874408Smdodd        },
13974408Smdodd        /** synthetic task catching any other tasks not in this list */
140112274Smdodd        ANY("") {
14174408Smdodd            @Override
142112274Smdodd            boolean matches(String s) {
143112274Smdodd                return true;
14474408Smdodd            }
14574408Smdodd        };
14644165Sjulian
147194581Srdivacky        String taskName;
14844165Sjulian        MessageKind[] msgs;
149112274Smdodd
150112274Smdodd        Task(String taskName, MessageKind... msgs) {
151112274Smdodd            this.taskName = taskName;
15244165Sjulian            this.msgs = msgs;
153112274Smdodd        }
154112274Smdodd
155112274Smdodd        boolean matches(String s) {
156112274Smdodd            return s.equals(taskName);
15744165Sjulian        }
15844165Sjulian    }
15944165Sjulian
16044165Sjulian    /**
16144165Sjulian     * This enum is used to represent the list of targets we need to keep track of during logging.
16244165Sjulian     * A regular expression is used to match a given target name.
16344165Sjulian     */
16444165Sjulian    enum Target {
16584931Sfjoe        /** jtreg target - executed when launching tests */
16644165Sjulian        JTREG("jtreg") {
16774408Smdodd            @Override
16844165Sjulian            String getDisplayMessage(BuildEvent e) {
16944165Sjulian                return "Running jtreg tests: " + e.getProject().getProperty("jtreg.tests");
17044165Sjulian            }
17144165Sjulian        },
17244165Sjulian        /** build bootstrap tool target - executed when bootstrapping javac */
17344165Sjulian        BUILD_BOOTSTRAP_JAVAC("build-bootstrap-javac-classes") {
174120047Smdodd            @Override
17544165Sjulian            String getDisplayMessage(BuildEvent e) {
17644165Sjulian                return "Building bootstrap javac...";
17744165Sjulian            }
178152315Sru        },
17944165Sjulian        /** build classes target - executed when building classes of given tool */
18044165Sjulian        BUILD_ALL_CLASSES("build-all-classes") {
18144165Sjulian            @Override
18244165Sjulian            String getDisplayMessage(BuildEvent e) {
18344165Sjulian                return "Building all classes...";
18444165Sjulian            }
18544165Sjulian        },
18644165Sjulian        /** synthetic target catching any other target not in this list */
18758313Slile        ANY("") {
18844165Sjulian            @Override
18944165Sjulian            String getDisplayMessage(BuildEvent e) {
19044165Sjulian                return "Executing Ant target(s): " + e.getProject().getProperty("ant.project.invoked-targets");
19144165Sjulian            }
19244165Sjulian            @Override
193112274Smdodd            boolean matches(String msg) {
194112274Smdodd                return true;
195112274Smdodd            }
19644165Sjulian        };
197112274Smdodd
19844165Sjulian        String targetName;
19944165Sjulian
20044165Sjulian        Target(String targetName) {
20144165Sjulian            this.targetName = targetName;
20244165Sjulian        }
20344165Sjulian
20444165Sjulian        boolean matches(String msg) {
205249925Sglebius            return msg.equals(targetName);
206249925Sglebius        }
20744165Sjulian
20874408Smdodd        abstract String getDisplayMessage(BuildEvent e);
20987914Sjlemon    }
21087914Sjlemon
21174408Smdodd    /**
21244627Sjulian     * A custom build event used to represent status changes which should be notified inside
21374408Smdodd     * Intellij
214193891Sbz     */
215275196Smelifaro    static class StatusEvent extends BuildEvent {
21644165Sjulian
217293544Smelifaro        /** the target to which the status update refers */
218293544Smelifaro        Target target;
219112285Smdodd
220172930Srwatson        StatusEvent(BuildEvent e, Target target) {
221112285Smdodd            super(new StatusTask(e, target.getDisplayMessage(e)));
222112285Smdodd            this.target = target;
223112285Smdodd            setMessage(getTask().getTaskName(), 2);
224112285Smdodd        }
225112308Smdodd
226112308Smdodd        /**
227148887Srwatson         * A custom task used to channel info regarding a status change
228148887Srwatson         */
22944165Sjulian        static class StatusTask extends org.apache.tools.ant.Task {
23074408Smdodd            StatusTask(BuildEvent event, String msg) {
23174408Smdodd                setProject(event.getProject());
232128636Sluigi                setOwningTarget(event.getTarget());
233128636Sluigi                setTaskName(msg);
23444627Sjulian            }
235186119Sqingli        }
236102291Sarchie    }
23796184Skbyanc
23844627Sjulian    /** wrapped ant logger (IntelliJ's own logger) */
23944627Sjulian    DefaultLogger logger;
24058313Slile
24158313Slile    /** flag - is this the first target we encounter? */
242152315Sru    boolean firstTarget = true;
243112278Smdodd
24444627Sjulian    /** flag - should subsequenet failures be suppressed ? */
24558313Slile    boolean suppressTaskFailures = false;
24644627Sjulian
24796184Skbyanc    /** flag - have we ran into a javac crash ? */
24874408Smdodd    boolean crashFound = false;
24996184Skbyanc
25096184Skbyanc    /** stack of status changes associated with pending targets */
25144627Sjulian    Stack<StatusEvent> statusEvents = new Stack<>();
25244627Sjulian
25344627Sjulian    /** stack of pending tasks */
25444165Sjulian    Stack<Task> tasks = new Stack<>();
25544165Sjulian
25644165Sjulian    public LangtoolsIdeaAntLogger(Project project) {
257301217Sgnn        for (Object o : project.getBuildListeners()) {
258128636Sluigi            if (o instanceof DefaultLogger) {
259128636Sluigi                this.logger = (DefaultLogger)o;
26074408Smdodd                project.removeBuildListener((BuildListener)o);
26174408Smdodd                project.addBuildListener(this);
262126951Smdodd            }
263126951Smdodd        }
264126951Smdodd        tasks.push(Task.ROOT);
265126951Smdodd    }
266126951Smdodd
267126951Smdodd    @Override
268126951Smdodd    public void buildStarted(BuildEvent event) {
269126951Smdodd        //do nothing
270126951Smdodd    }
271126951Smdodd
272126951Smdodd    @Override
273126951Smdodd    public void buildFinished(BuildEvent event) {
274126951Smdodd        //do nothing
275126951Smdodd    }
276126951Smdodd
277126951Smdodd    @Override
278126951Smdodd    public void targetStarted(BuildEvent event) {
279126951Smdodd        EnumSet<Target> statusKinds = firstTarget ?
280126951Smdodd                EnumSet.allOf(Target.class) :
281126951Smdodd                EnumSet.complementOf(EnumSet.of(Target.ANY));
282126951Smdodd
283126951Smdodd        String targetName = event.getTarget().getName();
284126951Smdodd
285126951Smdodd        for (Target statusKind : statusKinds) {
286126951Smdodd            if (statusKind.matches(targetName)) {
287126951Smdodd                StatusEvent statusEvent = new StatusEvent(event, statusKind);
288126951Smdodd                statusEvents.push(statusEvent);
28974408Smdodd                logger.taskStarted(statusEvent);
29074408Smdodd                firstTarget = false;
29174408Smdodd                return;
292301217Sgnn            }
293128636Sluigi        }
294287861Smelifaro    }
29574408Smdodd
29674408Smdodd    @Override
29774408Smdodd    public void targetFinished(BuildEvent event) {
29844165Sjulian        if (!statusEvents.isEmpty()) {
29974408Smdodd            StatusEvent lastEvent = statusEvents.pop();
300249925Sglebius            if (lastEvent.target.matches(event.getTarget().getName())) {
30144627Sjulian                logger.taskFinished(lastEvent);
30244627Sjulian            }
30344627Sjulian        }
30444627Sjulian    }
30544627Sjulian
30644627Sjulian    @Override
307108533Sschweikh    public void taskStarted(BuildEvent event) {
30844627Sjulian        String taskName = event.getTask().getTaskName();
30944165Sjulian        for (Task task : Task.values()) {
310249925Sglebius            if (task.matches(taskName)) {
31144627Sjulian                tasks.push(task);
31244627Sjulian                return;
313249925Sglebius            }
314249925Sglebius        }
315249925Sglebius    }
31644627Sjulian
31744165Sjulian    @Override
31874408Smdodd    public void taskFinished(BuildEvent event) {
31944165Sjulian        if (tasks.peek() == Task.ROOT) {
320105598Sbrooks            //we need to 'close' the root task to get nicer output
32144165Sjulian            logger.taskFinished(event);
32274408Smdodd        } else if (!suppressTaskFailures && event.getException() != null) {
32344165Sjulian            //the first (innermost) task failure should always be logged
32444165Sjulian            event.setMessage(event.getException().toString(), 0);
325112274Smdodd            event.setException(null);
326112274Smdodd            //note: we turn this into a plain message to avoid stack trace being logged by Idea
327112274Smdodd            logger.messageLogged(event);
32874408Smdodd            suppressTaskFailures = true;
32974408Smdodd        }
330243882Sglebius        tasks.pop();
331298075Spfg    }
33274408Smdodd
33374408Smdodd    @Override
334112281Smdodd    public void messageLogged(BuildEvent event) {
33574408Smdodd        String msg = event.getMessage();
336112268Smdodd
337112268Smdodd        boolean processed = false;
338112268Smdodd
339112274Smdodd        if (!tasks.isEmpty()) {
34074408Smdodd            Task task = tasks.peek();
34174408Smdodd            for (MessageKind messageKind : task.msgs) {
34244165Sjulian                if (messageKind.matches(msg)) {
34344165Sjulian                    event.setMessage(msg, messageKind.priority);
34444165Sjulian                    processed = true;
34544165Sjulian                    if (messageKind == MessageKind.JAVAC_CRASH) {
346243882Sglebius                        crashFound = true;
347298075Spfg                    }
34844165Sjulian                    break;
349112274Smdodd                }
350112291Smdodd            }
35144627Sjulian        }
35244627Sjulian
35344627Sjulian        if (event.getPriority() == MSG_ERR || crashFound) {
35444627Sjulian            //we log errors regardless of owning task
35544165Sjulian            logger.messageLogged(event);
35644165Sjulian            suppressTaskFailures = true;
35744165Sjulian        } else if (processed) {
35844165Sjulian            logger.messageLogged(event);
35944165Sjulian        }
36044165Sjulian    }
36144165Sjulian}
36244165Sjulian