1# IceCream distributed compiling support 2# 3# Stages directories with symlinks from gcc/g++ to icecc, for both 4# native and cross compilers. Depending on each configure or compile, 5# the directories are added at the head of the PATH list and ICECC_CXX 6# and ICEC_CC are set. 7# 8# For the cross compiler, creates a tar.gz of our toolchain and sets 9# ICECC_VERSION accordingly. 10# 11# The class now handles all 3 different compile 'stages' (i.e native ,cross-kernel and target) creating the 12# necessary environment tar.gz file to be used by the remote machines. 13# It also supports meta-toolchain generation 14# 15# If ICECC_PATH is not set in local.conf then the class will try to locate it using 'bb.utils.which' 16# but nothing is sure ;) 17# 18# If ICECC_ENV_EXEC is set in local.conf, then it should point to the icecc-create-env script provided by the user 19# or the default one provided by icecc-create-env.bb will be used 20# (NOTE that this is a modified version of the script need it and *not the one that comes with icecc* 21# 22# User can specify if specific recipes or recipes belonging to class should not use icecc to distribute 23# compile jobs to remote machines, but handled locally, by defining ICECC_CLASS_DISABLE and ICECC_RECIPE_DISABLE 24# with the appropriate values in local.conf. In addition the user can force to enable icecc for recipes 25# which set an empty PARALLEL_MAKE variable by defining ICECC_RECIPE_ENABLE. 26# 27######################################################################################### 28#Error checking is kept to minimum so double check any parameters you pass to the class 29########################################################################################### 30 31BB_BASEHASH_IGNORE_VARS += "ICECC_PARALLEL_MAKE ICECC_DISABLED ICECC_RECIPE_DISABLE \ 32 ICECC_CLASS_DISABLE ICECC_RECIPE_ENABLE ICECC_PATH ICECC_ENV_EXEC \ 33 ICECC_CARET_WORKAROUND ICECC_CFLAGS ICECC_ENV_VERSION \ 34 ICECC_DEBUG ICECC_LOGFILE ICECC_REPEAT_RATE ICECC_PREFERRED_HOST \ 35 ICECC_CLANG_REMOTE_CPP ICECC_IGNORE_UNVERIFIED ICECC_TEST_SOCKET \ 36 ICECC_ENV_DEBUG ICECC_REMOTE_CPP \ 37 " 38 39ICECC_ENV_EXEC ?= "${STAGING_BINDIR_NATIVE}/icecc-create-env" 40 41HOSTTOOLS_NONFATAL += "icecc patchelf" 42 43# This version can be incremented when changes are made to the environment that 44# invalidate the version on the compile nodes. Changing it will cause a new 45# environment to be created. 46# 47# A useful thing to do for testing Icecream changes locally is to add a 48# subversion in local.conf: 49# ICECC_ENV_VERSION:append = "-my-ver-1" 50ICECC_ENV_VERSION = "2" 51 52# Default to disabling the caret workaround, If set to "1" in local.conf, icecc 53# will locally recompile any files that have warnings, which can adversely 54# affect performance. 55# 56# See: https://github.com/icecc/icecream/issues/190 57export ICECC_CARET_WORKAROUND ??= "0" 58 59export ICECC_REMOTE_CPP ??= "0" 60 61ICECC_CFLAGS = "" 62CFLAGS += "${ICECC_CFLAGS}" 63CXXFLAGS += "${ICECC_CFLAGS}" 64 65# Debug flags when generating environments 66ICECC_ENV_DEBUG ??= "" 67 68# Disable recipe list contains a list of recipes that can not distribute 69# compile tasks for one reason or the other. When adding new entry, please 70# document why (how it failed) so that we can re-evaluate it later e.g. when 71# there is new version 72# 73# libgcc-initial - fails with CPP sanity check error if host sysroot contains 74# cross gcc built for another target tune/variant 75# pixman - prng_state: TLS reference mismatches non-TLS reference, possibly due to 76# pragma omp threadprivate(prng_state) 77# systemtap - _HelperSDT.c undefs macros and uses the identifiers in macros emitting 78# inline assembly 79# target-sdk-provides-dummy - ${HOST_PREFIX} is empty which triggers the "NULL 80# prefix" error. 81ICECC_RECIPE_DISABLE += "\ 82 libgcc-initial \ 83 pixman \ 84 systemtap \ 85 target-sdk-provides-dummy \ 86 " 87 88# Classes that should not use icecc. When adding new entry, please 89# document why (how it failed) so that we can re-evaluate it later 90# 91# image - Image aren't compiling, but the testing framework for images captures 92# PARALLEL_MAKE as part of the test environment. Many tests won't use 93# icecream, but leaving the high level of parallelism can cause them to 94# consume an unnecessary amount of resources. 95ICECC_CLASS_DISABLE += "\ 96 image \ 97 " 98 99def get_icecc_dep(d): 100 # INHIBIT_DEFAULT_DEPS doesn't apply to the patch command. Whether or not 101 # we need that built is the responsibility of the patch function / class, not 102 # the application. 103 if not d.getVar('INHIBIT_DEFAULT_DEPS'): 104 return "icecc-create-env-native" 105 return "" 106 107DEPENDS:prepend = "${@get_icecc_dep(d)} " 108 109get_cross_kernel_cc[vardepsexclude] += "KERNEL_CC" 110def get_cross_kernel_cc(bb,d): 111 if not icecc_is_kernel(bb, d): 112 return None 113 114 # evaluate the expression by the shell if necessary 115 kernel_cc = d.getVar('KERNEL_CC') 116 if '`' in kernel_cc or '$(' in kernel_cc: 117 import subprocess 118 kernel_cc = subprocess.check_output("echo %s" % kernel_cc, shell=True).decode("utf-8")[:-1] 119 120 kernel_cc = kernel_cc.replace('ccache', '').strip() 121 kernel_cc = kernel_cc.split(' ')[0] 122 kernel_cc = kernel_cc.strip() 123 return kernel_cc 124 125def get_icecc(d): 126 return d.getVar('ICECC_PATH') or bb.utils.which(os.getenv("PATH"), "icecc") 127 128def use_icecc(bb,d): 129 if d.getVar('ICECC_DISABLED') == "1": 130 # don't even try it, when explicitly disabled 131 return "no" 132 133 # allarch recipes don't use compiler 134 if icecc_is_allarch(bb, d): 135 return "no" 136 137 if icecc_is_cross_canadian(bb, d): 138 return "no" 139 140 pn = d.getVar('PN') 141 bpn = d.getVar('BPN') 142 143 # Enable/disable checks are made against BPN, because there is a good 144 # chance that if icecc should be skipped for a recipe, it should be skipped 145 # for all the variants of that recipe. PN is still checked in case a user 146 # specified a more specific recipe. 147 check_pn = set([pn, bpn]) 148 149 class_disable = (d.getVar('ICECC_CLASS_DISABLE') or "").split() 150 151 for bbclass in class_disable: 152 if bb.data.inherits_class(bbclass, d): 153 bb.debug(1, "%s: bbclass %s found in disable, disable icecc" % (pn, bbclass)) 154 return "no" 155 156 disabled_recipes = (d.getVar('ICECC_RECIPE_DISABLE') or "").split() 157 enabled_recipes = (d.getVar('ICECC_RECIPE_ENABLE') or "").split() 158 159 if check_pn & set(disabled_recipes): 160 bb.debug(1, "%s: found in disable list, disable icecc" % pn) 161 return "no" 162 163 if check_pn & set(enabled_recipes): 164 bb.debug(1, "%s: found in enabled recipes list, enable icecc" % pn) 165 return "yes" 166 167 if d.getVar('PARALLEL_MAKE') == "": 168 bb.debug(1, "%s: has empty PARALLEL_MAKE, disable icecc" % pn) 169 return "no" 170 171 return "yes" 172 173def icecc_is_allarch(bb, d): 174 return d.getVar("PACKAGE_ARCH") == "all" 175 176def icecc_is_kernel(bb, d): 177 return \ 178 bb.data.inherits_class("kernel", d); 179 180def icecc_is_native(bb, d): 181 return \ 182 bb.data.inherits_class("cross", d) or \ 183 bb.data.inherits_class("native", d); 184 185def icecc_is_cross_canadian(bb, d): 186 return bb.data.inherits_class("cross-canadian", d) 187 188def icecc_dir(bb, d): 189 return d.expand('${TMPDIR}/work-shared/ice') 190 191# Don't pollute allarch signatures with TARGET_FPU 192icecc_version[vardepsexclude] += "TARGET_FPU" 193def icecc_version(bb, d): 194 if use_icecc(bb, d) == "no": 195 return "" 196 197 parallel = d.getVar('ICECC_PARALLEL_MAKE') or "" 198 if not d.getVar('PARALLEL_MAKE') == "" and parallel: 199 d.setVar("PARALLEL_MAKE", parallel) 200 201 # Disable showing the caret in the GCC compiler output if the workaround is 202 # disabled 203 if d.getVar('ICECC_CARET_WORKAROUND') == '0': 204 d.setVar('ICECC_CFLAGS', '-fno-diagnostics-show-caret') 205 206 if icecc_is_native(bb, d): 207 archive_name = "local-host-env" 208 elif d.expand('${HOST_PREFIX}') == "": 209 bb.fatal(d.expand("${PN}"), " NULL prefix") 210 else: 211 prefix = d.expand('${HOST_PREFIX}' ) 212 distro = d.expand('${DISTRO}') 213 target_sys = d.expand('${TARGET_SYS}') 214 float = d.getVar('TARGET_FPU') or "hard" 215 archive_name = prefix + distro + "-" + target_sys + "-" + float 216 if icecc_is_kernel(bb, d): 217 archive_name += "-kernel" 218 219 import socket 220 ice_dir = icecc_dir(bb, d) 221 tar_file = os.path.join(ice_dir, "{archive}-{version}-@VERSION@-{hostname}.tar.gz".format( 222 archive=archive_name, 223 version=d.getVar('ICECC_ENV_VERSION'), 224 hostname=socket.gethostname() 225 )) 226 227 return tar_file 228 229def icecc_path(bb,d): 230 if use_icecc(bb, d) == "no": 231 # don't create unnecessary directories when icecc is disabled 232 return 233 234 staging = os.path.join(d.expand('${STAGING_BINDIR}'), "ice") 235 if icecc_is_kernel(bb, d): 236 staging += "-kernel" 237 238 return staging 239 240def icecc_get_external_tool(bb, d, tool): 241 external_toolchain_bindir = d.expand('${EXTERNAL_TOOLCHAIN}${bindir_cross}') 242 target_prefix = d.expand('${TARGET_PREFIX}') 243 return os.path.join(external_toolchain_bindir, '%s%s' % (target_prefix, tool)) 244 245def icecc_get_tool_link(tool, d): 246 import subprocess 247 try: 248 return subprocess.check_output("readlink -f %s" % tool, shell=True).decode("utf-8")[:-1] 249 except subprocess.CalledProcessError as e: 250 bb.note("icecc: one of the tools probably disappeared during recipe parsing, cmd readlink -f %s returned %d:\n%s" % (tool, e.returncode, e.output.decode("utf-8"))) 251 return tool 252 253def icecc_get_path_tool(tool, d): 254 # This is a little ugly, but we want to make sure we add an actual 255 # compiler to the toolchain, not ccache. Some distros (e.g. Fedora) 256 # have ccache enabled by default using symlinks PATH, meaning ccache 257 # would be found first when looking for the compiler. 258 paths = os.getenv("PATH").split(':') 259 while True: 260 p, hist = bb.utils.which(':'.join(paths), tool, history=True) 261 if not p or os.path.basename(icecc_get_tool_link(p, d)) != 'ccache': 262 return p 263 paths = paths[len(hist):] 264 265 return "" 266 267# Don't pollute native signatures with target TUNE_PKGARCH through STAGING_BINDIR_TOOLCHAIN 268icecc_get_tool[vardepsexclude] += "STAGING_BINDIR_TOOLCHAIN" 269def icecc_get_tool(bb, d, tool): 270 if icecc_is_native(bb, d): 271 return icecc_get_path_tool(tool, d) 272 elif icecc_is_kernel(bb, d): 273 return icecc_get_path_tool(get_cross_kernel_cc(bb, d), d) 274 else: 275 ice_dir = d.expand('${STAGING_BINDIR_TOOLCHAIN}') 276 target_sys = d.expand('${TARGET_SYS}') 277 for p in ice_dir.split(':'): 278 tool_bin = os.path.join(p, "%s-%s" % (target_sys, tool)) 279 if os.path.isfile(tool_bin): 280 return tool_bin 281 external_tool_bin = icecc_get_external_tool(bb, d, tool) 282 if os.path.isfile(external_tool_bin): 283 return external_tool_bin 284 return "" 285 286def icecc_get_and_check_tool(bb, d, tool): 287 # Check that g++ or gcc is not a symbolic link to icecc binary in 288 # PATH or icecc-create-env script will silently create an invalid 289 # compiler environment package. 290 t = icecc_get_tool(bb, d, tool) 291 if t: 292 link_path = icecc_get_tool_link(t, d) 293 if link_path == get_icecc(d): 294 bb.error("%s is a symlink to %s in PATH and this prevents icecc from working" % (t, link_path)) 295 return "" 296 else: 297 return t 298 else: 299 return t 300 301wait_for_file() { 302 local TIME_ELAPSED=0 303 local FILE_TO_TEST=$1 304 local TIMEOUT=$2 305 until [ -f "$FILE_TO_TEST" ] 306 do 307 TIME_ELAPSED=$(expr $TIME_ELAPSED + 1) 308 if [ $TIME_ELAPSED -gt $TIMEOUT ] 309 then 310 return 1 311 fi 312 sleep 1 313 done 314} 315 316def set_icecc_env(): 317 # dummy python version of set_icecc_env 318 return 319 320set_icecc_env[vardepsexclude] += "KERNEL_CC" 321set_icecc_env() { 322 if [ "${@use_icecc(bb, d)}" = "no" ] 323 then 324 return 325 fi 326 ICECC_VERSION="${@icecc_version(bb, d)}" 327 if [ "x${ICECC_VERSION}" = "x" ] 328 then 329 bbwarn "Cannot use icecc: could not get ICECC_VERSION" 330 return 331 fi 332 333 ICE_PATH="${@icecc_path(bb, d)}" 334 if [ "x${ICE_PATH}" = "x" ] 335 then 336 bbwarn "Cannot use icecc: could not get ICE_PATH" 337 return 338 fi 339 340 ICECC_BIN="${@get_icecc(d)}" 341 if [ -z "${ICECC_BIN}" ]; then 342 bbwarn "Cannot use icecc: icecc binary not found" 343 return 344 fi 345 if [ -z "$(which patchelf patchelf-uninative)" ]; then 346 bbwarn "Cannot use icecc: patchelf not found" 347 return 348 fi 349 350 ICECC_CC="${@icecc_get_and_check_tool(bb, d, "gcc")}" 351 ICECC_CXX="${@icecc_get_and_check_tool(bb, d, "g++")}" 352 # cannot use icecc_get_and_check_tool here because it assumes as without target_sys prefix 353 ICECC_WHICH_AS="${@bb.utils.which(os.getenv('PATH'), 'as')}" 354 if [ ! -x "${ICECC_CC}" -o ! -x "${ICECC_CXX}" ] 355 then 356 bbnote "Cannot use icecc: could not get ICECC_CC or ICECC_CXX" 357 return 358 fi 359 360 ICE_VERSION="$($ICECC_CC -dumpversion)" 361 ICECC_VERSION=$(echo ${ICECC_VERSION} | sed -e "s/@VERSION@/$ICE_VERSION/g") 362 if [ ! -x "${ICECC_ENV_EXEC}" ] 363 then 364 bbwarn "Cannot use icecc: invalid ICECC_ENV_EXEC" 365 return 366 fi 367 368 # Create symlinks to icecc and wrapper-scripts in the recipe-sysroot directory 369 mkdir -p $ICE_PATH/symlinks 370 if [ -n "${KERNEL_CC}" ]; then 371 compilers="${@get_cross_kernel_cc(bb,d)}" 372 else 373 compilers="${HOST_PREFIX}gcc ${HOST_PREFIX}g++" 374 fi 375 for compiler in $compilers; do 376 ln -sf $ICECC_BIN $ICE_PATH/symlinks/$compiler 377 rm -f $ICE_PATH/$compiler 378 cat <<-__EOF__ > $ICE_PATH/$compiler 379 #!/bin/sh -e 380 export ICECC_VERSION=$ICECC_VERSION 381 export ICECC_CC=$ICECC_CC 382 export ICECC_CXX=$ICECC_CXX 383 $ICE_PATH/symlinks/$compiler "\$@" 384 __EOF__ 385 chmod 775 $ICE_PATH/$compiler 386 done 387 388 ICECC_AS="$(${ICECC_CC} -print-prog-name=as)" 389 # for target recipes should return something like: 390 # /OE/tmp-eglibc/sysroots/x86_64-linux/usr/libexec/arm920tt-oe-linux-gnueabi/gcc/arm-oe-linux-gnueabi/4.8.2/as 391 # and just "as" for native, if it returns "as" in current directory (for whatever reason) use "as" from PATH 392 if [ "$(dirname "${ICECC_AS}")" = "." ] 393 then 394 ICECC_AS="${ICECC_WHICH_AS}" 395 fi 396 397 if [ ! -f "${ICECC_VERSION}.done" ] 398 then 399 mkdir -p "$(dirname "${ICECC_VERSION}")" 400 401 # the ICECC_VERSION generation step must be locked by a mutex 402 # in order to prevent race conditions 403 if flock -n "${ICECC_VERSION}.lock" \ 404 ${ICECC_ENV_EXEC} ${ICECC_ENV_DEBUG} "${ICECC_CC}" "${ICECC_CXX}" "${ICECC_AS}" "${ICECC_VERSION}" 405 then 406 touch "${ICECC_VERSION}.done" 407 elif ! wait_for_file "${ICECC_VERSION}.done" 30 408 then 409 # locking failed so wait for ${ICECC_VERSION}.done to appear 410 bbwarn "Timeout waiting for ${ICECC_VERSION}.done" 411 return 412 fi 413 fi 414 415 # Don't let ccache find the icecream compiler links that have been created, otherwise 416 # it can end up invoking icecream recursively. 417 export CCACHE_PATH="$PATH" 418 export CCACHE_DISABLE="1" 419 420 export PATH="$ICE_PATH:$PATH" 421 422 bbnote "Using icecc path: $ICE_PATH" 423 bbnote "Using icecc tarball: $ICECC_VERSION" 424} 425 426do_configure[network] = "1" 427do_configure:prepend() { 428 set_icecc_env 429} 430 431do_compile[network] = "1" 432do_compile:prepend() { 433 set_icecc_env 434} 435 436do_compile_kernelmodules[network] = "1" 437do_compile_kernelmodules:prepend() { 438 set_icecc_env 439} 440 441do_install[network] = "1" 442do_install:prepend() { 443 set_icecc_env 444} 445 446# IceCream is not (currently) supported in the extensible SDK 447ICECC_SDK_HOST_TASK = "nativesdk-icecc-toolchain" 448ICECC_SDK_HOST_TASK:task-populate-sdk-ext = "" 449 450# Don't include IceCream in uninative tarball 451ICECC_SDK_HOST_TASK:pn-uninative-tarball = "" 452 453# Add the toolchain scripts to the SDK 454TOOLCHAIN_HOST_TASK:append = " ${ICECC_SDK_HOST_TASK}" 455