I have a Python C++ extension that requires the following compilation flags when compiled using Clang on OS X:
CPPFLAGS='-std=c++11 -stdlib=libc++ -mmacosx-version-min=10.8'
LDFLAGS='-lc++'
Detecting OS X in my setup.py
is easy enough. I can do this:
if sys.prefix == 'darwin':
compile_args.append(['-mmacosx-version-min=10.8', '-stdlib=libc++'])
link_args.append('-lc++')
(See here for full context)
However, on GCC this compilation flag is invalid. So, compilation will fail if someone will try to use GCC on OS X if I write the setup.py
this way.
GCC and Clang support different compiler flags. So, I need to know which compiler will be invoked, so I can send different flags. What is the right way to detect the compiler in the setup.py
?
Edit 1:
Note that no Python exception is raised for compilation errors:
$ python setup.py build_ext --inplace
running build_ext
building 'spacy.strings' extension
gcc -pthread -fno-strict-aliasing -g -O2 -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -fPIC -c spacy/strings.cpp -o build/temp.linux-x86_64-2.7/spacy/strings.o -O3 -mmacosx-version-min=10.8 -stdlib=libc++
gcc: error: unrecognized command line option ‘-mmacosx-version-min=10.8’
gcc: error: unrecognized command line option ‘-stdlib=libc++’
error: command 'gcc' failed with exit status 1
$
(GNU C is a language, GCC is a compiler for that language.Clang defines __GNUC__ / __GNUC_MINOR__ / __GNUC_PATCHLEVEL__ according to the version of gcc that it claims full compatibility with.
Clang is much faster and uses far less memory than GCC. Clang aims to provide extremely clear and concise diagnostics (error and warning messages), and includes support for expressive diagnostics. GCC's warnings are sometimes acceptable, but are often confusing and it does not support expressive diagnostics.
Note that the Python bindings are part of the source distribution of Clang.
Clang is compatible with GCC. Its command-line interface shares many of GCC's flags and options. Clang implements many GNU language extensions and compiler intrinsics, some of which are purely for compatibility.
Add the following code to your setup.py
. It explicitly detects which flags are accepted by the compiler and then only those are added.
# check whether compiler supports a flag
def has_flag(compiler, flagname):
import tempfile
from distutils.errors import CompileError
with tempfile.NamedTemporaryFile('w', suffix='.cpp') as f:
f.write('int main (int argc, char **argv) { return 0; }')
try:
compiler.compile([f.name], extra_postargs=[flagname])
except CompileError:
return False
return True
# filter flags, returns list of accepted flags
def flag_filter(compiler, *flags):
result = []
for flag in flags:
if has_flag(compiler, flag):
result.append(flag)
return result
class BuildExt(build_ext):
# these flags are not checked and always added
compile_flags = {"msvc": ['/EHsc'], "unix": ["-std=c++11"]}
def build_extensions(self):
ct = self.compiler.compiler_type
opts = self.compile_flags.get(ct, [])
if ct == 'unix':
# only add flags which pass the flag_filter
opts += flag_filter(self.compiler,
'-fvisibility=hidden', '-stdlib=libc++', '-std=c++14')
for ext in self.extensions:
ext.extra_compile_args = opts
build_ext.build_extensions(self)
setup(
cmdclass=dict(build_ext=BuildExt),
# other options...
)
The has_flag
method was taken from this pybind11 example.
https://github.com/pybind/python_example
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With