Fix build recursion issue

Windows had an issue where it was trying to build the CFFI module after
it had already imported the CFFI module. Which caused permissions
errors during builds.

This fix does several things to make the Windows build work properly and
improve the Linux build too:

* The CFFI module is only build when needed, not as part of an sdist
  package
* Version numbering spilt out into separate file so __init__.py import
  is not required
* Merged _build_ffi.py and _build_wolfssl.py into one file
* Made CFFI only build when called as an executable (which happens
  during binary build time)
* Make tox use bdist-wheel instead of sdist
pull/39/head
Andrew Hutchings 2022-02-07 14:58:46 +00:00 committed by Daniele Lacamera
parent 744a49e5a7
commit b602083429
6 changed files with 308 additions and 333 deletions

View File

@ -26,8 +26,21 @@ import sys
from setuptools import setup
from setuptools.command.build_ext import build_ext
import wolfcrypt
from wolfcrypt._build_wolfssl import build_wolfssl
import re
VERSIONFILE = "wolfcrypt/_version.py"
verstrline = open(VERSIONFILE, "rt").read()
VSRE = r"^__version__ = ['\"]([^'\"]*)['\"]"
mo = re.search(VSRE, verstrline, re.M)
if mo:
verstr = mo.group(1)
else:
raise RuntimeError("Unable to find version string in %s." % (VERSIONFILE,))
VSRE = r"^__wolfssl_version__ = ['\"]([^'\"]*)['\"]"
mo = re.search(VSRE, verstrline, re.M)
if mo:
wolfverstr = mo.group(1)
else:
raise RuntimeError("Unable to find version string in %s." % (VERSIONFILE,))
# long_description
@ -38,30 +51,15 @@ with open("LICENSING.rst") as licensing_file:
long_description = long_description.replace(".. include:: LICENSING.rst\n",
licensing_file.read())
class cffiBuilder(build_ext, object):
def build_extension(self, ext):
""" Compile manually the wolfcrypt-py extension, bypass setuptools
"""
# if USE_LOCAL_WOLFSSL environment variable has been defined,
# do not clone and compile wolfSSL from GitHub
if os.environ.get("USE_LOCAL_WOLFSSL") is None:
build_wolfssl(wolfcrypt.__wolfssl_version__)
super(cffiBuilder, self).build_extension(ext)
setup(
name=wolfcrypt.__title__,
version=wolfcrypt.__version__,
description=wolfcrypt.__summary__,
name="wolfcrypt",
version=verstr,
description="Python module that encapsulates wolfSSL's crypto engine API.",
long_description=long_description,
author=wolfcrypt.__author__,
author_email=wolfcrypt.__email__,
url=wolfcrypt.__uri__,
license=wolfcrypt.__license__,
author="wolfSSL Inc.",
author_email="info@wolfssl.com",
url="https://github.com/wolfssl/wolfcrypt-py",
license="GPLv2 or Commercial License",
packages=["wolfcrypt"],
@ -83,6 +81,5 @@ setup(
],
setup_requires=["cffi"],
install_requires=["cffi"],
cmdclass={"build_ext" : cffiBuilder}
install_requires=["cffi"]
)

View File

@ -2,6 +2,7 @@
envlist = py3
[testenv]
wheel = true
setenv =
PYTHONPATH = {toxinidir}:{toxinidir}/wolfcrypt-py

View File

@ -18,22 +18,12 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
from wolfcrypt._version import __version__, __wolfssl_version__
__title__ = "wolfcrypt"
__summary__ = "Python module that encapsulates wolfSSL's crypto engine API."
__uri__ = "https://github.com/wolfssl/wolfcrypt-py"
# When bumping the C library version, reset the POST count to 0
__wolfssl_version__ = "v5.1.1-stable"
# We're using implicit post releases [PEP 440] to bump package version
# while maintaining the C library version intact for better reference.
# https://www.python.org/dev/peps/pep-0440/#implicit-post-releases
#
# MAJOR.MINOR.BUILD-POST
__version__ = "5.1.1-0"
__author__ = "wolfSSL Inc."
__email__ = "info@wolfssl.com"

