forked from pypa/distlib
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add wheel with a C extension to test mounting
This recreates the source to the `tests/minimext*.whl` wheels with a couple of differences: * The C extension is placed in the minimext package rather than being a top level package. * A Python implementation of the C extension is provided to make it easier for pure Python programmers to understand / modify. This also adds a script `build_wheels.sh` that will install Python interpreters and build the minimext wheel for different Python versions with and without adding the `EXTENSIONS` metadata file. Related to pypa#222
- Loading branch information
1 parent
888c48b
commit 4a67db8
Showing
8 changed files
with
304 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
BSD-licensed. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Distribution with a simple C extension that calculates Fibonacci numbers. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
#!/bin/bash -e | ||
# Copyright (C) 2024 Stewart Miles | ||
# Licensed to the Python Software Foundation under a contributor agreement. | ||
# See LICENSE.txt and CONTRIBUTORS.txt. | ||
|
||
readonly DEFAULT_PYTHON_VERSION="$(python --version | | ||
cut -d ' ' -f 2 | | ||
cut -d. -f 1,2)" | ||
readonly DEFAULT_PYTHON_VERSIONS="2.7 3.5 ${DEFAULT_PYTHON_VERSION}" | ||
|
||
|
||
help() { | ||
echo "\ | ||
Builds Linux wheels for this package using a range of Python distributions. | ||
This script requires a Ubuntu distribution and will leave the deadsnakes PPA | ||
https://launchpad.net/~deadsnakes/+archive/ubuntu/ppa | ||
and Python packages used by this script installed. | ||
$(basename "$0") [-d] [-I] [-S] [-p versions] [-h] | ||
-d: Enable dry run mode, simply display rather than execute commands. | ||
-S: Disable Python PPA installation. | ||
-I: Disable Python apt package installation | ||
-p: Space separated list of Python versions to install and build wheels for. | ||
This defaults to \"${DEFAULT_PYTHON_VERSIONS}\". | ||
-h: Display this help. | ||
" | ||
exit 1 | ||
} | ||
|
||
main() { | ||
readonly THIS_DIRECTORY="$(cd "$(dirname "${0}")"; pwd)" | ||
local dryrun= | ||
local install_python=1 | ||
local install_python_ppa=1 | ||
local selected_python_versions="${DEFAULT_PYTHON_VERSIONS}" | ||
|
||
while getopts "dhISp:" OPTION; do | ||
# shellcheck disable=SC2209 | ||
case "${OPTION}" in | ||
d) dryrun=echo | ||
;; | ||
I) install_python=0 | ||
;; | ||
S) install_python_ppa=0 | ||
;; | ||
p) selected_python_versions="${OPTARG}" | ||
;; | ||
h|*) help | ||
;; | ||
esac | ||
done | ||
|
||
IFS=' ' read -r -a python_versions <<< "${selected_python_versions}" | ||
|
||
if [[ $((install_python_ppa)) -eq 1 ]]; then | ||
set -x | ||
${dryrun} sudo add-apt-repository ppa:deadsnakes/ppa | ||
set +x | ||
fi | ||
|
||
if [[ $((install_python)) -eq 1 ]]; then | ||
# shellcheck disable=SC2207 | ||
readonly -a PYTHON_APT_PACKAGES=( | ||
$(for version in "${python_versions[@]}"; do | ||
echo "python${version}-dev"; | ||
done)) | ||
set -x | ||
${dryrun} sudo apt install "${PYTHON_APT_PACKAGES[@]}" | ||
set +x | ||
fi | ||
|
||
local wheels_directory="${THIS_DIRECTORY}/wheels" | ||
mkdir -p "${wheels_directory}" | ||
|
||
local venv_directory | ||
local versioned_python | ||
local version | ||
for version in "${python_versions[@]}"; do | ||
versioned_python="python${version}" | ||
venv_directory="${THIS_DIRECTORY}/.venv${version}" | ||
|
||
# Try to bootstrap pip if it isn't found. | ||
if ! ${dryrun} "${versioned_python}" -c "import pip" 2> /dev/null; then | ||
# shellcheck disable=SC2155 | ||
local temporary_directory="$(mktemp -d)" | ||
local get_pip="${temporary_directory}/get-pip-${version}.py" | ||
${dryrun} curl --output "${get_pip}" \ | ||
"https://bootstrap.pypa.io/pip/${version}/get-pip.py" | ||
${dryrun} "${versioned_python}" "${get_pip}" | ||
rm -rf "${temporary_directory}" | ||
fi | ||
|
||
# Install virtualenv as venv isn't available in all Python versions. | ||
${dryrun} "${versioned_python}" -m pip install virtualenv | ||
${dryrun} "${versioned_python}" -m virtualenv "${venv_directory}" | ||
( | ||
cd "${THIS_DIRECTORY}" | ||
${dryrun} source "${venv_directory}/bin/activate" | ||
# Upgrade pip and setuptools. | ||
${dryrun} pip install -U pip | ||
${dryrun} pip install -U setuptools | ||
# Build wheels to the wheels subdirectory. | ||
for embed_extension_metadata in 0 1; do | ||
set -x | ||
MINIMEXT_EMBED_EXTENSIONS_METADATA=${embed_extension_metadata} \ | ||
${dryrun} pip wheel . -w "${wheels_directory}" | ||
set +x | ||
done | ||
) | ||
done | ||
|
||
cp "${wheels_directory}"/*.whl "${THIS_DIRECTORY}/.." | ||
} | ||
|
||
main "$@" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
// Copyright (C) 2024 Stewart Miles | ||
// Licensed to the Python Software Foundation under a contributor agreement. | ||
// See LICENSE.txt and CONTRIBUTORS.txt. | ||
|
||
// Use the limited API to ensure ABI compatibility across all major Python | ||
// versions starting from 3.2 | ||
// https://docs.python.org/3/c-api/stable.html#limited-c-api | ||
#if !defined(Py_LIMITED_API) | ||
#define Py_LIMITED_API 3 | ||
#endif // !defined(Py_LIMITED_API) | ||
#define PY_SSIZE_T_CLEAN | ||
#include <Python.h> | ||
|
||
// Name and doc string for this module. | ||
#define MODULE_NAME calculate | ||
#define MODULE_DOCS "Calculates Fibonacci numbers." | ||
|
||
// Convert the argument into a string. | ||
#define _STRINGIFY(x) #x | ||
#define STRINGIFY(x) _STRINGIFY(x) | ||
|
||
// Calculate a Fibonacci number at the specified index of the sequence. | ||
static PyObject *fib(PyObject *self, PyObject *args) | ||
{ | ||
long int index; | ||
if (!PyArg_ParseTuple(args, "l", &index)) { | ||
PyErr_SetString(PyExc_ValueError, "An index must be specified."); | ||
} | ||
|
||
long int current_value = 1; | ||
long int previous_value = 0; | ||
index--; | ||
for ( ; index > 0 ; --index) { | ||
long int next_value = current_value + previous_value; | ||
previous_value = current_value; | ||
current_value = next_value; | ||
} | ||
return PyLong_FromLong(current_value); | ||
} | ||
|
||
// Exposes methods in this module. | ||
static PyMethodDef methods[] = | ||
{ | ||
{ | ||
"fib", | ||
fib, | ||
METH_VARARGS, | ||
PyDoc_STR("Calculate a Fibonacci number.\n" | ||
"\n" | ||
":param index: Index of the number in the Fibonacci sequence\n" | ||
" to calculate.\n" | ||
"\n" | ||
":returns: Fibonacci number at the specified index.\n" | ||
" For example an index of 7 will return 13\n"), | ||
}, | ||
}; | ||
|
||
#if PY_MAJOR_VERSION >= 3 | ||
// Defines the module. | ||
static struct PyModuleDef module = | ||
{ | ||
PyModuleDef_HEAD_INIT, | ||
STRINGIFY(MODULE_NAME), | ||
PyDoc_STR(MODULE_DOCS), | ||
-1, | ||
methods, | ||
}; | ||
#endif // PY_MAJOR_VERSION >= 3 | ||
|
||
// Expands to the init function name. | ||
#define _PYINIT_FUNCTION_NAME(prefix, name) prefix ## name | ||
#define PYINIT_FUNCTION_NAME(prefix, name) _PYINIT_FUNCTION_NAME(prefix, name) | ||
|
||
// Initialize this module. | ||
#if PY_MAJOR_VERSION >= 3 | ||
PyMODINIT_FUNC | ||
PYINIT_FUNCTION_NAME(PyInit_, MODULE_NAME)(void) | ||
{ | ||
return PyModule_Create(&module); | ||
} | ||
#else | ||
PyMODINIT_FUNC | ||
PYINIT_FUNCTION_NAME(init, MODULE_NAME)(void) | ||
{ | ||
// Ignore the returned module object. | ||
(void)Py_InitModule(STRINGIFY(MODULE_NAME), methods); | ||
} | ||
#endif // PY_MAJOR_VERSION >= 3 |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# Copyright (C) 2024 Stewart Miles | ||
# Licensed to the Python Software Foundation under a contributor agreement. | ||
# See LICENSE.txt and CONTRIBUTORS.txt. | ||
|
||
"""Python implementation of the calculate extension module.""" | ||
|
||
def fib(index): | ||
"""Calculate a Fibonacci number. | ||
:param index: Index of the number in the Fibonacci sequence | ||
to calculate. | ||
:returns: Fibonacci number at the specified index. | ||
For example an index of 7 will return 13 | ||
""" | ||
current_value = 1 | ||
previous_value = 0 | ||
index -= 1 | ||
while index > 0: | ||
next_value = current_value + previous_value | ||
previous_value = current_value | ||
current_value = next_value | ||
index -= 1 | ||
return current_value | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
# Copyright (C) 2024 Stewart Miles | ||
# Licensed to the Python Software Foundation under a contributor agreement. | ||
# See LICENSE.txt and CONTRIBUTORS.txt. | ||
import codecs | ||
import os | ||
import json | ||
from setuptools import Extension, setup | ||
from setuptools.command import egg_info | ||
import sys | ||
|
||
|
||
EMBED_EXTENSIONS_METADATA = ( | ||
int(os.getenv('MINIMEXT_EMBED_EXTENSIONS_METADATA', '0'))) | ||
|
||
|
||
class EggInfo(egg_info.egg_info): | ||
"""egg_info command that optionally writes extensions metadata. | ||
distlib.wheel.Wheel attempts to read the list of extensions from the | ||
undocumented JSON EXTENSIONS metadata file. | ||
This command will add the special file JSON EXTENSIONS metadata file to the | ||
*.dist-info directory in the wheel if the | ||
MINIMEXT_EMBED_EXTENSIONS_METADATA environment variable is set to 1. | ||
""" | ||
|
||
def run(self): | ||
egg_info.egg_info.run(self) | ||
if EMBED_EXTENSIONS_METADATA: | ||
build_ext = self.get_finalized_command('build_ext') | ||
extensions_dict = { | ||
ext_module.name: build_ext.get_ext_filename(ext_module.name) | ||
for ext_module in self.distribution.ext_modules | ||
} | ||
with open(os.path.join(self.egg_info, 'EXTENSIONS'), 'wb') as ( | ||
extensions_file): | ||
json.dump(extensions_dict, | ||
codecs.getwriter('utf-8')(extensions_file), | ||
indent=2) | ||
|
||
|
||
setup( | ||
name='minimext' + ('_metadata' if EMBED_EXTENSIONS_METADATA else ''), | ||
version='0.1', | ||
description='Calculates Fibonacci numbers.', | ||
long_description=( | ||
'Distribution that provides calculate.fib() and calculate_py.fib() ' | ||
'which calculate Fibonacci numbers. minimext.calculate is implemented ' | ||
'as a C extension to test distlib.wheel.Wheel.mount().'), | ||
packages=['minimext'], | ||
ext_modules=[ | ||
Extension(name='minimext.calculate', | ||
sources=['calculate.c'], | ||
py_limited_api=True, | ||
define_macros=[ | ||
('Py_LIMITED_API', str(sys.version_info.major)), | ||
]), | ||
], | ||
# The extension uses the limited API so tag the wheel as compatible with | ||
# Python 3.2 and later. | ||
# | ||
# Unfortunately the py_limited_api argument to Extension does not mark the | ||
# wheel as supporting the limited API, so set the see compatibility | ||
# manually. | ||
options={'bdist_wheel': {'py_limited_api': 'cp32'}}, | ||
cmdclass={'egg_info': EggInfo}, | ||
) |