kitty/setup.py

1745 lines
64 KiB
Python
Raw Normal View History

2016-11-21 07:55:53 +03:00
#!/usr/bin/env python3
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
import argparse
import glob
import json
import os
import platform
import re
2019-03-03 04:54:05 +03:00
import runpy
import shlex
import shutil
import subprocess
import sys
import sysconfig
import tempfile
2022-08-19 08:50:50 +03:00
import textwrap
import time
2019-06-28 09:30:12 +03:00
from contextlib import suppress
2021-10-07 11:13:12 +03:00
from functools import lru_cache, partial
2019-06-28 09:30:12 +03:00
from pathlib import Path
from typing import (
2022-06-01 06:17:19 +03:00
Callable, Dict, FrozenSet, Iterable, List, Optional, Sequence, Set, Tuple,
Union, cast
)
2021-10-27 06:15:45 +03:00
from glfw import glfw
from glfw.glfw import Command, CompileKey
2020-03-14 12:07:11 +03:00
if sys.version_info[:2] < (3, 8):
raise SystemExit('kitty requires python >= 3.8')
base = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, base)
2017-11-19 20:54:36 +03:00
del sys.path[0]
verbose = False
2019-07-05 16:43:34 +03:00
build_dir = 'build'
2019-07-11 17:49:22 +03:00
constants = os.path.join('kitty', 'constants.py')
with open(constants, 'rb') as f:
constants = f.read().decode('utf-8')
2020-03-15 11:00:02 +03:00
appname = re.search(r"^appname: str = '([^']+)'", constants, re.MULTILINE).group(1) # type: ignore
2017-02-09 21:34:05 +03:00
version = tuple(
map(
int,
2020-03-05 15:53:45 +03:00
re.search( # type: ignore
2020-03-15 11:00:02 +03:00
r"^version: Version = Version\((\d+), (\d+), (\d+)\)", constants, re.MULTILINE
2020-03-05 15:53:45 +03:00
).group(1, 2, 3)
2017-02-09 21:34:05 +03:00
)
)
2017-01-07 10:31:32 +03:00
_plat = sys.platform.lower()
2017-11-21 03:49:46 +03:00
is_macos = 'darwin' in _plat
is_openbsd = 'openbsd' in _plat
2020-12-31 07:56:15 +03:00
is_freebsd = 'freebsd' in _plat
is_netbsd = 'netbsd' in _plat
is_dragonflybsd = 'dragonfly' in _plat
is_bsd = is_freebsd or is_netbsd or is_dragonflybsd or is_openbsd
2021-01-15 08:09:24 +03:00
is_arm = platform.processor() == 'arm' or platform.machine() == 'arm64'
2020-03-14 12:07:11 +03:00
Env = glfw.Env
env = Env()
PKGCONFIG = os.environ.get('PKGCONFIG_EXE', 'pkg-config')
2020-03-14 12:07:11 +03:00
class Options(argparse.Namespace):
action: str = 'build'
debug: bool = False
verbose: int = 0
sanitize: bool = False
prefix: str = './linux-package'
incremental: bool = True
profile: bool = False
libdir_name: str = 'lib'
extra_logging: List[str] = []
extra_include_dirs: List[str] = []
extra_library_dirs: List[str] = []
link_time_optimization: bool = 'KITTY_NO_LTO' not in os.environ
update_check_interval: float = 24.0
shell_integration: str = 'enabled'
egl_library: Optional[str] = os.getenv('KITTY_EGL_LIBRARY')
startup_notification_library: Optional[str] = os.getenv('KITTY_STARTUP_NOTIFICATION_LIBRARY')
canberra_library: Optional[str] = os.getenv('KITTY_CANBERRA_LIBRARY')
fontconfig_library: Optional[str] = os.getenv('KITTY_FONTCONFIG_LIBRARY')
2020-03-14 12:07:11 +03:00
def emphasis(text: str) -> str:
2017-11-21 05:02:48 +03:00
if sys.stdout.isatty():
text = f'\033[32m{text}\033[39m'
2017-11-21 05:02:48 +03:00
return text
2020-03-14 12:07:11 +03:00
def error(text: str) -> str:
2017-11-21 05:02:48 +03:00
if sys.stdout.isatty():
text = f'\033[91m{text}\033[39m'
2017-11-21 05:02:48 +03:00
return text
def pkg_config(pkg: str, *args: str, extra_pc_dir: str = '', fatal: bool = True) -> List[str]:
env = os.environ.copy()
if extra_pc_dir:
pp = env.get('PKG_CONFIG_PATH', '')
if pp:
pp += os.pathsep
env['PKG_CONFIG_PATH'] = f'{pp}{extra_pc_dir}'
cmd = [PKGCONFIG, pkg] + list(args)
2017-11-21 02:28:35 +03:00
try:
return list(
filter(
None,
shlex.split(
subprocess.check_output(cmd, env=env, stderr=None if fatal else subprocess.DEVNULL).decode('utf-8')
2017-11-21 02:28:35 +03:00
)
2017-02-09 21:34:05 +03:00
)
)
2017-11-21 02:28:35 +03:00
except subprocess.CalledProcessError:
if fatal:
raise SystemExit(f'The package {error(pkg)} was not found on your system')
raise
2016-11-02 18:57:20 +03:00
2021-10-27 06:15:45 +03:00
def pkg_version(package: str) -> Tuple[int, int]:
ver = subprocess.check_output([
PKGCONFIG, package, '--modversion']).decode('utf-8').strip()
m = re.match(r'(\d+).(\d+)', ver)
if m is not None:
qmajor, qminor = map(int, m.groups())
return qmajor, qminor
2021-10-27 06:15:45 +03:00
return -1, -1
def libcrypto_flags() -> Tuple[List[str], List[str]]:
# Apple use their special snowflake TLS libraries and additionally
# have an ancient broken system OpenSSL, so we need to check for one
# installed by all the various macOS package managers.
extra_pc_dir = ''
try:
cflags = pkg_config('libcrypto', '--cflags-only-I', fatal=False)
except subprocess.CalledProcessError:
if is_macos:
import ssl
v = ssl.OPENSSL_VERSION_INFO
pats = f'{v[0]}.{v[1]}', f'{v[0]}'
for pat in pats:
q = f'opt/openssl@{pat}/lib/pkgconfig'
openssl_dirs = glob.glob(f'/opt/homebrew/{q}') + glob.glob(f'/usr/local/{q}')
if openssl_dirs:
break
if not openssl_dirs:
raise SystemExit(f'Failed to find OpenSSL version {v[0]}.{v[1]} on your system')
extra_pc_dir = os.pathsep.join(openssl_dirs)
cflags = pkg_config('libcrypto', '--cflags-only-I', extra_pc_dir=extra_pc_dir)
return cflags, pkg_config('libcrypto', '--libs', extra_pc_dir=extra_pc_dir)
2020-03-14 12:07:11 +03:00
def at_least_version(package: str, major: int, minor: int = 0) -> None:
q = f'{major}.{minor}'
if subprocess.run([PKGCONFIG, package, f'--atleast-version={q}']
2017-11-05 06:52:15 +03:00
).returncode != 0:
qmajor = qminor = 0
2017-11-05 06:52:15 +03:00
try:
ver = subprocess.check_output([PKGCONFIG, package, '--modversion']
).decode('utf-8').strip()
m = re.match(r'(\d+).(\d+)', ver)
if m is not None:
qmajor, qminor = map(int, m.groups())
2017-11-05 06:52:15 +03:00
except Exception:
ver = 'not found'
if qmajor < major or (qmajor == major and qminor < minor):
2022-01-28 14:34:13 +03:00
raise SystemExit(f'{error(package)} >= {major}.{minor} is required, found version: {ver}')
2017-11-05 06:52:15 +03:00
def cc_version() -> Tuple[List[str], Tuple[int, int]]:
2019-01-30 06:35:42 +03:00
if 'CC' in os.environ:
q = os.environ['CC']
2019-01-30 06:35:42 +03:00
else:
if is_macos:
q = 'clang'
2019-01-30 06:35:42 +03:00
else:
if shutil.which('gcc'):
q = 'gcc'
2019-01-30 06:35:42 +03:00
elif shutil.which('clang'):
q = 'clang'
2019-01-30 06:35:42 +03:00
else:
q = 'cc'
cc = shlex.split(q)
raw = subprocess.check_output(cc + ['-dumpversion']).decode('utf-8')
2020-03-24 16:03:32 +03:00
ver_ = raw.strip().split('.')[:2]
try:
2020-03-24 16:03:32 +03:00
if len(ver_) == 1:
ver = int(ver_[0]), 0
else:
ver = int(ver_[0]), int(ver_[1])
except Exception:
ver = (0, 0)
return cc, ver
2020-03-14 12:07:11 +03:00
def get_python_include_paths() -> List[str]:
ans = []
for name in sysconfig.get_path_names():
if 'include' in name:
ans.append(name)
2020-03-14 12:07:11 +03:00
def gp(x: str) -> Optional[str]:
return sysconfig.get_path(x)
return sorted(frozenset(filter(None, map(gp, sorted(ans)))))
2020-03-14 12:07:11 +03:00
def get_python_flags(cflags: List[str], for_main_executable: bool = False) -> List[str]:
cflags.extend(f'-I{x}' for x in get_python_include_paths())
libs: List[str] = []
libs += (sysconfig.get_config_var('LIBS') or '').split()
libs += (sysconfig.get_config_var('SYSLIBS') or '').split()
2017-01-10 07:25:44 +03:00
fw = sysconfig.get_config_var('PYTHONFRAMEWORK')
if fw:
2017-01-10 10:36:48 +03:00
for var in 'data include stdlib'.split():
val = sysconfig.get_path(var)
if val and f'/{fw}.framework' in val:
fdir = val[:val.index(f'/{fw}.framework')]
2017-02-09 21:34:05 +03:00
if os.path.isdir(
os.path.join(fdir, f'{fw}.framework')
2017-02-09 21:34:05 +03:00
):
framework_dir = fdir
2017-01-10 10:23:23 +03:00
break
else:
raise SystemExit('Failed to find Python framework')
ldlib = sysconfig.get_config_var('LDLIBRARY')
if ldlib:
libs.append(os.path.join(framework_dir, ldlib))
else:
ldlib = sysconfig.get_config_var('LIBDIR')
if ldlib:
libs += [f'-L{ldlib}']
ldlib = sysconfig.get_config_var('VERSION')
if ldlib:
libs += [f'-lpython{ldlib}{sys.abiflags}']
lval = sysconfig.get_config_var('LINKFORSHARED') or ''
if not for_main_executable:
# Python sets the stack size on macOS which is not allowed unless
# compiling an executable https://github.com/kovidgoyal/kitty/issues/289
lval = re.sub(r'-Wl,-stack_size,\d+', '', lval)
libs += list(filter(None, lval.split()))
return libs
def get_sanitize_args(cc: List[str], ccver: Tuple[int, int]) -> List[str]:
2017-11-01 12:05:57 +03:00
sanitize_args = ['-fsanitize=address']
2017-11-08 15:20:32 +03:00
if ccver >= (5, 0):
2017-11-01 12:05:57 +03:00
sanitize_args.append('-fsanitize=undefined')
# if cc == 'gcc' or (cc == 'clang' and ccver >= (4, 2)):
2017-11-01 12:05:57 +03:00
# sanitize_args.append('-fno-sanitize-recover=all')
sanitize_args.append('-fno-omit-frame-pointer')
return sanitize_args
def test_compile(
cc: List[str], *cflags: str,
2021-09-26 10:00:08 +03:00
src: str = '',
source_ext: str = 'c',
link_also: bool = True,
show_stderr: bool = False,
libraries: Iterable[str] = (),
ldflags: Iterable[str] = (),
) -> bool:
src = src or 'int main(void) { return 0; }'
with tempfile.TemporaryDirectory(prefix='kitty-test-compile-') as tdir:
with open(os.path.join(tdir, f'source.{source_ext}'), 'w', encoding='utf-8') as srcf:
2021-09-26 09:51:25 +03:00
print(src, file=srcf)
2021-09-26 09:56:21 +03:00
return subprocess.Popen(
2022-01-05 17:44:23 +03:00
cc + ['-Werror=implicit-function-declaration'] + list(cflags) + ([] if link_also else ['-c']) +
2021-09-26 09:51:25 +03:00
['-o', os.path.join(tdir, 'source.output'), srcf.name] +
[f'-l{x}' for x in libraries] + list(ldflags),
2021-09-26 09:51:25 +03:00
stdout=subprocess.DEVNULL, stdin=subprocess.DEVNULL,
stderr=None if show_stderr else subprocess.DEVNULL
2021-09-26 09:56:21 +03:00
).wait() == 0
2018-04-20 18:02:16 +03:00
def first_successful_compile(cc: List[str], *cflags: str, src: str = '', source_ext: str = 'c') -> str:
2018-04-20 18:26:29 +03:00
for x in cflags:
if test_compile(cc, *shlex.split(x), src=src, source_ext=source_ext):
2018-04-20 18:26:29 +03:00
return x
return ''
2021-05-01 12:28:06 +03:00
def set_arches(flags: List[str], arches: Iterable[str] = ('x86_64', 'arm64')) -> None:
while True:
try:
idx = flags.index('-arch')
except ValueError:
break
del flags[idx]
del flags[idx]
for arch in arches:
flags.extend(('-arch', arch))
def detect_librsync(cc: List[str], cflags: List[str], ldflags: List[str]) -> str:
if not test_compile(
cc, *cflags, libraries=('rsync',), ldflags=ldflags, show_stderr=True,
src='#include <librsync.h>\nint main(void) { rs_strerror(0); return 0; }'):
raise SystemExit('The librsync library is required')
# check for rs_sig_args() which was added to librsync in Apr 2020 version 2.3.0
2021-09-18 09:14:11 +03:00
if test_compile(cc, *cflags, libraries=('rsync',), ldflags=ldflags, src='''
#include <librsync.h>
int main(void) {
rs_magic_number magic_number = 0;
size_t block_len = 0, strong_len = 0;
rs_sig_args(1024, &magic_number, &block_len, &strong_len);
return 0;
}'''):
return '-DKITTY_HAS_RS_SIG_ARGS'
return ''
2021-10-07 11:13:12 +03:00
def is_gcc(cc: Iterable[str]) -> bool:
2021-10-07 11:21:55 +03:00
@lru_cache()
2021-10-07 11:13:12 +03:00
def f(cc: Tuple[str]) -> bool:
raw = subprocess.check_output(cc + ('--version',)).decode('utf-8').splitlines()[0]
return '(GCC)' in raw.split()
return f(tuple(cc))
2017-11-05 06:52:15 +03:00
def init_env(
2020-03-14 12:07:11 +03:00
debug: bool = False,
sanitize: bool = False,
native_optimizations: bool = True,
link_time_optimization: bool = True,
2020-03-14 12:07:11 +03:00
profile: bool = False,
egl_library: Optional[str] = None,
startup_notification_library: Optional[str] = None,
canberra_library: Optional[str] = None,
fontconfig_library: Optional[str] = None,
extra_logging: Iterable[str] = (),
extra_include_dirs: Iterable[str] = (),
2021-05-01 12:28:06 +03:00
ignore_compiler_warnings: bool = False,
build_universal_binary: bool = False,
2022-08-16 19:58:28 +03:00
extra_library_dirs: Iterable[str] = (),
verbose: bool = True
2020-03-14 12:07:11 +03:00
) -> Env:
native_optimizations = native_optimizations and not sanitize and not debug
if native_optimizations and is_macos and is_arm:
# see https://github.com/kovidgoyal/kitty/issues/3126
# -march=native is not supported when targeting Apple Silicon
native_optimizations = False
cc, ccver = cc_version()
2022-08-16 19:58:28 +03:00
if verbose:
print('CC:', cc, ccver)
2018-04-20 18:26:29 +03:00
stack_protector = first_successful_compile(cc, '-fstack-protector-strong', '-fstack-protector')
missing_braces = ''
if ccver < (5, 2) and is_gcc(cc):
missing_braces = '-Wno-missing-braces'
2017-11-08 13:43:21 +03:00
df = '-g3'
float_conversion = ''
2017-11-08 13:43:21 +03:00
if ccver >= (5, 0):
df += ' -Og'
float_conversion = '-Wfloat-conversion'
2021-02-23 14:36:00 +03:00
fortify_source = '' if sanitize and is_macos else '-D_FORTIFY_SOURCE=2'
2017-11-08 13:43:21 +03:00
optimize = df if debug or sanitize else '-O3'
sanitize_args = get_sanitize_args(cc, ccver) if sanitize else set()
cppflags_ = os.environ.get(
2019-07-08 04:16:16 +03:00
'OVERRIDE_CPPFLAGS', '-D{}DEBUG'.format('' if debug else 'N'),
)
cppflags = shlex.split(cppflags_)
for el in extra_logging:
cppflags.append('-DDEBUG_{}'.format(el.upper().replace('-', '_')))
has_copy_file_range = test_compile(cc, src='#define _GNU_SOURCE 1\n#include <unistd.h>\nint main() { copy_file_range(1, NULL, 2, NULL, 0, 0); return 0; }')
if has_copy_file_range:
cppflags.append('-DHAS_COPY_FILE_RANGE')
werror = '' if ignore_compiler_warnings else '-pedantic-errors -Werror'
std = '' if is_openbsd else '-std=c11'
sanitize_flag = ' '.join(sanitize_args)
march = '-march=native' if native_optimizations else ''
cflags_ = os.environ.get(
2017-02-09 21:34:05 +03:00
'OVERRIDE_CFLAGS', (
f'-Wextra {float_conversion} -Wno-missing-field-initializers -Wall -Wstrict-prototypes {std}'
f' {werror} {optimize} {sanitize_flag} -fwrapv {stack_protector} {missing_braces}'
f' -pipe {march} -fvisibility=hidden {fortify_source}'
2017-02-21 14:05:25 +03:00
)
2017-02-09 21:34:05 +03:00
)
cflags = shlex.split(cflags_) + shlex.split(
sysconfig.get_config_var('CCSHARED') or ''
2017-11-05 06:52:15 +03:00
)
ldflags_ = os.environ.get(
2017-11-05 06:52:15 +03:00
'OVERRIDE_LDFLAGS',
'-Wall ' + ' '.join(sanitize_args) + ('' if debug else ' -O3')
2017-02-09 21:34:05 +03:00
)
ldflags = shlex.split(ldflags_)
2017-11-19 20:54:36 +03:00
ldflags.append('-shared')
cppflags += shlex.split(os.environ.get('CPPFLAGS', ''))
cflags += shlex.split(os.environ.get('CFLAGS', ''))
ldflags += shlex.split(os.environ.get('LDFLAGS', ''))
if not debug and not sanitize and not is_openbsd and link_time_optimization:
2017-11-01 12:05:57 +03:00
# See https://github.com/google/sanitizers/issues/647
cflags.append('-flto')
ldflags.append('-flto')
if debug:
cflags.append('-DKITTY_DEBUG_BUILD')
if profile:
cppflags.append('-DWITH_PROFILER')
2017-11-01 12:05:57 +03:00
cflags.append('-g3')
ldflags.append('-lprofiler')
2022-06-02 07:51:34 +03:00
library_paths: Dict[str, List[str]] = {}
def add_lpath(which: str, name: str, val: Optional[str]) -> None:
if val:
if '"' in val:
raise SystemExit(f'Cannot have quotes in library paths: {val}')
library_paths.setdefault(which, []).append(f'{name}="{val}"')
add_lpath('glfw/egl_context.c', '_GLFW_EGL_LIBRARY', egl_library)
add_lpath('kitty/desktop.c', '_KITTY_STARTUP_NOTIFICATION_LIBRARY', startup_notification_library)
add_lpath('kitty/desktop.c', '_KITTY_CANBERRA_LIBRARY', canberra_library)
add_lpath('kitty/fontconfig.c', '_KITTY_FONTCONFIG_LIBRARY', fontconfig_library)
for path in extra_include_dirs:
cflags.append(f'-I{path}')
ldpaths = []
for path in extra_library_dirs:
ldpaths.append(f'-L{path}')
rs_cflag = detect_librsync(cc, cflags, ldflags + ldpaths)
if rs_cflag:
cflags.append(rs_cflag)
2021-05-01 12:28:06 +03:00
if build_universal_binary:
set_arches(cflags)
set_arches(ldflags)
return Env(cc, cppflags, cflags, ldflags, library_paths, ccver=ccver, ldpaths=ldpaths)
2017-11-19 10:24:02 +03:00
2020-03-14 12:07:11 +03:00
def kitty_env() -> Env:
2017-11-19 10:24:02 +03:00
ans = env.copy()
2017-11-19 20:54:36 +03:00
cflags = ans.cflags
cflags.append('-pthread')
# We add 4000 to the primary version because vim turns on SGR mouse mode
# automatically if this version is high enough
libcrypto_cflags, libcrypto_ldflags = libcrypto_flags()
cppflags = ans.cppflags
cppflags.append(f'-DPRIMARY_VERSION={version[0] + 4000}')
cppflags.append(f'-DSECONDARY_VERSION={version[1]}')
cppflags.append('-DXT_VERSION="{}"'.format('.'.join(map(str, version))))
at_least_version('harfbuzz', 1, 5)
cflags.extend(pkg_config('libpng', '--cflags-only-I'))
cflags.extend(pkg_config('lcms2', '--cflags-only-I'))
cflags.extend(libcrypto_cflags)
2017-11-21 03:49:46 +03:00
if is_macos:
platform_libs = [
'-framework', 'Carbon', '-framework', 'CoreText', '-framework', 'CoreGraphics',
]
test_program_src = '''#include <UserNotifications/UserNotifications.h>
int main(void) { return 0; }\n'''
2020-10-19 13:32:49 +03:00
user_notifications_framework = first_successful_compile(
ans.cc, '-framework UserNotifications', src=test_program_src, source_ext='m')
if user_notifications_framework:
platform_libs.extend(shlex.split(user_notifications_framework))
else:
cppflags.append('-DKITTY_USE_DEPRECATED_MACOS_NOTIFICATION_API')
# Apple deprecated OpenGL in Mojave (10.14) silence the endless
# warnings about it
cppflags.append('-DGL_SILENCE_DEPRECATION')
else:
cflags.extend(pkg_config('fontconfig', '--cflags-only-I'))
platform_libs = []
2017-10-30 19:54:01 +03:00
cflags.extend(pkg_config('harfbuzz', '--cflags-only-I'))
platform_libs.extend(pkg_config('harfbuzz', '--libs'))
pylib = get_python_flags(cflags)
2017-11-21 03:49:46 +03:00
gl_libs = ['-framework', 'OpenGL'] if is_macos else pkg_config('gl', '--libs')
libpng = pkg_config('libpng', '--libs')
lcms2 = pkg_config('lcms2', '--libs')
ans.ldpaths += pylib + platform_libs + gl_libs + libpng + lcms2 + libcrypto_ldflags
2017-11-21 03:49:46 +03:00
if is_macos:
2017-11-20 11:39:13 +03:00
ans.ldpaths.extend('-framework Cocoa'.split())
elif not is_openbsd:
2017-11-19 10:24:02 +03:00
ans.ldpaths += ['-lrt']
if '-ldl' not in ans.ldpaths:
ans.ldpaths.append('-ldl')
if '-lz' not in ans.ldpaths:
ans.ldpaths.append('-lz')
with suppress(FileExistsError):
os.mkdir(build_dir)
2017-11-19 10:24:02 +03:00
return ans
2020-03-14 12:07:11 +03:00
def define(x: str) -> str:
return f'-D{x}'
2020-03-14 12:07:11 +03:00
def run_tool(cmd: Union[str, List[str]], desc: Optional[str] = None) -> None:
if isinstance(cmd, str):
cmd = shlex.split(cmd[0])
2017-11-20 12:56:27 +03:00
if verbose:
desc = None
2017-11-19 09:08:35 +03:00
print(desc or ' '.join(cmd))
p = subprocess.Popen(cmd)
ret = p.wait()
if ret != 0:
2017-11-19 20:54:36 +03:00
if desc:
print(' '.join(cmd))
raise SystemExit(ret)
@lru_cache
def get_vcs_rev_define() -> str:
ans = ''
if os.path.exists('.git'):
try:
rev = subprocess.check_output(['git', 'rev-parse', 'HEAD']).decode('utf-8')
except FileNotFoundError:
try:
with open('.git/refs/heads/master') as f:
rev = f.read()
except NotADirectoryError:
with open('.git') as f:
gitloc = f.read()
2019-06-23 05:06:21 +03:00
with open(os.path.join(gitloc, 'refs/heads/master')) as f:
rev = f.read()
ans = rev.strip()
return ans
2022-06-01 06:17:19 +03:00
def get_source_specific_defines(env: Env, src: str) -> Tuple[str, Optional[List[str]]]:
if src == 'kitty/parser_dump.c':
return 'kitty/parser.c', ['DUMP_COMMANDS']
if src == 'kitty/data-types.c':
return src, [f'KITTY_VCS_REV="{get_vcs_rev_define()}"']
2022-06-01 06:17:19 +03:00
with suppress(KeyError):
return src, env.library_paths[src]
return src, None
2020-03-14 12:07:11 +03:00
def newer(dest: str, *sources: str) -> bool:
try:
dtime = os.path.getmtime(dest)
except OSError:
return True
for s in sources:
with suppress(FileNotFoundError):
if os.path.getmtime(s) >= dtime:
return True
return False
2020-03-14 12:07:11 +03:00
def dependecies_for(src: str, obj: str, all_headers: Iterable[str]) -> Iterable[str]:
dep_file = obj.rpartition('.')[0] + '.d'
try:
with open(dep_file) as f:
deps = f.read()
except FileNotFoundError:
yield src
yield from iter(all_headers)
else:
2017-11-05 06:52:15 +03:00
RE_INC = re.compile(
r'^(?P<target>.+?):\s+(?P<deps>.+?)$', re.MULTILINE
)
2017-09-30 09:55:03 +03:00
SPACE_TOK = '\x1B'
text = deps.replace('\\\n', ' ').replace('\\ ', SPACE_TOK)
for match in RE_INC.finditer(text):
2017-11-05 06:52:15 +03:00
files = (
f.replace(SPACE_TOK, ' ') for f in match.group('deps').split()
)
2017-09-30 09:55:03 +03:00
for path in files:
path = os.path.abspath(path)
if path.startswith(base):
yield path
2020-03-14 12:07:11 +03:00
def parallel_run(items: List[Command]) -> None:
try:
num_workers = max(2, os.cpu_count() or 1)
except Exception:
num_workers = 2
items = list(reversed(items))
2021-10-27 06:15:45 +03:00
workers: Dict[int, Tuple[Optional[Command], Optional['subprocess.Popen[bytes]']]] = {}
2017-11-19 09:08:35 +03:00
failed = None
2019-07-05 16:04:01 +03:00
num, total = 0, len(items)
2017-11-19 09:08:35 +03:00
2020-03-14 12:07:11 +03:00
def wait() -> None:
2017-11-19 09:08:35 +03:00
nonlocal failed
if not workers:
return
pid, s = os.wait()
compile_cmd, w = workers.pop(pid, (None, None))
if compile_cmd is None:
return
if ((s & 0xff) != 0 or ((s >> 8) & 0xff) != 0):
if failed is None:
failed = compile_cmd
elif compile_cmd.on_success is not None:
compile_cmd.on_success()
2017-11-19 09:08:35 +03:00
2019-07-08 05:06:30 +03:00
printed = False
isatty = sys.stdout.isatty()
2017-11-19 09:08:35 +03:00
while items and failed is None:
while len(workers) < num_workers and items:
compile_cmd = items.pop()
2019-07-05 16:04:01 +03:00
num += 1
2017-11-20 12:56:27 +03:00
if verbose:
print(' '.join(compile_cmd.cmd))
elif isatty:
print(f'\r\x1b[K[{num}/{total}] {compile_cmd.desc}', end='')
else:
print(f'[{num}/{total}] {compile_cmd.desc}', flush=True)
2019-07-08 05:06:30 +03:00
printed = True
w = subprocess.Popen(compile_cmd.cmd, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
workers[w.pid] = compile_cmd, w
2017-11-19 09:08:35 +03:00
wait()
while len(workers):
wait()
2019-07-08 05:06:30 +03:00
if not verbose and printed:
print(' done')
2017-11-19 09:08:35 +03:00
if failed:
2019-07-05 16:04:01 +03:00
print(failed.desc)
2020-03-14 12:07:11 +03:00
run_tool(list(failed.cmd))
2017-11-19 09:08:35 +03:00
class CompilationDatabase:
2020-03-14 12:07:11 +03:00
def __init__(self, incremental: bool):
self.incremental = incremental
2020-03-14 12:07:11 +03:00
self.compile_commands: List[Command] = []
self.link_commands: List[Command] = []
def add_command(
self,
desc: str,
cmd: List[str],
2021-10-27 06:15:45 +03:00
is_newer_func: Callable[[], bool],
2020-03-14 12:07:11 +03:00
key: Optional[CompileKey] = None,
2021-10-27 06:15:45 +03:00
on_success: Optional[Callable[[], None]] = None,
2020-03-14 12:07:11 +03:00
keyfile: Optional[str] = None
) -> None:
def no_op() -> None:
pass
queue = self.link_commands if keyfile is None else self.compile_commands
2020-03-14 12:07:11 +03:00
queue.append(Command(desc, cmd, is_newer_func, on_success or no_op, key, keyfile))
2020-03-14 12:07:11 +03:00
def build_all(self) -> None:
2020-03-14 12:07:11 +03:00
def sort_key(compile_cmd: Command) -> int:
if compile_cmd.keyfile:
return os.path.getsize(compile_cmd.keyfile)
return 0
items = []
for compile_cmd in self.compile_commands:
if not self.incremental or self.cmd_changed(compile_cmd) or compile_cmd.is_newer_func():
items.append(compile_cmd)
items.sort(key=sort_key, reverse=True)
parallel_run(items)
items = []
for compile_cmd in self.link_commands:
if not self.incremental or compile_cmd.is_newer_func():
items.append(compile_cmd)
parallel_run(items)
2020-03-14 12:07:11 +03:00
def cmd_changed(self, compile_cmd: Command) -> bool:
key, cmd = compile_cmd.key, compile_cmd.cmd
2020-03-14 12:07:11 +03:00
return bool(self.db.get(key) != cmd)
2020-03-14 12:07:11 +03:00
def __enter__(self) -> 'CompilationDatabase':
self.all_keys: Set[CompileKey] = set()
self.dbpath = os.path.abspath('compile_commands.json')
self.linkdbpath = os.path.join(os.path.dirname(self.dbpath), 'link_commands.json')
try:
with open(self.dbpath) as f:
compilation_database = json.load(f)
except FileNotFoundError:
compilation_database = []
try:
with open(self.linkdbpath) as f:
link_database = json.load(f)
except FileNotFoundError:
link_database = []
compilation_database = {
CompileKey(k['file'], k['output']): k['arguments'] for k in compilation_database
}
self.db = compilation_database
2020-03-14 12:08:56 +03:00
self.linkdb = {tuple(k['output']): k['arguments'] for k in link_database}
return self
2020-03-14 12:07:11 +03:00
def __exit__(self, *a: object) -> None:
cdb = self.db
for key in set(cdb) - self.all_keys:
del cdb[key]
compilation_database = [
2020-03-14 12:07:11 +03:00
{'file': c.key.src, 'arguments': c.cmd, 'directory': base, 'output': c.key.dest} for c in self.compile_commands if c.key is not None
]
with open(self.dbpath, 'w') as f:
json.dump(compilation_database, f, indent=2, sort_keys=True)
with open(self.linkdbpath, 'w') as f:
json.dump([{'output': c.key, 'arguments': c.cmd, 'directory': base} for c in self.link_commands], f, indent=2, sort_keys=True)
2020-03-14 12:07:11 +03:00
def compile_c_extension(
kenv: Env,
module: str,
compilation_database: CompilationDatabase,
sources: List[str],
headers: List[str],
desc_prefix: str = ''
) -> None:
prefix = os.path.basename(module)
2017-02-09 21:34:05 +03:00
objects = [
os.path.join(build_dir, f'{prefix}-{os.path.basename(src)}.o')
2017-02-09 21:34:05 +03:00
for src in sources
]
for original_src, dest in zip(sources, objects):
src = original_src
cppflags = kenv.cppflags[:]
2022-06-01 06:17:19 +03:00
src, defines = get_source_specific_defines(kenv, src)
if defines is not None:
cppflags.extend(map(define, defines))
cmd = kenv.cc + ['-MMD'] + cppflags + kenv.cflags
cmd += ['-c', src] + ['-o', dest]
key = CompileKey(original_src, os.path.basename(dest))
desc = f'Compiling {emphasis(desc_prefix + src)} ...'
2019-07-11 17:49:22 +03:00
compilation_database.add_command(desc, cmd, partial(newer, dest, *dependecies_for(src, dest, headers)), key=key, keyfile=src)
dest = os.path.join(build_dir, f'{module}.so')
real_dest = f'{module}.so'
os.makedirs(os.path.dirname(dest), exist_ok=True)
desc = f'Linking {emphasis(desc_prefix + module)} ...'
# Old versions of clang don't like -pthread being passed to the linker
# Don't treat linker warnings as errors (linker generates spurious
# warnings on some old systems)
unsafe = {'-pthread', '-Werror', '-pedantic-errors'}
linker_cflags = list(filter(lambda x: x not in unsafe, kenv.cflags))
cmd = kenv.cc + linker_cflags + kenv.ldflags + objects + kenv.ldpaths + ['-o', dest]
2020-03-14 12:07:11 +03:00
def on_success() -> None:
os.rename(dest, real_dest)
2022-01-29 15:14:36 +03:00
compilation_database.add_command(desc, cmd, partial(newer, real_dest, *objects), on_success=on_success, key=CompileKey('', f'{module}.so'))
2020-03-14 12:07:11 +03:00
def find_c_files() -> Tuple[List[str], List[str]]:
ans, headers = [], []
2019-07-11 17:49:22 +03:00
d = 'kitty'
2021-03-29 08:55:42 +03:00
exclude = {
'fontconfig.c', 'freetype.c', 'desktop.c', 'freetype_render_ui_text.c'
} if is_macos else {
'core_text.m', 'cocoa_window.m', 'macos_process_info.c'
}
2019-07-16 14:10:48 +03:00
for x in sorted(os.listdir(d)):
ext = os.path.splitext(x)[1]
if ext in ('.c', '.m') and os.path.basename(x) not in exclude:
2016-11-13 07:57:24 +03:00
ans.append(os.path.join('kitty', x))
elif ext == '.h':
headers.append(os.path.join('kitty', x))
2016-11-13 07:57:24 +03:00
ans.append('kitty/parser_dump.c')
2020-03-14 12:07:11 +03:00
return ans, headers
2016-11-11 19:41:40 +03:00
2020-03-14 12:07:11 +03:00
def compile_glfw(compilation_database: CompilationDatabase) -> None:
2017-11-21 03:49:46 +03:00
modules = 'cocoa' if is_macos else 'x11 wayland'
2017-11-19 20:54:36 +03:00
for module in modules.split():
try:
genv = glfw.init_env(env, pkg_config, pkg_version, at_least_version, test_compile, module)
except SystemExit as err:
if module != 'wayland':
raise
2017-11-21 04:37:53 +03:00
print(err, file=sys.stderr)
2017-11-21 05:02:48 +03:00
print(error('Disabling building of wayland backend'), file=sys.stderr)
continue
2017-11-19 20:54:36 +03:00
sources = [os.path.join('glfw', x) for x in genv.sources]
all_headers = [os.path.join('glfw', x) for x in genv.all_headers]
if module == 'wayland':
try:
2021-10-27 08:43:28 +03:00
glfw.build_wayland_protocols(genv, parallel_run, emphasis, newer, 'glfw')
except SystemExit as err:
print(err, file=sys.stderr)
print(error('Disabling building of wayland backend'), file=sys.stderr)
continue
compile_c_extension(
genv, f'kitty/glfw-{module}', compilation_database,
sources, all_headers, desc_prefix=f'[{module}] ')
2017-11-19 20:54:36 +03:00
2020-03-14 12:07:11 +03:00
def kittens_env() -> Env:
kenv = env.copy()
cflags = kenv.cflags
cflags.append('-pthread')
cflags.append('-Ikitty')
pylib = get_python_flags(cflags)
kenv.ldpaths += pylib
return kenv
2020-03-14 12:07:11 +03:00
def compile_kittens(compilation_database: CompilationDatabase) -> None:
kenv = kittens_env()
2020-03-14 12:07:11 +03:00
def list_files(q: str) -> List[str]:
2019-07-16 04:52:31 +03:00
return sorted(glob.glob(q))
2020-03-06 05:18:41 +03:00
def files(
kitten: str,
output: str,
extra_headers: Sequence[str] = (),
extra_sources: Sequence[str] = (),
filter_sources: Optional[Callable[[str], bool]] = None,
includes: Sequence[str] = (), libraries: Sequence[str] = (),
) -> Tuple[str, List[str], List[str], str, Sequence[str], Sequence[str]]:
sources = list(filter(filter_sources, list(extra_sources) + list_files(os.path.join('kittens', kitten, '*.c'))))
headers = list_files(os.path.join('kittens', kitten, '*.h')) + list(extra_headers)
return kitten, sources, headers, f'kittens/{kitten}/{output}', includes, libraries
for kitten, sources, all_headers, dest, includes, libraries in (
files('unicode_input', 'unicode_names'),
files('diff', 'diff_speedup'),
files('transfer', 'rsync', libraries=('rsync',)),
files(
'choose', 'subseq_matcher',
extra_headers=('kitty/charsets.h',),
extra_sources=('kitty/charsets.c',),
filter_sources=lambda x: 'windows_compat.c' not in x),
):
final_env = kenv.copy()
final_env.cflags.extend(f'-I{x}' for x in includes)
final_env.ldpaths[:0] = list(f'-l{x}' for x in libraries)
compile_c_extension(
final_env, dest, compilation_database, sources, all_headers + ['kitty/data-types.h'])
def init_env_from_args(args: Options, native_optimizations: bool = False) -> None:
2017-11-19 10:24:02 +03:00
global env
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.fontconfig_library,
2021-05-01 12:28:06 +03:00
args.extra_logging, args.extra_include_dirs, args.ignore_compiler_warnings,
2022-08-16 19:58:28 +03:00
args.build_universal_binary, args.extra_library_dirs, verbose=args.verbose > 0
)
@lru_cache
def extract_rst_targets() -> Dict[str, Dict[str, str]]:
2022-08-19 08:50:50 +03:00
m = runpy.run_path('docs/extract-rst-targets.py')
return cast(Dict[str, Dict[str, str]], m['main']())
def build_ref_map() -> str:
d = extract_rst_targets()
2022-08-19 08:59:47 +03:00
h = 'static const char docs_ref_map[] = {\n' + textwrap.fill(', '.join(map(str, bytearray(json.dumps(d).encode('utf-8'))))) + '\n};\n'
2022-08-19 08:50:50 +03:00
dest = 'kitty/docs_ref_map_generated.h'
q = ''
with suppress(FileNotFoundError), open(dest) as f:
q = f.read()
if q != h:
with open(dest, 'w') as f:
f.write(h)
return dest
def build(args: Options, native_optimizations: bool = True, call_init: bool = True) -> None:
if call_init:
init_env_from_args(args, native_optimizations)
2020-03-14 12:07:11 +03:00
sources, headers = find_c_files()
2022-08-19 08:50:50 +03:00
headers.append(build_ref_map())
compile_c_extension(
2020-03-14 12:07:11 +03:00
kitty_env(), 'kitty/fast_data_types', args.compilation_database, sources, headers
)
compile_glfw(args.compilation_database)
compile_kittens(args.compilation_database)
2020-03-14 12:07:11 +03:00
def safe_makedirs(path: str) -> None:
os.makedirs(path, exist_ok=True)
2022-08-16 19:58:28 +03:00
def update_go_generated_files(args: Options, kitty_exe: str) -> None:
# update all the various auto-generated go files, if needed
rc_sources = [x for x in glob.glob('kitty/rc/*.py') if os.path.basename(x) not in ('base.py', '__init__.py')]
rc_objects = glob.glob('tools/cmd/at/*_generated.go')
generated = rc_objects + glob.glob('constants_generated.go')
2022-08-24 12:41:08 +03:00
sources = ['gen-rc-go.py', 'kitty/constants.py', 'setup.py', 'kitty/docs_ref_map_generated.h', 'tools/cmd/at/template.go'] + rc_sources
if generated:
oldest_generated = min(map(os.path.getmtime, generated))
newest_source = max(map(os.path.getmtime, sources))
if oldest_generated > newest_source and len(rc_sources) == len(rc_objects) and 'constants_generated.go' in generated:
return
2022-08-16 19:58:28 +03:00
if args.verbose:
print('Updating Go generated files...')
cp = subprocess.run([kitty_exe, '+launch', os.path.join(base, 'gen-rc-go.py')])
if cp.returncode != 0:
raise SystemExit(cp.returncode)
def build_kitty_tool(args: Options, launcher_dir: str, for_freeze: bool = False) -> None:
sys.stdout.flush()
sys.stderr.flush()
go = shutil.which('go')
if not go:
raise SystemExit('The go tool was not found on this system. Install Go')
2022-08-16 19:58:28 +03:00
update_go_generated_files(args, os.path.join(launcher_dir, appname))
cmd = [go, 'build']
2022-08-16 07:29:20 +03:00
if args.verbose:
cmd.append('-v')
ld_flags = [f"-X 'kitty.VCSRevision={get_vcs_rev_define()}'"]
if for_freeze:
ld_flags.append("-X 'kitty.IsFrozenBuild=true")
2022-08-15 12:27:37 +03:00
if not args.debug:
ld_flags.append('-s')
ld_flags.append('-w')
cmd += ['-ldflags', ' '.join(ld_flags)]
dest = os.path.join(launcher_dir, 'kitty-tool')
src = os.path.abspath('tools/cmd')
def run_one(dest: str, **env: str) -> None:
c = cmd + ['-o', dest, src]
if args.verbose:
print(shlex.join(c))
e = os.environ.copy()
e.update(env)
cp = subprocess.run(c, env=e)
if cp.returncode != 0:
raise SystemExit(cp.returncode)
if args.build_universal_binary:
outs = []
for arch in ('amd64', 'arm64'):
d = dest + f'-{arch}'
run_one(d, GOOS='darwin', GOARCH=arch)
outs.append(d)
subprocess.check_call(['lipo', '-create', '-output', dest] + outs)
for x in outs:
os.remove(x)
else:
run_one(dest)
2022-08-15 12:27:37 +03:00
2020-03-14 12:07:11 +03:00
def build_launcher(args: Options, launcher_dir: str = '.', bundle_type: str = 'source') -> None:
werror = '' if args.ignore_compiler_warnings else '-pedantic-errors -Werror'
cflags = f'-Wall {werror} -fpie'.split()
cppflags = []
2020-03-06 05:18:41 +03:00
libs: List[str] = []
2022-07-05 18:05:07 +03:00
ldflags = shlex.split(os.environ.get('LDFLAGS', ''))
if args.profile or args.sanitize:
if args.sanitize:
cflags.append('-g3')
2022-07-05 18:05:07 +03:00
sanitize_args = get_sanitize_args(env.cc, env.ccver)
cflags.extend(sanitize_args)
ldflags.extend(sanitize_args)
libs += ['-lasan'] if is_gcc(env.cc) and not is_macos else []
else:
cflags.append('-g')
if args.profile:
libs.append('-lprofiler')
else:
cflags.append('-g3' if args.debug else '-O3')
if bundle_type.endswith('-freeze'):
cppflags.append('-DFOR_BUNDLE')
cppflags.append(f'-DPYVER="{sysconfig.get_python_version()}"')
cppflags.append(f'-DKITTY_LIB_DIR_NAME="{args.libdir_name}"')
elif bundle_type == 'source':
cppflags.append('-DFROM_SOURCE')
if bundle_type.startswith('macos-'):
klp = '../Resources/kitty'
elif bundle_type.startswith('linux-'):
klp = '../{}/kitty'.format(args.libdir_name.strip('/'))
elif bundle_type == 'source':
klp = os.path.relpath('.', launcher_dir)
else:
raise SystemExit(f'Unknown bundle type: {bundle_type}')
cppflags.append(f'-DKITTY_LIB_PATH="{klp}"')
pylib = get_python_flags(cflags, for_main_executable=True)
cppflags += shlex.split(os.environ.get('CPPFLAGS', ''))
cflags += shlex.split(os.environ.get('CFLAGS', ''))
for path in args.extra_include_dirs:
cflags.append(f'-I{path}')
2022-07-21 08:24:21 +03:00
if args.build_universal_binary:
set_arches(cflags)
set_arches(ldflags)
if bundle_type == 'linux-freeze':
# --disable-new-dtags prevents -rpath from generating RUNPATH instead of
# RPATH entries in the launcher. The ld dynamic linker does not search
# RUNPATH locations for transitive dependencies, unlike RPATH.
ldflags += ['-Wl,--disable-new-dtags', '-Wl,-rpath,$ORIGIN/../lib']
os.makedirs(launcher_dir, exist_ok=True)
2022-07-05 18:18:01 +03:00
os.makedirs(build_dir, exist_ok=True)
objects = []
for src in ('kitty/launcher/main.c',):
obj = os.path.join(build_dir, src.replace('/', '-').replace('.c', '.o'))
objects.append(obj)
cmd = env.cc + cppflags + cflags + ['-c', src, '-o', obj]
2022-07-07 05:17:17 +03:00
key = CompileKey(src, os.path.basename(obj))
args.compilation_database.add_command(f'Compiling {emphasis(src)} ...', cmd, partial(newer, obj, src), key=key, keyfile=src)
dest = os.path.join(launcher_dir, 'kitty')
desc = f'Linking {emphasis("launcher")} ...'
cmd = env.cc + ldflags + objects + libs + pylib + ['-o', dest]
args.compilation_database.add_command(desc, cmd, partial(newer, dest, *objects), key=CompileKey('', 'kitty'))
args.compilation_database.build_all()
# Packaging {{{
2020-03-14 12:07:11 +03:00
def copy_man_pages(ddir: str) -> None:
mandir = os.path.join(ddir, 'share', 'man')
safe_makedirs(mandir)
2021-04-28 11:11:48 +03:00
man_levels = '15'
with suppress(FileNotFoundError):
2021-04-28 11:11:48 +03:00
for x in man_levels:
shutil.rmtree(os.path.join(mandir, f'man{x}'))
2019-07-11 17:49:22 +03:00
src = 'docs/_build/man'
if not os.path.exists(src):
raise SystemExit('''\
2021-04-28 10:41:59 +03:00
The kitty man pages are missing. If you are building from git then run:
make && make docs
(needs the sphinx documentation system to be installed)
''')
2021-04-28 11:11:48 +03:00
for x in man_levels:
os.makedirs(os.path.join(mandir, f'man{x}'))
for y in glob.glob(os.path.join(src, f'*.{x}')):
shutil.copy2(y, os.path.join(mandir, f'man{x}'))
2020-03-14 12:07:11 +03:00
def copy_html_docs(ddir: str) -> None:
htmldir = os.path.join(ddir, 'share', 'doc', appname, 'html')
safe_makedirs(os.path.dirname(htmldir))
with suppress(FileNotFoundError):
shutil.rmtree(htmldir)
2019-07-11 17:49:22 +03:00
src = 'docs/_build/html'
if not os.path.exists(src):
raise SystemExit('''\
The kitty html docs are missing. If you are building from git then run:
make && make docs
(needs the sphinx documentation system to be installed)
''')
shutil.copytree(src, htmldir)
2020-03-14 12:07:11 +03:00
def compile_python(base_path: str) -> None:
import compileall
import py_compile
try:
num_workers = max(1, os.cpu_count() or 1)
except Exception:
num_workers = 1
for root, dirs, files in os.walk(base_path):
for f in files:
if f.rpartition('.')[-1] in ('pyc', 'pyo'):
os.remove(os.path.join(root, f))
2020-03-08 19:38:18 +03:00
exclude = re.compile('.*/shell-integration/ssh/bootstrap.py')
2020-03-14 12:07:11 +03:00
def c(base_path: str, **kw: object) -> None:
2020-03-06 05:18:41 +03:00
try:
2020-03-08 19:38:18 +03:00
kw['invalidation_mode'] = py_compile.PycInvalidationMode.UNCHECKED_HASH
2020-03-06 05:18:41 +03:00
except AttributeError:
2020-03-08 19:38:18 +03:00
pass
2020-03-14 12:07:11 +03:00
compileall.compile_dir(base_path, **kw) # type: ignore
2020-03-08 19:38:18 +03:00
for optimize in (0, 1, 2):
c(base_path, ddir='', rx=exclude, force=True, optimize=optimize, quiet=1, workers=num_workers)
2020-03-14 12:07:11 +03:00
def create_linux_bundle_gunk(ddir: str, libdir_name: str) -> None:
2019-07-11 17:49:22 +03:00
if not os.path.exists('docs/_build/html'):
2022-01-28 14:34:13 +03:00
make = 'gmake' if is_freebsd else 'make'
2021-05-04 23:48:23 +03:00
run_tool([make, 'docs'])
copy_man_pages(ddir)
copy_html_docs(ddir)
2021-07-13 05:30:15 +03:00
for (icdir, ext) in {'256x256': 'png', 'scalable': 'svg'}.items():
icdir = os.path.join(ddir, 'share', 'icons', 'hicolor', icdir, 'apps')
safe_makedirs(icdir)
shutil.copy2(f'logo/kitty.{ext}', icdir)
deskdir = os.path.join(ddir, 'share', 'applications')
safe_makedirs(deskdir)
with open(os.path.join(deskdir, 'kitty.desktop'), 'w') as f:
f.write(
'''\
[Desktop Entry]
Version=1.0
Type=Application
Name=kitty
GenericName=Terminal emulator
2021-01-12 17:31:44 +03:00
Comment=Fast, feature-rich, GPU based terminal
TryExec=kitty
Exec=kitty
Icon=kitty
Categories=System;TerminalEmulator;
'''
)
with open(os.path.join(deskdir, 'kitty-open.desktop'), 'w') as f:
f.write(
'''\
[Desktop Entry]
Version=1.0
Type=Application
Name=kitty URL Launcher
GenericName=Terminal emulator
Comment=Open URLs with kitty
TryExec=kitty
Exec=kitty +open %U
Icon=kitty
Categories=System;TerminalEmulator;
NoDisplay=true
MimeType=image/*;application/x-sh;application/x-shellscript;inode/directory;text/*;x-scheme-handler/kitty;
2017-02-09 21:34:05 +03:00
'''
)
2020-03-14 12:07:11 +03:00
base = Path(ddir)
in_src_launcher = base / (f'{libdir_name}/kitty/kitty/launcher/kitty')
2020-03-14 12:07:11 +03:00
launcher = base / 'bin/kitty'
2019-06-28 09:30:12 +03:00
if os.path.exists(in_src_launcher):
os.remove(in_src_launcher)
os.makedirs(os.path.dirname(in_src_launcher), exist_ok=True)
os.symlink(os.path.relpath(launcher, os.path.dirname(in_src_launcher)), in_src_launcher)
2020-03-14 12:07:11 +03:00
def macos_info_plist() -> bytes:
import plistlib
VERSION = '.'.join(map(str, version))
def access(what: str, verb: str = 'would like to access') -> str:
return f'A program running inside kitty {verb} {what}'
docs = [
{
'CFBundleTypeName': 'Terminal scripts',
'CFBundleTypeExtensions': ['command', 'sh', 'zsh', 'bash', 'fish', 'tool'],
'CFBundleTypeIconFile': f'{appname}.icns',
'CFBundleTypeRole': 'Editor',
},
{
'CFBundleTypeName': 'Folders',
'LSItemContentTypes': ['public.directory'],
'CFBundleTypeRole': 'Editor',
'LSHandlerRank': 'Alternate',
},
{
'LSItemContentTypes': ['public.unix-executable'],
'CFBundleTypeRole': 'Shell',
},
{
'CFBundleTypeName': 'Text files',
'LSItemContentTypes': ['public.text'],
'CFBundleTypeRole': 'Editor',
'LSHandlerRank': 'Alternate',
},
{
'CFBundleTypeName': 'Image files',
'LSItemContentTypes': ['public.image'],
'CFBundleTypeRole': 'Viewer',
'LSHandlerRank': 'Alternate',
},
# Allows dragging arbitrary files to kitty Dock icon, and list kitty in the Open With context menu.
{
'CFBundleTypeName': 'All files',
'LSItemContentTypes': ['public.archive', 'public.content', 'public.data'],
'CFBundleTypeRole': 'Editor',
'LSHandlerRank': 'Alternate',
},
]
url_schemes = [
{
'CFBundleURLName': 'File URL',
'CFBundleURLSchemes': ['file'],
},
{
'CFBundleURLName': 'FTP URL',
'CFBundleURLSchemes': ['ftp', 'ftps'],
},
{
'CFBundleURLName': 'Gemini URL',
'CFBundleURLSchemes': ['gemini'],
},
{
'CFBundleURLName': 'Git URL',
'CFBundleURLSchemes': ['git'],
},
{
'CFBundleURLName': 'Gopher URL',
'CFBundleURLSchemes': ['gopher'],
},
{
'CFBundleURLName': 'HTTP URL',
'CFBundleURLSchemes': ['http', 'https'],
},
{
'CFBundleURLName': 'IRC URL',
'CFBundleURLSchemes': ['irc', 'irc6', 'ircs'],
},
{
'CFBundleURLName': 'kitty URL',
'CFBundleURLSchemes': ['kitty'],
'LSHandlerRank': 'Owner',
'LSIsAppleDefaultForScheme': True,
},
{
'CFBundleURLName': 'Mail Address URL',
'CFBundleURLSchemes': ['mailto'],
},
{
'CFBundleURLName': 'News URL',
'CFBundleURLSchemes': ['news', 'nntp'],
},
{
'CFBundleURLName': 'SSH and SFTP URL',
'CFBundleURLSchemes': ['ssh', 'sftp'],
},
{
'CFBundleURLName': 'Telnet URL',
'CFBundleURLSchemes': ['telnet'],
},
]
services = [
{
'NSMenuItem': {'default': f'New {appname} Tab Here'},
'NSMessage': 'openTab',
'NSRequiredContext': {'NSTextContent': 'FilePath'},
'NSSendTypes': ['NSFilenamesPboardType', 'public.plain-text'],
},
{
'NSMenuItem': {'default': f'New {appname} Window Here'},
'NSMessage': 'openOSWindow',
'NSRequiredContext': {'NSTextContent': 'FilePath'},
'NSSendTypes': ['NSFilenamesPboardType', 'public.plain-text'],
},
{
'NSMenuItem': {'default': f'Open with {appname}'},
'NSMessage': 'openFileURLs',
'NSRequiredContext': {'NSTextContent': 'FilePath'},
'NSSendTypes': ['NSFilenamesPboardType', 'public.plain-text'],
},
]
pl = dict(
# Naming
CFBundleName=appname,
CFBundleDisplayName=appname,
# Identification
CFBundleIdentifier=f'net.kovidgoyal.{appname}',
# Bundle Version Info
CFBundleVersion=VERSION,
CFBundleShortVersionString=VERSION,
CFBundleInfoDictionaryVersion='6.0',
NSHumanReadableCopyright=time.strftime('Copyright %Y, Kovid Goyal'),
CFBundleGetInfoString='kitty - The fast, feature-rich, GPU based terminal emulator. https://sw.kovidgoyal.net/kitty/',
# Operating System Version
LSMinimumSystemVersion='10.12.0',
# Categorization
CFBundlePackageType='APPL',
CFBundleSignature='????',
LSApplicationCategoryType='public.app-category.utilities',
# App Execution
CFBundleExecutable=appname,
LSEnvironment={'KITTY_LAUNCHED_BY_LAUNCH_SERVICES': '1'},
LSRequiresNativeExecution=True,
NSSupportsSuddenTermination=False,
# Localization
# see https://github.com/kovidgoyal/kitty/issues/1233
CFBundleDevelopmentRegion='English',
CFBundleAllowMixedLocalizations=True,
TICapsLockLanguageSwitchCapable=True,
# User Interface and Graphics
CFBundleIconFile=f'{appname}.icns',
NSHighResolutionCapable=True,
NSSupportsAutomaticGraphicsSwitching=True,
# Needed for dark mode in Mojave when linking against older SDKs
NSRequiresAquaSystemAppearance='NO',
# Document and URL Types
CFBundleDocumentTypes=docs,
CFBundleURLTypes=url_schemes,
# Services
NSServices=services,
# Calendar and Reminders
NSCalendarsUsageDescription=access('your calendar data.'),
NSRemindersUsageDescription=access('your reminders.'),
# Camera and Microphone
NSCameraUsageDescription=access('the camera.'),
NSMicrophoneUsageDescription=access('the microphone.'),
# Contacts
NSContactsUsageDescription=access('your contacts.'),
# Location
NSLocationUsageDescription=access('your location information.'),
NSLocationTemporaryUsageDescriptionDictionary=access('your location temporarily.'),
# Motion
NSMotionUsageDescription=access('motion data.'),
# Networking
NSLocalNetworkUsageDescription=access('local network.'),
# Photos
NSPhotoLibraryUsageDescription=access('your photo library.'),
# Scripting
NSAppleScriptEnabled=False,
# Security
NSAppleEventsUsageDescription=access('AppleScript.'),
NSSystemAdministrationUsageDescription=access('elevated privileges.', 'requires'),
# Speech
NSSpeechRecognitionUsageDescription=access('speech recognition.'),
)
return plistlib.dumps(pl)
2020-03-14 12:07:11 +03:00
def create_macos_app_icon(where: str = 'Resources') -> None:
iconset_dir = os.path.abspath(os.path.join('logo', f'{appname}.iconset'))
icns_dir = os.path.join(where, f'{appname}.icns')
try:
subprocess.check_call([
'iconutil', '-c', 'icns', iconset_dir, '-o', icns_dir
])
except FileNotFoundError:
2022-01-29 15:14:36 +03:00
print(f'{error("iconutil not found")}, using png2icns (without retina support) to convert the logo', file=sys.stderr)
subprocess.check_call([
'png2icns', icns_dir
] + [os.path.join(iconset_dir, logo) for logo in [
# png2icns does not support retina icons, so only pass the non-retina icons
'icon_16x16.png',
'icon_32x32.png',
'icon_128x128.png',
'icon_256x256.png',
'icon_512x512.png',
]])
2022-08-14 11:18:45 +03:00
def create_minimal_macos_bundle(args: Options, launcher_dir: str) -> None:
kapp = os.path.join(launcher_dir, 'kitty.app')
if os.path.exists(kapp):
shutil.rmtree(kapp)
bin_dir = os.path.join(kapp, 'Contents/MacOS')
resources_dir = os.path.join(kapp, 'Contents/Resources')
os.makedirs(resources_dir)
os.makedirs(bin_dir)
2022-08-14 11:18:45 +03:00
with open(os.path.join(kapp, 'Contents/Info.plist'), 'wb') as f:
f.write(macos_info_plist())
build_launcher(args, bin_dir)
2022-08-15 12:27:37 +03:00
build_kitty_tool(args, launcher_dir=bin_dir)
2022-08-14 16:31:58 +03:00
kitty_exe = os.path.join(launcher_dir, appname)
with suppress(FileNotFoundError):
os.remove(kitty_exe)
os.symlink(os.path.join(os.path.relpath(bin_dir, launcher_dir), appname), kitty_exe)
create_macos_app_icon(resources_dir)
2020-03-14 12:07:11 +03:00
def create_macos_bundle_gunk(dest: str) -> None:
ddir = Path(dest)
2019-06-28 09:30:12 +03:00
os.mkdir(ddir / 'Contents')
with open(ddir / 'Contents/Info.plist', 'wb') as fp:
fp.write(macos_info_plist())
copy_man_pages(str(ddir))
copy_html_docs(str(ddir))
2019-06-28 09:30:12 +03:00
os.rename(ddir / 'share', ddir / 'Contents/Resources')
os.rename(ddir / 'bin', ddir / 'Contents/MacOS')
os.rename(ddir / 'lib', ddir / 'Contents/Frameworks')
os.rename(ddir / 'Contents/Frameworks/kitty', ddir / 'Contents/Resources/kitty')
2019-06-28 09:30:12 +03:00
launcher = ddir / 'Contents/MacOS/kitty'
in_src_launcher = ddir / 'Contents/Resources/kitty/kitty/launcher/kitty'
2019-06-28 09:30:12 +03:00
if os.path.exists(in_src_launcher):
os.remove(in_src_launcher)
os.makedirs(os.path.dirname(in_src_launcher), exist_ok=True)
os.symlink(os.path.relpath(launcher, os.path.dirname(in_src_launcher)), in_src_launcher)
create_macos_app_icon(os.path.join(ddir, 'Contents', 'Resources'))
2020-03-14 12:07:11 +03:00
def package(args: Options, bundle_type: str) -> None:
ddir = args.prefix
for_freeze = bundle_type.endswith('-freeze')
if bundle_type == 'linux-freeze':
args.libdir_name = 'lib'
libdir = os.path.join(ddir, args.libdir_name.strip('/'), 'kitty')
if os.path.exists(libdir):
shutil.rmtree(libdir)
2019-07-08 04:16:16 +03:00
launcher_dir = os.path.join(ddir, 'bin')
safe_makedirs(launcher_dir)
if for_freeze: # freeze launcher is built separately
args.compilation_database.build_all()
else:
build_launcher(args, launcher_dir, bundle_type)
2017-01-18 18:22:06 +03:00
os.makedirs(os.path.join(libdir, 'logo'))
build_terminfo = runpy.run_path('build-terminfo', run_name='import_build')
for x in (libdir, os.path.join(ddir, 'share')):
odir = os.path.join(x, 'terminfo')
safe_makedirs(odir)
build_terminfo['compile_terminfo'](odir)
shutil.copy2('terminfo/kitty.terminfo', os.path.join(libdir, 'terminfo'))
2022-04-12 20:13:22 +03:00
shutil.copy2('terminfo/kitty.termcap', os.path.join(libdir, 'terminfo'))
shutil.copy2('__main__.py', libdir)
shutil.copy2('logo/kitty-128.png', os.path.join(libdir, 'logo'))
shutil.copy2('logo/kitty.png', os.path.join(libdir, 'logo'))
shutil.copy2('logo/beam-cursor.png', os.path.join(libdir, 'logo'))
shutil.copy2('logo/beam-cursor@2x.png', os.path.join(libdir, 'logo'))
2021-09-02 10:35:14 +03:00
try:
shutil.copytree('shell-integration', os.path.join(libdir, 'shell-integration'), dirs_exist_ok=True)
except TypeError: # python < 3.8
shutil.copytree('shell-integration', os.path.join(libdir, 'shell-integration'))
allowed_extensions = frozenset('py glsl so'.split())
2020-03-14 12:07:11 +03:00
def src_ignore(parent: str, entries: Iterable[str]) -> List[str]:
2017-02-09 21:34:05 +03:00
return [
x for x in entries
2017-11-05 06:52:15 +03:00
if '.' in x and x.rpartition('.')[2] not in
allowed_extensions
2017-02-09 21:34:05 +03:00
]
shutil.copytree('kitty', os.path.join(libdir, 'kitty'), ignore=src_ignore)
shutil.copytree('kittens', os.path.join(libdir, 'kittens'), ignore=src_ignore)
if for_freeze:
shutil.copytree('kitty_tests', os.path.join(libdir, 'kitty_tests'))
2022-01-04 21:08:58 +03:00
def repl(name: str, raw: str, defval: Union[str, float, FrozenSet[str]], val: Union[str, float, FrozenSet[str]]) -> str:
if defval == val:
return raw
2022-01-04 21:08:58 +03:00
tname = type(defval).__name__
if tname == 'frozenset':
tname = 'typing.FrozenSet[str]'
prefix = f'{name}: {tname} ='
nraw = raw.replace(f'{prefix} {defval!r}', f'{prefix} {val!r}', 1)
if nraw == raw:
raise SystemExit(f'Failed to change the value of {name}')
return nraw
with open(os.path.join(libdir, 'kitty/options/types.py'), 'r+', encoding='utf-8') as f:
oraw = raw = f.read()
raw = repl('update_check_interval', raw, Options.update_check_interval, args.update_check_interval)
2022-01-04 21:08:58 +03:00
raw = repl('shell_integration', raw, frozenset(Options.shell_integration.split()), frozenset(args.shell_integration.split()))
if raw != oraw:
f.seek(0), f.truncate(), f.write(raw)
compile_python(libdir)
def should_be_executable(path: str) -> bool:
if path.endswith('.so'):
return True
q = path.split(os.sep)[-2:]
if len(q) == 2 and q[0] == 'ssh' and q[1] in ('askpass.py', 'kitty'):
return True
return False
for root, dirs, files in os.walk(libdir):
2020-03-05 18:08:51 +03:00
for f_ in files:
path = os.path.join(root, f_)
os.chmod(path, 0o755 if should_be_executable(path) else 0o644)
2022-08-16 19:58:28 +03:00
if not for_freeze:
build_kitty_tool(args, launcher_dir=launcher_dir)
if not is_macos:
create_linux_bundle_gunk(ddir, args.libdir_name)
if bundle_type.startswith('macos-'):
2019-06-25 05:56:19 +03:00
create_macos_bundle_gunk(ddir)
# }}}
2022-08-14 21:09:26 +03:00
def clean_launcher_dir(launcher_dir: str) -> None:
for x in glob.glob(os.path.join(launcher_dir, 'kitty*')):
if os.path.isdir(x):
shutil.rmtree(x)
else:
os.remove(x)
2020-03-14 12:07:11 +03:00
def clean() -> None:
2020-03-14 12:07:11 +03:00
def safe_remove(*entries: str) -> None:
for x in entries:
if os.path.exists(x):
if os.path.isdir(x):
shutil.rmtree(x)
else:
os.unlink(x)
2019-07-07 02:22:24 +03:00
safe_remove(
'build', 'compile_commands.json', 'link_commands.json',
'linux-package', 'kitty.app', 'asan-launcher',
2022-08-20 11:13:27 +03:00
'kitty-profile', 'docs/generated')
2022-08-14 21:09:26 +03:00
clean_launcher_dir('kitty/launcher')
def excluded(root: str, d: str) -> bool:
q = os.path.relpath(os.path.join(root, d), base).replace(os.sep, '/')
return q in ('.git', 'bypy/b')
for root, dirs, files in os.walk(base, topdown=True):
dirs[:] = [d for d in dirs if not excluded(root, d)]
remove_dirs = {d for d in dirs if d == '__pycache__' or d.endswith('.dSYM')}
2020-03-05 18:08:51 +03:00
for d in remove_dirs:
shutil.rmtree(os.path.join(root, d))
dirs.remove(d)
for f in files:
ext = f.rpartition('.')[-1]
if ext in ('so', 'dylib', 'pyc', 'pyo') or f.endswith('_generated.h') or f.endswith('_generated.go'):
os.unlink(os.path.join(root, f))
2018-03-12 05:22:08 +03:00
for x in glob.glob('glfw/wayland-*-protocol.[ch]'):
os.unlink(x)
2017-10-17 11:05:54 +03:00
2020-03-14 12:07:11 +03:00
def option_parser() -> argparse.ArgumentParser: # {{{
p = argparse.ArgumentParser()
p.add_argument(
'action',
nargs='?',
2020-03-14 12:07:11 +03:00
default=Options.action,
choices=('build',
'test',
'linux-package',
'kitty.app',
'linux-freeze',
'macos-freeze',
'build-launcher',
'build-frozen-launcher',
'build-frozen-tools',
'clean',
'export-ci-bundles',
'build-dep',
),
help='Action to perform (default is build)'
)
p.add_argument(
'--debug',
2020-03-14 12:07:11 +03:00
default=Options.debug,
action='store_true',
help='Build extension modules with debugging symbols'
)
p.add_argument(
'-v', '--verbose',
2020-03-14 12:07:11 +03:00
default=Options.verbose,
action='count',
help='Be verbose'
)
p.add_argument(
'--sanitize',
2020-03-14 12:07:11 +03:00
default=Options.sanitize,
action='store_true',
help='Turn on sanitization to detect memory access errors and undefined behavior. This is a big performance hit.'
)
p.add_argument(
'--prefix',
2020-03-14 12:07:11 +03:00
default=Options.prefix,
help='Where to create the linux package'
)
p.add_argument(
'--full',
dest='incremental',
2020-03-14 12:07:11 +03:00
default=Options.incremental,
action='store_false',
help='Do a full build, even for unchanged files'
)
p.add_argument(
'--profile',
2020-03-14 12:07:11 +03:00
default=Options.profile,
action='store_true',
help='Use the -pg compile flag to add profiling information'
)
p.add_argument(
'--libdir-name',
2020-03-14 12:07:11 +03:00
default=Options.libdir_name,
2018-03-12 05:41:43 +03:00
help='The name of the directory inside --prefix in which to store compiled files. Defaults to "lib"'
)
p.add_argument(
'--extra-logging',
action='append',
2020-03-14 12:07:11 +03:00
default=Options.extra_logging,
choices=('event-loop',),
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(
2021-10-06 05:38:44 +03:00
'--extra-include-dirs', '-I',
action='append',
default=Options.extra_include_dirs,
help='Extra include directories to use while compiling'
)
p.add_argument(
'--extra-library-dirs', '-L',
action='append',
default=Options.extra_library_dirs,
help='Extra library directories to use while linking'
)
p.add_argument(
'--update-check-interval',
type=float,
2020-03-14 12:07:11 +03:00
default=Options.update_check_interval,
help='When building a package, the default value for the update_check_interval setting will'
' be set to this number. Use zero to disable update checking.'
)
p.add_argument(
'--shell-integration',
type=str,
default=Options.shell_integration,
help='When building a package, the default value for the shell_integration setting will'
' be set to this. Use "enabled no-rc" if you intend to install the shell integration scripts system wide.'
)
p.add_argument(
'--egl-library',
type=str,
default=Options.egl_library,
help='The filename argument passed to dlopen for libEGL.'
' This can be used to change the name of the loaded library or specify an absolute path.'
)
p.add_argument(
'--startup-notification-library',
type=str,
default=Options.startup_notification_library,
help='The filename argument passed to dlopen for libstartup-notification-1.'
' This can be used to change the name of the loaded library or specify an absolute path.'
)
p.add_argument(
'--canberra-library',
type=str,
default=Options.canberra_library,
help='The filename argument passed to dlopen for libcanberra.'
' This can be used to change the name of the loaded library or specify an absolute path.'
)
p.add_argument(
'--fontconfig-library',
type=str,
default=Options.fontconfig_library,
help='The filename argument passed to dlopen for libfontconfig.'
' This can be used to change the name of the loaded library or specify an absolute path.'
)
p.add_argument(
'--disable-link-time-optimization',
dest='link_time_optimization',
default=Options.link_time_optimization,
action='store_false',
help='Turn off Link Time Optimization (LTO).'
)
p.add_argument(
'--ignore-compiler-warnings',
default=False, action='store_true',
help='Ignore any warnings from the compiler while building'
)
2021-05-01 12:28:06 +03:00
p.add_argument(
'--build-universal-binary',
default=False, action='store_true',
help='Build a universal binary (ARM + Intel on macOS, ignored on other platforms)'
)
return p
2018-05-31 20:32:38 +03:00
# }}}
def build_dep() -> None:
class Options(argparse.Namespace):
platform: str
deps: List[str]
p = argparse.ArgumentParser(prog=f'{sys.argv[0]} build-dep', description='Build dependencies for the kitty binary packages')
p.add_argument(
'--platform',
default='all',
choices='all macos linux linux-32 linux-arm64 linux-64'.split(),
help='Platforms to build the dep for'
)
p.add_argument(
'deps',
nargs='*',
default=[],
help='Names of the dependencies, if none provided, build all'
)
args = p.parse_args(sys.argv[2:], namespace=Options)
linux_platforms = [
['linux', '--arch=64'],
['linux', '--arch=32'],
['linux', '--arch=arm64'],
]
if args.platform == 'all':
platforms = linux_platforms + [['macos']]
elif args.platform == 'linux':
platforms = linux_platforms
elif args.platform == 'macos':
platforms = [['macos']]
elif '-' in args.platform:
parts = args.platform.split('-')
platforms = [[parts[0], f'--arch={parts[1]}']]
else:
raise SystemExit(f'Unknown platform: {args.platform}')
base = [sys.executable, '../bypy']
for pf in platforms:
cmd = base + pf + ['dependencies'] + args.deps
run_tool(cmd)
2020-03-14 12:07:11 +03:00
def main() -> None:
2017-11-20 12:56:27 +03:00
global verbose
if len(sys.argv) > 1 and sys.argv[1] == 'build-dep':
return build_dep()
2020-03-14 12:07:11 +03:00
args = option_parser().parse_args(namespace=Options())
2021-05-01 12:28:06 +03:00
if not is_macos:
args.build_universal_binary = False
2017-11-20 12:56:27 +03:00
verbose = args.verbose > 0
args.prefix = os.path.abspath(args.prefix)
2019-07-11 19:18:45 +03:00
os.chdir(base)
if args.action == 'test':
texe = os.path.abspath('kitty/launcher/kitty')
os.execl(texe, texe, '+launch', 'test.py')
if args.action == 'clean':
2017-10-17 11:05:54 +03:00
clean()
return
launcher_dir = 'kitty/launcher'
with CompilationDatabase(args.incremental) as cdb:
args.compilation_database = cdb
if args.action == 'build':
build(args)
if is_macos:
create_minimal_macos_bundle(args, launcher_dir)
else:
build_launcher(args, launcher_dir=launcher_dir)
2022-08-15 12:27:37 +03:00
build_kitty_tool(args, launcher_dir=launcher_dir)
elif args.action == 'build-launcher':
init_env_from_args(args, False)
build_launcher(args, launcher_dir=launcher_dir)
2022-08-15 12:27:37 +03:00
build_kitty_tool(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 == 'build-frozen-tools':
build_kitty_tool(args, launcher_dir=args.prefix, for_freeze=True)
elif args.action == 'linux-package':
build(args, native_optimizations=False)
package(args, bundle_type='linux-package')
elif args.action == 'linux-freeze':
build(args, native_optimizations=False)
package(args, bundle_type='linux-freeze')
elif args.action == 'macos-freeze':
init_env_from_args(args, native_optimizations=False)
build_launcher(args, launcher_dir=launcher_dir)
build(args, native_optimizations=False, call_init=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!')
elif args.action == 'export-ci-bundles':
2021-12-19 20:12:36 +03:00
cmd = [sys.executable, '../bypy', 'export', 'download.calibre-ebook.com:/srv/download/ci/kitty']
subprocess.check_call(cmd + ['linux'])
subprocess.check_call(cmd + ['macos'])
if __name__ == '__main__':
main()