File indexing completed on 2026-11-04 09:04:27
0001
0002
0003 from pathlib import Path
0004 import os
0005 import sys
0006 import subprocess
0007
0008 EXCLUDE_PATHS = (
0009 ".devcontainer",
0010 ".git",
0011 ".github",
0012 ".idea",
0013 "CI",
0014 "cmake",
0015
0016 "Detray/detectors",
0017
0018 "Detray/tests/tools",
0019 "Detray/python/Io/tests",
0020 "Detray/python/Tests/tests",
0021 "Detray/python/Plugins/tests",
0022
0023 "Traccc",
0024 "git",
0025 "Python",
0026 "Scripts",
0027
0028 "Tests/UnitTests/Plugins/DD4hep",
0029 "thirdparty",
0030 "white_papers/figures",
0031 )
0032 EXCLUDE_FILES = (
0033 ".gersemirc",
0034 ".gitignore",
0035 ".kodiak.toml",
0036 ".merge-sentinel.yml",
0037 ".policy.yml",
0038 ".pre-commit-config.yaml",
0039 "acts_logo_colored.svg",
0040 "CITATION.cff",
0041 "CMakeLists.txt",
0042 "CMakePresets.json",
0043 "CODE_OF_CONDUCT.md",
0044 "CODEOWNERS",
0045 "codecov.yml",
0046 "pytest.ini",
0047 "README.md",
0048 "readthedocs.yml",
0049 "sonar-project.properties",
0050
0051 "vertexing_event_mu20_beamspot.csv",
0052 "vertexing_event_mu20_tracks.csv",
0053 "vertexing_event_mu20_vertices_AMVF.csv",
0054 "event000000001-MuonDriftCircle.csv",
0055 "event000000001-MuonSimHit.csv",
0056
0057 "Magfield.ipynb",
0058 "SolenoidField.ipynb",
0059
0060 "generic-input-config.json",
0061 "generic-alignment-geo.json",
0062
0063 "codegen/src/codegen/sympy_common.py",
0064 "codegen/src/codegen/detray_backend.py",
0065 "CompressedIO.h",
0066 "generate_particle_data_table.py",
0067 "GeometryModule.h",
0068 "lazy_autodoc.py",
0069 "runtime_geometry_modules.md",
0070
0071 "acts-version-manager.js",
0072 "bugs.md",
0073 "deprecated.md",
0074 "Python/conftest.py",
0075 "serve.py",
0076 "SNIPPETS.md",
0077 "tex-mml-chtml.js",
0078 "tgeo_aux.py.in",
0079
0080 "docs/examples/test_generic.py",
0081 "docs/examples/test_performance_and_plotting.py",
0082 "docs/groups/python/performance_and_plotting.md",
0083 "todo.md",
0084
0085 "Detray/codegen/detray-sympy/tests/test_assumptions_D.py",
0086 "Detray/codegen/detray-sympy/tests/test_matrices.py",
0087
0088 "Detray/tests/include/detray/test/utils/perigee_stopper.hpp",
0089 "Detray/tests/include/detray/test/validation/propagation_validation.hpp",
0090
0091 "Detray/python/detray/detectors/impl/definitions.py",
0092 "Detray/python/detray/detectors/impl/type_helpers.py",
0093
0094 "Detray/codegen/detray-sympy/uv.lock",
0095 "Detray/python/detray/uv.lock",
0096
0097 "CI/pypi_readme.md",
0098
0099 "Core/include/Acts/Utilities/ProtoAxisHelpers.hpp",
0100 )
0101 SUFFIX_CPP = (
0102 ".hpp",
0103 ".cuh",
0104 ".sycl",
0105 ".hip",
0106 ".ipp",
0107 ".cpp",
0108 ".cu",
0109 )
0110 SUFFIX_IMAGE = (
0111 ".png",
0112 ".svg",
0113 ".jpg",
0114 ".gif",
0115 )
0116 SUFFIX_PYTHON = (".py",)
0117 SUFFIX_DOC = (
0118 ".md",
0119 ".rst",
0120 ".dox",
0121 ".html",
0122 ".bib",
0123 )
0124 SUFFIX_OTHER = (
0125 "",
0126 ".C",
0127 ".csv",
0128 ".css",
0129 ".gdml",
0130 ".hepmc3",
0131 ".lock",
0132 ".ico",
0133 ".in",
0134 ".ipynb",
0135 ".json",
0136 ".j2",
0137 ".onnx",
0138 ".root",
0139 ".toml",
0140 ".txt",
0141 ".yml",
0142 ".xml",
0143 ".sh",
0144 )
0145
0146
0147 def filter_paths(names, root, exclude_paths=(), exclude_files=()):
0148 """
0149 Filter names from os.walk() based on path substrings and file rules.
0150 Excludes entries if their full path matches exclude_paths or exclude_files.
0151 """
0152
0153 def keep(name):
0154 p = Path(root) / name
0155 p_str = p.as_posix()
0156 return not any(ep in p_str for ep in exclude_paths) and not any(
0157 ef in p_str if "/" in ef else p.name == ef for ef in exclude_files
0158 )
0159
0160 return [name for name in names if keep(name)]
0161
0162
0163 def file_can_be_removed(searchstring, scope):
0164
0165
0166 result = subprocess.run(["grep", "-IRq", "--", searchstring, *scope])
0167 if result.returncode not in (0, 1):
0168 result.check_returncode()
0169 return result.returncode == 1
0170
0171
0172 def count_files(path="."):
0173 count = 0
0174 for root, dirs, files in os.walk(path):
0175 dirs[:] = filter_paths(dirs, root, EXCLUDE_PATHS)
0176 files = filter_paths(files, root, EXCLUDE_PATHS, EXCLUDE_FILES)
0177
0178 count += len(files)
0179
0180 return count
0181
0182
0183 def check_wrong_extensions(walk_root):
0184 """
0185 Collect files with disallowed suffixes. Returns the number of problematic files.
0186 """
0187
0188 suffix_allowed = (
0189 SUFFIX_CPP + SUFFIX_IMAGE + SUFFIX_PYTHON + SUFFIX_DOC + SUFFIX_OTHER
0190 )
0191
0192 wrong_extension = []
0193 for root, dirs, files in os.walk(walk_root):
0194 dirs[:] = filter_paths(dirs, root, EXCLUDE_PATHS)
0195 files = filter_paths(files, root, EXCLUDE_PATHS, EXCLUDE_FILES)
0196
0197 for f in files:
0198 p = Path(root) / f
0199 if p.suffix not in suffix_allowed:
0200 wrong_extension.append(str(p))
0201
0202 if len(wrong_extension) != 0:
0203 print(
0204 "\n\n\033[31mERROR\033[0m "
0205 + f"The following {len(wrong_extension)} files have an unsupported extension:\n\n"
0206 + "\033[31m"
0207 + "\n".join(wrong_extension)
0208 + "\033[0m"
0209 + "\nCheck if you can change the format to one of the following:\n"
0210 + "\n".join(suffix_allowed)
0211 + "\nIf you really need that specific extension, add it to the list above.\n"
0212 )
0213
0214 return len(wrong_extension)
0215
0216
0217 def find_unused_by_suffix(walk_root, suffixes, search_key, search_scope):
0218 unused = []
0219
0220 for root, dirs, files in os.walk(walk_root):
0221 dirs[:] = filter_paths(dirs, root, EXCLUDE_PATHS)
0222 files = filter_paths(files, root, EXCLUDE_PATHS, EXCLUDE_FILES)
0223
0224 for f in files:
0225 p = Path(root) / f
0226 if p.suffix in suffixes and file_can_be_removed(
0227 search_key(p), search_scope
0228 ):
0229 unused.append(str(p))
0230
0231 return unused
0232
0233
0234 def find_unused_python_files(walk_root, dirs_base):
0235 unused = []
0236
0237 for root, dirs, files in os.walk(walk_root):
0238 dirs[:] = filter_paths(dirs, root, EXCLUDE_PATHS)
0239 files = filter_paths(files, root, EXCLUDE_PATHS, EXCLUDE_FILES)
0240
0241 for f in files:
0242 p = Path(root) / f
0243 if p.suffix not in SUFFIX_PYTHON:
0244 continue
0245
0246 if not file_can_be_removed(r"import .*" + p.stem, dirs_base):
0247 continue
0248
0249 if not file_can_be_removed(r"from " + p.stem + r" import", dirs_base):
0250 continue
0251
0252 if file_can_be_removed(p.name, dirs_base):
0253 unused.append(str(p))
0254
0255 return unused
0256
0257
0258 def main():
0259 print("\033[32mINFO\033[0m Start check_unused_files.py ...")
0260
0261 exit = 0
0262
0263 dirs_base = next(os.walk("."))[1]
0264 dirs_base.append(".")
0265 dirs_base[:] = filter_paths(dirs_base, Path("."), EXCLUDE_PATHS)
0266 dirs_base_docs = ("docs",)
0267 dirs_base_code = filter_paths(dirs_base, Path("."), dirs_base_docs)
0268
0269 exit += check_wrong_extensions(".")
0270
0271
0272 unused_files = []
0273
0274 unused_files += find_unused_by_suffix(
0275 ".", SUFFIX_CPP, lambda p: p.name, dirs_base_code
0276 )
0277
0278 unused_files += find_unused_python_files(".", dirs_base)
0279
0280
0281 unused_files += find_unused_by_suffix(
0282 ".", SUFFIX_DOC, lambda p: p.stem, dirs_base_docs
0283 )
0284
0285 unused_files += find_unused_by_suffix(
0286 ".", SUFFIX_IMAGE + SUFFIX_OTHER, lambda p: p.name, dirs_base
0287 )
0288
0289 if len(unused_files) != 0:
0290 print(
0291 "\n\n\033[31mERROR\033[0m "
0292 + f"The following {len(unused_files)} files seem to be unused:\n"
0293 + "\033[31m"
0294 + "\n".join(unused_files)
0295 + "\033[0m"
0296 + "\nYou have 3 options:"
0297 + "\n\t- Remove them"
0298 + "\n\t- Use them (check proper include)"
0299 + "\n\t- Modify the ignore list of this check\n"
0300 )
0301
0302 exit += 1
0303
0304 if exit == 0:
0305 print(
0306 "\n\n\033[32mINFO\033[0m Finished check_unused_files.py without any errors."
0307 )
0308
0309 return exit
0310
0311
0312 if "__main__" == __name__:
0313 sys.exit(main())