1// Copyright 2017 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15// soong_javac_wrapper expects a javac command line and argments, executes 16// it, and produces an ANSI colorized version of the output on stdout. 17// 18// It also hides the unhelpful and unhideable "warning there is a warning" 19// messages. 20// 21// Each javac build statement has an order-only dependency on the 22// soong_javac_wrapper tool, which means the javac command will not be rerun 23// if soong_javac_wrapper changes. That means that soong_javac_wrapper must 24// not do anything that will affect the results of the build. 25package main 26 27import ( 28 "bufio" 29 "fmt" 30 "io" 31 "os" 32 "os/exec" 33 "regexp" 34 "strconv" 35 "syscall" 36) 37 38// Regular expressions are based on 39// https://chromium.googlesource.com/chromium/src/+/master/build/android/gyp/javac.py 40// Colors are based on clang's output 41var ( 42 filelinePrefix = `^([-.\w/\\]+.java:[0-9]+: )` 43 warningRe = regexp.MustCompile(filelinePrefix + `?(warning:) .*$`) 44 errorRe = regexp.MustCompile(filelinePrefix + `(.*?:) .*$`) 45 markerRe = regexp.MustCompile(`()\s*(\^)\s*$`) 46 47 escape = "\x1b" 48 reset = escape + "[0m" 49 bold = escape + "[1m" 50 red = escape + "[31m" 51 green = escape + "[32m" 52 magenta = escape + "[35m" 53) 54 55func main() { 56 exitCode, err := Main(os.Stdout, os.Args[0], os.Args[1:]) 57 if err != nil { 58 fmt.Fprintln(os.Stderr, err.Error()) 59 } 60 os.Exit(exitCode) 61} 62 63func Main(out io.Writer, name string, args []string) (int, error) { 64 if len(args) < 1 { 65 return 1, fmt.Errorf("usage: %s javac ...", name) 66 } 67 68 pr, pw, err := os.Pipe() 69 if err != nil { 70 return 1, fmt.Errorf("creating output pipe: %s", err) 71 } 72 73 cmd := exec.Command(args[0], args[1:]...) 74 cmd.Stdin = os.Stdin 75 cmd.Stdout = pw 76 cmd.Stderr = pw 77 err = cmd.Start() 78 if err != nil { 79 return 1, fmt.Errorf("starting subprocess: %s", err) 80 } 81 82 pw.Close() 83 84 proc := processor{} 85 // Process subprocess stdout asynchronously 86 errCh := make(chan error) 87 go func() { 88 errCh <- proc.process(pr, out) 89 }() 90 91 // Wait for subprocess to finish 92 cmdErr := cmd.Wait() 93 94 // Wait for asynchronous stdout processing to finish 95 err = <-errCh 96 97 // Check for subprocess exit code 98 if cmdErr != nil { 99 if exitErr, ok := cmdErr.(*exec.ExitError); ok { 100 if status, ok := exitErr.Sys().(syscall.WaitStatus); ok { 101 if status.Exited() { 102 return status.ExitStatus(), nil 103 } else if status.Signaled() { 104 exitCode := 128 + int(status.Signal()) 105 return exitCode, nil 106 } else { 107 return 1, exitErr 108 } 109 } else { 110 return 1, nil 111 } 112 } 113 } 114 115 if err != nil { 116 return 1, err 117 } 118 119 return 0, nil 120} 121 122type processor struct { 123 silencedWarnings int 124} 125 126func (proc *processor) process(r io.Reader, w io.Writer) error { 127 scanner := bufio.NewScanner(r) 128 // Some javac wrappers output the entire list of java files being 129 // compiled on a single line, which can be very large, set the maximum 130 // buffer size to 2MB. 131 scanner.Buffer(nil, 2*1024*1024) 132 for scanner.Scan() { 133 proc.processLine(w, scanner.Text()) 134 } 135 err := scanner.Err() 136 if err != nil { 137 return fmt.Errorf("scanning input: %s", err) 138 } 139 return nil 140} 141 142func (proc *processor) processLine(w io.Writer, line string) { 143 for _, f := range warningFilters { 144 if f.MatchString(line) { 145 proc.silencedWarnings++ 146 return 147 } 148 } 149 for _, f := range filters { 150 if f.MatchString(line) { 151 return 152 } 153 } 154 if match := warningCount.FindStringSubmatch(line); match != nil { 155 c, err := strconv.Atoi(match[1]) 156 if err == nil { 157 c -= proc.silencedWarnings 158 if c == 0 { 159 return 160 } else { 161 line = fmt.Sprintf("%d warning", c) 162 if c > 1 { 163 line += "s" 164 } 165 } 166 } 167 } 168 for _, p := range colorPatterns { 169 var matched bool 170 if line, matched = applyColor(line, p.color, p.re); matched { 171 break 172 } 173 } 174 fmt.Fprintln(w, line) 175} 176 177// If line matches re, make it bold and apply color to the first submatch 178// Returns line, modified if it matched, and true if it matched. 179func applyColor(line, color string, re *regexp.Regexp) (string, bool) { 180 if m := re.FindStringSubmatchIndex(line); m != nil { 181 tagStart, tagEnd := m[4], m[5] 182 line = bold + line[:tagStart] + 183 color + line[tagStart:tagEnd] + reset + bold + 184 line[tagEnd:] + reset 185 return line, true 186 } 187 return line, false 188} 189 190var colorPatterns = []struct { 191 re *regexp.Regexp 192 color string 193}{ 194 {warningRe, magenta}, 195 {errorRe, red}, 196 {markerRe, green}, 197} 198 199var warningCount = regexp.MustCompile(`^([0-9]+) warning(s)?$`) 200 201var warningFilters = []*regexp.Regexp{ 202 regexp.MustCompile(`bootstrap class path not set in conjunction with -source`), 203} 204 205var filters = []*regexp.Regexp{ 206 regexp.MustCompile(`Note: (Some input files|.*\.java) uses? or overrides? a deprecated API.`), 207 regexp.MustCompile(`Note: Recompile with -Xlint:deprecation for details.`), 208 regexp.MustCompile(`Note: (Some input files|.*\.java) uses? unchecked or unsafe operations.`), 209 regexp.MustCompile(`Note: Recompile with -Xlint:unchecked for details.`), 210 211 regexp.MustCompile(`javadoc: warning - The old Doclet and Taglet APIs in the packages`), 212 regexp.MustCompile(`com.sun.javadoc, com.sun.tools.doclets and their implementations`), 213 regexp.MustCompile(`are planned to be removed in a future JDK release. These`), 214 regexp.MustCompile(`components have been superseded by the new APIs in jdk.javadoc.doclet.`), 215 regexp.MustCompile(`Users are strongly recommended to migrate to the new APIs.`), 216 217 regexp.MustCompile(`javadoc: option --boot-class-path not allowed with target 1.9`), 218} 219