diff --git a/configure.py b/configure.py index 60942c41..2e4b73a6 100755 --- a/configure.py +++ b/configure.py @@ -15,8 +15,10 @@ import argparse import sys from pathlib import Path +from typing import List, Sequence, Union from tools.project import ( + BuildConfigUnit, Object, ProgressCategory, ProjectConfig, @@ -767,6 +769,7 @@ config.libs = [ Object(NonMatching, "MetroidPrime/ScriptObjects/CEnergyBall.cpp"), Object(MatchingFor("GM8E01_00", "GM8E01_01"), "MetroidPrime/Enemies/CMetroidPrimeProjectile.cpp"), Object(MatchingFor("GM8E01_00", "GM8E01_01"), "MetroidPrime/Enemies/SPositionHistory.cpp"), + Object(Equivalent, "dummy.c"), ], ), RetroLib( @@ -1414,6 +1417,18 @@ for lib in config.libs: obj.options["extra_clang_flags"].append("-Wno-return-type") +def link_order_callback( + module_id: int, units: List[str] +) -> Sequence[Union[str, BuildConfigUnit]]: + if module_id == 0: # DOL + return units + [ + {"object": "dummy.o", "name": "dummy.c", "autogenerated": False} + ] + return units + + +config.link_order_callback = link_order_callback + # Optional extra categories for progress tracking config.progress_categories = [ ProgressCategory("game", "Game"), diff --git a/src/dummy.c b/src/dummy.c new file mode 100644 index 00000000..5c5b5a34 --- /dev/null +++ b/src/dummy.c @@ -0,0 +1 @@ +void custom_tu_smiley() {} diff --git a/tools/project.py b/tools/project.py index f794b8c6..d50adab7 100644 --- a/tools/project.py +++ b/tools/project.py @@ -17,7 +17,21 @@ import os import platform import sys from pathlib import Path -from typing import IO, Any, Dict, Iterable, List, Optional, Set, Tuple, Union, cast +from typing import ( + Any, + Callable, + Sequence, + cast, + Dict, + IO, + Iterable, + List, + Optional, + Set, + Tuple, + TypedDict, + Union, +) from . import ninja_syntax from .ninja_syntax import serialize_path @@ -179,6 +193,9 @@ class ProjectConfig: self.scratch_preset_id: Optional[int] = ( None # Default decomp.me preset ID for scratches ) + self.link_order_callback: Optional[ + Callable[[int, List[str]], Sequence[Union[str, BuildConfigUnit]]] + ] = None # Callback to add/remove/reorder units within a module # Progress output, progress.json and report.json config self.progress = True # Enable report.json generation and CLI progress output @@ -294,10 +311,38 @@ def make_flags_str(flags: Optional[List[str]]) -> str: return " ".join(flags) +# Unit configuration +class BuildConfigUnit(TypedDict): + object: Optional[str] + name: str + autogenerated: bool + + +# Module configuration +class BuildConfigModule(TypedDict): + name: str + module_id: int + ldscript: str + entry: str + units: List[BuildConfigUnit] + + +# Module link configuration +class BuildConfigLink(TypedDict): + modules: List[str] + + +# Build configuration generated by decomp-toolkit +class BuildConfig(BuildConfigModule): + version: str + modules: List[BuildConfigModule] + links: List[BuildConfigLink] + + # Load decomp-toolkit generated config.json def load_build_config( config: ProjectConfig, build_config_path: Path -) -> Optional[Dict[str, Any]]: +) -> Optional[BuildConfig]: if not build_config_path.is_file(): return None @@ -305,7 +350,7 @@ def load_build_config( return tuple(map(int, (v.split(".")))) f = open(build_config_path, "r", encoding="utf-8") - build_config: Dict[str, Any] = json.load(f) + build_config: BuildConfig = json.load(f) config_version = build_config.get("version") if config_version is None: print("Invalid config.json, regenerating...") @@ -321,6 +366,27 @@ def load_build_config( return None f.close() + + # Apply link order callback + if config.link_order_callback: + modules: List[BuildConfigModule] = [build_config, *build_config["modules"]] + for module in modules: + unit_names = list(map(lambda u: u["name"], module["units"])) + new_units = config.link_order_callback(module["module_id"], unit_names) + units: List[BuildConfigUnit] = [] + for new_unit in new_units: + if isinstance(new_unit, str): + units.append( + # Find existing unit or create a new one + next( + (u for u in module["units"] if u["name"] == new_unit), + {"object": None, "name": new_unit, "autogenerated": False}, + ) + ) + else: + units.append(new_unit) + module["units"] = units + return build_config @@ -338,7 +404,7 @@ def generate_build(config: ProjectConfig) -> None: def generate_build_ninja( config: ProjectConfig, objects: Dict[str, Object], - build_config: Optional[Dict[str, Any]], + build_config: Optional[BuildConfig], ) -> None: out = io.StringIO() n = ninja_syntax.Writer(out) @@ -699,9 +765,9 @@ def generate_build_ninja( return path.parent / (path.name + ".MAP") class LinkStep: - def __init__(self, config: Dict[str, Any]) -> None: - self.name: str = config["name"] - self.module_id: int = config["module_id"] + def __init__(self, config: BuildConfigModule) -> None: + self.name = config["name"] + self.module_id = config["module_id"] self.ldscript: Optional[Path] = Path(config["ldscript"]) self.entry = config["entry"] self.inputs: List[str] = [] @@ -907,13 +973,14 @@ def generate_build_ninja( return obj_path - def add_unit(build_obj, link_step: LinkStep): + def add_unit(build_obj: BuildConfigUnit, link_step: LinkStep): obj_path, obj_name = build_obj["object"], build_obj["name"] obj = objects.get(obj_name) if obj is None: if config.warn_missing_config and not build_obj["autogenerated"]: print(f"Missing configuration for {obj_name}") - link_step.add(obj_path) + if obj_path is not None: + link_step.add(Path(obj_path)) return link_built_obj = obj.completed @@ -942,12 +1009,7 @@ def generate_build_ninja( link_step.add(built_obj_path) elif obj_path is not None: # Use the original (extracted) object - link_step.add(obj_path) - else: - lib_name = obj.options["lib"] - sys.exit( - f"Missing object for {obj_name}: {obj.src_path} {lib_name} {obj}" - ) + link_step.add(Path(obj_path)) # Add DOL link step link_step = LinkStep(build_config) @@ -1268,7 +1330,7 @@ def generate_build_ninja( def generate_objdiff_config( config: ProjectConfig, objects: Dict[str, Object], - build_config: Optional[Dict[str, Any]], + build_config: Optional[BuildConfig], ) -> None: if build_config is None: return @@ -1333,7 +1395,7 @@ def generate_objdiff_config( } def add_unit( - build_obj: Dict[str, Any], module_name: str, progress_categories: List[str] + build_obj: BuildConfigUnit, module_name: str, progress_categories: List[str] ) -> None: obj_path, obj_name = build_obj["object"], build_obj["name"] base_object = Path(obj_name).with_suffix("") @@ -1481,7 +1543,7 @@ def generate_objdiff_config( def generate_compile_commands( config: ProjectConfig, objects: Dict[str, Object], - build_config: Optional[Dict[str, Any]], + build_config: Optional[BuildConfig], ) -> None: if build_config is None or not config.generate_compile_commands: return @@ -1570,7 +1632,7 @@ def generate_compile_commands( clangd_config = [] - def add_unit(build_obj: Dict[str, Any]) -> None: + def add_unit(build_obj: BuildConfigUnit) -> None: obj = objects.get(build_obj["name"]) if obj is None: return