From 915ba192772cfdf1d52e38baea0940b5a04697ba Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 28 Sep 2026 21:03:12 +0000 Subject: [PATCH] build: scope per-commit version defines to BuildInfo.cpp The git SHA, dirty flag and version were global defines, so every commit changed every compile command and forced a full rebuild. They now reach only src/util/BuildInfo.cpp, which exposes them through AppVersion::. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01XgLRarbAmBVepTPKCK7mgd --- lib/AppVersion/AppVersion.h | 25 +++++------ lib/hal/HalSystem.cpp | 3 +- scripts/git_branch.py | 42 ++++++++++--------- src/activities/boot_sleep/BootActivity.cpp | 2 +- src/activities/boot_sleep/SleepActivity.cpp | 2 +- src/activities/settings/OtaUpdateActivity.cpp | 2 +- src/activities/settings/SettingsActivity.cpp | 10 ++--- src/main.cpp | 2 +- src/network/CrossPointWebServer.cpp | 6 +-- src/network/HttpDownloader.cpp | 4 +- src/network/OtaUpdater.cpp | 10 ++--- src/network/UsbSerialFileTransfer.cpp | 6 +-- src/util/BatteryDiagnosticLog.cpp | 4 +- src/util/BuildInfo.cpp | 25 +++++++++++ 14 files changed, 84 insertions(+), 59 deletions(-) diff --git a/lib/AppVersion/AppVersion.h b/lib/AppVersion/AppVersion.h index 401ab92ad20..0876c8d5124 100644 --- a/lib/AppVersion/AppVersion.h +++ b/lib/AppVersion/AppVersion.h @@ -1,19 +1,20 @@ #pragma once +// Build identity. The version, commit and dirty flag change with every commit, +// so scripts/git_branch.py defines them only for src/util/BuildInfo.cpp, which +// implements these accessors. A global define would change every compile +// command and force a full rebuild after each commit. +namespace AppVersion { +const char* version(); // e.g. "1.6.0-x4-pro" +const char* versionLabel(); // "CrossInk " +const char* userAgent(); // "CrossInk-ESP32-" +const char* gitSha(); // short commit hash, or "unknown" +bool gitDirty(); // tracked files were modified when built +const char* gitDirtyFlag(); // "1", "0" or "unknown" +} // namespace AppVersion + // PlatformIO normally supplies these through build_flags/extra_scripts. Keep // fallbacks here so editor indexers and simulator-like tools still parse files. -#ifndef CROSSINK_VERSION -#define CROSSINK_VERSION "dev" -#endif - -#ifndef CROSSINK_GIT_SHA -#define CROSSINK_GIT_SHA "unknown" -#endif - -#ifndef CROSSINK_GIT_DIRTY -#define CROSSINK_GIT_DIRTY "unknown" -#endif - #ifndef CROSSINK_PIOENV #define CROSSINK_PIOENV "unknown" #endif diff --git a/lib/hal/HalSystem.cpp b/lib/hal/HalSystem.cpp index 9111a7db428..135cd25d93c 100644 --- a/lib/hal/HalSystem.cpp +++ b/lib/hal/HalSystem.cpp @@ -299,7 +299,8 @@ std::string getPanicInfo(bool full) { } else { std::string info; - info += "CrossInk version: " CROSSINK_VERSION; + info += "CrossInk version: "; + info += AppVersion::version(); info += "\nCrossInk device type: " CROSSINK_FIRMWARE_DEVICE_TYPE; char elfSha[65] = {}; esp_app_get_elf_sha256(elfSha, sizeof(elfSha)); diff --git a/scripts/git_branch.py b/scripts/git_branch.py index 70328a1f6dc..aedc80846d8 100644 --- a/scripts/git_branch.py +++ b/scripts/git_branch.py @@ -123,10 +123,11 @@ def short_branch_label(branch): return branch.rsplit('/', 1)[-1] or 'unknown' -def register_build_info(env, project_dir): - # The build time changes on every build. Defining it globally would change - # every compile command and force a full rebuild, so scope these defines to - # the one small file that exposes them. +def register_build_info(env, project_dir, scoped_defines): + # The build time changes on every build, and the commit, dirty flag and + # (in test builds) the version change on every commit. Defining them + # globally would change every compile command and force a full rebuild, + # so scope them to the one small file that exposes them. branch = run_git_value( project_dir, ['rev-parse', '--abbrev-ref', 'HEAD'], 'branch' ) @@ -139,6 +140,7 @@ def register_build_info(env, project_dir): ('CROSSINK_GIT_BRANCH_SHORT', f'\\"{short_branch_label(branch)}\\"'), ('CROSSINK_BUILD_NUMBER', f'\\"{get_build_number(project_dir)}\\"'), ('CROSSINK_BUILD_TIME', f'\\"{build_time}\\"'), + *scoped_defines, ] def add_build_info_defines(node_env, node): @@ -220,14 +222,13 @@ def inject_version(env): # Keep build provenance separate from CROSSINK_VERSION: production versions # intentionally omit the source revision, while diagnostics need the base # commit and whether the compiled tree had tracked modifications. - env.Append(CPPDEFINES=[ + # Values that change per commit go only to src/util/BuildInfo.cpp (see + # register_build_info); stable values stay global. + scoped = [ ('CROSSINK_GIT_SHA', f'\\"{get_git_short_sha(project_dir)}\\"'), ('CROSSINK_GIT_DIRTY', f'\\"{get_git_dirty(project_dir)}\\"'), - ('CROSSINK_PIOENV', f'\\"{pioenv}\\"'), - ]) - - if hasattr(env, 'AddBuildMiddleware'): - register_build_info(env, project_dir) + ] + env.Append(CPPDEFINES=[('CROSSINK_PIOENV', f'\\"{pioenv}\\"')]) if pioenv in {'default', 'sticky', 'x4-pro', 'x4-pro-light-sleep', 'x4-classic'}: version_string = get_hardware_version(project_dir, pioenv) @@ -237,15 +238,15 @@ def inject_version(env): print(f'CrossInk production build version: {version_string}') else: print(f'CrossInk build version: {version_string}') - env.Append(CPPDEFINES=[('CROSSINK_VERSION', f'\\"{version_string}\\"')]) + scoped.append(('CROSSINK_VERSION', f'\\"{version_string}\\"')) elif pioenv == 'debug': branch = get_git_branch(project_dir) short_hash = get_git_short_hash(project_dir) ci_version = get_crossink_version(project_dir) suffix = f'-{branch}+{short_hash}' + scoped.append(('CROSSINK_VERSION', f'\\"{ci_version}{suffix}\\"')) env.Append(CPPDEFINES=[ - ('CROSSINK_VERSION', f'\\"{ci_version}{suffix}\\"'), ('CROSSINK_BUILD_ENV', '\\"debug\\"'), 'CROSSINK_SHOW_SLEEP_BUILD_INFO', ]) @@ -256,8 +257,8 @@ def inject_version(env): short_hash = get_git_short_hash(project_dir) ci_version = get_crossink_version(project_dir) suffix = f'-{branch}+{short_hash}' + scoped.append(('CROSSINK_VERSION', f'\\"{ci_version}{suffix}\\"')) env.Append(CPPDEFINES=[ - ('CROSSINK_VERSION', f'\\"{ci_version}{suffix}\\"'), ('CROSSINK_BUILD_ENV', '\\"debug\\"'), 'CROSSINK_SHOW_SLEEP_BUILD_INFO', ]) @@ -268,8 +269,8 @@ def inject_version(env): short_hash = get_git_short_hash(project_dir) ci_version = get_crossink_version(project_dir) suffix = f'-{branch}+{short_hash}' + scoped.append(('CROSSINK_VERSION', f'\\"{ci_version}{suffix}\\"')) env.Append(CPPDEFINES=[ - ('CROSSINK_VERSION', f'\\"{ci_version}{suffix}\\"'), ('CROSSINK_BUILD_ENV', '\\"debug\\"'), 'CROSSINK_SHOW_SLEEP_BUILD_INFO', ]) @@ -280,19 +281,20 @@ def inject_version(env): short_hash = get_git_short_hash(project_dir) ci_version = get_crossink_version(project_dir) suffix = f'-{branch}+{short_hash}' - env.Append(CPPDEFINES=[ - ('CROSSINK_VERSION', f'\\"{ci_version}{suffix}\\"'), - ]) + scoped.append(('CROSSINK_VERSION', f'\\"{ci_version}{suffix}\\"')) print(f'CrossInk test build version: {ci_version}{suffix}') elif pioenv == 'gh_release_rc': # CI passes CROSSINK_RC_HASH as an env var; locally we derive it from git. version_string = get_release_candidate_version(project_dir) - env.Append(CPPDEFINES=[ - ('CROSSINK_VERSION', f'\\"{version_string}\\"'), - ]) + scoped.append(('CROSSINK_VERSION', f'\\"{version_string}\\"')) print(f'CrossInk RC build version: {version_string}') + if hasattr(env, 'AddBuildMiddleware'): + register_build_info(env, project_dir, scoped) + else: + env.Append(CPPDEFINES=scoped) + # PlatformIO/SCons entry point — Import and env are SCons builtins injected at runtime. # When run directly with Python (e.g. for validation), a lightweight fake env is used diff --git a/src/activities/boot_sleep/BootActivity.cpp b/src/activities/boot_sleep/BootActivity.cpp index bcbcdbf6044..5df55ed5e7d 100644 --- a/src/activities/boot_sleep/BootActivity.cpp +++ b/src/activities/boot_sleep/BootActivity.cpp @@ -122,7 +122,7 @@ void drawDefaultBootLogo(const GfxRenderer& renderer) { renderer.drawImage(Logo120, (pageWidth - 120) / 2, (pageHeight - 120) / 2, 120, 120); renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 70, tr(STR_CROSSINK), true, EpdFontFamily::BOLD); renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, tr(STR_BOOTING)); - renderer.drawCenteredText(SMALL_FONT_ID, pageHeight - 30, CROSSINK_VERSION); + renderer.drawCenteredText(SMALL_FONT_ID, pageHeight - 30, AppVersion::version()); } } // namespace diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp index f523b627bea..a852a6f9e81 100644 --- a/src/activities/boot_sleep/SleepActivity.cpp +++ b/src/activities/boot_sleep/SleepActivity.cpp @@ -759,7 +759,7 @@ void SleepActivity::renderDefaultSleepScreen() const { } #ifdef CROSSINK_SHOW_SLEEP_BUILD_INFO - const std::string buildInfo = std::string(CROSSINK_BUILD_ENV) + " " + CROSSINK_VERSION; + const std::string buildInfo = std::string(CROSSINK_BUILD_ENV) + " " + AppVersion::version(); const std::string visibleBuildInfo = renderer.truncatedText(SMALL_FONT_ID, buildInfo.c_str(), pageWidth - sleepBuildInfoSideMargin * 2); renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 118, visibleBuildInfo.c_str(), lightSleepScreen); diff --git a/src/activities/settings/OtaUpdateActivity.cpp b/src/activities/settings/OtaUpdateActivity.cpp index b65c3a8c593..5a1d5c241e7 100644 --- a/src/activities/settings/OtaUpdateActivity.cpp +++ b/src/activities/settings/OtaUpdateActivity.cpp @@ -152,7 +152,7 @@ void OtaUpdateActivity::render(RenderLock&&) { } else if (state == WAITING_CONFIRMATION) { renderer.drawCenteredText(UI_10_FONT_ID, top, tr(STR_NEW_UPDATE), true, EpdFontFamily::BOLD); renderer.drawText(UI_10_FONT_ID, metrics.contentSidePadding, top + height + metrics.verticalSpacing, - (std::string(tr(STR_CURRENT_VERSION)) + CROSSINK_VERSION).c_str()); + (std::string(tr(STR_CURRENT_VERSION)) + AppVersion::version()).c_str()); renderer.drawText(UI_10_FONT_ID, metrics.contentSidePadding, top + height * 2 + metrics.verticalSpacing * 2, (std::string(tr(STR_NEW_VERSION)) + updater.getLatestVersion()).c_str()); diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index f62b4a38570..32c6eceb9de 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -149,20 +149,18 @@ std::string formatCompactDuration(const uint32_t seconds) { return buf; } -constexpr const char* systemVersionLabel = "CrossInk " CROSSINK_VERSION; - // Space below the settings list for the System footer: the build details line // plus the version on one line, or two when it has to wrap. int systemVersionFooterReserve(const GfxRenderer& renderer, const int pageWidth, const ThemeMetrics& metrics) { const int maxWidth = pageWidth - systemVersionFooterSideMargin * 2; - const int versionLines = renderer.getTextWidth(SMALL_FONT_ID, systemVersionLabel) <= maxWidth ? 1 : 2; + const int versionLines = renderer.getTextWidth(SMALL_FONT_ID, AppVersion::versionLabel()) <= maxWidth ? 1 : 2; return metrics.verticalSpacing + systemVersionFooterBottomInset + versionLines * renderer.getLineHeight(SMALL_FONT_ID); } void drawSystemVersionFooter(const GfxRenderer& renderer, const int pageWidth, const int pageHeight, const ThemeMetrics& metrics) { - const std::string label = systemVersionLabel; + const std::string label = AppVersion::versionLabel(); const int maxWidth = pageWidth - systemVersionFooterSideMargin * 2; const int lineHeight = renderer.getLineHeight(SMALL_FONT_ID); const int detailsLineY = @@ -171,8 +169,8 @@ void drawSystemVersionFooter(const GfxRenderer& renderer, const int pageWidth, c // Short branch and commit; "*" marks uncommitted changes. char details[64]; - snprintf(details, sizeof(details), "%s %s%s", BuildInfo::shortBranch(), CROSSINK_GIT_SHA, - strcmp(CROSSINK_GIT_DIRTY, "1") == 0 ? "*" : ""); + snprintf(details, sizeof(details), "%s %s%s", BuildInfo::shortBranch(), AppVersion::gitSha(), + AppVersion::gitDirty() ? "*" : ""); drawCenteredTextLine(renderer, pageWidth, detailsLineY, renderer.truncatedText(SMALL_FONT_ID, details, maxWidth)); if (renderer.getTextWidth(SMALL_FONT_ID, label.c_str()) <= maxWidth) { diff --git a/src/main.cpp b/src/main.cpp index 132a2d300b5..25654c95717 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1366,7 +1366,7 @@ void setup() { (BoardConfig::isX4Pro() || CROSSINK_APP_DEVICE_X4CLASSIC) ? "DOWN" : "UP"); } - LOG_DBG("MAIN", "Starting CrossInk version " CROSSINK_VERSION); + LOG_DBG("MAIN", "Starting CrossInk version %s", AppVersion::version()); logMemoryStats("Boot"); // Resolve the single boot-presentation decision. Skipping the splash also diff --git a/src/network/CrossPointWebServer.cpp b/src/network/CrossPointWebServer.cpp index 001d6fe1e84..9e265636477 100644 --- a/src/network/CrossPointWebServer.cpp +++ b/src/network/CrossPointWebServer.cpp @@ -742,7 +742,7 @@ void CrossPointWebServer::handleStatus() const { const String ipAddr = apMode ? WiFi.softAPIP().toString() : WiFi.localIP().toString(); JsonDocument doc; - doc["version"] = CROSSINK_VERSION; + doc["version"] = AppVersion::version(); doc["ip"] = ipAddr; doc["mode"] = apMode ? "AP" : "STA"; doc["rssi"] = apMode ? 0 : WiFi.RSSI(); @@ -784,8 +784,8 @@ void CrossPointWebServer::handleStatus() const { // touches the SD card beyond the capacity cached at mount. JsonObject build = doc["build"].to(); build["env"] = CROSSINK_PIOENV; - build["gitSha"] = CROSSINK_GIT_SHA; - build["gitDirty"] = CROSSINK_GIT_DIRTY; + build["gitSha"] = AppVersion::gitSha(); + build["gitDirty"] = AppVersion::gitDirtyFlag(); build["gitBranch"] = BuildInfo::gitBranch(); build["buildNumber"] = BuildInfo::buildNumber(); build["buildTime"] = BuildInfo::buildTime(); diff --git a/src/network/HttpDownloader.cpp b/src/network/HttpDownloader.cpp index 5e61cb0e6d9..54716b6415c 100644 --- a/src/network/HttpDownloader.cpp +++ b/src/network/HttpDownloader.cpp @@ -108,7 +108,7 @@ struct Sink { void setRequestHeaders(esp_http_client_handle_t client, const std::string& username, const std::string& password, size_t resumeOffset, bool sendAuthorization) { - esp_http_client_set_header(client, "User-Agent", "CrossInk-ESP32-" CROSSINK_VERSION); + esp_http_client_set_header(client, "User-Agent", AppVersion::userAgent()); esp_http_client_set_header(client, "Connection", "close"); if (resumeOffset > 0) { char rangeHeader[40]; @@ -159,7 +159,7 @@ HttpDownloader::DownloadError runGetWolfSsl(const std::string& url, const std::s } // Replace SecureHttpClient's built-in User-Agent so strict servers receive // exactly one header while retaining CrossInk's device/version identity. - http.setUserAgent("CrossInk-ESP32-" CROSSINK_VERSION); + http.setUserAgent(AppVersion::userAgent()); if (sink.resumeOffset > 0) { char rangeHeader[40]; snprintf(rangeHeader, sizeof(rangeHeader), "bytes=%zu-", sink.resumeOffset); diff --git a/src/network/OtaUpdater.cpp b/src/network/OtaUpdater.cpp index fc255b7fddd..4b6b127a155 100644 --- a/src/network/OtaUpdater.cpp +++ b/src/network/OtaUpdater.cpp @@ -299,7 +299,7 @@ OtaUpdater::OtaUpdaterError OtaUpdater::checkForUpdate() { }; totalBytesReceived = 0; - LOG_DBG("OTA", "Checking for update (current: %s)", CROSSINK_VERSION); + LOG_DBG("OTA", "Checking for update (current: %s)", AppVersion::version()); esp_http_client_handle_t client_handle = esp_http_client_init(&client_config); if (!client_handle) { @@ -307,7 +307,7 @@ OtaUpdater::OtaUpdaterError OtaUpdater::checkForUpdate() { return INTERNAL_UPDATE_ERROR; } - esp_err = esp_http_client_set_header(client_handle, "User-Agent", "CrossInk-ESP32-" CROSSINK_VERSION); + esp_err = esp_http_client_set_header(client_handle, "User-Agent", AppVersion::userAgent()); if (esp_err != ESP_OK) { LOG_ERR("OTA", "esp_http_client_set_header Failed : %s", esp_err_to_name(esp_err)); esp_http_client_cleanup(client_handle); @@ -356,12 +356,12 @@ OtaUpdater::OtaUpdaterError OtaUpdater::checkForUpdate() { } bool OtaUpdater::isUpdateNewer() const { - if (!updateAvailable || latestVersion.empty() || latestVersion == CROSSINK_VERSION) { + if (!updateAvailable || latestVersion.empty() || latestVersion == AppVersion::version()) { return false; } - const int comparison = compareVersions(latestVersion.c_str(), CROSSINK_VERSION); - LOG_DBG("OTA", "Version comparison latest=%s current=%s result=%d", latestVersion.c_str(), CROSSINK_VERSION, + const int comparison = compareVersions(latestVersion.c_str(), AppVersion::version()); + LOG_DBG("OTA", "Version comparison latest=%s current=%s result=%d", latestVersion.c_str(), AppVersion::version(), comparison); return comparison > 0; } diff --git a/src/network/UsbSerialFileTransfer.cpp b/src/network/UsbSerialFileTransfer.cpp index 8e6168342c5..bed5c6e183f 100644 --- a/src/network/UsbSerialFileTransfer.cpp +++ b/src/network/UsbSerialFileTransfer.cpp @@ -1,5 +1,6 @@ #include "UsbSerialFileTransfer.h" +#include #include #include #include @@ -45,9 +46,6 @@ constexpr const char* HIDDEN_ITEMS[] = {"System Volume Information", "XTCache"}; #ifndef CROSSINK_FIRMWARE_DEVICE_TYPE #define CROSSINK_FIRMWARE_DEVICE_TYPE "unknown" #endif -#ifndef CROSSINK_VERSION -#define CROSSINK_VERSION "unknown" -#endif uint8_t commandMatchPos = 0; char lineBuffer[LINE_BUFFER_SIZE] = {}; @@ -308,7 +306,7 @@ bool removeRecursive(const char* path, size_t depth = 0) { void handleStatus() { char response[160]; snprintf(response, sizeof(response), "STATUS:protocol=1,device=%s,firmware=%s,free=%u,largest=%u\n", - CROSSINK_FIRMWARE_DEVICE_TYPE, CROSSINK_VERSION, ESP.getFreeHeap(), ESP.getMaxAllocHeap()); + CROSSINK_FIRMWARE_DEVICE_TYPE, AppVersion::version(), ESP.getFreeHeap(), ESP.getMaxAllocHeap()); writeLine(response); } diff --git a/src/util/BatteryDiagnosticLog.cpp b/src/util/BatteryDiagnosticLog.cpp index b4eac9c08a3..ad95305e249 100644 --- a/src/util/BatteryDiagnosticLog.cpp +++ b/src/util/BatteryDiagnosticLog.cpp @@ -180,12 +180,12 @@ void record(const Event event, const char* const deviceName, const char* const w formatOptional(soc, sizeof(soc), battery.socKnown, battery.soc); formatOptional(millivolts, sizeof(millivolts), battery.millivoltsKnown, battery.millivolts); formatOptional(charging, sizeof(charging), battery.chargingKnown, battery.charging ? 1u : 0u); - formatText(version, sizeof(version), CROSSINK_VERSION); + formatText(version, sizeof(version), AppVersion::version()); char row[ROW_LEN]; const int rowLen = snprintf(row, sizeof(row), "%s,%lu,%s,%s,%s,%s,%s,%s,%s,%s,%s\n", timestamp, static_cast(millis()), - soc, millivolts, charging, eventName(event), version, CROSSINK_GIT_SHA, CROSSINK_GIT_DIRTY, + soc, millivolts, charging, eventName(event), version, AppVersion::gitSha(), AppVersion::gitDirtyFlag(), deviceName ? deviceName : "", wakeRoute ? wakeRoute : ""); if (rowLen <= 0 || static_cast(rowLen) >= sizeof(row)) { file.close(); diff --git a/src/util/BuildInfo.cpp b/src/util/BuildInfo.cpp index 1e65c39c3b6..7e5946bf48d 100644 --- a/src/util/BuildInfo.cpp +++ b/src/util/BuildInfo.cpp @@ -1,5 +1,21 @@ #include "BuildInfo.h" +#include + +#include + +#ifndef CROSSINK_VERSION +#define CROSSINK_VERSION "dev" +#endif + +#ifndef CROSSINK_GIT_SHA +#define CROSSINK_GIT_SHA "unknown" +#endif + +#ifndef CROSSINK_GIT_DIRTY +#define CROSSINK_GIT_DIRTY "unknown" +#endif + #ifndef CROSSINK_GIT_BRANCH #define CROSSINK_GIT_BRANCH "unknown" #endif @@ -22,3 +38,12 @@ const char* shortBranch() { return CROSSINK_GIT_BRANCH_SHORT; } const char* buildNumber() { return CROSSINK_BUILD_NUMBER; } const char* buildTime() { return CROSSINK_BUILD_TIME; } } // namespace BuildInfo + +namespace AppVersion { +const char* version() { return CROSSINK_VERSION; } +const char* versionLabel() { return "CrossInk " CROSSINK_VERSION; } +const char* userAgent() { return "CrossInk-ESP32-" CROSSINK_VERSION; } +const char* gitSha() { return CROSSINK_GIT_SHA; } +bool gitDirty() { return std::strcmp(CROSSINK_GIT_DIRTY, "1") == 0; } +const char* gitDirtyFlag() { return CROSSINK_GIT_DIRTY; } +} // namespace AppVersion