Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for building extensions using MinGW compilers #184

Merged
merged 12 commits into from
Jun 28, 2024
Merged
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 @@ -21,7 +21,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 @@ -1075,6 +1075,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"]:
naveen521kk marked this conversation as resolved.
Show resolved Hide resolved
jaraco marked this conversation as resolved.
Show resolved Hide resolved
_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')
Loading