| #!/usr/bin/env python |
| # Copyright 2023 The IREE Authors |
| # |
| # Licensed under the Apache License v2.0 with LLVM Exceptions. |
| # See https://llvm.org/LICENSE.txt for license information. |
| # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| |
| """Prototype of an LLVM auto-integrate script. |
| |
| WARNING: This script is a WIP that is being developed while stellaraccident@ |
| does LLVM integrates. If this warning is still present without activity after |
| about Aug-2023, then consider it defunct. |
| |
| This script attempts to define a human-augmented workflow for staying up to |
| date with LLVM. Roughly, it encourages the following flow: |
| |
| 1. Integrator `start`'s a new integrate branch, which will reset llvm-project |
| to the next affecting change (currently defined as anything touching |
| MLIR but can be expanded). |
| 2. The integrator or automation periodically issues a `next` command, which |
| will advance LLVM to the next affecting commit (or an arbitrary future |
| commit). |
| 3. If the CI on the integrate branch signals a failure, then the integrator |
| should apply patches as appropriate to turn it green or manually decide |
| to advance anyway (i.e. around a breakage/revert). |
| 4. When convenient, land the integrate branch into main and start a new one. |
| |
| The integrate branch is expected to live and accumulate patches over several |
| days and is pushed to main based on a human decision. Since it is just a branch, |
| normal git commands can be used to navigate around trouble spots. |
| |
| Current verbs: |
| |
| * `start`: Starts a new integrate branch. |
| * `next`: Advances the current branch to the next affecting LLVM commit. |
| * `status`: Shows the status of the LLVM dependency, including |
| reverse-chronological commit summaries of the delta between where we are and |
| upstream main. |
| |
| Future enhancements: |
| |
| * Carried patches to LLVM. |
| * On `next`, we should see if we need to merge from `main` and do so. |
| * Consult special `llvm-patch/{commit}` branches for pre-integrate patches and |
| apply them when we have integrated the given patch. |
| """ |
| from typing import Optional, Tuple |
| |
| import argparse |
| from datetime import date |
| import sys |
| import textwrap |
| |
| import iree_utils |
| |
| LLVM_REPO_DIR = iree_utils.get_submodule_root("llvm-project") |
| TRACK_PATHS = ("mlir",) |
| |
| |
| class CurrentState: |
| """Current state of the llvm-project integrate.""" |
| |
| def __init__(self, args): |
| self.args = args |
| self.current_iree_branch = iree_utils.git_current_branch() |
| self.current_commit, self.current_summary = iree_utils.git_current_commit( |
| repo_dir=LLVM_REPO_DIR |
| ) |
| # The common commit between the llvm-project submodule and upstream. |
| self.merge_base_commit = iree_utils.git_merge_base( |
| self.current_commit, "upstream/main", repo_dir=LLVM_REPO_DIR |
| ) |
| # Whether the current llvm-project commit is clean (True) or |
| # carries patches (False). |
| self.is_clean = self.merge_base_commit == self.current_commit |
| # List of (commit, desc) tuples in reverse chronological order for |
| # commits that upstream is ahead. |
| self.new_commits = iree_utils.git_log_range( |
| refs=("upstream/main", f"^{self.merge_base_commit}"), |
| paths=TRACK_PATHS, |
| repo_dir=LLVM_REPO_DIR, |
| ) |
| |
| def find_next_commit(self) -> Tuple[str, str]: |
| """Finds the next LLVM commit to advance to. |
| |
| Returns (commit, desc). |
| """ |
| if self.args.advance_to: |
| for commit, desc in self.new_commits: |
| if commit == self.args.advance_to: |
| return commit, desc |
| else: |
| raise ValueError( |
| f"Requested advance to commit {self.args.advance_to} not found" |
| ) |
| else: |
| if not self.new_commits: |
| raise ValueError(f"No new commits") |
| else: |
| return next(reversed(self.new_commits)) |
| |
| def index_of_next_commit(self, needle_commit: str) -> int: |
| for i, (new_commit, desc) in enumerate(reversed(self.new_commits)): |
| if new_commit == needle_commit: |
| return i |
| return -1 |
| |
| |
| def do_start(args): |
| fetch(args) |
| state = CurrentState(args) |
| if not state.is_clean: |
| raise RuntimeError("Current branch state is unclean. Not implemented yet.") |
| if not state.new_commits: |
| print(f"Up to date! Not starting.") |
| return |
| |
| next_commit, next_desc = state.find_next_commit() |
| print(f"==> Starting new integrate") |
| # Create branch. |
| branch_name = args.branch_name |
| if not branch_name: |
| branch_name = f"increment-llvm-{date.today().strftime('%Y%m%d')}" |
| print(f" Creating branch {branch_name} (override with --branch-name=)") |
| iree_utils.git_create_branch( |
| branch_name, |
| checkout=True, |
| ref="HEAD", |
| force=args.reuse_branch, |
| ) |
| iree_utils.git_reset(next_commit, repo_dir=LLVM_REPO_DIR) |
| iree_utils.git_create_commit( |
| message=( |
| f"Start LLVM integrate ({len(state.new_commits)} commits behind)\n\n" |
| f"Advance LLVM to {next_commit}: {next_desc}" |
| ), |
| add_all=True, |
| ) |
| print("Pushing...") |
| iree_utils.git_push_branch("origin", branch_name, force=args.reuse_branch) |
| |
| |
| def do_next(args): |
| fetch(args) |
| state = CurrentState(args) |
| if state.current_iree_branch == "main": |
| raise RuntimeError("Cannot run auto_integrate next from main branch!") |
| |
| # TODO: Check if a merge from main is needed and do it. |
| if not state.is_clean: |
| raise RuntimeError("Current branch state is unclean. Not implemented yet.") |
| if not state.new_commits: |
| print(f"Up to date! Not starting.") |
| return |
| |
| next_commit, next_desc = state.find_next_commit() |
| index_commit = state.index_of_next_commit(next_commit) |
| print( |
| f"==> Advancing to next LLVM commit ({index_commit} " |
| f"of {len(state.new_commits)}):" |
| ) |
| print(f" {next_commit}: {next_desc}") |
| iree_utils.git_reset(next_commit, repo_dir=LLVM_REPO_DIR) |
| iree_utils.git_create_commit( |
| message=( |
| f"Advance LLVM to {next_commit}: {next_desc} " |
| f"({index_commit} of {len(state.new_commits)})" |
| ), |
| add_all=True, |
| ) |
| print("Pushing...") |
| iree_utils.git_exec(["push"]) |
| |
| |
| def do_status(args): |
| fetch(args) |
| state = CurrentState(args) |
| print(f"==> llvm-project is currently at {state.current_summary}:") |
| if state.is_clean: |
| print(f" : Current commit is clean (no patches)") |
| else: |
| # TODO: Also get the merge base with --independent to get the carried |
| # patches. |
| print( |
| f" : Current commit has diverging patches with base {state.merge_base_commit}" |
| ) |
| |
| # Compute the different commits. |
| print( |
| f"==> {len(state.new_commits)} affecting commits between upstream head and current:" |
| ) |
| for commit, desc in state.new_commits: |
| print(f" {commit}: {desc}") |
| |
| |
| def fetch(args): |
| print("==> Fetching origin and upstream revisions...") |
| setup_remotes(args) |
| iree_utils.git_fetch(repository="origin") |
| iree_utils.git_fetch(repository="origin", repo_dir=LLVM_REPO_DIR) |
| iree_utils.git_fetch(repository="upstream", repo_dir=LLVM_REPO_DIR) |
| |
| |
| def setup_remotes(args): |
| # We need to know what the real upstream repo is. |
| iree_utils.git_setup_remote( |
| "upstream", "https://github.com/llvm/llvm-project.git", repo_dir=LLVM_REPO_DIR |
| ) |
| |
| |
| def main(args): |
| if args.sub_command == "next": |
| do_next(args) |
| elif args.sub_command == "start": |
| do_start(args) |
| elif args.sub_command == "status": |
| do_status(args) |
| else: |
| raise ValueError(f"Unrecognized sub-command {args.sub_command}") |
| |
| |
| def parse_arguments(argv): |
| parser = argparse.ArgumentParser(description="IREE LLVM-bump-inator") |
| subparsers = parser.add_subparsers( |
| help="sub-command help", required=True, dest="sub_command" |
| ) |
| next_parser = subparsers.add_parser("next") |
| next_parser.add_argument( |
| "advance_to", default=None, help="Advance to the given LLVM commit" |
| ) |
| start_parser = subparsers.add_parser("start") |
| start_parser.add_argument( |
| "--branch-name", help="Integrate branch to create", default=None |
| ) |
| start_parser.add_argument( |
| "advance_to", default=None, help="Advance to the given LLVM commit" |
| ) |
| start_parser.add_argument( |
| "--reuse-branch", |
| help="Allow re-use of an existing branch", |
| action="store_true", |
| default=False, |
| ) |
| status_parser = subparsers.add_parser("status") |
| |
| args = parser.parse_args(argv) |
| return args |
| |
| |
| if __name__ == "__main__": |
| main(parse_arguments(sys.argv[1:])) |