Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 29 additions & 7 deletions .github/workflows/linuxbrew.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: linuxbrew
on: [push, pull_request]
on: [push]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref_name != 'master' }}
Expand All @@ -10,7 +10,7 @@ jobs:

strategy:
matrix:
python: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
python: ["3.14"]

env:
# For some unknown reason, linuxbrew tries to use "gcc-11" by default, which doesn't exist.
Expand All @@ -28,17 +28,39 @@ jobs:
- name: Install build dependencies
run: |
brew update
brew install python@${{ matrix.python }} gcc libxml2 libxmlsec1 pkg-config
brew install python@${{ matrix.python }} gcc libxml2 libxslt libxmlsec1 pkg-config
echo "/home/linuxbrew/.linuxbrew/opt/python@${{ matrix.python }}/libexec/bin" >> $GITHUB_PATH

- name: Configure Homebrew toolchain
run: |
HOMEBREW_PREFIX="$(brew --prefix)"
LIBXML2_PREFIX="$(brew --prefix libxml2)"
LIBXSLT_PREFIX="$(brew --prefix libxslt)"
XMLSEC_PREFIX="$(brew --prefix libxmlsec1)"
OPENSSL_PREFIX="$(brew --prefix openssl@3)"

{
echo "PKG_CONFIG_PATH=${LIBXML2_PREFIX}/lib/pkgconfig:${LIBXSLT_PREFIX}/lib/pkgconfig:${XMLSEC_PREFIX}/lib/pkgconfig:${OPENSSL_PREFIX}/lib/pkgconfig"
echo "CPPFLAGS=-I${LIBXML2_PREFIX}/include -I${LIBXSLT_PREFIX}/include -I${XMLSEC_PREFIX}/include/xmlsec1 -I${OPENSSL_PREFIX}/include"
echo "CFLAGS=-I${LIBXML2_PREFIX}/include -I${LIBXSLT_PREFIX}/include -I${XMLSEC_PREFIX}/include/xmlsec1 -I${OPENSSL_PREFIX}/include"
echo "LDFLAGS=-L${LIBXML2_PREFIX}/lib -L${LIBXSLT_PREFIX}/lib -L${XMLSEC_PREFIX}/lib -L${OPENSSL_PREFIX}/lib"
echo "LD_LIBRARY_PATH=${LIBXML2_PREFIX}/lib:${LIBXSLT_PREFIX}/lib:${XMLSEC_PREFIX}/lib:${OPENSSL_PREFIX}/lib"
echo "LIBRARY_PATH=${LIBXML2_PREFIX}/lib:${LIBXSLT_PREFIX}/lib:${XMLSEC_PREFIX}/lib:${OPENSSL_PREFIX}/lib"
echo "C_INCLUDE_PATH=${LIBXML2_PREFIX}/include:${LIBXSLT_PREFIX}/include:${XMLSEC_PREFIX}/include/xmlsec1:${OPENSSL_PREFIX}/include"
echo "XML2_CONFIG=${LIBXML2_PREFIX}/bin/xml2-config"
echo "XSLT_CONFIG=${LIBXSLT_PREFIX}/bin/xslt-config"
} >> $GITHUB_ENV

echo "${LIBXML2_PREFIX}/bin" >> $GITHUB_PATH
echo "${LIBXSLT_PREFIX}/bin" >> $GITHUB_PATH

- name: Build wheel
run: |
python3 -m venv build_venv
source build_venv/bin/activate
pip3 install --upgrade setuptools wheel build
export CFLAGS="-I$(brew --prefix)/include"
export LDFLAGS="-L$(brew --prefix)/lib"
python3 -m build
pip3 install --upgrade setuptools wheel build setuptools_scm pkgconfig
pip3 install --upgrade --no-binary=lxml -r requirements.txt
python3 -m build --no-isolation
rm -rf build/

- name: Run tests
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/macosx.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
name: macOS
on: [push, pull_request]
on:
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref_name != 'master' }}
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/manylinux.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
name: manylinux
on: [push, pull_request]
on:
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref_name != 'master' }}
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/sdist.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
name: sdist
on: [push, pull_request]
on:
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref_name != 'master' }}
Expand Down
12 changes: 0 additions & 12 deletions .github/workflows/wheels.yml
Original file line number Diff line number Diff line change
@@ -1,18 +1,6 @@
name: Wheel build

on:
release:
types: [created]
schedule:
# ┌───────────── minute (0 - 59)
# │ ┌───────────── hour (0 - 23)
# │ │ ┌───────────── day of the month (1 - 31)
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
# │ │ │ │ │
- cron: "42 3 * * 4"
push:
pull_request:
workflow_dispatch:

