Skip to content

Commit 4af58da

Browse files
vstinnerdiegorusso
authored andcommitted
pythongh-92906: Enable test_cext and test_cppext on Windows (python#117000)
On Windows in release mode, the test_cext and test_cppext can now build C and C++ extensions. * test_cext now also builds the C extension without options. * test_cppext now also builds the C++ extension without options. * Add C++14 test to test_cppext; C++11 is not supported by MSVC. * Make setup_venv_with_pip_setuptools_wheel() quiet when support.verbose is false. Only show stdout and stderr on failure.
1 parent 860d719 commit 4af58da

File tree

7 files changed

+148
-63
lines changed

7 files changed

+148
-63
lines changed

Lib/test/support/__init__.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2251,16 +2251,25 @@ def _findwheel(pkgname):
22512251
# and returns the path to the venv directory and the path to the python executable
22522252
@contextlib.contextmanager
22532253
def setup_venv_with_pip_setuptools_wheel(venv_dir):
2254+
import shlex
22542255
import subprocess
22552256
from .os_helper import temp_cwd
22562257

2258+
def run_command(cmd):
2259+
if verbose:
2260+
print()
2261+
print('Run:', ' '.join(map(shlex.quote, cmd)))
2262+
subprocess.run(cmd, check=True)
2263+
else:
2264+
subprocess.run(cmd,
2265+
stdout=subprocess.PIPE,
2266+
stderr=subprocess.STDOUT,
2267+
check=True)
2268+
22572269
with temp_cwd() as temp_dir:
22582270
# Create virtual environment to get setuptools
22592271
cmd = [sys.executable, '-X', 'dev', '-m', 'venv', venv_dir]
2260-
if verbose:
2261-
print()
2262-
print('Run:', ' '.join(cmd))
2263-
subprocess.run(cmd, check=True)
2272+
run_command(cmd)
22642273

22652274
venv = os.path.join(temp_dir, venv_dir)
22662275

@@ -2275,10 +2284,7 @@ def setup_venv_with_pip_setuptools_wheel(venv_dir):
22752284
'-m', 'pip', 'install',
22762285
_findwheel('setuptools'),
22772286
_findwheel('wheel')]
2278-
if verbose:
2279-
print()
2280-
print('Run:', ' '.join(cmd))
2281-
subprocess.run(cmd, check=True)
2287+
run_command(cmd)
22822288

22832289
yield python
22842290

Lib/test/test_cext/__init__.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
# gh-116869: Build a basic C test extension to check that the Python C API
22
# does not emit C compiler warnings.
33
#
4-
# Python C API must build with -Werror=declaration-after-statement.
4+
# The Python C API must be compatible with building
5+
# with the -Werror=declaration-after-statement compiler flag.
56

67
import os.path
8+
import shlex
79
import shutil
810
import subprocess
911
import unittest
@@ -14,9 +16,10 @@
1416
SETUP = os.path.join(os.path.dirname(__file__), 'setup.py')
1517

1618

