使用distutils进行编译时禁用输出

时间:2011-08-10 22:51:54

标签: python distutils

我有一个setup.py脚本需要探测编译器以获取某些内容,例如对TR1的支持,windows.h的存在(添加NOMINMAX定义)等等。我通过创建一个简单的程序来执行这些检查。尝试使用Distutils的Compiler类编译它。存在/没有错误是我的答案。

这很好用,但这意味着编译器的丑陋错误消息会打印到控制台。有没有办法在手动调用compile函数时抑制错误消息?

这是我的函数,它试图编译程序,现在通过将错误流传递给文件来消除错误消息(回答我自己的问题):

def see_if_compiles(program, include_dirs, define_macros):
    """ Try to compile the passed in program and report if it compiles successfully or not. """
    from distutils.ccompiler import new_compiler, CompileError
    from shutil import rmtree
    import tempfile
    import os

    try:
        tmpdir = tempfile.mkdtemp()
    except AttributeError:
        # Python 2.2 doesn't have mkdtemp().
        tmpdir = "compile_check_tempdir"
        try:
            os.mkdir(tmpdir)
        except OSError:
            print "Can't create temporary directory. Aborting."
            sys.exit()

    old = os.getcwd()

    os.chdir(tmpdir)

    # Write the program
    f = open('compiletest.cpp', 'w')
    f.write(program)
    f.close()

    # redirect the error stream to keep ugly compiler error messages off the command line
    devnull = open('errors.txt', 'w')
    oldstderr = os.dup(sys.stderr.fileno())
    os.dup2(devnull.fileno(), sys.stderr.fileno())
    #
    try:
        c = new_compiler()
        for macro in define_macros:
            c.define_macro(name=macro[0], value=macro[1])
        c.compile([f.name], include_dirs=include_dirs)
        success = True
    except CompileError:
        success = False
    # undo the error stream redirect
    os.dup2(oldstderr, sys.stderr.fileno())
    devnull.close()

    os.chdir(old)
    rmtree(tmpdir)
    return success

这是一个使用上述功能检查是否存在标题的函数。

def check_for_header(header, include_dirs, define_macros):
    """Check for the existence of a header file by creating a small program which includes it and see if it compiles."""
    program = "#include <%s>\n" % header
    sys.stdout.write("Checking for <%s>... " % header)
    success = see_if_compiles(program, include_dirs, define_macros)
    if (success):
        sys.stdout.write("OK\n");
    else:
        sys.stdout.write("Not found\n");
    return success

5 个答案:

答案 0 :(得分:5)

Zac的评论促使我看起来更多,我发现Mercurial的setup.py脚本有一个适合他的方法的工作方法。您不能只分配流,因为更改不会被编译器进程继承,但显然Python以dup2()的形式拥有我们的好朋友os.dup2()。这允许我们都知道和喜爱的相同操作系统级别的流诡计,这些诡计继承到子进程。

Mercurial的函数重定向到/ dev / null,但为了保持Windows兼容性,我只需重定向到文件然后将其删除。

Quoth Mercurial:

# simplified version of distutils.ccompiler.CCompiler.has_function
# that actually removes its temporary files.
def hasfunction(cc, funcname):
    tmpdir = tempfile.mkdtemp(prefix='hg-install-')
    devnull = oldstderr = None
    try:
        try:
            fname = os.path.join(tmpdir, 'funcname.c')
            f = open(fname, 'w')
            f.write('int main(void) {\n')
            f.write('    %s();\n' % funcname)
            f.write('}\n')
            f.close()
            # Redirect stderr to /dev/null to hide any error messages
            # from the compiler.
            # This will have to be changed if we ever have to check
            # for a function on Windows.
            devnull = open('/dev/null', 'w')
            oldstderr = os.dup(sys.stderr.fileno())
            os.dup2(devnull.fileno(), sys.stderr.fileno())
            objects = cc.compile([fname], output_dir=tmpdir)
            cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
        except:
            return False
        return True
    finally:
        if oldstderr is not None:
            os.dup2(oldstderr, sys.stderr.fileno())
        if devnull is not None:
            devnull.close()
        shutil.rmtree(tmpdir)

答案 1 :(得分:3)

这是我最近编写并发现有用的context manager,因为我在处理distutils.ccompiler.CCompiler.has_function时遇到与pymssql相同的问题。我打算使用你的方法(很好,感谢分享!)然后我认为可以用更少的代码完成,如果我使用context manager,它会更通用和灵活。这就是我想出的:

import contextlib


@contextlib.contextmanager
def stdchannel_redirected(stdchannel, dest_filename):
    """
    A context manager to temporarily redirect stdout or stderr

    e.g.:

    with stdchannel_redirected(sys.stderr, os.devnull):
        if compiler.has_function('clock_gettime', libraries=['rt']):
            libraries.append('rt')
    """

    try:
        oldstdchannel = os.dup(stdchannel.fileno())
        dest_file = open(dest_filename, 'w')
        os.dup2(dest_file.fileno(), stdchannel.fileno())

        yield
    finally:
        if oldstdchannel is not None:
            os.dup2(oldstdchannel, stdchannel.fileno())
        if dest_file is not None:
            dest_file.close()

我创建此内容的上下文位于this blog post。我觉得和你的几乎一样。

这使用我借给您的代码来进行重定向(例如:os.dup2等),但我将它包装在上下文管理器中,因此它更通用且可重用。

我在setup.py

中使用它
with stdchannel_redirected(sys.stderr, os.devnull):
    if compiler.has_function('clock_gettime', libraries=['rt']):
        libraries.append('rt')

答案 2 :(得分:2)

@Adam我只想指出Windows上有/ dev / null等价物。这是'NUL',但好的做法是从os.devnull

获取它

答案 3 :(得分:1)

我是编程和python的新手,所以如果这是一个愚蠢的建议,请忽略它,但是你不能只是将错误信息重新路由到文本文件而不是屏幕/交互窗口/其他什么?

我很确定我在某处读到了你可以做的事情

error = open('yourerrorlog.txt','w')
sys.stderr = error

再一次,对不起,我可能会重复你已经知道的事情,但如果问题是你想通过另一个功能(自动)调用错误,并且在手动运行时没有错误,你不能只是添加一个关键字参数如compile(arg1,arg2,manual = True)然后在你的“except:”下添加

if manual == False: print errors to console/interactive window
else: print to error  

然后当它被程序调用而不是手动调用时,只需用compile(arg1,arg2,manual = False)调用它,以便重定向到文件。

答案 4 :(得分:0)

以安静模式运行有帮助吗? setup.py -q build

不直接回答您的问题,但与您的用例有关:distutils中有一个config命令,它被设计为子类并用于检查C功能。它还没有记录,你必须阅读来源。