Build wheels for windows and macos with conan

This commit is contained in:
laggykiller 2024-02-24 03:28:30 +08:00
parent bb6becd3ef
commit 1bc6af1dc4
14 changed files with 390 additions and 280 deletions

292
setup.py
View file

@ -2,24 +2,32 @@
# setup.py: the distutils script
#
import os
import setuptools
import shutil
import subprocess
import json
import sys
from distutils import log
from distutils.command.build_ext import build_ext
from setuptools import Extension
import platform
from glob import glob
from setuptools import setup, Extension
# If you need to change anything, it should be enough to change setup.cfg.
PACKAGE_NAME = 'sqlcipher3'
VERSION = '0.5.2'
# Mapping from Conan architectures to Python machine types
CONAN_ARCHS = {
'x86_64': ['amd64', 'x86_64', 'x64'],
'x86': ['i386', 'i686', 'x86'],
'armv8': ['arm64', 'aarch64', 'aarch64_be', 'armv8b', 'armv8l'],
'ppc64le': ['ppc64le', 'powerpc'],
's390x': ['s390', 's390x'],
}
# define sqlite sources
sources = [os.path.join('src', source)
for source in ["module.c", "connection.c", "cursor.c", "cache.c",
"microprotocols.c", "prepare_protocol.c",
"statement.c", "util.c", "row.c", "blob.c"]]
sources = glob("src/*.c") + ["src/sqlcipher/sqlite3.c"]
include_dirs = ["./src"]
# define packages
packages = [PACKAGE_NAME]
@ -28,128 +36,170 @@ EXTENSION_MODULE_NAME = "._sqlite3"
# Work around clang raising hard error for unused arguments
if sys.platform == "darwin":
os.environ['CFLAGS'] = "-Qunused-arguments"
log.info("CFLAGS: " + os.environ['CFLAGS'])
def get_arch() -> str:
"""Get the Conan compilation target architecture.
If not explicitly set using the `SQLCIPHER3_COMPILE_TARGET` environment variable, this will be
determined using the host machine's platform information.
"""
env_arch = os.getenv('SQLCIPHER3_COMPILE_TARGET', '')
if env_arch:
return env_arch
if (
platform.architecture()[0] == '32bit'
and platform.machine().lower() in (CONAN_ARCHS['x86'] + CONAN_ARCHS['x86_64'])
):
return 'x86'
for k, v in CONAN_ARCHS.items():
if platform.machine().lower() in v:
return k
raise RuntimeError('Unable to determine the compilation target architecture')
def install_openssl(arch: str) -> dict:
"""Install openssl using Conan.
"""
settings = []
if platform.system() == 'Windows':
settings.append('os=Windows')
elif platform.system() == 'Darwin':
settings.append('os=Macos')
if arch == 'x86_64':
settings.append('os.version=10.9')
else:
settings.append('os.version=11.0')
settings.append('compiler=apple-clang')
settings.append('compiler.libcxx=libc++')
elif platform.system() == 'Linux':
settings.append('os=Linux')
settings.append(f'arch={arch}')
build = ['missing']
if os.path.isdir('/lib') and any(e.startswith('libc.musl') for e in os.listdir('/lib')):
# Need to compile openssl if musllinux
build.append('openssl*')
subprocess.run(['conan', 'profile', 'detect'])
conan_output = os.path.join('conan_output', arch)
result = subprocess.run([
'conan', 'install',
*[x for s in settings for x in ('-s', s)],
*[x for b in build for x in ('-b', b)],
'-of', conan_output, '--deployer=direct_deploy', '--format=json', '.'
], stdout=subprocess.PIPE).stdout.decode()
conan_info = json.loads(result)
return conan_info
def fetch_openssl_dir(conan_info: dict) -> str:
"""Find directory of openssl.
"""
for dep in conan_info['graph']['nodes'].values():
if dep.get('name') == 'openssl':
return dep.get('package_folder')
def quote_argument(arg):
q = '\\"' if sys.platform == 'win32' and sys.version_info < (3, 8) else '"'
return q + arg + q
define_macros = [('MODULE_NAME', quote_argument(PACKAGE_NAME + '.dbapi2'))]
define_macros = [
('MODULE_NAME', quote_argument(PACKAGE_NAME + '.dbapi2')),
('ENABLE_FTS3', '1'),
('ENABLE_FTS3_PARENTHESIS', '1'),
('ENABLE_FTS4', '1'),
('ENABLE_FTS5', '1'),
('ENABLE_JSON1', '1'),
('ENABLE_LOAD_EXTENSION', '1'),
('ENABLE_RTREE', '1'),
('ENABLE_STAT4', '1'),
('ENABLE_UPDATE_DELETE_LIMIT', '1'),
('SOUNDEX', '1'),
('USE_URI', '1'),
# Required for SQLCipher.
('SQLITE_HAS_CODEC', '1'),
('HAS_CODEC', '1'),
("SQLITE_TEMP_STORE", "2"),
# Increase the maximum number of "host parameters".
("SQLITE_MAX_VARIABLE_NUMBER", "250000"),
# Additional nice-to-have.
('SQLITE_DEFAULT_PAGE_SIZE', '4096'),
('SQLITE_DEFAULT_CACHE_SIZE', '-8000'),
]
arch = get_arch()
if arch == 'universal2':
conan_info_x64 = install_openssl('x86_64')
openssl_dir_x64 = fetch_openssl_dir(conan_info_x64)
conan_info_arm = install_openssl('armv8')
openssl_dir_arm = fetch_openssl_dir(conan_info_arm)
openssl_dir_universal2 = openssl_dir_arm.replace('armv8', 'universal2')
subprocess.run(
[
'python3',
'./lipo-dir-merge/lipo-dir-merge.py',
openssl_dir_x64,
openssl_dir_arm,
openssl_dir_universal2
]
)
shutil.rmtree(openssl_dir_x64)
shutil.move(openssl_dir_universal2, openssl_dir_x64)
openssl_dir = openssl_dir_x64
else:
conan_info = install_openssl(arch)
openssl_dir = fetch_openssl_dir(conan_info)
class SystemLibSqliteBuilder(build_ext):
description = "Builds a C extension linking against libsqlcipher library"
extra_link_args = []
if sys.platform != "win32":
# Include math library, required for fts5, and crypto.
extra_link_args.extend(["-lm", "-lcrypto"])
def build_extension(self, ext):
log.info(self.description)
ext.libraries.append('sqlcipher')
ext.define_macros.append(('SQLITE_HAS_CODEC', '1'))
build_ext.build_extension(self, ext)
openssl_lib_path = os.path.join(openssl_dir, "lib")
# Configure the compiler
include_dirs.append(os.path.join(openssl_dir, "include"))
define_macros.append(("inline", "__inline"))
class AmalgationLibSqliteBuilder(build_ext):
description = "Builds a C extension using a sqlcipher amalgamation"
# Configure the linker
if sys.platform == "win32":
# https://github.com/openssl/openssl/blob/master/NOTES-WINDOWS.md#linking-native-applications
extra_link_args.append("WS2_32.LIB")
extra_link_args.append("GDI32.LIB")
extra_link_args.append("ADVAPI32.LIB")
extra_link_args.append("CRYPT32.LIB")
extra_link_args.append("USER32.LIB")
extra_link_args.append('libcrypto.lib')
else:
extra_link_args.append('libcrypto.a')
amalgamation_root = "."
amalgamation_header = os.path.join(amalgamation_root, 'sqlite3.h')
amalgamation_source = os.path.join(amalgamation_root, 'sqlite3.c')
module = Extension(
name=PACKAGE_NAME + EXTENSION_MODULE_NAME,
sources=sources,
define_macros=define_macros,
library_dirs=[openssl_lib_path],
include_dirs=include_dirs,
extra_link_args=extra_link_args,
language="c",
)
header_dir = os.path.join(amalgamation_root, 'sqlcipher')
header_file = os.path.join(header_dir, 'sqlite3.h')
with open("README.md", "r", encoding="utf-8") as fr:
long_description = fr.read()
amalgamation_message = ('Sqlcipher amalgamation not found. Please download'
' or build the amalgamation and make sure the '
'following files are present in the sqlcipher3 '
'folder: sqlite3.h, sqlite3.c')
def check_amalgamation(self):
header_exists = os.path.exists(self.amalgamation_header)
source_exists = os.path.exists(self.amalgamation_source)
if not header_exists or not source_exists:
raise RuntimeError(self.amalgamation_message)
if not os.path.exists(self.header_dir):
os.mkdir(self.header_dir)
if not os.path.exists(self.header_file):
shutil.copy(self.amalgamation_header, self.header_file)
def build_extension(self, ext):
log.info(self.description)
# it is responsibility of user to provide amalgamation
self.check_amalgamation()
# Feature-ful library.
features = (
'ENABLE_FTS3',
'ENABLE_FTS3_PARENTHESIS',
'ENABLE_FTS4',
'ENABLE_FTS5',
'ENABLE_JSON1',
'ENABLE_LOAD_EXTENSION',
'ENABLE_RTREE',
'ENABLE_STAT4',
'ENABLE_UPDATE_DELETE_LIMIT',
'HAS_CODEC', # Required for SQLCipher.
'SOUNDEX',
'USE_URI',
)
for feature in features:
ext.define_macros.append(('SQLITE_%s' % feature, '1'))
# Required for SQLCipher.
ext.define_macros.append(("SQLITE_TEMP_STORE", "2"))
# Increase the maximum number of "host parameters".
ext.define_macros.append(("SQLITE_MAX_VARIABLE_NUMBER", "250000"))
# Additional nice-to-have.
ext.define_macros.extend((
('SQLITE_DEFAULT_PAGE_SIZE', '4096'),
('SQLITE_DEFAULT_CACHE_SIZE', '-8000'))) # 8MB.
ext.include_dirs.append(self.amalgamation_root)
ext.sources.append(os.path.join(self.amalgamation_root, "sqlite3.c"))
if sys.platform != "win32":
# Include math library, required for fts5, and crypto.
ext.extra_link_args.extend(["-lm", "-lcrypto"])
else:
# Try to locate openssl.
openssl_conf = os.environ.get('OPENSSL_CONF')
if not openssl_conf:
error_message = 'Fatal error: OpenSSL could not be detected!'
raise RuntimeError(error_message)
openssl = os.path.dirname(os.path.dirname(openssl_conf))
openssl_lib_path = os.path.join(openssl, "lib")
# Configure the compiler
ext.include_dirs.append(os.path.join(openssl, "include"))
ext.define_macros.append(("inline", "__inline"))
# Configure the linker
openssl_libname = os.environ.get('OPENSSL_LIBNAME') or 'libeay32.lib'
ext.extra_link_args.append(openssl_libname)
ext.extra_link_args.append('/LIBPATH:' + openssl_lib_path)
build_ext.build_extension(self, ext)
def __setattr__(self, k, v):
# Make sure we don't link against the SQLite
# library, no matter what setup.cfg says
if k == "libraries":
v = None
self.__dict__[k] = v
def get_setup_args():
return dict(
if __name__ == "__main__":
setup(
name=PACKAGE_NAME,
version=VERSION,
description="DB-API 2.0 interface for SQLCipher 3.x",
long_description='',
long_description=long_description,
author="Charles Leifer",
author_email="coleifer@gmail.com",
license="zlib/libpng",
@ -157,11 +207,7 @@ def get_setup_args():
url="https://github.com/coleifer/sqlcipher3",
package_dir={PACKAGE_NAME: "sqlcipher3"},
packages=packages,
ext_modules=[Extension(
name=PACKAGE_NAME + EXTENSION_MODULE_NAME,
sources=sources,
define_macros=define_macros)
],
ext_modules=[module],
classifiers=[
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
@ -173,12 +219,4 @@ def get_setup_args():
"Programming Language :: Python",
"Topic :: Database :: Database Engines/Servers",
"Topic :: Software Development :: Libraries :: Python Modules"],
cmdclass={
"build_static": AmalgationLibSqliteBuilder,
"build_ext": SystemLibSqliteBuilder
}
)
if __name__ == "__main__":
setuptools.setup(**get_setup_args())