Skip to content

Commit

Permalink
Merge pull request #184 from msys2-contrib/add-mingw-support
Browse files Browse the repository at this point in the history
Add support for building extensions using MinGW compilers
  • Loading branch information
jaraco authored Jun 28, 2024
2 parents 4fd2d56 + 5bdd6d3 commit 99ca390
Show file tree
Hide file tree
Showing 11 changed files with 128 additions and 16 deletions.
42 changes: 42 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,48 @@ jobs:
shell: C:\cygwin\bin\env.exe CYGWIN_NOWINPATH=1 CHERE_INVOKING=1 C:\cygwin\bin\bash.exe -leo pipefail -o igncr {0}
run: tox

test_msys2_mingw:
strategy:
matrix:
include:
- { sys: mingw64, env: x86_64, cc: gcc, cxx: g++ }
- { sys: mingw32, env: i686, cc: gcc, cxx: g++ }
- { sys: ucrt64, env: ucrt-x86_64, cc: gcc, cxx: g++ }
- { sys: clang64, env: clang-x86_64, cc: clang, cxx: clang++}
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: msys2/setup-msys2@v2
with:
msystem: ${{matrix.sys}}
install: |
mingw-w64-${{matrix.env}}-toolchain
mingw-w64-${{matrix.env}}-python
mingw-w64-${{matrix.env}}-python-pip
mingw-w64-${{matrix.env}}-python-virtualenv
mingw-w64-${{matrix.env}}-cc
git
- name: Install Dependencies
shell: msys2 {0}
run: |
export VIRTUALENV_NO_SETUPTOOLS=1
python -m virtualenv /tmp/venv
source /tmp/venv/bin/activate
# python-ruff doesn't work without rust
sed -i '/pytest-ruff/d' pyproject.toml
pip install -e .[test]
- name: Run tests
shell: msys2 {0}
env:
CC: ${{ matrix.cc }}
CXX: ${{ matrix.cxx }}
run: |
source /tmp/venv/bin/activate
pytest
ci_setuptools:
# Integration testing with setuptools
strategy:
Expand Down
6 changes: 5 additions & 1 deletion distutils/ccompiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
)
from .file_util import move_file
from .spawn import spawn
from .util import execute, split_quoted
from .util import execute, split_quoted, is_mingw


class CCompiler:
Expand Down Expand Up @@ -1080,6 +1080,10 @@ def get_default_compiler(osname=None, platform=None):
osname = os.name
if platform is None:
platform = sys.platform
# Mingw is a special case where sys.platform is 'win32' but we
# want to use the 'mingw32' compiler, so check it first
if is_mingw():
return 'mingw32'
for pattern, compiler in _default_compilers:
if (
re.match(pattern, platform) is not None
Expand Down
8 changes: 4 additions & 4 deletions distutils/command/build_ext.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
)
from ..extension import Extension
from ..sysconfig import customize_compiler, get_config_h_filename, get_python_version
from ..util import get_platform
from ..util import get_platform, is_mingw

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

if not isinstance(self.compiler, MSVCCompiler):
Expand Down Expand Up @@ -784,7 +784,7 @@ def get_libraries(self, ext): # noqa: C901
# A native build on an Android device or on Cygwin
if hasattr(sys, 'getandroidapilevel'):
link_libpython = True
elif sys.platform == 'cygwin':
elif sys.platform == 'cygwin' or is_mingw():
link_libpython = True
elif '_PYTHON_HOST_PLATFORM' in os.environ:
# We are cross-compiling for one of the relevant platforms
Expand Down
4 changes: 2 additions & 2 deletions distutils/cygwinccompiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def get_msvcr():
try:
msc_ver = int(match.group(1))
except AttributeError:
return
return []
try:
return _msvcr_lookup[msc_ver]
except KeyError:
Expand Down Expand Up @@ -275,7 +275,7 @@ def __init__(self, verbose=False, dry_run=False, force=False):

