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