Source code for programl.util.py.cc_system_includes

# Get C/C++ compiler includes. Code from CompilerGym, available at:
#
#     https://github.com/facebookresearch/CompilerGym
#
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
import os
import subprocess
import sys
import tempfile
from pathlib import Path
from typing import Iterable, List


def _communicate(process, input=None, timeout=None):
    """subprocess.communicate() which kills subprocess on timeout."""
    try:
        return process.communicate(input=input, timeout=timeout)
    except subprocess.TimeoutExpired:
        # kill() was added in Python 3.7.
        if sys.version_info >= (3, 7, 0):
            process.kill()
        else:
            process.terminate()
        raise


def _get_system_includes() -> Iterable[Path]:
    """Run the system compiler in verbose mode on a dummy input to get the
    system header search path.
    """
    system_compiler = os.environ.get("CXX", "c++")
    # Create a temporary directory to write the compiled 'binary' to, since
    # GNU assembler does not support piping to stdout.
    with tempfile.TemporaryDirectory() as d:
        process = subprocess.Popen(
            [system_compiler, "-xc++", "-v", "-c", "-", "-o", str(Path(d) / "a.out")],
            stdout=subprocess.DEVNULL,
            stderr=subprocess.PIPE,
            stdin=subprocess.PIPE,
            universal_newlines=True,
        )
        _, stderr = _communicate(process, input="", timeout=30)
    if process.returncode:
        raise OSError(
            f"Failed to invoke {system_compiler}. "
            f"Is there a working system compiler?\n"
            f"Error: {stderr.strip()}"
        )

    # Parse the compiler output that matches the conventional output format
    # used by clang and GCC:
    #
    #     #include <...> search starts here:
    #     /path/1
    #     /path/2
    #     End of search list
    in_search_list = False
    for line in stderr.split("\n"):
        if in_search_list and line.startswith("End of search list"):
            break
        elif in_search_list:
            # We have an include path to return.
            path = Path(line.strip())
            yield path
            # Compatibility fix for compiling benchmark sources which use the
            # '#include <endian.h>' header, which on macOS is located in a
            # 'machine/endian.h' directory.
            if (path / "machine").is_dir():
                yield path / "machine"
        elif line.startswith("#include <...> search starts here:"):
            in_search_list = True
    else:
        raise OSError(
            f"Failed to parse '#include <...>' search paths from {system_compiler}:\n"
            f"{stderr.strip()}"
        )


# Memoized search paths. Call get_system_includes() to access them.
_SYSTEM_INCLUDES = None


[docs]def get_system_includes() -> List[Path]: """Determine the system include paths for C/C++ compilation jobs. This uses the system compiler to determine the search paths for C/C++ system headers. By default, :code:`c++` is invoked. This can be overridden by setting :code:`os.environ["CXX"]`. :return: A list of paths to system header directories. :raises OSError: If the compiler fails, or if the search paths cannot be determined. """ # Memoize the system includes paths. global _SYSTEM_INCLUDES if _SYSTEM_INCLUDES is None: _SYSTEM_INCLUDES = list(_get_system_includes()) return _SYSTEM_INCLUDES