From 5851f3a4fd13a0e638555d818ce894b8e0564da2 Mon Sep 17 00:00:00 2001 From: mr-forust Date: Sat, 25 Jul 2026 19:45:50 +0200 Subject: [PATCH] ci: add versioned release pipeline --- .gitea/workflows/ci.yaml | 52 +++++++++++++ .gitea/workflows/release.yaml | 139 ++++++++++++++++++++++++++++++++++ .yamllint | 18 +++++ CHANGELOG.md | 18 +++++ CMakeLists.txt | 10 ++- VERSION | 1 + scripts/release.sh | 34 +++++++++ src/main.cpp | 3 +- 8 files changed, 273 insertions(+), 2 deletions(-) create mode 100644 .gitea/workflows/ci.yaml create mode 100644 .gitea/workflows/release.yaml create mode 100644 .yamllint create mode 100644 CHANGELOG.md create mode 100644 VERSION create mode 100755 scripts/release.sh diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml new file mode 100644 index 0000000..4e598fb --- /dev/null +++ b/.gitea/workflows/ci.yaml @@ -0,0 +1,52 @@ +name: ci + +"on": + push: + branches: + - main + pull_request: + workflow_dispatch: + +jobs: + build-linux-amd64: + runs-on: [self-hosted, linux, arch, homelab] + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Configure release build + shell: bash + run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release + + - name: Build + shell: bash + run: cmake --build build --parallel + + - name: Smoke test GUI + shell: bash + run: | + set +e + QT_QPA_PLATFORM=offscreen timeout 3s ./build/searchmyfiles + status="$?" + test "$status" = 0 || test "$status" = 124 + + - name: Upload Linux binary + uses: actions/upload-artifact@v4 + with: + name: folderscope-linux-amd64 + path: build/searchmyfiles + + lint-yaml: + runs-on: [self-hosted, linux, arch, homelab] + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Lint workflow files + shell: bash + run: | + docker run --rm \ + -v "$PWD:/work" \ + -w /work \ + cytopia/yamllint:latest \ + -c .yamllint . diff --git a/.gitea/workflows/release.yaml b/.gitea/workflows/release.yaml new file mode 100644 index 0000000..4efda4a --- /dev/null +++ b/.gitea/workflows/release.yaml @@ -0,0 +1,139 @@ +name: release + +"on": + push: + tags: + - "v[0-9]*.[0-9]*.[0-9]*" + workflow_dispatch: + +permissions: + contents: write + +jobs: + verify: + runs-on: [self-hosted, linux, arch, homelab] + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Verify tag matches VERSION + shell: bash + run: | + tag="${GITHUB_REF##*/}" + test "${tag#v}" = "$(tr -d '[:space:]' < VERSION)" + + - name: Configure release build + shell: bash + run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release + + - name: Build + shell: bash + run: cmake --build build --parallel + + - name: Smoke test GUI + shell: bash + run: | + set +e + QT_QPA_PLATFORM=offscreen timeout 3s ./build/searchmyfiles + status="$?" + test "$status" = 0 || test "$status" = 124 + + - name: Lint YAML + shell: bash + run: | + docker run --rm \ + -v "$PWD:/work" \ + -w /work \ + cytopia/yamllint:latest \ + -c .yamllint . + + linux-amd64: + needs: verify + runs-on: [self-hosted, linux, arch, homelab] + env: + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Configure release build + shell: bash + run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release + + - name: Build + shell: bash + run: cmake --build build --parallel + + - name: Package release + shell: bash + run: | + tag="${GITHUB_REF##*/}" + version="${tag#v}" + package="folderscope-${version}-linux-amd64" + rm -rf dist package + cmake --install build --prefix "$PWD/package/usr" + mkdir -p "package/${package}" + mv package/usr "package/${package}/" + cp README.md CHANGELOG.md VERSION "package/${package}/" + mkdir -p dist + tar -C package -czf "dist/${package}.tar.gz" "${package}" + sha256sum "dist/${package}.tar.gz" > "dist/${package}.tar.gz.sha256" + + - name: Create Gitea release + id: release + shell: bash + run: | + tag="${GITHUB_REF##*/}" + release_url="https://gitea.forust.xyz/api/v1/repos/forust/folderscope/releases" + existing="$(curl -fsS \ + "${release_url}/tags/${tag}" \ + -H "Authorization: token ${GITEA_TOKEN}")" || existing='{}' + release_id="$(printf '%s' "$existing" | jq -r '.id // empty')" + + if [ -z "$release_id" ]; then + payload="$(jq -n \ + --arg tag "${tag}" \ + --arg commit "${GITHUB_SHA}" \ + --arg name "FolderScope ${tag}" \ + --arg body "Release ${tag}. See CHANGELOG.md for notable changes." \ + '{tag_name: $tag, target_commitish: $commit, name: $name, body: $body, draft: false, prerelease: false}')" + response="$(curl -fsS \ + -X POST "${release_url}" \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "$payload")" + release_id="$(printf '%s' "$response" | jq -r '.id')" + fi + + test -n "$release_id" && test "$release_id" != "null" + echo "release_id=${release_id}" >> "$GITHUB_OUTPUT" + + - name: Upload Gitea release assets + shell: bash + run: | + release_url="https://gitea.forust.xyz/api/v1/repos/forust/folderscope/releases" + release_id="${{ steps.release.outputs.release_id }}" + for asset in dist/*; do + name="$(basename "$asset")" + release="$(curl -fsS \ + "${release_url}/${release_id}" \ + -H "Authorization: token ${GITEA_TOKEN}")" + asset_id="$(printf '%s' "$release" | jq -r --arg name "$name" \ + '.assets[]? | select(.name == $name) | .id' | head -n 1)" + if [ -n "$asset_id" ]; then + curl -fsS \ + -X DELETE "${release_url}/${release_id}/assets/${asset_id}" \ + -H "Authorization: token ${GITEA_TOKEN}" + fi + encoded="$(printf '%s' "$name" | jq -sRr @uri)" + curl -fsS \ + -X POST "${release_url}/${release_id}/assets?name=${encoded}" \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -F "attachment=@${asset}" + done + + - name: Upload workflow artifact + uses: actions/upload-artifact@v4 + with: + name: folderscope-linux-amd64 + path: dist/* diff --git a/.yamllint b/.yamllint new file mode 100644 index 0000000..763b282 --- /dev/null +++ b/.yamllint @@ -0,0 +1,18 @@ +extends: default + +rules: + comments: + min-spaces-from-content: 1 + comments-indentation: false + document-start: disable + line-length: + max: 140 + braces: + min-spaces-inside: 0 + max-spaces-inside: 1 + brackets: + min-spaces-inside: 0 + max-spaces-inside: 1 + indentation: + spaces: 2 + indent-sequences: consistent diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..5fefb86 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,18 @@ +# Changelog + +All notable changes to FolderScope are documented here. + +The project uses [Semantic Versioning](https://semver.org/). Release tags are +formatted as `vMAJOR.MINOR.PATCH`. + +## [0.2.0] - 2026-07-25 + +### Added + +- Initial Linux desktop release with C++20 and Qt 6. +- Advanced file search, content search, duplicate detection, summaries, and + CSV/TXT/HTML/XML/JSON export. +- Configurable columns, persistent UI settings, context actions, and progress + reporting. + +[0.2.0]: https://gitea.forust.xyz/forust/folderscope/releases/tag/v0.2.0 diff --git a/CMakeLists.txt b/CMakeLists.txt index 946f8f3..985c181 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,12 @@ cmake_minimum_required(VERSION 3.21) -project(searchmyfiles VERSION 0.2.0 LANGUAGES CXX) + +file(READ "${CMAKE_CURRENT_SOURCE_DIR}/VERSION" FOLDERSCOPE_VERSION) +string(STRIP "${FOLDERSCOPE_VERSION}" FOLDERSCOPE_VERSION) +if (NOT FOLDERSCOPE_VERSION MATCHES "^[0-9]+\\.[0-9]+\\.[0-9]+$") + message(FATAL_ERROR "VERSION must use MAJOR.MINOR.PATCH: ${FOLDERSCOPE_VERSION}") +endif() + +project(folderscope VERSION ${FOLDERSCOPE_VERSION} LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -13,6 +20,7 @@ qt_add_executable(searchmyfiles target_link_libraries(searchmyfiles PRIVATE Qt6::Widgets Qt6::Concurrent Qt6::DBus) target_compile_options(searchmyfiles PRIVATE -Wall -Wextra -Wpedantic) +target_compile_definitions(searchmyfiles PRIVATE FOLDERSCOPE_VERSION="${PROJECT_VERSION}") install(TARGETS searchmyfiles RUNTIME DESTINATION bin) install(FILES packaging/searchmyfiles.desktop diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..0ea3a94 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.2.0 diff --git a/scripts/release.sh b/scripts/release.sh new file mode 100755 index 0000000..04b3323 --- /dev/null +++ b/scripts/release.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ $# -ne 1 || ! "$1" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Usage: $0 MAJOR.MINOR.PATCH" >&2 + exit 2 +fi + +version="$1" +tag="v${version}" + +if ! git diff --quiet || ! git diff --cached --quiet; then + echo "Refusing to release with uncommitted changes." >&2 + exit 1 +fi +if git rev-parse -q --verify "refs/tags/${tag}" >/dev/null; then + echo "Tag ${tag} already exists." >&2 + exit 1 +fi +if ! grep -q "^## \[${version}\]" CHANGELOG.md; then + echo "Add a ## [${version}] section to CHANGELOG.md first." >&2 + exit 1 +fi + +current_version="$(tr -d '[:space:]' < VERSION)" +if [[ "$current_version" != "$version" ]]; then + printf '%s\n' "$version" > VERSION + git add VERSION + git commit -m "chore: release ${tag}" +fi +git tag -a "$tag" -m "Release ${tag}" + +echo "Created ${tag}. Publish it with:" +echo " git push origin main --follow-tags" diff --git a/src/main.cpp b/src/main.cpp index 2c4ac1a..a498ede 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1231,7 +1231,8 @@ public: menuBar()->addMenu(tr("&Help"))->addAction(tr("About"), this, [this] { QMessageBox::about(this, tr("About"), - tr("SearchMyFiles for Linux\n\nFast unindexed file search.\nC++20 + Qt 6.")); + tr("FolderScope %1\n\nFast unindexed file search.\nC++20 + Qt 6.") + .arg(QStringLiteral(FOLDERSCOPE_VERSION))); }); progress_ = new QProgressBar;