Update dtk-template

This commit is contained in:
Luke Street 2024-03-03 22:35:05 -07:00
parent dfe3217473
commit d0025a638b
8 changed files with 679 additions and 375 deletions

4
.flake8 Normal file
View file

@ -0,0 +1,4 @@
[flake8]
# E203: whitespace before ':'
# E501: line too long
extend-ignore = E203,E501

View file

@ -117,10 +117,11 @@ if not is_windows():
config.wrapper = args.wrapper config.wrapper = args.wrapper
# Tool versions # Tool versions
config.binutils_tag = "2.42-1"
config.compilers_tag = "20231018" config.compilers_tag = "20231018"
config.dtk_tag = "v0.6.3" config.dtk_tag = "v0.7.4"
config.sjiswrap_tag = "v1.1.1" config.sjiswrap_tag = "v1.1.1"
config.wibo_tag = "0.6.3" config.wibo_tag = "0.6.11"
# Project # Project
config.config_path = Path("config") / config.version / "config.yml" config.config_path = Path("config") / config.version / "config.yml"

View file

@ -13,60 +13,74 @@
import argparse import argparse
import os import os
import re import re
from typing import List
script_dir = os.path.dirname(os.path.realpath(__file__)) 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_dir = os.path.join(root_dir, "include") include_dirs = [
os.path.join(root_dir, "include"),
# Add additional include directories here
]
include_pattern = re.compile(r'^#include\s*[<"](.+?)[>"]$') include_pattern = re.compile(r'^#include\s*[<"](.+?)[>"]$')
guard_pattern = re.compile(r'^#ifndef\s+(.*)$') guard_pattern = re.compile(r"^#ifndef\s+(.*)$")
defines = set() defines = set()
def import_h_file(in_file: str, r_path: str) -> str:
rel_path = os.path.join(root_dir, r_path, in_file)
inc_path = os.path.join(include_dir, in_file)
if os.path.exists(rel_path):
return import_c_file(rel_path)
elif os.path.exists(inc_path):
return import_c_file(inc_path)
else:
print("Failed to locate", in_file)
exit(1)
def import_c_file(in_file) -> str: def import_h_file(in_file: str, r_path: str, deps: List[str]) -> str:
rel_path = os.path.join(root_dir, r_path, in_file)
if os.path.exists(rel_path):
return import_c_file(rel_path, deps)
for include_dir in include_dirs:
inc_path = os.path.join(include_dir, in_file)
if os.path.exists(inc_path):
return import_c_file(inc_path, deps)
else:
print("Failed to locate", in_file)
return ""
def import_c_file(in_file: str, deps: List[str]) -> str:
in_file = os.path.relpath(in_file, root_dir) in_file = os.path.relpath(in_file, root_dir)
out_text = '' deps.append(in_file)
out_text = ""
try: try:
with open(in_file, encoding="utf-8") as file: with open(in_file, encoding="utf-8") as file:
out_text += process_file(in_file, list(file)) out_text += process_file(in_file, list(file), deps)
except Exception: except Exception:
with open(in_file) as file: with open(in_file) as file:
out_text += process_file(in_file, list(file)) out_text += process_file(in_file, list(file), deps)
return out_text return out_text
def process_file(in_file: str, lines) -> str:
out_text = '' def process_file(in_file: str, lines: List[str], deps: List[str]) -> str:
out_text = ""
for idx, line in enumerate(lines): for idx, line in enumerate(lines):
guard_match = guard_pattern.match(line.strip()) guard_match = guard_pattern.match(line.strip())
if idx == 0: if idx == 0:
if guard_match: if guard_match:
if guard_match[1] in defines: if guard_match[1] in defines:
break break
defines.add(guard_match[1]) defines.add(guard_match[1])
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"):
out_text += f"/* \"{in_file}\" line {idx} \"{include_match[1]}\" */\n" out_text += f'/* "{in_file}" line {idx} "{include_match[1]}" */\n'
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), deps)
out_text += f"/* end \"{include_match[1]}\" */\n" out_text += f'/* end "{include_match[1]}" */\n'
else: else:
out_text += line out_text += line
return out_text return out_text
def sanitize_path(path: str) -> str:
return path.replace("\\", "/").replace(" ", "\\ ")
def main(): def main():
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="""Create a context file which can be used for decomp.me""" description="""Create a context file which can be used for decomp.me"""
@ -75,13 +89,32 @@ def main():
"c_file", "c_file",
help="""File from which to create context""", help="""File from which to create context""",
) )
parser.add_argument(
"-o",
"--output",
help="""Output file""",
default="ctx.c",
)
parser.add_argument(
"-d",
"--depfile",
help="""Dependency file""",
)
args = parser.parse_args() args = parser.parse_args()
output = import_c_file(args.c_file) deps = []
output = import_c_file(args.c_file, deps)
with open(os.path.join(root_dir, "ctx.c"), "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)
if args.depfile:
with open(os.path.join(root_dir, args.depfile), "w", encoding="utf-8") as f:
f.write(sanitize_path(args.output) + ":")
for dep in deps:
path = sanitize_path(dep)
f.write(f" \\\n\t{path}")
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View file

@ -16,14 +16,31 @@ import os
import platform import platform
import shutil import shutil
import stat import stat
import sys
import urllib.request import urllib.request
import zipfile import zipfile
from typing import Callable, Dict
from pathlib import Path from pathlib import Path
def dtk_url(tag): def binutils_url(tag):
uname = platform.uname()
system = uname.system.lower()
arch = uname.machine.lower()
if system == "darwin":
system = "macos"
arch = "universal"
elif arch == "amd64":
arch = "x86_64"
repo = "https://github.com/encounter/gc-wii-binutils"
return f"{repo}/releases/download/{tag}/{system}-{arch}.zip"
def compilers_url(tag: str) -> str:
return f"https://files.decomp.dev/compilers_{tag}.zip"
def dtk_url(tag: str) -> str:
uname = platform.uname() uname = platform.uname()
suffix = "" suffix = ""
system = uname.system.lower() system = uname.system.lower()
@ -39,28 +56,26 @@ def dtk_url(tag):
return f"{repo}/releases/download/{tag}/dtk-{system}-{arch}{suffix}" return f"{repo}/releases/download/{tag}/dtk-{system}-{arch}{suffix}"
def sjiswrap_url(tag): def sjiswrap_url(tag: str) -> str:
repo = "https://github.com/encounter/sjiswrap" repo = "https://github.com/encounter/sjiswrap"
return f"{repo}/releases/download/{tag}/sjiswrap-windows-x86.exe" return f"{repo}/releases/download/{tag}/sjiswrap-windows-x86.exe"
def wibo_url(tag): def wibo_url(tag: str) -> str:
repo = "https://github.com/decompals/wibo" repo = "https://github.com/decompals/wibo"
return f"{repo}/releases/download/{tag}/wibo" return f"{repo}/releases/download/{tag}/wibo"
def compilers_url(tag): TOOLS: Dict[str, Callable[[str], str]] = {
return f"https://files.decomp.dev/compilers_{tag}.zip" "binutils": binutils_url,
"compilers": compilers_url,
TOOLS = {
"dtk": dtk_url, "dtk": dtk_url,
"sjiswrap": sjiswrap_url, "sjiswrap": sjiswrap_url,
"wibo": wibo_url, "wibo": wibo_url,
"compilers": compilers_url,
} }
def main(): def main() -> None:
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument("tool", help="Tool name") parser.add_argument("tool", help="Tool name")
parser.add_argument("output", type=Path, help="output file path") parser.add_argument("output", type=Path, help="output file path")
@ -77,7 +92,11 @@ def main():
data = io.BytesIO(response.read()) data = io.BytesIO(response.read())
with zipfile.ZipFile(data) as f: with zipfile.ZipFile(data) as f:
f.extractall(output) f.extractall(output)
output.touch(mode=0o755) # Make all files executable
for root, _, files in os.walk(output):
for name in files:
os.chmod(os.path.join(root, name), 0o755)
output.touch(mode=0o755) # Update dir modtime
else: else:
with open(output, "wb") as f: with open(output, "wb") as f:
shutil.copyfileobj(response, f) shutil.copyfileobj(response, f)
@ -86,4 +105,4 @@ def main():
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View file

@ -21,50 +21,66 @@ use Python.
import re import re
import textwrap import textwrap
from io import StringIO
from pathlib import Path
from typing import Dict, List, Match, Optional, Tuple, Union
NinjaPath = Union[str, Path]
NinjaPaths = Union[
List[str],
List[Path],
List[NinjaPath],
List[Optional[str]],
List[Optional[Path]],
List[Optional[NinjaPath]],
]
NinjaPathOrPaths = Union[NinjaPath, NinjaPaths]
def escape_path(word): def escape_path(word: str) -> str:
return word.replace("$ ", "$$ ").replace(" ", "$ ").replace(":", "$:") return word.replace("$ ", "$$ ").replace(" ", "$ ").replace(":", "$:")
class Writer(object): class Writer(object):
def __init__(self, output, width=78): def __init__(self, output: StringIO, width: int = 78) -> None:
self.output = output self.output = output
self.width = width self.width = width
def newline(self): def newline(self) -> None:
self.output.write("\n") self.output.write("\n")
def comment(self, text): def comment(self, text: str) -> None:
for line in textwrap.wrap( for line in textwrap.wrap(
text, self.width - 2, break_long_words=False, break_on_hyphens=False text, self.width - 2, break_long_words=False, break_on_hyphens=False
): ):
self.output.write("# " + line + "\n") self.output.write("# " + line + "\n")
def variable(self, key, value, indent=0): def variable(
if value is None: self,
return key: str,
if isinstance(value, list): value: Optional[NinjaPathOrPaths],
value = " ".join(filter(None, value)) # Filter out empty strings. indent: int = 0,
) -> None:
value = " ".join(serialize_paths(value))
self._line("%s = %s" % (key, value), indent) self._line("%s = %s" % (key, value), indent)
def pool(self, name, depth): def pool(self, name: str, depth: int) -> None:
self._line("pool %s" % name) self._line("pool %s" % name)
self.variable("depth", depth, indent=1) self.variable("depth", str(depth), indent=1)
def rule( def rule(
self, self,
name, name: str,
command, command: str,
description=None, description: Optional[str] = None,
depfile=None, depfile: Optional[NinjaPath] = None,
generator=False, generator: bool = False,
pool=None, pool: Optional[str] = None,
restat=False, restat: bool = False,
rspfile=None, rspfile: Optional[NinjaPath] = None,
rspfile_content=None, rspfile_content: Optional[NinjaPath] = None,
deps=None, deps: Optional[NinjaPathOrPaths] = None,
): ) -> None:
self._line("rule %s" % name) self._line("rule %s" % name)
self.variable("command", command, indent=1) self.variable("command", command, indent=1)
if description: if description:
@ -86,32 +102,39 @@ class Writer(object):
def build( def build(
self, self,
outputs, outputs: NinjaPathOrPaths,
rule, rule: str,
inputs=None, inputs: Optional[NinjaPathOrPaths] = None,
implicit=None, implicit: Optional[NinjaPathOrPaths] = None,
order_only=None, order_only: Optional[NinjaPathOrPaths] = None,
variables=None, variables: Optional[
implicit_outputs=None, Union[
pool=None, List[Tuple[str, Optional[NinjaPathOrPaths]]],
dyndep=None, Dict[str, Optional[NinjaPathOrPaths]],
): ]
outputs = as_list(outputs) ] = None,
implicit_outputs: Optional[NinjaPathOrPaths] = None,
pool: Optional[str] = None,
dyndep: Optional[NinjaPath] = None,
) -> List[str]:
outputs = serialize_paths(outputs)
out_outputs = [escape_path(x) for x in outputs] out_outputs = [escape_path(x) for x in outputs]
all_inputs = [escape_path(x) for x in as_list(inputs)] all_inputs = [escape_path(x) for x in serialize_paths(inputs)]
if implicit: if implicit:
implicit = [escape_path(x) for x in as_list(implicit)] implicit = [escape_path(x) for x in serialize_paths(implicit)]
all_inputs.append("|") all_inputs.append("|")
all_inputs.extend(implicit) all_inputs.extend(map(str, implicit))
if order_only: if order_only:
order_only = [escape_path(x) for x in as_list(order_only)] order_only = [escape_path(x) for x in serialize_paths(order_only)]
all_inputs.append("||") all_inputs.append("||")
all_inputs.extend(order_only) all_inputs.extend(map(str, order_only))
if implicit_outputs: if implicit_outputs:
implicit_outputs = [escape_path(x) for x in as_list(implicit_outputs)] implicit_outputs = [
escape_path(x) for x in serialize_paths(implicit_outputs)
]
out_outputs.append("|") out_outputs.append("|")
out_outputs.extend(implicit_outputs) out_outputs.extend(map(str, implicit_outputs))
self._line( self._line(
"build %s: %s" % (" ".join(out_outputs), " ".join([rule] + all_inputs)) "build %s: %s" % (" ".join(out_outputs), " ".join([rule] + all_inputs))
@ -119,7 +142,7 @@ class Writer(object):
if pool is not None: if pool is not None:
self._line(" pool = %s" % pool) self._line(" pool = %s" % pool)
if dyndep is not None: if dyndep is not None:
self._line(" dyndep = %s" % dyndep) self._line(" dyndep = %s" % serialize_path(dyndep))
if variables: if variables:
if isinstance(variables, dict): if isinstance(variables, dict):
@ -132,16 +155,16 @@ class Writer(object):
return outputs return outputs
def include(self, path): def include(self, path: str) -> None:
self._line("include %s" % path) self._line("include %s" % path)
def subninja(self, path): def subninja(self, path: str) -> None:
self._line("subninja %s" % path) self._line("subninja %s" % path)
def default(self, paths): def default(self, paths: NinjaPathOrPaths) -> None:
self._line("default %s" % " ".join(as_list(paths))) self._line("default %s" % " ".join(serialize_paths(paths)))
def _count_dollars_before_index(self, s, i): def _count_dollars_before_index(self, s: str, i: int) -> int:
"""Returns the number of '$' characters right in front of s[i].""" """Returns the number of '$' characters right in front of s[i]."""
dollar_count = 0 dollar_count = 0
dollar_index = i - 1 dollar_index = i - 1
@ -150,7 +173,7 @@ class Writer(object):
dollar_index -= 1 dollar_index -= 1
return dollar_count return dollar_count
def _line(self, text, indent=0): def _line(self, text: str, indent: int = 0) -> None:
"""Write 'text' word-wrapped at self.width characters.""" """Write 'text' word-wrapped at self.width characters."""
leading_space = " " * indent leading_space = " " * indent
while len(leading_space) + len(text) > self.width: while len(leading_space) + len(text) > self.width:
@ -187,19 +210,21 @@ class Writer(object):
self.output.write(leading_space + text + "\n") self.output.write(leading_space + text + "\n")
def close(self): def close(self) -> None:
self.output.close() self.output.close()
def as_list(input): def serialize_path(input: Optional[NinjaPath]) -> str:
if input is None: return str(input).replace("\\", "/") if input else ""
return []
def serialize_paths(input: Optional[NinjaPathOrPaths]) -> List[str]:
if isinstance(input, list): if isinstance(input, list):
return input return [serialize_path(path) for path in input if path]
return [input] return [serialize_path(input)] if input else []
def escape(string): def escape(string: str) -> str:
"""Escape a string such that it can be embedded into a Ninja file without """Escape a string such that it can be embedded into a Ninja file without
further interpretation.""" further interpretation."""
assert "\n" not in string, "Ninja syntax does not allow newlines" assert "\n" not in string, "Ninja syntax does not allow newlines"
@ -207,17 +232,17 @@ def escape(string):
return string.replace("$", "$$") return string.replace("$", "$$")
def expand(string, vars, local_vars={}): def expand(string: str, vars: Dict[str, str], local_vars: Dict[str, str] = {}) -> str:
"""Expand a string containing $vars as Ninja would. """Expand a string containing $vars as Ninja would.
Note: doesn't handle the full Ninja variable syntax, but it's enough Note: doesn't handle the full Ninja variable syntax, but it's enough
to make configure.py's use of it work. to make configure.py's use of it work.
""" """
def exp(m): def exp(m: Match[str]) -> str:
var = m.group(1) var = m.group(1)
if var == "$": if var == "$":
return "$" return "$"
return local_vars.get(var, vars.get(var, "")) return local_vars.get(var, vars.get(var, ""))
return re.sub(r"\$(\$|\w*)", exp, string) return re.sub(r"\$(\$|\w*)", exp, string)

File diff suppressed because it is too large Load diff

View file

@ -25,7 +25,7 @@ def in_wsl() -> bool:
return "microsoft-standard" in uname().release return "microsoft-standard" in uname().release
def import_d_file(in_file) -> str: def import_d_file(in_file: str) -> str:
out_text = "" out_text = ""
with open(in_file) as file: with open(in_file) as file:
@ -60,7 +60,7 @@ def import_d_file(in_file) -> str:
return out_text return out_text
def main(): def main() -> None:
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="""Transform a .d file from Wine paths to normal paths""" description="""Transform a .d file from Wine paths to normal paths"""
) )
@ -81,4 +81,4 @@ def main():
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View file

@ -51,7 +51,7 @@ if __name__ == "__main__":
args = parser.parse_args() args = parser.parse_args()
api_key = args.api_key or os.environ.get("PROGRESS_API_KEY") api_key = args.api_key or os.environ.get("PROGRESS_API_KEY")
if not api_key: if not api_key:
raise "API key required" raise KeyError("API key required")
url = generate_url(args) url = generate_url(args)
entries = [] entries = []
@ -68,9 +68,12 @@ if __name__ == "__main__":
print("Publishing entry to", url) print("Publishing entry to", url)
json.dump(entries[0], sys.stdout, indent=4) json.dump(entries[0], sys.stdout, indent=4)
print() print()
r = requests.post(url, json={ r = requests.post(
"api_key": api_key, url,
"entries": entries, json={
}) "api_key": api_key,
"entries": entries,
},
)
r.raise_for_status() r.raise_for_status()
print("Done!") print("Done!")