Skip to content
Draft
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 137 additions & 0 deletions extensions/ccache.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
#
# 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
# (see prepare_compilation_vars in lib/functions/configuration/compilation-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.

# 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"
Comment on lines +44 to +46
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor EXT when checking cache-backend conflicts

If users enable extensions through the documented EXT alias, e.g. EXT=ccache,sccache with a user-provided sccache extension, this mutex check never sees the competing backend because it only inspects ENABLE_EXTENSIONS. The extension manager later loads both entries from EXT, so both cache backends can install their compiler wrappers and pre/post hooks despite the intended mutual exclusion.

Useful? React with 👍 / 👎.

fi
done

# Defaults. Caller env wins; these only fire when unset.
declare -g 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
declare -g CCACHE_DIR="${SRC}/cache/ccache"
Comment thread
iav marked this conversation as resolved.
Outdated
Comment thread
iav marked this conversation as resolved.
Outdated
fi

# Shared cache mode: world-rw so multiple users on a build host can share.
if [[ -z "${CCACHE_UMASK}" && "${PRIVATE_CCACHE}" != "yes" ]]; then
declare -g CCACHE_UMASK=000
Comment thread
iav marked this conversation as resolved.
Outdated
fi
}

# Install /usr/lib/ccache to the front of PATH inside every `env -i` make
# invocation (kernel / u-boot / atf / crust). The `${CCACHE}` variable is
# already consumed inline in each artifact's make recipe.
function _ext_ccache_inject_path() {
# common_make_envs is declared in the caller's scope by run_*_make_internal
# functions; append a PATH override that prefixes /usr/lib/ccache.
common_make_envs+=("PATH='/usr/lib/ccache:${PATH}'")
Comment thread
iav marked this conversation as resolved.
Outdated
}

function kernel_make_config__ccache() { _ext_ccache_inject_path; }
function uboot_make_config__ccache() { _ext_ccache_inject_path; }
function atf_make_config__ccache() { _ext_ccache_inject_path; }
function crust_make_config__ccache() { _ext_ccache_inject_path; }

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=$(_ext_ccache_get_stat "${stats_output}" "direct_cache_hit")
direct_miss=$(_ext_ccache_get_stat "${stats_output}" "direct_cache_miss")
pct=$(_ext_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 continue to receive the event.
call_extension_method "ccache_post_compilation" <<- 'CCACHE_POST_COMPILATION'
Comment thread
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.
function _ext_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.
function _ext_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
}
12 changes: 11 additions & 1 deletion lib/functions/compilation/atf.sh
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,17 @@ compile_atf() {

# - ENABLE_BACKTRACE="0" has been added to workaround a regression in ATF. Check: https://github.com/armbian/build/issues/1157

run_host_command_logged "CROSS_COMPILE='ccache ${ATF_COMPILER}'" CCACHE_BASEDIR="$(pwd)" "CC='ccache ${ATF_COMPILER}gcc'" \
call_extension_method "atf_make_config" <<- 'ATF_MAKE_CONFIG'
*Hook to customize ATF (TF-A) make environment before compilation*
Called right before invoking make for ATF. Extensions (compile-cache
wrappers like ccache/sccache, distcc setups, etc.) can modify env vars
exported by the make invocation; the actual wrap point is `${CCACHE}`
substituted into CROSS_COMPILE / CC below.
Available read-only variables:
- ATF_COMPILER, ATF, BOARD, BRANCH, LINUXFAMILY
ATF_MAKE_CONFIG

run_host_command_logged "CROSS_COMPILE='${CCACHE} ${ATF_COMPILER}'" CCACHE_BASEDIR="$(pwd)" "CC='${CCACHE} ${ATF_COMPILER}gcc'" \
"CFLAGS='-fdiagnostics-color=always -Wno-error=attributes -Wno-error=incompatible-pointer-types'" \
"TF_LDFLAGS='${binutils_flags_atf}'" \
make ENABLE_BACKTRACE="0" LOG_LEVEL="40" BUILD_STRING="armbian" $target_make "${CTHREADS}"
Expand Down
110 changes: 0 additions & 110 deletions lib/functions/compilation/ccache.sh

This file was deleted.

69 changes: 69 additions & 0 deletions lib/functions/compilation/compile-wrapper.sh
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
}
12 changes: 11 additions & 1 deletion lib/functions/compilation/crust.sh
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,20 @@ compile_crust() {
return 0
fi

declare binutils_version binutils_flags_crust=""
declare binutils_version
binutils_version=$(env or1k-elf-ld.bfd --version | head -1 | cut -d ")" -f 2 | xargs echo -n)
display_alert "Binutils version for Crust" "${binutils_version}" "info"

call_extension_method "crust_make_config" <<- 'CRUST_MAKE_CONFIG'
*Hook to customize Crust make environment before compilation*
Called right before invoking make for Crust (both defconfig and target make).
Extensions (compile-cache wrappers like ccache/sccache, distcc setups, etc.)
can modify env vars exported by the make invocation; the actual wrap
point is `${CCACHE}` substituted into `CROSS_COMPILE` below.
Available read-only variables:
- CRUST_COMPILER, BOARD, BRANCH, LINUXFAMILY
CRUST_MAKE_CONFIG

run_host_command_logged CCACHE_BASEDIR="$(pwd)" \
"CFLAGS='-fdiagnostics-color=always -Wno-error=attributes -Wno-error=incompatible-pointer-types'" \
make ${CRUSTCONFIG} "${CTHREADS}" "CROSS_COMPILE='$CCACHE $CRUST_COMPILER'"
Expand Down
4 changes: 2 additions & 2 deletions lib/functions/compilation/kernel.sh
Original file line number Diff line number Diff line change
Expand Up @@ -256,11 +256,11 @@ function kernel_build() {
cd "${kernel_work_dir}" || exit_with_error "Can't cd to kernel_work_dir: ${kernel_work_dir}"

display_alert "Building kernel" "${LINUXFAMILY} ${LINUXCONFIG} ${build_targets_build[*]}" "info"
do_with_ccache_statistics \
do_with_compile_wrapper \
run_kernel_make_long_running "${install_make_params_quoted[@]@Q}" "${build_targets_build[@]}" # "V=1" # "-s" silent mode, "V=1" verbose mode

display_alert "Installing kernel" "${LINUXFAMILY} ${LINUXCONFIG} ${build_targets_install[*]}" "info"
do_with_ccache_statistics \
do_with_compile_wrapper \
run_kernel_make_long_running "${install_make_params_quoted[@]@Q}" "${build_targets_install[@]}" # "V=1" # "-s" silent mode, "V=1" verbose mode

display_alert "Kernel built in" "$((SECONDS - ts)) seconds - ${version}-${LINUXFAMILY}" "info"
Expand Down
2 changes: 1 addition & 1 deletion lib/functions/compilation/uboot.sh
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ function compile_uboot_target() {

display_alert "${uboot_prefix}Compiling u-boot" "${version} ${target_make} with gcc '${gcc_version_main}'" "info"
declare -g if_error_detail_message="${uboot_prefix}Failed to build u-boot ${version} ${target_make}"
do_with_ccache_statistics run_host_command_logged_long_running \
do_with_compile_wrapper run_host_command_logged_long_running \
"env" "-i" "${uboot_make_envs[@]}" \
pipetty make "$target_make" "$CTHREADS" "${cross_compile}"

Expand Down
Loading
Loading