blob: 65902063934befa0e8cec2c3063294a441de030e [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
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))
319 return names
320
321def build_many(targets):
322 print "Building %d target(s)" % len(targets)
323
324 # To try and make up for the link phase being serial, try to do
325 # two full builds in parallel. Don't do too many because lots of
326 # parallel builds tends to use up available memory rather quickly.
327 parallel = 2
328 if all_options.jobs and all_options.jobs > 1:
329 j = max(all_options.jobs / parallel, 2)
330 make_command.append("-j" + str(j))
331
332 tracker = BuildTracker(parallel)
333 for target in targets:
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -0700334 steps = target.build()
335 tracker.add_sequence(target.log_name, target.name, steps)
336 tracker.run()
337
338def main():
339 global make_command
340
341 check_kernel()
342 check_build()
343
344 configs = scan_configs()
345
346 usage = ("""
347 %prog [options] all -- Build all targets
348 %prog [options] target target ... -- List specific targets
Bryan Huntsman895fdcc2018-05-25 13:05:57 -0700349 """)
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -0700350 parser = OptionParser(usage=usage, version=version)
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -0700351 parser.add_option('--list', action='store_true',
352 dest='list',
353 help='List available targets')
354 parser.add_option('-v', '--verbose', action='store_true',
355 dest='verbose',
356 help='Output to stdout in addition to log file')
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -0700357 parser.add_option('-j', '--jobs', type='int', dest="jobs",
358 help="Number of simultaneous jobs")
359 parser.add_option('-l', '--load-average', type='int',
360 dest='load_average',
361 help="Don't start multiple jobs unless load is below LOAD_AVERAGE")
362 parser.add_option('-k', '--keep-going', action='store_true',
363 dest='keep_going', default=False,
364 help="Keep building other targets if a target fails")
365 parser.add_option('-m', '--make-target', action='append',
366 help='Build the indicated make target (default: %s)' %
367 ' '.join(make_command))
368
369 (options, args) = parser.parse_args()
370 global all_options
371 all_options = options
372
373 if options.list:
374 print "Available targets:"
375 for target in configs:
376 print " %s" % target.name
377 sys.exit(0)
378
Bryan Huntsman895fdcc2018-05-25 13:05:57 -0700379 if options.make_target:
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -0700380 make_command = options.make_target
381
382 if args == ['all']:
383 build_many(configs)
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -0700384 elif len(args) > 0:
385 all_configs = {}
386 for t in configs:
387 all_configs[t.name] = t
388 targets = []
389 for t in args:
390 if t not in all_configs:
391 parser.error("Target '%s' not one of %s" % (t, all_configs.keys()))
392 targets.append(all_configs[t])
393 build_many(targets)
394 else:
395 parser.error("Must specify a target to build, or 'all'")
396
397if __name__ == "__main__":
398 main()