blob: bbc24ff57274988a5f33cb2bcbe25f65146e74bb [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
Andrew Jeffery89b64b62020-03-13 12:15:48 +103011from urllib.parse import urljoin
Andrew Jefferya4e31c62018-03-08 13:45:28 +103012from subprocess import check_call, call, CalledProcessError
Matthew Barthccb7f852016-11-23 17:43:02 -060013import os
14import sys
Matthew Barth33df8792016-12-19 14:30:17 -060015import argparse
William A. Kennington IIIa2156732018-06-30 18:38:09 -070016import multiprocessing
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -050017import re
William A. Kennington IIIe67f5fc2018-12-06 17:40:30 -080018import subprocess
William A. Kennington III3f1d1202018-12-06 18:02:07 -080019import shutil
William A. Kennington III4e1d0a12018-07-16 12:04:03 -070020import platform
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -050021
22
23class DepTree():
24 """
25 Represents package dependency tree, where each node is a DepTree with a
26 name and DepTree children.
27 """
28
29 def __init__(self, name):
30 """
31 Create new DepTree.
32
33 Parameter descriptions:
34 name Name of new tree node.
35 """
36 self.name = name
37 self.children = list()
38
39 def AddChild(self, name):
40 """
41 Add new child node to current node.
42
43 Parameter descriptions:
44 name Name of new child
45 """
46 new_child = DepTree(name)
47 self.children.append(new_child)
48 return new_child
49
50 def AddChildNode(self, node):
51 """
52 Add existing child node to current node.
53
54 Parameter descriptions:
55 node Tree node to add
56 """
57 self.children.append(node)
58
59 def RemoveChild(self, name):
60 """
61 Remove child node.
62
63 Parameter descriptions:
64 name Name of child to remove
65 """
66 for child in self.children:
67 if child.name == name:
68 self.children.remove(child)
69 return
70
71 def GetNode(self, name):
72 """
73 Return node with matching name. Return None if not found.
74
75 Parameter descriptions:
76 name Name of node to return
77 """
78 if self.name == name:
79 return self
80 for child in self.children:
81 node = child.GetNode(name)
82 if node:
83 return node
84 return None
85
86 def GetParentNode(self, name, parent_node=None):
87 """
88 Return parent of node with matching name. Return none if not found.
89
90 Parameter descriptions:
91 name Name of node to get parent of
92 parent_node Parent of current node
93 """
94 if self.name == name:
95 return parent_node
96 for child in self.children:
97 found_node = child.GetParentNode(name, self)
98 if found_node:
99 return found_node
100 return None
101
102 def GetPath(self, name, path=None):
103 """
104 Return list of node names from head to matching name.
105 Return None if not found.
106
107 Parameter descriptions:
108 name Name of node
109 path List of node names from head to current node
110 """
111 if not path:
112 path = []
113 if self.name == name:
114 path.append(self.name)
115 return path
116 for child in self.children:
117 match = child.GetPath(name, path + [self.name])
118 if match:
119 return match
120 return None
121
122 def GetPathRegex(self, name, regex_str, path=None):
123 """
124 Return list of node paths that end in name, or match regex_str.
125 Return empty list if not found.
126
127 Parameter descriptions:
128 name Name of node to search for
129 regex_str Regex string to match node names
130 path Path of node names from head to current node
131 """
132 new_paths = []
133 if not path:
134 path = []
135 match = re.match(regex_str, self.name)
136 if (self.name == name) or (match):
137 new_paths.append(path + [self.name])
138 for child in self.children:
139 return_paths = None
140 full_path = path + [self.name]
141 return_paths = child.GetPathRegex(name, regex_str, full_path)
142 for i in return_paths:
143 new_paths.append(i)
144 return new_paths
145
146 def MoveNode(self, from_name, to_name):
147 """
148 Mode existing from_name node to become child of to_name node.
149
150 Parameter descriptions:
151 from_name Name of node to make a child of to_name
152 to_name Name of node to make parent of from_name
153 """
154 parent_from_node = self.GetParentNode(from_name)
155 from_node = self.GetNode(from_name)
156 parent_from_node.RemoveChild(from_name)
157 to_node = self.GetNode(to_name)
158 to_node.AddChildNode(from_node)
159
160 def ReorderDeps(self, name, regex_str):
161 """
162 Reorder dependency tree. If tree contains nodes with names that
163 match 'name' and 'regex_str', move 'regex_str' nodes that are
164 to the right of 'name' node, so that they become children of the
165 'name' node.
166
167 Parameter descriptions:
168 name Name of node to look for
169 regex_str Regex string to match names to
170 """
171 name_path = self.GetPath(name)
172 if not name_path:
173 return
174 paths = self.GetPathRegex(name, regex_str)
175 is_name_in_paths = False
176 name_index = 0
177 for i in range(len(paths)):
178 path = paths[i]
179 if path[-1] == name:
180 is_name_in_paths = True
181 name_index = i
182 break
183 if not is_name_in_paths:
184 return
185 for i in range(name_index + 1, len(paths)):
186 path = paths[i]
187 if name in path:
188 continue
189 from_name = path[-1]
190 self.MoveNode(from_name, name)
191
192 def GetInstallList(self):
193 """
194 Return post-order list of node names.
195
196 Parameter descriptions:
197 """
198 install_list = []
199 for child in self.children:
200 child_install_list = child.GetInstallList()
201 install_list.extend(child_install_list)
202 install_list.append(self.name)
203 return install_list
204
205 def PrintTree(self, level=0):
206 """
207 Print pre-order node names with indentation denoting node depth level.
208
209 Parameter descriptions:
210 level Current depth level
211 """
212 INDENT_PER_LEVEL = 4
Andrew Jeffery89b64b62020-03-13 12:15:48 +1030213 print(' ' * (level * INDENT_PER_LEVEL) + self.name)
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500214 for child in self.children:
215 child.PrintTree(level + 1)
Matthew Barth33df8792016-12-19 14:30:17 -0600216
217
William A. Kennington III1fddb972019-02-06 18:03:53 -0800218def check_call_cmd(*cmd):
Matthew Barth33df8792016-12-19 14:30:17 -0600219 """
220 Verbose prints the directory location the given command is called from and
221 the command, then executes the command using check_call.
222
223 Parameter descriptions:
224 dir Directory location command is to be called from
225 cmd List of parameters constructing the complete command
226 """
William A. Kennington III1fddb972019-02-06 18:03:53 -0800227 printline(os.getcwd(), ">", " ".join(cmd))
Matthew Barth33df8792016-12-19 14:30:17 -0600228 check_call(cmd)
Matthew Barthccb7f852016-11-23 17:43:02 -0600229
230
Andrew Geisslera61acb52019-01-03 16:32:44 -0600231def clone_pkg(pkg, branch):
Matthew Barth33df8792016-12-19 14:30:17 -0600232 """
233 Clone the given openbmc package's git repository from gerrit into
234 the WORKSPACE location
235
236 Parameter descriptions:
237 pkg Name of the package to clone
Andrew Geisslera61acb52019-01-03 16:32:44 -0600238 branch Branch to clone from pkg
Matthew Barth33df8792016-12-19 14:30:17 -0600239 """
Andrew Jeffery7be94ca2018-03-08 13:15:33 +1030240 pkg_dir = os.path.join(WORKSPACE, pkg)
241 if os.path.exists(os.path.join(pkg_dir, '.git')):
242 return pkg_dir
Matthew Barthccb7f852016-11-23 17:43:02 -0600243 pkg_repo = urljoin('https://gerrit.openbmc-project.xyz/openbmc/', pkg)
Andrew Jeffery7be94ca2018-03-08 13:15:33 +1030244 os.mkdir(pkg_dir)
Andrew Geisslera61acb52019-01-03 16:32:44 -0600245 printline(pkg_dir, "> git clone", pkg_repo, branch, "./")
246 try:
247 # first try the branch
Andrew Jeffery7d4a26f2020-03-13 11:42:34 +1030248 clone = Repo.clone_from(pkg_repo, pkg_dir, branch=branch)
249 repo_inst = clone.working_dir
Andrew Geisslera61acb52019-01-03 16:32:44 -0600250 except:
251 printline("Input branch not found, default to master")
Andrew Jeffery7d4a26f2020-03-13 11:42:34 +1030252 clone = Repo.clone_from(pkg_repo, pkg_dir, branch="master")
253 repo_inst = clone.working_dir
Andrew Geisslera61acb52019-01-03 16:32:44 -0600254 return repo_inst
Matthew Barth33df8792016-12-19 14:30:17 -0600255
256
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030257def make_target_exists(target):
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800258 """
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030259 Runs a check against the makefile in the current directory to determine
260 if the target exists so that it can be built.
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800261
262 Parameter descriptions:
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030263 target The make target we are checking
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800264 """
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030265 try:
Andrew Jeffery881041f2020-03-13 11:46:30 +1030266 cmd = ['make', '-n', target]
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030267 with open(os.devnull, 'w') as devnull:
268 check_call(cmd, stdout=devnull, stderr=devnull)
269 return True
270 except CalledProcessError:
271 return False
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800272
William A. Kennington III3f1d1202018-12-06 18:02:07 -0800273
William A. Kennington IIIa2156732018-06-30 18:38:09 -0700274make_parallel = [
275 'make',
276 # Run enough jobs to saturate all the cpus
277 '-j', str(multiprocessing.cpu_count()),
278 # Don't start more jobs if the load avg is too high
279 '-l', str(multiprocessing.cpu_count()),
280 # Synchronize the output so logs aren't intermixed in stdout / stderr
281 '-O',
282]
283
William A. Kennington III3f1d1202018-12-06 18:02:07 -0800284
Andrew Jefferyff5c5d52020-03-13 10:12:14 +1030285def build_and_install(name, build_for_testing=False):
William A. Kennington III780ec092018-12-06 14:46:50 -0800286 """
287 Builds and installs the package in the environment. Optionally
288 builds the examples and test cases for package.
289
290 Parameter description:
Andrew Jefferyff5c5d52020-03-13 10:12:14 +1030291 name The name of the package we are building
William A. Kennington IIIa0454912018-12-06 14:47:16 -0800292 build_for_testing Enable options related to testing on the package?
William A. Kennington III780ec092018-12-06 14:46:50 -0800293 """
Andrew Jefferyff5c5d52020-03-13 10:12:14 +1030294 os.chdir(os.path.join(WORKSPACE, name))
William A. Kennington III54d4faf2018-12-06 17:46:24 -0800295
296 # Refresh dynamic linker run time bindings for dependencies
William A. Kennington III1fddb972019-02-06 18:03:53 -0800297 check_call_cmd('sudo', '-n', '--', 'ldconfig')
William A. Kennington III54d4faf2018-12-06 17:46:24 -0800298
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030299 pkg = Package()
300 if build_for_testing:
301 pkg.test()
William A. Kennington III3f1d1202018-12-06 18:02:07 -0800302 else:
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030303 pkg.install()
304
William A. Kennington III780ec092018-12-06 14:46:50 -0800305
Andrew Jefferyccf85d62020-03-13 10:25:42 +1030306def build_dep_tree(name, pkgdir, dep_added, head, branch, dep_tree=None):
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500307 """
Andrew Jefferyccf85d62020-03-13 10:25:42 +1030308 For each package (name), starting with the package to be unit tested,
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030309 extract its dependencies. For each package dependency defined, recursively
310 apply the same strategy
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500311
312 Parameter descriptions:
Andrew Jefferyccf85d62020-03-13 10:25:42 +1030313 name Name of the package
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500314 pkgdir Directory where package source is located
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800315 dep_added Current dict of dependencies and added status
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500316 head Head node of the dependency tree
Andrew Geisslera61acb52019-01-03 16:32:44 -0600317 branch Branch to clone from pkg
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500318 dep_tree Current dependency tree node
319 """
320 if not dep_tree:
321 dep_tree = head
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800322
William A. Kennington IIIbe6aab22018-12-06 15:01:54 -0800323 with open("/tmp/depcache", "r") as depcache:
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800324 cache = depcache.readline()
325
326 # Read out pkg dependencies
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030327 pkg = Package(name, pkgdir)
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800328
Andrew Jeffery89b64b62020-03-13 12:15:48 +1030329 for dep in set(pkg.build_system().dependencies()):
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800330 if dep in cache:
331 continue
332 # Dependency package not already known
333 if dep_added.get(dep) is None:
334 # Dependency package not added
335 new_child = dep_tree.AddChild(dep)
336 dep_added[dep] = False
Andrew Jeffery3b92fdd2020-03-13 11:49:18 +1030337 dep_pkgdir = clone_pkg(dep, branch)
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800338 # Determine this dependency package's
339 # dependencies and add them before
340 # returning to add this package
341 dep_added = build_dep_tree(dep,
342 dep_pkgdir,
343 dep_added,
344 head,
Andrew Geisslera61acb52019-01-03 16:32:44 -0600345 branch,
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800346 new_child)
347 else:
348 # Dependency package known and added
349 if dep_added[dep]:
Andrew Jeffery2cb0c7a2018-03-08 13:19:08 +1030350 continue
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500351 else:
William A. Kennington IIIc048cc02018-12-06 15:39:18 -0800352 # Cyclic dependency failure
Andrew Jefferyccf85d62020-03-13 10:25:42 +1030353 raise Exception("Cyclic dependencies found in "+name)
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500354
Andrew Jefferyccf85d62020-03-13 10:25:42 +1030355 if not dep_added[name]:
356 dep_added[name] = True
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -0500357
358 return dep_added
Matthew Barthccb7f852016-11-23 17:43:02 -0600359
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700360
William A. Kennington III90b106a2019-02-06 18:08:24 -0800361def run_cppcheck():
Andrew Jefferybf735102020-03-13 12:13:21 +1030362 match_re = re.compile(r'((?!\.mako\.).)*\.[ch](?:pp)?$', re.I)
Brad Bishop48424d42020-01-07 13:01:31 -0500363 cppcheck_files = []
364 stdout = subprocess.check_output(['git', 'ls-files'])
Patrick Venturead4354e2018-10-12 16:59:54 -0700365
Brad Bishop48424d42020-01-07 13:01:31 -0500366 for f in stdout.decode('utf-8').split():
367 if match_re.match(f):
368 cppcheck_files.append(f)
369
370 if not cppcheck_files:
371 # skip cppcheck if there arent' any c or cpp sources.
372 print("no files")
373 return None
374
375 # http://cppcheck.sourceforge.net/manual.pdf
376 params = ['cppcheck', '-j', str(multiprocessing.cpu_count()),
377 '--enable=all', '--file-list=-']
378
379 cppcheck_process = subprocess.Popen(
380 params,
381 stdout=subprocess.PIPE,
382 stderr=subprocess.PIPE,
383 stdin=subprocess.PIPE)
384 (stdout, stderr) = cppcheck_process.communicate(
Andrew Jeffery89b64b62020-03-13 12:15:48 +1030385 input='\n'.join(cppcheck_files).encode('utf-8'))
Brad Bishop48424d42020-01-07 13:01:31 -0500386
387 if cppcheck_process.wait():
Patrick Venturead4354e2018-10-12 16:59:54 -0700388 raise Exception('Cppcheck failed')
Andrew Jeffery89b64b62020-03-13 12:15:48 +1030389 print(stdout.decode('utf-8'))
390 print(stderr.decode('utf-8'))
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700391
Andrew Jefferye5fffa02020-03-13 11:51:57 +1030392
William A. Kennington III37a89a22018-12-13 14:32:02 -0800393def is_valgrind_safe():
394 """
395 Returns whether it is safe to run valgrind on our platform
396 """
William A. Kennington III0326ded2019-02-07 00:33:28 -0800397 src = 'unit-test-vg.c'
398 exe = './unit-test-vg'
399 with open(src, 'w') as h:
William A. Kennington IIIafb0f982019-04-26 17:30:28 -0700400 h.write('#include <errno.h>\n')
401 h.write('#include <stdio.h>\n')
William A. Kennington III0326ded2019-02-07 00:33:28 -0800402 h.write('#include <stdlib.h>\n')
403 h.write('#include <string.h>\n')
404 h.write('int main() {\n')
405 h.write('char *heap_str = malloc(16);\n')
406 h.write('strcpy(heap_str, "RandString");\n')
407 h.write('int res = strcmp("RandString", heap_str);\n')
408 h.write('free(heap_str);\n')
William A. Kennington IIIafb0f982019-04-26 17:30:28 -0700409 h.write('char errstr[64];\n')
410 h.write('strerror_r(EINVAL, errstr, sizeof(errstr));\n')
411 h.write('printf("%s\\n", errstr);\n')
William A. Kennington III0326ded2019-02-07 00:33:28 -0800412 h.write('return res;\n')
413 h.write('}\n')
414 try:
415 with open(os.devnull, 'w') as devnull:
416 check_call(['gcc', '-O2', '-o', exe, src],
417 stdout=devnull, stderr=devnull)
418 check_call(['valgrind', '--error-exitcode=99', exe],
419 stdout=devnull, stderr=devnull)
420 return True
421 except:
422 sys.stderr.write("###### Platform is not valgrind safe ######\n")
423 return False
424 finally:
425 os.remove(src)
426 os.remove(exe)
William A. Kennington III37a89a22018-12-13 14:32:02 -0800427
Andrew Jefferye5fffa02020-03-13 11:51:57 +1030428
William A. Kennington III282e3302019-02-04 16:55:05 -0800429def is_sanitize_safe():
430 """
431 Returns whether it is safe to run sanitizers on our platform
432 """
William A. Kennington III0b7fb2b2019-02-07 00:33:42 -0800433 src = 'unit-test-sanitize.c'
434 exe = './unit-test-sanitize'
435 with open(src, 'w') as h:
436 h.write('int main() { return 0; }\n')
437 try:
438 with open(os.devnull, 'w') as devnull:
439 check_call(['gcc', '-O2', '-fsanitize=address',
440 '-fsanitize=undefined', '-o', exe, src],
441 stdout=devnull, stderr=devnull)
442 check_call([exe], stdout=devnull, stderr=devnull)
443 return True
444 except:
445 sys.stderr.write("###### Platform is not sanitize safe ######\n")
446 return False
447 finally:
448 os.remove(src)
449 os.remove(exe)
William A. Kennington III282e3302019-02-04 16:55:05 -0800450
William A. Kennington III49d4e592019-02-06 17:59:27 -0800451
William A. Kennington IIIeaff24a2019-02-06 16:57:42 -0800452def maybe_make_valgrind():
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700453 """
454 Potentially runs the unit tests through valgrind for the package
455 via `make check-valgrind`. If the package does not have valgrind testing
456 then it just skips over this.
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700457 """
William A. Kennington III4e1d0a12018-07-16 12:04:03 -0700458 # Valgrind testing is currently broken by an aggressive strcmp optimization
459 # that is inlined into optimized code for POWER by gcc 7+. Until we find
460 # a workaround, just don't run valgrind tests on POWER.
461 # https://github.com/openbmc/openbmc/issues/3315
William A. Kennington III37a89a22018-12-13 14:32:02 -0800462 if not is_valgrind_safe():
William A. Kennington III75130192019-02-07 00:34:14 -0800463 sys.stderr.write("###### Skipping valgrind ######\n")
William A. Kennington III4e1d0a12018-07-16 12:04:03 -0700464 return
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700465 if not make_target_exists('check-valgrind'):
466 return
467
468 try:
Andrew Jeffery881041f2020-03-13 11:46:30 +1030469 cmd = make_parallel + ['check-valgrind']
William A. Kennington III1fddb972019-02-06 18:03:53 -0800470 check_call_cmd(*cmd)
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700471 except CalledProcessError:
William A. Kennington III90b106a2019-02-06 18:08:24 -0800472 for root, _, files in os.walk(os.getcwd()):
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700473 for f in files:
474 if re.search('test-suite-[a-z]+.log', f) is None:
475 continue
William A. Kennington III1fddb972019-02-06 18:03:53 -0800476 check_call_cmd('cat', os.path.join(root, f))
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700477 raise Exception('Valgrind tests failed')
478
Andrew Jefferye5fffa02020-03-13 11:51:57 +1030479
William A. Kennington IIIeaff24a2019-02-06 16:57:42 -0800480def maybe_make_coverage():
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700481 """
482 Potentially runs the unit tests through code coverage for the package
483 via `make check-code-coverage`. If the package does not have code coverage
484 testing then it just skips over this.
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700485 """
486 if not make_target_exists('check-code-coverage'):
487 return
488
489 # Actually run code coverage
490 try:
Andrew Jeffery881041f2020-03-13 11:46:30 +1030491 cmd = make_parallel + ['check-code-coverage']
William A. Kennington III1fddb972019-02-06 18:03:53 -0800492 check_call_cmd(*cmd)
William A. Kennington III0f0a6802018-07-16 11:52:33 -0700493 except CalledProcessError:
494 raise Exception('Code coverage failed')
Matthew Barthccb7f852016-11-23 17:43:02 -0600495
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030496
497class BuildSystem(object):
498 """
499 Build systems generally provide the means to configure, build, install and
500 test software. The BuildSystem class defines a set of interfaces on top of
501 which Autotools, Meson, CMake and possibly other build system drivers can
502 be implemented, separating out the phases to control whether a package
503 should merely be installed or also tested and analyzed.
504 """
505 def __init__(self, package, path):
506 """Initialise the driver with properties independent of the build system
507
508 Keyword arguments:
509 package: The name of the package. Derived from the path if None
510 path: The path to the package. Set to the working directory if None
511 """
512 self.path = "." if not path else path
Andrew Jeffery47fbfa52020-03-13 12:05:09 +1030513 realpath = os.path.realpath(self.path)
514 self.package = package if package else os.path.basename(realpath)
Andrew Jeffery3f8e5a72020-03-13 11:47:56 +1030515 self.build_for_testing = False
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030516
517 def probe(self):
518 """Test if the build system driver can be applied to the package
519
520 Return True if the driver can drive the package's build system,
521 otherwise False.
522
523 Generally probe() is implemented by testing for the presence of the
524 build system's configuration file(s).
525 """
526 raise NotImplemented
527
528 def dependencies(self):
529 """Provide the package's dependencies
530
531 Returns a list of dependencies. If no dependencies are required then an
532 empty list must be returned.
533
534 Generally dependencies() is implemented by analysing and extracting the
535 data from the build system configuration.
536 """
537 raise NotImplemented
538
539 def configure(self, build_for_testing):
540 """Configure the source ready for building
541
542 Should raise an exception if configuration failed.
543
544 Keyword arguments:
545 build_for_testing: Mark the package as being built for testing rather
546 than for installation as a dependency for the
547 package under test. Setting to True generally
548 implies that the package will be configured to build
549 with debug information, at a low level of
550 optimisation and possibly with sanitizers enabled.
551
552 Generally configure() is implemented by invoking the build system
553 tooling to generate Makefiles or equivalent.
554 """
555 raise NotImplemented
556
557 def build(self):
558 """Build the software ready for installation and/or testing
559
560 Should raise an exception if the build fails
561
562 Generally build() is implemented by invoking `make` or `ninja`.
563 """
564 raise NotImplemented
565
566 def install(self):
567 """Install the software ready for use
568
569 Should raise an exception if installation fails
570
571 Like build(), install() is generally implemented by invoking `make` or
572 `ninja`.
573 """
574 raise NotImplemented
575
576 def test(self):
577 """Build and run the test suite associated with the package
578
579 Should raise an exception if the build or testing fails.
580
581 Like install(), test() is generally implemented by invoking `make` or
582 `ninja`.
583 """
584 raise NotImplemented
585
586 def analyze(self):
587 """Run any supported analysis tools over the codebase
588
589 Should raise an exception if analysis fails.
590
591 Some analysis tools such as scan-build need injection into the build
592 system. analyze() provides the necessary hook to implement such
593 behaviour. Analyzers independent of the build system can also be
594 specified here but at the cost of possible duplication of code between
595 the build system driver implementations.
596 """
597 raise NotImplemented
598
599
600class Autotools(BuildSystem):
601 def __init__(self, package=None, path=None):
602 super(Autotools, self).__init__(package, path)
603
604 def probe(self):
605 return os.path.isfile(os.path.join(self.path, 'configure.ac'))
606
607 def dependencies(self):
608 configure_ac = os.path.join(self.path, 'configure.ac')
609
Andrew Jeffery47fbfa52020-03-13 12:05:09 +1030610 contents = ''
Andrew Jeffery7d4a26f2020-03-13 11:42:34 +1030611 # Prepend some special function overrides so we can parse out
612 # dependencies
Andrew Jeffery89b64b62020-03-13 12:15:48 +1030613 for macro in DEPENDENCIES.keys():
Andrew Jeffery47fbfa52020-03-13 12:05:09 +1030614 contents += ('m4_define([' + macro + '], [' + macro + '_START$' +
615 str(DEPENDENCIES_OFFSET[macro] + 1) +
616 macro + '_END])\n')
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030617 with open(configure_ac, "rt") as f:
Andrew Jeffery47fbfa52020-03-13 12:05:09 +1030618 contents += f.read()
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030619
Andrew Jeffery47fbfa52020-03-13 12:05:09 +1030620 autoconf_cmdline = ['autoconf', '-Wno-undefined', '-']
621 autoconf_process = subprocess.Popen(autoconf_cmdline,
Andrew Jeffery7d4a26f2020-03-13 11:42:34 +1030622 stdin=subprocess.PIPE,
623 stdout=subprocess.PIPE,
624 stderr=subprocess.PIPE)
Andrew Jeffery89b64b62020-03-13 12:15:48 +1030625 document = contents.encode('utf-8')
626 (stdout, stderr) = autoconf_process.communicate(input=document)
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030627 if not stdout:
628 print(stderr)
629 raise Exception("Failed to run autoconf for parsing dependencies")
630
631 # Parse out all of the dependency text
632 matches = []
Andrew Jeffery89b64b62020-03-13 12:15:48 +1030633 for macro in DEPENDENCIES.keys():
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030634 pattern = '(' + macro + ')_START(.*?)' + macro + '_END'
Andrew Jeffery89b64b62020-03-13 12:15:48 +1030635 for match in re.compile(pattern).finditer(stdout.decode('utf-8')):
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030636 matches.append((match.group(1), match.group(2)))
637
638 # Look up dependencies from the text
639 found_deps = []
640 for macro, deptext in matches:
641 for potential_dep in deptext.split(' '):
Andrew Jeffery89b64b62020-03-13 12:15:48 +1030642 for known_dep in DEPENDENCIES[macro].keys():
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030643 if potential_dep.startswith(known_dep):
644 found_deps.append(DEPENDENCIES[macro][known_dep])
645
646 return found_deps
647
648 def _configure_feature(self, flag, enabled):
649 """
650 Returns an configure flag as a string
651
652 Parameters:
653 flag The name of the flag
654 enabled Whether the flag is enabled or disabled
655 """
656 return '--' + ('enable' if enabled else 'disable') + '-' + flag
657
658 def configure(self, build_for_testing):
659 self.build_for_testing = build_for_testing
660 conf_flags = [
661 self._configure_feature('silent-rules', False),
662 self._configure_feature('examples', build_for_testing),
663 self._configure_feature('tests', build_for_testing),
664 ]
665 if not TEST_ONLY:
666 conf_flags.extend([
667 self._configure_feature('code-coverage', build_for_testing),
668 self._configure_feature('valgrind', build_for_testing),
669 ])
670 # Add any necessary configure flags for package
671 if CONFIGURE_FLAGS.get(self.package) is not None:
672 conf_flags.extend(CONFIGURE_FLAGS.get(self.package))
673 for bootstrap in ['bootstrap.sh', 'bootstrap', 'autogen.sh']:
674 if os.path.exists(bootstrap):
675 check_call_cmd('./' + bootstrap)
676 break
677 check_call_cmd('./configure', *conf_flags)
678
679 def build(self):
680 check_call_cmd(*make_parallel)
681
682 def install(self):
Andrew Jeffery881041f2020-03-13 11:46:30 +1030683 check_call_cmd('sudo', '-n', '--', *(make_parallel + ['install']))
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030684
685 def test(self):
686 try:
Andrew Jeffery881041f2020-03-13 11:46:30 +1030687 cmd = make_parallel + ['check']
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030688 for i in range(0, args.repeat):
689 check_call_cmd(*cmd)
690 except CalledProcessError:
691 for root, _, files in os.walk(os.getcwd()):
692 if 'test-suite.log' not in files:
693 continue
694 check_call_cmd('cat', os.path.join(root, 'test-suite.log'))
695 raise Exception('Unit tests failed')
696
697 def analyze(self):
698 maybe_make_valgrind()
699 maybe_make_coverage()
700 run_cppcheck()
701
702
703class CMake(BuildSystem):
704 def __init__(self, package=None, path=None):
705 super(CMake, self).__init__(package, path)
706
707 def probe(self):
708 return os.path.isfile(os.path.join(self.path, 'CMakeLists.txt'))
709
710 def dependencies(self):
711 return []
712
713 def configure(self, build_for_testing):
714 self.build_for_testing = build_for_testing
715 check_call_cmd('cmake', '-DCMAKE_EXPORT_COMPILE_COMMANDS=ON', '.')
716
717 def build(self):
718 check_call_cmd('cmake', '--build', '.', '--', '-j',
719 str(multiprocessing.cpu_count()))
720
721 def install(self):
722 pass
723
724 def test(self):
725 if make_target_exists('test'):
726 check_call_cmd('ctest', '.')
727
728 def analyze(self):
Andrew Geissler38356642020-03-19 15:12:44 -0500729 if TEST_ONLY:
730 return
731
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030732 if os.path.isfile('.clang-tidy'):
733 check_call_cmd('run-clang-tidy-8.py', '-p', '.')
734 maybe_make_valgrind()
735 maybe_make_coverage()
736 run_cppcheck()
737
738
739class Meson(BuildSystem):
740 def __init__(self, package=None, path=None):
741 super(Meson, self).__init__(package, path)
742
743 def probe(self):
744 return os.path.isfile(os.path.join(self.path, 'meson.build'))
745
746 def dependencies(self):
747 meson_build = os.path.join(self.path, 'meson.build')
748 if not os.path.exists(meson_build):
749 return []
750
751 found_deps = []
752 for root, dirs, files in os.walk(self.path):
753 if 'meson.build' not in files:
754 continue
755 with open(os.path.join(root, 'meson.build'), 'rt') as f:
756 build_contents = f.read()
Andrew Jeffery47fbfa52020-03-13 12:05:09 +1030757 pattern = r"dependency\('([^']*)'.*?\)\n"
758 for match in re.finditer(pattern, build_contents):
759 group = match.group(1)
760 maybe_dep = DEPENDENCIES['PKG_CHECK_MODULES'].get(group)
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030761 if maybe_dep is not None:
762 found_deps.append(maybe_dep)
763
764 return found_deps
765
766 def _parse_options(self, options_file):
767 """
768 Returns a set of options defined in the provides meson_options.txt file
769
770 Parameters:
771 options_file The file containing options
772 """
773 options_contents = ''
774 with open(options_file, "rt") as f:
775 options_contents += f.read()
Andrew Jeffery89b64b62020-03-13 12:15:48 +1030776 options = set()
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030777 pattern = 'option\\(\\s*\'([^\']*)\''
778 for match in re.compile(pattern).finditer(options_contents):
779 options.add(match.group(1))
780 return options
781
782 def _configure_feature(self, val):
783 """
784 Returns the meson flag which signifies the value
785
786 True is enabled which requires the feature.
787 False is disabled which disables the feature.
788 None is auto which autodetects the feature.
789
790 Parameters:
791 val The value being converted
792 """
793 if val is True:
794 return "enabled"
795 elif val is False:
796 return "disabled"
797 elif val is None:
798 return "auto"
799 else:
800 raise Exception("Bad meson feature value")
801
802 def configure(self, build_for_testing):
803 self.build_for_testing = build_for_testing
Andrew Jeffery89b64b62020-03-13 12:15:48 +1030804 meson_options = set()
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030805 if os.path.exists("meson_options.txt"):
806 meson_options = self._parse_options("meson_options.txt")
807 meson_flags = [
808 '-Db_colorout=never',
809 '-Dwerror=true',
810 '-Dwarning_level=3',
811 ]
812 if build_for_testing:
813 meson_flags.append('--buildtype=debug')
814 else:
815 meson_flags.append('--buildtype=debugoptimized')
816 if 'tests' in meson_options:
Andrew Jeffery47fbfa52020-03-13 12:05:09 +1030817 flag_args = self._configure_feature(build_for_testing)
818 meson_flags.append('-Dtests=' + flag_args)
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030819 if 'examples' in meson_options:
820 meson_flags.append('-Dexamples=' + str(build_for_testing).lower())
821 if MESON_FLAGS.get(self.package) is not None:
822 meson_flags.extend(MESON_FLAGS.get(self.package))
823 try:
Andrew Jeffery47fbfa52020-03-13 12:05:09 +1030824 check_call_cmd('meson', 'setup', '--reconfigure', 'build',
825 *meson_flags)
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030826 except:
827 shutil.rmtree('build')
828 check_call_cmd('meson', 'setup', 'build', *meson_flags)
829
830 def build(self):
831 check_call_cmd('ninja', '-C', 'build')
832
833 def install(self):
834 check_call_cmd('sudo', '-n', '--', 'ninja', '-C', 'build', 'install')
835
836 def test(self):
837 try:
838 check_call_cmd('meson', 'test', '-C', 'build')
839 except CalledProcessError:
840 for root, _, files in os.walk(os.getcwd()):
841 if 'testlog.txt' not in files:
842 continue
843 check_call_cmd('cat', os.path.join(root, 'testlog.txt'))
844 raise Exception('Unit tests failed')
845
846 def _setup_exists(self, setup):
847 """
848 Returns whether the meson build supports the named test setup.
849
850 Parameter descriptions:
851 setup The setup target to check
852 """
853 try:
854 with open(os.devnull, 'w') as devnull:
855 output = subprocess.check_output(
856 ['meson', 'test', '-C', 'build',
857 '--setup', setup, '-t', '0'],
858 stderr=subprocess.STDOUT)
859 except CalledProcessError as e:
860 output = e.output
Andrew Jeffery89b64b62020-03-13 12:15:48 +1030861 output = output.decode('utf-8')
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030862 return not re.search('Test setup .* not found from project', output)
863
864 def _maybe_valgrind(self):
865 """
866 Potentially runs the unit tests through valgrind for the package
Andrew Jeffery47fbfa52020-03-13 12:05:09 +1030867 via `meson test`. The package can specify custom valgrind
868 configurations by utilizing add_test_setup() in a meson.build
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030869 """
870 if not is_valgrind_safe():
871 sys.stderr.write("###### Skipping valgrind ######\n")
872 return
873 try:
874 if self._setup_exists('valgrind'):
875 check_call_cmd('meson', 'test', '-C', 'build',
876 '--setup', 'valgrind')
877 else:
878 check_call_cmd('meson', 'test', '-C', 'build',
879 '--wrapper', 'valgrind')
880 except CalledProcessError:
881 for root, _, files in os.walk(os.getcwd()):
882 if 'testlog-valgrind.txt' not in files:
883 continue
Andrew Jeffery47fbfa52020-03-13 12:05:09 +1030884 cat_args = os.path.join(root, 'testlog-valgrind.txt')
885 check_call_cmd('cat', cat_args)
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030886 raise Exception('Valgrind tests failed')
887
888 def analyze(self):
Andrew Geissler38356642020-03-19 15:12:44 -0500889 if TEST_ONLY:
890 return
891
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030892 self._maybe_valgrind()
893
894 # Run clang-tidy only if the project has a configuration
895 if os.path.isfile('.clang-tidy'):
896 check_call_cmd('run-clang-tidy-8.py', '-p',
897 'build')
898 # Run the basic clang static analyzer otherwise
899 else:
900 check_call_cmd('ninja', '-C', 'build',
901 'scan-build')
902
903 # Run tests through sanitizers
904 # b_lundef is needed if clang++ is CXX since it resolves the
905 # asan symbols at runtime only. We don't want to set it earlier
906 # in the build process to ensure we don't have undefined
907 # runtime code.
908 if is_sanitize_safe():
909 check_call_cmd('meson', 'configure', 'build',
910 '-Db_sanitize=address,undefined',
911 '-Db_lundef=false')
912 check_call_cmd('meson', 'test', '-C', 'build',
913 '--logbase', 'testlog-ubasan')
914 # TODO: Fix memory sanitizer
Andrew Jeffery6015ca12020-03-13 11:50:10 +1030915 # check_call_cmd('meson', 'configure', 'build',
916 # '-Db_sanitize=memory')
917 # check_call_cmd('meson', 'test', '-C', 'build'
918 # '--logbase', 'testlog-msan')
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030919 check_call_cmd('meson', 'configure', 'build',
920 '-Db_sanitize=none', '-Db_lundef=true')
921 else:
922 sys.stderr.write("###### Skipping sanitizers ######\n")
923
924 # Run coverage checks
925 check_call_cmd('meson', 'configure', 'build',
926 '-Db_coverage=true')
927 self.test()
928 # Only build coverage HTML if coverage files were produced
929 for root, dirs, files in os.walk('build'):
930 if any([f.endswith('.gcda') for f in files]):
931 check_call_cmd('ninja', '-C', 'build',
932 'coverage-html')
933 break
934 check_call_cmd('meson', 'configure', 'build',
935 '-Db_coverage=false')
936 run_cppcheck()
937
938
939class Package(object):
940 def __init__(self, name=None, path=None):
Andrew Jeffery881041f2020-03-13 11:46:30 +1030941 self.supported = [Autotools, Meson, CMake]
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030942 self.name = name
943 self.path = path
944 self.test_only = False
945
946 def build_systems(self):
Andrew Jeffery881041f2020-03-13 11:46:30 +1030947 instances = (system(self.name, self.path) for system in self.supported)
948 return (instance for instance in instances if instance.probe())
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030949
950 def build_system(self, preferred=None):
Andrew Geissler8cb74fc2020-03-19 14:48:05 -0500951 systems = list(self.build_systems())
952
953 if not systems:
954 return None
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030955
956 if preferred:
Andrew Jeffery881041f2020-03-13 11:46:30 +1030957 return {type(system): system for system in systems}[preferred]
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030958
959 return next(iter(systems))
960
961 def install(self, system=None):
962 if not system:
963 system = self.build_system()
964
965 system.configure(False)
966 system.build()
967 system.install()
968
Andrew Jeffery19d75672020-03-13 10:42:08 +1030969 def _test_one(self, system):
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030970 system.configure(True)
971 system.build()
972 system.install()
973 system.test()
974 system.analyze()
975
Andrew Jeffery19d75672020-03-13 10:42:08 +1030976 def test(self):
977 for system in self.build_systems():
978 self._test_one(system)
979
Andrew Jeffery15e423e2020-03-11 16:51:28 +1030980
Matt Spinler9bfaaad2019-10-25 09:51:50 -0500981def find_file(filename, basedir):
982 """
983 Finds all occurrences of a file in the base directory
984 and passes them back with their relative paths.
985
986 Parameter descriptions:
987 filename The name of the file to find
988 basedir The base directory search in
989 """
990
991 filepaths = []
992 for root, dirs, files in os.walk(basedir):
993 if filename in files:
Andrew Jeffery2ba45fb2020-03-13 11:25:01 +1030994 filepaths.append(os.path.join(root, filename))
Matt Spinler9bfaaad2019-10-25 09:51:50 -0500995 return filepaths
996
Andrew Jeffery9cc97d82020-03-13 11:57:44 +1030997
Matthew Barthccb7f852016-11-23 17:43:02 -0600998if __name__ == '__main__':
999 # CONFIGURE_FLAGS = [GIT REPO]:[CONFIGURE FLAGS]
1000 CONFIGURE_FLAGS = {
Matthew Barth1d1c6732017-03-24 10:00:28 -05001001 'sdbusplus': ['--enable-transaction'],
1002 'phosphor-logging':
Matt Spinler0744bb82020-01-16 08:23:35 -06001003 ['--enable-metadata-processing', '--enable-openpower-pel-extension',
Deepak Kodihalli3a4e1b42017-06-08 09:52:35 -05001004 'YAML_DIR=/usr/local/share/phosphor-dbus-yaml/yaml']
Matthew Barthccb7f852016-11-23 17:43:02 -06001005 }
1006
William A. Kennington III3f1d1202018-12-06 18:02:07 -08001007 # MESON_FLAGS = [GIT REPO]:[MESON FLAGS]
1008 MESON_FLAGS = {
1009 }
1010
Matthew Barthccb7f852016-11-23 17:43:02 -06001011 # DEPENDENCIES = [MACRO]:[library/header]:[GIT REPO]
1012 DEPENDENCIES = {
1013 'AC_CHECK_LIB': {'mapper': 'phosphor-objmgr'},
Matthew Barth710f3f02017-01-18 15:20:19 -06001014 'AC_CHECK_HEADER': {
1015 'host-ipmid': 'phosphor-host-ipmid',
Patrick Ventureb41a4462018-10-03 17:27:38 -07001016 'blobs-ipmid': 'phosphor-ipmi-blobs',
Matthew Barth710f3f02017-01-18 15:20:19 -06001017 'sdbusplus': 'sdbusplus',
William A. Kennington IIIb4f730a2018-09-12 11:21:20 -07001018 'sdeventplus': 'sdeventplus',
William A. Kennington III23705242019-01-15 18:17:25 -08001019 'stdplus': 'stdplus',
Patrick Venture22329962018-09-14 10:23:04 -07001020 'gpioplus': 'gpioplus',
Saqib Khan66145052017-02-14 12:02:07 -06001021 'phosphor-logging/log.hpp': 'phosphor-logging',
Patrick Williamseab8a372017-01-30 11:21:32 -06001022 },
Brad Bishopebb49112017-02-13 11:07:26 -05001023 'AC_PATH_PROG': {'sdbus++': 'sdbusplus'},
Patrick Williamseab8a372017-01-30 11:21:32 -06001024 'PKG_CHECK_MODULES': {
Matthew Barth19e261e2017-02-01 12:55:22 -06001025 'phosphor-dbus-interfaces': 'phosphor-dbus-interfaces',
Patrick Williamsf128b402017-03-29 06:45:59 -05001026 'openpower-dbus-interfaces': 'openpower-dbus-interfaces',
Matt Spinler7be19032018-04-13 09:43:14 -05001027 'ibm-dbus-interfaces': 'ibm-dbus-interfaces',
William A. Kennington III993ab332019-02-07 02:12:31 -08001028 'libipmid': 'phosphor-host-ipmid',
1029 'libipmid-host': 'phosphor-host-ipmid',
Brad Bishopebb49112017-02-13 11:07:26 -05001030 'sdbusplus': 'sdbusplus',
William A. Kennington IIIb4f730a2018-09-12 11:21:20 -07001031 'sdeventplus': 'sdeventplus',
William A. Kennington III23705242019-01-15 18:17:25 -08001032 'stdplus': 'stdplus',
Patrick Venture22329962018-09-14 10:23:04 -07001033 'gpioplus': 'gpioplus',
Brad Bishopebb49112017-02-13 11:07:26 -05001034 'phosphor-logging': 'phosphor-logging',
Marri Devender Raoa3eee8a2018-08-13 05:34:27 -05001035 'phosphor-snmp': 'phosphor-snmp',
Patrick Venturee584c3b2019-03-07 09:44:16 -08001036 'ipmiblob': 'ipmi-blob-tool',
Brad Bishopebb49112017-02-13 11:07:26 -05001037 },
Matthew Barthccb7f852016-11-23 17:43:02 -06001038 }
1039
William A. Kennington IIIe67f5fc2018-12-06 17:40:30 -08001040 # Offset into array of macro parameters MACRO(0, 1, ...N)
1041 DEPENDENCIES_OFFSET = {
1042 'AC_CHECK_LIB': 0,
1043 'AC_CHECK_HEADER': 0,
1044 'AC_PATH_PROG': 1,
1045 'PKG_CHECK_MODULES': 1,
1046 }
1047
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -05001048 # DEPENDENCIES_REGEX = [GIT REPO]:[REGEX STRING]
1049 DEPENDENCIES_REGEX = {
Patrick Ventured37b8052018-10-16 16:03:03 -07001050 'phosphor-logging': r'\S+-dbus-interfaces$'
Leonel Gonzaleza62a1a12017-03-24 11:03:47 -05001051 }
1052
Matthew Barth33df8792016-12-19 14:30:17 -06001053 # Set command line arguments
1054 parser = argparse.ArgumentParser()
1055 parser.add_argument("-w", "--workspace", dest="WORKSPACE", required=True,
1056 help="Workspace directory location(i.e. /home)")
1057 parser.add_argument("-p", "--package", dest="PACKAGE", required=True,
1058 help="OpenBMC package to be unit tested")
William A. Kennington III65b37fa2019-01-31 15:15:17 -08001059 parser.add_argument("-t", "--test-only", dest="TEST_ONLY",
1060 action="store_true", required=False, default=False,
1061 help="Only run test cases, no other validation")
Matthew Barth33df8792016-12-19 14:30:17 -06001062 parser.add_argument("-v", "--verbose", action="store_true",
1063 help="Print additional package status messages")
Andrew Jeffery468309d2018-03-08 13:46:33 +10301064 parser.add_argument("-r", "--repeat", help="Repeat tests N times",
1065 type=int, default=1)
Andrew Geisslera61acb52019-01-03 16:32:44 -06001066 parser.add_argument("-b", "--branch", dest="BRANCH", required=False,
1067 help="Branch to target for dependent repositories",
1068 default="master")
Lei YU7ef93302019-11-06 13:53:21 +08001069 parser.add_argument("-n", "--noformat", dest="FORMAT",
1070 action="store_false", required=False,
1071 help="Whether or not to run format code")
Matthew Barth33df8792016-12-19 14:30:17 -06001072 args = parser.parse_args(sys.argv[1:])
1073 WORKSPACE = args.WORKSPACE
1074 UNIT_TEST_PKG = args.PACKAGE
William A. Kennington III65b37fa2019-01-31 15:15:17 -08001075 TEST_ONLY = args.TEST_ONLY
Andrew Geisslera61acb52019-01-03 16:32:44 -06001076 BRANCH = args.BRANCH
Lei YU7ef93302019-11-06 13:53:21 +08001077 FORMAT_CODE = args.FORMAT
Matthew Barth33df8792016-12-19 14:30:17 -06001078 if args.verbose:
1079 def printline(*line):
1080 for arg in line:
Andrew Jeffery89b64b62020-03-13 12:15:48 +10301081 print(arg, end=' ')
1082 print()
Matthew Barth33df8792016-12-19 14:30:17 -06001083 else:
Andrew Jefferyfce557f2020-03-13 12:12:19 +10301084 def printline(*line):
1085 pass
Matthew Barthccb7f852016-11-23 17:43:02 -06001086
Lei YU7ef93302019-11-06 13:53:21 +08001087 CODE_SCAN_DIR = WORKSPACE + "/" + UNIT_TEST_PKG
1088
James Feist878df5c2018-07-26 14:54:28 -07001089 # First validate code formatting if repo has style formatting files.
Adriana Kobylakbcee22b2018-01-10 16:58:27 -06001090 # The format-code.sh checks for these files.
Lei YU7ef93302019-11-06 13:53:21 +08001091 if FORMAT_CODE:
1092 check_call_cmd("./format-code.sh", CODE_SCAN_DIR)
Andrew Geisslera28286d2018-01-10 11:00:00 -08001093
Andrew Geissler8cb74fc2020-03-19 14:48:05 -05001094 # Check if this repo has a supported make infrastructure
1095 pkg = Package(UNIT_TEST_PKG, os.path.join(WORKSPACE, UNIT_TEST_PKG))
1096 if not pkg.build_system():
1097 print("No valid build system, exit")
1098 sys.exit(0)
1099
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301100 prev_umask = os.umask(000)
James Feist878df5c2018-07-26 14:54:28 -07001101
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301102 # Determine dependencies and add them
1103 dep_added = dict()
1104 dep_added[UNIT_TEST_PKG] = False
William A. Kennington III40d5c7c2018-12-13 14:37:59 -08001105
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301106 # Create dependency tree
1107 dep_tree = DepTree(UNIT_TEST_PKG)
1108 build_dep_tree(UNIT_TEST_PKG,
1109 os.path.join(WORKSPACE, UNIT_TEST_PKG),
1110 dep_added,
1111 dep_tree,
1112 BRANCH)
William A. Kennington III65b37fa2019-01-31 15:15:17 -08001113
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301114 # Reorder Dependency Tree
Andrew Jeffery89b64b62020-03-13 12:15:48 +10301115 for pkg_name, regex_str in DEPENDENCIES_REGEX.items():
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301116 dep_tree.ReorderDeps(pkg_name, regex_str)
1117 if args.verbose:
1118 dep_tree.PrintTree()
William A. Kennington III65b37fa2019-01-31 15:15:17 -08001119
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301120 install_list = dep_tree.GetInstallList()
Andrew Geissler9ced4ed2019-11-18 14:33:58 -06001121
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301122 # We don't want to treat our package as a dependency
1123 install_list.remove(UNIT_TEST_PKG)
James Feist878df5c2018-07-26 14:54:28 -07001124
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301125 # Install reordered dependencies
1126 for dep in install_list:
1127 build_and_install(dep, False)
James Feist878df5c2018-07-26 14:54:28 -07001128
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301129 # Run package unit tests
1130 build_and_install(UNIT_TEST_PKG, True)
James Feist878df5c2018-07-26 14:54:28 -07001131
Andrew Jeffery15e423e2020-03-11 16:51:28 +10301132 os.umask(prev_umask)
Matt Spinler9bfaaad2019-10-25 09:51:50 -05001133
1134 # Run any custom CI scripts the repo has, of which there can be
1135 # multiple of and anywhere in the repository.
1136 ci_scripts = find_file('run-ci.sh', os.path.join(WORKSPACE, UNIT_TEST_PKG))
1137 if ci_scripts:
1138 os.chdir(os.path.join(WORKSPACE, UNIT_TEST_PKG))
1139 for ci_script in ci_scripts:
1140 check_call_cmd('sh', ci_script)