| #!/usr/bin/env python3 |
| # Copyright 2019 Google LLC |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # https://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| # pylint: disable=missing-docstring |
| """Updates LLVM-dependent submodules based on the current LLVM commit. |
| |
| Updates the third_party/llvm-bazel and third_party/tensorflow submodules to |
| commits that match the LLVM commit in 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://google.github.io/iree/developing-iree/repository-management#the-special-relationship-with-llvm-and-tensorflow. |
| |
| Typical usage: |
| Syntax: ./scripts/git/update_to_llvm_syncpoint.py |
| |
| By default, this will update llvm-bazel to the tag corresponding to the |
| current LLVM commit and update TensorFlow 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 = "REMOTE" |
| KEEP_COMMIT = "KEEP" |
| INTEGRATE_COMMIT = "INTEGRATE" |
| LATEST_MATCHING_COMMIT = "LATEST_MATCH" |
| |
| COMMIT_OPTIONS = { |
| REMOTE_HEAD_COMMIT: |
| "Update to the HEAD commit on the remote repository default branch", |
| KEEP_COMMIT: |
| "Do not modify the current commit", |
| INTEGRATE_COMMIT: |
| "Update to the commit where the current version of LLVM was first " |
| "integrated", |
| LATEST_MATCHING_COMMIT: |
| "Update to the most recent commit with a matching version of LLVM", |
| } |
| |
| |
| def parse_arguments(): |
| parser = argparse.ArgumentParser() |
| parser.add_argument("--repo", help="Repository root directory") |
| parser.add_argument("--llvm", |
| help="Path to the LLVM sources " |
| "(defaults to third_party/llvm-project)", |
| default=None) |
| parser.add_argument("--llvm_bazel", |
| help="Path to the LLVM Bazel BUILD files" |
| "(defaults to third_party/llvm-bazel)", |
| default=None) |
| parser.add_argument( |
| "--llvm_bazel_commit", |
| help=("Update llvm-bazel to this commit, or a named option:" |
| f" {COMMIT_OPTIONS}." |
| f" {LATEST_MATCHING_COMMIT} and {INTEGRATE_COMMIT} are equivalent" |
| " for this repository."), |
| default=LATEST_MATCHING_COMMIT) |
| parser.add_argument("--tensorflow", |
| help="Path to the tensorflow sources " |
| "(default to third_party/tensorflow)", |
| default=None) |
| parser.add_argument( |
| "--tensorflow_commit", |
| "--tf_commit", |
| help=("Update TensorFlow to this commit, or a named option:" |
| f" {COMMIT_OPTIONS}"), |
| default=LATEST_MATCHING_COMMIT) |
| 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.tensorflow: |
| args.tensorflow = os.path.join(args.repo, "third_party", "tensorflow") |
| if not args.llvm: |
| args.llvm = os.path.join(args.repo, "third_party", "llvm-project") |
| if not args.llvm_bazel: |
| args.llvm_bazel = os.path.join(args.repo, "third_party", "llvm-bazel") |
| |
| return args |
| |
| |
| def main(args): |
| print("IREE handy-dandy-LLVM-submodule-updater at your service...") |
| print(" IREE Path :", args.repo) |
| print(" LLVM Path :", args.llvm) |
| print(" TensorFlow Path :", args.tensorflow) |
| print(" LLVM Bazel Path :", args.llvm_bazel) |
| current_llvm_commit = get_commit(args.llvm) |
| current_tensorflow_commit = get_commit(args.tensorflow) |
| |
| print("Current Commits: llvm =", current_llvm_commit, "tensorflow =", |
| current_tensorflow_commit) |
| |
| # Update TensorFlow |
| new_tf_commit = find_new_tf_commit(args.tensorflow, current_llvm_commit, |
| args.tensorflow_commit) |
| print("\n*** Updating TensorFlow to", new_tf_commit, "***") |
| utils.execute(["git", "checkout", new_tf_commit], cwd=args.tensorflow) |
| stage_path(args.repo, args.tensorflow) |
| |
| validate_tf_commit(current_llvm_commit, |
| args.tensorflow, |
| exit_on_failure=args.validate) |
| |
| new_llvm_bazel_commit = find_new_llvm_bazel_commit(args.llvm_bazel, |
| current_llvm_commit, |
| args.llvm_bazel_commit) |
| print("\n*** Updating LLVM Bazel to", new_llvm_bazel_commit, "***") |
| utils.execute(["git", "checkout", new_llvm_bazel_commit], cwd=args.llvm_bazel) |
| stage_path(args.repo, args.llvm_bazel) |
| |
| validate_llvm_bazel_commit(current_llvm_commit, |
| args.llvm_bazel, |
| exit_on_failure=args.validate) |
| |
| # Export SUBMODULE_VERSIONS. |
| print() # Add line break. |
| submodule_versions.export_versions(args.repo) |
| |
| |
| def get_commit(path, rev="HEAD"): |
| return utils.execute(["git", "rev-parse", rev], |
| cwd=path, |
| silent=True, |
| capture_output=True).stdout.strip() |
| |
| |
| def find_new_llvm_bazel_commit(llvm_bazel_path, llvm_commit, llvm_bazel_commit): |
| # Explicitly force-fetch tags. Tags in llvm-bazel are not guaranteed to be |
| # stable. |
| utils.execute(["git", "fetch", "--tags", "--force"], cwd=llvm_bazel_path) |
| |
| if llvm_bazel_commit not in COMMIT_OPTIONS: |
| return get_commit(llvm_bazel_path, rev=llvm_bazel_commit) |
| |
| if llvm_bazel_commit == KEEP_COMMIT: |
| return get_commit(llvm_bazel_path) |
| |
| if llvm_bazel_commit == REMOTE_HEAD_COMMIT: |
| return get_commit(llvm_bazel_path, "origin/main") |
| |
| if (llvm_bazel_commit == INTEGRATE_COMMIT or |
| llvm_bazel_commit == LATEST_MATCHING_COMMIT): |
| return get_commit(llvm_bazel_path, f"llvm-project-{llvm_commit}") |
| |
| |
| def validate_llvm_bazel_commit(llvm_commit, |
| llvm_bazel_path, |
| exit_on_failure=True): |
| llvm_bazel_llvm_commit = find_llvm_bazel_llvm_commit(llvm_bazel_path) |
| |
| matches = llvm_bazel_llvm_commit == llvm_commit |
| if not matches: |
| print("WARNING: LLVM commit in llvm-bazel does not match that in IREE" |
| f" ({llvm_bazel_llvm_commit} vs {llvm_commit})") |
| if exit_on_failure: |
| sys.exit(1) |
| |
| |
| def find_llvm_bazel_llvm_commit(llvm_bazel_path): |
| return utils.execute( |
| ["git", "submodule", "status", "third_party/llvm-project"], |
| capture_output=True, |
| cwd=llvm_bazel_path).stdout.split()[0].lstrip("+-") |
| |
| |
| def find_new_tf_commit(tensorflow_path, llvm_commit, tf_commit): |
| utils.execute(["git", "fetch"], cwd=tensorflow_path) |
| |
| if tf_commit not in COMMIT_OPTIONS: |
| return get_commit(tensorflow_path, rev=tf_commit) |
| |
| if tf_commit == KEEP_COMMIT: |
| return get_commit(tensorflow_path) |
| |
| if tf_commit == REMOTE_HEAD_COMMIT: |
| return get_commit(tensorflow_path, "origin/master") |
| |
| # Find commits where the number of occurences 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 TF workspace file. |
| # 2. The commit that changed it to a new hash afterwards. |
| tf_integrate_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 occurences of llvm_commit changed. |
| # https://git-scm.com/docs/git-log#Documentation/git-log.txt--Sltstringgt |
| "-S", |
| llvm_commit, |
| # Search along the master branch |
| "origin/master", |
| # Only look in the TF workspace file where the llvm_commit is recorded |
| "--", |
| "tensorflow/workspace.bzl" |
| ], |
| capture_output=True, |
| cwd=tensorflow_path).stdout.split() |
| if len(tf_integrate_commits) > 2: |
| raise RuntimeError( |
| f"Expected one or two TF commits to involve LLVM commit {llvm_commit}," |
| f" but got {len(tf_integrate_commits)}") |
| |
| if not tf_integrate_commits: |
| raise RuntimeError( |
| f"TF does not have any references to LLVM commit {llvm_commit}." |
| " Maybe TF export is behind?") |
| |
| if tf_commit == INTEGRATE_COMMIT: |
| return tf_integrate_commits[-1] |
| |
| assert tf_commit == LATEST_MATCHING_COMMIT |
| if len(tf_integrate_commits) == 1: |
| # There hasn't been a subsequent integrate, use remote head. |
| return get_commit(tensorflow_path, "origin/master") |
| |
| # Use the commit one before the one that changed away from this LLVM version. |
| return get_commit(tensorflow_path, rev=f"{tf_integrate_commits[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, "tensorflow", "workspace.bzl") |
| pattern_text = r"""\s*LLVM_COMMIT\s*=\s*"(.+)"\s*""" |
| pattern = re.compile(pattern_text, flags=re.MULTILINE) |
| for line in open(workspace_path, "r", encoding="UTF-8"): |
| m = re.match(pattern, 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:", pattern_text) |
| sys.exit(1) |
| |
| |
| 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()) |