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.
24 from random import randint
27 from distutils.version import StrictVersion
30 if sys.hexversion >= 0x3000000:
31 import urllib.request as _urllib
33 import urllib2 as _urllib
36 def urlopen_closing(uri):
37 return contextlib.closing(_urllib.urlopen(uri))
41 DEFAULT_NEEDED_PROGS = ["make", "git", "gcc", "timeout"]
42 DEFAULT_OPTIONAL_PROGS = ["bzr", "java", "javac", "jar", "diffoscope"]
45 self.needed_progs = list(self.__class__.DEFAULT_NEEDED_PROGS)
46 self.optional_progs = list(self.__class__.DEFAULT_OPTIONAL_PROGS)
49 def find_prog(self, name, flags=os.X_OK, env=os.environ):
50 if not name or name[0] == os.sep:
51 raise ValueError(name)
53 prog_path = env.get("PATH", None)
54 # for windows compatibility, we'd need to take PATHEXT into account
57 for prog_dir in filter(None, prog_path.split(os.pathsep)):
58 # os.join() not necessary: non-empty prog_dir
59 # and name[0] != os.sep
60 prog = prog_dir + os.sep + name
61 if os.access(prog, flags):
67 """Checks whether a program is available.
68 Lazily evaluates missing entries.
70 Returns: None if prog not found, else path to the program [evaluates
74 return self.progs[prog]
78 have_it = self.find_prog(prog)
79 # java[c] needs special care
80 if have_it and prog in ('java', 'javac'):
81 with open(os.devnull, "w") as devnull:
82 if subprocess.call("%s -version | grep gcj" % prog,
84 stdout=devnull, stderr=devnull) != 1:
87 self.progs[prog] = have_it
90 def check_requirements(self):
91 """Checks program dependencies.
93 Returns: True if all mandatory programs are present, else False.
95 do_check_has_prog = self.has
97 missing_requirements = False
98 for prog in self.needed_progs:
99 if not do_check_has_prog(prog):
100 print("ERROR: your system lacks the '%s' program" % prog)
101 missing_requirements = True
103 # check optional programs here,
104 # else they'd get checked by each worker instance
105 for prog in self.optional_progs:
106 do_check_has_prog(prog)
108 return not missing_requirements
111 def get_toolchain_configs(toolchains_csv, buildrootdir):
112 """Fetch and return the possible toolchain configurations
114 This function returns an array of toolchain configurations. Each
115 toolchain configuration is itself an array of lines of the defconfig.
118 with open(toolchains_csv) as r:
119 # filter empty lines and comments
120 lines = [t for t in r.readlines() if len(t.strip()) > 0 and t[0] != '#']
124 (_, _, _, _, hostarch) = os.uname()
125 # ~2015 distros report x86 when on a 32bit install
126 if hostarch == 'i686' or hostarch == 'i386' or hostarch == 'x86':
129 for row in csv.reader(toolchains):
132 config_hostarch = row[1]
135 # Keep all toolchain configs that work regardless of the host
137 if config_hostarch == "any":
140 # Keep all toolchain configs that can work on the current host
142 if hostarch == config_hostarch:
145 # Assume that x86 32 bits toolchains work on x86_64 build
147 if hostarch == 'x86_64' and config_hostarch == "x86":
153 if not os.path.isabs(configfile):
154 configfile = os.path.join(buildrootdir, configfile)
156 with open(configfile) as r:
157 config = r.readlines()
158 configs.append(config)
162 def is_toolchain_usable(configfile, config):
163 """Check if the toolchain is actually usable."""
165 with open(configfile) as configf:
166 configlines = configf.readlines()
168 # Check that the toolchain configuration is still present
169 for toolchainline in config:
170 if toolchainline not in configlines:
171 print("WARN: toolchain can't be used", file=sys.stderr)
172 print(" Missing: %s" % toolchainline.strip(), file=sys.stderr)
175 # The latest Linaro toolchains on x86-64 hosts requires glibc
177 if platform.machine() == 'x86_64':
178 if 'BR2_TOOLCHAIN_EXTERNAL_LINARO_ARM=y\n' in configlines or \
179 'BR2_TOOLCHAIN_EXTERNAL_LINARO_AARCH64=y\n' in configlines or \
180 'BR2_TOOLCHAIN_EXTERNAL_LINARO_AARCH64_BE=y\n' in configlines or \
181 'BR2_TOOLCHAIN_EXTERNAL_LINARO_ARMEB=y\n' in configlines:
182 ldd_version_output = subprocess.check_output(['ldd', '--version'])
183 glibc_version = ldd_version_output.splitlines()[0].split()[-1]
184 if StrictVersion('2.14') > StrictVersion(glibc_version):
185 print("WARN: ignoring the Linaro ARM toolchains because too old host glibc", file=sys.stderr)
191 def fixup_config(sysinfo, configfile):
192 """Finalize the configuration and reject any problematic combinations
194 This function returns 'True' when the configuration has been
195 accepted, and 'False' when the configuration has not been accepted because
196 it is known to fail (in which case another random configuration will be
200 with open(configfile) as configf:
201 configlines = configf.readlines()
203 BR2_TOOLCHAIN_EXTERNAL_URL = 'BR2_TOOLCHAIN_EXTERNAL_URL="http://autobuild.buildroot.org/toolchains/tarballs/'
205 if "BR2_NEEDS_HOST_JAVA=y\n" in configlines and not sysinfo.has("java"):
207 # The ctng toolchain is affected by PR58854
208 if 'BR2_PACKAGE_LTTNG_TOOLS=y\n' in configlines and \
209 BR2_TOOLCHAIN_EXTERNAL_URL + 'armv5-ctng-linux-gnueabi.tar.xz"\n' in configlines:
211 # The ctng toolchain tigger an assembler error with guile package when compiled with -Os (same issue as for CS ARM 2014.05-29)
212 if 'BR2_PACKAGE_GUILE=y\n' in configlines and \
213 'BR2_OPTIMIZE_S=y\n' in configlines and \
214 BR2_TOOLCHAIN_EXTERNAL_URL + 'armv5-ctng-linux-gnueabi.tar.xz"\n' in configlines:
216 # The ctng toolchain is affected by PR58854
217 if 'BR2_PACKAGE_LTTNG_TOOLS=y\n' in configlines and \
218 BR2_TOOLCHAIN_EXTERNAL_URL + 'armv6-ctng-linux-uclibcgnueabi.tar.xz"\n' in configlines:
220 # The ctng toolchain is affected by PR58854
221 if 'BR2_PACKAGE_LTTNG_TOOLS=y\n' in configlines and \
222 BR2_TOOLCHAIN_EXTERNAL_URL + 'armv7-ctng-linux-gnueabihf.tar.xz"\n' in configlines:
224 # The ctng toolchain is affected by PR60155
225 if 'BR2_PACKAGE_SDL=y\n' in configlines and \
226 BR2_TOOLCHAIN_EXTERNAL_URL + 'powerpc-ctng-linux-uclibc.tar.xz"\n' in configlines:
228 # The ctng toolchain is affected by PR60155
229 if 'BR2_PACKAGE_LIBMPEG2=y\n' in configlines and \
230 BR2_TOOLCHAIN_EXTERNAL_URL + 'powerpc-ctng-linux-uclibc.tar.xz"\n' in configlines:
232 # This MIPS toolchain uses eglibc-2.18 which lacks SYS_getdents64
233 if 'BR2_PACKAGE_STRONGSWAN=y\n' in configlines and \
234 BR2_TOOLCHAIN_EXTERNAL_URL + 'mips64el-ctng_n64-linux-gnu.tar.xz"\n' in configlines:
236 # This MIPS toolchain uses eglibc-2.18 which lacks SYS_getdents64
237 if 'BR2_PACKAGE_PYTHON3=y\n' in configlines and \
238 BR2_TOOLCHAIN_EXTERNAL_URL + 'mips64el-ctng_n64-linux-gnu.tar.xz"\n' in configlines:
240 # libffi not available on sh2a and ARMv7-M, but propagating libffi
241 # arch dependencies in Buildroot is really too much work, so we
243 if 'BR2_sh2a=y\n' in configlines and \
244 'BR2_PACKAGE_LIBFFI=y\n' in configlines:
246 if 'BR2_ARM_CPU_ARMV7M=y\n' in configlines and \
247 'BR2_PACKAGE_LIBFFI=y\n' in configlines:
249 if 'BR2_nds32=y\n' in configlines and \
250 'BR2_PACKAGE_LIBFFI=y\n' in configlines:
252 if 'BR2_PACKAGE_SUNXI_BOARDS=y\n' in configlines:
253 configlines.remove('BR2_PACKAGE_SUNXI_BOARDS_FEX_FILE=""\n')
254 configlines.append('BR2_PACKAGE_SUNXI_BOARDS_FEX_FILE="a10/hackberry.fex"\n')
255 # This MIPS uClibc toolchain fails to build the gdb package
256 if 'BR2_PACKAGE_GDB=y\n' in configlines and \
257 BR2_TOOLCHAIN_EXTERNAL_URL + 'mipsel-ctng-linux-uclibc.tar.xz"\n' in configlines:
259 # This MIPS uClibc toolchain fails to build the rt-tests package
260 if 'BR2_PACKAGE_RT_TESTS=y\n' in configlines and \
261 BR2_TOOLCHAIN_EXTERNAL_URL + 'mipsel-ctng-linux-uclibc.tar.xz"\n' in configlines:
263 # This MIPS uClibc toolchain fails to build the civetweb package
264 if 'BR2_PACKAGE_CIVETWEB=y\n' in configlines and \
265 BR2_TOOLCHAIN_EXTERNAL_URL + 'mipsel-ctng-linux-uclibc.tar.xz"\n' in configlines:
267 # This MIPS ctng toolchain fails to build the python3 package
268 if 'BR2_PACKAGE_PYTHON3=y\n' in configlines and \
269 BR2_TOOLCHAIN_EXTERNAL_URL + 'mips64el-ctng_n64-linux-gnu.tar.xz"\n' in configlines:
271 # This MIPS uClibc toolchain fails to build the strace package
272 if 'BR2_PACKAGE_STRACE=y\n' in configlines and \
273 BR2_TOOLCHAIN_EXTERNAL_URL + 'mipsel-ctng-linux-uclibc.tar.xz"\n' in configlines:
275 # This MIPS uClibc toolchain fails to build the cdrkit package
276 if 'BR2_PACKAGE_CDRKIT=y\n' in configlines and \
277 'BR2_STATIC_LIBS=y\n' in configlines and \
278 BR2_TOOLCHAIN_EXTERNAL_URL + 'mipsel-ctng-linux-uclibc.tar.xz"\n' in configlines:
280 # uClibc vfork static linking issue
281 if 'BR2_PACKAGE_ALSA_LIB=y\n' in configlines and \
282 'BR2_STATIC_LIBS=y\n' in configlines and \
283 BR2_TOOLCHAIN_EXTERNAL_URL + 'i486-ctng-linux-uclibc.tar.xz"\n' in configlines:
285 # This MIPS uClibc toolchain fails to build the weston package
286 if 'BR2_PACKAGE_WESTON=y\n' in configlines and \
287 BR2_TOOLCHAIN_EXTERNAL_URL + 'mipsel-ctng-linux-uclibc.tar.xz"\n' in configlines:
289 # The cs nios2 2017.02 toolchain is affected by binutils PR19405
290 if 'BR2_TOOLCHAIN_EXTERNAL_CODESOURCERY_NIOSII=y\n' in configlines and \
291 'BR2_PACKAGE_BOOST=y\n' in configlines:
293 # The cs nios2 2017.02 toolchain is affected by binutils PR19405
294 if 'BR2_TOOLCHAIN_EXTERNAL_CODESOURCERY_NIOSII=y\n' in configlines and \
295 'BR2_PACKAGE_QT5BASE_GUI=y\n' in configlines:
297 # The cs nios2 2017.02 toolchain is affected by binutils PR19405
298 if 'BR2_TOOLCHAIN_EXTERNAL_CODESOURCERY_NIOSII=y\n' in configlines and \
299 'BR2_PACKAGE_FLANN=y\n' in configlines:
302 if 'BR2_PACKAGE_HOST_UBOOT_TOOLS_ENVIMAGE=y\n' in configlines:
303 bootenv = os.path.join(args.outputdir, "boot_env.txt")
304 with open(bootenv, "w+") as bootenvf:
305 bootenvf.write("prop=value")
306 configlines.remove('BR2_PACKAGE_HOST_UBOOT_TOOLS_ENVIMAGE_SOURCE=""\n')
307 configlines.append('BR2_PACKAGE_HOST_UBOOT_TOOLS_ENVIMAGE_SOURCE="%s"\n' % bootenv)
308 configlines.remove('BR2_PACKAGE_HOST_UBOOT_TOOLS_ENVIMAGE_SIZE=""\n')
309 configlines.append('BR2_PACKAGE_HOST_UBOOT_TOOLS_ENVIMAGE_SIZE="0x1000"\n')
311 if 'BR2_PACKAGE_HOST_UBOOT_TOOLS_BOOT_SCRIPT=y\n' in configlines:
312 bootscr = os.path.join(args.outputdir, "boot_script.txt")
313 with open(bootscr, "w+") as bootscrf:
314 bootscrf.write("prop=value")
315 configlines.remove('BR2_PACKAGE_HOST_UBOOT_TOOLS_BOOT_SCRIPT_SOURCE=""\n')
316 configlines.append('BR2_PACKAGE_HOST_UBOOT_TOOLS_BOOT_SCRIPT_SOURCE="%s"\n' % bootscr)
318 with open(configfile, "w+") as configf:
319 configf.writelines(configlines)
324 def gen_config(args):
325 """Generate a new random configuration
327 This function generates the configuration, by choosing a random
328 toolchain configuration and then generating a random selection of
332 sysinfo = SystemInfo()
334 # Select a random toolchain configuration
335 configs = get_toolchain_configs(args.toolchains_csv, args.buildrootdir)
337 i = randint(0, len(configs) - 1)
338 toolchainconfig = configs[i]
340 configlines = list(toolchainconfig)
342 # Combine with the minimal configuration
343 minimalconfigfile = os.path.join(args.buildrootdir,
344 'support/config-fragments/minimal.config')
345 with open(minimalconfigfile) as minimalf:
346 configlines += minimalf.readlines()
348 # Allow hosts with old certificates to download over https
349 configlines.append("BR2_WGET=\"wget --passive-ftp -nd -t 3 --no-check-certificate\"\n")
352 if randint(0, 15) == 0:
353 configlines.append("BR2_PER_PACKAGE_DIRECTORIES=y\n")
355 # Amend the configuration with a few things.
356 if randint(0, 20) == 0:
357 configlines.append("BR2_ENABLE_DEBUG=y\n")
358 if randint(0, 20) == 0:
359 configlines.append("BR2_ENABLE_RUNTIME_DEBUG=y\n")
360 if randint(0, 1) == 0:
361 configlines.append("BR2_INIT_BUSYBOX=y\n")
362 elif randint(0, 15) == 0:
363 configlines.append("BR2_INIT_SYSTEMD=y\n")
364 elif randint(0, 10) == 0:
365 configlines.append("BR2_ROOTFS_DEVICE_CREATION_DYNAMIC_EUDEV=y\n")
366 if randint(0, 20) == 0:
367 configlines.append("BR2_STATIC_LIBS=y\n")
368 if randint(0, 20) == 0:
369 configlines.append("BR2_PACKAGE_PYTHON_PY_ONLY=y\n")
370 if randint(0, 20) == 0:
371 configlines.append("BR2_PACKAGE_PYTHON3_PY_ONLY=y\n")
372 if randint(0, 5) == 0:
373 configlines.append("BR2_OPTIMIZE_2=y\n")
374 if randint(0, 4) == 0:
375 configlines.append("BR2_SYSTEM_ENABLE_NLS=y\n")
376 if randint(0, 4) == 0:
377 configlines.append("BR2_FORTIFY_SOURCE_2=y\n")
379 # Randomly enable BR2_REPRODUCIBLE 10% of times
380 # also enable tar filesystem images for testing
381 if sysinfo.has("diffoscope") and randint(0, 10) == 0:
382 configlines.append("BR2_REPRODUCIBLE=y\n")
383 configlines.append("BR2_TARGET_ROOTFS_TAR=y\n")
385 # Write out the configuration file
386 if not os.path.exists(args.outputdir):
387 os.makedirs(args.outputdir)
388 if args.outputdir == os.path.abspath(os.path.join(args.buildrootdir, "output")):
389 configfile = os.path.join(args.buildrootdir, ".config")
391 configfile = os.path.join(args.outputdir, ".config")
392 with open(configfile, "w+") as configf:
393 configf.writelines(configlines)
395 subprocess.check_call(["make", "O=%s" % args.outputdir, "-C", args.buildrootdir,
398 if not is_toolchain_usable(configfile, toolchainconfig):
401 # Now, generate the random selection of packages, and fixup
403 # Safe-guard, in case we can not quickly come to a valid
404 # configuration: allow at most 100 (arbitrary) iterations.
407 if bounded_loop == 0:
408 print("ERROR: cannot generate random configuration after 100 iterations",
412 subprocess.check_call(["make", "O=%s" % args.outputdir, "-C", args.buildrootdir,
413 "KCONFIG_PROBABILITY=%d" % randint(1, 30),
414 "randpackageconfig"])
416 if fixup_config(sysinfo, configfile):
419 subprocess.check_call(["make", "O=%s" % args.outputdir, "-C", args.buildrootdir,
422 subprocess.check_call(["make", "O=%s" % args.outputdir, "-C", args.buildrootdir,
425 return subprocess.call(["make", "O=%s" % args.outputdir, "-C", args.buildrootdir,
429 if __name__ == '__main__':
431 parser = argparse.ArgumentParser(description="Generate a random configuration")
432 parser.add_argument("--outputdir", "-o",
433 help="Output directory (relative to current directory)",
434 type=str, default='output')
435 parser.add_argument("--buildrootdir", "-b",
436 help="Buildroot directory (relative to current directory)",
437 type=str, default='.')
438 parser.add_argument("--toolchains-csv",
439 help="Path of the toolchain configuration file",
441 default="support/config-fragments/autobuild/toolchain-configs.csv")
442 args = parser.parse_args()
444 # We need the absolute path to use with O=, because the relative
445 # path to the output directory here is not relative to the
446 # Buildroot sources, but to the current directory.
447 args.outputdir = os.path.abspath(args.outputdir)
450 ret = gen_config(args)
451 except Exception as e:
452 print(str(e), file=sys.stderr)