dev command helper

This commit is contained in:
2026-09-06 22:19:32 -07:00
parent aa7c0181f5
commit 659d6010e3
2 changed files with 191 additions and 0 deletions
Executable
+188
View File
@@ -0,0 +1,188 @@
#!/usr/bin/env python3
import os
import re
import shutil
import struct
import subprocess
import sys
COMMAND_TO_METHOD = {
"bash-completions": "bash_completions",
}
METHOD_TO_COMMAND = {method: command for command, method in COMMAND_TO_METHOD.items()}
WAIT_FOR_PROCESS = "wait_for_process"
class DevHelper:
SHADER_EXTENSIONS = (".vert", ".frag", ".comp", ".geom", ".tesc", ".tese")
def __init__(self):
self.last_exit_status = 0
# Core logic
def call(self, *args, **kwargs):
if not args:
return
command, rest = args[0], args[1:]
method_name = COMMAND_TO_METHOD.get(command, command)
method = getattr(self, method_name, None)
if callable(method) and not method_name.startswith("_") and method_name != "call":
return method(*rest, **kwargs)
# Primary command wrappers
def build(self, *args, **kwargs):
command = f"rm -rf build && cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug"
self._exec_in_root(command, replace=True)
def shaders(self, *args, **kwargs):
shaders_dir = os.path.join(self._project_root(), "engine", "src", "shaders")
if not os.path.isdir(shaders_dir):
raise ValueError(f"Shaders directory not found: {shaders_dir}")
glslc = self._find_glslc()
shader_paths = sorted(
os.path.join(root, filename)
for root, _dirs, files in os.walk(shaders_dir)
for filename in files
if filename.endswith(self.SHADER_EXTENSIONS)
)
if not shader_paths:
print(f"No shaders found in {shaders_dir}")
return
for shader_path in shader_paths:
self._generate_shader_header(glslc, shader_path)
# Custom helpers
def ownit(self, *args, **kwargs):
file_or_directory = args[0] if args else None
if file_or_directory is None:
raise ValueError("Must provide a file or directory path.")
if sys.platform.startswith("linux"):
print(f"Take ownership of the file or directory? {file_or_directory}")
command = f"sudo chown -R {self._user_id()}:{self._group_id()} {file_or_directory}"
self._exec(command, replace=True)
else:
raise NotImplementedError(f"Not implemented for platform {sys.platform}")
def bash_completions(self, *args, **kwargs):
completions = sorted(
METHOD_TO_COMMAND.get(name, name)
for name in vars(DevHelper)
if not name.startswith("_") and name != "call" and callable(getattr(DevHelper, name))
)
print(" ".join(completions))
# Private helpers
def _find_glslc(self):
glslc = shutil.which("glslc")
if glslc is None:
raise RuntimeError(
"glslc not found on PATH (install the Vulkan SDK)"
)
return glslc
def _generate_shader_header(self, glslc, shader_path):
relative_path = os.path.relpath(shader_path, self._project_root())
print(f"Compiling shader: {relative_path}")
result = subprocess.run(
[glslc, shader_path, "-o", "-"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
if result.returncode != 0:
raise RuntimeError(
f"failed to compile {shader_path}: {result.stderr.decode()}"
)
spirv = result.stdout
if len(spirv) % 4 != 0:
raise RuntimeError(
f"invalid SPIR-V output for {shader_path} (size not a multiple of 4)"
)
word_count = len(spirv) // 4
words = struct.unpack(f"<{word_count}I", spirv)
filename = os.path.basename(shader_path)
variable_name = f"{filename.replace('.', '_')}_spv"
header_path = shader_path + ".spv.h"
body_lines = [
" " + ", ".join(f"0x{word:08x}" for word in words[i:i + 4]) + ","
for i in range(0, len(words), 4)
]
header = "\n".join([
"#pragma once",
"",
"#include <array>",
"#include <cstdint>",
"",
"namespace sophia::shaders {",
"",
f"inline constexpr std::array<uint32_t, {word_count}> {variable_name} = {{",
*body_lines,
"};",
"",
"} // namespace sophia::shaders",
"",
])
with open(header_path, "w") as f:
f.write(header)
print(f"Wrote {os.path.relpath(header_path, self._project_root())} "
f"({word_count * 4} bytes)")
def _wait_for_process_with_logging(self, command):
process = self._exec(command, capture=True)
for line in process.stdout:
print(line, end="")
process.wait()
self.last_exit_status = process.returncode
def _exec_in_root(self, command, **kwargs):
return self._exec(f"cd {self._project_root()} && {command}", **kwargs)
def _exec(self, command, *, replace=False, capture=False):
print(f"Running: {command}")
if replace:
os.execv("/bin/sh", ["/bin/sh", "-c", command])
if capture:
return subprocess.Popen(
["/bin/sh", "-c", f"{command} 2>&1"],
stdout=subprocess.PIPE,
text=True,
)
return subprocess.run(["/bin/sh", "-c", command])
def _project_root(self):
return os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
def _take_over_needed(self, file_or_directory):
result = subprocess.run(
["/bin/sh", "-c", f"find {file_or_directory} -not -user {self._user_id()} -print -quit | grep -q ."],
)
return result.returncode == 0
def _user_id(self):
if not sys.platform.startswith("linux"):
raise NotImplementedError(f"Not implemented for platform {sys.platform}")
return subprocess.run(["id", "-u"], stdout=subprocess.PIPE, text=True).stdout.strip()
def _group_id(self):
if not sys.platform.startswith("linux"):
raise NotImplementedError(f"Not implemented for platform {sys.platform}")
return subprocess.run(["id", "-g"], stdout=subprocess.PIPE, text=True).stdout.strip()
if __name__ == "__main__":
DevHelper().call(*sys.argv[1:])