mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-06-10 00:54:32 -04:00
588 lines
23 KiB
Python
588 lines
23 KiB
Python
#!/usr/bin/env python
|
|
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
|
|
|
__license__ = 'GPL v3'
|
|
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
|
__docformat__ = 'restructuredtext en'
|
|
|
|
import textwrap, os, shlex, subprocess, glob, shutil, sys, json, errno
|
|
from collections import namedtuple
|
|
|
|
from setup import Command, islinux, isbsd, isfreebsd, ismacos, ishaiku, SRC, iswindows
|
|
isunix = islinux or ismacos or isbsd or ishaiku
|
|
|
|
py_lib = os.path.join(sys.prefix, 'libs', 'python%d%d.lib' % sys.version_info[:2])
|
|
CompileCommand = namedtuple('CompileCommand', 'cmd src dest')
|
|
LinkCommand = namedtuple('LinkCommand', 'cmd objects dest')
|
|
|
|
|
|
def walk(path='.'):
|
|
for dirpath, dirnames, filenames in os.walk(path):
|
|
for f in filenames:
|
|
yield os.path.join(dirpath, f)
|
|
|
|
|
|
def init_symbol_name(name):
|
|
prefix = 'PyInit_'
|
|
return prefix + name
|
|
|
|
|
|
def absolutize(paths):
|
|
return list(set([x if os.path.isabs(x) else os.path.join(SRC, x.replace('/', os.sep)) for x in paths]))
|
|
|
|
|
|
class Extension:
|
|
|
|
def __init__(self, name, sources, **kwargs):
|
|
self.data = d = {}
|
|
self.name = d['name'] = name
|
|
self.sources = d['sources'] = absolutize(sources)
|
|
self.needs_cxx = d['needs_cxx'] = bool([1 for x in self.sources if os.path.splitext(x)[1] in ('.cpp', '.c++', '.cxx')])
|
|
self.needs_py2 = d['needs_py2'] = kwargs.get('needs_py2', False)
|
|
self.headers = d['headers'] = absolutize(kwargs.get('headers', []))
|
|
self.sip_files = d['sip_files'] = absolutize(kwargs.get('sip_files', []))
|
|
self.needs_exceptions = d['needs_exceptions'] = kwargs.get('needs_exceptions', False)
|
|
self.inc_dirs = d['inc_dirs'] = absolutize(kwargs.get('inc_dirs', []))
|
|
self.lib_dirs = d['lib_dirs'] = absolutize(kwargs.get('lib_dirs', []))
|
|
self.extra_objs = d['extra_objs'] = absolutize(kwargs.get('extra_objs', []))
|
|
self.error = d['error'] = kwargs.get('error', None)
|
|
self.libraries = d['libraries'] = kwargs.get('libraries', [])
|
|
self.cflags = d['cflags'] = kwargs.get('cflags', [])
|
|
self.uses_icu = 'icuuc' in self.libraries
|
|
if iswindows:
|
|
self.cflags.append('/DCALIBRE_MODINIT_FUNC=PyMODINIT_FUNC')
|
|
if self.needs_cxx and kwargs.get('needs_c++14'):
|
|
self.cflags.insert(0, '/std:c++14')
|
|
else:
|
|
return_type = 'PyObject*'
|
|
extern_decl = 'extern "C"' if self.needs_cxx else ''
|
|
|
|
self.cflags.append(
|
|
'-DCALIBRE_MODINIT_FUNC='
|
|
'{} __attribute__ ((visibility ("default"))) {}'.format(extern_decl, return_type))
|
|
|
|
if self.needs_cxx:
|
|
if kwargs.get('needs_c++11'):
|
|
self.cflags.insert(0, '-std=c++11')
|
|
elif kwargs.get('needs_c++14'):
|
|
self.cflags.insert(0, '-std=c++14')
|
|
else:
|
|
if kwargs.get('needs_c99'):
|
|
self.cflags.insert(0, '-std=c99')
|
|
|
|
self.ldflags = d['ldflags'] = kwargs.get('ldflags', [])
|
|
self.optional = d['options'] = kwargs.get('optional', False)
|
|
of = kwargs.get('optimize_level', None)
|
|
if of is None:
|
|
of = '/Ox' if iswindows else '-O3'
|
|
else:
|
|
flag = '/O%d' if iswindows else '-O%d'
|
|
of = flag % of
|
|
self.cflags.insert(0, of)
|
|
|
|
|
|
def lazy_load(name):
|
|
if name.startswith('!'):
|
|
name = name[1:]
|
|
from setup import build_environment
|
|
try:
|
|
return getattr(build_environment, name)
|
|
except AttributeError:
|
|
raise ImportError('The setup.build_environment module has no symbol named: %s' % name)
|
|
|
|
|
|
def expand_file_list(items, is_paths=True):
|
|
if not items:
|
|
return []
|
|
ans = []
|
|
for item in items:
|
|
if item.startswith('!'):
|
|
item = lazy_load(item)
|
|
if hasattr(item, 'rjust'):
|
|
item = [item]
|
|
ans.extend(expand_file_list(item, is_paths=is_paths))
|
|
else:
|
|
if '*' in item:
|
|
ans.extend(expand_file_list(sorted(glob.glob(os.path.join(SRC, item))), is_paths=is_paths))
|
|
else:
|
|
item = [item]
|
|
if is_paths:
|
|
item = absolutize(item)
|
|
ans.extend(item)
|
|
return ans
|
|
|
|
|
|
def is_ext_allowed(ext):
|
|
only = ext.get('only', '')
|
|
if only:
|
|
only = set(only.split())
|
|
q = set(filter(lambda x: globals()["is" + x], ["bsd", "freebsd", "haiku", "linux", "macos", "windows"]))
|
|
return len(q.intersection(only)) > 0
|
|
return True
|
|
|
|
|
|
def parse_extension(ext):
|
|
ext = ext.copy()
|
|
ext.pop('only', None)
|
|
kw = {}
|
|
name = ext.pop('name')
|
|
|
|
def get(k, default=''):
|
|
ans = ext.pop(k, default)
|
|
if iswindows:
|
|
ans = ext.pop('windows_' + k, ans)
|
|
elif ismacos:
|
|
ans = ext.pop('macos_' + k, ans)
|
|
elif isbsd:
|
|
ans = ext.pop('bsd_' + k, ans)
|
|
elif isfreebsd:
|
|
ans = ext.pop('freebsd_' + k, ans)
|
|
elif ishaiku:
|
|
ans = ext.pop('haiku_' + k, ans)
|
|
else:
|
|
ans = ext.pop('linux_' + k, ans)
|
|
return ans
|
|
for k in 'libraries qt_private ldflags cflags error'.split():
|
|
kw[k] = expand_file_list(get(k).split(), is_paths=False)
|
|
defines = get('defines')
|
|
if defines:
|
|
if 'cflags' not in kw:
|
|
kw['cflags'] = []
|
|
cflags = kw['cflags']
|
|
prefix = '/D' if iswindows else '-D'
|
|
cflags.extend(prefix + x for x in defines.split())
|
|
for k in 'inc_dirs lib_dirs sources headers sip_files'.split():
|
|
v = get(k)
|
|
if v:
|
|
kw[k] = expand_file_list(v.split())
|
|
kw.update(ext)
|
|
return Extension(name, **kw)
|
|
|
|
|
|
def read_extensions():
|
|
if hasattr(read_extensions, 'extensions'):
|
|
return read_extensions.extensions
|
|
with open(os.path.dirname(os.path.abspath(__file__)) + '/extensions.json', 'rb') as f:
|
|
ans = read_extensions.extensions = json.load(f)
|
|
return ans
|
|
|
|
|
|
def init_env(debug=False, sanitize=False):
|
|
from setup.build_environment import win_ld, is64bit, win_inc, win_lib, NMAKE, win_cc
|
|
from distutils import sysconfig
|
|
linker = None
|
|
if isunix:
|
|
cc = os.environ.get('CC', 'gcc')
|
|
cxx = os.environ.get('CXX', 'g++')
|
|
debug = '-ggdb' if debug else ''
|
|
cflags = os.environ.get('OVERRIDE_CFLAGS',
|
|
f'-Wall -DNDEBUG {debug} -fno-strict-aliasing -pipe')
|
|
cflags = shlex.split(cflags) + ['-fPIC']
|
|
ldflags = os.environ.get('OVERRIDE_LDFLAGS', '-Wall')
|
|
ldflags = shlex.split(ldflags)
|
|
cflags += shlex.split(os.environ.get('CFLAGS', ''))
|
|
ldflags += shlex.split(os.environ.get('LDFLAGS', ''))
|
|
cflags += ['-fvisibility=hidden']
|
|
if sanitize:
|
|
cflags.append('-fsanitize-address')
|
|
ldflags.append('-shared-libasan')
|
|
|
|
if islinux:
|
|
cflags.append('-pthread')
|
|
if sys.stdout.isatty():
|
|
cflags.append('-fdiagnostics-color=always')
|
|
ldflags.append('-shared')
|
|
|
|
if isbsd:
|
|
cflags.append('-pthread')
|
|
ldflags.append('-shared')
|
|
|
|
if ishaiku:
|
|
cflags.append('-lpthread')
|
|
ldflags.append('-shared')
|
|
|
|
if islinux or isbsd or ishaiku:
|
|
cflags.append('-I'+sysconfig.get_python_inc())
|
|
# getattr(..., 'abiflags') is for PY2 compat, since PY2 has no abiflags
|
|
# member
|
|
ldflags.append('-lpython{}{}'.format(
|
|
sysconfig.get_config_var('VERSION'), getattr(sys, 'abiflags', '')))
|
|
|
|
if ismacos:
|
|
cflags.append('-D_OSX')
|
|
ldflags.extend('-bundle -undefined dynamic_lookup'.split())
|
|
cflags.extend(['-fno-common', '-dynamic'])
|
|
cflags.append('-I'+sysconfig.get_python_inc())
|
|
|
|
if iswindows:
|
|
cc = cxx = win_cc
|
|
cflags = '/c /nologo /W3 /EHsc /utf-8'.split()
|
|
cflags.append('/Zi' if debug else '/DNDEBUG')
|
|
suffix = ('d' if debug else '')
|
|
cflags.append('/MD' + suffix)
|
|
ldflags = f'/DLL /nologo /INCREMENTAL:NO /NODEFAULTLIB:libcmt{suffix}.lib'.split()
|
|
if debug:
|
|
ldflags.append('/DEBUG')
|
|
# cflags = '/c /nologo /Ox /MD /W3 /EHsc /Zi'.split()
|
|
# ldflags = '/DLL /nologo /INCREMENTAL:NO /DEBUG'.split()
|
|
if is64bit:
|
|
cflags.append('/GS-')
|
|
|
|
for p in win_inc:
|
|
cflags.append('-I'+p)
|
|
for p in win_lib:
|
|
if p:
|
|
ldflags.append('/LIBPATH:'+p)
|
|
cflags.append('-I%s'%sysconfig.get_python_inc())
|
|
ldflags.append('/LIBPATH:'+os.path.join(sysconfig.PREFIX, 'libs'))
|
|
linker = win_ld
|
|
return namedtuple('Environment', 'cc cxx cflags ldflags linker make')(
|
|
cc=cc, cxx=cxx, cflags=cflags, ldflags=ldflags, linker=linker, make=NMAKE if iswindows else 'make')
|
|
|
|
|
|
class Build(Command):
|
|
|
|
short_description = 'Build calibre C/C++ extension modules'
|
|
DEFAULT_OUTPUTDIR = os.path.abspath(os.path.join(SRC, 'calibre', 'plugins'))
|
|
DEFAULT_BUILDDIR = os.path.abspath(os.path.join(os.path.dirname(SRC), 'build'))
|
|
|
|
description = textwrap.dedent('''\
|
|
calibre depends on several python extensions written in C/C++.
|
|
This command will compile them. You can influence the compile
|
|
process by several environment variables, listed below:
|
|
|
|
CC - C Compiler defaults to gcc
|
|
CXX - C++ Compiler, defaults to g++
|
|
CFLAGS - Extra compiler flags
|
|
LDFLAGS - Extra linker flags
|
|
|
|
POPPLER_INC_DIR - poppler header files
|
|
POPPLER_LIB_DIR - poppler-qt4 library
|
|
|
|
PODOFO_INC_DIR - podofo header files
|
|
PODOFO_LIB_DIR - podofo library files
|
|
|
|
QMAKE - Path to qmake
|
|
''')
|
|
|
|
def add_options(self, parser):
|
|
choices = [e['name'] for e in read_extensions() if is_ext_allowed(e)]+['all', 'headless']
|
|
parser.add_option('-1', '--only', choices=choices, default='all',
|
|
help=('Build only the named extension. Available: '+ ', '.join(choices)+'. Default:%default'))
|
|
parser.add_option('--no-compile', default=False, action='store_true',
|
|
help='Skip compiling all C/C++ extensions.')
|
|
parser.add_option('--build-dir', default=None,
|
|
help='Path to directory in which to place object files during the build process, defaults to "build"')
|
|
parser.add_option('--output-dir', default=None,
|
|
help='Path to directory in which to place the built extensions. Defaults to src/calibre/plugins')
|
|
parser.add_option('--debug', default=False, action='store_true',
|
|
help='Build in debug mode')
|
|
parser.add_option('--sanitize', default=False, action='store_true',
|
|
help='Build with sanitization support. Run with LD_PRELOAD=$(gcc -print-file-name=libasan.so)')
|
|
|
|
def dump_db(self, name, db):
|
|
try:
|
|
with open(f'{name}_commands.json', 'w') as f:
|
|
json.dump(db, f, indent=2)
|
|
except OSError as err:
|
|
if err.errno != errno.EROFS:
|
|
raise
|
|
|
|
def run(self, opts):
|
|
from setup.parallel_build import parallel_build, create_job
|
|
if opts.no_compile:
|
|
self.info('--no-compile specified, skipping compilation')
|
|
return
|
|
self.env = init_env(debug=opts.debug)
|
|
all_extensions = map(parse_extension, filter(is_ext_allowed, read_extensions()))
|
|
self.build_dir = os.path.abspath(opts.build_dir or self.DEFAULT_BUILDDIR)
|
|
self.output_dir = os.path.abspath(opts.output_dir or self.DEFAULT_OUTPUTDIR)
|
|
self.obj_dir = os.path.join(self.build_dir, 'objects')
|
|
for x in (self.output_dir, self.obj_dir):
|
|
os.makedirs(x, exist_ok=True)
|
|
pyqt_extensions, extensions = [], []
|
|
for ext in all_extensions:
|
|
if opts.only != 'all' and opts.only != ext.name:
|
|
continue
|
|
if ext.error:
|
|
if ext.optional:
|
|
self.warn(ext.error)
|
|
continue
|
|
else:
|
|
raise Exception(ext.error)
|
|
dest = self.dest(ext)
|
|
os.makedirs(self.d(dest), exist_ok=True)
|
|
(pyqt_extensions if ext.sip_files else extensions).append((ext, dest))
|
|
|
|
jobs = []
|
|
objects_map = {}
|
|
self.info(f'Building {len(extensions)+len(pyqt_extensions)} extensions')
|
|
ccdb = []
|
|
for (ext, dest) in extensions:
|
|
cmds, objects = self.get_compile_commands(ext, dest, ccdb)
|
|
objects_map[id(ext)] = objects
|
|
for cmd in cmds:
|
|
jobs.append(create_job(cmd.cmd))
|
|
self.dump_db('compile', ccdb)
|
|
if jobs:
|
|
self.info(f'Compiling {len(jobs)} files...')
|
|
if not parallel_build(jobs, self.info):
|
|
raise SystemExit(1)
|
|
jobs, link_commands, lddb = [], [], []
|
|
for (ext, dest) in extensions:
|
|
objects = objects_map[id(ext)]
|
|
cmd = self.get_link_command(ext, dest, objects, lddb)
|
|
if cmd is not None:
|
|
link_commands.append(cmd)
|
|
jobs.append(create_job(cmd.cmd))
|
|
self.dump_db('link', lddb)
|
|
if jobs:
|
|
self.info(f'Linking {len(jobs)} files...')
|
|
if not parallel_build(jobs, self.info):
|
|
raise SystemExit(1)
|
|
for cmd in link_commands:
|
|
self.post_link_cleanup(cmd)
|
|
|
|
jobs = []
|
|
sbf_map = {}
|
|
for (ext, dest) in pyqt_extensions:
|
|
cmd, sbf, cwd = self.get_sip_commands(ext)
|
|
sbf_map[id(ext)] = sbf
|
|
if cmd is not None:
|
|
jobs.append(create_job(cmd, cwd=cwd))
|
|
if jobs:
|
|
self.info(f'SIPing {len(jobs)} files...')
|
|
if not parallel_build(jobs, self.info):
|
|
raise SystemExit(1)
|
|
for (ext, dest) in pyqt_extensions:
|
|
sbf = sbf_map[id(ext)]
|
|
if not os.path.exists(sbf):
|
|
self.build_pyqt_extension(ext, dest, sbf)
|
|
|
|
if opts.only in {'all', 'headless'}:
|
|
self.build_headless()
|
|
|
|
def dest(self, ext):
|
|
ex = '.pyd' if iswindows else '.so'
|
|
return os.path.join(self.output_dir, getattr(ext, 'name', ext))+ex
|
|
|
|
def inc_dirs_to_cflags(self, dirs):
|
|
return ['-I'+x for x in dirs]
|
|
|
|
def lib_dirs_to_ldflags(self, dirs):
|
|
pref = '/LIBPATH:' if iswindows else '-L'
|
|
return [pref+x for x in dirs if x]
|
|
|
|
def libraries_to_ldflags(self, dirs):
|
|
pref = '' if iswindows else '-l'
|
|
suff = '.lib' if iswindows else ''
|
|
return [pref+x+suff for x in dirs]
|
|
|
|
def get_compile_commands(self, ext, dest, db):
|
|
compiler = self.env.cxx if ext.needs_cxx else self.env.cc
|
|
objects = []
|
|
ans = []
|
|
obj_dir = self.j(self.obj_dir, ext.name)
|
|
einc = self.inc_dirs_to_cflags(ext.inc_dirs)
|
|
os.makedirs(obj_dir, exist_ok=True)
|
|
|
|
for src in ext.sources:
|
|
obj = self.j(obj_dir, os.path.splitext(self.b(src))[0]+'.o')
|
|
objects.append(obj)
|
|
inf = '/Tp' if src.endswith('.cpp') or src.endswith('.cxx') else '/Tc'
|
|
sinc = [inf+src] if iswindows else ['-c', src]
|
|
oinc = ['/Fo'+obj] if iswindows else ['-o', obj]
|
|
cmd = [compiler] + self.env.cflags + ext.cflags + einc + sinc + oinc
|
|
db.append({'arguments': cmd, 'directory': os.getcwd(), 'file': os.path.relpath(src, os.getcwd()), 'output': os.path.relpath(obj, os.getcwd())})
|
|
if self.newer(obj, [src]+ext.headers):
|
|
ans.append(CompileCommand(cmd, src, obj))
|
|
return ans, objects
|
|
|
|
def get_link_command(self, ext, dest, objects, lddb):
|
|
compiler = self.env.cxx if ext.needs_cxx else self.env.cc
|
|
linker = self.env.linker if iswindows else compiler
|
|
dest = self.dest(ext)
|
|
elib = self.lib_dirs_to_ldflags(ext.lib_dirs)
|
|
xlib = self.libraries_to_ldflags(ext.libraries)
|
|
cmd = [linker]
|
|
if iswindows:
|
|
pre_ld_flags = []
|
|
if ext.uses_icu:
|
|
# windows has its own ICU libs that dont work
|
|
pre_ld_flags = elib
|
|
cmd += pre_ld_flags + self.env.ldflags + ext.ldflags + elib + xlib + \
|
|
['/EXPORT:' + init_symbol_name(ext.name)] + objects + ext.extra_objs + ['/OUT:'+dest]
|
|
else:
|
|
cmd += objects + ext.extra_objs + ['-o', dest] + self.env.ldflags + ext.ldflags + elib + xlib
|
|
lddb.append({'arguments': cmd, 'directory': os.getcwd(), 'output': os.path.relpath(dest, os.getcwd())})
|
|
|
|
if self.newer(dest, objects+ext.extra_objs):
|
|
return LinkCommand(cmd, objects, dest)
|
|
|
|
def post_link_cleanup(self, link_command):
|
|
if iswindows:
|
|
dest = link_command.dest
|
|
for x in ('.exp', '.lib'):
|
|
x = os.path.splitext(dest)[0]+x
|
|
if os.path.exists(x):
|
|
os.remove(x)
|
|
|
|
def check_call(self, *args, **kwargs):
|
|
"""print cmdline if an error occurred
|
|
|
|
If something is missing (qmake e.g.) you get a non-informative error
|
|
self.check_call(qmc + [ext.name+'.pro'])
|
|
so you would have to look at the source to see the actual command.
|
|
"""
|
|
try:
|
|
subprocess.check_call(*args, **kwargs)
|
|
except:
|
|
cmdline = ' '.join(['"%s"' % (arg) if ' ' in arg else arg for arg in args[0]])
|
|
print("Error while executing: %s\n" % (cmdline))
|
|
raise
|
|
|
|
def build_headless(self):
|
|
from setup.parallel_build import cpu_count
|
|
if iswindows or ishaiku:
|
|
return # Dont have headless operation on these platforms
|
|
from setup.build_environment import ft_inc_dirs, QMAKE
|
|
self.info('\n####### Building headless QPA plugin', '#'*7)
|
|
a = absolutize
|
|
headers = a([
|
|
'calibre/headless/headless_backingstore.h',
|
|
'calibre/headless/headless_integration.h',
|
|
])
|
|
sources = a([
|
|
'calibre/headless/main.cpp',
|
|
'calibre/headless/headless_backingstore.cpp',
|
|
'calibre/headless/headless_integration.cpp',
|
|
])
|
|
if ismacos:
|
|
sources.extend(a(['calibre/headless/coretext_fontdatabase.mm']))
|
|
else:
|
|
headers.extend(a(['calibre/headless/fontconfig_database.h']))
|
|
sources.extend(a(['calibre/headless/fontconfig_database.cpp']))
|
|
others = a(['calibre/headless/headless.json'])
|
|
target = self.dest('headless')
|
|
if not self.newer(target, headers + sources + others):
|
|
return
|
|
|
|
pro = textwrap.dedent(
|
|
'''\
|
|
TARGET = headless
|
|
PLUGIN_TYPE = platforms
|
|
PLUGIN_CLASS_NAME = HeadlessIntegrationPlugin
|
|
QT += core-private gui-private
|
|
TEMPLATE = lib
|
|
CONFIG += plugin
|
|
QT += fontdatabase_support_private service_support_private eventdispatcher_support_private
|
|
HEADERS = {headers}
|
|
SOURCES = {sources}
|
|
OTHER_FILES = {others}
|
|
INCLUDEPATH += {freetype}
|
|
DESTDIR = {destdir}
|
|
CONFIG -= create_cmake # Prevent qmake from generating a cmake build file which it puts in the calibre src directory
|
|
''').format(
|
|
headers=' '.join(headers), sources=' '.join(sources), others=' '.join(others), destdir=self.d(
|
|
target), freetype=' '.join(ft_inc_dirs))
|
|
bdir = self.j(self.build_dir, 'headless')
|
|
if not os.path.exists(bdir):
|
|
os.makedirs(bdir)
|
|
pf = self.j(bdir, 'headless.pro')
|
|
open(self.j(bdir, '.qmake.conf'), 'wb').close()
|
|
with open(pf, 'wb') as f:
|
|
f.write(pro.encode('utf-8'))
|
|
cwd = os.getcwd()
|
|
os.chdir(bdir)
|
|
try:
|
|
self.check_call([QMAKE] + [self.b(pf)])
|
|
self.check_call([self.env.make] + ['-j%d'%(cpu_count or 1)])
|
|
finally:
|
|
os.chdir(cwd)
|
|
if ismacos:
|
|
os.rename(self.j(self.d(target), 'libheadless.dylib'), self.j(self.d(target), 'headless.so'))
|
|
|
|
def create_sip_build_skeleton(self, src_dir, ext):
|
|
from setup.build_environment import pyqt_sip_abi_version
|
|
abi_version = ''
|
|
if pyqt_sip_abi_version():
|
|
abi_version = f'abi-version = "{pyqt_sip_abi_version()}"'
|
|
sipf = ext.sip_files[0]
|
|
needs_exceptions = 'true' if ext.needs_exceptions else 'false'
|
|
with open(os.path.join(src_dir, 'pyproject.toml'), 'w') as f:
|
|
f.write(f'''
|
|
[build-system]
|
|
requires = ["sip >=5.3", "PyQt-builder >=1"]
|
|
build-backend = "sipbuild.api"
|
|
|
|
[tool.sip.metadata]
|
|
name = "{ext.name}"
|
|
requires-dist = "PyQt5 (>=5.15)"
|
|
|
|
[tool.sip]
|
|
project-factory = "pyqtbuild:PyQtProject"
|
|
|
|
[tool.sip.project]
|
|
sip-files-dir = "."
|
|
{abi_version}
|
|
|
|
[tool.sip.bindings.pictureflow]
|
|
headers = {ext.headers}
|
|
sources = {ext.sources}
|
|
exceptions = {needs_exceptions}
|
|
include-dirs = {ext.inc_dirs}
|
|
qmake-QT = ["widgets"]
|
|
sip-file = "{os.path.basename(sipf)}"
|
|
''')
|
|
shutil.copy2(sipf, src_dir)
|
|
|
|
def get_sip_commands(self, ext):
|
|
from setup.build_environment import QMAKE
|
|
pyqt_dir = self.j(self.build_dir, 'pyqt')
|
|
src_dir = self.j(pyqt_dir, ext.name)
|
|
# TODO: Handle building extensions with multiple SIP files.
|
|
sipf = ext.sip_files[0]
|
|
sbf = self.j(src_dir, self.b(sipf)+'.sbf')
|
|
cmd = None
|
|
cwd = None
|
|
if self.newer(sbf, [sipf] + ext.headers + ext.sources):
|
|
shutil.rmtree(src_dir, ignore_errors=True)
|
|
os.makedirs(src_dir)
|
|
self.create_sip_build_skeleton(src_dir, ext)
|
|
cwd = src_dir
|
|
cmd = [
|
|
sys.executable, '-c',
|
|
'''from sipbuild.tools.build import main; main();''',
|
|
'--verbose', '--no-make', '--qmake', QMAKE
|
|
]
|
|
return cmd, sbf, cwd
|
|
|
|
def build_pyqt_extension(self, ext, dest, sbf):
|
|
self.info(f'\n####### Building {ext.name} extension', '#'*7)
|
|
src_dir = os.path.dirname(sbf)
|
|
cwd = os.getcwd()
|
|
try:
|
|
os.chdir(os.path.join(src_dir, 'build'))
|
|
self.check_call([self.env.make] + ([] if iswindows else ['-j%d'%(os.cpu_count() or 1)]))
|
|
e = 'pyd' if iswindows else 'so'
|
|
m = glob.glob(f'{ext.name}/{ext.name}.*{e}')
|
|
if len(m) != 1:
|
|
raise SystemExit(f'Found extra PyQt extension files: {m}')
|
|
shutil.copy2(m[0], dest)
|
|
with open(sbf, 'w') as f:
|
|
f.write('done')
|
|
finally:
|
|
os.chdir(cwd)
|
|
|
|
def clean(self):
|
|
self.output_dir = self.DEFAULT_OUTPUTDIR
|
|
extensions = map(parse_extension, filter(is_ext_allowed, read_extensions()))
|
|
for ext in extensions:
|
|
dest = self.dest(ext)
|
|
for x in (dest, dest+'.manifest'):
|
|
if os.path.exists(x):
|
|
os.remove(x)
|
|
build_dir = self.DEFAULT_BUILDDIR
|
|
if os.path.exists(build_dir):
|
|
shutil.rmtree(build_dir)
|