summaryrefslogblamecommitdiff
path: root/var/spack/repos/builtin/packages/gcc-runtime/package.py
blob: 085042b8ceb02c7f119d7139d66ea3c800e14138 (plain) (tree)
1
                                                                         




















                                                                         

                      





















                                                      











































































































                                                                                                  
# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)

import os
import re

from macholib import MachO, mach_o

from llnl.util import tty

from spack.package import *
from spack.util.elf import parse_elf


class GccRuntime(Package):
    """Package for GCC compiler runtime libraries"""

    homepage = "https://gcc.gnu.org"
    has_code = False

    tags = ["runtime"]

    maintainers("haampie")

    license("GPL-3.0-or-later WITH GCC-exception-3.1")

    requires("%gcc")

    LIBRARIES = [
        "asan",
        "atomic",
        "gcc_s",
        "gfortran",
        "gomp",
        "hwasan",
        "itm",
        "lsan",
        "quadmath",
        "ssp",
        "stdc++",
        "tsan",
        "ubsan",
    ]

    def install(self, spec, prefix):
        if spec.platform in ["linux", "cray", "freebsd"]:
            libraries = self._get_libraries_elf()
        elif spec.platform == "darwin":
            libraries = self._get_libraries_macho()
        else:
            raise RuntimeError("Unsupported platform")

        mkdir(prefix.lib)

        if not libraries:
            tty.warn("Could not detect any shared GCC runtime libraries")
            return

        for path, name in libraries:
            install(path, os.path.join(prefix.lib, name))

    def _get_libraries_elf(self):
        """Get the GCC runtime libraries for ELF binaries"""
        cc = Executable(self.compiler.cc)
        lib_regex = re.compile(rb"\blib[a-z-_]+\.so\.\d+\b")
        path_and_install_name = []

        for name in self.LIBRARIES:
            # Look for the dynamic library that gcc would use to link,
            # that is with .so extension and without abi suffix.
            path = cc(f"-print-file-name=lib{name}.so", output=str).strip()

            # gcc reports an absolute path on success
            if not os.path.isabs(path):
                continue

            # Now there are two options:
            # 1. the file is an ELF file
            # 2. the file is a linker script referencing the actual library
            with open(path, "rb") as f:
                try:
                    # Try to parse as an ELF file
                    soname = parse_elf(f, dynamic_section=True).dt_soname_str.decode("utf-8")
                except Exception:
                    # On failure try to "parse" as ld script; the actual
                    # library needs to be mentioned by filename.
                    f.seek(0)
                    script_matches = lib_regex.findall(f.read())
                    if len(script_matches) != 1:
                        continue
                    soname = script_matches[0].decode("utf-8")

            # Now locate and install the runtime library
            runtime_path = cc(f"-print-file-name={soname}", output=str).strip()

            if not os.path.isabs(runtime_path):
                continue

            path_and_install_name.append((runtime_path, soname))

        return path_and_install_name

    def _get_libraries_macho(self):
        """Same as _get_libraries_elf but for Mach-O binaries"""
        cc = Executable(self.compiler.cc)
        path_and_install_name = []

        for name in self.LIBRARIES:
            if name == "gcc_s":
                # On darwin, libgcc_s is versioned and can't be linked as -lgcc_s,
                # but needs a suffix we don't know, so we parse it from the link line.
                match = re.search(
                    r"\s-l(gcc_s\.[0-9.]+)\s", cc("-xc", "-", "-shared-libgcc", "-###", error=str)
                )
                if match is None:
                    continue
                name = match.group(1)

            path = cc(f"-print-file-name=lib{name}.dylib", output=str).strip()

            if not os.path.isabs(path):
                continue

            macho = MachO.MachO(path)

            # Get the LC_ID_DYLIB load command
            for load_command, _, data in macho.headers[-1].commands:
                if load_command.cmd == mach_o.LC_ID_DYLIB:
                    # Strip off @rpath/ prefix, or even an absolute path.
                    dylib_name = os.path.basename(data.rstrip(b"\x00").decode())
                    break
            else:
                continue

            # Locate by dylib name
            runtime_path = cc(f"-print-file-name={dylib_name}", output=str).strip()

            if not os.path.isabs(runtime_path):
                continue

            path_and_install_name.append((runtime_path, dylib_name))

        return path_and_install_name

    @property
    def libs(self):
        # Currently these libs are not linkable with -l, they all have a suffix.
        return LibraryList([])

    @property
    def headers(self):
        return HeaderList([])