mirror of
https://github.com/valentderah/totk-pc-recomp.git
synced 2026-11-11 01:41:02 +00:00
95 lines
4.6 KiB
Python
95 lines
4.6 KiB
Python
"""Build hactool: toolchain/hactool/hactool.exe (SciresM/hactool at a pinned commit + tools/hactool/*.patch).
|
|
|
|
Fetches the pinned commit into toolchain/src/hactool, resets it (local changes and build output there are discarded),
|
|
applies portable.patch (MacNativeGames' Windows-relevant changes) and unicode.patch (ours), and builds a static exe
|
|
with MSYS2 UCRT64 gcc; mbedtls comes with hactool's tree. The exe and hactool's LICENSE go to toolchain/hactool, where
|
|
tools/extract.py and tools/make_release.py take them. Run with .venv. On Linux: the system's cc and make build
|
|
toolchain/hactool/hactool (unicode.patch only adds a Windows manifest, under EXEEXT .exe).
|
|
|
|
python tools/build_hactool.py [--src DIR] [--out DIR]
|
|
"""
|
|
|
|
import argparse
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
URL = 'https://github.com/SciresM/hactool.git'
|
|
COMMIT = '1d64a83450e025622f3468c28fc4164dad2c5ef6'
|
|
PATCHES = [ROOT / 'tools' / 'hactool' / n for n in ('portable.patch', 'unicode.patch', 'mbedtls-asm-clobbers.patch')]
|
|
WIN = os.name == 'nt'
|
|
MSYS_BASH = Path(r'C:\msys64\usr\bin\bash.exe')
|
|
EXE = 'hactool.exe' if WIN else 'hactool'
|
|
CONFIG_MK = ('CC = gcc\nEXEEXT = .exe\nCFLAGS = -O2 -Wall -Wextra -pedantic -std=gnu11\n'
|
|
'LDFLAGS = -lmbedtls -lmbedx509 -lmbedcrypto -static\n') if WIN else \
|
|
('CC = cc\nEXEEXT =\nCFLAGS = -O2 -Wall -Wextra -pedantic -std=gnu11\n'
|
|
'LDFLAGS = -lmbedtls -lmbedx509 -lmbedcrypto\n')
|
|
|
|
|
|
def git(src, *args):
|
|
subprocess.run(['git', '-C', str(src), *args], check=True)
|
|
|
|
|
|
def fetch(src):
|
|
"""src holds COMMIT with no local changes or untracked files."""
|
|
if not (src / '.git').is_dir():
|
|
src.mkdir(parents=True, exist_ok=True)
|
|
git(src, 'init', '-q')
|
|
git(src, 'remote', 'add', 'origin', URL)
|
|
head = subprocess.run(['git', '-C', str(src), 'rev-parse', 'HEAD'], capture_output=True, text=True).stdout.strip()
|
|
if head != COMMIT:
|
|
git(src, 'fetch', '-q', '--depth', '1', 'origin', COMMIT)
|
|
git(src, 'checkout', '-q', '-f', COMMIT)
|
|
git(src, 'clean', '-q', '-fdx')
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
ap.add_argument('--src', type=Path, default=ROOT / 'toolchain' / 'src' / 'hactool', help='source checkout')
|
|
ap.add_argument('--out', type=Path, default=ROOT / 'toolchain' / 'hactool', help='where hactool(.exe) goes')
|
|
ap.add_argument('--cc', help='Linux: the C compiler command for hactool and its bundled mbedtls, for example '
|
|
'"clang --sysroot=DIR" (tools/make_release_linux.py); default cc')
|
|
args = ap.parse_args()
|
|
if WIN and not MSYS_BASH.is_file():
|
|
sys.exit(f'{MSYS_BASH} not found: install MSYS2 with the UCRT64 gcc and make (docs/building.md)')
|
|
if not WIN and not (shutil.which('make') and (args.cc or shutil.which('cc'))):
|
|
sys.exit('make and cc not found: install a C compiler and make (docs/building.md)')
|
|
src, out = args.src.resolve(), args.out.resolve()
|
|
|
|
fetch(src)
|
|
for p in PATCHES:
|
|
git(src, 'apply', '--whitespace=nowarn', str(p))
|
|
config = CONFIG_MK.replace('CC = cc\n', f'CC = {args.cc}\n') if args.cc else CONFIG_MK
|
|
(src / 'config.mk').write_text(config, encoding='utf-8', newline='\n')
|
|
|
|
jobs = f'-j{os.cpu_count() or 4}'
|
|
if WIN:
|
|
env = dict(os.environ, MSYSTEM='UCRT64', CHERE_INVOKING='1')
|
|
r = subprocess.run([str(MSYS_BASH), '-lc', f'make {jobs}'], cwd=src, env=env, capture_output=True, text=True)
|
|
else:
|
|
r = subprocess.run(['make', jobs, *([f'CC={args.cc}'] if args.cc else [])], cwd=src, capture_output=True,
|
|
text=True) # CC= on the command line: the bundled mbedtls's own make reads it too
|
|
(src / 'build.log').write_text(r.stdout + r.stderr, encoding='utf-8')
|
|
if r.returncode:
|
|
sys.exit(f'make failed ({r.returncode}); log: {src / "build.log"}')
|
|
|
|
out.mkdir(parents=True, exist_ok=True)
|
|
shutil.copy2(src / EXE, out / EXE)
|
|
shutil.copy2(src / 'LICENSE', out / 'LICENSE')
|
|
# hactool reads ~/.switch/prod.keys before it prints the banner and exits on a malformed key there, so the check
|
|
# runs with an empty home: it tests the binary, not the builder's keys.
|
|
with tempfile.TemporaryDirectory() as home:
|
|
env = dict(os.environ, HOME=home, USERPROFILE=home)
|
|
banner = subprocess.run([str(out / EXE)], capture_output=True, text=True, env=env)
|
|
if 'hactool (c) SciresM' not in banner.stdout + banner.stderr:
|
|
sys.exit(f'{out / EXE} does not start')
|
|
print(f'{out / EXE}: {COMMIT[:7]} + {", ".join(p.name for p in PATCHES)}')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|