1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-11-19 00:04:53 +00:00
CC-Tweaked/go4it
2021-09-26 09:56:06 +01:00

246 lines
7.8 KiB
Python
Executable File

#!/usr/bin/env python3
"""
The main build script for CC: Tweaked.
This sets up work trees for each branch, ensures they are up-to-date with the
remotes and then merges the primary branch (mc-1.15.x) into each other branch.
We then build each branch, push changes, and upload all versions.
"""
import argparse
import os
import os.path
import pathlib
import regex
import shutil
import subprocess
import sys
import xml.etree.ElementTree as ET
from dataclasses import dataclass
from textwrap import dedent
from typing import List, Optional
@dataclass
class Branch:
name: str
parent: Optional[str] = None
java: str = "1.8"
BRANCHES: List[Branch] = [
Branch('mc-1.16.x'),
Branch('mc-1.17.x', parent='mc-1.16.x', java="16"),
# Branch('mc-fabric-1.17.x', parent='mc-1.17.x', java="16"),
]
def log(msg: str, *args) -> None:
"""Print a format string to the console"""
print("\033[32m" + msg % tuple(args) + "\033[0m")
def git_in(branch: Branch, *cmd: str, **kwargs) -> subprocess.CompletedProcess:
"""Run a git command on a specific branch."""
return subprocess.run(["git", "-C", branch.name, *cmd], **kwargs)
def run_in(branch: Branch, *cmd: str, **kwargs) -> subprocess.CompletedProcess:
"""Run a command within the context of a specific branch."""
return subprocess.run(cmd, cwd=branch.name, **kwargs)
def setup() -> None:
"""
Setup the repository suitable for working with multiple versions. Namely:
- Register all remotes.
- Copy gradle files from the default branch.
"""
# Update git remote.
log("Updating from remotes")
subprocess.check_call(["git", "fetch", "origin"])
# Setup git repository.
for branch in BRANCHES:
if not os.path.isdir(branch.name):
log(f"Creating worktree for {branch.name}")
subprocess.check_call(["git", "worktree", "add", branch.name, branch.name])
# Setup gradle.
log("Setting up gradle project")
for file in ["gradle", "gradlew", "gradlew.bat"]:
src = f"{BRANCHES[0].name}/{file}"
if os.path.isdir(src):
shutil.copytree(src, file, dirs_exist_ok=True)
else:
shutil.copy2(src, file)
with open("settings.gradle", "w") as h:
h.write(dedent("""\
pluginManagement {
repositories {
maven {
name = 'Fabric'
url = 'https://maven.fabricmc.net/'
}
gradlePluginPortal()
}
}
rootProject.name = 'cc-tweaked'
"""))
for branch in BRANCHES:
h.write(f"include '{branch.name}'\n")
with open("build.gradle", "w") as h:
pass
def gen_runs() -> None:
"""
Generate .idea run files
"""
setup()
subprocess.check_call(["./gradlew", "--no-daemon", "genIntellijRuns"])
re = regex.compile(r"(mc-.*)_run(.*)\.xml")
for path in pathlib.Path(".idea/runConfigurations").glob("*.xml"):
group = re.match(path.name)
if not group:
continue
version, name = group[1], group[2]
for branch in BRANCHES:
if branch.name == version:
break
else:
print(f"Cannot find Java for version for {path}")
continue
component = 'testMod' if name.startswith('Test') else 'main'
xml = ET.parse(path)
# Put run configurations in folders
configuration = xml.find("./configuration")
if configuration:
configuration.set("folderName", version)
# Ensure they're linked to the right CC:T version
module = xml.find("./configuration/module")
if module is None:
print("Unknown module for " + path.name)
else:
module.set("name", f"cc-tweaked.{version}.{component}")
# Force a specific JDK version
version = xml.find("./configuration/option[@name='ALTERNATIVE_JRE_PATH']")
if version:
version.set("value", branch.java)
else:
root = xml.find("./configuration")
assert root
ET.SubElement(root, 'option', {'name': 'ALTERNATIVE_JRE_PATH', 'value': branch.java})
ET.SubElement(root, 'option', {'name': 'ALTERNATIVE_JRE_PATH_ENABLED', 'value': 'true'})
xml.write(path)
def check_git() -> None:
"""
Check all worktrees are in a sensible state prior to merging.
"""
setup()
# Ensure every worktree is on the right branch, has no uncommited changes and is
# up-to-date with the remote.
ok = True
for branch in BRANCHES:
status = git_in(branch, "status", "--porcelain", check=True, stdout=subprocess.PIPE).stdout
if len(status.strip()) > 0:
log(f"{branch.name} has changes. Build will not continue.")
ok = False
continue
actual_branch = git_in(branch, "rev-parse", "--abbrev-ref", "HEAD", check=True, stdout=subprocess.PIPE).stdout.decode("utf-8").strip()
if actual_branch != branch.name:
log(f"{branch.name} is actually on {actual_branch} right now")
ok = False
continue
if git_in(branch, "merge-base", "--is-ancestor", f"origin/{branch.name}", branch.name).returncode != 0:
log(f"{branch.name} is not up-to-date with remote.")
ok = False
continue
if not ok:
sys.exit(1)
def build() -> None:
"""
Merge in parent branches, then build all branches.
"""
check_git()
# Merge each branch into the next one.
for branch in BRANCHES:
if (
branch.parent is not None and
git_in(branch, "merge-base", "--is-ancestor", branch.parent, branch.name).returncode != 0
):
log(f"{branch.name} is not up-to-date with {branch.parent}.")
ret = git_in(branch, "merge", "--no-edit", branch.parent).returncode
if ret != 0:
log(f"Merge {branch.parent} -> {branch.name} failed. Aborting.")
sys.exit(ret)
log("Git state is up-to-date. Preparing to build - you might want to make a cup of tea.")
for branch in BRANCHES:
log(f"Building {branch.name}")
ret = run_in(branch, "xvfb-run", "./gradlew", "build", "--no-daemon").returncode
if ret != 0:
log(f"Build failed")
sys.exit(ret)
def release() -> None:
"""Publish releases for each version."""
build()
check = input("Are you sure you want to release? Make sure you've performed some manual checks first! [y/N]").lower()
if check != "y":
sys.exit(1)
subprocess.check_call(["git", "push", "origin", *(b.name for b in BRANCHES)])
for branch in BRANCHES:
log(f"Uploading {branch.name}")
ret = run_in(branch, "./gradlew", "uploadAll", "--no-daemon").returncode
if ret != 0:
log(f"Upload failed. Good luck in recovering from this!")
sys.exit(ret)
log(f"Finished. Well done, you may now enjoy your tea!")
def main() -> None:
# Validate arguments.
parser = argparse.ArgumentParser(description="Build scripts for CC: Tweaked")
subparsers = parser.add_subparsers(
description="The subcommand to run. Subcommands implicitly run their dependencies.",
dest="subcommand",
required=True,
)
subparsers.add_parser("setup", help="Setup the git repository and build environment.").set_defaults(func=setup)
subparsers.add_parser("gen-runs", help="Generate IntelliJ IDEA run configurations.").set_defaults(func=gen_runs)
subparsers.add_parser("check-git", help="Check the git worktrees are in a state ready for merging.").set_defaults(func=check_git)
subparsers.add_parser("build", help="Merge and build all branches.").set_defaults(func=build)
subparsers.add_parser("release", help="Publish a release.").set_defaults(func=release)
parser.parse_args().func()
if __name__ == "__main__":
main()