[ci] Invoke arm64 macOS tests if hal/drivers/metal changes (#14700) This commit changes CI rules to enable running arm64 macOS builds and tests on presubmit if we see changes to `hal/drivers/metal` directory. This can guard against breaking changes.
diff --git a/build_tools/github_actions/configure_ci.py b/build_tools/github_actions/configure_ci.py index f55081c..7b3861b 100755 --- a/build_tools/github_actions/configure_ci.py +++ b/build_tools/github_actions/configure_ci.py
@@ -45,7 +45,7 @@ import subprocess import sys import textwrap -from typing import Iterable, List, Mapping, Sequence, Set, Tuple +from typing import Iterable, List, Mapping, Optional, Sequence, Set, Tuple import yaml @@ -115,7 +115,9 @@ CONTROL_JOBS = frozenset(["setup", "summary"]) -POSTSUBMIT_ONLY_JOBS = frozenset( +# Jobs to run only on postsubmit by default. +# They may also run on presubmit only under certain conditions. +DEFAULT_POSTSUBMIT_ONLY_JOBS = frozenset( [ "build_test_all_windows", "build_test_all_macos_arm64", @@ -125,6 +127,13 @@ ] ) +# Jobs to run in presumbit if files under the corresponding path see changes. +# Each tuple consists of the CI job name and a list of file paths to match. +# The file paths should be specified using Unix shell-style wildcards. +PRESUBMIT_TOUCH_ONLY_JOBS = [ + ("build_test_all_macos_arm64", ["runtime/src/iree/hal/drivers/metal/*"]), +] + DEFAULT_BENCHMARK_PRESET_GROUP = [ "cuda", "x86_64", @@ -291,27 +300,30 @@ return (trailer_map, labels) -def get_modified_paths(base_ref: str) -> Iterable[str]: - return subprocess.run( - ["git", "diff", "--name-only", base_ref], - stdout=subprocess.PIPE, - check=True, - text=True, - timeout=60, - ).stdout.splitlines() - - -def modifies_included_path() -> bool: - base_ref = os.environ["BASE_REF"] +def get_modified_paths(base_ref: str) -> Optional[Iterable[str]]: + """Returns the paths of modified files in this code change.""" try: - return any(not skip_path(p) for p in get_modified_paths(base_ref)) + return subprocess.run( + ["git", "diff", "--name-only", base_ref], + stdout=subprocess.PIPE, + check=True, + text=True, + timeout=60, + ).stdout.splitlines() except TimeoutError as e: print( "Computing modified files timed out. Not using PR diff to determine" " jobs to run.", file=sys.stderr, ) + return None + + +def modifies_non_skip_paths(paths: Optional[Iterable[str]]) -> bool: + """Returns true if not all modified paths are in the skip set.""" + if paths is None: return True + return any(not skip_path(p) for p in paths) def get_runner_env(trailers: Mapping[str, str]) -> str: @@ -381,8 +393,20 @@ all_jobs: Set[str], *, is_pr: bool, - modifies: bool, + modified_paths: Optional[Iterable[str]], ) -> Set[str]: + """Returns the CI jobs to run. + + Args: + trailers: trailers from PR description. + all_jobs: all known supported jobs. + is_pr: whether this is for pull requests or not. + modified_paths: the paths of the files changed. These paths are + relative to the repo root directory. + + Returns: + The list of CI jobs to run. + """ if not is_pr: print( "Running all jobs because run was not triggered by a pull request" @@ -432,14 +456,21 @@ f" '{Trailer.EXTRA_JOBS}', but found {ambiguous_jobs}" ) - default_jobs = all_jobs - POSTSUBMIT_ONLY_JOBS + default_jobs = all_jobs - DEFAULT_POSTSUBMIT_ONLY_JOBS - if not modifies: + if not modifies_non_skip_paths(modified_paths): print( "Not including any jobs by default because all modified files" " are marked as excluded." ) default_jobs = frozenset() + else: + # Add jobs if the monitored files are changed. + for modified_path in modified_paths: + for job, match_paths in PRESUBMIT_TOUCH_ONLY_JOBS: + for match_path in match_paths: + if fnmatch.fnmatch(modified_path, match_path): + default_jobs |= {job} return (default_jobs | extra_jobs) - skip_jobs @@ -525,8 +556,8 @@ repo = os.environ["GITHUB_REPOSITORY"] workflow_ref = os.environ["GITHUB_WORKFLOW_REF"] workflow_file = parse_path_from_workflow_ref(repo=repo, workflow_ref=workflow_ref) + base_ref = os.environ["BASE_REF"] - modifies = modifies_included_path() try: benchmark_presets = get_benchmark_presets( trailers, labels, is_pr, is_llvm_integrate_pr @@ -535,7 +566,7 @@ enabled_jobs = get_enabled_jobs( trailers, all_jobs, - modifies=modifies, + modified_paths=get_modified_paths(base_ref), is_pr=is_pr, ) except ValueError as e:
diff --git a/build_tools/github_actions/configure_ci_test.py b/build_tools/github_actions/configure_ci_test.py index 66282c7..9b3aada 100644 --- a/build_tools/github_actions/configure_ci_test.py +++ b/build_tools/github_actions/configure_ci_test.py
@@ -147,11 +147,11 @@ trailers = {} all_jobs = {"job1", "job2", "job3"} is_pr = True - modifies = True + modified_paths = ["runtime/file"] jobs = configure_ci.get_enabled_jobs( trailers, all_jobs, - modifies=modifies, + modified_paths=modified_paths, is_pr=is_pr, ) self.assertCountEqual(jobs, all_jobs) @@ -159,14 +159,14 @@ def test_get_enabled_jobs_postsubmit(self): trailers = {} default_jobs = {"job1", "job2", "job3"} - postsubmit_job = next(iter(configure_ci.POSTSUBMIT_ONLY_JOBS)) + postsubmit_job = next(iter(configure_ci.DEFAULT_POSTSUBMIT_ONLY_JOBS)) all_jobs = default_jobs | {postsubmit_job} is_pr = False - modifies = True + modified_paths = ["runtime/file"] jobs = configure_ci.get_enabled_jobs( trailers, all_jobs, - modifies=modifies, + modified_paths=modified_paths, is_pr=is_pr, ) self.assertCountEqual(jobs, all_jobs) @@ -174,14 +174,14 @@ def test_get_enabled_jobs_no_postsubmit(self): trailers = {} default_jobs = {"job1", "job2", "job3"} - postsubmit_job = next(iter(configure_ci.POSTSUBMIT_ONLY_JOBS)) + postsubmit_job = next(iter(configure_ci.DEFAULT_POSTSUBMIT_ONLY_JOBS)) all_jobs = default_jobs | {postsubmit_job} is_pr = True - modifies = True + modified_paths = ["runtime/file"] jobs = configure_ci.get_enabled_jobs( trailers, all_jobs, - modifies=modifies, + modified_paths=modified_paths, is_pr=is_pr, ) self.assertCountEqual(jobs, default_jobs) @@ -189,14 +189,14 @@ def test_get_enabled_jobs_no_modifies(self): trailers = {} default_jobs = {"job1", "job2", "job3"} - postsubmit_job = next(iter(configure_ci.POSTSUBMIT_ONLY_JOBS)) + postsubmit_job = next(iter(configure_ci.DEFAULT_POSTSUBMIT_ONLY_JOBS)) all_jobs = default_jobs | {postsubmit_job} is_pr = True - modifies = False + modified_paths = ["experimental/file"] jobs = configure_ci.get_enabled_jobs( trailers, all_jobs, - modifies=modifies, + modified_paths=modified_paths, is_pr=is_pr, ) self.assertCountEqual(jobs, {}) @@ -204,14 +204,14 @@ def test_get_enabled_jobs_skip(self): trailers = {configure_ci.Trailer.SKIP_JOBS: "job1,job2"} default_jobs = {"job1", "job2", "job3"} - postsubmit_job = next(iter(configure_ci.POSTSUBMIT_ONLY_JOBS)) + postsubmit_job = next(iter(configure_ci.DEFAULT_POSTSUBMIT_ONLY_JOBS)) all_jobs = default_jobs | {postsubmit_job} is_pr = True - modifies = True + modified_paths = ["runtime/file"] jobs = configure_ci.get_enabled_jobs( trailers, all_jobs, - modifies=modifies, + modified_paths=modified_paths, is_pr=is_pr, ) self.assertCountEqual(jobs, {"job3"}) @@ -219,48 +219,62 @@ def test_get_enabled_jobs_skip_all(self): trailers = {configure_ci.Trailer.SKIP_JOBS: "all"} default_jobs = {"job1", "job2", "job3"} - postsubmit_job = next(iter(configure_ci.POSTSUBMIT_ONLY_JOBS)) + postsubmit_job = next(iter(configure_ci.DEFAULT_POSTSUBMIT_ONLY_JOBS)) all_jobs = default_jobs | {postsubmit_job} is_pr = True - modifies = True + modified_paths = ["runtime/file"] jobs = configure_ci.get_enabled_jobs( trailers, all_jobs, - modifies=modifies, + modified_paths=modified_paths, is_pr=is_pr, ) self.assertCountEqual(jobs, {}) def test_get_enabled_jobs_extra(self): - postsubmit_job = next(iter(configure_ci.POSTSUBMIT_ONLY_JOBS)) + postsubmit_job = next(iter(configure_ci.DEFAULT_POSTSUBMIT_ONLY_JOBS)) trailers = {configure_ci.Trailer.EXTRA_JOBS: postsubmit_job} default_jobs = {"job1", "job2", "job3"} all_jobs = default_jobs | {postsubmit_job} is_pr = True - modifies = True + modified_paths = ["runtime/file"] jobs = configure_ci.get_enabled_jobs( trailers, all_jobs, - modifies=modifies, + modified_paths=modified_paths, is_pr=is_pr, ) self.assertCountEqual(jobs, all_jobs) def test_get_enabled_jobs_exactly(self): - postsubmit_job = next(iter(configure_ci.POSTSUBMIT_ONLY_JOBS)) + postsubmit_job = next(iter(configure_ci.DEFAULT_POSTSUBMIT_ONLY_JOBS)) trailers = {configure_ci.Trailer.EXACTLY_JOBS: postsubmit_job} default_jobs = {"job1", "job2", "job3"} all_jobs = default_jobs | {postsubmit_job} is_pr = True - modifies = True + modified_paths = ["runtime/file"] jobs = configure_ci.get_enabled_jobs( trailers, all_jobs, - modifies=modifies, + modified_paths=modified_paths, is_pr=is_pr, ) self.assertCountEqual(jobs, {postsubmit_job}) + def test_get_enabled_jobs_metal(self): + trailers = {} + all_jobs = {"job1"} + is_pr = True + modified_paths = ["runtime/src/iree/hal/drivers/metal/file"] + jobs = configure_ci.get_enabled_jobs( + trailers, + all_jobs, + modified_paths=modified_paths, + is_pr=is_pr, + ) + expected_jobs = {"job1", "build_test_all_macos_arm64"} + self.assertCountEqual(jobs, expected_jobs) + def test_parse_path_from_workflow_ref(self): path = configure_ci.parse_path_from_workflow_ref( "octocat/example", "octocat/example/.github/test.yml@1234"