Skip to content

Commit ef591cf

Browse files
authored
gh-91321: Fix compatibility with C++ older than C++11 (#93784) (#93802)
* Fix the compatibility of the Python C API with C++ older than C++11. * _Py_NULL is only defined as nullptr on C++11 and newer. (cherry picked from commit 4caf5c2) * test_cppext now builds the C++ extension with setuptools. * Add @test.support.requires_venv_with_pip. (cherry picked from commit ca0cc9c)
1 parent 871b1dc commit ef591cf

File tree

7 files changed

+156
-88
lines changed

7 files changed

+156
-88
lines changed

Include/pyport.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,12 @@ extern "C++" {
3636
inline type _Py_CAST_impl(int ptr) {
3737
return reinterpret_cast<type>(ptr);
3838
}
39+
#if __cplusplus >= 201103
3940
template <typename type>
4041
inline type _Py_CAST_impl(std::nullptr_t) {
4142
return static_cast<type>(nullptr);
4243
}
44+
#endif
4345

4446
template <typename type, typename expr_type>
4547
inline type _Py_CAST_impl(expr_type *expr) {
@@ -70,8 +72,9 @@ extern "C++" {
7072
#endif
7173

7274
// Static inline functions should use _Py_NULL rather than using directly NULL
73-
// to prevent C++ compiler warnings. In C++, _Py_NULL uses nullptr.
74-
#ifdef __cplusplus
75+
// to prevent C++ compiler warnings. On C++11 and newer, _Py_NULL is defined as
76+
// nullptr.
77+
#if defined(__cplusplus) && __cplusplus >= 201103
7578
# define _Py_NULL nullptr
7679
#else
7780
# define _Py_NULL NULL

Lib/test/_testcppext.cpp

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66

77
#include "Python.h"
88

9+
#if __cplusplus >= 201103
10+
# define NAME _testcpp11ext
11+
#else
12+
# define NAME _testcpp03ext
13+
#endif
14+
915
PyDoc_STRVAR(_testcppext_add_doc,
1016
"add(x, y)\n"
1117
"\n"
@@ -16,7 +22,7 @@ _testcppext_add(PyObject *Py_UNUSED(module), PyObject *args)
1622
{
1723
long i, j;
1824
if (!PyArg_ParseTuple(args, "ll:foo", &i, &j)) {
19-
return nullptr;
25+
return _Py_NULL;
2026
}
2127
long res = i + j;
2228
return PyLong_FromLong(res);
@@ -47,8 +53,8 @@ static PyObject *
4753
test_api_casts(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
4854
{
4955
PyObject *obj = Py_BuildValue("(ii)", 1, 2);
50-
if (obj == nullptr) {
51-
return nullptr;
56+
if (obj == _Py_NULL) {
57+
return _Py_NULL;
5258
}
5359
Py_ssize_t refcnt = Py_REFCNT(obj);
5460
assert(refcnt >= 1);
@@ -77,9 +83,11 @@ test_api_casts(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
7783
// gh-93442: Pass 0 as NULL for PyObject*
7884
Py_XINCREF(0);
7985
Py_XDECREF(0);
80-
// ensure that nullptr works too
86+
#if _cplusplus >= 201103
87+
// Test nullptr passed as PyObject*
8188
Py_XINCREF(nullptr);
8289
Py_XDECREF(nullptr);
90+
#endif
8391

8492
Py_DECREF(obj);
8593
Py_RETURN_NONE;
@@ -90,16 +98,16 @@ static PyObject *
9098
test_unicode(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
9199
{
92100
PyObject *str = PyUnicode_FromString("abc");
93-
if (str == nullptr) {
94-
return nullptr;
101+
if (str == _Py_NULL) {
102+
return _Py_NULL;
95103
}
96104

97105
assert(PyUnicode_Check(str));
98106
assert(PyUnicode_GET_LENGTH(str) == 3);
99107

100108
// gh-92800: test PyUnicode_READ()
101109
const void* data = PyUnicode_DATA(str);
102-
assert(data != nullptr);
110+
assert(data != _Py_NULL);
103111
int kind = PyUnicode_KIND(str);
104112
assert(kind == PyUnicode_1BYTE_KIND);
105113
assert(PyUnicode_READ(kind, data, 0) == 'a');
@@ -118,9 +126,9 @@ test_unicode(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
118126

119127
static PyMethodDef _testcppext_methods[] = {
120128
{"add", _testcppext_add, METH_VARARGS, _testcppext_add_doc},
121-
{"test_api_casts", test_api_casts, METH_NOARGS, nullptr},
122-
{"test_unicode", test_unicode, METH_NOARGS, nullptr},
123-
{nullptr, nullptr, 0, nullptr} /* sentinel */
129+
{"test_api_casts", test_api_casts, METH_NOARGS, _Py_NULL},
130+
{"test_unicode", test_unicode, METH_NOARGS, _Py_NULL},
131+
{_Py_NULL, _Py_NULL, 0, _Py_NULL} /* sentinel */
124132
};
125133

126134

@@ -135,26 +143,32 @@ _testcppext_exec(PyObject *module)
135143

136144
static PyModuleDef_Slot _testcppext_slots[] = {
137145
{Py_mod_exec, reinterpret_cast<void*>(_testcppext_exec)},
138-
{0, nullptr}
146+
{0, _Py_NULL}
139147
};
140148

141149

142150
PyDoc_STRVAR(_testcppext_doc, "C++ test extension.");
143151

152+
#define _STR(NAME) #NAME
153+
#define STR(NAME) _STR(NAME)
154+
144155
static struct PyModuleDef _testcppext_module = {
145156
PyModuleDef_HEAD_INIT, // m_base
146-
"_testcppext", // m_name
157+
STR(NAME), // m_name
147158
_testcppext_doc, // m_doc
148159
0, // m_size
149160
_testcppext_methods, // m_methods
150161
_testcppext_slots, // m_slots
151-
nullptr, // m_traverse
152-
nullptr, // m_clear
153-
nullptr, // m_free
162+
_Py_NULL, // m_traverse
163+
_Py_NULL, // m_clear
164+
_Py_NULL, // m_free
154165
};
155166

167+
#define _FUNC_NAME(NAME) PyInit_ ## NAME
168+
#define FUNC_NAME(NAME) _FUNC_NAME(NAME)
169+
156170
PyMODINIT_FUNC
157-
PyInit__testcppext(void)
171+
FUNC_NAME(NAME)(void)
158172
{
159173
return PyModuleDef_Init(&_testcppext_module);
160174
}

Lib/test/setup_testcppext.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# gh-91321: Build a basic C++ test extension to check that the Python C API is
2+
# compatible with C++ and does not emit C++ compiler warnings.
3+
import sys
4+
from test import support
5+
6+
from setuptools import setup, Extension
7+
8+
9+
MS_WINDOWS = (sys.platform == 'win32')
10+
11+
12+
SOURCE = support.findfile('_testcppext.cpp')
13+
if not MS_WINDOWS:
14+
# C++ compiler flags for GCC and clang
15+
CPPFLAGS = [
16+
# gh-91321: The purpose of _testcppext extension is to check that building
17+
# a C++ extension using the Python C API does not emit C++ compiler
18+
# warnings
19+
'-Werror',
20+
# Warn on old-style cast (C cast) like: (PyObject*)op
21+
'-Wold-style-cast',
22+
# Warn when using NULL rather than _Py_NULL in static inline functions
23+
'-Wzero-as-null-pointer-constant',
24+
]
25+
else:
26+
# Don't pass any compiler flag to MSVC
27+
CPPFLAGS = []
28+
29+
30+
def main():
31+
cppflags = list(CPPFLAGS)
32+
if '-std=c++03' in sys.argv:
33+
sys.argv.remove('-std=c++03')
34+
std = 'c++03'
35+
name = '_testcpp03ext'
36+
else:
37+
# Python currently targets C++11
38+
std = 'c++11'
39+
name = '_testcpp11ext'
40+
41+
cppflags = [*CPPFLAGS, f'-std={std}']
42+
cpp_ext = Extension(
43+
name,
44+
sources=[SOURCE],
45+
language='c++',
46+
extra_compile_args=cppflags)
47+
setup(name=name, ext_modules=[cpp_ext])
48+
49+
50+
if __name__ == "__main__":
51+
main()

Lib/test/support/__init__.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2196,3 +2196,20 @@ def clear_ignored_deprecations(*tokens: object) -> None:
21962196
if warnings.filters != new_filters:
21972197
warnings.filters[:] = new_filters
21982198
warnings._filters_mutated()
2199+
2200+
2201+
# Skip a test if venv with pip is known to not work.
2202+
def requires_venv_with_pip():
2203+
# ensurepip requires zlib to open ZIP archives (.whl binary wheel packages)
2204+
try:
2205+
import zlib
2206+
except ImportError:
2207+
return unittest.skipIf(True, "venv: ensurepip requires zlib")
2208+
2209+
# bpo-26610: pip/pep425tags.py requires ctypes.
2210+
# gh-92820: setuptools/windows_support.py uses ctypes (setuptools 58.1).
2211+
try:
2212+
import ctypes
2213+
except ImportError:
2214+
ctypes = None
2215+
return unittest.skipUnless(ctypes, 'venv: pip requires ctypes')

Lib/test/test_cppext.py

Lines changed: 48 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,73 @@
11
# gh-91321: Build a basic C++ test extension to check that the Python C API is
22
# compatible with C++ and does not emit C++ compiler warnings.
3-
import contextlib
4-
import os
3+
import os.path
54
import sys
65
import unittest
7-
import warnings
6+
import subprocess
87
from test import support
98
from test.support import os_helper
109

11-
with warnings.catch_warnings():
12-
warnings.simplefilter('ignore', DeprecationWarning)
13-
from distutils.core import setup, Extension
14-
import distutils.sysconfig
15-
1610

1711
MS_WINDOWS = (sys.platform == 'win32')
1812

1913

20-
SOURCE = support.findfile('_testcppext.cpp')
21-
if not MS_WINDOWS:
22-
# C++ compiler flags for GCC and clang
23-
CPPFLAGS = [
24-
# Python currently targets C++11
25-
'-std=c++11',
26-
# gh-91321: The purpose of _testcppext extension is to check that building
27-
# a C++ extension using the Python C API does not emit C++ compiler
28-
# warnings
29-
'-Werror',
30-
# Warn on old-style cast (C cast) like: (PyObject*)op
31-
'-Wold-style-cast',
32-
# Warn when using NULL rather than _Py_NULL in static inline functions
33-
'-Wzero-as-null-pointer-constant',
34-
]
35-
else:
36-
# Don't pass any compiler flag to MSVC
37-
CPPFLAGS = []
14+
SETUP_TESTCPPEXT = support.findfile('setup_testcppext.py')
3815

3916

4017
@support.requires_subprocess()
4118
class TestCPPExt(unittest.TestCase):
42-
def build(self):
43-
cpp_ext = Extension(
44-
'_testcppext',
45-
sources=[SOURCE],
46-
language='c++',
47-
extra_compile_args=CPPFLAGS)
48-
capture_stdout = (not support.verbose)
19+
def test_build_cpp11(self):
20+
self.check_build(False)
4921

50-
try:
51-
try:
52-
if capture_stdout:
53-
stdout = support.captured_stdout()
54-
else:
55-
print()
56-
stdout = contextlib.nullcontext()
57-
with (stdout,
58-
support.swap_attr(sys, 'argv', ['setup.py', 'build_ext', '--verbose'])):
59-
setup(name="_testcppext", ext_modules=[cpp_ext])
60-
return
61-
except:
62-
if capture_stdout:
63-
# Show output on error
64-
print()
65-
print(stdout.getvalue())
66-
raise
67-
except SystemExit:
68-
self.fail("Build failed")
22+
def test_build_cpp03(self):
23+
self.check_build(True)
6924

7025
# With MSVC, the linker fails with: cannot open file 'python311.lib'
7126
# https://github.com/python/cpython/pull/32175#issuecomment-1111175897
7227
@unittest.skipIf(MS_WINDOWS, 'test fails on Windows')
73-
def test_build(self):
74-
# save/restore os.environ
75-
def restore_env(old_env):
76-
os.environ.clear()
77-
os.environ.update(old_env)
78-
self.addCleanup(restore_env, dict(os.environ))
79-
80-
def restore_sysconfig_vars(old_config_vars):
81-
distutils.sysconfig._config_vars.clear()
82-
distutils.sysconfig._config_vars.update(old_config_vars)
83-
self.addCleanup(restore_sysconfig_vars,
84-
dict(distutils.sysconfig._config_vars))
85-
28+
# the test uses venv+pip: skip if it's not available
29+
@support.requires_venv_with_pip()
30+
def check_build(self, std_cpp03):
8631
# Build in a temporary directory
8732
with os_helper.temp_cwd():
88-
self.build()
33+
self._check_build(std_cpp03)
34+
35+
def _check_build(self, std_cpp03):
36+
venv_dir = 'env'
37+
verbose = support.verbose
38+
39+
# Create virtual environment to get setuptools
40+
cmd = [sys.executable, '-X', 'dev', '-m', 'venv', venv_dir]
41+
if verbose:
42+
print()
43+
print('Run:', ' '.join(cmd))
44+
subprocess.run(cmd, check=True)
45+
46+
# Get the Python executable of the venv
47+
python_exe = 'python'
48+
if sys.executable.endswith('.exe'):
49+
python_exe += '.exe'
50+
if MS_WINDOWS:
51+
python = os.path.join(venv_dir, 'Scripts', python_exe)
52+
else:
53+
python = os.path.join(venv_dir, 'bin', python_exe)
54+
55+
# Build the C++ extension
56+
cmd = [python, '-X', 'dev',
57+
SETUP_TESTCPPEXT, 'build_ext', '--verbose']
58+
if std_cpp03:
59+
cmd.append('-std=c++03')
60+
if verbose:
61+
print('Run:', ' '.join(cmd))
62+
subprocess.run(cmd, check=True)
63+
else:
64+
proc = subprocess.run(cmd,
65+
stdout=subprocess.PIPE,
66+
stderr=subprocess.STDOUT,
67+
text=True)
68+
if proc.returncode:
69+
print(proc.stdout, end='')
70+
self.fail(f"Build failed with exit code {proc.returncode}")
8971

9072

9173
if __name__ == "__main__":

Lib/test/test_venv.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
import tempfile
1818
from test.support import (captured_stdout, captured_stderr, requires_zlib,
1919
skip_if_broken_multiprocessing_synchronize, verbose,
20-
requires_subprocess, is_emscripten, is_wasi)
20+
requires_subprocess, is_emscripten, is_wasi,
21+
requires_venv_with_pip)
2122
from test.support.os_helper import (can_symlink, EnvironmentVarGuard, rmtree)
2223
import unittest
2324
import venv
@@ -619,9 +620,7 @@ def do_test_with_pip(self, system_site_packages):
619620
if not system_site_packages:
620621
self.assert_pip_not_installed()
621622

622-
# Issue #26610: pip/pep425tags.py requires ctypes
623-
@unittest.skipUnless(ctypes, 'pip requires ctypes')
624-
@requires_zlib()
623+
@requires_venv_with_pip()
625624
def test_with_pip(self):
626625
self.do_test_with_pip(False)
627626
self.do_test_with_pip(True)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix the compatibility of the Python C API with C++ older than C++11. Patch by
2+
Victor Stinner.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy