blob: 4679906d5634bd380c5504e8efc1e9c11524fff6 [file] [log] [blame]
// Copyright 2017 Google Inc. All rights reserved.
//
// 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.
// soong_javac_wrapper expects a javac command line and argments, executes
// it, and produces an ANSI colorized version of the output on stdout.
//
// It also hides the unhelpful and unhideable "warning there is a warning"
// messages.
//
// Each javac build statement has an order-only dependency on the
// soong_javac_wrapper tool, which means the javac command will not be rerun
// if soong_javac_wrapper changes. That means that soong_javac_wrapper must
// not do anything that will affect the results of the build.
package main
import (
"bufio"
"fmt"
"io"
"os"
"os/exec"
"regexp"
"strconv"
"syscall"
)
// Regular expressions are based on
// https://chromium.googlesource.com/chromium/src/+/master/build/android/gyp/javac.py
// Colors are based on clang's output
var (
filelinePrefix = `^([-.\w/\\]+.java:[0-9]+: )`
warningRe = regexp.MustCompile(filelinePrefix + `?(warning:) .*$`)
errorRe = regexp.MustCompile(filelinePrefix + `(.*?:) .*$`)
markerRe = regexp.MustCompile(`()\s*(\^)\s*$`)
escape = "\x1b"
reset = escape + "[0m"
bold = escape + "[1m"
red = escape + "[31m"
green = escape + "[32m"
magenta = escape + "[35m"
)
func main() {
exitCode, err := Main(os.Stdout, os.Args[0], os.Args[1:])
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
}
os.Exit(exitCode)
}
func Main(out io.Writer, name string, args []string) (int, error) {
if len(args) < 1 {
return 1, fmt.Errorf("usage: %s javac ...", name)
}
pr, pw, err := os.Pipe()
if err != nil {
return 1, fmt.Errorf("creating output pipe: %s", err)
}
cmd := exec.Command(args[0], args[1:]...)
cmd.Stdin = os.Stdin
cmd.Stdout = pw
cmd.Stderr = pw
err = cmd.Start()
if err != nil {
return 1, fmt.Errorf("starting subprocess: %s", err)
}
pw.Close()
proc := processor{}
// Process subprocess stdout asynchronously
errCh := make(chan error)
go func() {
errCh <- proc.process(pr, out)
}()
// Wait for subprocess to finish
cmdErr := cmd.Wait()
// Wait for asynchronous stdout processing to finish
err = <-errCh
// Check for subprocess exit code
if cmdErr != nil {
if exitErr, ok := cmdErr.(*exec.ExitError); ok {
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
if status.Exited() {
return status.ExitStatus(), nil
} else if status.Signaled() {
exitCode := 128 + int(status.Signal())
return exitCode, nil
} else {
return 1, exitErr
}
} else {
return 1, nil
}
}
}
if err != nil {
return 1, err
}
return 0, nil
}
type processor struct {
silencedWarnings int
}
func (proc *processor) process(r io.Reader, w io.Writer) error {
scanner := bufio.NewScanner(r)
// Some javac wrappers output the entire list of java files being
// compiled on a single line, which can be very large, set the maximum
// buffer size to 2MB.
scanner.Buffer(nil, 2*1024*1024)
for scanner.Scan() {
proc.processLine(w, scanner.Text())
}
err := scanner.Err()
if err != nil {
return fmt.Errorf("scanning input: %s", err)
}
return nil
}
func (proc *processor) processLine(w io.Writer, line string) {
for _, f := range warningFilters {
if f.MatchString(line) {
proc.silencedWarnings++
return
}
}
for _, f := range filters {
if f.MatchString(line) {
return
}
}
if match := warningCount.FindStringSubmatch(line); match != nil {
c, err := strconv.Atoi(match[1])
if err == nil {
c -= proc.silencedWarnings
if c == 0 {
return
} else {
line = fmt.Sprintf("%d warning", c)
if c > 1 {
line += "s"
}
}
}
}
for _, p := range colorPatterns {
var matched bool
if line, matched = applyColor(line, p.color, p.re); matched {
break
}
}
fmt.Fprintln(w, line)
}
// If line matches re, make it bold and apply color to the first submatch
// Returns line, modified if it matched, and true if it matched.
func applyColor(line, color string, re *regexp.Regexp) (string, bool) {
if m := re.FindStringSubmatchIndex(line); m != nil {
tagStart, tagEnd := m[4], m[5]
line = bold + line[:tagStart] +
color + line[tagStart:tagEnd] + reset + bold +
line[tagEnd:] + reset
return line, true
}
return line, false
}
var colorPatterns = []struct {
re *regexp.Regexp
color string
}{
{warningRe, magenta},
{errorRe, red},
{markerRe, green},
}
var warningCount = regexp.MustCompile(`^([0-9]+) warning(s)?$`)
var warningFilters = []*regexp.Regexp{
regexp.MustCompile(`bootstrap class path not set in conjunction with -source`),
}
var filters = []*regexp.Regexp{
regexp.MustCompile(`Note: (Some input files|.*\.java) uses? or overrides? a deprecated API.`),
regexp.MustCompile(`Note: Recompile with -Xlint:deprecation for details.`),
regexp.MustCompile(`Note: (Some input files|.*\.java) uses? unchecked or unsafe operations.`),
regexp.MustCompile(`Note: Recompile with -Xlint:unchecked for details.`),
regexp.MustCompile(`javadoc: warning - The old Doclet and Taglet APIs in the packages`),
regexp.MustCompile(`com.sun.javadoc, com.sun.tools.doclets and their implementations`),
regexp.MustCompile(`are planned to be removed in a future JDK release. These`),
regexp.MustCompile(`components have been superseded by the new APIs in jdk.javadoc.doclet.`),
regexp.MustCompile(`Users are strongly recommended to migrate to the new APIs.`),
regexp.MustCompile(`javadoc: option --boot-class-path not allowed with target 1.9`),
}