Skip to content

Commit 73a1bf7

Browse files
committed
version: derive in cmake, not docker; filter non-release tags
Addresses review on #505. Why docker-only was wrong: the version was a docker --build-arg, so only published images carried a real one — local builds, release tarballs and the artifact tarball all reported the static project version (0.1.0). Derivation now lives in cmake/MoqxVersion.cmake with an explicit precedence chain: 1. -DMOQX_VERSION_STRING explicit (release workflow passes the tag) 2. <src>/VERSION stamped by CI; docker + source tarballs have no .git, so this is how they resolve 3. git describe any clone, including local dev builds 4. v${PROJECT_VERSION} last resort Tag filtering is load-bearing. The repo carries moving non-version tags (snapshot-latest, build-*, archive/*); an unfiltered describe returns "snapshot-latest" as the version of every image built from main — demonstrated on 4969f8c, where naive describe gives snapshot-latest and --match 'v[0-9]*' gives the sha. The v prefix is kept: the string is a git identifier and round-trips into git checkout / gh release view. Generalization payoff, all verified in a container build: - moqx --version -> moqx version v0.2.1-2-g59fe6a9b - admin /info -> {"service":"moqx","version":"v0.2.1-2-g59fe6a9b"} - startup log banner -> first line attributes the log stream to a build - /usr/local/VERSION -> identify an image without executing it - install tree VERSION -> release + snapshot tarballs carry the id The version is now an interface target (moqx_version) carrying a generated header, so any target can use it — not a private compile definition on moqx_core.
1 parent 59fe6a9 commit 73a1bf7

9 files changed

Lines changed: 177 additions & 19 deletions

File tree

.github/workflows/ci-main.yml

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -382,18 +382,26 @@ jobs:
382382
fi
383383
echo "short=$SHORT" >> "$GITHUB_OUTPUT"
384384
echo "rolling=${LABEL}-latest" >> "$GITHUB_OUTPUT"
385-
# Version baked into the binary's /info (relay dashboard badge):
386-
# exact tag on release commits, tag-distance-sha on snapshots.
387-
# Leading "v" stripped — /info reports bare semver-style ("0.2.1",
388-
# "0.2.1-14-gabc1234"); display layers add their own "v".
389-
echo "describe=$(git describe --tags --always | sed 's/^v//')" >> "$GITHUB_OUTPUT"
385+
386+
- name: Stamp build version
387+
run: |
388+
# Single source of truth for the version baked into the binary
389+
# (/info, --version, log banner) and shipped in artifacts. The
390+
# docker build context excludes .git, so cmake reads this file —
391+
# see cmake/MoqxVersion.cmake for the full resolution order.
392+
#
393+
# --match 'v[0-9]*' is load-bearing: the repo also carries moving,
394+
# non-version tags (snapshot-latest, build-*, archive/*) and an
395+
# unfiltered describe returns e.g. "snapshot-latest" as the version
396+
# of every image built from main.
397+
git describe --tags --match 'v[0-9]*' --always > VERSION
398+
echo "==> build version: $(cat VERSION)"
390399
391400
- name: Build Docker image
392401
run: |
393402
IMAGE="ghcr.io/${{ github.repository }}"
394403
ARCH="${{ matrix.arch }}"
395404
docker build -f docker/Dockerfile \
396-
--build-arg MOQX_VERSION_STRING="${{ steps.tags.outputs.describe }}" \
397405
-t "${IMAGE}:${{ steps.tags.outputs.short }}-${ARCH}" \
398406
-t "${IMAGE}:${{ steps.tags.outputs.rolling }}-${ARCH}" \
399407
.

.github/workflows/version-release.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,8 +324,13 @@ jobs:
324324
# path. Combined with the bundle step below this produces a
325325
# self-contained tarball that runs on noble / RHEL 9 / AL2023
326326
# without needing distro-specific packages installed.
327+
# Stamp the exact release tag rather than deriving it: this job's
328+
# checkout is shallow (no tags) and the tag may not exist yet when
329+
# the build runs, so `git describe` would yield a bare sha. See
330+
# cmake/MoqxVersion.cmake.
327331
cmake -S . -B _build -G Ninja \
328332
-DCMAKE_BUILD_TYPE=Release \
333+
-DMOQX_VERSION_STRING="${{ needs.validate.outputs.tag }}" \
329334
-DCMAKE_PREFIX_PATH="$(cat .scratch/cmake_prefix_path.txt)" \
330335
-DCMAKE_MODULE_PATH="${GITHUB_WORKSPACE}/cmake;${GITHUB_WORKSPACE}/deps/moxygen/build/fbcode_builder/CMake" \
331336
-DCMAKE_C_FLAGS="${{ matrix.cxx_flags }}" \

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,6 @@ docker/cloudflare.ini
3939
# gh-pages dashboard (lives on gh-pages branch, not main)
4040
/gh-pages/
4141
docker/grafana/provisioning/dashboards/archive/
42+
43+
# Build-version stamp written by CI (cmake/MoqxVersion.cmake reads it).
44+
/VERSION

CMakeLists.txt

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ if(NOT CMAKE_BUILD_TYPE)
1111
set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "" FORCE)
1212
endif()
1313

14+
# Build identifier (git describe / VERSION file / -DMOQX_VERSION_STRING).
15+
# Defines the moqx_version interface target carrying the generated
16+
# moqx/Version.h, and writes ${CMAKE_BINARY_DIR}/VERSION for packaging.
17+
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/MoqxVersion.cmake)
18+
1419
option(MOQX_BUILD_TESTS "Build tests" ON)
1520
option(MOQX_BUILD_BENCHMARKS "Build benchmarks" OFF)
1621
option(MOQX_ENABLE_SANITIZERS "Enable ASAN/UBSAN (non-Release)" OFF)
@@ -202,14 +207,10 @@ target_link_libraries(moqx_core PUBLIC
202207
)
203208

204209
target_compile_options(moqx_core PRIVATE -Wall -Wextra -Wpedantic)
205-
# Version reported by /info (and the relay dashboard badge). CI passes
206-
# `git describe --tags` here: the bare tag on release commits (v0.2.1),
207-
# tag-distance-sha on snapshots (v0.2.1-14-g2ed29c8). Local builds fall
208-
# back to the static project version.
209-
if(NOT MOQX_VERSION_STRING)
210-
set(MOQX_VERSION_STRING "${PROJECT_VERSION}")
211-
endif()
212-
target_compile_definitions(moqx_core PRIVATE MOQX_VERSION="${MOQX_VERSION_STRING}")
210+
# Build identifier — see cmake/MoqxVersion.cmake. PUBLIC so consumers (the
211+
# moqx binary, tests, tools) can include moqx/Version.h too: the version is
212+
# no longer a private compile definition on this one library.
213+
target_link_libraries(moqx_core PUBLIC moqx_version)
213214

214215
# --- Config types (resolved Config structs, header-only, no rfl dependency) ---
215216
# Library consumers that only need the Config API link this.
@@ -326,3 +327,8 @@ endif()
326327
include(${PROJECT_SOURCE_DIR}/cmake/Lint.cmake)
327328

328329
install(TARGETS moqx moqx-issuer moqx_core moqx_config_loader)
330+
331+
# Ship the build identifier alongside the binaries so an unpacked release or
332+
# snapshot tarball can be identified with `cat VERSION` — no git history, and
333+
# no need to run the binary (wrong arch, no libs, or a container layer).
334+
install(FILES ${CMAKE_BINARY_DIR}/VERSION DESTINATION .)

cmake/MoqxVersion.cmake

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Version derivation for moqx.
2+
#
3+
# Produces MOQX_VERSION_STRING: one canonical identifier consumed by every
4+
# binary (admin /info, --version, the startup log banner) and embedded in
5+
# build artifacts as a VERSION file, so a tarball or container can be
6+
# identified without its git history.
7+
#
8+
# Format is git-describe output with the "v" tag prefix retained, so the
9+
# string round-trips into `git checkout` / `gh release view`:
10+
#
11+
# v0.2.1 exact release tag
12+
# v0.2.1-14-gabc1234 14 commits past v0.2.1
13+
# v0.2.1-14-gabc1234-dirty ...with uncommitted changes (local builds)
14+
# abc1234 no reachable v* tag (shallow clone, fork)
15+
#
16+
# Resolution order, first hit wins:
17+
#
18+
# 1. -DMOQX_VERSION_STRING=... explicit override (CI, docker --build-arg)
19+
# 2. <source root>/VERSION source tarballs and the docker build context
20+
# carry no .git; CI writes this file for them
21+
# 3. git describe any real clone, including local dev builds
22+
# 4. v${PROJECT_VERSION} last resort, e.g. v0.1.0
23+
#
24+
# Only v-prefixed numeric tags are considered. The repo also carries moving
25+
# tags (snapshot-latest, build-*, archive/*) that are NOT versions; an
26+
# unfiltered `git describe` happily returns "snapshot-latest" and poisons
27+
# the version of every artifact built from main.
28+
#
29+
# Derivation happens at configure time. A dev who commits without re-running
30+
# cmake keeps the previous string until the next configure; CI configures
31+
# fresh every run, so published artifacts are always exact.
32+
33+
function(_moqx_version_from_git out_var)
34+
set(${out_var} "" PARENT_SCOPE)
35+
36+
find_package(Git QUIET)
37+
if(NOT GIT_FOUND)
38+
return()
39+
endif()
40+
41+
# A worktree's .git is a file, not a directory — EXISTS covers both.
42+
if(NOT EXISTS "${PROJECT_SOURCE_DIR}/.git")
43+
return()
44+
endif()
45+
46+
execute_process(
47+
COMMAND "${GIT_EXECUTABLE}" describe --tags --match "v[0-9]*" --always --dirty
48+
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
49+
OUTPUT_VARIABLE _described
50+
OUTPUT_STRIP_TRAILING_WHITESPACE
51+
ERROR_QUIET
52+
RESULT_VARIABLE _rc
53+
)
54+
if(_rc EQUAL 0 AND _described)
55+
set(${out_var} "${_described}" PARENT_SCOPE)
56+
endif()
57+
endfunction()
58+
59+
if(MOQX_VERSION_STRING)
60+
set(_moqx_version_source "explicit -DMOQX_VERSION_STRING")
61+
elseif(EXISTS "${PROJECT_SOURCE_DIR}/VERSION")
62+
file(READ "${PROJECT_SOURCE_DIR}/VERSION" MOQX_VERSION_STRING)
63+
string(STRIP "${MOQX_VERSION_STRING}" MOQX_VERSION_STRING)
64+
set(_moqx_version_source "VERSION file")
65+
else()
66+
_moqx_version_from_git(MOQX_VERSION_STRING)
67+
if(MOQX_VERSION_STRING)
68+
set(_moqx_version_source "git describe")
69+
else()
70+
set(MOQX_VERSION_STRING "v${PROJECT_VERSION}")
71+
set(_moqx_version_source "project() fallback")
72+
endif()
73+
endif()
74+
75+
message(STATUS "moqx version: ${MOQX_VERSION_STRING} (from ${_moqx_version_source})")
76+
77+
# Generated header — any target may link moqx_version to use it, so the
78+
# version is not welded to one library the way a target_compile_definitions
79+
# would be.
80+
set(MOQX_VERSION_HEADER_DIR "${CMAKE_BINARY_DIR}/generated")
81+
configure_file(
82+
"${PROJECT_SOURCE_DIR}/cmake/Version.h.in"
83+
"${MOQX_VERSION_HEADER_DIR}/moqx/Version.h"
84+
@ONLY
85+
)
86+
87+
add_library(moqx_version INTERFACE)
88+
target_include_directories(moqx_version INTERFACE "${MOQX_VERSION_HEADER_DIR}")
89+
90+
# Artifact-side copy: `cat VERSION` identifies an unpacked tarball or an
91+
# image layer with no binary to run and no git history to consult.
92+
file(WRITE "${CMAKE_BINARY_DIR}/VERSION" "${MOQX_VERSION_STRING}\n")

cmake/Version.h.in

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright (c) OpenMOQ contributors.
3+
* This source code is licensed under the Apache 2.0 license found in the
4+
* LICENSE file in the root directory of this source tree.
5+
*
6+
* GENERATED FILE — do not edit. Source template: cmake/Version.h.in,
7+
* derivation: cmake/MoqxVersion.cmake.
8+
*/
9+
10+
#pragma once
11+
12+
#include <string_view>
13+
14+
// Macro form: usable in string-literal concatenation (e.g. the admin /info
15+
// JSON body). Prefer openmoq::moqx::kVersion in new code.
16+
#define MOQX_VERSION "@MOQX_VERSION_STRING@"
17+
18+
namespace openmoq::moqx {
19+
20+
// Canonical build identifier: a git describe string with the "v" tag prefix
21+
// retained (v0.2.1, v0.2.1-14-gabc1234, v0.2.1-14-gabc1234-dirty), or a bare
22+
// commit sha when no v* tag is reachable.
23+
inline constexpr std::string_view kVersion{MOQX_VERSION};
24+
25+
} // namespace openmoq::moqx

