summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKlaus Aehlig <klaus.aehlig@huawei.com>2025-06-16 15:25:33 +0200
committerKlaus Aehlig <klaus.aehlig@huawei.com>2025-06-16 17:45:39 +0200
commit5fd66ef6b6276675deb7e6be2d7e4483d2824048 (patch)
tree3ac271095409333d33ab3612d0e96af88795cea6
parent0cf23b92126dc2ae07f1a2e065d9747992275fde (diff)
downloadjustbuild-5fd66ef6b6276675deb7e6be2d7e4483d2824048.tar.gz
lint: add strict_deps
Add a test verifying that no header files are picked up from an indirect dependency.
-rw-r--r--lint/TARGETS13
-rwxr-xr-xlint/run_strict_deps.py82
2 files changed, 95 insertions, 0 deletions
diff --git a/lint/TARGETS b/lint/TARGETS
index 5e260c79..b3963739 100644
--- a/lint/TARGETS
+++ b/lint/TARGETS
@@ -5,6 +5,7 @@
[ ["LINT: clang-tidy", "clang-tidy"]
, ["LINT: clang-format", "clang-format"]
, ["LINT: iwyu", "iwyu"]
+ , ["LINT: strict_deps", "strict_deps"]
]
}
, "clang toolchain":
@@ -79,4 +80,16 @@
, ["@", "tests", "", "ALL"]
]
}
+, "LINT: strict_deps":
+ { "type": ["@", "rules", "lint", "targets"]
+ , "tainted": ["test"]
+ , "name": ["strict deps"]
+ , "linter": ["run_strict_deps.py"]
+ , "summarizer": ["summary.py"]
+ , "targets":
+ [ ["@", "src", "src/buildtool/main", "just"]
+ , ["@", "src", "src/other_tools/just_mr", "just-mr"]
+ , ["@", "tests", "", "ALL"]
+ ]
+ }
}
diff --git a/lint/run_strict_deps.py b/lint/run_strict_deps.py
new file mode 100755
index 00000000..c963f3cd
--- /dev/null
+++ b/lint/run_strict_deps.py
@@ -0,0 +1,82 @@
+#!/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 json
+import os
+import sys
+
+
+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)
+
+ with open(os.environ.get("META")) as f:
+ direct = json.load(f)["direct deps artifact names"]
+
+ include_dirs = []
+ for i in range(len(cmd)):
+ if cmd[i] in ["-I", "-isystem"]:
+ include_dirs += [cmd[i + 1]]
+
+ with open(src) as f:
+ lines = f.read().splitlines()
+
+ failed = False
+
+ def include_covered(include_path):
+ for d in direct:
+ rel_path = os.path.relpath(include_path, d)
+ if not rel_path.startswith('../'):
+ return True
+ return False
+
+ def handle_resolved_include(i, to_include, resolved):
+ nonlocal failed
+ if not include_covered(resolved):
+ failed = True
+ print("%03d %s" % (i, lines[i]))
+ print(
+ " ---> including %r which is only provided by an indirect dependency"
+ % (resolved, ))
+
+ def handle_include(i, to_include):
+ for d in include_dirs:
+ candidate = os.path.join(d, to_include)
+ if os.path.exists(candidate):
+ handle_resolved_include(i, to_include, candidate)
+
+ for i in range(len(lines)):
+ to_include = None
+ if lines[i].startswith('#include "'):
+ to_include = lines[i].split('"')[1]
+ if lines[i].startswith('#include <'):
+ to_include = lines[i].split('<', 1)[1].split('>')[0]
+ if to_include:
+ handle_include(i, to_include)
+
+ return 1 if failed else 0
+
+
+if __name__ == "__main__":
+ sys.exit(run_lint(sys.argv[1], sys.argv[2:]))