blob: 9368b38918c7b15ea769e908967fbab90eddf117 [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
11import multiprocessing
12import os
13import platform
14import re
15import shutil
16import subprocess
17import sys
18from subprocess import CalledProcessError, check_call
19from tempfile import TemporaryDirectory
20from urllib.parse import urljoin
21
Matthew Barthd1810372016-12-19 16:57:21 -060022from git import Repo
Patrick Williamse795dfe2022-12-06 10:07:02 -060023
William A. Kennington III3992d102021-05-17 01:41:04 -070024# interpreter is not used directly but this resolves dependency ordering
25# that would be broken if we didn't include it.
Patrick Williamse08ffba2022-12-05 10:33:46 -060026from mesonbuild import interpreter # noqa: F401
William A. Kennington IIIfcd70772020-06-04 00:50:23 -070027from mesonbuild import coredata, optinterpreter
Patrick Williamse95626d2021-04-13 11:13:53 -050028from mesonbuild.mesonlib import OptionKey
Patrick Williams95095f12021-04-14 14:42:35 -050029from mesonbuild.mesonlib import version_compare as meson_version_compare
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -050030
31
Patrick Williamse08ffba2022-12-05 10:33:46 -060032class DepTree:
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -050033 """
34 Represents package dependency tree, where each node is a DepTree with a
35 name and DepTree children.
36 """
37
38 def __init__(self, name):
39 """
40 Create new DepTree.
41
42 Parameter descriptions:
43 name Name of new tree node.
44 """
45 self.name = name
46 self.children = list()
47
48 def AddChild(self, name):
49 """
50 Add new child node to current node.
51
52 Parameter descriptions:
53 name Name of new child
54 """
55 new_child = DepTree(name)
56 self.children.append(new_child)
57 return new_child
58
59 def AddChildNode(self, node):
60 """
61 Add existing child node to current node.
62
63 Parameter descriptions:
64 node Tree node to add
65 """
66 self.children.append(node)
67
68 def RemoveChild(self, name):
69 """
70 Remove child node.
71
72 Parameter descriptions:
73 name Name of child to remove
74 """
75 for child in self.children:
76 if child.name == name:
77 self.children.remove(child)
78 return
79
80 def GetNode(self, name):
81 """
82 Return node with matching name. Return None if not found.
83
84 Parameter descriptions:
85 name Name of node to return
86 """
87 if self.name == name:
88 return self
89 for child in self.children:
90 node = child.GetNode(name)
91 if node:
92 return node
93 return None
94
95 def GetParentNode(self, name, parent_node=None):
96 """
97 Return parent of node with matching name. Return none if not found.
98
99 Parameter descriptions:
100 name Name of node to get parent of
101 parent_node Parent of current node
102 """
103 if self.name == name:
104 return parent_node
105 for child in self.children:
106 found_node = child.GetParentNode(name, self)
107 if found_node:
108 return found_node
109 return None
110
111 def GetPath(self, name, path=None):
112 """
113 Return list of node names from head to matching name.
114 Return None if not found.
115
116 Parameter descriptions:
117 name Name of node
118 path List of node names from head to current node
119 """
120 if not path:
121 path = []
122 if self.name == name:
123 path.append(self.name)
124 return path
125 for child in self.children:
126 match = child.GetPath(name, path + [self.name])
127 if match:
128 return match
129 return None
130
131 def GetPathRegex(self, name, regex_str, path=None):
132 """
133 Return list of node paths that end in name, or match regex_str.
134 Return empty list if not found.
135
136 Parameter descriptions:
137 name Name of node to search for
138 regex_str Regex string to match node names
139 path Path of node names from head to current node
140 """
141 new_paths = []
142 if not path:
143 path = []
144 match = re.match(regex_str, self.name)
145 if (self.name == name) or (match):
146 new_paths.append(path + [self.name])
147 for child in self.children:
148 return_paths = None
149 full_path = path + [self.name]
150 return_paths = child.GetPathRegex(name, regex_str, full_path)
151 for i in return_paths:
152 new_paths.append(i)
153 return new_paths
154
155 def MoveNode(self, from_name, to_name):
156 """
157 Mode existing from_name node to become child of to_name node.
158
159 Parameter descriptions:
160 from_name Name of node to make a child of to_name
161 to_name Name of node to make parent of from_name
162 """
163 parent_from_node = self.GetParentNode(from_name)
164 from_node = self.GetNode(from_name)
165 parent_from_node.RemoveChild(from_name)
166 to_node = self.GetNode(to_name)
167 to_node.AddChildNode(from_node)
168
169 def ReorderDeps(self, name, regex_str):
170 """
171 Reorder dependency tree. If tree contains nodes with names that
172 match 'name' and 'regex_str', move 'regex_str' nodes that are
173 to the right of 'name' node, so that they become children of the
174 'name' node.
175
176 Parameter descriptions:
177 name Name of node to look for
178 regex_str Regex string to match names to
179 """
180 name_path = self.GetPath(name)
181 if not name_path:
182 return
183 paths = self.GetPathRegex(name, regex_str)
184 is_name_in_paths = False
185 name_index = 0
186 for i in range(len(paths)):
187 path = paths[i]
188 if path[-1] == name:
189 is_name_in_paths = True
190 name_index = i
191 break
192 if not is_name_in_paths:
193 return
194 for i in range(name_index + 1, len(paths)):
195 path = paths[i]
196 if name in path:
197 continue
198 from_name = path[-1]
199 self.MoveNode(from_name, name)
200
201 def GetInstallList(self):
202 """
203 Return post-order list of node names.
204
205 Parameter descriptions:
206 """
207 install_list = []
208 for child in self.children:
209 child_install_list = child.GetInstallList()
210 install_list.extend(child_install_list)
211 install_list.append(self.name)
212 return install_list
213
214 def PrintTree(self, level=0):
215 """
216 Print pre-order node names with indentation denoting node depth level.
217
218 Parameter descriptions:
219 level Current depth level
220 """
221 INDENT_PER_LEVEL = 4
Patrick Williamse08ffba2022-12-05 10:33:46 -0600222 print(" " * (level * INDENT_PER_LEVEL) + self.name)
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500223 for child in self.children:
224 child.PrintTree(level + 1)
Matthew Barth33df8792016-12-19 14:30:17 -0600225
226
Andrew Jeffery71924fd2023-04-12 23:27:05 +0930227def check_call_cmd(*cmd, **kwargs):
Matthew Barth33df8792016-12-19 14:30:17 -0600228 """
229 Verbose prints the directory location the given command is called from and
230 the command, then executes the command using check_call.
231
232 Parameter descriptions:
233 dir Directory location command is to be called from
234 cmd List of parameters constructing the complete command
235 """
William A. Kennington III1fddb972019-02-06 18:03:53 -0800236 printline(os.getcwd(), ">", " ".join(cmd))
Andrew Jeffery71924fd2023-04-12 23:27:05 +0930237 check_call(cmd, **kwargs)
Matthew Barthccb7f852016-11-23 17:43:02 -0600238
239
Andrew Geisslera61acb52019-01-03 16:32:44 -0600240def clone_pkg(pkg, branch):
Matthew Barth33df8792016-12-19 14:30:17 -0600241 """
242 Clone the given openbmc package's git repository from gerrit into
243 the WORKSPACE location
244
245 Parameter descriptions:
246 pkg Name of the package to clone
Andrew Geisslera61acb52019-01-03 16:32:44 -0600247 branch Branch to clone from pkg
Matthew Barth33df8792016-12-19 14:30:17 -0600248 """
Andrew Jeffery7be94ca2018-03-08 13:15:33 +1030249 pkg_dir = os.path.join(WORKSPACE, pkg)
Patrick Williamse08ffba2022-12-05 10:33:46 -0600250 if os.path.exists(os.path.join(pkg_dir, ".git")):
Andrew Jeffery7be94ca2018-03-08 13:15:33 +1030251 return pkg_dir
Patrick Williamse08ffba2022-12-05 10:33:46 -0600252 pkg_repo = urljoin("https://gerrit.openbmc.org/openbmc/", pkg)
Andrew Jeffery7be94ca2018-03-08 13:15:33 +1030253 os.mkdir(pkg_dir)
Andrew Geisslera61acb52019-01-03 16:32:44 -0600254 printline(pkg_dir, "> git clone", pkg_repo, branch, "./")
255 try:
256 # first try the branch
Andrew Jeffery7d4a26f2020-03-13 11:42:34 +1030257 clone = Repo.clone_from(pkg_repo, pkg_dir, branch=branch)
258 repo_inst = clone.working_dir
Patrick Williamse08ffba2022-12-05 10:33:46 -0600259 except Exception:
Andrew Geisslera61acb52019-01-03 16:32:44 -0600260 printline("Input branch not found, default to master")
Andrew Jeffery7d4a26f2020-03-13 11:42:34 +1030261 clone = Repo.clone_from(pkg_repo, pkg_dir, branch="master")
262 repo_inst = clone.working_dir
Andrew Geisslera61acb52019-01-03 16:32:44 -0600263 return repo_inst
Matthew Barth33df8792016-12-19 14:30:17 -0600264
265
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030266def make_target_exists(target):
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800267 """
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030268 Runs a check against the makefile in the current directory to determine
269 if the target exists so that it can be built.
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800270
271 Parameter descriptions:
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030272 target The make target we are checking
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800273 """
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030274 try:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600275 cmd = ["make", "-n", target]
276 with open(os.devnull, "w") as devnull:
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030277 check_call(cmd, stdout=devnull, stderr=devnull)
278 return True
279 except CalledProcessError:
280 return False
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800281
William A. Kennington III3f1d1202018-12-06 18:02:07 -0800282
William A. Kennington IIIa2156732018-06-30 18:38:09 -0700283make_parallel = [
Patrick Williamse08ffba2022-12-05 10:33:46 -0600284 "make",
William A. Kennington IIIa2156732018-06-30 18:38:09 -0700285 # Run enough jobs to saturate all the cpus
Patrick Williamse08ffba2022-12-05 10:33:46 -0600286 "-j",
287 str(multiprocessing.cpu_count()),
William A. Kennington IIIa2156732018-06-30 18:38:09 -0700288 # Don't start more jobs if the load avg is too high
Patrick Williamse08ffba2022-12-05 10:33:46 -0600289 "-l",
290 str(multiprocessing.cpu_count()),
William A. Kennington IIIa2156732018-06-30 18:38:09 -0700291 # Synchronize the output so logs aren't intermixed in stdout / stderr
Patrick Williamse08ffba2022-12-05 10:33:46 -0600292 "-O",
William A. Kennington IIIa2156732018-06-30 18:38:09 -0700293]
294
William A. Kennington III3f1d1202018-12-06 18:02:07 -0800295
Andrew Jefferyff5c5d52020-03-13 10:12:14 +1030296def build_and_install(name, build_for_testing=False):
William A. Kennington III780ec092018-12-06 14:46:50 -0800297 """
298 Builds and installs the package in the environment. Optionally
299 builds the examples and test cases for package.
300
301 Parameter description:
Andrew Jefferyff5c5d52020-03-13 10:12:14 +1030302 name The name of the package we are building
William A. Kennington IIIa0454912018-12-06 14:47:16 -0800303 build_for_testing Enable options related to testing on the package?
William A. Kennington III780ec092018-12-06 14:46:50 -0800304 """
Andrew Jefferyff5c5d52020-03-13 10:12:14 +1030305 os.chdir(os.path.join(WORKSPACE, name))
William A. Kennington III54d4faf2018-12-06 17:46:24 -0800306
307 # Refresh dynamic linker run time bindings for dependencies
Patrick Williamse08ffba2022-12-05 10:33:46 -0600308 check_call_cmd("sudo", "-n", "--", "ldconfig")
William A. Kennington III54d4faf2018-12-06 17:46:24 -0800309
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030310 pkg = Package()
311 if build_for_testing:
312 pkg.test()
William A. Kennington III3f1d1202018-12-06 18:02:07 -0800313 else:
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030314 pkg.install()
315
William A. Kennington III780ec092018-12-06 14:46:50 -0800316
Andrew Jefferyccf85d62020-03-13 10:25:42 +1030317def build_dep_tree(name, pkgdir, dep_added, head, branch, dep_tree=None):
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500318 """
Andrew Jefferyccf85d62020-03-13 10:25:42 +1030319 For each package (name), starting with the package to be unit tested,
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030320 extract its dependencies. For each package dependency defined, recursively
321 apply the same strategy
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500322
323 Parameter descriptions:
Andrew Jefferyccf85d62020-03-13 10:25:42 +1030324 name Name of the package
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500325 pkgdir Directory where package source is located
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800326 dep_added Current dict of dependencies and added status
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500327 head Head node of the dependency tree
Andrew Geisslera61acb52019-01-03 16:32:44 -0600328 branch Branch to clone from pkg
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500329 dep_tree Current dependency tree node
330 """
331 if not dep_tree:
332 dep_tree = head
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800333
William A. Kennington IIIbe6aab22018-12-06 15:01:54 -0800334 with open("/tmp/depcache", "r") as depcache:
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800335 cache = depcache.readline()
336
337 # Read out pkg dependencies
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030338 pkg = Package(name, pkgdir)
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800339
Patrick Williams38c6b7d2021-02-18 07:26:14 -0600340 build = pkg.build_system()
Patrick Williamse08ffba2022-12-05 10:33:46 -0600341 if not build:
Patrick Williams38c6b7d2021-02-18 07:26:14 -0600342 raise Exception(f"Unable to find build system for {name}.")
343
344 for dep in set(build.dependencies()):
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800345 if dep in cache:
346 continue
347 # Dependency package not already known
348 if dep_added.get(dep) is None:
Patrick Williams38c6b7d2021-02-18 07:26:14 -0600349 print(f"Adding {dep} dependency to {name}.")
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800350 # Dependency package not added
351 new_child = dep_tree.AddChild(dep)
352 dep_added[dep] = False
Andrew Jeffery3b92fdd2020-03-13 11:49:18 +1030353 dep_pkgdir = clone_pkg(dep, branch)
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800354 # Determine this dependency package's
355 # dependencies and add them before
356 # returning to add this package
Patrick Williamse08ffba2022-12-05 10:33:46 -0600357 dep_added = build_dep_tree(
358 dep, dep_pkgdir, dep_added, head, branch, new_child
359 )
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800360 else:
361 # Dependency package known and added
362 if dep_added[dep]:
Andrew Jeffery2cb0c7a2018-03-08 13:19:08 +1030363 continue
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500364 else:
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800365 # Cyclic dependency failure
Patrick Williamse08ffba2022-12-05 10:33:46 -0600366 raise Exception("Cyclic dependencies found in " + name)
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500367
Andrew Jefferyccf85d62020-03-13 10:25:42 +1030368 if not dep_added[name]:
369 dep_added[name] = True
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500370
371 return dep_added
Matthew Barthccb7f852016-11-23 17:43:02 -0600372
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700373
William A. Kennington III90b106a2019-02-06 18:08:24 -0800374def run_cppcheck():
Ed Tanousc7198552022-07-01 08:15:50 -0700375 if not os.path.exists(os.path.join("build", "compile_commands.json")):
Brad Bishop48424d42020-01-07 13:01:31 -0500376 return None
377
Patrick Williams485a0922022-08-12 11:44:55 -0500378 with TemporaryDirectory() as cpp_dir:
Patrick Williams485a0922022-08-12 11:44:55 -0500379 # http://cppcheck.sourceforge.net/manual.pdf
380 try:
381 check_call_cmd(
Patrick Williamse08ffba2022-12-05 10:33:46 -0600382 "cppcheck",
383 "-j",
384 str(multiprocessing.cpu_count()),
385 "--enable=style,performance,portability,missingInclude",
Brad Bishop688f8372023-03-02 22:56:31 -0500386 "--inline-suppr",
Patrick Williamse08ffba2022-12-05 10:33:46 -0600387 "--suppress=useStlAlgorithm",
388 "--suppress=unusedStructMember",
389 "--suppress=postfixOperator",
390 "--suppress=unreadVariable",
391 "--suppress=knownConditionTrueFalse",
392 "--library=googletest",
393 "--project=build/compile_commands.json",
394 f"--cppcheck-build-dir={cpp_dir}",
Patrick Williams485a0922022-08-12 11:44:55 -0500395 )
396 except subprocess.CalledProcessError:
397 print("cppcheck found errors")
Lei YUdbd7cd62022-07-19 19:24:01 +0800398
Andrew Jefferye5fffa02020-03-13 11:51:57 +1030399
William A. Kennington III37a89a22018-12-13 14:32:02 -0800400def is_valgrind_safe():
401 """
402 Returns whether it is safe to run valgrind on our platform
403 """
Patrick Williamse08ffba2022-12-05 10:33:46 -0600404 src = "unit-test-vg.c"
405 exe = "./unit-test-vg"
406 with open(src, "w") as h:
407 h.write("#include <errno.h>\n")
408 h.write("#include <stdio.h>\n")
409 h.write("#include <stdlib.h>\n")
410 h.write("#include <string.h>\n")
411 h.write("int main() {\n")
412 h.write("char *heap_str = malloc(16);\n")
William A. Kennington III0326ded2019-02-07 00:33:28 -0800413 h.write('strcpy(heap_str, "RandString");\n')
414 h.write('int res = strcmp("RandString", heap_str);\n')
Patrick Williamse08ffba2022-12-05 10:33:46 -0600415 h.write("free(heap_str);\n")
416 h.write("char errstr[64];\n")
417 h.write("strerror_r(EINVAL, errstr, sizeof(errstr));\n")
William A. Kennington IIIafb0f982019-04-26 17:30:28 -0700418 h.write('printf("%s\\n", errstr);\n')
Patrick Williamse08ffba2022-12-05 10:33:46 -0600419 h.write("return res;\n")
420 h.write("}\n")
William A. Kennington III0326ded2019-02-07 00:33:28 -0800421 try:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600422 with open(os.devnull, "w") as devnull:
423 check_call(
424 ["gcc", "-O2", "-o", exe, src], stdout=devnull, stderr=devnull
425 )
426 check_call(
427 ["valgrind", "--error-exitcode=99", exe],
428 stdout=devnull,
429 stderr=devnull,
430 )
William A. Kennington III0326ded2019-02-07 00:33:28 -0800431 return True
Patrick Williamse08ffba2022-12-05 10:33:46 -0600432 except Exception:
William A. Kennington III0326ded2019-02-07 00:33:28 -0800433 sys.stderr.write("###### Platform is not valgrind safe ######\n")
434 return False
435 finally:
436 os.remove(src)
437 os.remove(exe)
William A. Kennington III37a89a22018-12-13 14:32:02 -0800438
Andrew Jefferye5fffa02020-03-13 11:51:57 +1030439
William A. Kennington III282e3302019-02-04 16:55:05 -0800440def is_sanitize_safe():
441 """
442 Returns whether it is safe to run sanitizers on our platform
443 """
Patrick Williamse08ffba2022-12-05 10:33:46 -0600444 src = "unit-test-sanitize.c"
445 exe = "./unit-test-sanitize"
446 with open(src, "w") as h:
447 h.write("int main() { return 0; }\n")
William A. Kennington III0b7fb2b2019-02-07 00:33:42 -0800448 try:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600449 with open(os.devnull, "w") as devnull:
450 check_call(
451 [
452 "gcc",
453 "-O2",
454 "-fsanitize=address",
455 "-fsanitize=undefined",
456 "-o",
457 exe,
458 src,
459 ],
460 stdout=devnull,
461 stderr=devnull,
462 )
William A. Kennington III0b7fb2b2019-02-07 00:33:42 -0800463 check_call([exe], stdout=devnull, stderr=devnull)
Andrew Geisslercd9578b2021-05-04 10:18:01 -0500464
465 # TODO - Sanitizer not working on ppc64le
466 # https://github.com/openbmc/openbmc-build-scripts/issues/31
Patrick Williamse08ffba2022-12-05 10:33:46 -0600467 if platform.processor() == "ppc64le":
Andrew Geisslercd9578b2021-05-04 10:18:01 -0500468 sys.stderr.write("###### ppc64le is not sanitize safe ######\n")
469 return False
470 else:
471 return True
Patrick Williamse08ffba2022-12-05 10:33:46 -0600472 except Exception:
William A. Kennington III0b7fb2b2019-02-07 00:33:42 -0800473 sys.stderr.write("###### Platform is not sanitize safe ######\n")
474 return False
475 finally:
476 os.remove(src)
477 os.remove(exe)
William A. Kennington III282e3302019-02-04 16:55:05 -0800478
William A. Kennington III49d4e592019-02-06 17:59:27 -0800479
William A. Kennington IIIeaff24a2019-02-06 16:57:42 -0800480def maybe_make_valgrind():
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700481 """
482 Potentially runs the unit tests through valgrind for the package
483 via `make check-valgrind`. If the package does not have valgrind testing
484 then it just skips over this.
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700485 """
William A. Kennington III4e1d0a12018-07-16 12:04:03 -0700486 # Valgrind testing is currently broken by an aggressive strcmp optimization
487 # that is inlined into optimized code for POWER by gcc 7+. Until we find
488 # a workaround, just don't run valgrind tests on POWER.
489 # https://github.com/openbmc/openbmc/issues/3315
William A. Kennington III37a89a22018-12-13 14:32:02 -0800490 if not is_valgrind_safe():
William A. Kennington III75130192019-02-07 00:34:14 -0800491 sys.stderr.write("###### Skipping valgrind ######\n")
William A. Kennington III4e1d0a12018-07-16 12:04:03 -0700492 return
Patrick Williamse08ffba2022-12-05 10:33:46 -0600493 if not make_target_exists("check-valgrind"):
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700494 return
495
496 try:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600497 cmd = make_parallel + ["check-valgrind"]
William A. Kennington III1fddb972019-02-06 18:03:53 -0800498 check_call_cmd(*cmd)
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700499 except CalledProcessError:
William A. Kennington III90b106a2019-02-06 18:08:24 -0800500 for root, _, files in os.walk(os.getcwd()):
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700501 for f in files:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600502 if re.search("test-suite-[a-z]+.log", f) is None:
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700503 continue
Patrick Williamse08ffba2022-12-05 10:33:46 -0600504 check_call_cmd("cat", os.path.join(root, f))
505 raise Exception("Valgrind tests failed")
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700506
Andrew Jefferye5fffa02020-03-13 11:51:57 +1030507
William A. Kennington IIIeaff24a2019-02-06 16:57:42 -0800508def maybe_make_coverage():
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700509 """
510 Potentially runs the unit tests through code coverage for the package
511 via `make check-code-coverage`. If the package does not have code coverage
512 testing then it just skips over this.
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700513 """
Patrick Williamse08ffba2022-12-05 10:33:46 -0600514 if not make_target_exists("check-code-coverage"):
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700515 return
516
517 # Actually run code coverage
518 try:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600519 cmd = make_parallel + ["check-code-coverage"]
William A. Kennington III1fddb972019-02-06 18:03:53 -0800520 check_call_cmd(*cmd)
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700521 except CalledProcessError:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600522 raise Exception("Code coverage failed")
Matthew Barthccb7f852016-11-23 17:43:02 -0600523
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030524
525class BuildSystem(object):
526 """
527 Build systems generally provide the means to configure, build, install and
528 test software. The BuildSystem class defines a set of interfaces on top of
529 which Autotools, Meson, CMake and possibly other build system drivers can
530 be implemented, separating out the phases to control whether a package
531 should merely be installed or also tested and analyzed.
532 """
Lei YU08d2b922022-04-25 11:21:36 +0800533
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030534 def __init__(self, package, path):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600535 """Initialise the driver with properties independent of the build
536 system
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030537
538 Keyword arguments:
539 package: The name of the package. Derived from the path if None
540 path: The path to the package. Set to the working directory if None
541 """
542 self.path = "." if not path else path
Andrew Jeffery47fbfa52020-03-13 12:05:09 +1030543 realpath = os.path.realpath(self.path)
544 self.package = package if package else os.path.basename(realpath)
Andrew Jeffery3f8e5a72020-03-13 11:47:56 +1030545 self.build_for_testing = False
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030546
547 def probe(self):
548 """Test if the build system driver can be applied to the package
549
550 Return True if the driver can drive the package's build system,
551 otherwise False.
552
553 Generally probe() is implemented by testing for the presence of the
554 build system's configuration file(s).
555 """
Patrick Williamse08ffba2022-12-05 10:33:46 -0600556 raise NotImplementedError
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030557
558 def dependencies(self):
559 """Provide the package's dependencies
560
561 Returns a list of dependencies. If no dependencies are required then an
562 empty list must be returned.
563
564 Generally dependencies() is implemented by analysing and extracting the
565 data from the build system configuration.
566 """
Patrick Williamse08ffba2022-12-05 10:33:46 -0600567 raise NotImplementedError
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030568
569 def configure(self, build_for_testing):
570 """Configure the source ready for building
571
572 Should raise an exception if configuration failed.
573
574 Keyword arguments:
575 build_for_testing: Mark the package as being built for testing rather
576 than for installation as a dependency for the
577 package under test. Setting to True generally
578 implies that the package will be configured to build
579 with debug information, at a low level of
580 optimisation and possibly with sanitizers enabled.
581
582 Generally configure() is implemented by invoking the build system
583 tooling to generate Makefiles or equivalent.
584 """
Patrick Williamse08ffba2022-12-05 10:33:46 -0600585 raise NotImplementedError
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030586
587 def build(self):
588 """Build the software ready for installation and/or testing
589
590 Should raise an exception if the build fails
591
592 Generally build() is implemented by invoking `make` or `ninja`.
593 """
Patrick Williamse08ffba2022-12-05 10:33:46 -0600594 raise NotImplementedError
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030595
596 def install(self):
597 """Install the software ready for use
598
599 Should raise an exception if installation fails
600
601 Like build(), install() is generally implemented by invoking `make` or
602 `ninja`.
603 """
Patrick Williamse08ffba2022-12-05 10:33:46 -0600604 raise NotImplementedError
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030605
606 def test(self):
607 """Build and run the test suite associated with the package
608
609 Should raise an exception if the build or testing fails.
610
611 Like install(), test() is generally implemented by invoking `make` or
612 `ninja`.
613 """
Patrick Williamse08ffba2022-12-05 10:33:46 -0600614 raise NotImplementedError
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030615
616 def analyze(self):
617 """Run any supported analysis tools over the codebase
618
619 Should raise an exception if analysis fails.
620
621 Some analysis tools such as scan-build need injection into the build
622 system. analyze() provides the necessary hook to implement such
623 behaviour. Analyzers independent of the build system can also be
624 specified here but at the cost of possible duplication of code between
625 the build system driver implementations.
626 """
Patrick Williamse08ffba2022-12-05 10:33:46 -0600627 raise NotImplementedError
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030628
629
630class Autotools(BuildSystem):
631 def __init__(self, package=None, path=None):
632 super(Autotools, self).__init__(package, path)
633
634 def probe(self):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600635 return os.path.isfile(os.path.join(self.path, "configure.ac"))
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030636
637 def dependencies(self):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600638 configure_ac = os.path.join(self.path, "configure.ac")
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030639
Patrick Williamse08ffba2022-12-05 10:33:46 -0600640 contents = ""
Andrew Jeffery7d4a26f2020-03-13 11:42:34 +1030641 # Prepend some special function overrides so we can parse out
642 # dependencies
Andrew Jeffery89b64b62020-03-13 12:15:48 +1030643 for macro in DEPENDENCIES.keys():
Patrick Williamse08ffba2022-12-05 10:33:46 -0600644 contents += (
645 "m4_define(["
646 + macro
647 + "], ["
648 + macro
649 + "_START$"
650 + str(DEPENDENCIES_OFFSET[macro] + 1)
651 + macro
652 + "_END])\n"
653 )
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030654 with open(configure_ac, "rt") as f:
Andrew Jeffery47fbfa52020-03-13 12:05:09 +1030655 contents += f.read()
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030656
Patrick Williamse08ffba2022-12-05 10:33:46 -0600657 autoconf_cmdline = ["autoconf", "-Wno-undefined", "-"]
658 autoconf_process = subprocess.Popen(
659 autoconf_cmdline,
660 stdin=subprocess.PIPE,
661 stdout=subprocess.PIPE,
662 stderr=subprocess.PIPE,
663 )
664 document = contents.encode("utf-8")
Andrew Jeffery89b64b62020-03-13 12:15:48 +1030665 (stdout, stderr) = autoconf_process.communicate(input=document)
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030666 if not stdout:
667 print(stderr)
668 raise Exception("Failed to run autoconf for parsing dependencies")
669
670 # Parse out all of the dependency text
671 matches = []
Andrew Jeffery89b64b62020-03-13 12:15:48 +1030672 for macro in DEPENDENCIES.keys():
Patrick Williamse08ffba2022-12-05 10:33:46 -0600673 pattern = "(" + macro + ")_START(.*?)" + macro + "_END"
674 for match in re.compile(pattern).finditer(stdout.decode("utf-8")):
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030675 matches.append((match.group(1), match.group(2)))
676
677 # Look up dependencies from the text
678 found_deps = []
679 for macro, deptext in matches:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600680 for potential_dep in deptext.split(" "):
Andrew Jeffery89b64b62020-03-13 12:15:48 +1030681 for known_dep in DEPENDENCIES[macro].keys():
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030682 if potential_dep.startswith(known_dep):
683 found_deps.append(DEPENDENCIES[macro][known_dep])
684
685 return found_deps
686
687 def _configure_feature(self, flag, enabled):
688 """
689 Returns an configure flag as a string
690
691 Parameters:
692 flag The name of the flag
693 enabled Whether the flag is enabled or disabled
694 """
Patrick Williamse08ffba2022-12-05 10:33:46 -0600695 return "--" + ("enable" if enabled else "disable") + "-" + flag
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030696
697 def configure(self, build_for_testing):
698 self.build_for_testing = build_for_testing
699 conf_flags = [
Patrick Williamse08ffba2022-12-05 10:33:46 -0600700 self._configure_feature("silent-rules", False),
701 self._configure_feature("examples", build_for_testing),
702 self._configure_feature("tests", build_for_testing),
703 self._configure_feature("itests", INTEGRATION_TEST),
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030704 ]
Patrick Williamse08ffba2022-12-05 10:33:46 -0600705 conf_flags.extend(
706 [
707 self._configure_feature("code-coverage", build_for_testing),
708 self._configure_feature("valgrind", build_for_testing),
709 ]
710 )
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030711 # Add any necessary configure flags for package
712 if CONFIGURE_FLAGS.get(self.package) is not None:
713 conf_flags.extend(CONFIGURE_FLAGS.get(self.package))
Patrick Williamse08ffba2022-12-05 10:33:46 -0600714 for bootstrap in ["bootstrap.sh", "bootstrap", "autogen.sh"]:
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030715 if os.path.exists(bootstrap):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600716 check_call_cmd("./" + bootstrap)
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030717 break
Patrick Williamse08ffba2022-12-05 10:33:46 -0600718 check_call_cmd("./configure", *conf_flags)
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030719
720 def build(self):
721 check_call_cmd(*make_parallel)
722
723 def install(self):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600724 check_call_cmd("sudo", "-n", "--", *(make_parallel + ["install"]))
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030725
726 def test(self):
727 try:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600728 cmd = make_parallel + ["check"]
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030729 for i in range(0, args.repeat):
730 check_call_cmd(*cmd)
Andrew Jefferyd0809692021-05-14 16:23:57 +0930731
732 maybe_make_valgrind()
733 maybe_make_coverage()
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030734 except CalledProcessError:
735 for root, _, files in os.walk(os.getcwd()):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600736 if "test-suite.log" not in files:
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030737 continue
Patrick Williamse08ffba2022-12-05 10:33:46 -0600738 check_call_cmd("cat", os.path.join(root, "test-suite.log"))
739 raise Exception("Unit tests failed")
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030740
741 def analyze(self):
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030742 run_cppcheck()
743
744
745class CMake(BuildSystem):
746 def __init__(self, package=None, path=None):
747 super(CMake, self).__init__(package, path)
748
749 def probe(self):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600750 return os.path.isfile(os.path.join(self.path, "CMakeLists.txt"))
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030751
752 def dependencies(self):
753 return []
754
755 def configure(self, build_for_testing):
756 self.build_for_testing = build_for_testing
Ramin Izadpanah298d56b2020-11-17 16:55:28 +0000757 if INTEGRATION_TEST:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600758 check_call_cmd(
759 "cmake",
760 "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON",
761 "-DITESTS=ON",
762 ".",
763 )
Ramin Izadpanah298d56b2020-11-17 16:55:28 +0000764 else:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600765 check_call_cmd("cmake", "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON", ".")
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030766
767 def build(self):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600768 check_call_cmd(
769 "cmake",
770 "--build",
771 ".",
772 "--",
773 "-j",
774 str(multiprocessing.cpu_count()),
775 )
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030776
777 def install(self):
Michael Shen87ab5702023-01-13 07:41:11 +0000778 check_call_cmd("sudo", "cmake", "--install", ".")
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030779
780 def test(self):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600781 if make_target_exists("test"):
782 check_call_cmd("ctest", ".")
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030783
784 def analyze(self):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600785 if os.path.isfile(".clang-tidy"):
786 with TemporaryDirectory(prefix="build", dir=".") as build_dir:
Nan Zhou82aaba02022-09-16 00:21:07 +0000787 # clang-tidy needs to run on a clang-specific build
Patrick Williamse08ffba2022-12-05 10:33:46 -0600788 check_call_cmd(
789 "cmake",
790 "-DCMAKE_C_COMPILER=clang",
791 "-DCMAKE_CXX_COMPILER=clang++",
792 "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON",
793 "-H.",
794 "-B" + build_dir,
795 )
Nan Zhou524ebee2022-08-27 23:26:13 +0000796
Patrick Williamse08ffba2022-12-05 10:33:46 -0600797 check_call_cmd(
798 "run-clang-tidy", "-header-filter=.*", "-p", build_dir
799 )
Ed Tanous662890f2020-09-28 17:35:20 -0700800
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030801 maybe_make_valgrind()
802 maybe_make_coverage()
803 run_cppcheck()
804
805
806class Meson(BuildSystem):
807 def __init__(self, package=None, path=None):
808 super(Meson, self).__init__(package, path)
809
810 def probe(self):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600811 return os.path.isfile(os.path.join(self.path, "meson.build"))
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030812
813 def dependencies(self):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600814 meson_build = os.path.join(self.path, "meson.build")
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030815 if not os.path.exists(meson_build):
816 return []
817
818 found_deps = []
819 for root, dirs, files in os.walk(self.path):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600820 if "meson.build" not in files:
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030821 continue
Patrick Williamse08ffba2022-12-05 10:33:46 -0600822 with open(os.path.join(root, "meson.build"), "rt") as f:
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030823 build_contents = f.read()
Nan Zhouef13d532020-07-07 09:52:02 -0700824 pattern = r"dependency\('([^']*)'.*?\),?\n"
Andrew Jeffery47fbfa52020-03-13 12:05:09 +1030825 for match in re.finditer(pattern, build_contents):
826 group = match.group(1)
Patrick Williamse08ffba2022-12-05 10:33:46 -0600827 maybe_dep = DEPENDENCIES["PKG_CHECK_MODULES"].get(group)
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030828 if maybe_dep is not None:
829 found_deps.append(maybe_dep)
830
831 return found_deps
832
833 def _parse_options(self, options_file):
834 """
835 Returns a set of options defined in the provides meson_options.txt file
836
837 Parameters:
838 options_file The file containing options
839 """
Patrick Williamse08ffba2022-12-05 10:33:46 -0600840 oi = optinterpreter.OptionInterpreter("")
William A. Kennington III0ecd0bc2020-06-04 00:44:22 -0700841 oi.process(options_file)
842 return oi.options
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030843
William A. Kennington IIIfcd70772020-06-04 00:50:23 -0700844 def _configure_boolean(self, val):
845 """
846 Returns the meson flag which signifies the value
847
848 True is true which requires the boolean.
849 False is false which disables the boolean.
850
851 Parameters:
852 val The value being converted
853 """
854 if val is True:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600855 return "true"
William A. Kennington IIIfcd70772020-06-04 00:50:23 -0700856 elif val is False:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600857 return "false"
William A. Kennington IIIfcd70772020-06-04 00:50:23 -0700858 else:
859 raise Exception("Bad meson boolean value")
860
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030861 def _configure_feature(self, val):
862 """
863 Returns the meson flag which signifies the value
864
865 True is enabled which requires the feature.
866 False is disabled which disables the feature.
867 None is auto which autodetects the feature.
868
869 Parameters:
870 val The value being converted
871 """
872 if val is True:
873 return "enabled"
874 elif val is False:
875 return "disabled"
876 elif val is None:
877 return "auto"
878 else:
879 raise Exception("Bad meson feature value")
880
William A. Kennington IIIfcd70772020-06-04 00:50:23 -0700881 def _configure_option(self, opts, key, val):
882 """
883 Returns the meson flag which signifies the value
884 based on the type of the opt
885
886 Parameters:
887 opt The meson option which we are setting
888 val The value being converted
889 """
890 if isinstance(opts[key], coredata.UserBooleanOption):
891 str_val = self._configure_boolean(val)
892 elif isinstance(opts[key], coredata.UserFeatureOption):
893 str_val = self._configure_feature(val)
894 else:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600895 raise Exception("Unknown meson option type")
William A. Kennington IIIfcd70772020-06-04 00:50:23 -0700896 return "-D{}={}".format(key, str_val)
897
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030898 def configure(self, build_for_testing):
899 self.build_for_testing = build_for_testing
William A. Kennington III0ecd0bc2020-06-04 00:44:22 -0700900 meson_options = {}
William A. Kennington III08fa1d52023-11-29 17:01:19 -0800901 if os.path.exists("meson.options"):
902 meson_options = self._parse_options("meson.options")
903 elif os.path.exists("meson_options.txt"):
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030904 meson_options = self._parse_options("meson_options.txt")
905 meson_flags = [
Patrick Williamse08ffba2022-12-05 10:33:46 -0600906 "-Db_colorout=never",
907 "-Dwerror=true",
908 "-Dwarning_level=3",
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030909 ]
910 if build_for_testing:
Andrew Jeffery9d43ecf2023-05-25 10:31:08 +0930911 # -Ddebug=true -Doptimization=g is helpful for abi-dumper but isn't a combination that
912 # is supported by meson's build types. Configure it manually.
913 meson_flags.append("-Ddebug=true")
914 meson_flags.append("-Doptimization=g")
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030915 else:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600916 meson_flags.append("--buildtype=debugoptimized")
917 if OptionKey("tests") in meson_options:
918 meson_flags.append(
919 self._configure_option(
920 meson_options, OptionKey("tests"), build_for_testing
921 )
922 )
923 if OptionKey("examples") in meson_options:
924 meson_flags.append(
925 self._configure_option(
926 meson_options, OptionKey("examples"), build_for_testing
927 )
928 )
929 if OptionKey("itests") in meson_options:
930 meson_flags.append(
931 self._configure_option(
932 meson_options, OptionKey("itests"), INTEGRATION_TEST
933 )
934 )
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030935 if MESON_FLAGS.get(self.package) is not None:
936 meson_flags.extend(MESON_FLAGS.get(self.package))
937 try:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600938 check_call_cmd(
939 "meson", "setup", "--reconfigure", "build", *meson_flags
940 )
941 except Exception:
942 shutil.rmtree("build", ignore_errors=True)
943 check_call_cmd("meson", "setup", "build", *meson_flags)
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030944
945 def build(self):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600946 check_call_cmd("ninja", "-C", "build")
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030947
948 def install(self):
Patrick Williamse08ffba2022-12-05 10:33:46 -0600949 check_call_cmd("sudo", "-n", "--", "ninja", "-C", "build", "install")
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030950
951 def test(self):
Patrick Williams95095f12021-04-14 14:42:35 -0500952 # It is useful to check various settings of the meson.build file
953 # for compatibility, such as meson_version checks. We shouldn't
954 # do this in the configure path though because it affects subprojects
955 # and dependencies as well, but we only want this applied to the
956 # project-under-test (otherwise an upstream dependency could fail
957 # this check without our control).
958 self._extra_meson_checks()
959
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030960 try:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600961 test_args = ("--repeat", str(args.repeat), "-C", "build")
962 check_call_cmd("meson", "test", "--print-errorlogs", *test_args)
Brad Bishop7b8cef22020-08-26 15:58:09 -0400963
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030964 except CalledProcessError:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600965 raise Exception("Unit tests failed")
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030966
967 def _setup_exists(self, setup):
968 """
969 Returns whether the meson build supports the named test setup.
970
971 Parameter descriptions:
972 setup The setup target to check
973 """
974 try:
Patrick Williamse08ffba2022-12-05 10:33:46 -0600975 with open(os.devnull, "w"):
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030976 output = subprocess.check_output(
Patrick Williamse08ffba2022-12-05 10:33:46 -0600977 [
978 "meson",
979 "test",
980 "-C",
981 "build",
982 "--setup",
983 setup,
984 "-t",
985 "0",
986 ],
987 stderr=subprocess.STDOUT,
988 )
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030989 except CalledProcessError as e:
990 output = e.output
Patrick Williamse08ffba2022-12-05 10:33:46 -0600991 output = output.decode("utf-8")
992 return not re.search("Test setup .* not found from project", output)
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030993
994 def _maybe_valgrind(self):
995 """
996 Potentially runs the unit tests through valgrind for the package
Andrew Jeffery47fbfa52020-03-13 12:05:09 +1030997 via `meson test`. The package can specify custom valgrind
998 configurations by utilizing add_test_setup() in a meson.build
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030999 """
1000 if not is_valgrind_safe():
1001 sys.stderr.write("###### Skipping valgrind ######\n")
1002 return
1003 try:
Patrick Williamse08ffba2022-12-05 10:33:46 -06001004 if self._setup_exists("valgrind"):
1005 check_call_cmd(
1006 "meson",
1007 "test",
1008 "-t",
1009 "10",
1010 "-C",
1011 "build",
1012 "--print-errorlogs",
1013 "--setup",
1014 "valgrind",
1015 )
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301016 else:
Patrick Williamse08ffba2022-12-05 10:33:46 -06001017 check_call_cmd(
1018 "meson",
1019 "test",
1020 "-t",
1021 "10",
1022 "-C",
1023 "build",
1024 "--print-errorlogs",
1025 "--wrapper",
1026 "valgrind",
1027 )
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301028 except CalledProcessError:
Patrick Williamse08ffba2022-12-05 10:33:46 -06001029 raise Exception("Valgrind tests failed")
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301030
1031 def analyze(self):
1032 self._maybe_valgrind()
1033
1034 # Run clang-tidy only if the project has a configuration
Patrick Williamse08ffba2022-12-05 10:33:46 -06001035 if os.path.isfile(".clang-tidy"):
Manojkiran Eda1aa91992020-10-02 14:11:53 +05301036 os.environ["CXX"] = "clang++"
Patrick Williamse08ffba2022-12-05 10:33:46 -06001037 with TemporaryDirectory(prefix="build", dir=".") as build_dir:
1038 check_call_cmd("meson", "setup", build_dir)
Ed Tanous29e02312022-12-19 16:20:34 -08001039 if not os.path.isfile(".openbmc-no-clang"):
1040 check_call_cmd("meson", "compile", "-C", build_dir)
Nan Zhou82aaba02022-09-16 00:21:07 +00001041 try:
Ed Tanous1eb19942023-02-10 16:08:46 -08001042 check_call_cmd("ninja", "-C", build_dir, "clang-tidy")
Nan Zhou82aaba02022-09-16 00:21:07 +00001043 except subprocess.CalledProcessError:
Patrick Williamse08ffba2022-12-05 10:33:46 -06001044 check_call_cmd(
1045 "git", "-C", CODE_SCAN_DIR, "--no-pager", "diff"
1046 )
Nan Zhou82aaba02022-09-16 00:21:07 +00001047 raise
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301048 # Run the basic clang static analyzer otherwise
1049 else:
Patrick Williamse08ffba2022-12-05 10:33:46 -06001050 check_call_cmd("ninja", "-C", "build", "scan-build")
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301051
1052 # Run tests through sanitizers
1053 # b_lundef is needed if clang++ is CXX since it resolves the
1054 # asan symbols at runtime only. We don't want to set it earlier
1055 # in the build process to ensure we don't have undefined
1056 # runtime code.
1057 if is_sanitize_safe():
Patrick Williamse08ffba2022-12-05 10:33:46 -06001058 check_call_cmd(
1059 "meson",
1060 "configure",
1061 "build",
1062 "-Db_sanitize=address,undefined",
1063 "-Db_lundef=false",
1064 )
1065 check_call_cmd(
1066 "meson",
1067 "test",
1068 "-C",
1069 "build",
1070 "--print-errorlogs",
1071 "--logbase",
1072 "testlog-ubasan",
Andrew Jeffery71924fd2023-04-12 23:27:05 +09301073 env=os.environ | {"UBSAN_OPTIONS": "halt_on_error=1"},
Patrick Williamse08ffba2022-12-05 10:33:46 -06001074 )
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301075 # TODO: Fix memory sanitizer
Andrew Jeffery6015ca12020-03-13 11:50:10 +10301076 # check_call_cmd('meson', 'configure', 'build',
1077 # '-Db_sanitize=memory')
1078 # check_call_cmd('meson', 'test', '-C', 'build'
1079 # '--logbase', 'testlog-msan')
Patrick Williamse08ffba2022-12-05 10:33:46 -06001080 check_call_cmd("meson", "configure", "build", "-Db_sanitize=none")
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301081 else:
1082 sys.stderr.write("###### Skipping sanitizers ######\n")
1083
1084 # Run coverage checks
Patrick Williamse08ffba2022-12-05 10:33:46 -06001085 check_call_cmd("meson", "configure", "build", "-Db_coverage=true")
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301086 self.test()
1087 # Only build coverage HTML if coverage files were produced
Patrick Williamse08ffba2022-12-05 10:33:46 -06001088 for root, dirs, files in os.walk("build"):
1089 if any([f.endswith(".gcda") for f in files]):
1090 check_call_cmd("ninja", "-C", "build", "coverage-html")
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301091 break
Patrick Williamse08ffba2022-12-05 10:33:46 -06001092 check_call_cmd("meson", "configure", "build", "-Db_coverage=false")
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301093 run_cppcheck()
1094
Patrick Williams95095f12021-04-14 14:42:35 -05001095 def _extra_meson_checks(self):
Patrick Williamse08ffba2022-12-05 10:33:46 -06001096 with open(os.path.join(self.path, "meson.build"), "rt") as f:
Patrick Williams95095f12021-04-14 14:42:35 -05001097 build_contents = f.read()
1098
1099 # Find project's specified meson_version.
1100 meson_version = None
1101 pattern = r"meson_version:[^']*'([^']*)'"
1102 for match in re.finditer(pattern, build_contents):
1103 group = match.group(1)
1104 meson_version = group
1105
1106 # C++20 requires at least Meson 0.57 but Meson itself doesn't
1107 # identify this. Add to our unit-test checks so that we don't
1108 # get a meson.build missing this.
1109 pattern = r"'cpp_std=c\+\+20'"
1110 for match in re.finditer(pattern, build_contents):
Patrick Williamse08ffba2022-12-05 10:33:46 -06001111 if not meson_version or not meson_version_compare(
1112 meson_version, ">=0.57"
1113 ):
Patrick Williams95095f12021-04-14 14:42:35 -05001114 raise Exception(
1115 "C++20 support requires specifying in meson.build: "
1116 + "meson_version: '>=0.57'"
1117 )
1118
Patrick Williamsb9e07122023-07-12 10:16:28 -05001119 # C++23 requires at least Meson 1.1.1 but Meson itself doesn't
1120 # identify this. Add to our unit-test checks so that we don't
1121 # get a meson.build missing this.
1122 pattern = r"'cpp_std=c\+\+23'"
1123 for match in re.finditer(pattern, build_contents):
1124 if not meson_version or not meson_version_compare(
1125 meson_version, ">=1.1.1"
1126 ):
1127 raise Exception(
1128 "C++23 support requires specifying in meson.build: "
1129 + "meson_version: '>=1.1.1'"
1130 )
1131
Patrick Williamscebaea22023-04-12 10:45:23 -05001132 if "get_variable(" in build_contents:
1133 if not meson_version or not meson_version_compare(
1134 meson_version, ">=0.58"
1135 ):
1136 raise Exception(
1137 "dep.get_variable() with positional argument requires "
1138 + "meson_Version: '>=0.58'"
1139 )
1140
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301141
1142class Package(object):
1143 def __init__(self, name=None, path=None):
Manojkiran Eda29d28252020-06-15 18:58:09 +05301144 self.supported = [Meson, Autotools, CMake]
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301145 self.name = name
1146 self.path = path
1147 self.test_only = False
1148
1149 def build_systems(self):
Andrew Jeffery881041f2020-03-13 11:46:30 +10301150 instances = (system(self.name, self.path) for system in self.supported)
1151 return (instance for instance in instances if instance.probe())
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301152
1153 def build_system(self, preferred=None):
Andrew Geissler8cb74fc2020-03-19 14:48:05 -05001154 systems = list(self.build_systems())
1155
1156 if not systems:
1157 return None
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301158
1159 if preferred:
Andrew Jeffery881041f2020-03-13 11:46:30 +10301160 return {type(system): system for system in systems}[preferred]
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301161
1162 return next(iter(systems))
1163
1164 def install(self, system=None):
1165 if not system:
1166 system = self.build_system()
1167
1168 system.configure(False)
1169 system.build()
1170 system.install()
1171
Andrew Jeffery19d75672020-03-13 10:42:08 +10301172 def _test_one(self, system):
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301173 system.configure(True)
1174 system.build()
1175 system.install()
1176 system.test()
Andrew Jefferyd0809692021-05-14 16:23:57 +09301177 if not TEST_ONLY:
1178 system.analyze()
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301179
Andrew Jeffery19d75672020-03-13 10:42:08 +10301180 def test(self):
1181 for system in self.build_systems():
1182 self._test_one(system)
1183
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301184
Matt Spinler9bfaaad2019-10-25 09:51:50 -05001185def find_file(filename, basedir):
1186 """
Patrick Williams55448ad2020-12-14 14:28:28 -06001187 Finds all occurrences of a file (or list of files) in the base
1188 directory and passes them back with their relative paths.
Matt Spinler9bfaaad2019-10-25 09:51:50 -05001189
1190 Parameter descriptions:
Patrick Williams55448ad2020-12-14 14:28:28 -06001191 filename The name of the file (or list of files) to
1192 find
Matt Spinler9bfaaad2019-10-25 09:51:50 -05001193 basedir The base directory search in
1194 """
1195
Patrick Williams55448ad2020-12-14 14:28:28 -06001196 if not isinstance(filename, list):
Lei YU08d2b922022-04-25 11:21:36 +08001197 filename = [filename]
Patrick Williams55448ad2020-12-14 14:28:28 -06001198
Matt Spinler9bfaaad2019-10-25 09:51:50 -05001199 filepaths = []
1200 for root, dirs, files in os.walk(basedir):
Patrick Williamse08ffba2022-12-05 10:33:46 -06001201 if os.path.split(root)[-1] == "subprojects":
Brad Bishopeb667262021-08-06 13:54:58 -04001202 for f in files:
Patrick Williamse08ffba2022-12-05 10:33:46 -06001203 subproject = ".".join(f.split(".")[0:-1])
1204 if f.endswith(".wrap") and subproject in dirs:
Brad Bishopeb667262021-08-06 13:54:58 -04001205 # don't find files in meson subprojects with wraps
1206 dirs.remove(subproject)
Patrick Williams55448ad2020-12-14 14:28:28 -06001207 for f in filename:
1208 if f in files:
1209 filepaths.append(os.path.join(root, f))
Matt Spinler9bfaaad2019-10-25 09:51:50 -05001210 return filepaths
1211
Andrew Jeffery9cc97d82020-03-13 11:57:44 +10301212
Patrick Williamse08ffba2022-12-05 10:33:46 -06001213if __name__ == "__main__":
Matthew Barthccb7f852016-11-23 17:43:02 -06001214 # CONFIGURE_FLAGS = [GIT REPO]:[CONFIGURE FLAGS]
1215 CONFIGURE_FLAGS = {
Patrick Williamse08ffba2022-12-05 10:33:46 -06001216 "phosphor-logging": [
1217 "--enable-metadata-processing",
1218 "--enable-openpower-pel-extension",
1219 "YAML_DIR=/usr/local/share/phosphor-dbus-yaml/yaml",
1220 ]
Matthew Barthccb7f852016-11-23 17:43:02 -06001221 }
1222
William A. Kennington III3f1d1202018-12-06 18:02:07 -08001223 # MESON_FLAGS = [GIT REPO]:[MESON FLAGS]
1224 MESON_FLAGS = {
Patrick Williamse08ffba2022-12-05 10:33:46 -06001225 "phosphor-dbus-interfaces": [
1226 "-Ddata_com_ibm=true",
1227 "-Ddata_org_open_power=true",
1228 ],
1229 "phosphor-logging": ["-Dopenpower-pel-extension=enabled"],
William A. Kennington III3f1d1202018-12-06 18:02:07 -08001230 }
1231
Matthew Barthccb7f852016-11-23 17:43:02 -06001232 # DEPENDENCIES = [MACRO]:[library/header]:[GIT REPO]
1233 DEPENDENCIES = {
Patrick Williamse08ffba2022-12-05 10:33:46 -06001234 "AC_CHECK_LIB": {"mapper": "phosphor-objmgr"},
1235 "AC_CHECK_HEADER": {
1236 "host-ipmid": "phosphor-host-ipmid",
1237 "blobs-ipmid": "phosphor-ipmi-blobs",
1238 "sdbusplus": "sdbusplus",
1239 "sdeventplus": "sdeventplus",
1240 "stdplus": "stdplus",
1241 "gpioplus": "gpioplus",
1242 "phosphor-logging/log.hpp": "phosphor-logging",
Patrick Williamseab8a372017-01-30 11:21:32 -06001243 },
Patrick Williamse08ffba2022-12-05 10:33:46 -06001244 "AC_PATH_PROG": {"sdbus++": "sdbusplus"},
1245 "PKG_CHECK_MODULES": {
1246 "phosphor-dbus-interfaces": "phosphor-dbus-interfaces",
1247 "libipmid": "phosphor-host-ipmid",
1248 "libipmid-host": "phosphor-host-ipmid",
1249 "sdbusplus": "sdbusplus",
1250 "sdeventplus": "sdeventplus",
1251 "stdplus": "stdplus",
1252 "gpioplus": "gpioplus",
1253 "phosphor-logging": "phosphor-logging",
1254 "phosphor-snmp": "phosphor-snmp",
1255 "ipmiblob": "ipmi-blob-tool",
1256 "hei": "openpower-libhei",
1257 "phosphor-ipmi-blobs": "phosphor-ipmi-blobs",
1258 "libcr51sign": "google-misc",
Brad Bishopebb49112017-02-13 11:07:26 -05001259 },
Matthew Barthccb7f852016-11-23 17:43:02 -06001260 }
1261
William A. Kennington IIIe67f5fc2018-12-06 17:40:30 -08001262 # Offset into array of macro parameters MACRO(0, 1, ...N)
1263 DEPENDENCIES_OFFSET = {
Patrick Williamse08ffba2022-12-05 10:33:46 -06001264 "AC_CHECK_LIB": 0,
1265 "AC_CHECK_HEADER": 0,
1266 "AC_PATH_PROG": 1,
1267 "PKG_CHECK_MODULES": 1,
William A. Kennington IIIe67f5fc2018-12-06 17:40:30 -08001268 }
1269
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -05001270 # DEPENDENCIES_REGEX = [GIT REPO]:[REGEX STRING]
Patrick Williamse08ffba2022-12-05 10:33:46 -06001271 DEPENDENCIES_REGEX = {"phosphor-logging": r"\S+-dbus-interfaces$"}
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -05001272
Matthew Barth33df8792016-12-19 14:30:17 -06001273 # Set command line arguments
1274 parser = argparse.ArgumentParser()
Patrick Williamse08ffba2022-12-05 10:33:46 -06001275 parser.add_argument(
1276 "-w",
1277 "--workspace",
1278 dest="WORKSPACE",
1279 required=True,
1280 help="Workspace directory location(i.e. /home)",
1281 )
1282 parser.add_argument(
1283 "-p",
1284 "--package",
1285 dest="PACKAGE",
1286 required=True,
1287 help="OpenBMC package to be unit tested",
1288 )
1289 parser.add_argument(
1290 "-t",
1291 "--test-only",
1292 dest="TEST_ONLY",
1293 action="store_true",
1294 required=False,
1295 default=False,
1296 help="Only run test cases, no other validation",
1297 )
Ramin Izadpanah298d56b2020-11-17 16:55:28 +00001298 arg_inttests = parser.add_mutually_exclusive_group()
Patrick Williamse08ffba2022-12-05 10:33:46 -06001299 arg_inttests.add_argument(
1300 "--integration-tests",
1301 dest="INTEGRATION_TEST",
1302 action="store_true",
1303 required=False,
1304 default=True,
1305 help="Enable integration tests [default].",
1306 )
1307 arg_inttests.add_argument(
1308 "--no-integration-tests",
1309 dest="INTEGRATION_TEST",
1310 action="store_false",
1311 required=False,
1312 help="Disable integration tests.",
1313 )
1314 parser.add_argument(
1315 "-v",
1316 "--verbose",
1317 action="store_true",
1318 help="Print additional package status messages",
1319 )
1320 parser.add_argument(
1321 "-r", "--repeat", help="Repeat tests N times", type=int, default=1
1322 )
1323 parser.add_argument(
1324 "-b",
1325 "--branch",
1326 dest="BRANCH",
1327 required=False,
1328 help="Branch to target for dependent repositories",
1329 default="master",
1330 )
1331 parser.add_argument(
1332 "-n",
1333 "--noformat",
1334 dest="FORMAT",
1335 action="store_false",
1336 required=False,
1337 help="Whether or not to run format code",
1338 )
Matthew Barth33df8792016-12-19 14:30:17 -06001339 args = parser.parse_args(sys.argv[1:])
1340 WORKSPACE = args.WORKSPACE
1341 UNIT_TEST_PKG = args.PACKAGE
William A. Kennington III65b37fa2019-01-31 15:15:17 -08001342 TEST_ONLY = args.TEST_ONLY
Ramin Izadpanah298d56b2020-11-17 16:55:28 +00001343 INTEGRATION_TEST = args.INTEGRATION_TEST
Andrew Geisslera61acb52019-01-03 16:32:44 -06001344 BRANCH = args.BRANCH
Lei YU7ef93302019-11-06 13:53:21 +08001345 FORMAT_CODE = args.FORMAT
Matthew Barth33df8792016-12-19 14:30:17 -06001346 if args.verbose:
Patrick Williamse08ffba2022-12-05 10:33:46 -06001347
Matthew Barth33df8792016-12-19 14:30:17 -06001348 def printline(*line):
1349 for arg in line:
Patrick Williamse08ffba2022-12-05 10:33:46 -06001350 print(arg, end=" ")
Andrew Jeffery89b64b62020-03-13 12:15:48 +10301351 print()
Patrick Williamse08ffba2022-12-05 10:33:46 -06001352
Matthew Barth33df8792016-12-19 14:30:17 -06001353 else:
Patrick Williamse08ffba2022-12-05 10:33:46 -06001354
Andrew Jefferyfce557f2020-03-13 12:12:19 +10301355 def printline(*line):
1356 pass
Matthew Barthccb7f852016-11-23 17:43:02 -06001357
Patrick Williamsb6535952020-12-15 06:40:10 -06001358 CODE_SCAN_DIR = os.path.join(WORKSPACE, UNIT_TEST_PKG)
Lei YU7ef93302019-11-06 13:53:21 +08001359
Patrick Williams330b0772022-11-28 06:14:06 -06001360 # Run format-code.sh, which will in turn call any repo-level formatters.
Lei YU7ef93302019-11-06 13:53:21 +08001361 if FORMAT_CODE:
Patrick Williamse08ffba2022-12-05 10:33:46 -06001362 check_call_cmd(
1363 os.path.join(
1364 WORKSPACE, "openbmc-build-scripts", "scripts", "format-code.sh"
1365 ),
1366 CODE_SCAN_DIR,
1367 )
Andrew Geisslera28286d2018-01-10 11:00:00 -08001368
Ed Tanous32768b82022-01-05 14:14:06 -08001369 # Check to see if any files changed
Patrick Williamse08ffba2022-12-05 10:33:46 -06001370 check_call_cmd(
1371 "git", "-C", CODE_SCAN_DIR, "--no-pager", "diff", "--exit-code"
1372 )
Ed Tanous32768b82022-01-05 14:14:06 -08001373
Andrew Geissler8cb74fc2020-03-19 14:48:05 -05001374 # Check if this repo has a supported make infrastructure
Patrick Williamsb6535952020-12-15 06:40:10 -06001375 pkg = Package(UNIT_TEST_PKG, CODE_SCAN_DIR)
Andrew Geissler8cb74fc2020-03-19 14:48:05 -05001376 if not pkg.build_system():
1377 print("No valid build system, exit")
1378 sys.exit(0)
1379
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301380 prev_umask = os.umask(000)
James Feist878df5c2018-07-26 14:54:28 -07001381
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301382 # Determine dependencies and add them
1383 dep_added = dict()
1384 dep_added[UNIT_TEST_PKG] = False
William A. Kennington III40d5c7c2018-12-13 14:37:59 -08001385
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301386 # Create dependency tree
1387 dep_tree = DepTree(UNIT_TEST_PKG)
Patrick Williamsb6535952020-12-15 06:40:10 -06001388 build_dep_tree(UNIT_TEST_PKG, CODE_SCAN_DIR, dep_added, dep_tree, BRANCH)
William A. Kennington III65b37fa2019-01-31 15:15:17 -08001389
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301390 # Reorder Dependency Tree
Andrew Jeffery89b64b62020-03-13 12:15:48 +10301391 for pkg_name, regex_str in DEPENDENCIES_REGEX.items():
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301392 dep_tree.ReorderDeps(pkg_name, regex_str)
1393 if args.verbose:
1394 dep_tree.PrintTree()
William A. Kennington III65b37fa2019-01-31 15:15:17 -08001395
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301396 install_list = dep_tree.GetInstallList()
Andrew Geissler9ced4ed2019-11-18 14:33:58 -06001397
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301398 # We don't want to treat our package as a dependency
1399 install_list.remove(UNIT_TEST_PKG)
James Feist878df5c2018-07-26 14:54:28 -07001400
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301401 # Install reordered dependencies
1402 for dep in install_list:
1403 build_and_install(dep, False)
James Feist878df5c2018-07-26 14:54:28 -07001404
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301405 # Run package unit tests
1406 build_and_install(UNIT_TEST_PKG, True)
James Feist878df5c2018-07-26 14:54:28 -07001407
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301408 os.umask(prev_umask)
Matt Spinler9bfaaad2019-10-25 09:51:50 -05001409
1410 # Run any custom CI scripts the repo has, of which there can be
1411 # multiple of and anywhere in the repository.
Patrick Williamse08ffba2022-12-05 10:33:46 -06001412 ci_scripts = find_file(["run-ci.sh", "run-ci"], CODE_SCAN_DIR)
Matt Spinler9bfaaad2019-10-25 09:51:50 -05001413 if ci_scripts:
Patrick Williamsb6535952020-12-15 06:40:10 -06001414 os.chdir(CODE_SCAN_DIR)
Matt Spinler9bfaaad2019-10-25 09:51:50 -05001415 for ci_script in ci_scripts:
Patrick Williams55448ad2020-12-14 14:28:28 -06001416 check_call_cmd(ci_script)