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