]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-92906: Enable test_cext and test_cppext on Windows (#117000)
authorVictor Stinner <vstinner@python.org>
Tue, 19 Mar 2024 14:03:27 +0000 (15:03 +0100)
committerGitHub <noreply@github.com>
Tue, 19 Mar 2024 14:03:27 +0000 (14:03 +0000)
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.

Lib/test/support/__init__.py
Lib/test/test_cext/__init__.py
Lib/test/test_cext/extension.c
Lib/test/test_cext/setup.py
Lib/test/test_cppext/__init__.py
Lib/test/test_cppext/extension.cpp
Lib/test/test_cppext/setup.py

index ce693e51aab31cfa307b8fc8b5340386e4c6fa6c..a1c7987fa0db474ed57d8a4ba90ad6fa1b17463f 100644 (file)
@@ -2251,16 +2251,25 @@ def _findwheel(pkgname):
 # and returns the path to the venv directory and the path to the python executable
 @contextlib.contextmanager
 def setup_venv_with_pip_setuptools_wheel(venv_dir):
+    import shlex
     import subprocess
     from .os_helper import temp_cwd
 
+    def run_command(cmd):
+        if verbose:
+            print()
+            print('Run:', ' '.join(map(shlex.quote, cmd)))
+            subprocess.run(cmd, check=True)
+        else:
+            subprocess.run(cmd,
+                           stdout=subprocess.PIPE,
+                           stderr=subprocess.STDOUT,
+                           check=True)
+
     with temp_cwd() as temp_dir:
         # Create virtual environment to get setuptools
         cmd = [sys.executable, '-X', 'dev', '-m', 'venv', venv_dir]
-        if verbose:
-            print()
-            print('Run:', ' '.join(cmd))
-        subprocess.run(cmd, check=True)
+        run_command(cmd)
 
         venv = os.path.join(temp_dir, venv_dir)
 
@@ -2275,10 +2284,7 @@ def setup_venv_with_pip_setuptools_wheel(venv_dir):
                '-m', 'pip', 'install',
                _findwheel('setuptools'),
                _findwheel('wheel')]
-        if verbose:
-            print()
-            print('Run:', ' '.join(cmd))
-        subprocess.run(cmd, check=True)
+        run_command(cmd)
 
         yield python
 
index 992939241c591e3eeaec1e6b523e6f8efb94bcab..7bf2381d844d1c1aa55102855341880228e4706d 100644 (file)
@@ -1,9 +1,11 @@
 # gh-116869: Build a basic C test extension to check that the Python C API
 # does not emit C compiler warnings.
 #
-# Python C API must build with -Werror=declaration-after-statement.
+# The Python C API must be compatible with building
+# with the -Werror=declaration-after-statement compiler flag.
 
 import os.path
+import shlex
 import shutil
 import subprocess
 import unittest
@@ -14,9 +16,10 @@ SOURCE = os.path.join(os.path.dirname(__file__), 'extension.c')
 SETUP = os.path.join(os.path.dirname(__file__), 'setup.py')
 
 
-# With MSVC, the linker fails with: cannot open file 'python311.lib'
-# https://github.com/python/cpython/pull/32175#issuecomment-1111175897
-@unittest.skipIf(support.MS_WINDOWS, 'test fails on Windows')
+# 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',
@@ -26,17 +29,22 @@ SETUP = os.path.join(os.path.dirname(__file__), 'setup.py')
 @support.requires_subprocess()
 @support.requires_resource('cpu')
 class TestExt(unittest.TestCase):
-    def test_build_c99(self):
-        self.check_build('_test_c99_ext', std='c99')
+    # Default build with no options
+    def test_build(self):
+        self.check_build('_test_cext')
 
     def test_build_c11(self):
-        self.check_build('_test_c11_ext', std='c11')
+        self.check_build('_test_c11_cext', std='c11')
+
+    @unittest.skipIf(support.MS_WINDOWS, "MSVC doesn't support /std:c99")
+    def test_build_c99(self):
+        self.check_build('_test_c99_cext', std='c99')
 
     def test_build_limited(self):
-        self.check_build('_test_limited_ext', limited=True)
+        self.check_build('_test_limited_cext', limited=True)
 
     def test_build_limited_c11(self):
-        self.check_build('_test_limited_c11_ext', limited=True, std='c11')
+        self.check_build('_test_limited_c11_cext', limited=True, std='c11')
 
     def check_build(self, extension_name, std=None, limited=False):
         venv_dir = 'env'
@@ -58,7 +66,7 @@ class TestExt(unittest.TestCase):
                 env['CPYTHON_TEST_LIMITED'] = '1'
             env['CPYTHON_TEST_EXT_NAME'] = extension_name
             if support.verbose:
-                print('Run:', ' '.join(cmd))
+                print('Run:', ' '.join(map(shlex.quote, cmd)))
                 subprocess.run(cmd, check=True, env=env)
             else:
                 proc = subprocess.run(cmd,
@@ -67,6 +75,7 @@ class TestExt(unittest.TestCase):
                                       stderr=subprocess.STDOUT,
                                       text=True)
                 if proc.returncode:
+                    print('Run:', ' '.join(map(shlex.quote, cmd)))
                     print(proc.stdout, end='')
                     self.fail(
                         f"{operation} failed with exit code {proc.returncode}")
index a2433f2663c66775e47b0d14d4efa52884ac52b2..662abd4005e8d2b77cdb9951711376d049c8f9e7 100644 (file)
@@ -39,9 +39,11 @@ static PyMethodDef _testcext_methods[] = {
 static int
 _testcext_exec(PyObject *module)
 {
+#ifdef __STDC_VERSION__
     if (PyModule_AddIntMacro(module, __STDC_VERSION__) < 0) {
         return -1;
     }
+#endif
     return 0;
 }
 
index 5ca080a790a5bde1c1cc0b07e23fc83430c733f2..d6c4410fa5f1e3ecb24139d52e70e7305cd24cc6 100644 (file)
@@ -1,6 +1,7 @@
 # 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.
 import os
+import platform
 import shlex
 import sys
 import sysconfig
@@ -17,8 +18,8 @@ if not support.MS_WINDOWS:
         # extension using the Python C API does not emit C compiler warnings.
         '-Werror',
 
-        # gh-116869: The Python C API must build with
-        # -Werror=declaration-after-statement.
+        # gh-116869: The Python C API must be compatible with building
+        # with the -Werror=declaration-after-statement compiler flag.
         '-Werror=declaration-after-statement',
     ]
 else:
@@ -34,22 +35,44 @@ def main():
     cflags = list(CFLAGS)
     cflags.append(f'-DMODULE_NAME={module_name}')
 
+    # Add -std=STD or /std:STD (MSVC) compiler flag
     if std:
-        cflags.append(f'-std={std}')
+        if support.MS_WINDOWS:
+            cflags.append(f'/std:{std}')
+            std_prefix = '/std'
+        else:
+            cflags.append(f'-std={std}')
+            std_prefix = '-std'
 
         # Remove existing -std options to only test ours
         cmd = (sysconfig.get_config_var('CC') or '')
         if cmd is not None:
             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)
             # CC env var overrides sysconfig CC variable in setuptools
             os.environ['CC'] = cmd
 
+    # Define Py_LIMITED_API macro
     if limited:
         version = sys.hexversion
         cflags.append(f'-DPy_LIMITED_API={version:#x}')
 
+    # On Windows, add PCbuild\amd64\ to include and library directories
+    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'):
         if env_name in os.environ:
             print(f"{env_name} env var: {os.environ[env_name]!r}")
@@ -60,7 +83,9 @@ def main():
     ext = Extension(
         module_name,
         sources=[SOURCE],
-        extra_compile_args=cflags)
+        extra_compile_args=cflags,
+        include_dirs=include_dirs,
+        library_dirs=library_dirs)
     setup(name=f'internal_{module_name}',
           version='0.0',
           ext_modules=[ext])
index 66ead9a3d78c6646bdd953c42c326da9ebd74e46..b8414f217c7dde2c588c9d94ccab47888375dcc6 100644 (file)
@@ -1,9 +1,10 @@
 # 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.
 import os.path
+import shlex
 import shutil
-import unittest
 import subprocess
+import unittest
 from test import support
 
 
@@ -11,9 +12,10 @@ SOURCE = os.path.join(os.path.dirname(__file__), 'extension.cpp')
 SETUP = os.path.join(os.path.dirname(__file__), 'setup.py')
 
 
-# With MSVC, the linker fails with: cannot open file 'python311.lib'
-# https://github.com/python/cpython/pull/32175#issuecomment-1111175897
-@unittest.skipIf(support.MS_WINDOWS, 'test fails on Windows')
+# 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',
@@ -23,18 +25,25 @@ SETUP = os.path.join(os.path.dirname(__file__), 'setup.py')
 @support.requires_subprocess()
 @support.requires_resource('cpu')
 class TestCPPExt(unittest.TestCase):
-    def test_build_cpp11(self):
-        self.check_build(False, '_testcpp11ext')
+    def test_build(self):
+        self.check_build('_testcppext')
 
     def test_build_cpp03(self):
-        self.check_build(True, '_testcpp03ext')
+        self.check_build('_testcpp03ext', std='c++03')
+
+    @unittest.skipIf(support.MS_WINDOWS, "MSVC doesn't support /std:c++11")
+    def test_build_cpp11(self):
+        self.check_build('_testcpp11ext', std='c++11')
+
+    def test_build_cpp14(self):
+        self.check_build('_testcpp14ext', std='c++14')
 
-    def check_build(self, std_cpp03, extension_name):
+    def check_build(self, extension_name, std=None):
         venv_dir = 'env'
         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'
         os.mkdir(pkg_dir)
         shutil.copy(SETUP, os.path.join(pkg_dir, os.path.basename(SETUP)))
