X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;f=tools%2Fbuildman%2Fbuilder.py;h=a5a2ffdfdf2c76d86fb78d765d9977245da652ac;hb=48ae412424ddfda64f45a9d679a960357e57433e;hp=141bf6469136aa7f5f18bb6e07701dfcfd5d135d;hpb=b1fc3c56d2dc502e7a32afeea5282a0d795920e2;p=u-boot diff --git a/tools/buildman/builder.py b/tools/buildman/builder.py index 141bf64691..a5a2ffdfdf 100644 --- a/tools/buildman/builder.py +++ b/tools/buildman/builder.py @@ -1,9 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0+ # Copyright (c) 2013 The Chromium OS Authors. # # Bloat-o-meter code used here Copyright 2004 Matt Mackall # -# SPDX-License-Identifier: GPL-2.0+ -# import collections from datetime import datetime, timedelta @@ -12,8 +11,10 @@ import os import re import Queue import shutil +import signal import string import sys +import threading import time import builderthread @@ -93,22 +94,26 @@ u-boot/ source directory # Possible build outcomes OUTCOME_OK, OUTCOME_WARNING, OUTCOME_ERROR, OUTCOME_UNKNOWN = range(4) -# Translate a commit subject into a valid filename -trans_valid_chars = string.maketrans("/: ", "---") +# Translate a commit subject into a valid filename (and handle unicode) +trans_valid_chars = string.maketrans('/: ', '---') +trans_valid_chars = trans_valid_chars.decode('latin-1') -CONFIG_FILENAMES = [ +BASE_CONFIG_FILENAMES = [ + 'u-boot.cfg', 'u-boot-spl.cfg', 'u-boot-tpl.cfg' +] + +EXTRA_CONFIG_FILENAMES = [ '.config', '.config-spl', '.config-tpl', 'autoconf.mk', 'autoconf-spl.mk', 'autoconf-tpl.mk', 'autoconf.h', 'autoconf-spl.h','autoconf-tpl.h', - 'u-boot.cfg', 'u-boot-spl.cfg', 'u-boot-tpl.cfg' ] class Config: """Holds information about configuration settings for a board.""" - def __init__(self, target): + def __init__(self, config_filename, target): self.target = target self.config = {} - for fname in CONFIG_FILENAMES: + for fname in config_filename: self.config[fname] = {} def Add(self, fname, key, value): @@ -122,11 +127,19 @@ class Config: val = val ^ hash(key) & hash(value) return val +class Environment: + """Holds information about environment variables for a board.""" + def __init__(self, target): + self.target = target + self.environment = {} + + def Add(self, key, value): + self.environment[key] = value + class Builder: """Class for building U-Boot for a particular commit. Public members: (many should ->private) - active: True if the builder is active and has not been stopped already_done: Number of builds already completed base_dir: Base directory to use for builder checkout: True to check out source, False to skip that step. @@ -195,17 +208,24 @@ class Builder: value is itself a dictionary: key: config name value: config value + environment: Dictionary keyed by environment variable, Each + value is the value of environment variable. """ - def __init__(self, rc, err_lines, sizes, func_sizes, config): + def __init__(self, rc, err_lines, sizes, func_sizes, config, + environment): self.rc = rc self.err_lines = err_lines self.sizes = sizes self.func_sizes = func_sizes self.config = config + self.environment = environment 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): + no_subdirs=False, full_path=False, verbose_build=False, + incremental=False, per_board_out_dir=False, + config_only=False, squash_config_y=False, + warnings_as_errors=False): """Create a new Builder object Args: @@ -224,12 +244,18 @@ class Builder: 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' + incremental: Always perform incremental builds; don't run make + mrproper when configuring + per_board_out_dir: Build in a separate persistent directory per + board rather than a thread-specific directory + config_only: Only configure each build, don't build it + squash_config_y: Convert CONFIG options with the value 'y' to '1' + warnings_as_errors: Treat all compiler warnings as errors """ 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 @@ -252,7 +278,13 @@ class Builder: self.no_subdirs = no_subdirs self.full_path = full_path self.verbose_build = verbose_build + self.config_only = config_only + self.squash_config_y = squash_config_y + self.config_filenames = BASE_CONFIG_FILENAMES + if not self.squash_config_y: + self.config_filenames += EXTRA_CONFIG_FILENAMES + self.warnings_as_errors = warnings_as_errors self.col = terminal.Color() self._re_function = re.compile('(.*): In function.*') @@ -263,7 +295,8 @@ class Builder: self.queue = Queue.Queue() self.out_queue = Queue.Queue() for i in range(self.num_threads): - t = builderthread.BuilderThread(self, i) + t = builderthread.BuilderThread(self, i, incremental, + per_board_out_dir) t.setDaemon(True) t.start() self.threads.append(t) @@ -277,14 +310,21 @@ class Builder: ignore_lines = ['(make.*Waiting for unfinished)', '(Segmentation fault)'] self.re_make_err = re.compile('|'.join(ignore_lines)) + # Handle existing graceful with SIGINT / Ctrl-C + signal.signal(signal.SIGINT, self.signal_handler) + def __del__(self): """Get rid of all threads created by the builder""" for t in self.threads: del t + def signal_handler(self, signal, frame): + sys.exit(1) + def SetDisplayOptions(self, show_errors=False, show_sizes=False, show_detail=False, show_bloat=False, - list_error_boards=False, show_config=False): + list_error_boards=False, show_config=False, + show_environment=False): """Setup display options for the builder. show_errors: True to show summarised error/warning info @@ -293,6 +333,7 @@ class Builder: show_bloat: Show detail for each function list_error_boards: Show the boards which caused each error/warning show_config: Show config deltas + show_environment: Show environment deltas """ self._show_errors = show_errors self._show_sizes = show_sizes @@ -300,6 +341,7 @@ class Builder: self._show_bloat = show_bloat self._list_error_boards = list_error_boards self._show_config = show_config + self._show_environment = show_environment def _AddTimestamp(self): """Add a new timestamp to the list and record the build period. @@ -383,11 +425,6 @@ class Builder: if result: target = result.brd.target - if result.return_code < 0: - self.active = False - command.StopAll() - return - self.upto += 1 if result.return_code != 0: self.fail += 1 @@ -425,7 +462,7 @@ class Builder: name += target Print(line + name, newline=False) - length = 14 + len(name) + length = 16 + len(name) self.ClearLine(length) def _GetOutputDir(self, commit_upto): @@ -576,18 +613,45 @@ class Builder: key, value = values else: key = values[0] - value = '' + value = '1' if self.squash_config_y else '' if not key.startswith('CONFIG_'): continue elif not line or line[0] in ['#', '*', '/']: continue else: key, value = line.split('=', 1) + if self.squash_config_y and value == 'y': + value = '1' config[key] = value return config + def _ProcessEnvironment(self, fname): + """Read in a uboot.env file + + This function reads in environment variables from a file. + + Args: + fname: Filename to read + + Returns: + Dictionary: + key: environment variable (e.g. bootlimit) + value: value of environment variable (e.g. 1) + """ + environment = {} + if os.path.exists(fname): + with open(fname) as fd: + for line in fd.read().split('\0'): + try: + key, value = line.split('=', 1) + environment[key] = value + except ValueError: + # ignore lines we can't parse + pass + return environment + def GetBuildOutcome(self, commit_upto, target, read_func_sizes, - read_config): + read_config, read_environment): """Work out the outcome of a build. Args: @@ -595,6 +659,7 @@ class Builder: target: Target board to check read_func_sizes: True to read function size information read_config: True to read .config and autoconf.h files + read_environment: True to read uboot.env files Returns: Outcome object @@ -604,6 +669,7 @@ class Builder: sizes = {} func_sizes = {} config = {} + environment = {} if os.path.exists(done_file): with open(done_file, 'r') as fd: return_code = int(fd.readline()) @@ -649,16 +715,22 @@ class Builder: if read_config: output_dir = self.GetBuildDir(commit_upto, target) - for name in CONFIG_FILENAMES: + for name in self.config_filenames: fname = os.path.join(output_dir, name) config[name] = self._ProcessConfig(fname) - return Builder.Outcome(rc, err_lines, sizes, func_sizes, config) + if read_environment: + output_dir = self.GetBuildDir(commit_upto, target) + fname = os.path.join(output_dir, 'uboot.env') + environment = self._ProcessEnvironment(fname) + + return Builder.Outcome(rc, err_lines, sizes, func_sizes, config, + environment) - return Builder.Outcome(OUTCOME_UNKNOWN, [], {}, {}, {}) + return Builder.Outcome(OUTCOME_UNKNOWN, [], {}, {}, {}, {}) def GetResultSummary(self, boards_selected, commit_upto, read_func_sizes, - read_config): + read_config, read_environment): """Calculate a summary of the results of building a commit. Args: @@ -666,6 +738,7 @@ class Builder: commit_upto: Commit number to summarize (0..self.count-1) read_func_sizes: True to read function size information read_config: True to read .config and autoconf.h files + read_environment: True to read uboot.env files Returns: Tuple: @@ -682,6 +755,9 @@ class Builder: value is itself a dictionary: key: config name value: config value + Dictionary keyed by board.target. Each value is a dictionary: + key: environment variable + value: value of environment variable """ def AddLine(lines_summary, lines_boards, line, board): line = line.rstrip() @@ -697,10 +773,12 @@ class Builder: warn_lines_summary = [] warn_lines_boards = {} config = {} + environment = {} for board in boards_selected.itervalues(): outcome = self.GetBuildOutcome(commit_upto, board.target, - read_func_sizes, read_config) + read_func_sizes, read_config, + read_environment) board_dict[board.target] = outcome last_func = None last_was_warning = False @@ -726,15 +804,21 @@ class Builder: line, board) last_was_warning = is_warning last_func = None - tconfig = Config(board.target) - for fname in CONFIG_FILENAMES: + tconfig = Config(self.config_filenames, board.target) + for fname in self.config_filenames: if outcome.config: for key, value in outcome.config[fname].iteritems(): tconfig.Add(fname, key, value) config[board.target] = tconfig + tenvironment = Environment(board.target) + if outcome.environment: + for key, value in outcome.environment.iteritems(): + tenvironment.Add(key, value) + environment[board.target] = tenvironment + return (board_dict, err_lines_summary, err_lines_boards, - warn_lines_summary, warn_lines_boards, config) + warn_lines_summary, warn_lines_boards, config, environment) def AddOutcome(self, board_dict, arch_list, changes, char, color): """Add an output to our list of outcomes for each architecture @@ -787,12 +871,14 @@ class Builder: """ self._base_board_dict = {} for board in board_selected: - self._base_board_dict[board] = Builder.Outcome(0, [], [], {}, {}) + self._base_board_dict[board] = Builder.Outcome(0, [], [], {}, {}, + {}) self._base_err_lines = [] self._base_warn_lines = [] self._base_err_line_boards = {} self._base_warn_line_boards = {} self._base_config = None + self._base_environment = None def PrintFuncSizeDetail(self, fname, old, new): grow, shrink, add, remove, up, down = 0, 0, 0, 0, 0, 0 @@ -826,7 +912,7 @@ class Builder: delta.reverse() args = [add, -remove, grow, -shrink, up, -down, up - down] - if max(args) == 0: + if max(args) == 0 and min(args) == 0: return args = [self.ColourNum(x) for x in args] indent = ' ' * 15 @@ -987,8 +1073,8 @@ class Builder: def PrintResultSummary(self, board_selected, board_dict, err_lines, err_line_boards, warn_lines, warn_line_boards, - config, show_sizes, show_detail, show_bloat, - show_config): + config, environment, show_sizes, show_detail, + show_bloat, show_config, show_environment): """Compare results with the base results and display delta. Only boards mentioned in board_selected will be considered. This @@ -1013,10 +1099,13 @@ class Builder: value is itself a dictionary: key: config name value: config value + environment: Dictionary keyed by environment variable, Each + value is the value of environment variable. show_sizes: Show image size deltas show_detail: Show detail for each board show_bloat: Show detail for each function show_config: Show config changes + show_environment: Show environment changes """ def _BoardList(line, line_boards): """Helper function to get a line of boards containing a line @@ -1165,6 +1254,36 @@ class Builder: self.PrintSizeSummary(board_selected, board_dict, show_detail, show_bloat) + if show_environment and self._base_environment: + lines = [] + + for target in board_dict: + if target not in board_selected: + continue + + tbase = self._base_environment[target] + tenvironment = environment[target] + environment_plus = {} + environment_minus = {} + environment_change = {} + base = tbase.environment + for key, value in tenvironment.environment.iteritems(): + if key not in base: + environment_plus[key] = value + for key, value in base.iteritems(): + if key not in tenvironment.environment: + environment_minus[key] = value + for key, value in base.iteritems(): + new_value = tenvironment.environment.get(key) + if new_value and value != new_value: + desc = '%s -> %s' % (value, new_value) + environment_change[key] = desc + + _AddConfig(lines, target, environment_plus, environment_minus, + environment_change) + + _OutputConfigInfo(lines) + if show_config and self._base_config: summary = {} arch_config_plus = {} @@ -1183,7 +1302,7 @@ class Builder: arch_config_plus[arch] = {} arch_config_minus[arch] = {} arch_config_change[arch] = {} - for name in CONFIG_FILENAMES: + for name in self.config_filenames: arch_config_plus[arch][name] = {} arch_config_minus[arch][name] = {} arch_config_change[arch][name] = {} @@ -1200,7 +1319,7 @@ class Builder: tbase = self._base_config[target] tconfig = config[target] lines = [] - for name in CONFIG_FILENAMES: + for name in self.config_filenames: if not tconfig.config[name]: continue config_plus = {} @@ -1244,7 +1363,7 @@ class Builder: all_plus = {} all_minus = {} all_change = {} - for name in CONFIG_FILENAMES: + for name in self.config_filenames: all_plus.update(arch_config_plus[arch][name]) all_minus.update(arch_config_minus[arch][name]) all_change.update(arch_config_change[arch][name]) @@ -1271,6 +1390,7 @@ class Builder: self._base_err_line_boards = err_line_boards self._base_warn_line_boards = warn_line_boards self._base_config = config + self._base_environment = environment # Get a list of boards that did not get built, if needed not_built = [] @@ -1283,10 +1403,11 @@ class Builder: def ProduceResultSummary(self, commit_upto, commits, board_selected): (board_dict, err_lines, err_line_boards, warn_lines, - warn_line_boards, config) = self.GetResultSummary( + warn_line_boards, config, environment) = self.GetResultSummary( board_selected, commit_upto, read_func_sizes=self._show_bloat, - read_config=self._show_config) + read_config=self._show_config, + read_environment=self._show_environment) if commits: msg = '%02d: %s' % (commit_upto + 1, commits[commit_upto].subject) @@ -1294,8 +1415,8 @@ class Builder: self.PrintResultSummary(board_selected, board_dict, err_lines if self._show_errors else [], err_line_boards, warn_lines if self._show_errors else [], warn_line_boards, - config, self._show_sizes, self._show_detail, - self._show_bloat, self._show_config) + config, environment, self._show_sizes, self._show_detail, + self._show_bloat, self._show_config, self._show_environment) def ShowSummary(self, commits, board_selected): """Show a build summary for U-Boot for a given board list. @@ -1360,8 +1481,10 @@ class Builder: if os.path.exists(git_dir): gitutil.Fetch(git_dir, thread_dir) else: - Print('Cloning repo for thread %d' % thread_num) + Print('\rCloning repo for thread %d' % thread_num, + newline=False) gitutil.Clone(src_dir, thread_dir) + Print('\r%s\r' % (' ' * 30), newline=False) def _PrepareWorkingSpace(self, max_threads, setup_git): """Prepare the working directory for use. @@ -1389,8 +1512,14 @@ class Builder: for commit_upto in range(self.commit_count): dir_list.append(self._GetOutputDir(commit_upto)) + to_remove = [] for dirname in glob.glob(os.path.join(self.base_dir, '*')): if dirname not in dir_list: + to_remove.append(dirname) + if to_remove: + Print('Removing %d old build directories' % len(to_remove), + newline=False) + for dirname in to_remove: shutil.rmtree(dirname) def BuildBoards(self, commits, board_selected, keep_outputs, verbose): @@ -1416,6 +1545,7 @@ class Builder: self._PrepareWorkingSpace(min(self.num_threads, len(board_selected)), commits is not None) self._PrepareOutputSpace() + Print('\rStarting build...', newline=False) self.SetupBuild(board_selected, commits) self.ProcessResult(None) @@ -1428,8 +1558,11 @@ class Builder: job.step = self._step self.queue.put(job) - # Wait until all jobs are started - self.queue.join() + term = threading.Thread(target=self.queue.join) + term.setDaemon(True) + term.start() + while term.isAlive(): + term.join(100) # Wait until we have processed all output self.out_queue.join()