blob: 17a2136a270d58724e1e7ff31f8b67d31f444d99 [file] [log] [blame]
Aiden Grossman92241652025-03-26 12:37:16 -07001# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
2# See https://llvm.org/LICENSE.txt for license information.
3# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4"""Computes the list of projects that need to be tested from a diff.
5
6Does some things, spits out a list of projects.
7"""
8
9from collections.abc import Set
10import pathlib
11import platform
12import sys
13
14# This mapping lists out the dependencies for each project. These should be
15# direct dependencies. The code will handle transitive dependencies. Some
16# projects might have optional dependencies depending upon how they are built.
17# The dependencies listed here should be the dependencies required for the
18# configuration built/tested in the premerge CI.
19PROJECT_DEPENDENCIES = {
20 "llvm": set(),
21 "clang": {"llvm"},
22 "bolt": {"clang", "lld", "llvm"},
23 "clang-tools-extra": {"clang", "llvm"},
24 "compiler-rt": {"clang", "lld"},
25 "libc": {"clang", "lld"},
26 "openmp": {"clang", "lld"},
27 "flang": {"llvm", "clang"},
28 "lldb": {"llvm", "clang"},
29 "libclc": {"llvm", "clang"},
30 "lld": {"llvm"},
31 "mlir": {"llvm"},
32 "polly": {"llvm"},
33}
34
35# This mapping describes the additional projects that should be tested when a
36# specific project is touched. We enumerate them specifically rather than
37# just invert the dependencies list to give more control over what exactly is
38# tested.
39DEPENDENTS_TO_TEST = {
40 "llvm": {
41 "bolt",
42 "clang",
43 "clang-tools-extra",
44 "lld",
45 "lldb",
46 "mlir",
47 "polly",
48 "flang",
49 },
50 "lld": {"bolt", "cross-project-tests"},
51 # TODO(issues/132795): LLDB should be enabled on clang changes.
52 "clang": {"clang-tools-extra", "compiler-rt", "cross-project-tests"},
53 "clang-tools-extra": {"libc"},
54 "mlir": {"flang"},
Matheus Izvekov30747cf2025-04-18 17:42:29 -030055 # Test everything if ci scripts are changed.
56 # FIXME: Figure out what is missing and add here.
57 ".ci": {"llvm", "clang", "lld", "lldb"},
Aiden Grossman92241652025-03-26 12:37:16 -070058}
59
60DEPENDENT_RUNTIMES_TO_TEST = {"clang": {"libcxx", "libcxxabi", "libunwind"}}
61
62EXCLUDE_LINUX = {
63 "cross-project-tests", # TODO(issues/132796): Tests are failing.
64 "openmp", # https://github.com/google/llvm-premerge-checks/issues/410
65}
66
67EXCLUDE_WINDOWS = {
68 "cross-project-tests", # TODO(issues/132797): Tests are failing.
69 "compiler-rt", # TODO(issues/132798): Tests take excessive time.
70 "openmp", # TODO(issues/132799): Does not detect perl installation.
71 "libc", # No Windows Support.
72 "lldb", # TODO(issues/132800): Needs environment setup.
73 "bolt", # No Windows Support.
74}
75
76# These are projects that we should test if the project itself is changed but
77# where testing is not yet stable enough for it to be enabled on changes to
78# dependencies.
79EXCLUDE_DEPENDENTS_WINDOWS = {
80 "flang", # TODO(issues/132803): Flang is not stable.
81}
82
83EXCLUDE_MAC = {
84 "bolt",
85 "compiler-rt",
86 "cross-project-tests",
87 "flang",
88 "libc",
89 "libcxx",
90 "libcxxabi",
91 "libunwind",
92 "lldb",
93 "openmp",
94 "polly",
95}
96
97PROJECT_CHECK_TARGETS = {
98 "clang-tools-extra": "check-clang-tools",
99 "compiler-rt": "check-compiler-rt",
100 "cross-project-tests": "check-cross-project",
101 "libcxx": "check-cxx",
102 "libcxxabi": "check-cxxabi",
103 "libunwind": "check-unwind",
104 "lldb": "check-lldb",
105 "llvm": "check-llvm",
106 "clang": "check-clang",
107 "bolt": "check-bolt",
108 "lld": "check-lld",
109 "flang": "check-flang",
110 "libc": "check-libc",
111 "lld": "check-lld",
112 "lldb": "check-lldb",
113 "mlir": "check-mlir",
114 "openmp": "check-openmp",
115 "polly": "check-polly",
116}
117
Aiden Grossman7da71a62025-03-26 23:58:40 +0000118RUNTIMES = {"libcxx", "libcxxabi", "libunwind"}
119
Aiden Grossman92241652025-03-26 12:37:16 -0700120
121def _add_dependencies(projects: Set[str]) -> Set[str]:
122 projects_with_dependents = set(projects)
123 current_projects_count = 0
124 while current_projects_count != len(projects_with_dependents):
125 current_projects_count = len(projects_with_dependents)
126 for project in list(projects_with_dependents):
127 if project not in PROJECT_DEPENDENCIES:
128 continue
129 projects_with_dependents.update(PROJECT_DEPENDENCIES[project])
130 return projects_with_dependents
131
132
133def _compute_projects_to_test(modified_projects: Set[str], platform: str) -> Set[str]:
134 projects_to_test = set()
135 for modified_project in modified_projects:
Aiden Grossman7da71a62025-03-26 23:58:40 +0000136 if modified_project in RUNTIMES:
137 continue
Matheus Izvekov30747cf2025-04-18 17:42:29 -0300138 # Skip all projects where we cannot run tests.
139 if modified_project in PROJECT_CHECK_TARGETS:
140 projects_to_test.add(modified_project)
Aiden Grossman92241652025-03-26 12:37:16 -0700141 if modified_project not in DEPENDENTS_TO_TEST:
142 continue
143 for dependent_project in DEPENDENTS_TO_TEST[modified_project]:
144 if (
145 platform == "Windows"
146 and dependent_project in EXCLUDE_DEPENDENTS_WINDOWS
147 ):
148 continue
149 projects_to_test.add(dependent_project)
150 if platform == "Linux":
151 for to_exclude in EXCLUDE_LINUX:
152 if to_exclude in projects_to_test:
153 projects_to_test.remove(to_exclude)
154 elif platform == "Windows":
155 for to_exclude in EXCLUDE_WINDOWS:
156 if to_exclude in projects_to_test:
157 projects_to_test.remove(to_exclude)
158 elif platform == "Darwin":
159 for to_exclude in EXCLUDE_MAC:
160 if to_exclude in projects_to_test:
161 projects_to_test.remove(to_exclude)
162 else:
163 raise ValueError("Unexpected platform.")
164 return projects_to_test
165
166
167def _compute_projects_to_build(projects_to_test: Set[str]) -> Set[str]:
168 return _add_dependencies(projects_to_test)
169
170
171def _compute_project_check_targets(projects_to_test: Set[str]) -> Set[str]:
172 check_targets = set()
173 for project_to_test in projects_to_test:
174 if project_to_test not in PROJECT_CHECK_TARGETS:
175 continue
176 check_targets.add(PROJECT_CHECK_TARGETS[project_to_test])
177 return check_targets
178
179
180def _compute_runtimes_to_test(projects_to_test: Set[str]) -> Set[str]:
181 runtimes_to_test = set()
182 for project_to_test in projects_to_test:
183 if project_to_test not in DEPENDENT_RUNTIMES_TO_TEST:
184 continue
185 runtimes_to_test.update(DEPENDENT_RUNTIMES_TO_TEST[project_to_test])
186 return runtimes_to_test
187
188
189def _compute_runtime_check_targets(runtimes_to_test: Set[str]) -> Set[str]:
190 check_targets = set()
191 for runtime_to_test in runtimes_to_test:
192 check_targets.add(PROJECT_CHECK_TARGETS[runtime_to_test])
193 return check_targets
194
195
196def _get_modified_projects(modified_files: list[str]) -> Set[str]:
197 modified_projects = set()
198 for modified_file in modified_files:
Aiden Grossman21eeca32025-03-28 22:30:41 -0700199 path_parts = pathlib.Path(modified_file).parts
200 # Exclude files in the docs directory. They do not impact an test
201 # targets and there is a separate workflow used for ensuring the
202 # documentation builds.
203 if len(path_parts) > 2 and path_parts[1] == "docs":
204 continue
Aiden Grossmance296f12025-04-01 12:58:16 -0700205 # Exclude files for the gn build. We do not test it within premerge
206 # and changes occur often enough that they otherwise take up
207 # capacity.
208 if len(path_parts) > 3 and path_parts[:3] == ("llvm", "utils", "gn"):
209 continue
Aiden Grossman92241652025-03-26 12:37:16 -0700210 modified_projects.add(pathlib.Path(modified_file).parts[0])
211 return modified_projects
212
213
214def get_env_variables(modified_files: list[str], platform: str) -> Set[str]:
215 modified_projects = _get_modified_projects(modified_files)
216 projects_to_test = _compute_projects_to_test(modified_projects, platform)
217 projects_to_build = _compute_projects_to_build(projects_to_test)
218 projects_check_targets = _compute_project_check_targets(projects_to_test)
219 runtimes_to_test = _compute_runtimes_to_test(projects_to_test)
220 runtimes_check_targets = _compute_runtime_check_targets(runtimes_to_test)
221 # We use a semicolon to separate the projects/runtimes as they get passed
222 # to the CMake invocation and thus we need to use the CMake list separator
223 # (;). We use spaces to separate the check targets as they end up getting
224 # passed to ninja.
225 return {
226 "projects_to_build": ";".join(sorted(projects_to_build)),
227 "project_check_targets": " ".join(sorted(projects_check_targets)),
228 "runtimes_to_build": ";".join(sorted(runtimes_to_test)),
229 "runtimes_check_targets": " ".join(sorted(runtimes_check_targets)),
230 }
231
232
233if __name__ == "__main__":
234 current_platform = platform.system()
235 if len(sys.argv) == 2:
236 current_platform = sys.argv[1]
237 env_variables = get_env_variables(sys.stdin.readlines(), current_platform)
238 for env_variable in env_variables:
239 print(f"{env_variable}='{env_variables[env_variable]}'")