| #!/usr/bin/env python3 |
| # Lint as: 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 |
| """submodule_versions. |
| |
| Synchronizes the tracked SUBMODULE_VERSIONS.txt file with the submodule state |
| in git. |
| |
| Typical usage: |
| -------------- |
| Exporting current git submodule state to SUBMODULE_VERSIONS.txt: |
| Syntax: ./scripts/git/submodule_versions.py export |
| |
| Importing versions in SUBMODULE_VERSIONS.txt to git submodule state: |
| Syntax: ./scripts/git/submodule_versions.py import |
| |
| Checking whether SUBMODULE_VERSIONS.txt and git state are in sync: |
| Syntax: ./scripts/git/submodule_versions.py check |
| """ |
| |
| import argparse |
| import os |
| import re |
| import sys |
| |
| import utils |
| |
| VERSIONS_FILE = "SUBMODULE_VERSIONS.txt" |
| |
| |
| def get_submodule_versions(repo_dir): |
| raw_status = utils.execute(["git", "submodule", "status"], |
| cwd=repo_dir, |
| silent=True, |
| capture_output=True).stdout |
| status_lines = [] |
| for line in raw_status.splitlines(): |
| # Format is a status char followed by revision, space and path. |
| m = re.match(r"""^.([0-9a-z]+)\s+([^\s]+)""", line) |
| if m: |
| # Output as just the commit hash followed by space and path. |
| status_lines.append(m.group(1) + " " + m.group(2)) |
| return "\n".join(status_lines) + "\n" |
| |
| |
| def export_versions(repo_dir): |
| current_versions = get_submodule_versions(repo_dir) |
| versions_file_path = os.path.join(repo_dir, VERSIONS_FILE) |
| print("*** Exporting current submodule versions to:", versions_file_path) |
| with open(versions_file_path, "w", encoding="UTF-8") as f: |
| f.write(current_versions) |
| utils.execute(["git", "add", VERSIONS_FILE], cwd=repo_dir) |
| |
| |
| def parse_versions(versions_text): |
| versions = dict() |
| for line in versions_text.splitlines(): |
| comps = line.split(" ", maxsplit=2) |
| if len(comps) != 2: |
| continue |
| versions[comps[1]] = comps[0] |
| return versions |
| |
| |
| def get_diff_versions(repo_dir): |
| current_versions = parse_versions(get_submodule_versions(repo_dir)) |
| with open(os.path.join(repo_dir, VERSIONS_FILE), "r", encoding="UTF-8") as f: |
| written_versions = parse_versions(f.read()) |
| diff_versions = current_versions.items() ^ written_versions.items() |
| return { |
| k: (current_versions.get(k), written_versions.get(k)) |
| for k, _ in diff_versions |
| } |
| |
| |
| def sync_and_update_submodules(repo_dir): |
| print("*** Synchronizing/updating submodules") |
| utils.execute(["git", "submodule", "sync"], cwd=repo_dir) |
| utils.execute(["git", "submodule", "update"], cwd=repo_dir) |
| |
| |
| def import_versions(repo_dir): |
| print("*** Importing versions to git submodule state") |
| diff_versions = get_diff_versions(repo_dir) |
| if not diff_versions: |
| print("*** No submodule updates required") |
| return |
| for path, (current, written) in diff_versions.items(): |
| if current is None: |
| print(("Warning: Submodule %s does not exist but is " |
| "still in the version file") % (path,)) |
| continue |
| if written is None: |
| print(f"Warning: Submodule '{current}' is not in the version file") |
| continue |
| # Directly update the submodule commit hash in the index. |
| # See: https://stackoverflow.com/questions/33514642 |
| command = ["git", "update-index", "--cacheinfo", "160000", written, path] |
| print("Updating", path, "to", written) |
| utils.execute(command, cwd=repo_dir) |
| |
| |
| def init_submodules(repo_dir): |
| print("*** Initializing submodules") |
| utils.execute(["git", "submodule", "init"], cwd=repo_dir) |
| |
| |
| def parallel_shallow_update_submodules(repo_dir): |
| print("*** Making shallow clone of submodules") |
| utils.execute(["git", "submodule", "update", "--jobs", "8", "--depth", "1"], |
| cwd=repo_dir) |
| |
| |
| def check_submodule_versions(repo_dir): |
| diff_versions = get_diff_versions(repo_dir) |
| if diff_versions: |
| print("Submodule state differs from SUBMODULE_VERSIONS.txt file." |
| " Run (and commit) one of:") |
| print(" ./scripts/git/submodule_versions.py import" |
| " # Use version in SUBMODULE_VERSIONS.txt ('written')") |
| print(" ./scripts/git/submodule_versions.py export" |
| " # Use version in git state ('actual')") |
| for k, (current, written) in diff_versions.items(): |
| print(f"{k} : actual={current} written={written}") |
| return False |
| return True |
| |
| |
| def parse_arguments(): |
| parser = argparse.ArgumentParser() |
| parser.add_argument("--repo", help="Repository root directory") |
| parser.add_argument("command", |
| help="Command to run (show|import|export|check|init)") |
| args = parser.parse_args() |
| |
| # Default repo path. |
| if args.repo is None: |
| args.repo = utils.find_git_toplevel() |
| return args |
| |
| |
| def main(args): |
| if args.command == "show": |
| print(get_submodule_versions(args.repo)) |
| elif args.command == "export": |
| sync_and_update_submodules(args.repo) |
| export_versions(args.repo) |
| elif args.command == "check": |
| if not check_submodule_versions(args.repo): |
| sys.exit(1) |
| elif args.command == "import": |
| import_versions(args.repo) |
| sync_and_update_submodules(args.repo) |
| elif args.command == "init": |
| init_submodules(args.repo) |
| # Redundant, since import_versions will only update if they differ, |
| # but good to only print output about the import if it's actually |
| # needed. |
| if not check_submodule_versions(args.repo): |
| print( |
| "Warning: git submodule state does not match SUBMODULE_VERSIONS.txt. " |
| "Using state in SUBMODULE_VERSIONS.txt") |
| import_versions(args.repo) |
| parallel_shallow_update_submodules(args.repo) |
| else: |
| print("Unrecognized command:", args.command) |
| sys.exit(1) |
| |
| |
| if __name__ == "__main__": |
| main(parse_arguments()) |