View File

@ -21,13 +21,276 @@
import os
import sys
import re
import subprocess
from contextlib import contextmanager
from distutils.util import get_platform
from cffi import FFI
from wolfcrypt import __wolfssl_version__ as version
from wolfcrypt._build_wolfssl import wolfssl_inc_path, wolfssl_lib_path, ensure_wolfssl_src, make, make_flags, local_path
from wolfcrypt._version import __wolfssl_version__ as version
libwolfssl_path = ""
def local_path(path):
""" Return path relative to the root of this project
"""
current = os.path.abspath(os.getcwd())
return os.path.abspath(os.path.join(current, path))
WOLFSSL_SRC_PATH = local_path("lib/wolfssl")
def wolfssl_inc_path():
if sys.platform == "win32":
return os.path.join(WOLFSSL_SRC_PATH)
else:
wolfssl_path = os.environ.get("USE_LOCAL_WOLFSSL")
if wolfssl_path is None:
return local_path("lib/wolfssl")
else:
if os.path.isdir(wolfssl_path) and os.path.exists(wolfssl_path):
return wolfssl_path + "/include"
else:
return "/usr/local/include"
def wolfssl_lib_path():
if sys.platform == "win32":
return os.path.join(WOLFSSL_SRC_PATH, "build", "Release")
else:
wolfssl_path = os.environ.get("USE_LOCAL_WOLFSSL")
if wolfssl_path is None:
return local_path("lib/wolfssl/{}/{}/lib".format(
get_platform(), version))
else:
if os.path.isdir(wolfssl_path) and os.path.exists(wolfssl_path):
return wolfssl_path + "/lib"
else:
return "/usr/local/lib"
def call(cmd):
print("Calling: '{}' from working directory {}".format(cmd, os.getcwd()))
old_env = os.environ["PATH"]
os.environ["PATH"] = "{}:{}".format(WOLFSSL_SRC_PATH, old_env)
subprocess.check_call(cmd, shell=True, env=os.environ)
os.environ["PATH"] = old_env
@contextmanager
def chdir(new_path, mkdir=False):
old_path = os.getcwd()
if mkdir:
try:
os.mkdir(new_path)
except OSError:
pass
try:
yield os.chdir(new_path)
finally:
os.chdir(old_path)
def checkout_version(version):
""" Ensure that we have the right version
"""
with chdir(WOLFSSL_SRC_PATH):
current = ""
try:
current = subprocess.check_output(
["git", "describe", "--all", "--exact-match"]
).strip().decode().split('/')[-1]
except:
pass
if current != version:
tags = subprocess.check_output(
["git", "tag"]
).strip().decode().split("\n")
if version != "master" and version not in tags:
call("git fetch --depth=1 origin tag {}".format(version))
call("git checkout --force {}".format(version))
return True # rebuild needed
return False
def ensure_wolfssl_src(ref):
""" Ensure that wolfssl sources are presents and up-to-date
"""
if not os.path.isdir("lib"):
os.mkdir("lib")
with chdir("lib"):
subprocess.run(["git", "clone", "--depth=1", "https://github.com/wolfssl/wolfssl"])
if not os.path.isdir(os.path.join(WOLFSSL_SRC_PATH, "wolfssl")):
subprocess.run(["git", "submodule", "update", "--init", "--depth=1"])
return checkout_version(version)
def make_flags(prefix):
""" Returns compilation flags
"""
if sys.platform == "win32":
flags = []
flags.append("-DWOLFSSL_CRYPT_TESTS=no")
flags.append("-DWOLFSSL_EXAMPLES=no")
flags.append("-DBUILD_SHARED_LIBS=off")
flags.append("-DWOLFSSL_CRYPT_ONLY=yes")
flags.append("-DWOLFSSL_AES=yes")
flags.append("-DWOLFSSL_DES3=yes")
flags.append("-DWOLFSSL_CHACHA=yes")
flags.append("-DWOLFSSL_AESGCM=yes")
flags.append("-DWOLFSSL_SHA=yes")
flags.append("-DWOLFSSL_SHA384=yes")
flags.append("-DWOLFSSL_SHA512=yes")
flags.append("-DWOLFSSL_SHA3=yes")
flags.append("-DWOLFSSL_HKDF=yes")
flags.append("-DWOLFSSL_MD5=no")
flags.append("-DWOLFSSL_SHA224=no")
flags.append("-DWOLFSSL_POLY1305=no")
flags.append("-DWOLFSSL_RSA=yes")
flags.append("-DWOLFSSL_RSA_PSS=yes")
flags.append("-DWOLFSSL_ECC=yes")
flags.append("-DWOLFSSL_ED25519=yes")
flags.append("-DWOLFSSL_ED448=yes")
flags.append("-DWOLFSSL_CURVE25519=yes")
flags.append("-DWOLFSSL_DH=no")
flags.append("-DWOLFSSL_PWDBASED=yes")
flags.append("-DWOLFSSL_PKCS7=yes")
flags.append("-DWOLFSSL_OLD_TLS=no")
flags.append("-DWOLFSSL_OLD_NAMES=no")
flags.append("-DWOLFSSL_EXTENDED_MASTER=no")
flags.append("-DWOLFSSL_ERROR_STRINGS=no")
# Part of hack for missing CMake option
flags.append("-DCMAKE_C_FLAGS=\"/DWOLFSSL_KEY_GEN=1 /DWOLFCRYPT_ONLY=1 /DWOLFSSL_AESGCM_STREAM=1 /DWOLFSSL_AES_COUNTER=1\"")
return " ".join(flags)
else:
flags = []
if get_platform() in ["linux-x86_64", "linux-i686"]:
flags.append("CFLAGS=-fPIC")
# install location
flags.append("--prefix={}".format(prefix))
# crypt only, lib only
flags.append("--enable-cryptonly")
flags.append("--disable-crypttests")
flags.append("--disable-shared")
# symmetric ciphers
flags.append("--enable-aes")
flags.append("--enable-aesctr")
flags.append("--enable-des3")
flags.append("--enable-chacha")
flags.append("--enable-aesgcm-stream")
flags.append("--enable-aesgcm")
# hashes and MACs
flags.append("--enable-sha")
flags.append("--enable-sha384")
flags.append("--enable-sha512")
flags.append("--enable-sha3")
flags.append("--enable-hkdf")
flags.append("--disable-md5")
flags.append("--disable-sha224")
flags.append("--disable-poly1305")
# asymmetric ciphers
flags.append("--enable-rsa")
flags.append("--enable-rsapss")
flags.append("--enable-ecc")
flags.append("--enable-ed25519")
flags.append("--enable-ed448")
flags.append("--enable-curve25519")
flags.append("--enable-keygen")
flags.append("--disable-dh")
# pwdbased
flags.append("--enable-pwdbased")
flags.append("--enable-pkcs7")
# disabling other configs enabled by default
flags.append("--disable-oldtls")
flags.append("--disable-oldnames")
flags.append("--disable-extended-master")
flags.append("--disable-errorstrings")
return " ".join(flags)
# Horrid hack because we have no CMake option in 5.1.1 for this
def cmake_hack():
options_file = os.path.join(WOLFSSL_SRC_PATH, "wolfssl", "options.h")
with open(options_file, "r") as f:
contents = f.readlines()
contents.insert(26, "#undef WOLFSSL_KEY_GEN\n")
contents.insert(27, "#define WOLFSSL_KEY_GEN\n")
contents.insert(28, "#undef WOLFCRYPT_ONLY\n")
contents.insert(29, "#define WOLFCRYPT_ONLY\n")
contents.insert(30, "#undef WOLFSSL_AESGCM_STREAM\n")
contents.insert(31, "#define WOLFSSL_AESGCM_STREAM\n")
contents.insert(32, "#undef WOLFSSL_AES_COUNTER\n")
contents.insert(33, "#define WOLFSSL_AES_COUNTER\n")
with open(options_file, "w") as f:
contents = "".join(contents)
f.write(contents)
def make(configure_flags):
""" Create a release of wolfSSL C library
"""
if sys.platform == 'win32':
build_path = os.path.join(WOLFSSL_SRC_PATH, "build")
if not os.path.isdir(build_path):
os.mkdir(build_path)
with chdir(build_path):
call("cmake .. {}".format(configure_flags))
cmake_hack()
call("cmake --build . --config Release")
else:
with chdir(WOLFSSL_SRC_PATH):
call("git clean -fdX")
try:
call("./autogen.sh")
except subprocess.CalledProcessError:
call("libtoolize")
call("./autogen.sh")
call("./configure {}".format(configure_flags))
call("make")
call("make install-exec")
def build_wolfssl(version="master"):
prefix = local_path("lib/wolfssl/{}/{}".format(
get_platform(), version))
if sys.platform == 'win32':
libfile = os.path.join(WOLFSSL_SRC_PATH, "build", "Release", "wolfssl.lib")
else:
libfile = os.path.join(prefix, 'lib/libwolfssl.la')
ensure_wolfssl_src(version)
if not os.path.isfile(libfile):
make(make_flags(prefix))
def get_libwolfssl():
global libwolfssl_path
if sys.platform == "win32":
@ -53,12 +316,13 @@ def generate_libwolfssl():
get_platform(), version))
make(make_flags(prefix))
if get_libwolfssl() == 0:
generate_libwolfssl()
get_libwolfssl()
# detect features if user has built against local wolfSSL library
# if they are not, we are controlling build options in _build_wolfssl.py
# if they are not, we are controlling build options above
local_wolfssl = os.environ.get("USE_LOCAL_WOLFSSL")
if local_wolfssl is not None:
# Try to do native wolfSSL/wolfCrypt feature detection.
@ -626,4 +890,6 @@ if FIPS_ENABLED and (FIPS_VERSION > 5 or (FIPS_VERSION == 5 and FIPS_VERSION >=
ffibuilder.cdef(_cdef)
ffibuilder.compile(verbose=True)
if __name__ == "__main__":
ffibuilder.compile(verbose=True)

View File

@ -1,291 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2006-2022 wolfSSL Inc.
#
# This file is part of wolfSSL. (formerly known as CyaSSL)
#
# wolfSSL is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# wolfSSL is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
import os
import sys
import subprocess
from contextlib import contextmanager
from distutils.util import get_platform
from wolfcrypt.__init__ import __wolfssl_version__ as version
def local_path(path):
""" Return path relative to the root of this project
"""
current = os.path.abspath(os.getcwd())
return os.path.abspath(os.path.join(current, path))
WOLFSSL_SRC_PATH = local_path("lib/wolfssl")
def wolfssl_inc_path():
if sys.platform == "win32":
return os.path.join(WOLFSSL_SRC_PATH)
else:
wolfssl_path = os.environ.get("USE_LOCAL_WOLFSSL")
if wolfssl_path is None:
return local_path("lib/wolfssl")
else:
if os.path.isdir(wolfssl_path) and os.path.exists(wolfssl_path):
return wolfssl_path + "/include"
else:
return "/usr/local/include"
def wolfssl_lib_path():
if sys.platform == "win32":
return os.path.join(WOLFSSL_SRC_PATH, "build", "Release")
else:
wolfssl_path = os.environ.get("USE_LOCAL_WOLFSSL")
if wolfssl_path is None:
return local_path("lib/wolfssl/{}/{}/lib".format(
get_platform(), version))
else:
if os.path.isdir(wolfssl_path) and os.path.exists(wolfssl_path):
return wolfssl_path + "/lib"
else:
return "/usr/local/lib"
def call(cmd):
print("Calling: '{}' from working directory {}".format(cmd, os.getcwd()))
old_env = os.environ["PATH"]
os.environ["PATH"] = "{}:{}".format(WOLFSSL_SRC_PATH, old_env)
subprocess.check_call(cmd, shell=True, env=os.environ)
os.environ["PATH"] = old_env
@contextmanager
def chdir(new_path, mkdir=False):
old_path = os.getcwd()
if mkdir:
try:
os.mkdir(new_path)
except OSError:
pass
try:
yield os.chdir(new_path)
finally:
os.chdir(old_path)
def checkout_version(version):
""" Ensure that we have the right version
"""
with chdir(WOLFSSL_SRC_PATH):
current = ""
try:
current = subprocess.check_output(
["git", "describe", "--all", "--exact-match"]
).strip().decode().split('/')[-1]
except:
pass
if current != version:
tags = subprocess.check_output(
["git", "tag"]
).strip().decode().split("\n")
if version != "master" and version not in tags:
call("git fetch --depth=1 origin tag {}".format(version))
call("git checkout --force {}".format(version))
return True # rebuild needed
return False
def ensure_wolfssl_src(ref):
""" Ensure that wolfssl sources are presents and up-to-date
"""
if not os.path.isdir("lib"):
os.mkdir("lib")
with chdir("lib"):
subprocess.run(["git", "clone", "--depth=1", "https://github.com/wolfssl/wolfssl"])
if not os.path.isdir(os.path.join(WOLFSSL_SRC_PATH, "wolfssl")):
subprocess.run(["git", "submodule", "update", "--init", "--depth=1"])
return checkout_version(version)
def make_flags(prefix):
""" Returns compilation flags
"""
if sys.platform == "win32":
flags = []
flags.append("-DWOLFSSL_CRYPT_TESTS=no")
flags.append("-DWOLFSSL_EXAMPLES=no")
flags.append("-DBUILD_SHARED_LIBS=off")
flags.append("-DWOLFSSL_CRYPT_ONLY=yes")
flags.append("-DWOLFSSL_AES=yes")
flags.append("-DWOLFSSL_DES3=yes")
flags.append("-DWOLFSSL_CHACHA=yes")
flags.append("-DWOLFSSL_AESGCM=yes")
flags.append("-DWOLFSSL_SHA=yes")
flags.append("-DWOLFSSL_SHA384=yes")
flags.append("-DWOLFSSL_SHA512=yes")
flags.append("-DWOLFSSL_SHA3=yes")
flags.append("-DWOLFSSL_HKDF=yes")
flags.append("-DWOLFSSL_MD5=no")
flags.append("-DWOLFSSL_SHA224=no")
flags.append("-DWOLFSSL_POLY1305=no")
flags.append("-DWOLFSSL_RSA=yes")
flags.append("-DWOLFSSL_RSA_PSS=yes")
flags.append("-DWOLFSSL_ECC=yes")
flags.append("-DWOLFSSL_ED25519=yes")
flags.append("-DWOLFSSL_ED448=yes")
flags.append("-DWOLFSSL_CURVE25519=yes")
flags.append("-DWOLFSSL_DH=no")
flags.append("-DWOLFSSL_PWDBASED=yes")
flags.append("-DWOLFSSL_PKCS7=yes")
flags.append("-DWOLFSSL_OLD_TLS=no")
flags.append("-DWOLFSSL_OLD_NAMES=no")
flags.append("-DWOLFSSL_EXTENDED_MASTER=no")
flags.append("-DWOLFSSL_ERROR_STRINGS=no")
# Part of hack for missing CMake option
flags.append("-DCMAKE_C_FLAGS=\"/DWOLFSSL_KEY_GEN=1 /DWOLFCRYPT_ONLY=1 /DWOLFSSL_AESGCM_STREAM=1 /DWOLFSSL_AES_COUNTER=1\"")
return " ".join(flags)
else:
flags = []
if get_platform() in ["linux-x86_64", "linux-i686"]:
flags.append("CFLAGS=-fPIC")
# install location
flags.append("--prefix={}".format(prefix))
# crypt only, lib only
flags.append("--enable-cryptonly")
flags.append("--disable-crypttests")
flags.append("--disable-shared")
# symmetric ciphers
flags.append("--enable-aes")
flags.append("--enable-aesctr")
flags.append("--enable-des3")
flags.append("--enable-chacha")
flags.append("--enable-aesgcm-stream")
flags.append("--enable-aesgcm")
# hashes and MACs
flags.append("--enable-sha")
flags.append("--enable-sha384")
flags.append("--enable-sha512")
flags.append("--enable-sha3")
flags.append("--enable-hkdf")
flags.append("--disable-md5")
flags.append("--disable-sha224")
flags.append("--disable-poly1305")
# asymmetric ciphers
flags.append("--enable-rsa")
flags.append("--enable-rsapss")
flags.append("--enable-ecc")
flags.append("--enable-ed25519")
flags.append("--enable-ed448")
flags.append("--enable-curve25519")
flags.append("--enable-keygen")
flags.append("--disable-dh")
# pwdbased
flags.append("--enable-pwdbased")
flags.append("--enable-pkcs7")
# disabling other configs enabled by default
flags.append("--disable-oldtls")
flags.append("--disable-oldnames")
flags.append("--disable-extended-master")
flags.append("--disable-errorstrings")
return " ".join(flags)
# Horrid hack because we have no CMake option in 5.1.1 for this
def cmake_hack():
options_file = os.path.join(WOLFSSL_SRC_PATH, "wolfssl", "options.h")
with open(options_file, "r") as f:
contents = f.readlines()
contents.insert(26, "#undef WOLFSSL_KEY_GEN\n")
contents.insert(27, "#define WOLFSSL_KEY_GEN\n")
contents.insert(28, "#undef WOLFCRYPT_ONLY\n")
contents.insert(29, "#define WOLFCRYPT_ONLY\n")
contents.insert(30, "#undef WOLFSSL_AESGCM_STREAM\n")
contents.insert(31, "#define WOLFSSL_AESGCM_STREAM\n")
contents.insert(32, "#undef WOLFSSL_AES_COUNTER\n")
contents.insert(33, "#define WOLFSSL_AES_COUNTER\n")
with open(options_file, "w") as f:
contents = "".join(contents)
f.write(contents)
def make(configure_flags):
""" Create a release of wolfSSL C library
"""
if sys.platform == 'win32':
build_path = os.path.join(WOLFSSL_SRC_PATH, "build")
if not os.path.isdir(build_path):
os.mkdir(build_path)
with chdir(build_path):
call("cmake .. {}".format(configure_flags))
cmake_hack()
call("cmake --build . --config Release")
else:
with chdir(WOLFSSL_SRC_PATH):
call("git clean -fdX")
try:
call("./autogen.sh")
except subprocess.CalledProcessError:
call("libtoolize")
call("./autogen.sh")
call("./configure {}".format(configure_flags))
call("make")
call("make install-exec")
def build_wolfssl(version="master"):
prefix = local_path("lib/wolfssl/{}/{}".format(
get_platform(), version))
if sys.platform == 'win32':
libfile = os.path.join(WOLFSSL_SRC_PATH, "build", "Release", "wolfssl.lib")
else:
libfile = os.path.join(prefix, 'lib/libwolfssl.la')
ensure_wolfssl_src(version)
if not os.path.isfile(libfile):
make(make_flags(prefix))
if __name__ == "__main__":
build_wolfssl()

View File

@ -0,0 +1,12 @@
# When bumping the C library version, reset the POST count to 0
__wolfssl_version__ = "v5.1.1-stable"
# We're using implicit post releases [PEP 440] to bump package version
# while maintaining the C library version intact for better reference.
# https://www.python.org/dev/peps/pep-0440/#implicit-post-releases
#
# MAJOR.MINOR.BUILD-POST
__version__ = "5.1.1-0"