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