Add support for excluded globs and prelude macros to decompctx.py (#62)

* Add flag to exclude files matching a specific glob

* Add flag to declare macros to place in a prelude

* Add support to project.py for decompctx excludes and preludes

* Minor cleanup & formatting

---------

Co-authored-by: Luke Street <luke@street.dev>
This commit is contained in:
Max Roncace
2025-08-30 13:31:36 -04:00
committed by GitHub
parent d193664911
commit 755d8c109f
2 changed files with 61 additions and 4 deletions

View File

@@ -11,6 +11,7 @@
### ###
import argparse import argparse
import fnmatch
import os import os
import re import re
from typing import List from typing import List
@@ -19,6 +20,7 @@ script_dir = os.path.dirname(os.path.realpath(__file__))
root_dir = os.path.abspath(os.path.join(script_dir, "..")) root_dir = os.path.abspath(os.path.join(script_dir, ".."))
src_dir = os.path.join(root_dir, "src") src_dir = os.path.join(root_dir, "src")
include_dirs: List[str] = [] # Set with -I flag include_dirs: List[str] = [] # Set with -I flag
exclude_globs: List[str] = [] # Set with -x flag
include_pattern = re.compile(r'^#\s*include\s*[<"](.+?)[>"]') include_pattern = re.compile(r'^#\s*include\s*[<"](.+?)[>"]')
guard_pattern = re.compile(r"^#\s*ifndef\s+(.*)$") guard_pattern = re.compile(r"^#\s*ifndef\s+(.*)$")
@@ -28,6 +30,23 @@ defines = set()
deps = [] deps = []
def generate_prelude(defines) -> str:
if len(defines) == 0:
return ""
out_text = "/* decompctx prelude */\n"
for define in defines:
parts = define.split("=", 1)
if len(parts) == 2:
macro_name, macro_val = parts
out_text += f"#define {macro_name} {macro_val}\n"
else:
out_text += f"#define {parts[0]}\n"
out_text += "/* end decompctx prelude */\n\n"
return out_text
def import_h_file(in_file: str, r_path: str) -> str: def import_h_file(in_file: str, r_path: str) -> str:
rel_path = os.path.join(root_dir, r_path, in_file) rel_path = os.path.join(root_dir, r_path, in_file)
if os.path.exists(rel_path): if os.path.exists(rel_path):
@@ -73,7 +92,16 @@ def process_file(in_file: str, lines: List[str]) -> str:
print("Processing file", in_file) print("Processing file", in_file)
include_match = include_pattern.match(line.strip()) include_match = include_pattern.match(line.strip())
if include_match and not include_match[1].endswith(".s"): if include_match and not include_match[1].endswith(".s"):
excluded = False
for glob in exclude_globs:
if fnmatch.fnmatch(include_match[1], glob):
excluded = True
break
out_text += f'/* "{in_file}" line {idx} "{include_match[1]}" */\n' out_text += f'/* "{in_file}" line {idx} "{include_match[1]}" */\n'
if excluded:
out_text += "/* Skipped excluded file */\n"
else:
out_text += import_h_file(include_match[1], os.path.dirname(in_file)) out_text += import_h_file(include_match[1], os.path.dirname(in_file))
out_text += f'/* end "{include_match[1]}" */\n' out_text += f'/* end "{include_match[1]}" */\n'
else: else:
@@ -111,13 +139,29 @@ def main():
help="""Include directory""", help="""Include directory""",
action="append", action="append",
) )
parser.add_argument(
"-x",
"--exclude",
help="""Excluded file name glob""",
action="append",
)
parser.add_argument(
"-D",
"--define",
help="""Macro definition""",
action="append",
)
args = parser.parse_args() args = parser.parse_args()
if args.include is None: if args.include is None:
exit("No include directories specified") exit("No include directories specified")
global include_dirs global include_dirs
include_dirs = args.include include_dirs = args.include
output = import_c_file(args.c_file) global exclude_globs
exclude_globs = args.exclude or []
prelude_defines = args.define or []
output = generate_prelude(prelude_defines)
output += import_c_file(args.c_file)
with open(os.path.join(root_dir, args.output), "w", encoding="utf-8") as f: with open(os.path.join(root_dir, args.output), "w", encoding="utf-8") as f:
f.write(output) f.write(output)

View File

@@ -198,6 +198,12 @@ class ProjectConfig:
self.link_order_callback: Optional[Callable[[int, List[str]], List[str]]] = ( self.link_order_callback: Optional[Callable[[int, List[str]], List[str]]] = (
None # Callback to add/remove/reorder units within a module None # Callback to add/remove/reorder units within a module
) )
self.context_exclude_globs: List[str] = (
[] # Globs to exclude from context files
)
self.context_defines: List[str] = (
[] # Macros to define at the top of context files
)
# Progress output and report.json config # Progress output 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
@@ -492,7 +498,7 @@ def generate_build_ninja(
decompctx = config.tools_dir / "decompctx.py" decompctx = config.tools_dir / "decompctx.py"
n.rule( n.rule(
name="decompctx", name="decompctx",
command=f"$python {decompctx} $in -o $out -d $out.d $includes", command=f"$python {decompctx} $in -o $out -d $out.d $includes $excludes $defines",
description="CTX $in", description="CTX $in",
depfile="$out.d", depfile="$out.d",
deps="gcc", deps="gcc",
@@ -1048,12 +1054,19 @@ def generate_build_ninja(
): ):
include_dirs.append(flag[3:]) include_dirs.append(flag[3:])
includes = " ".join([f"-I {d}" for d in include_dirs]) includes = " ".join([f"-I {d}" for d in include_dirs])
excludes = " ".join([f"-x {d}" for d in config.context_exclude_globs])
defines = " ".join([f"-D {d}" for d in config.context_defines])
n.build( n.build(
outputs=obj.ctx_path, outputs=obj.ctx_path,
rule="decompctx", rule="decompctx",
inputs=src_path, inputs=src_path,
implicit=decompctx, implicit=decompctx,
variables={"includes": includes}, variables={
"includes": includes,
"excludes": excludes,
"defines": defines,
},
) )
n.newline() n.newline()