1*fc3fe1c2SSimon Glass# Copyright (c) 2013 The Chromium OS Authors. 2*fc3fe1c2SSimon Glass# 3*fc3fe1c2SSimon Glass# Bloat-o-meter code used here Copyright 2004 Matt Mackall <mpm@selenic.com> 4*fc3fe1c2SSimon Glass# 5*fc3fe1c2SSimon Glass# See file CREDITS for list of people who contributed to this 6*fc3fe1c2SSimon Glass# project. 7*fc3fe1c2SSimon Glass# 8*fc3fe1c2SSimon Glass# This program is free software; you can redistribute it and/or 9*fc3fe1c2SSimon Glass# modify it under the terms of the GNU General Public License as 10*fc3fe1c2SSimon Glass# published by the Free Software Foundation; either version 2 of 11*fc3fe1c2SSimon Glass# the License, or (at your option) any later version. 12*fc3fe1c2SSimon Glass# 13*fc3fe1c2SSimon Glass# This program is distributed in the hope that it will be useful, 14*fc3fe1c2SSimon Glass# but WITHOUT ANY WARRANTY; without even the implied warranty of 15*fc3fe1c2SSimon Glass# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16*fc3fe1c2SSimon Glass# GNU General Public License for more details. 17*fc3fe1c2SSimon Glass# 18*fc3fe1c2SSimon Glass# You should have received a copy of the GNU General Public License 19*fc3fe1c2SSimon Glass# along with this program; if not, write to the Free Software 20*fc3fe1c2SSimon Glass# Foundation, Inc., 59 Temple Place, Suite 330, Boston, 21*fc3fe1c2SSimon Glass# MA 02111-1307 USA 22*fc3fe1c2SSimon Glass# 23*fc3fe1c2SSimon Glass 24*fc3fe1c2SSimon Glassimport collections 25*fc3fe1c2SSimon Glassimport errno 26*fc3fe1c2SSimon Glassfrom datetime import datetime, timedelta 27*fc3fe1c2SSimon Glassimport glob 28*fc3fe1c2SSimon Glassimport os 29*fc3fe1c2SSimon Glassimport re 30*fc3fe1c2SSimon Glassimport Queue 31*fc3fe1c2SSimon Glassimport shutil 32*fc3fe1c2SSimon Glassimport string 33*fc3fe1c2SSimon Glassimport sys 34*fc3fe1c2SSimon Glassimport threading 35*fc3fe1c2SSimon Glassimport time 36*fc3fe1c2SSimon Glass 37*fc3fe1c2SSimon Glassimport command 38*fc3fe1c2SSimon Glassimport gitutil 39*fc3fe1c2SSimon Glassimport terminal 40*fc3fe1c2SSimon Glassimport toolchain 41*fc3fe1c2SSimon Glass 42*fc3fe1c2SSimon Glass 43*fc3fe1c2SSimon Glass""" 44*fc3fe1c2SSimon GlassTheory of Operation 45*fc3fe1c2SSimon Glass 46*fc3fe1c2SSimon GlassPlease see README for user documentation, and you should be familiar with 47*fc3fe1c2SSimon Glassthat before trying to make sense of this. 48*fc3fe1c2SSimon Glass 49*fc3fe1c2SSimon GlassBuildman works by keeping the machine as busy as possible, building different 50*fc3fe1c2SSimon Glasscommits for different boards on multiple CPUs at once. 51*fc3fe1c2SSimon Glass 52*fc3fe1c2SSimon GlassThe source repo (self.git_dir) contains all the commits to be built. Each 53*fc3fe1c2SSimon Glassthread works on a single board at a time. It checks out the first commit, 54*fc3fe1c2SSimon Glassconfigures it for that board, then builds it. Then it checks out the next 55*fc3fe1c2SSimon Glasscommit and builds it (typically without re-configuring). When it runs out 56*fc3fe1c2SSimon Glassof commits, it gets another job from the builder and starts again with that 57*fc3fe1c2SSimon Glassboard. 58*fc3fe1c2SSimon Glass 59*fc3fe1c2SSimon GlassClearly the builder threads could work either way - they could check out a 60*fc3fe1c2SSimon Glasscommit and then built it for all boards. Using separate directories for each 61*fc3fe1c2SSimon Glasscommit/board pair they could leave their build product around afterwards 62*fc3fe1c2SSimon Glassalso. 63*fc3fe1c2SSimon Glass 64*fc3fe1c2SSimon GlassThe intent behind building a single board for multiple commits, is to make 65*fc3fe1c2SSimon Glassuse of incremental builds. Since each commit is built incrementally from 66*fc3fe1c2SSimon Glassthe previous one, builds are faster. Reconfiguring for a different board 67*fc3fe1c2SSimon Glassremoves all intermediate object files. 68*fc3fe1c2SSimon Glass 69*fc3fe1c2SSimon GlassMany threads can be working at once, but each has its own working directory. 70*fc3fe1c2SSimon GlassWhen a thread finishes a build, it puts the output files into a result 71*fc3fe1c2SSimon Glassdirectory. 72*fc3fe1c2SSimon Glass 73*fc3fe1c2SSimon GlassThe base directory used by buildman is normally '../<branch>', i.e. 74*fc3fe1c2SSimon Glassa directory higher than the source repository and named after the branch 75*fc3fe1c2SSimon Glassbeing built. 76*fc3fe1c2SSimon Glass 77*fc3fe1c2SSimon GlassWithin the base directory, we have one subdirectory for each commit. Within 78*fc3fe1c2SSimon Glassthat is one subdirectory for each board. Within that is the build output for 79*fc3fe1c2SSimon Glassthat commit/board combination. 80*fc3fe1c2SSimon Glass 81*fc3fe1c2SSimon GlassBuildman also create working directories for each thread, in a .bm-work/ 82*fc3fe1c2SSimon Glasssubdirectory in the base dir. 83*fc3fe1c2SSimon Glass 84*fc3fe1c2SSimon GlassAs an example, say we are building branch 'us-net' for boards 'sandbox' and 85*fc3fe1c2SSimon Glass'seaboard', and say that us-net has two commits. We will have directories 86*fc3fe1c2SSimon Glasslike this: 87*fc3fe1c2SSimon Glass 88*fc3fe1c2SSimon Glassus-net/ base directory 89*fc3fe1c2SSimon Glass 01_of_02_g4ed4ebc_net--Add-tftp-speed-/ 90*fc3fe1c2SSimon Glass sandbox/ 91*fc3fe1c2SSimon Glass u-boot.bin 92*fc3fe1c2SSimon Glass seaboard/ 93*fc3fe1c2SSimon Glass u-boot.bin 94*fc3fe1c2SSimon Glass 02_of_02_g4ed4ebc_net--Check-tftp-comp/ 95*fc3fe1c2SSimon Glass sandbox/ 96*fc3fe1c2SSimon Glass u-boot.bin 97*fc3fe1c2SSimon Glass seaboard/ 98*fc3fe1c2SSimon Glass u-boot.bin 99*fc3fe1c2SSimon Glass .bm-work/ 100*fc3fe1c2SSimon Glass 00/ working directory for thread 0 (contains source checkout) 101*fc3fe1c2SSimon Glass build/ build output 102*fc3fe1c2SSimon Glass 01/ working directory for thread 1 103*fc3fe1c2SSimon Glass build/ build output 104*fc3fe1c2SSimon Glass ... 105*fc3fe1c2SSimon Glassu-boot/ source directory 106*fc3fe1c2SSimon Glass .git/ repository 107*fc3fe1c2SSimon Glass""" 108*fc3fe1c2SSimon Glass 109*fc3fe1c2SSimon Glass# Possible build outcomes 110*fc3fe1c2SSimon GlassOUTCOME_OK, OUTCOME_WARNING, OUTCOME_ERROR, OUTCOME_UNKNOWN = range(4) 111*fc3fe1c2SSimon Glass 112*fc3fe1c2SSimon Glass# Translate a commit subject into a valid filename 113*fc3fe1c2SSimon Glasstrans_valid_chars = string.maketrans("/: ", "---") 114*fc3fe1c2SSimon Glass 115*fc3fe1c2SSimon Glass 116*fc3fe1c2SSimon Glassdef Mkdir(dirname): 117*fc3fe1c2SSimon Glass """Make a directory if it doesn't already exist. 118*fc3fe1c2SSimon Glass 119*fc3fe1c2SSimon Glass Args: 120*fc3fe1c2SSimon Glass dirname: Directory to create 121*fc3fe1c2SSimon Glass """ 122*fc3fe1c2SSimon Glass try: 123*fc3fe1c2SSimon Glass os.mkdir(dirname) 124*fc3fe1c2SSimon Glass except OSError as err: 125*fc3fe1c2SSimon Glass if err.errno == errno.EEXIST: 126*fc3fe1c2SSimon Glass pass 127*fc3fe1c2SSimon Glass else: 128*fc3fe1c2SSimon Glass raise 129*fc3fe1c2SSimon Glass 130*fc3fe1c2SSimon Glassclass BuilderJob: 131*fc3fe1c2SSimon Glass """Holds information about a job to be performed by a thread 132*fc3fe1c2SSimon Glass 133*fc3fe1c2SSimon Glass Members: 134*fc3fe1c2SSimon Glass board: Board object to build 135*fc3fe1c2SSimon Glass commits: List of commit options to build. 136*fc3fe1c2SSimon Glass """ 137*fc3fe1c2SSimon Glass def __init__(self): 138*fc3fe1c2SSimon Glass self.board = None 139*fc3fe1c2SSimon Glass self.commits = [] 140*fc3fe1c2SSimon Glass 141*fc3fe1c2SSimon Glass 142*fc3fe1c2SSimon Glassclass ResultThread(threading.Thread): 143*fc3fe1c2SSimon Glass """This thread processes results from builder threads. 144*fc3fe1c2SSimon Glass 145*fc3fe1c2SSimon Glass It simply passes the results on to the builder. There is only one 146*fc3fe1c2SSimon Glass result thread, and this helps to serialise the build output. 147*fc3fe1c2SSimon Glass """ 148*fc3fe1c2SSimon Glass def __init__(self, builder): 149*fc3fe1c2SSimon Glass """Set up a new result thread 150*fc3fe1c2SSimon Glass 151*fc3fe1c2SSimon Glass Args: 152*fc3fe1c2SSimon Glass builder: Builder which will be sent each result 153*fc3fe1c2SSimon Glass """ 154*fc3fe1c2SSimon Glass threading.Thread.__init__(self) 155*fc3fe1c2SSimon Glass self.builder = builder 156*fc3fe1c2SSimon Glass 157*fc3fe1c2SSimon Glass def run(self): 158*fc3fe1c2SSimon Glass """Called to start up the result thread. 159*fc3fe1c2SSimon Glass 160*fc3fe1c2SSimon Glass We collect the next result job and pass it on to the build. 161*fc3fe1c2SSimon Glass """ 162*fc3fe1c2SSimon Glass while True: 163*fc3fe1c2SSimon Glass result = self.builder.out_queue.get() 164*fc3fe1c2SSimon Glass self.builder.ProcessResult(result) 165*fc3fe1c2SSimon Glass self.builder.out_queue.task_done() 166*fc3fe1c2SSimon Glass 167*fc3fe1c2SSimon Glass 168*fc3fe1c2SSimon Glassclass BuilderThread(threading.Thread): 169*fc3fe1c2SSimon Glass """This thread builds U-Boot for a particular board. 170*fc3fe1c2SSimon Glass 171*fc3fe1c2SSimon Glass An input queue provides each new job. We run 'make' to build U-Boot 172*fc3fe1c2SSimon Glass and then pass the results on to the output queue. 173*fc3fe1c2SSimon Glass 174*fc3fe1c2SSimon Glass Members: 175*fc3fe1c2SSimon Glass builder: The builder which contains information we might need 176*fc3fe1c2SSimon Glass thread_num: Our thread number (0-n-1), used to decide on a 177*fc3fe1c2SSimon Glass temporary directory 178*fc3fe1c2SSimon Glass """ 179*fc3fe1c2SSimon Glass def __init__(self, builder, thread_num): 180*fc3fe1c2SSimon Glass """Set up a new builder thread""" 181*fc3fe1c2SSimon Glass threading.Thread.__init__(self) 182*fc3fe1c2SSimon Glass self.builder = builder 183*fc3fe1c2SSimon Glass self.thread_num = thread_num 184*fc3fe1c2SSimon Glass 185*fc3fe1c2SSimon Glass def Make(self, commit, brd, stage, cwd, *args, **kwargs): 186*fc3fe1c2SSimon Glass """Run 'make' on a particular commit and board. 187*fc3fe1c2SSimon Glass 188*fc3fe1c2SSimon Glass The source code will already be checked out, so the 'commit' 189*fc3fe1c2SSimon Glass argument is only for information. 190*fc3fe1c2SSimon Glass 191*fc3fe1c2SSimon Glass Args: 192*fc3fe1c2SSimon Glass commit: Commit object that is being built 193*fc3fe1c2SSimon Glass brd: Board object that is being built 194*fc3fe1c2SSimon Glass stage: Stage of the build. Valid stages are: 195*fc3fe1c2SSimon Glass distclean - can be called to clean source 196*fc3fe1c2SSimon Glass config - called to configure for a board 197*fc3fe1c2SSimon Glass build - the main make invocation - it does the build 198*fc3fe1c2SSimon Glass args: A list of arguments to pass to 'make' 199*fc3fe1c2SSimon Glass kwargs: A list of keyword arguments to pass to command.RunPipe() 200*fc3fe1c2SSimon Glass 201*fc3fe1c2SSimon Glass Returns: 202*fc3fe1c2SSimon Glass CommandResult object 203*fc3fe1c2SSimon Glass """ 204*fc3fe1c2SSimon Glass return self.builder.do_make(commit, brd, stage, cwd, *args, 205*fc3fe1c2SSimon Glass **kwargs) 206*fc3fe1c2SSimon Glass 207*fc3fe1c2SSimon Glass def RunCommit(self, commit_upto, brd, work_dir, do_config, force_build): 208*fc3fe1c2SSimon Glass """Build a particular commit. 209*fc3fe1c2SSimon Glass 210*fc3fe1c2SSimon Glass If the build is already done, and we are not forcing a build, we skip 211*fc3fe1c2SSimon Glass the build and just return the previously-saved results. 212*fc3fe1c2SSimon Glass 213*fc3fe1c2SSimon Glass Args: 214*fc3fe1c2SSimon Glass commit_upto: Commit number to build (0...n-1) 215*fc3fe1c2SSimon Glass brd: Board object to build 216*fc3fe1c2SSimon Glass work_dir: Directory to which the source will be checked out 217*fc3fe1c2SSimon Glass do_config: True to run a make <board>_config on the source 218*fc3fe1c2SSimon Glass force_build: Force a build even if one was previously done 219*fc3fe1c2SSimon Glass 220*fc3fe1c2SSimon Glass Returns: 221*fc3fe1c2SSimon Glass tuple containing: 222*fc3fe1c2SSimon Glass - CommandResult object containing the results of the build 223*fc3fe1c2SSimon Glass - boolean indicating whether 'make config' is still needed 224*fc3fe1c2SSimon Glass """ 225*fc3fe1c2SSimon Glass # Create a default result - it will be overwritte by the call to 226*fc3fe1c2SSimon Glass # self.Make() below, in the event that we do a build. 227*fc3fe1c2SSimon Glass result = command.CommandResult() 228*fc3fe1c2SSimon Glass result.return_code = 0 229*fc3fe1c2SSimon Glass out_dir = os.path.join(work_dir, 'build') 230*fc3fe1c2SSimon Glass 231*fc3fe1c2SSimon Glass # Check if the job was already completed last time 232*fc3fe1c2SSimon Glass done_file = self.builder.GetDoneFile(commit_upto, brd.target) 233*fc3fe1c2SSimon Glass result.already_done = os.path.exists(done_file) 234*fc3fe1c2SSimon Glass if result.already_done and not force_build: 235*fc3fe1c2SSimon Glass # Get the return code from that build and use it 236*fc3fe1c2SSimon Glass with open(done_file, 'r') as fd: 237*fc3fe1c2SSimon Glass result.return_code = int(fd.readline()) 238*fc3fe1c2SSimon Glass err_file = self.builder.GetErrFile(commit_upto, brd.target) 239*fc3fe1c2SSimon Glass if os.path.exists(err_file) and os.stat(err_file).st_size: 240*fc3fe1c2SSimon Glass result.stderr = 'bad' 241*fc3fe1c2SSimon Glass else: 242*fc3fe1c2SSimon Glass # We are going to have to build it. First, get a toolchain 243*fc3fe1c2SSimon Glass if not self.toolchain: 244*fc3fe1c2SSimon Glass try: 245*fc3fe1c2SSimon Glass self.toolchain = self.builder.toolchains.Select(brd.arch) 246*fc3fe1c2SSimon Glass except ValueError as err: 247*fc3fe1c2SSimon Glass result.return_code = 10 248*fc3fe1c2SSimon Glass result.stdout = '' 249*fc3fe1c2SSimon Glass result.stderr = str(err) 250*fc3fe1c2SSimon Glass # TODO(sjg@chromium.org): This gets swallowed, but needs 251*fc3fe1c2SSimon Glass # to be reported. 252*fc3fe1c2SSimon Glass 253*fc3fe1c2SSimon Glass if self.toolchain: 254*fc3fe1c2SSimon Glass # Checkout the right commit 255*fc3fe1c2SSimon Glass if commit_upto is not None: 256*fc3fe1c2SSimon Glass commit = self.builder.commits[commit_upto] 257*fc3fe1c2SSimon Glass if self.builder.checkout: 258*fc3fe1c2SSimon Glass git_dir = os.path.join(work_dir, '.git') 259*fc3fe1c2SSimon Glass gitutil.Checkout(commit.hash, git_dir, work_dir, 260*fc3fe1c2SSimon Glass force=True) 261*fc3fe1c2SSimon Glass else: 262*fc3fe1c2SSimon Glass commit = self.builder.commit # Ick, fix this for BuildCommits() 263*fc3fe1c2SSimon Glass 264*fc3fe1c2SSimon Glass # Set up the environment and command line 265*fc3fe1c2SSimon Glass env = self.toolchain.MakeEnvironment() 266*fc3fe1c2SSimon Glass Mkdir(out_dir) 267*fc3fe1c2SSimon Glass args = ['O=build', '-s'] 268*fc3fe1c2SSimon Glass if self.builder.num_jobs is not None: 269*fc3fe1c2SSimon Glass args.extend(['-j', str(self.builder.num_jobs)]) 270*fc3fe1c2SSimon Glass config_args = ['%s_config' % brd.target] 271*fc3fe1c2SSimon Glass config_out = '' 272*fc3fe1c2SSimon Glass 273*fc3fe1c2SSimon Glass # If we need to reconfigure, do that now 274*fc3fe1c2SSimon Glass if do_config: 275*fc3fe1c2SSimon Glass result = self.Make(commit, brd, 'distclean', work_dir, 276*fc3fe1c2SSimon Glass 'distclean', *args, env=env) 277*fc3fe1c2SSimon Glass result = self.Make(commit, brd, 'config', work_dir, 278*fc3fe1c2SSimon Glass *(args + config_args), env=env) 279*fc3fe1c2SSimon Glass config_out = result.combined 280*fc3fe1c2SSimon Glass do_config = False # No need to configure next time 281*fc3fe1c2SSimon Glass if result.return_code == 0: 282*fc3fe1c2SSimon Glass result = self.Make(commit, brd, 'build', work_dir, *args, 283*fc3fe1c2SSimon Glass env=env) 284*fc3fe1c2SSimon Glass result.stdout = config_out + result.stdout 285*fc3fe1c2SSimon Glass else: 286*fc3fe1c2SSimon Glass result.return_code = 1 287*fc3fe1c2SSimon Glass result.stderr = 'No tool chain for %s\n' % brd.arch 288*fc3fe1c2SSimon Glass result.already_done = False 289*fc3fe1c2SSimon Glass 290*fc3fe1c2SSimon Glass result.toolchain = self.toolchain 291*fc3fe1c2SSimon Glass result.brd = brd 292*fc3fe1c2SSimon Glass result.commit_upto = commit_upto 293*fc3fe1c2SSimon Glass result.out_dir = out_dir 294*fc3fe1c2SSimon Glass return result, do_config 295*fc3fe1c2SSimon Glass 296*fc3fe1c2SSimon Glass def _WriteResult(self, result, keep_outputs): 297*fc3fe1c2SSimon Glass """Write a built result to the output directory. 298*fc3fe1c2SSimon Glass 299*fc3fe1c2SSimon Glass Args: 300*fc3fe1c2SSimon Glass result: CommandResult object containing result to write 301*fc3fe1c2SSimon Glass keep_outputs: True to store the output binaries, False 302*fc3fe1c2SSimon Glass to delete them 303*fc3fe1c2SSimon Glass """ 304*fc3fe1c2SSimon Glass # Fatal error 305*fc3fe1c2SSimon Glass if result.return_code < 0: 306*fc3fe1c2SSimon Glass return 307*fc3fe1c2SSimon Glass 308*fc3fe1c2SSimon Glass # Aborted? 309*fc3fe1c2SSimon Glass if result.stderr and 'No child processes' in result.stderr: 310*fc3fe1c2SSimon Glass return 311*fc3fe1c2SSimon Glass 312*fc3fe1c2SSimon Glass if result.already_done: 313*fc3fe1c2SSimon Glass return 314*fc3fe1c2SSimon Glass 315*fc3fe1c2SSimon Glass # Write the output and stderr 316*fc3fe1c2SSimon Glass output_dir = self.builder._GetOutputDir(result.commit_upto) 317*fc3fe1c2SSimon Glass Mkdir(output_dir) 318*fc3fe1c2SSimon Glass build_dir = self.builder.GetBuildDir(result.commit_upto, 319*fc3fe1c2SSimon Glass result.brd.target) 320*fc3fe1c2SSimon Glass Mkdir(build_dir) 321*fc3fe1c2SSimon Glass 322*fc3fe1c2SSimon Glass outfile = os.path.join(build_dir, 'log') 323*fc3fe1c2SSimon Glass with open(outfile, 'w') as fd: 324*fc3fe1c2SSimon Glass if result.stdout: 325*fc3fe1c2SSimon Glass fd.write(result.stdout) 326*fc3fe1c2SSimon Glass 327*fc3fe1c2SSimon Glass errfile = self.builder.GetErrFile(result.commit_upto, 328*fc3fe1c2SSimon Glass result.brd.target) 329*fc3fe1c2SSimon Glass if result.stderr: 330*fc3fe1c2SSimon Glass with open(errfile, 'w') as fd: 331*fc3fe1c2SSimon Glass fd.write(result.stderr) 332*fc3fe1c2SSimon Glass elif os.path.exists(errfile): 333*fc3fe1c2SSimon Glass os.remove(errfile) 334*fc3fe1c2SSimon Glass 335*fc3fe1c2SSimon Glass if result.toolchain: 336*fc3fe1c2SSimon Glass # Write the build result and toolchain information. 337*fc3fe1c2SSimon Glass done_file = self.builder.GetDoneFile(result.commit_upto, 338*fc3fe1c2SSimon Glass result.brd.target) 339*fc3fe1c2SSimon Glass with open(done_file, 'w') as fd: 340*fc3fe1c2SSimon Glass fd.write('%s' % result.return_code) 341*fc3fe1c2SSimon Glass with open(os.path.join(build_dir, 'toolchain'), 'w') as fd: 342*fc3fe1c2SSimon Glass print >>fd, 'gcc', result.toolchain.gcc 343*fc3fe1c2SSimon Glass print >>fd, 'path', result.toolchain.path 344*fc3fe1c2SSimon Glass print >>fd, 'cross', result.toolchain.cross 345*fc3fe1c2SSimon Glass print >>fd, 'arch', result.toolchain.arch 346*fc3fe1c2SSimon Glass fd.write('%s' % result.return_code) 347*fc3fe1c2SSimon Glass 348*fc3fe1c2SSimon Glass with open(os.path.join(build_dir, 'toolchain'), 'w') as fd: 349*fc3fe1c2SSimon Glass print >>fd, 'gcc', result.toolchain.gcc 350*fc3fe1c2SSimon Glass print >>fd, 'path', result.toolchain.path 351*fc3fe1c2SSimon Glass 352*fc3fe1c2SSimon Glass # Write out the image and function size information and an objdump 353*fc3fe1c2SSimon Glass env = result.toolchain.MakeEnvironment() 354*fc3fe1c2SSimon Glass lines = [] 355*fc3fe1c2SSimon Glass for fname in ['u-boot', 'spl/u-boot-spl']: 356*fc3fe1c2SSimon Glass cmd = ['%snm' % self.toolchain.cross, '--size-sort', fname] 357*fc3fe1c2SSimon Glass nm_result = command.RunPipe([cmd], capture=True, 358*fc3fe1c2SSimon Glass capture_stderr=True, cwd=result.out_dir, 359*fc3fe1c2SSimon Glass raise_on_error=False, env=env) 360*fc3fe1c2SSimon Glass if nm_result.stdout: 361*fc3fe1c2SSimon Glass nm = self.builder.GetFuncSizesFile(result.commit_upto, 362*fc3fe1c2SSimon Glass result.brd.target, fname) 363*fc3fe1c2SSimon Glass with open(nm, 'w') as fd: 364*fc3fe1c2SSimon Glass print >>fd, nm_result.stdout, 365*fc3fe1c2SSimon Glass 366*fc3fe1c2SSimon Glass cmd = ['%sobjdump' % self.toolchain.cross, '-h', fname] 367*fc3fe1c2SSimon Glass dump_result = command.RunPipe([cmd], capture=True, 368*fc3fe1c2SSimon Glass capture_stderr=True, cwd=result.out_dir, 369*fc3fe1c2SSimon Glass raise_on_error=False, env=env) 370*fc3fe1c2SSimon Glass rodata_size = '' 371*fc3fe1c2SSimon Glass if dump_result.stdout: 372*fc3fe1c2SSimon Glass objdump = self.builder.GetObjdumpFile(result.commit_upto, 373*fc3fe1c2SSimon Glass result.brd.target, fname) 374*fc3fe1c2SSimon Glass with open(objdump, 'w') as fd: 375*fc3fe1c2SSimon Glass print >>fd, dump_result.stdout, 376*fc3fe1c2SSimon Glass for line in dump_result.stdout.splitlines(): 377*fc3fe1c2SSimon Glass fields = line.split() 378*fc3fe1c2SSimon Glass if len(fields) > 5 and fields[1] == '.rodata': 379*fc3fe1c2SSimon Glass rodata_size = fields[2] 380*fc3fe1c2SSimon Glass 381*fc3fe1c2SSimon Glass cmd = ['%ssize' % self.toolchain.cross, fname] 382*fc3fe1c2SSimon Glass size_result = command.RunPipe([cmd], capture=True, 383*fc3fe1c2SSimon Glass capture_stderr=True, cwd=result.out_dir, 384*fc3fe1c2SSimon Glass raise_on_error=False, env=env) 385*fc3fe1c2SSimon Glass if size_result.stdout: 386*fc3fe1c2SSimon Glass lines.append(size_result.stdout.splitlines()[1] + ' ' + 387*fc3fe1c2SSimon Glass rodata_size) 388*fc3fe1c2SSimon Glass 389*fc3fe1c2SSimon Glass # Write out the image sizes file. This is similar to the output 390*fc3fe1c2SSimon Glass # of binutil's 'size' utility, but it omits the header line and 391*fc3fe1c2SSimon Glass # adds an additional hex value at the end of each line for the 392*fc3fe1c2SSimon Glass # rodata size 393*fc3fe1c2SSimon Glass if len(lines): 394*fc3fe1c2SSimon Glass sizes = self.builder.GetSizesFile(result.commit_upto, 395*fc3fe1c2SSimon Glass result.brd.target) 396*fc3fe1c2SSimon Glass with open(sizes, 'w') as fd: 397*fc3fe1c2SSimon Glass print >>fd, '\n'.join(lines) 398*fc3fe1c2SSimon Glass 399*fc3fe1c2SSimon Glass # Now write the actual build output 400*fc3fe1c2SSimon Glass if keep_outputs: 401*fc3fe1c2SSimon Glass patterns = ['u-boot', '*.bin', 'u-boot.dtb', '*.map', 402*fc3fe1c2SSimon Glass 'include/autoconf.mk', 'spl/u-boot-spl', 403*fc3fe1c2SSimon Glass 'spl/u-boot-spl.bin'] 404*fc3fe1c2SSimon Glass for pattern in patterns: 405*fc3fe1c2SSimon Glass file_list = glob.glob(os.path.join(result.out_dir, pattern)) 406*fc3fe1c2SSimon Glass for fname in file_list: 407*fc3fe1c2SSimon Glass shutil.copy(fname, build_dir) 408*fc3fe1c2SSimon Glass 409*fc3fe1c2SSimon Glass 410*fc3fe1c2SSimon Glass def RunJob(self, job): 411*fc3fe1c2SSimon Glass """Run a single job 412*fc3fe1c2SSimon Glass 413*fc3fe1c2SSimon Glass A job consists of a building a list of commits for a particular board. 414*fc3fe1c2SSimon Glass 415*fc3fe1c2SSimon Glass Args: 416*fc3fe1c2SSimon Glass job: Job to build 417*fc3fe1c2SSimon Glass """ 418*fc3fe1c2SSimon Glass brd = job.board 419*fc3fe1c2SSimon Glass work_dir = self.builder.GetThreadDir(self.thread_num) 420*fc3fe1c2SSimon Glass self.toolchain = None 421*fc3fe1c2SSimon Glass if job.commits: 422*fc3fe1c2SSimon Glass # Run 'make board_config' on the first commit 423*fc3fe1c2SSimon Glass do_config = True 424*fc3fe1c2SSimon Glass commit_upto = 0 425*fc3fe1c2SSimon Glass force_build = False 426*fc3fe1c2SSimon Glass for commit_upto in range(0, len(job.commits), job.step): 427*fc3fe1c2SSimon Glass result, request_config = self.RunCommit(commit_upto, brd, 428*fc3fe1c2SSimon Glass work_dir, do_config, 429*fc3fe1c2SSimon Glass force_build or self.builder.force_build) 430*fc3fe1c2SSimon Glass failed = result.return_code or result.stderr 431*fc3fe1c2SSimon Glass if failed and not do_config: 432*fc3fe1c2SSimon Glass # If our incremental build failed, try building again 433*fc3fe1c2SSimon Glass # with a reconfig. 434*fc3fe1c2SSimon Glass if self.builder.force_config_on_failure: 435*fc3fe1c2SSimon Glass result, request_config = self.RunCommit(commit_upto, 436*fc3fe1c2SSimon Glass brd, work_dir, True, True) 437*fc3fe1c2SSimon Glass do_config = request_config 438*fc3fe1c2SSimon Glass 439*fc3fe1c2SSimon Glass # If we built that commit, then config is done. But if we got 440*fc3fe1c2SSimon Glass # an warning, reconfig next time to force it to build the same 441*fc3fe1c2SSimon Glass # files that created warnings this time. Otherwise an 442*fc3fe1c2SSimon Glass # incremental build may not build the same file, and we will 443*fc3fe1c2SSimon Glass # think that the warning has gone away. 444*fc3fe1c2SSimon Glass # We could avoid this by using -Werror everywhere... 445*fc3fe1c2SSimon Glass # For errors, the problem doesn't happen, since presumably 446*fc3fe1c2SSimon Glass # the build stopped and didn't generate output, so will retry 447*fc3fe1c2SSimon Glass # that file next time. So we could detect warnings and deal 448*fc3fe1c2SSimon Glass # with them specially here. For now, we just reconfigure if 449*fc3fe1c2SSimon Glass # anything goes work. 450*fc3fe1c2SSimon Glass # Of course this is substantially slower if there are build 451*fc3fe1c2SSimon Glass # errors/warnings (e.g. 2-3x slower even if only 10% of builds 452*fc3fe1c2SSimon Glass # have problems). 453*fc3fe1c2SSimon Glass if (failed and not result.already_done and not do_config and 454*fc3fe1c2SSimon Glass self.builder.force_config_on_failure): 455*fc3fe1c2SSimon Glass # If this build failed, try the next one with a 456*fc3fe1c2SSimon Glass # reconfigure. 457*fc3fe1c2SSimon Glass # Sometimes if the board_config.h file changes it can mess 458*fc3fe1c2SSimon Glass # with dependencies, and we get: 459*fc3fe1c2SSimon Glass # make: *** No rule to make target `include/autoconf.mk', 460*fc3fe1c2SSimon Glass # needed by `depend'. 461*fc3fe1c2SSimon Glass do_config = True 462*fc3fe1c2SSimon Glass force_build = True 463*fc3fe1c2SSimon Glass else: 464*fc3fe1c2SSimon Glass force_build = False 465*fc3fe1c2SSimon Glass if self.builder.force_config_on_failure: 466*fc3fe1c2SSimon Glass if failed: 467*fc3fe1c2SSimon Glass do_config = True 468*fc3fe1c2SSimon Glass result.commit_upto = commit_upto 469*fc3fe1c2SSimon Glass if result.return_code < 0: 470*fc3fe1c2SSimon Glass raise ValueError('Interrupt') 471*fc3fe1c2SSimon Glass 472*fc3fe1c2SSimon Glass # We have the build results, so output the result 473*fc3fe1c2SSimon Glass self._WriteResult(result, job.keep_outputs) 474*fc3fe1c2SSimon Glass self.builder.out_queue.put(result) 475*fc3fe1c2SSimon Glass else: 476*fc3fe1c2SSimon Glass # Just build the currently checked-out build 477*fc3fe1c2SSimon Glass result = self.RunCommit(None, True) 478*fc3fe1c2SSimon Glass result.commit_upto = self.builder.upto 479*fc3fe1c2SSimon Glass self.builder.out_queue.put(result) 480*fc3fe1c2SSimon Glass 481*fc3fe1c2SSimon Glass def run(self): 482*fc3fe1c2SSimon Glass """Our thread's run function 483*fc3fe1c2SSimon Glass 484*fc3fe1c2SSimon Glass This thread picks a job from the queue, runs it, and then goes to the 485*fc3fe1c2SSimon Glass next job. 486*fc3fe1c2SSimon Glass """ 487*fc3fe1c2SSimon Glass alive = True 488*fc3fe1c2SSimon Glass while True: 489*fc3fe1c2SSimon Glass job = self.builder.queue.get() 490*fc3fe1c2SSimon Glass try: 491*fc3fe1c2SSimon Glass if self.builder.active and alive: 492*fc3fe1c2SSimon Glass self.RunJob(job) 493*fc3fe1c2SSimon Glass except Exception as err: 494*fc3fe1c2SSimon Glass alive = False 495*fc3fe1c2SSimon Glass print err 496*fc3fe1c2SSimon Glass self.builder.queue.task_done() 497*fc3fe1c2SSimon Glass 498*fc3fe1c2SSimon Glass 499*fc3fe1c2SSimon Glassclass Builder: 500*fc3fe1c2SSimon Glass """Class for building U-Boot for a particular commit. 501*fc3fe1c2SSimon Glass 502*fc3fe1c2SSimon Glass Public members: (many should ->private) 503*fc3fe1c2SSimon Glass active: True if the builder is active and has not been stopped 504*fc3fe1c2SSimon Glass already_done: Number of builds already completed 505*fc3fe1c2SSimon Glass base_dir: Base directory to use for builder 506*fc3fe1c2SSimon Glass checkout: True to check out source, False to skip that step. 507*fc3fe1c2SSimon Glass This is used for testing. 508*fc3fe1c2SSimon Glass col: terminal.Color() object 509*fc3fe1c2SSimon Glass count: Number of commits to build 510*fc3fe1c2SSimon Glass do_make: Method to call to invoke Make 511*fc3fe1c2SSimon Glass fail: Number of builds that failed due to error 512*fc3fe1c2SSimon Glass force_build: Force building even if a build already exists 513*fc3fe1c2SSimon Glass force_config_on_failure: If a commit fails for a board, disable 514*fc3fe1c2SSimon Glass incremental building for the next commit we build for that 515*fc3fe1c2SSimon Glass board, so that we will see all warnings/errors again. 516*fc3fe1c2SSimon Glass git_dir: Git directory containing source repository 517*fc3fe1c2SSimon Glass last_line_len: Length of the last line we printed (used for erasing 518*fc3fe1c2SSimon Glass it with new progress information) 519*fc3fe1c2SSimon Glass num_jobs: Number of jobs to run at once (passed to make as -j) 520*fc3fe1c2SSimon Glass num_threads: Number of builder threads to run 521*fc3fe1c2SSimon Glass out_queue: Queue of results to process 522*fc3fe1c2SSimon Glass re_make_err: Compiled regular expression for ignore_lines 523*fc3fe1c2SSimon Glass queue: Queue of jobs to run 524*fc3fe1c2SSimon Glass threads: List of active threads 525*fc3fe1c2SSimon Glass toolchains: Toolchains object to use for building 526*fc3fe1c2SSimon Glass upto: Current commit number we are building (0.count-1) 527*fc3fe1c2SSimon Glass warned: Number of builds that produced at least one warning 528*fc3fe1c2SSimon Glass 529*fc3fe1c2SSimon Glass Private members: 530*fc3fe1c2SSimon Glass _base_board_dict: Last-summarised Dict of boards 531*fc3fe1c2SSimon Glass _base_err_lines: Last-summarised list of errors 532*fc3fe1c2SSimon Glass _build_period_us: Time taken for a single build (float object). 533*fc3fe1c2SSimon Glass _complete_delay: Expected delay until completion (timedelta) 534*fc3fe1c2SSimon Glass _next_delay_update: Next time we plan to display a progress update 535*fc3fe1c2SSimon Glass (datatime) 536*fc3fe1c2SSimon Glass _show_unknown: Show unknown boards (those not built) in summary 537*fc3fe1c2SSimon Glass _timestamps: List of timestamps for the completion of the last 538*fc3fe1c2SSimon Glass last _timestamp_count builds. Each is a datetime object. 539*fc3fe1c2SSimon Glass _timestamp_count: Number of timestamps to keep in our list. 540*fc3fe1c2SSimon Glass _working_dir: Base working directory containing all threads 541*fc3fe1c2SSimon Glass """ 542*fc3fe1c2SSimon Glass class Outcome: 543*fc3fe1c2SSimon Glass """Records a build outcome for a single make invocation 544*fc3fe1c2SSimon Glass 545*fc3fe1c2SSimon Glass Public Members: 546*fc3fe1c2SSimon Glass rc: Outcome value (OUTCOME_...) 547*fc3fe1c2SSimon Glass err_lines: List of error lines or [] if none 548*fc3fe1c2SSimon Glass sizes: Dictionary of image size information, keyed by filename 549*fc3fe1c2SSimon Glass - Each value is itself a dictionary containing 550*fc3fe1c2SSimon Glass values for 'text', 'data' and 'bss', being the integer 551*fc3fe1c2SSimon Glass size in bytes of each section. 552*fc3fe1c2SSimon Glass func_sizes: Dictionary keyed by filename - e.g. 'u-boot'. Each 553*fc3fe1c2SSimon Glass value is itself a dictionary: 554*fc3fe1c2SSimon Glass key: function name 555*fc3fe1c2SSimon Glass value: Size of function in bytes 556*fc3fe1c2SSimon Glass """ 557*fc3fe1c2SSimon Glass def __init__(self, rc, err_lines, sizes, func_sizes): 558*fc3fe1c2SSimon Glass self.rc = rc 559*fc3fe1c2SSimon Glass self.err_lines = err_lines 560*fc3fe1c2SSimon Glass self.sizes = sizes 561*fc3fe1c2SSimon Glass self.func_sizes = func_sizes 562*fc3fe1c2SSimon Glass 563*fc3fe1c2SSimon Glass def __init__(self, toolchains, base_dir, git_dir, num_threads, num_jobs, 564*fc3fe1c2SSimon Glass checkout=True, show_unknown=True, step=1): 565*fc3fe1c2SSimon Glass """Create a new Builder object 566*fc3fe1c2SSimon Glass 567*fc3fe1c2SSimon Glass Args: 568*fc3fe1c2SSimon Glass toolchains: Toolchains object to use for building 569*fc3fe1c2SSimon Glass base_dir: Base directory to use for builder 570*fc3fe1c2SSimon Glass git_dir: Git directory containing source repository 571*fc3fe1c2SSimon Glass num_threads: Number of builder threads to run 572*fc3fe1c2SSimon Glass num_jobs: Number of jobs to run at once (passed to make as -j) 573*fc3fe1c2SSimon Glass checkout: True to check out source, False to skip that step. 574*fc3fe1c2SSimon Glass This is used for testing. 575*fc3fe1c2SSimon Glass show_unknown: Show unknown boards (those not built) in summary 576*fc3fe1c2SSimon Glass step: 1 to process every commit, n to process every nth commit 577*fc3fe1c2SSimon Glass """ 578*fc3fe1c2SSimon Glass self.toolchains = toolchains 579*fc3fe1c2SSimon Glass self.base_dir = base_dir 580*fc3fe1c2SSimon Glass self._working_dir = os.path.join(base_dir, '.bm-work') 581*fc3fe1c2SSimon Glass self.threads = [] 582*fc3fe1c2SSimon Glass self.active = True 583*fc3fe1c2SSimon Glass self.do_make = self.Make 584*fc3fe1c2SSimon Glass self.checkout = checkout 585*fc3fe1c2SSimon Glass self.num_threads = num_threads 586*fc3fe1c2SSimon Glass self.num_jobs = num_jobs 587*fc3fe1c2SSimon Glass self.already_done = 0 588*fc3fe1c2SSimon Glass self.force_build = False 589*fc3fe1c2SSimon Glass self.git_dir = git_dir 590*fc3fe1c2SSimon Glass self._show_unknown = show_unknown 591*fc3fe1c2SSimon Glass self._timestamp_count = 10 592*fc3fe1c2SSimon Glass self._build_period_us = None 593*fc3fe1c2SSimon Glass self._complete_delay = None 594*fc3fe1c2SSimon Glass self._next_delay_update = datetime.now() 595*fc3fe1c2SSimon Glass self.force_config_on_failure = True 596*fc3fe1c2SSimon Glass self._step = step 597*fc3fe1c2SSimon Glass 598*fc3fe1c2SSimon Glass self.col = terminal.Color() 599*fc3fe1c2SSimon Glass 600*fc3fe1c2SSimon Glass self.queue = Queue.Queue() 601*fc3fe1c2SSimon Glass self.out_queue = Queue.Queue() 602*fc3fe1c2SSimon Glass for i in range(self.num_threads): 603*fc3fe1c2SSimon Glass t = BuilderThread(self, i) 604*fc3fe1c2SSimon Glass t.setDaemon(True) 605*fc3fe1c2SSimon Glass t.start() 606*fc3fe1c2SSimon Glass self.threads.append(t) 607*fc3fe1c2SSimon Glass 608*fc3fe1c2SSimon Glass self.last_line_len = 0 609*fc3fe1c2SSimon Glass t = ResultThread(self) 610*fc3fe1c2SSimon Glass t.setDaemon(True) 611*fc3fe1c2SSimon Glass t.start() 612*fc3fe1c2SSimon Glass self.threads.append(t) 613*fc3fe1c2SSimon Glass 614*fc3fe1c2SSimon Glass ignore_lines = ['(make.*Waiting for unfinished)', '(Segmentation fault)'] 615*fc3fe1c2SSimon Glass self.re_make_err = re.compile('|'.join(ignore_lines)) 616*fc3fe1c2SSimon Glass 617*fc3fe1c2SSimon Glass def __del__(self): 618*fc3fe1c2SSimon Glass """Get rid of all threads created by the builder""" 619*fc3fe1c2SSimon Glass for t in self.threads: 620*fc3fe1c2SSimon Glass del t 621*fc3fe1c2SSimon Glass 622*fc3fe1c2SSimon Glass def _AddTimestamp(self): 623*fc3fe1c2SSimon Glass """Add a new timestamp to the list and record the build period. 624*fc3fe1c2SSimon Glass 625*fc3fe1c2SSimon Glass The build period is the length of time taken to perform a single 626*fc3fe1c2SSimon Glass build (one board, one commit). 627*fc3fe1c2SSimon Glass """ 628*fc3fe1c2SSimon Glass now = datetime.now() 629*fc3fe1c2SSimon Glass self._timestamps.append(now) 630*fc3fe1c2SSimon Glass count = len(self._timestamps) 631*fc3fe1c2SSimon Glass delta = self._timestamps[-1] - self._timestamps[0] 632*fc3fe1c2SSimon Glass seconds = delta.total_seconds() 633*fc3fe1c2SSimon Glass 634*fc3fe1c2SSimon Glass # If we have enough data, estimate build period (time taken for a 635*fc3fe1c2SSimon Glass # single build) and therefore completion time. 636*fc3fe1c2SSimon Glass if count > 1 and self._next_delay_update < now: 637*fc3fe1c2SSimon Glass self._next_delay_update = now + timedelta(seconds=2) 638*fc3fe1c2SSimon Glass if seconds > 0: 639*fc3fe1c2SSimon Glass self._build_period = float(seconds) / count 640*fc3fe1c2SSimon Glass todo = self.count - self.upto 641*fc3fe1c2SSimon Glass self._complete_delay = timedelta(microseconds= 642*fc3fe1c2SSimon Glass self._build_period * todo * 1000000) 643*fc3fe1c2SSimon Glass # Round it 644*fc3fe1c2SSimon Glass self._complete_delay -= timedelta( 645*fc3fe1c2SSimon Glass microseconds=self._complete_delay.microseconds) 646*fc3fe1c2SSimon Glass 647*fc3fe1c2SSimon Glass if seconds > 60: 648*fc3fe1c2SSimon Glass self._timestamps.popleft() 649*fc3fe1c2SSimon Glass count -= 1 650*fc3fe1c2SSimon Glass 651*fc3fe1c2SSimon Glass def ClearLine(self, length): 652*fc3fe1c2SSimon Glass """Clear any characters on the current line 653*fc3fe1c2SSimon Glass 654*fc3fe1c2SSimon Glass Make way for a new line of length 'length', by outputting enough 655*fc3fe1c2SSimon Glass spaces to clear out the old line. Then remember the new length for 656*fc3fe1c2SSimon Glass next time. 657*fc3fe1c2SSimon Glass 658*fc3fe1c2SSimon Glass Args: 659*fc3fe1c2SSimon Glass length: Length of new line, in characters 660*fc3fe1c2SSimon Glass """ 661*fc3fe1c2SSimon Glass if length < self.last_line_len: 662*fc3fe1c2SSimon Glass print ' ' * (self.last_line_len - length), 663*fc3fe1c2SSimon Glass print '\r', 664*fc3fe1c2SSimon Glass self.last_line_len = length 665*fc3fe1c2SSimon Glass sys.stdout.flush() 666*fc3fe1c2SSimon Glass 667*fc3fe1c2SSimon Glass def SelectCommit(self, commit, checkout=True): 668*fc3fe1c2SSimon Glass """Checkout the selected commit for this build 669*fc3fe1c2SSimon Glass """ 670*fc3fe1c2SSimon Glass self.commit = commit 671*fc3fe1c2SSimon Glass if checkout and self.checkout: 672*fc3fe1c2SSimon Glass gitutil.Checkout(commit.hash) 673*fc3fe1c2SSimon Glass 674*fc3fe1c2SSimon Glass def Make(self, commit, brd, stage, cwd, *args, **kwargs): 675*fc3fe1c2SSimon Glass """Run make 676*fc3fe1c2SSimon Glass 677*fc3fe1c2SSimon Glass Args: 678*fc3fe1c2SSimon Glass commit: Commit object that is being built 679*fc3fe1c2SSimon Glass brd: Board object that is being built 680*fc3fe1c2SSimon Glass stage: Stage that we are at (distclean, config, build) 681*fc3fe1c2SSimon Glass cwd: Directory where make should be run 682*fc3fe1c2SSimon Glass args: Arguments to pass to make 683*fc3fe1c2SSimon Glass kwargs: Arguments to pass to command.RunPipe() 684*fc3fe1c2SSimon Glass """ 685*fc3fe1c2SSimon Glass cmd = ['make'] + list(args) 686*fc3fe1c2SSimon Glass result = command.RunPipe([cmd], capture=True, capture_stderr=True, 687*fc3fe1c2SSimon Glass cwd=cwd, raise_on_error=False, **kwargs) 688*fc3fe1c2SSimon Glass return result 689*fc3fe1c2SSimon Glass 690*fc3fe1c2SSimon Glass def ProcessResult(self, result): 691*fc3fe1c2SSimon Glass """Process the result of a build, showing progress information 692*fc3fe1c2SSimon Glass 693*fc3fe1c2SSimon Glass Args: 694*fc3fe1c2SSimon Glass result: A CommandResult object 695*fc3fe1c2SSimon Glass """ 696*fc3fe1c2SSimon Glass col = terminal.Color() 697*fc3fe1c2SSimon Glass if result: 698*fc3fe1c2SSimon Glass target = result.brd.target 699*fc3fe1c2SSimon Glass 700*fc3fe1c2SSimon Glass if result.return_code < 0: 701*fc3fe1c2SSimon Glass self.active = False 702*fc3fe1c2SSimon Glass command.StopAll() 703*fc3fe1c2SSimon Glass return 704*fc3fe1c2SSimon Glass 705*fc3fe1c2SSimon Glass self.upto += 1 706*fc3fe1c2SSimon Glass if result.return_code != 0: 707*fc3fe1c2SSimon Glass self.fail += 1 708*fc3fe1c2SSimon Glass elif result.stderr: 709*fc3fe1c2SSimon Glass self.warned += 1 710*fc3fe1c2SSimon Glass if result.already_done: 711*fc3fe1c2SSimon Glass self.already_done += 1 712*fc3fe1c2SSimon Glass else: 713*fc3fe1c2SSimon Glass target = '(starting)' 714*fc3fe1c2SSimon Glass 715*fc3fe1c2SSimon Glass # Display separate counts for ok, warned and fail 716*fc3fe1c2SSimon Glass ok = self.upto - self.warned - self.fail 717*fc3fe1c2SSimon Glass line = '\r' + self.col.Color(self.col.GREEN, '%5d' % ok) 718*fc3fe1c2SSimon Glass line += self.col.Color(self.col.YELLOW, '%5d' % self.warned) 719*fc3fe1c2SSimon Glass line += self.col.Color(self.col.RED, '%5d' % self.fail) 720*fc3fe1c2SSimon Glass 721*fc3fe1c2SSimon Glass name = ' /%-5d ' % self.count 722*fc3fe1c2SSimon Glass 723*fc3fe1c2SSimon Glass # Add our current completion time estimate 724*fc3fe1c2SSimon Glass self._AddTimestamp() 725*fc3fe1c2SSimon Glass if self._complete_delay: 726*fc3fe1c2SSimon Glass name += '%s : ' % self._complete_delay 727*fc3fe1c2SSimon Glass # When building all boards for a commit, we can print a commit 728*fc3fe1c2SSimon Glass # progress message. 729*fc3fe1c2SSimon Glass if result and result.commit_upto is None: 730*fc3fe1c2SSimon Glass name += 'commit %2d/%-3d' % (self.commit_upto + 1, 731*fc3fe1c2SSimon Glass self.commit_count) 732*fc3fe1c2SSimon Glass 733*fc3fe1c2SSimon Glass name += target 734*fc3fe1c2SSimon Glass print line + name, 735*fc3fe1c2SSimon Glass length = 13 + len(name) 736*fc3fe1c2SSimon Glass self.ClearLine(length) 737*fc3fe1c2SSimon Glass 738*fc3fe1c2SSimon Glass def _GetOutputDir(self, commit_upto): 739*fc3fe1c2SSimon Glass """Get the name of the output directory for a commit number 740*fc3fe1c2SSimon Glass 741*fc3fe1c2SSimon Glass The output directory is typically .../<branch>/<commit>. 742*fc3fe1c2SSimon Glass 743*fc3fe1c2SSimon Glass Args: 744*fc3fe1c2SSimon Glass commit_upto: Commit number to use (0..self.count-1) 745*fc3fe1c2SSimon Glass """ 746*fc3fe1c2SSimon Glass commit = self.commits[commit_upto] 747*fc3fe1c2SSimon Glass subject = commit.subject.translate(trans_valid_chars) 748*fc3fe1c2SSimon Glass commit_dir = ('%02d_of_%02d_g%s_%s' % (commit_upto + 1, 749*fc3fe1c2SSimon Glass self.commit_count, commit.hash, subject[:20])) 750*fc3fe1c2SSimon Glass output_dir = os.path.join(self.base_dir, commit_dir) 751*fc3fe1c2SSimon Glass return output_dir 752*fc3fe1c2SSimon Glass 753*fc3fe1c2SSimon Glass def GetBuildDir(self, commit_upto, target): 754*fc3fe1c2SSimon Glass """Get the name of the build directory for a commit number 755*fc3fe1c2SSimon Glass 756*fc3fe1c2SSimon Glass The build directory is typically .../<branch>/<commit>/<target>. 757*fc3fe1c2SSimon Glass 758*fc3fe1c2SSimon Glass Args: 759*fc3fe1c2SSimon Glass commit_upto: Commit number to use (0..self.count-1) 760*fc3fe1c2SSimon Glass target: Target name 761*fc3fe1c2SSimon Glass """ 762*fc3fe1c2SSimon Glass output_dir = self._GetOutputDir(commit_upto) 763*fc3fe1c2SSimon Glass return os.path.join(output_dir, target) 764*fc3fe1c2SSimon Glass 765*fc3fe1c2SSimon Glass def GetDoneFile(self, commit_upto, target): 766*fc3fe1c2SSimon Glass """Get the name of the done file for a commit number 767*fc3fe1c2SSimon Glass 768*fc3fe1c2SSimon Glass Args: 769*fc3fe1c2SSimon Glass commit_upto: Commit number to use (0..self.count-1) 770*fc3fe1c2SSimon Glass target: Target name 771*fc3fe1c2SSimon Glass """ 772*fc3fe1c2SSimon Glass return os.path.join(self.GetBuildDir(commit_upto, target), 'done') 773*fc3fe1c2SSimon Glass 774*fc3fe1c2SSimon Glass def GetSizesFile(self, commit_upto, target): 775*fc3fe1c2SSimon Glass """Get the name of the sizes file for a commit number 776*fc3fe1c2SSimon Glass 777*fc3fe1c2SSimon Glass Args: 778*fc3fe1c2SSimon Glass commit_upto: Commit number to use (0..self.count-1) 779*fc3fe1c2SSimon Glass target: Target name 780*fc3fe1c2SSimon Glass """ 781*fc3fe1c2SSimon Glass return os.path.join(self.GetBuildDir(commit_upto, target), 'sizes') 782*fc3fe1c2SSimon Glass 783*fc3fe1c2SSimon Glass def GetFuncSizesFile(self, commit_upto, target, elf_fname): 784*fc3fe1c2SSimon Glass """Get the name of the funcsizes file for a commit number and ELF file 785*fc3fe1c2SSimon Glass 786*fc3fe1c2SSimon Glass Args: 787*fc3fe1c2SSimon Glass commit_upto: Commit number to use (0..self.count-1) 788*fc3fe1c2SSimon Glass target: Target name 789*fc3fe1c2SSimon Glass elf_fname: Filename of elf image 790*fc3fe1c2SSimon Glass """ 791*fc3fe1c2SSimon Glass return os.path.join(self.GetBuildDir(commit_upto, target), 792*fc3fe1c2SSimon Glass '%s.sizes' % elf_fname.replace('/', '-')) 793*fc3fe1c2SSimon Glass 794*fc3fe1c2SSimon Glass def GetObjdumpFile(self, commit_upto, target, elf_fname): 795*fc3fe1c2SSimon Glass """Get the name of the objdump file for a commit number and ELF file 796*fc3fe1c2SSimon Glass 797*fc3fe1c2SSimon Glass Args: 798*fc3fe1c2SSimon Glass commit_upto: Commit number to use (0..self.count-1) 799*fc3fe1c2SSimon Glass target: Target name 800*fc3fe1c2SSimon Glass elf_fname: Filename of elf image 801*fc3fe1c2SSimon Glass """ 802*fc3fe1c2SSimon Glass return os.path.join(self.GetBuildDir(commit_upto, target), 803*fc3fe1c2SSimon Glass '%s.objdump' % elf_fname.replace('/', '-')) 804*fc3fe1c2SSimon Glass 805*fc3fe1c2SSimon Glass def GetErrFile(self, commit_upto, target): 806*fc3fe1c2SSimon Glass """Get the name of the err file for a commit number 807*fc3fe1c2SSimon Glass 808*fc3fe1c2SSimon Glass Args: 809*fc3fe1c2SSimon Glass commit_upto: Commit number to use (0..self.count-1) 810*fc3fe1c2SSimon Glass target: Target name 811*fc3fe1c2SSimon Glass """ 812*fc3fe1c2SSimon Glass output_dir = self.GetBuildDir(commit_upto, target) 813*fc3fe1c2SSimon Glass return os.path.join(output_dir, 'err') 814*fc3fe1c2SSimon Glass 815*fc3fe1c2SSimon Glass def FilterErrors(self, lines): 816*fc3fe1c2SSimon Glass """Filter out errors in which we have no interest 817*fc3fe1c2SSimon Glass 818*fc3fe1c2SSimon Glass We should probably use map(). 819*fc3fe1c2SSimon Glass 820*fc3fe1c2SSimon Glass Args: 821*fc3fe1c2SSimon Glass lines: List of error lines, each a string 822*fc3fe1c2SSimon Glass Returns: 823*fc3fe1c2SSimon Glass New list with only interesting lines included 824*fc3fe1c2SSimon Glass """ 825*fc3fe1c2SSimon Glass out_lines = [] 826*fc3fe1c2SSimon Glass for line in lines: 827*fc3fe1c2SSimon Glass if not self.re_make_err.search(line): 828*fc3fe1c2SSimon Glass out_lines.append(line) 829*fc3fe1c2SSimon Glass return out_lines 830*fc3fe1c2SSimon Glass 831*fc3fe1c2SSimon Glass def ReadFuncSizes(self, fname, fd): 832*fc3fe1c2SSimon Glass """Read function sizes from the output of 'nm' 833*fc3fe1c2SSimon Glass 834*fc3fe1c2SSimon Glass Args: 835*fc3fe1c2SSimon Glass fd: File containing data to read 836*fc3fe1c2SSimon Glass fname: Filename we are reading from (just for errors) 837*fc3fe1c2SSimon Glass 838*fc3fe1c2SSimon Glass Returns: 839*fc3fe1c2SSimon Glass Dictionary containing size of each function in bytes, indexed by 840*fc3fe1c2SSimon Glass function name. 841*fc3fe1c2SSimon Glass """ 842*fc3fe1c2SSimon Glass sym = {} 843*fc3fe1c2SSimon Glass for line in fd.readlines(): 844*fc3fe1c2SSimon Glass try: 845*fc3fe1c2SSimon Glass size, type, name = line[:-1].split() 846*fc3fe1c2SSimon Glass except: 847*fc3fe1c2SSimon Glass print "Invalid line in file '%s': '%s'" % (fname, line[:-1]) 848*fc3fe1c2SSimon Glass continue 849*fc3fe1c2SSimon Glass if type in 'tTdDbB': 850*fc3fe1c2SSimon Glass # function names begin with '.' on 64-bit powerpc 851*fc3fe1c2SSimon Glass if '.' in name[1:]: 852*fc3fe1c2SSimon Glass name = 'static.' + name.split('.')[0] 853*fc3fe1c2SSimon Glass sym[name] = sym.get(name, 0) + int(size, 16) 854*fc3fe1c2SSimon Glass return sym 855*fc3fe1c2SSimon Glass 856*fc3fe1c2SSimon Glass def GetBuildOutcome(self, commit_upto, target, read_func_sizes): 857*fc3fe1c2SSimon Glass """Work out the outcome of a build. 858*fc3fe1c2SSimon Glass 859*fc3fe1c2SSimon Glass Args: 860*fc3fe1c2SSimon Glass commit_upto: Commit number to check (0..n-1) 861*fc3fe1c2SSimon Glass target: Target board to check 862*fc3fe1c2SSimon Glass read_func_sizes: True to read function size information 863*fc3fe1c2SSimon Glass 864*fc3fe1c2SSimon Glass Returns: 865*fc3fe1c2SSimon Glass Outcome object 866*fc3fe1c2SSimon Glass """ 867*fc3fe1c2SSimon Glass done_file = self.GetDoneFile(commit_upto, target) 868*fc3fe1c2SSimon Glass sizes_file = self.GetSizesFile(commit_upto, target) 869*fc3fe1c2SSimon Glass sizes = {} 870*fc3fe1c2SSimon Glass func_sizes = {} 871*fc3fe1c2SSimon Glass if os.path.exists(done_file): 872*fc3fe1c2SSimon Glass with open(done_file, 'r') as fd: 873*fc3fe1c2SSimon Glass return_code = int(fd.readline()) 874*fc3fe1c2SSimon Glass err_lines = [] 875*fc3fe1c2SSimon Glass err_file = self.GetErrFile(commit_upto, target) 876*fc3fe1c2SSimon Glass if os.path.exists(err_file): 877*fc3fe1c2SSimon Glass with open(err_file, 'r') as fd: 878*fc3fe1c2SSimon Glass err_lines = self.FilterErrors(fd.readlines()) 879*fc3fe1c2SSimon Glass 880*fc3fe1c2SSimon Glass # Decide whether the build was ok, failed or created warnings 881*fc3fe1c2SSimon Glass if return_code: 882*fc3fe1c2SSimon Glass rc = OUTCOME_ERROR 883*fc3fe1c2SSimon Glass elif len(err_lines): 884*fc3fe1c2SSimon Glass rc = OUTCOME_WARNING 885*fc3fe1c2SSimon Glass else: 886*fc3fe1c2SSimon Glass rc = OUTCOME_OK 887*fc3fe1c2SSimon Glass 888*fc3fe1c2SSimon Glass # Convert size information to our simple format 889*fc3fe1c2SSimon Glass if os.path.exists(sizes_file): 890*fc3fe1c2SSimon Glass with open(sizes_file, 'r') as fd: 891*fc3fe1c2SSimon Glass for line in fd.readlines(): 892*fc3fe1c2SSimon Glass values = line.split() 893*fc3fe1c2SSimon Glass rodata = 0 894*fc3fe1c2SSimon Glass if len(values) > 6: 895*fc3fe1c2SSimon Glass rodata = int(values[6], 16) 896*fc3fe1c2SSimon Glass size_dict = { 897*fc3fe1c2SSimon Glass 'all' : int(values[0]) + int(values[1]) + 898*fc3fe1c2SSimon Glass int(values[2]), 899*fc3fe1c2SSimon Glass 'text' : int(values[0]) - rodata, 900*fc3fe1c2SSimon Glass 'data' : int(values[1]), 901*fc3fe1c2SSimon Glass 'bss' : int(values[2]), 902*fc3fe1c2SSimon Glass 'rodata' : rodata, 903*fc3fe1c2SSimon Glass } 904*fc3fe1c2SSimon Glass sizes[values[5]] = size_dict 905*fc3fe1c2SSimon Glass 906*fc3fe1c2SSimon Glass if read_func_sizes: 907*fc3fe1c2SSimon Glass pattern = self.GetFuncSizesFile(commit_upto, target, '*') 908*fc3fe1c2SSimon Glass for fname in glob.glob(pattern): 909*fc3fe1c2SSimon Glass with open(fname, 'r') as fd: 910*fc3fe1c2SSimon Glass dict_name = os.path.basename(fname).replace('.sizes', 911*fc3fe1c2SSimon Glass '') 912*fc3fe1c2SSimon Glass func_sizes[dict_name] = self.ReadFuncSizes(fname, fd) 913*fc3fe1c2SSimon Glass 914*fc3fe1c2SSimon Glass return Builder.Outcome(rc, err_lines, sizes, func_sizes) 915*fc3fe1c2SSimon Glass 916*fc3fe1c2SSimon Glass return Builder.Outcome(OUTCOME_UNKNOWN, [], {}, {}) 917*fc3fe1c2SSimon Glass 918*fc3fe1c2SSimon Glass def GetResultSummary(self, boards_selected, commit_upto, read_func_sizes): 919*fc3fe1c2SSimon Glass """Calculate a summary of the results of building a commit. 920*fc3fe1c2SSimon Glass 921*fc3fe1c2SSimon Glass Args: 922*fc3fe1c2SSimon Glass board_selected: Dict containing boards to summarise 923*fc3fe1c2SSimon Glass commit_upto: Commit number to summarize (0..self.count-1) 924*fc3fe1c2SSimon Glass read_func_sizes: True to read function size information 925*fc3fe1c2SSimon Glass 926*fc3fe1c2SSimon Glass Returns: 927*fc3fe1c2SSimon Glass Tuple: 928*fc3fe1c2SSimon Glass Dict containing boards which passed building this commit. 929*fc3fe1c2SSimon Glass keyed by board.target 930*fc3fe1c2SSimon Glass List containing a summary of error/warning lines 931*fc3fe1c2SSimon Glass """ 932*fc3fe1c2SSimon Glass board_dict = {} 933*fc3fe1c2SSimon Glass err_lines_summary = [] 934*fc3fe1c2SSimon Glass 935*fc3fe1c2SSimon Glass for board in boards_selected.itervalues(): 936*fc3fe1c2SSimon Glass outcome = self.GetBuildOutcome(commit_upto, board.target, 937*fc3fe1c2SSimon Glass read_func_sizes) 938*fc3fe1c2SSimon Glass board_dict[board.target] = outcome 939*fc3fe1c2SSimon Glass for err in outcome.err_lines: 940*fc3fe1c2SSimon Glass if err and not err.rstrip() in err_lines_summary: 941*fc3fe1c2SSimon Glass err_lines_summary.append(err.rstrip()) 942*fc3fe1c2SSimon Glass return board_dict, err_lines_summary 943*fc3fe1c2SSimon Glass 944*fc3fe1c2SSimon Glass def AddOutcome(self, board_dict, arch_list, changes, char, color): 945*fc3fe1c2SSimon Glass """Add an output to our list of outcomes for each architecture 946*fc3fe1c2SSimon Glass 947*fc3fe1c2SSimon Glass This simple function adds failing boards (changes) to the 948*fc3fe1c2SSimon Glass relevant architecture string, so we can print the results out 949*fc3fe1c2SSimon Glass sorted by architecture. 950*fc3fe1c2SSimon Glass 951*fc3fe1c2SSimon Glass Args: 952*fc3fe1c2SSimon Glass board_dict: Dict containing all boards 953*fc3fe1c2SSimon Glass arch_list: Dict keyed by arch name. Value is a string containing 954*fc3fe1c2SSimon Glass a list of board names which failed for that arch. 955*fc3fe1c2SSimon Glass changes: List of boards to add to arch_list 956*fc3fe1c2SSimon Glass color: terminal.Colour object 957*fc3fe1c2SSimon Glass """ 958*fc3fe1c2SSimon Glass done_arch = {} 959*fc3fe1c2SSimon Glass for target in changes: 960*fc3fe1c2SSimon Glass if target in board_dict: 961*fc3fe1c2SSimon Glass arch = board_dict[target].arch 962*fc3fe1c2SSimon Glass else: 963*fc3fe1c2SSimon Glass arch = 'unknown' 964*fc3fe1c2SSimon Glass str = self.col.Color(color, ' ' + target) 965*fc3fe1c2SSimon Glass if not arch in done_arch: 966*fc3fe1c2SSimon Glass str = self.col.Color(color, char) + ' ' + str 967*fc3fe1c2SSimon Glass done_arch[arch] = True 968*fc3fe1c2SSimon Glass if not arch in arch_list: 969*fc3fe1c2SSimon Glass arch_list[arch] = str 970*fc3fe1c2SSimon Glass else: 971*fc3fe1c2SSimon Glass arch_list[arch] += str 972*fc3fe1c2SSimon Glass 973*fc3fe1c2SSimon Glass 974*fc3fe1c2SSimon Glass def ColourNum(self, num): 975*fc3fe1c2SSimon Glass color = self.col.RED if num > 0 else self.col.GREEN 976*fc3fe1c2SSimon Glass if num == 0: 977*fc3fe1c2SSimon Glass return '0' 978*fc3fe1c2SSimon Glass return self.col.Color(color, str(num)) 979*fc3fe1c2SSimon Glass 980*fc3fe1c2SSimon Glass def ResetResultSummary(self, board_selected): 981*fc3fe1c2SSimon Glass """Reset the results summary ready for use. 982*fc3fe1c2SSimon Glass 983*fc3fe1c2SSimon Glass Set up the base board list to be all those selected, and set the 984*fc3fe1c2SSimon Glass error lines to empty. 985*fc3fe1c2SSimon Glass 986*fc3fe1c2SSimon Glass Following this, calls to PrintResultSummary() will use this 987*fc3fe1c2SSimon Glass information to work out what has changed. 988*fc3fe1c2SSimon Glass 989*fc3fe1c2SSimon Glass Args: 990*fc3fe1c2SSimon Glass board_selected: Dict containing boards to summarise, keyed by 991*fc3fe1c2SSimon Glass board.target 992*fc3fe1c2SSimon Glass """ 993*fc3fe1c2SSimon Glass self._base_board_dict = {} 994*fc3fe1c2SSimon Glass for board in board_selected: 995*fc3fe1c2SSimon Glass self._base_board_dict[board] = Builder.Outcome(0, [], [], {}) 996*fc3fe1c2SSimon Glass self._base_err_lines = [] 997*fc3fe1c2SSimon Glass 998*fc3fe1c2SSimon Glass def PrintFuncSizeDetail(self, fname, old, new): 999*fc3fe1c2SSimon Glass grow, shrink, add, remove, up, down = 0, 0, 0, 0, 0, 0 1000*fc3fe1c2SSimon Glass delta, common = [], {} 1001*fc3fe1c2SSimon Glass 1002*fc3fe1c2SSimon Glass for a in old: 1003*fc3fe1c2SSimon Glass if a in new: 1004*fc3fe1c2SSimon Glass common[a] = 1 1005*fc3fe1c2SSimon Glass 1006*fc3fe1c2SSimon Glass for name in old: 1007*fc3fe1c2SSimon Glass if name not in common: 1008*fc3fe1c2SSimon Glass remove += 1 1009*fc3fe1c2SSimon Glass down += old[name] 1010*fc3fe1c2SSimon Glass delta.append([-old[name], name]) 1011*fc3fe1c2SSimon Glass 1012*fc3fe1c2SSimon Glass for name in new: 1013*fc3fe1c2SSimon Glass if name not in common: 1014*fc3fe1c2SSimon Glass add += 1 1015*fc3fe1c2SSimon Glass up += new[name] 1016*fc3fe1c2SSimon Glass delta.append([new[name], name]) 1017*fc3fe1c2SSimon Glass 1018*fc3fe1c2SSimon Glass for name in common: 1019*fc3fe1c2SSimon Glass diff = new.get(name, 0) - old.get(name, 0) 1020*fc3fe1c2SSimon Glass if diff > 0: 1021*fc3fe1c2SSimon Glass grow, up = grow + 1, up + diff 1022*fc3fe1c2SSimon Glass elif diff < 0: 1023*fc3fe1c2SSimon Glass shrink, down = shrink + 1, down - diff 1024*fc3fe1c2SSimon Glass delta.append([diff, name]) 1025*fc3fe1c2SSimon Glass 1026*fc3fe1c2SSimon Glass delta.sort() 1027*fc3fe1c2SSimon Glass delta.reverse() 1028*fc3fe1c2SSimon Glass 1029*fc3fe1c2SSimon Glass args = [add, -remove, grow, -shrink, up, -down, up - down] 1030*fc3fe1c2SSimon Glass if max(args) == 0: 1031*fc3fe1c2SSimon Glass return 1032*fc3fe1c2SSimon Glass args = [self.ColourNum(x) for x in args] 1033*fc3fe1c2SSimon Glass indent = ' ' * 15 1034*fc3fe1c2SSimon Glass print ('%s%s: add: %s/%s, grow: %s/%s bytes: %s/%s (%s)' % 1035*fc3fe1c2SSimon Glass tuple([indent, self.col.Color(self.col.YELLOW, fname)] + args)) 1036*fc3fe1c2SSimon Glass print '%s %-38s %7s %7s %+7s' % (indent, 'function', 'old', 'new', 1037*fc3fe1c2SSimon Glass 'delta') 1038*fc3fe1c2SSimon Glass for diff, name in delta: 1039*fc3fe1c2SSimon Glass if diff: 1040*fc3fe1c2SSimon Glass color = self.col.RED if diff > 0 else self.col.GREEN 1041*fc3fe1c2SSimon Glass msg = '%s %-38s %7s %7s %+7d' % (indent, name, 1042*fc3fe1c2SSimon Glass old.get(name, '-'), new.get(name,'-'), diff) 1043*fc3fe1c2SSimon Glass print self.col.Color(color, msg) 1044*fc3fe1c2SSimon Glass 1045*fc3fe1c2SSimon Glass 1046*fc3fe1c2SSimon Glass def PrintSizeDetail(self, target_list, show_bloat): 1047*fc3fe1c2SSimon Glass """Show details size information for each board 1048*fc3fe1c2SSimon Glass 1049*fc3fe1c2SSimon Glass Args: 1050*fc3fe1c2SSimon Glass target_list: List of targets, each a dict containing: 1051*fc3fe1c2SSimon Glass 'target': Target name 1052*fc3fe1c2SSimon Glass 'total_diff': Total difference in bytes across all areas 1053*fc3fe1c2SSimon Glass <part_name>: Difference for that part 1054*fc3fe1c2SSimon Glass show_bloat: Show detail for each function 1055*fc3fe1c2SSimon Glass """ 1056*fc3fe1c2SSimon Glass targets_by_diff = sorted(target_list, reverse=True, 1057*fc3fe1c2SSimon Glass key=lambda x: x['_total_diff']) 1058*fc3fe1c2SSimon Glass for result in targets_by_diff: 1059*fc3fe1c2SSimon Glass printed_target = False 1060*fc3fe1c2SSimon Glass for name in sorted(result): 1061*fc3fe1c2SSimon Glass diff = result[name] 1062*fc3fe1c2SSimon Glass if name.startswith('_'): 1063*fc3fe1c2SSimon Glass continue 1064*fc3fe1c2SSimon Glass if diff != 0: 1065*fc3fe1c2SSimon Glass color = self.col.RED if diff > 0 else self.col.GREEN 1066*fc3fe1c2SSimon Glass msg = ' %s %+d' % (name, diff) 1067*fc3fe1c2SSimon Glass if not printed_target: 1068*fc3fe1c2SSimon Glass print '%10s %-15s:' % ('', result['_target']), 1069*fc3fe1c2SSimon Glass printed_target = True 1070*fc3fe1c2SSimon Glass print self.col.Color(color, msg), 1071*fc3fe1c2SSimon Glass if printed_target: 1072*fc3fe1c2SSimon Glass print 1073*fc3fe1c2SSimon Glass if show_bloat: 1074*fc3fe1c2SSimon Glass target = result['_target'] 1075*fc3fe1c2SSimon Glass outcome = result['_outcome'] 1076*fc3fe1c2SSimon Glass base_outcome = self._base_board_dict[target] 1077*fc3fe1c2SSimon Glass for fname in outcome.func_sizes: 1078*fc3fe1c2SSimon Glass self.PrintFuncSizeDetail(fname, 1079*fc3fe1c2SSimon Glass base_outcome.func_sizes[fname], 1080*fc3fe1c2SSimon Glass outcome.func_sizes[fname]) 1081*fc3fe1c2SSimon Glass 1082*fc3fe1c2SSimon Glass 1083*fc3fe1c2SSimon Glass def PrintSizeSummary(self, board_selected, board_dict, show_detail, 1084*fc3fe1c2SSimon Glass show_bloat): 1085*fc3fe1c2SSimon Glass """Print a summary of image sizes broken down by section. 1086*fc3fe1c2SSimon Glass 1087*fc3fe1c2SSimon Glass The summary takes the form of one line per architecture. The 1088*fc3fe1c2SSimon Glass line contains deltas for each of the sections (+ means the section 1089*fc3fe1c2SSimon Glass got bigger, - means smaller). The nunmbers are the average number 1090*fc3fe1c2SSimon Glass of bytes that a board in this section increased by. 1091*fc3fe1c2SSimon Glass 1092*fc3fe1c2SSimon Glass For example: 1093*fc3fe1c2SSimon Glass powerpc: (622 boards) text -0.0 1094*fc3fe1c2SSimon Glass arm: (285 boards) text -0.0 1095*fc3fe1c2SSimon Glass nds32: (3 boards) text -8.0 1096*fc3fe1c2SSimon Glass 1097*fc3fe1c2SSimon Glass Args: 1098*fc3fe1c2SSimon Glass board_selected: Dict containing boards to summarise, keyed by 1099*fc3fe1c2SSimon Glass board.target 1100*fc3fe1c2SSimon Glass board_dict: Dict containing boards for which we built this 1101*fc3fe1c2SSimon Glass commit, keyed by board.target. The value is an Outcome object. 1102*fc3fe1c2SSimon Glass show_detail: Show detail for each board 1103*fc3fe1c2SSimon Glass show_bloat: Show detail for each function 1104*fc3fe1c2SSimon Glass """ 1105*fc3fe1c2SSimon Glass arch_list = {} 1106*fc3fe1c2SSimon Glass arch_count = {} 1107*fc3fe1c2SSimon Glass 1108*fc3fe1c2SSimon Glass # Calculate changes in size for different image parts 1109*fc3fe1c2SSimon Glass # The previous sizes are in Board.sizes, for each board 1110*fc3fe1c2SSimon Glass for target in board_dict: 1111*fc3fe1c2SSimon Glass if target not in board_selected: 1112*fc3fe1c2SSimon Glass continue 1113*fc3fe1c2SSimon Glass base_sizes = self._base_board_dict[target].sizes 1114*fc3fe1c2SSimon Glass outcome = board_dict[target] 1115*fc3fe1c2SSimon Glass sizes = outcome.sizes 1116*fc3fe1c2SSimon Glass 1117*fc3fe1c2SSimon Glass # Loop through the list of images, creating a dict of size 1118*fc3fe1c2SSimon Glass # changes for each image/part. We end up with something like 1119*fc3fe1c2SSimon Glass # {'target' : 'snapper9g45, 'data' : 5, 'u-boot-spl:text' : -4} 1120*fc3fe1c2SSimon Glass # which means that U-Boot data increased by 5 bytes and SPL 1121*fc3fe1c2SSimon Glass # text decreased by 4. 1122*fc3fe1c2SSimon Glass err = {'_target' : target} 1123*fc3fe1c2SSimon Glass for image in sizes: 1124*fc3fe1c2SSimon Glass if image in base_sizes: 1125*fc3fe1c2SSimon Glass base_image = base_sizes[image] 1126*fc3fe1c2SSimon Glass # Loop through the text, data, bss parts 1127*fc3fe1c2SSimon Glass for part in sorted(sizes[image]): 1128*fc3fe1c2SSimon Glass diff = sizes[image][part] - base_image[part] 1129*fc3fe1c2SSimon Glass col = None 1130*fc3fe1c2SSimon Glass if diff: 1131*fc3fe1c2SSimon Glass if image == 'u-boot': 1132*fc3fe1c2SSimon Glass name = part 1133*fc3fe1c2SSimon Glass else: 1134*fc3fe1c2SSimon Glass name = image + ':' + part 1135*fc3fe1c2SSimon Glass err[name] = diff 1136*fc3fe1c2SSimon Glass arch = board_selected[target].arch 1137*fc3fe1c2SSimon Glass if not arch in arch_count: 1138*fc3fe1c2SSimon Glass arch_count[arch] = 1 1139*fc3fe1c2SSimon Glass else: 1140*fc3fe1c2SSimon Glass arch_count[arch] += 1 1141*fc3fe1c2SSimon Glass if not sizes: 1142*fc3fe1c2SSimon Glass pass # Only add to our list when we have some stats 1143*fc3fe1c2SSimon Glass elif not arch in arch_list: 1144*fc3fe1c2SSimon Glass arch_list[arch] = [err] 1145*fc3fe1c2SSimon Glass else: 1146*fc3fe1c2SSimon Glass arch_list[arch].append(err) 1147*fc3fe1c2SSimon Glass 1148*fc3fe1c2SSimon Glass # We now have a list of image size changes sorted by arch 1149*fc3fe1c2SSimon Glass # Print out a summary of these 1150*fc3fe1c2SSimon Glass for arch, target_list in arch_list.iteritems(): 1151*fc3fe1c2SSimon Glass # Get total difference for each type 1152*fc3fe1c2SSimon Glass totals = {} 1153*fc3fe1c2SSimon Glass for result in target_list: 1154*fc3fe1c2SSimon Glass total = 0 1155*fc3fe1c2SSimon Glass for name, diff in result.iteritems(): 1156*fc3fe1c2SSimon Glass if name.startswith('_'): 1157*fc3fe1c2SSimon Glass continue 1158*fc3fe1c2SSimon Glass total += diff 1159*fc3fe1c2SSimon Glass if name in totals: 1160*fc3fe1c2SSimon Glass totals[name] += diff 1161*fc3fe1c2SSimon Glass else: 1162*fc3fe1c2SSimon Glass totals[name] = diff 1163*fc3fe1c2SSimon Glass result['_total_diff'] = total 1164*fc3fe1c2SSimon Glass result['_outcome'] = board_dict[result['_target']] 1165*fc3fe1c2SSimon Glass 1166*fc3fe1c2SSimon Glass count = len(target_list) 1167*fc3fe1c2SSimon Glass printed_arch = False 1168*fc3fe1c2SSimon Glass for name in sorted(totals): 1169*fc3fe1c2SSimon Glass diff = totals[name] 1170*fc3fe1c2SSimon Glass if diff: 1171*fc3fe1c2SSimon Glass # Display the average difference in this name for this 1172*fc3fe1c2SSimon Glass # architecture 1173*fc3fe1c2SSimon Glass avg_diff = float(diff) / count 1174*fc3fe1c2SSimon Glass color = self.col.RED if avg_diff > 0 else self.col.GREEN 1175*fc3fe1c2SSimon Glass msg = ' %s %+1.1f' % (name, avg_diff) 1176*fc3fe1c2SSimon Glass if not printed_arch: 1177*fc3fe1c2SSimon Glass print '%10s: (for %d/%d boards)' % (arch, count, 1178*fc3fe1c2SSimon Glass arch_count[arch]), 1179*fc3fe1c2SSimon Glass printed_arch = True 1180*fc3fe1c2SSimon Glass print self.col.Color(color, msg), 1181*fc3fe1c2SSimon Glass 1182*fc3fe1c2SSimon Glass if printed_arch: 1183*fc3fe1c2SSimon Glass print 1184*fc3fe1c2SSimon Glass if show_detail: 1185*fc3fe1c2SSimon Glass self.PrintSizeDetail(target_list, show_bloat) 1186*fc3fe1c2SSimon Glass 1187*fc3fe1c2SSimon Glass 1188*fc3fe1c2SSimon Glass def PrintResultSummary(self, board_selected, board_dict, err_lines, 1189*fc3fe1c2SSimon Glass show_sizes, show_detail, show_bloat): 1190*fc3fe1c2SSimon Glass """Compare results with the base results and display delta. 1191*fc3fe1c2SSimon Glass 1192*fc3fe1c2SSimon Glass Only boards mentioned in board_selected will be considered. This 1193*fc3fe1c2SSimon Glass function is intended to be called repeatedly with the results of 1194*fc3fe1c2SSimon Glass each commit. It therefore shows a 'diff' between what it saw in 1195*fc3fe1c2SSimon Glass the last call and what it sees now. 1196*fc3fe1c2SSimon Glass 1197*fc3fe1c2SSimon Glass Args: 1198*fc3fe1c2SSimon Glass board_selected: Dict containing boards to summarise, keyed by 1199*fc3fe1c2SSimon Glass board.target 1200*fc3fe1c2SSimon Glass board_dict: Dict containing boards for which we built this 1201*fc3fe1c2SSimon Glass commit, keyed by board.target. The value is an Outcome object. 1202*fc3fe1c2SSimon Glass err_lines: A list of errors for this commit, or [] if there is 1203*fc3fe1c2SSimon Glass none, or we don't want to print errors 1204*fc3fe1c2SSimon Glass show_sizes: Show image size deltas 1205*fc3fe1c2SSimon Glass show_detail: Show detail for each board 1206*fc3fe1c2SSimon Glass show_bloat: Show detail for each function 1207*fc3fe1c2SSimon Glass """ 1208*fc3fe1c2SSimon Glass better = [] # List of boards fixed since last commit 1209*fc3fe1c2SSimon Glass worse = [] # List of new broken boards since last commit 1210*fc3fe1c2SSimon Glass new = [] # List of boards that didn't exist last time 1211*fc3fe1c2SSimon Glass unknown = [] # List of boards that were not built 1212*fc3fe1c2SSimon Glass 1213*fc3fe1c2SSimon Glass for target in board_dict: 1214*fc3fe1c2SSimon Glass if target not in board_selected: 1215*fc3fe1c2SSimon Glass continue 1216*fc3fe1c2SSimon Glass 1217*fc3fe1c2SSimon Glass # If the board was built last time, add its outcome to a list 1218*fc3fe1c2SSimon Glass if target in self._base_board_dict: 1219*fc3fe1c2SSimon Glass base_outcome = self._base_board_dict[target].rc 1220*fc3fe1c2SSimon Glass outcome = board_dict[target] 1221*fc3fe1c2SSimon Glass if outcome.rc == OUTCOME_UNKNOWN: 1222*fc3fe1c2SSimon Glass unknown.append(target) 1223*fc3fe1c2SSimon Glass elif outcome.rc < base_outcome: 1224*fc3fe1c2SSimon Glass better.append(target) 1225*fc3fe1c2SSimon Glass elif outcome.rc > base_outcome: 1226*fc3fe1c2SSimon Glass worse.append(target) 1227*fc3fe1c2SSimon Glass else: 1228*fc3fe1c2SSimon Glass new.append(target) 1229*fc3fe1c2SSimon Glass 1230*fc3fe1c2SSimon Glass # Get a list of errors that have appeared, and disappeared 1231*fc3fe1c2SSimon Glass better_err = [] 1232*fc3fe1c2SSimon Glass worse_err = [] 1233*fc3fe1c2SSimon Glass for line in err_lines: 1234*fc3fe1c2SSimon Glass if line not in self._base_err_lines: 1235*fc3fe1c2SSimon Glass worse_err.append('+' + line) 1236*fc3fe1c2SSimon Glass for line in self._base_err_lines: 1237*fc3fe1c2SSimon Glass if line not in err_lines: 1238*fc3fe1c2SSimon Glass better_err.append('-' + line) 1239*fc3fe1c2SSimon Glass 1240*fc3fe1c2SSimon Glass # Display results by arch 1241*fc3fe1c2SSimon Glass if better or worse or unknown or new or worse_err or better_err: 1242*fc3fe1c2SSimon Glass arch_list = {} 1243*fc3fe1c2SSimon Glass self.AddOutcome(board_selected, arch_list, better, '', 1244*fc3fe1c2SSimon Glass self.col.GREEN) 1245*fc3fe1c2SSimon Glass self.AddOutcome(board_selected, arch_list, worse, '+', 1246*fc3fe1c2SSimon Glass self.col.RED) 1247*fc3fe1c2SSimon Glass self.AddOutcome(board_selected, arch_list, new, '*', self.col.BLUE) 1248*fc3fe1c2SSimon Glass if self._show_unknown: 1249*fc3fe1c2SSimon Glass self.AddOutcome(board_selected, arch_list, unknown, '?', 1250*fc3fe1c2SSimon Glass self.col.MAGENTA) 1251*fc3fe1c2SSimon Glass for arch, target_list in arch_list.iteritems(): 1252*fc3fe1c2SSimon Glass print '%10s: %s' % (arch, target_list) 1253*fc3fe1c2SSimon Glass if better_err: 1254*fc3fe1c2SSimon Glass print self.col.Color(self.col.GREEN, '\n'.join(better_err)) 1255*fc3fe1c2SSimon Glass if worse_err: 1256*fc3fe1c2SSimon Glass print self.col.Color(self.col.RED, '\n'.join(worse_err)) 1257*fc3fe1c2SSimon Glass 1258*fc3fe1c2SSimon Glass if show_sizes: 1259*fc3fe1c2SSimon Glass self.PrintSizeSummary(board_selected, board_dict, show_detail, 1260*fc3fe1c2SSimon Glass show_bloat) 1261*fc3fe1c2SSimon Glass 1262*fc3fe1c2SSimon Glass # Save our updated information for the next call to this function 1263*fc3fe1c2SSimon Glass self._base_board_dict = board_dict 1264*fc3fe1c2SSimon Glass self._base_err_lines = err_lines 1265*fc3fe1c2SSimon Glass 1266*fc3fe1c2SSimon Glass # Get a list of boards that did not get built, if needed 1267*fc3fe1c2SSimon Glass not_built = [] 1268*fc3fe1c2SSimon Glass for board in board_selected: 1269*fc3fe1c2SSimon Glass if not board in board_dict: 1270*fc3fe1c2SSimon Glass not_built.append(board) 1271*fc3fe1c2SSimon Glass if not_built: 1272*fc3fe1c2SSimon Glass print "Boards not built (%d): %s" % (len(not_built), 1273*fc3fe1c2SSimon Glass ', '.join(not_built)) 1274*fc3fe1c2SSimon Glass 1275*fc3fe1c2SSimon Glass 1276*fc3fe1c2SSimon Glass def ShowSummary(self, commits, board_selected, show_errors, show_sizes, 1277*fc3fe1c2SSimon Glass show_detail, show_bloat): 1278*fc3fe1c2SSimon Glass """Show a build summary for U-Boot for a given board list. 1279*fc3fe1c2SSimon Glass 1280*fc3fe1c2SSimon Glass Reset the result summary, then repeatedly call GetResultSummary on 1281*fc3fe1c2SSimon Glass each commit's results, then display the differences we see. 1282*fc3fe1c2SSimon Glass 1283*fc3fe1c2SSimon Glass Args: 1284*fc3fe1c2SSimon Glass commit: Commit objects to summarise 1285*fc3fe1c2SSimon Glass board_selected: Dict containing boards to summarise 1286*fc3fe1c2SSimon Glass show_errors: Show errors that occured 1287*fc3fe1c2SSimon Glass show_sizes: Show size deltas 1288*fc3fe1c2SSimon Glass show_detail: Show detail for each board 1289*fc3fe1c2SSimon Glass show_bloat: Show detail for each function 1290*fc3fe1c2SSimon Glass """ 1291*fc3fe1c2SSimon Glass self.commit_count = len(commits) 1292*fc3fe1c2SSimon Glass self.commits = commits 1293*fc3fe1c2SSimon Glass self.ResetResultSummary(board_selected) 1294*fc3fe1c2SSimon Glass 1295*fc3fe1c2SSimon Glass for commit_upto in range(0, self.commit_count, self._step): 1296*fc3fe1c2SSimon Glass board_dict, err_lines = self.GetResultSummary(board_selected, 1297*fc3fe1c2SSimon Glass commit_upto, read_func_sizes=show_bloat) 1298*fc3fe1c2SSimon Glass msg = '%02d: %s' % (commit_upto + 1, commits[commit_upto].subject) 1299*fc3fe1c2SSimon Glass print self.col.Color(self.col.BLUE, msg) 1300*fc3fe1c2SSimon Glass self.PrintResultSummary(board_selected, board_dict, 1301*fc3fe1c2SSimon Glass err_lines if show_errors else [], show_sizes, show_detail, 1302*fc3fe1c2SSimon Glass show_bloat) 1303*fc3fe1c2SSimon Glass 1304*fc3fe1c2SSimon Glass 1305*fc3fe1c2SSimon Glass def SetupBuild(self, board_selected, commits): 1306*fc3fe1c2SSimon Glass """Set up ready to start a build. 1307*fc3fe1c2SSimon Glass 1308*fc3fe1c2SSimon Glass Args: 1309*fc3fe1c2SSimon Glass board_selected: Selected boards to build 1310*fc3fe1c2SSimon Glass commits: Selected commits to build 1311*fc3fe1c2SSimon Glass """ 1312*fc3fe1c2SSimon Glass # First work out how many commits we will build 1313*fc3fe1c2SSimon Glass count = (len(commits) + self._step - 1) / self._step 1314*fc3fe1c2SSimon Glass self.count = len(board_selected) * count 1315*fc3fe1c2SSimon Glass self.upto = self.warned = self.fail = 0 1316*fc3fe1c2SSimon Glass self._timestamps = collections.deque() 1317*fc3fe1c2SSimon Glass 1318*fc3fe1c2SSimon Glass def BuildBoardsForCommit(self, board_selected, keep_outputs): 1319*fc3fe1c2SSimon Glass """Build all boards for a single commit""" 1320*fc3fe1c2SSimon Glass self.SetupBuild(board_selected) 1321*fc3fe1c2SSimon Glass self.count = len(board_selected) 1322*fc3fe1c2SSimon Glass for brd in board_selected.itervalues(): 1323*fc3fe1c2SSimon Glass job = BuilderJob() 1324*fc3fe1c2SSimon Glass job.board = brd 1325*fc3fe1c2SSimon Glass job.commits = None 1326*fc3fe1c2SSimon Glass job.keep_outputs = keep_outputs 1327*fc3fe1c2SSimon Glass self.queue.put(brd) 1328*fc3fe1c2SSimon Glass 1329*fc3fe1c2SSimon Glass self.queue.join() 1330*fc3fe1c2SSimon Glass self.out_queue.join() 1331*fc3fe1c2SSimon Glass print 1332*fc3fe1c2SSimon Glass self.ClearLine(0) 1333*fc3fe1c2SSimon Glass 1334*fc3fe1c2SSimon Glass def BuildCommits(self, commits, board_selected, show_errors, keep_outputs): 1335*fc3fe1c2SSimon Glass """Build all boards for all commits (non-incremental)""" 1336*fc3fe1c2SSimon Glass self.commit_count = len(commits) 1337*fc3fe1c2SSimon Glass 1338*fc3fe1c2SSimon Glass self.ResetResultSummary(board_selected) 1339*fc3fe1c2SSimon Glass for self.commit_upto in range(self.commit_count): 1340*fc3fe1c2SSimon Glass self.SelectCommit(commits[self.commit_upto]) 1341*fc3fe1c2SSimon Glass self.SelectOutputDir() 1342*fc3fe1c2SSimon Glass Mkdir(self.output_dir) 1343*fc3fe1c2SSimon Glass 1344*fc3fe1c2SSimon Glass self.BuildBoardsForCommit(board_selected, keep_outputs) 1345*fc3fe1c2SSimon Glass board_dict, err_lines = self.GetResultSummary() 1346*fc3fe1c2SSimon Glass self.PrintResultSummary(board_selected, board_dict, 1347*fc3fe1c2SSimon Glass err_lines if show_errors else []) 1348*fc3fe1c2SSimon Glass 1349*fc3fe1c2SSimon Glass if self.already_done: 1350*fc3fe1c2SSimon Glass print '%d builds already done' % self.already_done 1351*fc3fe1c2SSimon Glass 1352*fc3fe1c2SSimon Glass def GetThreadDir(self, thread_num): 1353*fc3fe1c2SSimon Glass """Get the directory path to the working dir for a thread. 1354*fc3fe1c2SSimon Glass 1355*fc3fe1c2SSimon Glass Args: 1356*fc3fe1c2SSimon Glass thread_num: Number of thread to check. 1357*fc3fe1c2SSimon Glass """ 1358*fc3fe1c2SSimon Glass return os.path.join(self._working_dir, '%02d' % thread_num) 1359*fc3fe1c2SSimon Glass 1360*fc3fe1c2SSimon Glass def _PrepareThread(self, thread_num): 1361*fc3fe1c2SSimon Glass """Prepare the working directory for a thread. 1362*fc3fe1c2SSimon Glass 1363*fc3fe1c2SSimon Glass This clones or fetches the repo into the thread's work directory. 1364*fc3fe1c2SSimon Glass 1365*fc3fe1c2SSimon Glass Args: 1366*fc3fe1c2SSimon Glass thread_num: Thread number (0, 1, ...) 1367*fc3fe1c2SSimon Glass """ 1368*fc3fe1c2SSimon Glass thread_dir = self.GetThreadDir(thread_num) 1369*fc3fe1c2SSimon Glass Mkdir(thread_dir) 1370*fc3fe1c2SSimon Glass git_dir = os.path.join(thread_dir, '.git') 1371*fc3fe1c2SSimon Glass 1372*fc3fe1c2SSimon Glass # Clone the repo if it doesn't already exist 1373*fc3fe1c2SSimon Glass # TODO(sjg@chromium): Perhaps some git hackery to symlink instead, so 1374*fc3fe1c2SSimon Glass # we have a private index but uses the origin repo's contents? 1375*fc3fe1c2SSimon Glass if self.git_dir: 1376*fc3fe1c2SSimon Glass src_dir = os.path.abspath(self.git_dir) 1377*fc3fe1c2SSimon Glass if os.path.exists(git_dir): 1378*fc3fe1c2SSimon Glass gitutil.Fetch(git_dir, thread_dir) 1379*fc3fe1c2SSimon Glass else: 1380*fc3fe1c2SSimon Glass print 'Cloning repo for thread %d' % thread_num 1381*fc3fe1c2SSimon Glass gitutil.Clone(src_dir, thread_dir) 1382*fc3fe1c2SSimon Glass 1383*fc3fe1c2SSimon Glass def _PrepareWorkingSpace(self, max_threads): 1384*fc3fe1c2SSimon Glass """Prepare the working directory for use. 1385*fc3fe1c2SSimon Glass 1386*fc3fe1c2SSimon Glass Set up the git repo for each thread. 1387*fc3fe1c2SSimon Glass 1388*fc3fe1c2SSimon Glass Args: 1389*fc3fe1c2SSimon Glass max_threads: Maximum number of threads we expect to need. 1390*fc3fe1c2SSimon Glass """ 1391*fc3fe1c2SSimon Glass Mkdir(self._working_dir) 1392*fc3fe1c2SSimon Glass for thread in range(max_threads): 1393*fc3fe1c2SSimon Glass self._PrepareThread(thread) 1394*fc3fe1c2SSimon Glass 1395*fc3fe1c2SSimon Glass def _PrepareOutputSpace(self): 1396*fc3fe1c2SSimon Glass """Get the output directories ready to receive files. 1397*fc3fe1c2SSimon Glass 1398*fc3fe1c2SSimon Glass We delete any output directories which look like ones we need to 1399*fc3fe1c2SSimon Glass create. Having left over directories is confusing when the user wants 1400*fc3fe1c2SSimon Glass to check the output manually. 1401*fc3fe1c2SSimon Glass """ 1402*fc3fe1c2SSimon Glass dir_list = [] 1403*fc3fe1c2SSimon Glass for commit_upto in range(self.commit_count): 1404*fc3fe1c2SSimon Glass dir_list.append(self._GetOutputDir(commit_upto)) 1405*fc3fe1c2SSimon Glass 1406*fc3fe1c2SSimon Glass for dirname in glob.glob(os.path.join(self.base_dir, '*')): 1407*fc3fe1c2SSimon Glass if dirname not in dir_list: 1408*fc3fe1c2SSimon Glass shutil.rmtree(dirname) 1409*fc3fe1c2SSimon Glass 1410*fc3fe1c2SSimon Glass def BuildBoards(self, commits, board_selected, show_errors, keep_outputs): 1411*fc3fe1c2SSimon Glass """Build all commits for a list of boards 1412*fc3fe1c2SSimon Glass 1413*fc3fe1c2SSimon Glass Args: 1414*fc3fe1c2SSimon Glass commits: List of commits to be build, each a Commit object 1415*fc3fe1c2SSimon Glass boards_selected: Dict of selected boards, key is target name, 1416*fc3fe1c2SSimon Glass value is Board object 1417*fc3fe1c2SSimon Glass show_errors: True to show summarised error/warning info 1418*fc3fe1c2SSimon Glass keep_outputs: True to save build output files 1419*fc3fe1c2SSimon Glass """ 1420*fc3fe1c2SSimon Glass self.commit_count = len(commits) 1421*fc3fe1c2SSimon Glass self.commits = commits 1422*fc3fe1c2SSimon Glass 1423*fc3fe1c2SSimon Glass self.ResetResultSummary(board_selected) 1424*fc3fe1c2SSimon Glass Mkdir(self.base_dir) 1425*fc3fe1c2SSimon Glass self._PrepareWorkingSpace(min(self.num_threads, len(board_selected))) 1426*fc3fe1c2SSimon Glass self._PrepareOutputSpace() 1427*fc3fe1c2SSimon Glass self.SetupBuild(board_selected, commits) 1428*fc3fe1c2SSimon Glass self.ProcessResult(None) 1429*fc3fe1c2SSimon Glass 1430*fc3fe1c2SSimon Glass # Create jobs to build all commits for each board 1431*fc3fe1c2SSimon Glass for brd in board_selected.itervalues(): 1432*fc3fe1c2SSimon Glass job = BuilderJob() 1433*fc3fe1c2SSimon Glass job.board = brd 1434*fc3fe1c2SSimon Glass job.commits = commits 1435*fc3fe1c2SSimon Glass job.keep_outputs = keep_outputs 1436*fc3fe1c2SSimon Glass job.step = self._step 1437*fc3fe1c2SSimon Glass self.queue.put(job) 1438*fc3fe1c2SSimon Glass 1439*fc3fe1c2SSimon Glass # Wait until all jobs are started 1440*fc3fe1c2SSimon Glass self.queue.join() 1441*fc3fe1c2SSimon Glass 1442*fc3fe1c2SSimon Glass # Wait until we have processed all output 1443*fc3fe1c2SSimon Glass self.out_queue.join() 1444*fc3fe1c2SSimon Glass print 1445*fc3fe1c2SSimon Glass self.ClearLine(0) 1446