240 lines
7.8 KiB
Python
240 lines
7.8 KiB
Python
# SPDX-License-Identifier: BSD-2-Clause
|
|||
|
|
|
||
|
|
# Copyright (c) 2024 Phil Thompson <phil@riverbankcomputing.com>
|
||
|
|
|
||
|
|
|
||
|
|
import os
|
||
|
|
import shutil
|
||
|
|
import subprocess
|
||
|
|
import sys
|
||
|
|
|
||
|
|
from ..exceptions import UserException
|
||
|
|
from ..py_versions import MINIMUM_SETUPTOOLS, OLDEST_SUPPORTED_MINOR
|
||
|
|
from ..version import SIP_VERSION, SIP_VERSION_STR
|
||
|
|
|
||
|
|
from .abi_version import (get_latest_version, get_module_source_dir,
|
||
|
|
get_source_version_range, parse_abi_version)
|
||
|
|
|
||
|
|
|
||
|
|
def module(sip_module, abi_version, project, sdist, setup_cfg, sip_h, sip_rst,
|
||
|
|
target_dir):
|
||
|
|
""" Create the various elements of a sip module. """
|
||
|
|
|
||
|
|
# Check we have the required source.
|
||
|
|
if abi_version:
|
||
|
|
major_version, minor_version = parse_abi_version(abi_version)
|
||
|
|
else:
|
||
|
|
major_version = get_latest_version()
|
||
|
|
minor_version = None
|
||
|
|
|
||
|
|
oldest_source, latest_source = get_source_version_range(major_version)
|
||
|
|
|
||
|
|
if minor_version is None:
|
||
|
|
minor_version = latest_source
|
||
|
|
elif minor_version < oldest_source:
|
||
|
|
raise UserException(
|
||
|
|
f"ABI v{major_version}.{oldest_source} is the oldest available")
|
||
|
|
|
||
|
|
module_source_dir = get_module_source_dir((major_version, minor_version))
|
||
|
|
|
||
|
|
if project is None:
|
||
|
|
project = sip_module.replace('.', '_')
|
||
|
|
|
||
|
|
# Create the patches.
|
||
|
|
patches = _create_patches(sip_module, module_source_dir, project)
|
||
|
|
|
||
|
|
# The names of generated files.
|
||
|
|
sdist_dir = project + '-' + patches['@SIP_MODULE_VERSION@']
|
||
|
|
sip_h_fn = 'sip.h'
|
||
|
|
sip_rst_fn = 'sip.rst'
|
||
|
|
|
||
|
|
if target_dir:
|
||
|
|
sdist_dir = os.path.join(target_dir, sdist_dir)
|
||
|
|
sip_h_fn = os.path.join(target_dir, sip_h_fn)
|
||
|
|
sip_rst_fn = os.path.join(target_dir, sip_rst_fn)
|
||
|
|
|
||
|
|
# Generate the required files.
|
||
|
|
if sdist:
|
||
|
|
_create_sdist(sdist_dir, module_source_dir, patches, setup_cfg)
|
||
|
|
|
||
|
|
if sip_h:
|
||
|
|
_create_sip_file(sip_h_fn, module_source_dir, patches)
|
||
|
|
|
||
|
|
if sip_rst:
|
||
|
|
_create_sip_file(sip_rst_fn, module_source_dir, patches)
|
||
|
|
|
||
|
|
|
||
|
|
def copy_sip_h(abi_version, target_dir, sip_module='', version_info=True):
|
||
|
|
""" Copy the sip.h file. """
|
||
|
|
|
||
|
|
module_source_dir = get_module_source_dir(abi_version)
|
||
|
|
patches = _create_patches(sip_module, module_source_dir,
|
||
|
|
version_info=version_info)
|
||
|
|
_install_source_file('sip.h', module_source_dir, target_dir, patches)
|
||
|
|
|
||
|
|
|
||
|
|
def copy_sip_pyi(abi_version, target_dir):
|
||
|
|
""" Copy the sip.pyi file. """
|
||
|
|
|
||
|
|
module_source_dir = get_module_source_dir(abi_version)
|
||
|
|
shutil.copy(os.path.join(module_source_dir, 'sip.pyi'), target_dir)
|
||
|
|
|
||
|
|
|
||
|
|
def copy_nonshared_sources(abi_version, target_dir):
|
||
|
|
""" Copy the module sources as a non-shared module. """
|
||
|
|
|
||
|
|
# Copy the patched sip.h.
|
||
|
|
copy_sip_h(abi_version, target_dir)
|
||
|
|
|
||
|
|
# Copy the remaining source code.
|
||
|
|
sources = []
|
||
|
|
|
||
|
|
module_source_dir = get_module_source_dir(abi_version)
|
||
|
|
|
||
|
|
for fn in os.listdir(module_source_dir):
|
||
|
|
if fn.endswith('.c') or fn.endswith('.cpp') or fn.endswith('.h'):
|
||
|
|
src_fn = os.path.join(module_source_dir, fn)
|
||
|
|
dst_fn = os.path.join(target_dir, fn)
|
||
|
|
shutil.copyfile(src_fn, dst_fn)
|
||
|
|
|
||
|
|
if not fn.endswith('.h'):
|
||
|
|
sources.append(dst_fn)
|
||
|
|
|
||
|
|
return sources
|
||
|
|
|
||
|
|
|
||
|
|
def _create_patches(sip_module, module_source_dir, project='',
|
||
|
|
version_info=True):
|
||
|
|
""" Return a dict of the patches. """
|
||
|
|
|
||
|
|
sip_module_parts = sip_module.split('.')
|
||
|
|
sip_module_package_name = '.'.join(sip_module_parts[:-1])
|
||
|
|
sip_module_name = sip_module_parts[-1]
|
||
|
|
|
||
|
|
# We special case this because this should be the only package requiring
|
||
|
|
# the support.
|
||
|
|
legacy = (sip_module == 'PyQt5.sip')
|
||
|
|
|
||
|
|
if version_info:
|
||
|
|
sip_version = SIP_VERSION
|
||
|
|
sip_version_str = SIP_VERSION_STR
|
||
|
|
else:
|
||
|
|
sip_version = 0
|
||
|
|
sip_version_str = ''
|
||
|
|
|
||
|
|
# Get the full version number of the sip module.
|
||
|
|
abi_major = abi_minor = abi_patch = '???'
|
||
|
|
|
||
|
|
with open(os.path.join(module_source_dir, 'sip.h.in')) as vf:
|
||
|
|
for line in vf:
|
||
|
|
parts = line.strip().split()
|
||
|
|
if len(parts) == 3 and parts[0] == '#define':
|
||
|
|
name = parts[1]
|
||
|
|
value = parts[2]
|
||
|
|
|
||
|
|
if name == 'SIP_ABI_MAJOR_VERSION':
|
||
|
|
abi_major = value
|
||
|
|
elif name == 'SIP_ABI_MINOR_VERSION':
|
||
|
|
abi_minor = value
|
||
|
|
elif name == 'SIP_MODULE_PATCH_VERSION':
|
||
|
|
abi_patch = value
|
||
|
|
|
||
|
|
sip_module_version = f'{abi_major}.{abi_minor}.{abi_patch}'
|
||
|
|
|
||
|
|
return {
|
||
|
|
# The public patches are those that might be needed in setup.cfg or any
|
||
|
|
# automatically generated user documentation.
|
||
|
|
'@SIP_MODULE_FQ_NAME@': sip_module,
|
||
|
|
'@SIP_MODULE_PROJECT_NAME@': project,
|
||
|
|
'@SIP_MODULE_PACKAGE_NAME@': sip_module_package_name,
|
||
|
|
'@SIP_MODULE_VERSION@': sip_module_version,
|
||
|
|
|
||
|
|
# These are internal.
|
||
|
|
'@_SIP_MINIMUM_SETUPTOOLS@': MINIMUM_SETUPTOOLS,
|
||
|
|
'@_SIP_MODULE_FQ_NAME@': sip_module,
|
||
|
|
'@_SIP_MODULE_NAME@': sip_module_name,
|
||
|
|
'@_SIP_MODULE_SHARED@': '1' if sip_module else '0',
|
||
|
|
'@_SIP_MODULE_ENTRY@': 'PyInit_' + sip_module_name,
|
||
|
|
'@_SIP_MODULE_LEGACY@': "1" if legacy else "0",
|
||
|
|
'@_SIP_OLDEST_SUPPORTED_MINOR@': str(OLDEST_SUPPORTED_MINOR),
|
||
|
|
'@_SIP_OLDEST_SUPPORTED_MINOR_HEX@': format(OLDEST_SUPPORTED_MINOR,
|
||
|
|
'02x'),
|
||
|
|
'@_SIP_VERSION@': hex(sip_version),
|
||
|
|
'@_SIP_VERSION_STR@': sip_version_str
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
def _create_sdist(sdist_dir, module_source_dir, patches, setup_cfg):
|
||
|
|
""" Create the sdist. """
|
||
|
|
|
||
|
|
# Remove any existing source directory.
|
||
|
|
shutil.rmtree(sdist_dir, ignore_errors=True)
|
||
|
|
|
||
|
|
os.mkdir(sdist_dir)
|
||
|
|
|
||
|
|
# The source directory doesn't have sub-directories.
|
||
|
|
for name in os.listdir(module_source_dir):
|
||
|
|
if name in ('setup.cfg.in', 'sip.pyi', 'sip.rst.in'):
|
||
|
|
continue
|
||
|
|
|
||
|
|
if name != 'MANIFEST.in' and name.endswith('.in'):
|
||
|
|
name = name[:-3]
|
||
|
|
|
||
|
|
# Don't install the default README if we are not using the default
|
||
|
|
# setup.cfg.
|
||
|
|
if name != 'README' or setup_cfg is None:
|
||
|
|
_install_source_file(name, module_source_dir, sdist_dir,
|
||
|
|
patches)
|
||
|
|
else:
|
||
|
|
shutil.copy(os.path.join(module_source_dir, name), sdist_dir)
|
||
|
|
|
||
|
|
# Write setup.cfg as required.
|
||
|
|
if setup_cfg is None:
|
||
|
|
setup_cfg = os.path.join(module_source_dir, 'setup.cfg.in')
|
||
|
|
|
||
|
|
_install_file(setup_cfg, os.path.join(sdist_dir, 'setup.cfg'), patches)
|
||
|
|
|
||
|
|
# Create the sdist file using setuptools. This means any user supplied
|
||
|
|
# setup.cfg should be handled correctly.
|
||
|
|
saved_cwd = os.getcwd()
|
||
|
|
os.chdir(sdist_dir)
|
||
|
|
|
||
|
|
subprocess.run(
|
||
|
|
[sys.executable, 'setup.py', '--quiet', 'sdist', '--dist-dir',
|
||
|
|
'..'])
|
||
|
|
|
||
|
|
os.chdir(saved_cwd)
|
||
|
|
|
||
|
|
# Tidy up.
|
||
|
|
shutil.rmtree(sdist_dir)
|
||
|
|
|
||
|
|
|
||
|
|
def _create_sip_file(sip_file_fn, module_source_dir, patches):
|
||
|
|
""" Create a patched file from the module source directory. """
|
||
|
|
|
||
|
|
dname, fname = os.path.split(os.path.abspath(sip_file_fn))
|
||
|
|
_install_source_file(fname, module_source_dir, dname, patches)
|
||
|
|
|
||
|
|
|
||
|
|
def _install_source_file(name, module_source_dir, target_dir, patches):
|
||
|
|
""" Install a source file in a target directory. """
|
||
|
|
|
||
|
|
_install_file(os.path.join(module_source_dir, name) + '.in',
|
||
|
|
os.path.join(target_dir, name), patches)
|
||
|
|
|
||
|
|
|
||
|
|
def _install_file(name_in, name_out, patches):
|
||
|
|
""" Install a file. """
|
||
|
|
|
||
|
|
# Read the file.
|
||
|
|
with open(name_in) as f:
|
||
|
|
data = f.read()
|
||
|
|
|
||
|
|
# Patch the file.
|
||
|
|
for patch_name, patch in patches.items():
|
||
|
|
data = data.replace(patch_name, patch)
|
||
|
|
|
||
|
|
# Write the file.
|
||
|
|
with open(name_out, 'w') as f:
|
||
|
|
f.write(data)
|