summaryrefslogtreecommitdiff
path: root/lib/spack/spack/abi.py
blob: dd12d6dbafe48410fa502dd461b3b885f70158ae (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# Copyright 2013-2023 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

from llnl.util.lang import memoized

import spack.spec
import spack.version
from spack.compilers.clang import Clang
from spack.util.executable import Executable, ProcessError


class ABI:
    """This class provides methods to test ABI compatibility between specs.
    The current implementation is rather rough and could be improved."""

    def architecture_compatible(
        self, target: spack.spec.Spec, constraint: spack.spec.Spec
    ) -> bool:
        """Return true if architecture of target spec is ABI compatible
        to the architecture of constraint spec. If either the target
        or constraint specs have no architecture, target is also defined
        as architecture ABI compatible to constraint."""
        return (
            not target.architecture
            or not constraint.architecture
            or target.architecture.intersects(constraint.architecture)
        )

    @memoized
    def _gcc_get_libstdcxx_version(self, version):
        """Returns gcc ABI compatibility info by getting the library version of
        a compiler's libstdc++ or libgcc_s"""
        from spack.build_environment import dso_suffix

        spec = spack.spec.CompilerSpec("gcc", version)
        compilers = spack.compilers.compilers_for_spec(spec)
        if not compilers:
            return None
        compiler = compilers[0]
        rungcc = None
        libname = None
        output = None
        if compiler.cxx:
            rungcc = Executable(compiler.cxx)
            libname = "libstdc++." + dso_suffix
        elif compiler.cc:
            rungcc = Executable(compiler.cc)
            libname = "libgcc_s." + dso_suffix
        else:
            return None
        try:
            # Some gcc's are actually clang and don't respond properly to
            # --print-file-name (they just print the filename, not the
            # full path).  Ignore these and expect them to be handled as clang.
            if Clang.default_version(rungcc.exe[0]) != "unknown":
                return None

            output = rungcc("--print-file-name=%s" % libname, output=str)
        except ProcessError:
            return None
        if not output:
            return None
        libpath = os.path.realpath(output.strip())
        if not libpath:
            return None
        return os.path.basename(libpath)

    @memoized
    def _gcc_compiler_compare(self, pversion, cversion):
        """Returns true iff the gcc version pversion and cversion
        are ABI compatible."""
        plib = self._gcc_get_libstdcxx_version(pversion)
        clib = self._gcc_get_libstdcxx_version(cversion)
        if not plib or not clib:
            return False
        return plib == clib

    def _intel_compiler_compare(
        self, pversion: spack.version.ClosedOpenRange, cversion: spack.version.ClosedOpenRange
    ) -> bool:
        """Returns true iff the intel version pversion and cversion
        are ABI compatible"""

        # Test major and minor versions.  Ignore build version.
        pv = pversion.lo
        cv = cversion.lo
        return pv.up_to(2) == cv.up_to(2)

    def compiler_compatible(
        self, parent: spack.spec.Spec, child: spack.spec.Spec, loose: bool = False
    ) -> bool:
        """Return true if compilers for parent and child are ABI compatible."""
        if not parent.compiler or not child.compiler:
            return True

        if parent.compiler.name != child.compiler.name:
            # Different compiler families are assumed ABI incompatible
            return False

        if loose:
            return True

        # TODO: Can we move the specialized ABI matching stuff
        # TODO: into compiler classes?
        for pversion in parent.compiler.versions:
            for cversion in child.compiler.versions:
                # For a few compilers use specialized comparisons.
                # Otherwise match on version match.
                if pversion.intersects(cversion):
                    return True
                elif parent.compiler.name == "gcc" and self._gcc_compiler_compare(
                    pversion, cversion
                ):
                    return True
                elif parent.compiler.name == "intel" and self._intel_compiler_compare(
                    pversion, cversion
                ):
                    return True
        return False

    def compatible(
        self, target: spack.spec.Spec, constraint: spack.spec.Spec, loose: bool = False
    ) -> bool:
        """Returns true if target spec is ABI compatible to constraint spec"""
        return self.architecture_compatible(target, constraint) and self.compiler_compatible(
            target, constraint, loose=loose
        )