]> Git Repo - buildroot-mgba.git/blob - utils/genrandconfig
Merge branch 'next'
[buildroot-mgba.git] / utils / genrandconfig
1 #!/usr/bin/env python
2
3 # Copyright (C) 2014 by Thomas Petazzoni <[email protected]>
4 #
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.
9 #
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.
14 #
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
18
19 # This script generates a random configuration for testing Buildroot.
20
21 from __future__ import print_function
22
23 import contextlib
24 import csv
25 import os
26 from random import randint
27 import subprocess
28 import sys
29 from distutils.version import StrictVersion
30 import platform
31
32 if sys.hexversion >= 0x3000000:
33     import urllib.request as _urllib
34 else:
35     import urllib2 as _urllib
36
37
38 def urlopen_closing(uri):
39     return contextlib.closing(_urllib.urlopen(uri))
40
41
42 if sys.hexversion >= 0x3000000:
43     def decode_byte_list(bl):
44         return [b.decode() for b in bl]
45 else:
46     def decode_byte_list(e):
47         return e
48
49
50 class SystemInfo:
51     DEFAULT_NEEDED_PROGS = ["make", "git", "gcc", "timeout"]
52     DEFAULT_OPTIONAL_PROGS = ["bzr", "java", "javac", "jar"]
53
54     def __init__(self):
55         self.needed_progs = list(self.__class__.DEFAULT_NEEDED_PROGS)
56         self.optional_progs = list(self.__class__.DEFAULT_OPTIONAL_PROGS)
57         self.progs = {}
58
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)
62
63         prog_path = env.get("PATH", None)
64         # for windows compatibility, we'd need to take PATHEXT into account
65
66         if prog_path:
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):
72                     return prog
73         # --
74         return None
75
76     def has(self, prog):
77         """Checks whether a program is available.
78         Lazily evaluates missing entries.
79
80         Returns: None if prog not found, else path to the program [evaluates
81         to True]
82         """
83         try:
84             return self.progs[prog]
85         except KeyError:
86             pass
87
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,
93                                    shell=True,
94                                    stdout=devnull, stderr=devnull) != 1:
95                     have_it = False
96         # --
97         self.progs[prog] = have_it
98         return have_it
99
100     def check_requirements(self):
101         """Checks program dependencies.
102
103         Returns: True if all mandatory programs are present, else False.
104         """
105         do_check_has_prog = self.has
106
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
112
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)
117
118         return not missing_requirements
119
120
121 def get_toolchain_configs(toolchains_csv, buildrootdir):
122     """Fetch and return the possible toolchain configurations
123
124     This function returns an array of toolchain configurations. Each
125     toolchain configuration is itself an array of lines of the defconfig.
126     """
127
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)
132     configs = []
133
134     (_, _, _, _, hostarch) = os.uname()
135     # ~2015 distros report x86 when on a 32bit install
136     if hostarch == 'i686' or hostarch == 'i386' or hostarch == 'x86':
137         hostarch = 'x86'
138
139     for row in csv.reader(toolchains):
140         config = {}
141         configfile = row[0]
142         config_hostarch = row[1]
143         keep = False
144
145         # Keep all toolchain configs that work regardless of the host
146         # architecture
147         if config_hostarch == "any":
148             keep = True
149
150         # Keep all toolchain configs that can work on the current host
151         # architecture
152         if hostarch == config_hostarch:
153             keep = True
154
155         # Assume that x86 32 bits toolchains work on x86_64 build
156         # machines
157         if hostarch == 'x86_64' and config_hostarch == "x86":
158             keep = True
159
160         if not keep:
161             continue
162
163         if not os.path.isabs(configfile):
164             configfile = os.path.join(buildrootdir, configfile)
165
166         with open(configfile) as r:
167             config = r.readlines()
168         configs.append(config)
169     return configs
170
171
172 def is_toolchain_usable(configfile, config):
173     """Check if the toolchain is actually usable."""
174
175     with open(configfile) as configf:
176         configlines = configf.readlines()
177
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)
183             return False
184
185     # The latest Linaro toolchains on x86-64 hosts requires glibc
186     # 2.14+ on the host.
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)
195                 return False
196
197     return True
198
199
200 def fixup_config(configfile):
201     """Finalize the configuration and reject any problematic combinations
202
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
206     generated).
207     """
208
209     sysinfo = SystemInfo()
210     with open(configfile) as configf:
211         configlines = configf.readlines()
212
213     if "BR2_NEEDS_HOST_JAVA=y\n" in configlines and not sysinfo.has("java"):
214         return False
215     if "BR2_NEEDS_HOST_JAVAC=y\n" in configlines and not sysinfo.has("javac"):
216         return False
217     if "BR2_NEEDS_HOST_JAR=y\n" in configlines and not sysinfo.has("jar"):
218         return False
219     # python-nfc needs bzr
220     if 'BR2_PACKAGE_PYTHON_NFC=y\n' in configlines and not sysinfo.has("bzr"):
221         return False
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:
225         return False
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:
230         return False
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:
234         return False
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:
238         return False
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:
242         return False
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:
246         return False
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:
250         return False
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:
254         return False
255     # libffi not available on sh2a and ARMv7-M, but propagating libffi
256     # arch dependencies in Buildroot is really too much work, so we
257     # handle this here.
258     if 'BR2_sh2a=y\n' in configlines and \
259        'BR2_PACKAGE_LIBFFI=y\n' in configlines:
260         return False
261     if 'BR2_ARM_CPU_ARMV7M=y\n' in configlines and \
262        'BR2_PACKAGE_LIBFFI=y\n' in configlines:
263         return False
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:
270         return False
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:
274         return False
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:
278         return False
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:
282         return False
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:
286         return False
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:
291         return False
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:
296         return False
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:
300         return False
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:
304         return False
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:
308         return False
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:
312         return False
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:
316         return False
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:
320         return False
321
322     with open(configfile, "w+") as configf:
323         configf.writelines(configlines)
324
325     return True
326
327
328 def gen_config(args):
329     """Generate a new random configuration
330
331     This function generates the configuration, by choosing a random
332     toolchain configuration and then generating a random selection of
333     packages.
334     """
335
336     # Select a random toolchain configuration
337     configs = get_toolchain_configs(args.toolchains_csv, args.buildrootdir)
338
339     i = randint(0, len(configs) - 1)
340     toolchainconfig = configs[i]
341
342     configlines = list(toolchainconfig)
343
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()
349
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")
363
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")
369     else:
370         configfile = os.path.join(args.outputdir, ".config")
371     with open(configfile, "w+") as configf:
372         configf.writelines(configlines)
373
374     subprocess.check_call(["make", "O=%s" % args.outputdir, "-C", args.buildrootdir,
375                            "olddefconfig"])
376
377     if not is_toolchain_usable(configfile, toolchainconfig):
378         return 2
379
380     # Now, generate the random selection of packages, and fixup
381     # things if needed.
382     # Safe-guard, in case we can not quickly come to a valid
383     # configuration: allow at most 100 (arbitrary) iterations.
384     bounded_loop = 100
385     while True:
386         if bounded_loop == 0:
387             print("ERROR: cannot generate random configuration after 100 iterations",
388                   file=sys.stderr)
389             return 1
390         bounded_loop -= 1
391         subprocess.check_call(["make", "O=%s" % args.outputdir, "-C", args.buildrootdir,
392                                "KCONFIG_PROBABILITY=%d" % randint(1, 30),
393                                "randpackageconfig"])
394
395         if fixup_config(configfile):
396             break
397
398     subprocess.check_call(["make", "O=%s" % args.outputdir, "-C", args.buildrootdir,
399                            "olddefconfig"])
400
401     subprocess.check_call(["make", "O=%s" % args.outputdir, "-C", args.buildrootdir,
402                            "savedefconfig"])
403
404     return 0
405
406
407 if __name__ == '__main__':
408     import argparse
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",
418                         type=str,
419                         default="support/config-fragments/autobuild/toolchain-configs.csv")
420     args = parser.parse_args()
421
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)
426
427     try:
428         ret = gen_config(args)
429     except Exception as e:
430         print(str(e), file=sys.stderr)
431         parser.exit(1)
432     parser.exit(ret)
This page took 0.058481 seconds and 4 git commands to generate.