summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorrobojumper <robojumper@gmail.com>2024-10-02 02:11:29 +0200
committerGitHub <noreply@github.com>2024-10-01 20:11:29 -0400
commit812e86b39cfeb934b99c3dbbd278c874fc3e1fba (patch)
tree17ecaf0c1fb359fbb011e099637a14a335921d16 /tools
parenteae2d7a2bf7d9b829955236eb17ab934156cf374 (diff)
Update from dtk-template (#48)
* Update from dtk-template * Update dtk-template
Diffstat (limited to 'tools')
-rw-r--r--tools/project.py263
1 files changed, 124 insertions, 139 deletions
diff --git a/tools/project.py b/tools/project.py
index 2e7ad6ba..727d45c5 100644
--- a/tools/project.py
+++ b/tools/project.py
@@ -17,7 +17,7 @@ import os
import platform
import sys
from pathlib import Path
-from typing import Any, Dict, List, Optional, Set, Tuple, Union, cast
+from typing import IO, Any, Dict, List, Optional, Set, Tuple, Union, cast
from . import ninja_syntax
from .ninja_syntax import serialize_path
@@ -157,6 +157,7 @@ class ProjectConfig:
)
# Progress output, progress.json and report.json config
+ self.progress = True # Enable progress output
self.progress_all: bool = True # Include combined "all" category
self.progress_modules: bool = True # Include combined "modules" category
self.progress_each_module: bool = (
@@ -213,11 +214,13 @@ CHAIN = "cmd /c " if is_windows() else ""
EXE = ".exe" if is_windows() else ""
-def make_flags_str(cflags: Union[str, List[str]]) -> str:
- if isinstance(cflags, list):
- return " ".join(cflags)
+def make_flags_str(flags: Optional[Union[str, List[str]]]) -> str:
+ if flags is None:
+ return ""
+ elif isinstance(flags, list):
+ return " ".join(flags)
else:
- return cflags
+ return flags
# Load decomp-toolkit generated config.json
@@ -234,14 +237,14 @@ def load_build_config(
build_config: Dict[str, Any] = json.load(f)
config_version = build_config.get("version")
if config_version is None:
- # Invalid config.json
+ print("Invalid config.json, regenerating...")
f.close()
os.remove(build_config_path)
return None
dtk_version = str(config.dtk_tag)[1:] # Strip v
if versiontuple(config_version) < versiontuple(dtk_version):
- # Outdated config.json
+ print("Outdated config.json, regenerating...")
f.close()
os.remove(build_config_path)
return None
@@ -282,7 +285,7 @@ def generate_build_ninja(
# Variables
###
n.comment("Variables")
- n.variable("ldflags", " ".join(config.ldflags or []))
+ n.variable("ldflags", make_flags_str(config.ldflags))
if config.linker_version is None:
sys.exit("ProjectConfig.linker_version missing")
n.variable("mw_version", Path(config.linker_version))
@@ -1034,7 +1037,12 @@ def generate_build_ninja(
n.build(
outputs=progress_path,
rule="progress",
- implicit=[ok_path, configure_script, python_lib, config.config_path],
+ implicit=[
+ ok_path,
+ configure_script,
+ python_lib,
+ report_path,
+ ],
)
###
@@ -1147,8 +1155,10 @@ def generate_build_ninja(
if build_config:
if config.non_matching:
n.default(link_outputs)
- else:
+ elif config.progress:
n.default(progress_path)
+ else:
+ n.default(ok_path)
else:
n.default(build_config_path)
@@ -1188,7 +1198,6 @@ def generate_objdiff_config(
}
# decomp.me compiler name mapping
- # Commented out versions have not been added to decomp.me yet
COMPILER_MAP = {
"GC/1.0": "mwcc_233_144",
"GC/1.1": "mwcc_233_159",
@@ -1242,6 +1251,7 @@ def generate_objdiff_config(
src_exists = obj.src_path is not None and obj.src_path.exists()
if src_exists:
unit_config["base_path"] = obj.src_obj_path
+ unit_config["metadata"]["source_path"] = obj.src_path
cflags = obj.options["cflags"]
reverse_fn_order = False
@@ -1299,7 +1309,6 @@ def generate_objdiff_config(
{
"complete": obj.completed,
"reverse_fn_order": reverse_fn_order,
- "source_path": obj.src_path,
"progress_categories": progress_categories,
}
)
@@ -1355,144 +1364,120 @@ def generate_objdiff_config(
# Calculate, print and write progress to progress.json
def calculate_progress(config: ProjectConfig) -> None:
config.validate()
- objects = config.objects()
out_path = config.out_path()
- build_config = load_build_config(config, out_path / "config.json")
- if build_config is None:
- return
-
- class ProgressUnit:
- def __init__(self, name: str) -> None:
- self.name: str = name
- self.code_total: int = 0
- self.code_fancy_frac: int = config.progress_code_fancy_frac
- self.code_fancy_item: str = config.progress_code_fancy_item
- self.code_progress: int = 0
- self.data_total: int = 0
- self.data_fancy_frac: int = config.progress_data_fancy_frac
- self.data_fancy_item: str = config.progress_data_fancy_item
- self.data_progress: int = 0
- self.objects_progress: int = 0
- self.objects_total: int = 0
- self.objects: Set[Object] = set()
-
- def add(self, build_obj: Dict[str, Any]) -> None:
- self.code_total += build_obj["code_size"]
- self.data_total += build_obj["data_size"]
-
- # Avoid counting the same object in different modules twice
- include_object = build_obj["name"] not in self.objects
- if include_object:
- self.objects.add(build_obj["name"])
- self.objects_total += 1
-
- if build_obj["autogenerated"]:
- # Skip autogenerated objects
- return
-
- obj = objects.get(build_obj["name"])
- if obj is None or not obj.completed:
- return
-
- self.code_progress += build_obj["code_size"]
- self.data_progress += build_obj["data_size"]
- if include_object:
- self.objects_progress += 1
-
- def code_frac(self) -> float:
- if self.code_total == 0:
- return 1.0
- return self.code_progress / self.code_total
-
- def data_frac(self) -> float:
- if self.data_total == 0:
- return 1.0
- return self.data_progress / self.data_total
-
- progress_units: Dict[str, ProgressUnit] = {}
- if config.progress_all:
- progress_units["all"] = ProgressUnit("All")
- progress_units["dol"] = ProgressUnit("DOL")
- if len(build_config["modules"]) > 0:
- if config.progress_modules:
- progress_units["modules"] = ProgressUnit("Modules")
- if len(config.progress_categories) > 0:
- for category in config.progress_categories:
- progress_units[category.id] = ProgressUnit(category.name)
- if config.progress_each_module:
- for module in build_config["modules"]:
- progress_units[module["name"]] = ProgressUnit(module["name"])
-
- def add_unit(id: str, unit: Dict[str, Any]) -> None:
- progress = progress_units.get(id)
- if progress is not None:
- progress.add(unit)
-
- # Add DOL units
- for unit in build_config["units"]:
- add_unit("all", unit)
- add_unit("dol", unit)
- obj = objects.get(unit["name"])
- if obj is not None:
- category_opt = obj.options["progress_category"]
- if isinstance(category_opt, list):
- for id in category_opt:
- add_unit(id, unit)
- elif category_opt is not None:
- add_unit(category_opt, unit)
-
- # Add REL units
- for module in build_config["modules"]:
- for unit in module["units"]:
- add_unit("all", unit)
- add_unit("modules", unit)
- add_unit(module["name"], unit)
- obj = objects.get(unit["name"])
- if obj is not None:
- category_opt = obj.options["progress_category"]
- if isinstance(category_opt, list):
- for id in category_opt:
- add_unit(id, unit)
- elif category_opt is not None:
- add_unit(category_opt, unit)
+ report_path = out_path / "report.json"
+ if not report_path.is_file():
+ sys.exit(f"Report file {report_path} does not exist")
+
+ report_data: Dict[str, Any] = {}
+ with open(report_path, "r", encoding="utf-8") as f:
+ report_data = json.load(f)
+
+ # Convert string numbers (u64) to int
+ def convert_numbers(data: Dict[str, Any]) -> None:
+ for key, value in data.items():
+ if isinstance(value, str) and value.isdigit():
+ data[key] = int(value)
+
+ convert_numbers(report_data["measures"])
+ for category in report_data["categories"]:
+ convert_numbers(category["measures"])
+
+ # Output to GitHub Actions job summary, if available
+ summary_path = os.getenv("GITHUB_STEP_SUMMARY")
+ summary_file: Optional[IO[str]] = None
+ if summary_path:
+ summary_file = open(summary_path, "a", encoding="utf-8")
+ summary_file.write("```\n")
+
+ def progress_print(s: str) -> None:
+ print(s)
+ if summary_file:
+ summary_file.write(s + "\n")
# Print human-readable progress
- print("Progress:")
+ progress_print("Progress:")
+
+ def print_category(name: str, measures: Dict[str, Any]) -> None:
+ total_code = measures.get("total_code", 0)
+ matched_code = measures.get("matched_code", 0)
+ matched_code_percent = measures.get("matched_code_percent", 0)
+ total_data = measures.get("total_data", 0)
+ matched_data = measures.get("matched_data", 0)
+ matched_data_percent = measures.get("matched_data_percent", 0)
+ total_functions = measures.get("total_functions", 0)
+ matched_functions = measures.get("matched_functions", 0)
+ complete_code_percent = measures.get("complete_code_percent", 0)
+ total_units = measures.get("total_units", 0)
+ complete_units = measures.get("complete_units", 0)
+
+ progress_print(
+ f" {name}: {matched_code_percent:.2f}% matched, {complete_code_percent:.2f}% linked ({complete_units} / {total_units} files)"
+ )
+ progress_print(
+ f" Code: {matched_code} / {total_code} bytes ({matched_functions} / {total_functions} functions)"
+ )
+ progress_print(
+ f" Data: {matched_data} / {total_data} bytes ({matched_data_percent:.2f}%)"
+ )
- for unit in progress_units.values():
- if unit.objects_total == 0:
- continue
+ print_category("All", report_data["measures"])
+ for category in report_data["categories"]:
+ print_category(category["name"], category["measures"])
- code_frac = unit.code_frac()
- data_frac = unit.data_frac()
- print(
- f" {unit.name}: {code_frac:.2%} code, {data_frac:.2%} data ({unit.objects_progress} / {unit.objects_total} files)"
- )
- print(f" Code: {unit.code_progress} / {unit.code_total} bytes")
- print(f" Data: {unit.data_progress} / {unit.data_total} bytes")
- if config.progress_use_fancy:
- print(
- "\nYou have {} out of {} {} and {} out of {} {}.".format(
- math.floor(code_frac * unit.code_fancy_frac),
- unit.code_fancy_frac,
- unit.code_fancy_item,
- math.floor(data_frac * unit.data_fancy_frac),
- unit.data_fancy_frac,
- unit.data_fancy_item,
- )
+ if config.progress_use_fancy:
+ measures = report_data["measures"]
+ total_code = measures.get("total_code", 0)
+ total_data = measures.get("total_data", 0)
+ if total_code == 0 or total_data == 0:
+ return
+ code_frac = measures.get("complete_code", 0) / total_code
+ data_frac = measures.get("complete_data", 0) / total_data
+
+ progress_print(
+ "\nYou have {} out of {} {} and {} out of {} {}.".format(
+ math.floor(code_frac * config.progress_code_fancy_frac),
+ config.progress_code_fancy_frac,
+ config.progress_code_fancy_item,
+ math.floor(data_frac * config.progress_data_fancy_frac),
+ config.progress_data_fancy_frac,
+ config.progress_data_fancy_item,
)
+ )
+
+ # Finalize GitHub Actions job summary
+ if summary_file:
+ summary_file.write("```\n")
+ summary_file.close()
# Generate and write progress.json
progress_json: Dict[str, Any] = {}
- for id, unit in progress_units.items():
- if unit.objects_total == 0:
- continue
+
+ def add_category(id: str, measures: Dict[str, Any]) -> None:
progress_json[id] = {
- "code": unit.code_progress,
- "code/total": unit.code_total,
- "data": unit.data_progress,
- "data/total": unit.data_total,
+ "code": measures.get("complete_code", 0),
+ "code/total": measures.get("total_code", 0),
+ "data": measures.get("complete_data", 0),
+ "data/total": measures.get("total_data", 0),
+ "matched_code": measures.get("matched_code", 0),
+ "matched_code/total": measures.get("total_code", 0),
+ "matched_data": measures.get("matched_data", 0),
+ "matched_data/total": measures.get("total_data", 0),
+ "matched_functions": measures.get("matched_functions", 0),
+ "matched_functions/total": measures.get("total_functions", 0),
+ "fuzzy_match": int(measures.get("fuzzy_match_percent", 0) * 100),
+ "fuzzy_match/total": 10000,
+ "units": measures.get("complete_units", 0),
+ "units/total": measures.get("total_units", 0),
}
+ if config.progress_all:
+ add_category("all", report_data["measures"])
+ else:
+ # Support for old behavior where "dol" was the main category
+ add_category("dol", report_data["measures"])
+ for category in report_data["categories"]:
+ add_category(category["id"], category["measures"])
+
with open(out_path / "progress.json", "w", encoding="utf-8") as w:
json.dump(progress_json, w, indent=4)