Files
st/tools/project.py
T
Yanis 9474561b03 Update tools and migrate to dsd v0.11.0 (#40)
* update tools and migrate to dsd v0.11.0

* add phony commands and remove format script
2026-04-04 16:20:36 +02:00

955 lines
29 KiB
Python

import os
import sys
import json
import subprocess
import ninja_syntax
from pathlib import Path
from typing import Any, Optional, Dict, List
from get_platform import get_platform
COMPILER_MAP = {
"1.2/base": "mwcc_20_72",
"1.2/sp2": "mwcc_20_79",
"1.2/sp2p3": "mwcc_20_82",
"1.2/sp3": "mwcc_20_84",
"1.2/sp4": "mwcc_20_87",
"2.0/base": "mwcc_30_114",
"2.0/sp1": "mwcc_30_123",
"2.0/sp1p2": "mwcc_30_126",
"2.0/sp1p5": "mwcc_30_131",
"2.0/sp1p6": "mwcc_30_133",
"2.0/sp1p7": "mwcc_30_134",
"2.0/sp2": "mwcc_30_136",
"2.0/sp2p2": "mwcc_30_137",
"2.0/sp2p3": "mwcc_30_138",
"2.0/sp2p4": "mwcc_30_139",
"dsi/1.1": "mwcc_40_1018",
"dsi/1.1p1": "mwcc_40_1024",
"dsi/1.2": "mwcc_40_1026",
"dsi/1.2p1": "mwcc_40_1027",
"dsi/1.2p2": "mwcc_40_1028",
"dsi/1.3": "mwcc_40_1034",
"dsi/1.3p1": "mwcc_40_1036",
"dsi/1.6sp1": "mwcc_40_1051",
"dsi/1.6sp2": "mwcc_40_1051",
}
Library = Dict[str, Any]
def get_c_cpp_files(dirs: list[Path]):
for dir in dirs:
for root, _, files in os.walk(dir):
root = Path(root)
for file in files:
if is_cpp(file) or is_c(file):
yield root / file
def is_cpp(name: str | Path):
return Path(name).suffix in [".cpp"]
def is_c(name: str | Path):
return Path(name).suffix in [".c"]
class Object:
def __init__(self, name: str, **options: Any):
self.name = name
self.options: Dict[str, Any] = {
"source": name,
"mw_version": None,
"asflags": None,
"extra_asflags": [],
"cflags": None,
"extra_cflags": [],
"asm_dir": None,
"src_dir": None,
}
self.options.update(options)
self.src_path: Optional[Path] = None
self.asm_path: Optional[Path] = None
self.src_obj_path: Optional[Path] = None
self.asm_obj_path: Optional[Path] = None
self.ctx_path: Optional[Path] = None
def resolve(self, config: "ProjectConfig", lib: Library):
# Use object options, then library options
obj = Object(self.name, **lib)
for key, value in self.options.items():
if value is not None or key not in obj.options:
obj.options[key] = value
# Use default options from config
def set_default(key: str, value: Any) -> None:
if obj.options[key] is None:
obj.options[key] = value
set_default("asflags", config.asflags)
set_default("mw_version", config.mwcc_version)
set_default("asm_dir", config.asm_path)
set_default("src_dir", config.src_path)
# Resolve paths
obj.src_path = Path(obj.options["src_dir"]) / obj.options["source"]
if obj.options["asm_dir"] is not None:
path: Path = Path(obj.options["asm_dir"]) / obj.options["source"]
obj.asm_path = path.with_suffix(".s")
base_name = Path(self.name).with_suffix("")
obj.src_obj_path = config.build_path / "src" / f"{base_name}.o"
obj.asm_obj_path = config.build_path / "mod" / f"{base_name}.o"
obj.ctx_path = config.build_path / "src" / f"{base_name}.ctx"
return obj
class ProjectConfig:
def __init__(self, game: str, mwcc_root: Optional[str], mwcc_tag: str, wine: str, dsd: Optional[str], cfg_script: Path):
# Tools
self.dsd_tag: Optional[str] = None
self.wibo_tag: Optional[str] = None
self.objdiff_tag: Optional[str] = None
self.mwcc_tag = mwcc_tag
self.mwcc_root = Path(mwcc_root).resolve() if mwcc_root is not None else self.tools_path / "mwccarm"
self.cfg_script = cfg_script
# Platform info
self.platform = get_platform()
"""Host platform information"""
assert self.platform is not None
self.default_wibo_path = "./wibo"
self.wine_path = wine if self.platform.system != "windows" else ""
self.dsd_path = (Path(dsd) if dsd is not None else (self.root_path / f"dsd{self.platform.exe}")).resolve()
self.objdiff_path = (self.root_path / f"objdiff-cli{self.platform.exe}").resolve()
self.cc_path = (self.mwcc_path / "mwccarm.exe").resolve()
self.ld_path = (self.mwcc_path / "mwldarm.exe").resolve()
self.python_path = Path(sys.executable).resolve()
self.dsd_base_flags = [
"--force-color", # Force color output
]
self.game = game
"""Name of the game"""
self.game_versions: list[str] = []
"""Versions of the game"""
self.delinks_jsons: dict[str, Optional[Any]] = {}
"""Delinks JSON data from dsd (version: json)"""
self.delinks_files: dict[str, list[str]] = {}
"""Paths to every delinks.txt file"""
self.relocs_files: dict[str, list[str]] = {}
"""Paths to every relocs.txt file"""
self.symbols_files: dict[str, list[str]] = {}
"""Paths to every symbols.txt file"""
self.libs: Optional[List[Library]] = None
"""List of libraries"""
self.asflags: Optional[List[str]] = None
"""Assembler flags"""
self.cflags_base: Optional[List[str]] = None
"""Base C flags"""
self.ldflags: Optional[List[str]] = None
"""Base C flags"""
includes = [self.root_path / "include"]
for root, dirs, _ in os.walk(self.libs_path):
for dir in dirs:
if dir == "include":
includes.append(Path(root) / dir)
self.includes = " ".join(f"-i {include}" for include in includes)
"""C/C++ includes"""
self.auto_add_sources: bool = False
"""Adds rules for files missing from the libs list (with the base cflags as the default)"""
self.warn_missing_source: bool = True
"""Warn on missing source file"""
def get_game_config(self, version: str):
"""Root directory for dsd configs"""
config_path = self.config_path / version
assert config_path.is_dir(), f"Version '{version}' not recognized"
return config_path
def get_game_build(self, version: str):
"""Path to build directory"""
return self.build_path / version
def get_game_extract(self, version: str):
"""Path to extract directory"""
return self.extract_path / version
@property
def dsd_version(self):
assert self.dsd_tag is not None
return self.dsd_tag
@property
def wibo_version(self):
assert self.wibo_tag is not None
return self.wibo_tag
@property
def objdiff_version(self):
assert self.objdiff_tag is not None
return self.objdiff_tag
@property
def mwcc_version(self):
assert self.mwcc_tag is not None
return self.mwcc_tag
@property
def current_path(self):
return Path(__name__)
@property
def root_path(self):
return self.current_path.parent
@property
def build_ninja_path(self):
return self.root_path / "build.ninja"
@property
def arm7_bios_path(self):
return self.root_path / "arm7_bios.bin"
@property
def config_path(self):
return self.root_path / "config"
@property
def build_path(self):
return self.root_path / "build"
@property
def asm_path(self):
return self.root_path / "asm"
@property
def src_path(self):
return self.root_path / "src"
@property
def libs_path(self):
return self.root_path / "libs"
@property
def extract_path(self):
return self.root_path / "extract"
@property
def tools_path(self):
return self.root_path / "tools"
@property
def mwcc_path(self):
return self.mwcc_root / self.mwcc_version
@property
def dsd_flags(self):
return " ".join(self.dsd_base_flags)
def dsd_configs(self, version: str) -> list[str]:
return self.delinks_files[version] + self.relocs_files[version] + self.symbols_files[version]
def arm9_config_yaml(self, version: str) -> Path:
return self.get_game_config(version) / "arm9" / "config.yaml"
def baserom(self, version: str) -> Path:
return self.extract_path / f'baserom_{self.game}_{version}.nds'
def build_rom(self, version: str) -> str:
return f"{self.game}_{version}.nds"
def baserom_config(self, version: str,) -> Path:
return self.get_game_extract(version) / 'config.yaml'
def build_rom_config(self, version: str) -> Path:
return self.get_game_build(version) / "build" / "rom_config.yaml"
def source_object_files(self, version: str) -> list[str]:
files: list[str] = []
for source_file in get_c_cpp_files([self.src_path, self.libs_path]):
src_obj_path = self.get_game_build(version) / source_file
files.append(str(src_obj_path.with_suffix(".o")))
return files
def arm9_o(self, version: str) -> Path:
return self.get_game_build(version) / "arm9.o"
def arm9_disassembly_dir(self, version: str) -> Path:
return self.get_game_build(version) / "asm"
def objdiff_report(self, version: str) -> Path:
return f"report_{version}.json"
def files(self, version: str) -> list[dict[str, str]]:
if self.delinks_jsons[version] is None:
return []
return self.delinks_jsons[version]['files']
def delink_files(self, version: str) -> list[str]:
delink_files = [file['delink_file'] for file in self.files(version)]
return list(set(delink_files))
def arm9_lcf_file(self, version: str) -> str:
if self.delinks_jsons[version] is None:
return ""
return self.delinks_jsons[version]['arm9_lcf_file']
def arm9_objects_file(self, version: str) -> str:
if self.delinks_jsons[version] is None:
return ""
return self.delinks_jsons[version]['arm9_objects_file']
def get_config_files(self, version: str, name: str) -> list[str]:
return [
f"{root}/{file}"
for root, _, files in os.walk(self.get_game_config(version))
for file in files
if file == name
]
def get_decompme_compiler(self):
return COMPILER_MAP[self.mwcc_version]
# Creates a map of object names to Object instances
# Options are fully resolved from the library and object
def objects(self) -> Dict[str, Object]:
out = {}
for lib in self.libs or {}:
objects: List[Object] = lib["objects"]
for obj in objects:
if obj.name in out:
sys.exit(f"Duplicate object name {obj.name}")
out[obj.name] = obj.resolve(self, lib)
return out
def check_can_run_dsd(self) -> bool:
try:
output = subprocess.run([str(self.dsd_path), "--version"], capture_output=True, text=True, check=True)
version = output.stdout.strip().split(" ")[-1]
if not version.startswith("v"):
version = "v" + version
# If it's not the correct version, Ninja will download it and then rerun this script
return version == self.dsd_version
except subprocess.CalledProcessError:
return False
except FileNotFoundError:
return False
def add_download_tool_builds(cfg: ProjectConfig, n: ninja_syntax.Writer, args: Any):
downloads: list[str] = []
if args.dsd is None:
downloads.append(str(cfg.dsd_path))
n.build(
rule="download_tool",
outputs=str(cfg.dsd_path),
variables={
"tool": "dsd",
"tag": cfg.dsd_version,
"path": cfg.dsd_path,
},
)
n.newline()
downloads.append(str(cfg.objdiff_path))
n.build(
rule="download_tool",
outputs=str(cfg.objdiff_path),
variables={
"tool": "objdiff",
"tag": cfg.objdiff_version,
"path": cfg.objdiff_path,
}
)
n.newline()
if args.compiler is None:
downloads.extend([str(cfg.cc_path), str(cfg.ld_path)])
n.build(
rule="download_tool",
outputs=[str(cfg.cc_path), str(cfg.ld_path)],
variables={
"tool": "mwccarm",
"tag": "latest",
"path": str(cfg.tools_path),
},
)
n.newline()
if cfg.platform.system != "windows" and cfg.wine_path == cfg.default_wibo_path:
downloads.append(str(cfg.wine_path))
n.build(
rule="download_tool",
outputs=cfg.wine_path,
variables={
"tool": "wibo",
"tag": cfg.wibo_version,
"path": cfg.wine_path,
},
)
n.newline()
n.build(
inputs=downloads,
rule="phony",
outputs="download_tools",
)
n.newline()
def add_configure_build(cfg: ProjectConfig, n: ninja_syntax.Writer):
dsd_cfg = []
for version in cfg.game_versions:
dsd_cfg.extend(cfg.dsd_configs(version))
n.build(
outputs="build.ninja",
rule="configure",
implicit=[
str(cfg.cfg_script),
# Require dsd to exist when rerunning configure.py
str(cfg.dsd_path),
*dsd_cfg,
]
)
def add_extract_build(cfg: ProjectConfig, version: str, n: ninja_syntax.Writer, args: Any):
if not args.no_extract:
n.build(
inputs=str(cfg.baserom(version)),
implicit=str(cfg.dsd_path),
rule="extract",
outputs=str(cfg.baserom_config(version)),
variables={
"output_path": str(cfg.get_game_extract(version))
}
)
n.newline()
def add_delink_and_lcf_builds(cfg: ProjectConfig, version: str, n: ninja_syntax.Writer):
rom_config = str(cfg.baserom_config(version))
delink_files = cfg.delink_files(version)
if len(delink_files) > 0:
n.comment("Delink ELF binaries when any delinks.txt file is modified")
n.build(
inputs=cfg.dsd_configs(version) + [rom_config],
implicit=str(cfg.dsd_path),
rule="delink",
outputs=delink_files,
variables={
"config_path": str(cfg.arm9_config_yaml(version)),
}
)
n.newline()
n.build(
inputs=delink_files,
rule="phony",
outputs=f"delink_{version}"
)
n.newline()
lcf_file = cfg.arm9_lcf_file(version)
objects_file = cfg.arm9_objects_file(version)
n.build(
inputs=cfg.delinks_files[version] + [str(rom_config)],
implicit=str(cfg.dsd_path),
rule="lcf",
outputs=[lcf_file, objects_file],
variables={
"config_path": str(cfg.arm9_config_yaml(version)),
}
)
n.newline()
def add_disassemble_builds(cfg: ProjectConfig, version: str, n: ninja_syntax.Writer):
n.build(
inputs=cfg.dsd_configs(version),
implicit=str(cfg.dsd_path),
rule="disassemble",
outputs=f"dis_{version}",
variables={
"config_path": str(cfg.arm9_config_yaml(version)),
"output_path": str(cfg.arm9_disassembly_dir(version)),
}
)
n.newline()
def add_mwcc_builds(cfg: ProjectConfig, version: str, objects: Dict[str, Object], n: ninja_syntax.Writer, mwcc_implicit: list[str]):
file_map: dict[str, list[str]] = {}
for object in objects.values():
file_map[str(object.src_path)] = object.options["cflags"] + object.options["extra_cflags"]
if cfg.auto_add_sources:
for source_file in get_c_cpp_files([cfg.src_path, cfg.libs_path]):
if str(source_file) not in file_map:
file_map[str(source_file)] = cfg.cflags_base
for src_file, cc_flags in file_map.items():
source_file = Path(src_file)
src_obj_path = cfg.get_game_build(version) / source_file
if cfg.warn_missing_source and not source_file.exists():
print(f"WARNING: path not found for `{source_file}`")
if "-lang=c++" not in cc_flags or "-lang=c" not in cc_flags:
if is_cpp(source_file):
cc_flags.append("-lang=c++")
elif is_c(source_file):
cc_flags.append("-lang=c")
n.build(
inputs=str(source_file),
implicit=mwcc_implicit,
rule="mwcc",
outputs=str(src_obj_path.with_suffix(".o")),
variables={
"game_version": version.upper(),
"cc_flags": " ".join(cc_flags),
"basedir": str(src_obj_path.parent),
"basefile": str(src_obj_path.with_suffix("")),
},
)
n.newline()
extension = source_file.suffix
ctx_file = str(src_obj_path.with_suffix(f".ctx{extension}"))
n.build(
inputs=str(source_file),
rule="m2ctx",
outputs=ctx_file,
variables={
"version": version.upper(),
},
)
n.newline()
def add_mwld_and_rom_builds(cfg: ProjectConfig, version: str, n: ninja_syntax.Writer):
n.comment("Run linker")
objects_to_link = [file['object_to_link'] for file in cfg.files(version)]
elf_file = str(cfg.arm9_o(version))
lcf_file = cfg.arm9_lcf_file(version)
objects_file = cfg.arm9_objects_file(version)
if len(objects_to_link) > 0:
ld_implicit = [str(cfg.ld_path)]
if cfg.wine_path == cfg.default_wibo_path:
ld_implicit.append(cfg.wine_path)
# Only passed to the final arm9.o link
arm9_ld_flags = " ".join([
"-m Entry", # Set entry function
])
n.build(
inputs=[*objects_to_link, lcf_file, objects_file],
implicit=ld_implicit,
rule="mwld",
outputs=elf_file,
variables={
'extra_ld_flags': arm9_ld_flags,
'lcf_file': str(lcf_file),
'objects_file': str(objects_file),
}
)
n.newline()
n.build(
inputs=elf_file,
rule="phony",
outputs=f"arm9_{version}",
)
n.newline()
rom_config_file = str(cfg.build_rom_config(version))
n.build(
inputs=elf_file,
implicit=str(cfg.dsd_path),
rule="rom_config",
outputs=rom_config_file,
variables={
"config_path": str(cfg.arm9_config_yaml(version)),
}
)
n.newline()
rom_file = cfg.build_rom(version)
n.build(
inputs=rom_config_file,
implicit=str(cfg.dsd_path),
rule="rom_build",
outputs=rom_file,
)
n.newline()
n.build(
inputs=rom_file,
rule="phony",
outputs=f"rom_{version}",
)
n.newline()
n.build(
inputs=rom_file,
rule="sha1",
variables={
"sha1_file": str(Path(rom_file).with_suffix(".sha1"))
},
outputs=f"sha1_{version}",
)
n.newline()
def add_check_builds(cfg: ProjectConfig, version: str, n: ninja_syntax.Writer):
n.build(
inputs=str(cfg.arm9_o(version)),
rule="check_modules",
outputs=f"check_modules_{version}",
variables={
"config_path": str(cfg.arm9_config_yaml(version)),
},
)
n.newline()
n.build(
inputs=str(cfg.arm9_o(version)),
rule="check_symbols",
outputs=f"check_symbols_{version}",
variables={
"config_path": str(cfg.arm9_config_yaml(version)),
"elf_path": str(cfg.arm9_o(version)),
},
)
n.newline()
n.build(
inputs=[f"check_modules_{version}", f"check_symbols_{version}"],
rule="phony",
outputs=f"check_{version}",
)
n.newline()
def add_objdiff_builds(cfg: ProjectConfig, version: str, n: ninja_syntax.Writer):
out_path = cfg.get_game_build(version) / "objdiff.json"
n.build(
inputs=cfg.dsd_configs(version),
implicit=str(cfg.dsd_path),
rule="objdiff",
outputs=str(out_path),
variables={
"config_path": str(cfg.arm9_config_yaml(version)),
"out_path": str(cfg.get_game_build(version)),
}
)
n.newline()
n.build(
inputs=str(out_path),
rule="phony",
outputs=f"objdiff_{version}.json",
)
n.newline()
delink_files = cfg.delink_files(version)
n.build(
inputs=[str(out_path)],
implicit=[str(cfg.objdiff_path)] + delink_files + cfg.source_object_files(version),
rule="objdiff_report",
outputs=str(cfg.objdiff_report(version)),
variables={
"dir": str(cfg.get_game_build(version)),
"filename": "report.json"
}
)
n.newline()
n.build(
inputs=str(cfg.objdiff_report(version)),
rule="phony",
outputs=f"report_{version}",
)
n.newline()
def add_apply_build(cfg: ProjectConfig, version: str, n: ninja_syntax.Writer):
n.build(
inputs=cfg.dsd_configs(version) + [str(cfg.arm9_o(version))],
implicit=str(cfg.dsd_path),
rule="apply",
outputs=f"apply_{version}",
variables={
"config_path": str(cfg.arm9_config_yaml(version)),
"elf_path": str(cfg.arm9_o(version)),
}
)
n.newline()
def create_objdiff_fixup_config(cfg: ProjectConfig, objects: Dict[str, Object]):
out_json = {}
out_json["root_path"] = str(cfg.root_path.resolve())
out_json["versions"] = {}
for version in cfg.game_versions:
out_json["versions"][version] = {}
out_json["units"] = {}
for version in cfg.game_versions:
out_json["versions"][version]["objdiff"] = str(cfg.get_game_build(version) / "objdiff.json")
for name, object in objects.items():
out_json["units"][name] = {}
for name, object in objects.items():
out_json["units"][name]["cflags"] = object.options["cflags"]
out_json["units"][name]["extra_cflags"] = object.options["extra_cflags"]
out_json["units"][name]["mw_version"] = COMPILER_MAP[object.options["mw_version"]]
cfg.build_path.mkdir(exist_ok=True)
out_path = cfg.build_path / "objdiff_cfg.json"
with out_path.open("w") as f:
json.dump(out_json, f, indent=2)
def process_project(cfg: ProjectConfig, args: Any):
objects = cfg.objects()
create_objdiff_fixup_config(cfg, objects)
with cfg.build_ninja_path.open("w") as file:
n = ninja_syntax.Writer(file)
n.rule(
name="download_tool",
command=f'{cfg.python_path} tools/download_tool.py $tool $tag --path $path'
)
n.newline()
if cfg.arm7_bios_path.is_file():
n.variable("arm7_bios_flag", f"--arm7-bios {cfg.arm7_bios_path.relative_to(cfg.root_path)}")
else:
n.variable("arm7_bios_flag", "")
n.newline()
n.rule(
name="extract",
command=f"{cfg.dsd_path} {cfg.dsd_flags} rom extract --rom $in --output-path $output_path $arm7_bios_flag"
)
n.newline()
n.rule(
name="delink",
command=f"{cfg.dsd_path} {cfg.dsd_flags} delink --config-path $config_path"
)
n.newline()
n.rule(
name="disassemble",
command=f"{cfg.dsd_path} {cfg.dsd_flags} dis --config-path $config_path --asm-path $output_path --ual"
)
n.newline()
# -MMD excludes all includes instead of just system includes for some reason, so use -MD instead.
mwcc_cmd = f'{cfg.wine_path} "{cfg.cc_path}" $cc_flags {cfg.includes} -DVERSION=$game_version -MD -c $in -o $basedir'
mwcc_implicit = [str(cfg.cc_path)]
if cfg.platform.system != "windows":
transform_dep = "tools/transform_dep.py"
mwcc_cmd += f" && $python {transform_dep} $basefile.d $basefile.d"
mwcc_implicit.append(transform_dep)
if cfg.wine_path == cfg.default_wibo_path:
mwcc_implicit.append(cfg.wine_path)
n.rule(
name="mwcc",
command=mwcc_cmd,
depfile="$basefile.d",
)
n.newline()
n.rule(
name="lcf",
command=f"{cfg.dsd_path} {cfg.dsd_flags} lcf -c $config_path"
)
n.newline()
n.rule(
name="mwld",
command=f'{cfg.wine_path} "{cfg.ld_path}" {' '.join(cfg.ldflags)} $extra_ld_flags @$objects_file $lcf_file -o $out'
)
n.newline()
n.rule(
name="rom_config",
command=f"{cfg.dsd_path} {cfg.dsd_flags} rom config --elf $in --config $config_path"
)
n.newline()
n.rule(
name="rom_build",
command=f"{cfg.dsd_path} {cfg.dsd_flags} rom build --config $in --rom $out $arm7_bios_flag"
)
n.newline()
cflags = " ".join(cfg.cflags_base)
dsd_objdiff_args = " ".join([
"--scratch", # Metadata for creating decomp.me scratches
f"--compiler {cfg.get_decompme_compiler()}", # decomp.me compiler name
f'--c-flags "{cflags} -lang=c++"', # decomp.me compiler flags
"--custom-make ninja", # Command for rebuilding files
])
n.rule(
name="objdiff",
command=f"touch {cfg.dsd_path} && {cfg.dsd_path} {cfg.dsd_flags} objdiff --config-path $config_path --output-path $out_path {dsd_objdiff_args}"
)
n.newline()
n.rule(
name="objdiff_report",
command=f"{cfg.objdiff_path} -C $dir report generate -o $filename"
)
n.newline()
n.rule(
name="m2ctx",
command=f"{cfg.python_path} tools/m2ctx.py -g $version -f $out $in"
)
n.newline()
n.rule(
name="check_modules",
command=f"{cfg.dsd_path} {cfg.dsd_flags} check modules --config-path $config_path --fail"
)
n.newline()
n.rule(
name="check_symbols",
command=f"{cfg.dsd_path} {cfg.dsd_flags} check symbols --config-path $config_path --elf-path $elf_path --fail --max-lines 20"
)
n.newline()
n.rule(
name="apply",
command=f"{cfg.dsd_path} {cfg.dsd_flags} apply --config-path $config_path --elf-path $elf_path"
)
n.newline()
n.rule(
name="sha1",
command=f"{cfg.python_path} tools/sha1.py $in -c $sha1_file"
)
n.newline()
configure_cmdline = subprocess.list2cmdline(sys.argv[1:])
n.rule(
name="configure",
command=f"{cfg.python_path} tools/configure.py {configure_cmdline}",
generator=True
)
n.newline()
n.rule(
name="format",
command=f"{cfg.dsd_path} format --config-path $config_path",
)
n.newline()
n.rule(
name="post_objdiff",
command=f"{cfg.python_path} tools/objdiff_config.py"
)
n.newline()
add_download_tool_builds(cfg, n, args)
add_configure_build(cfg, n)
if cfg.check_can_run_dsd():
defaults = []
cmds_map: dict[str, list] = {
"delink": list(),
"dis": list(),
"sha1": list(),
"check": list(),
"apply": list(),
"format": list(),
}
for version in cfg.game_versions:
add_extract_build(cfg, version, n, args)
add_delink_and_lcf_builds(cfg, version, n)
cmds_map["delink"].append(f"delink_{version}")
add_disassemble_builds(cfg, version, n)
cmds_map["dis"].append(f"dis_{version}")
add_mwcc_builds(cfg, version, objects, n, mwcc_implicit)
add_mwld_and_rom_builds(cfg, version, n)
cmds_map["sha1"].append(f"sha1_{version}")
add_check_builds(cfg, version, n)
cmds_map["check"].append(f"check_{version}")
add_objdiff_builds(cfg, version, n)
add_apply_build(cfg, version, n)
cmds_map["apply"].append(f"apply_{version}")
n.build(
rule="format",
outputs=f"format_{version}",
variables={
"config_path": str(cfg.arm9_config_yaml(version)),
}
)
n.newline()
cmds_map["format"].append(f"format_{version}")
defaults.extend([f"check_{version}", f"sha1_{version}"])
n.build(
rule="post_objdiff",
implicit=[f"objdiff_{version}.json" for version in cfg.game_versions],
outputs="objdiff"
)
n.newline()
for rule, cmds in cmds_map.items():
cmds.sort()
n.build(
rule="phony",
outputs=rule,
implicit=cmds,
)
n.default(["format", "objdiff", *defaults])
else:
n.default(["download_tools"])