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 "syscall" 35) 36 37// Regular expressions are based on 38// https://chromium.googlesource.com/chromium/src/+/master/build/android/gyp/javac.py 39// Colors are based on clang's output 40var ( 41 filelinePrefix = `^([-.\w/\\]+.java:[0-9]+: )` 42 warningRe = regexp.MustCompile(filelinePrefix + `?(warning:) .*$`) 43 errorRe = regexp.MustCompile(filelinePrefix + `(.*?:) .*$`) 44 markerRe = regexp.MustCompile(`()\s*(\^)\s*$`) 45 46 escape = "\x1b" 47 reset = escape + "[0m" 48 bold = escape + "[1m" 49 red = escape + "[31m" 50 green = escape + "[32m" 51 magenta = escape + "[35m" 52) 53 54func main() { 55 exitCode, err := Main(os.Stdout, os.Args[0], os.Args[1:]) 56 if err != nil { 57 fmt.Fprintln(os.Stderr, err.Error()) 58 } 59 os.Exit(exitCode) 60} 61 62func Main(out io.Writer, name string, args []string) (int, error) { 63 if len(args) < 1 { 64 return 1, fmt.Errorf("usage: %s javac ...", name) 65 } 66 67 pr, pw, err := os.Pipe() 68 if err != nil { 69 return 1, fmt.Errorf("creating output pipe: %s", err) 70 } 71 72 cmd := exec.Command(args[0], args[1:]...) 73 cmd.Stdin = os.Stdin 74 cmd.Stdout = pw 75 cmd.Stderr = pw 76 err = cmd.Start() 77 if err != nil { 78 return 1, fmt.Errorf("starting subprocess: %s", err) 79 } 80 81 pw.Close() 82 83 // Process subprocess stdout asynchronously 84 errCh := make(chan error) 85 go func() { 86 errCh <- process(pr, out) 87 }() 88 89 // Wait for subprocess to finish 90 cmdErr := cmd.Wait() 91 92 // Wait for asynchronous stdout processing to finish 93 err = <-errCh 94 95 // Check for subprocess exit code 96 if cmdErr != nil { 97 if exitErr, ok := cmdErr.(*exec.ExitError); ok { 98 if status, ok := exitErr.Sys().(syscall.WaitStatus); ok { 99 if status.Exited() { 100 return status.ExitStatus(), nil 101 } else if status.Signaled() { 102 exitCode := 128 + int(status.Signal()) 103 return exitCode, nil 104 } else { 105 return 1, exitErr 106 } 107 } else { 108 return 1, nil 109 } 110 } 111 } 112 113 if err != nil { 114 return 1, err 115 } 116 117 return 0, nil 118} 119 120func process(r io.Reader, w io.Writer) error { 121 scanner := bufio.NewScanner(r) 122 // Some javac wrappers output the entire list of java files being 123 // compiled on a single line, which can be very large, set the maximum 124 // buffer size to 2MB. 125 scanner.Buffer(nil, 2*1024*1024) 126 for scanner.Scan() { 127 processLine(w, scanner.Text()) 128 } 129 err := scanner.Err() 130 if err != nil { 131 return fmt.Errorf("scanning input: %s", err) 132 } 133 return nil 134} 135 136func processLine(w io.Writer, line string) { 137 for _, f := range filters { 138 if f.MatchString(line) { 139 return 140 } 141 } 142 for _, p := range colorPatterns { 143 var matched bool 144 if line, matched = applyColor(line, p.color, p.re); matched { 145 break 146 } 147 } 148 fmt.Fprintln(w, line) 149} 150 151// If line matches re, make it bold and apply color to the first submatch 152// Returns line, modified if it matched, and true if it matched. 153func applyColor(line, color string, re *regexp.Regexp) (string, bool) { 154 if m := re.FindStringSubmatchIndex(line); m != nil { 155 tagStart, tagEnd := m[4], m[5] 156 line = bold + line[:tagStart] + 157 color + line[tagStart:tagEnd] + reset + bold + 158 line[tagEnd:] + reset 159 return line, true 160 } 161 return line, false 162} 163 164var colorPatterns = []struct { 165 re *regexp.Regexp 166 color string 167}{ 168 {warningRe, magenta}, 169 {errorRe, red}, 170 {markerRe, green}, 171} 172 173var filters = []*regexp.Regexp{ 174 regexp.MustCompile(`Note: (Some input files|.*\.java) uses? or overrides? a deprecated API.`), 175 regexp.MustCompile(`Note: Recompile with -Xlint:deprecation for details.`), 176 regexp.MustCompile(`Note: (Some input files|.*\.java) uses? unchecked or unsafe operations.`), 177 regexp.MustCompile(`Note: Recompile with -Xlint:unchecked for details.`), 178 regexp.MustCompile(`bootstrap class path not set in conjunction with -source`), 179 180 regexp.MustCompile(`javadoc: warning - The old Doclet and Taglet APIs in the packages`), 181 regexp.MustCompile(`com.sun.javadoc, com.sun.tools.doclets and their implementations`), 182 regexp.MustCompile(`are planned to be removed in a future JDK release. These`), 183 regexp.MustCompile(`components have been superseded by the new APIs in jdk.javadoc.doclet.`), 184 regexp.MustCompile(`Users are strongly recommended to migrate to the new APIs.`), 185 186 regexp.MustCompile(`javadoc: option --boot-class-path not allowed with target 1.9`), 187} 188