javac_wrapper.go 5.9 KB

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