summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKlaus Aehlig <klaus.aehlig@huawei.com>2025-06-13 14:50:44 +0200
committerKlaus Aehlig <klaus.aehlig@huawei.com>2025-06-16 17:43:13 +0200
commit0cf23b92126dc2ae07f1a2e065d9747992275fde (patch)
treed72d2edcbb462bb8f768aa86289c367ed159452f
parent6628cdb47abbac1d2982698f75aa4ff9fd08bdfc (diff)
downloadjustbuild-0cf23b92126dc2ae07f1a2e065d9747992275fde.tar.gz
Add lint target for iwyu
Co-authored-by: Maksim Denisov <denisov.maksim@huawei.com> Co-authored-by: Paul Cristian Sarbu <paul.cristian.sarbu@huawei.com>
-rw-r--r--lint/TARGETS16
-rw-r--r--lint/iwyu-mapping.imp126
-rwxr-xr-xlint/run_iwyu.py117
3 files changed, 259 insertions, 0 deletions
diff --git a/lint/TARGETS b/lint/TARGETS
index 0a52a37d..5e260c79 100644
--- a/lint/TARGETS
+++ b/lint/TARGETS
@@ -4,6 +4,7 @@
, "dirs":
[ ["LINT: clang-tidy", "clang-tidy"]
, ["LINT: clang-format", "clang-format"]
+ , ["LINT: iwyu", "iwyu"]
]
}
, "clang toolchain":
@@ -63,4 +64,19 @@
, "outs": ["format.diff"]
, "deps": ["LINT: clang-format", "create-diff.py"]
}
+, "iwyu config":
+ {"type": "install", "files": {"iwyu-mapping": "iwyu-mapping.imp"}}
+, "LINT: iwyu":
+ { "type": ["@", "rules", "lint", "targets"]
+ , "tainted": ["test"]
+ , "name": ["iwyu"]
+ , "linter": ["run_iwyu.py"]
+ , "summarizer": ["summary.py"]
+ , "config": ["iwyu config", "clang"]
+ , "targets":
+ [ ["@", "src", "src/buildtool/main", "just"]
+ , ["@", "src", "src/other_tools/just_mr", "just-mr"]
+ , ["@", "tests", "", "ALL"]
+ ]
+ }
}
diff --git a/lint/iwyu-mapping.imp b/lint/iwyu-mapping.imp
new file mode 100644
index 00000000..4c36bb59
--- /dev/null
+++ b/lint/iwyu-mapping.imp
@@ -0,0 +1,126 @@
+# Mapping file for include-what-you-use.
+# Check rules at: https://github.com/include-what-you-use/include-what-you-use/blob/master/docs/IWYUMappings.md
+
+# Take into account that the order in which entries are given for the same
+# symbol is important.
+# For instance for grpc::Status <grpcpp/support/status.h> has a higher priority:
+# { "symbol": [ "grpc::Status", "private", "<grpcpp/support/status.h>", "public"] },
+# { "symbol": [ "grpc::Status", "private", "<grpcpp/grpcpp.h>", "public"] },
+[
+ # Map C headers
+ { "include": ["<stdlib.h>", "private", "<cstdlib>", "public"] },
+ { "include": ["<stdio.h>", "private", "<cstdio>", "public"] },
+ { "include": ["<stdint.h>", "private", "<cstdint>", "public"] },
+ { "include": ["<math.h>", "private", "<cmath>", "public"] },
+ { "include": ["<string.h>", "private", "<cstring>", "public"] },
+ { "include": ["<errno.h>", "private", "<cerrno>", "public"] },
+ { "include": ["<time.h>", "private", "<ctime>", "public"] },
+ { "include": ["<ctype.h>", "private", "<cctype>", "public"] },
+
+ # Hide std implementation details headers:
+ { "include": ["<asm/errno-base.h>", "private", "<cerrno>", "public"] },
+ { "include": ["<bits/exception.h>", "private", "<exception>", "public"] },
+ { "include": ["<bits/fs_ops.h>", "private", "<filesystem>", "public"] },
+ { "include": ["<bits/fs_path.h>", "private", "<filesystem>", "public"] },
+ { "include": ["<bits/fs_fwd.h>", "private", "<filesystem>", "public"] },
+ { "include": ["<bits/fs_dir.h>", "private", "<filesystem>", "public"] },
+ { "include": ["<bits/std_function.h>", "private", "<functional>", "public"] },
+ { "include": ["<bits/shared_ptr.h>", "private", "<memory>", "public"] },
+ { "include": ["<bits/utility.h>", "private", "<utility>", "public"] },
+ { "include": ["<bits/chrono.h>", "private", "<chrono>", "public"] },
+ { "include": ["<bits/stdint-uintn.h>", "private", "<cstdint>", "public"] },
+ { "include": ["<bits/stdint-intn.h>", "private", "<cstdint>", "public"] },
+ { "include": ["<bits/refwrap.h>", "private", "<functional>", "public"] },
+ { "include": ["<bits/time.h>", "private", "<ctime>", "public"] },
+ { "include": ["<bits/types/struct_FILE.h>", "private", "<cstdio>", "public"] },
+ { "include": ["<bits/types/time_t.h>", "private", "<ctime>", "public"] },
+
+ # Map symbols:
+ { "symbol": ["timespec", "private", "<ctime>", "public"] },
+ { "symbol": ["tm", "private", "<ctime>", "public"] },
+ { "symbol": ["AT_FDCWD", "private", "<fcntl.h>", "public"] },
+ { "symbol": ["SEEK_END", "private", <cstdio>, "public"] },
+ { "symbol": ["SEEK_SET", "private", <cstdio>, "public"] },
+ { "symbol": ["pid_t", "private", <sys/types.h>, "public"] },
+ { "symbol": ["std::size_t", "private", "<cstddef>", "public"] },
+ { "symbol": ["size_t", "private", "<cstddef>", "public"] },
+ { "symbol": ["std::nullptr_t", "private", "<cstddef>", "public"] },
+ { "symbol": ["std::ptrdiff_t", "private", "<cstddef>", "public"] },
+ { "symbol": ["FILE", "private", "<cstdio>", "public"] },
+ { "symbol": ["NULL", "private", "<cstddef>", "public"] },
+ { "symbol": ["std::thread::id", "private", "<thread>", "public"] },
+ { "symbol": ["uint", "private", "<cstdint>", "public"] },
+ { "symbol": ["std::uint", "private", "<cstdint>", "public"] },
+ { "symbol": ["std::hash", "private", "<functional>", "public"] },
+
+ # Trick to "ignore" std::allocator and similar types we don't want to
+ # include manually. Taken from:
+ # https://github.com/include-what-you-use/include-what-you-use/blob/d2d092919f2774b5463e236e1ee9d56fb46ceb60/gcc.symbols.imp
+ { "symbol": [ "std::allocator", "private", "<memory>", "public"] },
+ { "symbol": [ "std::allocator", "private", "<string>", "public"] },
+ { "symbol": [ "std::allocator", "private", "<vector>", "public"] },
+ { "symbol": [ "std::allocator", "private", "<list>", "public"] },
+ { "symbol": [ "std::allocator", "private", "<map>", "public"] },
+ { "symbol": [ "std::allocator", "private", "<set>", "public"] },
+ { "symbol": [ "std::allocator", "private", "<unordered_map>", "public"] },
+ { "symbol": [ "std::allocator", "private", "<unordered_set>", "public"] },
+
+ { "symbol": [ "std::char_traits", "private", "<string>", "public"] },
+ { "symbol": [ "std::char_traits", "private", "<ostream>", "public"] },
+ { "symbol": [ "std::char_traits", "private", "<istream>", "public"] },
+ { "symbol": [ "std::char_traits", "private", "<iostream>", "public"] },
+ { "symbol": [ "std::char_traits", "private", "<ofstream>", "public"] },
+ { "symbol": [ "std::char_traits", "private", "<ifstream>", "public"] },
+ { "symbol": [ "std::char_traits", "private", "<fstream>", "public"] },
+ { "symbol": [ "std::char_traits", "private", "<sstream>", "public"] },
+
+ { "include": ["<locale>", "public", "<regex>", "public"] },
+
+ # Model the dependencies between the public standard stream headers,
+ # in order to include only the top-most library.
+ # Taken from:
+ # https://github.com/include-what-you-use/include-what-you-use/blob/377eaef70cdda47368939f4d9beabfabe3f628f0/iwyu_include_picker.cc#L623
+ { "include": [ "<ios>", "public", "<istream>", "public" ] },
+ { "include": [ "<ios>", "public", "<ostream>", "public" ] },
+ { "include": [ "<iosfwd>", "public", "<ios>", "public" ] },
+ { "include": [ "<iosfwd>", "public", "<streambuf>", "public" ] },
+ { "include": [ "<istream>", "public", "<fstream>", "public" ] },
+ { "include": [ "<istream>", "public", "<iostream>", "public" ] },
+ { "include": [ "<istream>", "public", "<sstream>", "public" ] },
+ { "include": [ "<ostream>", "public", "<fstream>", "public" ] },
+ { "include": [ "<ostream>", "public", "<iostream>", "public" ] },
+ { "include": [ "<ostream>", "public", "<istream>", "public" ] },
+ { "include": [ "<ostream>", "public", "<sstream>", "public" ] },
+ { "include": [ "<streambuf>", "public", "<ios>", "public"] },
+
+ # Use common headers for third-parties:
+ { "include": ["@\"gsl/.*\"", "private", "\"gsl/gsl\"", "public"] },
+ { "include": ["@\"nlohmann/.*\"", "private", "\"nlohmann/json.hpp\"", "public"] },
+
+ # "git2/XXX.h" => <git2.h> where XXX doesn't contain '/'
+ { "include": ["@\"git2/[^/]+\\.h\"", "private", "<git2.h>", "public"] },
+
+ # "fmt/core.h" is for compatibility reasons; otherwise, include the suggested
+ # headers if they don't break the pkg build
+ { "include": ["\"fmt/format.h\"", "private", "\"fmt/core.h\"", "public"] },
+ { "include": ["\"fmt/base.h\"", "private", "\"fmt/core.h\"", "public"] },
+
+ { "include": ["@\"catch2/matchers/.*\"", "private", "\"catch2/matchers/catch_matchers_all.hpp\"", "public"] },
+ { "include": ["@\"catch2/generators/.*\"", "private", "\"catch2/generators/catch_generators_all.hpp\"", "public"] },
+
+ # For compatibility reasons map all openssl headers to sha.h
+ { "include": ["@\"openssl/.*\"", "private", "\"openssl/sha.h\"", "public"] },
+
+ # GRPC:
+ { "include": ["@\"grpcpp/.*\"", "private", "<grpcpp/grpcpp.h>", "public"] },
+ { "include": ["@<grpcpp/.*>", "private", "<grpcpp/grpcpp.h>", "public"] },
+ { "symbol": [ "grpc::Status", "private", "<grpcpp/support/status.h>", "public"] },
+ { "symbol": [ "grpc::Status", "private", "<grpcpp/grpcpp.h>", "public"] },
+
+ # bazel Digest should be included via our bazel_types header:
+ { "include":
+ [ "\"build/bazel/remote/execution/v2/remote_execution.pb.h\"", "private"
+ , "\"src/buildtool/common/bazel_types.hpp\"", "public" ] },
+ # bazel_re namespace alias should also come from the bazel_types header:
+ { "symbol": ["bazel_re", "private", "\"src/buildtool/common/bazel_types.hpp\"", "public"] },
+]
diff --git a/lint/run_iwyu.py b/lint/run_iwyu.py
new file mode 100755
index 00000000..f4be3604
--- /dev/null
+++ b/lint/run_iwyu.py
@@ -0,0 +1,117 @@
+#!/usr/bin/env python3
+# Copyright 2025 Huawei Cloud Computing Technology Co., Ltd.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import json
+import os
+import shutil
+import subprocess
+import sys
+
+CXX_LIB_VERSION = "13.3.0"
+
+def dump_meta(src, cmd):
+ OUT = os.environ.get("OUT")
+ if OUT:
+ with open(os.path.join(OUT, "config.json"), "w") as f:
+ json.dump({"src": src, "cmd": cmd}, f)
+
+
+def run_lint(src, cmd):
+ dump_meta(src, cmd)
+ config = os.environ.get("CONFIG")
+ shutil.copyfile(os.path.join(config, "iwyu-mapping"), "iwyu-mapping.imp")
+
+ # Currently, .tpp files cannot be processed to produce meaningful suggestions
+ # Their corresponding .hpp files are analyzed separately, so just skip
+ if src.endswith(".tpp"):
+ return 0
+
+ # IWYU doesn't process headers by default
+ if src.endswith(".hpp"):
+ # To analyze .hpp files, we need to "pack" them into a source file:
+ # _fake.cpp includes the header and marks it as associated.
+ # We could simply change the command line, but this may cause
+ # IWYU to search for associated files by default (using stem).
+ # Example:
+ # // src/utils/cpp/gsl.hpp considered a source file:
+ # #include "gsl/gsl" // analysis happens because of names
+ # ...
+ TMPDIR = os.environ.get("TMPDIR")
+ if TMPDIR:
+ fake_stem = os.path.join(TMPDIR, "_fake")
+ with open(fake_stem + ".cpp", "w+") as fake:
+ content = "#include \"%s\" // IWYU pragma: associated" % (
+ src.split(os.path.sep, 1)[1])
+ fake.write(content)
+ src = os.path.realpath(fake.name)
+ else:
+ print("Failed to get TMPDIR", file=sys.stderr)
+ return 1
+
+ # The command line must be adjusted as well
+ e_index = cmd.index("-E")
+ cmd = cmd[:e_index] + [
+ "-c", src, "-o",
+ os.path.realpath(fake_stem) + ".o"
+ ] + cmd[e_index + 2:]
+
+ iwyu_flags = [
+ "--cxx17ns", # suggest the more concise syntax introduced in C++17
+ "--no_fwd_decls", # do not use forward declarations
+ "--no_default_mappings", # do not add iwyu's default mappings
+ "--mapping_file=iwyu-mapping.imp", # specifies a mapping file
+ "--error", # return 1 if there are any suggestions
+ "--verbose=2", # provide explanations
+ "--max_line_length=1000" # don't limit explanation messages
+ ]
+
+ iwyu_options = []
+ for option in iwyu_flags:
+ iwyu_options.append("-Xiwyu")
+ iwyu_options.append(option)
+
+ # add include paths from the bundled toolchain
+ baseincludepath = os.path.join(
+ config, "toolchain", "include", "c++", CXX_LIB_VERSION
+ )
+ # We're using the native toolchain, so arch-specific headers are
+ # only available for one arch. Hence we can try all supported candidates
+ # and add the ones found
+ for arch in ["x86_64", "arm"]:
+ idir = os.path.join(baseincludepath,
+ "%s-pc-linux-gnu" % (arch,))
+ if os.path.exists(idir):
+ iwyu_options += ["-isystem", idir]
+ iwyu_options += [
+ "-isystem", baseincludepath,
+ ]
+
+ iwyu_options.extend(cmd[1:])
+
+ # IWYU uses <> for headers from -isystem and quoted for -I
+ # https://github.com/include-what-you-use/include-what-you-use/issues/1070#issuecomment-1163177107
+ # So we "ask" IWYU here to include all non-system dependencies with quotes
+ iwyu_options = ["-I" if x == "-isystem" else x for x in iwyu_options]
+
+ new_cmd = [os.path.join(config, "toolchain", "bin", "include-what-you-use")]
+ new_cmd.extend(iwyu_options)
+ new_cmd.append(src)
+
+ print("Running cmd %r" % (new_cmd), file=sys.stderr)
+ return subprocess.run(new_cmd, stdout=subprocess.DEVNULL).returncode
+
+
+if __name__ == "__main__":
+ sys.exit(run_lint(sys.argv[1], sys.argv[2:]))