def ShowActions(series, why_selected, boards_selected, builder, options, board_warnings): """Display a list of actions that we would take, if not a dry run. Args: series: Series object why_selected: Dictionary where each key is a buildman argument provided by the user, and the value is the list of boards brought in by that argument. For example, 'arm' might bring in 400 boards, so in this case the key would be 'arm' and the value would be a list of board names. boards_selected: Dict of selected boards, key is target name, value is Board object builder: The builder that will be used to build the commits options: Command line options object board_warnings: List of warnings obtained from board selected """ col = terminal.Color() print('Dry run, so not doing much. But I would do this:') print() if series: commits = series.commits else: commits = None print(GetActionSummary(False, commits, boards_selected, options)) print('Build directory: %s' % builder.base_dir) if commits: for upto in range(0, len(series.commits), options.step): commit = series.commits[upto] print(' ', col.Color(col.YELLOW, commit.hash[:8], bright=False), end=' ') print(commit.subject) print() for arg in why_selected: if arg != 'all': print(arg, ': %d boards' % len(why_selected[arg])) if options.verbose: print(' %s' % ' '.join(why_selected[arg])) print(('Total boards to build for each commit: %d\n' % len(why_selected['all']))) if board_warnings: for warning in board_warnings: print(col.Color(col.YELLOW, warning))
def ShowActions(self, args, cmd, process_tags): """Show what actions we will/would perform Args: args: List of patch files we created cmd: The git command we would have run process_tags: Process tags as if they were aliases """ to_set = set(gitutil.BuildEmailList(self.to)) cc_set = set(gitutil.BuildEmailList(self.cc)) col = terminal.Color() print('Dry run, so not doing much. But I would do this:') print() print('Send a total of %d patch%s with %scover letter.' % (len(args), '' if len(args) == 1 else 'es', self.get('cover') and 'a ' or 'no ')) # TODO: Colour the patches according to whether they passed checks for upto in range(len(args)): commit = self.commits[upto] print(col.Color(col.GREEN, ' %s' % args[upto])) cc_list = list(self._generated_cc[commit.patch]) for email in set(cc_list) - to_set - cc_set: if email == None: email = col.Color(col.YELLOW, "<alias '%s' not found>" % tag) if email: print(' Cc: ', email) print for item in to_set: print('To:\t ', item) for item in cc_set - to_set: print('Cc:\t ', item) print('Version: ', self.get('version')) print('Prefix:\t ', self.get('prefix')) if self.cover: print('Cover: %d lines' % len(self.cover)) cover_cc = gitutil.BuildEmailList(self.get('cover_cc', '')) all_ccs = itertools.chain(cover_cc, *self._generated_cc.values()) for email in set(all_ccs) - to_set - cc_set: print(' Cc: ', email) if cmd: print('Git command: %s' % cmd)
def DoChecks(self): """Check that each version has a change log Print an error if something is wrong. """ col = terminal.Color() if self.get('version'): changes_copy = dict(self.changes) for version in range(2, int(self.version) + 1): if self.changes.get(version): del changes_copy[version] else: str = 'Change log missing for v%d' % version print col.Color(col.RED, str) for version in changes_copy: str = 'Change log for unknown version v%d' % version print col.Color(col.RED, str) elif self.changes: str = 'Change log exists, but no version is set' print col.Color(col.RED, str)
def ShowActions(self, args, cmd, process_tags): """Show what actions we will/would perform Args: args: List of patch files we created cmd: The git command we would have run process_tags: Process tags as if they were aliases """ col = terminal.Color() print 'Dry run, so not doing much. But I would do this:' print print 'Send a total of %d patch%s with %scover letter.' % ( len(args), '' if len(args) == 1 else 'es', self.get('cover') and 'a ' or 'no ') # TODO: Colour the patches according to whether they passed checks for upto in range(len(args)): commit = self.commits[upto] print col.Color(col.GREEN, ' %s' % args[upto]) cc_list = [] if process_tags: cc_list += gitutil.BuildEmailList(commit.tags) cc_list += gitutil.BuildEmailList(commit.cc_list) for email in cc_list: if email == None: email = col.Color(col.YELLOW, "<alias '%s' not found>" % tag) if email: print ' Cc: ', email print for item in gitutil.BuildEmailList(self.get('to', '<none>')): print 'To:\t ', item for item in gitutil.BuildEmailList(self.cc): print 'Cc:\t ', item print 'Version: ', self.get('version') print 'Prefix:\t ', self.get('prefix') if self.cover: print 'Cover: %d lines' % len(self.cover) if cmd: print 'Git command: %s' % cmd
def ApplyPatch(verbose, fname): """Apply a patch with git am to test it TODO: Convert these to use command, with stderr option Args: fname: filename of patch file to apply """ col = terminal.Color() cmd = ['git', 'am', fname] pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = pipe.communicate() re_error = re.compile('^error: patch failed: (.+):(\d+)') for line in stderr.splitlines(): if verbose: print line match = re_error.match(line) if match: print checkpatch.GetWarningMsg(col, 'warning', match.group(1), int(match.group(2)), 'Patch failed') return pipe.returncode == 0, stdout
def setUp(self): # Set up commits to build self.commits = [] sequence = 0 for commit_info in commits: comm = commit.Commit(commit_info[0]) comm.subject = commit_info[1] comm.return_code = commit_info[2] comm.error_list = commit_info[3] if sequence < 6: comm.error_list += [migration] comm.sequence = sequence sequence += 1 self.commits.append(comm) # Set up boards to build self.boards = board.Boards() for brd in boards: self.boards.AddBoard(board.Board(*brd)) self.boards.SelectBoards([]) # Add some test settings bsettings.Setup(None) bsettings.AddFile(settings_data) # Set up the toolchains self.toolchains = toolchain.Toolchains() self.toolchains.Add('arm-linux-gcc', test=False) self.toolchains.Add('sparc-linux-gcc', test=False) self.toolchains.Add('powerpc-linux-gcc', test=False) self.toolchains.Add('gcc', test=False) # Avoid sending any output terminal.SetPrintTestMode() self._col = terminal.Color() self.base_dir = tempfile.mkdtemp() if not os.path.isdir(self.base_dir): os.mkdir(self.base_dir)
def CheckPatches(verbose, args): '''Run the checkpatch.pl script on each patch''' error_count, warning_count, check_count = 0, 0, 0 col = terminal.Color() for fname in args: result = CheckPatch(fname, verbose) if not result.ok: error_count += result.errors warning_count += result.warnings check_count += result.checks print('%d errors, %d warnings, %d checks for %s:' % (result.errors, result.warnings, result.checks, col.Color(col.BLUE, fname))) if (len(result.problems) != result.errors + result.warnings + result.checks): print("Internal error: some problems lost") for item in result.problems: sys.stderr.write( GetWarningMsg(col, item.get('type', '<unknown>'), item.get('file', '<unknown>'), item.get('line', 0), item.get('msg', 'message'))) print #print(stdout) if error_count or warning_count or check_count: str = 'checkpatch.pl found %d error(s), %d warning(s), %d checks(s)' color = col.GREEN if warning_count: color = col.YELLOW if error_count: color = col.RED print(col.Color(color, str % (error_count, warning_count, check_count))) return False return True
def LookupEmail(lookup_name, alias=None, raise_on_error=True, level=0): """If an email address is an alias, look it up and return the full name TODO: Why not just use git's own alias feature? Args: lookup_name: Alias or email address to look up alias: Dictionary containing aliases (None to use settings default) raise_on_error: True to raise an error when an alias fails to match, False to just print a message. Returns: tuple: list containing a list of email addresses Raises: OSError if a recursive alias reference was found ValueError if an alias was not found >>> alias = {} >>> alias['fred'] = ['*****@*****.**'] >>> alias['john'] = ['*****@*****.**'] >>> alias['mary'] = ['*****@*****.**'] >>> alias['boys'] = ['fred', ' john', '*****@*****.**'] >>> alias['all'] = ['fred ', 'john', ' mary '] >>> alias['loop'] = ['other', 'john', ' mary '] >>> alias['other'] = ['loop', 'john', ' mary '] >>> LookupEmail('mary', alias) ['*****@*****.**'] >>> LookupEmail('*****@*****.**', alias) ['*****@*****.**'] >>> LookupEmail('boys', alias) ['*****@*****.**', '*****@*****.**'] >>> LookupEmail('all', alias) ['*****@*****.**', '*****@*****.**', '*****@*****.**'] >>> LookupEmail('odd', alias) Traceback (most recent call last): ... ValueError: Alias 'odd' not found >>> LookupEmail('loop', alias) Traceback (most recent call last): ... OSError: Recursive email alias at 'other' >>> LookupEmail('odd', alias, raise_on_error=False) \033[1;31mAlias 'odd' not found\033[0m [] >>> # In this case the loop part will effectively be ignored. >>> LookupEmail('loop', alias, raise_on_error=False) \033[1;31mRecursive email alias at 'other'\033[0m \033[1;31mRecursive email alias at 'john'\033[0m \033[1;31mRecursive email alias at 'mary'\033[0m ['*****@*****.**', '*****@*****.**'] """ if not alias: alias = settings.alias lookup_name = lookup_name.strip() if '@' in lookup_name: # Perhaps a real email address return [lookup_name] lookup_name = lookup_name.lower() col = terminal.Color() out_list = [] if level > 10: msg = "Recursive email alias at '%s'" % lookup_name if raise_on_error: raise OSError, msg else: print col.Color(col.RED, msg) return out_list if lookup_name: if not lookup_name in alias: msg = "Alias '%s' not found" % lookup_name if raise_on_error: raise ValueError, msg else: print col.Color(col.RED, msg) return out_list for item in alias[lookup_name]: todo = LookupEmail(item, alias, raise_on_error, level + 1) for new_item in todo: if not new_item in out_list: out_list.append(new_item) #print "No match for alias '%s'" % lookup_name return out_list
def DoBuildman(options, args): """The main control code for buildman Args: options: Command line options object args: Command line arguments (list of strings) """ gitutil.Setup() bsettings.Setup() options.git_dir = os.path.join(options.git, '.git') toolchains = toolchain.Toolchains() toolchains.Scan(options.list_tool_chains) if options.list_tool_chains: toolchains.List() print return # Work out how many commits to build. We want to build everything on the # branch. We also build the upstream commit as a control so we can see # problems introduced by the first commit on the branch. col = terminal.Color() count = options.count if count == -1: if not options.branch: str = 'Please use -b to specify a branch to build' print col.Color(col.RED, str) sys.exit(1) count = gitutil.CountCommitsInBranch(options.git_dir, options.branch) if count is None: str = "Branch '%s' not found or has no upstream" % options.branch print col.Color(col.RED, str) sys.exit(1) count += 1 # Build upstream commit also if not count: str = ("No commits found to process in branch '%s': " "set branch's upstream or use -c flag" % options.branch) print col.Color(col.RED, str) sys.exit(1) # Work out what subset of the boards we are building boards = board.Boards() boards.ReadBoards(os.path.join(options.git, 'boards.cfg')) why_selected = boards.SelectBoards(args) selected = boards.GetSelected() if not len(selected): print col.Color(col.RED, 'No matching boards found') sys.exit(1) # Read the metadata from the commits. First look at the upstream commit, # then the ones in the branch. We would like to do something like # upstream/master~..branch but that isn't possible if upstream/master is # a merge commit (it will list all the commits that form part of the # merge) range_expr = gitutil.GetRangeInBranch(options.git_dir, options.branch) upstream_commit = gitutil.GetUpstream(options.git_dir, options.branch) series = patchstream.GetMetaDataForList(upstream_commit, options.git_dir, 1) # Conflicting tags are not a problem for buildman, since it does not use # them. For example, Series-version is not useful for buildman. On the # other hand conflicting tags will cause an error. So allow later tags # to overwrite earlier ones. series.allow_overwrite = True series = patchstream.GetMetaDataForList(range_expr, options.git_dir, None, series) # By default we have one thread per CPU. But if there are not enough jobs # we can have fewer threads and use a high '-j' value for make. if not options.threads: options.threads = min(multiprocessing.cpu_count(), len(selected)) if not options.jobs: options.jobs = max(1, (multiprocessing.cpu_count() + len(selected) - 1) / len(selected)) if not options.step: options.step = len(series.commits) - 1 # Create a new builder with the selected options output_dir = os.path.join('..', options.branch) builder = Builder(toolchains, output_dir, options.git_dir, options.threads, options.jobs, checkout=True, show_unknown=options.show_unknown, step=options.step) builder.force_config_on_failure = not options.quick # For a dry run, just show our actions as a sanity check if options.dry_run: ShowActions(series, why_selected, selected, builder, options) else: builder.force_build = options.force_build # Work out which boards to build board_selected = boards.GetSelectedDict() print GetActionSummary(options.summary, count, board_selected, options) if options.summary: # We can't show function sizes without board details at present if options.show_bloat: options.show_detail = True builder.ShowSummary(series.commits, board_selected, options.show_errors, options.show_sizes, options.show_detail, options.show_bloat) else: builder.BuildBoards(series.commits, board_selected, options.show_errors, options.keep_outputs)
def testOutput(self): """Test basic builder operation and output This does a line-by-line verification of the summary output. """ global base_dir base_dir = tempfile.mkdtemp() if not os.path.isdir(base_dir): os.mkdir(base_dir) build = builder.Builder(self.toolchains, base_dir, None, 1, 2, checkout=False, show_unknown=False) build.do_make = self.Make board_selected = self.boards.GetSelectedDict() # Build the boards for the pre-defined commits and warnings/errors # associated with each. This calls our Make() to inject the fake output. build.BuildBoards(self.commits, board_selected, keep_outputs=False, verbose=False) lines = terminal.GetPrintTestLines() count = 0 for line in lines: if line.text.strip(): count += 1 # We should get two starting messages, then an update for every commit # built. self.assertEqual(count, len(commits) * len(boards) + 2) build.SetDisplayOptions(show_errors=True) build.ShowSummary(self.commits, board_selected) #terminal.EchoPrintTestLines() lines = terminal.GetPrintTestLines() # Upstream commit: no errors self.assertEqual(lines[0].text, '01: %s' % commits[0][1]) # Second commit: all archs should fail with warnings self.assertEqual(lines[1].text, '02: %s' % commits[1][1]) col = terminal.Color() self.assertSummary(lines[2].text, 'arm', 'w+', ['board1'], outcome=OUTCOME_WARN) self.assertSummary(lines[3].text, 'powerpc', 'w+', ['board2', 'board3'], outcome=OUTCOME_WARN) self.assertSummary(lines[4].text, 'sandbox', 'w+', ['board4'], outcome=OUTCOME_WARN) # Second commit: The warnings should be listed self.assertEqual(lines[5].text, 'w+%s' % errors[0].rstrip().replace('\n', '\nw+')) self.assertEqual(lines[5].colour, col.MAGENTA) # Third commit: Still fails self.assertEqual(lines[6].text, '03: %s' % commits[2][1]) self.assertSummary(lines[7].text, 'arm', '', ['board1'], outcome=OUTCOME_OK) self.assertSummary(lines[8].text, 'powerpc', '+', ['board2', 'board3']) self.assertSummary(lines[9].text, 'sandbox', '+', ['board4']) # Expect a compiler error self.assertEqual(lines[10].text, '+%s' % errors[1].rstrip().replace('\n', '\n+')) # Fourth commit: Compile errors are fixed, just have warning for board3 self.assertEqual(lines[11].text, '04: %s' % commits[3][1]) expect = '%10s: ' % 'powerpc' expect += ' ' + col.Color(col.GREEN, '') expect += ' ' expect += col.Color(col.GREEN, ' %s' % 'board2') expect += ' ' + col.Color(col.YELLOW, 'w+') expect += ' ' expect += col.Color(col.YELLOW, ' %s' % 'board3') self.assertEqual(lines[12].text, expect) self.assertSummary(lines[13].text, 'sandbox', 'w+', ['board4'], outcome=OUTCOME_WARN) # Compile error fixed self.assertEqual(lines[14].text, '-%s' % errors[1].rstrip().replace('\n', '\n-')) self.assertEqual(lines[14].colour, col.GREEN) self.assertEqual(lines[15].text, 'w+%s' % errors[2].rstrip().replace('\n', '\nw+')) self.assertEqual(lines[15].colour, col.MAGENTA) # Fifth commit self.assertEqual(lines[16].text, '05: %s' % commits[4][1]) self.assertSummary(lines[17].text, 'powerpc', '', ['board3'], outcome=OUTCOME_OK) self.assertSummary(lines[18].text, 'sandbox', '+', ['board4']) # The second line of errors[3] is a duplicate, so buildman will drop it expect = errors[3].rstrip().split('\n') expect = [expect[0]] + expect[2:] self.assertEqual(lines[19].text, '+%s' % '\n'.join(expect).replace('\n', '\n+')) self.assertEqual(lines[20].text, 'w-%s' % errors[2].rstrip().replace('\n', '\nw-')) # Sixth commit self.assertEqual(lines[21].text, '06: %s' % commits[5][1]) self.assertSummary(lines[22].text, 'sandbox', '', ['board4'], outcome=OUTCOME_OK) # The second line of errors[3] is a duplicate, so buildman will drop it expect = errors[3].rstrip().split('\n') expect = [expect[0]] + expect[2:] self.assertEqual(lines[23].text, '-%s' % '\n'.join(expect).replace('\n', '\n-')) self.assertEqual(lines[24].text, 'w-%s' % errors[0].rstrip().replace('\n', '\nw-')) # Seventh commit self.assertEqual(lines[25].text, '07: %s' % commits[6][1]) self.assertSummary(lines[26].text, 'sandbox', '+', ['board4']) # Pick out the correct error lines expect_str = errors[4].rstrip().replace('%(basedir)s', '').split('\n') expect = expect_str[3:8] + [expect_str[-1]] self.assertEqual(lines[27].text, '+%s' % '\n'.join(expect).replace('\n', '\n+')) # Now the warnings lines expect = [expect_str[0]] + expect_str[10:12] + [expect_str[9]] self.assertEqual(lines[28].text, 'w+%s' % '\n'.join(expect).replace('\n', '\nw+')) self.assertEqual(len(lines), 29) shutil.rmtree(base_dir)
def ApplyPatches(verbose, args, start_point): """Apply the patches with git am to make sure all is well Args: verbose: Print out 'git am' output verbatim args: List of patch files to apply start_point: Number of commits back from HEAD to start applying. Normally this is len(args), but it can be larger if a start offset was given. """ error_count = 0 col = terminal.Color() # Figure out our current position cmd = ['git', 'name-rev', 'HEAD', '--name-only'] pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE) stdout, stderr = pipe.communicate() if pipe.returncode: str = 'Could not find current commit name' print col.Color(col.RED, str) print stdout return False old_head = stdout.splitlines()[0] if old_head == 'undefined': str = "Invalid HEAD '%s'" % stdout.strip() print col.Color(col.RED, str) return False # Checkout the required start point cmd = ['git', 'checkout', 'HEAD~%d' % start_point] pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = pipe.communicate() if pipe.returncode: str = 'Could not move to commit before patch series' print col.Color(col.RED, str) print stdout, stderr return False # Apply all the patches for fname in args: ok, stdout = ApplyPatch(verbose, fname) if not ok: print col.Color( col.RED, 'git am returned errors for %s: will ' 'skip this patch' % fname) if verbose: print stdout error_count += 1 cmd = ['git', 'am', '--skip'] pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE) stdout, stderr = pipe.communicate() if pipe.returncode != 0: print col.Color(col.RED, 'Unable to skip patch! Aborting...') print stdout break # Return to our previous position cmd = ['git', 'checkout', old_head] pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = pipe.communicate() if pipe.returncode: print col.Color(col.RED, 'Could not move back to head commit') print stdout, stderr return error_count == 0
def testOutput(self): """Test basic builder operation and output This does a line-by-line verification of the summary output. """ global base_dir base_dir = tempfile.mkdtemp() if not os.path.isdir(base_dir): os.mkdir(base_dir) build = builder.Builder(self.toolchains, base_dir, None, 1, 2, checkout=False, show_unknown=False) build.do_make = self.Make board_selected = self.boards.GetSelectedDict() build.BuildBoards(self.commits, board_selected, keep_outputs=False, verbose=False) lines = terminal.GetPrintTestLines() count = 0 for line in lines: if line.text.strip(): count += 1 # We should get one starting message, then an update for every commit # built. self.assertEqual(count, len(commits) * len(boards) + 1) build.SetDisplayOptions(show_errors=True) build.ShowSummary(self.commits, board_selected) #terminal.EchoPrintTestLines() lines = terminal.GetPrintTestLines() self.assertEqual(lines[0].text, '01: %s' % commits[0][1]) self.assertEqual(lines[1].text, '02: %s' % commits[1][1]) # We expect all archs to fail col = terminal.Color() self.assertSummary(lines[2].text, 'sandbox', '+', ['board4']) self.assertSummary(lines[3].text, 'arm', '+', ['board1']) self.assertSummary(lines[4].text, 'powerpc', '+', ['board2', 'board3']) # Now we should have the compiler warning self.assertEqual(lines[5].text, 'w+%s' % errors[0].rstrip().replace('\n', '\nw+')) self.assertEqual(lines[5].colour, col.MAGENTA) self.assertEqual(lines[6].text, '03: %s' % commits[2][1]) self.assertSummary(lines[7].text, 'sandbox', '+', ['board4']) self.assertSummary(lines[8].text, 'arm', '', ['board1'], ok=True) self.assertSummary(lines[9].text, 'powerpc', '+', ['board2', 'board3']) # Compiler error self.assertEqual(lines[10].text, '+%s' % errors[1].rstrip().replace('\n', '\n+')) self.assertEqual(lines[11].text, '04: %s' % commits[3][1]) self.assertSummary(lines[12].text, 'sandbox', '', ['board4'], ok=True) self.assertSummary(lines[13].text, 'powerpc', '', ['board2', 'board3'], ok=True) # Compile error fixed self.assertEqual(lines[14].text, '-%s' % errors[1].rstrip().replace('\n', '\n-')) self.assertEqual(lines[14].colour, col.GREEN) self.assertEqual(lines[15].text, 'w+%s' % errors[2].rstrip().replace('\n', '\nw+')) self.assertEqual(lines[15].colour, col.MAGENTA) self.assertEqual(lines[16].text, '05: %s' % commits[4][1]) self.assertSummary(lines[17].text, 'sandbox', '+', ['board4']) self.assertSummary(lines[18].text, 'powerpc', '', ['board3'], ok=True) # The second line of errors[3] is a duplicate, so buildman will drop it expect = errors[3].rstrip().split('\n') expect = [expect[0]] + expect[2:] self.assertEqual(lines[19].text, '+%s' % '\n'.join(expect).replace('\n', '\n+')) self.assertEqual(lines[20].text, 'w-%s' % errors[2].rstrip().replace('\n', '\nw-')) self.assertEqual(lines[21].text, '06: %s' % commits[5][1]) self.assertSummary(lines[22].text, 'sandbox', '', ['board4'], ok=True) # The second line of errors[3] is a duplicate, so buildman will drop it expect = errors[3].rstrip().split('\n') expect = [expect[0]] + expect[2:] self.assertEqual(lines[23].text, '-%s' % '\n'.join(expect).replace('\n', '\n-')) self.assertEqual(lines[24].text, 'w-%s' % errors[0].rstrip().replace('\n', '\nw-')) self.assertEqual(lines[25].text, '07: %s' % commits[6][1]) self.assertSummary(lines[26].text, 'sandbox', '+', ['board4']) # Pick out the correct error lines expect_str = errors[4].rstrip().replace('%(basedir)s', '').split('\n') expect = expect_str[3:8] + [expect_str[-1]] self.assertEqual(lines[27].text, '+%s' % '\n'.join(expect).replace('\n', '\n+')) # Now the warnings lines expect = [expect_str[0]] + expect_str[10:12] + [expect_str[9]] self.assertEqual(lines[28].text, 'w+%s' % '\n'.join(expect).replace('\n', '\nw+')) self.assertEqual(len(lines), 29) shutil.rmtree(base_dir)
def __init__(self, toolchains, base_dir, git_dir, num_threads, num_jobs, gnu_make='make', checkout=True, show_unknown=True, step=1, no_subdirs=False, full_path=False, verbose_build=False): """Create a new Builder object Args: toolchains: Toolchains object to use for building base_dir: Base directory to use for builder git_dir: Git directory containing source repository num_threads: Number of builder threads to run num_jobs: Number of jobs to run at once (passed to make as -j) gnu_make: the command name of GNU Make. checkout: True to check out source, False to skip that step. This is used for testing. show_unknown: Show unknown boards (those not built) in summary step: 1 to process every commit, n to process every nth commit no_subdirs: Don't create subdirectories when building current source for a single board full_path: Return the full path in CROSS_COMPILE and don't set PATH verbose_build: Run build with V=1 and don't use 'make -s' """ self.toolchains = toolchains self.base_dir = base_dir self._working_dir = os.path.join(base_dir, '.bm-work') self.threads = [] self.active = True self.do_make = self.Make self.gnu_make = gnu_make self.checkout = checkout self.num_threads = num_threads self.num_jobs = num_jobs self.already_done = 0 self.force_build = False self.git_dir = git_dir self._show_unknown = show_unknown self._timestamp_count = 10 self._build_period_us = None self._complete_delay = None self._next_delay_update = datetime.now() self.force_config_on_failure = True self.force_build_failures = False self.force_reconfig = False self._step = step self.in_tree = False self._error_lines = 0 self.no_subdirs = no_subdirs self.full_path = full_path self.verbose_build = verbose_build self.col = terminal.Color() self._re_function = re.compile('(.*): In function.*') self._re_files = re.compile('In file included from.*') self._re_warning = re.compile('(.*):(\d*):(\d*): warning: .*') self._re_note = re.compile( '(.*):(\d*):(\d*): note: this is the location of the previous.*') self.queue = Queue.Queue() self.out_queue = Queue.Queue() for i in range(self.num_threads): t = builderthread.BuilderThread(self, i) t.setDaemon(True) t.start() self.threads.append(t) self.last_line_len = 0 t = builderthread.ResultThread(self) t.setDaemon(True) t.start() self.threads.append(t) ignore_lines = [ '(make.*Waiting for unfinished)', '(Segmentation fault)' ] self.re_make_err = re.compile('|'.join(ignore_lines))
elif options.full_help: pager = os.getenv('PAGER') if not pager: pager = 'more' fname = os.path.join(os.path.dirname(sys.argv[0]), 'README') command.Run(pager, fname) # Process commits, produce patches files, check them, email them else: gitutil.Setup() if options.count == -1: # Work out how many patches to send if we can options.count = gitutil.CountCommitsToBranch() - options.start col = terminal.Color() if not options.count: str = 'No commits found to process - please use -c flag' sys.exit(col.Color(col.RED, str)) # Read the metadata from the commits if options.count: series = patchstream.GetMetaData(options.start, options.count) cover_fname, args = gitutil.CreatePatches(options.start, options.count, series) # Fix up the patch files to our liking, and insert the cover letter series = patchstream.FixPatches(series, args) if series and cover_fname and series.get('cover'): patchstream.InsertCoverLetter(cover_fname, series, options.count)
def DoBuildman(options, args, toolchains=None, make_func=None, boards=None, clean_dir=False): """The main control code for buildman Args: options: Command line options object args: Command line arguments (list of strings) toolchains: Toolchains to use - this should be a Toolchains() object. If None, then it will be created and scanned make_func: Make function to use for the builder. This is called to execute 'make'. If this is None, the normal function will be used, which calls the 'make' tool with suitable arguments. This setting is useful for tests. board: Boards() object to use, containing a list of available boards. If this is None it will be created and scanned. """ global builder if options.full_help: pager = os.getenv('PAGER') if not pager: pager = 'more' fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), 'README') command.Run(pager, fname) return 0 gitutil.Setup() col = terminal.Color() options.git_dir = os.path.join(options.git, '.git') no_toolchains = toolchains is None if no_toolchains: toolchains = toolchain.Toolchains(options.override_toolchain) if options.fetch_arch: if options.fetch_arch == 'list': sorted_list = toolchains.ListArchs() print col.Color( col.BLUE, 'Available architectures: %s\n' % ' '.join(sorted_list)) return 0 else: fetch_arch = options.fetch_arch if fetch_arch == 'all': fetch_arch = ','.join(toolchains.ListArchs()) print col.Color(col.CYAN, '\nDownloading toolchains: %s' % fetch_arch) for arch in fetch_arch.split(','): print ret = toolchains.FetchAndInstall(arch) if ret: return ret return 0 if no_toolchains: toolchains.GetSettings() toolchains.Scan(options.list_tool_chains and options.verbose) if options.list_tool_chains: toolchains.List() print return 0 # Work out how many commits to build. We want to build everything on the # branch. We also build the upstream commit as a control so we can see # problems introduced by the first commit on the branch. count = options.count has_range = options.branch and '..' in options.branch if count == -1: if not options.branch: count = 1 else: if has_range: count, msg = gitutil.CountCommitsInRange( options.git_dir, options.branch) else: count, msg = gitutil.CountCommitsInBranch( options.git_dir, options.branch) if count is None: sys.exit(col.Color(col.RED, msg)) elif count == 0: sys.exit( col.Color(col.RED, "Range '%s' has no commits" % options.branch)) if msg: print col.Color(col.YELLOW, msg) count += 1 # Build upstream commit also if not count: str = ("No commits found to process in branch '%s': " "set branch's upstream or use -c flag" % options.branch) sys.exit(col.Color(col.RED, str)) # Work out what subset of the boards we are building if not boards: board_file = os.path.join(options.output_dir, 'boards.cfg') genboardscfg = os.path.join(options.git, 'tools/genboardscfg.py') status = subprocess.call([genboardscfg, '-o', board_file]) if status != 0: sys.exit("Failed to generate boards.cfg") boards = board.Boards() boards.ReadBoards(board_file) exclude = [] if options.exclude: for arg in options.exclude: exclude += arg.split(',') if options.boards: requested_boards = [] for b in options.boards: requested_boards += b.split(',') else: requested_boards = None why_selected, board_warnings = boards.SelectBoards(args, exclude, requested_boards) selected = boards.GetSelected() if not len(selected): sys.exit(col.Color(col.RED, 'No matching boards found')) # Read the metadata from the commits. First look at the upstream commit, # then the ones in the branch. We would like to do something like # upstream/master~..branch but that isn't possible if upstream/master is # a merge commit (it will list all the commits that form part of the # merge) # Conflicting tags are not a problem for buildman, since it does not use # them. For example, Series-version is not useful for buildman. On the # other hand conflicting tags will cause an error. So allow later tags # to overwrite earlier ones by setting allow_overwrite=True if options.branch: if count == -1: if has_range: range_expr = options.branch else: range_expr = gitutil.GetRangeInBranch(options.git_dir, options.branch) upstream_commit = gitutil.GetUpstream(options.git_dir, options.branch) series = patchstream.GetMetaDataForList(upstream_commit, options.git_dir, 1, series=None, allow_overwrite=True) series = patchstream.GetMetaDataForList(range_expr, options.git_dir, None, series, allow_overwrite=True) else: # Honour the count series = patchstream.GetMetaDataForList(options.branch, options.git_dir, count, series=None, allow_overwrite=True) else: series = None if not options.dry_run: options.verbose = True if not options.summary: options.show_errors = True # By default we have one thread per CPU. But if there are not enough jobs # we can have fewer threads and use a high '-j' value for make. if not options.threads: options.threads = min(multiprocessing.cpu_count(), len(selected)) if not options.jobs: options.jobs = max(1, (multiprocessing.cpu_count() + len(selected) - 1) / len(selected)) if not options.step: options.step = len(series.commits) - 1 gnu_make = command.Output(os.path.join(options.git, 'scripts/show-gnu-make'), raise_on_error=False).rstrip() if not gnu_make: sys.exit('GNU Make not found') # Create a new builder with the selected options. output_dir = options.output_dir if options.branch: dirname = options.branch.replace('/', '_') # As a special case allow the board directory to be placed in the # output directory itself rather than any subdirectory. if not options.no_subdirs: output_dir = os.path.join(options.output_dir, dirname) if clean_dir and os.path.exists(output_dir): shutil.rmtree(output_dir) CheckOutputDir(output_dir) builder = Builder(toolchains, output_dir, options.git_dir, options.threads, options.jobs, gnu_make=gnu_make, checkout=True, show_unknown=options.show_unknown, step=options.step, no_subdirs=options.no_subdirs, full_path=options.full_path, verbose_build=options.verbose_build, incremental=options.incremental, per_board_out_dir=options.per_board_out_dir, config_only=options.config_only, squash_config_y=not options.preserve_config_y, warnings_as_errors=options.warnings_as_errors) builder.force_config_on_failure = not options.quick if make_func: builder.do_make = make_func # For a dry run, just show our actions as a sanity check if options.dry_run: ShowActions(series, why_selected, selected, builder, options, board_warnings) else: builder.force_build = options.force_build builder.force_build_failures = options.force_build_failures builder.force_reconfig = options.force_reconfig builder.in_tree = options.in_tree # Work out which boards to build board_selected = boards.GetSelectedDict() if series: commits = series.commits # Number the commits for test purposes for commit in range(len(commits)): commits[commit].sequence = commit else: commits = None Print( GetActionSummary(options.summary, commits, board_selected, options)) # We can't show function sizes without board details at present if options.show_bloat: options.show_detail = True builder.SetDisplayOptions(options.show_errors, options.show_sizes, options.show_detail, options.show_bloat, options.list_error_boards, options.show_config, options.show_environment) if options.summary: builder.ShowSummary(commits, board_selected) else: fail, warned = builder.BuildBoards(commits, board_selected, options.keep_outputs, options.verbose) if fail: return 128 elif warned: return 129 return 0
def __init__(self, toolchains, base_dir, git_dir, num_threads, num_jobs, gnu_make='make', checkout=True, show_unknown=True, step=1): """Create a new Builder object Args: toolchains: Toolchains object to use for building base_dir: Base directory to use for builder git_dir: Git directory containing source repository num_threads: Number of builder threads to run num_jobs: Number of jobs to run at once (passed to make as -j) gnu_make: the command name of GNU Make. checkout: True to check out source, False to skip that step. This is used for testing. show_unknown: Show unknown boards (those not built) in summary step: 1 to process every commit, n to process every nth commit """ self.toolchains = toolchains self.base_dir = base_dir self._working_dir = os.path.join(base_dir, '.bm-work') self.threads = [] self.active = True self.do_make = self.Make self.gnu_make = gnu_make self.checkout = checkout self.num_threads = num_threads self.num_jobs = num_jobs self.already_done = 0 self.force_build = False self.git_dir = git_dir self._show_unknown = show_unknown self._timestamp_count = 10 self._build_period_us = None self._complete_delay = None self._next_delay_update = datetime.now() self.force_config_on_failure = True self.force_build_failures = False self.force_reconfig = False self._step = step self.in_tree = False self._error_lines = 0 self.col = terminal.Color() self.queue = Queue.Queue() self.out_queue = Queue.Queue() for i in range(self.num_threads): t = builderthread.BuilderThread(self, i) t.setDaemon(True) t.start() self.threads.append(t) self.last_line_len = 0 t = builderthread.ResultThread(self) t.setDaemon(True) t.start() self.threads.append(t) ignore_lines = [ '(make.*Waiting for unfinished)', '(Segmentation fault)' ] self.re_make_err = re.compile('|'.join(ignore_lines))
def test_property_raise(self): s = terminal.Color('text') print(s.unknown)
def test_unknown_bg(self): s = terminal.Color('text') print(s.unknown_bg)
def DoBuildman(options, args): """The main control code for buildman Args: options: Command line options object args: Command line arguments (list of strings) """ gitutil.Setup() bsettings.Setup(options.config_file) options.git_dir = os.path.join(options.git, '.git') toolchains = toolchain.Toolchains() toolchains.Scan(options.list_tool_chains) if options.list_tool_chains: toolchains.List() print return # Work out how many commits to build. We want to build everything on the # branch. We also build the upstream commit as a control so we can see # problems introduced by the first commit on the branch. col = terminal.Color() count = options.count if count == -1: if not options.branch: count = 1 else: count = gitutil.CountCommitsInBranch(options.git_dir, options.branch) if count is None: str = ("Branch '%s' not found or has no upstream" % options.branch) print col.Color(col.RED, str) sys.exit(1) count += 1 # Build upstream commit also if not count: str = ("No commits found to process in branch '%s': " "set branch's upstream or use -c flag" % options.branch) print col.Color(col.RED, str) sys.exit(1) # Work out what subset of the boards we are building board_file = os.path.join(options.git, 'boards.cfg') if not os.path.exists(board_file): print 'Could not find %s' % board_file status = subprocess.call( [os.path.join(options.git, 'tools/genboardscfg.py')]) if status != 0: print >> sys.stderr, "Failed to generate boards.cfg" sys.exit(1) boards = board.Boards() boards.ReadBoards(os.path.join(options.git, 'boards.cfg')) why_selected = boards.SelectBoards(args) selected = boards.GetSelected() if not len(selected): print col.Color(col.RED, 'No matching boards found') sys.exit(1) # Read the metadata from the commits. First look at the upstream commit, # then the ones in the branch. We would like to do something like # upstream/master~..branch but that isn't possible if upstream/master is # a merge commit (it will list all the commits that form part of the # merge) if options.branch: if count == -1: range_expr = gitutil.GetRangeInBranch(options.git_dir, options.branch) upstream_commit = gitutil.GetUpstream(options.git_dir, options.branch) series = patchstream.GetMetaDataForList(upstream_commit, options.git_dir, 1) # Conflicting tags are not a problem for buildman, since it does # not use them. For example, Series-version is not useful for # buildman. On the other hand conflicting tags will cause an # error. So allow later tags to overwrite earlier ones. series.allow_overwrite = True series = patchstream.GetMetaDataForList(range_expr, options.git_dir, None, series) else: # Honour the count series = patchstream.GetMetaDataForList(options.branch, options.git_dir, count) else: series = None options.verbose = True options.show_errors = True # By default we have one thread per CPU. But if there are not enough jobs # we can have fewer threads and use a high '-j' value for make. if not options.threads: options.threads = min(multiprocessing.cpu_count(), len(selected)) if not options.jobs: options.jobs = max(1, (multiprocessing.cpu_count() + len(selected) - 1) / len(selected)) if not options.step: options.step = len(series.commits) - 1 gnu_make = command.Output( os.path.join(options.git, 'scripts/show-gnu-make')).rstrip() if not gnu_make: print >> sys.stderr, 'GNU Make not found' sys.exit(1) # Create a new builder with the selected options if options.branch: dirname = options.branch else: dirname = 'current' output_dir = os.path.join(options.output_dir, dirname) builder = Builder(toolchains, output_dir, options.git_dir, options.threads, options.jobs, gnu_make=gnu_make, checkout=True, show_unknown=options.show_unknown, step=options.step) builder.force_config_on_failure = not options.quick # For a dry run, just show our actions as a sanity check if options.dry_run: ShowActions(series, why_selected, selected, builder, options) else: builder.force_build = options.force_build builder.force_build_failures = options.force_build_failures builder.force_reconfig = options.force_reconfig builder.in_tree = options.in_tree # Work out which boards to build board_selected = boards.GetSelectedDict() if series: commits = series.commits else: commits = None print GetActionSummary(options.summary, commits, board_selected, options) builder.SetDisplayOptions(options.show_errors, options.show_sizes, options.show_detail, options.show_bloat) if options.summary: # We can't show function sizes without board details at present if options.show_bloat: options.show_detail = True builder.ShowSummary(commits, board_selected) else: builder.BuildBoards(commits, board_selected, options.keep_outputs, options.verbose)
def test_radd_raise(self): foo = terminal.Color('foo') print(1 + foo.green)
def test_repr(self): foo = terminal.Color('foo') foo.fgcolor = 'red' assert repr(foo) == repr(str(foo))
def _CheckOutput(self, lines, list_error_boards=False, filter_dtb_warnings=False, filter_migration_warnings=False): """Check for expected output from the build summary Args: lines: Iterator containing the lines returned from the summary list_error_boards: Adjust the check for output produced with the --list-error-boards flag filter_dtb_warnings: Adjust the check for output produced with the --filter-dtb-warnings flag """ def add_line_prefix(prefix, boards, error_str, colour): """Add a prefix to each line of a string The training \n in error_str is removed before processing Args: prefix: String prefix to add error_str: Error string containing the lines colour: Expected colour for the line. Note that the board list, if present, always appears in magenta Returns: New string where each line has the prefix added """ lines = error_str.strip().splitlines() new_lines = [] for line in lines: if boards: expect = self._col.Color(colour, prefix + '(') expect += self._col.Color(self._col.MAGENTA, boards, bright=False) expect += self._col.Color(colour, ') %s' % line) else: expect = self._col.Color(colour, prefix + line) new_lines.append(expect) return '\n'.join(new_lines) col = terminal.Color() boards01234 = ('board0 board1 board2 board3 board4' if list_error_boards else '') boards1234 = 'board1 board2 board3 board4' if list_error_boards else '' boards234 = 'board2 board3 board4' if list_error_boards else '' boards34 = 'board3 board4' if list_error_boards else '' boards4 = 'board4' if list_error_boards else '' # Upstream commit: migration warnings only self.assertEqual(next(lines).text, '01: %s' % commits[0][1]) if not filter_migration_warnings: self.assertSummary(next(lines).text, 'arm', 'w+', ['board0', 'board1'], outcome=OUTCOME_WARN) self.assertSummary(next(lines).text, 'powerpc', 'w+', ['board2', 'board3'], outcome=OUTCOME_WARN) self.assertSummary(next(lines).text, 'sandbox', 'w+', ['board4'], outcome=OUTCOME_WARN) self.assertEqual( next(lines).text, add_line_prefix('+', boards01234, migration, col.RED)) # Second commit: all archs should fail with warnings self.assertEqual(next(lines).text, '02: %s' % commits[1][1]) if filter_migration_warnings: self.assertSummary(next(lines).text, 'arm', 'w+', ['board1'], outcome=OUTCOME_WARN) self.assertSummary(next(lines).text, 'powerpc', 'w+', ['board2', 'board3'], outcome=OUTCOME_WARN) self.assertSummary(next(lines).text, 'sandbox', 'w+', ['board4'], outcome=OUTCOME_WARN) # Second commit: The warnings should be listed self.assertEqual( next(lines).text, add_line_prefix('w+', boards1234, errors[0], col.YELLOW)) # Third commit: Still fails self.assertEqual(next(lines).text, '03: %s' % commits[2][1]) if filter_migration_warnings: self.assertSummary(next(lines).text, 'arm', '', ['board1'], outcome=OUTCOME_OK) self.assertSummary( next(lines).text, 'powerpc', '+', ['board2', 'board3']) self.assertSummary(next(lines).text, 'sandbox', '+', ['board4']) # Expect a compiler error self.assertEqual( next(lines).text, add_line_prefix('+', boards234, errors[1], col.RED)) # Fourth commit: Compile errors are fixed, just have warning for board3 self.assertEqual(next(lines).text, '04: %s' % commits[3][1]) if filter_migration_warnings: expect = '%10s: ' % 'powerpc' expect += ' ' + col.Color(col.GREEN, '') expect += ' ' expect += col.Color(col.GREEN, ' %s' % 'board2') expect += ' ' + col.Color(col.YELLOW, 'w+') expect += ' ' expect += col.Color(col.YELLOW, ' %s' % 'board3') self.assertEqual(next(lines).text, expect) else: self.assertSummary(next(lines).text, 'powerpc', 'w+', ['board2', 'board3'], outcome=OUTCOME_WARN) self.assertSummary(next(lines).text, 'sandbox', 'w+', ['board4'], outcome=OUTCOME_WARN) # Compile error fixed self.assertEqual( next(lines).text, add_line_prefix('-', boards234, errors[1], col.GREEN)) if not filter_dtb_warnings: self.assertEqual( next(lines).text, add_line_prefix('w+', boards34, errors[2], col.YELLOW)) # Fifth commit self.assertEqual(next(lines).text, '05: %s' % commits[4][1]) if filter_migration_warnings: self.assertSummary(next(lines).text, 'powerpc', '', ['board3'], outcome=OUTCOME_OK) self.assertSummary(next(lines).text, 'sandbox', '+', ['board4']) # The second line of errors[3] is a duplicate, so buildman will drop it expect = errors[3].rstrip().split('\n') expect = [expect[0]] + expect[2:] expect = '\n'.join(expect) self.assertEqual( next(lines).text, add_line_prefix('+', boards4, expect, col.RED)) if not filter_dtb_warnings: self.assertEqual( next(lines).text, add_line_prefix('w-', boards34, errors[2], col.CYAN)) # Sixth commit self.assertEqual(next(lines).text, '06: %s' % commits[5][1]) if filter_migration_warnings: self.assertSummary(next(lines).text, 'sandbox', '', ['board4'], outcome=OUTCOME_OK) else: self.assertSummary(next(lines).text, 'sandbox', 'w+', ['board4'], outcome=OUTCOME_WARN) # The second line of errors[3] is a duplicate, so buildman will drop it expect = errors[3].rstrip().split('\n') expect = [expect[0]] + expect[2:] expect = '\n'.join(expect) self.assertEqual( next(lines).text, add_line_prefix('-', boards4, expect, col.GREEN)) self.assertEqual( next(lines).text, add_line_prefix('w-', boards4, errors[0], col.CYAN)) # Seventh commit self.assertEqual(next(lines).text, '07: %s' % commits[6][1]) if filter_migration_warnings: self.assertSummary(next(lines).text, 'sandbox', '+', ['board4']) else: self.assertSummary(next(lines).text, 'arm', '', ['board0', 'board1'], outcome=OUTCOME_OK) self.assertSummary(next(lines).text, 'powerpc', '', ['board2', 'board3'], outcome=OUTCOME_OK) self.assertSummary(next(lines).text, 'sandbox', '+', ['board4']) # Pick out the correct error lines expect_str = errors[4].rstrip().replace('%(basedir)s', '').split('\n') expect = expect_str[3:8] + [expect_str[-1]] expect = '\n'.join(expect) if not filter_migration_warnings: self.assertEqual( next(lines).text, add_line_prefix('-', boards01234, migration, col.GREEN)) self.assertEqual( next(lines).text, add_line_prefix('+', boards4, expect, col.RED)) # Now the warnings lines expect = [expect_str[0]] + expect_str[10:12] + [expect_str[9]] expect = '\n'.join(expect) self.assertEqual( next(lines).text, add_line_prefix('w+', boards4, expect, col.YELLOW))