|  | #!/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()) |