mirror of
https://github.com/SMGCommunity/Petari.git
synced 2025-02-17 04:28:15 +00:00
Update dtk-template, fix building all source files
This commit is contained in:
parent
1e43e70ede
commit
f092002f26
38
.gitignore
vendored
38
.gitignore
vendored
@ -1,13 +1,35 @@
|
||||
# IDE folders
|
||||
.idea/
|
||||
.vs/
|
||||
|
||||
# Caches
|
||||
__pycache__
|
||||
.idea
|
||||
.vscode
|
||||
.ninja_*
|
||||
.mypy_cache
|
||||
*.exe
|
||||
build
|
||||
build.ninja
|
||||
objdiff.json
|
||||
.cache/
|
||||
|
||||
# Original files
|
||||
orig/*/*
|
||||
!orig/*/.gitkeep
|
||||
*.dol
|
||||
*.rel
|
||||
*.elf
|
||||
*.o
|
||||
*.map
|
||||
*.MAP
|
||||
|
||||
# Build files
|
||||
build/
|
||||
.ninja_*
|
||||
build.ninja
|
||||
|
||||
# decompctx output
|
||||
ctx.*
|
||||
*.ctx
|
||||
|
||||
# Generated configs
|
||||
objdiff.json
|
||||
compile_commands.json
|
||||
|
||||
# Miscellaneous
|
||||
/*.txt
|
||||
ctx.c
|
||||
*.exe
|
||||
|
11
configure.py
11
configure.py
@ -113,6 +113,12 @@ parser.add_argument(
|
||||
action="store_true",
|
||||
help="builds equivalent (but non-matching) or modded objects",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-progress",
|
||||
dest="progress",
|
||||
action="store_false",
|
||||
help="disable progress calculation",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
config = ProjectConfig()
|
||||
@ -129,6 +135,7 @@ config.generate_map = args.map
|
||||
config.non_matching = args.non_matching
|
||||
config.shift_jis = False
|
||||
config.sjiswrap_path = args.sjiswrap
|
||||
config.progress = args.progress
|
||||
if not is_windows():
|
||||
config.wrapper = args.wrapper
|
||||
# Don't build asm unless we're --non-matching
|
||||
@ -138,8 +145,8 @@ if not config.non_matching:
|
||||
# Tool versions
|
||||
config.binutils_tag = "2.42-1"
|
||||
config.compilers_tag = "20240706"
|
||||
config.dtk_tag = "v0.9.5"
|
||||
config.objdiff_tag = "v2.0.0"
|
||||
config.dtk_tag = "v1.1.2"
|
||||
config.objdiff_tag = "v2.3.2"
|
||||
config.sjiswrap_tag = "v1.1.1"
|
||||
config.wibo_tag = "0.6.11"
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "Game/AreaObj/AreaObj.hpp"
|
||||
#include "Game/LiveActor/LiveActor.hpp"
|
||||
|
||||
class MercatorTransformCube : public AreaObj {
|
||||
public:
|
||||
@ -23,4 +24,4 @@ namespace MR {
|
||||
void convertMercatorPlaneToSphereTransAndRotate(TVec3f *, TPos3f *, const TVec3f &, bool);
|
||||
|
||||
MapPartsRailMover* createMapPartsRailMoverForMercator(LiveActor *, const JMapInfoIter &, bool);
|
||||
};
|
||||
};
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include "Game/AreaObj/AreaObj.hpp"
|
||||
#include "Game/AreaObj/AreaForm.hpp"
|
||||
#include "Game/AreaObj/AreaObjFollower.hpp"
|
||||
#include "Game/Map/SleepControllerHolder.hpp"
|
||||
#include "Game/Util.hpp"
|
||||
|
@ -2,6 +2,7 @@
|
||||
#include "Game/Util/CameraUtil.hpp"
|
||||
#include "Game/Util/JMapUtil.hpp"
|
||||
#include "Game/Util/DirectDraw.hpp"
|
||||
#include "Game/Util/ObjUtil.hpp"
|
||||
|
||||
BigBubbleGoalArea::BigBubbleGoalArea(int type, const char *pName) : AreaObj(type, pName) {
|
||||
mTranslation.x = 0.0f;
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include "Game/AreaObj/CameraRepulsiveArea.hpp"
|
||||
#include "Game/Util/AreaObjUtil.hpp"
|
||||
|
||||
CameraRepulsiveArea::~CameraRepulsiveArea() {
|
||||
|
||||
@ -53,4 +54,4 @@ TVec3f CameraRepulsiveCylinder::getRepulsion(const TVec3f &rRep) {
|
||||
|
||||
const char* CameraRepulsiveArea::getManagerName() const {
|
||||
return "CameraRepulsiveArea";
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "Game/AreaObj/ChangeBgmCube.hpp"
|
||||
#include "Game/AudioLib/AudWrap.hpp"
|
||||
#include "Game/GameAudio/AudStageBgmTable.hpp"
|
||||
#include "Game/Util/ObjUtil.hpp"
|
||||
|
||||
ChangeBgmCube::ChangeBgmCube(int a1, const char *pName) : AreaObj(a1, pName) {
|
||||
_3C = 0;
|
||||
|
@ -1,4 +1,4 @@
|
||||
#include "Game/AreaObj/CollisionArea.hpp"
|
||||
#include "Game/AreaObj/CollisionArea.hpp"
|
||||
#include "Game/MapObj/DynamicCollisionObj.hpp"
|
||||
#include "Game/Util/JMapUtil.hpp"
|
||||
#include "Game/Util/LiveActorUtil.hpp"
|
||||
@ -222,4 +222,4 @@ void CollisionArea::movement() {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
#include "Game/AreaObj/MercatorTransformCube.hpp"
|
||||
#include "Game/Util/AreaObjUtil.hpp"
|
||||
#include "Game/Util/SceneUtil.hpp"
|
||||
#include "JSystem/JMath/JMath.hpp"
|
||||
|
||||
MercatorTransformCube::MercatorTransformCube(int type, const char *pName) : AreaObj(type, pName) {
|
||||
|
612
tools/project.py
612
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, Iterable, List, Optional, Set, Tuple, Union, cast
|
||||
|
||||
from . import ninja_syntax
|
||||
from .ninja_syntax import serialize_path
|
||||
@ -81,6 +81,20 @@ class Object:
|
||||
set_default("shift_jis", config.shift_jis)
|
||||
set_default("src_dir", config.src_dir)
|
||||
|
||||
# Validate progress categories
|
||||
def check_category(category: str):
|
||||
if not any(category == c.id for c in config.progress_categories):
|
||||
sys.exit(
|
||||
f"Progress category '{category}' missing from config.progress_categories"
|
||||
)
|
||||
|
||||
progress_category = obj.options["progress_category"]
|
||||
if isinstance(progress_category, list):
|
||||
for category in progress_category:
|
||||
check_category(category)
|
||||
elif progress_category is not None:
|
||||
check_category(progress_category)
|
||||
|
||||
# Resolve paths
|
||||
build_dir = config.out_path()
|
||||
obj.src_path = Path(obj.options["src_dir"]) / obj.options["source"]
|
||||
@ -155,14 +169,21 @@ class ProjectConfig:
|
||||
self.custom_build_steps: Optional[Dict[str, List[Dict[str, Any]]]] = (
|
||||
None # Custom build steps, types are ["pre-compile", "post-compile", "post-link", "post-build"]
|
||||
)
|
||||
self.generate_compile_commands: bool = (
|
||||
True # Generate compile_commands.json for clangd
|
||||
)
|
||||
|
||||
# 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 = (
|
||||
False # Include individual modules, disable for large numbers of modules
|
||||
)
|
||||
self.progress_categories: List[ProgressCategory] = [] # Additional categories
|
||||
self.print_progress_categories: Union[bool, List[str]] = (
|
||||
True # Print additional progress categories in the CLI progress output
|
||||
)
|
||||
|
||||
# Progress fancy printing
|
||||
self.progress_use_fancy: bool = False
|
||||
@ -199,9 +220,40 @@ class ProjectConfig:
|
||||
out[obj.name] = obj.resolve(self, lib)
|
||||
return out
|
||||
|
||||
# Gets the output path for build-related files.
|
||||
def out_path(self) -> Path:
|
||||
return self.build_dir / str(self.version)
|
||||
|
||||
# Gets the path to the compilers directory.
|
||||
# Exits the program if neither `compilers_path` nor `compilers_tag` is provided.
|
||||
def compilers(self) -> Path:
|
||||
if self.compilers_path:
|
||||
return self.compilers_path
|
||||
elif self.compilers_tag:
|
||||
return self.build_dir / "compilers"
|
||||
else:
|
||||
sys.exit("ProjectConfig.compilers_tag missing")
|
||||
|
||||
# Gets the wrapper to use for compiler commands, if set.
|
||||
def compiler_wrapper(self) -> Optional[Path]:
|
||||
wrapper = self.wrapper
|
||||
|
||||
if self.use_wibo():
|
||||
wrapper = self.build_dir / "tools" / "wibo"
|
||||
if not is_windows() and wrapper is None:
|
||||
wrapper = Path("wine")
|
||||
|
||||
return wrapper
|
||||
|
||||
# Determines whether or not to use wibo as the compiler wrapper.
|
||||
def use_wibo(self) -> bool:
|
||||
return (
|
||||
self.wibo_tag is not None
|
||||
and sys.platform == "linux"
|
||||
and platform.machine() in ("i386", "x86_64")
|
||||
and self.wrapper is None
|
||||
)
|
||||
|
||||
|
||||
def is_windows() -> bool:
|
||||
return os.name == "nt"
|
||||
@ -213,13 +265,26 @@ CHAIN = "cmd /c " if is_windows() else ""
|
||||
EXE = ".exe" if is_windows() else ""
|
||||
|
||||
|
||||
def make_flags_str(flags: Optional[Union[str, List[str]]]) -> str:
|
||||
def file_is_asm(path: Path) -> bool:
|
||||
return path.suffix.lower() == ".s"
|
||||
|
||||
|
||||
def file_is_c(path: Path) -> bool:
|
||||
return path.suffix.lower() == ".c"
|
||||
|
||||
|
||||
def file_is_cpp(path: Path) -> bool:
|
||||
return path.suffix.lower() in (".cc", ".cp", ".cpp", ".cxx")
|
||||
|
||||
|
||||
def file_is_c_cpp(path: Path) -> bool:
|
||||
return file_is_c(path) or file_is_cpp(path)
|
||||
|
||||
|
||||
def make_flags_str(flags: Optional[List[str]]) -> str:
|
||||
if flags is None:
|
||||
return ""
|
||||
elif isinstance(flags, list):
|
||||
return " ".join(flags)
|
||||
else:
|
||||
return flags
|
||||
return " ".join(flags)
|
||||
|
||||
|
||||
# Load decomp-toolkit generated config.json
|
||||
@ -252,13 +317,14 @@ def load_build_config(
|
||||
return build_config
|
||||
|
||||
|
||||
# Generate build.ninja and objdiff.json
|
||||
# Generate build.ninja, objdiff.json and compile_commands.json
|
||||
def generate_build(config: ProjectConfig) -> None:
|
||||
config.validate()
|
||||
objects = config.objects()
|
||||
build_config = load_build_config(config, config.out_path() / "config.json")
|
||||
generate_build_ninja(config, objects, build_config)
|
||||
generate_objdiff_config(config, objects, build_config)
|
||||
generate_compile_commands(config, objects, build_config)
|
||||
|
||||
|
||||
# Generate build.ninja
|
||||
@ -405,16 +471,10 @@ def generate_build_ninja(
|
||||
else:
|
||||
sys.exit("ProjectConfig.sjiswrap_tag missing")
|
||||
|
||||
wrapper = config.compiler_wrapper()
|
||||
# Only add an implicit dependency on wibo if we download it
|
||||
wrapper = config.wrapper
|
||||
wrapper_implicit: Optional[Path] = None
|
||||
if (
|
||||
config.wibo_tag is not None
|
||||
and sys.platform == "linux"
|
||||
and platform.machine() in ("i386", "x86_64")
|
||||
and config.wrapper is None
|
||||
):
|
||||
wrapper = build_tools_path / "wibo"
|
||||
if wrapper is not None and config.use_wibo():
|
||||
wrapper_implicit = wrapper
|
||||
n.build(
|
||||
outputs=wrapper,
|
||||
@ -425,15 +485,11 @@ def generate_build_ninja(
|
||||
"tag": config.wibo_tag,
|
||||
},
|
||||
)
|
||||
if not is_windows() and wrapper is None:
|
||||
wrapper = Path("wine")
|
||||
wrapper_cmd = f"{wrapper} " if wrapper else ""
|
||||
|
||||
compilers = config.compilers()
|
||||
compilers_implicit: Optional[Path] = None
|
||||
if config.compilers_path:
|
||||
compilers = config.compilers_path
|
||||
elif config.compilers_tag:
|
||||
compilers = config.build_dir / "compilers"
|
||||
if config.compilers_path is None and config.compilers_tag is not None:
|
||||
compilers_implicit = compilers
|
||||
n.build(
|
||||
outputs=compilers,
|
||||
@ -444,8 +500,6 @@ def generate_build_ninja(
|
||||
"tag": config.compilers_tag,
|
||||
},
|
||||
)
|
||||
else:
|
||||
sys.exit("ProjectConfig.compilers_tag missing")
|
||||
|
||||
binutils_implicit = None
|
||||
if config.binutils_path:
|
||||
@ -659,7 +713,6 @@ def generate_build_ninja(
|
||||
n.comment(f"Link {self.name}")
|
||||
if self.module_id == 0:
|
||||
elf_path = build_path / f"{self.name}.elf"
|
||||
dol_path = build_path / f"{self.name}.dol"
|
||||
elf_ldflags = f"$ldflags -lcf {serialize_path(self.ldscript)}"
|
||||
if config.generate_map:
|
||||
elf_map = map_path(elf_path)
|
||||
@ -724,17 +777,36 @@ def generate_build_ninja(
|
||||
source_added: Set[Path] = set()
|
||||
|
||||
def c_build(obj: Object, src_path: Path) -> Optional[Path]:
|
||||
cflags_str = make_flags_str(obj.options["cflags"])
|
||||
if obj.options["extra_cflags"] is not None:
|
||||
extra_cflags_str = make_flags_str(obj.options["extra_cflags"])
|
||||
cflags_str += " " + extra_cflags_str
|
||||
used_compiler_versions.add(obj.options["mw_version"])
|
||||
|
||||
# Avoid creating duplicate build rules
|
||||
if obj.src_obj_path is None or obj.src_obj_path in source_added:
|
||||
return obj.src_obj_path
|
||||
source_added.add(obj.src_obj_path)
|
||||
|
||||
cflags = obj.options["cflags"]
|
||||
extra_cflags = obj.options["extra_cflags"]
|
||||
|
||||
# Add appropriate language flag if it doesn't exist already
|
||||
# Added directly to the source so it flows to other generation tasks
|
||||
if not any(flag.startswith("-lang") for flag in cflags) and (
|
||||
extra_cflags is None
|
||||
or not any(flag.startswith("-lang") for flag in extra_cflags)
|
||||
):
|
||||
# Ensure extra_cflags is a unique instance,
|
||||
# and insert into there to avoid modifying shared sets of flags
|
||||
if extra_cflags is None:
|
||||
extra_cflags = []
|
||||
extra_cflags = obj.options["extra_cflags"] = list(extra_cflags)
|
||||
if file_is_cpp(src_path):
|
||||
extra_cflags.insert(0, "-lang=c++")
|
||||
else:
|
||||
extra_cflags.insert(0, "-lang=c")
|
||||
|
||||
cflags_str = make_flags_str(cflags)
|
||||
if extra_cflags is not None:
|
||||
extra_cflags_str = make_flags_str(extra_cflags)
|
||||
cflags_str += " " + extra_cflags_str
|
||||
used_compiler_versions.add(obj.options["mw_version"])
|
||||
|
||||
# Add MWCC build rule
|
||||
lib_name = obj.options["lib"]
|
||||
n.comment(f"{obj.name}: {lib_name} (linked {obj.completed})")
|
||||
@ -766,7 +838,7 @@ def generate_build_ninja(
|
||||
if obj.options["host"] and obj.host_obj_path is not None:
|
||||
n.build(
|
||||
outputs=obj.host_obj_path,
|
||||
rule="host_cc" if src_path.suffix == ".c" else "host_cpp",
|
||||
rule="host_cc" if file_is_c(src_path) else "host_cpp",
|
||||
inputs=src_path,
|
||||
variables={
|
||||
"basedir": os.path.dirname(obj.host_obj_path),
|
||||
@ -826,10 +898,10 @@ def generate_build_ninja(
|
||||
link_built_obj = obj.completed
|
||||
built_obj_path: Optional[Path] = None
|
||||
if obj.src_path is not None and obj.src_path.exists():
|
||||
if obj.src_path.suffix in (".c", ".cp", ".cpp"):
|
||||
if file_is_c_cpp(obj.src_path):
|
||||
# Add MWCC & host build rules
|
||||
built_obj_path = c_build(obj, obj.src_path)
|
||||
elif obj.src_path.suffix == ".s":
|
||||
elif file_is_asm(obj.src_path):
|
||||
# Add assembler build rule
|
||||
built_obj_path = asm_build(obj, obj.src_path, obj.src_obj_path)
|
||||
else:
|
||||
@ -1036,7 +1108,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,
|
||||
],
|
||||
)
|
||||
|
||||
###
|
||||
@ -1149,8 +1226,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)
|
||||
|
||||
@ -1169,6 +1248,13 @@ def generate_objdiff_config(
|
||||
if build_config is None:
|
||||
return
|
||||
|
||||
# Load existing objdiff.json
|
||||
existing_units = {}
|
||||
if Path("objdiff.json").is_file():
|
||||
with open("objdiff.json", "r", encoding="utf-8") as r:
|
||||
existing_config = json.load(r)
|
||||
existing_units = {unit["name"]: unit for unit in existing_config["units"]}
|
||||
|
||||
objdiff_config: Dict[str, Any] = {
|
||||
"min_version": "2.0.0-beta.5",
|
||||
"custom_make": "ninja",
|
||||
@ -1226,15 +1312,27 @@ def generate_objdiff_config(
|
||||
) -> None:
|
||||
obj_path, obj_name = build_obj["object"], build_obj["name"]
|
||||
base_object = Path(obj_name).with_suffix("")
|
||||
name = str(Path(module_name) / base_object).replace(os.sep, "/")
|
||||
unit_config: Dict[str, Any] = {
|
||||
"name": Path(module_name) / base_object,
|
||||
"name": name,
|
||||
"target_path": obj_path,
|
||||
"base_path": None,
|
||||
"scratch": None,
|
||||
"metadata": {
|
||||
"auto_generated": build_obj["autogenerated"],
|
||||
"complete": None,
|
||||
"reverse_fn_order": None,
|
||||
"source_path": None,
|
||||
"progress_categories": progress_categories,
|
||||
"auto_generated": build_obj["autogenerated"],
|
||||
},
|
||||
"symbol_mappings": None,
|
||||
}
|
||||
|
||||
# Preserve existing symbol mappings
|
||||
existing_unit = existing_units.get(name)
|
||||
if existing_unit is not None:
|
||||
unit_config["symbol_mappings"] = existing_unit.get("symbol_mappings")
|
||||
|
||||
obj = objects.get(obj_name)
|
||||
if obj is None:
|
||||
objdiff_config["units"].append(unit_config)
|
||||
@ -1243,33 +1341,24 @@ 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
|
||||
if type(cflags) is list:
|
||||
for flag in cflags:
|
||||
if not flag.startswith("-inline "):
|
||||
continue
|
||||
for value in flag.split(" ")[1].split(","):
|
||||
if value == "deferred":
|
||||
reverse_fn_order = True
|
||||
elif value == "nodeferred":
|
||||
reverse_fn_order = False
|
||||
for flag in cflags:
|
||||
if not flag.startswith("-inline "):
|
||||
continue
|
||||
for value in flag.split(" ")[1].split(","):
|
||||
if value == "deferred":
|
||||
reverse_fn_order = True
|
||||
elif value == "nodeferred":
|
||||
reverse_fn_order = False
|
||||
|
||||
# Filter out include directories
|
||||
def keep_flag(flag):
|
||||
return not flag.startswith("-i ") and not flag.startswith("-I ")
|
||||
# Filter out include directories
|
||||
def keep_flag(flag):
|
||||
return not flag.startswith("-i ") and not flag.startswith("-I ")
|
||||
|
||||
cflags = list(filter(keep_flag, cflags))
|
||||
|
||||
# Add appropriate lang flag
|
||||
if obj.src_path is not None and not any(
|
||||
flag.startswith("-lang") for flag in cflags
|
||||
):
|
||||
if obj.src_path.suffix in (".cp", ".cpp"):
|
||||
cflags.insert(0, "-lang=c++")
|
||||
else:
|
||||
cflags.insert(0, "-lang=c")
|
||||
cflags = list(filter(keep_flag, cflags))
|
||||
|
||||
compiler_version = COMPILER_MAP.get(obj.options["mw_version"])
|
||||
if compiler_version is None:
|
||||
@ -1300,7 +1389,6 @@ def generate_objdiff_config(
|
||||
{
|
||||
"complete": obj.completed,
|
||||
"reverse_fn_order": reverse_fn_order,
|
||||
"source_path": obj.src_path,
|
||||
"progress_categories": progress_categories,
|
||||
}
|
||||
)
|
||||
@ -1344,136 +1432,294 @@ def generate_objdiff_config(
|
||||
for category in config.progress_categories:
|
||||
add_category(category.id, category.name)
|
||||
|
||||
def cleandict(d):
|
||||
if isinstance(d, dict):
|
||||
return {k: cleandict(v) for k, v in d.items() if v is not None}
|
||||
elif isinstance(d, list):
|
||||
return [cleandict(v) for v in d]
|
||||
else:
|
||||
return d
|
||||
|
||||
# Write objdiff.json
|
||||
with open("objdiff.json", "w", encoding="utf-8") as w:
|
||||
|
||||
def unix_path(input: Any) -> str:
|
||||
return str(input).replace(os.sep, "/") if input else ""
|
||||
|
||||
json.dump(objdiff_config, w, indent=4, default=unix_path)
|
||||
json.dump(cleandict(objdiff_config), w, indent=2, default=unix_path)
|
||||
|
||||
|
||||
def generate_compile_commands(
|
||||
config: ProjectConfig,
|
||||
objects: Dict[str, Object],
|
||||
build_config: Optional[Dict[str, Any]],
|
||||
) -> None:
|
||||
if build_config is None or not config.generate_compile_commands:
|
||||
return
|
||||
|
||||
# The following code attempts to convert mwcc flags to clang flags
|
||||
# for use with clangd.
|
||||
|
||||
# Flags to ignore explicitly
|
||||
CFLAG_IGNORE: Set[str] = {
|
||||
# Search order modifier
|
||||
# Has a different meaning to Clang, and would otherwise
|
||||
# be picked up by the include passthrough prefix
|
||||
"-I-",
|
||||
"-i-",
|
||||
}
|
||||
CFLAG_IGNORE_PREFIX: Tuple[str, ...] = tuple()
|
||||
|
||||
# Flags to replace
|
||||
CFLAG_REPLACE: Dict[str, str] = {}
|
||||
CFLAG_REPLACE_PREFIX: Tuple[Tuple[str, str], ...] = (
|
||||
# Includes
|
||||
("-i ", "-I"),
|
||||
("-I ", "-I"),
|
||||
("-I+", "-I"),
|
||||
# Defines
|
||||
("-d ", "-D"),
|
||||
("-D ", "-D"),
|
||||
("-D+", "-D"),
|
||||
)
|
||||
|
||||
# Flags with a finite set of options
|
||||
CFLAG_REPLACE_OPTIONS: Tuple[Tuple[str, Dict[str, Tuple[str, ...]]], ...] = (
|
||||
# Exceptions
|
||||
(
|
||||
"-Cpp_exceptions",
|
||||
{
|
||||
"off": ("-fno-cxx-exceptions",),
|
||||
"on": ("-fcxx-exceptions",),
|
||||
},
|
||||
),
|
||||
# RTTI
|
||||
(
|
||||
"-RTTI",
|
||||
{
|
||||
"off": ("-fno-rtti",),
|
||||
"on": ("-frtti",),
|
||||
},
|
||||
),
|
||||
# Language configuration
|
||||
(
|
||||
"-lang",
|
||||
{
|
||||
"c": ("--language=c", "--std=c89"),
|
||||
"c99": ("--language=c", "--std=c99"),
|
||||
"c++": ("--language=c++", "--std=c++98"),
|
||||
"cplus": ("--language=c++", "--std=c++98"),
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
# Flags to pass through
|
||||
CFLAG_PASSTHROUGH: Set[str] = set()
|
||||
CFLAG_PASSTHROUGH_PREFIX: Tuple[str, ...] = (
|
||||
"-I", # includes
|
||||
"-D", # defines
|
||||
)
|
||||
|
||||
clangd_config = []
|
||||
|
||||
def add_unit(build_obj: Dict[str, Any]) -> None:
|
||||
obj = objects.get(build_obj["name"])
|
||||
if obj is None:
|
||||
return
|
||||
|
||||
# Skip unresolved objects
|
||||
if (
|
||||
obj.src_path is None
|
||||
or obj.src_obj_path is None
|
||||
or not file_is_c_cpp(obj.src_path)
|
||||
):
|
||||
return
|
||||
|
||||
# Gather cflags for source file
|
||||
cflags: list[str] = []
|
||||
|
||||
def append_cflags(flags: Iterable[str]) -> None:
|
||||
# Match a flag against either a set of concrete flags, or a set of prefixes.
|
||||
def flag_match(
|
||||
flag: str, concrete: Set[str], prefixes: Tuple[str, ...]
|
||||
) -> bool:
|
||||
if flag in concrete:
|
||||
return True
|
||||
|
||||
for prefix in prefixes:
|
||||
if flag.startswith(prefix):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
# Determine whether a flag should be ignored.
|
||||
def should_ignore(flag: str) -> bool:
|
||||
return flag_match(flag, CFLAG_IGNORE, CFLAG_IGNORE_PREFIX)
|
||||
|
||||
# Determine whether a flag should be passed through.
|
||||
def should_passthrough(flag: str) -> bool:
|
||||
return flag_match(flag, CFLAG_PASSTHROUGH, CFLAG_PASSTHROUGH_PREFIX)
|
||||
|
||||
# Attempts replacement for the given flag.
|
||||
def try_replace(flag: str) -> bool:
|
||||
replacement = CFLAG_REPLACE.get(flag)
|
||||
if replacement is not None:
|
||||
cflags.append(replacement)
|
||||
return True
|
||||
|
||||
for prefix, replacement in CFLAG_REPLACE_PREFIX:
|
||||
if flag.startswith(prefix):
|
||||
cflags.append(flag.replace(prefix, replacement, 1))
|
||||
return True
|
||||
|
||||
for prefix, options in CFLAG_REPLACE_OPTIONS:
|
||||
if not flag.startswith(prefix):
|
||||
continue
|
||||
|
||||
# "-lang c99" and "-lang=c99" are both generally valid option forms
|
||||
option = flag.removeprefix(prefix).removeprefix("=").lstrip()
|
||||
replacements = options.get(option)
|
||||
if replacements is not None:
|
||||
cflags.extend(replacements)
|
||||
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
for flag in flags:
|
||||
# Ignore flags first
|
||||
if should_ignore(flag):
|
||||
continue
|
||||
|
||||
# Then find replacements
|
||||
if try_replace(flag):
|
||||
continue
|
||||
|
||||
# Pass flags through last
|
||||
if should_passthrough(flag):
|
||||
cflags.append(flag)
|
||||
continue
|
||||
|
||||
append_cflags(obj.options["cflags"])
|
||||
if isinstance(obj.options["extra_cflags"], list):
|
||||
append_cflags(obj.options["extra_cflags"])
|
||||
|
||||
unit_config = {
|
||||
"directory": Path.cwd(),
|
||||
"file": obj.src_path,
|
||||
"output": obj.src_obj_path,
|
||||
"arguments": [
|
||||
"clang",
|
||||
"-nostdinc",
|
||||
"-fno-builtin",
|
||||
"--target=powerpc-eabi",
|
||||
*cflags,
|
||||
"-c",
|
||||
obj.src_path,
|
||||
"-o",
|
||||
obj.src_obj_path,
|
||||
],
|
||||
}
|
||||
clangd_config.append(unit_config)
|
||||
|
||||
# Add DOL units
|
||||
for unit in build_config["units"]:
|
||||
add_unit(unit)
|
||||
|
||||
# Add REL units
|
||||
for module in build_config["modules"]:
|
||||
for unit in module["units"]:
|
||||
add_unit(unit)
|
||||
|
||||
# Write compile_commands.json
|
||||
with open("compile_commands.json", "w", encoding="utf-8") as w:
|
||||
|
||||
def default_format(o):
|
||||
if isinstance(o, Path):
|
||||
return o.resolve().as_posix()
|
||||
return str(o)
|
||||
|
||||
json.dump(clangd_config, w, indent=2, default=default_format)
|
||||
|
||||
|
||||
# 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
|
||||
report_path = out_path / "report.json"
|
||||
if not report_path.is_file():
|
||||
sys.exit(f"Report file {report_path} does not exist")
|
||||
|
||||
class ProgressUnit:
|
||||
def __init__(self, name: str) -> None:
|
||||
self.name: str = name
|
||||
self.code_total: int = 0
|
||||
self.code_progress: int = 0
|
||||
self.data_total: int = 0
|
||||
self.data_progress: int = 0
|
||||
self.objects: Set[Object] = set()
|
||||
self.objects_progress: int = 0
|
||||
report_data: Dict[str, Any] = {}
|
||||
with open(report_path, "r", encoding="utf-8") as f:
|
||||
report_data = json.load(f)
|
||||
|
||||
def add(self, build_obj: Dict[str, Any]) -> None:
|
||||
self.code_total += build_obj["code_size"]
|
||||
self.data_total += build_obj["data_size"]
|
||||
# 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)
|
||||
|
||||
# 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"])
|
||||
convert_numbers(report_data["measures"])
|
||||
for category in report_data["categories"]:
|
||||
convert_numbers(category["measures"])
|
||||
|
||||
if build_obj["autogenerated"]:
|
||||
# Skip autogenerated objects
|
||||
return
|
||||
# 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")
|
||||
|
||||
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)
|
||||
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:")
|
||||
|
||||
for unit in progress_units.values():
|
||||
if len(unit.objects) == 0:
|
||||
continue
|
||||
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)
|
||||
|
||||
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} / {len(unit.objects)} files)"
|
||||
progress_print(
|
||||
f" {name}: {matched_code_percent:.2f}% matched, {complete_code_percent:.2f}% linked ({complete_units} / {total_units} files)"
|
||||
)
|
||||
print(f" Code: {unit.code_progress} / {unit.code_total} bytes")
|
||||
print(f" Data: {unit.data_progress} / {unit.data_total} bytes")
|
||||
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}%)"
|
||||
)
|
||||
|
||||
print_category("All", report_data["measures"])
|
||||
for category in report_data["categories"]:
|
||||
if config.print_progress_categories is True or (
|
||||
isinstance(config.print_progress_categories, list)
|
||||
and category["id"] in config.print_progress_categories
|
||||
):
|
||||
print_category(category["name"], category["measures"])
|
||||
|
||||
if config.progress_use_fancy:
|
||||
unit = progress_units.get("all") or progress_units.get("dol")
|
||||
if unit is None or len(unit.objects) == 0:
|
||||
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
|
||||
|
||||
code_frac = unit.code_frac()
|
||||
data_frac = unit.data_frac()
|
||||
print(
|
||||
progress_print(
|
||||
"\nYou have {} out of {} {} and {} out of {} {}.".format(
|
||||
math.floor(code_frac * config.progress_code_fancy_frac),
|
||||
config.progress_code_fancy_frac,
|
||||
@ -1484,17 +1730,39 @@ def calculate_progress(config: ProjectConfig) -> None:
|
||||
)
|
||||
)
|
||||
|
||||
# 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 len(unit.objects) == 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)
|
||||
json.dump(progress_json, w, indent=2)
|
||||
|
Loading…
x
Reference in New Issue
Block a user