diff --git a/Dockerfile.standalone b/Dockerfile.standalone index b942aff8..e33a2cc3 100644 --- a/Dockerfile.standalone +++ b/Dockerfile.standalone @@ -101,6 +101,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ ninja-build \ libusb-1.0-0 \ git \ + ccache \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* \ && pip install --no-cache-dir packaging @@ -171,6 +172,24 @@ ENV IDF_PATH=/opt/esp-idf ENV IDF_TOOLS_PATH=/root/.espressif ENV ARDUINO_ESP32_PATH=/opt/arduino-esp32 +# ── ccache for ESP-IDF compiles ────────────────────────────────────────────── +# ESP-IDF's build system honours IDF_CCACHE_ENABLE=1 and routes every C/C++ +# compile through ccache. Cold first compile per container is unchanged +# (cache is empty), but the second and subsequent compiles drop from +# ~5-7 minutes to ~30-60 seconds because every ESP-IDF base object +# (FreeRTOS, lwIP, esp_wifi, libsodium, …) hits the cache. +# +# Cache lives at /var/cache/ccache. Mount as a docker volume in +# docker-compose.yml so the cache survives `docker compose up -d --build`. +# Without the volume, the cache rebuilds itself on first compile after each +# image rebuild — still better than no cache. +ENV CCACHE_DIR=/var/cache/ccache +ENV IDF_CCACHE_ENABLE=1 +RUN mkdir -p /var/cache/ccache \ + && ccache --max-size=2G \ + && ccache --set-config=compression=true \ + && ccache --set-config=compression_level=6 + # Install ESP-IDF Python dependencies using the final image's Python # The requirements.txt has version constraints required by ESP-IDF 4.4.x RUN grep -v 'esp-windows-curses' /opt/esp-idf/requirements.txt \ diff --git a/backend/app/services/espidf_compiler.py b/backend/app/services/espidf_compiler.py index 12b592f7..be992b9d 100644 --- a/backend/app/services/espidf_compiler.py +++ b/backend/app/services/espidf_compiler.py @@ -951,6 +951,14 @@ class ESPIDFCompiler: str(project_dir), ] + # ccache: ESP-IDF's tools/cmake/project.cmake enables ccache iff + # the CMake variable `CCACHE_ENABLE` is truthy. We don't go through + # `idf.py` (which would translate the env var for us), so wire it + # in here. Default ON; set IDF_CCACHE_ENABLE=0 in the env to + # bypass without rebuilding the image. + if os.environ.get('IDF_CCACHE_ENABLE', '1') not in ('0', 'false', 'False', ''): + cmake_cmd.append('-DCCACHE_ENABLE=1') + logger.info(f'[espidf] cmake: {" ".join(cmake_cmd)}') def _run_cmake(): diff --git a/docker-compose.yml b/docker-compose.yml index 77f4d5cf..38673a6a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,9 +23,15 @@ services: - IDF_PATH=/opt/esp-idf - IDF_TOOLS_PATH=/root/.espressif - ARDUINO_ESP32_PATH=/opt/arduino-esp32 + # ccache for ESP-IDF compiles — the named volume below persists hits + # across container restarts and image rebuilds. Set IDF_CCACHE_ENABLE=0 + # to disable without changing the Dockerfile. + - IDF_CCACHE_ENABLE=1 + - CCACHE_DIR=/var/cache/ccache volumes: - ./data:/app/data - arduino-libs:/root/.arduino15 + - ccache:/var/cache/ccache healthcheck: test: ["CMD", "curl", "-f", "http://localhost/health"] interval: 30s @@ -35,3 +41,4 @@ services: volumes: arduino-libs: + ccache: