【问题标题】:How to tell which specific compiler will be invoked for a Python C extension: GCC or Clang?如何判断将为 Python C 扩展调用哪个特定编译器:GCC 还是 Clang?
【发布时间】:2015-04-22 20:46:29
【问题描述】:

我有一个 Python C++ 扩展,在 OS X 上使用 Clang 编译时需要以下编译标志:

CPPFLAGS='-std=c++11 -stdlib=libc++ -mmacosx-version-min=10.8'
LDFLAGS='-lc++'

在我的setup.py 中检测 OS X 非常简单。我可以这样做:

if sys.prefix == 'darwin':
    compile_args.append(['-mmacosx-version-min=10.8', '-stdlib=libc++'])
    link_args.append('-lc++')

(请参阅here 了解完整上下文)

但是,在 GCC 上,这个编译标志是无效的。因此,如果我以这种方式编写 setup.py,如果有人尝试在 OS X 上使用 GCC,编译将失败。

GCC 和 Clang 支持不同的编译器标志。因此,我需要知道将调用哪个编译器,以便发送不同的标志。在setup.py中检测编译器的正确方法是什么?

编辑 1:
请注意,编译错误不会引发 Python 异常:

$ 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
$

【问题讨论】:

  • 当您向 GCC 传递一个错误标志时,会发生什么异常(在您的 Python 脚本中)?正确的方法是try: send_flags_for_clang() except ThatException: send_flags_for_gcc()。
  • 查看编辑 --- 未引发异常。

标签: python setuptools setup.py distutils compiler-flags


【解决方案1】:

我遇到了您的问题,因为我需要相同类型的开关。此外,在我的情况下,sys.prefix 并不是很好,因为无论平台如何,clang 的标志都是如此。

我不确定它是否完美,但这是最适合我的。因此,我检查是否设置了 CC 变量;如果没有,我会检查我猜distutils 的位置。

欢迎任何更好的解决方案!

import os
import distutils

try:
    if os.environ['CC'] == "clang":
        clang = True
except KeyError:
    clang = False

if clang or distutils.sysconfig_get_config_vars()['CC'] == 'clang':
    try:
        _ = os.environ['CFLAGS']
    except KeyError:
        os.environ['CFLAGS'] = ""
    os.environ['CFLAGS'] += " -Wno-unused-function"
    os.environ['CFLAGS'] += " -Wno-int-conversion"
    os.environ['CFLAGS'] += " -Wno-incompatible-pointer-types

脾气暴躁的人注意:我很想使用extra_compile_args 选项,但它会将标志放在clang 编译命令中的错误位置。

【讨论】:

【解决方案2】:

将以下代码添加到您的setup.py。它显式检测编译器接受哪些标志,然后只添加那些。

# 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...
)

has_flag 方法取自这个 pybind11 示例。 https://github.com/pybind/python_example

【讨论】:

    【解决方案3】:

    这是一个跨编译器和跨平台的解决方案:

    from setuptools import setup
    from setuptools.command.build_ext import build_ext
    
    
    class build_ext_ex(build_ext):
    
        extra_args = {
            'extension_name': {
                'clang': (
                    ['-std=c++11', '-stdlib=libc++', '-mmacosx-version-min=10.8'],
                    ['-lc++']
                )
            }
        }
    
        def build_extension(self, ext):
            extra_args = self.extra_args.get(ext.name)
            if extra_args is not None:
                # only Unix compilers and their ports have `compiler_so`
                compiler_cmd = getattr(self.compiler, 'compiler_so', None)
                # account for absolute path and Windows version
                if compiler_cmd is not None and 'clang' in compiler_cmd[0]:
                    cname = 'clang'
                else:
                    cname = self.compiler.compiler_type
    
                extra_args = extra_args.get(cname)
                if extra_args is not None:
                    ext.extra_compile_args = extra_args[0]
                    ext.extra_link_args    = extra_args[1]
    
            build_ext.build_extension(self, ext)
    
    
    setup(
        ...
        cmdclass = {'build_ext': build_ext_ex},
        ...
    )
    

    ...以及支持的编译器类型列表(由setup.py build_ext --help-compiler 返回):

    --compiler=bcpp     Borland C++ Compiler
    --compiler=cygwin   Cygwin port of GNU C Compiler for Win32
    --compiler=mingw32  Mingw32 port of GNU C Compiler for Win32
    --compiler=msvc     Microsoft Visual C++
    --compiler=unix     standard UNIX-style compiler
    

    如果您遇到与@xoolive 相同的问题,您可以改写build_extensions(),并将选项分别附加到self.compiler.compiler_so 和self.compiler.linker_so 的末尾。

    【讨论】:

      猜你喜欢
      • 2017-08-06
      • 1970-01-01
      • 2017-01-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-02-08
      • 2011-07-04
      • 1970-01-01
      相关资源
      最近更新 更多