yams/.build.yml
2025-09-17 12:55:10 -04:00

392 lines
15 KiB
YAML

# .build.yml
#
# SourceHut build manifest for YAMS (single Linux target).
# Key points:
# - Compute version from Git ref (GIT_REF) or release channel (RELEASE_CHANNEL) at runtime; no manifest expressions.
# - Persist computed version into ~/.buildenv for subsequent tasks.
# - Add static build tags for search/badges.
# - Gate packaging steps so they run for tag builds and for nightly/weekly channels.
# - Install Conan via pipx (or venv fallback), since Debian doesn't provide python3-conan.
image: debian/trixie
# Other possible images:
# image: ubuntu/22.04
# image: debian/stable
# Static metadata for search and badges (unrelated to Git tags)
tags:
- linux
- release
- cmake
- conan
- yams
- nightly
- weekly
environment:
# Define consistent directories.
BUILD_DIR: build/yams-release
STAGE_DIR: stage
# Optional: set this to "nightly" or "weekly" via scheduler/env to produce
# date/week-based releases on non-tag builds.
# RELEASE_CHANNEL:
packages:
# Build dependencies (Debian/apt)
- build-essential # gcc, make, etc.
- cmake
- ninja-build
- zip
- ccache
- dpkg-dev
- fakeroot
- rpm
- lld
- clang
- llvm
- python3
- python3-pip
- python3-venv
- pipx
- git
- libncurses-dev
- libtinfo-dev
- pkg-config
# Optional feature dependencies (enable async I/O, archive extraction, media metadata)
- liburing-dev # enables io_uring async path instead of fallback
- libarchive-dev # enables archive extraction features
- libtag1-dev # enables taglib (audio metadata) detection
- libsqlite3-dev # enables SQLite database support
- libssl-dev
- libcurl4-openssl-dev
- libsqlite3-dev
- libncurses-dev
- protobuf-compiler
- libprotobuf-dev
tasks:
- normalize_submodules: |
set -e
# On SourceHut, SSH-based submodule URLs fail. Rewrite to HTTPS before any submodule ops.
if [ -f .gitmodules ]; then
sed -i 's#git@github.com:#https://github.com/#g' .gitmodules || true
git submodule sync --recursive || true
fi
# Prevent automatic submodule recursion from causing failures
git config --global submodule.recurse false || true
# If submodules were initialized by the runner, deinit private ones to avoid fetch attempts
if git config -f .gitmodules --get-regexp '^submodule\..*\.url' >/dev/null 2>&1; then
while read -r name url; do
case "$url" in
*homebrew-yams*|*yams-ghidra-plugin*)
git submodule deinit -f "$(echo "$name" | sed -E 's/^submodule\.([^.]*)\.url/\1/')" || true ;;
esac
done < <(git config -f .gitmodules --get-regexp '^submodule\..*\.url')
fi
# Determine version from the Git ref and persist to ~/.buildenv for later tasks.
- compute_version: |
set -e
# Detect repository root (some runners check out into ./, others into ./yams)
if [ -d yams ] && [ -f yams/CMakeLists.txt ]; then
REPO_DIR="yams"
else
REPO_DIR="."
fi
cd "${REPO_DIR}"
CHANNEL="${RELEASE_CHANNEL:-}"
# Auto-channelize main branch builds as nightly when not tagged
if [[ -z "${CHANNEL}" && "${GIT_REF:-}" == "refs/heads/main" ]]; then
CHANNEL="nightly"
export RELEASE_CHANNEL="nightly"
# Persist for subsequent tasks which may source this
echo "RELEASE_CHANNEL=nightly" >> ~/.buildenv
fi
# Determine numeric base version from latest tag (strip leading v), fallback to 0.0.0
RAW_TAG_NUMERIC="$(git describe --tags --abbrev=0 2>/dev/null || echo 0.0.0)"
BASE_NUMERIC_VERSION="${RAW_TAG_NUMERIC#v}"
if [ "$CHANNEL" = "nightly" ]; then
CLEAN_VERSION_DISPLAY="nightly-$(date -u +%Y%m%d)"
CLEAN_VERSION_CMAKE="${BASE_NUMERIC_VERSION}"
elif [ "$CHANNEL" = "weekly" ]; then
CLEAN_VERSION_DISPLAY="weekly-$(date -u +%G)w$(date -u +%V)"
CLEAN_VERSION_CMAKE="${BASE_NUMERIC_VERSION}"
else
# Prefer tag-triggered builds; otherwise use last tag as numeric
if [[ "${GIT_REF:-}" =~ ^refs/tags/ ]]; then
RAW_TAG="${GIT_REF#refs/tags/}"
CLEAN_VERSION_DISPLAY="${RAW_TAG#v}"
CLEAN_VERSION_CMAKE="${RAW_TAG#v}"
else
CLEAN_VERSION_DISPLAY="${BASE_NUMERIC_VERSION}"
CLEAN_VERSION_CMAKE="${BASE_NUMERIC_VERSION}"
fi
fi
# Persist for subsequent tasks
echo "YAMS_VERSION=${CLEAN_VERSION_DISPLAY}" >> ~/.buildenv
echo "YAMS_VERSION_CMAKE=${CLEAN_VERSION_CMAKE}" >> ~/.buildenv
echo "Computed YAMS_VERSION(display)=${CLEAN_VERSION_DISPLAY}, YAMS_VERSION_CMAKE=${CLEAN_VERSION_CMAKE} (GIT_REF=${GIT_REF:-n/a}, RELEASE_CHANNEL=${CHANNEL:-n/a})"
- setup: |
set -e
# Ensure pipx/venv/pip path is prepared
export PATH="${HOME}/.local/bin:${PATH}"
# Configure git identity and signing key (requested)
git config --global user.name "Trevon" || true
git config --global user.email "git@trevon.dev" || true
git config --global user.signingkey "C1E8284DAE319BA313FCB40038686EA797D7A8A7" || true
# Note: actual signed commits/tags require the private key in the agent; we only set identity here.
# Configure ccache (optional acceleration)
export CCACHE_DIR="${HOME}/.cache/ccache"
mkdir -p "${CCACHE_DIR}"
echo "CCACHE_DIR=${CCACHE_DIR}" >> ~/.buildenv
if command -v ccache >/dev/null 2>&1; then
ccache --set-config=max_size=2G || true
ccache --zero-stats || true
ccache -s || true
fi
# Install Conan via pipx when available; otherwise fallback to user pip or an isolated venv
if ! command -v conan >/dev/null 2>&1; then
if command -v pipx >/dev/null 2>&1; then
pipx install 'conan<3' --pip-args='--upgrade' || true
fi
fi
if ! command -v conan >/dev/null 2>&1; then
python3 -m pip install --user --upgrade 'conan<3' || true
hash -r
fi
if ! command -v conan >/dev/null 2>&1; then
python3 -m venv /opt/conan
. /opt/conan/bin/activate
python -m pip install --upgrade pip setuptools wheel
python -m pip install 'conan<3'
export PATH="/opt/conan/bin:${PATH}"
hash -r
fi
# Persist Conan PATH and CPU count for subsequent tasks and fail fast if missing
if command -v conan >/dev/null 2>&1; then
CONAN_DIR="$(dirname "$(command -v conan)")"
echo "PATH=${CONAN_DIR}:\$PATH" >> ~/.buildenv
else
echo "Conan is not available after installation attempts" >&2
exit 1
fi
CONAN_CPU_COUNT="${CONAN_CPU_COUNT:-$(nproc || getconf _NPROCESSORS_ONLN || echo 2)}"
echo "CONAN_CPU_COUNT=${CONAN_CPU_COUNT}" >> ~/.buildenv
# Detect and move into repository root
if [ -d yams ] && [ -f yams/CMakeLists.txt ]; then
REPO_DIR="yams"
else
REPO_DIR="."
fi
cd "${REPO_DIR}"
# Configure the Conan profile.
# Prefer clang to avoid GCC 15 regressions with onnxruntime
export CC=clang
export CXX=clang++
echo "CC=clang" >> ~/.buildenv
echo "CXX=clang++" >> ~/.buildenv
conan --version
conan profile detect --force || true
# Ensure default profile uses clang and libstdc++11
conan profile update settings.compiler=clang default || true
conan profile update settings.compiler.libcxx=libstdc++11 default || true
# Keep C++20
sed -i 's/^settings\.compiler\.cppstd=.*/settings.compiler.cppstd=20/' ~/.conan2/profiles/default || true
echo "Conan profile configured (clang):"
conan profile show -pr default || conan profile show || true
- yams_semver_guard: |
set -e
if [ -d yams ] && [ -f yams/CMakeLists.txt ]; then
REPO_DIR="yams"
else
REPO_DIR="."
fi
cd "${REPO_DIR}"
echo "Running YAMS semver guard..."
python3 scripts/ci/check_yams_semver.py
- build: |
set -e
export PATH="${HOME}/.local/bin:/opt/conan/bin:${PATH}"
if ! command -v conan >/dev/null 2>&1; then
echo "conan not found in PATH; aborting build" >&2
exit 1
fi
if [ -d yams ] && [ -f yams/CMakeLists.txt ]; then
REPO_DIR="yams"
else
REPO_DIR="."
fi
cd "${REPO_DIR}"
export YAMS_DISABLE_MODEL_PRELOAD=1
CLEAN_VERSION_CMAKE="${YAMS_VERSION_CMAKE}"
echo "Starting build (CMake version ${CLEAN_VERSION_CMAKE})"
# Fetch dependencies and generate CMake toolchain/deps.
# Use clang toolchain to avoid GCC 15 onnxruntime 1.18 build issues.
conan install . \
-of "${BUILD_DIR}" \
-pr:h=./conan/profiles/host-linux-clang \
-s build_type=Release \
-c tools.build:jobs="${CONAN_CPU_COUNT}" \
--build=missing
# Configure with CMake (uses preset toolchain path under ${BUILD_DIR}).
cmake --preset yams-release \
-DYAMS_VERSION="${CLEAN_VERSION_CMAKE}" \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DCURSES_NEED_NCURSES=ON \
-DCURSES_NEED_WIDE=ON
# Build the project.
cmake --build --preset yams-release
# Install the built artifacts into the staging directory.
mkdir -p "${STAGE_DIR}"
cmake --install "${BUILD_DIR}" --config Release --prefix "${STAGE_DIR}"
# Show ccache stats after build (if available)
if command -v ccache >/dev/null 2>&1; then
ccache -s || true
fi
- package: |
set -e
# Package on tag builds or when explicitly channelized as nightly/weekly
# Auto-channelize main branch builds as nightly when not tagged
if [[ -z "${RELEASE_CHANNEL:-}" && "${GIT_REF:-}" == "refs/heads/main" ]]; then
export RELEASE_CHANNEL=nightly
fi
if [[ "${GIT_REF:-}" != refs/tags/* && "${RELEASE_CHANNEL:-}" != "nightly" && "${RELEASE_CHANNEL:-}" != "weekly" ]]; then
echo "Skipping packaging (not a tag build and no release channel): GIT_REF=${GIT_REF:-n/a} RELEASE_CHANNEL=${RELEASE_CHANNEL:-n/a}"
exit 0
fi
if [ -d yams ] && [ -f yams/CMakeLists.txt ]; then
REPO_DIR="yams"
else
REPO_DIR="."
fi
cd "${REPO_DIR}"
CLEAN_VERSION="${YAMS_VERSION}"
echo "Packaging the release..."
cd "${STAGE_DIR}"
tar -czf "../yams-${CLEAN_VERSION}-linux-x86_64.tar.gz" .
echo "Created asset: yams-${CLEAN_VERSION}-linux-x86_64.tar.gz"
# Also publish a raw stage archive for debugging/inspection
tar -czf "../yams-${CLEAN_VERSION}-stage.tar.gz" .
if command -v zip >/dev/null 2>&1; then
zip -qry "../yams-${CLEAN_VERSION}-stage.zip" .
fi
- package_cpack: |
set -e
# Package on tag builds or when explicitly channelized as nightly/weekly
# Auto-channelize main branch builds as nightly when not tagged
if [[ -z "${RELEASE_CHANNEL:-}" && "${GIT_REF:-}" == "refs/heads/main" ]]; then
export RELEASE_CHANNEL=nightly
fi
if [[ "${GIT_REF:-}" != refs/tags/* && "${RELEASE_CHANNEL:-}" != "nightly" && "${RELEASE_CHANNEL:-}" != "weekly" ]]; then
echo "Skipping CPack packaging (not a tag build and no release channel): GIT_REF=${GIT_REF:-n/a} RELEASE_CHANNEL=${RELEASE_CHANNEL:-n/a}"
exit 0
fi
if [ -d yams ] && [ -f yams/CMakeLists.txt ]; then
REPO_DIR="yams"
else
REPO_DIR="."
fi
cd "${REPO_DIR}"
CLEAN_VERSION="${YAMS_VERSION_CMAKE}"
echo "Packaging with CPack..."
cd "${BUILD_DIR}"
# Generate DEB package
cpack -G DEB \
-D CPACK_PACKAGE_VERSION="${CLEAN_VERSION}" \
-D CPACK_PACKAGE_CONTACT="Trevon <git@trevon.dev>" \
-D CPACK_DEBIAN_PACKAGE_MAINTAINER="Trevon <git@trevon.dev>"
# Generate RPM package if rpmbuild is available; otherwise skip
if command -v rpmbuild >/dev/null 2>&1; then
cpack -G RPM -D CPACK_PACKAGE_VERSION="${CLEAN_VERSION}" || {
echo "CPack RPM failed; continuing without RPM artifact" >&2
}
else
echo "rpmbuild not found; skipping RPM packaging on this image"
fi
- collect_artifacts: |
set -e
ART_DIR="artifacts"
rm -rf "${ART_DIR}" && mkdir -p "${ART_DIR}"
# Determine repository root
if [ -d yams ] && [ -f yams/CMakeLists.txt ]; then
REPO_DIR="yams"
else
REPO_DIR="."
fi
# Publish a minimal, size-safe artifact set so SourceHut can retain them reliably.
# Policy:
# - Tag builds: prefer distro packages (.deb, .rpm) if available; fall back to the tarball.
# - Nightly/weekly: publish only the primary tarball (avoid duplicates and stage archives).
shopt -s nullglob || true
if [[ "${GIT_REF:-}" == refs/tags/* ]]; then
# Prefer distro packages on tagged releases
if compgen -G "${REPO_DIR}/${BUILD_DIR}/*.deb" > /dev/null; then
for f in "${REPO_DIR}/${BUILD_DIR}"/*.deb; do cp -v "$f" "${ART_DIR}/"; done
fi
if compgen -G "${REPO_DIR}/${BUILD_DIR}/*.rpm" > /dev/null; then
for f in "${REPO_DIR}/${BUILD_DIR}"/*.rpm; do cp -v "$f" "${ART_DIR}/"; done
fi
# Fallback to tarball when packages are missing
if ! compgen -G "${ART_DIR}/*" > /dev/null; then
for f in "${REPO_DIR}"/yams-*-linux-x86_64.tar.gz; do cp -v "$f" "${ART_DIR}/"; done
fi
else
# Nightly/weekly: publish only the primary tarball
for f in "${REPO_DIR}"/yams-*-linux-x86_64.tar.gz; do cp -v "$f" "${ART_DIR}/"; done
fi
# Provide stable filenames expected by SourceHut (no globbing in artifacts list)
if compgen -G "${ART_DIR}/*.tar.gz" > /dev/null; then
TARBALL=$(ls -1 "${ART_DIR}"/*.tar.gz | head -1)
cp -v "$TARBALL" "${ART_DIR}/yams.tar.gz"
fi
if compgen -G "${ART_DIR}/*.deb" > /dev/null; then
DEB=$(ls -1 "${ART_DIR}"/*.deb | head -1)
cp -v "$DEB" "${ART_DIR}/yams.deb"
fi
if compgen -G "${ART_DIR}/*.rpm" > /dev/null; then
RPM=$(ls -1 "${ART_DIR}"/*.rpm | head -1)
cp -v "$RPM" "${ART_DIR}/yams.rpm"
fi
echo "Collected artifacts:" && ls -l "${ART_DIR}" || true
# Fallback for CI branches: if no artifacts were collected (packaging gated),
# compress the staged install to provide a basic artifact for inspection.
if [ "$(find "${ART_DIR}" -type f | wc -l | tr -d ' ')" -eq 0 ]; then
if [ -d "${REPO_DIR}/${STAGE_DIR}" ]; then
echo "No release artifacts; creating CI tarball from ${REPO_DIR}/${STAGE_DIR}" >&2
tar -C "${REPO_DIR}/${STAGE_DIR}" -czf "${ART_DIR}/yams.tar.gz" .
ls -l "${ART_DIR}" || true
else
echo "WARNING: Stage dir ${REPO_DIR}/${STAGE_DIR} missing; no artifacts to publish" >&2
fi
fi
artifacts:
# SourceHut interprets artifact names literally (no shell globs). Provide
# stable filenames in collect_artifacts.
- "artifacts/yams.tar.gz"
- "artifacts/yams.deb"
- "artifacts/yams.rpm"