From cbae9bc1c8ea95fe9cdc9e15f95c8541b5400e6a Mon Sep 17 00:00:00 2001
From: Norman Feske <norman.feske@genode-labs.com>
Date: Fri, 29 Jan 2021 16:11:12 +0100
Subject: [PATCH] Add ccache support to build system

This patch simplifies the use of ccache with the build system. Up until
now, each developer had to set up the ccache hooks manually, adjust the
PATH variable, and customize the etc/tools.conf in each build directory.
With the patch, ccache can be enabled by un-commenting a single line in
the etc/build.conf file.

Fixes #4004
---
 .../src/kernel/pistachio/target.mk            |  5 ++
 tool/builddir/build.conf/ccache               |  3 +
 tool/builddir/build.mk                        | 79 +++++++++++++++++--
 tool/create_builddir                          |  2 +-
 4 files changed, 80 insertions(+), 9 deletions(-)
 create mode 100644 tool/builddir/build.conf/ccache

diff --git a/repos/base-pistachio/src/kernel/pistachio/target.mk b/repos/base-pistachio/src/kernel/pistachio/target.mk
index 064034247a..30e8d89d1d 100644
--- a/repos/base-pistachio/src/kernel/pistachio/target.mk
+++ b/repos/base-pistachio/src/kernel/pistachio/target.mk
@@ -18,6 +18,11 @@ $(KERNEL_BUILD_DIR)/Makefile:
 	$(VERBOSE_MK) MAKEFLAGS= $(MAKE) $(VERBOSE_DIR) -C $(KERNEL_SRC) BUILDDIR=$(dir $@)
 	$(VERBOSE)cp $(REP_DIR)/config/kernel $(KERNEL_BUILD_DIR)/config/config.out
 
+#
+# Prevent passing 'CCACHE=yes'. The environment variable is evaluated by the
+# kernel's build system, which expects it to be empty or set to 'ccache'.
+#
+unexport CCACHE
 
 #
 # How to pass custom compiler flags to the Pistachio build system
diff --git a/tool/builddir/build.conf/ccache b/tool/builddir/build.conf/ccache
new file mode 100644
index 0000000000..d74373014b
--- /dev/null
+++ b/tool/builddir/build.conf/ccache
@@ -0,0 +1,3 @@
+# enable use of compiler cache
+#CCACHE := yes
+
diff --git a/tool/builddir/build.mk b/tool/builddir/build.mk
index a793c75baa..9ac5c6c97f 100644
--- a/tool/builddir/build.mk
+++ b/tool/builddir/build.mk
@@ -85,6 +85,13 @@ BASE_DIR     := $(realpath $(shell echo $(BASE_DIR)))
 #
 export SHELL := $(shell which bash)
 
+#
+# Discharge variables evaluated by ccache mechanism that may be inherited when
+# using the build system in a nested fashion.
+#
+undefine CUSTOM_CXX
+undefine CUSTOM_CC
+
 #
 # Fetch SPECS configuration from all source repositories and the build directory
 #
@@ -118,7 +125,7 @@ export SPECS
 
 include $(BASE_DIR)/mk/global.mk
 
-export LIBGCC_INC_DIR = $(shell dirname `$(CUSTOM_CXX_LIB) -print-libgcc-file-name`)/include
+export LIBGCC_INC_DIR := $(shell dirname `$(CUSTOM_CXX_LIB) -print-libgcc-file-name`)/include
 
 #
 # Find out about the target directories to build
@@ -129,6 +136,11 @@ ifeq ($(MAKECMDGOALS),)
 DST_DIRS := *
 endif
 
+#
+# Helper function to check if a needed tool is installed
+#
+check_tool = $(if $(shell which $(1)),,$(error Need to have '$(1)' installed.))
+
 #
 # Tool chain version check
 #
@@ -143,7 +155,6 @@ endif
 endif
 
 ifneq ($(STATIC_ANALYZE),)
-check_tool = $(if $(shell which $(1)),,$(error Need to have '$(1)' installed.))
 $(call check_tool,scan-build)
 
 MAKE := scan-build --use-c++=$(CUSTOM_CXX) --use-cc=$(CUSTOM_CC) $(MAKE)
