blob: d36e96faaf018054c19115c444ee7b80c24c2246 [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',
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -0700310 )
311 arch64_pats = (
Kyle Yan6a20fae2017-02-14 13:34:41 -0800312 r'msm*_defconfig',
313 r'sdm[0-9]*_defconfig',
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -0700314 )
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
326def 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
345def 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
429if __name__ == "__main__":
430 main()