forked from armbian/build
-
Notifications
You must be signed in to change notification settings - Fork 0
refactor(compilation): extract ccache as extension (prep) + add compile_wrapper hooks #122
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
iav
wants to merge
11
commits into
main
Choose a base branch
from
refactor/ccache-as-extension
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from 8 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
37b57e8
chore(crust): drop dead binutils_flags_crust=""
iav 334a3d6
fix(atf): use ${CCACHE} variable instead of hardcoded ccache
iav 34519fc
feat(compilation): add atf_make_config and crust_make_config extensio…
iav cc66be1
feat(compilation): add do_with_compile_wrapper and compile_wrapper_pr…
iav 4cb6c26
feat(extensions): add extensions/ccache.sh implementing compile_wrapp…
iav 5ac9880
refactor(compilation): migrate kernel/uboot call sites to do_with_com…
iav 29a4bb9
refactor(compilation): remove legacy ccache.sh and inline ccache setup
iav 05bf8ee
fix(extensions/ccache): defer env setup to make_config; restore legac…
iav 2185ea6
fix(compilation): dispatch ccache env setup from prepare_compilation_…
iav d4a7cdb
fix(compilation): clear stale CCACHE before hook; move shim closer to…
iav f4f17e7
fix(compilation): legacy lib.config env-setup fallback; honor EXT and…
iav File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,205 @@ | ||
| # | ||
| # SPDX-License-Identifier: GPL-2.0 | ||
| # Copyright (c) 2013-2026 Igor Pecovnik, igor@armbian.com | ||
| # This file is a part of the Armbian Build Framework https://github.com/armbian/build/ | ||
| # | ||
| # Compile-cache backend: ccache (https://ccache.dev/). | ||
| # | ||
| # Wraps compiler invocations for kernel / u-boot / ATF / Crust through the | ||
| # `ccache` binary and emits per-build hit/miss stats. Implements the generic | ||
| # `compile_wrapper_pre/post` hooks (so stats are still shown on interrupt) | ||
| # and the per-component `*_make_config` hooks (so the env is set up uniformly | ||
| # regardless of which artifact is being built). | ||
| # | ||
| # Auto-enabled by core when USE_CCACHE=yes or PRIVATE_CCACHE=yes is set, or | ||
| # when the ccache-remote extension is requested (see | ||
| # main_config_redefine_user_target in main-config.sh). | ||
| # Can also be enabled explicitly via ENABLE_EXTENSIONS=ccache regardless of | ||
| # the USE_CCACHE toggle. | ||
| # | ||
| # Mutually exclusive with other compile-cache extensions (sccache, …) — the | ||
| # mutex is enforced in extension_prepare_config__ccache. | ||
| # | ||
| # Ordering invariant: all per-build env wiring (CCACHE_DIR, CCACHE_UMASK, | ||
| # PATH prefix, ${CCACHE}) is set inside the *_make_config hooks, which fire | ||
| # during the compile phase — after every extension_prepare_config_* hook | ||
| # has run. This makes the extension safe against other extensions that set | ||
| # USE_CCACHE / PRIVATE_CCACHE late in extension_prepare_config (e.g. | ||
| # ccache-remote) regardless of alphabetical ordering. | ||
|
|
||
| # Cross-hook state for SHOW_CCACHE pre→post (dir-size diff calculation). | ||
| declare -g __ext_ccache_dir_actual="" | ||
| declare -g __ext_ccache_dir_size_before="" | ||
|
|
||
| # Mutually-exclusive list of compile-cache extensions. Update when a new | ||
| # backend extension is added (sccache, …). | ||
| declare -g -a __ext_ccache_conflicting_exts=("sccache") | ||
|
|
||
| function extension_prepare_config__ccache() { | ||
| local other | ||
| for other in "${__ext_ccache_conflicting_exts[@]}"; do | ||
| if [[ ",${ENABLE_EXTENSIONS:-}," == *",${other},"* ]]; then | ||
| exit_with_error "${EXTENSION}: 'ccache' and '${other}' extensions are mutually exclusive — choose one compile-cache backend" | ||
| fi | ||
| done | ||
| # All env / CCACHE_DIR / CCACHE_UMASK / PATH setup lives in the | ||
| # *_make_config hooks — see ordering invariant in the file header. | ||
| } | ||
|
|
||
| # Resolve the cache directory ccache will use and export it so child shells | ||
| # (run_host_command_logged spawns a new bash; ccache reads CCACHE_DIR from | ||
| # env, not from bash globals) pick it up. Also exports CCACHE_UMASK in the | ||
| # shared-cache case so multi-user shared caches stay world-rw. | ||
| # | ||
| # Reads USE_CCACHE / PRIVATE_CCACHE at call time so values set late in | ||
| # extension_prepare_config (e.g. by ccache-remote) are respected without | ||
| # any ordering dependency. | ||
| function _ext_ccache_export_env() { | ||
| # Make the binary substitution available wherever scripts reference ${CCACHE}. | ||
| export CCACHE="ccache" | ||
|
|
||
| # private ccache dir avoids permission issues when build is run as root | ||
| # while user invokes via sudo; see https://ccache.samba.org/manual.html#_sharing_a_cache | ||
| if [[ "${PRIVATE_CCACHE}" == "yes" && -z "${CCACHE_DIR}" ]]; then | ||
| export CCACHE_DIR="${SRC}/cache/ccache" | ||
| elif [[ -n "${CCACHE_DIR}" ]]; then | ||
| # CCACHE_DIR set by user or another extension: make sure it is exported. | ||
| export CCACHE_DIR | ||
| fi | ||
|
|
||
| # Shared-cache mode: world-rw so multiple users on a build host can share | ||
| # the same directory. ccache reads CCACHE_UMASK from env at write time. | ||
| if [[ -z "${CCACHE_UMASK}" && "${PRIVATE_CCACHE}" != "yes" ]]; then | ||
| export CCACHE_UMASK=000 | ||
| elif [[ -n "${CCACHE_UMASK}" ]]; then | ||
| export CCACHE_UMASK | ||
| fi | ||
| } | ||
|
|
||
| # Prepend /usr/lib/ccache to the PATH entry already present in | ||
| # common_make_envs (placed there by run_*_make_internal so that, for | ||
| # example, kernel-make.sh inserts ${PYTHON3_INFO[USERBASE]}/bin for dtb | ||
| # checks). We must not append a second `PATH=` entry because `env -i` | ||
| # keeps only the last one, which would drop the python tools prefix and | ||
| # cause builds that depend on those tools to fail only when ccache is | ||
| # enabled. | ||
| function _ext_ccache_prepend_path_in_envs() { | ||
| local -n envs="$1" | ||
| local i found=0 | ||
| for i in "${!envs[@]}"; do | ||
| if [[ "${envs[$i]}" == PATH=* || "${envs[$i]}" == "PATH='"* ]]; then | ||
| # Strip leading "PATH=" or "PATH='" and trailing "'" | ||
| local existing="${envs[$i]#PATH=}" | ||
| existing="${existing#\'}" | ||
| existing="${existing%\'}" | ||
| envs[$i]="PATH='/usr/lib/ccache:${existing}'" | ||
| found=1 | ||
| break | ||
| fi | ||
| done | ||
| if (( ! found )); then | ||
| envs+=("PATH='/usr/lib/ccache:${PATH}'") | ||
| fi | ||
| } | ||
|
|
||
| # Per-artifact make hooks. Settles env at compile time, which is after every | ||
| # extension_prepare_config_* — so PRIVATE_CCACHE / CCACHE_DIR set late by | ||
| # ccache-remote (or any other extension) is honoured here. | ||
| function kernel_make_config__ccache() { | ||
| _ext_ccache_export_env | ||
| _ext_ccache_prepend_path_in_envs common_make_envs | ||
|
iav marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| function uboot_make_config__ccache() { | ||
| _ext_ccache_export_env | ||
| _ext_ccache_prepend_path_in_envs uboot_make_envs 2> /dev/null || true | ||
| } | ||
|
|
||
| function atf_make_config__ccache() { | ||
| _ext_ccache_export_env | ||
| # ATF build does not use a common_make_envs-style array; ${CCACHE} | ||
| # substitution inside `CROSS_COMPILE='${CCACHE} ${ATF_COMPILER}'` | ||
| # (set in atf.sh after this hook runs) is enough. PATH prepend is | ||
| # inherited from the caller env. | ||
| } | ||
|
|
||
| function crust_make_config__ccache() { | ||
| _ext_ccache_export_env | ||
| # Same as ATF: Crust's make line consumes ${CCACHE} directly. | ||
| } | ||
|
|
||
| function compile_wrapper_pre__ccache() { | ||
| display_alert "Clearing ccache statistics" "ccache" "ccache" | ||
| run_host_command_logged ccache --zero-stats | ||
|
|
||
| if [[ "${SHOW_CCACHE}" == "yes" ]]; then | ||
| display_alert "CCACHE_DIR" "${CCACHE_DIR:-"unset"}" "ccache" | ||
| display_alert "CCACHE_TEMPDIR" "${CCACHE_TEMPDIR:-"unset"}" "ccache" | ||
|
|
||
| __ext_ccache_dir_actual="$(ccache --show-config | grep "cache_dir =" | cut -d "=" -f 2 | xargs echo)" | ||
|
|
||
| local ccache_dir_size_before_human | ||
| __ext_ccache_dir_size_before="$(du -sb "${__ext_ccache_dir_actual}" | cut -f 1)" | ||
| ccache_dir_size_before_human="$(numfmt --to=iec-i --suffix=B --format="%.2f" "${__ext_ccache_dir_size_before}")" | ||
| display_alert "ccache dir size before" "${ccache_dir_size_before_human}" "ccache" | ||
|
|
||
| wait_for_disk_sync "before ccache config" | ||
| display_alert "ccache configuration" "ccache" "ccache" | ||
| run_host_command_logged ccache --show-config "&&" sync | ||
| fi | ||
|
|
||
| display_alert "Running ccache'd build..." "ccache" "ccache" | ||
| } | ||
|
|
||
| function compile_wrapper_post__ccache() { | ||
| local stats_output direct_hit direct_miss pct | ||
| stats_output=$(ccache --print-stats 2>&1 || true) | ||
| direct_hit=$(ccache_get_stat "${stats_output}" "direct_cache_hit") | ||
| direct_miss=$(ccache_get_stat "${stats_output}" "direct_cache_miss") | ||
| pct=$(ccache_hit_pct "${direct_hit}" "${direct_miss}") | ||
| display_alert "Ccache result" "hit=${direct_hit} miss=${direct_miss} (${pct}%)" "info" | ||
|
|
||
| # Backward-compat: invoke the legacy ccache_post_compilation hook so | ||
| # 3rd-party extensions that listened to it (e.g. ccache-remote) continue | ||
| # to receive the event and can use ccache_get_stat / ccache_hit_pct. | ||
| call_extension_method "ccache_post_compilation" <<- 'CCACHE_POST_COMPILATION' | ||
|
iav marked this conversation as resolved.
|
||
| *called after ccache-wrapped compilation completes (success or failure)* | ||
| Legacy hook preserved for backward compatibility; new extensions should | ||
| implement compile_wrapper_post__<name> instead. | ||
| CCACHE_POST_COMPILATION | ||
|
|
||
| if [[ "${SHOW_CCACHE}" == "yes" ]]; then | ||
| display_alert "Display ccache statistics" "ccache" "ccache" | ||
| run_host_command_logged ccache --show-stats --verbose | ||
|
|
||
| local ccache_dir_size_after ccache_dir_size_diff ccache_dir_size_diff_human | ||
| ccache_dir_size_after="$(du -sb "${__ext_ccache_dir_actual}" | cut -f 1)" | ||
| ccache_dir_size_diff="$((ccache_dir_size_after - __ext_ccache_dir_size_before))" | ||
| ccache_dir_size_diff_human="$(numfmt --to=iec-i --suffix=B --format="%.2f" "${ccache_dir_size_diff}")" | ||
| display_alert "ccache dir size change" "${ccache_dir_size_diff_human}" "ccache" | ||
| fi | ||
| } | ||
|
|
||
| # Parse a single numeric field from "ccache --print-stats" tab-separated | ||
| # output; returns 0 if field not found or not numeric. Public name preserved | ||
| # for backward compatibility with extensions/ccache-remote (which calls this | ||
| # from its ccache_post_compilation__show_remote_stats hook). | ||
| function ccache_get_stat() { | ||
| local stats_output="$1" field="$2" | ||
| local val | ||
| val=$(echo "${stats_output}" | grep "^${field}" | cut -f2 || true) | ||
| [[ "${val}" =~ ^[0-9]+$ ]] || val=0 | ||
| echo "${val}" | ||
| } | ||
|
|
||
| # Hit percentage from hit and miss counts. Public name preserved for the | ||
| # same backward-compat reason as ccache_get_stat. | ||
| function ccache_hit_pct() { | ||
| local hit="$1" miss="$2" | ||
| local total=$((hit + miss)) | ||
| if [[ ${total} -gt 0 ]]; then | ||
| echo $((hit * 100 / total)) | ||
| else | ||
| echo 0 | ||
| fi | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If users enable extensions through the documented
EXTalias, e.g.EXT=ccache,sccachewith a user-providedsccacheextension, this mutex check never sees the competing backend because it only inspectsENABLE_EXTENSIONS. The extension manager later loads both entries fromEXT, so both cache backends can install their compiler wrappers and pre/post hooks despite the intended mutual exclusion.Useful? React with 👍 / 👎.