17-
# With MSVC, the linker fails with: cannot open file 'python311.lib'
18-
# https://github.com/python/cpython/pull/32175#issuecomment-1111175897
19-
@unittest.skipIf(support.MS_WINDOWS, 'test fails on Windows')
19+
# With MSVC on a debug build, the linker fails with: cannot open file
20+
# 'python311.lib', it should look 'python311_d.lib'.
21+
@unittest.skipIf(support.MS_WINDOWS and support.Py_DEBUG,
22+
'test fails on Windows debug build')
2023
# Building and running an extension in clang sanitizing mode is not
2124
# straightforward
2225
@support.skip_if_sanitizer('test does not work with analyzing builds',
@@ -26,17 +29,22 @@
2629
@support.requires_subprocess()
2730
@support.requires_resource('cpu')
2831
class TestExt(unittest.TestCase):
29-
def test_build_c99(self):
30-
self.check_build('_test_c99_ext', std='c99')
32+
# Default build with no options
33+
def test_build(self):
34+
self.check_build('_test_cext')
3135

3236
def test_build_c11(self):
33-
self.check_build('_test_c11_ext', std='c11')
37+
self.check_build('_test_c11_cext', std='c11')
38+
39+
@unittest.skipIf(support.MS_WINDOWS, "MSVC doesn't support /std:c99")
40+
def test_build_c99(self):
41+
self.check_build('_test_c99_cext', std='c99')
3442

3543
def test_build_limited(self):
36-
self.check_build('_test_limited_ext', limited=True)
44+
self.check_build('_test_limited_cext', limited=True)
3745

3846
def test_build_limited_c11(self):
39-
self.check_build('_test_limited_c11_ext', limited=True, std='c11')
47+
self.check_build('_test_limited_c11_cext', limited=True, std='c11')
4048

4149
def check_build(self, extension_name, std=None, limited=False):
4250
venv_dir = 'env'
@@ -58,7 +66,7 @@ def run_cmd(operation, cmd):
5866
env['CPYTHON_TEST_LIMITED'] = '1'
5967
env['CPYTHON_TEST_EXT_NAME'] = extension_name
6068
if support.verbose:
61-
print('Run:', ' '.join(cmd))
69+
print('Run:', ' '.join(map(shlex.quote, cmd)))
6270
subprocess.run(cmd, check=True, env=env)
6371
else:
6472
proc = subprocess.run(cmd,
@@ -67,6 +75,7 @@ def run_cmd(operation, cmd):
6775
stderr=subprocess.STDOUT,
6876
text=True)
6977
if proc.returncode:
78+
print('Run:', ' '.join(map(shlex.quote, cmd)))
7079
print(proc.stdout, end='')
7180
self.fail(
7281
f"{operation} failed with exit code {proc.returncode}")

Lib/test/test_cext/extension.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,11 @@ static PyMethodDef _testcext_methods[] = {
3939
static int
4040
_testcext_exec(PyObject *module)
4141
{
42+
#ifdef __STDC_VERSION__
4243
if (PyModule_AddIntMacro(module, __STDC_VERSION__) < 0) {
4344
return -1;
4445
}
46+
#endif
4547
return 0;
4648
}
4749

Lib/test/test_cext/setup.py

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
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.
33
import os
4+
import platform
45
import shlex
56
import sys
67
import sysconfig
@@ -17,8 +18,8 @@
1718
# extension using the Python C API does not emit C compiler warnings.
1819
'-Werror',
1920

20-
# gh-116869: The Python C API must build with
21-
# -Werror=declaration-after-statement.
21+
# gh-116869: The Python C API must be compatible with building
22+
# with the -Werror=declaration-after-statement compiler flag.
2223
'-Werror=declaration-after-statement',
2324
]
2425
else:
@@ -34,22 +35,44 @@ def main():
3435
cflags = list(CFLAGS)
3536
cflags.append(f'-DMODULE_NAME={module_name}')
3637

38+
# Add -std=STD or /std:STD (MSVC) compiler flag
3739
if std:
38-
cflags.append(f'-std={std}')
40+
if support.MS_WINDOWS:
41+
cflags.append(f'/std:{std}')
42+
std_prefix = '/std'
43+
else:
44+
cflags.append(f'-std={std}')
45+
std_prefix = '-std'
3946

4047
# Remove existing -std options to only test ours
4148
cmd = (sysconfig.get_config_var('CC') or '')
4249
if cmd is not None:
4350
cmd = shlex.split(cmd)
44-
cmd = [arg for arg in cmd if not arg.startswith('-std=')]
51+
cmd = [arg for arg in cmd if not arg.startswith(std_prefix)]
4552
cmd = shlex.join(cmd)
4653
# CC env var overrides sysconfig CC variable in setuptools
4754
os.environ['CC'] = cmd
4855

56+
# Define Py_LIMITED_API macro
4957
if limited:
5058
version = sys.hexversion
5159
cflags.append(f'-DPy_LIMITED_API={version:#x}')
5260

61+
# On Windows, add PCbuild\amd64\ to include and library directories
62+
include_dirs = []
63+
library_dirs = []
64+
if support.MS_WINDOWS:
65+
srcdir = sysconfig.get_config_var('srcdir')
66+
machine = platform.uname().machine
67+
pcbuild = os.path.join(srcdir, 'PCbuild', machine)
68+
if os.path.exists(pcbuild):
69+
# pyconfig.h is generated in PCbuild\amd64\
70+
include_dirs.append(pcbuild)
71+
# python313.lib is generated in PCbuild\amd64\
72+
library_dirs.append(pcbuild)
73+
print(f"Add PCbuild directory: {pcbuild}")
74+
75+
# Display information to help debugging
5376
for env_name in ('CC', 'CFLAGS'):
5477
if env_name in os.environ:
5578
print(f"{env_name} env var: {os.environ[env_name]!r}")
@@ -60,7 +83,9 @@ def main():
6083
ext = Extension(
6184
module_name,
6285
sources=[SOURCE],
63-
extra_compile_args=cflags)
86+
extra_compile_args=cflags,
87+
include_dirs=include_dirs,
88+
library_dirs=library_dirs)
6489
setup(name=f'internal_{module_name}',
6590
version='0.0',
6691
ext_modules=[ext])

Lib/test/test_cppext/__init__.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
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.
33
import os.path
4+
import shlex
45
import shutil
5-
import unittest
66
import subprocess
7+
import unittest
78
from test import support
89

910

1011
SOURCE = os.path.join(os.path.dirname(__file__), 'extension.cpp')
1112
SETUP = os.path.join(os.path.dirname(__file__), 'setup.py')
1213

1314

