diff --git a/.circleci/config.yml b/.circleci/config.yml index 863a3ddc9..ca9a2e4a3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -10,7 +10,7 @@ jobs: - run: if grep -Inr '\s$' kitty kitty_tests kittens docs *.py *.asciidoc *.rst .gitattributes .gitignore; then echo Trailing whitespace found, aborting.; exit 1; fi - run: python3 -m flake8 --count . - run: python3 setup.py build --debug --verbose - - run: python3 test.py + - run: ./kitty/launcher/kitty +launch test.py - run: make FAIL_WARN=-W man - run: make FAIL_WARN=-W html - run: python3 setup.py linux-package --update-check-interval=0 @@ -26,7 +26,7 @@ jobs: steps: - checkout - run: /opt/py3.5/bin/python3 setup.py build --debug --verbose --sanitize - - run: ./asan-launcher test.py + - run: ./kitty/launcher/kitty +launch test.py lin-37: docker: @@ -38,7 +38,7 @@ jobs: steps: - checkout - run: /opt/py3.7/bin/python3 setup.py build --debug --verbose --sanitize - - run: ./asan-launcher test.py + - run: ./kitty/launcher/kitty +launch test.py lin-bundle: docker: @@ -50,7 +50,7 @@ jobs: - run: echo "export LD_LIBRARY_PATH=$SW/lib" >> $BASH_ENV - run: echo "export PKG_CONFIG_PATH=$SW/lib/pkgconfig" >> $BASH_ENV - run: $SW/bin/python3 setup.py build --debug --verbose - - run: $SW/bin/python3 test.py + - run: ./kitty/launcher/kitty +launch test.py mac-bundle: macos: @@ -65,7 +65,7 @@ jobs: - run: curl https://download.calibre-ebook.com/travis/kitty/osx.tar.xz | tar xJ -C $SW - run: echo "export PATH=$SW/bin:$PATH" >> $BASH_ENV - run: python3 setup.py build --debug --verbose - - run: python3 test.py + - run: ./kitty/launcher/kitty +launch test.py mac-brew: macos: @@ -78,8 +78,8 @@ jobs: - run: brew bundle - run: python3 logo/make.py - run: python3 setup.py build --debug --verbose - - run: python3 test.py - - run: python3 setup.py macos-bundle + - run: ./kitty/launcher/kitty +launch test.py + - run: python3 setup.py linux-package workflows: diff --git a/.gitignore b/.gitignore index 766eecf30..26413017e 100644 --- a/.gitignore +++ b/.gitignore @@ -8,8 +8,7 @@ kitty.app logo/*.iconset compile_commands.json glad/out -asan-launcher -kitty-profile +kitty/launcher *.dSYM dev __pycache__ diff --git a/asan-launcher.c b/asan-launcher.c deleted file mode 100644 index 6dc1bd5f0..000000000 --- a/asan-launcher.c +++ /dev/null @@ -1,19 +0,0 @@ -/* - * asan-launcher.c - * Copyright (C) 2017 Kovid Goyal - * - * Distributed under terms of the GPL3 license. - */ - -#include - -#define MAX_ARGC 1024 - -int main(int argc, char *argv[]) { - wchar_t *argvw[MAX_ARGC + 1] = {0}; - argvw[0] = L"kitty"; - for (int i = 1; i < argc; i++) argvw[i] = Py_DecodeLocale(argv[i], NULL); - int ret = Py_Main(argc, argvw); - for (int i = 1; i < argc; i++) PyMem_RawFree(argvw[i]); - return ret; -} diff --git a/docs/build.rst b/docs/build.rst index 378cfbdfd..f9555d376 100644 --- a/docs/build.rst +++ b/docs/build.rst @@ -44,17 +44,10 @@ Now build the native code parts of |kitty| with the following command:: You can run |kitty|, as:: - python3 . + ./kitty/launcher/kitty -If that works, you can create a script to launch |kitty|: - -.. code-block:: sh - - #!/usr/bin/env python3 - import runpy - runpy.run_path('/path/to/kitty/dir', run_name='__main__') - -And place it in :file:`~/bin` or :file:`/usr/bin` so that you can run |kitty| using +If that works, you can create a symlink to the launcher in :file:`~/bin` or +some other directory on your PATH so that you can run |kitty| using just ``kitty``. diff --git a/docs/performance.rst b/docs/performance.rst index 45f9606f2..8516d369d 100644 --- a/docs/performance.rst +++ b/docs/performance.rst @@ -14,12 +14,12 @@ render loop to reduce CPU usage. See :ref:`conf-kitty-performance` for details. See also the :opt:`sync_to_monitor` option to further decrease latency at the cost of some `tearing `_ while scrolling. -You can generate detailed per-function performance data using -`gperftools `_. Build |kitty| with -`make profile` which will create an executable called `kitty-profile`. Run -that and perform the task you want to analyse, for example, scrolling a large -file with `less`. After you quit, function call statistics will be printed to -`stdout` and you can use tools like *kcachegrind* for more detailed analysis. +You can generate detailed per-function performance data using `gperftools +`_. Build |kitty| with `make +profile`. Run kitty and perform the task you want to analyse, for example, +scrolling a large file with `less`. After you quit, function call statistics +will be printed to `stdout` and you can use tools like *kcachegrind* for more +detailed analysis. Here are some CPU usage numbers for the task of scrolling a file continuously in less. The CPU usage is for the terminal process and X together and is diff --git a/kitty/launcher/kitty b/kitty/launcher/kitty deleted file mode 100755 index b02eb9d70..000000000 --- a/kitty/launcher/kitty +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env python3 -# vim:fileencoding=utf-8 -# License: GPL v3 Copyright: 2018, Kovid Goyal -# Launch kitty from source -import os -import sys - -base = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -sys.path.insert(0, base) -with open(os.path.join(base, '__main__.py')) as f: - src = f.read() -code = compile(src, f.name, 'exec') -exec(code, {'__name__': '__main__'}) diff --git a/kitty/main.py b/kitty/main.py index 30ea6b0a3..ac06950d3 100644 --- a/kitty/main.py +++ b/kitty/main.py @@ -4,9 +4,9 @@ import locale import os +import shutil import sys -from contextlib import contextmanager -from contextlib import suppress +from contextlib import contextmanager, suppress from .borders import load_borders_program from .boss import Boss @@ -174,7 +174,7 @@ def setup_profiling(args): if stop_profiler is not None: import subprocess stop_profiler() - exe = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'kitty-profile') + exe = kitty_exe() cg = '/tmp/kitty-profile.callgrind' print('Post processing profile data for', exe, '...') subprocess.call(['pprof', '--callgrind', exe, '/tmp/kitty-profile.log'], stdout=open(cg, 'wb')) @@ -225,11 +225,14 @@ def _main(): except Exception: log_error('Failed to set locale with no LANG, ignoring') - # Ensure kitty is in PATH - rpath = os.path.dirname(kitty_exe()) - items = frozenset(os.environ['PATH'].split(os.pathsep)) - if rpath and rpath not in items: - os.environ['PATH'] = rpath + os.pathsep + os.environ.get('PATH', '') + # Ensure the correct kitty is in PATH + rpath = sys._xoptions.get('bundle_exe_dir') + if rpath: + modify_path = is_macos or getattr(sys, 'frozen', False) or sys._xoptions.get('kitty_from_source') == '1' + if modify_path or not shutil.which('kitty'): + existing_paths = list(filter(None, os.environ.get('PATH', '').split(os.pathsep))) + existing_paths.insert(0, rpath) + os.environ['PATH'] = os.pathsep.join(existing_paths) args = sys.argv[1:] if is_macos and os.environ.pop('KITTY_LAUNCHED_BY_LAUNCH_SERVICES', None) == '1': diff --git a/launcher.c b/launcher.c index f07f4f913..f34021e2c 100644 --- a/launcher.c +++ b/launcher.c @@ -20,9 +20,16 @@ #endif #include #include +#include #define MIN(x, y) ((x) < (y)) ? (x) : (y) #define MAX_ARGC 1024 +#ifndef KITTY_LIB_PATH +#define KITTY_LIB_PATH "../.." +#endif +#ifndef KITTY_LIB_DIR_NAME +#define KITTY_LIB_DIR_NAME "lib" +#endif static inline bool safe_realpath(const char* src, char *buf, size_t buf_sz) { @@ -33,17 +40,15 @@ safe_realpath(const char* src, char *buf, size_t buf_sz) { return true; } -#if defined(FOR_BUNDLE) || defined(__APPLE__) static inline void set_bundle_exe_dir(const wchar_t *exe_dir) { wchar_t buf[PATH_MAX+1] = {0}; swprintf(buf, PATH_MAX, L"bundle_exe_dir=%ls", exe_dir); PySys_AddXOption(buf); } -#endif #ifdef FOR_BUNDLE -static int run_embedded(const char* exe_dir_, int argc, wchar_t **argv) { +static int run_embedded(const char* exe_dir_, const char *libpath, int argc, wchar_t **argv) { int num; Py_NoSiteFlag = 1; Py_FrozenFlag = 1; @@ -61,7 +66,7 @@ static int run_embedded(const char* exe_dir_, int argc, wchar_t **argv) { #ifdef __APPLE__ const char *python_relpath = "../Resources/Python/lib"; #else - const char *python_relpath = "../lib"; + 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, @@ -70,17 +75,12 @@ static int run_embedded(const char* exe_dir_, int argc, wchar_t **argv) { ); if (num < 0 || num >= PATH_MAX) { fprintf(stderr, "Failed to create path to python stdlib\n"); return 1; } Py_SetPath(stdlib); -#ifdef __APPLE__ - num = swprintf(stdlib, PATH_MAX, L"%ls/../Frameworks/kitty", exe_dir); -#else - num = swprintf(stdlib, PATH_MAX, L"%ls/../lib/kitty", exe_dir); -#endif PyMem_RawFree(exe_dir); if (num < 0 || num >= PATH_MAX) { fprintf(stderr, "Failed to create path to kitty lib\n"); return 1; } Py_Initialize(); PySys_SetArgvEx(argc - 1, argv + 1, 0); PySys_SetObject("frozen", Py_True); - PyObject *kitty = PyUnicode_FromWideChar(stdlib, -1); + 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; } @@ -94,27 +94,34 @@ end: if (Py_FinalizeEx() < 0) ret = 120; return ret; } - #else -static int run_embedded(const char* exe_dir_, int argc, wchar_t **argv) { - (void)exe_dir_; -#ifdef __APPLE__ +static int run_embedded(const char* exe_dir_, const char *libpath, int argc, wchar_t **argv) { + (void)libpath; wchar_t *exe_dir = Py_DecodeLocale(exe_dir_, NULL); - if (exe_dir == NULL) { fprintf(stderr, "Fatal error: cannot decode exe_dir\n"); return 1; } + if (exe_dir == NULL) { fprintf(stderr, "Fatal error: cannot decode exe_dir: %s\n", exe_dir_); return 1; } set_bundle_exe_dir(exe_dir); +#ifdef FROM_SOURCE + PySys_AddXOption(L"kitty_from_source=1"); #endif + PyMem_RawFree(exe_dir); return Py_Main(argc, argv); } #endif +// read_exe_path() {{{ #ifdef __APPLE__ static inline bool -read_exe_path(char *exe, size_t buf_sz) { +read_exe_path(char *exe, size_t buf_sz, bool *is_symlink) { (void)buf_sz; + *is_symlink = false; uint32_t size = PATH_MAX; char apple[PATH_MAX+1] = {0}; if (_NSGetExecutablePath(apple, &size) != 0) { fprintf(stderr, "Failed to get path to executable\n"); return false; } + struct stat buf; + if (lstat(apple, &buf) == 0) { + *is_symlink = S_ISLNK(buf.st_mode); + } if (!safe_realpath(apple, exe, buf_sz)) { fprintf(stderr, "realpath() failed on the executable's path\n"); return false; } return true; } @@ -123,7 +130,8 @@ read_exe_path(char *exe, size_t buf_sz) { #include static inline bool -read_exe_path(char *exe, size_t buf_sz) { +read_exe_path(char *exe, size_t buf_sz, bool *is_symlink) { + *is_symlink = false; int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 }; size_t length = buf_sz; int error = sysctl(name, 4, exe, &length, NULL, 0); @@ -136,7 +144,8 @@ read_exe_path(char *exe, size_t buf_sz) { #elif defined(__NetBSD__) static inline bool -read_exe_path(char *exe, size_t buf_sz) { +read_exe_path(char *exe, size_t buf_sz, bool *is_symlink) { + *is_symlink = false; if (!safe_realpath("/proc/curproc/exe", exe, buf_sz)) { fprintf(stderr, "Failed to read /proc/curproc/exe\n"); return false; } return true; } @@ -144,30 +153,28 @@ read_exe_path(char *exe, size_t buf_sz) { #else static inline bool -read_exe_path(char *exe, size_t buf_sz) { +read_exe_path(char *exe, size_t buf_sz, bool *is_symlink) { + *is_symlink = false; if (!safe_realpath("/proc/self/exe", exe, buf_sz)) { fprintf(stderr, "Failed to read /proc/self/exe\n"); return false; } return true; } -#endif +#endif // }}} int main(int argc, char *argv[]) { char exe[PATH_MAX+1] = {0}; - if (!read_exe_path(exe, sizeof(exe))) return 1; + bool is_symlink = false; + if (!read_exe_path(exe, sizeof(exe), &is_symlink)) return 1; +#ifdef __APPLE__ + // Cocoa has issues with bundle executables launched via symlinks + if (is_symlink) execv(exe, argv); +#endif char *exe_dir = dirname(exe); int num, num_args, i, ret=0; char lib[PATH_MAX+1] = {0}; char *final_argv[MAX_ARGC + 1] = {0}; wchar_t *argvw[MAX_ARGC + 1] = {0}; -#ifdef WITH_PROFILER - num = snprintf(lib, PATH_MAX, "%s%s", exe_dir, "/"); -#else -#ifdef FOR_LAUNCHER - num = snprintf(lib, PATH_MAX, "%s%s", exe_dir, "/../Frameworks/kitty"); -#else - num = snprintf(lib, PATH_MAX, "%s%s%s%s", exe_dir, "/../", LIB_DIR_NAME, "/kitty"); -#endif -#endif + 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; @@ -187,7 +194,7 @@ int main(int argc, char *argv[]) { ret = 1; goto end; } } - ret = run_embedded(exe_dir, num_args, argvw); + ret = run_embedded(exe_dir, lib, num_args, argvw); 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 13afc7117..ce65ef4ad 100755 --- a/setup.py +++ b/setup.py @@ -15,6 +15,7 @@ import subprocess import sys import sysconfig import time +from collections import namedtuple from contextlib import suppress base = os.path.dirname(os.path.abspath(__file__)) @@ -177,11 +178,12 @@ def first_successful_compile(cc, *cflags, src=None): class Env: - def __init__(self, cc, cppflags, cflags, ldflags, ldpaths=None): + def __init__(self, cc, cppflags, cflags, ldflags, ldpaths=None, ccver=None): self.cc, self.cppflags, self.cflags, self.ldflags, self.ldpaths = cc, cppflags, cflags, ldflags, [] if ldpaths is None else ldpaths + self.ccver = ccver def copy(self): - return Env(self.cc, list(self.cppflags), list(self.cflags), list(self.ldflags), list(self.ldpaths)) + return Env(self.cc, list(self.cppflags), list(self.cflags), list(self.ldflags), list(self.ldpaths), self.ccver) def init_env( @@ -242,7 +244,7 @@ def init_env( cppflags.append('-DWITH_PROFILER') cflags.append('-g3') ldflags.append('-lprofiler') - return Env(cc, cppflags, cflags, ldflags) + return Env(cc, cppflags, cflags, ldflags, ccver=ccver) def kitty_env(): @@ -395,7 +397,43 @@ def parallel_run(todo, desc='Compiling {} ...'): run_tool(failed[1]) -def compile_c_extension(kenv, module, incremental, compilation_database, all_keys, sources, headers): +CompileKey = namedtuple('CompileKey', 'src dest') + + +class CompilationDatabase: + + def cmd_changed(self, key, cmd): + self.all_keys.add(key) + return self.db.get(key) != cmd + + def update_cmd(self, key, cmd): + self.db[key] = cmd + + def __enter__(self): + self.all_keys = set() + try: + with open('compile_commands.json') as f: + compilation_database = json.load(f) + except FileNotFoundError: + compilation_database = [] + compilation_database = { + CompileKey(k['file'], k.get('output')): k['arguments'] for k in compilation_database + } + self.db = compilation_database + return self + + def __exit__(self, *a): + cdb = self.db + for key in set(cdb) - self.all_keys: + del cdb[key] + compilation_database = [ + {'file': k.src, 'arguments': v, 'directory': base, 'output': k.dest} for k, v in cdb.items() + ] + with open('compile_commands.json', 'w') as f: + json.dump(compilation_database, f, indent=2, sort_keys=True) + + +def compile_c_extension(kenv, module, incremental, compilation_database, sources, headers): prefix = os.path.basename(module) objects = [ os.path.join(build_dir, prefix + '-' + os.path.basename(src) + '.o') @@ -415,16 +453,15 @@ def compile_c_extension(kenv, module, incremental, compilation_database, all_key cppflags.extend(map(define, defines)) cmd = [kenv.cc, '-MMD'] + cppflags + kenv.cflags - key = original_src, os.path.basename(dest) - all_keys.add(key) - cmd_changed = compilation_database.get(key, [])[:-4] != cmd + cmd += ['-c', src] + ['-o', dest] + key = CompileKey(original_src, os.path.basename(dest)) + cmd_changed = compilation_database.cmd_changed(key, cmd) must_compile = not incremental or cmd_changed src = os.path.join(base, src) if must_compile or newer( dest, *dependecies_for(src, dest, headers) ): - cmd += ['-c', src] + ['-o', dest] - compilation_database[key] = cmd + compilation_database.update_cmd(key, cmd) todo[original_src] = cmd if todo: parallel_run(todo) @@ -462,7 +499,7 @@ def find_c_files(): return tuple(ans), tuple(headers) -def compile_glfw(incremental, compilation_database, all_keys): +def compile_glfw(incremental, compilation_database): modules = 'cocoa' if is_macos else 'x11 wayland' for module in modules.split(): try: @@ -482,7 +519,7 @@ def compile_glfw(incremental, compilation_database, all_keys): print(err, file=sys.stderr) print(error('Disabling building of wayland backend'), file=sys.stderr) continue - compile_c_extension(genv, 'kitty/glfw-' + module, incremental, compilation_database, all_keys, sources, all_headers) + compile_c_extension(genv, 'kitty/glfw-' + module, incremental, compilation_database, sources, all_headers) def kittens_env(): @@ -495,7 +532,7 @@ def kittens_env(): return kenv -def compile_kittens(incremental, compilation_database, all_keys): +def compile_kittens(incremental, compilation_database): kenv = kittens_env() def list_files(q): @@ -516,82 +553,69 @@ def compile_kittens(incremental, compilation_database, all_keys): filter_sources=lambda x: 'windows_compat.c' not in x), ): compile_c_extension( - kenv, dest, incremental, compilation_database, all_keys, sources, all_headers + ['kitty/data-types.h']) + kenv, dest, incremental, compilation_database, sources, all_headers + ['kitty/data-types.h']) def build(args, native_optimizations=True): global env - try: - with open('compile_commands.json') as f: - compilation_database = json.load(f) - except FileNotFoundError: - compilation_database = [] - all_keys = set() - compilation_database = { - (k['file'], k.get('output')): k['arguments'] for k in compilation_database - } env = init_env(args.debug, args.sanitize, native_optimizations, args.profile, args.extra_logging) - try: - compile_c_extension( - kitty_env(), 'kitty/fast_data_types', args.incremental, compilation_database, all_keys, *find_c_files() - ) - compile_glfw(args.incremental, compilation_database, all_keys) - compile_kittens(args.incremental, compilation_database, all_keys) - for key in set(compilation_database) - all_keys: - del compilation_database[key] - finally: - compilation_database = [ - {'file': k[0], 'arguments': v, 'directory': base, 'output': k[1]} for k, v in compilation_database.items() - ] - with open('compile_commands.json', 'w') as f: - json.dump(compilation_database, f, indent=2, sort_keys=True) + compile_c_extension( + kitty_env(), 'kitty/fast_data_types', args.incremental, args.compilation_database, *find_c_files() + ) + compile_glfw(args.incremental, args.compilation_database) + compile_kittens(args.incremental, args.compilation_database) def safe_makedirs(path): os.makedirs(path, exist_ok=True) -def build_asan_launcher(args): - dest = 'asan-launcher' - src = 'asan-launcher.c' - if args.incremental and not newer(dest, src): - return - cc, ccver = cc_version() - cflags = '-g3 -Wall -Werror -fpie -std=c99'.split() - pylib = get_python_flags(cflags) - sanitize_lib = ['-lasan'] if cc == 'gcc' and not is_macos else [] - cflags.extend(get_sanitize_args(cc, ccver)) - cmd = [cc] + cflags + [src, '-o', dest] + sanitize_lib + pylib - run_tool(cmd, desc='Creating {} ...'.format(emphasis('asan-launcher'))) - - -def build_launcher(args, launcher_dir='.', for_bundle=False, sh_launcher=False, for_freeze=False): +def build_launcher(args, launcher_dir='.', bundle_type='source'): cflags = '-Wall -Werror -fpie'.split() cppflags = [] libs = [] - if args.profile: - cppflags.append('-DWITH_PROFILER'), cflags.append('-g') - libs.append('-lprofiler') + if args.profile or args.sanitize: + if args.sanitize: + cflags.append('-g3') + cflags.extend(get_sanitize_args(env.cc, env.ccver)) + libs += ['-lasan'] if env.cc == 'gcc' and not is_macos else [] + else: + cflags.append('-g') + if args.profile: + libs.append('-lprofiler') else: cflags.append('-O3') - if for_bundle or for_freeze: + if bundle_type.endswith('-freeze'): cppflags.append('-DFOR_BUNDLE') cppflags.append('-DPYVER="{}"'.format(sysconfig.get_python_version())) - elif sh_launcher: - cppflags.append('-DFOR_LAUNCHER') - cppflags.append('-DLIB_DIR_NAME="{}"'.format(args.libdir_name.strip('/'))) + cppflags.append('-DKITTY_LIB_DIR_NAME="{}"'.format(args.libdir_name)) + elif bundle_type == 'source': + cppflags.append('-DFROM_SOURCE') + if bundle_type.startswith('macos-'): + klp = '../Frameworks/kitty' + elif bundle_type.startswith('linux-'): + klp = '../{}/kitty'.format(args.libdir_name.strip('/')) + elif bundle_type == 'source': + klp = '../..' + else: + raise SystemExit('Unknown bundle type: {}'.format(bundle_type)) + cppflags.append('-DKITTY_LIB_PATH="{}"'.format(klp)) pylib = get_python_flags(cflags) - exe = 'kitty-profile' if args.profile else 'kitty' cppflags += shlex.split(os.environ.get('CPPFLAGS', '')) cflags += shlex.split(os.environ.get('CFLAGS', '')) ldflags = shlex.split(os.environ.get('LDFLAGS', '')) - if for_freeze: + if bundle_type == 'linux-freeze': ldflags += ['-Wl,-rpath,$ORIGIN/../lib'] + os.makedirs(launcher_dir, exist_ok=True) + dest = os.path.join(launcher_dir, 'kitty') + src = 'launcher.c' cmd = [env.cc] + cppflags + cflags + [ - 'launcher.c', '-o', - os.path.join(launcher_dir, exe) - ] + ldflags + libs + pylib - run_tool(cmd) + src, '-o', dest] + ldflags + libs + pylib + key = CompileKey('launcher.c', 'kitty') + must_compile = not args.incremental or args.compilation_database.cmd_changed(key, cmd) + if must_compile or newer(dest, src): + run_tool(cmd, 'Building {}...'.format(emphasis('launcher'))) + args.compilation_database.update_cmd(key, cmd) # Packaging {{{ @@ -645,9 +669,9 @@ def compile_python(base_path): compileall.compile_dir(base_path, **kwargs) -def package(args, for_bundle=False, sh_launcher=False): +def package(args, bundle_type): ddir = args.prefix - if for_bundle or sh_launcher: + if bundle_type == 'linux-freeze': args.libdir_name = 'lib' libdir = os.path.join(ddir, args.libdir_name.strip('/'), 'kitty') if os.path.exists(libdir): @@ -688,7 +712,7 @@ def package(args, for_bundle=False, sh_launcher=False): shutil.copy2('kitty/launcher/kitty', os.path.join(libdir, 'kitty', 'launcher')) launcher_dir = os.path.join(ddir, 'bin') safe_makedirs(launcher_dir) - build_launcher(args, launcher_dir, for_bundle, sh_launcher, args.for_freeze) + build_launcher(args, launcher_dir, bundle_type) if not is_macos: # {{{ linux desktop gunk copy_man_pages(ddir) copy_html_docs(ddir) @@ -714,7 +738,7 @@ Categories=System;TerminalEmulator; ) # }}} - if for_bundle or sh_launcher: # macOS bundle gunk {{{ + if bundle_type.startswith('macos-'): # macOS bundle gunk {{{ import plistlib logo_dir = os.path.abspath(os.path.join('logo', appname + '.iconset')) os.chdir(ddir) @@ -766,9 +790,7 @@ Categories=System;TerminalEmulator; os.rename('../lib', 'Frameworks') if not os.path.exists(logo_dir): raise SystemExit('The kitty logo has not been generated, you need to run logo/make.py') - cmd = [env.cc] + ['-Wall', '-Werror'] + [ - os.path.join(base, 'symlink-deref.c'), '-o', os.path.join('MacOS', 'kitty-deref-symlink')] - run_tool(cmd) + os.symlink(os.path.join('MacOS', 'kitty'), os.path.join('MacOS', 'kitty-deref-symlink')) subprocess.check_call([ 'iconutil', '-c', 'icns', logo_dir, '-o', @@ -809,7 +831,7 @@ def option_parser(): # {{{ 'action', nargs='?', default='build', - choices='build test linux-package kitty.app macos-bundle osx-bundle clean'.split(), + choices='build test linux-package kitty.app linux-freeze macos-freeze clean'.split(), help='Action to perform (default is build)' ) p.add_argument( @@ -828,10 +850,7 @@ def option_parser(): # {{{ '--sanitize', default=False, action='store_true', - help='Turn on sanitization to detect memory access errors and undefined behavior. Note that if you do turn it on,' - ' a special executable will be built for running the test suite. If you want to run normal kitty' - ' with sanitization, use LD_PRELOAD=libasan.so (for gcc) and' - ' LD_PRELOAD=/usr/lib/clang/4.0.0/lib/linux/libclang_rt.asan-x86_64.so (for clang, changing path as appropriate).' + help='Turn on sanitization to detect memory access errors and undefined behavior. This is a big performance hit.' ) p.add_argument( '--prefix', @@ -889,34 +908,39 @@ def main(): verbose = args.verbose > 0 args.prefix = os.path.abspath(args.prefix) os.chdir(os.path.dirname(os.path.abspath(__file__))) - if args.action == 'build': - build(args) - if args.sanitize: - build_asan_launcher(args) - if args.profile: - build_launcher(args) - print('kitty profile executable is', 'kitty-profile') - elif args.action == 'test': + if args.action == 'test': os.execlp( sys.executable, sys.executable, os.path.join(base, 'test.py') ) - elif args.action == 'linux-package': - build(args, native_optimizations=False) - if not os.path.exists(os.path.join(base, 'docs/_build/html')): - run_tool(['make', 'docs']) - package(args) - elif args.action in ('macos-bundle', 'osx-bundle'): - build(args, native_optimizations=False) - package(args, for_bundle=True) - elif args.action == 'kitty.app': - args.prefix = 'kitty.app' - if os.path.exists(args.prefix): - shutil.rmtree(args.prefix) - build(args) - package(args, for_bundle=False, sh_launcher=True) - print('kitty.app successfully built!') - elif args.action == 'clean': + if args.action == 'clean': clean() + return + + with CompilationDatabase() as cdb: + args.compilation_database = cdb + if args.action == 'build': + build(args) + build_launcher(args, launcher_dir='kitty/launcher') + elif args.action == 'linux-package': + build(args, native_optimizations=False) + if not os.path.exists(os.path.join(base, 'docs/_build/html')): + run_tool(['make', 'docs']) + package(args, bundle_type='linux-package') + elif args.action == 'linux-freeze': + build(args, native_optimizations=False) + if not os.path.exists(os.path.join(base, 'docs/_build/html')): + run_tool(['make', 'docs']) + package(args, bundle_type='linux-freeze') + elif args.action == 'macos-freeze': + build(args, native_optimizations=False) + package(args, bundle_type='macos-freeze') + elif args.action == 'kitty.app': + args.prefix = 'kitty.app' + if os.path.exists(args.prefix): + shutil.rmtree(args.prefix) + build(args) + package(args, bundle_type='macos-package') + print('kitty.app successfully built!') if __name__ == '__main__': diff --git a/symlink-deref.c b/symlink-deref.c deleted file mode 100644 index 1bdf75e9f..000000000 --- a/symlink-deref.c +++ /dev/null @@ -1,47 +0,0 @@ -/* - * symlink-deref.c - * Copyright (C) 2019 Kovid Goyal - * - * Distributed under terms of the GPL3 license. - */ - - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static inline bool -safe_realpath(const char* src, char *buf, size_t buf_sz) { - char* ans = realpath(src, NULL); - if (ans == NULL) return false; - snprintf(buf, buf_sz, "%s", ans); - free(ans); - return true; -} - -static inline bool -read_exe_path(char *exe, size_t buf_sz) { - (void)buf_sz; - uint32_t size = PATH_MAX; - char apple[PATH_MAX+1] = {0}; - if (_NSGetExecutablePath(apple, &size) != 0) { fprintf(stderr, "Failed to get path to executable\n"); return false; } - if (!safe_realpath(apple, exe, buf_sz)) { fprintf(stderr, "realpath() failed on the executable's path\n"); return false; } - return true; -} - - -int -main(int argc, char *argv[]) { - char exe[PATH_MAX+1] = {0}; - char real_exe[PATH_MAX+1] = {0}; - if (!read_exe_path(exe, sizeof(exe))) return 1; - snprintf(real_exe, sizeof(real_exe), "%s/kitty", dirname(exe)); - return execv(real_exe, argv); -}