xref: /openbmc/openbmc-build-scripts/scripts/unit-test.py (revision bc5f06f5964cedc3424ec6e8595a164bf0dd6559)
189b64b6fSAndrew Jeffery#!/usr/bin/env python3
2ccb7f854SMatthew Barth
3ccb7f854SMatthew Barth"""
4ccb7f854SMatthew BarthThis script determines the given package's openbmc dependencies from its
5ccb7f854SMatthew Barthconfigure.ac file where it downloads, configures, builds, and installs each of
6ccb7f854SMatthew Barththese dependencies. Then the given package is configured, built, and installed
7ccb7f854SMatthew Barthprior to executing its unit tests.
8ccb7f854SMatthew Barth"""
9ccb7f854SMatthew Barth
10e08ffba8SPatrick Williamsimport argparse
11bf83a1b3SAndrew Jefferyimport json
12e08ffba8SPatrick Williamsimport multiprocessing
13e08ffba8SPatrick Williamsimport os
14e08ffba8SPatrick Williamsimport platform
15e08ffba8SPatrick Williamsimport re
16e08ffba8SPatrick Williamsimport shutil
17e08ffba8SPatrick Williamsimport subprocess
18e08ffba8SPatrick Williamsimport sys
19e08ffba8SPatrick Williamsfrom subprocess import CalledProcessError, check_call
20e08ffba8SPatrick Williamsfrom tempfile import TemporaryDirectory
21e08ffba8SPatrick Williamsfrom urllib.parse import urljoin
22e08ffba8SPatrick Williams
23d1810373SMatthew Barthfrom git import Repo
24e795dfe4SPatrick Williams
253992d10cSWilliam A. Kennington III# interpreter is not used directly but this resolves dependency ordering
263992d10cSWilliam A. Kennington III# that would be broken if we didn't include it.
27e08ffba8SPatrick Williamsfrom mesonbuild import interpreter  # noqa: F401
28fb6653ceSEwelina Walkuszfrom mesonbuild import optinterpreter, options
2995095f17SPatrick Williamsfrom mesonbuild.mesonlib import version_compare as meson_version_compare
305618dd5cSEwelina Walkuszfrom mesonbuild.options import OptionKey, OptionStore
31a62a1a13SLeonel Gonzalez
32a62a1a13SLeonel Gonzalez
33e08ffba8SPatrick Williamsclass DepTree:
34a62a1a13SLeonel Gonzalez    """
35a62a1a13SLeonel Gonzalez    Represents package dependency tree, where each node is a DepTree with a
36a62a1a13SLeonel Gonzalez    name and DepTree children.
37a62a1a13SLeonel Gonzalez    """
38a62a1a13SLeonel Gonzalez
39a62a1a13SLeonel Gonzalez    def __init__(self, name):
40a62a1a13SLeonel Gonzalez        """
41a62a1a13SLeonel Gonzalez        Create new DepTree.
42a62a1a13SLeonel Gonzalez
43a62a1a13SLeonel Gonzalez        Parameter descriptions:
44a62a1a13SLeonel Gonzalez        name               Name of new tree node.
45a62a1a13SLeonel Gonzalez        """
46a62a1a13SLeonel Gonzalez        self.name = name
47a62a1a13SLeonel Gonzalez        self.children = list()
48a62a1a13SLeonel Gonzalez
49a62a1a13SLeonel Gonzalez    def AddChild(self, name):
50a62a1a13SLeonel Gonzalez        """
51a62a1a13SLeonel Gonzalez        Add new child node to current node.
52a62a1a13SLeonel Gonzalez
53a62a1a13SLeonel Gonzalez        Parameter descriptions:
54a62a1a13SLeonel Gonzalez        name               Name of new child
55a62a1a13SLeonel Gonzalez        """
56a62a1a13SLeonel Gonzalez        new_child = DepTree(name)
57a62a1a13SLeonel Gonzalez        self.children.append(new_child)
58a62a1a13SLeonel Gonzalez        return new_child
59a62a1a13SLeonel Gonzalez
60a62a1a13SLeonel Gonzalez    def AddChildNode(self, node):
61a62a1a13SLeonel Gonzalez        """
62a62a1a13SLeonel Gonzalez        Add existing child node to current node.
63a62a1a13SLeonel Gonzalez
64a62a1a13SLeonel Gonzalez        Parameter descriptions:
65a62a1a13SLeonel Gonzalez        node               Tree node to add
66a62a1a13SLeonel Gonzalez        """
67a62a1a13SLeonel Gonzalez        self.children.append(node)
68a62a1a13SLeonel Gonzalez
69a62a1a13SLeonel Gonzalez    def RemoveChild(self, name):
70a62a1a13SLeonel Gonzalez        """
71a62a1a13SLeonel Gonzalez        Remove child node.
72a62a1a13SLeonel Gonzalez
73a62a1a13SLeonel Gonzalez        Parameter descriptions:
74a62a1a13SLeonel Gonzalez        name               Name of child to remove
75a62a1a13SLeonel Gonzalez        """
76a62a1a13SLeonel Gonzalez        for child in self.children:
77a62a1a13SLeonel Gonzalez            if child.name == name:
78a62a1a13SLeonel Gonzalez                self.children.remove(child)
79a62a1a13SLeonel Gonzalez                return
80a62a1a13SLeonel Gonzalez
81a62a1a13SLeonel Gonzalez    def GetNode(self, name):
82a62a1a13SLeonel Gonzalez        """
83a62a1a13SLeonel Gonzalez        Return node with matching name. Return None if not found.
84a62a1a13SLeonel Gonzalez
85a62a1a13SLeonel Gonzalez        Parameter descriptions:
86a62a1a13SLeonel Gonzalez        name               Name of node to return
87a62a1a13SLeonel Gonzalez        """
88a62a1a13SLeonel Gonzalez        if self.name == name:
89a62a1a13SLeonel Gonzalez            return self
90a62a1a13SLeonel Gonzalez        for child in self.children:
91a62a1a13SLeonel Gonzalez            node = child.GetNode(name)
92a62a1a13SLeonel Gonzalez            if node:
93a62a1a13SLeonel Gonzalez                return node
94a62a1a13SLeonel Gonzalez        return None
95a62a1a13SLeonel Gonzalez
96a62a1a13SLeonel Gonzalez    def GetParentNode(self, name, parent_node=None):
97a62a1a13SLeonel Gonzalez        """
98a62a1a13SLeonel Gonzalez        Return parent of node with matching name. Return none if not found.
99a62a1a13SLeonel Gonzalez
100a62a1a13SLeonel Gonzalez        Parameter descriptions:
101a62a1a13SLeonel Gonzalez        name               Name of node to get parent of
102a62a1a13SLeonel Gonzalez        parent_node        Parent of current node
103a62a1a13SLeonel Gonzalez        """
104a62a1a13SLeonel Gonzalez        if self.name == name:
105a62a1a13SLeonel Gonzalez            return parent_node
106a62a1a13SLeonel Gonzalez        for child in self.children:
107a62a1a13SLeonel Gonzalez            found_node = child.GetParentNode(name, self)
108a62a1a13SLeonel Gonzalez            if found_node:
109a62a1a13SLeonel Gonzalez                return found_node
110a62a1a13SLeonel Gonzalez        return None
111a62a1a13SLeonel Gonzalez
112a62a1a13SLeonel Gonzalez    def GetPath(self, name, path=None):
113a62a1a13SLeonel Gonzalez        """
114a62a1a13SLeonel Gonzalez        Return list of node names from head to matching name.
115a62a1a13SLeonel Gonzalez        Return None if not found.
116a62a1a13SLeonel Gonzalez
117a62a1a13SLeonel Gonzalez        Parameter descriptions:
118a62a1a13SLeonel Gonzalez        name               Name of node
119a62a1a13SLeonel Gonzalez        path               List of node names from head to current node
120a62a1a13SLeonel Gonzalez        """
121a62a1a13SLeonel Gonzalez        if not path:
122a62a1a13SLeonel Gonzalez            path = []
123a62a1a13SLeonel Gonzalez        if self.name == name:
124a62a1a13SLeonel Gonzalez            path.append(self.name)
125a62a1a13SLeonel Gonzalez            return path
126a62a1a13SLeonel Gonzalez        for child in self.children:
127a62a1a13SLeonel Gonzalez            match = child.GetPath(name, path + [self.name])
128a62a1a13SLeonel Gonzalez            if match:
129a62a1a13SLeonel Gonzalez                return match
130a62a1a13SLeonel Gonzalez        return None
131a62a1a13SLeonel Gonzalez
132a62a1a13SLeonel Gonzalez    def GetPathRegex(self, name, regex_str, path=None):
133a62a1a13SLeonel Gonzalez        """
134a62a1a13SLeonel Gonzalez        Return list of node paths that end in name, or match regex_str.
135a62a1a13SLeonel Gonzalez        Return empty list if not found.
136a62a1a13SLeonel Gonzalez
137a62a1a13SLeonel Gonzalez        Parameter descriptions:
138a62a1a13SLeonel Gonzalez        name               Name of node to search for
139a62a1a13SLeonel Gonzalez        regex_str          Regex string to match node names
140a62a1a13SLeonel Gonzalez        path               Path of node names from head to current node
141a62a1a13SLeonel Gonzalez        """
142a62a1a13SLeonel Gonzalez        new_paths = []
143a62a1a13SLeonel Gonzalez        if not path:
144a62a1a13SLeonel Gonzalez            path = []
145a62a1a13SLeonel Gonzalez        match = re.match(regex_str, self.name)
146a62a1a13SLeonel Gonzalez        if (self.name == name) or (match):
147a62a1a13SLeonel Gonzalez            new_paths.append(path + [self.name])
148a62a1a13SLeonel Gonzalez        for child in self.children:
149a62a1a13SLeonel Gonzalez            return_paths = None
150a62a1a13SLeonel Gonzalez            full_path = path + [self.name]
151a62a1a13SLeonel Gonzalez            return_paths = child.GetPathRegex(name, regex_str, full_path)
152a62a1a13SLeonel Gonzalez            for i in return_paths:
153a62a1a13SLeonel Gonzalez                new_paths.append(i)
154a62a1a13SLeonel Gonzalez        return new_paths
155a62a1a13SLeonel Gonzalez
156a62a1a13SLeonel Gonzalez    def MoveNode(self, from_name, to_name):
157a62a1a13SLeonel Gonzalez        """
158a62a1a13SLeonel Gonzalez        Mode existing from_name node to become child of to_name node.
159a62a1a13SLeonel Gonzalez
160a62a1a13SLeonel Gonzalez        Parameter descriptions:
161a62a1a13SLeonel Gonzalez        from_name          Name of node to make a child of to_name
162a62a1a13SLeonel Gonzalez        to_name            Name of node to make parent of from_name
163a62a1a13SLeonel Gonzalez        """
164a62a1a13SLeonel Gonzalez        parent_from_node = self.GetParentNode(from_name)
165a62a1a13SLeonel Gonzalez        from_node = self.GetNode(from_name)
166a62a1a13SLeonel Gonzalez        parent_from_node.RemoveChild(from_name)
167a62a1a13SLeonel Gonzalez        to_node = self.GetNode(to_name)
168a62a1a13SLeonel Gonzalez        to_node.AddChildNode(from_node)
169a62a1a13SLeonel Gonzalez
170a62a1a13SLeonel Gonzalez    def ReorderDeps(self, name, regex_str):
171a62a1a13SLeonel Gonzalez        """
172a62a1a13SLeonel Gonzalez        Reorder dependency tree.  If tree contains nodes with names that
173a62a1a13SLeonel Gonzalez        match 'name' and 'regex_str', move 'regex_str' nodes that are
174a62a1a13SLeonel Gonzalez        to the right of 'name' node, so that they become children of the
175a62a1a13SLeonel Gonzalez        'name' node.
176a62a1a13SLeonel Gonzalez
177a62a1a13SLeonel Gonzalez        Parameter descriptions:
178a62a1a13SLeonel Gonzalez        name               Name of node to look for
179a62a1a13SLeonel Gonzalez        regex_str          Regex string to match names to
180a62a1a13SLeonel Gonzalez        """
181a62a1a13SLeonel Gonzalez        name_path = self.GetPath(name)
182a62a1a13SLeonel Gonzalez        if not name_path:
183a62a1a13SLeonel Gonzalez            return
184a62a1a13SLeonel Gonzalez        paths = self.GetPathRegex(name, regex_str)
185a62a1a13SLeonel Gonzalez        is_name_in_paths = False
186a62a1a13SLeonel Gonzalez        name_index = 0
187a62a1a13SLeonel Gonzalez        for i in range(len(paths)):
188a62a1a13SLeonel Gonzalez            path = paths[i]
189a62a1a13SLeonel Gonzalez            if path[-1] == name:
190a62a1a13SLeonel Gonzalez                is_name_in_paths = True
191a62a1a13SLeonel Gonzalez                name_index = i
192a62a1a13SLeonel Gonzalez                break
193a62a1a13SLeonel Gonzalez        if not is_name_in_paths:
194a62a1a13SLeonel Gonzalez            return
195a62a1a13SLeonel Gonzalez        for i in range(name_index + 1, len(paths)):
196a62a1a13SLeonel Gonzalez            path = paths[i]
197a62a1a13SLeonel Gonzalez            if name in path:
198a62a1a13SLeonel Gonzalez                continue
199a62a1a13SLeonel Gonzalez            from_name = path[-1]
200a62a1a13SLeonel Gonzalez            self.MoveNode(from_name, name)
201a62a1a13SLeonel Gonzalez
202a62a1a13SLeonel Gonzalez    def GetInstallList(self):
203a62a1a13SLeonel Gonzalez        """
204a62a1a13SLeonel Gonzalez        Return post-order list of node names.
205a62a1a13SLeonel Gonzalez
206a62a1a13SLeonel Gonzalez        Parameter descriptions:
207a62a1a13SLeonel Gonzalez        """
208a62a1a13SLeonel Gonzalez        install_list = []
209a62a1a13SLeonel Gonzalez        for child in self.children:
210a62a1a13SLeonel Gonzalez            child_install_list = child.GetInstallList()
211a62a1a13SLeonel Gonzalez            install_list.extend(child_install_list)
212a62a1a13SLeonel Gonzalez        install_list.append(self.name)
213a62a1a13SLeonel Gonzalez        return install_list
214a62a1a13SLeonel Gonzalez
215a62a1a13SLeonel Gonzalez    def PrintTree(self, level=0):
216a62a1a13SLeonel Gonzalez        """
217a62a1a13SLeonel Gonzalez        Print pre-order node names with indentation denoting node depth level.
218a62a1a13SLeonel Gonzalez
219a62a1a13SLeonel Gonzalez        Parameter descriptions:
220a62a1a13SLeonel Gonzalez        level              Current depth level
221a62a1a13SLeonel Gonzalez        """
222a62a1a13SLeonel Gonzalez        INDENT_PER_LEVEL = 4
223e08ffba8SPatrick Williams        print(" " * (level * INDENT_PER_LEVEL) + self.name)
224a62a1a13SLeonel Gonzalez        for child in self.children:
225a62a1a13SLeonel Gonzalez            child.PrintTree(level + 1)
22633df8790SMatthew Barth
22733df8790SMatthew Barth
22871924fdbSAndrew Jefferydef check_call_cmd(*cmd, **kwargs):
22933df8790SMatthew Barth    """
23033df8790SMatthew Barth    Verbose prints the directory location the given command is called from and
23133df8790SMatthew Barth    the command, then executes the command using check_call.
23233df8790SMatthew Barth
23333df8790SMatthew Barth    Parameter descriptions:
23433df8790SMatthew Barth    dir                 Directory location command is to be called from
23533df8790SMatthew Barth    cmd                 List of parameters constructing the complete command
23633df8790SMatthew Barth    """
2371fddb973SWilliam A. Kennington III    printline(os.getcwd(), ">", " ".join(cmd))
23871924fdbSAndrew Jeffery    check_call(cmd, **kwargs)
239ccb7f854SMatthew Barth
240ccb7f854SMatthew Barth
241a61acb50SAndrew Geisslerdef clone_pkg(pkg, branch):
24233df8790SMatthew Barth    """
24333df8790SMatthew Barth    Clone the given openbmc package's git repository from gerrit into
24433df8790SMatthew Barth    the WORKSPACE location
24533df8790SMatthew Barth
24633df8790SMatthew Barth    Parameter descriptions:
24733df8790SMatthew Barth    pkg                 Name of the package to clone
248a61acb50SAndrew Geissler    branch              Branch to clone from pkg
24933df8790SMatthew Barth    """
2507be94cadSAndrew Jeffery    pkg_dir = os.path.join(WORKSPACE, pkg)
251e08ffba8SPatrick Williams    if os.path.exists(os.path.join(pkg_dir, ".git")):
2527be94cadSAndrew Jeffery        return pkg_dir
253e08ffba8SPatrick Williams    pkg_repo = urljoin("https://gerrit.openbmc.org/openbmc/", pkg)
2547be94cadSAndrew Jeffery    os.mkdir(pkg_dir)
255a61acb50SAndrew Geissler    printline(pkg_dir, "> git clone", pkg_repo, branch, "./")
256a61acb50SAndrew Geissler    try:
257a61acb50SAndrew Geissler        # first try the branch
2587d4a26f9SAndrew Jeffery        clone = Repo.clone_from(pkg_repo, pkg_dir, branch=branch)
2597d4a26f9SAndrew Jeffery        repo_inst = clone.working_dir
260e08ffba8SPatrick Williams    except Exception:
261a61acb50SAndrew Geissler        printline("Input branch not found, default to master")
2627d4a26f9SAndrew Jeffery        clone = Repo.clone_from(pkg_repo, pkg_dir, branch="master")
2637d4a26f9SAndrew Jeffery        repo_inst = clone.working_dir
264a61acb50SAndrew Geissler    return repo_inst
26533df8790SMatthew Barth
26633df8790SMatthew Barth
26715e423efSAndrew Jefferydef make_target_exists(target):
268c048cc0fSWilliam A. Kennington III    """
26915e423efSAndrew Jeffery    Runs a check against the makefile in the current directory to determine
27015e423efSAndrew Jeffery    if the target exists so that it can be built.
271c048cc0fSWilliam A. Kennington III
272c048cc0fSWilliam A. Kennington III    Parameter descriptions:
27315e423efSAndrew Jeffery    target              The make target we are checking
274c048cc0fSWilliam A. Kennington III    """
27515e423efSAndrew Jeffery    try:
276e08ffba8SPatrick Williams        cmd = ["make", "-n", target]
277e08ffba8SPatrick Williams        with open(os.devnull, "w") as devnull:
27815e423efSAndrew Jeffery            check_call(cmd, stdout=devnull, stderr=devnull)
27915e423efSAndrew Jeffery        return True
28015e423efSAndrew Jeffery    except CalledProcessError:
28115e423efSAndrew Jeffery        return False
282c048cc0fSWilliam A. Kennington III
2833f1d1201SWilliam A. Kennington III
284a215673dSWilliam A. Kennington IIImake_parallel = [
285e08ffba8SPatrick Williams    "make",
286a215673dSWilliam A. Kennington III    # Run enough jobs to saturate all the cpus
287e08ffba8SPatrick Williams    "-j",
288e08ffba8SPatrick Williams    str(multiprocessing.cpu_count()),
289a215673dSWilliam A. Kennington III    # Don't start more jobs if the load avg is too high
290e08ffba8SPatrick Williams    "-l",
291e08ffba8SPatrick Williams    str(multiprocessing.cpu_count()),
292a215673dSWilliam A. Kennington III    # Synchronize the output so logs aren't intermixed in stdout / stderr
293e08ffba8SPatrick Williams    "-O",
294a215673dSWilliam A. Kennington III]
295a215673dSWilliam A. Kennington III
2963f1d1201SWilliam A. Kennington III
297ff5c5d56SAndrew Jefferydef build_and_install(name, build_for_testing=False):
298ccb7f854SMatthew Barth    """
299780ec095SWilliam A. Kennington III    Builds and installs the package in the environment. Optionally
300780ec095SWilliam A. Kennington III    builds the examples and test cases for package.
301ccb7f854SMatthew Barth
302780ec095SWilliam A. Kennington III    Parameter description:
303ff5c5d56SAndrew Jeffery    name                The name of the package we are building
304a0454915SWilliam A. Kennington III    build_for_testing   Enable options related to testing on the package?
305ccb7f854SMatthew Barth    """
306ff5c5d56SAndrew Jeffery    os.chdir(os.path.join(WORKSPACE, name))
30754d4fafaSWilliam A. Kennington III
30854d4fafaSWilliam A. Kennington III    # Refresh dynamic linker run time bindings for dependencies
309e08ffba8SPatrick Williams    check_call_cmd("sudo", "-n", "--", "ldconfig")
31054d4fafaSWilliam A. Kennington III
31115e423efSAndrew Jeffery    pkg = Package()
31225e5814dSWilliam A. Kennington III    if build_for_testing:
31315e423efSAndrew Jeffery        pkg.test()
31425e5814dSWilliam A. Kennington III    else:
31515e423efSAndrew Jeffery        pkg.install()
31615e423efSAndrew Jeffery
317ccb7f854SMatthew Barth
318ccf85d6eSAndrew Jefferydef build_dep_tree(name, pkgdir, dep_added, head, branch, dep_tree=None):
319a62a1a13SLeonel Gonzalez    """
320ccf85d6eSAndrew Jeffery    For each package (name), starting with the package to be unit tested,
32115e423efSAndrew Jeffery    extract its dependencies. For each package dependency defined, recursively
32215e423efSAndrew Jeffery    apply the same strategy
323a62a1a13SLeonel Gonzalez
324a62a1a13SLeonel Gonzalez    Parameter descriptions:
325ccf85d6eSAndrew Jeffery    name                Name of the package
326a62a1a13SLeonel Gonzalez    pkgdir              Directory where package source is located
327c048cc0fSWilliam A. Kennington III    dep_added           Current dict of dependencies and added status
328a62a1a13SLeonel Gonzalez    head                Head node of the dependency tree
329a61acb50SAndrew Geissler    branch              Branch to clone from pkg
330a62a1a13SLeonel Gonzalez    dep_tree            Current dependency tree node
331a62a1a13SLeonel Gonzalez    """
332a62a1a13SLeonel Gonzalez    if not dep_tree:
333a62a1a13SLeonel Gonzalez        dep_tree = head
334c048cc0fSWilliam A. Kennington III
335be6aab2eSWilliam A. Kennington III    with open("/tmp/depcache", "r") as depcache:
336c048cc0fSWilliam A. Kennington III        cache = depcache.readline()
337c048cc0fSWilliam A. Kennington III
338c048cc0fSWilliam A. Kennington III    # Read out pkg dependencies
33915e423efSAndrew Jeffery    pkg = Package(name, pkgdir)
340c048cc0fSWilliam A. Kennington III
34138c6b7ddSPatrick Williams    build = pkg.build_system()
342e08ffba8SPatrick Williams    if not build:
34338c6b7ddSPatrick Williams        raise Exception(f"Unable to find build system for {name}.")
34438c6b7ddSPatrick Williams
34538c6b7ddSPatrick Williams    for dep in set(build.dependencies()):
346c048cc0fSWilliam A. Kennington III        if dep in cache:
3472cb0c7afSAndrew Jeffery            continue
348a62a1a13SLeonel Gonzalez        # Dependency package not already known
349c048cc0fSWilliam A. Kennington III        if dep_added.get(dep) is None:
35038c6b7ddSPatrick Williams            print(f"Adding {dep} dependency to {name}.")
351a62a1a13SLeonel Gonzalez            # Dependency package not added
352c048cc0fSWilliam A. Kennington III            new_child = dep_tree.AddChild(dep)
353c048cc0fSWilliam A. Kennington III            dep_added[dep] = False
354a61acb50SAndrew Geissler            dep_pkgdir = clone_pkg(dep, branch)
355a62a1a13SLeonel Gonzalez            # Determine this dependency package's
356a62a1a13SLeonel Gonzalez            # dependencies and add them before
357a62a1a13SLeonel Gonzalez            # returning to add this package
358e08ffba8SPatrick Williams            dep_added = build_dep_tree(
359e08ffba8SPatrick Williams                dep, dep_pkgdir, dep_added, head, branch, new_child
360e08ffba8SPatrick Williams            )
361a62a1a13SLeonel Gonzalez        else:
362a62a1a13SLeonel Gonzalez            # Dependency package known and added
363c048cc0fSWilliam A. Kennington III            if dep_added[dep]:
364a62a1a13SLeonel Gonzalez                continue
365a62a1a13SLeonel Gonzalez            else:
366a62a1a13SLeonel Gonzalez                # Cyclic dependency failure
367ccf85d6eSAndrew Jeffery                raise Exception("Cyclic dependencies found in " + name)
368a62a1a13SLeonel Gonzalez
369ccf85d6eSAndrew Jeffery    if not dep_added[name]:
370ccf85d6eSAndrew Jeffery        dep_added[name] = True
371a62a1a13SLeonel Gonzalez
372a62a1a13SLeonel Gonzalez    return dep_added
373ccb7f854SMatthew Barth
3740f0a680eSWilliam A. Kennington III
37590b106a0SWilliam A. Kennington IIIdef run_cppcheck():
376d8e150a5SEwelina Walkusz    if (
377d8e150a5SEwelina Walkusz        not os.path.exists(os.path.join("build", "compile_commands.json"))
378d8e150a5SEwelina Walkusz        or NO_CPPCHECK
379d8e150a5SEwelina Walkusz    ):
38048424d44SBrad Bishop        return None
38148424d44SBrad Bishop
382485a0923SPatrick Williams    with TemporaryDirectory() as cpp_dir:
38348424d44SBrad Bishop        # http://cppcheck.sourceforge.net/manual.pdf
384c7198558SEd Tanous        try:
385c7198558SEd Tanous            check_call_cmd(
386e08ffba8SPatrick Williams                "cppcheck",
387e08ffba8SPatrick Williams                "-j",
388e08ffba8SPatrick Williams                str(multiprocessing.cpu_count()),
389e08ffba8SPatrick Williams                "--enable=style,performance,portability,missingInclude",
390688f837aSBrad Bishop                "--inline-suppr",
391e08ffba8SPatrick Williams                "--suppress=useStlAlgorithm",
392e08ffba8SPatrick Williams                "--suppress=unusedStructMember",
393e08ffba8SPatrick Williams                "--suppress=postfixOperator",
394e08ffba8SPatrick Williams                "--suppress=unreadVariable",
395e08ffba8SPatrick Williams                "--suppress=knownConditionTrueFalse",
396e08ffba8SPatrick Williams                "--library=googletest",
397e08ffba8SPatrick Williams                "--project=build/compile_commands.json",
398e08ffba8SPatrick Williams                f"--cppcheck-build-dir={cpp_dir}",
399c7198558SEd Tanous            )
400c7198558SEd Tanous        except subprocess.CalledProcessError:
401c7198558SEd Tanous            print("cppcheck found errors")
402dbd7cd62SLei YU
403e5fffa07SAndrew Jeffery
40437a89a2aSWilliam A. Kennington IIIdef is_valgrind_safe():
40537a89a2aSWilliam A. Kennington III    """
40637a89a2aSWilliam A. Kennington III    Returns whether it is safe to run valgrind on our platform
40737a89a2aSWilliam A. Kennington III    """
408e08ffba8SPatrick Williams    src = "unit-test-vg.c"
409e08ffba8SPatrick Williams    exe = "./unit-test-vg"
410e08ffba8SPatrick Williams    with open(src, "w") as h:
411e08ffba8SPatrick Williams        h.write("#include <errno.h>\n")
412e08ffba8SPatrick Williams        h.write("#include <stdio.h>\n")
413e08ffba8SPatrick Williams        h.write("#include <stdlib.h>\n")
414e08ffba8SPatrick Williams        h.write("#include <string.h>\n")
415e08ffba8SPatrick Williams        h.write("int main() {\n")
416e08ffba8SPatrick Williams        h.write("char *heap_str = malloc(16);\n")
4170326dedaSWilliam A. Kennington III        h.write('strcpy(heap_str, "RandString");\n')
4180326dedaSWilliam A. Kennington III        h.write('int res = strcmp("RandString", heap_str);\n')
419e08ffba8SPatrick Williams        h.write("free(heap_str);\n")
420e08ffba8SPatrick Williams        h.write("char errstr[64];\n")
421e08ffba8SPatrick Williams        h.write("strerror_r(EINVAL, errstr, sizeof(errstr));\n")
422afb0f986SWilliam A. Kennington III        h.write('printf("%s\\n", errstr);\n')
423e08ffba8SPatrick Williams        h.write("return res;\n")
424e08ffba8SPatrick Williams        h.write("}\n")
4250326dedaSWilliam A. Kennington III    try:
426e08ffba8SPatrick Williams        with open(os.devnull, "w") as devnull:
427e08ffba8SPatrick Williams            check_call(
428e08ffba8SPatrick Williams                ["gcc", "-O2", "-o", exe, src], stdout=devnull, stderr=devnull
429e08ffba8SPatrick Williams            )
430e08ffba8SPatrick Williams            check_call(
431e08ffba8SPatrick Williams                ["valgrind", "--error-exitcode=99", exe],
432e08ffba8SPatrick Williams                stdout=devnull,
433e08ffba8SPatrick Williams                stderr=devnull,
434e08ffba8SPatrick Williams            )
4350326dedaSWilliam A. Kennington III        return True
436e08ffba8SPatrick Williams    except Exception:
4370326dedaSWilliam A. Kennington III        sys.stderr.write("###### Platform is not valgrind safe ######\n")
4380326dedaSWilliam A. Kennington III        return False
4390326dedaSWilliam A. Kennington III    finally:
4400326dedaSWilliam A. Kennington III        os.remove(src)
4410326dedaSWilliam A. Kennington III        os.remove(exe)
44237a89a2aSWilliam A. Kennington III
443e5fffa07SAndrew Jeffery
444282e3301SWilliam A. Kennington IIIdef is_sanitize_safe():
445282e3301SWilliam A. Kennington III    """
446282e3301SWilliam A. Kennington III    Returns whether it is safe to run sanitizers on our platform
447282e3301SWilliam A. Kennington III    """
448e08ffba8SPatrick Williams    src = "unit-test-sanitize.c"
449e08ffba8SPatrick Williams    exe = "./unit-test-sanitize"
450e08ffba8SPatrick Williams    with open(src, "w") as h:
451e08ffba8SPatrick Williams        h.write("int main() { return 0; }\n")
4520b7fb2bcSWilliam A. Kennington III    try:
453e08ffba8SPatrick Williams        with open(os.devnull, "w") as devnull:
454e08ffba8SPatrick Williams            check_call(
455e08ffba8SPatrick Williams                [
456e08ffba8SPatrick Williams                    "gcc",
457e08ffba8SPatrick Williams                    "-O2",
458e08ffba8SPatrick Williams                    "-fsanitize=address",
459e08ffba8SPatrick Williams                    "-fsanitize=undefined",
460e08ffba8SPatrick Williams                    "-o",
461e08ffba8SPatrick Williams                    exe,
462e08ffba8SPatrick Williams                    src,
463e08ffba8SPatrick Williams                ],
464e08ffba8SPatrick Williams                stdout=devnull,
465e08ffba8SPatrick Williams                stderr=devnull,
466e08ffba8SPatrick Williams            )
4670b7fb2bcSWilliam A. Kennington III            check_call([exe], stdout=devnull, stderr=devnull)
468cd9578b2SAndrew Geissler
469cd9578b2SAndrew Geissler        # TODO - Sanitizer not working on ppc64le
470cd9578b2SAndrew Geissler        # https://github.com/openbmc/openbmc-build-scripts/issues/31
471e08ffba8SPatrick Williams        if platform.processor() == "ppc64le":
472cd9578b2SAndrew Geissler            sys.stderr.write("###### ppc64le is not sanitize safe ######\n")
473cd9578b2SAndrew Geissler            return False
474cd9578b2SAndrew Geissler        else:
4750b7fb2bcSWilliam A. Kennington III            return True
476e08ffba8SPatrick Williams    except Exception:
4770b7fb2bcSWilliam A. Kennington III        sys.stderr.write("###### Platform is not sanitize safe ######\n")
4780b7fb2bcSWilliam A. Kennington III        return False
4790b7fb2bcSWilliam A. Kennington III    finally:
4800b7fb2bcSWilliam A. Kennington III        os.remove(src)
4810b7fb2bcSWilliam A. Kennington III        os.remove(exe)
482282e3301SWilliam A. Kennington III
48349d4e594SWilliam A. Kennington III
484eaff24a6SWilliam A. Kennington IIIdef maybe_make_valgrind():
4850f0a680eSWilliam A. Kennington III    """
4860f0a680eSWilliam A. Kennington III    Potentially runs the unit tests through valgrind for the package
4870f0a680eSWilliam A. Kennington III    via `make check-valgrind`. If the package does not have valgrind testing
4880f0a680eSWilliam A. Kennington III    then it just skips over this.
4890f0a680eSWilliam A. Kennington III    """
4904e1d0a12SWilliam A. Kennington III    # Valgrind testing is currently broken by an aggressive strcmp optimization
4914e1d0a12SWilliam A. Kennington III    # that is inlined into optimized code for POWER by gcc 7+. Until we find
4924e1d0a12SWilliam A. Kennington III    # a workaround, just don't run valgrind tests on POWER.
4934e1d0a12SWilliam A. Kennington III    # https://github.com/openbmc/openbmc/issues/3315
49437a89a2aSWilliam A. Kennington III    if not is_valgrind_safe():
49575130195SWilliam A. Kennington III        sys.stderr.write("###### Skipping valgrind ######\n")
4964e1d0a12SWilliam A. Kennington III        return
497e08ffba8SPatrick Williams    if not make_target_exists("check-valgrind"):
4980f0a680eSWilliam A. Kennington III        return
4990f0a680eSWilliam A. Kennington III
5000f0a680eSWilliam A. Kennington III    try:
501e08ffba8SPatrick Williams        cmd = make_parallel + ["check-valgrind"]
5021fddb973SWilliam A. Kennington III        check_call_cmd(*cmd)
5030f0a680eSWilliam A. Kennington III    except CalledProcessError:
50490b106a0SWilliam A. Kennington III        for root, _, files in os.walk(os.getcwd()):
5050f0a680eSWilliam A. Kennington III            for f in files:
506e08ffba8SPatrick Williams                if re.search("test-suite-[a-z]+.log", f) is None:
5070f0a680eSWilliam A. Kennington III                    continue
508e08ffba8SPatrick Williams                check_call_cmd("cat", os.path.join(root, f))
509e08ffba8SPatrick Williams        raise Exception("Valgrind tests failed")
5100f0a680eSWilliam A. Kennington III
511e5fffa07SAndrew Jeffery
512eaff24a6SWilliam A. Kennington IIIdef maybe_make_coverage():
5130f0a680eSWilliam A. Kennington III    """
5140f0a680eSWilliam A. Kennington III    Potentially runs the unit tests through code coverage for the package
5150f0a680eSWilliam A. Kennington III    via `make check-code-coverage`. If the package does not have code coverage
5160f0a680eSWilliam A. Kennington III    testing then it just skips over this.
5170f0a680eSWilliam A. Kennington III    """
518e08ffba8SPatrick Williams    if not make_target_exists("check-code-coverage"):
5190f0a680eSWilliam A. Kennington III        return
5200f0a680eSWilliam A. Kennington III
5210f0a680eSWilliam A. Kennington III    # Actually run code coverage
5220f0a680eSWilliam A. Kennington III    try:
523e08ffba8SPatrick Williams        cmd = make_parallel + ["check-code-coverage"]
5241fddb973SWilliam A. Kennington III        check_call_cmd(*cmd)
5250f0a680eSWilliam A. Kennington III    except CalledProcessError:
526e08ffba8SPatrick Williams        raise Exception("Code coverage failed")
527ccb7f854SMatthew Barth
52815e423efSAndrew Jeffery
52915e423efSAndrew Jefferyclass BuildSystem(object):
53015e423efSAndrew Jeffery    """
53115e423efSAndrew Jeffery    Build systems generally provide the means to configure, build, install and
53215e423efSAndrew Jeffery    test software. The BuildSystem class defines a set of interfaces on top of
53315e423efSAndrew Jeffery    which Autotools, Meson, CMake and possibly other build system drivers can
53415e423efSAndrew Jeffery    be implemented, separating out the phases to control whether a package
53515e423efSAndrew Jeffery    should merely be installed or also tested and analyzed.
53615e423efSAndrew Jeffery    """
53708d2b929SLei YU
53815e423efSAndrew Jeffery    def __init__(self, package, path):
539e08ffba8SPatrick Williams        """Initialise the driver with properties independent of the build
540e08ffba8SPatrick Williams        system
54115e423efSAndrew Jeffery
54215e423efSAndrew Jeffery        Keyword arguments:
54315e423efSAndrew Jeffery        package: The name of the package. Derived from the path if None
54415e423efSAndrew Jeffery        path: The path to the package. Set to the working directory if None
54515e423efSAndrew Jeffery        """
54615e423efSAndrew Jeffery        self.path = "." if not path else path
54747fbfa57SAndrew Jeffery        realpath = os.path.realpath(self.path)
54847fbfa57SAndrew Jeffery        self.package = package if package else os.path.basename(realpath)
54915e423efSAndrew Jeffery        self.build_for_testing = False
55015e423efSAndrew Jeffery
55115e423efSAndrew Jeffery    def probe(self):
55215e423efSAndrew Jeffery        """Test if the build system driver can be applied to the package
55315e423efSAndrew Jeffery
55415e423efSAndrew Jeffery        Return True if the driver can drive the package's build system,
55515e423efSAndrew Jeffery        otherwise False.
55615e423efSAndrew Jeffery
55715e423efSAndrew Jeffery        Generally probe() is implemented by testing for the presence of the
55815e423efSAndrew Jeffery        build system's configuration file(s).
55915e423efSAndrew Jeffery        """
560e08ffba8SPatrick Williams        raise NotImplementedError
56115e423efSAndrew Jeffery
56215e423efSAndrew Jeffery    def dependencies(self):
56315e423efSAndrew Jeffery        """Provide the package's dependencies
56415e423efSAndrew Jeffery
56515e423efSAndrew Jeffery        Returns a list of dependencies. If no dependencies are required then an
56615e423efSAndrew Jeffery        empty list must be returned.
56715e423efSAndrew Jeffery
56815e423efSAndrew Jeffery        Generally dependencies() is implemented by analysing and extracting the
56915e423efSAndrew Jeffery        data from the build system configuration.
57015e423efSAndrew Jeffery        """
571e08ffba8SPatrick Williams        raise NotImplementedError
57215e423efSAndrew Jeffery
57315e423efSAndrew Jeffery    def configure(self, build_for_testing):
57415e423efSAndrew Jeffery        """Configure the source ready for building
57515e423efSAndrew Jeffery
57615e423efSAndrew Jeffery        Should raise an exception if configuration failed.
57715e423efSAndrew Jeffery
57815e423efSAndrew Jeffery        Keyword arguments:
57915e423efSAndrew Jeffery        build_for_testing: Mark the package as being built for testing rather
58015e423efSAndrew Jeffery                           than for installation as a dependency for the
58115e423efSAndrew Jeffery                           package under test. Setting to True generally
58215e423efSAndrew Jeffery                           implies that the package will be configured to build
58315e423efSAndrew Jeffery                           with debug information, at a low level of
58415e423efSAndrew Jeffery                           optimisation and possibly with sanitizers enabled.
58515e423efSAndrew Jeffery
58615e423efSAndrew Jeffery        Generally configure() is implemented by invoking the build system
58715e423efSAndrew Jeffery        tooling to generate Makefiles or equivalent.
58815e423efSAndrew Jeffery        """
589e08ffba8SPatrick Williams        raise NotImplementedError
59015e423efSAndrew Jeffery
59115e423efSAndrew Jeffery    def build(self):
59215e423efSAndrew Jeffery        """Build the software ready for installation and/or testing
59315e423efSAndrew Jeffery
59415e423efSAndrew Jeffery        Should raise an exception if the build fails
59515e423efSAndrew Jeffery
59615e423efSAndrew Jeffery        Generally build() is implemented by invoking `make` or `ninja`.
59715e423efSAndrew Jeffery        """
598e08ffba8SPatrick Williams        raise NotImplementedError
59915e423efSAndrew Jeffery
60015e423efSAndrew Jeffery    def install(self):
60115e423efSAndrew Jeffery        """Install the software ready for use
60215e423efSAndrew Jeffery
60315e423efSAndrew Jeffery        Should raise an exception if installation fails
60415e423efSAndrew Jeffery
60515e423efSAndrew Jeffery        Like build(), install() is generally implemented by invoking `make` or
60615e423efSAndrew Jeffery        `ninja`.
60715e423efSAndrew Jeffery        """
608e08ffba8SPatrick Williams        raise NotImplementedError
60915e423efSAndrew Jeffery
61015e423efSAndrew Jeffery    def test(self):
61115e423efSAndrew Jeffery        """Build and run the test suite associated with the package
61215e423efSAndrew Jeffery
61315e423efSAndrew Jeffery        Should raise an exception if the build or testing fails.
61415e423efSAndrew Jeffery
61515e423efSAndrew Jeffery        Like install(), test() is generally implemented by invoking `make` or
61615e423efSAndrew Jeffery        `ninja`.
61715e423efSAndrew Jeffery        """
618e08ffba8SPatrick Williams        raise NotImplementedError
61915e423efSAndrew Jeffery
62015e423efSAndrew Jeffery    def analyze(self):
62115e423efSAndrew Jeffery        """Run any supported analysis tools over the codebase
62215e423efSAndrew Jeffery
62315e423efSAndrew Jeffery        Should raise an exception if analysis fails.
62415e423efSAndrew Jeffery
62515e423efSAndrew Jeffery        Some analysis tools such as scan-build need injection into the build
62615e423efSAndrew Jeffery        system. analyze() provides the necessary hook to implement such
62715e423efSAndrew Jeffery        behaviour. Analyzers independent of the build system can also be
62815e423efSAndrew Jeffery        specified here but at the cost of possible duplication of code between
62915e423efSAndrew Jeffery        the build system driver implementations.
63015e423efSAndrew Jeffery        """
631e08ffba8SPatrick Williams        raise NotImplementedError
63215e423efSAndrew Jeffery
63315e423efSAndrew Jeffery
63415e423efSAndrew Jefferyclass Autotools(BuildSystem):
63515e423efSAndrew Jeffery    def __init__(self, package=None, path=None):
63615e423efSAndrew Jeffery        super(Autotools, self).__init__(package, path)
63715e423efSAndrew Jeffery
63815e423efSAndrew Jeffery    def probe(self):
639e08ffba8SPatrick Williams        return os.path.isfile(os.path.join(self.path, "configure.ac"))
64015e423efSAndrew Jeffery
64115e423efSAndrew Jeffery    def dependencies(self):
642e08ffba8SPatrick Williams        configure_ac = os.path.join(self.path, "configure.ac")
64315e423efSAndrew Jeffery
644e08ffba8SPatrick Williams        contents = ""
6457d4a26f9SAndrew Jeffery        # Prepend some special function overrides so we can parse out
6467d4a26f9SAndrew Jeffery        # dependencies
64789b64b6fSAndrew Jeffery        for macro in DEPENDENCIES.keys():
648e08ffba8SPatrick Williams            contents += (
649e08ffba8SPatrick Williams                "m4_define(["
650e08ffba8SPatrick Williams                + macro
651e08ffba8SPatrick Williams                + "], ["
652e08ffba8SPatrick Williams                + macro
653e08ffba8SPatrick Williams                + "_START$"
654e08ffba8SPatrick Williams                + str(DEPENDENCIES_OFFSET[macro] + 1)
655e08ffba8SPatrick Williams                + macro
656e08ffba8SPatrick Williams                + "_END])\n"
657e08ffba8SPatrick Williams            )
65815e423efSAndrew Jeffery        with open(configure_ac, "rt") as f:
65947fbfa57SAndrew Jeffery            contents += f.read()
66015e423efSAndrew Jeffery
661e08ffba8SPatrick Williams        autoconf_cmdline = ["autoconf", "-Wno-undefined", "-"]
662e08ffba8SPatrick Williams        autoconf_process = subprocess.Popen(
663e08ffba8SPatrick Williams            autoconf_cmdline,
6647d4a26f9SAndrew Jeffery            stdin=subprocess.PIPE,
6657d4a26f9SAndrew Jeffery            stdout=subprocess.PIPE,
666e08ffba8SPatrick Williams            stderr=subprocess.PIPE,
667e08ffba8SPatrick Williams        )
668e08ffba8SPatrick Williams        document = contents.encode("utf-8")
66989b64b6fSAndrew Jeffery        (stdout, stderr) = autoconf_process.communicate(input=document)
67015e423efSAndrew Jeffery        if not stdout:
67115e423efSAndrew Jeffery            print(stderr)
67215e423efSAndrew Jeffery            raise Exception("Failed to run autoconf for parsing dependencies")
67315e423efSAndrew Jeffery
67415e423efSAndrew Jeffery        # Parse out all of the dependency text
67515e423efSAndrew Jeffery        matches = []
67689b64b6fSAndrew Jeffery        for macro in DEPENDENCIES.keys():
677e08ffba8SPatrick Williams            pattern = "(" + macro + ")_START(.*?)" + macro + "_END"
678e08ffba8SPatrick Williams            for match in re.compile(pattern).finditer(stdout.decode("utf-8")):
67915e423efSAndrew Jeffery                matches.append((match.group(1), match.group(2)))
68015e423efSAndrew Jeffery
68115e423efSAndrew Jeffery        # Look up dependencies from the text
68215e423efSAndrew Jeffery        found_deps = []
68315e423efSAndrew Jeffery        for macro, deptext in matches:
684e08ffba8SPatrick Williams            for potential_dep in deptext.split(" "):
68589b64b6fSAndrew Jeffery                for known_dep in DEPENDENCIES[macro].keys():
68615e423efSAndrew Jeffery                    if potential_dep.startswith(known_dep):
68715e423efSAndrew Jeffery                        found_deps.append(DEPENDENCIES[macro][known_dep])
68815e423efSAndrew Jeffery
68915e423efSAndrew Jeffery        return found_deps
69015e423efSAndrew Jeffery
69115e423efSAndrew Jeffery    def _configure_feature(self, flag, enabled):
69215e423efSAndrew Jeffery        """
69315e423efSAndrew Jeffery        Returns an configure flag as a string
69415e423efSAndrew Jeffery
69515e423efSAndrew Jeffery        Parameters:
69615e423efSAndrew Jeffery        flag                The name of the flag
69715e423efSAndrew Jeffery        enabled             Whether the flag is enabled or disabled
69815e423efSAndrew Jeffery        """
699e08ffba8SPatrick Williams        return "--" + ("enable" if enabled else "disable") + "-" + flag
70015e423efSAndrew Jeffery
70115e423efSAndrew Jeffery    def configure(self, build_for_testing):
70215e423efSAndrew Jeffery        self.build_for_testing = build_for_testing
70315e423efSAndrew Jeffery        conf_flags = [
704e08ffba8SPatrick Williams            self._configure_feature("silent-rules", False),
705e08ffba8SPatrick Williams            self._configure_feature("examples", build_for_testing),
706e08ffba8SPatrick Williams            self._configure_feature("tests", build_for_testing),
707e08ffba8SPatrick Williams            self._configure_feature("itests", INTEGRATION_TEST),
70815e423efSAndrew Jeffery        ]
709e08ffba8SPatrick Williams        conf_flags.extend(
710e08ffba8SPatrick Williams            [
711889ac26aSGeorge Liu                self._configure_feature("code-coverage", False),
712e08ffba8SPatrick Williams                self._configure_feature("valgrind", build_for_testing),
713e08ffba8SPatrick Williams            ]
714e08ffba8SPatrick Williams        )
71515e423efSAndrew Jeffery        # Add any necessary configure flags for package
71615e423efSAndrew Jeffery        if CONFIGURE_FLAGS.get(self.package) is not None:
71715e423efSAndrew Jeffery            conf_flags.extend(CONFIGURE_FLAGS.get(self.package))
718e08ffba8SPatrick Williams        for bootstrap in ["bootstrap.sh", "bootstrap", "autogen.sh"]:
71915e423efSAndrew Jeffery            if os.path.exists(bootstrap):
720e08ffba8SPatrick Williams                check_call_cmd("./" + bootstrap)
72115e423efSAndrew Jeffery                break
722e08ffba8SPatrick Williams        check_call_cmd("./configure", *conf_flags)
72315e423efSAndrew Jeffery
72415e423efSAndrew Jeffery    def build(self):
72515e423efSAndrew Jeffery        check_call_cmd(*make_parallel)
72615e423efSAndrew Jeffery
72715e423efSAndrew Jeffery    def install(self):
728e08ffba8SPatrick Williams        check_call_cmd("sudo", "-n", "--", *(make_parallel + ["install"]))
729d40ad0beSJohnathan Mantey        check_call_cmd("sudo", "-n", "--", "ldconfig")
73015e423efSAndrew Jeffery
73115e423efSAndrew Jeffery    def test(self):
73215e423efSAndrew Jeffery        try:
733e08ffba8SPatrick Williams            cmd = make_parallel + ["check"]
73415e423efSAndrew Jeffery            for i in range(0, args.repeat):
73515e423efSAndrew Jeffery                check_call_cmd(*cmd)
736d080969bSAndrew Jeffery
737d080969bSAndrew Jeffery            maybe_make_valgrind()
738d080969bSAndrew Jeffery            maybe_make_coverage()
73915e423efSAndrew Jeffery        except CalledProcessError:
74015e423efSAndrew Jeffery            for root, _, files in os.walk(os.getcwd()):
741e08ffba8SPatrick Williams                if "test-suite.log" not in files:
74215e423efSAndrew Jeffery                    continue
743e08ffba8SPatrick Williams                check_call_cmd("cat", os.path.join(root, "test-suite.log"))
744e08ffba8SPatrick Williams            raise Exception("Unit tests failed")
74515e423efSAndrew Jeffery
74615e423efSAndrew Jeffery    def analyze(self):
74715e423efSAndrew Jeffery        run_cppcheck()
74815e423efSAndrew Jeffery
74915e423efSAndrew Jeffery
75015e423efSAndrew Jefferyclass CMake(BuildSystem):
75115e423efSAndrew Jeffery    def __init__(self, package=None, path=None):
75215e423efSAndrew Jeffery        super(CMake, self).__init__(package, path)
75315e423efSAndrew Jeffery
75415e423efSAndrew Jeffery    def probe(self):
755e08ffba8SPatrick Williams        return os.path.isfile(os.path.join(self.path, "CMakeLists.txt"))
75615e423efSAndrew Jeffery
75715e423efSAndrew Jeffery    def dependencies(self):
75815e423efSAndrew Jeffery        return []
75915e423efSAndrew Jeffery
76015e423efSAndrew Jeffery    def configure(self, build_for_testing):
76115e423efSAndrew Jeffery        self.build_for_testing = build_for_testing
762298d56b3SRamin Izadpanah        if INTEGRATION_TEST:
763e08ffba8SPatrick Williams            check_call_cmd(
764e08ffba8SPatrick Williams                "cmake",
765e08ffba8SPatrick Williams                "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON",
76604770ccdSMichal Orzel                "-DCMAKE_CXX_FLAGS='-DBOOST_USE_VALGRIND'",
767e08ffba8SPatrick Williams                "-DITESTS=ON",
768e08ffba8SPatrick Williams                ".",
769e08ffba8SPatrick Williams            )
770298d56b3SRamin Izadpanah        else:
77104770ccdSMichal Orzel            check_call_cmd(
77204770ccdSMichal Orzel                "cmake",
77304770ccdSMichal Orzel                "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON",
77404770ccdSMichal Orzel                "-DCMAKE_CXX_FLAGS='-DBOOST_USE_VALGRIND'",
77504770ccdSMichal Orzel                ".",
77604770ccdSMichal Orzel            )
77715e423efSAndrew Jeffery
77815e423efSAndrew Jeffery    def build(self):
779e08ffba8SPatrick Williams        check_call_cmd(
780e08ffba8SPatrick Williams            "cmake",
781e08ffba8SPatrick Williams            "--build",
782e08ffba8SPatrick Williams            ".",
783e08ffba8SPatrick Williams            "--",
784e08ffba8SPatrick Williams            "-j",
785e08ffba8SPatrick Williams            str(multiprocessing.cpu_count()),
786e08ffba8SPatrick Williams        )
78715e423efSAndrew Jeffery
78815e423efSAndrew Jeffery    def install(self):
78987ab5705SMichael Shen        check_call_cmd("sudo", "cmake", "--install", ".")
790d40ad0beSJohnathan Mantey        check_call_cmd("sudo", "-n", "--", "ldconfig")
79115e423efSAndrew Jeffery
79215e423efSAndrew Jeffery    def test(self):
793e08ffba8SPatrick Williams        if make_target_exists("test"):
794e08ffba8SPatrick Williams            check_call_cmd("ctest", ".")
79515e423efSAndrew Jeffery
79615e423efSAndrew Jeffery    def analyze(self):
797e08ffba8SPatrick Williams        if os.path.isfile(".clang-tidy"):
798e08ffba8SPatrick Williams            with TemporaryDirectory(prefix="build", dir=".") as build_dir:
799662890f1SEd Tanous                # clang-tidy needs to run on a clang-specific build
800e08ffba8SPatrick Williams                check_call_cmd(
801e08ffba8SPatrick Williams                    "cmake",
802e08ffba8SPatrick Williams                    "-DCMAKE_C_COMPILER=clang",
803e08ffba8SPatrick Williams                    "-DCMAKE_CXX_COMPILER=clang++",
804e08ffba8SPatrick Williams                    "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON",
805e08ffba8SPatrick Williams                    "-H.",
806e08ffba8SPatrick Williams                    "-B" + build_dir,
807e08ffba8SPatrick Williams                )
80882aaba03SNan Zhou
809e08ffba8SPatrick Williams                check_call_cmd(
810e08ffba8SPatrick Williams                    "run-clang-tidy", "-header-filter=.*", "-p", build_dir
811e08ffba8SPatrick Williams                )
812662890f1SEd Tanous
81315e423efSAndrew Jeffery        maybe_make_valgrind()
81415e423efSAndrew Jeffery        maybe_make_coverage()
81515e423efSAndrew Jeffery        run_cppcheck()
81615e423efSAndrew Jeffery
81715e423efSAndrew Jeffery
81815e423efSAndrew Jefferyclass Meson(BuildSystem):
819bf83a1b3SAndrew Jeffery    @staticmethod
820bf83a1b3SAndrew Jeffery    def _project_name(path):
821bf83a1b3SAndrew Jeffery        doc = subprocess.check_output(
822bf83a1b3SAndrew Jeffery            ["meson", "introspect", "--projectinfo", path],
823bf83a1b3SAndrew Jeffery            stderr=subprocess.STDOUT,
824bf83a1b3SAndrew Jeffery        ).decode("utf-8")
825bf83a1b3SAndrew Jeffery        return json.loads(doc)["descriptive_name"]
826bf83a1b3SAndrew Jeffery
82715e423efSAndrew Jeffery    def __init__(self, package=None, path=None):
82815e423efSAndrew Jeffery        super(Meson, self).__init__(package, path)
82915e423efSAndrew Jeffery
83015e423efSAndrew Jeffery    def probe(self):
831e08ffba8SPatrick Williams        return os.path.isfile(os.path.join(self.path, "meson.build"))
83215e423efSAndrew Jeffery
83315e423efSAndrew Jeffery    def dependencies(self):
834e08ffba8SPatrick Williams        meson_build = os.path.join(self.path, "meson.build")
83515e423efSAndrew Jeffery        if not os.path.exists(meson_build):
83615e423efSAndrew Jeffery            return []
83715e423efSAndrew Jeffery
83815e423efSAndrew Jeffery        found_deps = []
83915e423efSAndrew Jeffery        for root, dirs, files in os.walk(self.path):
840e08ffba8SPatrick Williams            if "meson.build" not in files:
84115e423efSAndrew Jeffery                continue
842e08ffba8SPatrick Williams            with open(os.path.join(root, "meson.build"), "rt") as f:
84315e423efSAndrew Jeffery                build_contents = f.read()
844ef13d536SNan Zhou            pattern = r"dependency\('([^']*)'.*?\),?\n"
84547fbfa57SAndrew Jeffery            for match in re.finditer(pattern, build_contents):
84647fbfa57SAndrew Jeffery                group = match.group(1)
847e08ffba8SPatrick Williams                maybe_dep = DEPENDENCIES["PKG_CHECK_MODULES"].get(group)
84815e423efSAndrew Jeffery                if maybe_dep is not None:
84915e423efSAndrew Jeffery                    found_deps.append(maybe_dep)
85015e423efSAndrew Jeffery
85115e423efSAndrew Jeffery        return found_deps
85215e423efSAndrew Jeffery
85315e423efSAndrew Jeffery    def _parse_options(self, options_file):
85415e423efSAndrew Jeffery        """
85515e423efSAndrew Jeffery        Returns a set of options defined in the provides meson_options.txt file
85615e423efSAndrew Jeffery
85715e423efSAndrew Jeffery        Parameters:
85815e423efSAndrew Jeffery        options_file        The file containing options
85915e423efSAndrew Jeffery        """
8605618dd5cSEwelina Walkusz        store = OptionStore()
8615618dd5cSEwelina Walkusz        oi = optinterpreter.OptionInterpreter(store, "")
8620ecd0bc5SWilliam A. Kennington III        oi.process(options_file)
8630ecd0bc5SWilliam A. Kennington III        return oi.options
86415e423efSAndrew Jeffery
865fcd70776SWilliam A. Kennington III    def _configure_boolean(self, val):
866fcd70776SWilliam A. Kennington III        """
867fcd70776SWilliam A. Kennington III        Returns the meson flag which signifies the value
868fcd70776SWilliam A. Kennington III
869fcd70776SWilliam A. Kennington III        True is true which requires the boolean.
870fcd70776SWilliam A. Kennington III        False is false which disables the boolean.
871fcd70776SWilliam A. Kennington III
872fcd70776SWilliam A. Kennington III        Parameters:
873fcd70776SWilliam A. Kennington III        val                 The value being converted
874fcd70776SWilliam A. Kennington III        """
875fcd70776SWilliam A. Kennington III        if val is True:
876e08ffba8SPatrick Williams            return "true"
877fcd70776SWilliam A. Kennington III        elif val is False:
878e08ffba8SPatrick Williams            return "false"
879fcd70776SWilliam A. Kennington III        else:
880fcd70776SWilliam A. Kennington III            raise Exception("Bad meson boolean value")
881fcd70776SWilliam A. Kennington III
88215e423efSAndrew Jeffery    def _configure_feature(self, val):
88315e423efSAndrew Jeffery        """
88415e423efSAndrew Jeffery        Returns the meson flag which signifies the value
88515e423efSAndrew Jeffery
88615e423efSAndrew Jeffery        True is enabled which requires the feature.
88715e423efSAndrew Jeffery        False is disabled which disables the feature.
88815e423efSAndrew Jeffery        None is auto which autodetects the feature.
88915e423efSAndrew Jeffery
89015e423efSAndrew Jeffery        Parameters:
89115e423efSAndrew Jeffery        val                 The value being converted
89215e423efSAndrew Jeffery        """
89315e423efSAndrew Jeffery        if val is True:
89415e423efSAndrew Jeffery            return "enabled"
89515e423efSAndrew Jeffery        elif val is False:
89615e423efSAndrew Jeffery            return "disabled"
89715e423efSAndrew Jeffery        elif val is None:
89815e423efSAndrew Jeffery            return "auto"
89915e423efSAndrew Jeffery        else:
90015e423efSAndrew Jeffery            raise Exception("Bad meson feature value")
90115e423efSAndrew Jeffery
902fcd70776SWilliam A. Kennington III    def _configure_option(self, opts, key, val):
903fcd70776SWilliam A. Kennington III        """
904fcd70776SWilliam A. Kennington III        Returns the meson flag which signifies the value
905fcd70776SWilliam A. Kennington III        based on the type of the opt
906fcd70776SWilliam A. Kennington III
907fcd70776SWilliam A. Kennington III        Parameters:
908fcd70776SWilliam A. Kennington III        opt                 The meson option which we are setting
909fcd70776SWilliam A. Kennington III        val                 The value being converted
910fcd70776SWilliam A. Kennington III        """
911fb6653ceSEwelina Walkusz        if isinstance(opts[key], options.UserBooleanOption):
912fcd70776SWilliam A. Kennington III            str_val = self._configure_boolean(val)
913fb6653ceSEwelina Walkusz        elif isinstance(opts[key], options.UserFeatureOption):
914fcd70776SWilliam A. Kennington III            str_val = self._configure_feature(val)
915fcd70776SWilliam A. Kennington III        else:
916e08ffba8SPatrick Williams            raise Exception("Unknown meson option type")
917fcd70776SWilliam A. Kennington III        return "-D{}={}".format(key, str_val)
918fcd70776SWilliam A. Kennington III
91933aec43bSJohnathan Mantey    def get_configure_flags(self, build_for_testing):
92015e423efSAndrew Jeffery        self.build_for_testing = build_for_testing
9210ecd0bc5SWilliam A. Kennington III        meson_options = {}
92208fa1d5aSWilliam A. Kennington III        if os.path.exists("meson.options"):
92308fa1d5aSWilliam A. Kennington III            meson_options = self._parse_options("meson.options")
92408fa1d5aSWilliam A. Kennington III        elif os.path.exists("meson_options.txt"):
92515e423efSAndrew Jeffery            meson_options = self._parse_options("meson_options.txt")
92615e423efSAndrew Jeffery        meson_flags = [
927e08ffba8SPatrick Williams            "-Db_colorout=never",
928e08ffba8SPatrick Williams            "-Dwerror=true",
929e08ffba8SPatrick Williams            "-Dwarning_level=3",
93004770ccdSMichal Orzel            "-Dcpp_args='-DBOOST_USE_VALGRIND'",
93115e423efSAndrew Jeffery        ]
93215e423efSAndrew Jeffery        if build_for_testing:
9339d43ecfeSAndrew Jeffery            # -Ddebug=true -Doptimization=g is helpful for abi-dumper but isn't a combination that
9349d43ecfeSAndrew Jeffery            # is supported by meson's build types. Configure it manually.
9359d43ecfeSAndrew Jeffery            meson_flags.append("-Ddebug=true")
9369d43ecfeSAndrew Jeffery            meson_flags.append("-Doptimization=g")
93715e423efSAndrew Jeffery        else:
938e08ffba8SPatrick Williams            meson_flags.append("--buildtype=debugoptimized")
939e08ffba8SPatrick Williams        if OptionKey("tests") in meson_options:
940e08ffba8SPatrick Williams            meson_flags.append(
941e08ffba8SPatrick Williams                self._configure_option(
942e08ffba8SPatrick Williams                    meson_options, OptionKey("tests"), build_for_testing
943e08ffba8SPatrick Williams                )
944e08ffba8SPatrick Williams            )
945e08ffba8SPatrick Williams        if OptionKey("examples") in meson_options:
946e08ffba8SPatrick Williams            meson_flags.append(
947e08ffba8SPatrick Williams                self._configure_option(
948e08ffba8SPatrick Williams                    meson_options, OptionKey("examples"), build_for_testing
949e08ffba8SPatrick Williams                )
950e08ffba8SPatrick Williams            )
951e08ffba8SPatrick Williams        if OptionKey("itests") in meson_options:
952e08ffba8SPatrick Williams            meson_flags.append(
953e08ffba8SPatrick Williams                self._configure_option(
954e08ffba8SPatrick Williams                    meson_options, OptionKey("itests"), INTEGRATION_TEST
955e08ffba8SPatrick Williams                )
956e08ffba8SPatrick Williams            )
95715e423efSAndrew Jeffery        if MESON_FLAGS.get(self.package) is not None:
95815e423efSAndrew Jeffery            meson_flags.extend(MESON_FLAGS.get(self.package))
95933aec43bSJohnathan Mantey        return meson_flags
96033aec43bSJohnathan Mantey
96133aec43bSJohnathan Mantey    def configure(self, build_for_testing):
96233aec43bSJohnathan Mantey        meson_flags = self.get_configure_flags(build_for_testing)
96315e423efSAndrew Jeffery        try:
964e08ffba8SPatrick Williams            check_call_cmd(
965e08ffba8SPatrick Williams                "meson", "setup", "--reconfigure", "build", *meson_flags
966e08ffba8SPatrick Williams            )
967e08ffba8SPatrick Williams        except Exception:
968e08ffba8SPatrick Williams            shutil.rmtree("build", ignore_errors=True)
969e08ffba8SPatrick Williams            check_call_cmd("meson", "setup", "build", *meson_flags)
97015e423efSAndrew Jeffery
971bf83a1b3SAndrew Jeffery        self.package = Meson._project_name("build")
972bf83a1b3SAndrew Jeffery
97315e423efSAndrew Jeffery    def build(self):
974e08ffba8SPatrick Williams        check_call_cmd("ninja", "-C", "build")
97515e423efSAndrew Jeffery
97615e423efSAndrew Jeffery    def install(self):
977e08ffba8SPatrick Williams        check_call_cmd("sudo", "-n", "--", "ninja", "-C", "build", "install")
978d40ad0beSJohnathan Mantey        check_call_cmd("sudo", "-n", "--", "ldconfig")
97915e423efSAndrew Jeffery
98015e423efSAndrew Jeffery    def test(self):
98195095f17SPatrick Williams        # It is useful to check various settings of the meson.build file
98295095f17SPatrick Williams        # for compatibility, such as meson_version checks.  We shouldn't
98395095f17SPatrick Williams        # do this in the configure path though because it affects subprojects
98495095f17SPatrick Williams        # and dependencies as well, but we only want this applied to the
98595095f17SPatrick Williams        # project-under-test (otherwise an upstream dependency could fail
98695095f17SPatrick Williams        # this check without our control).
98795095f17SPatrick Williams        self._extra_meson_checks()
98895095f17SPatrick Williams
98915e423efSAndrew Jeffery        try:
990e08ffba8SPatrick Williams            test_args = ("--repeat", str(args.repeat), "-C", "build")
991e08ffba8SPatrick Williams            check_call_cmd("meson", "test", "--print-errorlogs", *test_args)
9927b8cef25SBrad Bishop
99315e423efSAndrew Jeffery        except CalledProcessError:
994e08ffba8SPatrick Williams            raise Exception("Unit tests failed")
99515e423efSAndrew Jeffery
99615e423efSAndrew Jeffery    def _setup_exists(self, setup):
99715e423efSAndrew Jeffery        """
99815e423efSAndrew Jeffery        Returns whether the meson build supports the named test setup.
99915e423efSAndrew Jeffery
100015e423efSAndrew Jeffery        Parameter descriptions:
100115e423efSAndrew Jeffery        setup              The setup target to check
100215e423efSAndrew Jeffery        """
100315e423efSAndrew Jeffery        try:
1004e08ffba8SPatrick Williams            with open(os.devnull, "w"):
100515e423efSAndrew Jeffery                output = subprocess.check_output(
1006e08ffba8SPatrick Williams                    [
1007e08ffba8SPatrick Williams                        "meson",
1008e08ffba8SPatrick Williams                        "test",
1009e08ffba8SPatrick Williams                        "-C",
1010e08ffba8SPatrick Williams                        "build",
1011e08ffba8SPatrick Williams                        "--setup",
10128bf1ecd7SAndrew Jeffery                        "{}:{}".format(self.package, setup),
1013fdfd8d48SAndrew Jeffery                        "__likely_not_a_test__",
1014e08ffba8SPatrick Williams                    ],
1015e08ffba8SPatrick Williams                    stderr=subprocess.STDOUT,
1016e08ffba8SPatrick Williams                )
101715e423efSAndrew Jeffery        except CalledProcessError as e:
101815e423efSAndrew Jeffery            output = e.output
1019e08ffba8SPatrick Williams        output = output.decode("utf-8")
10208bf1ecd7SAndrew Jeffery        return not re.search("Unknown test setup '[^']+'[.]", output)
102115e423efSAndrew Jeffery
102215e423efSAndrew Jeffery    def _maybe_valgrind(self):
102315e423efSAndrew Jeffery        """
102415e423efSAndrew Jeffery        Potentially runs the unit tests through valgrind for the package
102547fbfa57SAndrew Jeffery        via `meson test`. The package can specify custom valgrind
102647fbfa57SAndrew Jeffery        configurations by utilizing add_test_setup() in a meson.build
102715e423efSAndrew Jeffery        """
102815e423efSAndrew Jeffery        if not is_valgrind_safe():
102915e423efSAndrew Jeffery            sys.stderr.write("###### Skipping valgrind ######\n")
103015e423efSAndrew Jeffery            return
103115e423efSAndrew Jeffery        try:
1032e08ffba8SPatrick Williams            if self._setup_exists("valgrind"):
1033e08ffba8SPatrick Williams                check_call_cmd(
1034e08ffba8SPatrick Williams                    "meson",
1035e08ffba8SPatrick Williams                    "test",
1036e08ffba8SPatrick Williams                    "-t",
1037e08ffba8SPatrick Williams                    "10",
1038e08ffba8SPatrick Williams                    "-C",
1039e08ffba8SPatrick Williams                    "build",
1040e08ffba8SPatrick Williams                    "--print-errorlogs",
1041e08ffba8SPatrick Williams                    "--setup",
10428bf1ecd7SAndrew Jeffery                    "{}:valgrind".format(self.package),
1043e08ffba8SPatrick Williams                )
104415e423efSAndrew Jeffery            else:
1045e08ffba8SPatrick Williams                check_call_cmd(
1046e08ffba8SPatrick Williams                    "meson",
1047e08ffba8SPatrick Williams                    "test",
1048e08ffba8SPatrick Williams                    "-t",
1049e08ffba8SPatrick Williams                    "10",
1050e08ffba8SPatrick Williams                    "-C",
1051e08ffba8SPatrick Williams                    "build",
1052e08ffba8SPatrick Williams                    "--print-errorlogs",
1053e08ffba8SPatrick Williams                    "--wrapper",
105434b4f1cbSAndrew Jeffery                    "valgrind --error-exitcode=1",
1055e08ffba8SPatrick Williams                )
105615e423efSAndrew Jeffery        except CalledProcessError:
1057e08ffba8SPatrick Williams            raise Exception("Valgrind tests failed")
105815e423efSAndrew Jeffery
105915e423efSAndrew Jeffery    def analyze(self):
106015e423efSAndrew Jeffery        self._maybe_valgrind()
106115e423efSAndrew Jeffery
106215e423efSAndrew Jeffery        # Run clang-tidy only if the project has a configuration
1063e08ffba8SPatrick Williams        if os.path.isfile(".clang-tidy"):
1064966d67daSEd Tanous            os.environ["CC"] = "clang"
10651aa91992SManojkiran Eda            os.environ["CXX"] = "clang++"
1066e08ffba8SPatrick Williams            with TemporaryDirectory(prefix="build", dir=".") as build_dir:
1067e08ffba8SPatrick Williams                check_call_cmd("meson", "setup", build_dir)
106829e02313SEd Tanous                if not os.path.isfile(".openbmc-no-clang"):
106929e02313SEd Tanous                    check_call_cmd("meson", "compile", "-C", build_dir)
107088db4c20SEd Tanous                try:
1071ac9c9c73SLei YU                    check_call_cmd("ninja", "-C", build_dir, "clang-tidy-fix")
107288db4c20SEd Tanous                except subprocess.CalledProcessError:
1073e08ffba8SPatrick Williams                    check_call_cmd(
1074e08ffba8SPatrick Williams                        "git", "-C", CODE_SCAN_DIR, "--no-pager", "diff"
1075e08ffba8SPatrick Williams                    )
107688db4c20SEd Tanous                    raise
107715e423efSAndrew Jeffery        # Run the basic clang static analyzer otherwise
107815e423efSAndrew Jeffery        else:
1079e08ffba8SPatrick Williams            check_call_cmd("ninja", "-C", "build", "scan-build")
108015e423efSAndrew Jeffery
108115e423efSAndrew Jeffery        # Run tests through sanitizers
108215e423efSAndrew Jeffery        # b_lundef is needed if clang++ is CXX since it resolves the
108315e423efSAndrew Jeffery        # asan symbols at runtime only. We don't want to set it earlier
108415e423efSAndrew Jeffery        # in the build process to ensure we don't have undefined
108515e423efSAndrew Jeffery        # runtime code.
108615e423efSAndrew Jeffery        if is_sanitize_safe():
108733aec43bSJohnathan Mantey            meson_flags = self.get_configure_flags(self.build_for_testing)
108833aec43bSJohnathan Mantey            meson_flags.append("-Db_sanitize=address,undefined")
108933aec43bSJohnathan Mantey            try:
1090e08ffba8SPatrick Williams                check_call_cmd(
109133aec43bSJohnathan Mantey                    "meson", "setup", "--reconfigure", "build", *meson_flags
1092e08ffba8SPatrick Williams                )
109333aec43bSJohnathan Mantey            except Exception:
109433aec43bSJohnathan Mantey                shutil.rmtree("build", ignore_errors=True)
109533aec43bSJohnathan Mantey                check_call_cmd("meson", "setup", "build", *meson_flags)
1096e08ffba8SPatrick Williams            check_call_cmd(
1097e08ffba8SPatrick Williams                "meson",
1098e08ffba8SPatrick Williams                "test",
1099e08ffba8SPatrick Williams                "-C",
1100e08ffba8SPatrick Williams                "build",
1101e08ffba8SPatrick Williams                "--print-errorlogs",
1102e08ffba8SPatrick Williams                "--logbase",
1103e08ffba8SPatrick Williams                "testlog-ubasan",
1104e08ffba8SPatrick Williams            )
110515e423efSAndrew Jeffery            # TODO: Fix memory sanitizer
110615e423efSAndrew Jeffery            # check_call_cmd('meson', 'configure', 'build',
110715e423efSAndrew Jeffery            #                '-Db_sanitize=memory')
110815e423efSAndrew Jeffery            # check_call_cmd('meson', 'test', '-C', 'build'
110915e423efSAndrew Jeffery            #                '--logbase', 'testlog-msan')
1110*bc5f06f5SLei YU            meson_flags = [
1111*bc5f06f5SLei YU                s.replace(
1112*bc5f06f5SLei YU                    "-Db_sanitize=address,undefined", "-Db_sanitize=none"
1113*bc5f06f5SLei YU                )
1114*bc5f06f5SLei YU                for s in meson_flags
1115*bc5f06f5SLei YU            ]
111652c3aec3SEwelina Walkusz            try:
111752c3aec3SEwelina Walkusz                check_call_cmd(
111852c3aec3SEwelina Walkusz                    "meson", "setup", "--reconfigure", "build", *meson_flags
111952c3aec3SEwelina Walkusz                )
112052c3aec3SEwelina Walkusz            except Exception:
112152c3aec3SEwelina Walkusz                shutil.rmtree("build", ignore_errors=True)
112252c3aec3SEwelina Walkusz                check_call_cmd("meson", "setup", "build", *meson_flags)
112315e423efSAndrew Jeffery        else:
112415e423efSAndrew Jeffery            sys.stderr.write("###### Skipping sanitizers ######\n")
112515e423efSAndrew Jeffery
112615e423efSAndrew Jeffery        # Run coverage checks
1127e08ffba8SPatrick Williams        check_call_cmd("meson", "configure", "build", "-Db_coverage=true")
112815e423efSAndrew Jeffery        self.test()
112915e423efSAndrew Jeffery        # Only build coverage HTML if coverage files were produced
1130e08ffba8SPatrick Williams        for root, dirs, files in os.walk("build"):
1131e08ffba8SPatrick Williams            if any([f.endswith(".gcda") for f in files]):
1132e08ffba8SPatrick Williams                check_call_cmd("ninja", "-C", "build", "coverage-html")
113315e423efSAndrew Jeffery                break
1134e08ffba8SPatrick Williams        check_call_cmd("meson", "configure", "build", "-Db_coverage=false")
113515e423efSAndrew Jeffery        run_cppcheck()
113615e423efSAndrew Jeffery
113795095f17SPatrick Williams    def _extra_meson_checks(self):
1138e08ffba8SPatrick Williams        with open(os.path.join(self.path, "meson.build"), "rt") as f:
113995095f17SPatrick Williams            build_contents = f.read()
114095095f17SPatrick Williams
114195095f17SPatrick Williams        # Find project's specified meson_version.
114295095f17SPatrick Williams        meson_version = None
114395095f17SPatrick Williams        pattern = r"meson_version:[^']*'([^']*)'"
114495095f17SPatrick Williams        for match in re.finditer(pattern, build_contents):
114595095f17SPatrick Williams            group = match.group(1)
114695095f17SPatrick Williams            meson_version = group
114795095f17SPatrick Williams
114895095f17SPatrick Williams        # C++20 requires at least Meson 0.57 but Meson itself doesn't
114995095f17SPatrick Williams        # identify this.  Add to our unit-test checks so that we don't
115095095f17SPatrick Williams        # get a meson.build missing this.
115195095f17SPatrick Williams        pattern = r"'cpp_std=c\+\+20'"
115295095f17SPatrick Williams        for match in re.finditer(pattern, build_contents):
1153e08ffba8SPatrick Williams            if not meson_version or not meson_version_compare(
1154e08ffba8SPatrick Williams                meson_version, ">=0.57"
1155e08ffba8SPatrick Williams            ):
115695095f17SPatrick Williams                raise Exception(
115795095f17SPatrick Williams                    "C++20 support requires specifying in meson.build: "
115895095f17SPatrick Williams                    + "meson_version: '>=0.57'"
115995095f17SPatrick Williams                )
116095095f17SPatrick Williams
1161b9e07122SPatrick Williams        # C++23 requires at least Meson 1.1.1 but Meson itself doesn't
1162b9e07122SPatrick Williams        # identify this.  Add to our unit-test checks so that we don't
1163b9e07122SPatrick Williams        # get a meson.build missing this.
1164b9e07122SPatrick Williams        pattern = r"'cpp_std=c\+\+23'"
1165b9e07122SPatrick Williams        for match in re.finditer(pattern, build_contents):
1166b9e07122SPatrick Williams            if not meson_version or not meson_version_compare(
1167b9e07122SPatrick Williams                meson_version, ">=1.1.1"
1168b9e07122SPatrick Williams            ):
1169b9e07122SPatrick Williams                raise Exception(
1170b9e07122SPatrick Williams                    "C++23 support requires specifying in meson.build: "
1171b9e07122SPatrick Williams                    + "meson_version: '>=1.1.1'"
1172b9e07122SPatrick Williams                )
1173b9e07122SPatrick Williams
1174cebaea28SPatrick Williams        if "get_variable(" in build_contents:
1175cebaea28SPatrick Williams            if not meson_version or not meson_version_compare(
1176cebaea28SPatrick Williams                meson_version, ">=0.58"
1177cebaea28SPatrick Williams            ):
1178cebaea28SPatrick Williams                raise Exception(
1179cebaea28SPatrick Williams                    "dep.get_variable() with positional argument requires "
1180cebaea28SPatrick Williams                    + "meson_Version: '>=0.58'"
1181cebaea28SPatrick Williams                )
1182cebaea28SPatrick Williams
118315e423efSAndrew Jeffery
118415e423efSAndrew Jefferyclass Package(object):
118515e423efSAndrew Jeffery    def __init__(self, name=None, path=None):
118629d2825fSManojkiran Eda        self.supported = [Meson, Autotools, CMake]
118715e423efSAndrew Jeffery        self.name = name
118815e423efSAndrew Jeffery        self.path = path
118915e423efSAndrew Jeffery        self.test_only = False
119015e423efSAndrew Jeffery
119115e423efSAndrew Jeffery    def build_systems(self):
119215e423efSAndrew Jeffery        instances = (system(self.name, self.path) for system in self.supported)
119315e423efSAndrew Jeffery        return (instance for instance in instances if instance.probe())
119415e423efSAndrew Jeffery
119515e423efSAndrew Jeffery    def build_system(self, preferred=None):
11968cb74fcaSAndrew Geissler        systems = list(self.build_systems())
11978cb74fcaSAndrew Geissler
11988cb74fcaSAndrew Geissler        if not systems:
11998cb74fcaSAndrew Geissler            return None
120015e423efSAndrew Jeffery
120115e423efSAndrew Jeffery        if preferred:
120215e423efSAndrew Jeffery            return {type(system): system for system in systems}[preferred]
120315e423efSAndrew Jeffery
120415e423efSAndrew Jeffery        return next(iter(systems))
120515e423efSAndrew Jeffery
120615e423efSAndrew Jeffery    def install(self, system=None):
120715e423efSAndrew Jeffery        if not system:
120815e423efSAndrew Jeffery            system = self.build_system()
120915e423efSAndrew Jeffery
121015e423efSAndrew Jeffery        system.configure(False)
121115e423efSAndrew Jeffery        system.build()
121215e423efSAndrew Jeffery        system.install()
121315e423efSAndrew Jeffery
121419d75671SAndrew Jeffery    def _test_one(self, system):
121515e423efSAndrew Jeffery        system.configure(True)
121615e423efSAndrew Jeffery        system.build()
121715e423efSAndrew Jeffery        system.install()
121815e423efSAndrew Jeffery        system.test()
1219d080969bSAndrew Jeffery        if not TEST_ONLY:
122015e423efSAndrew Jeffery            system.analyze()
122115e423efSAndrew Jeffery
122219d75671SAndrew Jeffery    def test(self):
122319d75671SAndrew Jeffery        for system in self.build_systems():
122419d75671SAndrew Jeffery            self._test_one(system)
122519d75671SAndrew Jeffery
122615e423efSAndrew Jeffery
12279bfaaad4SMatt Spinlerdef find_file(filename, basedir):
12289bfaaad4SMatt Spinler    """
122955448adbSPatrick Williams    Finds all occurrences of a file (or list of files) in the base
123055448adbSPatrick Williams    directory and passes them back with their relative paths.
12319bfaaad4SMatt Spinler
12329bfaaad4SMatt Spinler    Parameter descriptions:
123355448adbSPatrick Williams    filename              The name of the file (or list of files) to
123455448adbSPatrick Williams                          find
12359bfaaad4SMatt Spinler    basedir               The base directory search in
12369bfaaad4SMatt Spinler    """
12379bfaaad4SMatt Spinler
123855448adbSPatrick Williams    if not isinstance(filename, list):
123955448adbSPatrick Williams        filename = [filename]
124055448adbSPatrick Williams
12419bfaaad4SMatt Spinler    filepaths = []
12429bfaaad4SMatt Spinler    for root, dirs, files in os.walk(basedir):
1243e08ffba8SPatrick Williams        if os.path.split(root)[-1] == "subprojects":
1244eb667265SBrad Bishop            for f in files:
1245e08ffba8SPatrick Williams                subproject = ".".join(f.split(".")[0:-1])
1246e08ffba8SPatrick Williams                if f.endswith(".wrap") and subproject in dirs:
1247eb667265SBrad Bishop                    # don't find files in meson subprojects with wraps
1248eb667265SBrad Bishop                    dirs.remove(subproject)
124955448adbSPatrick Williams        for f in filename:
125055448adbSPatrick Williams            if f in files:
125155448adbSPatrick Williams                filepaths.append(os.path.join(root, f))
12529bfaaad4SMatt Spinler    return filepaths
12539bfaaad4SMatt Spinler
12549cc97d88SAndrew Jeffery
1255e08ffba8SPatrick Williamsif __name__ == "__main__":
1256ccb7f854SMatthew Barth    # CONFIGURE_FLAGS = [GIT REPO]:[CONFIGURE FLAGS]
1257ccb7f854SMatthew Barth    CONFIGURE_FLAGS = {
1258e08ffba8SPatrick Williams        "phosphor-logging": [
1259e08ffba8SPatrick Williams            "--enable-metadata-processing",
1260e08ffba8SPatrick Williams            "--enable-openpower-pel-extension",
1261e08ffba8SPatrick Williams            "YAML_DIR=/usr/local/share/phosphor-dbus-yaml/yaml",
1262e08ffba8SPatrick Williams        ]
1263ccb7f854SMatthew Barth    }
1264ccb7f854SMatthew Barth
12653f1d1201SWilliam A. Kennington III    # MESON_FLAGS = [GIT REPO]:[MESON FLAGS]
12663f1d1201SWilliam A. Kennington III    MESON_FLAGS = {
1267e08ffba8SPatrick Williams        "phosphor-dbus-interfaces": [
1268e08ffba8SPatrick Williams            "-Ddata_com_ibm=true",
1269e08ffba8SPatrick Williams            "-Ddata_org_open_power=true",
1270e08ffba8SPatrick Williams        ],
1271e08ffba8SPatrick Williams        "phosphor-logging": ["-Dopenpower-pel-extension=enabled"],
12723f1d1201SWilliam A. Kennington III    }
12733f1d1201SWilliam A. Kennington III
1274ccb7f854SMatthew Barth    # DEPENDENCIES = [MACRO]:[library/header]:[GIT REPO]
1275ccb7f854SMatthew Barth    DEPENDENCIES = {
1276e08ffba8SPatrick Williams        "AC_CHECK_LIB": {"mapper": "phosphor-objmgr"},
1277e08ffba8SPatrick Williams        "AC_CHECK_HEADER": {
1278e08ffba8SPatrick Williams            "host-ipmid": "phosphor-host-ipmid",
1279e08ffba8SPatrick Williams            "blobs-ipmid": "phosphor-ipmi-blobs",
1280e08ffba8SPatrick Williams            "sdbusplus": "sdbusplus",
1281e08ffba8SPatrick Williams            "sdeventplus": "sdeventplus",
1282e08ffba8SPatrick Williams            "stdplus": "stdplus",
1283e08ffba8SPatrick Williams            "gpioplus": "gpioplus",
1284e08ffba8SPatrick Williams            "phosphor-logging/log.hpp": "phosphor-logging",
1285eab8a371SPatrick Williams        },
1286e08ffba8SPatrick Williams        "AC_PATH_PROG": {"sdbus++": "sdbusplus"},
1287e08ffba8SPatrick Williams        "PKG_CHECK_MODULES": {
1288e08ffba8SPatrick Williams            "phosphor-dbus-interfaces": "phosphor-dbus-interfaces",
1289e08ffba8SPatrick Williams            "libipmid": "phosphor-host-ipmid",
1290e08ffba8SPatrick Williams            "libipmid-host": "phosphor-host-ipmid",
1291e08ffba8SPatrick Williams            "sdbusplus": "sdbusplus",
1292e08ffba8SPatrick Williams            "sdeventplus": "sdeventplus",
1293e08ffba8SPatrick Williams            "stdplus": "stdplus",
1294e08ffba8SPatrick Williams            "gpioplus": "gpioplus",
1295e08ffba8SPatrick Williams            "phosphor-logging": "phosphor-logging",
1296e08ffba8SPatrick Williams            "phosphor-snmp": "phosphor-snmp",
1297e08ffba8SPatrick Williams            "ipmiblob": "ipmi-blob-tool",
1298e08ffba8SPatrick Williams            "hei": "openpower-libhei",
1299e08ffba8SPatrick Williams            "phosphor-ipmi-blobs": "phosphor-ipmi-blobs",
1300e08ffba8SPatrick Williams            "libcr51sign": "google-misc",
1301ebb49112SBrad Bishop        },
1302ccb7f854SMatthew Barth    }
1303ccb7f854SMatthew Barth
1304e67f5fc2SWilliam A. Kennington III    # Offset into array of macro parameters MACRO(0, 1, ...N)
1305e67f5fc2SWilliam A. Kennington III    DEPENDENCIES_OFFSET = {
1306e08ffba8SPatrick Williams        "AC_CHECK_LIB": 0,
1307e08ffba8SPatrick Williams        "AC_CHECK_HEADER": 0,
1308e08ffba8SPatrick Williams        "AC_PATH_PROG": 1,
1309e08ffba8SPatrick Williams        "PKG_CHECK_MODULES": 1,
1310e67f5fc2SWilliam A. Kennington III    }
1311e67f5fc2SWilliam A. Kennington III
1312a62a1a13SLeonel Gonzalez    # DEPENDENCIES_REGEX = [GIT REPO]:[REGEX STRING]
1313e08ffba8SPatrick Williams    DEPENDENCIES_REGEX = {"phosphor-logging": r"\S+-dbus-interfaces$"}
1314a62a1a13SLeonel Gonzalez
131533df8790SMatthew Barth    # Set command line arguments
131633df8790SMatthew Barth    parser = argparse.ArgumentParser()
1317e08ffba8SPatrick Williams    parser.add_argument(
1318e08ffba8SPatrick Williams        "-w",
1319e08ffba8SPatrick Williams        "--workspace",
1320e08ffba8SPatrick Williams        dest="WORKSPACE",
1321e08ffba8SPatrick Williams        required=True,
1322e08ffba8SPatrick Williams        help="Workspace directory location(i.e. /home)",
1323e08ffba8SPatrick Williams    )
1324e08ffba8SPatrick Williams    parser.add_argument(
1325e08ffba8SPatrick Williams        "-p",
1326e08ffba8SPatrick Williams        "--package",
1327e08ffba8SPatrick Williams        dest="PACKAGE",
1328e08ffba8SPatrick Williams        required=True,
1329e08ffba8SPatrick Williams        help="OpenBMC package to be unit tested",
1330e08ffba8SPatrick Williams    )
1331e08ffba8SPatrick Williams    parser.add_argument(
1332e08ffba8SPatrick Williams        "-t",
1333e08ffba8SPatrick Williams        "--test-only",
1334e08ffba8SPatrick Williams        dest="TEST_ONLY",
1335e08ffba8SPatrick Williams        action="store_true",
1336e08ffba8SPatrick Williams        required=False,
1337e08ffba8SPatrick Williams        default=False,
1338e08ffba8SPatrick Williams        help="Only run test cases, no other validation",
1339e08ffba8SPatrick Williams    )
1340d8e150a5SEwelina Walkusz    parser.add_argument(
1341d8e150a5SEwelina Walkusz        "--no-cppcheck",
1342d8e150a5SEwelina Walkusz        dest="NO_CPPCHECK",
1343d8e150a5SEwelina Walkusz        action="store_true",
1344d8e150a5SEwelina Walkusz        required=False,
1345d8e150a5SEwelina Walkusz        default=False,
1346d8e150a5SEwelina Walkusz        help="Do not run cppcheck",
1347d8e150a5SEwelina Walkusz    )
1348298d56b3SRamin Izadpanah    arg_inttests = parser.add_mutually_exclusive_group()
1349e08ffba8SPatrick Williams    arg_inttests.add_argument(
1350e08ffba8SPatrick Williams        "--integration-tests",
1351e08ffba8SPatrick Williams        dest="INTEGRATION_TEST",
1352e08ffba8SPatrick Williams        action="store_true",
1353e08ffba8SPatrick Williams        required=False,
1354e08ffba8SPatrick Williams        default=True,
1355e08ffba8SPatrick Williams        help="Enable integration tests [default].",
1356e08ffba8SPatrick Williams    )
1357e08ffba8SPatrick Williams    arg_inttests.add_argument(
1358e08ffba8SPatrick Williams        "--no-integration-tests",
1359e08ffba8SPatrick Williams        dest="INTEGRATION_TEST",
1360e08ffba8SPatrick Williams        action="store_false",
1361e08ffba8SPatrick Williams        required=False,
1362e08ffba8SPatrick Williams        help="Disable integration tests.",
1363e08ffba8SPatrick Williams    )
1364e08ffba8SPatrick Williams    parser.add_argument(
1365e08ffba8SPatrick Williams        "-v",
1366e08ffba8SPatrick Williams        "--verbose",
1367e08ffba8SPatrick Williams        action="store_true",
1368e08ffba8SPatrick Williams        help="Print additional package status messages",
1369e08ffba8SPatrick Williams    )
1370e08ffba8SPatrick Williams    parser.add_argument(
1371e08ffba8SPatrick Williams        "-r", "--repeat", help="Repeat tests N times", type=int, default=1
1372e08ffba8SPatrick Williams    )
1373e08ffba8SPatrick Williams    parser.add_argument(
1374e08ffba8SPatrick Williams        "-b",
1375e08ffba8SPatrick Williams        "--branch",
1376e08ffba8SPatrick Williams        dest="BRANCH",
1377e08ffba8SPatrick Williams        required=False,
1378a61acb50SAndrew Geissler        help="Branch to target for dependent repositories",
1379e08ffba8SPatrick Williams        default="master",
1380e08ffba8SPatrick Williams    )
1381e08ffba8SPatrick Williams    parser.add_argument(
1382e08ffba8SPatrick Williams        "-n",
1383e08ffba8SPatrick Williams        "--noformat",
1384e08ffba8SPatrick Williams        dest="FORMAT",
1385e08ffba8SPatrick Williams        action="store_false",
1386e08ffba8SPatrick Williams        required=False,
1387e08ffba8SPatrick Williams        help="Whether or not to run format code",
1388e08ffba8SPatrick Williams    )
138933df8790SMatthew Barth    args = parser.parse_args(sys.argv[1:])
139033df8790SMatthew Barth    WORKSPACE = args.WORKSPACE
139133df8790SMatthew Barth    UNIT_TEST_PKG = args.PACKAGE
139265b37fafSWilliam A. Kennington III    TEST_ONLY = args.TEST_ONLY
1393d8e150a5SEwelina Walkusz    NO_CPPCHECK = args.NO_CPPCHECK
1394298d56b3SRamin Izadpanah    INTEGRATION_TEST = args.INTEGRATION_TEST
1395a61acb50SAndrew Geissler    BRANCH = args.BRANCH
13967ef9330bSLei YU    FORMAT_CODE = args.FORMAT
139733df8790SMatthew Barth    if args.verbose:
1398e08ffba8SPatrick Williams
139933df8790SMatthew Barth        def printline(*line):
140033df8790SMatthew Barth            for arg in line:
1401e08ffba8SPatrick Williams                print(arg, end=" ")
140289b64b6fSAndrew Jeffery            print()
1403e08ffba8SPatrick Williams
140433df8790SMatthew Barth    else:
1405e08ffba8SPatrick Williams
1406fce557f0SAndrew Jeffery        def printline(*line):
1407fce557f0SAndrew Jeffery            pass
1408ccb7f854SMatthew Barth
1409b6535953SPatrick Williams    CODE_SCAN_DIR = os.path.join(WORKSPACE, UNIT_TEST_PKG)
14107ef9330bSLei YU
1411330b0777SPatrick Williams    # Run format-code.sh, which will in turn call any repo-level formatters.
14127ef9330bSLei YU    if FORMAT_CODE:
1413e08ffba8SPatrick Williams        check_call_cmd(
1414e08ffba8SPatrick Williams            os.path.join(
1415e08ffba8SPatrick Williams                WORKSPACE, "openbmc-build-scripts", "scripts", "format-code.sh"
1416e08ffba8SPatrick Williams            ),
1417e08ffba8SPatrick Williams            CODE_SCAN_DIR,
1418e08ffba8SPatrick Williams        )
141931502ddbSAndrew Geissler
142032768b87SEd Tanous        # Check to see if any files changed
1421e08ffba8SPatrick Williams        check_call_cmd(
1422e08ffba8SPatrick Williams            "git", "-C", CODE_SCAN_DIR, "--no-pager", "diff", "--exit-code"
1423e08ffba8SPatrick Williams        )
142432768b87SEd Tanous
14258cb74fcaSAndrew Geissler    # Check if this repo has a supported make infrastructure
1426b6535953SPatrick Williams    pkg = Package(UNIT_TEST_PKG, CODE_SCAN_DIR)
14278cb74fcaSAndrew Geissler    if not pkg.build_system():
14288cb74fcaSAndrew Geissler        print("No valid build system, exit")
14298cb74fcaSAndrew Geissler        sys.exit(0)
14308cb74fcaSAndrew Geissler
1431ccb7f854SMatthew Barth    prev_umask = os.umask(000)
143215e423efSAndrew Jeffery
1433a62a1a13SLeonel Gonzalez    # Determine dependencies and add them
1434a62a1a13SLeonel Gonzalez    dep_added = dict()
1435a62a1a13SLeonel Gonzalez    dep_added[UNIT_TEST_PKG] = False
143615e423efSAndrew Jeffery
1437a62a1a13SLeonel Gonzalez    # Create dependency tree
1438a62a1a13SLeonel Gonzalez    dep_tree = DepTree(UNIT_TEST_PKG)
1439b6535953SPatrick Williams    build_dep_tree(UNIT_TEST_PKG, CODE_SCAN_DIR, dep_added, dep_tree, BRANCH)
1440a62a1a13SLeonel Gonzalez
1441a62a1a13SLeonel Gonzalez    # Reorder Dependency Tree
144289b64b6fSAndrew Jeffery    for pkg_name, regex_str in DEPENDENCIES_REGEX.items():
1443a62a1a13SLeonel Gonzalez        dep_tree.ReorderDeps(pkg_name, regex_str)
1444a62a1a13SLeonel Gonzalez    if args.verbose:
1445a62a1a13SLeonel Gonzalez        dep_tree.PrintTree()
144615e423efSAndrew Jeffery
1447a62a1a13SLeonel Gonzalez    install_list = dep_tree.GetInstallList()
144815e423efSAndrew Jeffery
1449d61316dcSWilliam A. Kennington III    # We don't want to treat our package as a dependency
1450d61316dcSWilliam A. Kennington III    install_list.remove(UNIT_TEST_PKG)
145115e423efSAndrew Jeffery
145215e423efSAndrew Jeffery    # Install reordered dependencies
1453d61316dcSWilliam A. Kennington III    for dep in install_list:
1454d61316dcSWilliam A. Kennington III        build_and_install(dep, False)
145515e423efSAndrew Jeffery
14560f0a680eSWilliam A. Kennington III    # Run package unit tests
1457d61316dcSWilliam A. Kennington III    build_and_install(UNIT_TEST_PKG, True)
145832383355SWilliam A. Kennington III
1459ccb7f854SMatthew Barth    os.umask(prev_umask)
1460878df5c3SJames Feist
14619bfaaad4SMatt Spinler    # Run any custom CI scripts the repo has, of which there can be
14629bfaaad4SMatt Spinler    # multiple of and anywhere in the repository.
1463e08ffba8SPatrick Williams    ci_scripts = find_file(["run-ci.sh", "run-ci"], CODE_SCAN_DIR)
14649bfaaad4SMatt Spinler    if ci_scripts:
1465b6535953SPatrick Williams        os.chdir(CODE_SCAN_DIR)
14669bfaaad4SMatt Spinler        for ci_script in ci_scripts:
146755448adbSPatrick Williams            check_call_cmd(ci_script)
1468