libtrx: update; migrate tools

This commit is contained in:
Marcin Kurczewski 2024-04-30 00:56:52 +02:00
parent 418e2b8803
commit 6442709c73
25 changed files with 74 additions and 611 deletions

View File

@ -15,21 +15,6 @@ jobs:
submodules: 'true'
fetch-depth: 0
- name: Check JSON files validity
shell: python
run: |
import json
from pathlib import Path
errors = False
for path in Path('.').rglob('**/*.json'):
try:
json.loads(path.read_text())
except json.JSONDecodeError as ex:
print(f'Malformed JSON in {path}: {ex}')
errors = True
if errors:
exit(1)
- name: Install dependencies
run: |
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -

View File

@ -10,6 +10,7 @@
#include "util.h"
#include <libtrx/log.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>

View File

@ -1,6 +1,7 @@
#include "inject_util.h"
#include <libtrx/log.h>
#include <windows.h>
void InjectImpl(bool enable, void (*from)(void), void (*to)(void))

View File

@ -1,9 +1,10 @@
#include "inject_exec.h"
#include "lib/winmm.h"
#include <SDL2/SDL.h>
#include <libtrx/filesystem.h>
#include <libtrx/log.h>
#include <SDL2/SDL.h>
#include <stdio.h>
#include <windows.h>

View File

@ -5,9 +5,10 @@
#include "global/vars.h"
#include "lib/winmm.h"
#include <inttypes.h>
#include <libtrx/filesystem.h>
#include <libtrx/log.h>
#include <inttypes.h>
#include <stdarg.h>
#include <stdio.h>

@ -1 +1 @@
Subproject commit b1d1e82e5ce3fd47d746b78209733a11d7ef8996
Subproject commit 7a11c77fbdf10c2f959258d57cc8d11a6cd915dc

View File

@ -1,28 +1,22 @@
#!/usr/bin/env python3
from pathlib import Path
from shared.docker import BaseGameEntrypoint
from libtrx.cli.game_docker_entrypoint import run_script
class WindowsEntrypoint(BaseGameEntrypoint):
BUILD_ROOT = Path("/app/build/win/")
COMPILE_ARGS = [
run_script(
ship_dir=Path("/app/data/ship/"),
build_root=Path("/app/build/win/"),
compile_args=[
"--cross",
"/app/tools/docker/game-win/meson_linux_mingw32.txt",
]
RELEASE_ZIP_SUFFIX = "Windows"
RELEASE_ZIP_FILES = [
(BUILD_ROOT / "TR2X.exe", "TR2X.exe"),
(BUILD_ROOT / "TR2X.dll", "TR2X.dll"),
]
def post_compile(self) -> None:
if self.target == "release":
for path in self.BUILD_ROOT.glob("*.exe"):
self.compress_exe(path)
for path in self.BUILD_ROOT.glob("*.dll"):
self.compress_exe(path)
if __name__ == "__main__":
WindowsEntrypoint().run()
],
release_zip_filename="TR2X-{version}-Windows.zip",
release_zip_files=[
(Path("/app/build/win/TR2X.exe"), "TR2X.exe"),
(Path("/app/build/win/TR2X.dll"), "TR2X.dll"),
],
compressable_exes=[
Path("/app/build/win/TR2X.exe"),
Path("/app/build/win/TR2X.dll"),
],
)

View File

