compiler-wrapper: adds an IWYU component

This first patch makes it possible to run IWYU as a part of the build
process. It's not currently possible for us to make changes to packages:
this functionality will appear in a later CL.

BUG=b:237320348
TEST=Tested locally

Change-Id: I00610284143cf478b242b2c0ca1c05e2c8d43de4
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/toolchain-utils/+/3820351
Reviewed-by: Ryan Beltran <ryanbeltran@chromium.org>
Auto-Submit: Christopher Di Bella <cjdb@google.com>
Commit-Queue: Ryan Beltran <ryanbeltran@chromium.org>
Tested-by: Christopher Di Bella <cjdb@google.com>
diff --git a/compiler_wrapper/compiler_wrapper.go b/compiler_wrapper/compiler_wrapper.go
index 1386374..dcaada9 100644
--- a/compiler_wrapper/compiler_wrapper.go
+++ b/compiler_wrapper/compiler_wrapper.go
@@ -151,6 +151,7 @@
 		}
 	} else {
 		cSrcFile, tidyFlags, tidyMode := processClangTidyFlags(mainBuilder)
+		cSrcFile, iwyuFlags, iwyuMode := processIWYUFlags(mainBuilder)
 		if mainBuilder.target.compilerType == clangType {
 			err := prepareClangCommand(mainBuilder)
 			if err != nil {
@@ -176,6 +177,20 @@
 					return 0, err
 				}
 			}
+
+			if iwyuMode != iwyuModeNone {
+				if iwyuMode == iwyuModeError {
+					panic(fmt.Sprintf("Unknown IWYU mode"))
+				}
+
+				allowCCache = false
+				clangCmdWithoutRemoteBuildAndCCache := mainBuilder.build()
+				err := runIWYU(env, clangCmdWithoutRemoteBuildAndCCache, cSrcFile, iwyuFlags)
+				if err != nil {
+					return 0, err
+				}
+			}
+
 			if remoteBuildUsed, err = processRemoteBuildAndCCacheFlags(allowCCache, mainBuilder); err != nil {
 				return 0, err
 			}
diff --git a/compiler_wrapper/iwyu_flag.go b/compiler_wrapper/iwyu_flag.go
new file mode 100644
index 0000000..c1e6af6
--- /dev/null
+++ b/compiler_wrapper/iwyu_flag.go
@@ -0,0 +1,155 @@
+// Copyright 2022 The ChromiumOS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package main
+
+import (
+	"bufio"
+	"bytes"
+	"fmt"
+	"path/filepath"
+	"strings"
+)
+
+type useIWYUMode int
+
+const iwyuCrashSubstring = "PLEASE submit a bug report"
+
+const (
+	iwyuModeNone useIWYUMode = iota
+	iwyuModeAll
+	iwyuModeError
+)
+
+var srcFileSuffixes = []string{
+	".c",
+	".cc",
+	".cpp",
+	".C",
+	".cxx",
+	".c++",
+}
+
+func findWithIWYUFlag(args []builderArg) (string, []builderArg) {
+	for i := range args {
+		if args[i].value == "--with-iwyu" {
+			args = append(args[:i], args[i+1:]...)
+			return "1", args
+		}
+	}
+	return "", args
+}
+
+func processIWYUFlags(builder *commandBuilder) (cSrcFile string, iwyuFlags []string, mode useIWYUMode) {
+	builder.transformArgs(func(arg builderArg) string {
+		const prefix = "-iwyu-flag="
+		if !strings.HasPrefix(arg.value, prefix) {
+			return arg.value
+		}
+
+		iwyuFlags = append(iwyuFlags, arg.value[len(prefix):])
+		return ""
+	})
+
+	withIWYU, _ := builder.env.getenv("WITH_IWYU")
+	if withIWYU == "" {
+		withIWYU, builder.args = findWithIWYUFlag(builder.args)
+		if withIWYU == "" {
+			return "", iwyuFlags, iwyuModeNone
+		}
+	}
+
+	cSrcFile = ""
+	lastArg := ""
+	for _, arg := range builder.args {
+		if lastArg != "-o" {
+			for _, suffix := range srcFileSuffixes {
+				if strings.HasSuffix(arg.value, suffix) {
+					cSrcFile = arg.value
+					break
+				}
+			}
+		}
+		lastArg = arg.value
+	}
+
+	if cSrcFile == "" {
+		return "", iwyuFlags, iwyuModeNone
+	}
+
+	if withIWYU != "1" {
+		return "", iwyuFlags, iwyuModeError
+	}
+
+	return cSrcFile, iwyuFlags, iwyuModeAll
+}
+
+func calcIWYUInvocation(env env, clangCmd *command, cSrcFile string, iwyuFlags ...string) (*command, error) {
+	resourceDir, err := getClangResourceDir(env, clangCmd.Path)
+	if err != nil {
+		return nil, err
+	}
+
+	iwyuPath := filepath.Join(filepath.Dir(clangCmd.Path), "include-what-you-use")
+	args := append([]string{}, iwyuFlags...)
+	args = append(args, "-resource-dir="+resourceDir)
+	args = append(args, clangCmd.Args...)
+
+	for i := 0; i < len(args); i++ {
+		for j := 0; j < len(srcFileSuffixes); j++ {
+			if strings.HasSuffix(args[i], srcFileSuffixes[j]) {
+				args = append(args[:i], args[i+1:]...)
+				break
+			}
+		}
+	}
+	args = append(args, cSrcFile)
+
+	return &command{
+		Path:       iwyuPath,
+		Args:       args,
+		EnvUpdates: clangCmd.EnvUpdates,
+	}, nil
+}
+
+func runIWYU(env env, clangCmd *command, cSrcFile string, extraIWYUFlags []string) error {
+	extraIWYUFlags = append(extraIWYUFlags, "-Xiwyu", "--mapping_file=/usr/share/include-what-you-use/libcxx.imp", "-Xiwyu", "--no_fwd_decls")
+	iwyuCmd, err := calcIWYUInvocation(env, clangCmd, cSrcFile, extraIWYUFlags...)
+	if err != nil {
+		return fmt.Errorf("calculating include-what-you-use invocation: %v", err)
+	}
+
+	// Note: We pass nil as stdin as we checked before that the compiler
+	// was invoked with a source file argument.
+	var stderr bytes.Buffer
+	stderr_writer := bufio.NewWriter(&stderr)
+	exitCode, err := wrapSubprocessErrorWithSourceLoc(iwyuCmd,
+		env.run(iwyuCmd, nil, nil, stderr_writer))
+	stderr_ := stderr.String()
+	fmt.Fprintln(env.stderr(), stderr_)
+
+	if err == nil && exitCode != 0 {
+		// Note: We continue on purpose when include-what-you-use fails
+		// to maintain compatibility with the previous wrapper.
+		fmt.Fprintln(env.stderr(), "include-what-you-use failed")
+	}
+
+	var path strings.Builder
+	path.WriteString(strings.TrimSuffix(iwyuCmd.Path, "include-what-you-use"))
+	path.WriteString("fix_includes.py")
+	fixIncludesCmd := &command{
+		Path:       path.String(),
+		Args:       []string{"--nocomment"},
+		EnvUpdates: clangCmd.EnvUpdates,
+	}
+
+	exitCode, err = wrapSubprocessErrorWithSourceLoc(fixIncludesCmd,
+		env.run(fixIncludesCmd, strings.NewReader(stderr_), env.stdout(), env.stderr()))
+	if err == nil && exitCode != 0 {
+		// Note: We continue on purpose when include-what-you-use fails
+		// to maintain compatibility with the previous wrapper.
+		fmt.Fprint(env.stderr(), "include-what-you-use failed")
+	}
+	return nil
+}
diff --git a/compiler_wrapper/testdata/cros_clang_host_golden/clangtidy.json b/compiler_wrapper/testdata/cros_clang_host_golden/clangtidy.json
index c1cf050..d0a604b 100644
--- a/compiler_wrapper/testdata/cros_clang_host_golden/clangtidy.json
+++ b/compiler_wrapper/testdata/cros_clang_host_golden/clangtidy.json
@@ -27,7 +27,7 @@
           "path": "/tmp/stable/clang-tidy",
           "args": [
             "-checks=*,-bugprone-narrowing-conversions,-cppcoreguidelines-*,-fuchsia-*,-google-readability*,-google-runtime-references,-hicpp-*,-llvm-*,-misc-non-private-member-variables-in-classes,-misc-unused-parameters,-modernize-*,-readability-*",
-            "main.cc",
+            "",
             "--",
             "-resource-dir=someResourceDir",
             "-Qunused-arguments",
@@ -117,7 +117,7 @@
           "path": "/tmp/stable/clang-tidy",
           "args": [
             "-checks=*,-bugprone-narrowing-conversions,-cppcoreguidelines-*,-fuchsia-*,-google-readability*,-google-runtime-references,-hicpp-*,-llvm-*,-misc-non-private-member-variables-in-classes,-misc-unused-parameters,-modernize-*,-readability-*",
-            "main.cc",
+            "",
             "--",
             "-resource-dir=someResourceDir",
             "-Qunused-arguments",
@@ -209,7 +209,7 @@
           "path": "/tmp/stable/clang-tidy",
           "args": [
             "-checks=*,-bugprone-narrowing-conversions,-cppcoreguidelines-*,-fuchsia-*,-google-readability*,-google-runtime-references,-hicpp-*,-llvm-*,-misc-non-private-member-variables-in-classes,-misc-unused-parameters,-modernize-*,-readability-*",
-            "main.cc",
+            "",
             "--",
             "-resource-dir=someResourceDir",
             "-Qunused-arguments",
@@ -305,7 +305,7 @@
           "path": "/tmp/stable/clang-tidy",
           "args": [
             "-checks=*,-bugprone-narrowing-conversions,-cppcoreguidelines-*,-fuchsia-*,-google-readability*,-google-runtime-references,-hicpp-*,-llvm-*,-misc-non-private-member-variables-in-classes,-misc-unused-parameters,-modernize-*,-readability-*",
-            "main.cc",
+            "",
             "--",
             "-resource-dir=someResourceDir",
             "-Qunused-arguments",
diff --git a/compiler_wrapper/testdata/cros_hardened_golden/clangtidy.json b/compiler_wrapper/testdata/cros_hardened_golden/clangtidy.json
index f743894..bfef279 100644
--- a/compiler_wrapper/testdata/cros_hardened_golden/clangtidy.json
+++ b/compiler_wrapper/testdata/cros_hardened_golden/clangtidy.json
@@ -27,7 +27,7 @@
           "path": "../../usr/bin/clang-tidy",
           "args": [
             "-checks=*,-bugprone-narrowing-conversions,-cppcoreguidelines-*,-fuchsia-*,-google-readability*,-google-runtime-references,-hicpp-*,-llvm-*,-misc-non-private-member-variables-in-classes,-misc-unused-parameters,-modernize-*,-readability-*",
-            "main.cc",
+            "",
             "--",
             "-resource-dir=someResourceDir",
             "--sysroot=/usr/x86_64-cros-linux-gnu",
@@ -141,7 +141,7 @@
           "path": "../../usr/bin/clang-tidy",
           "args": [
             "-checks=*,-bugprone-narrowing-conversions,-cppcoreguidelines-*,-fuchsia-*,-google-readability*,-google-runtime-references,-hicpp-*,-llvm-*,-misc-non-private-member-variables-in-classes,-misc-unused-parameters,-modernize-*,-readability-*",
-            "main.cc",
+            "",
             "--",
             "-resource-dir=someResourceDir",
             "--sysroot=/usr/x86_64-cros-linux-gnu",
@@ -258,7 +258,7 @@
           "path": "../../usr/bin/clang-tidy",
           "args": [
             "-checks=*,-bugprone-narrowing-conversions,-cppcoreguidelines-*,-fuchsia-*,-google-readability*,-google-runtime-references,-hicpp-*,-llvm-*,-misc-non-private-member-variables-in-classes,-misc-unused-parameters,-modernize-*,-readability-*",
-            "main.cc",
+            "",
             "--",
             "-resource-dir=someResourceDir",
             "--sysroot=/usr/x86_64-cros-linux-gnu",
@@ -379,7 +379,7 @@
           "path": "../../usr/bin/clang-tidy",
           "args": [
             "-checks=*,-bugprone-narrowing-conversions,-cppcoreguidelines-*,-fuchsia-*,-google-readability*,-google-runtime-references,-hicpp-*,-llvm-*,-misc-non-private-member-variables-in-classes,-misc-unused-parameters,-modernize-*,-readability-*",
-            "main.cc",
+            "",
             "--",
             "-resource-dir=someResourceDir",
             "--sysroot=/usr/x86_64-cros-linux-gnu",
diff --git a/compiler_wrapper/testdata/cros_hardened_llvmnext_golden/clangtidy.json b/compiler_wrapper/testdata/cros_hardened_llvmnext_golden/clangtidy.json
index f743894..bfef279 100644
--- a/compiler_wrapper/testdata/cros_hardened_llvmnext_golden/clangtidy.json
+++ b/compiler_wrapper/testdata/cros_hardened_llvmnext_golden/clangtidy.json
@@ -27,7 +27,7 @@
           "path": "../../usr/bin/clang-tidy",
           "args": [
             "-checks=*,-bugprone-narrowing-conversions,-cppcoreguidelines-*,-fuchsia-*,-google-readability*,-google-runtime-references,-hicpp-*,-llvm-*,-misc-non-private-member-variables-in-classes,-misc-unused-parameters,-modernize-*,-readability-*",
-            "main.cc",
+            "",
             "--",
             "-resource-dir=someResourceDir",
             "--sysroot=/usr/x86_64-cros-linux-gnu",
@@ -141,7 +141,7 @@
           "path": "../../usr/bin/clang-tidy",
           "args": [
             "-checks=*,-bugprone-narrowing-conversions,-cppcoreguidelines-*,-fuchsia-*,-google-readability*,-google-runtime-references,-hicpp-*,-llvm-*,-misc-non-private-member-variables-in-classes,-misc-unused-parameters,-modernize-*,-readability-*",
-            "main.cc",
+            "",
             "--",
             "-resource-dir=someResourceDir",
             "--sysroot=/usr/x86_64-cros-linux-gnu",
@@ -258,7 +258,7 @@
           "path": "../../usr/bin/clang-tidy",
           "args": [
             "-checks=*,-bugprone-narrowing-conversions,-cppcoreguidelines-*,-fuchsia-*,-google-readability*,-google-runtime-references,-hicpp-*,-llvm-*,-misc-non-private-member-variables-in-classes,-misc-unused-parameters,-modernize-*,-readability-*",
-            "main.cc",
+            "",
             "--",
             "-resource-dir=someResourceDir",
             "--sysroot=/usr/x86_64-cros-linux-gnu",
@@ -379,7 +379,7 @@
           "path": "../../usr/bin/clang-tidy",
           "args": [
             "-checks=*,-bugprone-narrowing-conversions,-cppcoreguidelines-*,-fuchsia-*,-google-readability*,-google-runtime-references,-hicpp-*,-llvm-*,-misc-non-private-member-variables-in-classes,-misc-unused-parameters,-modernize-*,-readability-*",
-            "main.cc",
+            "",
             "--",
             "-resource-dir=someResourceDir",
             "--sysroot=/usr/x86_64-cros-linux-gnu",
diff --git a/compiler_wrapper/testdata/cros_hardened_noccache_golden/clangtidy.json b/compiler_wrapper/testdata/cros_hardened_noccache_golden/clangtidy.json
index f743894..bfef279 100644
--- a/compiler_wrapper/testdata/cros_hardened_noccache_golden/clangtidy.json
+++ b/compiler_wrapper/testdata/cros_hardened_noccache_golden/clangtidy.json
@@ -27,7 +27,7 @@
           "path": "../../usr/bin/clang-tidy",
           "args": [
             "-checks=*,-bugprone-narrowing-conversions,-cppcoreguidelines-*,-fuchsia-*,-google-readability*,-google-runtime-references,-hicpp-*,-llvm-*,-misc-non-private-member-variables-in-classes,-misc-unused-parameters,-modernize-*,-readability-*",
-            "main.cc",
+            "",
             "--",
             "-resource-dir=someResourceDir",
             "--sysroot=/usr/x86_64-cros-linux-gnu",
@@ -141,7 +141,7 @@
           "path": "../../usr/bin/clang-tidy",
           "args": [
             "-checks=*,-bugprone-narrowing-conversions,-cppcoreguidelines-*,-fuchsia-*,-google-readability*,-google-runtime-references,-hicpp-*,-llvm-*,-misc-non-private-member-variables-in-classes,-misc-unused-parameters,-modernize-*,-readability-*",
-            "main.cc",
+            "",
             "--",
             "-resource-dir=someResourceDir",
             "--sysroot=/usr/x86_64-cros-linux-gnu",
@@ -258,7 +258,7 @@
           "path": "../../usr/bin/clang-tidy",
           "args": [
             "-checks=*,-bugprone-narrowing-conversions,-cppcoreguidelines-*,-fuchsia-*,-google-readability*,-google-runtime-references,-hicpp-*,-llvm-*,-misc-non-private-member-variables-in-classes,-misc-unused-parameters,-modernize-*,-readability-*",
-            "main.cc",
+            "",
             "--",
             "-resource-dir=someResourceDir",
             "--sysroot=/usr/x86_64-cros-linux-gnu",
@@ -379,7 +379,7 @@
           "path": "../../usr/bin/clang-tidy",
           "args": [
             "-checks=*,-bugprone-narrowing-conversions,-cppcoreguidelines-*,-fuchsia-*,-google-readability*,-google-runtime-references,-hicpp-*,-llvm-*,-misc-non-private-member-variables-in-classes,-misc-unused-parameters,-modernize-*,-readability-*",
-            "main.cc",
+            "",
             "--",
             "-resource-dir=someResourceDir",
             "--sysroot=/usr/x86_64-cros-linux-gnu",
diff --git a/compiler_wrapper/testdata/cros_nonhardened_golden/clangtidy.json b/compiler_wrapper/testdata/cros_nonhardened_golden/clangtidy.json
index 830abee..3d5078d 100644
--- a/compiler_wrapper/testdata/cros_nonhardened_golden/clangtidy.json
+++ b/compiler_wrapper/testdata/cros_nonhardened_golden/clangtidy.json
@@ -27,7 +27,7 @@
           "path": "../../usr/bin/clang-tidy",
           "args": [
             "-checks=*,-bugprone-narrowing-conversions,-cppcoreguidelines-*,-fuchsia-*,-google-readability*,-google-runtime-references,-hicpp-*,-llvm-*,-misc-non-private-member-variables-in-classes,-misc-unused-parameters,-modernize-*,-readability-*",
-            "main.cc",
+            "",
             "--",
             "-resource-dir=someResourceDir",
             "--sysroot=/usr/x86_64-cros-linux-gnu",
@@ -127,7 +127,7 @@
           "path": "../../usr/bin/clang-tidy",
           "args": [
             "-checks=*,-bugprone-narrowing-conversions,-cppcoreguidelines-*,-fuchsia-*,-google-readability*,-google-runtime-references,-hicpp-*,-llvm-*,-misc-non-private-member-variables-in-classes,-misc-unused-parameters,-modernize-*,-readability-*",
-            "main.cc",
+            "",
             "--",
             "-resource-dir=someResourceDir",
             "--sysroot=/usr/x86_64-cros-linux-gnu",
@@ -230,7 +230,7 @@
           "path": "../../usr/bin/clang-tidy",
           "args": [
             "-checks=*,-bugprone-narrowing-conversions,-cppcoreguidelines-*,-fuchsia-*,-google-readability*,-google-runtime-references,-hicpp-*,-llvm-*,-misc-non-private-member-variables-in-classes,-misc-unused-parameters,-modernize-*,-readability-*",
-            "main.cc",
+            "",
             "--",
             "-resource-dir=someResourceDir",
             "--sysroot=/usr/x86_64-cros-linux-gnu",
@@ -337,7 +337,7 @@
           "path": "../../usr/bin/clang-tidy",
           "args": [
             "-checks=*,-bugprone-narrowing-conversions,-cppcoreguidelines-*,-fuchsia-*,-google-readability*,-google-runtime-references,-hicpp-*,-llvm-*,-misc-non-private-member-variables-in-classes,-misc-unused-parameters,-modernize-*,-readability-*",
-            "main.cc",
+            "",
             "--",
             "-resource-dir=someResourceDir",
             "--sysroot=/usr/x86_64-cros-linux-gnu",