| #!/usr/bin/env python3 | |
| # Copyright 2019 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 | |
| # pylint: disable=missing-docstring | |
| """Updates LLVM-dependent submodules based on the current LLVM commit. | |
| Updates the third_party/tensorflow and third_party/mlir-hlo submodules to | |
| commits that match the LLVM commit in the third_party/llvm-project submodule. | |
| We have special conditions around these submodules since they are synced as part | |
| of the integration of LLVM into Google's source repository. See | |
| https://github.com/google/iree/blob/main/docs/developers/developing_iree/repository_management.md#the-special-relationship-with-llvm-and-tensorflow. | |
| Typical usage: | |
| Syntax: ./scripts/git/update_to_llvm_syncpoint.py | |
| By default, this will update TensorFlow and MLIR-HLO to the most recent commit | |
| that has a matching LLVM commit. | |
| """ | |
| import argparse | |
| import re | |
| import os | |
| import sys | |
| import submodule_versions | |
| import utils | |
| REMOTE_HEAD_COMMIT_OPTION = "REMOTE" | |
| KEEP_COMMIT_OPTION = "KEEP" | |
| INTEGRATE_COMMIT_OPTION = "INTEGRATE" | |
| LATEST_MATCHING_COMMIT_OPTION = "LATEST_MATCH" | |
| COMMIT_OPTIONS = { | |
| REMOTE_HEAD_COMMIT_OPTION: | |
| "Update to the HEAD commit on the remote repository default branch", | |
| KEEP_COMMIT_OPTION: | |
| "Do not modify the current commit", | |
| INTEGRATE_COMMIT_OPTION: | |
| "Update to the commit where the current version of LLVM was first " | |
| "integrated", | |
| LATEST_MATCHING_COMMIT_OPTION: | |
| "Update to the most recent commit with a matching version of LLVM", | |
| } | |
| TF_LLVM_WORKSPACE_FILEPATH = "third_party/llvm/workspace.bzl" | |
| TF_WORKSPACE_LLVM_COMMIT_REGEXP = re.compile( | |
| r"""\s*LLVM_COMMIT\s*=\s*"(.+)"\s*""", flags=re.MULTILINE) | |
| MLIR_HLO_LLVM_VERSION_FILEPATH = "build_tools/llvm_version.txt" | |
| def parse_arguments(): | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument("--repo", help="Repository root directory") | |
| parser.add_argument("--llvm_path", | |
| help="Path to the LLVM sources " | |
| "(defaults to third_party/llvm-project)", | |
| default=None) | |
| parser.add_argument("--tensorflow_path", | |
| help="Path to the tensorflow sources " | |
| "(default to third_party/tensorflow)", | |
| default=None) | |
| parser.add_argument("--tensorflow_rev", | |
| "--tf_rev", | |
| "--tensorflow_commit", | |
| "--tf_commit", | |
| help=("Update TensorFlow to this rev, or a named option:" | |
| f" {COMMIT_OPTIONS}"), | |
| default=LATEST_MATCHING_COMMIT_OPTION) | |
| parser.add_argument("--mlir_hlo_path", | |
| help="Path to the tensorflow sources " | |
| "(default to third_party/tensorflow)", | |
| default=None) | |
| parser.add_argument("--mlir_hlo_rev", | |
| "--mlir_hlo_commit", | |
| help=("Update mlir-hlo to this rev, or a named option:" | |
| f" {COMMIT_OPTIONS}"), | |
| default=LATEST_MATCHING_COMMIT_OPTION) | |
| parser.add_argument( | |
| "--validate", | |
| help="Validate that the selected commits all match the LLVM commit", | |
| type=utils.str2bool, | |
| nargs="?", | |
| default=True, | |
| ) | |
| args = parser.parse_args() | |
| # Default repo path. | |
| if args.repo is None: | |
| args.repo = utils.find_git_toplevel() | |
| # Set some defaults. | |
| if not args.llvm_path: | |
| args.llvm_path = os.path.join(args.repo, "third_party", "llvm-project") | |
| if not args.tensorflow_path: | |
| args.tensorflow_path = os.path.join(args.repo, "third_party", "tensorflow") | |
| if not args.mlir_hlo_path: | |
| args.mlir_hlo_path = os.path.join(args.repo, "third_party", "mlir-hlo") | |
| return args | |
| def main(args): | |
| print("IREE handy-dandy-LLVM-submodule-updater at your service...") | |
| print(f" IREE Path: {args.repo}") | |
| print(f" LLVM Path: {args.llvm_path}") | |
| print(f" TensorFlow Path: {args.tensorflow_path}") | |
| print(f" MLIR-HLO Path: {args.tensorflow_path}") | |
| current_llvm_commit = parse_rev(args.llvm_path, "HEAD") | |
| current_tf_commit = parse_rev(args.tensorflow_path, "HEAD") | |
| current_mlir_hlo_commit = parse_rev(args.mlir_hlo_path, "HEAD") | |
| print("Current Commits:") | |
| print(f" llvm = {current_llvm_commit}") | |
| print(f" tensorflow = {current_tf_commit}") | |
| print(f" mlir-hlo = {current_mlir_hlo_commit}") | |
| # Update TensorFlow | |
| new_tf_commit = find_new_commit_from_version_file(args.tensorflow_path, | |
| TF_LLVM_WORKSPACE_FILEPATH, | |
| current_llvm_commit, | |
| args.tensorflow_rev) | |
| print("\n*** Updating TensorFlow to", new_tf_commit, "***") | |
| utils.execute(["git", "checkout", new_tf_commit], cwd=args.tensorflow_path) | |
| stage_path(args.repo, args.tensorflow_path) | |
| validate_tf_commit(current_llvm_commit, | |
| args.tensorflow_path, | |
| exit_on_failure=args.validate) | |
| # Update MLIR-HLO | |
| new_mlir_hlo_commit = find_new_commit_from_version_file( | |
| args.mlir_hlo_path, MLIR_HLO_LLVM_VERSION_FILEPATH, current_llvm_commit, | |
| args.mlir_hlo_rev) | |
| print("\n*** Updating MLIR-HLO to", new_mlir_hlo_commit, "***") | |
| utils.execute(["git", "checkout", new_mlir_hlo_commit], | |
| cwd=args.mlir_hlo_path) | |
| stage_path(args.repo, args.mlir_hlo_path) | |
| validate_mlir_hlo_commit(current_llvm_commit, | |
| args.mlir_hlo_path, | |
| exit_on_failure=args.validate) | |
| # Export SUBMODULE_VERSIONS.txt. | |
| print() # Add line break. | |
| submodule_versions.export_versions(args.repo) | |
| def parse_rev(path, rev): | |
| return utils.execute(["git", "rev-parse", rev], | |
| cwd=path, | |
| silent=True, | |
| capture_output=True).stdout.strip() | |
| def find_llvm_commit_changes_to_file(repo_path, | |
| filepath, | |
| llvm_commit, | |
| branch="origin/master"): | |
| """Finds commits where the occurrence of the given LLVM commit hash changes. | |
| In normal cases, there should be at most two commits that match this: | |
| 1. The commit that first introduced the new hash in the file. | |
| 2. The commit that changed it to a new hash afterwards. | |
| """ | |
| commits = utils.execute( | |
| [ | |
| "git", | |
| "log", | |
| # Only follow the first parent of a merge commit. We don't want to go | |
| # off to some random PR. | |
| "--first-parent", | |
| # Just print the commit hash | |
| "--format=%H", | |
| # Look for commits where the number of occurrences of llvm_commit | |
| # changed. | |
| # https://git-scm.com/docs/git-log#Documentation/git-log.txt--Sltstringgt | |
| "-S", | |
| llvm_commit, | |
| # Search along the appropriate branch | |
| branch, | |
| # Only look in the specified file where the llvm_commit is recorded | |
| "--", | |
| filepath, | |
| ], | |
| capture_output=True, | |
| cwd=repo_path).stdout.split() | |
| if len(commits) > 2: | |
| raise RuntimeError( | |
| f"Expected one or two commits in {repo_path} {branch} {filepath} to " | |
| f" involve LLVM commit {llvm_commit}, but got {len(commits)}") | |
| if not commits: | |
| raise RuntimeError( | |
| f"{repo_path} {branch} {filepath} does not have any references to LLVM" | |
| f" commit {llvm_commit}. Maybe export is behind?") | |
| return commits | |
| def find_new_commit_from_version_file(repo_path, | |
| version_filepath, | |
| llvm_commit, | |
| rev, | |
| branch="origin/master"): | |
| utils.execute(["git", "fetch"], cwd=repo_path) | |
| if rev not in COMMIT_OPTIONS: | |
| return parse_rev(repo_path, rev) | |
| if rev == KEEP_COMMIT_OPTION: | |
| return parse_rev(repo_path, "HEAD") | |
| if rev == REMOTE_HEAD_COMMIT_OPTION: | |
| return parse_rev(repo_path, branch) | |
| commit_options = find_llvm_commit_changes_to_file(repo_path, version_filepath, | |
| llvm_commit) | |
| if rev == INTEGRATE_COMMIT_OPTION: | |
| return commit_options[-1] | |
| assert rev == LATEST_MATCHING_COMMIT_OPTION | |
| if len(commit_options) == 1: | |
| # There hasn't been a subsequent integrate, use remote head. | |
| return parse_rev(repo_path, branch) | |
| # Use the commit one before the one that changed away from this LLVM version. | |
| return parse_rev(repo_path, f"{commit_options[0]}^") | |
| def validate_tf_commit(llvm_commit, tensorflow_path, exit_on_failure=True): | |
| tf_llvm_commit = find_tensorflow_llvm_commit(tensorflow_path) | |
| matches = tf_llvm_commit == llvm_commit | |
| if not matches: | |
| print("WARNING: LLVM commit in TF does not match that in IREE" | |
| f" ({tf_llvm_commit} vs {llvm_commit})") | |
| if exit_on_failure: | |
| sys.exit(1) | |
| def find_tensorflow_llvm_commit(tensorflow_path): | |
| # TensorFlow keeps its commit in workspace.bzl on a line like: | |
| # LLVM_COMMIT = "..." | |
| # Yeah. This is how we do it. | |
| workspace_path = os.path.join(tensorflow_path, TF_LLVM_WORKSPACE_FILEPATH) | |
| for line in open(workspace_path, "r", encoding="UTF-8"): | |
| m = re.match(TF_WORKSPACE_LLVM_COMMIT_REGEXP, line) | |
| if m: | |
| return m.group(1) | |
| print(f"ERROR: Could not find LLVM commit in {workspace_path}.") | |
| print("Please file a bug)") | |
| print("Expected pattern match for:", TF_WORKSPACE_LLVM_COMMIT_REGEXP.pattern) | |
| sys.exit(1) | |
| def validate_mlir_hlo_commit(llvm_commit, mlir_hlo_path, exit_on_failure=True): | |
| mlir_hlo_llvm_commit = find_mlir_hlo_llvm_commit(mlir_hlo_path) | |
| matches = mlir_hlo_llvm_commit == llvm_commit | |
| if not matches: | |
| print("WARNING: LLVM commit in mlir-hlo does not match that in IREE" | |
| f" ({mlir_hlo_llvm_commit} vs {llvm_commit})") | |
| if exit_on_failure: | |
| sys.exit(1) | |
| def find_mlir_hlo_llvm_commit(mlir_hlo_path): | |
| llvm_version_file_path = os.path.join(mlir_hlo_path, | |
| MLIR_HLO_LLVM_VERSION_FILEPATH) | |
| with open(llvm_version_file_path, "r", encoding="UTF-8") as f: | |
| return f.read().strip() | |
| def stage_path(repo_path, to_stage): | |
| # TODO(laurenzo): Move to utils.py. | |
| utils.execute(["git", "add", to_stage], cwd=repo_path) | |
| if __name__ == "__main__": | |
| main(parse_arguments()) |