diff options
author | Klaus Aehlig <klaus.aehlig@huawei.com> | 2025-06-13 14:50:44 +0200 |
---|---|---|
committer | Klaus Aehlig <klaus.aehlig@huawei.com> | 2025-06-16 17:43:13 +0200 |
commit | 0cf23b92126dc2ae07f1a2e065d9747992275fde (patch) | |
tree | d72d2edcbb462bb8f768aa86289c367ed159452f | |
parent | 6628cdb47abbac1d2982698f75aa4ff9fd08bdfc (diff) | |
download | justbuild-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/TARGETS | 16 | ||||
-rw-r--r-- | lint/iwyu-mapping.imp | 126 | ||||
-rwxr-xr-x | lint/run_iwyu.py | 117 |
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:])) |