@@ -310,6 +321,64 @@ gen_deps_and_build_targets: $(INSTALL_DIR) $(DEBUG_DIR) $(LIB_DEP_FILE)
 again: $(INSTALL_DIR) $(DEBUG_DIR)
 	@$(VERBOSE_MK)$(MAKE) $(VERBOSE_DIR) -f $(LIB_DEP_FILE) all
 
+#
+# Read tools configuration to obtain the cross-compiler prefix passed
+# to the run script.
+#
+-include $(call select_from_repositories,etc/tools.conf)
+
+
+##
+## Compiler-cache support
+##
+
+#
+# To hook the ccache into the build process, the compiler executables are
+# shadowed by symlinks named after the compiler but pointing to the ccache
+# program. When invoked, the ccache program uses argv0 to query the real
+# compiler executable.
+#
+# If the configured tool-chain path is absolute, we assume that it is not
+# already part of the PATH variable. In this (default) case, we supplement the
+# tool-chain path to the CCACHE_PATH as evaluated by the ccache program.
+#
+# Should the tool-chain path not be absolute, the tool-chain executables must
+# already be reachable via the regular PATH variable. Otherwise, the build
+# would not work without ccache either.
+#
+
+ifeq ($(CCACHE),yes)
+
+$(call check_tool,ccache)
+
+ifneq ($(dir $(CUSTOM_CXX)),$(dir $(CUSTOM_CC)))
+$(error ccache enabled but the compilers $(CUSTOM_CXX) and $(CUSTOM_CC)\
+        reside in different directories)
+endif
+
+CCACHE_TOOL_DIR    := $(addsuffix /var/tool/ccache,$(BUILD_BASE_DIR))
+CCACHED_CUSTOM_CC  := $(CCACHE_TOOL_DIR)/$(notdir $(CUSTOM_CC))
+CCACHED_CUSTOM_CXX := $(CCACHE_TOOL_DIR)/$(notdir $(CUSTOM_CXX))
+
+gen_deps_and_build_targets: $(CCACHED_CUSTOM_CC) $(CCACHED_CUSTOM_CXX)
+
+# create ccache symlinks at var/tool/ccache/
+$(CCACHED_CUSTOM_CC) $(CCACHED_CUSTOM_CXX):
+	$(VERBOSE_MK)mkdir -p $(dir $@)
+	$(VERBOSE_MK)ln -sf `which ccache` $@
+
+# supplement tool-chain directory to the search-path variable used by ccache
+ifneq ($(filter /%,$(CUSTOM_CXX)),)
+export CCACHE_PATH := $(dir $(CUSTOM_CXX)):$(PATH)
+endif
+
+# override CUSTOM_CC and CUSTOM_CXX to point to the ccache symlinks
+export CUSTOM_CC  := $(CCACHED_CUSTOM_CC)
+export CUSTOM_CXX := $(CCACHED_CUSTOM_CXX)
+
+endif
+
+
 ##
 ## Rules for running automated test cases
 ##
@@ -319,12 +388,6 @@ RUN_OPT ?=
 # helper for run/% rule
 RUN_SCRIPT = $(call select_from_repositories,run/$*.run)
 
-#
-# Read tools configuration to obtain the cross-compiler prefix passed
-# to the run script.
-#
--include $(call select_from_repositories,etc/tools.conf)
-
 run/%: $(call select_from_repositories,run/%.run) $(RUN_ENV)
 	$(VERBOSE)test -f "$(RUN_SCRIPT)" || (echo "Error: No run script for $*"; exit -1)
 	$(VERBOSE)$(GENODE_DIR)/tool/run/run --genode-dir $(GENODE_DIR) \
diff --git a/tool/create_builddir b/tool/create_builddir
index be357f1749..cbff207612 100755
--- a/tool/create_builddir
+++ b/tool/create_builddir
@@ -102,7 +102,7 @@ $(BUILD_DIR)/etc/build.conf:
 	@echo 'BASE_DIR    := $$(GENODE_DIR)/repos/base' >> $@
 	@echo 'CONTRIB_DIR := $(CONTRIB_ABS_DIR)' >> $@
 	@echo >> $@
-	@for i in make_j run; do \
+	@for i in make_j ccache run; do \
 		cat $(GENODE_DIR)/tool/builddir/build.conf/$$i; done >> $@
 	@for i in ${BUILD_CONF(${PLATFORM})}; do \
 		cat $(GENODE_DIR)/tool/builddir/build.conf/$$i; done >> $@