summaryrefslogtreecommitdiff
path: root/lib/spack/spack/analyzers/libabigail.py
blob: 92e2faee0ef9fc8b84bed15b7b946598d20c6a9c (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
# Copyright 2013-2022 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 llnl.util.tty as tty

import spack
import spack.binary_distribution
import spack.bootstrap
import spack.error
import spack.hooks
import spack.monitor
import spack.package_base
import spack.repo
import spack.util.executable

from .analyzer_base import AnalyzerBase


class Libabigail(AnalyzerBase):

    name = "libabigail"
    outfile = "spack-analyzer-libabigail.json"
    description = "Application Binary Interface (ABI) features for objects"

    def __init__(self, spec, dirname=None):
        """
        init for an analyzer ensures we have all needed dependencies.

        For the libabigail analyzer, this means Libabigail.
        Since the output for libabigail is one file per object, we communicate
        with the monitor multiple times.
        """
        super(Libabigail, self).__init__(spec, dirname)

        # This doesn't seem to work to import on the module level
        tty.debug("Preparing to use Libabigail, will install if missing.")

        with spack.bootstrap.ensure_bootstrap_configuration():
            # libabigail won't install lib/bin/share without docs
            spec = spack.spec.Spec("libabigail+docs")
            spack.bootstrap.ensure_executables_in_path_or_raise(
                ["abidw"], abstract_spec=spec
            )
            self.abidw = spack.util.executable.which('abidw')

    def run(self):
        """
        Run libabigail, and save results to filename.

        This run function differs in that we write as we generate and then
        return a dict with the analyzer name as the key, and the value of a
        dict of results, where the key is the object name, and the value is
        the output file written to.
        """
        manifest = spack.binary_distribution.get_buildfile_manifest(self.spec)

        # This result will store a path to each file
        result = {}

        # Generate an output file for each binary or object
        for obj in manifest.get("binary_to_relocate_fullpath", []):

            # We want to preserve the path in the install directory in case
            # a library has an equivalenly named lib or executable, for example
            outdir = os.path.dirname(obj.replace(self.spec.package.prefix,
                                     '').strip(os.path.sep))
            outfile = "spack-analyzer-libabigail-%s.xml" % os.path.basename(obj)
            outfile = os.path.join(self.output_dir, outdir, outfile)
            outdir = os.path.dirname(outfile)

            # Create the output directory
            if not os.path.exists(outdir):
                os.makedirs(outdir)

            # Sometimes libabigail segfaults and dumps
            try:
                self.abidw(obj, "--out-file", outfile)
                result[obj] = outfile
                tty.info("Writing result to %s" % outfile)
            except spack.error.SpackError:
                tty.warn("Issue running abidw for %s" % obj)

        return {self.name: result}

    def save_result(self, result, overwrite=False):
        """
        Read saved ABI results and upload to monitor server.

        ABI results are saved to individual files, so each one needs to be
        read and uploaded. Result here should be the lookup generated in run(),
        the key is the analyzer name, and each value is the result file.
        We currently upload the entire xml as text because libabigail can't
        easily read gzipped xml, but this will be updated when it can.
        """
        if not spack.monitor.cli:
            return

        name = self.spec.package.name

        for obj, filename in result.get(self.name, {}).items():

            # Don't include the prefix
            rel_path = obj.replace(self.spec.prefix + os.path.sep, "")

            # We've already saved the results to file during run
            content = spack.monitor.read_file(filename)

            # A result needs an analyzer, value or binary_value, and name
            data = {"value": content, "install_file": rel_path, "name": "abidw-xml"}
            tty.info("Sending result for %s %s to monitor." % (name, rel_path))
            spack.hooks.on_analyzer_save(self.spec.package, {"libabigail": [data]})