Skip to content

Commit b266061

Browse files
committed
Merge pull request pypa/distutils#184 from msys2-contrib/add-mingw-support
Add support for building extensions using MinGW compilers
2 parents 62135e6 + 2317473 commit b266061

File tree

11 files changed

+128
-16
lines changed

11 files changed

+128
-16
lines changed

.github/workflows/main.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,48 @@ jobs:
120120
shell: C:\cygwin\bin\env.exe CYGWIN_NOWINPATH=1 CHERE_INVOKING=1 C:\cygwin\bin\bash.exe -leo pipefail -o igncr {0}
121121
run: tox
122122

123+
test_msys2_mingw:
124+
strategy:
125+
matrix:
126+
include:
127+
- { sys: mingw64, env: x86_64, cc: gcc, cxx: g++ }
128+
- { sys: mingw32, env: i686, cc: gcc, cxx: g++ }
129+
- { sys: ucrt64, env: ucrt-x86_64, cc: gcc, cxx: g++ }
130+
- { sys: clang64, env: clang-x86_64, cc: clang, cxx: clang++}
131+
runs-on: windows-latest
132+
steps:
133+
- uses: actions/checkout@v4
134+
- uses: msys2/setup-msys2@v2
135+
with:
136+
msystem: ${{matrix.sys}}
137+
install: |
138+
mingw-w64-${{matrix.env}}-toolchain
139+
mingw-w64-${{matrix.env}}-python
140+
mingw-w64-${{matrix.env}}-python-pip
141+
mingw-w64-${{matrix.env}}-python-virtualenv
142+
mingw-w64-${{matrix.env}}-cc
143+
git
144+
- name: Install Dependencies
145+
shell: msys2 {0}
146+
run: |
147+
export VIRTUALENV_NO_SETUPTOOLS=1
148+
149+
python -m virtualenv /tmp/venv
150+
source /tmp/venv/bin/activate
151+
152+
# python-ruff doesn't work without rust
153+
sed -i '/pytest-ruff/d' pyproject.toml
154+
155+
pip install -e .[test]
156+
- name: Run tests
157+
shell: msys2 {0}
158+
env:
159+
CC: ${{ matrix.cc }}
160+
CXX: ${{ matrix.cxx }}
161+
run: |
162+
source /tmp/venv/bin/activate
163+
pytest
164+
123165
ci_setuptools:
124166
# Integration testing with setuptools
125167
strategy:

distutils/ccompiler.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
)
2323
from .file_util import move_file
2424
from .spawn import spawn
25-
from .util import execute, split_quoted
25+
from .util import execute, split_quoted, is_mingw
2626

2727

2828
class CCompiler:
@@ -1080,6 +1080,10 @@ def get_default_compiler(osname=None, platform=None):
10801080
osname = os.name
10811081
if platform is None:
10821082
platform = sys.platform
1083+
# Mingw is a special case where sys.platform is 'win32' but we
1084+
# want to use the 'mingw32' compiler, so check it first
1085+
if is_mingw():
1086+
return 'mingw32'
10831087
for pattern, compiler in _default_compilers:
10841088
if (
10851089
re.match(pattern, platform) is not None

distutils/command/build_ext.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
)
2424
from ..extension import Extension
2525
from ..sysconfig import customize_compiler, get_config_h_filename, get_python_version
26-
from ..util import get_platform
26+
from ..util import get_platform, is_mingw
2727

2828
# An extension name is just a dot-separated list of Python NAMEs (ie.
2929
# the same as a fully-qualified module name).
@@ -212,7 +212,7 @@ def finalize_options(self): # noqa: C901
212212
# for extensions under windows use different directories
213213
# for Release and Debug builds.
214214
# also Python's library directory must be appended to library_dirs
215-
if os.name == 'nt':
215+
if os.name == 'nt' and not is_mingw():
216216
# the 'libs' directory is for binary installs - we assume that
217217
# must be the *native* platform. But we don't really support
218218
# cross-compiling via a binary install anyway, so we let it go.
@@ -754,7 +754,7 @@ def get_libraries(self, ext): # noqa: C901
754754
# pyconfig.h that MSVC groks. The other Windows compilers all seem
755755
# to need it mentioned explicitly, though, so that's what we do.
756756
# Append '_d' to the python import library on debug builds.
757-
if sys.platform == "win32":
757+
if sys.platform == "win32" and not is_mingw():
758758
from .._msvccompiler import MSVCCompiler
759759

