blob: e5ee8e23baa8ec10c66ce3027ee9c041bd2dae73 [file] [log] [blame]
Andrew Jeffery89b64b62020-03-13 12:15:48 +10301#!/usr/bin/env python3
Matthew Barthccb7f852016-11-23 17:43:02 -06002
3"""
4This script determines the given package's openbmc dependencies from its
5configure.ac file where it downloads, configures, builds, and installs each of
6these dependencies. Then the given package is configured, built, and installed
7prior to executing its unit tests.
8"""
9
Patrick Williamse08ffba2022-12-05 10:33:46 -060010import argparse
Andrew Jefferybf83a1b2024-05-15 22:05:32 +093011import json
Patrick Williamse08ffba2022-12-05 10:33:46 -060012import multiprocessing
13import os
14import platform
15import re
16import shutil
17import subprocess
18import sys
19from subprocess import CalledProcessError, check_call
20from tempfile import TemporaryDirectory
21from urllib.parse import urljoin
22
Matthew Barthd1810372016-12-19 16:57:21 -060023from git import Repo
Patrick Williamse795dfe2022-12-06 10:07:02 -060024
William A. Kennington III3992d102021-05-17 01:41:04 -070025# interpreter is not used directly but this resolves dependency ordering
26# that would be broken if we didn't include it.
Patrick Williamse08ffba2022-12-05 10:33:46 -060027from mesonbuild import interpreter # noqa: F401
Ewelina Walkuszfb6653c2024-07-15 16:01:27 +020028from mesonbuild import optinterpreter, options
Patrick Williams95095f12021-04-14 14:42:35 -050029from mesonbuild.mesonlib import version_compare as meson_version_compare
Ewelina Walkusz5618dd52025-01-20 15:12:54 +010030from mesonbuild.options import OptionKey, OptionStore
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -050031
32
Patrick Williamse08ffba2022-12-05 10:33:46 -060033class DepTree:
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -050034 """
35 Represents package dependency tree, where each node is a DepTree with a
36 name and DepTree children.
37 """
38
39 def __init__(self, name):
40 """
41 Create new DepTree.
42
43 Parameter descriptions:
44 name Name of new tree node.
45 """
46 self.name = name
47 self.children = list()
48
49 def AddChild(self, name):
50 """
51 Add new child node to current node.
52
53 Parameter descriptions:
54 name Name of new child
55 """
56 new_child = DepTree(name)
57 self.children.append(new_child)
58 return new_child
59
60 def AddChildNode(self, node):
61 """
62 Add existing child node to current node.
63
64 Parameter descriptions:
65 node Tree node to add
66 """
67 self.children.append(node)
68
69 def RemoveChild(self, name):
70 """
71 Remove child node.
72
73 Parameter descriptions:
74 name Name of child to remove
75 """
76 for child in self.children:
77 if child.name == name:
78 self.children.remove(child)
79 return
80
81 def GetNode(self, name):
82 """
83 Return node with matching name. Return None if not found.
84
85 Parameter descriptions:
86 name Name of node to return
87 """
88 if self.name == name:
89 return self
90 for child in self.children:
91 node = child.GetNode(name)
92 if node:
93 return node
94 return None
95
96 def GetParentNode(self, name, parent_node=None):
97 """
98 Return parent of node with matching name. Return none if not found.
99
100 Parameter descriptions:
101 name Name of node to get parent of
102 parent_node Parent of current node
103 """
104 if self.name == name:
105 return parent_node
106 for child in self.children:
107 found_node = child.GetParentNode(name, self)
108 if found_node:
109 return found_node
110 return None
111
112 def GetPath(self, name, path=None):
113 """
114 Return list of node names from head to matching name.
115 Return None if not found.
116
117 Parameter descriptions:
118 name Name of node
119 path List of node names from head to current node
120 """
121 if not path:
122 path = []
123 if self.name == name:
124 path.append(self.name)
125 return path
126 for child in self.children:
127 match = child.GetPath(name, path + [self.name])
128 if match:
129 return match
130 return None
131
132 def GetPathRegex(self, name, regex_str, path=None):
133 """
134 Return list of node paths that end in name, or match regex_str.
135 Return empty list if not found.
136
137 Parameter descriptions:
138 name Name of node to search for
139 regex_str Regex string to match node names
140 path Path of node names from head to current node
141 """
142 new_paths = []
143 if not path:
144 path = []
145 match = re.match(regex_str, self.name)
146 if (self.name == name) or (match):
147 new_paths.append(path + [self.name])
148 for child in self.children:
149 return_paths = None
150 full_path = path + [self.name]
151 return_paths = child.GetPathRegex(name, regex_str, full_path)
152 for i in return_paths:
153 new_paths.append(i)
154 return new_paths
155
156 def MoveNode(self, from_name, to_name):
157 """
158 Mode existing from_name node to become child of to_name node.
159
160 Parameter descriptions:
161 from_name Name of node to make a child of to_name
162 to_name Name of node to make parent of from_name
163 """
164 parent_from_node = self.GetParentNode(from_name)
165 from_node = self.GetNode(from_name)
166 parent_from_node.RemoveChild(from_name)
167 to_node = self.GetNode(to_name)
168 to_node.AddChildNode(from_node)
169
170 def ReorderDeps(self, name, regex_str):
171 """
172 Reorder dependency tree. If tree contains nodes with names that
173 match 'name' and 'regex_str', move 'regex_str' nodes that are
174 to the right of 'name' node, so that they become children of the
175 'name' node.
176
177 Parameter descriptions:
178 name Name of node to look for
179 regex_str Regex string to match names to
180 """
181 name_path = self.GetPath(name)
182 if not name_path:
183 return
184 paths = self.GetPathRegex(name, regex_str)
185 is_name_in_paths = False
186 name_index = 0
187 for i in range(len(paths)):
188 path = paths[i]
189 if path[-1] == name:
190 is_name_in_paths = True
191 name_index = i
192 break
193 if not is_name_in_paths:
194 return
195 for i in range(name_index + 1, len(paths)):
196 path = paths[i]
197 if name in path:
198 continue
199 from_name = path[-1]
200 self.MoveNode(from_name, name)
201
202 def GetInstallList(self):
203 """
204 Return post-order list of node names.
205
206 Parameter descriptions:
207 """
208 install_list = []
209 for child in self.children:
210 child_install_list = child.GetInstallList()
211 install_list.extend(child_install_list)
212 install_list.append(self.name)
213 return install_list
214
215 def PrintTree(self, level=0):
216 """
217 Print pre-order node names with indentation denoting node depth level.
218
219 Parameter descriptions:
220 level Current depth level
221 """
222 INDENT_PER_LEVEL = 4
Patrick Williamse08ffba2022-12-05 10:33:46 -0600223 print(" " * (level * INDENT_PER_LEVEL) + self.name)
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500224 for child in self.children:
225 child.PrintTree(level + 1)
Matthew Barth33df8792016-12-19 14:30:17 -0600226
227
Andrew Jeffery71924fd2023-04-12 23:27:05 +0930228def check_call_cmd(*cmd, **kwargs):
Matthew Barth33df8792016-12-19 14:30:17 -0600229 """
230 Verbose prints the directory location the given command is called from and
231 the command, then executes the command using check_call.
232
233 Parameter descriptions:
234 dir Directory location command is to be called from
235 cmd List of parameters constructing the complete command
236 """
William A. Kennington III1fddb972019-02-06 18:03:53 -0800237 printline(os.getcwd(), ">", " ".join(cmd))
Andrew Jeffery71924fd2023-04-12 23:27:05 +0930238 check_call(cmd, **kwargs)
Matthew Barthccb7f852016-11-23 17:43:02 -0600239
240
Andrew Geisslera61acb52019-01-03 16:32:44 -0600241def clone_pkg(pkg, branch):
Matthew Barth33df8792016-12-19 14:30:17 -0600242 """
243 Clone the given openbmc package's git repository from gerrit into
244 the WORKSPACE location
245
246 Parameter descriptions:
247 pkg Name of the package to clone
Andrew Geisslera61acb52019-01-03 16:32:44 -0600248 branch Branch to clone from pkg
Matthew Barth33df8792016-12-19 14:30:17 -0600249 """
Andrew Jeffery7be94ca2018-03-08 13:15:33 +1030250 pkg_dir = os.path.join(WORKSPACE, pkg)
Patrick Williamse08ffba2022-12-05 10:33:46 -0600251 if os.path.exists(os.path.join(pkg_dir, ".git")):
Andrew Jeffery7be94ca2018-03-08 13:15:33 +1030252 return pkg_dir
Patrick Williamse08ffba2022-12-05 10:33:46 -0600253 pkg_repo = urljoin("https://gerrit.openbmc.org/openbmc/", pkg)
Andrew Jeffery7be94ca2018-03-08 13:15:33 +1030254 os.mkdir(pkg_dir)
Andrew Geisslera61acb52019-01-03 16:32:44 -0600255 printline(pkg_dir, "> git clone", pkg_repo, branch, "./")
256 try:
257 # first try the branch
Andrew Jeffery7d4a26f2020-03-13 11:42:34 +1030258 clone = Repo.clone_from(pkg_repo, pkg_dir, branch=branch)
259 repo_inst = clone.working_dir
Patrick Williamse08ffba2022-12-05 10:33:46 -0600260 except Exception:
Andrew Geisslera61acb52019-01-03 16:32:44 -0600261 printline("Input branch not found, default to master")
Andrew Jeffery7d4a26f2020-03-13 11:42:34 +1030262 clone = Repo.clone_from(pkg_repo, pkg_dir, branch="master")
263 repo_inst = clone.working_dir
Andrew Geisslera61acb52019-01-03 16:32:44 -0600264 return repo_inst
Matthew Barth33df8792016-12-19 14:30:17 -0600265
266
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030267def make_target_exists(target):
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800268 """
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030269 Runs a check against the makefile in the current directory to determine
270 if the target exists so that it can be built.
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800271
272 Parameter descriptions:
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030273 target The make target we are checking
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800274 """
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030275 try:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600276 cmd = ["make", "-n", target]
277 with open(os.devnull, "w") as devnull:
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030278 check_call(cmd, stdout=devnull, stderr=devnull)
279 return True
280 except CalledProcessError:
281 return False
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800282
William A. Kennington III3f1d1202018-12-06 18:02:07 -0800283
William A. Kennington IIIa2156732018-06-30 18:38:09 -0700284make_parallel = [
Patrick Williamse08ffba2022-12-05 10:33:46 -0600285 "make",
William A. Kennington IIIa2156732018-06-30 18:38:09 -0700286 # Run enough jobs to saturate all the cpus
Patrick Williamse08ffba2022-12-05 10:33:46 -0600287 "-j",
288 str(multiprocessing.cpu_count()),
William A. Kennington IIIa2156732018-06-30 18:38:09 -0700289 # Don't start more jobs if the load avg is too high
Patrick Williamse08ffba2022-12-05 10:33:46 -0600290 "-l",
291 str(multiprocessing.cpu_count()),
William A. Kennington IIIa2156732018-06-30 18:38:09 -0700292 # Synchronize the output so logs aren't intermixed in stdout / stderr
Patrick Williamse08ffba2022-12-05 10:33:46 -0600293 "-O",
William A. Kennington IIIa2156732018-06-30 18:38:09 -0700294]
295
William A. Kennington III3f1d1202018-12-06 18:02:07 -0800296
Andrew Jefferyff5c5d52020-03-13 10:12:14 +1030297def build_and_install(name, build_for_testing=False):
William A. Kennington III780ec092018-12-06 14:46:50 -0800298 """
299 Builds and installs the package in the environment. Optionally
300 builds the examples and test cases for package.
301
302 Parameter description:
Andrew Jefferyff5c5d52020-03-13 10:12:14 +1030303 name The name of the package we are building
William A. Kennington IIIa0454912018-12-06 14:47:16 -0800304 build_for_testing Enable options related to testing on the package?
William A. Kennington III780ec092018-12-06 14:46:50 -0800305 """
Andrew Jefferyff5c5d52020-03-13 10:12:14 +1030306 os.chdir(os.path.join(WORKSPACE, name))
William A. Kennington III54d4faf2018-12-06 17:46:24 -0800307
308 # Refresh dynamic linker run time bindings for dependencies
Patrick Williamse08ffba2022-12-05 10:33:46 -0600309 check_call_cmd("sudo", "-n", "--", "ldconfig")
William A. Kennington III54d4faf2018-12-06 17:46:24 -0800310
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030311 pkg = Package()
312 if build_for_testing:
313 pkg.test()
William A. Kennington III3f1d1202018-12-06 18:02:07 -0800314 else:
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030315 pkg.install()
316
William A. Kennington III780ec092018-12-06 14:46:50 -0800317
Andrew Jefferyccf85d62020-03-13 10:25:42 +1030318def build_dep_tree(name, pkgdir, dep_added, head, branch, dep_tree=None):
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500319 """
Andrew Jefferyccf85d62020-03-13 10:25:42 +1030320 For each package (name), starting with the package to be unit tested,
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030321 extract its dependencies. For each package dependency defined, recursively
322 apply the same strategy
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500323
324 Parameter descriptions:
Andrew Jefferyccf85d62020-03-13 10:25:42 +1030325 name Name of the package
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500326 pkgdir Directory where package source is located
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800327 dep_added Current dict of dependencies and added status
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500328 head Head node of the dependency tree
Andrew Geisslera61acb52019-01-03 16:32:44 -0600329 branch Branch to clone from pkg
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500330 dep_tree Current dependency tree node
331 """
332 if not dep_tree:
333 dep_tree = head
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800334
William A. Kennington IIIbe6aab22018-12-06 15:01:54 -0800335 with open("/tmp/depcache", "r") as depcache:
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800336 cache = depcache.readline()
337
338 # Read out pkg dependencies
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030339 pkg = Package(name, pkgdir)
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800340
Patrick Williams38c6b7d2021-02-18 07:26:14 -0600341 build = pkg.build_system()
Patrick Williamse08ffba2022-12-05 10:33:46 -0600342 if not build:
Patrick Williams38c6b7d2021-02-18 07:26:14 -0600343 raise Exception(f"Unable to find build system for {name}.")
344
345 for dep in set(build.dependencies()):
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800346 if dep in cache:
347 continue
348 # Dependency package not already known
349 if dep_added.get(dep) is None:
Patrick Williams38c6b7d2021-02-18 07:26:14 -0600350 print(f"Adding {dep} dependency to {name}.")
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800351 # Dependency package not added
352 new_child = dep_tree.AddChild(dep)
353 dep_added[dep] = False
Andrew Jeffery3b92fdd2020-03-13 11:49:18 +1030354 dep_pkgdir = clone_pkg(dep, branch)
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800355 # Determine this dependency package's
356 # dependencies and add them before
357 # returning to add this package
Patrick Williamse08ffba2022-12-05 10:33:46 -0600358 dep_added = build_dep_tree(
359 dep, dep_pkgdir, dep_added, head, branch, new_child
360 )
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800361 else:
362 # Dependency package known and added
363 if dep_added[dep]:
Andrew Jeffery2cb0c7a2018-03-08 13:19:08 +1030364 continue
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500365 else:
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800366 # Cyclic dependency failure
Patrick Williamse08ffba2022-12-05 10:33:46 -0600367 raise Exception("Cyclic dependencies found in " + name)
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500368
Andrew Jefferyccf85d62020-03-13 10:25:42 +1030369 if not dep_added[name]:
370 dep_added[name] = True
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500371
372 return dep_added
Matthew Barthccb7f852016-11-23 17:43:02 -0600373
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700374
William A. Kennington III90b106a2019-02-06 18:08:24 -0800375def run_cppcheck():
Ed Tanousc7198552022-07-01 08:15:50 -0700376 if not os.path.exists(os.path.join("build", "compile_commands.json")):
Brad Bishop48424d42020-01-07 13:01:31 -0500377 return None
378
Patrick Williams485a0922022-08-12 11:44:55 -0500379 with TemporaryDirectory() as cpp_dir:
Patrick Williams485a0922022-08-12 11:44:55 -0500380 # http://cppcheck.sourceforge.net/manual.pdf
381 try:
382 check_call_cmd(
Patrick Williamse08ffba2022-12-05 10:33:46 -0600383 "cppcheck",
384 "-j",
385 str(multiprocessing.cpu_count()),
386 "--enable=style,performance,portability,missingInclude",
Brad Bishop688f8372023-03-02 22:56:31 -0500387 "--inline-suppr",
Patrick Williamse08ffba2022-12-05 10:33:46 -0600388 "--suppress=useStlAlgorithm",
389 "--suppress=unusedStructMember",
390 "--suppress=postfixOperator",
391 "--suppress=unreadVariable",
392 "--suppress=knownConditionTrueFalse",
393 "--library=googletest",
394 "--project=build/compile_commands.json",
395 f"--cppcheck-build-dir={cpp_dir}",
Patrick Williams485a0922022-08-12 11:44:55 -0500396 )
397 except subprocess.CalledProcessError:
398 print("cppcheck found errors")
Lei YUdbd7cd62022-07-19 19:24:01 +0800399
Andrew Jefferye5fffa02020-03-13 11:51:57 +1030400
William A. Kennington III37a89a22018-12-13 14:32:02 -0800401def is_valgrind_safe():
402 """
403 Returns whether it is safe to run valgrind on our platform
404 """
Patrick Williamse08ffba2022-12-05 10:33:46 -0600405 src = "unit-test-vg.c"
406 exe = "./unit-test-vg"
407 with open(src, "w") as h:
408 h.write("#include <errno.h>\n")
409 h.write("#include <stdio.h>\n")
410 h.write("#include <stdlib.h>\n")
411 h.write("#include <string.h>\n")
412 h.write("int main() {\n")
413 h.write("char *heap_str = malloc(16);\n")
William A. Kennington III0326ded2019-02-07 00:33:28 -0800414 h.write('strcpy(heap_str, "RandString");\n')
415 h.write('int res = strcmp("RandString", heap_str);\n')
Patrick Williamse08ffba2022-12-05 10:33:46 -0600416 h.write("free(heap_str);\n")
417 h.write("char errstr[64];\n")
418 h.write("strerror_r(EINVAL, errstr, sizeof(errstr));\n")
William A. Kennington IIIafb0f982019-04-26 17:30:28 -0700419 h.write('printf("%s\\n", errstr);\n')
Patrick Williamse08ffba2022-12-05 10:33:46 -0600420 h.write("return res;\n")
421 h.write("}\n")
William A. Kennington III0326ded2019-02-07 00:33:28 -0800422 try:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600423 with open(os.devnull, "w") as devnull:
424 check_call(
425 ["gcc", "-O2", "-o", exe, src], stdout=devnull, stderr=devnull
426 )
427 check_call(
428 ["valgrind", "--error-exitcode=99", exe],
429 stdout=devnull,
430 stderr=devnull,
431 )
William A. Kennington III0326ded2019-02-07 00:33:28 -0800432 return True
Patrick Williamse08ffba2022-12-05 10:33:46 -0600433 except Exception:
William A. Kennington III0326ded2019-02-07 00:33:28 -0800434 sys.stderr.write("###### Platform is not valgrind safe ######\n")
435 return False
436 finally:
437 os.remove(src)
438 os.remove(exe)
William A. Kennington III37a89a22018-12-13 14:32:02 -0800439
Andrew Jefferye5fffa02020-03-13 11:51:57 +1030440
William A. Kennington III282e3302019-02-04 16:55:05 -0800441def is_sanitize_safe():
442 """
443 Returns whether it is safe to run sanitizers on our platform
444 """
Patrick Williamse08ffba2022-12-05 10:33:46 -0600445 src = "unit-test-sanitize.c"
446 exe = "./unit-test-sanitize"
447 with open(src, "w") as h:
448 h.write("int main() { return 0; }\n")
William A. Kennington III0b7fb2b2019-02-07 00:33:42 -0800449 try:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600450 with open(os.devnull, "w") as devnull:
451 check_call(
452 [
453 "gcc",
454 "-O2",
455 "-fsanitize=address",
456 "-fsanitize=undefined",
457 "-o",
458 exe,
459 src,
460 ],
461 stdout=devnull,
462 stderr=devnull,
463 )
William A. Kennington III0b7fb2b2019-02-07 00:33:42 -0800464 check_call([exe], stdout=devnull, stderr=devnull)
Andrew Geisslercd9578b2021-05-04 10:18:01 -0500465
466 # TODO - Sanitizer not working on ppc64le
467 # https://github.com/openbmc/openbmc-build-scripts/issues/31
Patrick Williamse08ffba2022-12-05 10:33:46 -0600468 if platform.processor() == "ppc64le":
Andrew Geisslercd9578b2021-05-04 10:18:01 -0500469 sys.stderr.write("###### ppc64le is not sanitize safe ######\n")
470 return False
471 else:
472 return True
Patrick Williamse08ffba2022-12-05 10:33:46 -0600473 except Exception:
William A. Kennington III0b7fb2b2019-02-07 00:33:42 -0800474 sys.stderr.write("###### Platform is not sanitize safe ######\n")
475 return False
476 finally:
477 os.remove(src)
478 os.remove(exe)
William A. Kennington III282e3302019-02-04 16:55:05 -0800479
William A. Kennington III49d4e592019-02-06 17:59:27 -0800480
William A. Kennington IIIeaff24a2019-02-06 16:57:42 -0800481def maybe_make_valgrind():
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700482 """
483 Potentially runs the unit tests through valgrind for the package
484 via `make check-valgrind`. If the package does not have valgrind testing
485 then it just skips over this.
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700486 """
William A. Kennington III4e1d0a12018-07-16 12:04:03 -0700487 # Valgrind testing is currently broken by an aggressive strcmp optimization
488 # that is inlined into optimized code for POWER by gcc 7+. Until we find
489 # a workaround, just don't run valgrind tests on POWER.
490 # https://github.com/openbmc/openbmc/issues/3315
William A. Kennington III37a89a22018-12-13 14:32:02 -0800491 if not is_valgrind_safe():
William A. Kennington III75130192019-02-07 00:34:14 -0800492 sys.stderr.write("###### Skipping valgrind ######\n")
William A. Kennington III4e1d0a12018-07-16 12:04:03 -0700493 return
Patrick Williamse08ffba2022-12-05 10:33:46 -0600494 if not make_target_exists("check-valgrind"):
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700495 return
496
497 try:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600498 cmd = make_parallel + ["check-valgrind"]
William A. Kennington III1fddb972019-02-06 18:03:53 -0800499 check_call_cmd(*cmd)
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700500 except CalledProcessError:
William A. Kennington III90b106a2019-02-06 18:08:24 -0800501 for root, _, files in os.walk(os.getcwd()):
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700502 for f in files:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600503 if re.search("test-suite-[a-z]+.log", f) is None:
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700504 continue
Patrick Williamse08ffba2022-12-05 10:33:46 -0600505 check_call_cmd("cat", os.path.join(root, f))
506 raise Exception("Valgrind tests failed")
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700507
Andrew Jefferye5fffa02020-03-13 11:51:57 +1030508
William A. Kennington IIIeaff24a2019-02-06 16:57:42 -0800509def maybe_make_coverage():
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700510 """
511 Potentially runs the unit tests through code coverage for the package
512 via `make check-code-coverage`. If the package does not have code coverage
513 testing then it just skips over this.
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700514 """
Patrick Williamse08ffba2022-12-05 10:33:46 -0600515 if not make_target_exists("check-code-coverage"):
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700516 return
517
518 # Actually run code coverage
519 try:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600520 cmd = make_parallel + ["check-code-coverage"]
William A. Kennington III1fddb972019-02-06 18:03:53 -0800521 check_call_cmd(*cmd)
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700522 except CalledProcessError:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600523 raise Exception("Code coverage failed")
Matthew Barthccb7f852016-11-23 17:43:02 -0600524
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030525
526class BuildSystem(object):
527 """
528 Build systems generally provide the means to configure, build, install and
529 test software. The BuildSystem class defines a set of interfaces on top of
530 which Autotools, Meson, CMake and possibly other build system drivers can
531 be implemented, separating out the phases to control whether a package
532 should merely be installed or also tested and analyzed.
533 """
Lei YU08d2b922022-04-25 11:21:36 +0800534
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030535 def __init__(self, package, path):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600536 """Initialise the driver with properties independent of the build
537 system
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030538
539 Keyword arguments:
540 package: The name of the package. Derived from the path if None
541 path: The path to the package. Set to the working directory if None
542 """
543 self.path = "." if not path else path
Andrew Jeffery47fbfa52020-03-13 12:05:09 +1030544 realpath = os.path.realpath(self.path)
545 self.package = package if package else os.path.basename(realpath)
Andrew Jeffery3f8e5a72020-03-13 11:47:56 +1030546 self.build_for_testing = False
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030547
548 def probe(self):
549 """Test if the build system driver can be applied to the package
550
551 Return True if the driver can drive the package's build system,
552 otherwise False.
553
554 Generally probe() is implemented by testing for the presence of the
555 build system's configuration file(s).
556 """
Patrick Williamse08ffba2022-12-05 10:33:46 -0600557 raise NotImplementedError
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030558
559 def dependencies(self):
560 """Provide the package's dependencies
561
562 Returns a list of dependencies. If no dependencies are required then an
563 empty list must be returned.
564
565 Generally dependencies() is implemented by analysing and extracting the
566 data from the build system configuration.
567 """
Patrick Williamse08ffba2022-12-05 10:33:46 -0600568 raise NotImplementedError
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030569
570 def configure(self, build_for_testing):
571 """Configure the source ready for building
572
573 Should raise an exception if configuration failed.
574
575 Keyword arguments:
576 build_for_testing: Mark the package as being built for testing rather
577 than for installation as a dependency for the
578 package under test. Setting to True generally
579 implies that the package will be configured to build
580 with debug information, at a low level of
581 optimisation and possibly with sanitizers enabled.
582
583 Generally configure() is implemented by invoking the build system
584 tooling to generate Makefiles or equivalent.
585 """
Patrick Williamse08ffba2022-12-05 10:33:46 -0600586 raise NotImplementedError
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030587
588 def build(self):
589 """Build the software ready for installation and/or testing
590
591 Should raise an exception if the build fails
592
593 Generally build() is implemented by invoking `make` or `ninja`.
594 """
Patrick Williamse08ffba2022-12-05 10:33:46 -0600595 raise NotImplementedError
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030596
597 def install(self):
598 """Install the software ready for use
599
600 Should raise an exception if installation fails
601
602 Like build(), install() is generally implemented by invoking `make` or
603 `ninja`.
604 """
Patrick Williamse08ffba2022-12-05 10:33:46 -0600605 raise NotImplementedError
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030606
607 def test(self):
608 """Build and run the test suite associated with the package
609
610 Should raise an exception if the build or testing fails.
611
612 Like install(), test() is generally implemented by invoking `make` or
613 `ninja`.
614 """
Patrick Williamse08ffba2022-12-05 10:33:46 -0600615 raise NotImplementedError
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030616
617 def analyze(self):
618 """Run any supported analysis tools over the codebase
619
620 Should raise an exception if analysis fails.
621
622 Some analysis tools such as scan-build need injection into the build
623 system. analyze() provides the necessary hook to implement such
624 behaviour. Analyzers independent of the build system can also be
625 specified here but at the cost of possible duplication of code between
626 the build system driver implementations.
627 """
Patrick Williamse08ffba2022-12-05 10:33:46 -0600628 raise NotImplementedError
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030629
630
631class Autotools(BuildSystem):
632 def __init__(self, package=None, path=None):
633 super(Autotools, self).__init__(package, path)
634
635 def probe(self):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600636 return os.path.isfile(os.path.join(self.path, "configure.ac"))
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030637
638 def dependencies(self):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600639 configure_ac = os.path.join(self.path, "configure.ac")
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030640
Patrick Williamse08ffba2022-12-05 10:33:46 -0600641 contents = ""
Andrew Jeffery7d4a26f2020-03-13 11:42:34 +1030642 # Prepend some special function overrides so we can parse out
643 # dependencies
Andrew Jeffery89b64b62020-03-13 12:15:48 +1030644 for macro in DEPENDENCIES.keys():
Patrick Williamse08ffba2022-12-05 10:33:46 -0600645 contents += (
646 "m4_define(["
647 + macro
648 + "], ["
649 + macro
650 + "_START$"
651 + str(DEPENDENCIES_OFFSET[macro] + 1)
652 + macro
653 + "_END])\n"
654 )
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030655 with open(configure_ac, "rt") as f:
Andrew Jeffery47fbfa52020-03-13 12:05:09 +1030656 contents += f.read()
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030657
Patrick Williamse08ffba2022-12-05 10:33:46 -0600658 autoconf_cmdline = ["autoconf", "-Wno-undefined", "-"]
659 autoconf_process = subprocess.Popen(
660 autoconf_cmdline,
661 stdin=subprocess.PIPE,
662 stdout=subprocess.PIPE,
663 stderr=subprocess.PIPE,
664 )
665 document = contents.encode("utf-8")
Andrew Jeffery89b64b62020-03-13 12:15:48 +1030666 (stdout, stderr) = autoconf_process.communicate(input=document)
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030667 if not stdout:
668 print(stderr)
669 raise Exception("Failed to run autoconf for parsing dependencies")
670
671 # Parse out all of the dependency text
672 matches = []
Andrew Jeffery89b64b62020-03-13 12:15:48 +1030673 for macro in DEPENDENCIES.keys():
Patrick Williamse08ffba2022-12-05 10:33:46 -0600674 pattern = "(" + macro + ")_START(.*?)" + macro + "_END"
675 for match in re.compile(pattern).finditer(stdout.decode("utf-8")):
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030676 matches.append((match.group(1), match.group(2)))
677
678 # Look up dependencies from the text
679 found_deps = []
680 for macro, deptext in matches:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600681 for potential_dep in deptext.split(" "):
Andrew Jeffery89b64b62020-03-13 12:15:48 +1030682 for known_dep in DEPENDENCIES[macro].keys():
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030683 if potential_dep.startswith(known_dep):
684 found_deps.append(DEPENDENCIES[macro][known_dep])
685
686 return found_deps
687
688 def _configure_feature(self, flag, enabled):
689 """
690 Returns an configure flag as a string
691
692 Parameters:
693 flag The name of the flag
694 enabled Whether the flag is enabled or disabled
695 """
Patrick Williamse08ffba2022-12-05 10:33:46 -0600696 return "--" + ("enable" if enabled else "disable") + "-" + flag
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030697
698 def configure(self, build_for_testing):
699 self.build_for_testing = build_for_testing
700 conf_flags = [
Patrick Williamse08ffba2022-12-05 10:33:46 -0600701 self._configure_feature("silent-rules", False),
702 self._configure_feature("examples", build_for_testing),
703 self._configure_feature("tests", build_for_testing),
704 self._configure_feature("itests", INTEGRATION_TEST),
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030705 ]
Patrick Williamse08ffba2022-12-05 10:33:46 -0600706 conf_flags.extend(
707 [
George Liu889ac262024-08-06 14:32:42 +0800708 self._configure_feature("code-coverage", False),
Patrick Williamse08ffba2022-12-05 10:33:46 -0600709 self._configure_feature("valgrind", build_for_testing),
710 ]
711 )
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030712 # Add any necessary configure flags for package
713 if CONFIGURE_FLAGS.get(self.package) is not None:
714 conf_flags.extend(CONFIGURE_FLAGS.get(self.package))
Patrick Williamse08ffba2022-12-05 10:33:46 -0600715 for bootstrap in ["bootstrap.sh", "bootstrap", "autogen.sh"]:
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030716 if os.path.exists(bootstrap):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600717 check_call_cmd("./" + bootstrap)
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030718 break
Patrick Williamse08ffba2022-12-05 10:33:46 -0600719 check_call_cmd("./configure", *conf_flags)
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030720
721 def build(self):
722 check_call_cmd(*make_parallel)
723
724 def install(self):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600725 check_call_cmd("sudo", "-n", "--", *(make_parallel + ["install"]))
Johnathan Manteyd40ad0b2024-09-30 10:35:27 -0700726 check_call_cmd("sudo", "-n", "--", "ldconfig")
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030727
728 def test(self):
729 try:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600730 cmd = make_parallel + ["check"]
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030731 for i in range(0, args.repeat):
732 check_call_cmd(*cmd)
Andrew Jefferyd0809692021-05-14 16:23:57 +0930733
734 maybe_make_valgrind()
735 maybe_make_coverage()
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030736 except CalledProcessError:
737 for root, _, files in os.walk(os.getcwd()):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600738 if "test-suite.log" not in files:
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030739 continue
Patrick Williamse08ffba2022-12-05 10:33:46 -0600740 check_call_cmd("cat", os.path.join(root, "test-suite.log"))
741 raise Exception("Unit tests failed")
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030742
743 def analyze(self):
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030744 run_cppcheck()
745
746
747class CMake(BuildSystem):
748 def __init__(self, package=None, path=None):
749 super(CMake, self).__init__(package, path)
750
751 def probe(self):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600752 return os.path.isfile(os.path.join(self.path, "CMakeLists.txt"))
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030753
754 def dependencies(self):
755 return []
756
757 def configure(self, build_for_testing):
758 self.build_for_testing = build_for_testing
Ramin Izadpanah298d56b2020-11-17 16:55:28 +0000759 if INTEGRATION_TEST:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600760 check_call_cmd(
761 "cmake",
762 "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON",
Michal Orzel04770cc2024-06-18 10:38:22 +0200763 "-DCMAKE_CXX_FLAGS='-DBOOST_USE_VALGRIND'",
Patrick Williamse08ffba2022-12-05 10:33:46 -0600764 "-DITESTS=ON",
765 ".",
766 )
Ramin Izadpanah298d56b2020-11-17 16:55:28 +0000767 else:
Michal Orzel04770cc2024-06-18 10:38:22 +0200768 check_call_cmd(
769 "cmake",
770 "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON",
771 "-DCMAKE_CXX_FLAGS='-DBOOST_USE_VALGRIND'",
772 ".",
773 )
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030774
775 def build(self):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600776 check_call_cmd(
777 "cmake",
778 "--build",
779 ".",
780 "--",
781 "-j",
782 str(multiprocessing.cpu_count()),
783 )
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030784
785 def install(self):
Michael Shen87ab5702023-01-13 07:41:11 +0000786 check_call_cmd("sudo", "cmake", "--install", ".")
Johnathan Manteyd40ad0b2024-09-30 10:35:27 -0700787 check_call_cmd("sudo", "-n", "--", "ldconfig")
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030788
789 def test(self):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600790 if make_target_exists("test"):
791 check_call_cmd("ctest", ".")
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030792
793 def analyze(self):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600794 if os.path.isfile(".clang-tidy"):
795 with TemporaryDirectory(prefix="build", dir=".") as build_dir:
Nan Zhou82aaba02022-09-16 00:21:07 +0000796 # clang-tidy needs to run on a clang-specific build
Patrick Williamse08ffba2022-12-05 10:33:46 -0600797 check_call_cmd(
798 "cmake",
799 "-DCMAKE_C_COMPILER=clang",
800 "-DCMAKE_CXX_COMPILER=clang++",
801 "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON",
802 "-H.",
803 "-B" + build_dir,
804 )
Nan Zhou524ebee2022-08-27 23:26:13 +0000805
Patrick Williamse08ffba2022-12-05 10:33:46 -0600806 check_call_cmd(
807 "run-clang-tidy", "-header-filter=.*", "-p", build_dir
808 )
Ed Tanous662890f2020-09-28 17:35:20 -0700809
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030810 maybe_make_valgrind()
811 maybe_make_coverage()
812 run_cppcheck()
813
814
815class Meson(BuildSystem):
Andrew Jefferybf83a1b2024-05-15 22:05:32 +0930816 @staticmethod
817 def _project_name(path):
818 doc = subprocess.check_output(
819 ["meson", "introspect", "--projectinfo", path],
820 stderr=subprocess.STDOUT,
821 ).decode("utf-8")
822 return json.loads(doc)["descriptive_name"]
823
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030824 def __init__(self, package=None, path=None):
825 super(Meson, self).__init__(package, path)
826
827 def probe(self):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600828 return os.path.isfile(os.path.join(self.path, "meson.build"))
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030829
830 def dependencies(self):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600831 meson_build = os.path.join(self.path, "meson.build")
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030832 if not os.path.exists(meson_build):
833 return []
834
835 found_deps = []
836 for root, dirs, files in os.walk(self.path):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600837 if "meson.build" not in files:
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030838 continue
Patrick Williamse08ffba2022-12-05 10:33:46 -0600839 with open(os.path.join(root, "meson.build"), "rt") as f:
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030840 build_contents = f.read()
Nan Zhouef13d532020-07-07 09:52:02 -0700841 pattern = r"dependency\('([^']*)'.*?\),?\n"
Andrew Jeffery47fbfa52020-03-13 12:05:09 +1030842 for match in re.finditer(pattern, build_contents):
843 group = match.group(1)
Patrick Williamse08ffba2022-12-05 10:33:46 -0600844 maybe_dep = DEPENDENCIES["PKG_CHECK_MODULES"].get(group)
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030845 if maybe_dep is not None:
846 found_deps.append(maybe_dep)
847
848 return found_deps
849
850 def _parse_options(self, options_file):
851 """
852 Returns a set of options defined in the provides meson_options.txt file
853
854 Parameters:
855 options_file The file containing options
856 """
Ewelina Walkusz5618dd52025-01-20 15:12:54 +0100857 store = OptionStore()
858 oi = optinterpreter.OptionInterpreter(store, "")
William A. Kennington III0ecd0bc2020-06-04 00:44:22 -0700859 oi.process(options_file)
860 return oi.options
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030861
William A. Kennington IIIfcd70772020-06-04 00:50:23 -0700862 def _configure_boolean(self, val):
863 """
864 Returns the meson flag which signifies the value
865
866 True is true which requires the boolean.
867 False is false which disables the boolean.
868
869 Parameters:
870 val The value being converted
871 """
872 if val is True:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600873 return "true"
William A. Kennington IIIfcd70772020-06-04 00:50:23 -0700874 elif val is False:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600875 return "false"
William A. Kennington IIIfcd70772020-06-04 00:50:23 -0700876 else:
877 raise Exception("Bad meson boolean value")
878
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030879 def _configure_feature(self, val):
880 """
881 Returns the meson flag which signifies the value
882
883 True is enabled which requires the feature.
884 False is disabled which disables the feature.
885 None is auto which autodetects the feature.
886
887 Parameters:
888 val The value being converted
889 """
890 if val is True:
891 return "enabled"
892 elif val is False:
893 return "disabled"
894 elif val is None:
895 return "auto"
896 else:
897 raise Exception("Bad meson feature value")
898
William A. Kennington IIIfcd70772020-06-04 00:50:23 -0700899 def _configure_option(self, opts, key, val):
900 """
901 Returns the meson flag which signifies the value
902 based on the type of the opt
903
904 Parameters:
905 opt The meson option which we are setting
906 val The value being converted
907 """
Ewelina Walkuszfb6653c2024-07-15 16:01:27 +0200908 if isinstance(opts[key], options.UserBooleanOption):
William A. Kennington IIIfcd70772020-06-04 00:50:23 -0700909 str_val = self._configure_boolean(val)
Ewelina Walkuszfb6653c2024-07-15 16:01:27 +0200910 elif isinstance(opts[key], options.UserFeatureOption):
William A. Kennington IIIfcd70772020-06-04 00:50:23 -0700911 str_val = self._configure_feature(val)
912 else:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600913 raise Exception("Unknown meson option type")
William A. Kennington IIIfcd70772020-06-04 00:50:23 -0700914 return "-D{}={}".format(key, str_val)
915
Johnathan Mantey33aec432024-10-09 12:24:10 -0700916 def get_configure_flags(self, build_for_testing):
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030917 self.build_for_testing = build_for_testing
William A. Kennington III0ecd0bc2020-06-04 00:44:22 -0700918 meson_options = {}
William A. Kennington III08fa1d52023-11-29 17:01:19 -0800919 if os.path.exists("meson.options"):
920 meson_options = self._parse_options("meson.options")
921 elif os.path.exists("meson_options.txt"):
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030922 meson_options = self._parse_options("meson_options.txt")
923 meson_flags = [
Patrick Williamse08ffba2022-12-05 10:33:46 -0600924 "-Db_colorout=never",
925 "-Dwerror=true",
926 "-Dwarning_level=3",
Michal Orzel04770cc2024-06-18 10:38:22 +0200927 "-Dcpp_args='-DBOOST_USE_VALGRIND'",
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030928 ]
929 if build_for_testing:
Andrew Jeffery9d43ecf2023-05-25 10:31:08 +0930930 # -Ddebug=true -Doptimization=g is helpful for abi-dumper but isn't a combination that
931 # is supported by meson's build types. Configure it manually.
932 meson_flags.append("-Ddebug=true")
933 meson_flags.append("-Doptimization=g")
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030934 else:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600935 meson_flags.append("--buildtype=debugoptimized")
936 if OptionKey("tests") in meson_options:
937 meson_flags.append(
938 self._configure_option(
939 meson_options, OptionKey("tests"), build_for_testing
940 )
941 )
942 if OptionKey("examples") in meson_options:
943 meson_flags.append(
944 self._configure_option(
945 meson_options, OptionKey("examples"), build_for_testing
946 )
947 )
948 if OptionKey("itests") in meson_options:
949 meson_flags.append(
950 self._configure_option(
951 meson_options, OptionKey("itests"), INTEGRATION_TEST
952 )
953 )
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030954 if MESON_FLAGS.get(self.package) is not None:
955 meson_flags.extend(MESON_FLAGS.get(self.package))
Johnathan Mantey33aec432024-10-09 12:24:10 -0700956 return meson_flags
957
958 def configure(self, build_for_testing):
959 meson_flags = self.get_configure_flags(build_for_testing)
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030960 try:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600961 check_call_cmd(
962 "meson", "setup", "--reconfigure", "build", *meson_flags
963 )
964 except Exception:
965 shutil.rmtree("build", ignore_errors=True)
966 check_call_cmd("meson", "setup", "build", *meson_flags)
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030967
Andrew Jefferybf83a1b2024-05-15 22:05:32 +0930968 self.package = Meson._project_name("build")
969
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030970 def build(self):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600971 check_call_cmd("ninja", "-C", "build")
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030972
973 def install(self):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600974 check_call_cmd("sudo", "-n", "--", "ninja", "-C", "build", "install")
Johnathan Manteyd40ad0b2024-09-30 10:35:27 -0700975 check_call_cmd("sudo", "-n", "--", "ldconfig")
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030976
977 def test(self):
Patrick Williams95095f12021-04-14 14:42:35 -0500978 # It is useful to check various settings of the meson.build file
979 # for compatibility, such as meson_version checks. We shouldn't
980 # do this in the configure path though because it affects subprojects
981 # and dependencies as well, but we only want this applied to the
982 # project-under-test (otherwise an upstream dependency could fail
983 # this check without our control).
984 self._extra_meson_checks()
985
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030986 try:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600987 test_args = ("--repeat", str(args.repeat), "-C", "build")
988 check_call_cmd("meson", "test", "--print-errorlogs", *test_args)
Brad Bishop7b8cef22020-08-26 15:58:09 -0400989
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030990 except CalledProcessError:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600991 raise Exception("Unit tests failed")
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030992
993 def _setup_exists(self, setup):
994 """
995 Returns whether the meson build supports the named test setup.
996
997 Parameter descriptions:
998 setup The setup target to check
999 """
1000 try:
Patrick Williamse08ffba2022-12-05 10:33:46 -06001001 with open(os.devnull, "w"):
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301002 output = subprocess.check_output(
Patrick Williamse08ffba2022-12-05 10:33:46 -06001003 [
1004 "meson",
1005 "test",
1006 "-C",
1007 "build",
1008 "--setup",
Andrew Jeffery8bf1ecd2024-05-16 15:06:28 +09301009 "{}:{}".format(self.package, setup),
Andrew Jefferyfdfd8d42024-05-15 22:27:25 +09301010 "__likely_not_a_test__",
Patrick Williamse08ffba2022-12-05 10:33:46 -06001011 ],
1012 stderr=subprocess.STDOUT,
1013 )
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301014 except CalledProcessError as e:
1015 output = e.output
Patrick Williamse08ffba2022-12-05 10:33:46 -06001016 output = output.decode("utf-8")
Andrew Jeffery8bf1ecd2024-05-16 15:06:28 +09301017 return not re.search("Unknown test setup '[^']+'[.]", output)
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301018
1019 def _maybe_valgrind(self):
1020 """
1021 Potentially runs the unit tests through valgrind for the package
Andrew Jeffery47fbfa52020-03-13 12:05:09 +10301022 via `meson test`. The package can specify custom valgrind
1023 configurations by utilizing add_test_setup() in a meson.build
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301024 """
1025 if not is_valgrind_safe():
1026 sys.stderr.write("###### Skipping valgrind ######\n")
1027 return
1028 try:
Patrick Williamse08ffba2022-12-05 10:33:46 -06001029 if self._setup_exists("valgrind"):
1030 check_call_cmd(
1031 "meson",
1032 "test",
1033 "-t",
1034 "10",
1035 "-C",
1036 "build",
1037 "--print-errorlogs",
1038 "--setup",
Andrew Jeffery8bf1ecd2024-05-16 15:06:28 +09301039 "{}:valgrind".format(self.package),
Patrick Williamse08ffba2022-12-05 10:33:46 -06001040 )
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301041 else:
Patrick Williamse08ffba2022-12-05 10:33:46 -06001042 check_call_cmd(
1043 "meson",
1044 "test",
1045 "-t",
1046 "10",
1047 "-C",
1048 "build",
1049 "--print-errorlogs",
1050 "--wrapper",
Andrew Jeffery34b4f1c2024-05-20 12:19:31 +09301051 "valgrind --error-exitcode=1",
Patrick Williamse08ffba2022-12-05 10:33:46 -06001052 )
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301053 except CalledProcessError:
Patrick Williamse08ffba2022-12-05 10:33:46 -06001054 raise Exception("Valgrind tests failed")
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301055
1056 def analyze(self):
1057 self._maybe_valgrind()
1058
1059 # Run clang-tidy only if the project has a configuration
Patrick Williamse08ffba2022-12-05 10:33:46 -06001060 if os.path.isfile(".clang-tidy"):
Manojkiran Eda1aa91992020-10-02 14:11:53 +05301061 os.environ["CXX"] = "clang++"
Patrick Williamse08ffba2022-12-05 10:33:46 -06001062 with TemporaryDirectory(prefix="build", dir=".") as build_dir:
1063 check_call_cmd("meson", "setup", build_dir)
Ed Tanous29e02312022-12-19 16:20:34 -08001064 if not os.path.isfile(".openbmc-no-clang"):
1065 check_call_cmd("meson", "compile", "-C", build_dir)
Nan Zhou82aaba02022-09-16 00:21:07 +00001066 try:
Lei YUac9c9c72023-12-07 07:29:31 +00001067 check_call_cmd("ninja", "-C", build_dir, "clang-tidy-fix")
Nan Zhou82aaba02022-09-16 00:21:07 +00001068 except subprocess.CalledProcessError:
Patrick Williamse08ffba2022-12-05 10:33:46 -06001069 check_call_cmd(
1070 "git", "-C", CODE_SCAN_DIR, "--no-pager", "diff"
1071 )
Nan Zhou82aaba02022-09-16 00:21:07 +00001072 raise
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301073 # Run the basic clang static analyzer otherwise
1074 else:
Patrick Williamse08ffba2022-12-05 10:33:46 -06001075 check_call_cmd("ninja", "-C", "build", "scan-build")
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301076
1077 # Run tests through sanitizers
1078 # b_lundef is needed if clang++ is CXX since it resolves the
1079 # asan symbols at runtime only. We don't want to set it earlier
1080 # in the build process to ensure we don't have undefined
1081 # runtime code.
1082 if is_sanitize_safe():
Johnathan Mantey33aec432024-10-09 12:24:10 -07001083 meson_flags = self.get_configure_flags(self.build_for_testing)
1084 meson_flags.append("-Db_sanitize=address,undefined")
1085 try:
1086 check_call_cmd(
1087 "meson", "setup", "--reconfigure", "build", *meson_flags
1088 )
1089 except Exception:
1090 shutil.rmtree("build", ignore_errors=True)
1091 check_call_cmd("meson", "setup", "build", *meson_flags)
Patrick Williamse08ffba2022-12-05 10:33:46 -06001092 check_call_cmd(
1093 "meson",
1094 "test",
1095 "-C",
1096 "build",
1097 "--print-errorlogs",
1098 "--logbase",
1099 "testlog-ubasan",
Andrew Jeffery71924fd2023-04-12 23:27:05 +09301100 env=os.environ | {"UBSAN_OPTIONS": "halt_on_error=1"},
Patrick Williamse08ffba2022-12-05 10:33:46 -06001101 )
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301102 # TODO: Fix memory sanitizer
Andrew Jeffery6015ca12020-03-13 11:50:10 +10301103 # check_call_cmd('meson', 'configure', 'build',
1104 # '-Db_sanitize=memory')
1105 # check_call_cmd('meson', 'test', '-C', 'build'
1106 # '--logbase', 'testlog-msan')
Ewelina Walkusz52c3aec2025-01-22 11:22:54 +01001107 meson_flags.remove("-Db_sanitize=address,undefined")
1108 try:
1109 check_call_cmd(
1110 "meson", "setup", "--reconfigure", "build", *meson_flags
1111 )
1112 except Exception:
1113 shutil.rmtree("build", ignore_errors=True)
1114 check_call_cmd("meson", "setup", "build", *meson_flags)
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301115 else:
1116 sys.stderr.write("###### Skipping sanitizers ######\n")
1117
1118 # Run coverage checks
Patrick Williamse08ffba2022-12-05 10:33:46 -06001119 check_call_cmd("meson", "configure", "build", "-Db_coverage=true")
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301120 self.test()
1121 # Only build coverage HTML if coverage files were produced
Patrick Williamse08ffba2022-12-05 10:33:46 -06001122 for root, dirs, files in os.walk("build"):
1123 if any([f.endswith(".gcda") for f in files]):
1124 check_call_cmd("ninja", "-C", "build", "coverage-html")
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301125 break
Patrick Williamse08ffba2022-12-05 10:33:46 -06001126 check_call_cmd("meson", "configure", "build", "-Db_coverage=false")
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301127 run_cppcheck()
1128
Patrick Williams95095f12021-04-14 14:42:35 -05001129 def _extra_meson_checks(self):
Patrick Williamse08ffba2022-12-05 10:33:46 -06001130 with open(os.path.join(self.path, "meson.build"), "rt") as f:
Patrick Williams95095f12021-04-14 14:42:35 -05001131 build_contents = f.read()
1132
1133 # Find project's specified meson_version.
1134 meson_version = None
1135 pattern = r"meson_version:[^']*'([^']*)'"
1136 for match in re.finditer(pattern, build_contents):
1137 group = match.group(1)
1138 meson_version = group
1139
1140 # C++20 requires at least Meson 0.57 but Meson itself doesn't
1141 # identify this. Add to our unit-test checks so that we don't
1142 # get a meson.build missing this.
1143 pattern = r"'cpp_std=c\+\+20'"
1144 for match in re.finditer(pattern, build_contents):
Patrick Williamse08ffba2022-12-05 10:33:46 -06001145 if not meson_version or not meson_version_compare(
1146 meson_version, ">=0.57"
1147 ):
Patrick Williams95095f12021-04-14 14:42:35 -05001148 raise Exception(
1149 "C++20 support requires specifying in meson.build: "
1150 + "meson_version: '>=0.57'"
1151 )
1152
Patrick Williamsb9e07122023-07-12 10:16:28 -05001153 # C++23 requires at least Meson 1.1.1 but Meson itself doesn't
1154 # identify this. Add to our unit-test checks so that we don't
1155 # get a meson.build missing this.
1156 pattern = r"'cpp_std=c\+\+23'"
1157 for match in re.finditer(pattern, build_contents):
1158 if not meson_version or not meson_version_compare(
1159 meson_version, ">=1.1.1"
1160 ):
1161 raise Exception(
1162 "C++23 support requires specifying in meson.build: "
1163 + "meson_version: '>=1.1.1'"
1164 )
1165
Patrick Williamscebaea22023-04-12 10:45:23 -05001166 if "get_variable(" in build_contents:
1167 if not meson_version or not meson_version_compare(
1168 meson_version, ">=0.58"
1169 ):
1170 raise Exception(
1171 "dep.get_variable() with positional argument requires "
1172 + "meson_Version: '>=0.58'"
1173 )
1174
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301175
1176class Package(object):
1177 def __init__(self, name=None, path=None):
Manojkiran Eda29d28252020-06-15 18:58:09 +05301178 self.supported = [Meson, Autotools, CMake]
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301179 self.name = name
1180 self.path = path
1181 self.test_only = False
1182
1183 def build_systems(self):
Andrew Jeffery881041f2020-03-13 11:46:30 +10301184 instances = (system(self.name, self.path) for system in self.supported)
1185 return (instance for instance in instances if instance.probe())
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301186
1187 def build_system(self, preferred=None):
Andrew Geissler8cb74fc2020-03-19 14:48:05 -05001188 systems = list(self.build_systems())
1189
1190 if not systems:
1191 return None
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301192
1193 if preferred:
Andrew Jeffery881041f2020-03-13 11:46:30 +10301194 return {type(system): system for system in systems}[preferred]
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301195
1196 return next(iter(systems))
1197
1198 def install(self, system=None):
1199 if not system:
1200 system = self.build_system()
1201
1202 system.configure(False)
1203 system.build()
1204 system.install()
1205
Andrew Jeffery19d75672020-03-13 10:42:08 +10301206 def _test_one(self, system):
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301207 system.configure(True)
1208 system.build()
1209 system.install()
1210 system.test()
Andrew Jefferyd0809692021-05-14 16:23:57 +09301211 if not TEST_ONLY:
1212 system.analyze()
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301213
Andrew Jeffery19d75672020-03-13 10:42:08 +10301214 def test(self):
1215 for system in self.build_systems():
1216 self._test_one(system)
1217
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301218
Matt Spinler9bfaaad2019-10-25 09:51:50 -05001219def find_file(filename, basedir):
1220 """
Patrick Williams55448ad2020-12-14 14:28:28 -06001221 Finds all occurrences of a file (or list of files) in the base
1222 directory and passes them back with their relative paths.
Matt Spinler9bfaaad2019-10-25 09:51:50 -05001223
1224 Parameter descriptions:
Patrick Williams55448ad2020-12-14 14:28:28 -06001225 filename The name of the file (or list of files) to
1226 find
Matt Spinler9bfaaad2019-10-25 09:51:50 -05001227 basedir The base directory search in
1228 """
1229
Patrick Williams55448ad2020-12-14 14:28:28 -06001230 if not isinstance(filename, list):
Lei YU08d2b922022-04-25 11:21:36 +08001231 filename = [filename]
Patrick Williams55448ad2020-12-14 14:28:28 -06001232
Matt Spinler9bfaaad2019-10-25 09:51:50 -05001233 filepaths = []
1234 for root, dirs, files in os.walk(basedir):
Patrick Williamse08ffba2022-12-05 10:33:46 -06001235 if os.path.split(root)[-1] == "subprojects":
Brad Bishopeb667262021-08-06 13:54:58 -04001236 for f in files:
Patrick Williamse08ffba2022-12-05 10:33:46 -06001237 subproject = ".".join(f.split(".")[0:-1])
1238 if f.endswith(".wrap") and subproject in dirs:
Brad Bishopeb667262021-08-06 13:54:58 -04001239 # don't find files in meson subprojects with wraps
1240 dirs.remove(subproject)
Patrick Williams55448ad2020-12-14 14:28:28 -06001241 for f in filename:
1242 if f in files:
1243 filepaths.append(os.path.join(root, f))
Matt Spinler9bfaaad2019-10-25 09:51:50 -05001244 return filepaths
1245
Andrew Jeffery9cc97d82020-03-13 11:57:44 +10301246
Patrick Williamse08ffba2022-12-05 10:33:46 -06001247if __name__ == "__main__":
Matthew Barthccb7f852016-11-23 17:43:02 -06001248 # CONFIGURE_FLAGS = [GIT REPO]:[CONFIGURE FLAGS]
1249 CONFIGURE_FLAGS = {
Patrick Williamse08ffba2022-12-05 10:33:46 -06001250 "phosphor-logging": [
1251 "--enable-metadata-processing",
1252 "--enable-openpower-pel-extension",
1253 "YAML_DIR=/usr/local/share/phosphor-dbus-yaml/yaml",
1254 ]
Matthew Barthccb7f852016-11-23 17:43:02 -06001255 }
1256
William A. Kennington III3f1d1202018-12-06 18:02:07 -08001257 # MESON_FLAGS = [GIT REPO]:[MESON FLAGS]
1258 MESON_FLAGS = {
Patrick Williamse08ffba2022-12-05 10:33:46 -06001259 "phosphor-dbus-interfaces": [
1260 "-Ddata_com_ibm=true",
1261 "-Ddata_org_open_power=true",
1262 ],
1263 "phosphor-logging": ["-Dopenpower-pel-extension=enabled"],
William A. Kennington III3f1d1202018-12-06 18:02:07 -08001264 }
1265
Matthew Barthccb7f852016-11-23 17:43:02 -06001266 # DEPENDENCIES = [MACRO]:[library/header]:[GIT REPO]
1267 DEPENDENCIES = {
Patrick Williamse08ffba2022-12-05 10:33:46 -06001268 "AC_CHECK_LIB": {"mapper": "phosphor-objmgr"},
1269 "AC_CHECK_HEADER": {
1270 "host-ipmid": "phosphor-host-ipmid",
1271 "blobs-ipmid": "phosphor-ipmi-blobs",
1272 "sdbusplus": "sdbusplus",
1273 "sdeventplus": "sdeventplus",
1274 "stdplus": "stdplus",
1275 "gpioplus": "gpioplus",
1276 "phosphor-logging/log.hpp": "phosphor-logging",
Patrick Williamseab8a372017-01-30 11:21:32 -06001277 },
Patrick Williamse08ffba2022-12-05 10:33:46 -06001278 "AC_PATH_PROG": {"sdbus++": "sdbusplus"},
1279 "PKG_CHECK_MODULES": {
1280 "phosphor-dbus-interfaces": "phosphor-dbus-interfaces",
1281 "libipmid": "phosphor-host-ipmid",
1282 "libipmid-host": "phosphor-host-ipmid",
1283 "sdbusplus": "sdbusplus",
1284 "sdeventplus": "sdeventplus",
1285 "stdplus": "stdplus",
1286 "gpioplus": "gpioplus",
1287 "phosphor-logging": "phosphor-logging",
1288 "phosphor-snmp": "phosphor-snmp",
1289 "ipmiblob": "ipmi-blob-tool",
1290 "hei": "openpower-libhei",
1291 "phosphor-ipmi-blobs": "phosphor-ipmi-blobs",
1292 "libcr51sign": "google-misc",
Brad Bishopebb49112017-02-13 11:07:26 -05001293 },
Matthew Barthccb7f852016-11-23 17:43:02 -06001294 }
1295
William A. Kennington IIIe67f5fc2018-12-06 17:40:30 -08001296 # Offset into array of macro parameters MACRO(0, 1, ...N)
1297 DEPENDENCIES_OFFSET = {
Patrick Williamse08ffba2022-12-05 10:33:46 -06001298 "AC_CHECK_LIB": 0,
1299 "AC_CHECK_HEADER": 0,
1300 "AC_PATH_PROG": 1,
1301 "PKG_CHECK_MODULES": 1,
William A. Kennington IIIe67f5fc2018-12-06 17:40:30 -08001302 }
1303
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -05001304 # DEPENDENCIES_REGEX = [GIT REPO]:[REGEX STRING]
Patrick Williamse08ffba2022-12-05 10:33:46 -06001305 DEPENDENCIES_REGEX = {"phosphor-logging": r"\S+-dbus-interfaces$"}
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -05001306
Matthew Barth33df8792016-12-19 14:30:17 -06001307 # Set command line arguments
1308 parser = argparse.ArgumentParser()
Patrick Williamse08ffba2022-12-05 10:33:46 -06001309 parser.add_argument(
1310 "-w",
1311 "--workspace",
1312 dest="WORKSPACE",
1313 required=True,
1314 help="Workspace directory location(i.e. /home)",
1315 )
1316 parser.add_argument(
1317 "-p",
1318 "--package",
1319 dest="PACKAGE",
1320 required=True,
1321 help="OpenBMC package to be unit tested",
1322 )
1323 parser.add_argument(
1324 "-t",
1325 "--test-only",
1326 dest="TEST_ONLY",
1327 action="store_true",
1328 required=False,
1329 default=False,
1330 help="Only run test cases, no other validation",
1331 )
Ramin Izadpanah298d56b2020-11-17 16:55:28 +00001332 arg_inttests = parser.add_mutually_exclusive_group()
Patrick Williamse08ffba2022-12-05 10:33:46 -06001333 arg_inttests.add_argument(
1334 "--integration-tests",
1335 dest="INTEGRATION_TEST",
1336 action="store_true",
1337 required=False,
1338 default=True,
1339 help="Enable integration tests [default].",
1340 )
1341 arg_inttests.add_argument(
1342 "--no-integration-tests",
1343 dest="INTEGRATION_TEST",
1344 action="store_false",
1345 required=False,
1346 help="Disable integration tests.",
1347 )
1348 parser.add_argument(
1349 "-v",
1350 "--verbose",
1351 action="store_true",
1352 help="Print additional package status messages",
1353 )
1354 parser.add_argument(
1355 "-r", "--repeat", help="Repeat tests N times", type=int, default=1
1356 )
1357 parser.add_argument(
1358 "-b",
1359 "--branch",
1360 dest="BRANCH",
1361 required=False,
1362 help="Branch to target for dependent repositories",
1363 default="master",
1364 )
1365 parser.add_argument(
1366 "-n",
1367 "--noformat",
1368 dest="FORMAT",
1369 action="store_false",
1370 required=False,
1371 help="Whether or not to run format code",
1372 )
Matthew Barth33df8792016-12-19 14:30:17 -06001373 args = parser.parse_args(sys.argv[1:])
1374 WORKSPACE = args.WORKSPACE
1375 UNIT_TEST_PKG = args.PACKAGE
William A. Kennington III65b37fa2019-01-31 15:15:17 -08001376 TEST_ONLY = args.TEST_ONLY
Ramin Izadpanah298d56b2020-11-17 16:55:28 +00001377 INTEGRATION_TEST = args.INTEGRATION_TEST
Andrew Geisslera61acb52019-01-03 16:32:44 -06001378 BRANCH = args.BRANCH
Lei YU7ef93302019-11-06 13:53:21 +08001379 FORMAT_CODE = args.FORMAT
Matthew Barth33df8792016-12-19 14:30:17 -06001380 if args.verbose:
Patrick Williamse08ffba2022-12-05 10:33:46 -06001381
Matthew Barth33df8792016-12-19 14:30:17 -06001382 def printline(*line):
1383 for arg in line:
Patrick Williamse08ffba2022-12-05 10:33:46 -06001384 print(arg, end=" ")
Andrew Jeffery89b64b62020-03-13 12:15:48 +10301385 print()
Patrick Williamse08ffba2022-12-05 10:33:46 -06001386
Matthew Barth33df8792016-12-19 14:30:17 -06001387 else:
Patrick Williamse08ffba2022-12-05 10:33:46 -06001388
Andrew Jefferyfce557f2020-03-13 12:12:19 +10301389 def printline(*line):
1390 pass
Matthew Barthccb7f852016-11-23 17:43:02 -06001391
Patrick Williamsb6535952020-12-15 06:40:10 -06001392 CODE_SCAN_DIR = os.path.join(WORKSPACE, UNIT_TEST_PKG)
Lei YU7ef93302019-11-06 13:53:21 +08001393
Patrick Williams330b0772022-11-28 06:14:06 -06001394 # Run format-code.sh, which will in turn call any repo-level formatters.
Lei YU7ef93302019-11-06 13:53:21 +08001395 if FORMAT_CODE:
Patrick Williamse08ffba2022-12-05 10:33:46 -06001396 check_call_cmd(
1397 os.path.join(
1398 WORKSPACE, "openbmc-build-scripts", "scripts", "format-code.sh"
1399 ),
1400 CODE_SCAN_DIR,
1401 )
Andrew Geisslera28286d2018-01-10 11:00:00 -08001402
Ed Tanous32768b82022-01-05 14:14:06 -08001403 # Check to see if any files changed
Patrick Williamse08ffba2022-12-05 10:33:46 -06001404 check_call_cmd(
1405 "git", "-C", CODE_SCAN_DIR, "--no-pager", "diff", "--exit-code"
1406 )
Ed Tanous32768b82022-01-05 14:14:06 -08001407
Andrew Geissler8cb74fc2020-03-19 14:48:05 -05001408 # Check if this repo has a supported make infrastructure
Patrick Williamsb6535952020-12-15 06:40:10 -06001409 pkg = Package(UNIT_TEST_PKG, CODE_SCAN_DIR)
Andrew Geissler8cb74fc2020-03-19 14:48:05 -05001410 if not pkg.build_system():
1411 print("No valid build system, exit")
1412 sys.exit(0)
1413
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301414 prev_umask = os.umask(000)
James Feist878df5c2018-07-26 14:54:28 -07001415
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301416 # Determine dependencies and add them
1417 dep_added = dict()
1418 dep_added[UNIT_TEST_PKG] = False
William A. Kennington III40d5c7c2018-12-13 14:37:59 -08001419
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301420 # Create dependency tree
1421 dep_tree = DepTree(UNIT_TEST_PKG)
Patrick Williamsb6535952020-12-15 06:40:10 -06001422 build_dep_tree(UNIT_TEST_PKG, CODE_SCAN_DIR, dep_added, dep_tree, BRANCH)
William A. Kennington III65b37fa2019-01-31 15:15:17 -08001423
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301424 # Reorder Dependency Tree
Andrew Jeffery89b64b62020-03-13 12:15:48 +10301425 for pkg_name, regex_str in DEPENDENCIES_REGEX.items():
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301426 dep_tree.ReorderDeps(pkg_name, regex_str)
1427 if args.verbose:
1428 dep_tree.PrintTree()
William A. Kennington III65b37fa2019-01-31 15:15:17 -08001429
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301430 install_list = dep_tree.GetInstallList()
Andrew Geissler9ced4ed2019-11-18 14:33:58 -06001431
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301432 # We don't want to treat our package as a dependency
1433 install_list.remove(UNIT_TEST_PKG)
James Feist878df5c2018-07-26 14:54:28 -07001434
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301435 # Install reordered dependencies
1436 for dep in install_list:
1437 build_and_install(dep, False)
James Feist878df5c2018-07-26 14:54:28 -07001438
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301439 # Run package unit tests
1440 build_and_install(UNIT_TEST_PKG, True)
James Feist878df5c2018-07-26 14:54:28 -07001441
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301442 os.umask(prev_umask)
Matt Spinler9bfaaad2019-10-25 09:51:50 -05001443
1444 # Run any custom CI scripts the repo has, of which there can be
1445 # multiple of and anywhere in the repository.
Patrick Williamse08ffba2022-12-05 10:33:46 -06001446 ci_scripts = find_file(["run-ci.sh", "run-ci"], CODE_SCAN_DIR)
Matt Spinler9bfaaad2019-10-25 09:51:50 -05001447 if ci_scripts:
Patrick Williamsb6535952020-12-15 06:40:10 -06001448 os.chdir(CODE_SCAN_DIR)
Matt Spinler9bfaaad2019-10-25 09:51:50 -05001449 for ci_script in ci_scripts:
Patrick Williams55448ad2020-12-14 14:28:28 -06001450 check_call_cmd(ci_script)