Channagoud Kadabi | 1a82c2b | 2016-07-01 12:28:20 -0700 | [diff] [blame] | 1 | #! /usr/bin/env python2 |
| 2 | |
| 3 | # Copyright (c) 2009-2015, The Linux Foundation. All rights reserved. |
| 4 | # |
| 5 | # Redistribution and use in source and binary forms, with or without |
| 6 | # modification, are permitted provided that the following conditions are met: |
| 7 | # * Redistributions of source code must retain the above copyright |
| 8 | # notice, this list of conditions and the following disclaimer. |
| 9 | # * Redistributions in binary form must reproduce the above copyright |
| 10 | # notice, this list of conditions and the following disclaimer in the |
| 11 | # documentation and/or other materials provided with the distribution. |
| 12 | # * Neither the name of The Linux Foundation nor |
| 13 | # the names of its contributors may be used to endorse or promote |
| 14 | # products derived from this software without specific prior written |
| 15 | # permission. |
| 16 | # |
| 17 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| 18 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| 19 | # IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| 20 | # NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
| 21 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| 22 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| 23 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; |
| 24 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
| 25 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR |
| 26 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
| 27 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 28 | |
| 29 | # Build the kernel for all targets using the Android build environment. |
| 30 | |
| 31 | from collections import namedtuple |
| 32 | import glob |
| 33 | from optparse import OptionParser |
| 34 | import os |
| 35 | import re |
| 36 | import shutil |
| 37 | import subprocess |
| 38 | import sys |
| 39 | import threading |
| 40 | import Queue |
| 41 | |
| 42 | version = 'build-all.py, version 1.99' |
| 43 | |
| 44 | build_dir = '../all-kernels' |
| 45 | make_command = ["vmlinux", "modules", "dtbs"] |
| 46 | all_options = {} |
| 47 | compile64 = os.environ.get('CROSS_COMPILE64') |
| 48 | |
| 49 | def error(msg): |
| 50 | sys.stderr.write("error: %s\n" % msg) |
| 51 | |
| 52 | def fail(msg): |
| 53 | """Fail with a user-printed message""" |
| 54 | error(msg) |
| 55 | sys.exit(1) |
| 56 | |
| 57 | if not os.environ.get('CROSS_COMPILE'): |
| 58 | fail("CROSS_COMPILE must be set in the environment") |
| 59 | |
| 60 | def check_kernel(): |
| 61 | """Ensure that PWD is a kernel directory""" |
| 62 | have_defconfig = any([ |
| 63 | os.path.isfile('arch/arm64/configs/msm_defconfig'), |
Kyle Yan | 6a20fae | 2017-02-14 13:34:41 -0800 | [diff] [blame] | 64 | os.path.isfile('arch/arm64/configs/sdm845_defconfig')]) |
Channagoud Kadabi | 1a82c2b | 2016-07-01 12:28:20 -0700 | [diff] [blame] | 65 | |
| 66 | if not all([os.path.isfile('MAINTAINERS'), have_defconfig]): |
| 67 | fail("This doesn't seem to be an MSM kernel dir") |
| 68 | |
| 69 | def check_build(): |
| 70 | """Ensure that the build directory is present.""" |
| 71 | if not os.path.isdir(build_dir): |
| 72 | try: |
| 73 | os.makedirs(build_dir) |
| 74 | except OSError as exc: |
| 75 | if exc.errno == errno.EEXIST: |
| 76 | pass |
| 77 | else: |
| 78 | raise |
| 79 | |
| 80 | failed_targets = [] |
| 81 | |
| 82 | BuildResult = namedtuple('BuildResult', ['status', 'messages']) |
| 83 | |
| 84 | class BuildSequence(namedtuple('BuildSequence', ['log_name', 'short_name', 'steps'])): |
| 85 | |
| 86 | def set_width(self, width): |
| 87 | self.width = width |
| 88 | |
| 89 | def __enter__(self): |
| 90 | self.log = open(self.log_name, 'w') |
| 91 | def __exit__(self, type, value, traceback): |
| 92 | self.log.close() |
| 93 | |
| 94 | def run(self): |
| 95 | self.status = None |
| 96 | messages = ["Building: " + self.short_name] |
| 97 | def printer(line): |
| 98 | text = "[%-*s] %s" % (self.width, self.short_name, line) |
| 99 | messages.append(text) |
| 100 | self.log.write(text) |
| 101 | self.log.write('\n') |
| 102 | for step in self.steps: |
| 103 | st = step.run(printer) |
| 104 | if st: |
| 105 | self.status = BuildResult(self.short_name, messages) |
| 106 | break |
| 107 | if not self.status: |
| 108 | self.status = BuildResult(None, messages) |
| 109 | |
| 110 | class BuildTracker: |
| 111 | """Manages all of the steps necessary to perform a build. The |
| 112 | build consists of one or more sequences of steps. The different |
| 113 | sequences can be processed independently, while the steps within a |
| 114 | sequence must be done in order.""" |
| 115 | |
| 116 | def __init__(self, parallel_builds): |
| 117 | self.sequence = [] |
| 118 | self.lock = threading.Lock() |
| 119 | self.parallel_builds = parallel_builds |
| 120 | |
| 121 | def add_sequence(self, log_name, short_name, steps): |
| 122 | self.sequence.append(BuildSequence(log_name, short_name, steps)) |
| 123 | |
| 124 | def longest_name(self): |
| 125 | longest = 0 |
| 126 | for seq in self.sequence: |
| 127 | longest = max(longest, len(seq.short_name)) |
| 128 | return longest |
| 129 | |
| 130 | def __repr__(self): |
| 131 | return "BuildTracker(%s)" % self.sequence |
| 132 | |
| 133 | def run_child(self, seq): |
| 134 | seq.set_width(self.longest) |
| 135 | tok = self.build_tokens.get() |
| 136 | with self.lock: |
| 137 | print "Building:", seq.short_name |
| 138 | with seq: |
| 139 | seq.run() |
| 140 | self.results.put(seq.status) |
| 141 | self.build_tokens.put(tok) |
| 142 | |
| 143 | def run(self): |
| 144 | self.longest = self.longest_name() |
| 145 | self.results = Queue.Queue() |
| 146 | children = [] |
| 147 | errors = [] |
| 148 | self.build_tokens = Queue.Queue() |
| 149 | nthreads = self.parallel_builds |
| 150 | print "Building with", nthreads, "threads" |
| 151 | for i in range(nthreads): |
| 152 | self.build_tokens.put(True) |
| 153 | for seq in self.sequence: |
| 154 | child = threading.Thread(target=self.run_child, args=[seq]) |
| 155 | children.append(child) |
| 156 | child.start() |
| 157 | for child in children: |
| 158 | stats = self.results.get() |
| 159 | if all_options.verbose: |
| 160 | with self.lock: |
| 161 | for line in stats.messages: |
| 162 | print line |
| 163 | sys.stdout.flush() |
| 164 | if stats.status: |
| 165 | errors.append(stats.status) |
| 166 | for child in children: |
| 167 | child.join() |
| 168 | if errors: |
| 169 | fail("\n ".join(["Failed targets:"] + errors)) |
| 170 | |
| 171 | class PrintStep: |
| 172 | """A step that just prints a message""" |
| 173 | def __init__(self, message): |
| 174 | self.message = message |
| 175 | |
| 176 | def run(self, outp): |
| 177 | outp(self.message) |
| 178 | |
| 179 | class MkdirStep: |
| 180 | """A step that makes a directory""" |
| 181 | def __init__(self, direc): |
| 182 | self.direc = direc |
| 183 | |
| 184 | def run(self, outp): |
| 185 | outp("mkdir %s" % self.direc) |
| 186 | os.mkdir(self.direc) |
| 187 | |
| 188 | class RmtreeStep: |
| 189 | def __init__(self, direc): |
| 190 | self.direc = direc |
| 191 | |
| 192 | def run(self, outp): |
| 193 | outp("rmtree %s" % self.direc) |
| 194 | shutil.rmtree(self.direc, ignore_errors=True) |
| 195 | |
| 196 | class CopyfileStep: |
| 197 | def __init__(self, src, dest): |
| 198 | self.src = src |
| 199 | self.dest = dest |
| 200 | |
| 201 | def run(self, outp): |
| 202 | outp("cp %s %s" % (self.src, self.dest)) |
| 203 | shutil.copyfile(self.src, self.dest) |
| 204 | |
| 205 | class ExecStep: |
| 206 | def __init__(self, cmd, **kwargs): |
| 207 | self.cmd = cmd |
| 208 | self.kwargs = kwargs |
| 209 | |
| 210 | def run(self, outp): |
| 211 | outp("exec: %s" % (" ".join(self.cmd),)) |
| 212 | with open('/dev/null', 'r') as devnull: |
| 213 | proc = subprocess.Popen(self.cmd, stdin=devnull, |
| 214 | stdout=subprocess.PIPE, |
| 215 | stderr=subprocess.STDOUT, |
| 216 | **self.kwargs) |
| 217 | stdout = proc.stdout |
| 218 | while True: |
| 219 | line = stdout.readline() |
| 220 | if not line: |
| 221 | break |
| 222 | line = line.rstrip('\n') |
| 223 | outp(line) |
| 224 | result = proc.wait() |
| 225 | if result != 0: |
| 226 | return ('error', result) |
| 227 | else: |
| 228 | return None |
| 229 | |
| 230 | class Builder(): |
| 231 | |
| 232 | def __init__(self, name, defconfig): |
| 233 | self.name = name |
| 234 | self.defconfig = defconfig |
| 235 | |
| 236 | self.confname = self.defconfig.split('/')[-1] |
| 237 | |
| 238 | # Determine if this is a 64-bit target based on the location |
| 239 | # of the defconfig. |
| 240 | self.make_env = os.environ.copy() |
| 241 | if "/arm64/" in defconfig: |
| 242 | if compile64: |
| 243 | self.make_env['CROSS_COMPILE'] = compile64 |
| 244 | else: |
| 245 | fail("Attempting to build 64-bit, without setting CROSS_COMPILE64") |
| 246 | self.make_env['ARCH'] = 'arm64' |
| 247 | else: |
| 248 | self.make_env['ARCH'] = 'arm' |
| 249 | self.make_env['KCONFIG_NOTIMESTAMP'] = 'true' |
| 250 | self.log_name = "%s/log-%s.log" % (build_dir, self.name) |
| 251 | |
| 252 | def build(self): |
| 253 | steps = [] |
| 254 | dest_dir = os.path.join(build_dir, self.name) |
| 255 | log_name = "%s/log-%s.log" % (build_dir, self.name) |
| 256 | steps.append(PrintStep('Building %s in %s log %s' % |
| 257 | (self.name, dest_dir, log_name))) |
| 258 | if not os.path.isdir(dest_dir): |
| 259 | steps.append(MkdirStep(dest_dir)) |
| 260 | defconfig = self.defconfig |
| 261 | dotconfig = '%s/.config' % dest_dir |
| 262 | savedefconfig = '%s/defconfig' % dest_dir |
| 263 | |
| 264 | staging_dir = 'install_staging' |
| 265 | modi_dir = '%s' % staging_dir |
| 266 | hdri_dir = '%s/usr' % staging_dir |
| 267 | steps.append(RmtreeStep(os.path.join(dest_dir, staging_dir))) |
| 268 | |
| 269 | steps.append(ExecStep(['make', 'O=%s' % dest_dir, |
| 270 | self.confname], env=self.make_env)) |
| 271 | |
| 272 | if not all_options.updateconfigs: |
| 273 | # Build targets can be dependent upon the completion of |
| 274 | # previous build targets, so build them one at a time. |
| 275 | cmd_line = ['make', |
| 276 | 'INSTALL_HDR_PATH=%s' % hdri_dir, |
| 277 | 'INSTALL_MOD_PATH=%s' % modi_dir, |
| 278 | 'O=%s' % dest_dir] |
| 279 | build_targets = [] |
| 280 | for c in make_command: |
| 281 | if re.match(r'^-{1,2}\w', c): |
| 282 | cmd_line.append(c) |
| 283 | else: |
| 284 | build_targets.append(c) |
| 285 | for t in build_targets: |
| 286 | steps.append(ExecStep(cmd_line + [t], env=self.make_env)) |
| 287 | |
| 288 | # Copy the defconfig back. |
| 289 | if all_options.configs or all_options.updateconfigs: |
| 290 | steps.append(ExecStep(['make', 'O=%s' % dest_dir, |
| 291 | 'savedefconfig'], env=self.make_env)) |
| 292 | steps.append(CopyfileStep(savedefconfig, defconfig)) |
| 293 | |
| 294 | return steps |
| 295 | |
| 296 | def update_config(file, str): |
| 297 | print 'Updating %s with \'%s\'\n' % (file, str) |
| 298 | with open(file, 'a') as defconfig: |
| 299 | defconfig.write(str + '\n') |
| 300 | |
| 301 | def scan_configs(): |
| 302 | """Get the full list of defconfigs appropriate for this tree.""" |
| 303 | names = [] |
| 304 | arch_pats = ( |
| 305 | r'[fm]sm[0-9]*_defconfig', |
| 306 | r'apq*_defconfig', |
| 307 | r'qsd*_defconfig', |
Kyle Yan | 6a20fae | 2017-02-14 13:34:41 -0800 | [diff] [blame] | 308 | r'mpq*_defconfig', |
| 309 | r'sdm[0-9]*_defconfig', |
Channagoud Kadabi | 1a82c2b | 2016-07-01 12:28:20 -0700 | [diff] [blame] | 310 | ) |
| 311 | arch64_pats = ( |
Kyle Yan | 6a20fae | 2017-02-14 13:34:41 -0800 | [diff] [blame] | 312 | r'msm*_defconfig', |
| 313 | r'sdm[0-9]*_defconfig', |
Channagoud Kadabi | 1a82c2b | 2016-07-01 12:28:20 -0700 | [diff] [blame] | 314 | ) |
| 315 | for p in arch_pats: |
| 316 | for n in glob.glob('arch/arm/configs/' + p): |
| 317 | name = os.path.basename(n)[:-10] |
| 318 | names.append(Builder(name, n)) |
| 319 | if 'CROSS_COMPILE64' in os.environ: |
| 320 | for p in arch64_pats: |
| 321 | for n in glob.glob('arch/arm64/configs/' + p): |
| 322 | name = os.path.basename(n)[:-10] + "-64" |
| 323 | names.append(Builder(name, n)) |
| 324 | return names |
| 325 | |
| 326 | def build_many(targets): |
| 327 | print "Building %d target(s)" % len(targets) |
| 328 | |
| 329 | # To try and make up for the link phase being serial, try to do |
| 330 | # two full builds in parallel. Don't do too many because lots of |
| 331 | # parallel builds tends to use up available memory rather quickly. |
| 332 | parallel = 2 |
| 333 | if all_options.jobs and all_options.jobs > 1: |
| 334 | j = max(all_options.jobs / parallel, 2) |
| 335 | make_command.append("-j" + str(j)) |
| 336 | |
| 337 | tracker = BuildTracker(parallel) |
| 338 | for target in targets: |
| 339 | if all_options.updateconfigs: |
| 340 | update_config(target.defconfig, all_options.updateconfigs) |
| 341 | steps = target.build() |
| 342 | tracker.add_sequence(target.log_name, target.name, steps) |
| 343 | tracker.run() |
| 344 | |
| 345 | def main(): |
| 346 | global make_command |
| 347 | |
| 348 | check_kernel() |
| 349 | check_build() |
| 350 | |
| 351 | configs = scan_configs() |
| 352 | |
| 353 | usage = (""" |
| 354 | %prog [options] all -- Build all targets |
| 355 | %prog [options] target target ... -- List specific targets |
| 356 | %prog [options] perf -- Build all perf targets |
| 357 | %prog [options] noperf -- Build all non-perf targets""") |
| 358 | parser = OptionParser(usage=usage, version=version) |
| 359 | parser.add_option('--configs', action='store_true', |
| 360 | dest='configs', |
| 361 | help="Copy configs back into tree") |
| 362 | parser.add_option('--list', action='store_true', |
| 363 | dest='list', |
| 364 | help='List available targets') |
| 365 | parser.add_option('-v', '--verbose', action='store_true', |
| 366 | dest='verbose', |
| 367 | help='Output to stdout in addition to log file') |
| 368 | parser.add_option('--oldconfig', action='store_true', |
| 369 | dest='oldconfig', |
| 370 | help='Only process "make oldconfig"') |
| 371 | parser.add_option('--updateconfigs', |
| 372 | dest='updateconfigs', |
| 373 | help="Update defconfigs with provided option setting, " |
| 374 | "e.g. --updateconfigs=\'CONFIG_USE_THING=y\'") |
| 375 | parser.add_option('-j', '--jobs', type='int', dest="jobs", |
| 376 | help="Number of simultaneous jobs") |
| 377 | parser.add_option('-l', '--load-average', type='int', |
| 378 | dest='load_average', |
| 379 | help="Don't start multiple jobs unless load is below LOAD_AVERAGE") |
| 380 | parser.add_option('-k', '--keep-going', action='store_true', |
| 381 | dest='keep_going', default=False, |
| 382 | help="Keep building other targets if a target fails") |
| 383 | parser.add_option('-m', '--make-target', action='append', |
| 384 | help='Build the indicated make target (default: %s)' % |
| 385 | ' '.join(make_command)) |
| 386 | |
| 387 | (options, args) = parser.parse_args() |
| 388 | global all_options |
| 389 | all_options = options |
| 390 | |
| 391 | if options.list: |
| 392 | print "Available targets:" |
| 393 | for target in configs: |
| 394 | print " %s" % target.name |
| 395 | sys.exit(0) |
| 396 | |
| 397 | if options.oldconfig: |
| 398 | make_command = ["oldconfig"] |
| 399 | elif options.make_target: |
| 400 | make_command = options.make_target |
| 401 | |
| 402 | if args == ['all']: |
| 403 | build_many(configs) |
| 404 | elif args == ['perf']: |
| 405 | targets = [] |
| 406 | for t in configs: |
| 407 | if "perf" in t.name: |
| 408 | targets.append(t) |
| 409 | build_many(targets) |
| 410 | elif args == ['noperf']: |
| 411 | targets = [] |
| 412 | for t in configs: |
| 413 | if "perf" not in t.name: |
| 414 | targets.append(t) |
| 415 | build_many(targets) |
| 416 | elif len(args) > 0: |
| 417 | all_configs = {} |
| 418 | for t in configs: |
| 419 | all_configs[t.name] = t |
| 420 | targets = [] |
| 421 | for t in args: |
| 422 | if t not in all_configs: |
| 423 | parser.error("Target '%s' not one of %s" % (t, all_configs.keys())) |
| 424 | targets.append(all_configs[t]) |
| 425 | build_many(targets) |
| 426 | else: |
| 427 | parser.error("Must specify a target to build, or 'all'") |
| 428 | |
| 429 | if __name__ == "__main__": |
| 430 | main() |