blob: 9d79b79443d3bcdde8227eccea9f604f5d3201ec [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():
Ewelina Walkuszd8e150a2025-02-04 14:06:02 +0100376 if (
377 not os.path.exists(os.path.join("build", "compile_commands.json"))
378 or NO_CPPCHECK
379 ):
Brad Bishop48424d42020-01-07 13:01:31 -0500380 return None
381
Patrick Williams485a0922022-08-12 11:44:55 -0500382 with TemporaryDirectory() as cpp_dir:
Patrick Williams485a0922022-08-12 11:44:55 -0500383 # http://cppcheck.sourceforge.net/manual.pdf
384 try:
385 check_call_cmd(
Patrick Williamse08ffba2022-12-05 10:33:46 -0600386 "cppcheck",
387 "-j",
388 str(multiprocessing.cpu_count()),
389 "--enable=style,performance,portability,missingInclude",
Brad Bishop688f8372023-03-02 22:56:31 -0500390 "--inline-suppr",
Patrick Williamse08ffba2022-12-05 10:33:46 -0600391 "--suppress=useStlAlgorithm",
392 "--suppress=unusedStructMember",
393 "--suppress=postfixOperator",
394 "--suppress=unreadVariable",
395 "--suppress=knownConditionTrueFalse",
396 "--library=googletest",
397 "--project=build/compile_commands.json",
398 f"--cppcheck-build-dir={cpp_dir}",
Patrick Williams485a0922022-08-12 11:44:55 -0500399 )
400 except subprocess.CalledProcessError:
401 print("cppcheck found errors")
Lei YUdbd7cd62022-07-19 19:24:01 +0800402
Andrew Jefferye5fffa02020-03-13 11:51:57 +1030403
William A. Kennington III37a89a22018-12-13 14:32:02 -0800404def is_valgrind_safe():
405 """
406 Returns whether it is safe to run valgrind on our platform
407 """
Patrick Williamse08ffba2022-12-05 10:33:46 -0600408 src = "unit-test-vg.c"
409 exe = "./unit-test-vg"
410 with open(src, "w") as h:
411 h.write("#include <errno.h>\n")
412 h.write("#include <stdio.h>\n")
413 h.write("#include <stdlib.h>\n")
414 h.write("#include <string.h>\n")
415 h.write("int main() {\n")
416 h.write("char *heap_str = malloc(16);\n")
William A. Kennington III0326ded2019-02-07 00:33:28 -0800417 h.write('strcpy(heap_str, "RandString");\n')
418 h.write('int res = strcmp("RandString", heap_str);\n')
Patrick Williamse08ffba2022-12-05 10:33:46 -0600419 h.write("free(heap_str);\n")
420 h.write("char errstr[64];\n")
421 h.write("strerror_r(EINVAL, errstr, sizeof(errstr));\n")
William A. Kennington IIIafb0f982019-04-26 17:30:28 -0700422 h.write('printf("%s\\n", errstr);\n')
Patrick Williamse08ffba2022-12-05 10:33:46 -0600423 h.write("return res;\n")
424 h.write("}\n")
William A. Kennington III0326ded2019-02-07 00:33:28 -0800425 try:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600426 with open(os.devnull, "w") as devnull:
427 check_call(
428 ["gcc", "-O2", "-o", exe, src], stdout=devnull, stderr=devnull
429 )
430 check_call(
431 ["valgrind", "--error-exitcode=99", exe],
432 stdout=devnull,
433 stderr=devnull,
434 )
William A. Kennington III0326ded2019-02-07 00:33:28 -0800435 return True
Patrick Williamse08ffba2022-12-05 10:33:46 -0600436 except Exception:
William A. Kennington III0326ded2019-02-07 00:33:28 -0800437 sys.stderr.write("###### Platform is not valgrind safe ######\n")
438 return False
439 finally:
440 os.remove(src)
441 os.remove(exe)
William A. Kennington III37a89a22018-12-13 14:32:02 -0800442
Andrew Jefferye5fffa02020-03-13 11:51:57 +1030443
William A. Kennington III282e3302019-02-04 16:55:05 -0800444def is_sanitize_safe():
445 """
446 Returns whether it is safe to run sanitizers on our platform
447 """
Patrick Williamse08ffba2022-12-05 10:33:46 -0600448 src = "unit-test-sanitize.c"
449 exe = "./unit-test-sanitize"
450 with open(src, "w") as h:
451 h.write("int main() { return 0; }\n")
William A. Kennington III0b7fb2b2019-02-07 00:33:42 -0800452 try:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600453 with open(os.devnull, "w") as devnull:
454 check_call(
455 [
456 "gcc",
457 "-O2",
458 "-fsanitize=address",
459 "-fsanitize=undefined",
460 "-o",
461 exe,
462 src,
463 ],
464 stdout=devnull,
465 stderr=devnull,
466 )
William A. Kennington III0b7fb2b2019-02-07 00:33:42 -0800467 check_call([exe], stdout=devnull, stderr=devnull)
Andrew Geisslercd9578b2021-05-04 10:18:01 -0500468
469 # TODO - Sanitizer not working on ppc64le
470 # https://github.com/openbmc/openbmc-build-scripts/issues/31
Patrick Williamse08ffba2022-12-05 10:33:46 -0600471 if platform.processor() == "ppc64le":
Andrew Geisslercd9578b2021-05-04 10:18:01 -0500472 sys.stderr.write("###### ppc64le is not sanitize safe ######\n")
473 return False
474 else:
475 return True
Patrick Williamse08ffba2022-12-05 10:33:46 -0600476 except Exception:
William A. Kennington III0b7fb2b2019-02-07 00:33:42 -0800477 sys.stderr.write("###### Platform is not sanitize safe ######\n")
478 return False
479 finally:
480 os.remove(src)
481 os.remove(exe)
William A. Kennington III282e3302019-02-04 16:55:05 -0800482
William A. Kennington III49d4e592019-02-06 17:59:27 -0800483
William A. Kennington IIIeaff24a2019-02-06 16:57:42 -0800484def maybe_make_valgrind():
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700485 """
486 Potentially runs the unit tests through valgrind for the package
487 via `make check-valgrind`. If the package does not have valgrind testing
488 then it just skips over this.
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700489 """
William A. Kennington III4e1d0a12018-07-16 12:04:03 -0700490 # Valgrind testing is currently broken by an aggressive strcmp optimization
491 # that is inlined into optimized code for POWER by gcc 7+. Until we find
492 # a workaround, just don't run valgrind tests on POWER.
493 # https://github.com/openbmc/openbmc/issues/3315
William A. Kennington III37a89a22018-12-13 14:32:02 -0800494 if not is_valgrind_safe():
William A. Kennington III75130192019-02-07 00:34:14 -0800495 sys.stderr.write("###### Skipping valgrind ######\n")
William A. Kennington III4e1d0a12018-07-16 12:04:03 -0700496 return
Patrick Williamse08ffba2022-12-05 10:33:46 -0600497 if not make_target_exists("check-valgrind"):
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700498 return
499
500 try:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600501 cmd = make_parallel + ["check-valgrind"]
William A. Kennington III1fddb972019-02-06 18:03:53 -0800502 check_call_cmd(*cmd)
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700503 except CalledProcessError:
William A. Kennington III90b106a2019-02-06 18:08:24 -0800504 for root, _, files in os.walk(os.getcwd()):
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700505 for f in files:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600506 if re.search("test-suite-[a-z]+.log", f) is None:
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700507 continue
Patrick Williamse08ffba2022-12-05 10:33:46 -0600508 check_call_cmd("cat", os.path.join(root, f))
509 raise Exception("Valgrind tests failed")
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700510
Andrew Jefferye5fffa02020-03-13 11:51:57 +1030511
William A. Kennington IIIeaff24a2019-02-06 16:57:42 -0800512def maybe_make_coverage():
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700513 """
514 Potentially runs the unit tests through code coverage for the package
515 via `make check-code-coverage`. If the package does not have code coverage
516 testing then it just skips over this.
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700517 """
Patrick Williamse08ffba2022-12-05 10:33:46 -0600518 if not make_target_exists("check-code-coverage"):
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700519 return
520
521 # Actually run code coverage
522 try:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600523 cmd = make_parallel + ["check-code-coverage"]
William A. Kennington III1fddb972019-02-06 18:03:53 -0800524 check_call_cmd(*cmd)
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700525 except CalledProcessError:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600526 raise Exception("Code coverage failed")
Matthew Barthccb7f852016-11-23 17:43:02 -0600527
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030528
529class BuildSystem(object):
530 """
531 Build systems generally provide the means to configure, build, install and
532 test software. The BuildSystem class defines a set of interfaces on top of
533 which Autotools, Meson, CMake and possibly other build system drivers can
534 be implemented, separating out the phases to control whether a package
535 should merely be installed or also tested and analyzed.
536 """
Lei YU08d2b922022-04-25 11:21:36 +0800537
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030538 def __init__(self, package, path):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600539 """Initialise the driver with properties independent of the build
540 system
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030541
542 Keyword arguments:
543 package: The name of the package. Derived from the path if None
544 path: The path to the package. Set to the working directory if None
545 """
546 self.path = "." if not path else path
Andrew Jeffery47fbfa52020-03-13 12:05:09 +1030547 realpath = os.path.realpath(self.path)
548 self.package = package if package else os.path.basename(realpath)
Andrew Jeffery3f8e5a72020-03-13 11:47:56 +1030549 self.build_for_testing = False
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030550
551 def probe(self):
552 """Test if the build system driver can be applied to the package
553
554 Return True if the driver can drive the package's build system,
555 otherwise False.
556
557 Generally probe() is implemented by testing for the presence of the
558 build system's configuration file(s).
559 """
Patrick Williamse08ffba2022-12-05 10:33:46 -0600560 raise NotImplementedError
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030561
562 def dependencies(self):
563 """Provide the package's dependencies
564
565 Returns a list of dependencies. If no dependencies are required then an
566 empty list must be returned.
567
568 Generally dependencies() is implemented by analysing and extracting the
569 data from the build system configuration.
570 """
Patrick Williamse08ffba2022-12-05 10:33:46 -0600571 raise NotImplementedError
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030572
573 def configure(self, build_for_testing):
574 """Configure the source ready for building
575
576 Should raise an exception if configuration failed.
577
578 Keyword arguments:
579 build_for_testing: Mark the package as being built for testing rather
580 than for installation as a dependency for the
581 package under test. Setting to True generally
582 implies that the package will be configured to build
583 with debug information, at a low level of
584 optimisation and possibly with sanitizers enabled.
585
586 Generally configure() is implemented by invoking the build system
587 tooling to generate Makefiles or equivalent.
588 """
Patrick Williamse08ffba2022-12-05 10:33:46 -0600589 raise NotImplementedError
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030590
591 def build(self):
592 """Build the software ready for installation and/or testing
593
594 Should raise an exception if the build fails
595
596 Generally build() is implemented by invoking `make` or `ninja`.
597 """
Patrick Williamse08ffba2022-12-05 10:33:46 -0600598 raise NotImplementedError
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030599
600 def install(self):
601 """Install the software ready for use
602
603 Should raise an exception if installation fails
604
605 Like build(), install() is generally implemented by invoking `make` or
606 `ninja`.
607 """
Patrick Williamse08ffba2022-12-05 10:33:46 -0600608 raise NotImplementedError
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030609
610 def test(self):
611 """Build and run the test suite associated with the package
612
613 Should raise an exception if the build or testing fails.
614
615 Like install(), test() is generally implemented by invoking `make` or
616 `ninja`.
617 """
Patrick Williamse08ffba2022-12-05 10:33:46 -0600618 raise NotImplementedError
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030619
620 def analyze(self):
621 """Run any supported analysis tools over the codebase
622
623 Should raise an exception if analysis fails.
624
625 Some analysis tools such as scan-build need injection into the build
626 system. analyze() provides the necessary hook to implement such
627 behaviour. Analyzers independent of the build system can also be
628 specified here but at the cost of possible duplication of code between
629 the build system driver implementations.
630 """
Patrick Williamse08ffba2022-12-05 10:33:46 -0600631 raise NotImplementedError
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030632
633
634class Autotools(BuildSystem):
635 def __init__(self, package=None, path=None):
636 super(Autotools, self).__init__(package, path)
637
638 def probe(self):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600639 return os.path.isfile(os.path.join(self.path, "configure.ac"))
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030640
641 def dependencies(self):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600642 configure_ac = os.path.join(self.path, "configure.ac")
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030643
Patrick Williamse08ffba2022-12-05 10:33:46 -0600644 contents = ""
Andrew Jeffery7d4a26f2020-03-13 11:42:34 +1030645 # Prepend some special function overrides so we can parse out
646 # dependencies
Andrew Jeffery89b64b62020-03-13 12:15:48 +1030647 for macro in DEPENDENCIES.keys():
Patrick Williamse08ffba2022-12-05 10:33:46 -0600648 contents += (
649 "m4_define(["
650 + macro
651 + "], ["
652 + macro
653 + "_START$"
654 + str(DEPENDENCIES_OFFSET[macro] + 1)
655 + macro
656 + "_END])\n"
657 )
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030658 with open(configure_ac, "rt") as f:
Andrew Jeffery47fbfa52020-03-13 12:05:09 +1030659 contents += f.read()
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030660
Patrick Williamse08ffba2022-12-05 10:33:46 -0600661 autoconf_cmdline = ["autoconf", "-Wno-undefined", "-"]
662 autoconf_process = subprocess.Popen(
663 autoconf_cmdline,
664 stdin=subprocess.PIPE,
665 stdout=subprocess.PIPE,
666 stderr=subprocess.PIPE,
667 )
668 document = contents.encode("utf-8")
Andrew Jeffery89b64b62020-03-13 12:15:48 +1030669 (stdout, stderr) = autoconf_process.communicate(input=document)
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030670 if not stdout:
671 print(stderr)
672 raise Exception("Failed to run autoconf for parsing dependencies")
673
674 # Parse out all of the dependency text
675 matches = []
Andrew Jeffery89b64b62020-03-13 12:15:48 +1030676 for macro in DEPENDENCIES.keys():
Patrick Williamse08ffba2022-12-05 10:33:46 -0600677 pattern = "(" + macro + ")_START(.*?)" + macro + "_END"
678 for match in re.compile(pattern).finditer(stdout.decode("utf-8")):
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030679 matches.append((match.group(1), match.group(2)))
680
681 # Look up dependencies from the text
682 found_deps = []
683 for macro, deptext in matches:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600684 for potential_dep in deptext.split(" "):
Andrew Jeffery89b64b62020-03-13 12:15:48 +1030685 for known_dep in DEPENDENCIES[macro].keys():
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030686 if potential_dep.startswith(known_dep):
687 found_deps.append(DEPENDENCIES[macro][known_dep])
688
689 return found_deps
690
691 def _configure_feature(self, flag, enabled):
692 """
693 Returns an configure flag as a string
694
695 Parameters:
696 flag The name of the flag
697 enabled Whether the flag is enabled or disabled
698 """
Patrick Williamse08ffba2022-12-05 10:33:46 -0600699 return "--" + ("enable" if enabled else "disable") + "-" + flag
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030700
701 def configure(self, build_for_testing):
702 self.build_for_testing = build_for_testing
703 conf_flags = [
Patrick Williamse08ffba2022-12-05 10:33:46 -0600704 self._configure_feature("silent-rules", False),
705 self._configure_feature("examples", build_for_testing),
706 self._configure_feature("tests", build_for_testing),
707 self._configure_feature("itests", INTEGRATION_TEST),
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030708 ]
Patrick Williamse08ffba2022-12-05 10:33:46 -0600709 conf_flags.extend(
710 [
George Liu889ac262024-08-06 14:32:42 +0800711 self._configure_feature("code-coverage", False),
Patrick Williamse08ffba2022-12-05 10:33:46 -0600712 self._configure_feature("valgrind", build_for_testing),
713 ]
714 )
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030715 # Add any necessary configure flags for package
716 if CONFIGURE_FLAGS.get(self.package) is not None:
717 conf_flags.extend(CONFIGURE_FLAGS.get(self.package))
Patrick Williamse08ffba2022-12-05 10:33:46 -0600718 for bootstrap in ["bootstrap.sh", "bootstrap", "autogen.sh"]:
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030719 if os.path.exists(bootstrap):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600720 check_call_cmd("./" + bootstrap)
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030721 break
Patrick Williamse08ffba2022-12-05 10:33:46 -0600722 check_call_cmd("./configure", *conf_flags)
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030723
724 def build(self):
725 check_call_cmd(*make_parallel)
726
727 def install(self):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600728 check_call_cmd("sudo", "-n", "--", *(make_parallel + ["install"]))
Johnathan Manteyd40ad0b2024-09-30 10:35:27 -0700729 check_call_cmd("sudo", "-n", "--", "ldconfig")
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030730
731 def test(self):
732 try:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600733 cmd = make_parallel + ["check"]
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030734 for i in range(0, args.repeat):
735 check_call_cmd(*cmd)
Andrew Jefferyd0809692021-05-14 16:23:57 +0930736
737 maybe_make_valgrind()
738 maybe_make_coverage()
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030739 except CalledProcessError:
740 for root, _, files in os.walk(os.getcwd()):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600741 if "test-suite.log" not in files:
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030742 continue
Patrick Williamse08ffba2022-12-05 10:33:46 -0600743 check_call_cmd("cat", os.path.join(root, "test-suite.log"))
744 raise Exception("Unit tests failed")
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030745
746 def analyze(self):
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030747 run_cppcheck()
748
749
750class CMake(BuildSystem):
751 def __init__(self, package=None, path=None):
752 super(CMake, self).__init__(package, path)
753
754 def probe(self):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600755 return os.path.isfile(os.path.join(self.path, "CMakeLists.txt"))
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030756
757 def dependencies(self):
758 return []
759
760 def configure(self, build_for_testing):
761 self.build_for_testing = build_for_testing
Ramin Izadpanah298d56b2020-11-17 16:55:28 +0000762 if INTEGRATION_TEST:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600763 check_call_cmd(
764 "cmake",
765 "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON",
Michal Orzel04770cc2024-06-18 10:38:22 +0200766 "-DCMAKE_CXX_FLAGS='-DBOOST_USE_VALGRIND'",
Patrick Williamse08ffba2022-12-05 10:33:46 -0600767 "-DITESTS=ON",
768 ".",
769 )
Ramin Izadpanah298d56b2020-11-17 16:55:28 +0000770 else:
Michal Orzel04770cc2024-06-18 10:38:22 +0200771 check_call_cmd(
772 "cmake",
773 "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON",
774 "-DCMAKE_CXX_FLAGS='-DBOOST_USE_VALGRIND'",
775 ".",
776 )
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030777
778 def build(self):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600779 check_call_cmd(
780 "cmake",
781 "--build",
782 ".",
783 "--",
784 "-j",
785 str(multiprocessing.cpu_count()),
786 )
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030787
788 def install(self):
Michael Shen87ab5702023-01-13 07:41:11 +0000789 check_call_cmd("sudo", "cmake", "--install", ".")
Johnathan Manteyd40ad0b2024-09-30 10:35:27 -0700790 check_call_cmd("sudo", "-n", "--", "ldconfig")
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030791
792 def test(self):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600793 if make_target_exists("test"):
794 check_call_cmd("ctest", ".")
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030795
796 def analyze(self):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600797 if os.path.isfile(".clang-tidy"):
798 with TemporaryDirectory(prefix="build", dir=".") as build_dir:
Nan Zhou82aaba02022-09-16 00:21:07 +0000799 # clang-tidy needs to run on a clang-specific build
Patrick Williamse08ffba2022-12-05 10:33:46 -0600800 check_call_cmd(
801 "cmake",
802 "-DCMAKE_C_COMPILER=clang",
803 "-DCMAKE_CXX_COMPILER=clang++",
804 "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON",
805 "-H.",
806 "-B" + build_dir,
807 )
Nan Zhou524ebee2022-08-27 23:26:13 +0000808
Patrick Williamse08ffba2022-12-05 10:33:46 -0600809 check_call_cmd(
810 "run-clang-tidy", "-header-filter=.*", "-p", build_dir
811 )
Ed Tanous662890f2020-09-28 17:35:20 -0700812
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030813 maybe_make_valgrind()
814 maybe_make_coverage()
815 run_cppcheck()
816
817
818class Meson(BuildSystem):
Andrew Jefferybf83a1b2024-05-15 22:05:32 +0930819 @staticmethod
820 def _project_name(path):
821 doc = subprocess.check_output(
822 ["meson", "introspect", "--projectinfo", path],
823 stderr=subprocess.STDOUT,
824 ).decode("utf-8")
825 return json.loads(doc)["descriptive_name"]
826
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030827 def __init__(self, package=None, path=None):
828 super(Meson, self).__init__(package, path)
829
830 def probe(self):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600831 return os.path.isfile(os.path.join(self.path, "meson.build"))
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030832
833 def dependencies(self):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600834 meson_build = os.path.join(self.path, "meson.build")
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030835 if not os.path.exists(meson_build):
836 return []
837
838 found_deps = []
839 for root, dirs, files in os.walk(self.path):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600840 if "meson.build" not in files:
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030841 continue
Patrick Williamse08ffba2022-12-05 10:33:46 -0600842 with open(os.path.join(root, "meson.build"), "rt") as f:
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030843 build_contents = f.read()
Nan Zhouef13d532020-07-07 09:52:02 -0700844 pattern = r"dependency\('([^']*)'.*?\),?\n"
Andrew Jeffery47fbfa52020-03-13 12:05:09 +1030845 for match in re.finditer(pattern, build_contents):
846 group = match.group(1)
Patrick Williamse08ffba2022-12-05 10:33:46 -0600847 maybe_dep = DEPENDENCIES["PKG_CHECK_MODULES"].get(group)
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030848 if maybe_dep is not None:
849 found_deps.append(maybe_dep)
850
851 return found_deps
852
853 def _parse_options(self, options_file):
854 """
855 Returns a set of options defined in the provides meson_options.txt file
856
857 Parameters:
858 options_file The file containing options
859 """
Ewelina Walkusz5618dd52025-01-20 15:12:54 +0100860 store = OptionStore()
861 oi = optinterpreter.OptionInterpreter(store, "")
William A. Kennington III0ecd0bc2020-06-04 00:44:22 -0700862 oi.process(options_file)
863 return oi.options
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030864
William A. Kennington IIIfcd70772020-06-04 00:50:23 -0700865 def _configure_boolean(self, val):
866 """
867 Returns the meson flag which signifies the value
868
869 True is true which requires the boolean.
870 False is false which disables the boolean.
871
872 Parameters:
873 val The value being converted
874 """
875 if val is True:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600876 return "true"
William A. Kennington IIIfcd70772020-06-04 00:50:23 -0700877 elif val is False:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600878 return "false"
William A. Kennington IIIfcd70772020-06-04 00:50:23 -0700879 else:
880 raise Exception("Bad meson boolean value")
881
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030882 def _configure_feature(self, val):
883 """
884 Returns the meson flag which signifies the value
885
886 True is enabled which requires the feature.
887 False is disabled which disables the feature.
888 None is auto which autodetects the feature.
889
890 Parameters:
891 val The value being converted
892 """
893 if val is True:
894 return "enabled"
895 elif val is False:
896 return "disabled"
897 elif val is None:
898 return "auto"
899 else:
900 raise Exception("Bad meson feature value")
901
William A. Kennington IIIfcd70772020-06-04 00:50:23 -0700902 def _configure_option(self, opts, key, val):
903 """
904 Returns the meson flag which signifies the value
905 based on the type of the opt
906
907 Parameters:
908 opt The meson option which we are setting
909 val The value being converted
910 """
Ewelina Walkuszfb6653c2024-07-15 16:01:27 +0200911 if isinstance(opts[key], options.UserBooleanOption):
William A. Kennington IIIfcd70772020-06-04 00:50:23 -0700912 str_val = self._configure_boolean(val)
Ewelina Walkuszfb6653c2024-07-15 16:01:27 +0200913 elif isinstance(opts[key], options.UserFeatureOption):
William A. Kennington IIIfcd70772020-06-04 00:50:23 -0700914 str_val = self._configure_feature(val)
915 else:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600916 raise Exception("Unknown meson option type")
William A. Kennington IIIfcd70772020-06-04 00:50:23 -0700917 return "-D{}={}".format(key, str_val)
918
Johnathan Mantey33aec432024-10-09 12:24:10 -0700919 def get_configure_flags(self, build_for_testing):
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030920 self.build_for_testing = build_for_testing
William A. Kennington III0ecd0bc2020-06-04 00:44:22 -0700921 meson_options = {}
William A. Kennington III08fa1d52023-11-29 17:01:19 -0800922 if os.path.exists("meson.options"):
923 meson_options = self._parse_options("meson.options")
924 elif os.path.exists("meson_options.txt"):
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030925 meson_options = self._parse_options("meson_options.txt")
926 meson_flags = [
Patrick Williamse08ffba2022-12-05 10:33:46 -0600927 "-Db_colorout=never",
928 "-Dwerror=true",
929 "-Dwarning_level=3",
Michal Orzel04770cc2024-06-18 10:38:22 +0200930 "-Dcpp_args='-DBOOST_USE_VALGRIND'",
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030931 ]
932 if build_for_testing:
Andrew Jeffery9d43ecf2023-05-25 10:31:08 +0930933 # -Ddebug=true -Doptimization=g is helpful for abi-dumper but isn't a combination that
934 # is supported by meson's build types. Configure it manually.
935 meson_flags.append("-Ddebug=true")
936 meson_flags.append("-Doptimization=g")
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030937 else:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600938 meson_flags.append("--buildtype=debugoptimized")
939 if OptionKey("tests") in meson_options:
940 meson_flags.append(
941 self._configure_option(
942 meson_options, OptionKey("tests"), build_for_testing
943 )
944 )
945 if OptionKey("examples") in meson_options:
946 meson_flags.append(
947 self._configure_option(
948 meson_options, OptionKey("examples"), build_for_testing
949 )
950 )
951 if OptionKey("itests") in meson_options:
952 meson_flags.append(
953 self._configure_option(
954 meson_options, OptionKey("itests"), INTEGRATION_TEST
955 )
956 )
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030957 if MESON_FLAGS.get(self.package) is not None:
958 meson_flags.extend(MESON_FLAGS.get(self.package))
Johnathan Mantey33aec432024-10-09 12:24:10 -0700959 return meson_flags
960
961 def configure(self, build_for_testing):
962 meson_flags = self.get_configure_flags(build_for_testing)
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030963 try:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600964 check_call_cmd(
965 "meson", "setup", "--reconfigure", "build", *meson_flags
966 )
967 except Exception:
968 shutil.rmtree("build", ignore_errors=True)
969 check_call_cmd("meson", "setup", "build", *meson_flags)
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030970
Andrew Jefferybf83a1b2024-05-15 22:05:32 +0930971 self.package = Meson._project_name("build")
972
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030973 def build(self):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600974 check_call_cmd("ninja", "-C", "build")
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030975
976 def install(self):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600977 check_call_cmd("sudo", "-n", "--", "ninja", "-C", "build", "install")
Johnathan Manteyd40ad0b2024-09-30 10:35:27 -0700978 check_call_cmd("sudo", "-n", "--", "ldconfig")
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030979
980 def test(self):
Patrick Williams95095f12021-04-14 14:42:35 -0500981 # It is useful to check various settings of the meson.build file
982 # for compatibility, such as meson_version checks. We shouldn't
983 # do this in the configure path though because it affects subprojects
984 # and dependencies as well, but we only want this applied to the
985 # project-under-test (otherwise an upstream dependency could fail
986 # this check without our control).
987 self._extra_meson_checks()
988
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030989 try:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600990 test_args = ("--repeat", str(args.repeat), "-C", "build")
991 check_call_cmd("meson", "test", "--print-errorlogs", *test_args)
Brad Bishop7b8cef22020-08-26 15:58:09 -0400992
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030993 except CalledProcessError:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600994 raise Exception("Unit tests failed")
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030995
996 def _setup_exists(self, setup):
997 """
998 Returns whether the meson build supports the named test setup.
999
1000 Parameter descriptions:
1001 setup The setup target to check
1002 """
1003 try:
Patrick Williamse08ffba2022-12-05 10:33:46 -06001004 with open(os.devnull, "w"):
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301005 output = subprocess.check_output(
Patrick Williamse08ffba2022-12-05 10:33:46 -06001006 [
1007 "meson",
1008 "test",
1009 "-C",
1010 "build",
1011 "--setup",
Andrew Jeffery8bf1ecd2024-05-16 15:06:28 +09301012 "{}:{}".format(self.package, setup),
Andrew Jefferyfdfd8d42024-05-15 22:27:25 +09301013 "__likely_not_a_test__",
Patrick Williamse08ffba2022-12-05 10:33:46 -06001014 ],
1015 stderr=subprocess.STDOUT,
1016 )
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301017 except CalledProcessError as e:
1018 output = e.output
Patrick Williamse08ffba2022-12-05 10:33:46 -06001019 output = output.decode("utf-8")
Andrew Jeffery8bf1ecd2024-05-16 15:06:28 +09301020 return not re.search("Unknown test setup '[^']+'[.]", output)
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301021
1022 def _maybe_valgrind(self):
1023 """
1024 Potentially runs the unit tests through valgrind for the package
Andrew Jeffery47fbfa52020-03-13 12:05:09 +10301025 via `meson test`. The package can specify custom valgrind
1026 configurations by utilizing add_test_setup() in a meson.build
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301027 """
1028 if not is_valgrind_safe():
1029 sys.stderr.write("###### Skipping valgrind ######\n")
1030 return
1031 try:
Patrick Williamse08ffba2022-12-05 10:33:46 -06001032 if self._setup_exists("valgrind"):
1033 check_call_cmd(
1034 "meson",
1035 "test",
1036 "-t",
1037 "10",
1038 "-C",
1039 "build",
1040 "--print-errorlogs",
1041 "--setup",
Andrew Jeffery8bf1ecd2024-05-16 15:06:28 +09301042 "{}:valgrind".format(self.package),
Patrick Williamse08ffba2022-12-05 10:33:46 -06001043 )
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301044 else:
Patrick Williamse08ffba2022-12-05 10:33:46 -06001045 check_call_cmd(
1046 "meson",
1047 "test",
1048 "-t",
1049 "10",
1050 "-C",
1051 "build",
1052 "--print-errorlogs",
1053 "--wrapper",
Andrew Jeffery34b4f1c2024-05-20 12:19:31 +09301054 "valgrind --error-exitcode=1",
Patrick Williamse08ffba2022-12-05 10:33:46 -06001055 )
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301056 except CalledProcessError:
Patrick Williamse08ffba2022-12-05 10:33:46 -06001057 raise Exception("Valgrind tests failed")
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301058
1059 def analyze(self):
1060 self._maybe_valgrind()
1061
1062 # Run clang-tidy only if the project has a configuration
Patrick Williamse08ffba2022-12-05 10:33:46 -06001063 if os.path.isfile(".clang-tidy"):
Ed Tanous966d67d2025-03-10 09:03:36 -07001064 os.environ["CC"] = "clang"
Manojkiran Eda1aa91992020-10-02 14:11:53 +05301065 os.environ["CXX"] = "clang++"
Patrick Williamse08ffba2022-12-05 10:33:46 -06001066 with TemporaryDirectory(prefix="build", dir=".") as build_dir:
1067 check_call_cmd("meson", "setup", build_dir)
Ed Tanous29e02312022-12-19 16:20:34 -08001068 if not os.path.isfile(".openbmc-no-clang"):
1069 check_call_cmd("meson", "compile", "-C", build_dir)
Nan Zhou82aaba02022-09-16 00:21:07 +00001070 try:
Lei YUac9c9c72023-12-07 07:29:31 +00001071 check_call_cmd("ninja", "-C", build_dir, "clang-tidy-fix")
Nan Zhou82aaba02022-09-16 00:21:07 +00001072 except subprocess.CalledProcessError:
Patrick Williamse08ffba2022-12-05 10:33:46 -06001073 check_call_cmd(
1074 "git", "-C", CODE_SCAN_DIR, "--no-pager", "diff"
1075 )
Nan Zhou82aaba02022-09-16 00:21:07 +00001076 raise
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301077 # Run the basic clang static analyzer otherwise
1078 else:
Patrick Williamse08ffba2022-12-05 10:33:46 -06001079 check_call_cmd("ninja", "-C", "build", "scan-build")
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301080
1081 # Run tests through sanitizers
1082 # b_lundef is needed if clang++ is CXX since it resolves the
1083 # asan symbols at runtime only. We don't want to set it earlier
1084 # in the build process to ensure we don't have undefined
1085 # runtime code.
1086 if is_sanitize_safe():
Johnathan Mantey33aec432024-10-09 12:24:10 -07001087 meson_flags = self.get_configure_flags(self.build_for_testing)
1088 meson_flags.append("-Db_sanitize=address,undefined")
1089 try:
1090 check_call_cmd(
1091 "meson", "setup", "--reconfigure", "build", *meson_flags
1092 )
1093 except Exception:
1094 shutil.rmtree("build", ignore_errors=True)
1095 check_call_cmd("meson", "setup", "build", *meson_flags)
Patrick Williamse08ffba2022-12-05 10:33:46 -06001096 check_call_cmd(
1097 "meson",
1098 "test",
1099 "-C",
1100 "build",
1101 "--print-errorlogs",
1102 "--logbase",
1103 "testlog-ubasan",
1104 )
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301105 # TODO: Fix memory sanitizer
Andrew Jeffery6015ca12020-03-13 11:50:10 +10301106 # check_call_cmd('meson', 'configure', 'build',
1107 # '-Db_sanitize=memory')
1108 # check_call_cmd('meson', 'test', '-C', 'build'
1109 # '--logbase', 'testlog-msan')
Ewelina Walkusz52c3aec2025-01-22 11:22:54 +01001110 meson_flags.remove("-Db_sanitize=address,undefined")
1111 try:
1112 check_call_cmd(
1113 "meson", "setup", "--reconfigure", "build", *meson_flags
1114 )
1115 except Exception:
1116 shutil.rmtree("build", ignore_errors=True)
1117 check_call_cmd("meson", "setup", "build", *meson_flags)
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301118 else:
1119 sys.stderr.write("###### Skipping sanitizers ######\n")
1120
1121 # Run coverage checks
Patrick Williamse08ffba2022-12-05 10:33:46 -06001122 check_call_cmd("meson", "configure", "build", "-Db_coverage=true")
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301123 self.test()
1124 # Only build coverage HTML if coverage files were produced
Patrick Williamse08ffba2022-12-05 10:33:46 -06001125 for root, dirs, files in os.walk("build"):
1126 if any([f.endswith(".gcda") for f in files]):
1127 check_call_cmd("ninja", "-C", "build", "coverage-html")
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301128 break
Patrick Williamse08ffba2022-12-05 10:33:46 -06001129 check_call_cmd("meson", "configure", "build", "-Db_coverage=false")
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301130 run_cppcheck()
1131
Patrick Williams95095f12021-04-14 14:42:35 -05001132 def _extra_meson_checks(self):
Patrick Williamse08ffba2022-12-05 10:33:46 -06001133 with open(os.path.join(self.path, "meson.build"), "rt") as f:
Patrick Williams95095f12021-04-14 14:42:35 -05001134 build_contents = f.read()
1135
1136 # Find project's specified meson_version.
1137 meson_version = None
1138 pattern = r"meson_version:[^']*'([^']*)'"
1139 for match in re.finditer(pattern, build_contents):
1140 group = match.group(1)
1141 meson_version = group
1142
1143 # C++20 requires at least Meson 0.57 but Meson itself doesn't
1144 # identify this. Add to our unit-test checks so that we don't
1145 # get a meson.build missing this.
1146 pattern = r"'cpp_std=c\+\+20'"
1147 for match in re.finditer(pattern, build_contents):
Patrick Williamse08ffba2022-12-05 10:33:46 -06001148 if not meson_version or not meson_version_compare(
1149 meson_version, ">=0.57"
1150 ):
Patrick Williams95095f12021-04-14 14:42:35 -05001151 raise Exception(
1152 "C++20 support requires specifying in meson.build: "
1153 + "meson_version: '>=0.57'"
1154 )
1155
Patrick Williamsb9e07122023-07-12 10:16:28 -05001156 # C++23 requires at least Meson 1.1.1 but Meson itself doesn't
1157 # identify this. Add to our unit-test checks so that we don't
1158 # get a meson.build missing this.
1159 pattern = r"'cpp_std=c\+\+23'"
1160 for match in re.finditer(pattern, build_contents):
1161 if not meson_version or not meson_version_compare(
1162 meson_version, ">=1.1.1"
1163 ):
1164 raise Exception(
1165 "C++23 support requires specifying in meson.build: "
1166 + "meson_version: '>=1.1.1'"
1167 )
1168
Patrick Williamscebaea22023-04-12 10:45:23 -05001169 if "get_variable(" in build_contents:
1170 if not meson_version or not meson_version_compare(
1171 meson_version, ">=0.58"
1172 ):
1173 raise Exception(
1174 "dep.get_variable() with positional argument requires "
1175 + "meson_Version: '>=0.58'"
1176 )
1177
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301178
1179class Package(object):
1180 def __init__(self, name=None, path=None):
Manojkiran Eda29d28252020-06-15 18:58:09 +05301181 self.supported = [Meson, Autotools, CMake]
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301182 self.name = name
1183 self.path = path
1184 self.test_only = False
1185
1186 def build_systems(self):
Andrew Jeffery881041f2020-03-13 11:46:30 +10301187 instances = (system(self.name, self.path) for system in self.supported)
1188 return (instance for instance in instances if instance.probe())
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301189
1190 def build_system(self, preferred=None):
Andrew Geissler8cb74fc2020-03-19 14:48:05 -05001191 systems = list(self.build_systems())
1192
1193 if not systems:
1194 return None
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301195
1196 if preferred:
Andrew Jeffery881041f2020-03-13 11:46:30 +10301197 return {type(system): system for system in systems}[preferred]
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301198
1199 return next(iter(systems))
1200
1201 def install(self, system=None):
1202 if not system:
1203 system = self.build_system()
1204
1205 system.configure(False)
1206 system.build()
1207 system.install()
1208
Andrew Jeffery19d75672020-03-13 10:42:08 +10301209 def _test_one(self, system):
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301210 system.configure(True)
1211 system.build()
1212 system.install()
1213 system.test()
Andrew Jefferyd0809692021-05-14 16:23:57 +09301214 if not TEST_ONLY:
1215 system.analyze()
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301216
Andrew Jeffery19d75672020-03-13 10:42:08 +10301217 def test(self):
1218 for system in self.build_systems():
1219 self._test_one(system)
1220
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301221
Matt Spinler9bfaaad2019-10-25 09:51:50 -05001222def find_file(filename, basedir):
1223 """
Patrick Williams55448ad2020-12-14 14:28:28 -06001224 Finds all occurrences of a file (or list of files) in the base
1225 directory and passes them back with their relative paths.
Matt Spinler9bfaaad2019-10-25 09:51:50 -05001226
1227 Parameter descriptions:
Patrick Williams55448ad2020-12-14 14:28:28 -06001228 filename The name of the file (or list of files) to
1229 find
Matt Spinler9bfaaad2019-10-25 09:51:50 -05001230 basedir The base directory search in
1231 """
1232
Patrick Williams55448ad2020-12-14 14:28:28 -06001233 if not isinstance(filename, list):
Lei YU08d2b922022-04-25 11:21:36 +08001234 filename = [filename]
Patrick Williams55448ad2020-12-14 14:28:28 -06001235
Matt Spinler9bfaaad2019-10-25 09:51:50 -05001236 filepaths = []
1237 for root, dirs, files in os.walk(basedir):
Patrick Williamse08ffba2022-12-05 10:33:46 -06001238 if os.path.split(root)[-1] == "subprojects":
Brad Bishopeb667262021-08-06 13:54:58 -04001239 for f in files:
Patrick Williamse08ffba2022-12-05 10:33:46 -06001240 subproject = ".".join(f.split(".")[0:-1])
1241 if f.endswith(".wrap") and subproject in dirs:
Brad Bishopeb667262021-08-06 13:54:58 -04001242 # don't find files in meson subprojects with wraps
1243 dirs.remove(subproject)
Patrick Williams55448ad2020-12-14 14:28:28 -06001244 for f in filename:
1245 if f in files:
1246 filepaths.append(os.path.join(root, f))
Matt Spinler9bfaaad2019-10-25 09:51:50 -05001247 return filepaths
1248
Andrew Jeffery9cc97d82020-03-13 11:57:44 +10301249
Patrick Williamse08ffba2022-12-05 10:33:46 -06001250if __name__ == "__main__":
Matthew Barthccb7f852016-11-23 17:43:02 -06001251 # CONFIGURE_FLAGS = [GIT REPO]:[CONFIGURE FLAGS]
1252 CONFIGURE_FLAGS = {
Patrick Williamse08ffba2022-12-05 10:33:46 -06001253 "phosphor-logging": [
1254 "--enable-metadata-processing",
1255 "--enable-openpower-pel-extension",
1256 "YAML_DIR=/usr/local/share/phosphor-dbus-yaml/yaml",
1257 ]
Matthew Barthccb7f852016-11-23 17:43:02 -06001258 }
1259
William A. Kennington III3f1d1202018-12-06 18:02:07 -08001260 # MESON_FLAGS = [GIT REPO]:[MESON FLAGS]
1261 MESON_FLAGS = {
Patrick Williamse08ffba2022-12-05 10:33:46 -06001262 "phosphor-dbus-interfaces": [
1263 "-Ddata_com_ibm=true",
1264 "-Ddata_org_open_power=true",
1265 ],
1266 "phosphor-logging": ["-Dopenpower-pel-extension=enabled"],
William A. Kennington III3f1d1202018-12-06 18:02:07 -08001267 }
1268
Matthew Barthccb7f852016-11-23 17:43:02 -06001269 # DEPENDENCIES = [MACRO]:[library/header]:[GIT REPO]
1270 DEPENDENCIES = {
Patrick Williamse08ffba2022-12-05 10:33:46 -06001271 "AC_CHECK_LIB": {"mapper": "phosphor-objmgr"},
1272 "AC_CHECK_HEADER": {
1273 "host-ipmid": "phosphor-host-ipmid",
1274 "blobs-ipmid": "phosphor-ipmi-blobs",
1275 "sdbusplus": "sdbusplus",
1276 "sdeventplus": "sdeventplus",
1277 "stdplus": "stdplus",
1278 "gpioplus": "gpioplus",
1279 "phosphor-logging/log.hpp": "phosphor-logging",
Patrick Williamseab8a372017-01-30 11:21:32 -06001280 },
Patrick Williamse08ffba2022-12-05 10:33:46 -06001281 "AC_PATH_PROG": {"sdbus++": "sdbusplus"},
1282 "PKG_CHECK_MODULES": {
1283 "phosphor-dbus-interfaces": "phosphor-dbus-interfaces",
1284 "libipmid": "phosphor-host-ipmid",
1285 "libipmid-host": "phosphor-host-ipmid",
1286 "sdbusplus": "sdbusplus",
1287 "sdeventplus": "sdeventplus",
1288 "stdplus": "stdplus",
1289 "gpioplus": "gpioplus",
1290 "phosphor-logging": "phosphor-logging",
1291 "phosphor-snmp": "phosphor-snmp",
1292 "ipmiblob": "ipmi-blob-tool",
1293 "hei": "openpower-libhei",
1294 "phosphor-ipmi-blobs": "phosphor-ipmi-blobs",
1295 "libcr51sign": "google-misc",
Brad Bishopebb49112017-02-13 11:07:26 -05001296 },
Matthew Barthccb7f852016-11-23 17:43:02 -06001297 }
1298
William A. Kennington IIIe67f5fc2018-12-06 17:40:30 -08001299 # Offset into array of macro parameters MACRO(0, 1, ...N)
1300 DEPENDENCIES_OFFSET = {
Patrick Williamse08ffba2022-12-05 10:33:46 -06001301 "AC_CHECK_LIB": 0,
1302 "AC_CHECK_HEADER": 0,
1303 "AC_PATH_PROG": 1,
1304 "PKG_CHECK_MODULES": 1,
William A. Kennington IIIe67f5fc2018-12-06 17:40:30 -08001305 }
1306
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -05001307 # DEPENDENCIES_REGEX = [GIT REPO]:[REGEX STRING]
Patrick Williamse08ffba2022-12-05 10:33:46 -06001308 DEPENDENCIES_REGEX = {"phosphor-logging": r"\S+-dbus-interfaces$"}
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -05001309
Matthew Barth33df8792016-12-19 14:30:17 -06001310 # Set command line arguments
1311 parser = argparse.ArgumentParser()
Patrick Williamse08ffba2022-12-05 10:33:46 -06001312 parser.add_argument(
1313 "-w",
1314 "--workspace",
1315 dest="WORKSPACE",
1316 required=True,
1317 help="Workspace directory location(i.e. /home)",
1318 )
1319 parser.add_argument(
1320 "-p",
1321 "--package",
1322 dest="PACKAGE",
1323 required=True,
1324 help="OpenBMC package to be unit tested",
1325 )
1326 parser.add_argument(
1327 "-t",
1328 "--test-only",
1329 dest="TEST_ONLY",
1330 action="store_true",
1331 required=False,
1332 default=False,
1333 help="Only run test cases, no other validation",
1334 )
Ewelina Walkuszd8e150a2025-02-04 14:06:02 +01001335 parser.add_argument(
1336 "--no-cppcheck",
1337 dest="NO_CPPCHECK",
1338 action="store_true",
1339 required=False,
1340 default=False,
1341 help="Do not run cppcheck",
1342 )
Ramin Izadpanah298d56b2020-11-17 16:55:28 +00001343 arg_inttests = parser.add_mutually_exclusive_group()
Patrick Williamse08ffba2022-12-05 10:33:46 -06001344 arg_inttests.add_argument(
1345 "--integration-tests",
1346 dest="INTEGRATION_TEST",
1347 action="store_true",
1348 required=False,
1349 default=True,
1350 help="Enable integration tests [default].",
1351 )
1352 arg_inttests.add_argument(
1353 "--no-integration-tests",
1354 dest="INTEGRATION_TEST",
1355 action="store_false",
1356 required=False,
1357 help="Disable integration tests.",
1358 )
1359 parser.add_argument(
1360 "-v",
1361 "--verbose",
1362 action="store_true",
1363 help="Print additional package status messages",
1364 )
1365 parser.add_argument(
1366 "-r", "--repeat", help="Repeat tests N times", type=int, default=1
1367 )
1368 parser.add_argument(
1369 "-b",
1370 "--branch",
1371 dest="BRANCH",
1372 required=False,
1373 help="Branch to target for dependent repositories",
1374 default="master",
1375 )
1376 parser.add_argument(
1377 "-n",
1378 "--noformat",
1379 dest="FORMAT",
1380 action="store_false",
1381 required=False,
1382 help="Whether or not to run format code",
1383 )
Matthew Barth33df8792016-12-19 14:30:17 -06001384 args = parser.parse_args(sys.argv[1:])
1385 WORKSPACE = args.WORKSPACE
1386 UNIT_TEST_PKG = args.PACKAGE
William A. Kennington III65b37fa2019-01-31 15:15:17 -08001387 TEST_ONLY = args.TEST_ONLY
Ewelina Walkuszd8e150a2025-02-04 14:06:02 +01001388 NO_CPPCHECK = args.NO_CPPCHECK
Ramin Izadpanah298d56b2020-11-17 16:55:28 +00001389 INTEGRATION_TEST = args.INTEGRATION_TEST
Andrew Geisslera61acb52019-01-03 16:32:44 -06001390 BRANCH = args.BRANCH
Lei YU7ef93302019-11-06 13:53:21 +08001391 FORMAT_CODE = args.FORMAT
Matthew Barth33df8792016-12-19 14:30:17 -06001392 if args.verbose:
Patrick Williamse08ffba2022-12-05 10:33:46 -06001393
Matthew Barth33df8792016-12-19 14:30:17 -06001394 def printline(*line):
1395 for arg in line:
Patrick Williamse08ffba2022-12-05 10:33:46 -06001396 print(arg, end=" ")
Andrew Jeffery89b64b62020-03-13 12:15:48 +10301397 print()
Patrick Williamse08ffba2022-12-05 10:33:46 -06001398
Matthew Barth33df8792016-12-19 14:30:17 -06001399 else:
Patrick Williamse08ffba2022-12-05 10:33:46 -06001400
Andrew Jefferyfce557f2020-03-13 12:12:19 +10301401 def printline(*line):
1402 pass
Matthew Barthccb7f852016-11-23 17:43:02 -06001403
Patrick Williamsb6535952020-12-15 06:40:10 -06001404 CODE_SCAN_DIR = os.path.join(WORKSPACE, UNIT_TEST_PKG)
Lei YU7ef93302019-11-06 13:53:21 +08001405
Patrick Williams330b0772022-11-28 06:14:06 -06001406 # Run format-code.sh, which will in turn call any repo-level formatters.
Lei YU7ef93302019-11-06 13:53:21 +08001407 if FORMAT_CODE:
Patrick Williamse08ffba2022-12-05 10:33:46 -06001408 check_call_cmd(
1409 os.path.join(
1410 WORKSPACE, "openbmc-build-scripts", "scripts", "format-code.sh"
1411 ),
1412 CODE_SCAN_DIR,
1413 )
Andrew Geisslera28286d2018-01-10 11:00:00 -08001414
Ed Tanous32768b82022-01-05 14:14:06 -08001415 # Check to see if any files changed
Patrick Williamse08ffba2022-12-05 10:33:46 -06001416 check_call_cmd(
1417 "git", "-C", CODE_SCAN_DIR, "--no-pager", "diff", "--exit-code"
1418 )
Ed Tanous32768b82022-01-05 14:14:06 -08001419
Andrew Geissler8cb74fc2020-03-19 14:48:05 -05001420 # Check if this repo has a supported make infrastructure
Patrick Williamsb6535952020-12-15 06:40:10 -06001421 pkg = Package(UNIT_TEST_PKG, CODE_SCAN_DIR)
Andrew Geissler8cb74fc2020-03-19 14:48:05 -05001422 if not pkg.build_system():
1423 print("No valid build system, exit")
1424 sys.exit(0)
1425
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301426 prev_umask = os.umask(000)
James Feist878df5c2018-07-26 14:54:28 -07001427
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301428 # Determine dependencies and add them
1429 dep_added = dict()
1430 dep_added[UNIT_TEST_PKG] = False
William A. Kennington III40d5c7c2018-12-13 14:37:59 -08001431
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301432 # Create dependency tree
1433 dep_tree = DepTree(UNIT_TEST_PKG)
Patrick Williamsb6535952020-12-15 06:40:10 -06001434 build_dep_tree(UNIT_TEST_PKG, CODE_SCAN_DIR, dep_added, dep_tree, BRANCH)
William A. Kennington III65b37fa2019-01-31 15:15:17 -08001435
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301436 # Reorder Dependency Tree
Andrew Jeffery89b64b62020-03-13 12:15:48 +10301437 for pkg_name, regex_str in DEPENDENCIES_REGEX.items():
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301438 dep_tree.ReorderDeps(pkg_name, regex_str)
1439 if args.verbose:
1440 dep_tree.PrintTree()
William A. Kennington III65b37fa2019-01-31 15:15:17 -08001441
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301442 install_list = dep_tree.GetInstallList()
Andrew Geissler9ced4ed2019-11-18 14:33:58 -06001443
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301444 # We don't want to treat our package as a dependency
1445 install_list.remove(UNIT_TEST_PKG)
James Feist878df5c2018-07-26 14:54:28 -07001446
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301447 # Install reordered dependencies
1448 for dep in install_list:
1449 build_and_install(dep, False)
James Feist878df5c2018-07-26 14:54:28 -07001450
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301451 # Run package unit tests
1452 build_and_install(UNIT_TEST_PKG, True)
James Feist878df5c2018-07-26 14:54:28 -07001453
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301454 os.umask(prev_umask)
Matt Spinler9bfaaad2019-10-25 09:51:50 -05001455
1456 # Run any custom CI scripts the repo has, of which there can be
1457 # multiple of and anywhere in the repository.
Patrick Williamse08ffba2022-12-05 10:33:46 -06001458 ci_scripts = find_file(["run-ci.sh", "run-ci"], CODE_SCAN_DIR)
Matt Spinler9bfaaad2019-10-25 09:51:50 -05001459 if ci_scripts:
Patrick Williamsb6535952020-12-15 06:40:10 -06001460 os.chdir(CODE_SCAN_DIR)
Matt Spinler9bfaaad2019-10-25 09:51:50 -05001461 for ci_script in ci_scripts:
Patrick Williams55448ad2020-12-14 14:28:28 -06001462 check_call_cmd(ci_script)