blob: ee1c34e817885658d49b254954d6a41a5a48adba [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.
Rahul Shahare850650f2020-01-28 10:39:05 +0530271 cmd_line = ['make',
272 'INSTALL_HDR_PATH=%s' % hdri_dir,
273 'INSTALL_MOD_PATH=%s' % modi_dir,
274 'O=%s' % dest_dir,
275 'REAL_CC=%s' % clang_bin]
Bryan Huntsman895fdcc2018-05-25 13:05:57 -0700276 build_targets = []
277 for c in make_command:
278 if re.match(r'^-{1,2}\w', c):
279 cmd_line.append(c)
Satya Durga Srinivasu Prabhala41259a42017-05-22 17:00:49 -0700280 else:
Bryan Huntsman895fdcc2018-05-25 13:05:57 -0700281 build_targets.append(c)
282 for t in build_targets:
283 steps.append(ExecStep(cmd_line + [t], env=self.make_env))
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -0700284
285 return steps
286
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -0700287def scan_configs():
288 """Get the full list of defconfigs appropriate for this tree."""
289 names = []
Bryan Huntsman6915ec22018-06-04 14:18:41 -0700290 for defconfig in glob.glob('arch/arm*/configs/vendor/*_defconfig'):
291 target = os.path.basename(defconfig)[:-10]
292 name = target + "-llvm"
293 if 'arch/arm64' in defconfig:
294 name = name + "-64"
295 names.append(Builder(name, defconfig))
296
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -0700297 return names
298
299def build_many(targets):
300 print "Building %d target(s)" % len(targets)
301
302 # To try and make up for the link phase being serial, try to do
303 # two full builds in parallel. Don't do too many because lots of
304 # parallel builds tends to use up available memory rather quickly.
305 parallel = 2
306 if all_options.jobs and all_options.jobs > 1:
307 j = max(all_options.jobs / parallel, 2)
308 make_command.append("-j" + str(j))
309
310 tracker = BuildTracker(parallel)
311 for target in targets:
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -0700312 steps = target.build()
313 tracker.add_sequence(target.log_name, target.name, steps)
314 tracker.run()
315
316def main():
317 global make_command
318
319 check_kernel()
320 check_build()
321
322 configs = scan_configs()
323
324 usage = ("""
325 %prog [options] all -- Build all targets
326 %prog [options] target target ... -- List specific targets
Bryan Huntsman895fdcc2018-05-25 13:05:57 -0700327 """)
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -0700328 parser = OptionParser(usage=usage, version=version)
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -0700329 parser.add_option('--list', action='store_true',
330 dest='list',
331 help='List available targets')
332 parser.add_option('-v', '--verbose', action='store_true',
333 dest='verbose',
334 help='Output to stdout in addition to log file')
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -0700335 parser.add_option('-j', '--jobs', type='int', dest="jobs",
336 help="Number of simultaneous jobs")
337 parser.add_option('-l', '--load-average', type='int',
338 dest='load_average',
339 help="Don't start multiple jobs unless load is below LOAD_AVERAGE")
340 parser.add_option('-k', '--keep-going', action='store_true',
341 dest='keep_going', default=False,
342 help="Keep building other targets if a target fails")
343 parser.add_option('-m', '--make-target', action='append',
344 help='Build the indicated make target (default: %s)' %
345 ' '.join(make_command))
346
347 (options, args) = parser.parse_args()
348 global all_options
349 all_options = options
350
351 if options.list:
352 print "Available targets:"
353 for target in configs:
354 print " %s" % target.name
355 sys.exit(0)
356
Bryan Huntsman895fdcc2018-05-25 13:05:57 -0700357 if options.make_target:
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -0700358 make_command = options.make_target
359
360 if args == ['all']:
361 build_many(configs)
Channagoud Kadabi1a82c2b2016-07-01 12:28:20 -0700362 elif len(args) > 0:
363 all_configs = {}
364 for t in configs:
365 all_configs[t.name] = t
366 targets = []
367 for t in args:
368 if t not in all_configs:
369 parser.error("Target '%s' not one of %s" % (t, all_configs.keys()))
370 targets.append(all_configs[t])
371 build_many(targets)
372 else:
373 parser.error("Must specify a target to build, or 'all'")
374
375if __name__ == "__main__":
376 main()