760760
if not isinstance(self.compiler, MSVCCompiler):
@@ -784,7 +784,7 @@ def get_libraries(self, ext): # noqa: C901
784784
# A native build on an Android device or on Cygwin
785785
if hasattr(sys, 'getandroidapilevel'):
786786
link_libpython = True
787-
elif sys.platform == 'cygwin':
787+
elif sys.platform == 'cygwin' or is_mingw():
788788
link_libpython = True
789789
elif '_PYTHON_HOST_PLATFORM' in os.environ:
790790
# We are cross-compiling for one of the relevant platforms

distutils/cygwinccompiler.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def get_msvcr():
5757
try:
5858
msc_ver = int(match.group(1))
5959
except AttributeError:
60-
return
60+
return []
6161
try:
6262
return _msvcr_lookup[msc_ver]
6363
except KeyError:
@@ -275,7 +275,7 @@ def __init__(self, verbose=False, dry_run=False, force=False):
275275

276276
self.set_executables(
277277
compiler=f'{self.cc} -O -Wall',
278-
compiler_so=f'{self.cc} -mdll -O -Wall',
278+
compiler_so=f'{self.cc} -shared -O -Wall',
279279
compiler_cxx=f'{self.cxx} -O -Wall',
280280
linker_exe=f'{self.cc}',
281281
linker_so=f'{self.linker_dll} {shared_option}',

distutils/sysconfig.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from ._functools import pass_none
2020
from .compat import py39
2121
from .errors import DistutilsPlatformError
22+
from .util import is_mingw
2223

2324
IS_PYPY = '__pypy__' in sys.builtin_module_names
2425

@@ -121,8 +122,10 @@ def get_python_inc(plat_specific=False, prefix=None):
121122
"""
122123
default_prefix = BASE_EXEC_PREFIX if plat_specific else BASE_PREFIX
123124
resolved_prefix = prefix if prefix is not None else default_prefix
125+
# MinGW imitates posix like layout, but os.name != posix
126+
os_name = "posix" if is_mingw() else os.name
124127
try:
125-
getter = globals()[f'_get_python_inc_{os.name}']
128+
getter = globals()[f'_get_python_inc_{os_name}']
126129
except KeyError:
127130
raise DistutilsPlatformError(
128131
"I don't know where Python installs its C header files "
@@ -244,7 +247,7 @@ def get_python_lib(plat_specific=False, standard_lib=False, prefix=None):
244247
else:
245248
prefix = plat_specific and EXEC_PREFIX or PREFIX
246249

247-
if os.name == "posix":
250+
if os.name == "posix" or is_mingw():
248251
if plat_specific or standard_lib:
249252
# Platform-specific modules (any module from a non-pure-Python
250253
# module distribution) or standard Python library modules.
@@ -290,7 +293,7 @@ def customize_compiler(compiler): # noqa: C901
290293
Mainly needed on Unix, so we can plug in the information that
291294
varies across Unices and is stored in Python's Makefile.
292295
"""
293-
if compiler.compiler_type == "unix":
296+
if compiler.compiler_type in ["unix", "cygwin", "mingw32"]:
294297
_customize_macos()
295298

296299
(

distutils/tests/test_cygwinccompiler.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,12 @@ def test_check_config_h(self):
7171
assert check_config_h()[0] == CONFIG_H_OK
7272

7373
def test_get_msvcr(self):
74-
# none
74+
# []
7575
sys.version = (
7676
'2.6.1 (r261:67515, Dec 6 2008, 16:42:21) '
7777
'\n[GCC 4.0.1 (Apple Computer, Inc. build 5370)]'
7878
)
79-
assert get_msvcr() is None
79+
assert get_msvcr() == []
8080

8181
# MSVC 7.0
8282
sys.version = (
@@ -114,3 +114,10 @@ def test_get_msvcr(self):
114114
)
115115
with pytest.raises(ValueError):
116116
get_msvcr()
117+
118+
@pytest.mark.skipif('sys.platform != "cygwin"')
119+
def test_dll_libraries_not_none(self):
120+
from distutils.cygwinccompiler import CygwinCCompiler
121+
122+
compiler = CygwinCCompiler()
123+
assert compiler.dll_libraries is not None

distutils/tests/test_install.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from distutils.errors import DistutilsOptionError
1414
from distutils.extension import Extension
1515
from distutils.tests import missing_compiler_executable, support
16+
from distutils.util import is_mingw
1617

1718
import pytest
1819

@@ -117,7 +118,7 @@ def _expanduser(path):
117118
assert 'usersite' in cmd.config_vars
118119

119120
actual_headers = os.path.relpath(cmd.install_headers, site.USER_BASE)
120-
if os.name == 'nt':
121+
if os.name == 'nt' and not is_mingw():
121122
site_path = os.path.relpath(os.path.dirname(orig_site), orig_base)
122123
include = os.path.join(site_path, 'Include')
123124
else:
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import pytest
2+
3+
from distutils.util import split_quoted, is_mingw
4+
from distutils.errors import DistutilsPlatformError, CCompilerError
5+
6+
7+
class TestMingw32CCompiler:
8+
@pytest.mark.skipif(not is_mingw(), reason='not on mingw')
9+
def test_compiler_type(self):
10+
from distutils.cygwinccompiler import Mingw32CCompiler
11+
12+
compiler = Mingw32CCompiler()
13+
assert compiler.compiler_type == 'mingw32'
14+
15+
@pytest.mark.skipif(not is_mingw(), reason='not on mingw')
16+
def test_set_executables(self, monkeypatch):
17+
from distutils.cygwinccompiler import Mingw32CCompiler
18+
19+
monkeypatch.setenv('CC', 'cc')
20+
monkeypatch.setenv('CXX', 'c++')
21+
22+
compiler = Mingw32CCompiler()
23+
24+
assert compiler.compiler == split_quoted('cc -O -Wall')
25+
assert compiler.compiler_so == split_quoted('cc -shared -O -Wall')
26+
assert compiler.compiler_cxx == split_quoted('c++ -O -Wall')
27+
assert compiler.linker_exe == split_quoted('cc')
28+
assert compiler.linker_so == split_quoted('cc -shared')
29+
30+
@pytest.mark.skipif(not is_mingw(), reason='not on mingw')
31+
def test_runtime_library_dir_option(self):
32+
from distutils.cygwinccompiler import Mingw32CCompiler
33+
34+
compiler = Mingw32CCompiler()
35+
with pytest.raises(DistutilsPlatformError):
36+
compiler.runtime_library_dir_option('/usr/lib')
37+
38+
@pytest.mark.skipif(not is_mingw(), reason='not on mingw')
39+
def test_cygwincc_error(self, monkeypatch):
40+
import distutils.cygwinccompiler
41+
42+
monkeypatch.setattr(distutils.cygwinccompiler, 'is_cygwincc', lambda _: True)
43+
44+
with pytest.raises(CCompilerError):
45+
distutils.cygwinccompiler.Mingw32CCompiler()

distutils/tests/test_sysconfig.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import subprocess
88
import sys
99
from distutils import sysconfig
10-
from distutils.ccompiler import get_default_compiler # noqa: F401
10+
from distutils.ccompiler import new_compiler # noqa: F401
1111
from distutils.unixccompiler import UnixCCompiler
1212
from test.support import swap_item
1313

@@ -110,7 +110,7 @@ def set_executables(self, **kw):
110110

111111
return comp
112112

113-
@pytest.mark.skipif("get_default_compiler() != 'unix'")
113+
@pytest.mark.skipif("not isinstance(new_compiler(), UnixCCompiler)")
114114
@pytest.mark.usefixtures('disable_macos_customization')
115115
def test_customize_compiler(self):
116116
# Make sure that sysconfig._config_vars is initialized

distutils/tests/test_util.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ def _join(*path):
108108

109109
# windows
110110
os.name = 'nt'
111+
os.sep = '\\'
111112

112113
def _isabs(path):
113114
return path.startswith('c:\\')

0 commit comments

Comments
 (0)