forked from armbian/build
-
Notifications
You must be signed in to change notification settings - Fork 0
refactor(compilation): extract ccache into a swappable extension #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
5
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.
+375
−140
Draft
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
97cf949
chore(crust): drop dead binutils_flags_crust=""
iav c0a988f
fix(atf): use ${CCACHE} variable instead of hardcoded ccache
iav 771c11b
feat(compilation): add atf/crust _make_config hooks and do_with_compi…
iav c23f5bb
feat(extensions): add ccache extension
iav d3170e8
refactor(compilation): migrate ccache to extension; remove legacy cca…
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,193 @@ | ||
| # | ||
| # 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_prepare_vars` (env exports) and `compile_wrapper_pre/post` hooks | ||
| # (stats, also fired on interrupt). Per-artifact `*_make_config` hooks | ||
| # inject CCACHE_UMASK into the env-i make envs since the kernel/u-boot | ||
| # auto-passthrough only covers CCACHE_DIR. | ||
| # | ||
| # Auto-enabled by core when USE_CCACHE=yes / 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: env wiring lives in compile_prepare_vars__ccache, | ||
| # which fires from prepare_compilation_vars (start-end.sh:20) — late | ||
| # enough that every extension_prepare_config_* and the userpatches / | ||
| # user_config phases have settled values like PRIVATE_CCACHE, and early | ||
| # enough that ${CCACHE} substitution and PATH prefix propagate into the | ||
| # arrays built by run_*_make_internal. Order is fixed by core, not by | ||
| # alphabetical hook name resolution. | ||
|
|
||
| # 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() { | ||
| # Use the shared normalization (EXT fallback + comma/whitespace handling). | ||
| local _ext_list other | ||
| _ext_list="$(extension_list_normalized)" | ||
| for other in "${__ext_ccache_conflicting_exts[@]}"; do | ||
| if [[ "${_ext_list}" == *",${other},"* ]]; then | ||
| exit_with_error "${EXTENSION}: 'ccache' and '${other}' extensions are mutually exclusive — choose one compile-cache backend" | ||
| fi | ||
| done | ||
| # All env setup lives in compile_prepare_vars__ccache (called from | ||
| # prepare_compilation_vars). See ordering invariant in the file header. | ||
| } | ||
|
|
||
| # Main env setup. Runs from prepare_compilation_vars — late enough that | ||
| # values set by other extensions (PRIVATE_CCACHE from ccache-remote) and | ||
| # by userpatches/lib.config / user_config hooks are settled, and early | ||
| # enough that ${CCACHE} substitution in run_*_make_internal sees the | ||
| # exported value. | ||
| function compile_prepare_vars__ccache() { | ||
| # Make the binary substitution available wherever scripts reference | ||
| # ${CCACHE} (kernel-make.sh:60, uboot.sh:259, atf.sh:86, crust.sh:55,59). | ||
| export CCACHE="ccache" | ||
|
|
||
| # Drop a wrapper directory in front of PATH so bare `gcc` invocations | ||
| # also route through ccache via /usr/lib/ccache symlinks. This becomes | ||
| # part of the PATH captured by kernel-make.sh:23 and uboot.sh:243 when | ||
| # they build the env-i make environment. | ||
| export PATH="/usr/lib/ccache:${PATH}" | ||
|
|
||
| # 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; | ||
| # the *_make_config hooks below inject it into env-i make envs since | ||
| # kernel/uboot auto-passthrough only covers CCACHE_DIR. | ||
| if [[ -z "${CCACHE_UMASK}" && "${PRIVATE_CCACHE}" != "yes" ]]; then | ||
| export CCACHE_UMASK=000 | ||
| elif [[ -n "${CCACHE_UMASK}" ]]; then | ||
| export CCACHE_UMASK | ||
| fi | ||
| } | ||
|
|
||
| # Inject CCACHE_UMASK into the given env-i make envs array if set. Core | ||
| # kernel-make.sh / uboot.sh auto-pass CCACHE_DIR but not CCACHE_UMASK, | ||
| # and env -i would otherwise drop it so ccache writes fall back to the | ||
| # default umask — breaking world-rw guarantees for shared caches. | ||
| function _ext_ccache_inject_umask() { | ||
| local -n envs="$1" | ||
| [[ -n "${CCACHE_UMASK}" ]] && envs+=("CCACHE_UMASK=${CCACHE_UMASK@Q}") | ||
| return 0 | ||
| } | ||
|
|
||
| function kernel_make_config__ccache() { _ext_ccache_inject_umask common_make_envs; } | ||
| function uboot_make_config__ccache() { _ext_ccache_inject_umask uboot_make_envs; } | ||
| # ATF / Crust don't use env -i (their make runs in the host shell), so | ||
| # CCACHE_UMASK propagates naturally from the export in compile_prepare_vars. | ||
| # No make_config hook needed for them. | ||
|
|
||
| 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)" | ||
|
|
||
| # Guard against ccache versions / configs where cache_dir isn't reported | ||
| # yet (first-run, broken config). Empty path would feed `du -sb ""`, | ||
| # breaking the build under `set -e`; fall back to a zero baseline so | ||
| # the post-hook can still compute a (meaningless but harmless) diff. | ||
| local ccache_dir_size_before_human | ||
| if [[ -n "${__ext_ccache_dir_actual}" && -d "${__ext_ccache_dir_actual}" ]]; then | ||
| __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" | ||
| else | ||
| __ext_ccache_dir_size_before="0" | ||
| display_alert "ccache dir size before" "unavailable (cache dir not resolved)" "wrn" | ||
| fi | ||
|
|
||
| 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 | ||
|
|
||
| # Mirror the pre-hook guard: skip diff if we never resolved the dir. | ||
| if [[ -n "${__ext_ccache_dir_actual}" && -d "${__ext_ccache_dir_actual}" ]]; then | ||
| 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" | ||
| else | ||
| display_alert "ccache dir size change" "unavailable (cache dir not resolved)" "wrn" | ||
| fi | ||
| 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.
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,69 @@ | ||
| #!/usr/bin/env bash | ||
| # | ||
| # 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/ | ||
|
|
||
| # Generic compile-time wrapper, backend-agnostic. | ||
| # | ||
| # Wraps a compilation command (`make ...` invocation in run_host_command_logged | ||
| # or similar) with two extension hooks: | ||
| # | ||
| # compile_wrapper_pre — called before the build. Extensions zero stats, | ||
| # start long-lived helper servers, assert backend | ||
| # health, etc. | ||
| # compile_wrapper_post — called after the build (also on SIGINT/errexit via | ||
| # the registered cleanup handler). Extensions show | ||
| # stats, push final cache batches, etc. | ||
| # | ||
| # Extensions are identified by the conventional double-underscore suffix: | ||
| # compile_wrapper_pre__<extension_name>() | ||
| # compile_wrapper_post__<extension_name>() | ||
| # | ||
| # Default behavior with no extensions registered: passthrough — the command | ||
| # runs unchanged. This makes the wrapper safe to drop in everywhere a | ||
| # compilation invocation lives, without committing to any specific cache | ||
| # backend. | ||
| function do_with_compile_wrapper() { | ||
| # Register the post-hook as a cleanup handler BEFORE running pre hooks. | ||
| # A compile_wrapper_pre implementation may start a long-lived helper | ||
| # (e.g. `sccache --start-server`); if another pre hook then fails or the | ||
| # user interrupts during pre-hook execution, the post hook must still | ||
| # fire so backend cleanup (stop helpers, flush state) is not skipped. | ||
| add_cleanup_handler _do_with_compile_wrapper_run_post | ||
|
|
||
| call_extension_method "compile_wrapper_pre" <<- 'COMPILE_WRAPPER_PRE' | ||
| *pre-compilation hook for cache wrappers (ccache, sccache, …) and | ||
| similar backend-agnostic setup* | ||
| Called once right before the wrapped compilation command runs. | ||
| Implementations may zero stats counters, start a long-lived helper | ||
| process, validate that a remote backend is reachable, etc. | ||
| The matching compile_wrapper_post hook is guaranteed to fire even | ||
| if a later pre hook fails or the build is interrupted, so cleanup | ||
| of resources started here is safe to rely on. | ||
| COMPILE_WRAPPER_PRE | ||
|
|
||
| local build_exit_code=0 | ||
| "$@" || build_exit_code=$? | ||
|
|
||
| # Remove and explicitly run the post-hook on the success path so it does | ||
| # not also fire from the cleanup handler at script exit. | ||
| execute_and_remove_cleanup_handler _do_with_compile_wrapper_run_post | ||
|
|
||
| return ${build_exit_code} | ||
| } | ||
|
|
||
| # Internal — invoked by do_with_compile_wrapper either directly on success or | ||
| # via the cleanup-handler chain on interruption. Kept as a separate function | ||
| # so it has a stable name to register with add_cleanup_handler. | ||
| function _do_with_compile_wrapper_run_post() { | ||
| call_extension_method "compile_wrapper_post" <<- 'COMPILE_WRAPPER_POST' | ||
| *post-compilation hook for cache wrappers and similar* | ||
| Called once after the wrapped compilation command completes (success | ||
| or failure) or is interrupted. Implementations may display stats, | ||
| flush a remote cache write buffer, shut down a helper server, etc. | ||
| COMPILE_WRAPPER_POST | ||
| } |
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.
Uh oh!
There was an error while loading. Please reload this page.