blob: f0608bb2728fa709b1b5fa164a201e6eb68da211 [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
Matthew Barthd1810372016-12-19 16:57:21 -060010from git import Repo
William A. Kennington IIIfcd70772020-06-04 00:50:23 -070011from mesonbuild import coredata, optinterpreter
Andrew Jeffery89b64b62020-03-13 12:15:48 +103012from urllib.parse import urljoin
Andrew Jefferya4e31c62018-03-08 13:45:28 +103013from subprocess import check_call, call, CalledProcessError
Matthew Barthccb7f852016-11-23 17:43:02 -060014import os
15import sys
Matthew Barth33df8792016-12-19 14:30:17 -060016import argparse
William A. Kennington IIIa2156732018-06-30 18:38:09 -070017import multiprocessing
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -050018import re
William A. Kennington IIIe67f5fc2018-12-06 17:40:30 -080019import subprocess
William A. Kennington III3f1d1202018-12-06 18:02:07 -080020import shutil
William A. Kennington III4e1d0a12018-07-16 12:04:03 -070021import platform
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -050022
23
24class DepTree():
25 """
26 Represents package dependency tree, where each node is a DepTree with a
27 name and DepTree children.
28 """
29
30 def __init__(self, name):
31 """
32 Create new DepTree.
33
34 Parameter descriptions:
35 name Name of new tree node.
36 """
37 self.name = name
38 self.children = list()
39
40 def AddChild(self, name):
41 """
42 Add new child node to current node.
43
44 Parameter descriptions:
45 name Name of new child
46 """
47 new_child = DepTree(name)
48 self.children.append(new_child)
49 return new_child
50
51 def AddChildNode(self, node):
52 """
53 Add existing child node to current node.
54
55 Parameter descriptions:
56 node Tree node to add
57 """
58 self.children.append(node)
59
60 def RemoveChild(self, name):
61 """
62 Remove child node.
63
64 Parameter descriptions:
65 name Name of child to remove
66 """
67 for child in self.children:
68 if child.name == name:
69 self.children.remove(child)
70 return
71
72 def GetNode(self, name):
73 """
74 Return node with matching name. Return None if not found.
75
76 Parameter descriptions:
77 name Name of node to return
78 """
79 if self.name == name:
80 return self
81 for child in self.children:
82 node = child.GetNode(name)
83 if node:
84 return node
85 return None
86
87 def GetParentNode(self, name, parent_node=None):
88 """
89 Return parent of node with matching name. Return none if not found.
90
91 Parameter descriptions:
92 name Name of node to get parent of
93 parent_node Parent of current node
94 """
95 if self.name == name:
96 return parent_node
97 for child in self.children:
98 found_node = child.GetParentNode(name, self)
99 if found_node:
100 return found_node
101 return None
102
103 def GetPath(self, name, path=None):
104 """
105 Return list of node names from head to matching name.
106 Return None if not found.
107
108 Parameter descriptions:
109 name Name of node
110 path List of node names from head to current node
111 """
112 if not path:
113 path = []
114 if self.name == name:
115 path.append(self.name)
116 return path
117 for child in self.children:
118 match = child.GetPath(name, path + [self.name])
119 if match:
120 return match
121 return None
122
123 def GetPathRegex(self, name, regex_str, path=None):
124 """
125 Return list of node paths that end in name, or match regex_str.
126 Return empty list if not found.
127
128 Parameter descriptions:
129 name Name of node to search for
130 regex_str Regex string to match node names
131 path Path of node names from head to current node
132 """
133 new_paths = []
134 if not path:
135 path = []
136 match = re.match(regex_str, self.name)
137 if (self.name == name) or (match):
138 new_paths.append(path + [self.name])
139 for child in self.children:
140 return_paths = None
141 full_path = path + [self.name]
142 return_paths = child.GetPathRegex(name, regex_str, full_path)
143 for i in return_paths:
144 new_paths.append(i)
145 return new_paths
146
147 def MoveNode(self, from_name, to_name):
148 """
149 Mode existing from_name node to become child of to_name node.
150
151 Parameter descriptions:
152 from_name Name of node to make a child of to_name
153 to_name Name of node to make parent of from_name
154 """
155 parent_from_node = self.GetParentNode(from_name)
156 from_node = self.GetNode(from_name)
157 parent_from_node.RemoveChild(from_name)
158 to_node = self.GetNode(to_name)
159 to_node.AddChildNode(from_node)
160
161 def ReorderDeps(self, name, regex_str):
162 """
163 Reorder dependency tree. If tree contains nodes with names that
164 match 'name' and 'regex_str', move 'regex_str' nodes that are
165 to the right of 'name' node, so that they become children of the
166 'name' node.
167
168 Parameter descriptions:
169 name Name of node to look for
170 regex_str Regex string to match names to
171 """
172 name_path = self.GetPath(name)
173 if not name_path:
174 return
175 paths = self.GetPathRegex(name, regex_str)
176 is_name_in_paths = False
177 name_index = 0
178 for i in range(len(paths)):
179 path = paths[i]
180 if path[-1] == name:
181 is_name_in_paths = True
182 name_index = i
183 break
184 if not is_name_in_paths:
185 return
186 for i in range(name_index + 1, len(paths)):
187 path = paths[i]
188 if name in path:
189 continue
190 from_name = path[-1]
191 self.MoveNode(from_name, name)
192
193 def GetInstallList(self):
194 """
195 Return post-order list of node names.
196
197 Parameter descriptions:
198 """
199 install_list = []
200 for child in self.children:
201 child_install_list = child.GetInstallList()
202 install_list.extend(child_install_list)
203 install_list.append(self.name)
204 return install_list
205
206 def PrintTree(self, level=0):
207 """
208 Print pre-order node names with indentation denoting node depth level.
209
210 Parameter descriptions:
211 level Current depth level
212 """
213 INDENT_PER_LEVEL = 4
Andrew Jeffery89b64b62020-03-13 12:15:48 +1030214 print(' ' * (level * INDENT_PER_LEVEL) + self.name)
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500215 for child in self.children:
216 child.PrintTree(level + 1)
Matthew Barth33df8792016-12-19 14:30:17 -0600217
218
William A. Kennington III1fddb972019-02-06 18:03:53 -0800219def check_call_cmd(*cmd):
Matthew Barth33df8792016-12-19 14:30:17 -0600220 """
221 Verbose prints the directory location the given command is called from and
222 the command, then executes the command using check_call.
223
224 Parameter descriptions:
225 dir Directory location command is to be called from
226 cmd List of parameters constructing the complete command
227 """
William A. Kennington III1fddb972019-02-06 18:03:53 -0800228 printline(os.getcwd(), ">", " ".join(cmd))
Matthew Barth33df8792016-12-19 14:30:17 -0600229 check_call(cmd)
Matthew Barthccb7f852016-11-23 17:43:02 -0600230
231
Andrew Geisslera61acb52019-01-03 16:32:44 -0600232def clone_pkg(pkg, branch):
Matthew Barth33df8792016-12-19 14:30:17 -0600233 """
234 Clone the given openbmc package's git repository from gerrit into
235 the WORKSPACE location
236
237 Parameter descriptions:
238 pkg Name of the package to clone
Andrew Geisslera61acb52019-01-03 16:32:44 -0600239 branch Branch to clone from pkg
Matthew Barth33df8792016-12-19 14:30:17 -0600240 """
Andrew Jeffery7be94ca2018-03-08 13:15:33 +1030241 pkg_dir = os.path.join(WORKSPACE, pkg)
242 if os.path.exists(os.path.join(pkg_dir, '.git')):
243 return pkg_dir
Matthew Barthccb7f852016-11-23 17:43:02 -0600244 pkg_repo = urljoin('https://gerrit.openbmc-project.xyz/openbmc/', pkg)
Andrew Jeffery7be94ca2018-03-08 13:15:33 +1030245 os.mkdir(pkg_dir)
Andrew Geisslera61acb52019-01-03 16:32:44 -0600246 printline(pkg_dir, "> git clone", pkg_repo, branch, "./")
247 try:
248 # first try the branch
Andrew Jeffery7d4a26f2020-03-13 11:42:34 +1030249 clone = Repo.clone_from(pkg_repo, pkg_dir, branch=branch)
250 repo_inst = clone.working_dir
Andrew Geisslera61acb52019-01-03 16:32:44 -0600251 except:
252 printline("Input branch not found, default to master")
Andrew Jeffery7d4a26f2020-03-13 11:42:34 +1030253 clone = Repo.clone_from(pkg_repo, pkg_dir, branch="master")
254 repo_inst = clone.working_dir
Andrew Geisslera61acb52019-01-03 16:32:44 -0600255 return repo_inst
Matthew Barth33df8792016-12-19 14:30:17 -0600256
257
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030258def make_target_exists(target):
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800259 """
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030260 Runs a check against the makefile in the current directory to determine
261 if the target exists so that it can be built.
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800262
263 Parameter descriptions:
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030264 target The make target we are checking
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800265 """
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030266 try:
Andrew Jeffery881041f2020-03-13 11:46:30 +1030267 cmd = ['make', '-n', target]
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030268 with open(os.devnull, 'w') as devnull:
269 check_call(cmd, stdout=devnull, stderr=devnull)
270 return True
271 except CalledProcessError:
272 return False
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800273
William A. Kennington III3f1d1202018-12-06 18:02:07 -0800274
William A. Kennington IIIa2156732018-06-30 18:38:09 -0700275make_parallel = [
276 'make',
277 # Run enough jobs to saturate all the cpus
278 '-j', str(multiprocessing.cpu_count()),
279 # Don't start more jobs if the load avg is too high
280 '-l', str(multiprocessing.cpu_count()),
281 # Synchronize the output so logs aren't intermixed in stdout / stderr
282 '-O',
283]
284
William A. Kennington III3f1d1202018-12-06 18:02:07 -0800285
Andrew Jefferyff5c5d52020-03-13 10:12:14 +1030286def build_and_install(name, build_for_testing=False):
William A. Kennington III780ec092018-12-06 14:46:50 -0800287 """
288 Builds and installs the package in the environment. Optionally
289 builds the examples and test cases for package.
290
291 Parameter description:
Andrew Jefferyff5c5d52020-03-13 10:12:14 +1030292 name The name of the package we are building
William A. Kennington IIIa0454912018-12-06 14:47:16 -0800293 build_for_testing Enable options related to testing on the package?
William A. Kennington III780ec092018-12-06 14:46:50 -0800294 """
Andrew Jefferyff5c5d52020-03-13 10:12:14 +1030295 os.chdir(os.path.join(WORKSPACE, name))
William A. Kennington III54d4faf2018-12-06 17:46:24 -0800296
297 # Refresh dynamic linker run time bindings for dependencies
William A. Kennington III1fddb972019-02-06 18:03:53 -0800298 check_call_cmd('sudo', '-n', '--', 'ldconfig')
William A. Kennington III54d4faf2018-12-06 17:46:24 -0800299
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030300 pkg = Package()
301 if build_for_testing:
302 pkg.test()
William A. Kennington III3f1d1202018-12-06 18:02:07 -0800303 else:
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030304 pkg.install()
305
William A. Kennington III780ec092018-12-06 14:46:50 -0800306
Andrew Jefferyccf85d62020-03-13 10:25:42 +1030307def build_dep_tree(name, pkgdir, dep_added, head, branch, dep_tree=None):
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500308 """
Andrew Jefferyccf85d62020-03-13 10:25:42 +1030309 For each package (name), starting with the package to be unit tested,
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030310 extract its dependencies. For each package dependency defined, recursively
311 apply the same strategy
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500312
313 Parameter descriptions:
Andrew Jefferyccf85d62020-03-13 10:25:42 +1030314 name Name of the package
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500315 pkgdir Directory where package source is located
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800316 dep_added Current dict of dependencies and added status
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500317 head Head node of the dependency tree
Andrew Geisslera61acb52019-01-03 16:32:44 -0600318 branch Branch to clone from pkg
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500319 dep_tree Current dependency tree node
320 """
321 if not dep_tree:
322 dep_tree = head
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800323
William A. Kennington IIIbe6aab22018-12-06 15:01:54 -0800324 with open("/tmp/depcache", "r") as depcache:
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800325 cache = depcache.readline()
326
327 # Read out pkg dependencies
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030328 pkg = Package(name, pkgdir)
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800329
Patrick Williams38c6b7d2021-02-18 07:26:14 -0600330 build = pkg.build_system()
331 if build == None:
332 raise Exception(f"Unable to find build system for {name}.")
333
334 for dep in set(build.dependencies()):
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800335 if dep in cache:
336 continue
337 # Dependency package not already known
338 if dep_added.get(dep) is None:
Patrick Williams38c6b7d2021-02-18 07:26:14 -0600339 print(f"Adding {dep} dependency to {name}.")
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800340 # Dependency package not added
341 new_child = dep_tree.AddChild(dep)
342 dep_added[dep] = False
Andrew Jeffery3b92fdd2020-03-13 11:49:18 +1030343 dep_pkgdir = clone_pkg(dep, branch)
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800344 # Determine this dependency package's
345 # dependencies and add them before
346 # returning to add this package
347 dep_added = build_dep_tree(dep,
348 dep_pkgdir,
349 dep_added,
350 head,
Andrew Geisslera61acb52019-01-03 16:32:44 -0600351 branch,
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800352 new_child)
353 else:
354 # Dependency package known and added
355 if dep_added[dep]:
Andrew Jeffery2cb0c7a2018-03-08 13:19:08 +1030356 continue
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500357 else:
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800358 # Cyclic dependency failure
Andrew Jefferyccf85d62020-03-13 10:25:42 +1030359 raise Exception("Cyclic dependencies found in "+name)
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500360
Andrew Jefferyccf85d62020-03-13 10:25:42 +1030361 if not dep_added[name]:
362 dep_added[name] = True
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500363
364 return dep_added
Matthew Barthccb7f852016-11-23 17:43:02 -0600365
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700366
William A. Kennington III90b106a2019-02-06 18:08:24 -0800367def run_cppcheck():
Andrew Jefferybf735102020-03-13 12:13:21 +1030368 match_re = re.compile(r'((?!\.mako\.).)*\.[ch](?:pp)?$', re.I)
Brad Bishop48424d42020-01-07 13:01:31 -0500369 cppcheck_files = []
370 stdout = subprocess.check_output(['git', 'ls-files'])
Patrick Venturead4354e2018-10-12 16:59:54 -0700371
Brad Bishop48424d42020-01-07 13:01:31 -0500372 for f in stdout.decode('utf-8').split():
373 if match_re.match(f):
374 cppcheck_files.append(f)
375
376 if not cppcheck_files:
377 # skip cppcheck if there arent' any c or cpp sources.
378 print("no files")
379 return None
380
381 # http://cppcheck.sourceforge.net/manual.pdf
382 params = ['cppcheck', '-j', str(multiprocessing.cpu_count()),
Manojkiran Eda89a46cc2020-08-22 17:08:11 +0530383 '--enable=all', '--library=googletest', '--file-list=-']
Brad Bishop48424d42020-01-07 13:01:31 -0500384
385 cppcheck_process = subprocess.Popen(
386 params,
387 stdout=subprocess.PIPE,
388 stderr=subprocess.PIPE,
389 stdin=subprocess.PIPE)
390 (stdout, stderr) = cppcheck_process.communicate(
Andrew Jeffery89b64b62020-03-13 12:15:48 +1030391 input='\n'.join(cppcheck_files).encode('utf-8'))
Brad Bishop48424d42020-01-07 13:01:31 -0500392
393 if cppcheck_process.wait():
Patrick Venturead4354e2018-10-12 16:59:54 -0700394 raise Exception('Cppcheck failed')
Andrew Jeffery89b64b62020-03-13 12:15:48 +1030395 print(stdout.decode('utf-8'))
396 print(stderr.decode('utf-8'))
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700397
Andrew Jefferye5fffa02020-03-13 11:51:57 +1030398
William A. Kennington III37a89a22018-12-13 14:32:02 -0800399def is_valgrind_safe():
400 """
401 Returns whether it is safe to run valgrind on our platform
402 """
William A. Kennington III0326ded2019-02-07 00:33:28 -0800403 src = 'unit-test-vg.c'
404 exe = './unit-test-vg'
405 with open(src, 'w') as h:
William A. Kennington IIIafb0f982019-04-26 17:30:28 -0700406 h.write('#include <errno.h>\n')
407 h.write('#include <stdio.h>\n')
William A. Kennington III0326ded2019-02-07 00:33:28 -0800408 h.write('#include <stdlib.h>\n')
409 h.write('#include <string.h>\n')
410 h.write('int main() {\n')
411 h.write('char *heap_str = malloc(16);\n')
412 h.write('strcpy(heap_str, "RandString");\n')
413 h.write('int res = strcmp("RandString", heap_str);\n')
414 h.write('free(heap_str);\n')
William A. Kennington IIIafb0f982019-04-26 17:30:28 -0700415 h.write('char errstr[64];\n')
416 h.write('strerror_r(EINVAL, errstr, sizeof(errstr));\n')
417 h.write('printf("%s\\n", errstr);\n')
William A. Kennington III0326ded2019-02-07 00:33:28 -0800418 h.write('return res;\n')
419 h.write('}\n')
420 try:
421 with open(os.devnull, 'w') as devnull:
422 check_call(['gcc', '-O2', '-o', exe, src],
423 stdout=devnull, stderr=devnull)
424 check_call(['valgrind', '--error-exitcode=99', exe],
425 stdout=devnull, stderr=devnull)
426 return True
427 except:
428 sys.stderr.write("###### Platform is not valgrind safe ######\n")
429 return False
430 finally:
431 os.remove(src)
432 os.remove(exe)
William A. Kennington III37a89a22018-12-13 14:32:02 -0800433
Andrew Jefferye5fffa02020-03-13 11:51:57 +1030434
William A. Kennington III282e3302019-02-04 16:55:05 -0800435def is_sanitize_safe():
436 """
437 Returns whether it is safe to run sanitizers on our platform
438 """
William A. Kennington III0b7fb2b2019-02-07 00:33:42 -0800439 src = 'unit-test-sanitize.c'
440 exe = './unit-test-sanitize'
441 with open(src, 'w') as h:
442 h.write('int main() { return 0; }\n')
443 try:
444 with open(os.devnull, 'w') as devnull:
445 check_call(['gcc', '-O2', '-fsanitize=address',
446 '-fsanitize=undefined', '-o', exe, src],
447 stdout=devnull, stderr=devnull)
448 check_call([exe], stdout=devnull, stderr=devnull)
449 return True
450 except:
451 sys.stderr.write("###### Platform is not sanitize safe ######\n")
452 return False
453 finally:
454 os.remove(src)
455 os.remove(exe)
William A. Kennington III282e3302019-02-04 16:55:05 -0800456
William A. Kennington III49d4e592019-02-06 17:59:27 -0800457
William A. Kennington IIIeaff24a2019-02-06 16:57:42 -0800458def maybe_make_valgrind():
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700459 """
460 Potentially runs the unit tests through valgrind for the package
461 via `make check-valgrind`. If the package does not have valgrind testing
462 then it just skips over this.
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700463 """
William A. Kennington III4e1d0a12018-07-16 12:04:03 -0700464 # Valgrind testing is currently broken by an aggressive strcmp optimization
465 # that is inlined into optimized code for POWER by gcc 7+. Until we find
466 # a workaround, just don't run valgrind tests on POWER.
467 # https://github.com/openbmc/openbmc/issues/3315
William A. Kennington III37a89a22018-12-13 14:32:02 -0800468 if not is_valgrind_safe():
William A. Kennington III75130192019-02-07 00:34:14 -0800469 sys.stderr.write("###### Skipping valgrind ######\n")
William A. Kennington III4e1d0a12018-07-16 12:04:03 -0700470 return
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700471 if not make_target_exists('check-valgrind'):
472 return
473
474 try:
Andrew Jeffery881041f2020-03-13 11:46:30 +1030475 cmd = make_parallel + ['check-valgrind']
William A. Kennington III1fddb972019-02-06 18:03:53 -0800476 check_call_cmd(*cmd)
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700477 except CalledProcessError:
William A. Kennington III90b106a2019-02-06 18:08:24 -0800478 for root, _, files in os.walk(os.getcwd()):
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700479 for f in files:
480 if re.search('test-suite-[a-z]+.log', f) is None:
481 continue
William A. Kennington III1fddb972019-02-06 18:03:53 -0800482 check_call_cmd('cat', os.path.join(root, f))
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700483 raise Exception('Valgrind tests failed')
484
Andrew Jefferye5fffa02020-03-13 11:51:57 +1030485
William A. Kennington IIIeaff24a2019-02-06 16:57:42 -0800486def maybe_make_coverage():
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700487 """
488 Potentially runs the unit tests through code coverage for the package
489 via `make check-code-coverage`. If the package does not have code coverage
490 testing then it just skips over this.
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700491 """
492 if not make_target_exists('check-code-coverage'):
493 return
494
495 # Actually run code coverage
496 try:
Andrew Jeffery881041f2020-03-13 11:46:30 +1030497 cmd = make_parallel + ['check-code-coverage']
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:
500 raise Exception('Code coverage failed')
Matthew Barthccb7f852016-11-23 17:43:02 -0600501
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030502
503class BuildSystem(object):
504 """
505 Build systems generally provide the means to configure, build, install and
506 test software. The BuildSystem class defines a set of interfaces on top of
507 which Autotools, Meson, CMake and possibly other build system drivers can
508 be implemented, separating out the phases to control whether a package
509 should merely be installed or also tested and analyzed.
510 """
511 def __init__(self, package, path):
512 """Initialise the driver with properties independent of the build system
513
514 Keyword arguments:
515 package: The name of the package. Derived from the path if None
516 path: The path to the package. Set to the working directory if None
517 """
518 self.path = "." if not path else path
Andrew Jeffery47fbfa52020-03-13 12:05:09 +1030519 realpath = os.path.realpath(self.path)
520 self.package = package if package else os.path.basename(realpath)
Andrew Jeffery3f8e5a72020-03-13 11:47:56 +1030521 self.build_for_testing = False
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030522
523 def probe(self):
524 """Test if the build system driver can be applied to the package
525
526 Return True if the driver can drive the package's build system,
527 otherwise False.
528
529 Generally probe() is implemented by testing for the presence of the
530 build system's configuration file(s).
531 """
532 raise NotImplemented
533
534 def dependencies(self):
535 """Provide the package's dependencies
536
537 Returns a list of dependencies. If no dependencies are required then an
538 empty list must be returned.
539
540 Generally dependencies() is implemented by analysing and extracting the
541 data from the build system configuration.
542 """
543 raise NotImplemented
544
545 def configure(self, build_for_testing):
546 """Configure the source ready for building
547
548 Should raise an exception if configuration failed.
549
550 Keyword arguments:
551 build_for_testing: Mark the package as being built for testing rather
552 than for installation as a dependency for the
553 package under test. Setting to True generally
554 implies that the package will be configured to build
555 with debug information, at a low level of
556 optimisation and possibly with sanitizers enabled.
557
558 Generally configure() is implemented by invoking the build system
559 tooling to generate Makefiles or equivalent.
560 """
561 raise NotImplemented
562
563 def build(self):
564 """Build the software ready for installation and/or testing
565
566 Should raise an exception if the build fails
567
568 Generally build() is implemented by invoking `make` or `ninja`.
569 """
570 raise NotImplemented
571
572 def install(self):
573 """Install the software ready for use
574
575 Should raise an exception if installation fails
576
577 Like build(), install() is generally implemented by invoking `make` or
578 `ninja`.
579 """
580 raise NotImplemented
581
582 def test(self):
583 """Build and run the test suite associated with the package
584
585 Should raise an exception if the build or testing fails.
586
587 Like install(), test() is generally implemented by invoking `make` or
588 `ninja`.
589 """
590 raise NotImplemented
591
592 def analyze(self):
593 """Run any supported analysis tools over the codebase
594
595 Should raise an exception if analysis fails.
596
597 Some analysis tools such as scan-build need injection into the build
598 system. analyze() provides the necessary hook to implement such
599 behaviour. Analyzers independent of the build system can also be
600 specified here but at the cost of possible duplication of code between
601 the build system driver implementations.
602 """
603 raise NotImplemented
604
605
606class Autotools(BuildSystem):
607 def __init__(self, package=None, path=None):
608 super(Autotools, self).__init__(package, path)
609
610 def probe(self):
611 return os.path.isfile(os.path.join(self.path, 'configure.ac'))
612
613 def dependencies(self):
614 configure_ac = os.path.join(self.path, 'configure.ac')
615
Andrew Jeffery47fbfa52020-03-13 12:05:09 +1030616 contents = ''
Andrew Jeffery7d4a26f2020-03-13 11:42:34 +1030617 # Prepend some special function overrides so we can parse out
618 # dependencies
Andrew Jeffery89b64b62020-03-13 12:15:48 +1030619 for macro in DEPENDENCIES.keys():
Andrew Jeffery47fbfa52020-03-13 12:05:09 +1030620 contents += ('m4_define([' + macro + '], [' + macro + '_START$' +
621 str(DEPENDENCIES_OFFSET[macro] + 1) +
622 macro + '_END])\n')
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030623 with open(configure_ac, "rt") as f:
Andrew Jeffery47fbfa52020-03-13 12:05:09 +1030624 contents += f.read()
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030625
Andrew Jeffery47fbfa52020-03-13 12:05:09 +1030626 autoconf_cmdline = ['autoconf', '-Wno-undefined', '-']
627 autoconf_process = subprocess.Popen(autoconf_cmdline,
Andrew Jeffery7d4a26f2020-03-13 11:42:34 +1030628 stdin=subprocess.PIPE,
629 stdout=subprocess.PIPE,
630 stderr=subprocess.PIPE)
Andrew Jeffery89b64b62020-03-13 12:15:48 +1030631 document = contents.encode('utf-8')
632 (stdout, stderr) = autoconf_process.communicate(input=document)
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030633 if not stdout:
634 print(stderr)
635 raise Exception("Failed to run autoconf for parsing dependencies")
636
637 # Parse out all of the dependency text
638 matches = []
Andrew Jeffery89b64b62020-03-13 12:15:48 +1030639 for macro in DEPENDENCIES.keys():
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030640 pattern = '(' + macro + ')_START(.*?)' + macro + '_END'
Andrew Jeffery89b64b62020-03-13 12:15:48 +1030641 for match in re.compile(pattern).finditer(stdout.decode('utf-8')):
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030642 matches.append((match.group(1), match.group(2)))
643
644 # Look up dependencies from the text
645 found_deps = []
646 for macro, deptext in matches:
647 for potential_dep in deptext.split(' '):
Andrew Jeffery89b64b62020-03-13 12:15:48 +1030648 for known_dep in DEPENDENCIES[macro].keys():
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030649 if potential_dep.startswith(known_dep):
650 found_deps.append(DEPENDENCIES[macro][known_dep])
651
652 return found_deps
653
654 def _configure_feature(self, flag, enabled):
655 """
656 Returns an configure flag as a string
657
658 Parameters:
659 flag The name of the flag
660 enabled Whether the flag is enabled or disabled
661 """
662 return '--' + ('enable' if enabled else 'disable') + '-' + flag
663
664 def configure(self, build_for_testing):
665 self.build_for_testing = build_for_testing
666 conf_flags = [
667 self._configure_feature('silent-rules', False),
668 self._configure_feature('examples', build_for_testing),
669 self._configure_feature('tests', build_for_testing),
Ramin Izadpanah298d56b2020-11-17 16:55:28 +0000670 self._configure_feature('itests', INTEGRATION_TEST),
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030671 ]
672 if not TEST_ONLY:
673 conf_flags.extend([
674 self._configure_feature('code-coverage', build_for_testing),
675 self._configure_feature('valgrind', build_for_testing),
676 ])
677 # Add any necessary configure flags for package
678 if CONFIGURE_FLAGS.get(self.package) is not None:
679 conf_flags.extend(CONFIGURE_FLAGS.get(self.package))
680 for bootstrap in ['bootstrap.sh', 'bootstrap', 'autogen.sh']:
681 if os.path.exists(bootstrap):
682 check_call_cmd('./' + bootstrap)
683 break
684 check_call_cmd('./configure', *conf_flags)
685
686 def build(self):
687 check_call_cmd(*make_parallel)
688
689 def install(self):
Andrew Jeffery881041f2020-03-13 11:46:30 +1030690 check_call_cmd('sudo', '-n', '--', *(make_parallel + ['install']))
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030691
692 def test(self):
693 try:
Andrew Jeffery881041f2020-03-13 11:46:30 +1030694 cmd = make_parallel + ['check']
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030695 for i in range(0, args.repeat):
696 check_call_cmd(*cmd)
697 except CalledProcessError:
698 for root, _, files in os.walk(os.getcwd()):
699 if 'test-suite.log' not in files:
700 continue
701 check_call_cmd('cat', os.path.join(root, 'test-suite.log'))
702 raise Exception('Unit tests failed')
703
704 def analyze(self):
705 maybe_make_valgrind()
706 maybe_make_coverage()
707 run_cppcheck()
708
709
710class CMake(BuildSystem):
711 def __init__(self, package=None, path=None):
712 super(CMake, self).__init__(package, path)
713
714 def probe(self):
715 return os.path.isfile(os.path.join(self.path, 'CMakeLists.txt'))
716
717 def dependencies(self):
718 return []
719
720 def configure(self, build_for_testing):
721 self.build_for_testing = build_for_testing
Ramin Izadpanah298d56b2020-11-17 16:55:28 +0000722 if INTEGRATION_TEST:
723 check_call_cmd('cmake', '-DCMAKE_EXPORT_COMPILE_COMMANDS=ON',
724 '-DITESTS=ON', '.')
725 else:
726 check_call_cmd('cmake', '-DCMAKE_EXPORT_COMPILE_COMMANDS=ON', '.')
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030727
728 def build(self):
729 check_call_cmd('cmake', '--build', '.', '--', '-j',
730 str(multiprocessing.cpu_count()))
731
732 def install(self):
733 pass
734
735 def test(self):
736 if make_target_exists('test'):
737 check_call_cmd('ctest', '.')
738
739 def analyze(self):
Andrew Geissler38356642020-03-19 15:12:44 -0500740 if TEST_ONLY:
741 return
742
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030743 if os.path.isfile('.clang-tidy'):
Ed Tanousbc5c1c42020-10-01 17:49:20 -0700744 try:
745 os.mkdir("tidy-build")
746 except FileExistsError as e:
747 pass
748 # clang-tidy needs to run on a clang-specific build
749 check_call_cmd('cmake', '-DCMAKE_C_COMPILER=clang',
750 '-DCMAKE_CXX_COMPILER=clang++',
751 '-DCMAKE_EXPORT_COMPILE_COMMANDS=ON',
752 '-H.',
753 '-Btidy-build')
754 # we need to cd here because otherwise clang-tidy doesn't find the
755 # .clang-tidy file in the roots of repos. Its arguably a "bug"
756 # with run-clang-tidy at a minimum it's "weird" that it requires
757 # the .clang-tidy to be up a dir
Ed Tanous662890f2020-09-28 17:35:20 -0700758 os.chdir("tidy-build")
759 try:
Ed Tanousbc5c1c42020-10-01 17:49:20 -0700760 check_call_cmd('run-clang-tidy.py', "-header-filter=.*", '-p',
761 '.')
Ed Tanous662890f2020-09-28 17:35:20 -0700762 finally:
763 os.chdir("..")
764
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030765 maybe_make_valgrind()
766 maybe_make_coverage()
767 run_cppcheck()
768
769
770class Meson(BuildSystem):
771 def __init__(self, package=None, path=None):
772 super(Meson, self).__init__(package, path)
773
774 def probe(self):
775 return os.path.isfile(os.path.join(self.path, 'meson.build'))
776
777 def dependencies(self):
778 meson_build = os.path.join(self.path, 'meson.build')
779 if not os.path.exists(meson_build):
780 return []
781
782 found_deps = []
783 for root, dirs, files in os.walk(self.path):
784 if 'meson.build' not in files:
785 continue
786 with open(os.path.join(root, 'meson.build'), 'rt') as f:
787 build_contents = f.read()
Nan Zhouef13d532020-07-07 09:52:02 -0700788 pattern = r"dependency\('([^']*)'.*?\),?\n"
Andrew Jeffery47fbfa52020-03-13 12:05:09 +1030789 for match in re.finditer(pattern, build_contents):
790 group = match.group(1)
791 maybe_dep = DEPENDENCIES['PKG_CHECK_MODULES'].get(group)
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030792 if maybe_dep is not None:
793 found_deps.append(maybe_dep)
794
795 return found_deps
796
797 def _parse_options(self, options_file):
798 """
799 Returns a set of options defined in the provides meson_options.txt file
800
801 Parameters:
802 options_file The file containing options
803 """
William A. Kennington III0ecd0bc2020-06-04 00:44:22 -0700804 oi = optinterpreter.OptionInterpreter('')
805 oi.process(options_file)
806 return oi.options
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030807
William A. Kennington IIIfcd70772020-06-04 00:50:23 -0700808 def _configure_boolean(self, val):
809 """
810 Returns the meson flag which signifies the value
811
812 True is true which requires the boolean.
813 False is false which disables the boolean.
814
815 Parameters:
816 val The value being converted
817 """
818 if val is True:
819 return 'true'
820 elif val is False:
821 return 'false'
822 else:
823 raise Exception("Bad meson boolean value")
824
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030825 def _configure_feature(self, val):
826 """
827 Returns the meson flag which signifies the value
828
829 True is enabled which requires the feature.
830 False is disabled which disables the feature.
831 None is auto which autodetects the feature.
832
833 Parameters:
834 val The value being converted
835 """
836 if val is True:
837 return "enabled"
838 elif val is False:
839 return "disabled"
840 elif val is None:
841 return "auto"
842 else:
843 raise Exception("Bad meson feature value")
844
William A. Kennington IIIfcd70772020-06-04 00:50:23 -0700845 def _configure_option(self, opts, key, val):
846 """
847 Returns the meson flag which signifies the value
848 based on the type of the opt
849
850 Parameters:
851 opt The meson option which we are setting
852 val The value being converted
853 """
854 if isinstance(opts[key], coredata.UserBooleanOption):
855 str_val = self._configure_boolean(val)
856 elif isinstance(opts[key], coredata.UserFeatureOption):
857 str_val = self._configure_feature(val)
858 else:
859 raise Exception('Unknown meson option type')
860 return "-D{}={}".format(key, str_val)
861
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030862 def configure(self, build_for_testing):
863 self.build_for_testing = build_for_testing
William A. Kennington III0ecd0bc2020-06-04 00:44:22 -0700864 meson_options = {}
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030865 if os.path.exists("meson_options.txt"):
866 meson_options = self._parse_options("meson_options.txt")
867 meson_flags = [
868 '-Db_colorout=never',
869 '-Dwerror=true',
870 '-Dwarning_level=3',
871 ]
872 if build_for_testing:
873 meson_flags.append('--buildtype=debug')
874 else:
875 meson_flags.append('--buildtype=debugoptimized')
876 if 'tests' in meson_options:
William A. Kennington IIIfcd70772020-06-04 00:50:23 -0700877 meson_flags.append(self._configure_option(meson_options, 'tests', build_for_testing))
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030878 if 'examples' in meson_options:
William A. Kennington IIIfcd70772020-06-04 00:50:23 -0700879 meson_flags.append(self._configure_option(meson_options, 'examples', build_for_testing))
Ramin Izadpanah298d56b2020-11-17 16:55:28 +0000880 if 'itests' in meson_options:
881 meson_flags.append(self._configure_option(meson_options, 'itests', INTEGRATION_TEST))
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030882 if MESON_FLAGS.get(self.package) is not None:
883 meson_flags.extend(MESON_FLAGS.get(self.package))
884 try:
Andrew Jeffery47fbfa52020-03-13 12:05:09 +1030885 check_call_cmd('meson', 'setup', '--reconfigure', 'build',
886 *meson_flags)
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030887 except:
888 shutil.rmtree('build')
889 check_call_cmd('meson', 'setup', 'build', *meson_flags)
890
891 def build(self):
892 check_call_cmd('ninja', '-C', 'build')
893
894 def install(self):
895 check_call_cmd('sudo', '-n', '--', 'ninja', '-C', 'build', 'install')
896
897 def test(self):
898 try:
Brad Bishop7b8cef22020-08-26 15:58:09 -0400899 test_args = ('--repeat', str(args.repeat), '-C', 'build')
900 check_call_cmd('meson', 'test', *test_args)
901
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030902 except CalledProcessError:
903 for root, _, files in os.walk(os.getcwd()):
904 if 'testlog.txt' not in files:
905 continue
906 check_call_cmd('cat', os.path.join(root, 'testlog.txt'))
907 raise Exception('Unit tests failed')
908
909 def _setup_exists(self, setup):
910 """
911 Returns whether the meson build supports the named test setup.
912
913 Parameter descriptions:
914 setup The setup target to check
915 """
916 try:
917 with open(os.devnull, 'w') as devnull:
918 output = subprocess.check_output(
919 ['meson', 'test', '-C', 'build',
920 '--setup', setup, '-t', '0'],
921 stderr=subprocess.STDOUT)
922 except CalledProcessError as e:
923 output = e.output
Andrew Jeffery89b64b62020-03-13 12:15:48 +1030924 output = output.decode('utf-8')
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030925 return not re.search('Test setup .* not found from project', output)
926
927 def _maybe_valgrind(self):
928 """
929 Potentially runs the unit tests through valgrind for the package
Andrew Jeffery47fbfa52020-03-13 12:05:09 +1030930 via `meson test`. The package can specify custom valgrind
931 configurations by utilizing add_test_setup() in a meson.build
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030932 """
933 if not is_valgrind_safe():
934 sys.stderr.write("###### Skipping valgrind ######\n")
935 return
936 try:
937 if self._setup_exists('valgrind'):
938 check_call_cmd('meson', 'test', '-C', 'build',
939 '--setup', 'valgrind')
940 else:
941 check_call_cmd('meson', 'test', '-C', 'build',
942 '--wrapper', 'valgrind')
943 except CalledProcessError:
944 for root, _, files in os.walk(os.getcwd()):
945 if 'testlog-valgrind.txt' not in files:
946 continue
Andrew Jeffery47fbfa52020-03-13 12:05:09 +1030947 cat_args = os.path.join(root, 'testlog-valgrind.txt')
948 check_call_cmd('cat', cat_args)
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030949 raise Exception('Valgrind tests failed')
950
951 def analyze(self):
Andrew Geissler38356642020-03-19 15:12:44 -0500952 if TEST_ONLY:
953 return
954
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030955 self._maybe_valgrind()
956
957 # Run clang-tidy only if the project has a configuration
958 if os.path.isfile('.clang-tidy'):
Manojkiran Eda1aa91992020-10-02 14:11:53 +0530959 os.environ["CXX"] = "clang++"
960 check_call_cmd('meson', 'setup', 'build-clang')
Ed Tanousbc5c1c42020-10-01 17:49:20 -0700961 check_call_cmd('run-clang-tidy.py', '-p',
Manojkiran Eda1aa91992020-10-02 14:11:53 +0530962 'build-clang')
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030963 # Run the basic clang static analyzer otherwise
964 else:
965 check_call_cmd('ninja', '-C', 'build',
966 'scan-build')
967
968 # Run tests through sanitizers
969 # b_lundef is needed if clang++ is CXX since it resolves the
970 # asan symbols at runtime only. We don't want to set it earlier
971 # in the build process to ensure we don't have undefined
972 # runtime code.
973 if is_sanitize_safe():
974 check_call_cmd('meson', 'configure', 'build',
975 '-Db_sanitize=address,undefined',
976 '-Db_lundef=false')
977 check_call_cmd('meson', 'test', '-C', 'build',
978 '--logbase', 'testlog-ubasan')
979 # TODO: Fix memory sanitizer
Andrew Jeffery6015ca12020-03-13 11:50:10 +1030980 # check_call_cmd('meson', 'configure', 'build',
981 # '-Db_sanitize=memory')
982 # check_call_cmd('meson', 'test', '-C', 'build'
983 # '--logbase', 'testlog-msan')
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030984 check_call_cmd('meson', 'configure', 'build',
Lei YUf684ad62020-09-21 17:02:26 +0800985 '-Db_sanitize=none')
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030986 else:
987 sys.stderr.write("###### Skipping sanitizers ######\n")
988
989 # Run coverage checks
990 check_call_cmd('meson', 'configure', 'build',
991 '-Db_coverage=true')
992 self.test()
993 # Only build coverage HTML if coverage files were produced
994 for root, dirs, files in os.walk('build'):
995 if any([f.endswith('.gcda') for f in files]):
996 check_call_cmd('ninja', '-C', 'build',
997 'coverage-html')
998 break
999 check_call_cmd('meson', 'configure', 'build',
1000 '-Db_coverage=false')
1001 run_cppcheck()
1002
1003
1004class Package(object):
1005 def __init__(self, name=None, path=None):
Manojkiran Eda29d28252020-06-15 18:58:09 +05301006 self.supported = [Meson, Autotools, CMake]
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301007 self.name = name
1008 self.path = path
1009 self.test_only = False
1010
1011 def build_systems(self):
Andrew Jeffery881041f2020-03-13 11:46:30 +10301012 instances = (system(self.name, self.path) for system in self.supported)
1013 return (instance for instance in instances if instance.probe())
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301014
1015 def build_system(self, preferred=None):
Andrew Geissler8cb74fc2020-03-19 14:48:05 -05001016 systems = list(self.build_systems())
1017
1018 if not systems:
1019 return None
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301020
1021 if preferred:
Andrew Jeffery881041f2020-03-13 11:46:30 +10301022 return {type(system): system for system in systems}[preferred]
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301023
1024 return next(iter(systems))
1025
1026 def install(self, system=None):
1027 if not system:
1028 system = self.build_system()
1029
1030 system.configure(False)
1031 system.build()
1032 system.install()
1033
Andrew Jeffery19d75672020-03-13 10:42:08 +10301034 def _test_one(self, system):
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301035 system.configure(True)
1036 system.build()
1037 system.install()
1038 system.test()
1039 system.analyze()
1040
Andrew Jeffery19d75672020-03-13 10:42:08 +10301041 def test(self):
1042 for system in self.build_systems():
1043 self._test_one(system)
1044
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301045
Matt Spinler9bfaaad2019-10-25 09:51:50 -05001046def find_file(filename, basedir):
1047 """
Patrick Williams55448ad2020-12-14 14:28:28 -06001048 Finds all occurrences of a file (or list of files) in the base
1049 directory and passes them back with their relative paths.
Matt Spinler9bfaaad2019-10-25 09:51:50 -05001050
1051 Parameter descriptions:
Patrick Williams55448ad2020-12-14 14:28:28 -06001052 filename The name of the file (or list of files) to
1053 find
Matt Spinler9bfaaad2019-10-25 09:51:50 -05001054 basedir The base directory search in
1055 """
1056
Patrick Williams55448ad2020-12-14 14:28:28 -06001057 if not isinstance(filename, list):
1058 filename = [ filename ]
1059
Matt Spinler9bfaaad2019-10-25 09:51:50 -05001060 filepaths = []
1061 for root, dirs, files in os.walk(basedir):
Patrick Williams55448ad2020-12-14 14:28:28 -06001062 for f in filename:
1063 if f in files:
1064 filepaths.append(os.path.join(root, f))
Matt Spinler9bfaaad2019-10-25 09:51:50 -05001065 return filepaths
1066
Andrew Jeffery9cc97d82020-03-13 11:57:44 +10301067
Matthew Barthccb7f852016-11-23 17:43:02 -06001068if __name__ == '__main__':
1069 # CONFIGURE_FLAGS = [GIT REPO]:[CONFIGURE FLAGS]
1070 CONFIGURE_FLAGS = {
Matthew Barth1d1c6732017-03-24 10:00:28 -05001071 'phosphor-logging':
Matt Spinler0744bb82020-01-16 08:23:35 -06001072 ['--enable-metadata-processing', '--enable-openpower-pel-extension',
Deepak Kodihalli3a4e1b42017-06-08 09:52:35 -05001073 'YAML_DIR=/usr/local/share/phosphor-dbus-yaml/yaml']
Matthew Barthccb7f852016-11-23 17:43:02 -06001074 }
1075
William A. Kennington III3f1d1202018-12-06 18:02:07 -08001076 # MESON_FLAGS = [GIT REPO]:[MESON FLAGS]
1077 MESON_FLAGS = {
Matt Spinler8448cfd2020-09-10 17:01:48 -05001078 'phosphor-dbus-interfaces':
1079 ['-Ddata_com_ibm=true', '-Ddata_org_open_power=true']
William A. Kennington III3f1d1202018-12-06 18:02:07 -08001080 }
1081
Matthew Barthccb7f852016-11-23 17:43:02 -06001082 # DEPENDENCIES = [MACRO]:[library/header]:[GIT REPO]
1083 DEPENDENCIES = {
1084 'AC_CHECK_LIB': {'mapper': 'phosphor-objmgr'},
Matthew Barth710f3f02017-01-18 15:20:19 -06001085 'AC_CHECK_HEADER': {
1086 'host-ipmid': 'phosphor-host-ipmid',
Patrick Ventureb41a4462018-10-03 17:27:38 -07001087 'blobs-ipmid': 'phosphor-ipmi-blobs',
Matthew Barth710f3f02017-01-18 15:20:19 -06001088 'sdbusplus': 'sdbusplus',
William A. Kennington IIIb4f730a2018-09-12 11:21:20 -07001089 'sdeventplus': 'sdeventplus',
William A. Kennington III23705242019-01-15 18:17:25 -08001090 'stdplus': 'stdplus',
Patrick Venture22329962018-09-14 10:23:04 -07001091 'gpioplus': 'gpioplus',
Saqib Khan66145052017-02-14 12:02:07 -06001092 'phosphor-logging/log.hpp': 'phosphor-logging',
Patrick Williamseab8a372017-01-30 11:21:32 -06001093 },
Brad Bishopebb49112017-02-13 11:07:26 -05001094 'AC_PATH_PROG': {'sdbus++': 'sdbusplus'},
Patrick Williamseab8a372017-01-30 11:21:32 -06001095 'PKG_CHECK_MODULES': {
Matthew Barth19e261e2017-02-01 12:55:22 -06001096 'phosphor-dbus-interfaces': 'phosphor-dbus-interfaces',
William A. Kennington III993ab332019-02-07 02:12:31 -08001097 'libipmid': 'phosphor-host-ipmid',
1098 'libipmid-host': 'phosphor-host-ipmid',
Brad Bishopebb49112017-02-13 11:07:26 -05001099 'sdbusplus': 'sdbusplus',
William A. Kennington IIIb4f730a2018-09-12 11:21:20 -07001100 'sdeventplus': 'sdeventplus',
William A. Kennington III23705242019-01-15 18:17:25 -08001101 'stdplus': 'stdplus',
Patrick Venture22329962018-09-14 10:23:04 -07001102 'gpioplus': 'gpioplus',
Brad Bishopebb49112017-02-13 11:07:26 -05001103 'phosphor-logging': 'phosphor-logging',
Marri Devender Raoa3eee8a2018-08-13 05:34:27 -05001104 'phosphor-snmp': 'phosphor-snmp',
Patrick Venturee584c3b2019-03-07 09:44:16 -08001105 'ipmiblob': 'ipmi-blob-tool',
Ben Tyner300ed3c2020-05-14 15:30:37 -05001106 'hei': 'openpower-libhei',
Sui Chen40cf5212020-11-24 12:12:23 -08001107 'phosphor-ipmi-blobs': 'phosphor-ipmi-blobs',
Brad Bishopebb49112017-02-13 11:07:26 -05001108 },
Matthew Barthccb7f852016-11-23 17:43:02 -06001109 }
1110
William A. Kennington IIIe67f5fc2018-12-06 17:40:30 -08001111 # Offset into array of macro parameters MACRO(0, 1, ...N)
1112 DEPENDENCIES_OFFSET = {
1113 'AC_CHECK_LIB': 0,
1114 'AC_CHECK_HEADER': 0,
1115 'AC_PATH_PROG': 1,
1116 'PKG_CHECK_MODULES': 1,
1117 }
1118
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -05001119 # DEPENDENCIES_REGEX = [GIT REPO]:[REGEX STRING]
1120 DEPENDENCIES_REGEX = {
Patrick Ventured37b8052018-10-16 16:03:03 -07001121 'phosphor-logging': r'\S+-dbus-interfaces$'
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -05001122 }
1123
Matthew Barth33df8792016-12-19 14:30:17 -06001124 # Set command line arguments
1125 parser = argparse.ArgumentParser()
1126 parser.add_argument("-w", "--workspace", dest="WORKSPACE", required=True,
1127 help="Workspace directory location(i.e. /home)")
1128 parser.add_argument("-p", "--package", dest="PACKAGE", required=True,
1129 help="OpenBMC package to be unit tested")
William A. Kennington III65b37fa2019-01-31 15:15:17 -08001130 parser.add_argument("-t", "--test-only", dest="TEST_ONLY",
1131 action="store_true", required=False, default=False,
1132 help="Only run test cases, no other validation")
Ramin Izadpanah298d56b2020-11-17 16:55:28 +00001133 arg_inttests = parser.add_mutually_exclusive_group()
1134 arg_inttests.add_argument("--integration-tests", dest="INTEGRATION_TEST",
1135 action="store_true", required=False, default=True,
1136 help="Enable integration tests [default].")
1137 arg_inttests.add_argument("--no-integration-tests", dest="INTEGRATION_TEST",
1138 action="store_false", required=False,
1139 help="Disable integration tests.")
Matthew Barth33df8792016-12-19 14:30:17 -06001140 parser.add_argument("-v", "--verbose", action="store_true",
1141 help="Print additional package status messages")
Andrew Jeffery468309d2018-03-08 13:46:33 +10301142 parser.add_argument("-r", "--repeat", help="Repeat tests N times",
1143 type=int, default=1)
Andrew Geisslera61acb52019-01-03 16:32:44 -06001144 parser.add_argument("-b", "--branch", dest="BRANCH", required=False,
1145 help="Branch to target for dependent repositories",
1146 default="master")
Lei YU7ef93302019-11-06 13:53:21 +08001147 parser.add_argument("-n", "--noformat", dest="FORMAT",
1148 action="store_false", required=False,
1149 help="Whether or not to run format code")
Matthew Barth33df8792016-12-19 14:30:17 -06001150 args = parser.parse_args(sys.argv[1:])
1151 WORKSPACE = args.WORKSPACE
1152 UNIT_TEST_PKG = args.PACKAGE
William A. Kennington III65b37fa2019-01-31 15:15:17 -08001153 TEST_ONLY = args.TEST_ONLY
Ramin Izadpanah298d56b2020-11-17 16:55:28 +00001154 INTEGRATION_TEST = args.INTEGRATION_TEST
Andrew Geisslera61acb52019-01-03 16:32:44 -06001155 BRANCH = args.BRANCH
Lei YU7ef93302019-11-06 13:53:21 +08001156 FORMAT_CODE = args.FORMAT
Matthew Barth33df8792016-12-19 14:30:17 -06001157 if args.verbose:
1158 def printline(*line):
1159 for arg in line:
Andrew Jeffery89b64b62020-03-13 12:15:48 +10301160 print(arg, end=' ')
1161 print()
Matthew Barth33df8792016-12-19 14:30:17 -06001162 else:
Andrew Jefferyfce557f2020-03-13 12:12:19 +10301163 def printline(*line):
1164 pass
Matthew Barthccb7f852016-11-23 17:43:02 -06001165
Patrick Williamsb6535952020-12-15 06:40:10 -06001166 CODE_SCAN_DIR = os.path.join(WORKSPACE, UNIT_TEST_PKG)
Lei YU7ef93302019-11-06 13:53:21 +08001167
James Feist878df5c2018-07-26 14:54:28 -07001168 # First validate code formatting if repo has style formatting files.
Adriana Kobylakbcee22b2018-01-10 16:58:27 -06001169 # The format-code.sh checks for these files.
Lei YU7ef93302019-11-06 13:53:21 +08001170 if FORMAT_CODE:
Patrick Williams5b4a1e82020-12-15 06:46:31 -06001171 format_scripts = find_file(['format-code.sh', 'format-code'],
1172 CODE_SCAN_DIR)
Andrew Geissler31502dd2021-01-07 14:04:02 -06001173
1174 # use default format-code.sh if no other found
1175 if not format_scripts:
1176 format_scripts.append(os.path.join(WORKSPACE, "format-code.sh"))
1177
Patrick Williams5b4a1e82020-12-15 06:46:31 -06001178 for f in format_scripts:
1179 check_call_cmd(f, CODE_SCAN_DIR)
Andrew Geisslera28286d2018-01-10 11:00:00 -08001180
Andrew Geissler8cb74fc2020-03-19 14:48:05 -05001181 # Check if this repo has a supported make infrastructure
Patrick Williamsb6535952020-12-15 06:40:10 -06001182 pkg = Package(UNIT_TEST_PKG, CODE_SCAN_DIR)
Andrew Geissler8cb74fc2020-03-19 14:48:05 -05001183 if not pkg.build_system():
1184 print("No valid build system, exit")
1185 sys.exit(0)
1186
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301187 prev_umask = os.umask(000)
James Feist878df5c2018-07-26 14:54:28 -07001188
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301189 # Determine dependencies and add them
1190 dep_added = dict()
1191 dep_added[UNIT_TEST_PKG] = False
William A. Kennington III40d5c7c2018-12-13 14:37:59 -08001192
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301193 # Create dependency tree
1194 dep_tree = DepTree(UNIT_TEST_PKG)
Patrick Williamsb6535952020-12-15 06:40:10 -06001195 build_dep_tree(UNIT_TEST_PKG, CODE_SCAN_DIR, dep_added, dep_tree, BRANCH)
William A. Kennington III65b37fa2019-01-31 15:15:17 -08001196
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301197 # Reorder Dependency Tree
Andrew Jeffery89b64b62020-03-13 12:15:48 +10301198 for pkg_name, regex_str in DEPENDENCIES_REGEX.items():
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301199 dep_tree.ReorderDeps(pkg_name, regex_str)
1200 if args.verbose:
1201 dep_tree.PrintTree()
William A. Kennington III65b37fa2019-01-31 15:15:17 -08001202
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301203 install_list = dep_tree.GetInstallList()
Andrew Geissler9ced4ed2019-11-18 14:33:58 -06001204
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301205 # We don't want to treat our package as a dependency
1206 install_list.remove(UNIT_TEST_PKG)
James Feist878df5c2018-07-26 14:54:28 -07001207
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301208 # Install reordered dependencies
1209 for dep in install_list:
1210 build_and_install(dep, False)
James Feist878df5c2018-07-26 14:54:28 -07001211
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301212 # Run package unit tests
1213 build_and_install(UNIT_TEST_PKG, True)
James Feist878df5c2018-07-26 14:54:28 -07001214
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301215 os.umask(prev_umask)
Matt Spinler9bfaaad2019-10-25 09:51:50 -05001216
1217 # Run any custom CI scripts the repo has, of which there can be
1218 # multiple of and anywhere in the repository.
Patrick Williamsb6535952020-12-15 06:40:10 -06001219 ci_scripts = find_file(['run-ci.sh', 'run-ci'], CODE_SCAN_DIR)
Matt Spinler9bfaaad2019-10-25 09:51:50 -05001220 if ci_scripts:
Patrick Williamsb6535952020-12-15 06:40:10 -06001221 os.chdir(CODE_SCAN_DIR)
Matt Spinler9bfaaad2019-10-25 09:51:50 -05001222 for ci_script in ci_scripts:
Patrick Williams55448ad2020-12-14 14:28:28 -06001223 check_call_cmd(ci_script)