docker/Dockerfile

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,21 @@ WORKDIR /src
1313
# Moxygen tarball (pre-extracted bookworm build)
1414
COPY .docker-deps/moxygen /opt/moxygen
1515

16-
# MoQX source — only what cmake needs
17-
COPY CMakeLists.txt CMakePresets.json ./
16+
# MoQX source — only what cmake needs.
17+
# VERSIO[N] is an optional-copy glob: CI stamps a VERSION file into the build
18+
# context (the context excludes .git, so cmake can't run git describe here),
19+
# but a bare `docker build` has none and must still work. The glob matches
20+
# nothing in that case, and the COPY still succeeds because CMakeLists.txt
21+
# does — a plain `COPY VERSION` would hard-fail instead.
22+
COPY CMakeLists.txt CMakePresets.json VERSIO[N] ./
1823
COPY cmake/ cmake/
1924
COPY src/ src/
2025
COPY deps/catapult/ deps/catapult/
2126
COPY deps/moxygen/build/fbcode_builder/CMake deps/moxygen/build/fbcode_builder/CMake
2227

23-
# Version string baked into /info — CI passes `git describe --tags` (the
24-
# build context has no .git, so it can't be derived here). Empty means the
25-
# CMake fallback (static project version) applies.
28+
# Explicit override for one-off builds; normally empty, in which case cmake
29+
# falls through to the VERSION file copied above (then to the static project
30+
# version if that is absent too). See cmake/MoqxVersion.cmake.
2631
ARG MOQX_VERSION_STRING=""
2732

2833
RUN cmake -S . -B _build --preset default \
@@ -44,6 +49,10 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
4449

4550
COPY --from=builder /install/bin/moqx /usr/local/bin/moqx
4651
COPY --from=builder /install/bin/moqx-issuer /usr/local/bin/moqx-issuer
52+
# Build identifier as a plain file, so an image can be identified without
53+
# executing it — inspecting a layer, or a foreign-arch image that cannot run
54+
# here. `docker run --rm <image> moqx --version` reports the same string.
55+
COPY --from=builder /install/VERSION /usr/local/VERSION
4756
COPY docker/entrypoint.sh /usr/local/bin/entrypoint.sh
4857
COPY docker/config.docker.yaml /usr/local/share/moqx/config.docker.yaml
4958
RUN chmod +x /usr/local/bin/entrypoint.sh

src/admin/BuiltinRoutes.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <proxygen/lib/http/HTTPMessage.h>
1212

1313
#include "admin/AdminServer.h"
14+
#include "moqx/Version.h"
1415

1516
namespace openmoq::moqx::admin {
1617

src/main.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "bpf/QuicReuseportSteering.h"
1616
#include "config/loader/ConfigInit.h"
1717
#include "logging/LogSetup.h"
18+
#include "moqx/Version.h"
1819
#include "stats/StatsRegistry.h"
1920

2021
#include <csignal>
@@ -76,6 +77,10 @@ int main(int argc, char* argv[]) {
7677
" serve Start the relay (default)\n" +
7778
cfg::configSubcommandUsage() + "\nUsage: moqx [subcommand] --config <path>"
7879
);
80+
// Powers `moqx --version` (gflags handles the flag inside folly::Init).
81+
// Works with no config file present, so an image or tarball can be
82+
// identified without deploying it.
83+
google::SetVersionString(MOQX_VERSION);
7984
// MOQX_LOGGING is the moqx-namespaced alias for folly's own FOLLY_LOGGING env
8085
// var (folly::Init reads FOLLY_LOGGING). Promote it here — before folly::Init
8186
// — so the knob works for any launch method (docker, systemd, bare metal)
@@ -89,6 +94,10 @@ int main(int argc, char* argv[]) {
8994
combineLoggingArgs(argc, argv);
9095
folly::Init init(&argc, &argv, true);
9196

97+
// First line in every relay's log: attributes the whole log stream to an
98+
// exact build (see cmake/MoqxVersion.cmake for the identifier's format).
99+
XLOG(INFO) << "moqx " << kVersion << " starting";
100+
92101
std::string_view subcommand = kServeCommand;
93102
if (argc > 1) {
94103
subcommand = argv[1];

0 commit comments

Comments
 (0)