""" Configure the project for building. """ #! /usr/bin/env python3 import argparse import os import shutil import sys from pathlib import Path from typing import Dict, List, Set, Union import ninja_syntax import splat import splat.scripts.split as split from splat.segtypes.linker_entry import LinkerEntry #MARK: Constants ROOT = Path(__file__).parent.resolve() TOOLS_DIR = ROOT / "tools" OUTDIR = "out" YAML_FILE = "config/sly1.yaml" BASENAME = "SCUS_971.98" LD_PATH = f"{BASENAME}.ld" ELF_PATH = f"{OUTDIR}/{BASENAME}" MAP_PATH = f"{OUTDIR}/{BASENAME}.map" PRE_ELF_PATH = f"{OUTDIR}/{BASENAME}.elf" COMMON_INCLUDES = "-Iinclude -isystem include/sdk/ee -isystem include/gcc" CC_DIR = f"{TOOLS_DIR}/cc/bin" COMMON_COMPILE_FLAGS = "-x c++ -V 2.95.2 -O2 -G0 -ffast-math" WINE = "wine" GAME_GCC_CMD = f"{CC_DIR}/ee-gcc.exe -c {COMMON_INCLUDES} {COMMON_COMPILE_FLAGS} $in" COMPILE_CMD = f"{GAME_GCC_CMD}" if sys.platform == "linux" or sys.platform == "linux2": COMPILE_CMD = f"{WINE} {GAME_GCC_CMD}" def clean(): """ Clean all products of the build process. """ files_to_clean = [ ".splache", ".ninja_log", "build.ninja", "permuter_settings.toml", LD_PATH ] for filename in files_to_clean: if os.path.exists(filename): os.remove(filename) shutil.rmtree("asm", ignore_errors=True) shutil.rmtree("assets", ignore_errors=True) shutil.rmtree("obj", ignore_errors=True) shutil.rmtree("out", ignore_errors=True) def write_permuter_settings(): """ Write the permuter settings file, comprising the compiler and assembler commands. """ with open("permuter_settings.toml", "w", encoding="utf-8") as f: f.write(f"""compiler_command = "{COMPILE_CMD} -D__GNUC__" assembler_command = "mips-linux-gnu-as -march=r5900 -mabi=eabi -Iinclude" compiler_type = "gcc" [preserve_macros] [decompme.compilers] "tools/build/cc/gcc/gcc" = "ee-gcc2.96" """) #MARK: Build def build_stuff(linker_entries: List[LinkerEntry], skip_checksum=False): """ Build the objects and the final ELF file. """ built_objects: Set[Path] = set() def build( object_paths: Union[Path, List[Path]], src_paths: List[Path], task: str, variables: Dict[str, str] = None, implicit_outputs: List[str] = None, ): """ Helper function to build objects. """ # Handle none parameters if variables is None: variables = {} if implicit_outputs is None: implicit_outputs = [] # Convert object_paths to list if it is not already if not isinstance(object_paths, list): object_paths = [object_paths] # Convert paths to strings object_strs = [str(obj) for obj in object_paths] # Add object paths to built_objects for object_path in object_paths: if object_path.suffix == ".o": built_objects.add(object_path) ninja.build( outputs=object_strs, rule=task, inputs=[str(s) for s in src_paths], variables=variables, implicit_outputs=implicit_outputs, ) ninja = ninja_syntax.Writer(open(str(ROOT / "build.ninja"), "w", encoding="utf-8"), width=9999) #MARK: Rules cross = "mips-linux-gnu-" ld_args = "-EL -T config/undefined_syms_auto.txt -T config/undefined_funcs_auto.txt -Map $mapfile -T $in -o $out" ninja.rule( "as", description="as $in", command=f"cpp {COMMON_INCLUDES} $in -o - | {cross}as -no-pad-sections -EL -march=5900 -mabi=eabi -Iinclude -o $out", ) ninja.rule( "cc", description="cc $in", command=f"{COMPILE_CMD} -o $out && {cross}strip $out -N dummy-symbol-name", ) ninja.rule( "ld", description="link $out", command=f"{cross}ld {ld_args}", ) ninja.rule( "sha1sum", description="sha1sum $in", command="sha1sum -c $in && touch $out", ) ninja.rule( "elf", description="elf $out", command=f"{cross}objcopy $in $out -O binary", ) # Build all the objects for entry in linker_entries: seg = entry.segment if seg.type[0] == ".": continue if entry.object_path is None: continue if isinstance(seg, splat.segtypes.common.asm.CommonSegAsm) or isinstance( seg, splat.segtypes.common.data.CommonSegData ): build(entry.object_path, entry.src_paths, "as") elif isinstance(seg, splat.segtypes.common.c.CommonSegC): build(entry.object_path, entry.src_paths, "cc") elif isinstance(seg, splat.segtypes.common.databin.CommonSegDatabin): build(entry.object_path, entry.src_paths, "as") elif isinstance(seg, splat.segtypes.common.rodatabin.CommonSegRodatabin): build(entry.object_path, entry.src_paths, "as") elif isinstance(seg, splat.segtypes.common.textbin.CommonSegTextbin): build(entry.object_path, entry.src_paths, "as") elif isinstance(seg, splat.segtypes.common.bin.CommonSegBin): build(entry.object_path, entry.src_paths, "as") else: print(f"ERROR: Unsupported build segment type {seg.type}") sys.exit(1) ninja.build( PRE_ELF_PATH, "ld", LD_PATH, implicit=[str(obj) for obj in built_objects], variables={"mapfile": MAP_PATH}, ) ninja.build( ELF_PATH, "elf", PRE_ELF_PATH, ) if not skip_checksum: ninja.build( ELF_PATH + ".ok", "sha1sum", "checksum.sha1", implicit=[ELF_PATH], ) else: print("Skipping checksum step") #MARK: Main def main(): """ Main function, parses arguments and runs the configuration. """ parser = argparse.ArgumentParser(description="Configure the project") parser.add_argument( "-c", "--clean", help="Clean artifacts and build", action="store_true", ) parser.add_argument( "-C", "--only-clean", help="Only clean artifacts", action="store_true", ) parser.add_argument( "-s", "--skip-checksum", help="Skip the checksum step", action="store_true", ) args = parser.parse_args() do_clean = (args.clean or args.only_clean) or False do_skip_checksum = args.skip_checksum or False if do_clean: clean() if args.only_clean: return split.main([YAML_FILE], modes="all", verbose=False) linker_entries = split.linker_writer.entries build_stuff(linker_entries, do_skip_checksum) write_permuter_settings() if __name__ == "__main__": main()