Skip to content

Commit

Permalink
Test cases: Unwanted RPATH overwrite and Failing to patch internal no…
Browse files Browse the repository at this point in the history
…n-py extensions (#134)

* Adding minimal test wheel based on PR #134

* Separate tests for each change in PR #134

#134

* non-py extension does not show in auditwheel show (ignoring assert)

* Confirming tests on macOS

* pytest in travis is weirdly triggering a manual test

* Sorry! Forgot to install zlib-devel
  • Loading branch information
thomaslima authored and auvipy committed Mar 18, 2021
1 parent 5945b3b commit 6079b82
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 2 deletions.
9 changes: 9 additions & 0 deletions tests/integration/nonpy_rpath/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Python 3 extension with non-Python library dependency

This example was inspired from https://gist.github.com/physacco/2e1b52415f3a964ad2a542a99bebed8f

This test extension builds two libraries: `_nonpy_rpath.*.so` and `lib_cryptexample.*.so`, where the `*` is a string composed of Python ABI versions and platform tags.

The extension `lib_cryptexample.*.so` should be repaired by auditwheel because it is a needed library, even though it is not a Python extension.

[Issue #136](https://github.com/pypa/auditwheel/issues/136) documents the underlying problem that this test case is designed to solve.
6 changes: 6 additions & 0 deletions tests/integration/nonpy_rpath/extensions/testcrypt.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#include "testcrypt.h"
#include <crypt.h>

std::string crypt_something() {
return std::string(crypt("will error out", NULL));
}
4 changes: 4 additions & 0 deletions tests/integration/nonpy_rpath/extensions/testcrypt.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#pragma once
#include <string>

std::string crypt_something(void);
25 changes: 25 additions & 0 deletions tests/integration/nonpy_rpath/nonpy_rpath.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include "extensions/testcrypt.h"

// Module method definitions
static PyObject* crypt_something(PyObject *self, PyObject *args) {
return PyUnicode_FromString(crypt_something().c_str());
}

/* Module initialization */
PyMODINIT_FUNC PyInit__nonpy_rpath(void)
{
static PyMethodDef module_methods[] = {
{"crypt_something", (PyCFunction)crypt_something, METH_NOARGS, "crypt_something."},
{NULL} /* Sentinel */
};
static struct PyModuleDef moduledef = {
PyModuleDef_HEAD_INIT,
"_nonpy_rpath",
"_nonpy_rpath module",
-1,
module_methods,
};
return PyModule_Create(&moduledef);
}
3 changes: 3 additions & 0 deletions tests/integration/nonpy_rpath/nonpy_rpath/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from ._nonpy_rpath import crypt_something

__all__ = ["crypt_something"]
98 changes: 98 additions & 0 deletions tests/integration/nonpy_rpath/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import setuptools.command.build_ext
from setuptools import setup, find_packages, Distribution
from setuptools.extension import Extension, Library
import os

# despite its name, setuptools.command.build_ext.link_shared_object won't
# link a shared object on Linux, but a static library and patches distutils
# for this ... We're patching this back now.


def always_link_shared_object(
self,
objects,
output_libname,
output_dir=None,
libraries=None,
library_dirs=None,
runtime_library_dirs=None,
export_symbols=None,
debug=0,
extra_preargs=None,
extra_postargs=None,
build_temp=None,
target_lang=None,
):
self.link(
self.SHARED_LIBRARY,
objects,
output_libname,
output_dir,
libraries,
library_dirs,
runtime_library_dirs,
export_symbols,
debug,
extra_preargs,
extra_postargs,
build_temp,
target_lang,
)


setuptools.command.build_ext.libtype = "shared"
setuptools.command.build_ext.link_shared_object = always_link_shared_object

libtype = setuptools.command.build_ext.libtype
build_ext_cmd = Distribution().get_command_obj("build_ext")
build_ext_cmd.initialize_options()
build_ext_cmd.setup_shlib_compiler()


def libname(name):
""" gets 'name' and returns something like libname.cpython-37m-darwin.so"""
filename = build_ext_cmd.get_ext_filename(name)
fn, ext = os.path.splitext(filename)
return build_ext_cmd.shlib_compiler.library_filename(fn, libtype)


pkg_name = "nonpy_rpath"
crypt_name = "_cryptexample"
crypt_soname = libname(crypt_name)

build_cmd = Distribution().get_command_obj("build")
build_cmd.finalize_options()
build_platlib = build_cmd.build_platlib


def link_args(soname=None):
args = []
if soname:
args += ["-Wl,-soname," + soname]
loader_path = "$ORIGIN"
args += ["-Wl,-rpath," + loader_path]
return args


nonpy_rpath_module = Extension(
pkg_name + "._nonpy_rpath",
language="c++",
sources=["nonpy_rpath.cpp"],
extra_link_args=link_args(),
extra_objects=[build_platlib + "/nonpy_rpath/" + crypt_soname],
)
crypt_example = Library(
pkg_name + "." + crypt_name,
language="c++",
extra_compile_args=["-lcrypt"],
extra_link_args=link_args(crypt_soname) + ["-lcrypt"],
sources=["extensions/testcrypt.cpp"],
)

setup(
name="nonpy_rpath",
version="0.1.0",
packages=find_packages(),
description="Test package for nonpy_rpath",
ext_modules=[crypt_example, nonpy_rpath_module],
)
36 changes: 34 additions & 2 deletions tests/integration/test_manylinux.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,10 +219,10 @@ def assert_show_output(manylinux_ctr, wheel, expected_tag, strict):
assert match['tag'] == expected_tag
else:
expected_match = TAG_RE.match(expected_tag)
assert expected_match
assert expected_match, f"No match for tag {expected_tag}"
expected_glibc = (int(expected_match['major']), int(expected_match['minor']))
actual_match = TAG_RE.match(match['tag'])
assert actual_match
assert actual_match, f"No match for tag {match['tag']}"
actual_glibc = (int(actual_match['major']), int(actual_match['minor']))
assert expected_match['arch'] == actual_match['arch']
assert actual_glibc <= expected_glibc
Expand Down Expand Up @@ -705,3 +705,35 @@ def test_build_wheel_compat(target_policy, only_plat, any_manylinux_container,
['python', '-c',
'from sys import exit; from testsimple import run; exit(run())']
)


def test_nonpy_rpath(any_manylinux_container, docker_python, io_folder):
# Tests https://github.com/pypa/auditwheel/issues/136
policy, tag, manylinux_ctr = any_manylinux_container
docker_exec(
manylinux_ctr,
['bash', '-c', 'cd /auditwheel_src/tests/integration/nonpy_rpath '
'&& python -m pip wheel --no-deps -w /io .']
)

orig_wheel, *_ = os.listdir(io_folder)
assert orig_wheel.startswith("nonpy_rpath-0.1.0")
assert 'manylinux' not in orig_wheel

# Repair the wheel using the appropriate manylinux container
repair_command = \
f'auditwheel repair --plat {policy} --only-plat -w /io /io/{orig_wheel}'
docker_exec(manylinux_ctr, repair_command)
filenames = os.listdir(io_folder)
assert len(filenames) == 2
repaired_wheel = f'nonpy_rpath-0.1.0-{PYTHON_ABI}-{tag}.whl'
assert repaired_wheel in filenames
assert_show_output(manylinux_ctr, repaired_wheel, policy, False)

# Test the resulting wheel outside the manylinux container
docker_exec(docker_python, "pip install /io/" + repaired_wheel)
docker_exec(
docker_python,
["python", "-c",
"import nonpy_rpath; assert nonpy_rpath.crypt_something().startswith('*')"]
)

0 comments on commit 6079b82

Please sign in to comment.