Skip to content

Commit

Permalink
Add link_order_callback feature
Browse files Browse the repository at this point in the history
  • Loading branch information
encounter committed Dec 31, 2024
1 parent d96afde commit 8e17caa
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 19 deletions.
10 changes: 10 additions & 0 deletions configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import argparse
import sys
from pathlib import Path
from typing import List

from tools.project import (
Object,
Expand Down Expand Up @@ -767,6 +768,7 @@ def MatchingFor(*versions):
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(
Expand Down Expand Up @@ -1407,6 +1409,14 @@ def MatchingFor(*versions):
obj.options["extra_clang_flags"].append("-Wno-return-type")


def link_order_callback(module_id: int, units: List[str]) -> List[str]:
if module_id == 0: # DOL
return units + ["dummy.c"]
return units


config.link_order_callback = link_order_callback

# Optional extra categories for progress tracking
config.progress_categories = [
ProgressCategory("game", "Game"),
Expand Down
1 change: 1 addition & 0 deletions src/dummy.c
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
void custom_tu_smiley() {}
96 changes: 77 additions & 19 deletions tools/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,20 @@
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,
cast,
Dict,
IO,
Iterable,
List,
Optional,
Set,
Tuple,
TypedDict,
Union,
)

from . import ninja_syntax
from .ninja_syntax import serialize_path
Expand Down Expand Up @@ -179,6 +192,9 @@ def __init__(self) -> None:
self.scratch_preset_id: Optional[int] = (
None # Default decomp.me preset ID for scratches
)
self.link_order_callback: Optional[Callable[[int, List[str]], List[str]]] = (
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
Expand Down Expand Up @@ -294,18 +310,46 @@ 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

def versiontuple(v: str) -> Tuple[int, ...]:
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...")
Expand All @@ -321,6 +365,24 @@ def versiontuple(v: str) -> Tuple[int, ...]:
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"]))
unit_names = config.link_order_callback(module["module_id"], unit_names)
units: List[BuildConfigUnit] = []
for unit_name in unit_names:
# Find existing unit or create a new one
unit = next(
(u for u in module["units"] if u["name"] == unit_name), None
)
if not unit:
unit = {"object": None, "name": unit_name, "autogenerated": False}
units.append(unit)
module["units"] = units

return build_config


Expand All @@ -338,7 +400,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)
Expand Down Expand Up @@ -699,9 +761,9 @@ def map_path(path: Path) -> Path:
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] = []
Expand Down Expand Up @@ -907,13 +969,14 @@ def asm_build(

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
Expand Down Expand Up @@ -942,12 +1005,7 @@ def add_unit(build_obj, link_step: LinkStep):
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)
Expand Down Expand Up @@ -1268,7 +1326,7 @@ def add_unit(build_obj, link_step: LinkStep):
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
Expand Down Expand Up @@ -1333,7 +1391,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("")
Expand Down Expand Up @@ -1481,7 +1539,7 @@ def unix_path(input: Any) -> str:
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
Expand Down Expand Up @@ -1570,7 +1628,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
Expand Down

0 comments on commit 8e17caa

Please sign in to comment.