From 40b115bea2ac375e134c3fc99928c8d7812eccab Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 25 Jun 2026 09:35:05 +0530 Subject: [PATCH] Use the slangc binary instead trying to get the C++ extension working everywhere is too fragile --- .github/workflows/ci.py | 18 +++- kitty/constants.py | 2 + kitty/shaders/compiler.cpp | 194 ------------------------------------- kitty/slangc.pyi | 6 -- kitty_tests/check_build.py | 5 +- setup.py | 20 ---- 6 files changed, 21 insertions(+), 224 deletions(-) delete mode 100644 kitty/shaders/compiler.cpp delete mode 100644 kitty/slangc.pyi diff --git a/.github/workflows/ci.py b/.github/workflows/ci.py index ea3c830d2..637c74a3c 100644 --- a/.github/workflows/ci.py +++ b/.github/workflows/ci.py @@ -25,6 +25,7 @@ is_codeql = os.environ.get('KITTY_CODEQL') == '1' is_macos = 'darwin' in sys.platform.lower() running_under_sanitizer = os.environ.get('KITTY_SANITIZE') == '1' SW = '' +SLANG_INSTALL_DIR = '/tmp/slang' def do_print_crash_reports() -> None: @@ -131,7 +132,7 @@ def install_slang_compiler() -> None: if url is None: raise SystemExit(f'Could not find slang release asset: {asset_name}') - install_dir = '/tmp/slang' + install_dir = SLANG_INSTALL_DIR os.makedirs(install_dir, exist_ok=True) data = download_with_retry(url) with tarfile.open(fileobj=io.BytesIO(data), mode='r:gz') as tf: @@ -187,12 +188,27 @@ def build_kitty() -> None: run(cmd) +def add_to_path(path: str, prepend: bool = False) -> None: + if existing := os.environ.get('PATH') or '': + parts = existing.split(os.pathsep) + parts.insert(0 if prepend else len(parts), path) + seen = set() + ans = [] + for x in parts: + if x not in seen: + seen.add(x) + ans.append(x) + path = os.pathsep.join(ans) + os.environ['PATH'] = path + + def test_kitty() -> None: if is_macos: run('ulimit -c unlimited') run('sudo chmod -R 777 /cores') if running_under_sanitizer: os.environ['MallocNanoZone'] = '0' + add_to_path(os.path.join(SW if is_bundle else SLANG_INSTALL_DIR, 'bin')) run('./test.py', print_crash_reports=True) diff --git a/kitty/constants.py b/kitty/constants.py index 9cb05de14..52756546d 100644 --- a/kitty/constants.py +++ b/kitty/constants.py @@ -35,6 +35,8 @@ kitty_run_data: dict[str, Any] = getattr(sys, 'kitty_run_data', {}) launched_by_launch_services = kitty_run_data.get('launched_by_launch_services', False) is_quick_access_terminal_app = kitty_run_data.get('is_quick_access_terminal_app', False) unserialize_launch_flag = 'kitty-unserialize-data=' +slangc = ['slang'] + if getattr(sys, 'frozen', False): extensions_dir: str = kitty_run_data['extensions_dir'] diff --git a/kitty/shaders/compiler.cpp b/kitty/shaders/compiler.cpp deleted file mode 100644 index ff811ca10..000000000 --- a/kitty/shaders/compiler.cpp +++ /dev/null @@ -1,194 +0,0 @@ -/* - * compiler.cpp - * Copyright (C) 2026 Kovid Goyal - * - * Distributed under terms of the GPL3 license. - */ - -#include -#if __has_include("shader-slang/slang.h") -#include -#include -#include -#else -#include -#include -#include -#endif -#include - -using namespace slang; - -class ScopedPyObject { // {{{ -private: - PyObject* ptr; - -public: - // Default constructor - ScopedPyObject() : ptr(nullptr) {} - - // Constructor that takes ownership of a PyObject* - // Set 'is_strong_ref' to false if you are passing a borrowed reference - explicit ScopedPyObject(PyObject* p, bool is_strong_ref = true) : ptr(p) { - if (!is_strong_ref && ptr) { - Py_INCREF(ptr); - } - } - - // Destructor automatically decrements the reference count - ~ScopedPyObject() { - Py_XDECREF(ptr); - } - - // Delete copy operations to prevent accidental double-decref - ScopedPyObject(const ScopedPyObject&) = delete; - ScopedPyObject& operator=(const ScopedPyObject&) = delete; - - // Move constructor transfers ownership - ScopedPyObject(ScopedPyObject&& other) noexcept : ptr(other.ptr) { - other.ptr = nullptr; - } - - // Move assignment operator - ScopedPyObject& operator=(ScopedPyObject&& other) noexcept { - if (this != &other) { - Py_XDECREF(ptr); - ptr = other.ptr; - other.ptr = nullptr; - } - return *this; - } - - // Allow assignment directly from a raw PyObject* (assumes strong reference) - ScopedPyObject& operator=(PyObject* p) { - Py_XDECREF(ptr); - ptr = p; - return *this; - } - - // Smart pointer operators - PyObject* get() const { return ptr; } - PyObject* operator->() const { return ptr; } - explicit operator bool() const { return ptr != nullptr; } - - // Release ownership without changing the ref count (returns raw pointer) - PyObject* release() { - PyObject* temp = ptr; - ptr = nullptr; - return temp; - } - - // Safely reset to a new raw pointer - void reset(PyObject* p = nullptr, bool is_strong_ref = true) { - Py_XDECREF(ptr); - ptr = p; - if (!is_strong_ref && ptr) { - Py_INCREF(ptr); - } - } -}; // }}} - -typedef struct GlobalSession { - PyObject_HEAD - - Slang::ComPtr ptr; -} GlobalSession; - -static std::string -get_slang_result_string(SlangResult result) { - switch (result) { - case SLANG_OK: return "SLANG_OK: Operation succeeded."; - case SLANG_FAIL: return "SLANG_FAIL: Generic operational failure."; - case SLANG_E_NOT_AVAILABLE: return "SLANG_E_NOT_AVAILABLE: The requested feature or interface is not available."; - case SLANG_E_NOT_IMPLEMENTED: return "SLANG_E_NOT_IMPLEMENTED: The feature has not been implemented."; - case SLANG_E_INVALID_ARG: return "SLANG_E_INVALID_ARG: One or more arguments are invalid."; - case SLANG_E_OUT_OF_MEMORY: return "SLANG_E_OUT_OF_MEMORY: The compiler ran out of memory."; - case SLANG_E_BUFFER_TOO_SMALL: return "SLANG_E_BUFFER_TOO_SMALL: The destination buffer is too small to hold the data."; - case SLANG_E_UNINITIALIZED: return "SLANG_E_UNINITIALIZED: A component or object was used without initialization."; - case SLANG_E_TIME_OUT: return "SLANG_E_TIME_OUT: The compilation or operation timed out."; - // Internal status codes often returned by compile steps - default: - if (result < 0) return "SLANG_ERROR_UNKNOWN: Code (0x" + std::to_string(result) + ")"; - return "SLANG_STATUS_UNKNOWN: Code (0x" + std::to_string(result) + ")"; - } -} - -static PyObject *Error = nullptr; - -static void -set_python_error(SlangResult r, const char *msg, PyObject *exc_class = nullptr) { - if (exc_class == nullptr) exc_class = Error; - PyErr_Format(exc_class, "%s: %s", msg, get_slang_result_string(r).c_str()); -} - -static PyObject* -new_gs(PyTypeObject *type, PyObject *args, PyObject *kwds) { - GlobalSession *self; - static const char* kw[] = {"enable_glsl_input", NULL}; - int enable_glsl_input = 0; - if (args && !PyArg_ParseTupleAndKeywords(args, kwds, "|p", const_cast(kw), &enable_glsl_input)) return NULL; - self = reinterpret_cast(type->tp_alloc(type, 0)); - ScopedPyObject ans(reinterpret_cast(self)); - if (self != NULL) { - self->ptr = nullptr; - SlangGlobalSessionDesc desc = {.enableGLSL=static_cast(enable_glsl_input)}; - SlangResult result = createGlobalSession(&desc, self->ptr.writeRef()); - if (SLANG_FAILED(result)) { - set_python_error(result, "failed to create slang global session"); - ans.reset(); - } - } - return ans.release(); -} - -static void -dealloc_gs(GlobalSession* self) { - self->ptr = nullptr; - Py_TYPE(self)->tp_free((PyObject*)self); -} - -static PyMethodDef gs_methods[] = { - {NULL} /* Sentinel */ -}; - - -PyTypeObject GlobalSession_Type = { - PyVarObject_HEAD_INIT(NULL, 0) -}; - -static char doc[] = "Compile shaders"; -static PyMethodDef methods[] = { - {NULL} /* Sentinel */ -}; - -static int -exec_module(PyObject *mod) { - Error = PyErr_NewException("slangc.Error", NULL, NULL); - if (Error == nullptr) return -1; - GlobalSession_Type.tp_name = "slangc.GlobalSession"; - GlobalSession_Type.tp_basicsize = sizeof(GlobalSession); - GlobalSession_Type.tp_dealloc = (destructor)dealloc_gs; - GlobalSession_Type.tp_flags = Py_TPFLAGS_DEFAULT; - GlobalSession_Type.tp_doc = "GlobalSession"; - GlobalSession_Type.tp_methods = gs_methods; - GlobalSession_Type.tp_new = new_gs; - if (PyType_Ready(&GlobalSession_Type) < 0) { return -1; } - if (PyModule_AddObject(mod, "GlobalSession", (PyObject *)&GlobalSession_Type) != 0) return -1; - Py_INCREF(&GlobalSession_Type); - if (PyModule_AddObject(mod, "Error", Error) < 0) { return -1; } - Py_INCREF(Error); - return 0; -} - -static PyModuleDef_Slot slots[] = { {Py_mod_exec, (void*)exec_module}, {0, NULL} }; - -static struct PyModuleDef module_def = {PyModuleDef_HEAD_INIT}; - -PyMODINIT_FUNC -PyInit_slangc(void) { - module_def.m_name = "slangc"; - module_def.m_slots = slots; - module_def.m_doc = doc; - module_def.m_methods = methods; - return PyModuleDef_Init(&module_def); -} diff --git a/kitty/slangc.pyi b/kitty/slangc.pyi deleted file mode 100644 index d74f7d964..000000000 --- a/kitty/slangc.pyi +++ /dev/null @@ -1,6 +0,0 @@ -class Error(Exception): - pass - - -class GlobalSession: - def __init__(self, enable_glsl_input: bool = False): ... diff --git a/kitty_tests/check_build.py b/kitty_tests/check_build.py index 7d8e7b8e3..f03bb3666 100644 --- a/kitty_tests/check_build.py +++ b/kitty_tests/check_build.py @@ -19,7 +19,7 @@ from . import BaseTest class TestBuild(BaseTest): def test_exe(self) -> None: - from kitty.constants import kitten_exe, kitty_exe, str_version + from kitty.constants import kitten_exe, kitty_exe, slangc, str_version exe = kitty_exe() self.assertTrue(os.access(exe, os.X_OK)) self.assertTrue(os.path.isfile(exe)) @@ -28,12 +28,11 @@ class TestBuild(BaseTest): self.assertTrue(os.access(exe, os.X_OK)) self.assertTrue(os.path.isfile(exe)) self.assertIn(str_version, subprocess.check_output([exe, '--version']).decode()) + self.assertTrue(shutil.which(slangc[0]), f'slang compiler not found on PATH: {slangc[0]}') def test_loading_extensions(self) -> None: import kitty.fast_data_types as fdt from kittens.transfer import rsync - from kitty.slangc import GlobalSession - GlobalSession() del fdt, rsync def test_loading_shaders(self) -> None: diff --git a/setup.py b/setup.py index d79733e07..0f0a7edd4 100755 --- a/setup.py +++ b/setup.py @@ -630,22 +630,6 @@ def init_env( return ans -def slang_env(args: Options) -> Env: - ans = env.copy() - cflags = [] - for x in ans.cflags: - if x == '-Wstrict-prototypes': - continue - if x.startswith('-std='): - x = '-std=c++20' - cflags.append(x) - cflags[:0] = pkg_config('slang-compiler', '--cflags-only-I') - pylib = get_python_flags(args, cflags) - ans.cflags = cflags - ans.ldflags = ['-lstdc++'] + pylib + ans.ldflags + pkg_config('slang-compiler', '--libs') - return ans - - def kitty_env(args: Options) -> Env: ans = env.copy() cflags = ans.cflags @@ -1232,10 +1216,6 @@ def build(args: Options, native_optimizations: bool = True, call_init: bool = Tr kitty_env(args), 'kitty/fast_data_types', args.compilation_database, sources, headers, build_dsym=args.build_dsym, ) - sources, headers = ['kitty/shaders/compiler.cpp'], [] - compile_c_extension( - slang_env(args), 'kitty/slangc', args.compilation_database, sources, headers, build_dsym=args.build_dsym - ) compile_glfw(args.compilation_database, args.build_dsym) compile_kittens(args) add_builtin_fonts(args)