blob: c591e26959af3be5788c95ff94ace45522c143dd [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
Bryan Huntsman6915ec22018-06-04 14:18:41 -0700233 self.confname = re.sub('arch/arm[64]*/configs/', '', self.defconfig)
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -0700234
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
Bryan Huntsman895fdcc2018-05-25 13:05:57 -0700269 # Build targets can be dependent upon the completion of
270 # previous build targets, so build them one at a time.
271 if os.environ.get('ARCH') == "arm64":
272 cmd_line = ['make',
273 'INSTALL_HDR_PATH=%s' % hdri_dir,
274 'INSTALL_MOD_PATH=%s' % modi_dir,
275 'O=%s' % dest_dir,
276 'REAL_CC=%s' % clang_bin]
277 else:
278 cmd_line = ['make',
279 'INSTALL_HDR_PATH=%s' % hdri_dir,
280 'INSTALL_MOD_PATH=%s' % modi_dir,
281 'O=%s' % dest_dir]
282
283 build_targets = []
284 for c in make_command:
285 if re.match(r'^-{1,2}\w', c):
286 cmd_line.append(c)
Satya Durga Srinivasu Prabhala41259a42017-05-22 17:00:49 -0700287 else:
Bryan Huntsman895fdcc2018-05-25 13:05:57 -0700288 build_targets.append(c)
289 for t in build_targets:
290 steps.append(ExecStep(cmd_line + [t], env=self.make_env))
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -0700291
292 return steps
293
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -0700294def scan_configs():
295 """Get the full list of defconfigs appropriate for this tree."""
296 names = []
297 arch_pats = (
298 r'[fm]sm[0-9]*_defconfig',
299 r'apq*_defconfig',
300 r'qsd*_defconfig',
Kyle Yan6a20fae2017-02-14 13:34:41 -0800301 r'mpq*_defconfig',
Jeevan Shriram36f01412018-03-29 12:21:40 -0700302 r'sdm*_defconfig',
Runmin Wang37c5e5a2017-04-27 11:40:04 -0700303 r'sdx*_defconfig',
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -0700304 )
305 arch64_pats = (
Kyle Yan6a20fae2017-02-14 13:34:41 -0800306 r'msm*_defconfig',
Jeevan Shriram36f01412018-03-29 12:21:40 -0700307 r'sdm*_defconfig',
Runmin Wang37c5e5a2017-04-27 11:40:04 -0700308 r'sdx*_defconfig',
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -0700309 )
310 for p in arch_pats:
311 for n in glob.glob('arch/arm/configs/' + p):
312 name = os.path.basename(n)[:-10]
313 names.append(Builder(name, n))
314 if 'CROSS_COMPILE64' in os.environ:
315 for p in arch64_pats:
316 for n in glob.glob('arch/arm64/configs/' + p):
Satya Durga Srinivasu Prabhalaccc529e2017-05-30 16:03:32 -0700317 name = os.path.basename(n)[:-10] + "-llvm" + "-64"
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -0700318 names.append(Builder(name, n))
Bryan Huntsman6915ec22018-06-04 14:18:41 -0700319 for defconfig in glob.glob('arch/arm*/configs/vendor/*_defconfig'):
320 target = os.path.basename(defconfig)[:-10]
321 name = target + "-llvm"
322 if 'arch/arm64' in defconfig:
323 name = name + "-64"
324 names.append(Builder(name, defconfig))
325
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -0700326 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:
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -0700341 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
Bryan Huntsman895fdcc2018-05-25 13:05:57 -0700356 """)
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -0700357 parser = OptionParser(usage=usage, version=version)
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -0700358 parser.add_option('--list', action='store_true',
359 dest='list',
360 help='List available targets')
361 parser.add_option('-v', '--verbose', action='store_true',
362 dest='verbose',
363 help='Output to stdout in addition to log file')
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -0700364 parser.add_option('-j', '--jobs', type='int', dest="jobs",
365 help="Number of simultaneous jobs")
366 parser.add_option('-l', '--load-average', type='int',
367 dest='load_average',
368 help="Don't start multiple jobs unless load is below LOAD_AVERAGE")
369 parser.add_option('-k', '--keep-going', action='store_true',
370 dest='keep_going', default=False,
371 help="Keep building other targets if a target fails")
372 parser.add_option('-m', '--make-target', action='append',
373 help='Build the indicated make target (default: %s)' %
374 ' '.join(make_command))
375
376 (options, args) = parser.parse_args()
377 global all_options
378 all_options = options
379
380 if options.list:
381 print "Available targets:"
382 for target in configs:
383 print " %s" % target.name
384 sys.exit(0)
385
Bryan Huntsman895fdcc2018-05-25 13:05:57 -0700386 if options.make_target:
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -0700387 make_command = options.make_target
388
389 if args == ['all']:
390 build_many(configs)
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -0700391 elif len(args) > 0:
392 all_configs = {}
393 for t in configs:
394 all_configs[t.name] = t
395 targets = []
396 for t in args:
397 if t not in all_configs:
398 parser.error("Target '%s' not one of %s" % (t, all_configs.keys()))
399 targets.append(all_configs[t])
400 build_many(targets)
401 else:
402 parser.error("Must specify a target to build, or 'all'")
403
404if __name__ == "__main__":
405 main()