Repository: pypa/setuptools

Issue:
[FR] Allow environment variable override of EXT_SUFFIX
### What's the problem this feature will solve?

I'm trying to cross-compile other people's packages, but the value of `EXT_SUFFIX` is always taken from the host Python.

### Describe the solution you'd like

When I set `SETUPTOOLS_EXT_SUFFIX` environment variable, the value is used instead of `sysconfig.get_config_var("EXT_SUFFIX")`. This would allow me to set the suffix precisely for all packages in a build.

As I've already set environment variables to control the builds, another environment variable is the best solution for matching these. (Command-line and per-environment settings may be lost during the build process.)

### Alternative Solutions

I have previously tried building with other tools (and even written an entire backend that can handle this), but doing it for arbitrary projects means porting them to build backends other than setuptools.

It would be possible to extract the wheels and rename individual files, however, this leaves traces and interferes with reproducibility.

Rather than a completely open-ended environment variable, setuptools could try and automatically determine what the suffix should be. (But the point of overriding the automatic detection is to avoid this kind of logic and let things be set in the environment, so I'd probably _still_ want the option of a complete override.)

### Additional context

_No response_

### Code of Conduct

- [X] I agree to follow the PSF Code of Conduct
[FR] Allow environment variable override of EXT_SUFFIX
### What's the problem this feature will solve?

I'm trying to cross-compile other people's packages, but the value of `EXT_SUFFIX` is always taken from the host Python.

### Describe the solution you'd like

When I set `SETUPTOOLS_EXT_SUFFIX` environment variable, the value is used instead of `sysconfig.get_config_var("EXT_SUFFIX")`. This would allow me to set the suffix precisely for all packages in a build.

As I've already set environment variables to control the builds, another environment variable is the best solution for matching these. (Command-line and per-environment settings may be lost during the build process.)

### Alternative Solutions

I have previously tried building with other tools (and even written an entire backend that can handle this), but doing it for arbitrary projects means porting them to build backends other than setuptools.

It would be possible to extract the wheels and rename individual files, however, this leaves traces and interferes with reproducibility.

Rather than a completely open-ended environment variable, setuptools could try and automatically determine what the suffix should be. (But the point of overriding the automatic detection is to avoid this kind of logic and let things be set in the environment, so I'd probably _still_ want the option of a complete override.)

### Additional context

_No response_

### Code of Conduct

- [X] I agree to follow the PSF Code of Conduct


Discussion:

Resolving patch:
diff --git a/setuptools/command/build_ext.py b/setuptools/command/build_ext.py
--- a/setuptools/command/build_ext.py
+++ b/setuptools/command/build_ext.py
@@ -104,14 +104,20 @@ def copy_extensions_to_source(self):
                 self.write_stub(package_dir or os.curdir, ext, True)
 
     def get_ext_filename(self, fullname):
-        filename = _build_ext.get_ext_filename(self, fullname)
+        so_ext = os.getenv('SETUPTOOLS_EXT_SUFFIX')
+        if so_ext:
+            filename = os.path.join(*fullname.split('.')) + so_ext
+        else:
+            filename = _build_ext.get_ext_filename(self, fullname)
+            so_ext = get_config_var('EXT_SUFFIX')
+
         if fullname in self.ext_map:
             ext = self.ext_map[fullname]
             use_abi3 = getattr(ext, 'py_limited_api') and get_abi3_suffix()
             if use_abi3:
-                so_ext = get_config_var('EXT_SUFFIX')
                 filename = filename[:-len(so_ext)]
-                filename = filename + get_abi3_suffix()
+                so_ext = get_abi3_suffix()
+                filename = filename + so_ext
             if isinstance(ext, Library):
                 fn, ext = os.path.splitext(filename)
                 return self.shlib_compiler.library_filename(fn, libtype)


Test patch:
diff --git a/setuptools/tests/test_build_ext.py b/setuptools/tests/test_build_ext.py
--- a/setuptools/tests/test_build_ext.py
+++ b/setuptools/tests/test_build_ext.py
@@ -1,3 +1,4 @@
+import os
 import sys
 import distutils.command.build_ext as orig
 from distutils.sysconfig import get_config_var
@@ -12,6 +13,9 @@
 from .textwrap import DALS
 
 
+IS_PYPY = '__pypy__' in sys.builtin_module_names
+
+
 class TestBuildExt:
     def test_get_ext_filename(self):
         """
@@ -47,6 +51,38 @@ def test_abi3_filename(self):
         else:
             assert 'abi3' in res
 
+    def test_ext_suffix_override(self):
+        """
+        SETUPTOOLS_EXT_SUFFIX variable always overrides
+        default extension options.
+        """
+        dist = Distribution()
+        cmd = build_ext(dist)
+        cmd.ext_map['for_abi3'] = ext = Extension(
+            'for_abi3',
+            ['s.c'],
+            # Override shouldn't affect abi3 modules
+            py_limited_api=True,
+        )
+        # Mock value needed to pass tests
+        ext._links_to_dynamic = False
+
+        if not IS_PYPY:
+            expect = cmd.get_ext_filename('for_abi3')
+        else:
+            # PyPy builds do not use ABI3 tag, so they will
+            # also get the overridden suffix.
+            expect = 'for_abi3.test-suffix'
+
+        try:
+            os.environ['SETUPTOOLS_EXT_SUFFIX'] = '.test-suffix'
+            res = cmd.get_ext_filename('normal')
+            assert 'normal.test-suffix' == res
+            res = cmd.get_ext_filename('for_abi3')
+            assert expect == res
+        finally:
+            del os.environ['SETUPTOOLS_EXT_SUFFIX']
+
 
 def test_build_ext_config_handling(tmpdir_cwd):
     files = {