blob: 4a60ebc71d095a92807c15fb91b9e6e12c6c83ae [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"""
Bryan Huntsman3e408fe2018-06-04 15:11:10 -070062 if not os.path.isfile('MAINTAINERS'):
63 fail("This doesn't seem to be a kernel dir")
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -070064
65def check_build():
66 """Ensure that the build directory is present."""
67 if not os.path.isdir(build_dir):
68 try:
69 os.makedirs(build_dir)
70 except OSError as exc:
71 if exc.errno == errno.EEXIST:
72 pass
73 else:
74 raise
75
76failed_targets = []
77
78BuildResult = namedtuple('BuildResult', ['status', 'messages'])
79
80class BuildSequence(namedtuple('BuildSequence', ['log_name', 'short_name', 'steps'])):
81
82 def set_width(self, width):
83 self.width = width
84
85 def __enter__(self):
86 self.log = open(self.log_name, 'w')
87 def __exit__(self, type, value, traceback):
88 self.log.close()
89
90 def run(self):
91 self.status = None
92 messages = ["Building: " + self.short_name]
93 def printer(line):
94 text = "[%-*s] %s" % (self.width, self.short_name, line)
95 messages.append(text)
96 self.log.write(text)
97 self.log.write('\n')
98 for step in self.steps:
99 st = step.run(printer)
100 if st:
101 self.status = BuildResult(self.short_name, messages)
102 break
103 if not self.status:
104 self.status = BuildResult(None, messages)
105
106class BuildTracker:
107 """Manages all of the steps necessary to perform a build. The
108 build consists of one or more sequences of steps. The different
109 sequences can be processed independently, while the steps within a
110 sequence must be done in order."""
111
112 def __init__(self, parallel_builds):
113 self.sequence = []
114 self.lock = threading.Lock()
115 self.parallel_builds = parallel_builds
116
117 def add_sequence(self, log_name, short_name, steps):
118 self.sequence.append(BuildSequence(log_name, short_name, steps))
119
120 def longest_name(self):
121 longest = 0
122 for seq in self.sequence:
123 longest = max(longest, len(seq.short_name))
124 return longest
125
126 def __repr__(self):
127 return "BuildTracker(%s)" % self.sequence
128
129 def run_child(self, seq):
130 seq.set_width(self.longest)
131 tok = self.build_tokens.get()
132 with self.lock:
133 print "Building:", seq.short_name
134 with seq:
135 seq.run()
136 self.results.put(seq.status)
137 self.build_tokens.put(tok)
138
139 def run(self):
140 self.longest = self.longest_name()
141 self.results = Queue.Queue()
142 children = []
143 errors = []
144 self.build_tokens = Queue.Queue()
145 nthreads = self.parallel_builds
146 print "Building with", nthreads, "threads"
147 for i in range(nthreads):
148 self.build_tokens.put(True)
149 for seq in self.sequence:
150 child = threading.Thread(target=self.run_child, args=[seq])
151 children.append(child)
152 child.start()
153 for child in children:
154 stats = self.results.get()
155 if all_options.verbose:
156 with self.lock:
157 for line in stats.messages:
158 print line
159 sys.stdout.flush()
160 if stats.status:
161 errors.append(stats.status)
162 for child in children:
163 child.join()
164 if errors:
165 fail("\n ".join(["Failed targets:"] + errors))
166
167class PrintStep:
168 """A step that just prints a message"""
169 def __init__(self, message):
170 self.message = message
171
172 def run(self, outp):
173 outp(self.message)
174
175class MkdirStep:
176 """A step that makes a directory"""
177 def __init__(self, direc):
178 self.direc = direc
179
180 def run(self, outp):
181 outp("mkdir %s" % self.direc)
182 os.mkdir(self.direc)
183
184class RmtreeStep:
185 def __init__(self, direc):
186 self.direc = direc
187
188 def run(self, outp):
189 outp("rmtree %s" % self.direc)
190 shutil.rmtree(self.direc, ignore_errors=True)
191
192class CopyfileStep:
193 def __init__(self, src, dest):
194 self.src = src
195 self.dest = dest
196
197 def run(self, outp):
198 outp("cp %s %s" % (self.src, self.dest))
199 shutil.copyfile(self.src, self.dest)
200
201class ExecStep:
202 def __init__(self, cmd, **kwargs):
203 self.cmd = cmd
204 self.kwargs = kwargs
205
206 def run(self, outp):
207 outp("exec: %s" % (" ".join(self.cmd),))
208 with open('/dev/null', 'r') as devnull:
209 proc = subprocess.Popen(self.cmd, stdin=devnull,
210 stdout=subprocess.PIPE,
211 stderr=subprocess.STDOUT,
212 **self.kwargs)
213 stdout = proc.stdout
214 while True:
215 line = stdout.readline()
216 if not line:
217 break
218 line = line.rstrip('\n')
219 outp(line)
220 result = proc.wait()
221 if result != 0:
222 return ('error', result)
223 else:
224 return None
225
226class Builder():
227
228 def __init__(self, name, defconfig):
229 self.name = name
230 self.defconfig = defconfig
231
232 self.confname = self.defconfig.split('/')[-1]
233
234 # Determine if this is a 64-bit target based on the location
235 # of the defconfig.
236 self.make_env = os.environ.copy()
237 if "/arm64/" in defconfig:
238 if compile64:
239 self.make_env['CROSS_COMPILE'] = compile64
240 else:
241 fail("Attempting to build 64-bit, without setting CROSS_COMPILE64")
242 self.make_env['ARCH'] = 'arm64'
243 else:
244 self.make_env['ARCH'] = 'arm'
245 self.make_env['KCONFIG_NOTIMESTAMP'] = 'true'
246 self.log_name = "%s/log-%s.log" % (build_dir, self.name)
247
248 def build(self):
249 steps = []
250 dest_dir = os.path.join(build_dir, self.name)
251 log_name = "%s/log-%s.log" % (build_dir, self.name)
252 steps.append(PrintStep('Building %s in %s log %s' %
253 (self.name, dest_dir, log_name)))
254 if not os.path.isdir(dest_dir):
255 steps.append(MkdirStep(dest_dir))
256 defconfig = self.defconfig
257 dotconfig = '%s/.config' % dest_dir
258 savedefconfig = '%s/defconfig' % dest_dir
259
260 staging_dir = 'install_staging'
261 modi_dir = '%s' % staging_dir
262 hdri_dir = '%s/usr' % staging_dir
263 steps.append(RmtreeStep(os.path.join(dest_dir, staging_dir)))
264
265 steps.append(ExecStep(['make', 'O=%s' % dest_dir,
266 self.confname], env=self.make_env))
267
268 if not all_options.updateconfigs:
269 # Build targets can be dependent upon the completion of
270 # previous build targets, so build them one at a time.
271 cmd_line = ['make',
272 'INSTALL_HDR_PATH=%s' % hdri_dir,
273 'INSTALL_MOD_PATH=%s' % modi_dir,
274 'O=%s' % dest_dir]
275 build_targets = []
276 for c in make_command:
277 if re.match(r'^-{1,2}\w', c):
278 cmd_line.append(c)
279 else:
280 build_targets.append(c)
281 for t in build_targets:
282 steps.append(ExecStep(cmd_line + [t], env=self.make_env))
283
284 # Copy the defconfig back.
285 if all_options.configs or all_options.updateconfigs:
286 steps.append(ExecStep(['make', 'O=%s' % dest_dir,
287 'savedefconfig'], env=self.make_env))
288 steps.append(CopyfileStep(savedefconfig, defconfig))
289
290 return steps
291
292def update_config(file, str):
293 print 'Updating %s with \'%s\'\n' % (file, str)
294 with open(file, 'a') as defconfig:
295 defconfig.write(str + '\n')
296
297def scan_configs():
298 """Get the full list of defconfigs appropriate for this tree."""
299 names = []
300 arch_pats = (
301 r'[fm]sm[0-9]*_defconfig',
302 r'apq*_defconfig',
303 r'qsd*_defconfig',
Kyle Yan6a20fae2017-02-14 13:34:41 -0800304 r'mpq*_defconfig',
305 r'sdm[0-9]*_defconfig',
Runmin Wang37c5e5a2017-04-27 11:40:04 -0700306 r'sdx*_defconfig',
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -0700307 )
308 arch64_pats = (
Kyle Yan6a20fae2017-02-14 13:34:41 -0800309 r'msm*_defconfig',
310 r'sdm[0-9]*_defconfig',
Runmin Wang37c5e5a2017-04-27 11:40:04 -0700311 r'sdx*_defconfig',
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -0700312 )
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()