• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2022 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package main
6
7import (
8	"bufio"
9	"bytes"
10	"fmt"
11	"path/filepath"
12	"strings"
13)
14
15type useIWYUMode int
16
17const iwyuCrashSubstring = "PLEASE submit a bug report"
18
19const (
20	iwyuModeNone useIWYUMode = iota
21	iwyuModeAll
22	iwyuModeError
23)
24
25var srcFileSuffixes = []string{
26	".c",
27	".cc",
28	".cpp",
29	".C",
30	".cxx",
31	".c++",
32}
33
34func findWithIWYUFlag(args []builderArg) (string, []builderArg) {
35	for i := range args {
36		if args[i].value == "--with-iwyu" {
37			args = append(args[:i], args[i+1:]...)
38			return "1", args
39		}
40	}
41	return "", args
42}
43
44func processIWYUFlags(builder *commandBuilder) (cSrcFile string, iwyuFlags []string, mode useIWYUMode) {
45	builder.transformArgs(func(arg builderArg) string {
46		const prefix = "-iwyu-flag="
47		if !strings.HasPrefix(arg.value, prefix) {
48			return arg.value
49		}
50
51		iwyuFlags = append(iwyuFlags, arg.value[len(prefix):])
52		return ""
53	})
54
55	cSrcFile = ""
56	lastArg := ""
57	for _, arg := range builder.args {
58		if lastArg != "-o" {
59			for _, suffix := range srcFileSuffixes {
60				if strings.HasSuffix(arg.value, suffix) {
61					cSrcFile = arg.value
62					break
63				}
64			}
65		}
66		lastArg = arg.value
67	}
68
69	if cSrcFile == "" {
70		return "", iwyuFlags, iwyuModeNone
71	}
72
73	withIWYU, _ := builder.env.getenv("WITH_IWYU")
74	if withIWYU == "" {
75		withIWYU, builder.args = findWithIWYUFlag(builder.args)
76		if withIWYU == "" {
77			return cSrcFile, iwyuFlags, iwyuModeNone
78		}
79	}
80
81	if withIWYU != "1" {
82		return cSrcFile, iwyuFlags, iwyuModeError
83	}
84
85	return cSrcFile, iwyuFlags, iwyuModeAll
86}
87
88func calcIWYUInvocation(env env, clangCmd *command, cSrcFile string, iwyuFlags ...string) (*command, error) {
89	resourceDir, err := getClangResourceDir(env, clangCmd.Path)
90	if err != nil {
91		return nil, err
92	}
93
94	iwyuPath := filepath.Join(filepath.Dir(clangCmd.Path), "include-what-you-use")
95	args := append([]string{}, iwyuFlags...)
96	args = append(args, "-resource-dir="+resourceDir)
97	args = append(args, clangCmd.Args...)
98
99	for i := 0; i < len(args); i++ {
100		for j := 0; j < len(srcFileSuffixes); j++ {
101			if strings.HasSuffix(args[i], srcFileSuffixes[j]) {
102				args = append(args[:i], args[i+1:]...)
103				break
104			}
105		}
106	}
107	args = append(args, cSrcFile)
108
109	return &command{
110		Path:       iwyuPath,
111		Args:       args,
112		EnvUpdates: clangCmd.EnvUpdates,
113	}, nil
114}
115
116func runIWYU(env env, clangCmd *command, cSrcFile string, extraIWYUFlags []string) error {
117	extraIWYUFlags = append(extraIWYUFlags, "-Xiwyu", "--mapping_file=/usr/share/include-what-you-use/libcxx.imp", "-Xiwyu", "--no_fwd_decls")
118	iwyuCmd, err := calcIWYUInvocation(env, clangCmd, cSrcFile, extraIWYUFlags...)
119	if err != nil {
120		return fmt.Errorf("calculating include-what-you-use invocation: %v", err)
121	}
122
123	// Note: We pass nil as stdin as we checked before that the compiler
124	// was invoked with a source file argument.
125	var stderr bytes.Buffer
126	stderrWriter := bufio.NewWriter(&stderr)
127	exitCode, err := wrapSubprocessErrorWithSourceLoc(iwyuCmd,
128		env.run(iwyuCmd, nil, nil, stderrWriter))
129	stderrMessage := stderr.String()
130	fmt.Fprintln(env.stderr(), stderrMessage)
131
132	if err == nil && exitCode != 0 {
133		// Note: We continue on purpose when include-what-you-use fails
134		// to maintain compatibility with the previous wrapper.
135		fmt.Fprintln(env.stderr(), "include-what-you-use failed")
136	}
137
138	var path strings.Builder
139	path.WriteString(strings.TrimSuffix(iwyuCmd.Path, "include-what-you-use"))
140	path.WriteString("fix_includes.py")
141	fixIncludesCmd := &command{
142		Path:       path.String(),
143		Args:       []string{"--nocomment"},
144		EnvUpdates: clangCmd.EnvUpdates,
145	}
146
147	exitCode, err = wrapSubprocessErrorWithSourceLoc(fixIncludesCmd,
148		env.run(fixIncludesCmd, strings.NewReader(stderrMessage), env.stdout(), env.stderr()))
149	if err == nil && exitCode != 0 {
150		// Note: We continue on purpose when include-what-you-use fails
151		// to maintain compatibility with the previous wrapper.
152		fmt.Fprint(env.stderr(), "include-what-you-use failed")
153	}
154
155	return err
156}
157