[3.12] gh-127906: Backport test_cppext changes from the main branch (#127915)

This commit is contained in:
Victor Stinner 2024-12-13 13:51:06 +01:00 committed by GitHub
parent c77bfd768f
commit 6544f99463
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 99 additions and 42 deletions

View file

@ -1,45 +1,52 @@
# gh-91321: Build a basic C++ test extension to check that the Python C API is # gh-91321: Build a basic C++ test extension to check that the Python C API is
# compatible with C++ and does not emit C++ compiler warnings. # compatible with C++ and does not emit C++ compiler warnings.
import os.path import os.path
import shlex
import shutil import shutil
import sys
import unittest
import subprocess import subprocess
import sysconfig import unittest
from test import support from test import support
from test.support import os_helper
SOURCE = os.path.join(os.path.dirname(__file__), 'extension.cpp') SOURCE = os.path.join(os.path.dirname(__file__), 'extension.cpp')
SETUP = os.path.join(os.path.dirname(__file__), 'setup.py') SETUP = os.path.join(os.path.dirname(__file__), 'setup.py')
# With MSVC on a debug build, the linker fails with: cannot open file
# 'python311.lib', it should look 'python311_d.lib'.
@unittest.skipIf(support.MS_WINDOWS and support.Py_DEBUG,
'test fails on Windows debug build')
# Building and running an extension in clang sanitizing mode is not
# straightforward
@support.skip_if_sanitizer('test does not work with analyzing builds',
address=True, memory=True, ub=True, thread=True)
# the test uses venv+pip: skip if it's not available
@support.requires_venv_with_pip()
@support.requires_subprocess() @support.requires_subprocess()
@support.requires_resource('cpu')
class TestCPPExt(unittest.TestCase): class TestCPPExt(unittest.TestCase):
@support.requires_resource('cpu') def test_build(self):
def test_build_cpp11(self): self.check_build('_testcppext')
self.check_build(False, '_testcpp11ext')
@support.requires_resource('cpu')
def test_build_cpp03(self): def test_build_cpp03(self):
self.check_build(True, '_testcpp03ext') self.check_build('_testcpp03ext', std='c++03')
# With MSVC, the linker fails with: cannot open file 'python311.lib' @unittest.skipIf(support.MS_WINDOWS, "MSVC doesn't support /std:c++11")
# https://github.com/python/cpython/pull/32175#issuecomment-1111175897 def test_build_cpp11(self):
@unittest.skipIf(support.MS_WINDOWS, 'test fails on Windows') self.check_build('_testcpp11ext', std='c++11')
# Building and running an extension in clang sanitizing mode is not
# straightforward # Only test C++14 on MSVC.
@unittest.skipIf( # On s390x RHEL7, GCC 4.8.5 doesn't support C++14.
'-fsanitize' in (sysconfig.get_config_var('PY_CFLAGS') or ''), @unittest.skipIf(not support.MS_WINDOWS, "need Windows")
'test does not work with analyzing builds') def test_build_cpp14(self):
# the test uses venv+pip: skip if it's not available self.check_build('_testcpp14ext', std='c++14')
@support.requires_venv_with_pip()
def check_build(self, std_cpp03, extension_name): def check_build(self, extension_name, std=None):
venv_dir = 'env' venv_dir = 'env'
with support.setup_venv_with_pip_setuptools_wheel(venv_dir) as python_exe: with support.setup_venv_with_pip_setuptools_wheel(venv_dir) as python_exe:
self._check_build(std_cpp03, extension_name, python_exe) self._check_build(extension_name, python_exe, std=std)
def _check_build(self, std_cpp03, extension_name, python_exe): def _check_build(self, extension_name, python_exe, std):
pkg_dir = 'pkg' pkg_dir = 'pkg'
os.mkdir(pkg_dir) os.mkdir(pkg_dir)
shutil.copy(SETUP, os.path.join(pkg_dir, os.path.basename(SETUP))) shutil.copy(SETUP, os.path.join(pkg_dir, os.path.basename(SETUP)))
@ -47,10 +54,11 @@ class TestCPPExt(unittest.TestCase):
def run_cmd(operation, cmd): def run_cmd(operation, cmd):
env = os.environ.copy() env = os.environ.copy()
env['CPYTHON_TEST_CPP_STD'] = 'c++03' if std_cpp03 else 'c++11' if std:
env['CPYTHON_TEST_CPP_STD'] = std
env['CPYTHON_TEST_EXT_NAME'] = extension_name env['CPYTHON_TEST_EXT_NAME'] = extension_name
if support.verbose: if support.verbose:
print('Run:', ' '.join(cmd)) print('Run:', ' '.join(map(shlex.quote, cmd)))
subprocess.run(cmd, check=True, env=env) subprocess.run(cmd, check=True, env=env)
else: else:
proc = subprocess.run(cmd, proc = subprocess.run(cmd,
@ -59,6 +67,7 @@ class TestCPPExt(unittest.TestCase):
stderr=subprocess.STDOUT, stderr=subprocess.STDOUT,
text=True) text=True)
if proc.returncode: if proc.returncode:
print('Run:', ' '.join(map(shlex.quote, cmd)))
print(proc.stdout, end='') print(proc.stdout, end='')
self.fail( self.fail(
f"{operation} failed with exit code {proc.returncode}") f"{operation} failed with exit code {proc.returncode}")
@ -67,6 +76,8 @@ class TestCPPExt(unittest.TestCase):
cmd = [python_exe, '-X', 'dev', cmd = [python_exe, '-X', 'dev',
'-m', 'pip', 'install', '--no-build-isolation', '-m', 'pip', 'install', '--no-build-isolation',
os.path.abspath(pkg_dir)] os.path.abspath(pkg_dir)]
if support.verbose:
cmd.append('-v')
run_cmd('Install', cmd) run_cmd('Install', cmd)
# Do a reference run. Until we test that running python # Do a reference run. Until we test that running python

View file

@ -8,10 +8,8 @@
#include "Python.h" #include "Python.h"
#if __cplusplus >= 201103 #ifndef MODULE_NAME
# define NAME _testcpp11ext # error "MODULE_NAME macro must be defined"
#else
# define NAME _testcpp03ext
#endif #endif
#define _STR(NAME) #NAME #define _STR(NAME) #NAME
@ -160,7 +158,7 @@ PyType_Slot VirtualPyObject_Slots[] = {
}; };
PyType_Spec VirtualPyObject_Spec = { PyType_Spec VirtualPyObject_Spec = {
/* .name */ STR(NAME) ".VirtualPyObject", /* .name */ STR(MODULE_NAME) ".VirtualPyObject",
/* .basicsize */ sizeof(VirtualPyObject), /* .basicsize */ sizeof(VirtualPyObject),
/* .itemsize */ 0, /* .itemsize */ 0,
/* .flags */ Py_TPFLAGS_DEFAULT, /* .flags */ Py_TPFLAGS_DEFAULT,
@ -227,6 +225,10 @@ _testcppext_exec(PyObject *module)
if (!result) return -1; if (!result) return -1;
Py_DECREF(result); Py_DECREF(result);
// test Py_BUILD_ASSERT() and Py_BUILD_ASSERT_EXPR()
Py_BUILD_ASSERT(sizeof(int) == sizeof(unsigned int));
assert(Py_BUILD_ASSERT_EXPR(sizeof(int) == sizeof(unsigned int)) == 0);
return 0; return 0;
} }
@ -240,7 +242,7 @@ PyDoc_STRVAR(_testcppext_doc, "C++ test extension.");
static struct PyModuleDef _testcppext_module = { static struct PyModuleDef _testcppext_module = {
PyModuleDef_HEAD_INIT, // m_base PyModuleDef_HEAD_INIT, // m_base
STR(NAME), // m_name STR(MODULE_NAME), // m_name
_testcppext_doc, // m_doc _testcppext_doc, // m_doc
0, // m_size 0, // m_size
_testcppext_methods, // m_methods _testcppext_methods, // m_methods
@ -254,7 +256,7 @@ static struct PyModuleDef _testcppext_module = {
#define FUNC_NAME(NAME) _FUNC_NAME(NAME) #define FUNC_NAME(NAME) _FUNC_NAME(NAME)
PyMODINIT_FUNC PyMODINIT_FUNC
FUNC_NAME(NAME)(void) FUNC_NAME(MODULE_NAME)(void)
{ {
return PyModuleDef_Init(&_testcppext_module); return PyModuleDef_Init(&_testcppext_module);
} }

View file

@ -1,8 +1,8 @@
# gh-91321: Build a basic C++ test extension to check that the Python C API is # gh-91321: Build a basic C++ test extension to check that the Python C API is
# compatible with C++ and does not emit C++ compiler warnings. # compatible with C++ and does not emit C++ compiler warnings.
import os import os
import platform
import shlex import shlex
import sys
import sysconfig import sysconfig
from test import support from test import support
@ -10,6 +10,7 @@ from setuptools import setup, Extension
SOURCE = 'extension.cpp' SOURCE = 'extension.cpp'
if not support.MS_WINDOWS: if not support.MS_WINDOWS:
# C++ compiler flags for GCC and clang # C++ compiler flags for GCC and clang
CPPFLAGS = [ CPPFLAGS = [
@ -19,34 +20,77 @@ if not support.MS_WINDOWS:
'-Werror', '-Werror',
] ]
else: else:
# Don't pass any compiler flag to MSVC # MSVC compiler flags
CPPFLAGS = [] CPPFLAGS = [
# Display warnings level 1 to 4
'/W4',
# Treat all compiler warnings as compiler errors
'/WX',
]
def main(): def main():
cppflags = list(CPPFLAGS) cppflags = list(CPPFLAGS)
std = os.environ["CPYTHON_TEST_CPP_STD"] std = os.environ.get("CPYTHON_TEST_CPP_STD", "")
name = os.environ["CPYTHON_TEST_EXT_NAME"] module_name = os.environ["CPYTHON_TEST_EXT_NAME"]
cppflags = [*CPPFLAGS, f'-std={std}'] cppflags = list(CPPFLAGS)
cppflags.append(f'-DMODULE_NAME={module_name}')
# Add -std=STD or /std:STD (MSVC) compiler flag
if std:
if support.MS_WINDOWS:
cppflags.append(f'/std:{std}')
else:
cppflags.append(f'-std={std}')
# gh-105776: When "gcc -std=11" is used as the C++ compiler, -std=c11 # gh-105776: When "gcc -std=11" is used as the C++ compiler, -std=c11
# option emits a C++ compiler warning. Remove "-std11" option from the # option emits a C++ compiler warning. Remove "-std11" option from the
# CC command. # CC command.
cmd = (sysconfig.get_config_var('CC') or '') cmd = (sysconfig.get_config_var('CC') or '')
if cmd is not None: if cmd is not None:
if support.MS_WINDOWS:
std_prefix = '/std'
else:
std_prefix = '-std'
cmd = shlex.split(cmd) cmd = shlex.split(cmd)
cmd = [arg for arg in cmd if not arg.startswith('-std=')] cmd = [arg for arg in cmd if not arg.startswith(std_prefix)]
cmd = shlex.join(cmd) cmd = shlex.join(cmd)
# CC env var overrides sysconfig CC variable in setuptools # CC env var overrides sysconfig CC variable in setuptools
os.environ['CC'] = cmd os.environ['CC'] = cmd
cpp_ext = Extension( # On Windows, add PCbuild\amd64\ to include and library directories
name, include_dirs = []
library_dirs = []
if support.MS_WINDOWS:
srcdir = sysconfig.get_config_var('srcdir')
machine = platform.uname().machine
pcbuild = os.path.join(srcdir, 'PCbuild', machine)
if os.path.exists(pcbuild):
# pyconfig.h is generated in PCbuild\amd64\
include_dirs.append(pcbuild)
# python313.lib is generated in PCbuild\amd64\
library_dirs.append(pcbuild)
print(f"Add PCbuild directory: {pcbuild}")
# Display information to help debugging
for env_name in ('CC', 'CFLAGS', 'CPPFLAGS'):
if env_name in os.environ:
print(f"{env_name} env var: {os.environ[env_name]!r}")
else:
print(f"{env_name} env var: <missing>")
print(f"extra_compile_args: {cppflags!r}")
ext = Extension(
module_name,
sources=[SOURCE], sources=[SOURCE],
language='c++', language='c++',
extra_compile_args=cppflags) extra_compile_args=cppflags,
setup(name='internal' + name, version='0.0', ext_modules=[cpp_ext]) include_dirs=include_dirs,
library_dirs=library_dirs)
setup(name=f'internal_{module_name}',
version='0.0',
ext_modules=[ext])
if __name__ == "__main__": if __name__ == "__main__":