diff --git a/classes/sanity-external-toolchain.bbclass b/classes/sanity-external-toolchain.bbclass new file mode 100644 index 0000000..1155b17 --- /dev/null +++ b/classes/sanity-external-toolchain.bbclass @@ -0,0 +1,94 @@ +TOOLCHAIN_SANITY_VERSION = "1" + +def check_toolchain_sanity(d, generate_events=False): + import shlex + import tempfile + + if not d.getVar('TCMODE', True).startswith('external'): + return + + extpath = d.getVar('EXTERNAL_TOOLCHAIN', True) + + # Test 1: EXTERNAL_TOOLCHAIN exists + if not os.path.exists(extpath): + raise_exttc_sanity_error('EXTERNAL_TOOLCHAIN path `%s` does not exist' % extdir, d, generate_events) + extpath = os.path.realpath(extpath) + + sanity_file = d.expand('${TOPDIR}/conf/exttc_sanity_info') + version = d.getVar('TOOLCHAIN_SANITY_VERSION', True) + check, config = should_run(sanity_file, {'version': version, 'path': extpath}) + if not check: + return + cfgdata = config['DEFAULT'] + + # Test 2: EXTERNAL_TARGET_SYS is set correctly + if d.getVar('EXTERNAL_TARGET_SYS', True) == 'UNKNOWN': + raise_exttc_sanity_error('Unable to locate prefixed gcc binary for %s in EXTERNAL_TOOLCHAIN/bin (%s/bin)' % (d.getVar('TARGET_SYS', True), d.getVar('EXTERNAL_TOOLCHAIN', True)), d, generate_events) + + # Test 3: gcc binary exists + gcc = d.expand('${EXTERNAL_TOOLCHAIN}/bin/${TARGET_PREFIX}gcc') + if not os.path.exists(gcc): + raise_exttc_sanity_error('Compiler path `%s` does not exist' % gcc, d, generate_events) + + # Test 4: we can run it to get the version + cmd = d.expand('${EXTERNAL_TOOLCHAIN}/bin/${TARGET_PREFIX}gcc -dumpversion') + sourcery_version = exttc_sanity_run(shlex.split(cmd), d, generate_events) + if cfgdata.get('sourcery_version') == sourcery_version: + return + + # Test 5: we can compile an empty test app + with tempfile.TemporaryDirectory() as tmpdir: + with open(os.path.join(tmpdir, 'test.c'), 'w') as f: + f.write('int main() {}') + + # The external toolchain recipes haven't necessarily been built, so we + # need to drop --sysroot= and --no-sysroot-suffix and use the bits in + # the external toolchain sysroots for this test + l = d.createCopy() + l.setVar('TOOLCHAIN_OPTIONS', '') + l.setVar('HOST_CC_ARCH_remove', '--no-sysroot-suffix') + cmd = l.expand('${EXTERNAL_TOOLCHAIN}/bin/${CC} ${CFLAGS} ${LDFLAGS} test.c -o test') + exttc_sanity_run(shlex.split(cmd), d, generate_events, tmpdir) + + with open(sanity_file, 'w') as f: + config.write(f) + +def should_run(cfgfile, expected): + import configparser + + config = configparser.ConfigParser() + readfiles = config.read(cfgfile) + cfgdata = config['DEFAULT'] + + if cfgfile in readfiles and cfgdata == expected: + return False, None + + cfgdata.update(expected) + return True, config + +def raise_exttc_sanity_error(msg, d, generate_events): + msg = 'Sanity check of the external toolchain failed: ' + msg + if generate_events: + try: + bb.event.fire(bb.event.SanityCheckFailed(msg, None), d) + except TypeError: + bb.event.fire(bb.event.SanityCheckFailed(msg), d) + else: + bb.fatal(msg) + +def exttc_sanity_run(cmd, d, generate_events, cwd='/'): + import subprocess + try: + return subprocess.check_output(cmd, stderr=subprocess.STDOUT, cwd=cwd) + except subprocess.CalledProcessError as exc: + if not isinstance(cmd, str): + cmd = subprocess.list2cmdline(cmd) + output = exc.output.decode() + output_indented = ''.join(' ' + l for l in output.splitlines(keepends=True)) + raise_exttc_sanity_error('\n Command: %s\n Exit code: %s\n Output:\n%s' % (cmd, exc.returncode, output_indented), d, generate_events) + +python toolchain_sanity_eventhandler() { + check_toolchain_sanity(d, e.generateevents) +} +toolchain_sanity_eventhandler[eventmask] = "bb.event.SanityCheck" +addhandler toolchain_sanity_eventhandler diff --git a/conf/distro/include/tcmode-external-sourcery.inc b/conf/distro/include/tcmode-external-sourcery.inc index 6d77287..feae78d 100644 --- a/conf/distro/include/tcmode-external-sourcery.inc +++ b/conf/distro/include/tcmode-external-sourcery.inc @@ -56,6 +56,9 @@ PREFERRED_PROVIDER_linux-libc-headers = "linux-libc-headers-external" # Support use of an external toolchain with the SDK/ADE/etc TOOLCHAIN_TARGET_TASK_append = " sdk-env-external-toolchain" +# Sanity check the toolchain configuration and toolchain +INHERIT += "sanity-external-toolchain" + # Pull in our utility functions for use elsewhere INHERIT += "external-common" @@ -180,7 +183,7 @@ python toolchain_metadata_setup () { with tempfile.NamedTemporaryFile(suffix='.c') as f: try: - subprocess.check_output([d.expand('${EXTERNAL_TOOLCHAIN}/bin/${EXTERNAL_TARGET_SYS}-gcc'), '-msgxx-glibc', '-E', f.name], stderr=subprocess.STDOUT, env=testenv, cwd=d.getVar('TOPDIR', True)) + subprocess.check_output([d.expand('${EXTERNAL_TOOLCHAIN}/bin/${TARGET_PREFIX}gcc'), '-msgxx-glibc', '-E', f.name], stderr=subprocess.STDOUT, env=testenv, cwd=d.getVar('TOPDIR', True)) except (OSError, subprocess.CalledProcessError): pass else: @@ -256,34 +259,6 @@ def set_vars_from_toolchains(codebench_path, d): d.setVar('EXTERNAL_TARGET_SYS', triplets[0]) d.setVar('EXTERNAL_TOOLCHAIN', os.path.join(toolchains_path, toolchain_subdir)) - -python toolchain_sanity_check () { - d = e.data - external_toolchain = d.getVar('EXTERNAL_TOOLCHAIN', True) - if not external_toolchain or external_toolchain == 'UNDEFINED': - bb.fatal("EXTERNAL_TOOLCHAIN must be set to the path to your sourcery toolchain") - - if not os.path.exists(external_toolchain): - bb.fatal("EXTERNAL_TOOLCHAIN is invalid: path '%s' does not exist" % external_toolchain) - - bindir = os.path.join(external_toolchain, 'bin') - if not os.path.exists(bindir): - bb.fatal("EXTERNAL_TOOLCHAIN is invalid: path '%s' does not exist" % bindir) - - if d.getVar('EXTERNAL_TARGET_SYS', True) == 'UNKNOWN': - bb.fatal('Unable to locate prefixed gcc binary for %s in EXTERNAL_TOOLCHAIN/bin (%s/bin)' % (d.getVar('TARGET_SYS', True), d.getVar('EXTERNAL_TOOLCHAIN', True))) - - if d.getVar('GCC_VERSION', True) == 'UNKNOWN': - bb.warn("EXTERNAL_TOOLCHAIN gcc version extraction failed, see debug messages for details") -} - -# This runs at TreeDataPreparationStarted time, as we want bitbake -e to work, -# so it has to run after ConfigParsed, and we want to see these errors rather -# than the highly verbose unbuildable -external recipe errors which occur when -# generating the runqueue, so we need to run before BuildStarted. -toolchain_sanity_check[eventmask] = "bb.event.TreeDataPreparationStarted" -addhandler toolchain_sanity_check - GCCVERSION ?= "${@'.'.join('${GCC_VERSION}'.split('.')[:2])}%" GCC_VERSION = "${@external_run(d, 'gcc', '-dumpversion').rstrip()}" GCC_VERSION_allarch = ""