5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 # General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 # This script generates a random configuration for testing Buildroot.
21 from __future__ import print_function
26 from random import randint
29 from distutils.version import StrictVersion
32 if sys.hexversion >= 0x3000000:
33 import urllib.request as _urllib
35 import urllib2 as _urllib
38 def urlopen_closing(uri):
39 return contextlib.closing(_urllib.urlopen(uri))
42 if sys.hexversion >= 0x3000000:
43 def decode_byte_list(bl):
44 return [b.decode() for b in bl]
46 def decode_byte_list(e):
51 DEFAULT_NEEDED_PROGS = ["make", "git", "gcc", "timeout"]
52 DEFAULT_OPTIONAL_PROGS = ["bzr", "java", "javac", "jar"]
55 self.needed_progs = list(self.__class__.DEFAULT_NEEDED_PROGS)
56 self.optional_progs = list(self.__class__.DEFAULT_OPTIONAL_PROGS)
59 def find_prog(self, name, flags=os.X_OK, env=os.environ):
60 if not name or name[0] == os.sep:
61 raise ValueError(name)
63 prog_path = env.get("PATH", None)
64 # for windows compatibility, we'd need to take PATHEXT into account
67 for prog_dir in filter(None, prog_path.split(os.pathsep)):
68 # os.join() not necessary: non-empty prog_dir
69 # and name[0] != os.sep
70 prog = prog_dir + os.sep + name
71 if os.access(prog, flags):
77 """Checks whether a program is available.
78 Lazily evaluates missing entries.
80 Returns: None if prog not found, else path to the program [evaluates
84 return self.progs[prog]
88 have_it = self.find_prog(prog)
89 # java[c] needs special care
90 if have_it and prog in ('java', 'javac'):
91 with open(os.devnull, "w") as devnull:
92 if subprocess.call("%s -version | grep gcj" % prog,
94 stdout=devnull, stderr=devnull) != 1:
97 self.progs[prog] = have_it
100 def check_requirements(self):
101 """Checks program dependencies.
103 Returns: True if all mandatory programs are present, else False.
105 do_check_has_prog = self.has
107 missing_requirements = False
108 for prog in self.needed_progs:
109 if not do_check_has_prog(prog):
110 print("ERROR: your system lacks the '%s' program" % prog)
111 missing_requirements = True
113 # check optional programs here,
114 # else they'd get checked by each worker instance
115 for prog in self.optional_progs:
116 do_check_has_prog(prog)
118 return not missing_requirements
121 def get_toolchain_configs(toolchains_csv, buildrootdir):
122 """Fetch and return the possible toolchain configurations
124 This function returns an array of toolchain configurations. Each
125 toolchain configuration is itself an array of lines of the defconfig.
128 with open(toolchains_csv) as r:
129 # filter empty lines and comments
130 lines = [ t for t in r.readlines() if len(t.strip()) > 0 and t[0] != '#' ]
131 toolchains = decode_byte_list(lines)
134 (_, _, _, _, hostarch) = os.uname()
135 # ~2015 distros report x86 when on a 32bit install
136 if hostarch == 'i686' or hostarch == 'i386' or hostarch == 'x86':
139 for row in csv.reader(toolchains):
142 config_hostarch = row[1]
145 # Keep all toolchain configs that work regardless of the host
147 if config_hostarch == "any":
150 # Keep all toolchain configs that can work on the current host
152 if hostarch == config_hostarch:
155 # Assume that x86 32 bits toolchains work on x86_64 build
157 if hostarch == 'x86_64' and config_hostarch == "x86":
163 if not os.path.isabs(configfile):
164 configfile = os.path.join(buildrootdir, configfile)
166 with open(configfile) as r:
167 config = r.readlines()
168 configs.append(config)
172 def is_toolchain_usable(configfile, config):
173 """Check if the toolchain is actually usable."""
175 with open(configfile) as configf:
176 configlines = configf.readlines()
178 # Check that the toolchain configuration is still present
179 for toolchainline in config:
180 if toolchainline not in configlines:
181 print("WARN: toolchain can't be used", file=sys.stderr)
182 print(" Missing: %s" % toolchainline.strip(), file=sys.stderr)
185 # The latest Linaro toolchains on x86-64 hosts requires glibc
187 if platform.machine() == 'x86_64':
188 if 'BR2_TOOLCHAIN_EXTERNAL_LINARO_ARM=y\n' in configlines or \
189 'BR2_TOOLCHAIN_EXTERNAL_LINARO_AARCH64=y\n' in configlines or \
190 'BR2_TOOLCHAIN_EXTERNAL_LINARO_ARMEB=y\n' in configlines:
191 ldd_version_output = subprocess.check_output(['ldd', '--version'])
192 glibc_version = ldd_version_output.splitlines()[0].split()[-1]
193 if StrictVersion('2.14') > StrictVersion(glibc_version):
194 print("WARN: ignoring the Linaro ARM toolchains because too old host glibc", file=sys.stderr)
200 def fixup_config(configfile):
201 """Finalize the configuration and reject any problematic combinations
203 This function returns 'True' when the configuration has been
204 accepted, and 'False' when the configuration has not been accepted because
205 it is known to fail (in which case another random configuration will be
209 sysinfo = SystemInfo()
210 with open(configfile) as configf:
211 configlines = configf.readlines()
213 if "BR2_NEEDS_HOST_JAVA=y\n" in configlines and not sysinfo.has("java"):
215 if "BR2_NEEDS_HOST_JAVAC=y\n" in configlines and not sysinfo.has("javac"):
217 if "BR2_NEEDS_HOST_JAR=y\n" in configlines and not sysinfo.has("jar"):
219 # python-nfc needs bzr
220 if 'BR2_PACKAGE_PYTHON_NFC=y\n' in configlines and not sysinfo.has("bzr"):
222 # The ctng toolchain is affected by PR58854
223 if 'BR2_PACKAGE_LTTNG_TOOLS=y\n' in configlines and \
224 'BR2_TOOLCHAIN_EXTERNAL_URL="http://autobuild.buildroot.org/toolchains/tarballs/armv5-ctng-linux-gnueabi.tar.xz"\n' in configlines:
226 # The ctng toolchain tigger an assembler error with guile package when compiled with -Os (same issue as for CS ARM 2014.05-29)
227 if 'BR2_PACKAGE_GUILE=y\n' in configlines and \
228 'BR2_OPTIMIZE_S=y\n' in configlines and \
229 'BR2_TOOLCHAIN_EXTERNAL_URL="http://autobuild.buildroot.org/toolchains/tarballs/armv5-ctng-linux-gnueabi.tar.xz"\n' in configlines:
231 # The ctng toolchain is affected by PR58854
232 if 'BR2_PACKAGE_LTTNG_TOOLS=y\n' in configlines and \
233 'BR2_TOOLCHAIN_EXTERNAL_URL="http://autobuild.buildroot.org/toolchains/tarballs/armv6-ctng-linux-uclibcgnueabi.tar.xz"\n' in configlines:
235 # The ctng toolchain is affected by PR58854
236 if 'BR2_PACKAGE_LTTNG_TOOLS=y\n' in configlines and \
237 'BR2_TOOLCHAIN_EXTERNAL_URL="http://autobuild.buildroot.org/toolchains/tarballs/armv7-ctng-linux-gnueabihf.tar.xz"\n' in configlines:
239 # The ctng toolchain is affected by PR60155
240 if 'BR2_PACKAGE_SDL=y\n' in configlines and \
241 'BR2_TOOLCHAIN_EXTERNAL_URL="http://autobuild.buildroot.org/toolchains/tarballs/powerpc-ctng-linux-uclibc.tar.xz"\n' in configlines:
243 # The ctng toolchain is affected by PR60155
244 if 'BR2_PACKAGE_LIBMPEG2=y\n' in configlines and \
245 'BR2_TOOLCHAIN_EXTERNAL_URL="http://autobuild.buildroot.org/toolchains/tarballs/powerpc-ctng-linux-uclibc.tar.xz"\n' in configlines:
247 # This MIPS toolchain uses eglibc-2.18 which lacks SYS_getdents64
248 if 'BR2_PACKAGE_STRONGSWAN=y\n' in configlines and \
249 'BR2_TOOLCHAIN_EXTERNAL_URL="http://autobuild.buildroot.org/toolchains/tarballs/mips64el-ctng_n64-linux-gnu.tar.xz"\n' in configlines:
251 # This MIPS toolchain uses eglibc-2.18 which lacks SYS_getdents64
252 if 'BR2_PACKAGE_PYTHON3=y\n' in configlines and \
253 'BR2_TOOLCHAIN_EXTERNAL_URL="http://autobuild.buildroot.org/toolchains/tarballs/mips64el-ctng_n64-linux-gnu.tar.xz"\n' in configlines:
255 # libffi not available on sh2a and ARMv7-M, but propagating libffi
256 # arch dependencies in Buildroot is really too much work, so we
258 if 'BR2_sh2a=y\n' in configlines and \
259 'BR2_PACKAGE_LIBFFI=y\n' in configlines:
261 if 'BR2_ARM_CPU_ARMV7M=y\n' in configlines and \
262 'BR2_PACKAGE_LIBFFI=y\n' in configlines:
264 if 'BR2_PACKAGE_SUNXI_BOARDS=y\n' in configlines:
265 configlines.remove('BR2_PACKAGE_SUNXI_BOARDS_FEX_FILE=""\n')
266 configlines.append('BR2_PACKAGE_SUNXI_BOARDS_FEX_FILE="a10/hackberry.fex"\n')
267 # This MIPS uClibc toolchain fails to build the gdb package
268 if 'BR2_PACKAGE_GDB=y\n' in configlines and \
269 'BR2_TOOLCHAIN_EXTERNAL_URL="http://autobuild.buildroot.org/toolchains/tarballs/mipsel-ctng-linux-uclibc.tar.xz"\n' in configlines:
271 # This MIPS uClibc toolchain fails to build the rt-tests package
272 if 'BR2_PACKAGE_RT_TESTS=y\n' in configlines and \
273 'BR2_TOOLCHAIN_EXTERNAL_URL="http://autobuild.buildroot.org/toolchains/tarballs/mipsel-ctng-linux-uclibc.tar.xz"\n' in configlines:
275 # This MIPS uClibc toolchain fails to build the civetweb package
276 if 'BR2_PACKAGE_CIVETWEB=y\n' in configlines and \
277 'BR2_TOOLCHAIN_EXTERNAL_URL="http://autobuild.buildroot.org/toolchains/tarballs/mipsel-ctng-linux-uclibc.tar.xz"\n' in configlines:
279 # This MIPS ctng toolchain fails to build the python3 package
280 if 'BR2_PACKAGE_PYTHON3=y\n' in configlines and \
281 'BR2_TOOLCHAIN_EXTERNAL_URL="http://autobuild.buildroot.org/toolchains/tarballs/mips64el-ctng_n64-linux-gnu.tar.xz"\n' in configlines:
283 # This MIPS uClibc toolchain fails to build the strace package
284 if 'BR2_PACKAGE_STRACE=y\n' in configlines and \
285 'BR2_TOOLCHAIN_EXTERNAL_URL="http://autobuild.buildroot.org/toolchains/tarballs/mipsel-ctng-linux-uclibc.tar.xz"\n' in configlines:
287 # This MIPS uClibc toolchain fails to build the cdrkit package
288 if 'BR2_PACKAGE_CDRKIT=y\n' in configlines and \
289 'BR2_STATIC_LIBS=y\n' in configlines and \
290 'BR2_TOOLCHAIN_EXTERNAL_URL="http://autobuild.buildroot.org/toolchains/tarballs/mipsel-ctng-linux-uclibc.tar.xz"\n' in configlines:
292 # uClibc vfork static linking issue
293 if 'BR2_PACKAGE_ALSA_LIB=y\n' in configlines and \
294 'BR2_STATIC_LIBS=y\n' in configlines and \
295 'BR2_TOOLCHAIN_EXTERNAL_URL="http://autobuild.buildroot.org/toolchains/tarballs/i486-ctng-linux-uclibc.tar.xz"\n' in configlines:
297 # This MIPS uClibc toolchain fails to build the weston package
298 if 'BR2_PACKAGE_WESTON=y\n' in configlines and \
299 'BR2_TOOLCHAIN_EXTERNAL_URL="http://autobuild.buildroot.org/toolchains/tarballs/mipsel-ctng-linux-uclibc.tar.xz"\n' in configlines:
301 # The cs nios2 2017.02 toolchain is affected by binutils PR19405
302 if 'BR2_TOOLCHAIN_EXTERNAL_CODESOURCERY_NIOSII=y\n' in configlines and \
303 'BR2_PACKAGE_BOOST=y\n' in configlines:
305 # The cs nios2 2017.02 toolchain is affected by binutils PR19405
306 if 'BR2_TOOLCHAIN_EXTERNAL_CODESOURCERY_NIOSII=y\n' in configlines and \
307 'BR2_PACKAGE_QT5BASE_GUI=y\n' in configlines:
309 # The cs nios2 2017.02 toolchain is affected by binutils PR19405
310 if 'BR2_TOOLCHAIN_EXTERNAL_CODESOURCERY_NIOSII=y\n' in configlines and \
311 'BR2_PACKAGE_QT_GUI_MODULE=y\n' in configlines:
313 # The cs nios2 2017.02 toolchain is affected by binutils PR19405
314 if 'BR2_TOOLCHAIN_EXTERNAL_CODESOURCERY_NIOSII=y\n' in configlines and \
315 'BR2_PACKAGE_FLANN=y\n' in configlines:
317 # or1k affected by binutils PR21464
318 if 'BR2_or1k=y\n' in configlines and \
319 'BR2_PACKAGE_QT_GUI_MODULE=y\n' in configlines:
322 with open(configfile, "w+") as configf:
323 configf.writelines(configlines)
328 def gen_config(args):
329 """Generate a new random configuration
331 This function generates the configuration, by choosing a random
332 toolchain configuration and then generating a random selection of
336 # Select a random toolchain configuration
337 configs = get_toolchain_configs(args.toolchains_csv, args.buildrootdir)
339 i = randint(0, len(configs) - 1)
340 toolchainconfig = configs[i]
342 configlines = list(toolchainconfig)
344 # Combine with the minimal configuration
345 minimalconfigfile = os.path.join(args.buildrootdir,
346 'support/config-fragments/minimal.config')
347 with open(minimalconfigfile) as minimalf:
348 configlines += minimalf.readlines()
350 # Amend the configuration with a few things.
351 if randint(0, 20) == 0:
352 configlines.append("BR2_ENABLE_DEBUG=y\n")
353 if randint(0, 1) == 0:
354 configlines.append("BR2_INIT_BUSYBOX=y\n")
355 elif randint(0, 15) == 0:
356 configlines.append("BR2_INIT_SYSTEMD=y\n")
357 elif randint(0, 10) == 0:
358 configlines.append("BR2_ROOTFS_DEVICE_CREATION_DYNAMIC_EUDEV=y\n")
359 if randint(0, 20) == 0:
360 configlines.append("BR2_STATIC_LIBS=y\n")
361 if randint(0, 20) == 0:
362 configlines.append("BR2_PACKAGE_PYTHON_PY_ONLY=y\n")
364 # Write out the configuration file
365 if not os.path.exists(args.outputdir):
366 os.makedirs(args.outputdir)
367 if args.outputdir == os.path.abspath(os.path.join(args.buildrootdir, "output")):
368 configfile = os.path.join(args.buildrootdir, ".config")
370 configfile = os.path.join(args.outputdir, ".config")
371 with open(configfile, "w+") as configf:
372 configf.writelines(configlines)
374 subprocess.check_call(["make", "O=%s" % args.outputdir, "-C", args.buildrootdir,
377 if not is_toolchain_usable(configfile, toolchainconfig):
380 # Now, generate the random selection of packages, and fixup
382 # Safe-guard, in case we can not quickly come to a valid
383 # configuration: allow at most 100 (arbitrary) iterations.
386 if bounded_loop == 0:
387 print("ERROR: cannot generate random configuration after 100 iterations",
391 subprocess.check_call(["make", "O=%s" % args.outputdir, "-C", args.buildrootdir,
392 "KCONFIG_PROBABILITY=%d" % randint(1, 30),
393 "randpackageconfig"])
395 if fixup_config(configfile):
398 subprocess.check_call(["make", "O=%s" % args.outputdir, "-C", args.buildrootdir,
401 subprocess.check_call(["make", "O=%s" % args.outputdir, "-C", args.buildrootdir,
407 if __name__ == '__main__':
409 parser = argparse.ArgumentParser(description="Generate a random configuration")
410 parser.add_argument("--outputdir", "-o",
411 help="Output directory (relative to current directory)",
412 type=str, default='output')
413 parser.add_argument("--buildrootdir", "-b",
414 help="Buildroot directory (relative to current directory)",
415 type=str, default='.')
416 parser.add_argument("--toolchains-csv",
417 help="Path of the toolchain configuration file",
419 default="support/config-fragments/autobuild/toolchain-configs.csv")
420 args = parser.parse_args()
422 # We need the absolute path to use with O=, because the relative
423 # path to the output directory here is not relative to the
424 # Buildroot sources, but to the current directory.
425 args.outputdir = os.path.abspath(args.outputdir)
428 ret = gen_config(args)
429 except Exception as e:
430 print(str(e), file=sys.stderr)