blob: f5e5fa2a9bf9981859f895704eb88d179581d626 [file] [log] [blame]
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -07001#! /usr/bin/env python2
2
Satya Durga Srinivasu Prabhala41259a42017-05-22 17:00:49 -07003# Copyright (c) 2009-2015, 2017, 2019, The Linux Foundation. All rights reserved.
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -07004#
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')
Satya Durga Srinivasu Prabhala41259a42017-05-22 17:00:49 -070048clang_bin = os.environ.get('CLANG_BIN')
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -070049
50def error(msg):
51 sys.stderr.write("error: %s\n" % msg)
52
53def fail(msg):
54 """Fail with a user-printed message"""
55 error(msg)
56 sys.exit(1)
57
58if not os.environ.get('CROSS_COMPILE'):
59 fail("CROSS_COMPILE must be set in the environment")
60
61def check_kernel():
62 """Ensure that PWD is a kernel directory"""
Bryan Huntsman3e408fe2018-06-04 15:11:10 -070063 if not os.path.isfile('MAINTAINERS'):
64 fail("This doesn't seem to be a kernel dir")
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -070065
66def check_build():
67 """Ensure that the build directory is present."""
68 if not os.path.isdir(build_dir):
69 try:
70 os.makedirs(build_dir)
71 except OSError as exc:
72 if exc.errno == errno.EEXIST:
73 pass
74 else:
75 raise
76
77failed_targets = []
78
79BuildResult = namedtuple('BuildResult', ['status', 'messages'])
80
81class BuildSequence(namedtuple('BuildSequence', ['log_name', 'short_name', 'steps'])):
82
83 def set_width(self, width):
84 self.width = width
85
86 def __enter__(self):
87 self.log = open(self.log_name, 'w')
88 def __exit__(self, type, value, traceback):
89 self.log.close()
90
91 def run(self):
92 self.status = None
93 messages = ["Building: " + self.short_name]
94 def printer(line):
95 text = "[%-*s] %s" % (self.width, self.short_name, line)
96 messages.append(text)
97 self.log.write(text)
98 self.log.write('\n')
99 for step in self.steps:
100 st = step.run(printer)
101 if st:
102 self.status = BuildResult(self.short_name, messages)
103 break
104 if not self.status:
105 self.status = BuildResult(None, messages)
106
107class BuildTracker:
108 """Manages all of the steps necessary to perform a build. The
109 build consists of one or more sequences of steps. The different
110 sequences can be processed independently, while the steps within a
111 sequence must be done in order."""
112
113 def __init__(self, parallel_builds):
114 self.sequence = []
115 self.lock = threading.Lock()
116 self.parallel_builds = parallel_builds
117
118 def add_sequence(self, log_name, short_name, steps):
119 self.sequence.append(BuildSequence(log_name, short_name, steps))
120
121 def longest_name(self):
122 longest = 0
123 for seq in self.sequence:
124 longest = max(longest, len(seq.short_name))
125 return longest
126
127 def __repr__(self):
128 return "BuildTracker(%s)" % self.sequence
129
130 def run_child(self, seq):
131 seq.set_width(self.longest)
132 tok = self.build_tokens.get()
133 with self.lock:
134 print "Building:", seq.short_name
135 with seq:
136 seq.run()
137 self.results.put(seq.status)
138 self.build_tokens.put(tok)
139
140 def run(self):
141 self.longest = self.longest_name()
142 self.results = Queue.Queue()
143 children = []
144 errors = []
145 self.build_tokens = Queue.Queue()
146 nthreads = self.parallel_builds
147 print "Building with", nthreads, "threads"
148 for i in range(nthreads):
149 self.build_tokens.put(True)
150 for seq in self.sequence:
151 child = threading.Thread(target=self.run_child, args=[seq])
152 children.append(child)
153 child.start()
154 for child in children:
155 stats = self.results.get()
156 if all_options.verbose:
157 with self.lock:
158 for line in stats.messages:
159 print line
160 sys.stdout.flush()
161 if stats.status:
162 errors.append(stats.status)
163 for child in children:
164 child.join()
165 if errors:
166 fail("\n ".join(["Failed targets:"] + errors))
167
168class PrintStep:
169 """A step that just prints a message"""
170 def __init__(self, message):
171 self.message = message
172
173 def run(self, outp):
174 outp(self.message)
175
176class MkdirStep:
177 """A step that makes a directory"""
178 def __init__(self, direc):
179 self.direc = direc
180
181 def run(self, outp):
182 outp("mkdir %s" % self.direc)
183 os.mkdir(self.direc)
184
185class RmtreeStep:
186 def __init__(self, direc):
187 self.direc = direc
188
189 def run(self, outp):
190 outp("rmtree %s" % self.direc)
191 shutil.rmtree(self.direc, ignore_errors=True)
192
193class CopyfileStep:
194 def __init__(self, src, dest):
195 self.src = src
196 self.dest = dest
197
198 def run(self, outp):
199 outp("cp %s %s" % (self.src, self.dest))
200 shutil.copyfile(self.src, self.dest)
201
202class ExecStep:
203 def __init__(self, cmd, **kwargs):
204 self.cmd = cmd
205 self.kwargs = kwargs
206
207 def run(self, outp):
208 outp("exec: %s" % (" ".join(self.cmd),))
209 with open('/dev/null', 'r') as devnull:
210 proc = subprocess.Popen(self.cmd, stdin=devnull,
211 stdout=subprocess.PIPE,
212 stderr=subprocess.STDOUT,
213 **self.kwargs)
214 stdout = proc.stdout
215 while True:
216 line = stdout.readline()
217 if not line:
218 break
219 line = line.rstrip('\n')
220 outp(line)
221 result = proc.wait()
222 if result != 0:
223 return ('error', result)
224 else:
225 return None
226
227class Builder():
228
229 def __init__(self, name, defconfig):
230 self.name = name
231 self.defconfig = defconfig
232
233 self.confname = self.defconfig.split('/')[-1]
234
235 # Determine if this is a 64-bit target based on the location
236 # of the defconfig.
237 self.make_env = os.environ.copy()
238 if "/arm64/" in defconfig:
239 if compile64:
240 self.make_env['CROSS_COMPILE'] = compile64
241 else:
242 fail("Attempting to build 64-bit, without setting CROSS_COMPILE64")
243 self.make_env['ARCH'] = 'arm64'
244 else:
245 self.make_env['ARCH'] = 'arm'
246 self.make_env['KCONFIG_NOTIMESTAMP'] = 'true'
247 self.log_name = "%s/log-%s.log" % (build_dir, self.name)
248
249 def build(self):
250 steps = []
251 dest_dir = os.path.join(build_dir, self.name)
252 log_name = "%s/log-%s.log" % (build_dir, self.name)
253 steps.append(PrintStep('Building %s in %s log %s' %
254 (self.name, dest_dir, log_name)))
255 if not os.path.isdir(dest_dir):
256 steps.append(MkdirStep(dest_dir))
257 defconfig = self.defconfig
258 dotconfig = '%s/.config' % dest_dir
259 savedefconfig = '%s/defconfig' % dest_dir
260
261 staging_dir = 'install_staging'
262 modi_dir = '%s' % staging_dir
263 hdri_dir = '%s/usr' % staging_dir
264 steps.append(RmtreeStep(os.path.join(dest_dir, staging_dir)))
265
266 steps.append(ExecStep(['make', 'O=%s' % dest_dir,
267 self.confname], env=self.make_env))
268
269 if not all_options.updateconfigs:
270 # Build targets can be dependent upon the completion of
271 # previous build targets, so build them one at a time.
Satya Durga Srinivasu Prabhala41259a42017-05-22 17:00:49 -0700272 if os.environ.get('ARCH') == "arm64":
273 cmd_line = ['make',
274 'INSTALL_HDR_PATH=%s' % hdri_dir,
275 'INSTALL_MOD_PATH=%s' % modi_dir,
276 'O=%s' % dest_dir,
277 'REAL_CC=%s' % clang_bin]
278 else:
279 cmd_line = ['make',
280 'INSTALL_HDR_PATH=%s' % hdri_dir,
281 'INSTALL_MOD_PATH=%s' % modi_dir,
282 'O=%s' % dest_dir]
283
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -0700284 build_targets = []
285 for c in make_command:
286 if re.match(r'^-{1,2}\w', c):
287 cmd_line.append(c)
288 else:
289 build_targets.append(c)
290 for t in build_targets:
291 steps.append(ExecStep(cmd_line + [t], env=self.make_env))
292
293 # Copy the defconfig back.
294 if all_options.configs or all_options.updateconfigs:
295 steps.append(ExecStep(['make', 'O=%s' % dest_dir,
296 'savedefconfig'], env=self.make_env))
297 steps.append(CopyfileStep(savedefconfig, defconfig))
298
299 return steps
300
301def update_config(file, str):
302 print 'Updating %s with \'%s\'\n' % (file, str)
303 with open(file, 'a') as defconfig:
304 defconfig.write(str + '\n')
305
306def scan_configs():
307 """Get the full list of defconfigs appropriate for this tree."""
308 names = []
309 arch_pats = (
310 r'[fm]sm[0-9]*_defconfig',
311 r'apq*_defconfig',
312 r'qsd*_defconfig',
Kyle Yan6a20fae2017-02-14 13:34:41 -0800313 r'mpq*_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 arch64_pats = (
Kyle Yan6a20fae2017-02-14 13:34:41 -0800318 r'msm*_defconfig',
319 r'sdm[0-9]*_defconfig',
Runmin Wang37c5e5a2017-04-27 11:40:04 -0700320 r'sdx*_defconfig',
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -0700321 )
322 for p in arch_pats:
323 for n in glob.glob('arch/arm/configs/' + p):
324 name = os.path.basename(n)[:-10]
325 names.append(Builder(name, n))
326 if 'CROSS_COMPILE64' in os.environ:
327 for p in arch64_pats:
328 for n in glob.glob('arch/arm64/configs/' + p):
329 name = os.path.basename(n)[:-10] + "-64"
330 names.append(Builder(name, n))
331 return names
332
333def build_many(targets):
334 print "Building %d target(s)" % len(targets)
335
336 # To try and make up for the link phase being serial, try to do
337 # two full builds in parallel. Don't do too many because lots of
338 # parallel builds tends to use up available memory rather quickly.
339 parallel = 2
340 if all_options.jobs and all_options.jobs > 1:
341 j = max(all_options.jobs / parallel, 2)
342 make_command.append("-j" + str(j))
343
344 tracker = BuildTracker(parallel)
345 for target in targets:
346 if all_options.updateconfigs:
347 update_config(target.defconfig, all_options.updateconfigs)
348 steps = target.build()
349 tracker.add_sequence(target.log_name, target.name, steps)
350 tracker.run()
351
352def main():
353 global make_command
354
355 check_kernel()
356 check_build()
357
358 configs = scan_configs()
359
360 usage = ("""
361 %prog [options] all -- Build all targets
362 %prog [options] target target ... -- List specific targets
363 %prog [options] perf -- Build all perf targets
364 %prog [options] noperf -- Build all non-perf targets""")
365 parser = OptionParser(usage=usage, version=version)
366 parser.add_option('--configs', action='store_true',
367 dest='configs',
368 help="Copy configs back into tree")
369 parser.add_option('--list', action='store_true',
370 dest='list',
371 help='List available targets')
372 parser.add_option('-v', '--verbose', action='store_true',
373 dest='verbose',
374 help='Output to stdout in addition to log file')
375 parser.add_option('--oldconfig', action='store_true',
376 dest='oldconfig',
377 help='Only process "make oldconfig"')
378 parser.add_option('--updateconfigs',
379 dest='updateconfigs',
380 help="Update defconfigs with provided option setting, "
381 "e.g. --updateconfigs=\'CONFIG_USE_THING=y\'")
382 parser.add_option('-j', '--jobs', type='int', dest="jobs",
383 help="Number of simultaneous jobs")
384 parser.add_option('-l', '--load-average', type='int',
385 dest='load_average',
386 help="Don't start multiple jobs unless load is below LOAD_AVERAGE")
387 parser.add_option('-k', '--keep-going', action='store_true',
388 dest='keep_going', default=False,
389 help="Keep building other targets if a target fails")
390 parser.add_option('-m', '--make-target', action='append',
391 help='Build the indicated make target (default: %s)' %
392 ' '.join(make_command))
393
394 (options, args) = parser.parse_args()
395 global all_options
396 all_options = options
397
398 if options.list:
399 print "Available targets:"
400 for target in configs:
401 print " %s" % target.name
402 sys.exit(0)
403
404 if options.oldconfig:
405 make_command = ["oldconfig"]
406 elif options.make_target:
407 make_command = options.make_target
408
409 if args == ['all']:
410 build_many(configs)
411 elif args == ['perf']:
412 targets = []
413 for t in configs:
414 if "perf" in t.name:
415 targets.append(t)
416 build_many(targets)
417 elif args == ['noperf']:
418 targets = []
419 for t in configs:
420 if "perf" not in t.name:
421 targets.append(t)
422 build_many(targets)
423 elif len(args) > 0:
424 all_configs = {}
425 for t in configs:
426 all_configs[t.name] = t
427 targets = []
428 for t in args:
429 if t not in all_configs:
430 parser.error("Target '%s' not one of %s" % (t, all_configs.keys()))
431 targets.append(all_configs[t])
432 build_many(targets)
433 else:
434 parser.error("Must specify a target to build, or 'all'")
435
436if __name__ == "__main__":
437 main()