Skip to content

Commit 91f75bb

Browse files
authored
Merge pull request pypa/distutils#295 from pypa/feature/compilers-module-redux
Move compilers into their own package
2 parents cbe4a1b + cdd4c28 commit 91f75bb

17 files changed

+2970
-2898
lines changed

distutils/_msvccompiler.py

Lines changed: 2 additions & 603 deletions
Large diffs are not rendered by default.

distutils/ccompiler.py

Lines changed: 16 additions & 1258 deletions
Large diffs are not rendered by default.

distutils/compilers/C/base.py

Lines changed: 1265 additions & 0 deletions
Large diffs are not rendered by default.

distutils/compilers/C/cygwin.py

Lines changed: 340 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,340 @@
1+
"""distutils.cygwinccompiler
2+
3+
Provides the CygwinCCompiler class, a subclass of UnixCCompiler that
4+
handles the Cygwin port of the GNU C compiler to Windows. It also contains
5+
the Mingw32CCompiler class which handles the mingw32 port of GCC (same as
6+
cygwin in no-cygwin mode).
7+
"""
8+
9+
import copy
10+
import os
11+
import pathlib
12+
import shlex
13+
import sys
14+
import warnings
15+
from subprocess import check_output
16+
17+
from ...errors import (
18+
DistutilsExecError,
19+
DistutilsPlatformError,
20+
)
21+
from ...file_util import write_file
22+
from ...sysconfig import get_config_vars
23+
from ...version import LooseVersion, suppress_known_deprecation
24+
from . import unix
25+
from .errors import (
26+
CompileError,
27+
Error,
28+
)
29+
30+
31+
def get_msvcr():
32+
"""No longer needed, but kept for backward compatibility."""
33+
return []
34+
35+
36+
_runtime_library_dirs_msg = (
37+
"Unable to set runtime library search path on Windows, "
38+
"usually indicated by `runtime_library_dirs` parameter to Extension"
39+
)
40+
41+
42+
class Compiler(unix.Compiler):
43+
"""Handles the Cygwin port of the GNU C compiler to Windows."""
44+
45+
compiler_type = 'cygwin'
46+
obj_extension = ".o"
47+
static_lib_extension = ".a"
48+
shared_lib_extension = ".dll.a"
49+
dylib_lib_extension = ".dll"
50+
static_lib_format = "lib%s%s"
51+
shared_lib_format = "lib%s%s"
52+
dylib_lib_format = "cyg%s%s"
53+
exe_extension = ".exe"
54+
55+
def __init__(self, verbose=False, dry_run=False, force=False):
56+
super().__init__(verbose, dry_run, force)
57+
58+
status, details = check_config_h()
59+
self.debug_print(f"Python's GCC status: {status} (details: {details})")
60+
if status is not CONFIG_H_OK:
61+
self.warn(
62+
"Python's pyconfig.h doesn't seem to support your compiler. "
63+
f"Reason: {details}. "
64+
"Compiling may fail because of undefined preprocessor macros."
65+
)
66+
67+
self.cc, self.cxx = get_config_vars('CC', 'CXX')
68+
69+
# Override 'CC' and 'CXX' environment variables for
70+
# building using MINGW compiler for MSVC python.
71+
self.cc = os.environ.get('CC', self.cc or 'gcc')
72+
self.cxx = os.environ.get('CXX', self.cxx or 'g++')
73+
74+
self.linker_dll = self.cc
75+
self.linker_dll_cxx = self.cxx
76+
shared_option = "-shared"
77+
78+
self.set_executables(
79+
compiler=f'{self.cc} -mcygwin -O -Wall',
80+
compiler_so=f'{self.cc} -mcygwin -mdll -O -Wall',
81+
compiler_cxx=f'{self.cxx} -mcygwin -O -Wall',
82+
compiler_so_cxx=f'{self.cxx} -mcygwin -mdll -O -Wall',
83+
linker_exe=f'{self.cc} -mcygwin',
84+
linker_so=f'{self.linker_dll} -mcygwin {shared_option}',
85+
linker_exe_cxx=f'{self.cxx} -mcygwin',
86+
linker_so_cxx=f'{self.linker_dll_cxx} -mcygwin {shared_option}',
87+
)
88+
89+
self.dll_libraries = get_msvcr()
90+
91+
@property
92+
def gcc_version(self):
93+
# Older numpy depended on this existing to check for ancient
94+
# gcc versions. This doesn't make much sense with clang etc so
95+
# just hardcode to something recent.
96+
# https://github.com/numpy/numpy/pull/20333
97+
warnings.warn(
98+
"gcc_version attribute of CygwinCCompiler is deprecated. "
99+
"Instead of returning actual gcc version a fixed value 11.2.0 is returned.",
100+
DeprecationWarning,
101+
stacklevel=2,
102+
)
103+
with suppress_known_deprecation():
104+
return LooseVersion("11.2.0")
105+
106+
def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts):
107+
"""Compiles the source by spawning GCC and windres if needed."""
108+
if ext in ('.rc', '.res'):
109+
# gcc needs '.res' and '.rc' compiled to object files !!!
110+
try:
111+
self.spawn(["windres", "-i", src, "-o", obj])
112+
except DistutilsExecError as msg:
113+
raise CompileError(msg)
114+
else: # for other files use the C-compiler
115+
try:
116+
if self.detect_language(src) == 'c++':
117+
self.spawn(
118+
self.compiler_so_cxx
119+
+ cc_args
120+
+ [src, '-o', obj]
121+
+ extra_postargs
122+
)
123+
else:
124+
self.spawn(
125+
self.compiler_so + cc_args + [src, '-o', obj] + extra_postargs
126+
)
127+
except DistutilsExecError as msg:
128+
raise CompileError(msg)
129+
130+
def link(
131+
self,
132+
target_desc,
133+
objects,
134+
output_filename,
135+
output_dir=None,
136+
libraries=None,
137+
library_dirs=None,
138+
runtime_library_dirs=None,
139+
export_symbols=None,
140+
debug=False,
141+
extra_preargs=None,
142+
extra_postargs=None,
143+
build_temp=None,
144+
target_lang=None,
145+
):
146+
"""Link the objects."""
147+
# use separate copies, so we can modify the lists
148+
extra_preargs = copy.copy(extra_preargs or [])
149+
libraries = copy.copy(libraries or [])
150+
objects = copy.copy(objects or [])
151+
152+
if runtime_library_dirs:
153+
self.warn(_runtime_library_dirs_msg)
154+
155+
# Additional libraries
156+
libraries.extend(self.dll_libraries)
157+
158+
# handle export symbols by creating a def-file
159+
# with executables this only works with gcc/ld as linker
160+
if (export_symbols is not None) and (
161+
target_desc != self.EXECUTABLE or self.linker_dll == "gcc"
162+
):
163+
# (The linker doesn't do anything if output is up-to-date.
164+
# So it would probably better to check if we really need this,
165+
# but for this we had to insert some unchanged parts of
166+
# UnixCCompiler, and this is not what we want.)
167+
168+
# we want to put some files in the same directory as the
169+
# object files are, build_temp doesn't help much
170+
# where are the object files
171+
temp_dir = os.path.dirname(objects[0])
172+
# name of dll to give the helper files the same base name
173+
(dll_name, dll_extension) = os.path.splitext(
174+
os.path.basename(output_filename)
175+
)
176+
177+
# generate the filenames for these files
178+
def_file = os.path.join(temp_dir, dll_name + ".def")
179+
180+
# Generate .def file
181+
contents = [f"LIBRARY {os.path.basename(output_filename)}", "EXPORTS"]
182+
contents.extend(export_symbols)
183+
self.execute(write_file, (def_file, contents), f"writing {def_file}")
184+
185+
# next add options for def-file
186+
187+
# for gcc/ld the def-file is specified as any object files
188+
objects.append(def_file)
189+
190+
# end: if ((export_symbols is not None) and
191+
# (target_desc != self.EXECUTABLE or self.linker_dll == "gcc")):
192+
193+
# who wants symbols and a many times larger output file
194+
# should explicitly switch the debug mode on
195+
# otherwise we let ld strip the output file
196+
# (On my machine: 10KiB < stripped_file < ??100KiB
197+
# unstripped_file = stripped_file + XXX KiB
198+
# ( XXX=254 for a typical python extension))
199+
if not debug:
200+
extra_preargs.append("-s")
201+
202+
super().link(
203+
target_desc,
204+
objects,
205+
output_filename,
206+
output_dir,
207+
libraries,
208+
library_dirs,
209+
runtime_library_dirs,
210+
None, # export_symbols, we do this in our def-file
211+
debug,
212+
extra_preargs,
213+
extra_postargs,
214+
build_temp,
215+
target_lang,
216+
)
217+
218+
def runtime_library_dir_option(self, dir):
219+
# cygwin doesn't support rpath. While in theory we could error
220+
# out like MSVC does, code might expect it to work like on Unix, so
221+
# just warn and hope for the best.
222+
self.warn(_runtime_library_dirs_msg)
223+
return []
224+
225+
# -- Miscellaneous methods -----------------------------------------
226+
227+
def _make_out_path(self, output_dir, strip_dir, src_name):
228+
# use normcase to make sure '.rc' is really '.rc' and not '.RC'
229+
norm_src_name = os.path.normcase(src_name)
230+
return super()._make_out_path(output_dir, strip_dir, norm_src_name)
231+
232+
@property
233+
def out_extensions(self):
234+
"""
235+
Add support for rc and res files.
236+
"""
237+
return {
238+
**super().out_extensions,
239+
**{ext: ext + self.obj_extension for ext in ('.res', '.rc')},
240+
}
241+
242+
243+
# the same as cygwin plus some additional parameters
244+
class MinGW32Compiler(Compiler):
245+
"""Handles the Mingw32 port of the GNU C compiler to Windows."""
246+
247+
compiler_type = 'mingw32'
248+
249+
def __init__(self, verbose=False, dry_run=False, force=False):
250+
super().__init__(verbose, dry_run, force)
251+
252+
shared_option = "-shared"
253+
254+
if is_cygwincc(self.cc):
255+
raise Error('Cygwin gcc cannot be used with --compiler=mingw32')
256+
257+
self.set_executables(
258+
compiler=f'{self.cc} -O -Wall',
259+
compiler_so=f'{self.cc} -shared -O -Wall',
260+
compiler_so_cxx=f'{self.cxx} -shared -O -Wall',
261+
compiler_cxx=f'{self.cxx} -O -Wall',
262+
linker_exe=f'{self.cc}',
263+
linker_so=f'{self.linker_dll} {shared_option}',
264+
linker_exe_cxx=f'{self.cxx}',
265+
linker_so_cxx=f'{self.linker_dll_cxx} {shared_option}',
266+
)
267+
268+
def runtime_library_dir_option(self, dir):
269+
raise DistutilsPlatformError(_runtime_library_dirs_msg)
270+
271+
272+
# Because these compilers aren't configured in Python's pyconfig.h file by
273+
# default, we should at least warn the user if he is using an unmodified
274+
# version.
275+
276+
CONFIG_H_OK = "ok"
277+
CONFIG_H_NOTOK = "not ok"
278+
CONFIG_H_UNCERTAIN = "uncertain"
279+
280+
281+
def check_config_h():
282+
"""Check if the current Python installation appears amenable to building
283+
extensions with GCC.
284+
285+
Returns a tuple (status, details), where 'status' is one of the following
286+
constants:
287+
288+
- CONFIG_H_OK: all is well, go ahead and compile
289+
- CONFIG_H_NOTOK: doesn't look good
290+
- CONFIG_H_UNCERTAIN: not sure -- unable to read pyconfig.h
291+
292+
'details' is a human-readable string explaining the situation.
293+
294+
Note there are two ways to conclude "OK": either 'sys.version' contains
295+
the string "GCC" (implying that this Python was built with GCC), or the
296+
installed "pyconfig.h" contains the string "__GNUC__".
297+
"""
298+
299+
# XXX since this function also checks sys.version, it's not strictly a
300+
# "pyconfig.h" check -- should probably be renamed...
301+
302+
from distutils import sysconfig
303+
304+
# if sys.version contains GCC then python was compiled with GCC, and the
305+
# pyconfig.h file should be OK
306+
if "GCC" in sys.version:
307+
return CONFIG_H_OK, "sys.version mentions 'GCC'"
308+
309+
# Clang would also work
310+
if "Clang" in sys.version:
311+
return CONFIG_H_OK, "sys.version mentions 'Clang'"
312+
313+
# let's see if __GNUC__ is mentioned in python.h
314+
fn = sysconfig.get_config_h_filename()
315+
try:
316+
config_h = pathlib.Path(fn).read_text(encoding='utf-8')
317+
except OSError as exc:
318+
return (CONFIG_H_UNCERTAIN, f"couldn't read '{fn}': {exc.strerror}")
319+
else:
320+
substring = '__GNUC__'
321+
if substring in config_h:
322+
code = CONFIG_H_OK
323+
mention_inflected = 'mentions'
324+
else:
325+
code = CONFIG_H_NOTOK
326+
mention_inflected = 'does not mention'
327+
return code, f"{fn!r} {mention_inflected} {substring!r}"
328+
329+
330+
def is_cygwincc(cc):
331+
"""Try to determine if the compiler that would be used is from cygwin."""
332+
out_string = check_output(shlex.split(cc) + ['-dumpmachine'])
333+
return out_string.strip().endswith(b'cygwin')
334+
335+
336+
get_versions = None
337+
"""
338+
A stand-in for the previous get_versions() function to prevent failures
339+
when monkeypatched. See pypa/setuptools#2969.
340+
"""

distutils/compilers/C/errors.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
class Error(Exception):
2+
"""Some compile/link operation failed."""
3+
4+
5+
class PreprocessError(Error):
6+
"""Failure to preprocess one or more C/C++ files."""
7+
8+
9+
class CompileError(Error):
10+
"""Failure to compile one or more C/C++ source files."""
11+
12+
13+
class LibError(Error):
14+
"""Failure to create a static library from one or more C/C++ object
15+
files."""
16+
17+
18+
class LinkError(Error):
19+
"""Failure to link one or more C/C++ object files into an executable
20+
or shared library file."""
21+
22+
23+
class UnknownFileType(Error):
24+
"""Attempt to process an unknown file type."""

0 commit comments

Comments
 (0)