concurrency:
Expand Down
24 changes: 15 additions & 9 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#define _PYXMLSEC_FREE_XMLSEC 1
#define _PYXMLSEC_FREE_CRYPTOLIB 2
#define _PYXMLSEC_FREE_ALL 3
#define _PYXMLSEC_FREE_ALL_BUT_CRYPTOLIB 4

static int free_mode = _PYXMLSEC_FREE_NONE;

Expand All @@ -44,13 +45,20 @@ static void PyXmlSec_Free(int what) {
PYXMLSEC_DEBUGF("free resources %d", what);
switch (what) {
case _PYXMLSEC_FREE_ALL:
xmlSecCryptoShutdown();
xmlSecCryptoAppShutdown();
case _PYXMLSEC_FREE_CRYPTOLIB:
#ifndef XMLSEC_NO_CRYPTO_DYNAMIC_LOADING
xmlSecCryptoDLUnloadLibrary(PyXmlSec_GetCryptoLibName());
#endif
case _PYXMLSEC_FREE_XMLSEC:
xmlSecShutdown();
break;
case _PYXMLSEC_FREE_ALL_BUT_CRYPTOLIB:
xmlSecCryptoShutdown();
xmlSecCryptoAppShutdown();
xmlSecShutdown();
break;
}
free_mode = _PYXMLSEC_FREE_NONE;
}
Expand Down Expand Up @@ -94,7 +102,10 @@ static int PyXmlSec_Init(void) {
// We thus reinstall our callback now.
PyXmlSec_InstallErrorCallback();

free_mode = _PYXMLSEC_FREE_ALL;
// Keep the dynamically loaded crypto backend resident for the lifetime of
// the process. Python-level constants cache xmlsec transform/keydata ids,
// and unloading the backend invalidates those pointers after shutdown/init.
free_mode = _PYXMLSEC_FREE_ALL_BUT_CRYPTOLIB;
return 0;
}

Expand All @@ -113,8 +124,8 @@ static PyObject* PyXmlSec_PyInit(PyObject *self) {
static char PyXmlSec_PyShutdown__doc__[] = \
"shutdown() -> None\n"
"Shutdowns the library and cleanup any leftover resources.\n\n"
"This is called automatically upon interpreter termination and\n"
"should not need to be called explicitly.";
"This is not called automatically upon interpreter termination because\n"
"xmlsec-owned objects may still be finalized during Python shutdown.";
static PyObject* PyXmlSec_PyShutdown(PyObject* self) {
PyXmlSec_Free(free_mode);
Py_RETURN_NONE;
Expand Down Expand Up @@ -487,11 +498,6 @@ int PyXmlSec_EncModule_Init(PyObject* package);
// templates management
int PyXmlSec_TemplateModule_Init(PyObject* package);

static int PyXmlSec_PyClear(PyObject *self) {
PyXmlSec_Free(free_mode);
return 0;
}

static PyModuleDef PyXmlSecModule = {
PyModuleDef_HEAD_INIT,
STRINGIFY(MODULE_NAME), /* name of module */
Expand All @@ -501,7 +507,7 @@ static PyModuleDef PyXmlSecModule = {
PyXmlSec_MainMethods, /* m_methods */
NULL, /* m_slots */
NULL, /* m_traverse */
PyXmlSec_PyClear, /* m_clear */
NULL, /* m_clear */
NULL, /* m_free */
};

Expand Down
35 changes: 35 additions & 0 deletions tests/test_xmlsec.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import subprocess
import sys
import unittest
from pathlib import Path

import xmlsec
from tests import base

Expand All @@ -11,3 +16,33 @@ def test_reinitialize_module(self):
"""
xmlsec.shutdown()
xmlsec.init()


class TestInterpreterShutdown(unittest.TestCase):
def test_interpreter_exit_with_live_xmlsec_objects(self):
key_path = Path(__file__).with_name('data') / 'rsakey.pem'
script = f"""
import xmlsec

key = xmlsec.Key.from_file({str(key_path)!r}, format=xmlsec.constants.KeyDataFormatPem)
ctx = xmlsec.SignatureContext()
ctx.key = key
"""
proc = subprocess.run([sys.executable, '-c', script], capture_output=True, text=True)
self.assertEqual(proc.returncode, 0, proc.stderr)

def test_reinitialize_module_preserves_constants(self):
script = """
import xmlsec

transform = xmlsec.constants.TransformExclC14N
keydata = xmlsec.constants.KeyDataAes

xmlsec.shutdown()
xmlsec.init()

assert transform.name == "exc-c14n"
assert keydata.name == "aes"
"""
proc = subprocess.run([sys.executable, '-c', script], capture_output=True, text=True)
self.assertEqual(proc.returncode, 0, proc.stderr)
Loading