Repository: mesonbuild/meson

Issue:
Wrong Python extension module file name on Python version smaller than 3.8.7
Meson names the extension module that way because it uses what the `sysconfig` module tells him to use, see https://github.com/mesonbuild/meson/blob/e68fcac919b332c7f9469672a243d2aab1bfea0a/mesonbuild/modules/python.py#L444

In Python 3.7.9 on Windows:
```
python -c 'import sysconfig; v = sysconfig.get_config_vars(); print((v.get("EXT_SUFFIX"), v.get("SO"), v.get(".so")))'
('.pyd', '.pyd', None)
```
While with Python 3.8.10 on Windows:
```
python -c 'import sysconfig; v = sysconfig.get_config_vars(); print((v.get("EXT_SUFFIX"), v.get("SO"), v.get(".so")))'
('.cp38-win_amd64.pyd', '.cp38-win_amd64.pyd', None)
```

Digging through the Python changelogs, I found that `sysconfig` was fixed to behave correctly on Windows in Python 3.8.7, see https://docs.python.org/3.8/whatsnew/changelog.html#python-3-8-7-final :

> [bpo-39825](https://bugs.python.org/issue?@action=redirect&bpo=39825): Windows: Change `sysconfig.get_config_var('EXT_SUFFIX')` to the expected full `platform_tag.extension` format. Previously it was hard-coded to `.pyd`, now it is compatible with `distutils.sysconfig` and will result in something like `.cp38-win_amd64.pyd`. This brings windows into conformance with the other platforms.

Looking for the right fix.


Discussion:

Resolving patch:
diff --git a/mesonbuild/modules/python.py b/mesonbuild/modules/python.py
--- a/mesonbuild/modules/python.py
+++ b/mesonbuild/modules/python.py
@@ -372,6 +372,15 @@ def links_against_libpython():
 variables = sysconfig.get_config_vars()
 variables.update({'base_prefix': getattr(sys, 'base_prefix', sys.prefix)})
 
+if sys.version_info < (3, 0):
+    suffix = variables.get('SO')
+elif sys.version_info < (3, 8, 7):
+    # https://bugs.python.org/issue?@action=redirect&bpo=39825
+    from distutils.sysconfig import get_config_var
+    suffix = get_config_var('EXT_SUFFIX')
+else:
+    suffix = variables.get('EXT_SUFFIX')
+
 print(json.dumps({
   'variables': variables,
   'paths': paths,
@@ -382,6 +391,7 @@ def links_against_libpython():
   'is_pypy': '__pypy__' in sys.builtin_module_names,
   'is_venv': sys.prefix != variables['base_prefix'],
   'link_libpython': links_against_libpython(),
+  'suffix': suffix,
 }))
 '''
 
@@ -440,8 +450,6 @@ def sanity(self, state: T.Optional['ModuleState'] = None) -> bool:
             mlog.debug(stderr)
 
         if info is not None and self._check_version(info['version']):
-            variables = info['variables']
-            info['suffix'] = variables.get('EXT_SUFFIX') or variables.get('SO') or variables.get('.so')
             self.info = T.cast('PythonIntrospectionDict', info)
             self.platlib = self._get_path(state, 'platlib')
             self.purelib = self._get_path(state, 'purelib')


Test patch:
diff --git a/run_project_tests.py b/run_project_tests.py
--- a/run_project_tests.py
+++ b/run_project_tests.py
@@ -177,7 +177,15 @@ def get_path(self, compiler: str, env: environment.Environment) -> T.Optional[Pa
                 return p
             if self.typ == 'python_lib':
                 return p.with_suffix(python_suffix)
-        if self.typ in ['file', 'dir']:
+            if self.typ == 'py_implib':
+                p = p.with_suffix(python_suffix)
+                if env.machines.host.is_windows() and canonical_compiler == 'msvc':
+                    return p.with_suffix('.lib')
+                elif env.machines.host.is_windows() or env.machines.host.is_cygwin():
+                    return p.with_suffix('.dll.a')
+                else:
+                    return None
+        elif self.typ in {'file', 'dir'}:
             return p
         elif self.typ == 'shared_lib':
             if env.machines.host.is_windows() or env.machines.host.is_cygwin():
@@ -211,15 +219,13 @@ def get_path(self, compiler: str, env: environment.Environment) -> T.Optional[Pa
             if self.version:
                 p = p.with_name('{}-{}'.format(p.name, self.version[0]))
             return p.with_suffix('.pdb') if has_pdb else None
-        elif self.typ in {'implib', 'implibempty', 'py_implib'}:
+        elif self.typ in {'implib', 'implibempty'}:
             if env.machines.host.is_windows() and canonical_compiler == 'msvc':
                 # only MSVC doesn't generate empty implibs
                 if self.typ == 'implibempty' and compiler == 'msvc':
                     return None
                 return p.parent / (re.sub(r'^lib', '', p.name) + '.lib')
             elif env.machines.host.is_windows() or env.machines.host.is_cygwin():
-                if self.typ == 'py_implib':
-                    p = p.with_suffix(python_suffix)
                 return p.with_suffix('.dll.a')
             else:
                 return None