Aiden Grossman | 9224165 | 2025-03-26 12:37:16 -0700 | [diff] [blame] | 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"}, |
Matheus Izvekov | 30747cf | 2025-04-18 17:42:29 -0300 | [diff] [blame] | 55 | # Test everything if ci scripts are changed. |
| 56 | # FIXME: Figure out what is missing and add here. |
| 57 | ".ci": {"llvm", "clang", "lld", "lldb"}, |
Aiden Grossman | 9224165 | 2025-03-26 12:37:16 -0700 | [diff] [blame] | 58 | } |
| 59 | |
| 60 | DEPENDENT_RUNTIMES_TO_TEST = {"clang": {"libcxx", "libcxxabi", "libunwind"}} |
| 61 | |
| 62 | EXCLUDE_LINUX = { |
| 63 | "cross-project-tests", # TODO(issues/132796): Tests are failing. |
| 64 | "openmp", # https://github.com/google/llvm-premerge-checks/issues/410 |
| 65 | } |
| 66 | |
| 67 | EXCLUDE_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. |
| 79 | EXCLUDE_DEPENDENTS_WINDOWS = { |
| 80 | "flang", # TODO(issues/132803): Flang is not stable. |
| 81 | } |
| 82 | |
| 83 | EXCLUDE_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 | |
| 97 | PROJECT_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 Grossman | 7da71a6 | 2025-03-26 23:58:40 +0000 | [diff] [blame] | 118 | RUNTIMES = {"libcxx", "libcxxabi", "libunwind"} |
| 119 | |
Aiden Grossman | 9224165 | 2025-03-26 12:37:16 -0700 | [diff] [blame] | 120 | |
| 121 | def _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 | |
| 133 | def _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 Grossman | 7da71a6 | 2025-03-26 23:58:40 +0000 | [diff] [blame] | 136 | if modified_project in RUNTIMES: |
| 137 | continue |
Matheus Izvekov | 30747cf | 2025-04-18 17:42:29 -0300 | [diff] [blame] | 138 | # Skip all projects where we cannot run tests. |
| 139 | if modified_project in PROJECT_CHECK_TARGETS: |
| 140 | projects_to_test.add(modified_project) |
Aiden Grossman | 9224165 | 2025-03-26 12:37:16 -0700 | [diff] [blame] | 141 | 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 | |
| 167 | def _compute_projects_to_build(projects_to_test: Set[str]) -> Set[str]: |
| 168 | return _add_dependencies(projects_to_test) |
| 169 | |
| 170 | |
| 171 | def _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 | |
| 180 | def _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 | |
| 189 | def _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 | |
| 196 | def _get_modified_projects(modified_files: list[str]) -> Set[str]: |
| 197 | modified_projects = set() |
| 198 | for modified_file in modified_files: |
Aiden Grossman | 21eeca3 | 2025-03-28 22:30:41 -0700 | [diff] [blame] | 199 | 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 Grossman | ce296f1 | 2025-04-01 12:58:16 -0700 | [diff] [blame] | 205 | # 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 Grossman | 9224165 | 2025-03-26 12:37:16 -0700 | [diff] [blame] | 210 | modified_projects.add(pathlib.Path(modified_file).parts[0]) |
| 211 | return modified_projects |
| 212 | |
| 213 | |
| 214 | def 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 | |
| 233 | if __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]}'") |