Source code for httpstan.build_ext
"""Lightly modified build_ext which captures stderr.
The customization of build_ext here is non-standard and confusing.
It does, however, work.
isort:skip_file
"""
import setuptools
import setuptools.command.build_ext as build_ext
import io
import logging
import os
import sys
import tempfile
from typing import IO, Any, List, TextIO
from httpstan.config import HTTPSTAN_DEBUG
[docs]def run_build_ext(extensions: List[setuptools.Extension], build_lib: str) -> str:
"""Configure and call `build_ext.run()`, capturing stderr.
Compiled extension module will be placed in `build_lib`.
All messages sent to stderr will be saved and returned. These
messages are typically messages from the compiler or linker.
"""
# utility functions for silencing compiler output
def _has_fileno(stream: TextIO) -> bool:
"""Returns whether the stream object has a working fileno()
Suggests whether _redirect_stderr is likely to work.
"""
try:
stream.fileno()
except (AttributeError, OSError, IOError, io.UnsupportedOperation): # pragma: no cover
return False
return True
def _redirect_stderr_to(stream: IO[Any]) -> int:
"""Redirect stderr for subprocesses to /dev/null.
Returns
-------
orig_stderr: copy of original stderr file descriptor
"""
sys.stderr.flush()
stderr_fileno = sys.stderr.fileno()
orig_stderr = os.dup(stderr_fileno)
os.dup2(stream.fileno(), stderr_fileno)
return orig_stderr
if HTTPSTAN_DEBUG: # pragma: no cover
logging.getLogger().setLevel(logging.DEBUG)
dist = setuptools.Distribution()
# Make sure build respects distutils configuration
dist.parse_config_files(dist.find_config_files()) # type: ignore
build_extension = build_ext.build_ext(dist) # type: ignore
build_extension.build_lib = build_lib
# silence stderr for compilation, if stderr is silenceable
stream = tempfile.TemporaryFile(prefix="httpstan_")
redirect_stderr = _has_fileno(sys.stderr) and not HTTPSTAN_DEBUG
compiler_output = ""
if redirect_stderr:
orig_stderr = _redirect_stderr_to(stream)
# NOTE: work-around for differences between setuptools and distutils. A bit of a hack.
# The sequence here is important. `finalize_options` needs to be called first.
# A cleaner approach would make a custom command, following instructions here:
# https://setuptools.pypa.io/en/latest/userguide/extension.html
build_extension.finalize_options()
for extension in extensions:
# WISHLIST: understand setuptools internals enough to know what this is
# and why setting this here is required when using setuptools but not
# when using distutils.
extension._needs_stub = False
build_extension.extensions = extensions
try:
build_extension.run()
finally:
if redirect_stderr:
stream.seek(0)
compiler_output = stream.read().decode()
stream.close()
# restore
os.dup2(orig_stderr, sys.stderr.fileno())
return compiler_output