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