]> Git Repo - J-u-boot.git/blame - tools/buildman/control.py
buildman: Create a function to get number of built commits
[J-u-boot.git] / tools / buildman / control.py
CommitLineData
83d290c5 1# SPDX-License-Identifier: GPL-2.0+
fc3fe1c2
SG
2# Copyright (c) 2013 The Chromium OS Authors.
3#
fc3fe1c2 4
9ef05b95
SG
5"""Control module for buildman
6
7This holds the main control logic for buildman, when not running tests.
8"""
9
fc3fe1c2
SG
10import multiprocessing
11import os
883a321a 12import shutil
fc3fe1c2
SG
13import sys
14
c52bd225 15from buildman import boards
0ede00fd 16from buildman import bsettings
2b4806e4 17from buildman import cfgutil
0ede00fd
SG
18from buildman import toolchain
19from buildman.builder import Builder
bf776679
SG
20from patman import gitutil
21from patman import patchstream
4583c002
SG
22from u_boot_pylib import command
23from u_boot_pylib import terminal
4583c002 24from u_boot_pylib.terminal import tprint
fc3fe1c2 25
b8be2bd8
SG
26TEST_BUILDER = None
27
9ef05b95 28def get_plural(count):
fc3fe1c2
SG
29 """Returns a plural 's' if count is not 1"""
30 return 's' if count != 1 else ''
31
1d0c55d8
SG
32
33def count_build_commits(commits, step):
34 """Calculate the number of commits to be built
35
36 Args:
37 commits (list of Commit): Commits to build or None
38 step (int): Step value for commits, typically 1
39
40 Returns:
41 Number of commits that will be built
42 """
43 if commits:
44 count = len(commits)
45 return (count + step - 1) // step
46 return 0
47
48
49def get_action_summary(is_summary, commit_count, selected, threads, jobs):
fc3fe1c2
SG
50 """Return a string summarising the intended action.
51
1d3a5a52
SG
52 Args:
53 is_summary (bool): True if this is a summary (otherwise it is building)
54 commits (list): List of commits being built
55 selected (list of Board): List of Board objects that are marked
56 step (int): Step increment through commits
57 threads (int): Number of processor threads being used
58 jobs (int): Number of jobs to build at once
59
fc3fe1c2
SG
60 Returns:
61 Summary string.
62 """
1d0c55d8
SG
63 if commit_count:
64 commit_str = f'{commit_count} commit{get_plural(commit_count)}'
fea5858e
SG
65 else:
66 commit_str = 'current source'
b8be2bd8
SG
67 msg = (f"{'Summary of' if is_summary else 'Building'} "
68 f'{commit_str} for {len(selected)} boards')
1d3a5a52
SG
69 msg += (f' ({threads} thread{get_plural(threads)}, '
70 f'{jobs} job{get_plural(jobs)} per thread)')
b8be2bd8
SG
71 return msg
72
73# pylint: disable=R0913
1b820ee1
SG
74def show_actions(series, why_selected, boards_selected, output_dir,
75 board_warnings, step, threads, jobs, verbose):
fc3fe1c2
SG
76 """Display a list of actions that we would take, if not a dry run.
77
78 Args:
79 series: Series object
80 why_selected: Dictionary where each key is a buildman argument
8d7523c5
SG
81 provided by the user, and the value is the list of boards
82 brought in by that argument. For example, 'arm' might bring
83 in 400 boards, so in this case the key would be 'arm' and
fc3fe1c2
SG
84 the value would be a list of board names.
85 boards_selected: Dict of selected boards, key is target name,
86 value is Board object
d233dfb0 87 output_dir (str): Output directory for builder
0689036a 88 board_warnings: List of warnings obtained from board selected
1b820ee1
SG
89 step (int): Step increment through commits
90 threads (int): Number of processor threads being used
91 jobs (int): Number of jobs to build at once
92 verbose (bool): True to indicate why each board was selected
fc3fe1c2
SG
93 """
94 col = terminal.Color()
c05aa036
SG
95 print('Dry run, so not doing much. But I would do this:')
96 print()
fea5858e
SG
97 if series:
98 commits = series.commits
99 else:
100 commits = None
1d0c55d8
SG
101 print(get_action_summary(False, count_build_commits(commits, step),
102 boards_selected, threads, jobs))
d233dfb0 103 print(f'Build directory: {output_dir}')
fea5858e 104 if commits:
1b820ee1 105 for upto in range(0, len(series.commits), step):
fea5858e 106 commit = series.commits[upto]
252ac589 107 print(' ', col.build(col.YELLOW, commit.hash[:8], bright=False), end=' ')
c05aa036
SG
108 print(commit.subject)
109 print()
fc3fe1c2
SG
110 for arg in why_selected:
111 if arg != 'all':
b8be2bd8 112 print(arg, f': {len(why_selected[arg])} boards')
1b820ee1 113 if verbose:
b8be2bd8
SG
114 print(f" {' '.join(why_selected[arg])}")
115 print('Total boards to build for each '
116 f"commit: {len(why_selected['all'])}\n")
0689036a
SG
117 if board_warnings:
118 for warning in board_warnings:
252ac589 119 print(col.build(col.YELLOW, warning))
fc3fe1c2 120
9ef05b95 121def show_toolchain_prefix(brds, toolchains):
57cb9d52
SG
122 """Show information about a the tool chain used by one or more boards
123
4e9162d5
SG
124 The function checks that all boards use the same toolchain, then prints
125 the correct value for CROSS_COMPILE.
57cb9d52
SG
126
127 Args:
128 boards: Boards object containing selected boards
129 toolchains: Toolchains object containing available toolchains
57cb9d52
SG
130
131 Return:
132 None on success, string error message otherwise
133 """
6014db68 134 board_selected = brds.get_selected_dict()
57cb9d52 135 tc_set = set()
cc2c0d18 136 for brd in board_selected.values():
57cb9d52
SG
137 tc_set.add(toolchains.Select(brd.arch))
138 if len(tc_set) != 1:
ea78233d 139 sys.exit('Supplied boards must share one toolchain')
b8be2bd8
SG
140 tchain = tc_set.pop()
141 print(tchain.GetEnvArgs(toolchain.VAR_CROSS_COMPILE))
57cb9d52 142
d7713ad3 143def get_allow_missing(opt_allow, opt_no_allow, num_selected, has_branch):
b8be2bd8
SG
144 """Figure out whether to allow external blobs
145
146 Uses the allow-missing setting and the provided arguments to decide whether
147 missing external blobs should be allowed
148
149 Args:
150 opt_allow (bool): True if --allow-missing flag is set
151 opt_no_allow (bool): True if --no-allow-missing flag is set
152 num_selected (int): Number of selected board
153 has_branch (bool): True if a git branch (to build) has been provided
154
155 Returns:
156 bool: True to allow missing external blobs, False to produce an error if
157 external blobs are used
158 """
d7713ad3
TR
159 allow_missing = False
160 am_setting = bsettings.GetGlobalItemValue('allow-missing')
161 if am_setting:
162 if am_setting == 'always':
163 allow_missing = True
164 if 'multiple' in am_setting and num_selected > 1:
165 allow_missing = True
166 if 'branch' in am_setting and has_branch:
167 allow_missing = True
168
169 if opt_allow:
170 allow_missing = True
171 if opt_no_allow:
172 allow_missing = False
173 return allow_missing
174
d230c014 175
aeb2381b
SG
176def count_commits(branch, count, col, git_dir):
177 """Could the number of commits in the branch/ranch being built
178
179 Args:
180 branch (str): Name of branch to build, or None if none
181 count (int): Number of commits to build, or -1 for all
182 col (Terminal.Color): Color object to use
183 git_dir (str): Git directory to use, e.g. './.git'
184
185 Returns:
186 tuple:
187 Number of commits being built
188 True if the 'branch' string contains a range rather than a simple
189 name
190 """
191 has_range = branch and '..' in branch
192 if count == -1:
193 if not branch:
194 count = 1
195 else:
196 if has_range:
197 count, msg = gitutil.count_commits_in_range(git_dir, branch)
198 else:
199 count, msg = gitutil.count_commits_in_branch(git_dir, branch)
200 if count is None:
201 sys.exit(col.build(col.RED, msg))
202 elif count == 0:
203 sys.exit(col.build(col.RED,
204 f"Range '{branch}' has no commits"))
205 if msg:
206 print(col.build(col.YELLOW, msg))
207 count += 1 # Build upstream commit also
208
209 if not count:
210 msg = (f"No commits found to process in branch '{branch}': "
211 "set branch's upstream or use -c flag")
212 sys.exit(col.build(col.RED, msg))
213 return count, has_range
214
215
9df59e4c 216def determine_series(selected, col, git_dir, count, branch, work_in_output):
d230c014
SG
217 """Determine the series which is to be built, if any
218
6378bad3
SG
219 If there is a series, the commits in that series are numbered by setting
220 their sequence value (starting from 0). This is used by tests.
221
d230c014 222 Args:
1d3a5a52 223 selected (list of Board): List of Board objects that are marked
9df59e4c
SG
224 selected
225 col (Terminal.Color): Color object to use
226 git_dir (str): Git directory to use, e.g. './.git'
d230c014 227 count (int): Number of commits in branch
d230c014 228 branch (str): Name of branch to build, or None if none
9df59e4c 229 work_in_output (bool): True to work in the output directory
d230c014
SG
230
231 Returns:
232 Series: Series to build, or None for none
233
234 Read the metadata from the commits. First look at the upstream commit,
235 then the ones in the branch. We would like to do something like
236 upstream/master~..branch but that isn't possible if upstream/master is
237 a merge commit (it will list all the commits that form part of the
238 merge)
239
240 Conflicting tags are not a problem for buildman, since it does not use
241 them. For example, Series-version is not useful for buildman. On the
242 other hand conflicting tags will cause an error. So allow later tags
243 to overwrite earlier ones by setting allow_overwrite=True
244 """
9df59e4c
SG
245
246 # Work out how many commits to build. We want to build everything on the
247 # branch. We also build the upstream commit as a control so we can see
248 # problems introduced by the first commit on the branch.
aeb2381b 249 count, has_range = count_commits(branch, count, col, git_dir)
9df59e4c
SG
250 if work_in_output:
251 if len(selected) != 1:
252 sys.exit(col.build(col.RED,
253 '-w can only be used with a single board'))
254 if count != 1:
255 sys.exit(col.build(col.RED,
256 '-w can only be used with a single commit'))
257
d230c014
SG
258 if branch:
259 if count == -1:
260 if has_range:
261 range_expr = branch
262 else:
263 range_expr = gitutil.get_range_in_branch(git_dir, branch)
264 upstream_commit = gitutil.get_upstream(git_dir, branch)
265 series = patchstream.get_metadata_for_list(upstream_commit,
266 git_dir, 1, series=None, allow_overwrite=True)
267
268 series = patchstream.get_metadata_for_list(range_expr,
269 git_dir, None, series, allow_overwrite=True)
270 else:
271 # Honour the count
272 series = patchstream.get_metadata_for_list(branch,
273 git_dir, count, series=None, allow_overwrite=True)
6378bad3
SG
274
275 # Number the commits for test purposes
276 for i, commit in enumerate(series.commits):
277 commit.sequence = i
d230c014
SG
278 else:
279 series = None
280 return series
281
282
f7a36d54
SG
283def do_fetch_arch(toolchains, col, fetch_arch):
284 """Handle the --fetch-arch option
285
286 Args:
287 toolchains (Toolchains): Tool chains to use
288 col (terminal.Color): Color object to build
289 fetch_arch (str): Argument passed to the --fetch-arch option
290
291 Returns:
292 int: Return code for buildman
293 """
294 if fetch_arch == 'list':
295 sorted_list = toolchains.ListArchs()
296 print(col.build(
297 col.BLUE,
298 f"Available architectures: {' '.join(sorted_list)}\n"))
299 return 0
300
301 if fetch_arch == 'all':
302 fetch_arch = ','.join(toolchains.ListArchs())
303 print(col.build(col.CYAN,
304 f'\nDownloading toolchains: {fetch_arch}'))
305 for arch in fetch_arch.split(','):
306 print()
307 ret = toolchains.FetchAndInstall(arch)
308 if ret:
309 return ret
310 return 0
311
312
b8680646
SG
313def get_toolchains(toolchains, col, override_toolchain, fetch_arch,
314 list_tool_chains, verbose):
315 """Get toolchains object to use
316
317 Args:
318 toolchains (Toolchains or None): Toolchains to use. If None, then a
319 Toolchains object will be created and scanned
320 col (Terminal.Color): Color object
321 override_toolchain (str or None): Override value for toolchain, or None
322 fetch_arch (bool): True to fetch the toolchain for the architectures
323 list_tool_chains (bool): True to list all tool chains
324 verbose (bool): True for verbose output when listing toolchains
325
326 Returns:
327 Either:
328 int: Operation completed and buildman should exit with exit code
329 Toolchains: Toolchains object to use
330 """
331 no_toolchains = toolchains is None
332 if no_toolchains:
333 toolchains = toolchain.Toolchains(override_toolchain)
334
335 if fetch_arch:
336 return do_fetch_arch(toolchains, col, fetch_arch)
337
338 if no_toolchains:
339 toolchains.GetSettings()
340 toolchains.Scan(list_tool_chains and verbose)
341 if list_tool_chains:
342 toolchains.List()
343 print()
344 return 0
345 return toolchains
346
347
180c718a
SG
348def get_boards_obj(output_dir, regen_board_list, maintainer_check, threads,
349 verbose):
350 """Object the Boards object to use
351
352 Creates the output directory and ensures there is a boards.cfg file, then
353 read it in.
354
355 Args:
356 output_dir (str): Output directory to use
357 regen_board_list (bool): True to just regenerate the board list
358 maintainer_check (bool): True to just run a maintainer check
359 threads (int or None): Number of threads to use to create boards file
360 verbose (bool): False to suppress output from boards-file generation
361
362 Returns:
363 Either:
364 int: Operation completed and buildman should exit with exit code
365 Boards: Boards object to use
366 """
367 brds = boards.Boards()
368 nr_cpus = threads or multiprocessing.cpu_count()
369 if maintainer_check:
370 warnings = brds.build_board_list(jobs=nr_cpus)[1]
371 if warnings:
372 for warn in warnings:
373 print(warn, file=sys.stderr)
374 return 2
375 return 0
376
377 if not os.path.exists(output_dir):
378 os.makedirs(output_dir)
379 board_file = os.path.join(output_dir, 'boards.cfg')
380 if regen_board_list and regen_board_list != '-':
381 board_file = regen_board_list
382
383 okay = brds.ensure_board_list(board_file, nr_cpus, force=regen_board_list,
384 quiet=not verbose)
385 if regen_board_list:
386 return 0 if okay else 2
387 brds.read_boards(board_file)
388 return brds
389
390
0d4874fc
SG
391def determine_boards(brds, args, col, opt_boards, exclude_list):
392 """Determine which boards to build
393
394 Each element of args and exclude can refer to a board name, arch or SoC
395
396 Args:
397 brds (Boards): Boards object
398 args (list of str): Arguments describing boards to build
399 col (Terminal.Color): Color object
400 opt_boards (list of str): Specific boards to build, or None for all
401 exclude_list (list of str): Arguments describing boards to exclude
402
403 Returns:
404 tuple:
405 list of Board: List of Board objects that are marked selected
406 why_selected: Dictionary where each key is a buildman argument
407 provided by the user, and the value is the list of boards
408 brought in by that argument. For example, 'arm' might bring
409 in 400 boards, so in this case the key would be 'arm' and
410 the value would be a list of board names.
411 board_warnings: List of warnings obtained from board selected
412 """
413 exclude = []
414 if exclude_list:
415 for arg in exclude_list:
416 exclude += arg.split(',')
417
418 if opt_boards:
419 requested_boards = []
420 for brd in opt_boards:
421 requested_boards += brd.split(',')
422 else:
423 requested_boards = None
424 why_selected, board_warnings = brds.select_boards(args, exclude,
425 requested_boards)
426 selected = brds.get_selected()
427 if not selected:
428 sys.exit(col.build(col.RED, 'No matching boards found'))
429 return selected, why_selected, board_warnings
430
431
168d792f
SG
432def adjust_options(options, series, selected):
433 """Adjust options according to various constraints
434
435 Updates verbose, show_errors, threads, jobs and step
436
437 Args:
438 options (Options): Options object to adjust
439 series (Series): Series being built / summarised
440 selected (list of Board): List of Board objects that are marked
441 """
442 if not series and not options.dry_run:
443 options.verbose = True
444 if not options.summary:
445 options.show_errors = True
446
447 # By default we have one thread per CPU. But if there are not enough jobs
448 # we can have fewer threads and use a high '-j' value for make.
449 if options.threads is None:
450 options.threads = min(multiprocessing.cpu_count(), len(selected))
451 if not options.jobs:
452 options.jobs = max(1, (multiprocessing.cpu_count() +
453 len(selected) - 1) // len(selected))
454
455 if not options.step:
456 options.step = len(series.commits) - 1
457
4ec76822
SG
458 # We can't show function sizes without board details at present
459 if options.show_bloat:
460 options.show_detail = True
461
e48b946b
SG
462
463def setup_output_dir(output_dir, work_in_output, branch, no_subdirs, col,
464 clean_dir):
465 """Set up the output directory
466
467 Args:
468 output_dir (str): Output directory provided by the user, or None if none
469 work_in_output (bool): True to work in the output directory
470 branch (str): Name of branch to build, or None if none
471 no_subdirs (bool): True to put the output in the top-level output dir
472 clean_dir: Used for tests only, indicates that the existing output_dir
473 should be removed before starting the build
474
475 Returns:
476 str: Updated output directory pathname
477 """
478 if not output_dir:
479 if work_in_output:
480 sys.exit(col.build(col.RED, '-w requires that you specify -o'))
481 output_dir = '..'
482 if branch and not no_subdirs:
483 # As a special case allow the board directory to be placed in the
484 # output directory itself rather than any subdirectory.
485 dirname = branch.replace('/', '_')
486 output_dir = os.path.join(output_dir, dirname)
487 if clean_dir and os.path.exists(output_dir):
488 shutil.rmtree(output_dir)
489 return output_dir
490
a659b8dc 491
68f917c0
SG
492def run_builder(builder, commits, board_selected, options):
493 """Run the builder or show the summary
494
495 Args:
496 commits (list of Commit): List of commits being built, None if no branch
497 boards_selected (dict): Dict of selected boards:
498 key: target name
499 value: Board object
500 options (Options): Options to use
501
502 Returns:
503 int: Return code for buildman
504 """
a659b8dc
SG
505 gnu_make = command.output(os.path.join(options.git,
506 'scripts/show-gnu-make'), raise_on_error=False).rstrip()
507 if not gnu_make:
508 sys.exit('GNU Make not found')
509 builder.gnu_make = gnu_make
510
68f917c0 511 if not options.ide:
1d0c55d8
SG
512 commit_count = count_build_commits(commits, options.step)
513 tprint(get_action_summary(options.summary, commit_count, board_selected,
514 options.threads, options.jobs))
68f917c0
SG
515
516 builder.SetDisplayOptions(
517 options.show_errors, options.show_sizes, options.show_detail,
518 options.show_bloat, options.list_error_boards, options.show_config,
519 options.show_environment, options.filter_dtb_warnings,
520 options.filter_migration_warnings, options.ide)
521 if options.summary:
522 builder.ShowSummary(commits, board_selected)
523 else:
524 fail, warned, excs = builder.BuildBoards(
525 commits, board_selected, options.keep_outputs, options.verbose)
526 if excs:
527 return 102
528 if fail:
529 return 100
530 if warned and not options.ignore_warnings:
531 return 101
532 return 0
e48b946b 533
75584e1f
SG
534
535def calc_adjust_cfg(adjust_cfg, reproducible_builds):
536 """Calculate the value to use for adjust_cfg
537
538 Args:
539 adjust_cfg (list of str): List of configuration changes. See cfgutil for
540 details
541 reproducible_builds (bool): True to adjust the configuration to get
542 reproduceable builds
543
544 Returns:
545 adjust_cfg (list of str): List of configuration changes
546 """
547 adjust_cfg = cfgutil.convert_list_to_dict(adjust_cfg)
548
549 # Drop LOCALVERSION_AUTO since it changes the version string on every commit
550 if reproducible_builds:
551 # If these are mentioned, leave the local version alone
552 if 'LOCALVERSION' in adjust_cfg or 'LOCALVERSION_AUTO' in adjust_cfg:
553 print('Not dropping LOCALVERSION_AUTO for reproducible build')
554 else:
555 adjust_cfg['LOCALVERSION_AUTO'] = '~'
556 return adjust_cfg
557
558
9ef05b95
SG
559def do_buildman(options, args, toolchains=None, make_func=None, brds=None,
560 clean_dir=False, test_thread_exceptions=False):
fc3fe1c2
SG
561 """The main control code for buildman
562
563 Args:
564 options: Command line options object
565 args: Command line arguments (list of strings)
d4144e45
SG
566 toolchains: Toolchains to use - this should be a Toolchains()
567 object. If None, then it will be created and scanned
568 make_func: Make function to use for the builder. This is called
569 to execute 'make'. If this is None, the normal function
570 will be used, which calls the 'make' tool with suitable
571 arguments. This setting is useful for tests.
cc2c0d18 572 brds: Boards() object to use, containing a list of available
823e60b6 573 boards. If this is None it will be created and scanned.
24993313
SG
574 clean_dir: Used for tests only, indicates that the existing output_dir
575 should be removed before starting the build
8116c78f
SG
576 test_thread_exceptions: Uses for tests only, True to make the threads
577 raise an exception instead of reporting their result. This simulates
578 a failure in the code somewhere
fc3fe1c2 579 """
b8be2bd8
SG
580 # Used so testing can obtain the builder: pylint: disable=W0603
581 global TEST_BUILDER
883a321a 582
0157b187 583 gitutil.setup()
713bea38 584 col = terminal.Color()
fc3fe1c2 585
d230c014 586 git_dir = os.path.join(options.git, '.git')
fc3fe1c2 587
b8680646
SG
588 toolchains = get_toolchains(toolchains, col, options.override_toolchain,
589 options.fetch_arch, options.list_tool_chains,
590 options.verbose)
e48b946b
SG
591 output_dir = setup_output_dir(
592 options.output_dir, options.work_in_output, options.branch,
593 options.no_subdirs, col, clean_dir)
eb70a2c0 594
fc3fe1c2 595 # Work out what subset of the boards we are building
cc2c0d18 596 if not brds:
372b4458 597 brds = get_boards_obj(output_dir, options.regen_board_list,
180c718a
SG
598 options.maintainer_check, options.threads,
599 options.verbose)
600 if isinstance(brds, int):
601 return brds
3cf4ae6f 602
0d4874fc
SG
603 selected, why_selected, board_warnings = determine_boards(
604 brds, args, col, options.boards, options.exclude)
fc3fe1c2 605
4e9162d5 606 if options.print_prefix:
ea78233d 607 show_toolchain_prefix(brds, toolchains)
57cb9d52
SG
608 return 0
609
9df59e4c
SG
610 series = determine_series(selected, col, git_dir, options.count,
611 options.branch, options.work_in_output)
fc3fe1c2 612
168d792f 613 adjust_options(options, series, selected)
fc3fe1c2 614
168d792f
SG
615 # For a dry run, just show our actions as a sanity check
616 if options.dry_run:
617 show_actions(series, why_selected, selected, output_dir, board_warnings,
618 options.step, options.threads, options.jobs,
619 options.verbose)
620 return 0
fc3fe1c2 621
ffd06d3d 622 # Create a new builder with the selected options
d230c014 623 builder = Builder(toolchains, output_dir, git_dir,
a659b8dc 624 options.threads, options.jobs, checkout=True,
5971ab5c 625 show_unknown=options.show_unknown, step=options.step,
d2ce658d 626 no_subdirs=options.no_subdirs, full_path=options.full_path,
f79f1e0c 627 verbose_build=options.verbose_build,
eb70a2c0 628 mrproper=options.mrproper,
b50113f3 629 per_board_out_dir=options.per_board_out_dir,
b464f8e7 630 config_only=options.config_only,
2371d1bc 631 squash_config_y=not options.preserve_config_y,
d829f121 632 warnings_as_errors=options.warnings_as_errors,
8116c78f 633 work_in_output=options.work_in_output,
2b4806e4 634 test_thread_exceptions=test_thread_exceptions,
75584e1f
SG
635 adjust_cfg=calc_adjust_cfg(options.adjust_cfg,
636 options.reproducible_builds),
f6df5edc
SG
637 allow_missing=get_allow_missing(options.allow_missing,
638 options.no_allow_missing,
639 len(selected), options.branch),
640 no_lto=options.no_lto,
ffd06d3d
SG
641 reproducible_builds=options.reproducible_builds,
642 force_build = options.force_build,
643 force_build_failures = options.force_build_failures,
644 force_reconfig = options.force_reconfig, in_tree = options.in_tree,
645 force_config_on_failure=not options.quick, make_func=make_func)
646
b8be2bd8 647 TEST_BUILDER = builder
f0207d77 648
985d7ae4
SG
649 return run_builder(builder, series.commits if series else None,
650 brds.get_selected_dict(), options)
This page took 0.515538 seconds and 4 git commands to generate.