self.set_executables(
compiler=f'{self.cc} -O -Wall',
compiler_so=f'{self.cc} -mdll -O -Wall',
compiler_so=f'{self.cc} -shared -O -Wall',
compiler_cxx=f'{self.cxx} -O -Wall',
linker_exe=f'{self.cc}',
linker_so=f'{self.linker_dll} {shared_option}',
Expand Down
9 changes: 6 additions & 3 deletions distutils/sysconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from ._functools import pass_none
from .compat import py39
from .errors import DistutilsPlatformError
from .util import is_mingw

IS_PYPY = '__pypy__' in sys.builtin_module_names

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

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

(
Expand Down
11 changes: 9 additions & 2 deletions distutils/tests/test_cygwinccompiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,12 @@ def test_check_config_h(self):
assert check_config_h()[0] == CONFIG_H_OK

def test_get_msvcr(self):
# none
# []
sys.version = (
'2.6.1 (r261:67515, Dec 6 2008, 16:42:21) '
'\n[GCC 4.0.1 (Apple Computer, Inc. build 5370)]'
)
assert get_msvcr() is None
assert get_msvcr() == []

# MSVC 7.0
sys.version = (
Expand Down Expand Up @@ -114,3 +114,10 @@ def test_get_msvcr(self):
)
with pytest.raises(ValueError):
get_msvcr()

@pytest.mark.skipif('sys.platform != "cygwin"')
def test_dll_libraries_not_none(self):
from distutils.cygwinccompiler import CygwinCCompiler

compiler = CygwinCCompiler()
assert compiler.dll_libraries is not None
3 changes: 2 additions & 1 deletion distutils/tests/test_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from distutils.errors import DistutilsOptionError
from distutils.extension import Extension
from distutils.tests import missing_compiler_executable, support
from distutils.util import is_mingw

import pytest

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

actual_headers = os.path.relpath(cmd.install_headers, site.USER_BASE)
if os.name == 'nt':
if os.name == 'nt' and not is_mingw():
site_path = os.path.relpath(os.path.dirname(orig_site), orig_base)
include = os.path.join(site_path, 'Include')
else:
Expand Down
45 changes: 45 additions & 0 deletions distutils/tests/test_mingwccompiler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import pytest

from distutils.util import split_quoted, is_mingw
from distutils.errors import DistutilsPlatformError, CCompilerError


class TestMingw32CCompiler:
@pytest.mark.skipif(not is_mingw(), reason='not on mingw')
def test_compiler_type(self):
from distutils.cygwinccompiler import Mingw32CCompiler

compiler = Mingw32CCompiler()
assert compiler.compiler_type == 'mingw32'

@pytest.mark.skipif(not is_mingw(), reason='not on mingw')
def test_set_executables(self, monkeypatch):
from distutils.cygwinccompiler import Mingw32CCompiler

monkeypatch.setenv('CC', 'cc')
monkeypatch.setenv('CXX', 'c++')

compiler = Mingw32CCompiler()

assert compiler.compiler == split_quoted('cc -O -Wall')
assert compiler.compiler_so == split_quoted('cc -shared -O -Wall')
assert compiler.compiler_cxx == split_quoted('c++ -O -Wall')
assert compiler.linker_exe == split_quoted('cc')
assert compiler.linker_so == split_quoted('cc -shared')

@pytest.mark.skipif(not is_mingw(), reason='not on mingw')
def test_runtime_library_dir_option(self):
from distutils.cygwinccompiler import Mingw32CCompiler

compiler = Mingw32CCompiler()
with pytest.raises(DistutilsPlatformError):
compiler.runtime_library_dir_option('/usr/lib')

@pytest.mark.skipif(not is_mingw(), reason='not on mingw')
def test_cygwincc_error(self, monkeypatch):
import distutils.cygwinccompiler

monkeypatch.setattr(distutils.cygwinccompiler, 'is_cygwincc', lambda _: True)

with pytest.raises(CCompilerError):
distutils.cygwinccompiler.Mingw32CCompiler()
4 changes: 2 additions & 2 deletions distutils/tests/test_sysconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import subprocess
import sys
from distutils import sysconfig
from distutils.ccompiler import get_default_compiler # noqa: F401
from distutils.ccompiler import new_compiler # noqa: F401
from distutils.unixccompiler import UnixCCompiler
from test.support import swap_item

Expand Down Expand Up @@ -110,7 +110,7 @@ def set_executables(self, **kw):

return comp

@pytest.mark.skipif("get_default_compiler() != 'unix'")
@pytest.mark.skipif("not isinstance(new_compiler(), UnixCCompiler)")
@pytest.mark.usefixtures('disable_macos_customization')
def test_customize_compiler(self):
# Make sure that sysconfig._config_vars is initialized
Expand Down
1 change: 1 addition & 0 deletions distutils/tests/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ def _join(*path):

# windows
os.name = 'nt'
os.sep = '\\'

def _isabs(path):
return path.startswith('c:\\')
Expand Down
11 changes: 10 additions & 1 deletion distutils/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ def change_root(new_root, pathname):

elif os.name == 'nt':
(drive, path) = os.path.splitdrive(pathname)
if path[0] == '\\':
if path[0] == os.sep:
path = path[1:]
return os.path.join(new_root, path)

Expand Down Expand Up @@ -499,3 +499,12 @@ def rfc822_escape(header):
suffix = indent if ends_in_newline else ""

return indent.join(lines) + suffix


def is_mingw():
"""Returns True if the current platform is mingw.
Python compiled with Mingw-w64 has sys.platform == 'win32' and
get_platform() starts with 'mingw'.
"""
return sys.platform == 'win32' and get_platform().startswith('mingw')

0 comments on commit 99ca390

Please sign in to comment.