summaryrefslogtreecommitdiff
path: root/lib/spack/spack/abi.py
blob: b57a1426dd3d9b9a0b4cf337521be7785146a82b (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
132
133
##############################################################################
# Copyright (c) 2013-2017, Lawrence Livermore National Security, LLC.
# Produced at the Lawrence Livermore National Laboratory.
#
# This file is part of Spack.
# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
# LLNL-CODE-647188
#
# For details, see https://github.com/llnl/spack
# Please also see the NOTICE and LICENSE files for our notice and the LGPL.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License (as
# published by the Free Software Foundation) version 2.1, February 1999.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
# conditions of the GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################

import os
import spack
import spack.spec
from spack.build_environment import dso_suffix
from spack.spec import CompilerSpec
from spack.util.executable import Executable, ProcessError
from spack.compilers.clang import Clang
from llnl.util.lang import memoized


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

    def architecture_compatible(self, parent, child):
        """Return true if parent and child have ABI compatible targets."""
        return not parent.architecture or not child.architecture or \
            parent.architecture == child.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"""
        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.readlink(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, cversion):
        """Returns true iff the intel version pversion and cversion
           are ABI compatible"""

        # Test major and minor versions.  Ignore build version.
        if (len(pversion.version) < 2 or len(cversion.version) < 2):
            return False
        return pversion.version[:2] == cversion.version[:2]

    def compiler_compatible(self, parent, child, **kwargs):
        """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 kwargs.get('loose', False):
            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.satisfies(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, parent, child, **kwargs):
        """Returns true iff a parent and child spec are ABI compatible"""
        loosematch = kwargs.get('loose', False)
        return self.architecture_compatible(parent, child) and \
            self.compiler_compatible(parent, child, loose=loosematch)