@ -5,12 +5,12 @@ from dataclasses import dataclass
from enum import StrEnum
from pathlib import Path
from shared.common import PROGRESS_FILE, SRC_DIR
from shared.ida_progress import Symbol, parse_progress_file
from tr2x.ida_progress import Symbol, parse_progress_file
from tr2x.paths import TR2X_PROGRESS_FILE, TR2X_SRC_DIR
FUNCS_H_FILE = SRC_DIR / "global/funcs.h"
VARS_H_FILE = SRC_DIR / "global/vars.h"
TYPES_H_FILE = SRC_DIR / "global/types.h"
FUNCS_H_FILE = TR2X_SRC_DIR / "global/funcs.h"
VARS_H_FILE = TR2X_SRC_DIR / "global/vars.h"
TYPES_H_FILE = TR2X_SRC_DIR / "global/types.h"
COMMON_HEADER = [
@ -160,7 +160,7 @@ def make_types_h(types: list[str]) -> None:
def main() -> None:
progress_file = parse_progress_file(PROGRESS_FILE)
progress_file = parse_progress_file(TR2X_PROGRESS_FILE)
make_funcs_h(progress_file.functions)
make_vars_h(progress_file.variables)

View File

@ -1,100 +0,0 @@
#!/usr/bin/env python3
# regenerate the .ICO file from .PSD.
import argparse
import tempfile
from dataclasses import dataclass
from pathlib import Path
from subprocess import check_call
@dataclass
class IconSpec:
size: int
type: str
SPECS = [
IconSpec(size=32, type="bmp"),
IconSpec(size=16, type="bmp"),
IconSpec(size=256, type="png"),
IconSpec(size=128, type="png"),
IconSpec(size=64, type="png"),
IconSpec(size=48, type="png"),
IconSpec(size=32, type="png"),
IconSpec(size=16, type="png"),
]
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument("path", type=Path)
parser.add_argument("-o", "--output", type=Path, required=True)
return parser.parse_args()
def resize_transformer(path: Path, spec: IconSpec) -> None:
check_call(
[
"convert",
f"{path}[0]",
"-filter",
"lanczos",
"-resize",
f"{spec.size}x{spec.size}",
f'PNG:{path}',
]
)
def quantize_transformer(path: Path, spec: IconSpec) -> None:
quantized_path = path.with_stem(f"{path.stem}-quantized")
check_call(["pngquant", path, "--output", quantized_path])
path.write_bytes(quantized_path.read_bytes())
quantized_path.unlink()
def optimize_transformer(path: Path, spec: IconSpec) -> None:
check_call(["zopflipng", "-y", path, path])
def convert_transformer(path: Path, spec: IconSpec) -> None:
if spec.type != "png":
check_call(["convert", path, f'{spec.type.upper()}:{path}'])
TRANSFORMERS = [
resize_transformer,
quantize_transformer,
optimize_transformer,
convert_transformer,
]
def generate_icon(source_path: Path, target_path: Path) -> None:
aux_paths = []
with tempfile.TemporaryDirectory() as tmpdir:
tmp_path = Path(tmpdir)
for spec in SPECS:
aux_path = tmp_path / f"{spec.size}-{spec.type}.tmp"
aux_path.write_bytes(source_path.read_bytes())
for transform in TRANSFORMERS:
transform(aux_path, spec)
aux_paths.append(aux_path)
# NOTE: image order is important for certain software.
check_call(["identify", *aux_paths])
check_call(["convert", *aux_paths, target_path])
def main() -> None:
args = parse_args()
if args.output.exists():
args.output.unlink()
generate_icon(args.path, args.output)
if __name__ == "__main__":
main()

View File

@ -2,7 +2,7 @@
import argparse
from pathlib import Path
from shared.versioning import generate_version
from libtrx.versioning import generate_version
TEMPLATE = """
const char *g_TR2XVersion = "TR2X {version}";

View File

@ -2,8 +2,8 @@
import argparse
from pathlib import Path
from shared.common import DATA_DIR
from shared.versioning import generate_version
from libtrx.versioning import generate_version
from tr2x.paths import TR2X_DATA_DIR
def parse_args() -> argparse.Namespace:
@ -17,7 +17,7 @@ def write_rc_template(
) -> None:
template = input_path.read_text()
template = template.replace("{version}", version)
template = template.replace("{icon_path}", str(DATA_DIR / "icon.ico"))
template = template.replace("{icon_path}", str(TR2X_DATA_DIR / "icon.ico"))
output_path.write_text(template)
@ -27,7 +27,7 @@ def main() -> None:
for output_path in args.output:
write_rc_template(
input_path=DATA_DIR / output_path.name,
input_path=TR2X_DATA_DIR / output_path.name,
output_path=output_path,
version=version,
)

View File

@ -1,25 +1,4 @@
#!/usr/bin/env python3
import argparse
from pathlib import Path
from subprocess import run
from libtrx.versioning import generate_version
from shared.versioning import generate_version
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument("-o", "--output", type=Path)
return parser.parse_args()
def main() -> None:
args = parse_args()
version = generate_version()
if args.output:
args.output.write_text(version)
else:
print(version, end="")
if __name__ == "__main__":
main()
print(generate_version(), end="")

View File

@ -8,8 +8,8 @@ from enum import StrEnum
from pathlib import Path
from typing import Any, Dict, List, Optional
from shared.ida_progress import parse_progress_file, Symbol
from shared.common import PROGRESS_FILE
from tr2x.ida_progress import parse_progress_file, Symbol
from tr2x.paths import TR2X_PROGRESS_FILE
try:
import idaapi
@ -93,7 +93,7 @@ def import_symbols(symbols: list[Symbol]) -> None:
def main():
progress_file = parse_progress_file(PROGRESS_FILE)
progress_file = parse_progress_file(TR2X_PROGRESS_FILE)
with tempfile.TemporaryDirectory() as tmpdir:
tmpdir = Path(tmpdir)

1
tools/libtrx Symbolic link
View File

@ -0,0 +1 @@
../subprojects/libtrx/tools/libtrx

View File

@ -1,28 +1,5 @@
#!/usr/bin/env python3
import re
from pathlib import Path
from libtrx.changelog import get_current_version_changelog
from tr2x.paths import TR2X_REPO_DIR
TOOLS_DIR = Path(__file__).parent
ROOT_DIR = TOOLS_DIR.parent
CHANGELOG_PATH = ROOT_DIR / "CHANGELOG.md"
def get_current_changelog() -> str:
sections = [
section
for section in CHANGELOG_PATH.read_text().split("\n\n")
if re.search(r"- \w", section)
]
if sections:
section = sections[0]
return "\n".join(
line for line in section.splitlines() if not line.startswith("#")
)
def main() -> None:
print(get_current_changelog())
if __name__ == "__main__":
main()
print(get_current_version_changelog(TR2X_REPO_DIR / "CHANGELOG.md"))

View File

@ -1,165 +1,8 @@
#!/usr/bin/env python3
import argparse
import re
import sys
from datetime import datetime
from pathlib import Path
from subprocess import check_output, run
from libtrx.cli.release import run_script
from tr2x.paths import TR2X_REPO_DIR
from shared.common import REPO_DIR
from shared.versioning import get_branch_version
HEADER = "## [Unreleased](https://github.com/LostArtefacts/TR2X/compare/stable...develop) - ××××-××-××"
CHANGELOG_PATH = REPO_DIR / "CHANGELOG.md"
def update_changelog(
changelog: str, old_version: str, new_version: str
) -> str:
if f"[{new_version}]" in changelog:
return changelog
changelog = re.sub("Unreleased", new_version, changelog, count=1)
changelog = re.sub("stable", old_version, changelog, count=1)
changelog = re.sub("develop", new_version, changelog, count=1)
changelog = re.sub(
"××××-××-××", datetime.now().strftime("%Y-%m-%d"), changelog
)
changelog = HEADER + "\n\n" + changelog
return changelog
class Git:
def checkout_branch(self, branch_name: str) -> None:
if check_output(["git", "diff", "--cached", "--name-only"]):
raise RuntimeError("Staged files")
check_output(["git", "checkout", branch_name])
def reset(self, target: str, hard: bool = False) -> None:
check_output(
["git", "reset", "develop", *(["--hard"] if hard else [])]
)
def delete_tag(self, tag_name: str) -> None:
run(["git", "tag", "-d", tag_name])
def create_tag(self, tag_name: str) -> None:
check_output(["git", "tag", tag_name])
def add(self, target: str) -> None:
check_output(["git", "add", target])
def commit(self, message: str) -> None:
check_output(["git", "commit", "-m", message])
def push(
self, upstream: str, targets: list[str], force: bool = False
) -> None:
check_output(
[
"git",
"push",
upstream,
*targets,
*(["--force-with-lease"] if force else []),
]
)
class BaseCommand:
name: str = NotImplemented
help: str = NotImplemented
def __init__(self, git: Git) -> None:
self.git = git
def decorate_parser(self, parser: argparse.ArgumentParser) -> None:
parser.add_argument("version")
def run(self, args: argparse.Namespace) -> None:
raise NotImplementedError("not implemented")
class CommitCommand(BaseCommand):
name = "commit"
help = "Create and tag a commit with the release information"
def decorate_parser(self, parser: argparse.ArgumentParser) -> None:
super().decorate_parser(parser)
parser.add_argument(
"-d",
"--dry-run",
action='store_true',
help="only output the changelog to stdout, do not commit anything",
)
def run(self, args: argparse.Namespace) -> None:
self.git.checkout_branch("develop")
old_version = get_branch_version("origin/stable")
new_version = args.version
old_changelog = CHANGELOG_PATH.read_text()
new_changelog = update_changelog(
old_changelog, old_version, args.version
)
if old_changelog == new_changelog:
return
if args.dry_run:
print(new_changelog)
return
CHANGELOG_PATH.write_text(new_changelog)
self.git.add(CHANGELOG_PATH)
self.git.commit(f"docs: release {args.version}")
self.git.delete_tag(args.version)
self.git.create_tag(args.version)
class BranchCommand(BaseCommand):
name = "branch"
help = "Merge branch to the specified tag"
def run(self, args: argparse.Namespace) -> None:
self.git.checkout_branch("stable")
self.git.reset(args.version, hard=True)
self.git.checkout_branch("develop")
class PushCommand(BaseCommand):
name = "push"
help = (
"Push the develop and stable branches, and the version tag to GitHub"
)
def run(self, args) -> None:
self.git.push(
"origin", ["develop", "stable", args.version], force=True
)
def parse_args(commands: list[BaseCommand]) -> None:
parser = argparse.ArgumentParser(
description="Argument parser with subcommands"
)
subparsers = parser.add_subparsers(title="subcommands", dest="subcommand")
for command in commands:
subparser = subparsers.add_parser(command.name, help=command.help)
command.decorate_parser(subparser)
subparser.set_defaults(command=command)
result = parser.parse_args()
if not hasattr(result, "command"):
parser.error("missing command")
return result
def main() -> None:
git = Git()
commands = [
command_cls(git) for command_cls in BaseCommand.__subclasses__()
]
args = parse_args(commands)
args.command.run(args)
main()
run_script(
project_name="TR2X",
changelog_path=TR2X_REPO_DIR / "CHANGELOG.md",
)

View File

@ -473,7 +473,8 @@ class FunctionTreeGrid(Container):
// GRID_MAX_SQUARES
)
* (GRID_SQUARE_SIZE + GRID_SQUARE_MARGIN)
) + GRID_SQUARE_MARGIN,
)
+ GRID_SQUARE_MARGIN,
)
for result in squarify(

View File

@ -1,9 +0,0 @@
from pathlib import Path
TOOLS_DIR = Path(__file__).parent.parent
REPO_DIR = TOOLS_DIR.parent
DOCS_DIR = REPO_DIR / "docs"
DATA_DIR = REPO_DIR / "data"
SRC_DIR = REPO_DIR / "src"
PROGRESS_FILE = DOCS_DIR / "progress.txt"

View File

@ -1,91 +0,0 @@
import argparse
import os
from pathlib import Path
from subprocess import check_call, run
from shared.common import DATA_DIR
from shared.packaging import create_zip
from shared.versioning import generate_version
SHIP_DIR = DATA_DIR / "ship"
class BaseGameEntrypoint:
BUILD_ROOT: Path = ...
COMPILE_ARGS: list[str] = ...
STRIP_TOOL = "strip"
UPX_TOOL = "upx"
RELEASE_ZIP_SUFFIX: str = ...
RELEASE_ZIP_FILES: list[tuple[Path, str]] = ...
def __init__(self) -> None:
self.target = os.environ.get("TARGET", "debug")
def run(self) -> None:
args = self.parse_args()
args.func(args)
def parse_args(self) -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Docker entrypoint")
subparsers = parser.add_subparsers(dest="action", help="Subcommands")
compile_parser = subparsers.add_parser(
"compile", help="Compile action"
)
compile_parser.set_defaults(func=self.compile)
package_parser = subparsers.add_parser(
"package", help="Package action"
)
package_parser.add_argument("-o", "--output", type=Path)
package_parser.set_defaults(func=self.package)
args = parser.parse_args()
if not hasattr(args, "func"):
args.action = "compile"
args.func = self.compile
return args
def compile(self, args: argparse.Namespace) -> None:
pkg_config_path = os.environ.get("PKG_CONFIG_PATH")
if not Path("/app/build/linux/build.jinja").exists():
command = [
"meson",
"--buildtype",
self.target,
*self.COMPILE_ARGS,
self.BUILD_ROOT,
]
if pkg_config_path:
command.extend(["--pkg-config-path", pkg_config_path])
check_call(command)
check_call(["meson", "compile"], cwd=self.BUILD_ROOT)
self.post_compile()
def post_compile(self) -> None:
pass
def compress_exe(self, path: Path) -> None:
if run([self.UPX_TOOL, "-t", str(path)]).returncode != 0:
check_call([self.STRIP_TOOL, str(path)])
check_call([self.UPX_TOOL, str(path)])
def package(self, args: argparse.Namespace) -> None:
if args.output:
zip_path = args.output
else:
version = generate_version()
zip_path = Path(f"TR2X-{version}-{self.RELEASE_ZIP_SUFFIX}.zip")
source_files = [
*[
(path, path.relative_to(SHIP_DIR))
for path in SHIP_DIR.rglob("*")
if path.is_file()
],
*self.RELEASE_ZIP_FILES,
]
create_zip(zip_path, source_files)
print(f"Created {zip_path}")

View File

@ -1,15 +0,0 @@
import sys
import zipfile
from collections.abc import Iterable
from pathlib import Path
def create_zip(
output_path: Path, source_files: Iterable[tuple[Path, str]]
) -> None:
with zipfile.ZipFile(output_path, "w") as handle:
for source_path, archive_name in source_files:
if not source_path.exists():
print(f"WARNING: {source_path} does not exist", file=sys.stderr)
continue
handle.write(source_path, archive_name)

View File

@ -1,27 +0,0 @@
from subprocess import run
from shared.common import SRC_DIR
def get_branch_version(branch: str | None) -> str:
return run(
[
"git",
"describe",
*([branch] if branch else ["--dirty"]),
"--always",
"--abbrev=7",
"--tags",
"--exclude",
"latest",
],
cwd=SRC_DIR,
text=True,
capture_output=True,
check=False,
).stdout.strip()
def generate_version() -> str:
version = get_branch_version(None)
return version or "?"

View File

@ -1,104 +1,16 @@
#!/usr/bin/env python3
import argparse
import re
from pathlib import Path
from shutil import which
from subprocess import run
from libtrx.cli.sort_imports import run_script
from tr2x.paths import TR2X_REPO_DIR, TR2X_SRC_DIR
TOOLS_DIR = Path(__file__).parent
REPO_DIR = TOOLS_DIR.parent
SRC_PATH = REPO_DIR / "src"
def fix_imports(path: Path) -> None:
iwyu_result = run(
["include-what-you-use", "-I", "src", path],
capture_output=True,
text=True,
).stderr
run(
[which("fix_include") and "fix_include" or "iwyu-fix-includes"],
input=iwyu_result,
text=True,
)
def sort_import_group(includes: list[str]) -> list[str]:
group = sorted(includes)
FIXED_PAIRS = [
("<ddrawi.h>", "<d3dhal.h>"),
]
for before, after in FIXED_PAIRS:
if before in group and after in group:
group.remove(after)
group.insert(group.index(before) + 1, after)
return group
def sort_imports(path: Path) -> None:
source = path.read_text()
rel_path = path.relative_to(SRC_PATH)
own_include = str(rel_path.with_suffix(".h"))
own_include = {
# files headers of which are not a 1:1 match with their filename
}.get(str(rel_path), own_include)
def cb(match):
includes = re.findall(r'#include (["<][^"<>]+[">])', match.group(0))
groups = {
"own": set(),
"proj": set(),
"lib": set(),
}
for include in includes:
if include.strip('"') == own_include:
groups["own"].add(include)
elif include.startswith("<"):
groups["lib"].add(include)
else:
groups["proj"].add(include)
groups = {key: value for key, value in groups.items() if value}
ret = "\n\n".join(
"\n".join(
f"#include {include}" for include in sort_import_group(group)
)
for group in groups.values()
).strip()
return ret
source = re.sub(
"^#include [^\n]+(\n*#include [^\n]+)*",
cb,
source,
flags=re.M,
)
if source != path.read_text():
path.write_text(source)
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument(metavar="path", type=Path, nargs="*", dest="paths")
return parser.parse_args()
def main() -> None:
args = parse_args()
paths = [path.absolute() for path in args.paths]
if not paths:
paths = sorted(
path
for path in SRC_PATH.glob("**/*.[ch]")
if path != SRC_PATH / "init.c"
)
for path in paths:
fix_imports(path)
sort_imports(path)
if __name__ == "__main__":
main()
run_script(
root_dir=TR2X_SRC_DIR,
include_dirs=[
TR2X_SRC_DIR,
TR2X_REPO_DIR / "build/linux",
TR2X_REPO_DIR / "build/windows",
],
system_include_dirs=[TR2X_REPO_DIR / "subprojects/libtrx/include"],
own_include_map={},
fix_map={},
forced_order=["<ddrawi.h>", "<d3dhal.h>"],
)

9
tools/tr2x/paths.py Normal file
View File

@ -0,0 +1,9 @@
from pathlib import Path
TR2X_TOOLS_DIR = Path(__file__).parent.parent
TR2X_REPO_DIR = TR2X_TOOLS_DIR.parent
TR2X_DOCS_DIR = TR2X_REPO_DIR / "docs"
TR2X_DATA_DIR = TR2X_REPO_DIR / "data"
TR2X_SRC_DIR = TR2X_REPO_DIR / "src"
TR2X_PROGRESS_FILE = TR2X_DOCS_DIR / "progress.txt"