14-
# With MSVC, the linker fails with: cannot open file 'python311.lib'
15-
# https://github.com/python/cpython/pull/32175#issuecomment-1111175897
16-
@unittest.skipIf(support.MS_WINDOWS, 'test fails on Windows')
15+
# With MSVC on a debug build, the linker fails with: cannot open file
16+
# 'python311.lib', it should look 'python311_d.lib'.
17+
@unittest.skipIf(support.MS_WINDOWS and support.Py_DEBUG,
18+
'test fails on Windows debug build')
1719
# Building and running an extension in clang sanitizing mode is not
1820
# straightforward
1921
@support.skip_if_sanitizer('test does not work with analyzing builds',
@@ -23,29 +25,37 @@
2325
@support.requires_subprocess()
2426
@support.requires_resource('cpu')
2527
class TestCPPExt(unittest.TestCase):
26-
def test_build_cpp11(self):
27-
self.check_build(False, '_testcpp11ext')
28+
def test_build(self):
29+
self.check_build('_testcppext')
2830

2931
def test_build_cpp03(self):
30-
self.check_build(True, '_testcpp03ext')
32+
self.check_build('_testcpp03ext', std='c++03')
33+
34+
@unittest.skipIf(support.MS_WINDOWS, "MSVC doesn't support /std:c++11")
35+
def test_build_cpp11(self):
36+
self.check_build('_testcpp11ext', std='c++11')
37+
38+
def test_build_cpp14(self):
39+
self.check_build('_testcpp14ext', std='c++14')
3140

32-
def check_build(self, std_cpp03, extension_name):
41+
def check_build(self, extension_name, std=None):
3342
venv_dir = 'env'
3443
with support.setup_venv_with_pip_setuptools_wheel(venv_dir) as python_exe:
35-
self._check_build(std_cpp03, extension_name, python_exe)
44+
self._check_build(extension_name, python_exe, std=std)
3645

37-
def _check_build(self, std_cpp03, extension_name, python_exe):
46+
def _check_build(self, extension_name, python_exe, std):
3847
pkg_dir = 'pkg'
3948
os.mkdir(pkg_dir)
4049
shutil.copy(SETUP, os.path.join(pkg_dir, os.path.basename(SETUP)))
4150
shutil.copy(SOURCE, os.path.join(pkg_dir, os.path.basename(SOURCE)))
4251

4352
def run_cmd(operation, cmd):
4453
env = os.environ.copy()
45-
env['CPYTHON_TEST_CPP_STD'] = 'c++03' if std_cpp03 else 'c++11'
54+
if std:
55+
env['CPYTHON_TEST_CPP_STD'] = std
4656
env['CPYTHON_TEST_EXT_NAME'] = extension_name
4757
if support.verbose:
48-
print('Run:', ' '.join(cmd))
58+
print('Run:', ' '.join(map(shlex.quote, cmd)))
4959
subprocess.run(cmd, check=True, env=env)
5060
else:
5161
proc = subprocess.run(cmd,
@@ -54,6 +64,7 @@ def run_cmd(operation, cmd):
5464
stderr=subprocess.STDOUT,
5565
text=True)
5666
if proc.returncode:
67+
print('Run:', ' '.join(map(shlex.quote, cmd)))
5768
print(proc.stdout, end='')
5869
self.fail(
5970
f"{operation} failed with exit code {proc.returncode}")

Lib/test/test_cppext/extension.cpp

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,8 @@
88

99
#include "Python.h"
1010

11-
#if __cplusplus >= 201103
12-
# define NAME _testcpp11ext
13-
#else
14-
# define NAME _testcpp03ext
11+
#ifndef MODULE_NAME
12+
# error "MODULE_NAME macro must be defined"
1513
#endif
1614

1715
#define _STR(NAME) #NAME
@@ -160,7 +158,7 @@ PyType_Slot VirtualPyObject_Slots[] = {
160158
};
161159

162160
PyType_Spec VirtualPyObject_Spec = {
163-
/* .name */ STR(NAME) ".VirtualPyObject",
161+
/* .name */ STR(MODULE_NAME) ".VirtualPyObject",
164162
/* .basicsize */ sizeof(VirtualPyObject),
165163
/* .itemsize */ 0,
166164
/* .flags */ Py_TPFLAGS_DEFAULT,
@@ -240,7 +238,7 @@ PyDoc_STRVAR(_testcppext_doc, "C++ test extension.");
240238

241239
static struct PyModuleDef _testcppext_module = {
242240
PyModuleDef_HEAD_INIT, // m_base
243-
STR(NAME), // m_name
241+
STR(MODULE_NAME), // m_name
244242
_testcppext_doc, // m_doc
245243
0, // m_size
246244
_testcppext_methods, // m_methods
@@ -254,7 +252,7 @@ static struct PyModuleDef _testcppext_module = {
254252
#define FUNC_NAME(NAME) _FUNC_NAME(NAME)
255253

256254
PyMODINIT_FUNC
257-
FUNC_NAME(NAME)(void)
255+
FUNC_NAME(MODULE_NAME)(void)
258256
{
259257
return PyModuleDef_Init(&_testcppext_module);
260258
}

0 commit comments

Comments
 (0)