diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1d5ab14e..b6b757db 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -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: diff --git a/distutils/ccompiler.py b/distutils/ccompiler.py index da522ea6..2066f19b 100644 --- a/distutils/ccompiler.py +++ b/distutils/ccompiler.py @@ -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: @@ -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 diff --git a/distutils/command/build_ext.py b/distutils/command/build_ext.py index 8341b0b0..65544b6e 100644 --- a/distutils/command/build_ext.py +++ b/distutils/command/build_ext.py @@ -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). @@ -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. @@ -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): @@ -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 diff --git a/distutils/cygwinccompiler.py b/distutils/cygwinccompiler.py index cf50ae51..7b812fd0 100644 --- a/distutils/cygwinccompiler.py +++ b/distutils/cygwinccompiler.py @@ -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: @@ -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}', diff --git a/distutils/sysconfig.py b/distutils/sysconfig.py index 2323225c..4ba0be56 100644 --- a/distutils/sysconfig.py +++ b/distutils/sysconfig.py @@ -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 @@ -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 " @@ -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. @@ -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() ( diff --git a/distutils/tests/test_cygwinccompiler.py b/distutils/tests/test_cygwinccompiler.py index d95654f5..2e1640b7 100644 --- a/distutils/tests/test_cygwinccompiler.py +++ b/distutils/tests/test_cygwinccompiler.py @@ -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 = ( @@ -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 diff --git a/distutils/tests/test_install.py b/distutils/tests/test_install.py index 6682f041..b3ffb2e6 100644 --- a/distutils/tests/test_install.py +++ b/distutils/tests/test_install.py @@ -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 @@ -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: diff --git a/distutils/tests/test_mingwccompiler.py b/distutils/tests/test_mingwccompiler.py new file mode 100644 index 00000000..d81360e7 --- /dev/null +++ b/distutils/tests/test_mingwccompiler.py @@ -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() diff --git a/distutils/tests/test_sysconfig.py b/distutils/tests/test_sysconfig.py index faa8e31c..889a398c 100644 --- a/distutils/tests/test_sysconfig.py +++ b/distutils/tests/test_sysconfig.py @@ -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 @@ -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 diff --git a/distutils/tests/test_util.py b/distutils/tests/test_util.py index 78d8b1e3..0de4e1a5 100644 --- a/distutils/tests/test_util.py +++ b/distutils/tests/test_util.py @@ -108,6 +108,7 @@ def _join(*path): # windows os.name = 'nt' + os.sep = '\\' def _isabs(path): return path.startswith('c:\\') diff --git a/distutils/util.py b/distutils/util.py index b6178a09..9db89b09 100644 --- a/distutils/util.py +++ b/distutils/util.py @@ -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) @@ -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')