Revision control

Copy as Markdown

#!/usr/bin/env python3
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# Purpose: Publish android packages to local maven repo, but only if changed since last publish.
# Dependencies: None
# Usage: ./automation/publish_to_maven_local_if_modified.py
import argparse
import hashlib
import os
import subprocess
import sys
import time
from pathlib import Path
def fatal_err(msg):
print(f"\033[31mError: {msg}\033[0m")
exit(1)
def run_cmd_checked(*args, **kwargs):
"""Run a command, throwing an exception if it exits with non-zero status."""
kwargs["check"] = True
return subprocess.run(*args, **kwargs)
def find_project_root():
"""Find the absolute path of the project repository root."""
# As a convention, we expect this file in [project-root]/automation/.
automation_dir = Path(__file__).parent
# Therefore the automation dir's parent is the project root we're looking for.
return automation_dir.parent
LAST_CONTENTS_HASH_FILE = ".lastAutoPublishContentsHash"
GITIGNORED_FILES_THAT_AFFECT_THE_BUILD = ["local.properties"]
parser = argparse.ArgumentParser(
description="Publish android packages to local maven repo, but only if changed since last publish"
)
parser.parse_args()
root_dir = find_project_root()
if str(root_dir) != os.path.abspath(os.curdir):
fatal_err(
f"This only works if run from the repo root ({root_dir!r} != {os.path.abspath(os.curdir)!r})"
)
# Calculate a hash reflecting the current state of the repo.
contents_hash = hashlib.sha256()
contents_hash.update(
run_cmd_checked(["git", "rev-parse", "HEAD"], capture_output=True).stdout
)
contents_hash.update(b"\x00")
# Get a diff of all tracked (staged and unstaged) files.
changes = run_cmd_checked(["git", "diff", "HEAD", "."], capture_output=True).stdout
contents_hash.update(changes)
contents_hash.update(b"\x00")
# But unfortunately it can only tell us the names of untracked
# files, and it won't tell us anything about files that are in
# .gitignore but can still affect the build.
untracked_files = []
# Get a list of all untracked files sans standard exclusions.
# -o is for getting other (i.e. untracked) files
# --exclude-standard is to handle standard Git exclusions: .git/info/exclude, .gitignore in each directory,
# and the user's global exclusion file.
changes_others = run_cmd_checked(
["git", "ls-files", "-o", "--exclude-standard"], capture_output=True
).stdout
changes_lines = iter(ln.strip() for ln in changes_others.split(b"\n"))
try:
ln = next(changes_lines)
while ln:
untracked_files.append(ln)
ln = next(changes_lines)
except StopIteration:
pass
# Then, account for some excluded files that we care about.
untracked_files.extend(GITIGNORED_FILES_THAT_AFFECT_THE_BUILD)
# Finally, get hashes of everything.
# Skip files that don't exist, e.g. missing GITIGNORED_FILES_THAT_AFFECT_THE_BUILD. `hash-object` errors out if it gets
# a non-existent file, so we hope that disk won't change between this filter and the cmd run just below.
filtered_untracked = [nm for nm in untracked_files if os.path.isfile(nm)]
# Reading contents of the files is quite slow when there are lots of them, so delegate to `git hash-object`.
git_hash_object_cmd = ["git", "hash-object"]
git_hash_object_cmd.extend(filtered_untracked)
changes_untracked = run_cmd_checked(git_hash_object_cmd, capture_output=True).stdout
contents_hash.update(changes_untracked)
contents_hash.update(b"\x00")
contents_hash = contents_hash.hexdigest()
# If the contents hash has changed since last publish, re-publish.
last_contents_hash = ""
try:
with open(LAST_CONTENTS_HASH_FILE) as f:
last_contents_hash = f.read().strip()
except FileNotFoundError:
pass
if contents_hash == last_contents_hash:
print("Contents have not changed, no need to publish")
else:
print("Contents have changed, publishing")
if sys.platform.startswith("win"):
run_cmd_checked(
["gradlew.bat", "publishToMavenLocal", f"-Plocal={time.time_ns()}"],
shell=True,
)
else:
run_cmd_checked(
["./gradlew", "publishToMavenLocal", f"-Plocal={time.time_ns()}"]
)
with open(LAST_CONTENTS_HASH_FILE, "w") as f:
f.write(contents_hash)
f.write("\n")