Merge branch 'link-order'

This commit is contained in:
Phillip Stephens 2024-12-31 06:24:28 -08:00
commit 76ea68dbc1
3 changed files with 97 additions and 19 deletions

View File

@ -15,8 +15,10 @@
import argparse import argparse
import sys import sys
from pathlib import Path from pathlib import Path
from typing import List, Sequence, Union
from tools.project import ( from tools.project import (
BuildConfigUnit,
Object, Object,
ProgressCategory, ProgressCategory,
ProjectConfig, ProjectConfig,
@ -767,6 +769,7 @@ config.libs = [
Object(NonMatching, "MetroidPrime/ScriptObjects/CEnergyBall.cpp"), Object(NonMatching, "MetroidPrime/ScriptObjects/CEnergyBall.cpp"),
Object(MatchingFor("GM8E01_00", "GM8E01_01"), "MetroidPrime/Enemies/CMetroidPrimeProjectile.cpp"), Object(MatchingFor("GM8E01_00", "GM8E01_01"), "MetroidPrime/Enemies/CMetroidPrimeProjectile.cpp"),
Object(MatchingFor("GM8E01_00", "GM8E01_01"), "MetroidPrime/Enemies/SPositionHistory.cpp"), Object(MatchingFor("GM8E01_00", "GM8E01_01"), "MetroidPrime/Enemies/SPositionHistory.cpp"),
Object(Equivalent, "dummy.c"),
], ],
), ),
RetroLib( RetroLib(
@ -1414,6 +1417,18 @@ for lib in config.libs:
obj.options["extra_clang_flags"].append("-Wno-return-type") 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 # Optional extra categories for progress tracking
config.progress_categories = [ config.progress_categories = [
ProgressCategory("game", "Game"), ProgressCategory("game", "Game"),

1
src/dummy.c Normal file
View File

@ -0,0 +1 @@
void custom_tu_smiley() {}

View File

@ -17,7 +17,21 @@ import os
import platform import platform
import sys import sys
from pathlib import Path 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 . import ninja_syntax
from .ninja_syntax import serialize_path from .ninja_syntax import serialize_path
@ -179,6 +193,9 @@ class ProjectConfig:
self.scratch_preset_id: Optional[int] = ( self.scratch_preset_id: Optional[int] = (
None # Default decomp.me preset ID for scratches 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 # Progress output, progress.json and report.json config
self.progress = True # Enable report.json generation and CLI progress output 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) 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 # Load decomp-toolkit generated config.json
def load_build_config( def load_build_config(
config: ProjectConfig, build_config_path: Path config: ProjectConfig, build_config_path: Path
) -> Optional[Dict[str, Any]]: ) -> Optional[BuildConfig]:
if not build_config_path.is_file(): if not build_config_path.is_file():
return None return None
@ -305,7 +350,7 @@ def load_build_config(
return tuple(map(int, (v.split(".")))) return tuple(map(int, (v.split("."))))
f = open(build_config_path, "r", encoding="utf-8") 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") config_version = build_config.get("version")
if config_version is None: if config_version is None:
print("Invalid config.json, regenerating...") print("Invalid config.json, regenerating...")
@ -321,6 +366,27 @@ def load_build_config(
return None return None
f.close() 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 return build_config
@ -338,7 +404,7 @@ def generate_build(config: ProjectConfig) -> None:
def generate_build_ninja( def generate_build_ninja(
config: ProjectConfig, config: ProjectConfig,
objects: Dict[str, Object], objects: Dict[str, Object],
build_config: Optional[Dict[str, Any]], build_config: Optional[BuildConfig],
) -> None: ) -> None:
out = io.StringIO() out = io.StringIO()
n = ninja_syntax.Writer(out) n = ninja_syntax.Writer(out)
@ -699,9 +765,9 @@ def generate_build_ninja(
return path.parent / (path.name + ".MAP") return path.parent / (path.name + ".MAP")
class LinkStep: class LinkStep:
def __init__(self, config: Dict[str, Any]) -> None: def __init__(self, config: BuildConfigModule) -> None:
self.name: str = config["name"] self.name = config["name"]
self.module_id: int = config["module_id"] self.module_id = config["module_id"]
self.ldscript: Optional[Path] = Path(config["ldscript"]) self.ldscript: Optional[Path] = Path(config["ldscript"])
self.entry = config["entry"] self.entry = config["entry"]
self.inputs: List[str] = [] self.inputs: List[str] = []
@ -907,13 +973,14 @@ def generate_build_ninja(
return obj_path 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_path, obj_name = build_obj["object"], build_obj["name"]
obj = objects.get(obj_name) obj = objects.get(obj_name)
if obj is None: if obj is None:
if config.warn_missing_config and not build_obj["autogenerated"]: if config.warn_missing_config and not build_obj["autogenerated"]:
print(f"Missing configuration for {obj_name}") 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 return
link_built_obj = obj.completed link_built_obj = obj.completed
@ -942,12 +1009,7 @@ def generate_build_ninja(
link_step.add(built_obj_path) link_step.add(built_obj_path)
elif obj_path is not None: elif obj_path is not None:
# Use the original (extracted) object # Use the original (extracted) object
link_step.add(obj_path) link_step.add(Path(obj_path))
else:
lib_name = obj.options["lib"]
sys.exit(
f"Missing object for {obj_name}: {obj.src_path} {lib_name} {obj}"
)
# Add DOL link step # Add DOL link step
link_step = LinkStep(build_config) link_step = LinkStep(build_config)
@ -1268,7 +1330,7 @@ def generate_build_ninja(
def generate_objdiff_config( def generate_objdiff_config(
config: ProjectConfig, config: ProjectConfig,
objects: Dict[str, Object], objects: Dict[str, Object],
build_config: Optional[Dict[str, Any]], build_config: Optional[BuildConfig],
) -> None: ) -> None:
if build_config is None: if build_config is None:
return return
@ -1333,7 +1395,7 @@ def generate_objdiff_config(
} }
def add_unit( 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: ) -> None:
obj_path, obj_name = build_obj["object"], build_obj["name"] obj_path, obj_name = build_obj["object"], build_obj["name"]
base_object = Path(obj_name).with_suffix("") base_object = Path(obj_name).with_suffix("")
@ -1481,7 +1543,7 @@ def generate_objdiff_config(
def generate_compile_commands( def generate_compile_commands(
config: ProjectConfig, config: ProjectConfig,
objects: Dict[str, Object], objects: Dict[str, Object],
build_config: Optional[Dict[str, Any]], build_config: Optional[BuildConfig],
) -> None: ) -> None:
if build_config is None or not config.generate_compile_commands: if build_config is None or not config.generate_compile_commands:
return return
@ -1570,7 +1632,7 @@ def generate_compile_commands(
clangd_config = [] clangd_config = []
def add_unit(build_obj: Dict[str, Any]) -> None: def add_unit(build_obj: BuildConfigUnit) -> None:
obj = objects.get(build_obj["name"]) obj = objects.get(build_obj["name"])
if obj is None: if obj is None:
return return