mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-03 21:23:43 +02:00
Use the slangc binary instead trying to get the C++ extension working everywhere is too fragile
This commit is contained in:
18
.github/workflows/ci.py
vendored
18
.github/workflows/ci.py
vendored
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -1,194 +0,0 @@
|
||||
/*
|
||||
* compiler.cpp
|
||||
* Copyright (C) 2026 Kovid Goyal <kovid at kovidgoyal.net>
|
||||
*
|
||||
* Distributed under terms of the GPL3 license.
|
||||
*/
|
||||
|
||||
#include <Python.h>
|
||||
#if __has_include("shader-slang/slang.h")
|
||||
#include <shader-slang/slang.h>
|
||||
#include <shader-slang/slang-com-ptr.h>
|
||||
#include <shader-slang/slang-com-helper.h>
|
||||
#else
|
||||
#include <slang.h>
|
||||
#include <slang-com-ptr.h>
|
||||
#include <slang-com-helper.h>
|
||||
#endif
|
||||
#include <string>
|
||||
|
||||
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<IGlobalSession> 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<char**>(kw), &enable_glsl_input)) return NULL;
|
||||
self = reinterpret_cast<GlobalSession *>(type->tp_alloc(type, 0));
|
||||
ScopedPyObject ans(reinterpret_cast<PyObject*>(self));
|
||||
if (self != NULL) {
|
||||
self->ptr = nullptr;
|
||||
SlangGlobalSessionDesc desc = {.enableGLSL=static_cast<bool>(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);
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
class Error(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class GlobalSession:
|
||||
def __init__(self, enable_glsl_input: bool = False): ...
|
||||
@@ -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:
|
||||
|
||||
20
setup.py
20
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)
|
||||
|
||||
Reference in New Issue
Block a user