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 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"),

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 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