From 9114bda24ca01de66319235071a2d7db1caae01f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 17 Feb 2021 15:50:53 +0530 Subject: [PATCH] Get the new bypy based freezing process working with linux builds --- bypy/init_env.py | 39 +++++++++++- bypy/linux/__main__.py | 45 ++++++++++---- bypy/site.py | 31 ++++++++++ bypy/sources.json | 10 +++- kitty/check_build.py | 52 ++++++++++++++++ kitty/conf/utils.py | 2 +- kitty/constants.py | 9 ++- launcher.c | 132 +++++++++++++++++++---------------------- setup.py | 26 ++++++-- 9 files changed, 252 insertions(+), 94 deletions(-) create mode 100644 bypy/site.py create mode 100644 kitty/check_build.py diff --git a/bypy/init_env.py b/bypy/init_env.py index dee410601..5384b4f34 100644 --- a/bypy/init_env.py +++ b/bypy/init_env.py @@ -7,12 +7,13 @@ import re import shutil import subprocess import sys +import tempfile from contextlib import suppress from bypy.constants import ( LIBDIR, PREFIX, PYTHON, SRC as KITTY_DIR, ismacos, worker_env ) -from bypy.utils import run_shell +from bypy.utils import run_shell, walk def read_src_file(name): @@ -43,8 +44,34 @@ def run(*args, **extra_env): return subprocess.call(list(args), env=env, cwd=cwd) +def build_frozen_launcher(extra_include_dirs): + inc_dirs = [f'--extra-include-dirs={x}' for x in extra_include_dirs] + cmd = [PYTHON, 'setup.py', '--prefix', build_frozen_launcher.prefix] + inc_dirs + ['build-frozen-launcher'] + if run(*cmd, cwd=build_frozen_launcher.writeable_src_dir) != 0: + print('Building of frozen kitty launcher failed', file=sys.stderr) + os.chdir(KITTY_DIR) + run_shell() + raise SystemExit('Building of kitty launcher failed') + return build_frozen_launcher.writeable_src_dir + + +def check_build(kitty_exe): + with tempfile.TemporaryDirectory() as tdir: + env = { + 'KITTY_CONFIG_DIRECTORY': os.path.join(tdir, 'conf'), + 'KITTY_CACHE_DIRECTORY': os.path.join(tdir, 'cache') + } + [os.mkdir(x) for x in env.values()] + if subprocess.call([kitty_exe, '+runpy', 'from kitty.check_build import main; main()'], env=env) != 0: + print('Checking of kitty build failed', file=sys.stderr) + os.chdir(os.path.dirname(kitty_exe)) + run_shell() + raise SystemExit('Checking of kitty build failed') + + def build_c_extensions(ext_dir, args): writeable_src_dir = os.path.join(ext_dir, 'src') + build_frozen_launcher.writeable_src_dir = writeable_src_dir shutil.copytree( KITTY_DIR, writeable_src_dir, symlinks=True, ignore=shutil.ignore_patterns('b', 'build', 'dist', '*_commands.json', '*.o')) @@ -58,11 +85,19 @@ def build_c_extensions(ext_dir, args): run_shell() raise SystemExit('Building of kitty launcher failed') + for x in walk(writeable_src_dir): + if x.rpartition('.') in ('o', 'so', 'dylib', 'pyd'): + os.unlink(x) cmd = [PYTHON, 'setup.py'] + if run(*cmd, cwd=writeable_src_dir) != 0: + print('Building of kitty failed', file=sys.stderr) + os.chdir(KITTY_DIR) + run_shell() + raise SystemExit('Building of kitty package failed') bundle = 'macos-freeze' if ismacos else 'linux-freeze' cmd.append(bundle) dest = kitty_constants['appname'] + ('.app' if ismacos else '') - dest = os.path.join(ext_dir, dest) + dest = build_frozen_launcher.prefix = os.path.join(ext_dir, dest) cmd += ['--prefix', dest] if run(*cmd, cwd=writeable_src_dir) != 0: print('Building of kitty package failed', file=sys.stderr) diff --git a/bypy/linux/__main__.py b/bypy/linux/__main__.py index 7d95b6f9a..0d1842051 100644 --- a/bypy/linux/__main__.py +++ b/bypy/linux/__main__.py @@ -14,12 +14,14 @@ import time from bypy.constants import ( OUTPUT_DIR, PREFIX, is64bit, python_major_minor_version ) -from bypy.utils import get_dll_path, py_compile, walk +from bypy.freeze import ( + extract_extension_modules, freeze_python, path_to_freeze_dir +) +from bypy.utils import get_dll_path, mkdtemp, py_compile, walk j = os.path.join -self_dir = os.path.dirname(os.path.abspath(__file__)) arch = 'x86_64' if is64bit else 'i686' - +self_dir = os.path.dirname(os.path.abspath(__file__)) py_ver = '.'.join(map(str, python_major_minor_version())) iv = globals()['init_env'] kitty_constants = iv['kitty_constants'] @@ -27,12 +29,8 @@ kitty_constants = iv['kitty_constants'] def binary_includes(): return tuple(map(get_dll_path, ( - 'expat', 'sqlite3', 'ffi', 'z', 'lzma', 'png16', 'lcms2', - - # dont include freetype because fontconfig is closely coupled to it - # and also distros often patch freetype - # 'iconv.so.2', 'pcre.so.1', 'graphite2.so.3', 'glib-2.0.so.0', 'freetype.so.6', - + 'expat', 'sqlite3', 'ffi', 'z', 'lzma', 'png16', 'lcms2', 'crypt', + 'iconv', 'pcre', 'graphite2', 'glib-2.0', 'freetype', 'harfbuzz', 'xkbcommon', 'xkbcommon-x11', 'ncursesw', 'readline', ))) + ( @@ -49,6 +47,7 @@ class Env: self.py_dir = j(self.lib_dir, 'python' + py_ver) os.makedirs(self.py_dir) self.bin_dir = j(self.base, 'bin') + self.obj_dir = mkdtemp('launchers-') def ignore_in_lib(base, items, ignored_dirs=None): @@ -106,10 +105,30 @@ def copy_python(env): shutil.copy2(y, env.py_dir) srcdir = j(srcdir, 'site-packages') - dest = j(env.py_dir, 'site-packages') - import_site_packages(srcdir, dest) + site_packages_dir = j(env.py_dir, 'site-packages') + import_site_packages(srcdir, site_packages_dir) + pdir = os.path.join(env.lib_dir, 'kitty-extensions') + os.makedirs(pdir, exist_ok=True) + kitty_dir = os.path.join(env.base, 'lib', 'kitty') + for x in ('kitty', 'kittens'): + dest = os.path.join(env.py_dir, x) + os.rename(os.path.join(kitty_dir, x), dest) + if x == 'kitty': + shutil.rmtree(os.path.join(dest, 'launcher')) + os.rename(os.path.join(kitty_dir, '__main__.py'), os.path.join(env.py_dir, 'kitty_main.py')) + shutil.rmtree(os.path.join(kitty_dir, '__pycache__')) + print('Extracting extension modules from', env.py_dir, 'to', pdir) + ext_map = extract_extension_modules(env.py_dir, pdir) + shutil.copy(os.path.join(os.path.dirname(self_dir), 'site.py'), os.path.join(env.py_dir, 'site.py')) + for q in walk(os.path.join(env.py_dir, 'kitty')): + if os.path.splitext(q)[1] not in ('.py', '.glsl'): + os.unlink(q) py_compile(env.py_dir) - py_compile(os.path.join(env.base, 'lib', 'kitty')) + freeze_python(env.py_dir, pdir, env.obj_dir, ext_map, develop_mode_env_var='KITTY_DEVELOP_FROM') + + +def build_launcher(env): + iv['build_frozen_launcher']([path_to_freeze_dir(), env.obj_dir]) def is_elf(path): @@ -204,10 +223,12 @@ def main(): env = Env(os.path.join(ext_dir, kitty_constants['appname'])) copy_libs(env) copy_python(env) + build_launcher(env) files = find_binaries(env) fix_permissions(files) if not args.dont_strip: strip_binaries(files) + iv['check_build'](os.path.join(env.base, 'bin', 'kitty')) create_tarfile(env, args.compression_level) diff --git a/bypy/site.py b/bypy/site.py new file mode 100644 index 000000000..29bc65a0c --- /dev/null +++ b/bypy/site.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8 +# License: GPLv3 Copyright: 2021, Kovid Goyal + +import builtins +import sys + +import _sitebuiltins + + +def set_quit(): + eof = 'Ctrl-D (i.e. EOF)' + builtins.quit = _sitebuiltins.Quitter('quit', eof) + builtins.exit = _sitebuiltins.Quitter('exit', eof) + + +def set_helper(): + builtins.help = _sitebuiltins._Helper() + + +def main(): + sys.argv[0] = sys.calibre_basename + set_helper() + set_quit() + mod = __import__(sys.calibre_module, fromlist=[1]) + func = getattr(mod, sys.calibre_function) + return func() + + +if __name__ == '__main__': + main() diff --git a/bypy/sources.json b/bypy/sources.json index f5ee677da..0577c70b6 100644 --- a/bypy/sources.json +++ b/bypy/sources.json @@ -110,7 +110,6 @@ { "name": "xz", - "os": "macos,linux", "unix": { "filename": "xz-5.2.5.tar.gz", "hash": "sha256:f6f4910fd033078738bd82bfba4f49219d03b17eb0794eb91efbae419f4aba10", @@ -118,6 +117,15 @@ } }, + { + "name": "xcrypt", + "unix": { + "filename": "xcrypt-4.4.17.tar.gz", + "hash": "sha256:7665168d0409574a03f7b484682e68334764c29c21ca5df438955a381384ca07", + "urls": ["https://github.com/besser82/libxcrypt/archive/v4.4.17.tar.gz"] + } + }, + { "name": "python", "unix": { diff --git a/kitty/check_build.py b/kitty/check_build.py new file mode 100644 index 000000000..828b96ed5 --- /dev/null +++ b/kitty/check_build.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8 +# License: GPLv3 Copyright: 2021, Kovid Goyal + + +import os +import unittest + + +class TestBuild(unittest.TestCase): + + def test_exe(self) -> None: + from kitty.constants import kitty_exe + exe = kitty_exe() + self.assertTrue(os.access(exe, os.X_OK)) + self.assertTrue(os.path.isfile(exe)) + self.assertIn('kitty', os.path.basename(exe)) + + def test_loading_extensions(self) -> None: + import kitty.fast_data_types as fdt + from kittens.unicode_input import unicode_names + from kittens.choose import subseq_matcher + from kittens.diff import diff_speedup + del fdt, unicode_names, subseq_matcher, diff_speedup + + def test_loading_shaders(self) -> None: + from kitty.utils import load_shaders + for name in 'cell border bgimage tint blit graphics'.split(): + load_shaders(name) + + def test_glfw_modules(self) -> None: + from kitty.constants import is_macos, glfw_path + modules = ('cocoa',) if is_macos else ('x11', 'wayland') + for name in modules: + path = glfw_path(name) + self.assertTrue(os.path.isfile(path)) + self.assertTrue(os.access(path, os.X_OK)) + + def test_all_kitten_names(self) -> None: + from kittens.runner import all_kitten_names + names = all_kitten_names() + self.assertIn('diff', names) + self.assertIn('hints', names) + self.assertGreater(len(names), 8) + + +def main() -> None: + tests = unittest.defaultTestLoader.loadTestsFromTestCase(TestBuild) + r = unittest.TextTestRunner(verbosity=4) + result = r.run(tests) + if result.errors or result.failures: + raise SystemExit(1) diff --git a/kitty/conf/utils.py b/kitty/conf/utils.py index fd853d96a..e1622328f 100644 --- a/kitty/conf/utils.py +++ b/kitty/conf/utils.py @@ -257,7 +257,7 @@ def load_config( try: with open(path, encoding='utf-8', errors='replace') as f: vals = parse_config(f) - except FileNotFoundError: + except (FileNotFoundError, PermissionError): continue ans = merge_configs(ans, vals) if overrides is not None: diff --git a/kitty/constants.py b/kitty/constants.py index ae6675535..0ebfc048e 100644 --- a/kitty/constants.py +++ b/kitty/constants.py @@ -24,10 +24,12 @@ version: Version = Version(0, 19, 3) str_version: str = '.'.join(map(str, version)) _plat = sys.platform.lower() is_macos: bool = 'darwin' in _plat -if hasattr(sys, 'kitty_base_dir'): - kitty_base_dir: str = getattr(sys, 'kitty_base_dir') +if getattr(sys, 'frozen', False): + extensions_dir: str = getattr(sys, 'kitty_extensions_dir') + kitty_base_dir = os.path.join(os.path.dirname(extensions_dir), 'kitty') else: kitty_base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + extensions_dir = os.path.join(kitty_base_dir, 'kitty') @run_once @@ -128,7 +130,8 @@ except KeyError: def glfw_path(module: str) -> str: - return os.path.join(kitty_base_dir, 'kitty', 'glfw-{}.so'.format(module)) + prefix = 'kitty.' if getattr(sys, 'frozen', False) else '' + return os.path.join(extensions_dir, f'{prefix}glfw-{module}.so') def detect_if_wayland_ok() -> bool: diff --git a/launcher.c b/launcher.c index e45c3e38b..a58f7a1bb 100644 --- a/launcher.c +++ b/launcher.c @@ -21,8 +21,8 @@ #endif #include #include +#include -#define MAX_ARGC 1024 #ifndef KITTY_LIB_PATH #define KITTY_LIB_PATH "../.." #endif @@ -41,78 +41,83 @@ safe_realpath(const char* src, char *buf, size_t buf_sz) { } #endif -static inline void -set_xoptions(const wchar_t *exe_dir, const char *lc_ctype, bool from_source) { +static inline bool +set_xoptions(const char *exe_dir_c, const char *lc_ctype, bool from_source) { + wchar_t *exe_dir = Py_DecodeLocale(exe_dir_c, NULL); + if (exe_dir == NULL) { fprintf(stderr, "Fatal error: cannot decode exe_dir: %s\n", exe_dir_c); return false; } wchar_t buf[PATH_MAX+1] = {0}; swprintf(buf, PATH_MAX, L"bundle_exe_dir=%ls", exe_dir); PySys_AddXOption(buf); + PyMem_RawFree(exe_dir); if (from_source) PySys_AddXOption(L"kitty_from_source=1"); if (lc_ctype) { swprintf(buf, PATH_MAX, L"lc_ctype_before_python=%s", lc_ctype); PySys_AddXOption(buf); } + return true; } -#ifdef FOR_BUNDLE -static int -run_embedded(const char* exe_dir_, const char *libpath, int argc, wchar_t **argv, const char *lc_ctype) { - int num; - Py_NoSiteFlag = 1; - Py_FrozenFlag = 1; - Py_IgnoreEnvironmentFlag = 1; - Py_DontWriteBytecodeFlag = 1; - Py_NoUserSiteDirectory = 1; - Py_IsolatedFlag = 1; - Py_SetProgramName(L"kitty"); +typedef struct { + const char *exe, *exe_dir, *lc_ctype, *lib_dir; + char **argv; + int argc; +} RunData; - int ret = 1; - wchar_t *exe_dir = Py_DecodeLocale(exe_dir_, NULL); - if (exe_dir == NULL) { fprintf(stderr, "Fatal error: cannot decode exe_dir\n"); return 1; } - wchar_t stdlib[PATH_MAX+1] = {0}; +#ifdef FOR_BUNDLE +#include + +static int +run_embedded(const RunData run_data) { + bypy_pre_initialize_interpreter(false); + wchar_t extensions_dir[PATH_MAX+1] = {0}, python_home[PATH_MAX+1] = {0}; #ifdef __APPLE__ const char *python_relpath = "../Resources/Python/lib"; #else const char *python_relpath = "../" KITTY_LIB_DIR_NAME; #endif - num = swprintf(stdlib, PATH_MAX, L"%ls/%s/python%s:%ls/%s/python%s/lib-dynload:%ls/%s/python%s/site-packages", - exe_dir, python_relpath, PYVER, - exe_dir, python_relpath, PYVER, - exe_dir, python_relpath, PYVER - ); - if (num < 0 || num >= PATH_MAX) { fprintf(stderr, "Failed to create path to python stdlib\n"); return 1; } - Py_SetPath(stdlib); - Py_Initialize(); - set_xoptions(exe_dir, lc_ctype, false); - PyMem_RawFree(exe_dir); - PySys_SetArgvEx(argc - 1, argv + 1, 0); - PySys_SetObject("frozen", Py_True); - PyObject *kitty = PyUnicode_FromString(libpath); - if (kitty == NULL) { fprintf(stderr, "Failed to allocate python kitty lib object\n"); goto end; } - PyObject *runpy = PyImport_ImportModule("runpy"); - if (runpy == NULL) { PyErr_Print(); fprintf(stderr, "Unable to import runpy\n"); Py_CLEAR(kitty); goto end; } - PyObject *run_name = PyUnicode_FromString("__main__"); - if (run_name == NULL) { fprintf(stderr, "Failed to allocate run_name\n"); goto end; } - PyObject *res = PyObject_CallMethod(runpy, "run_path", "OOO", kitty, Py_None, run_name); - Py_CLEAR(runpy); Py_CLEAR(kitty); Py_CLEAR(run_name); - if (res == NULL) PyErr_Print(); - else { ret = 0; Py_CLEAR(res); } -end: - if (Py_FinalizeEx() < 0) ret = 120; - return ret; + int num = swprintf(extensions_dir, PATH_MAX, L"%s/%s/kitty-extensions", run_data.exe_dir, python_relpath); + if (num < 0 || num >= PATH_MAX) { fprintf(stderr, "Failed to create path to extensions_dir: %s/%s\n", run_data.exe_dir, python_relpath); return 1; } + num = swprintf(python_home, PATH_MAX, L"%s/%s/python%s", run_data.exe_dir, python_relpath, PYVER); + if (num < 0 || num >= PATH_MAX) { fprintf(stderr, "Failed to create path to python home: %s/%s\n", run_data.exe_dir, python_relpath); return 1; } + bypy_initialize_interpreter(L"kitty", python_home, L"kitty_main", extensions_dir, run_data.argc, run_data.argv); + if (!set_xoptions(run_data.exe_dir, run_data.lc_ctype, false)) return 1; + set_sys_bool("frozen", true); + set_sys_string("kitty_extensions_dir", extensions_dir); + return bypy_run_interpreter(); } + #else + static int -run_embedded(const char* exe_dir_, const char *libpath, int argc, wchar_t **argv, const char *lc_ctype) { - (void)libpath; - wchar_t *exe_dir = Py_DecodeLocale(exe_dir_, NULL); - if (exe_dir == NULL) { fprintf(stderr, "Fatal error: cannot decode exe_dir: %s\n", exe_dir_); return 1; } +free_argv(wchar_t **argv) { + wchar_t **p = argv; + while (*p) { PyMem_RawFree(*p); p++; } + free(argv); + return 1; +} + +static int +run_embedded(const RunData run_data) { bool from_source = false; #ifdef FROM_SOURCE from_source = true; #endif - set_xoptions(exe_dir, lc_ctype, from_source); - PyMem_RawFree(exe_dir); - return Py_Main(argc, argv); + if (!set_xoptions(run_data.exe_dir, run_data.lc_ctype, from_source)) return 1; + int argc = run_data.argc + 1; + wchar_t **argv = calloc(argc, sizeof(wchar_t*)); + if (!argv) { fprintf(stderr, "Out of memory creating argv\n"); return 1; } + argv[0] = Py_DecodeLocale(run_data.exe, NULL); + if (!argv[0]) { fprintf(stderr, "Failed to decode path to exe\n"); return free_argv(argv); } + argv[1] = Py_DecodeLocale(run_data.lib_dir, NULL); + if (!argv[1]) { fprintf(stderr, "Failed to decode path to lib_dir\n"); return free_argv(argv); } + for (int i=1; i < run_data.argc; i++) { + argv[i+1] = Py_DecodeLocale(run_data.argv[i], NULL); + if (!argv[i+1]) { fprintf(stderr, "Failed to decode the command line argument: %s\n", run_data.argv[i]); return free_argv(argv); } + } + int ret = Py_Main(argc, argv); + // we cannot free argv properly as Py_Main odifies it + free(argv); + return ret; } #endif @@ -180,41 +185,26 @@ read_exe_path(char *exe, size_t buf_sz) { int main(int argc, char *argv[]) { char exe[PATH_MAX+1] = {0}; + char exe_dir_buf[PATH_MAX+1] = {0}; const char *lc_ctype = NULL; #ifdef __APPLE__ lc_ctype = getenv("LC_CTYPE"); #endif if (!read_exe_path(exe, sizeof(exe))) return 1; - - char *exe_dir = dirname(exe); - int num, num_args, i, ret=0; + strncpy(exe_dir_buf, exe, sizeof(exe_dir_buf)); + char *exe_dir = dirname(exe_dir_buf); + int num, ret=0; char lib[PATH_MAX+1] = {0}; - char *final_argv[MAX_ARGC + 1] = {0}; - wchar_t *argvw[MAX_ARGC + 1] = {0}; num = snprintf(lib, PATH_MAX, "%s/%s", exe_dir, KITTY_LIB_PATH); if (num < 0 || num >= PATH_MAX) { fprintf(stderr, "Failed to create path to kitty lib\n"); return 1; } - final_argv[0] = exe; - final_argv[1] = lib; - for (i = 1, num_args=2; i < argc && i + 1 <= MAX_ARGC; i++) { - final_argv[i+1] = argv[i]; - num_args++; - } #if PY_VERSION_HEX >= 0x03070000 // Always use UTF-8 mode, see https://github.com/kovidgoyal/kitty/issues/924 Py_UTF8Mode = 1; #endif - for (i = 0; i < num_args; i++) { - argvw[i] = Py_DecodeLocale(final_argv[i], NULL); - if (argvw[i] == NULL) { - fprintf(stderr, "Fatal error: cannot decode argv[%d]\n", i); - ret = 1; goto end; - } - } if (lc_ctype) lc_ctype = strdup(lc_ctype); - ret = run_embedded(exe_dir, lib, num_args, argvw, lc_ctype); + RunData run_data = {.exe = exe, .exe_dir = exe_dir, .lib_dir = lib, .argc = argc, .argv = argv, .lc_ctype = lc_ctype}; + ret = run_embedded(run_data); if (lc_ctype) free((void*)lc_ctype); -end: - for (i = 0; i < num_args; i++) { if(argvw[i]) PyMem_RawFree(argvw[i]); } return ret; } diff --git a/setup.py b/setup.py index ce5309ee5..a2d4fde6d 100755 --- a/setup.py +++ b/setup.py @@ -69,6 +69,7 @@ class Options(argparse.Namespace): for_freeze: bool = False libdir_name: str = 'lib' extra_logging: List[str] = [] + extra_include_dirs: List[str] = [] link_time_optimization: bool = 'KITTY_NO_LTO' not in os.environ update_check_interval: float = 24 egl_library: Optional[str] = os.getenv('KITTY_EGL_LIBRARY') @@ -259,7 +260,8 @@ def init_env( egl_library: Optional[str] = None, startup_notification_library: Optional[str] = None, canberra_library: Optional[str] = None, - extra_logging: Iterable[str] = () + extra_logging: Iterable[str] = (), + extra_include_dirs: Iterable[str] = (), ) -> Env: native_optimizations = native_optimizations and not sanitize and not debug if native_optimizations and is_macos and is_arm: @@ -344,6 +346,9 @@ def init_env( if desktop_libs != []: library_paths['kitty/desktop.c'] = desktop_libs + for path in extra_include_dirs: + cflags.append(f'-I{path}') + return Env(cc, cppflags, cflags, ldflags, library_paths, ccver=ccver) @@ -745,7 +750,7 @@ def init_env_from_args(args: Options, native_optimizations: bool = False) -> Non env = init_env( args.debug, args.sanitize, native_optimizations, args.link_time_optimization, args.profile, args.egl_library, args.startup_notification_library, args.canberra_library, - args.extra_logging + args.extra_logging, args.extra_include_dirs ) @@ -797,6 +802,8 @@ def build_launcher(args: Options, launcher_dir: str = '.', bundle_type: str = 's cppflags += shlex.split(os.environ.get('CPPFLAGS', '')) cflags += shlex.split(os.environ.get('CFLAGS', '')) ldflags = shlex.split(os.environ.get('LDFLAGS', '')) + for path in args.extra_include_dirs: + cflags.append(f'-I{path}') if bundle_type == 'linux-freeze': ldflags += ['-Wl,-rpath,$ORIGIN/../lib'] os.makedirs(launcher_dir, exist_ok=True) @@ -1026,7 +1033,8 @@ def package(args: Options, bundle_type: str) -> None: shutil.rmtree(libdir) launcher_dir = os.path.join(ddir, 'bin') safe_makedirs(launcher_dir) - build_launcher(args, launcher_dir, bundle_type) + if not bundle_type.endswith('-freeze'): # freeze launcher is built separately + build_launcher(args, launcher_dir, bundle_type) os.makedirs(os.path.join(libdir, 'logo')) build_terminfo = runpy.run_path('build-terminfo', run_name='import_build') # type: ignore for x in (libdir, os.path.join(ddir, 'share')): @@ -1103,7 +1111,7 @@ def option_parser() -> argparse.ArgumentParser: # {{{ 'action', nargs='?', default=Options.action, - choices='build test linux-package kitty.app linux-freeze macos-freeze build-launcher clean export-ci-bundles'.split(), + choices='build test linux-package kitty.app linux-freeze macos-freeze build-launcher build-frozen-launcher clean export-ci-bundles'.split(), help='Action to perform (default is build)' ) p.add_argument( @@ -1161,6 +1169,12 @@ def option_parser() -> argparse.ArgumentParser: # {{{ help='Turn on extra logging for debugging in this build. Can be specified multiple times, to turn' ' on different types of logging.' ) + p.add_argument( + '--extra-include-dirs', + action='append', + default=Options.extra_include_dirs, + help='Extra include directories to use while compiling' + ) p.add_argument( '--update-check-interval', type=float, @@ -1226,6 +1240,10 @@ def main() -> None: elif args.action == 'build-launcher': init_env_from_args(args, False) build_launcher(args, launcher_dir=launcher_dir) + elif args.action == 'build-frozen-launcher': + init_env_from_args(args, False) + bundle_type = ('macos' if is_macos else 'linux') + '-freeze' + build_launcher(args, launcher_dir=os.path.join(args.prefix, 'bin'), bundle_type=bundle_type) elif args.action == 'linux-package': build(args, native_optimizations=False) package(args, bundle_type='linux-package')