blob: bd468cd4bb4e4903789e614e3210c1cbc871435a [file] [log] [blame]
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -07001#! /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
31from collections import namedtuple
32import glob
33from optparse import OptionParser
34import os
35import re
36import shutil
37import subprocess
38import sys
39import threading
40import Queue
41
42version = 'build-all.py, version 1.99'
43
44build_dir = '../all-kernels'
45make_command = ["vmlinux", "modules", "dtbs"]
46all_options = {}
47compile64 = os.environ.get('CROSS_COMPILE64')
48
49def error(msg):
50 sys.stderr.write("error: %s\n" % msg)
51
52def fail(msg):
53 """Fail with a user-printed message"""
54 error(msg)
55 sys.exit(1)
56
57if not os.environ.get('CROSS_COMPILE'):
58 fail("CROSS_COMPILE must be set in the environment")
59
60def check_kernel():
61 """Ensure that PWD is a kernel directory"""
62 have_defconfig = any([
63 os.path.isfile('arch/arm64/configs/msm_defconfig'),
Kyle Yan6a20fae2017-02-14 13:34:41 -080064 os.path.isfile('arch/arm64/configs/sdm845_defconfig')])
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -070065
66 if not all([os.path.isfile('MAINTAINERS'), have_defconfig]):
67 fail("This doesn't seem to be an MSM kernel dir")
68
69def 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
80failed_targets = []
81
82BuildResult = namedtuple('BuildResult', ['status', 'messages'])
83
84class 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
110class 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
171class 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
179class 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
188class 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
196class 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
205class 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
230class 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
296def 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
301def 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 Yan6a20fae2017-02-14 13:34:41 -0800308 r'mpq*_defconfig',
309 r'sdm[0-9]*_defconfig',
Runmin Wang37c5e5a2017-04-27 11:40:04 -0700310 r'sdx*_defconfig',
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -0700311 )
312 arch64_pats = (
Kyle Yan6a20fae2017-02-14 13:34:41 -0800313 r'msm*_defconfig',
314 r'sdm[0-9]*_defconfig',
Runmin Wang37c5e5a2017-04-27 11:40:04 -0700315 r'sdx*_defconfig',
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -0700316 )
317 for p in arch_pats:
318 for n in glob.glob('arch/arm/configs/' + p):
319 name = os.path.basename(n)[:-10]
320 names.append(Builder(name, n))
321 if 'CROSS_COMPILE64' in os.environ:
322 for p in arch64_pats:
323 for n in glob.glob('arch/arm64/configs/' + p):
324 name = os.path.basename(n)[:-10] + "-64"
325 names.append(Builder(name, n))
326 return names
327
328def build_many(targets):
329 print "Building %d target(s)" % len(targets)
330
331 # To try and make up for the link phase being serial, try to do
332 # two full builds in parallel. Don't do too many because lots of
333 # parallel builds tends to use up available memory rather quickly.
334 parallel = 2
335 if all_options.jobs and all_options.jobs > 1:
336 j = max(all_options.jobs / parallel, 2)
337 make_command.append("-j" + str(j))
338
339 tracker = BuildTracker(parallel)
340 for target in targets:
341 if all_options.updateconfigs:
342 update_config(target.defconfig, all_options.updateconfigs)
343 steps = target.build()
344 tracker.add_sequence(target.log_name, target.name, steps)
345 tracker.run()
346
347def main():
348 global make_command
349
350 check_kernel()
351 check_build()
352
353 configs = scan_configs()
354
355 usage = ("""
356 %prog [options] all -- Build all targets
357 %prog [options] target target ... -- List specific targets
358 %prog [options] perf -- Build all perf targets
359 %prog [options] noperf -- Build all non-perf targets""")
360 parser = OptionParser(usage=usage, version=version)
361 parser.add_option('--configs', action='store_true',
362 dest='configs',
363 help="Copy configs back into tree")
364 parser.add_option('--list', action='store_true',
365 dest='list',
366 help='List available targets')
367 parser.add_option('-v', '--verbose', action='store_true',
368 dest='verbose',
369 help='Output to stdout in addition to log file')
370 parser.add_option('--oldconfig', action='store_true',
371 dest='oldconfig',
372 help='Only process "make oldconfig"')
373 parser.add_option('--updateconfigs',
374 dest='updateconfigs',
375 help="Update defconfigs with provided option setting, "
376 "e.g. --updateconfigs=\'CONFIG_USE_THING=y\'")
377 parser.add_option('-j', '--jobs', type='int', dest="jobs",
378 help="Number of simultaneous jobs")
379 parser.add_option('-l', '--load-average', type='int',
380 dest='load_average',
381 help="Don't start multiple jobs unless load is below LOAD_AVERAGE")
382 parser.add_option('-k', '--keep-going', action='store_true',
383 dest='keep_going', default=False,
384 help="Keep building other targets if a target fails")
385 parser.add_option('-m', '--make-target', action='append',
386 help='Build the indicated make target (default: %s)' %
387 ' '.join(make_command))
388
389 (options, args) = parser.parse_args()
390 global all_options
391 all_options = options
392
393 if options.list:
394 print "Available targets:"
395 for target in configs:
396 print " %s" % target.name
397 sys.exit(0)
398
399 if options.oldconfig:
400 make_command = ["oldconfig"]
401 elif options.make_target:
402 make_command = options.make_target
403
404 if args == ['all']:
405 build_many(configs)
406 elif args == ['perf']:
407 targets = []
408 for t in configs:
409 if "perf" in t.name:
410 targets.append(t)
411 build_many(targets)
412 elif args == ['noperf']:
413 targets = []
414 for t in configs:
415 if "perf" not in t.name:
416 targets.append(t)
417 build_many(targets)
418 elif len(args) > 0:
419 all_configs = {}
420 for t in configs:
421 all_configs[t.name] = t
422 targets = []
423 for t in args:
424 if t not in all_configs:
425 parser.error("Target '%s' not one of %s" % (t, all_configs.keys()))
426 targets.append(all_configs[t])
427 build_many(targets)
428 else:
429 parser.error("Must specify a target to build, or 'all'")
430
431if __name__ == "__main__":
432 main()