xref: /openbmc/openbmc/poky/meta/lib/oeqa/utils/metadata.py (revision 43a6b7c2a48b0cb1381af4d3192d22a12ead65f0)
1# Copyright (C) 2016 Intel Corporation
2#
3# SPDX-License-Identifier: MIT
4#
5# Functions to get metadata from the testing host used
6# for analytics of test results.
7
8from collections import OrderedDict
9from collections.abc import MutableMapping
10from xml.dom.minidom import parseString
11from xml.etree.ElementTree import Element, tostring
12
13from oe.lsb import get_os_release
14from oeqa.utils.commands import runCmd, get_bb_vars
15
16
17def metadata_from_bb():
18    """ Returns test's metadata as OrderedDict.
19
20        Data will be gathered using bitbake -e thanks to get_bb_vars.
21    """
22    metadata_config_vars = ('MACHINE', 'BB_NUMBER_THREADS', 'PARALLEL_MAKE')
23
24    info_dict = OrderedDict()
25    hostname = runCmd('hostname')
26    info_dict['hostname'] = hostname.output
27    data_dict = get_bb_vars()
28
29    # Distro information
30    info_dict['distro'] = {'id': data_dict.get('DISTRO', 'NODISTRO'),
31                                'version_id': data_dict.get('DISTRO_VERSION', 'NO_DISTRO_VERSION'),
32                                'pretty_name': '%s %s' % (data_dict.get('DISTRO', 'NODISTRO'), data_dict.get('DISTRO_VERSION', 'NO_DISTRO_VERSION'))}
33
34    # Host distro information
35    os_release = get_os_release()
36    if os_release:
37        info_dict['host_distro'] = OrderedDict()
38        for key in ('ID', 'VERSION_ID', 'PRETTY_NAME'):
39            if key in os_release:
40                info_dict['host_distro'][key.lower()] = os_release[key]
41
42    info_dict['layers'] = get_layers(data_dict['BBLAYERS'])
43    info_dict['bitbake'] = git_rev_info(os.path.dirname(bb.__file__))
44
45    info_dict['config'] = OrderedDict()
46    for var in sorted(metadata_config_vars):
47        info_dict['config'][var] = data_dict[var]
48    return info_dict
49
50def metadata_from_data_store(d):
51    """ Returns test's metadata as OrderedDict.
52
53        Data will be collected from the provided data store.
54    """
55    # TODO: Getting metadata from the data store would
56    # be useful when running within bitbake.
57    pass
58
59def git_rev_info(path):
60    """Get git revision information as a dict"""
61    info = OrderedDict()
62
63    try:
64        from git import Repo, InvalidGitRepositoryError, NoSuchPathError
65    except ImportError:
66        import subprocess
67        try:
68            info['branch'] = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"], cwd=path).decode('utf-8').strip()
69        except subprocess.CalledProcessError:
70            pass
71        try:
72            info['commit'] = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=path).decode('utf-8').strip()
73        except subprocess.CalledProcessError:
74            pass
75        try:
76            info['commit_count'] = int(subprocess.check_output(["git", "rev-list", "--count", "HEAD"], cwd=path).decode('utf-8').strip())
77        except subprocess.CalledProcessError:
78            pass
79        try:
80            info['commit_time'] = int(subprocess.check_output(["git", "show", "--no-patch", "--format=%ct", "HEAD"], cwd=path).decode('utf-8').strip())
81        except subprocess.CalledProcessError:
82            pass
83        return info
84    try:
85        repo = Repo(path, search_parent_directories=True)
86    except (InvalidGitRepositoryError, NoSuchPathError):
87        return info
88    info['commit'] = repo.head.commit.hexsha
89    info['commit_count'] = repo.head.commit.count()
90    info['commit_time'] = repo.head.commit.committed_date
91    try:
92        info['branch'] = repo.active_branch.name
93    except TypeError:
94        info['branch'] = '(nobranch)'
95    return info
96
97def get_layers(layers):
98    """Returns layer information in dict format"""
99    layer_dict = OrderedDict()
100    for layer in layers.split():
101        layer_name = os.path.basename(layer)
102        layer_dict[layer_name] = git_rev_info(layer)
103    return layer_dict
104
105def write_metadata_file(file_path, metadata):
106    """ Writes metadata to a XML file in directory. """
107
108    xml = dict_to_XML('metadata', metadata)
109    xml_doc = parseString(tostring(xml).decode('UTF-8'))
110    with open(file_path, 'w') as f:
111        f.write(xml_doc.toprettyxml())
112
113def dict_to_XML(tag, dictionary, **kwargs):
114    """ Return XML element converting dicts recursively. """
115
116    elem = Element(tag, **kwargs)
117    for key, val in dictionary.items():
118        if tag == 'layers':
119            child = (dict_to_XML('layer', val, name=key))
120        elif isinstance(val, MutableMapping):
121            child = (dict_to_XML(key, val))
122        else:
123            if tag == 'config':
124                child = Element('variable', name=key)
125            else:
126                child = Element(key)
127            child.text = str(val)
128        elem.append(child)
129    return elem
130