Refactor the compilation code
Makes it a bit more efficient. All compiling is done in parallel, followed by all linking.
This commit is contained in:
128
setup.py
128
setup.py
@@ -15,6 +15,7 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
import sysconfig
|
import sysconfig
|
||||||
import time
|
import time
|
||||||
|
from functools import partial
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -367,12 +368,12 @@ def dependecies_for(src, obj, all_headers):
|
|||||||
yield path
|
yield path
|
||||||
|
|
||||||
|
|
||||||
def parallel_run(todo, desc='Compiling {} ...'):
|
def parallel_run(items):
|
||||||
try:
|
try:
|
||||||
num_workers = max(2, os.cpu_count())
|
num_workers = max(2, os.cpu_count())
|
||||||
except Exception:
|
except Exception:
|
||||||
num_workers = 2
|
num_workers = 2
|
||||||
items = list(reversed(tuple(todo.items())))
|
items = list(reversed(items))
|
||||||
workers = {}
|
workers = {}
|
||||||
failed = None
|
failed = None
|
||||||
|
|
||||||
@@ -381,37 +382,66 @@ def parallel_run(todo, desc='Compiling {} ...'):
|
|||||||
if not workers:
|
if not workers:
|
||||||
return
|
return
|
||||||
pid, s = os.wait()
|
pid, s = os.wait()
|
||||||
name, cmd, w = workers.pop(pid, (None, None, None))
|
compile_cmd, w = workers.pop(pid, (None, None))
|
||||||
if name is not None and ((s & 0xff) != 0 or ((s >> 8) & 0xff) != 0) and failed is None:
|
if compile_cmd is not None and ((s & 0xff) != 0 or ((s >> 8) & 0xff) != 0) and failed is None:
|
||||||
failed = name, cmd
|
failed = compile_cmd
|
||||||
|
elif compile_cmd.on_success is not None:
|
||||||
|
compile_cmd.on_success()
|
||||||
|
|
||||||
while items and failed is None:
|
while items and failed is None:
|
||||||
while len(workers) < num_workers and items:
|
while len(workers) < num_workers and items:
|
||||||
name, cmd = items.pop()
|
compile_cmd = items.pop()
|
||||||
if verbose:
|
if verbose:
|
||||||
print(' '.join(cmd))
|
print(' '.join(compile_cmd.cmd))
|
||||||
else:
|
else:
|
||||||
print(desc.format(emphasis(name)))
|
print(compile_cmd.desc)
|
||||||
w = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
|
w = subprocess.Popen(compile_cmd.cmd, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
|
||||||
workers[w.pid] = name, cmd, w
|
workers[w.pid] = compile_cmd, w
|
||||||
wait()
|
wait()
|
||||||
while len(workers):
|
while len(workers):
|
||||||
wait()
|
wait()
|
||||||
if failed:
|
if failed:
|
||||||
run_tool(failed[1])
|
run_tool(failed.cmd)
|
||||||
|
|
||||||
|
|
||||||
CompileKey = namedtuple('CompileKey', 'src dest')
|
CompileKey = namedtuple('CompileKey', 'src dest')
|
||||||
|
Command = namedtuple('Command', 'desc cmd is_newer_func on_success key keyfile')
|
||||||
|
|
||||||
|
|
||||||
class CompilationDatabase:
|
class CompilationDatabase:
|
||||||
|
|
||||||
def cmd_changed(self, key, cmd):
|
def __init__(self, incremental):
|
||||||
self.all_keys.add(key)
|
self.incremental = incremental
|
||||||
return self.db.get(key) != cmd
|
self.compile_commands = []
|
||||||
|
self.link_commands = []
|
||||||
|
|
||||||
def update_cmd(self, key, cmd):
|
def add_command(self, desc, cmd, is_newer_func, key=None, on_success=None, keyfile=None):
|
||||||
self.db[key] = cmd
|
queue = self.link_commands if key is None else self.compile_commands
|
||||||
|
queue.append(Command(desc, cmd, is_newer_func, on_success, key, keyfile))
|
||||||
|
|
||||||
|
def build_all(self):
|
||||||
|
items = []
|
||||||
|
|
||||||
|
def sort_key(compile_cmd):
|
||||||
|
if compile_cmd.keyfile:
|
||||||
|
return os.path.getsize(compile_cmd.keyfile)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
def cmd_changed(self, compile_cmd):
|
||||||
|
key, cmd = compile_cmd.key, compile_cmd.cmd
|
||||||
|
return self.db.get(key) != cmd
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
self.all_keys = set()
|
self.all_keys = set()
|
||||||
@@ -432,21 +462,19 @@ class CompilationDatabase:
|
|||||||
for key in set(cdb) - self.all_keys:
|
for key in set(cdb) - self.all_keys:
|
||||||
del cdb[key]
|
del cdb[key]
|
||||||
compilation_database = [
|
compilation_database = [
|
||||||
{'file': k.src, 'arguments': v, 'directory': base, 'output': k.dest} for k, v in cdb.items()
|
{'file': c.key.src, 'arguments': c.cmd, 'directory': base, 'output': c.key.dest} for c in self.compile_commands
|
||||||
]
|
]
|
||||||
with open(self.dbpath, 'w') as f:
|
with open(self.dbpath, 'w') as f:
|
||||||
json.dump(compilation_database, f, indent=2, sort_keys=True)
|
json.dump(compilation_database, f, indent=2, sort_keys=True)
|
||||||
|
|
||||||
|
|
||||||
def compile_c_extension(kenv, module, incremental, compilation_database, sources, headers):
|
def compile_c_extension(kenv, module, compilation_database, sources, headers):
|
||||||
prefix = os.path.basename(module)
|
prefix = os.path.basename(module)
|
||||||
objects = [
|
objects = [
|
||||||
os.path.join(build_dir, prefix + '-' + os.path.basename(src) + '.o')
|
os.path.join(build_dir, prefix + '-' + os.path.basename(src) + '.o')
|
||||||
for src in sources
|
for src in sources
|
||||||
]
|
]
|
||||||
|
|
||||||
todo = {}
|
|
||||||
|
|
||||||
for original_src, dest in zip(sources, objects):
|
for original_src, dest in zip(sources, objects):
|
||||||
src = original_src
|
src = original_src
|
||||||
cppflags = kenv.cppflags[:]
|
cppflags = kenv.cppflags[:]
|
||||||
@@ -460,32 +488,23 @@ def compile_c_extension(kenv, module, incremental, compilation_database, sources
|
|||||||
cmd = [kenv.cc, '-MMD'] + cppflags + kenv.cflags
|
cmd = [kenv.cc, '-MMD'] + cppflags + kenv.cflags
|
||||||
cmd += ['-c', src] + ['-o', dest]
|
cmd += ['-c', src] + ['-o', dest]
|
||||||
key = CompileKey(original_src, os.path.basename(dest))
|
key = CompileKey(original_src, os.path.basename(dest))
|
||||||
cmd_changed = compilation_database.cmd_changed(key, cmd)
|
desc = 'Compiling {} ...'.format(emphasis(src))
|
||||||
must_compile = not incremental or cmd_changed
|
compilation_database.add_command(desc, cmd, partial(newer, dest, *dependecies_for(os.path.join(base, src), dest, headers)), key=key, keyfile=src)
|
||||||
src = os.path.join(base, src)
|
|
||||||
if must_compile or newer(
|
|
||||||
dest, *dependecies_for(src, dest, headers)
|
|
||||||
):
|
|
||||||
compilation_database.update_cmd(key, cmd)
|
|
||||||
todo[original_src] = cmd
|
|
||||||
if todo:
|
|
||||||
parallel_run(todo)
|
|
||||||
dest = os.path.join(build_dir, module + '.so')
|
dest = os.path.join(build_dir, module + '.so')
|
||||||
real_dest = os.path.join(base, module + '.so')
|
real_dest = os.path.join(base, module + '.so')
|
||||||
os.makedirs(os.path.dirname(dest), exist_ok=True)
|
os.makedirs(os.path.dirname(dest), exist_ok=True)
|
||||||
if not incremental or newer(real_dest, *objects):
|
desc = 'Linking {} ...'.format(emphasis(module))
|
||||||
# Old versions of clang don't like -pthread being passed to the linker
|
# Old versions of clang don't like -pthread being passed to the linker
|
||||||
# Don't treat linker warnings as errors (linker generates spurious
|
# Don't treat linker warnings as errors (linker generates spurious
|
||||||
# warnings on some old systems)
|
# warnings on some old systems)
|
||||||
unsafe = {'-pthread', '-Werror', '-pedantic-errors'}
|
unsafe = {'-pthread', '-Werror', '-pedantic-errors'}
|
||||||
linker_cflags = list(filter(lambda x: x not in unsafe, kenv.cflags))
|
linker_cflags = list(filter(lambda x: x not in unsafe, kenv.cflags))
|
||||||
try:
|
cmd = [kenv.cc] + linker_cflags + kenv.ldflags + objects + kenv.ldpaths + ['-o', dest]
|
||||||
run_tool([kenv.cc] + linker_cflags + kenv.ldflags + objects + kenv.ldpaths + ['-o', dest], desc='Linking {} ...'.format(emphasis(module)))
|
|
||||||
except Exception:
|
def on_success():
|
||||||
with suppress(EnvironmentError):
|
os.rename(dest, real_dest)
|
||||||
os.remove(dest)
|
|
||||||
else:
|
compilation_database.add_command(desc, cmd, partial(newer, real_dest, *objects), on_success=on_success)
|
||||||
os.rename(dest, real_dest)
|
|
||||||
|
|
||||||
|
|
||||||
def find_c_files():
|
def find_c_files():
|
||||||
@@ -505,7 +524,7 @@ def find_c_files():
|
|||||||
return tuple(ans), tuple(headers)
|
return tuple(ans), tuple(headers)
|
||||||
|
|
||||||
|
|
||||||
def compile_glfw(incremental, compilation_database):
|
def compile_glfw(compilation_database):
|
||||||
modules = 'cocoa' if is_macos else 'x11 wayland'
|
modules = 'cocoa' if is_macos else 'x11 wayland'
|
||||||
for module in modules.split():
|
for module in modules.split():
|
||||||
try:
|
try:
|
||||||
@@ -525,7 +544,7 @@ def compile_glfw(incremental, compilation_database):
|
|||||||
print(err, file=sys.stderr)
|
print(err, file=sys.stderr)
|
||||||
print(error('Disabling building of wayland backend'), file=sys.stderr)
|
print(error('Disabling building of wayland backend'), file=sys.stderr)
|
||||||
continue
|
continue
|
||||||
compile_c_extension(genv, 'kitty/glfw-' + module, incremental, compilation_database, sources, all_headers)
|
compile_c_extension(genv, 'kitty/glfw-' + module, compilation_database, sources, all_headers)
|
||||||
|
|
||||||
|
|
||||||
def kittens_env():
|
def kittens_env():
|
||||||
@@ -538,7 +557,7 @@ def kittens_env():
|
|||||||
return kenv
|
return kenv
|
||||||
|
|
||||||
|
|
||||||
def compile_kittens(incremental, compilation_database):
|
def compile_kittens(compilation_database):
|
||||||
kenv = kittens_env()
|
kenv = kittens_env()
|
||||||
|
|
||||||
def list_files(q):
|
def list_files(q):
|
||||||
@@ -559,17 +578,17 @@ def compile_kittens(incremental, compilation_database):
|
|||||||
filter_sources=lambda x: 'windows_compat.c' not in x),
|
filter_sources=lambda x: 'windows_compat.c' not in x),
|
||||||
):
|
):
|
||||||
compile_c_extension(
|
compile_c_extension(
|
||||||
kenv, dest, incremental, compilation_database, sources, all_headers + ['kitty/data-types.h'])
|
kenv, dest, compilation_database, sources, all_headers + ['kitty/data-types.h'])
|
||||||
|
|
||||||
|
|
||||||
def build(args, native_optimizations=True):
|
def build(args, native_optimizations=True):
|
||||||
global env
|
global env
|
||||||
env = init_env(args.debug, args.sanitize, native_optimizations, args.profile, args.extra_logging)
|
env = init_env(args.debug, args.sanitize, native_optimizations, args.profile, args.extra_logging)
|
||||||
compile_c_extension(
|
compile_c_extension(
|
||||||
kitty_env(), 'kitty/fast_data_types', args.incremental, args.compilation_database, *find_c_files()
|
kitty_env(), 'kitty/fast_data_types', args.compilation_database, *find_c_files()
|
||||||
)
|
)
|
||||||
compile_glfw(args.incremental, args.compilation_database)
|
compile_glfw(args.compilation_database)
|
||||||
compile_kittens(args.incremental, args.compilation_database)
|
compile_kittens(args.compilation_database)
|
||||||
|
|
||||||
|
|
||||||
def safe_makedirs(path):
|
def safe_makedirs(path):
|
||||||
@@ -618,10 +637,9 @@ def build_launcher(args, launcher_dir='.', bundle_type='source'):
|
|||||||
cmd = [env.cc] + cppflags + cflags + [
|
cmd = [env.cc] + cppflags + cflags + [
|
||||||
src, '-o', dest] + ldflags + libs + pylib
|
src, '-o', dest] + ldflags + libs + pylib
|
||||||
key = CompileKey('launcher.c', 'kitty')
|
key = CompileKey('launcher.c', 'kitty')
|
||||||
must_compile = not args.incremental or args.compilation_database.cmd_changed(key, cmd)
|
desc = 'Building {}...'.format(emphasis('launcher'))
|
||||||
if must_compile or newer(dest, src):
|
args.compilation_database.add_command(desc, cmd, partial(newer, dest, src), key=key, keyfile=src)
|
||||||
run_tool(cmd, 'Building {}...'.format(emphasis('launcher')))
|
args.compilation_database.build_all()
|
||||||
args.compilation_database.update_cmd(key, cmd)
|
|
||||||
|
|
||||||
|
|
||||||
# Packaging {{{
|
# Packaging {{{
|
||||||
@@ -959,7 +977,7 @@ def main():
|
|||||||
clean()
|
clean()
|
||||||
return
|
return
|
||||||
|
|
||||||
with CompilationDatabase() as cdb:
|
with CompilationDatabase(args.incremental) as cdb:
|
||||||
args.compilation_database = cdb
|
args.compilation_database = cdb
|
||||||
if args.action == 'build':
|
if args.action == 'build':
|
||||||
build(args)
|
build(args)
|
||||||
|
|||||||
Reference in New Issue
Block a user