blob: 0f8babf4f27d9501a485ca9d571a055177ef05f0 [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'),
64 os.path.isfile('arch/arm64/configs/msmskunk_defconfig')])
65
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',
308 r'mpq*_defconfig',
309 )
310 arch64_pats = (
311 r'msm*_defconfig',
312 )
313 for p in arch_pats:
314 for n in glob.glob('arch/arm/configs/' + p):
315 name = os.path.basename(n)[:-10]
316 names.append(Builder(name, n))
317 if 'CROSS_COMPILE64' in os.environ:
318 for p in arch64_pats:
319 for n in glob.glob('arch/arm64/configs/' + p):
320 name = os.path.basename(n)[:-10] + "-64"
321 names.append(Builder(name, n))
322 return names
323
324def build_many(targets):
325 print "Building %d target(s)" % len(targets)
326
327 # To try and make up for the link phase being serial, try to do
328 # two full builds in parallel. Don't do too many because lots of
329 # parallel builds tends to use up available memory rather quickly.
330 parallel = 2
331 if all_options.jobs and all_options.jobs > 1:
332 j = max(all_options.jobs / parallel, 2)
333 make_command.append("-j" + str(j))
334
335 tracker = BuildTracker(parallel)
336 for target in targets:
337 if all_options.updateconfigs:
338 update_config(target.defconfig, all_options.updateconfigs)
339 steps = target.build()
340 tracker.add_sequence(target.log_name, target.name, steps)
341 tracker.run()
342
343def main():
344 global make_command
345
346 check_kernel()
347 check_build()
348
349 configs = scan_configs()
350
351 usage = ("""
352 %prog [options] all -- Build all targets
353 %prog [options] target target ... -- List specific targets
354 %prog [options] perf -- Build all perf targets
355 %prog [options] noperf -- Build all non-perf targets""")
356 parser = OptionParser(usage=usage, version=version)
357 parser.add_option('--configs', action='store_true',
358 dest='configs',
359 help="Copy configs back into tree")
360 parser.add_option('--list', action='store_true',
361 dest='list',
362 help='List available targets')
363 parser.add_option('-v', '--verbose', action='store_true',
364 dest='verbose',
365 help='Output to stdout in addition to log file')
366 parser.add_option('--oldconfig', action='store_true',
367 dest='oldconfig',
368 help='Only process "make oldconfig"')
369 parser.add_option('--updateconfigs',
370 dest='updateconfigs',
371 help="Update defconfigs with provided option setting, "
372 "e.g. --updateconfigs=\'CONFIG_USE_THING=y\'")
373 parser.add_option('-j', '--jobs', type='int', dest="jobs",
374 help="Number of simultaneous jobs")
375 parser.add_option('-l', '--load-average', type='int',
376 dest='load_average',
377 help="Don't start multiple jobs unless load is below LOAD_AVERAGE")
378 parser.add_option('-k', '--keep-going', action='store_true',
379 dest='keep_going', default=False,
380 help="Keep building other targets if a target fails")
381 parser.add_option('-m', '--make-target', action='append',
382 help='Build the indicated make target (default: %s)' %
383 ' '.join(make_command))
384
385 (options, args) = parser.parse_args()
386 global all_options
387 all_options = options
388
389 if options.list:
390 print "Available targets:"
391 for target in configs:
392 print " %s" % target.name
393 sys.exit(0)
394
395 if options.oldconfig:
396 make_command = ["oldconfig"]
397 elif options.make_target:
398 make_command = options.make_target
399
400 if args == ['all']:
401 build_many(configs)
402 elif args == ['perf']:
403 targets = []
404 for t in configs:
405 if "perf" in t.name:
406 targets.append(t)
407 build_many(targets)
408 elif args == ['noperf']:
409 targets = []
410 for t in configs:
411 if "perf" not in t.name:
412 targets.append(t)
413 build_many(targets)
414 elif len(args) > 0:
415 all_configs = {}
416 for t in configs:
417 all_configs[t.name] = t
418 targets = []
419 for t in args:
420 if t not in all_configs:
421 parser.error("Target '%s' not one of %s" % (t, all_configs.keys()))
422 targets.append(all_configs[t])
423 build_many(targets)
424 else:
425 parser.error("Must specify a target to build, or 'all'")
426
427if __name__ == "__main__":
428 main()