@@ -42,10 +51,11 @@ class TestCPPExt(unittest.TestCase):
 
         def run_cmd(operation, cmd):
             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
             if support.verbose:
-                print('Run:', ' '.join(cmd))
+                print('Run:', ' '.join(map(shlex.quote, cmd)))
                 subprocess.run(cmd, check=True, env=env)
             else:
                 proc = subprocess.run(cmd,
@@ -54,6 +64,7 @@ class TestCPPExt(unittest.TestCase):
                                       stderr=subprocess.STDOUT,
                                       text=True)
                 if proc.returncode:
+                    print('Run:', ' '.join(map(shlex.quote, cmd)))
                     print(proc.stdout, end='')
                     self.fail(
                         f"{operation} failed with exit code {proc.returncode}")
index 90669b10cb2c6dfaa702328666c75fdc9d4ae298..a569c2251d1ad75aa393c4f4a3ea10ab1543c469 100644 (file)
@@ -8,10 +8,8 @@
 
 #include "Python.h"
 
-#if __cplusplus >= 201103
-#  define NAME _testcpp11ext
-#else
-#  define NAME _testcpp03ext
+#ifndef MODULE_NAME
+#  error "MODULE_NAME macro must be defined"
 #endif
 
 #define _STR(NAME) #NAME
@@ -160,7 +158,7 @@ PyType_Slot VirtualPyObject_Slots[] = {
 };
 
 PyType_Spec VirtualPyObject_Spec = {
-    /* .name */ STR(NAME) ".VirtualPyObject",
+    /* .name */ STR(MODULE_NAME) ".VirtualPyObject",
     /* .basicsize */ sizeof(VirtualPyObject),
     /* .itemsize */ 0,
     /* .flags */ Py_TPFLAGS_DEFAULT,
@@ -240,7 +238,7 @@ PyDoc_STRVAR(_testcppext_doc, "C++ test extension.");
 
 static struct PyModuleDef _testcppext_module = {
     PyModuleDef_HEAD_INIT,  // m_base
-    STR(NAME),  // m_name
+    STR(MODULE_NAME),  // m_name
     _testcppext_doc,  // m_doc
     0,  // m_size
     _testcppext_methods,  // m_methods
@@ -254,7 +252,7 @@ static struct PyModuleDef _testcppext_module = {
 #define FUNC_NAME(NAME) _FUNC_NAME(NAME)
 
 PyMODINIT_FUNC
-FUNC_NAME(NAME)(void)
+FUNC_NAME(MODULE_NAME)(void)
 {
     return PyModuleDef_Init(&_testcppext_module);
 }
index c7ba1efb4dd05a4af3fe6dce221568aa16f115bc..77e47bcd0cbe1634ea71d2d2e0474d271d3561dc 100644 (file)
@@ -1,8 +1,8 @@
 # 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.
 import os
+import platform
 import shlex
-import sys
 import sysconfig
 from test import support
 
@@ -25,28 +25,62 @@ else:
 
 def main():
     cppflags = list(CPPFLAGS)
-    std = os.environ["CPYTHON_TEST_CPP_STD"]
-    name = os.environ["CPYTHON_TEST_EXT_NAME"]
-
-    cppflags = [*CPPFLAGS, f'-std={std}']
-
-    # 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
-    # CC command.
-    cmd = (sysconfig.get_config_var('CC') or '')
-    if cmd is not None:
-        cmd = shlex.split(cmd)
-        cmd = [arg for arg in cmd if not arg.startswith('-std=')]
-        cmd = shlex.join(cmd)
-        # CC env var overrides sysconfig CC variable in setuptools
-        os.environ['CC'] = cmd
-
-    cpp_ext = Extension(
-        name,
+    std = os.environ.get("CPYTHON_TEST_CPP_STD", "")
+    module_name = os.environ["CPYTHON_TEST_EXT_NAME"]
+
+    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}')
+            std_prefix = '/std'
+        else:
+            cppflags.append(f'-std={std}')
+            std_prefix = '-std'
+
+        # Remove existing -std options to only test ours
+        cmd = (sysconfig.get_config_var('CC') or '')
+        if cmd is not None:
+            cmd = shlex.split(cmd)
+            cmd = [arg for arg in cmd if not arg.startswith(std_prefix)]
+            cmd = shlex.join(cmd)
+            # CC env var overrides sysconfig CC variable in setuptools
+            os.environ['CC'] = cmd
+
+    # On Windows, add PCbuild\amd64\ to include and library directories
+    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],
         language='c++',
-        extra_compile_args=cppflags)
-    setup(name='internal' + name, version='0.0', ext_modules=[cpp_ext])
+        extra_compile_args=cppflags,
+        include_dirs=include_dirs,
+        library_dirs=library_dirs)
+    setup(name=f'internal_{module_name}',
+          version='0.0',
+          ext_modules=[ext])
 
 
 if __name__ == "__main__":