diff --git a/.github/workflows/build-packages.yml b/.github/workflows/build-packages.yml new file mode 100644 index 0000000..04f8771 --- /dev/null +++ b/.github/workflows/build-packages.yml @@ -0,0 +1,216 @@ +name: Build Packages + +on: + workflow_call: + inputs: + expected-tag: + required: false + type: string + default: '' + if-no-files-found: + required: false + type: string + default: warn + outputs: + version: + description: Project version read from CMakeLists.txt + value: ${{ jobs.prepare.outputs.version }} + +jobs: + prepare: + name: Prepare package build + runs-on: ubuntu-latest + outputs: + version: ${{ steps.version.outputs.version }} + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Read project version + id: version + run: | + VERSION="$(cmake -DPRINT_VERSION=ON -P packaging/sync-package-version.cmake)" + + if [ -n "${{ inputs.expected-tag }}" ] && [ "${{ inputs.expected-tag }}" != "v${VERSION}" ]; then + echo "Tag ${{ inputs.expected-tag }} does not match project version ${VERSION}. Expected v${VERSION}." + exit 1 + fi + + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" + + - name: Verify package metadata version + run: cmake -DVERIFY_ONLY=ON -P packaging/sync-package-version.cmake + + deb: + name: Debian packages (Ubuntu) + needs: prepare + runs-on: ubuntu-latest + container: ubuntu:25.04 + strategy: + fail-fast: false + matrix: + component: [rdhm-agent, rdhm-monitor] + env: + DEBIAN_FRONTEND: noninteractive + steps: + - name: Install base tools + run: apt-get update && apt-get install -y git ca-certificates + + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install common build dependencies + run: | + apt-get install -y \ + build-essential cmake pkg-config debhelper devscripts \ + fakeroot dpkg-dev + + - name: Install monitor build dependencies + if: matrix.component == 'rdhm-monitor' + run: | + apt-get install -y \ + qt6-base-dev qt6-declarative-dev libqt6quickcontrols2-6 \ + qt6-base-dev-tools qt6-declarative-dev-tools \ + libavahi-client-dev qml6-module-qtquick \ + qml6-module-qtquick-controls qml6-module-qtquick-layouts \ + libgl1-mesa-dev + + - name: Build package + run: | + VERSION="$(cmake -DPRINT_VERSION=ON -P packaging/sync-package-version.cmake)" + PKG_DIR=$(mktemp -d) + cp -a . "$PKG_DIR/${{ matrix.component }}-${VERSION}" + cp -a packaging/deb/${{ matrix.component }}/debian \ + "$PKG_DIR/${{ matrix.component }}-${VERSION}/debian" + cd "$PKG_DIR/${{ matrix.component }}-${VERSION}" + dpkg-buildpackage -b -us -uc + mkdir -p /tmp/rdhm-packages + cp "$PKG_DIR"/*.deb /tmp/rdhm-packages/ + ls -lh /tmp/rdhm-packages/*.deb + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: deb-${{ matrix.component }} + path: /tmp/rdhm-packages/*.deb + if-no-files-found: ${{ inputs.if-no-files-found }} + + rpm: + name: RPM packages (Fedora) + needs: prepare + runs-on: ubuntu-latest + container: fedora:41 + strategy: + fail-fast: false + matrix: + component: [rdhm-agent, rdhm-monitor] + steps: + - name: Install base tools + run: dnf install -y git + + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install common build dependencies + run: | + dnf install -y \ + cmake gcc-c++ make rpm-build systemd-rpm-macros + + - name: Install monitor build dependencies + if: matrix.component == 'rdhm-monitor' + run: | + dnf install -y \ + qt6-qtbase-devel qt6-qtdeclarative-devel \ + qt6-qtquickcontrols2-devel avahi-devel \ + mesa-libGL-devel + + - name: Build package + run: | + RPMBUILD_DIR=$(mktemp -d) + mkdir -p "$RPMBUILD_DIR"/{BUILD,RPMS,SOURCES,SPECS,SRPMS} + + SPEC="packaging/rpm/${{ matrix.component }}.spec" + VERSION="$(cmake -DPRINT_VERSION=ON -P packaging/sync-package-version.cmake)" + PKG_NAME="${{ matrix.component }}-${VERSION}" + + tar czf "$RPMBUILD_DIR/SOURCES/${PKG_NAME}.tar.gz" \ + --transform "s,^\.,${PKG_NAME}," \ + --exclude='.git' --exclude='build' . + + cp "$SPEC" "$RPMBUILD_DIR/SPECS/" + + rpmbuild -bb \ + --define "_topdir $RPMBUILD_DIR" \ + "$RPMBUILD_DIR/SPECS/${{ matrix.component }}.spec" + + mkdir -p /tmp/rdhm-packages + find "$RPMBUILD_DIR/RPMS/" -name '*.rpm' -exec cp {} /tmp/rdhm-packages/ \; + ls -lh /tmp/rdhm-packages/*.rpm + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: rpm-${{ matrix.component }} + path: /tmp/rdhm-packages/*.rpm + if-no-files-found: ${{ inputs.if-no-files-found }} + + pacman: + name: Pacman packages (Arch) + needs: prepare + runs-on: ubuntu-latest + container: archlinux:latest + strategy: + fail-fast: false + matrix: + component: [rdhm-agent, rdhm-monitor] + steps: + - name: Install base tools + run: | + pacman -Syu --noconfirm + pacman -S --noconfirm git base-devel cmake sudo + + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install monitor build dependencies + if: matrix.component == 'rdhm-monitor' + run: | + pacman -S --noconfirm \ + qt6-base qt6-declarative avahi + + - name: Build package + run: | + useradd -m builder + echo 'builder ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers + + BUILD_DIR=$(mktemp -d) + PKGBUILD_DIR="packaging/arch/${{ matrix.component }}" + VERSION="$(cmake -DPRINT_VERSION=ON -P packaging/sync-package-version.cmake)" + PKG_NAME="${{ matrix.component }}-${VERSION}" + + tar czf "$BUILD_DIR/${PKG_NAME}.tar.gz" \ + --transform "s,^\.,${PKG_NAME}," \ + --exclude='.git' --exclude='build' . + + cp "$PKGBUILD_DIR"/* "$BUILD_DIR/" + test -f "$BUILD_DIR/${PKG_NAME}.tar.gz" + ls -lh "$BUILD_DIR/${PKG_NAME}.tar.gz" + + chown -R builder:builder "$BUILD_DIR" + cd "$BUILD_DIR" + sudo -u builder makepkg -sf --noconfirm --skipchecksums + + mkdir -p /tmp/rdhm-packages + cp "$BUILD_DIR"/*.pkg.tar.zst /tmp/rdhm-packages/ + ls -lh /tmp/rdhm-packages/*.pkg.tar.zst + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: pacman-${{ matrix.component }} + path: /tmp/rdhm-packages/*.pkg.tar.zst + if-no-files-found: ${{ inputs.if-no-files-found }} diff --git a/.github/workflows/packaging.yml b/.github/workflows/packaging.yml index de080e7..324d88b 100644 --- a/.github/workflows/packaging.yml +++ b/.github/workflows/packaging.yml @@ -3,152 +3,5 @@ name: Package Build on: [pull_request] jobs: - deb: - name: Debian packages (Ubuntu) - runs-on: ubuntu-latest - container: ubuntu:25.04 - strategy: - fail-fast: false - matrix: - component: [rdhm-agent, rdhm-monitor] - env: - DEBIAN_FRONTEND: noninteractive - steps: - - name: Install base tools - run: apt-get update && apt-get install -y git ca-certificates - - - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Install common build dependencies - run: | - apt-get install -y \ - build-essential cmake pkg-config debhelper devscripts \ - fakeroot dpkg-dev - - - name: Install monitor build dependencies - if: matrix.component == 'rdhm-monitor' - run: | - apt-get install -y \ - qt6-base-dev qt6-declarative-dev libqt6quickcontrols2-6 \ - qt6-base-dev-tools qt6-declarative-dev-tools \ - libavahi-client-dev qml6-module-qtquick \ - qml6-module-qtquick-controls qml6-module-qtquick-layouts \ - libgl1-mesa-dev - - - name: Build package - run: | - PKG_DIR=$(mktemp -d) - cp -a . "$PKG_DIR/${{ matrix.component }}-${{ env.VERSION }}" - cp -a packaging/deb/${{ matrix.component }}/debian \ - "$PKG_DIR/${{ matrix.component }}-${{ env.VERSION }}/debian" - cd "$PKG_DIR/${{ matrix.component }}-${{ env.VERSION }}" - dpkg-buildpackage -b -us -uc - ls -lh "$PKG_DIR"/*.deb - env: - VERSION: "0.2.0" - - - name: Upload artifact - uses: actions/upload-artifact@v4 - with: - name: deb-${{ matrix.component }} - path: /tmp/tmp.*/**.deb - if-no-files-found: warn - - rpm: - name: RPM packages (Fedora) - runs-on: ubuntu-latest - container: fedora:41 - strategy: - fail-fast: false - matrix: - component: [rdhm-agent, rdhm-monitor] - steps: - - name: Install base tools - run: dnf install -y git - - - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Install common build dependencies - run: | - dnf install -y \ - cmake gcc-c++ make rpm-build systemd-rpm-macros - - - name: Install monitor build dependencies - if: matrix.component == 'rdhm-monitor' - run: | - dnf install -y \ - qt6-qtbase-devel qt6-qtdeclarative-devel \ - qt6-qtquickcontrols2-devel avahi-devel \ - mesa-libGL-devel - - - name: Build package - run: | - RPMBUILD_DIR=$(mktemp -d) - mkdir -p "$RPMBUILD_DIR"/{BUILD,RPMS,SOURCES,SPECS,SRPMS} - - SPEC="packaging/rpm/${{ matrix.component }}.spec" - VERSION=$(grep '^Version:' "$SPEC" | awk '{print $2}') - PKG_NAME="${{ matrix.component }}-${VERSION}" - - tar czf "$RPMBUILD_DIR/SOURCES/${PKG_NAME}.tar.gz" \ - --transform "s,^\.,${PKG_NAME}," \ - --exclude='.git' --exclude='build' . - - cp "$SPEC" "$RPMBUILD_DIR/SPECS/" - - rpmbuild -bb \ - --define "_topdir $RPMBUILD_DIR" \ - "$RPMBUILD_DIR/SPECS/${{ matrix.component }}.spec" - - find "$RPMBUILD_DIR/RPMS/" -name '*.rpm' -exec ls -lh {} \; - - pacman: - name: Pacman packages (Arch) - runs-on: ubuntu-latest - container: archlinux:latest - strategy: - fail-fast: false - matrix: - component: [rdhm-agent, rdhm-monitor] - steps: - - name: Install base tools - run: | - pacman -Syu --noconfirm - pacman -S --noconfirm git base-devel cmake sudo - - - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Install monitor build dependencies - if: matrix.component == 'rdhm-monitor' - run: | - pacman -S --noconfirm \ - qt6-base qt6-declarative avahi - - - name: Build package - run: | - # makepkg refuses to run as root — create a build user - useradd -m builder - echo 'builder ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers - - BUILD_DIR=$(mktemp -d) - PKGBUILD_DIR="packaging/arch/${{ matrix.component }}" - VERSION=$(grep '^pkgver=' "$PKGBUILD_DIR/PKGBUILD" | cut -d= -f2) - PKG_NAME="${{ matrix.component }}-${VERSION}" - - # Create source tarball that makepkg expects - tar czf "$BUILD_DIR/${PKG_NAME}.tar.gz" \ - --transform "s,^\.,${PKG_NAME}," \ - --exclude='.git' --exclude='build' . - - cp "$PKGBUILD_DIR"/* "$BUILD_DIR/" - - chown -R builder:builder "$BUILD_DIR" - cd "$BUILD_DIR" - sudo -u builder makepkg -sf --noconfirm --skipchecksums - ls -lh "$BUILD_DIR"/*.pkg.tar.zst + packages: + uses: ./.github/workflows/build-packages.yml diff --git a/.github/workflows/release-packages.yml b/.github/workflows/release-packages.yml new file mode 100644 index 0000000..ddfcd45 --- /dev/null +++ b/.github/workflows/release-packages.yml @@ -0,0 +1,35 @@ +name: Release Packages + +on: + push: + tags: + - 'v*.*.*' + +permissions: + contents: write + +jobs: + packages: + uses: ./.github/workflows/build-packages.yml + with: + expected-tag: ${{ github.ref_name }} + if-no-files-found: error + + release: + name: Create draft release + needs: packages + runs-on: ubuntu-latest + steps: + - name: Download packages + uses: actions/download-artifact@v4 + with: + path: dist + merge-multiple: true + + - name: Create draft release + uses: softprops/action-gh-release@v2 + with: + draft: true + name: Remote Disc Health Monitor ${{ github.ref_name }} + tag_name: ${{ github.ref_name }} + files: dist/* diff --git a/CMakeLists.txt b/CMakeLists.txt index 6dfd501..55ce06a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,18 @@ set(CMAKE_CXX_STANDARD 23) option(BUILD_AGENT "Build the agent component" ON) option(BUILD_MONITOR "Build the monitor component" ON) +add_custom_target(sync-package-version + COMMAND ${CMAKE_COMMAND} -P ${PROJECT_SOURCE_DIR}/packaging/sync-package-version.cmake + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + COMMENT "Synchronizing package metadata with project version ${PROJECT_VERSION}" +) + +add_custom_target(verify-package-version + COMMAND ${CMAKE_COMMAND} -DVERIFY_ONLY=ON -P ${PROJECT_SOURCE_DIR}/packaging/sync-package-version.cmake + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + COMMENT "Verifying package metadata against project version ${PROJECT_VERSION}" +) + if(MSVC) set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) # when msvc, build gtest with shared crt library SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) diff --git a/packaging/deb/rdhm-agent/debian/control b/packaging/deb/rdhm-agent/debian/control index 9acb2f6..704c492 100644 --- a/packaging/deb/rdhm-agent/debian/control +++ b/packaging/deb/rdhm-agent/debian/control @@ -2,7 +2,7 @@ Source: rdhm-agent Section: admin Priority: optional Maintainer: Michał Walenciak -Uploaders: Wojciech Kucjas +Uploaders: Michał Walenciak Build-Depends: debhelper-compat (= 13), cmake (>= 3.16), g++ (>= 10), diff --git a/packaging/sync-package-version.cmake b/packaging/sync-package-version.cmake new file mode 100644 index 0000000..97f847f --- /dev/null +++ b/packaging/sync-package-version.cmake @@ -0,0 +1,189 @@ +cmake_minimum_required(VERSION 3.16) + +set(PROJECT_ROOT "${CMAKE_CURRENT_LIST_DIR}/..") + +file(READ "${PROJECT_ROOT}/CMakeLists.txt" ROOT_CMAKELISTS) +string(REGEX MATCH + "project\\([^)]*VERSION[ \t\r\n]+([0-9]+\\.[0-9]+\\.[0-9]+)[^)]*\\)" + PROJECT_DECLARATION + "${ROOT_CMAKELISTS}" +) + +if(NOT PROJECT_DECLARATION) + message(FATAL_ERROR "Could not find project(... VERSION x.y.z) in ${PROJECT_ROOT}/CMakeLists.txt") +endif() + +set(PROJECT_PACKAGE_VERSION "${CMAKE_MATCH_1}") +set(PACKAGE_RELEASE "1") + +if(PRINT_VERSION) + execute_process(COMMAND "${CMAKE_COMMAND}" -E echo "${PROJECT_PACKAGE_VERSION}") + return() +endif() + +if(VERIFY_ONLY) + set(SYNC_VERIFY_ONLY TRUE) +else() + set(SYNC_VERIFY_ONLY FALSE) +endif() + +set(SYNC_MISMATCHES "") + +function(_record_mismatch path) + list(APPEND SYNC_MISMATCHES "${path}") + set(SYNC_MISMATCHES "${SYNC_MISMATCHES}" PARENT_SCOPE) +endfunction() + +function(_write_if_changed path content) + if(EXISTS "${path}") + file(READ "${path}" old_content) + else() + set(old_content "") + endif() + + if(NOT old_content STREQUAL content) + if(SYNC_VERIFY_ONLY) + _record_mismatch("${path}") + else() + file(WRITE "${path}" "${content}") + message(STATUS "Updated ${path}") + endif() + endif() +endfunction() + +function(_replace_in_file path pattern replacement) + file(READ "${path}" old_content) + string(REGEX MATCH "${pattern}" matched_content "${old_content}") + if(NOT matched_content) + if(SYNC_VERIFY_ONLY) + _record_mismatch("${path}") + return() + endif() + message(FATAL_ERROR "Could not update ${path}; pattern did not match: ${pattern}") + endif() + + string(REGEX REPLACE "${pattern}" "${replacement}" new_content "${old_content}") + + if(old_content STREQUAL new_content) + return() + else() + _write_if_changed("${path}" "${new_content}") + endif() +endfunction() + +function(_sync_debian_changelog component) + set(changelog_path "${PROJECT_ROOT}/packaging/deb/${component}/debian/changelog") + set(control_path "${PROJECT_ROOT}/packaging/deb/${component}/debian/control") + set(expected_version "${PROJECT_PACKAGE_VERSION}-${PACKAGE_RELEASE}") + + file(READ "${changelog_path}" changelog) + string(REGEX MATCH "^${component} \\(([^)]+)\\)" first_entry "${changelog}") + + if(NOT first_entry) + if(SYNC_VERIFY_ONLY) + _record_mismatch("${changelog_path}") + return() + endif() + message(FATAL_ERROR "Could not parse first Debian changelog entry in ${changelog_path}") + endif() + + if(CMAKE_MATCH_1 STREQUAL expected_version) + return() + endif() + + if(SYNC_VERIFY_ONLY) + _record_mismatch("${changelog_path}") + return() + endif() + + file(READ "${control_path}" control) + string(REGEX MATCH "(^|\n)Maintainer:[ \t]*([^\n]+)" maintainer_match "${control}") + if(maintainer_match) + set(maintainer "${CMAKE_MATCH_2}") + else() + set(maintainer "Michał Walenciak ") + endif() + + string(TIMESTAMP debian_date "%a, %d %b %Y %H:%M:%S +0000" UTC) + set(new_entry +"${component} (${expected_version}) unstable; urgency=medium + + * Release version ${PROJECT_PACKAGE_VERSION}. + + -- ${maintainer} ${debian_date} + +") + _write_if_changed("${changelog_path}" "${new_entry}${changelog}") +endfunction() + +function(_sync_rpm_changelog component maintainer) + set(spec_path "${PROJECT_ROOT}/packaging/rpm/${component}.spec") + set(expected_version "${PROJECT_PACKAGE_VERSION}-${PACKAGE_RELEASE}") + + file(READ "${spec_path}" spec) + string(REGEX MATCH "\\* [^\n]+ - ([0-9]+\\.[0-9]+\\.[0-9]+-[0-9]+)" first_entry "${spec}") + + if(first_entry AND CMAKE_MATCH_1 STREQUAL expected_version) + return() + endif() + + if(SYNC_VERIFY_ONLY) + _record_mismatch("${spec_path}") + return() + endif() + + string(TIMESTAMP rpm_date "%a %b %d %Y" UTC) + set(new_changelog +"%changelog +* ${rpm_date} ${maintainer} - ${expected_version} +- Release version ${PROJECT_PACKAGE_VERSION} +") + string(REGEX REPLACE "%changelog\n" "${new_changelog}" updated_spec "${spec}") + _write_if_changed("${spec_path}" "${updated_spec}") +endfunction() + +_replace_in_file( + "${PROJECT_ROOT}/packaging/arch/rdhm-agent/PKGBUILD" + "pkgver=[^\n]+" + "pkgver=${PROJECT_PACKAGE_VERSION}" +) +_replace_in_file( + "${PROJECT_ROOT}/packaging/arch/rdhm-monitor/PKGBUILD" + "pkgver=[^\n]+" + "pkgver=${PROJECT_PACKAGE_VERSION}" +) + +_replace_in_file( + "${PROJECT_ROOT}/packaging/rpm/rdhm-agent.spec" + "Version:[ \t]+[^\n]+" + "Version: ${PROJECT_PACKAGE_VERSION}" +) +_replace_in_file( + "${PROJECT_ROOT}/packaging/rpm/rdhm-monitor.spec" + "Version:[ \t]+[^\n]+" + "Version: ${PROJECT_PACKAGE_VERSION}" +) + +_replace_in_file( + "${PROJECT_ROOT}/packaging/deb/rdhm-agent/build.sh" + "PKG_DIR=\"\\$BUILD_DIR/rdhm-agent-[^\"]+\"" + "PKG_DIR=\"$BUILD_DIR/rdhm-agent-${PROJECT_PACKAGE_VERSION}\"" +) +_replace_in_file( + "${PROJECT_ROOT}/packaging/deb/rdhm-monitor/build.sh" + "PKG_DIR=\"\\$BUILD_DIR/rdhm-monitor-[^\"]+\"" + "PKG_DIR=\"$BUILD_DIR/rdhm-monitor-${PROJECT_PACKAGE_VERSION}\"" +) + +_sync_debian_changelog("rdhm-agent") +_sync_debian_changelog("rdhm-monitor") + +_sync_rpm_changelog("rdhm-agent" "Michał Walenciak ") +_sync_rpm_changelog("rdhm-monitor" "Michał Walenciak ") + +if(SYNC_MISMATCHES) + list(JOIN SYNC_MISMATCHES "\n " mismatch_text) + message(FATAL_ERROR "Package metadata is out of sync with project version ${PROJECT_PACKAGE_VERSION}:\n ${mismatch_text}\nRun: cmake -P packaging/sync-package-version.cmake") +endif() + +message(STATUS "Package metadata is synchronized with version ${PROJECT_PACKAGE_VERSION}")