icecc.bbclass 16 KB

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