Skip to content

Commit 9224165

Browse files
[CI] Add Python Script for Computing Projects/Runtimes to Test
This patch adds a python script, compute_projects, and associated unit tests for computing the projects and runtimes that need to be tested in premerge. Rewriting in Python opens up a couple new improvements/opportunities: 1. I personally find python to be much easier to work with than shell scripts for tasks like this. Particularly it becomes a lot easier to work with paths with proper array support. 2. Unit testing becomes easier which makes it a lot easier to reason about behavior changes, especially in review. 3. Most of the configuration is now setup in some dictionaries, which makes changes much easier to apply for most of the common changes. This preserves the behavior of the existing premerge scripts as much as possible. Reviewers: ldionne, lnihlen, Endilll, tstellar, Keenuts Reviewed By: Keenuts Pull Request: #132634
1 parent 0779406 commit 9224165

File tree

2 files changed

+388
-0
lines changed

2 files changed

+388
-0
lines changed

.ci/compute_projects.py

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
# 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+
6+
Does some things, spits out a list of projects.
7+
"""
8+
9+
from collections.abc import Set
10+
import pathlib
11+
import platform
12+
import 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.
19+
PROJECT_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.
39+
DEPENDENTS_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"},
55+
}
56+
57+
DEPENDENT_RUNTIMES_TO_TEST = {"clang": {"libcxx", "libcxxabi", "libunwind"}}
58+
59+
EXCLUDE_LINUX = {
60+
"cross-project-tests", # TODO(issues/132796): Tests are failing.
61+
"openmp", # https://github.com/google/llvm-premerge-checks/issues/410
62+
}
63+
64+
EXCLUDE_WINDOWS = {
65+
"cross-project-tests", # TODO(issues/132797): Tests are failing.
66+
"compiler-rt", # TODO(issues/132798): Tests take excessive time.
67+
"openmp", # TODO(issues/132799): Does not detect perl installation.
68+
"libc", # No Windows Support.
69+
"lldb", # TODO(issues/132800): Needs environment setup.
70+
"bolt", # No Windows Support.
71+
}
72+
73+
# These are projects that we should test if the project itself is changed but
74+
# where testing is not yet stable enough for it to be enabled on changes to
75+
# dependencies.
76+
EXCLUDE_DEPENDENTS_WINDOWS = {
77+
"flang", # TODO(issues/132803): Flang is not stable.
78+
}
79+
80+
EXCLUDE_MAC = {
81+
"bolt",
82+
"compiler-rt",
83+
"cross-project-tests",
84+
"flang",
85+
"libc",
86+
"libcxx",
87+
"libcxxabi",
88+
"libunwind",
89+
"lldb",
90+
"openmp",
91+
"polly",
92+
}
93+
94+
PROJECT_CHECK_TARGETS = {
95+
"clang-tools-extra": "check-clang-tools",
96+
"compiler-rt": "check-compiler-rt",
97+
"cross-project-tests": "check-cross-project",
98+
"libcxx": "check-cxx",
99+
"libcxxabi": "check-cxxabi",
100+
"libunwind": "check-unwind",
101+
"lldb": "check-lldb",
102+
"llvm": "check-llvm",
103+
"clang": "check-clang",
104+
"bolt": "check-bolt",
105+
"lld": "check-lld",
106+
"flang": "check-flang",
107+
"libc": "check-libc",
108+
"lld": "check-lld",
109+
"lldb": "check-lldb",
110+
"mlir": "check-mlir",
111+
"openmp": "check-openmp",
112+
"polly": "check-polly",
113+
}
114+
115+
116+
def _add_dependencies(projects: Set[str]) -> Set[str]:
117+
projects_with_dependents = set(projects)
118+
current_projects_count = 0
119+
while current_projects_count != len(projects_with_dependents):
120+
current_projects_count = len(projects_with_dependents)
121+
for project in list(projects_with_dependents):
122+
if project not in PROJECT_DEPENDENCIES:
123+
continue
124+
projects_with_dependents.update(PROJECT_DEPENDENCIES[project])
125+
return projects_with_dependents
126+
127+
128+
def _compute_projects_to_test(modified_projects: Set[str], platform: str) -> Set[str]:
129+
projects_to_test = set()
130+
for modified_project in modified_projects:
131+
# Skip all projects where we cannot run tests.
132+
if modified_project not in PROJECT_CHECK_TARGETS:
133+
continue
134+
projects_to_test.add(modified_project)
135+
if modified_project not in DEPENDENTS_TO_TEST:
136+
continue
137+
for dependent_project in DEPENDENTS_TO_TEST[modified_project]:
138+
if (
139+
platform == "Windows"
140+
and dependent_project in EXCLUDE_DEPENDENTS_WINDOWS
141+
):
142+
continue
143+
projects_to_test.add(dependent_project)
144+
if platform == "Linux":
145+
for to_exclude in EXCLUDE_LINUX:
146+
if to_exclude in projects_to_test:
147+
projects_to_test.remove(to_exclude)
148+
elif platform == "Windows":
149+
for to_exclude in EXCLUDE_WINDOWS:
150+
if to_exclude in projects_to_test:
151+
projects_to_test.remove(to_exclude)
152+
elif platform == "Darwin":
153+
for to_exclude in EXCLUDE_MAC:
154+
if to_exclude in projects_to_test:
155+
projects_to_test.remove(to_exclude)
156+
else:
157+
raise ValueError("Unexpected platform.")
158+
return projects_to_test
159+
160+
161+
def _compute_projects_to_build(projects_to_test: Set[str]) -> Set[str]:
162+
return _add_dependencies(projects_to_test)
163+
164+
165+
def _compute_project_check_targets(projects_to_test: Set[str]) -> Set[str]:
166+
check_targets = set()
167+
for project_to_test in projects_to_test:
168+
if project_to_test not in PROJECT_CHECK_TARGETS:
169+
continue
170+
check_targets.add(PROJECT_CHECK_TARGETS[project_to_test])
171+
return check_targets
172+
173+
174+
def _compute_runtimes_to_test(projects_to_test: Set[str]) -> Set[str]:
175+
runtimes_to_test = set()
176+
for project_to_test in projects_to_test:
177+
if project_to_test not in DEPENDENT_RUNTIMES_TO_TEST:
178+
continue
179+
runtimes_to_test.update(DEPENDENT_RUNTIMES_TO_TEST[project_to_test])
180+
return runtimes_to_test
181+
182+
183+
def _compute_runtime_check_targets(runtimes_to_test: Set[str]) -> Set[str]:
184+
check_targets = set()
185+
for runtime_to_test in runtimes_to_test:
186+
check_targets.add(PROJECT_CHECK_TARGETS[runtime_to_test])
187+
return check_targets
188+
189+
190+
def _get_modified_projects(modified_files: list[str]) -> Set[str]:
191+
modified_projects = set()
192+
for modified_file in modified_files:
193+
modified_projects.add(pathlib.Path(modified_file).parts[0])
194+
return modified_projects
195+
196+
197+
def get_env_variables(modified_files: list[str], platform: str) -> Set[str]:
198+
modified_projects = _get_modified_projects(modified_files)
199+
projects_to_test = _compute_projects_to_test(modified_projects, platform)
200+
projects_to_build = _compute_projects_to_build(projects_to_test)
201+
projects_check_targets = _compute_project_check_targets(projects_to_test)
202+
runtimes_to_test = _compute_runtimes_to_test(projects_to_test)
203+
runtimes_check_targets = _compute_runtime_check_targets(runtimes_to_test)
204+
# We use a semicolon to separate the projects/runtimes as they get passed
205+
# to the CMake invocation and thus we need to use the CMake list separator
206+
# (;). We use spaces to separate the check targets as they end up getting
207+
# passed to ninja.
208+
return {
209+
"projects_to_build": ";".join(sorted(projects_to_build)),
210+
"project_check_targets": " ".join(sorted(projects_check_targets)),
211+
"runtimes_to_build": ";".join(sorted(runtimes_to_test)),
212+
"runtimes_check_targets": " ".join(sorted(runtimes_check_targets)),
213+
}
214+
215+
216+
if __name__ == "__main__":
217+
current_platform = platform.system()
218+
if len(sys.argv) == 2:
219+
current_platform = sys.argv[1]
220+
env_variables = get_env_variables(sys.stdin.readlines(), current_platform)
221+
for env_variable in env_variables:
222+
print(f"{env_variable}='{env_variables[env_variable]}'")

.ci/compute_projects_test.py

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
# 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+
"""Does some stuff."""
5+
6+
import unittest
7+
8+
import compute_projects
9+
10+
11+
class TestComputeProjects(unittest.TestCase):
12+
def test_llvm(self):
13+
env_variables = compute_projects.get_env_variables(
14+
["llvm/CMakeLists.txt"], "Linux"
15+
)
16+
self.assertEqual(
17+
env_variables["projects_to_build"],
18+
"bolt;clang;clang-tools-extra;flang;lld;lldb;llvm;mlir;polly",
19+
)
20+
self.assertEqual(
21+
env_variables["project_check_targets"],
22+
"check-bolt check-clang check-clang-tools check-flang check-lld check-lldb check-llvm check-mlir check-polly",
23+
)
24+
self.assertEqual(
25+
env_variables["runtimes_to_build"], "libcxx;libcxxabi;libunwind"
26+
)
27+
self.assertEqual(
28+
env_variables["runtimes_check_targets"],
29+
"check-cxx check-cxxabi check-unwind",
30+
)
31+
32+
def test_llvm_windows(self):
33+
env_variables = compute_projects.get_env_variables(
34+
["llvm/CMakeLists.txt"], "Windows"
35+
)
36+
self.assertEqual(
37+
env_variables["projects_to_build"],
38+
"clang;clang-tools-extra;lld;llvm;mlir;polly",
39+
)
40+
self.assertEqual(
41+
env_variables["project_check_targets"],
42+
"check-clang check-clang-tools check-lld check-llvm check-mlir check-polly",
43+
)
44+
self.assertEqual(
45+
env_variables["runtimes_to_build"], "libcxx;libcxxabi;libunwind"
46+
)
47+
self.assertEqual(
48+
env_variables["runtimes_check_targets"],
49+
"check-cxx check-cxxabi check-unwind",
50+
)
51+
52+
def test_llvm_mac(self):
53+
env_variables = compute_projects.get_env_variables(
54+
["llvm/CMakeLists.txt"], "Darwin"
55+
)
56+
self.assertEqual(
57+
env_variables["projects_to_build"],
58+
"clang;clang-tools-extra;lld;llvm;mlir",
59+
)
60+
self.assertEqual(
61+
env_variables["project_check_targets"],
62+
"check-clang check-clang-tools check-lld check-llvm check-mlir",
63+
)
64+
self.assertEqual(
65+
env_variables["runtimes_to_build"], "libcxx;libcxxabi;libunwind"
66+
)
67+
self.assertEqual(
68+
env_variables["runtimes_check_targets"],
69+
"check-cxx check-cxxabi check-unwind",
70+
)
71+
72+
def test_clang(self):
73+
env_variables = compute_projects.get_env_variables(
74+
["clang/CMakeLists.txt"], "Linux"
75+
)
76+
self.assertEqual(
77+
env_variables["projects_to_build"],
78+
"clang;clang-tools-extra;compiler-rt;lld;llvm",
79+
)
80+
self.assertEqual(
81+
env_variables["project_check_targets"],
82+
"check-clang check-clang-tools check-compiler-rt",
83+
)
84+
self.assertEqual(
85+
env_variables["runtimes_to_build"], "libcxx;libcxxabi;libunwind"
86+
)
87+
self.assertEqual(
88+
env_variables["runtimes_check_targets"],
89+
"check-cxx check-cxxabi check-unwind",
90+
)
91+
92+
def test_clang_windows(self):
93+
env_variables = compute_projects.get_env_variables(
94+
["clang/CMakeLists.txt"], "Windows"
95+
)
96+
self.assertEqual(
97+
env_variables["projects_to_build"], "clang;clang-tools-extra;llvm"
98+
)
99+
self.assertEqual(
100+
env_variables["project_check_targets"], "check-clang check-clang-tools"
101+
)
102+
self.assertEqual(
103+
env_variables["runtimes_to_build"], "libcxx;libcxxabi;libunwind"
104+
)
105+
self.assertEqual(
106+
env_variables["runtimes_check_targets"],
107+
"check-cxx check-cxxabi check-unwind",
108+
)
109+
110+
def test_bolt(self):
111+
env_variables = compute_projects.get_env_variables(
112+
["bolt/CMakeLists.txt"], "Linux"
113+
)
114+
self.assertEqual(env_variables["projects_to_build"], "bolt;clang;lld;llvm")
115+
self.assertEqual(env_variables["project_check_targets"], "check-bolt")
116+
self.assertEqual(env_variables["runtimes_to_build"], "")
117+
self.assertEqual(env_variables["runtimes_check_targets"], "")
118+
119+
def test_lldb(self):
120+
env_variables = compute_projects.get_env_variables(
121+
["lldb/CMakeLists.txt"], "Linux"
122+
)
123+
self.assertEqual(env_variables["projects_to_build"], "clang;lldb;llvm")
124+
self.assertEqual(env_variables["project_check_targets"], "check-lldb")
125+
self.assertEqual(env_variables["runtimes_to_build"], "")
126+
self.assertEqual(env_variables["runtimes_check_targets"], "")
127+
128+
def test_mlir(self):
129+
env_variables = compute_projects.get_env_variables(
130+
["mlir/CMakeLists.txt"], "Linux"
131+
)
132+
self.assertEqual(env_variables["projects_to_build"], "clang;flang;llvm;mlir")
133+
self.assertEqual(
134+
env_variables["project_check_targets"], "check-flang check-mlir"
135+
)
136+
self.assertEqual(env_variables["runtimes_to_build"], "")
137+
self.assertEqual(env_variables["runtimes_check_targets"], "")
138+
139+
def test_flang(self):
140+
env_variables = compute_projects.get_env_variables(
141+
["flang/CMakeLists.txt"], "Linux"
142+
)
143+
self.assertEqual(env_variables["projects_to_build"], "clang;flang;llvm")
144+
self.assertEqual(env_variables["project_check_targets"], "check-flang")
145+
self.assertEqual(env_variables["runtimes_to_build"], "")
146+
self.assertEqual(env_variables["runtimes_check_targets"], "")
147+
148+
def test_invalid_subproject(self):
149+
env_variables = compute_projects.get_env_variables(
150+
[".ci/compute_projects.py"], "Linux"
151+
)
152+
self.assertEqual(env_variables["projects_to_build"], "")
153+
self.assertEqual(env_variables["project_check_targets"], "")
154+
self.assertEqual(env_variables["runtimes_to_build"], "")
155+
self.assertEqual(env_variables["runtimes_check_targets"], "")
156+
157+
def test_top_level_file(self):
158+
env_variables = compute_projects.get_env_variables(["README.md"], "Linux")
159+
self.assertEqual(env_variables["projects_to_build"], "")
160+
self.assertEqual(env_variables["project_check_targets"], "")
161+
self.assertEqual(env_variables["runtimes_to_build"], "")
162+
self.assertEqual(env_variables["runtimes_check_targets"], "")
163+
164+
165+
if __name__ == "__main__":
166+
unittest.main()

0 commit comments

Comments
 (0)