publish_to_maven_local_if_modified.py (4465B)
1 #!/usr/bin/env python3 2 # This Source Code Form is subject to the terms of the Mozilla Public 3 # License, v. 2.0. If a copy of the MPL was not distributed with this 4 # file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 6 7 # Purpose: Publish android packages to local maven repo, but only if changed since last publish. 8 # Dependencies: None 9 # Usage: ./automation/publish_to_maven_local_if_modified.py 10 11 import argparse 12 import hashlib 13 import os 14 import subprocess 15 import sys 16 import time 17 from pathlib import Path 18 19 20 def fatal_err(msg): 21 print(f"\033[31mError: {msg}\033[0m") 22 sys.exit(1) 23 24 25 def run_cmd_checked(*args, **kwargs): 26 """Run a command, throwing an exception if it exits with non-zero status.""" 27 return subprocess.run(*args, check=True, **kwargs) 28 29 30 def find_project_root(): 31 """Find the absolute path of the project repository root.""" 32 # As a convention, we expect this file in [project-root]/automation/. 33 automation_dir = Path(__file__).parent 34 35 # Therefore the automation dir's parent is the project root we're looking for. 36 return automation_dir.parent 37 38 39 LAST_CONTENTS_HASH_FILE = ".lastAutoPublishContentsHash" 40 41 GITIGNORED_FILES_THAT_AFFECT_THE_BUILD = ["local.properties"] 42 43 parser = argparse.ArgumentParser( 44 description="Publish android packages to local maven repo, but only if changed since last publish" 45 ) 46 parser.parse_args() 47 48 root_dir = find_project_root() 49 if str(root_dir) != os.path.abspath(os.curdir): 50 fatal_err( 51 f"This only works if run from the repo root ({root_dir!r} != {os.path.abspath(os.curdir)!r})" 52 ) 53 54 # Calculate a hash reflecting the current state of the repo. 55 56 contents_hash = hashlib.sha256() 57 58 contents_hash.update( 59 run_cmd_checked(["git", "rev-parse", "HEAD"], capture_output=True).stdout 60 ) 61 contents_hash.update(b"\x00") 62 63 # Get a diff of all tracked (staged and unstaged) files. 64 65 changes = run_cmd_checked(["git", "diff", "HEAD", "."], capture_output=True).stdout 66 contents_hash.update(changes) 67 contents_hash.update(b"\x00") 68 69 # But unfortunately it can only tell us the names of untracked 70 # files, and it won't tell us anything about files that are in 71 # .gitignore but can still affect the build. 72 73 untracked_files = [] 74 75 # Get a list of all untracked files sans standard exclusions. 76 77 # -o is for getting other (i.e. untracked) files 78 # --exclude-standard is to handle standard Git exclusions: .git/info/exclude, .gitignore in each directory, 79 # and the user's global exclusion file. 80 changes_others = run_cmd_checked( 81 ["git", "ls-files", "-o", "--exclude-standard"], capture_output=True 82 ).stdout 83 changes_lines = iter(ln.strip() for ln in changes_others.split(b"\n")) 84 85 try: 86 ln = next(changes_lines) 87 while ln: 88 untracked_files.append(ln) 89 ln = next(changes_lines) 90 except StopIteration: 91 pass 92 93 # Then, account for some excluded files that we care about. 94 untracked_files.extend(GITIGNORED_FILES_THAT_AFFECT_THE_BUILD) 95 96 # Finally, get hashes of everything. 97 # Skip files that don't exist, e.g. missing GITIGNORED_FILES_THAT_AFFECT_THE_BUILD. `hash-object` errors out if it gets 98 # a non-existent file, so we hope that disk won't change between this filter and the cmd run just below. 99 filtered_untracked = [nm for nm in untracked_files if os.path.isfile(nm)] 100 # Reading contents of the files is quite slow when there are lots of them, so delegate to `git hash-object`. 101 git_hash_object_cmd = ["git", "hash-object"] 102 git_hash_object_cmd.extend(filtered_untracked) 103 changes_untracked = run_cmd_checked(git_hash_object_cmd, capture_output=True).stdout 104 contents_hash.update(changes_untracked) 105 contents_hash.update(b"\x00") 106 107 contents_hash = contents_hash.hexdigest() 108 109 # If the contents hash has changed since last publish, re-publish. 110 last_contents_hash = "" 111 try: 112 with open(LAST_CONTENTS_HASH_FILE) as f: 113 last_contents_hash = f.read().strip() 114 except FileNotFoundError: 115 pass 116 117 if contents_hash == last_contents_hash: 118 print("Contents have not changed, no need to publish") 119 else: 120 print("Contents have changed, publishing") 121 if sys.platform.startswith("win"): 122 run_cmd_checked( 123 ["gradlew.bat", "publishToMavenLocal", f"-Plocal={time.time_ns()}"], 124 shell=True, 125 ) 126 else: 127 run_cmd_checked([ 128 "./gradlew", 129 "publishToMavenLocal", 130 f"-Plocal={time.time_ns()}", 131 ]) 132 with open(LAST_CONTENTS_HASH_FILE, "w") as f: 133 f.write(contents_hash) 134 f.write("\n")