198 lines
6.2 KiB
Python
198 lines
6.2 KiB
Python
# SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
# Copyright (c) 2024 Phil Thompson <phil@riverbankcomputing.com>
|
|
|
|
|
|
import os
|
|
|
|
from .exceptions import UserException
|
|
from .installable import Installable
|
|
from .py_versions import OLDEST_SUPPORTED_MINOR
|
|
from .version import SIP_VERSION_STR
|
|
|
|
|
|
class Buildable:
|
|
""" Encapsulate the components used to build something that can be
|
|
installed.
|
|
"""
|
|
|
|
def __init__(self, project, name):
|
|
""" Initialise the buildable. """
|
|
|
|
self.project = project
|
|
self.name = name
|
|
self.builder_settings = []
|
|
self.installables = []
|
|
|
|
self.build_dir = os.path.join(project.build_dir, name)
|
|
os.makedirs(self.build_dir, exist_ok=True)
|
|
|
|
|
|
class BuildableFromSources(Buildable):
|
|
""" Encapsulate the sources used to build an extension module, executable
|
|
etc.
|
|
"""
|
|
|
|
def __init__(self, project, name, target, *, uses_limited_api=False):
|
|
""" Initialise the buildable. """
|
|
|
|
super().__init__(project, name)
|
|
|
|
if project.py_debug:
|
|
uses_limited_api = False
|
|
elif uses_limited_api and not project.sip_module:
|
|
raise UserException(
|
|
"{0} cannot use the limited API without using a shared "
|
|
"'sip' module".format(name))
|
|
|
|
self.target = target
|
|
self.uses_limited_api = uses_limited_api
|
|
|
|
self.define_macros = []
|
|
self.sources = []
|
|
self.headers = []
|
|
self.include_dirs = []
|
|
self.libraries = []
|
|
self.library_dirs = []
|
|
self.extra_compile_args = []
|
|
self.extra_link_args = []
|
|
self.extra_objects = []
|
|
self.debug = False
|
|
|
|
if self.uses_limited_api:
|
|
self.define_macros.append(
|
|
'Py_LIMITED_API=0x03{0:02x}0000'.format(
|
|
OLDEST_SUPPORTED_MINOR))
|
|
|
|
def make_names_relative(self):
|
|
""" Make all file and directory names relative to the build directory.
|
|
"""
|
|
|
|
# Make the file names relative to the build directory.
|
|
self.include_dirs = self._relative_names(self.include_dirs)
|
|
self.headers = self._relative_names(self.headers)
|
|
self.sources = self._relative_names(self.sources)
|
|
self.library_dirs = self._relative_names(self.library_dirs)
|
|
|
|
def _relative_names(self, names):
|
|
""" Return a list of times made relative to build directory. Note that
|
|
we only really do this for cosmetic reasons to simplify what the user
|
|
might see.
|
|
"""
|
|
|
|
rel_names = []
|
|
|
|
for fn in names:
|
|
try:
|
|
common = os.path.commonpath([fn, self.build_dir])
|
|
_, common = os.path.splitdrive(common)
|
|
|
|
if len(common) > 1:
|
|
# Only convert to a relative name if there is at least one
|
|
# parent directory in common.
|
|
fn = os.path.relpath(fn, self.build_dir)
|
|
except ValueError:
|
|
# This is most likely to happen if the build directory is on a
|
|
# different Windows drive.
|
|
pass
|
|
|
|
rel_names.append(fn)
|
|
|
|
return rel_names
|
|
|
|
|
|
class BuildableExecutable(BuildableFromSources):
|
|
""" Encapsulate the sources used to build an executable. """
|
|
|
|
|
|
class BuildableModule(BuildableFromSources):
|
|
""" Encapsulate the sources used to build an extension module. """
|
|
|
|
def __init__(self, project, name, fq_name, *, uses_limited_api=False):
|
|
""" Initialise the sources. """
|
|
|
|
super().__init__(project, name, fq_name.split('.')[-1],
|
|
uses_limited_api=uses_limited_api)
|
|
|
|
self.fq_name = fq_name
|
|
|
|
self.exceptions = False
|
|
self.static = False
|
|
|
|
def get_install_subdir(self):
|
|
""" Return the sub-directory the extension module should be installed
|
|
in relative to the eventual target directory.
|
|
"""
|
|
|
|
return os.sep.join(self.fq_name.split('.')[:-1])
|
|
|
|
def get_module_extension(self):
|
|
""" Return the filename extension that a module should have. """
|
|
|
|
if self.project.py_platform == 'win32':
|
|
return '.pyd'
|
|
|
|
from importlib.machinery import EXTENSION_SUFFIXES
|
|
|
|
if self.uses_limited_api:
|
|
for s in EXTENSION_SUFFIXES:
|
|
if '.abi3' in s:
|
|
return s
|
|
|
|
return EXTENSION_SUFFIXES[0]
|
|
|
|
|
|
class BuildableBindings(BuildableModule):
|
|
""" Encapsulate the sources used to build the extension module for a set of
|
|
bindings.
|
|
"""
|
|
|
|
def __init__(self, bindings, fq_name, *, uses_limited_api=False):
|
|
""" Initialise the sources. """
|
|
|
|
super().__init__(bindings.project, fq_name.split('.')[-1], fq_name,
|
|
uses_limited_api=uses_limited_api)
|
|
|
|
self.bindings = bindings
|
|
|
|
def get_bindings_installable(self, name):
|
|
""" Return an installable for the buildable's bindings directory. """
|
|
|
|
target_subdir = os.path.join(self.project.get_bindings_dir(),
|
|
self.name)
|
|
|
|
return Installable(name, target_subdir=target_subdir)
|
|
|
|
def write_configuration(self, bindings_dir):
|
|
""" Write the configuration of the bindings and add it as an
|
|
installable.
|
|
"""
|
|
|
|
# Make sure the bindings directory exists.
|
|
bindings_dir = os.path.join(bindings_dir, self.name)
|
|
os.makedirs(bindings_dir, exist_ok=True)
|
|
|
|
config_path = os.path.join(bindings_dir, self.name + '.toml')
|
|
|
|
# Create an installable for the configuration file.
|
|
installable = self.get_bindings_installable('config')
|
|
installable.files.append(config_path)
|
|
self.installables.append(installable)
|
|
|
|
# Write the configuration file.
|
|
bindings = self.bindings
|
|
|
|
with open(config_path, 'w') as cf:
|
|
sip_version_str = SIP_VERSION_STR if self.project.version_info else ''
|
|
tags = ', '.join(['"{}"'.format(t) for t in bindings.tags])
|
|
disabled = ', '.join(
|
|
['"{}"'.format(f) for f in bindings.disabled_features])
|
|
|
|
cf.write("# Automatically generated configuration for {0}.\n".format(self.fq_name))
|
|
cf.write('''
|
|
sip-version = "{}"
|
|
sip-abi-version = "{}"
|
|
module-tags = [{}]
|
|
module-disabled-features = [{}]
|
|
'''.format(sip_version_str, self.project.abi_version, tags, disabled))
|