使用 distutils 编译时禁用输出

发布于 2024-11-29 03:10:13 字数 2227 浏览 0 评论 0 原文

我有一个 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

I have a setup.py script which needs to probe the compiler for certain things like the support for TR1, the presence of windows.h (to add NOMINMAX define), etc. I do these checks by creating a simple program and trying to compile it with Distutils' Compiler class. The presence/lack of errors is my answer.

This works well, but it means that the compiler's ugly error messages get printed to the console. Is there a way to suppress error messages for when the compile function is called manually?

Here is my function which tries to compile the program, which now DOES eliminate the error messages by piping the error stream to a file (answered my own question):

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

Here is a function which uses the above to check for the presence of a header.

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

暖心男生 2024-12-06 03:10:13

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

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

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)

Zac's comment spurred me to look a bit more and I found that Mercurial's setup.py script has a working method for his approach. You can't just assign the stream because the change won't get inherited by the compiler process, but apparently Python has our good friend dup2() in the form of os.dup2(). That allows the same OS-level stream shenanigans that we all know and love, which do get inherited to child processes.

Mercurial's function redirects to /dev/null, but to keep Windows compatibility I just redirect to a file then delete it.

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)
哭了丶谁疼 2024-12-06 03:10:13

这是我最近编写的一个上下文管理器,它很有用,因为我有与 distutils.ccompiler.CCompiler.has_function 在处理 pymssql。我本来打算使用你的方法(很好,感谢分享!),但后来我想,如果我使用 上下文管理器。这是我想到的:

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()

我创建此内容的上下文位于 这篇博文。我想和你的差不多。

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

我在 setup.py 中像这样使用它:

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

Here's a context manager that I recently wrote and found useful, because I was having the same problem with distutils.ccompiler.CCompiler.has_function while working on pymssql. I was going to use your approach (nice, thanks for sharing!) but then I thought that it could be done with less code and would be more general and flexible if I used a context manager. Here's what I came up with:

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()

The context for why I created this is at this blog post. Pretty much the same as yours I think.

This uses code that I borrowed from you to do the redirection (e.g.: os.dup2, etc.), but I wrapped it in a context manager so it's more general and reusable.

I use it like this in a setup.py:

with stdchannel_redirected(sys.stderr, os.devnull):
    if compiler.has_function('clock_gettime', libraries=['rt']):
        libraries.append('rt')
瀞厅☆埖开 2024-12-06 03:10:13

@Adam 我只是想指出 Windows 上有 /dev/null 等效项。它是“NUL”,但好的做法是从 os.devnull 获取它

@Adam I just want to point out that there is /dev/null equivalent on Windows. It's 'NUL' but good practice is to get it from os.devnull

笨死的猪 2024-12-06 03:10:13

我对编程和Python还很陌生,所以如果这是一个愚蠢的建议,请忽略它,但是你不能将错误消息重新路由到文本文件而不是屏幕/交互式窗口/其他什么吗?

我很确定我在某处读过您可以做类似的事情

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

,抱歉,我可能会重复您已经知道的事情,但如果问题是您希望在由另一个函数(自动)调用时出现错误,并且在运行时没有错误手册,你不能只添加一个像compile(arg1, arg2, manual=True)这样的关键字参数,然后在你的“除了:”下添加

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

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

I'm pretty new to programming and python, so disregard this if it's a stupid suggestion, but can't you just reroute the error messages to a text file instead of the screen/interactive window/whatever?

I'm pretty sure I read somewhere you can do something like

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

Again, sorry I'm probably repeating something you already know, but if the problem is you WANT the errors when it's called by another function (automated) and no errors when it's ran manual, can't you just add a keyword argument like compile(arg1, arg2, manual=True ) and then under your "except:" you add

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

Then when it's called by the program and not manually you just call it with compile(arg1,arg2, manual=False) so that it redirects to the file.

扬花落满肩 2024-12-06 03:10:13

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

不是对您的问题的直接答案,但与您的用例相关:distutils 中有一个配置命令,它被设计为子类化并用于检查 C 功能。它还没有记录,你必须阅读源代码。

Does running in quiet mode help at all? setup.py -q build

Not a direct answer to your question, but related to your use case: there is a config command in distutils that’s designed to be subclassed and used to check for C features. It’s not documented yet, you have to read the source.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文