diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..a8fe081c1 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,91 @@ +# SPDX-FileCopyrightText: 2026 Institute for Automation of Complex Power Systems, RWTH Aachen University +# SPDX-License-Identifier: Apache-2.0 + +name: Build + +on: + workflow_call: + inputs: + docker_image: + required: true + type: string + +env: + CMAKE_BUILD_OPTS: --parallel $(nproc) + CMAKE_EXTRA_OPTS: -DCMAKE_BUILD_TYPE=Release + CCACHE_DIR: ${{ github.workspace }}/.ccache + +permissions: + contents: read + packages: write + +jobs: + build-source: + name: "build:source [${{ matrix.distro }}]" + runs-on: ubuntu-latest + container: + image: ${{ inputs.docker_image }}/dev-${{ matrix.image_name }}:${{ github.sha }} + options: --user root + strategy: + fail-fast: false + matrix: + include: + - distro: fedora + image_name: fedora + cmake_extra_opts: >- + -DVILLAS_COMPILE_WARNING_AS_ERROR=ON + -DCMAKE_C_COMPILER_LAUNCHER=ccache + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + - distro: fedora-minimal + image_name: fedora-minimal + cmake_extra_opts: >- + -DVILLAS_COMPILE_WARNING_AS_ERROR=ON + -DWITH_API=OFF + -DWITH_CLIENTS=OFF + -DWITH_CONFIG=OFF + -DWITH_DOC=OFF + -DWITH_FPGA=OFF + -DWITH_GRAPHVIZ=OFF + -DWITH_HOOKS=OFF + -DWITH_LUA=OFF + -DWITH_OPENMP=OFF + -DWITH_PLUGINS=OFF + -DWITH_SRC=OFF + -DWITH_TESTS=OFF + -DWITH_TOOLS=OFF + -DWITH_WEB=OFF + -DCMAKE_MODULE_PATH=/usr/local/lib64/cmake + -DCMAKE_PREFIX_PATH=/usr/local + - distro: debian + image_name: debian + - distro: rocky + image_name: rocky + - distro: rocky9 + image_name: rocky9 + - distro: ubuntu + image_name: ubuntu + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + submodules: recursive + + - name: ccache + if: matrix.distro == 'fedora' + uses: actions/cache@v6 + with: + path: ${{ github.workspace }}/.ccache + key: ccache-fedora-${{ github.sha }} + restore-keys: ccache-fedora- + + - name: Configure + run: cmake -S . -B build ${{ matrix.cmake_extra_opts || env.CMAKE_EXTRA_OPTS }} + + - name: Build + run: cmake --build build ${{ env.CMAKE_BUILD_OPTS }} + + - uses: actions/upload-artifact@v7 + with: + name: build-${{ matrix.distro }} + path: build/ + retention-days: 7 diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml new file mode 100644 index 000000000..02b50dd24 --- /dev/null +++ b/.github/workflows/checks.yml @@ -0,0 +1,70 @@ +# SPDX-FileCopyrightText: 2026 Institute for Automation of Complex Power Systems, RWTH Aachen University +# SPDX-License-Identifier: Apache-2.0 + +name: Checks + +on: + workflow_call: + +permissions: + contents: read + +jobs: + build-openapi: + name: build:openapi + runs-on: ubuntu-latest + container: + image: node:24-alpine + steps: + - uses: actions/checkout@v7 + + - name: Lint OpenAPI spec + run: npx -y @redocly/cli lint --config doc/redocly.yaml doc/openapi/openapi.yaml + + - name: Build OpenAPI docs + run: npx -y @redocly/cli build-docs --config doc/redocly.yaml doc/openapi/openapi.yaml --output openapi.html + + - uses: actions/upload-artifact@v7 + with: + name: openapi + path: openapi.html + + pre-commit: + name: test:pre-commit + runs-on: ubuntu-latest + container: + image: python:3.12.10-slim-bookworm + options: --user root + steps: + - name: Install git and ruby + run: apt-get update && apt-get -y install git ruby + + - uses: actions/checkout@v7 + + - name: Add Git safe directory + run: git config --global --add safe.directory '*' + + - name: Install pre-commit + run: pip install --no-cache-dir pre-commit + + - name: Run pre-commit + run: pre-commit run --all-files + + - name: Upload log + if: failure() + uses: actions/upload-artifact@v7 + with: + name: pre-commit-log + path: /github/home/.cache/pre-commit/pre-commit.log + + reuse: + name: test:reuse + runs-on: ubuntu-latest + container: + image: fsfe/reuse:latest + options: --entrypoint "" + steps: + - uses: actions/checkout@v7 + + - name: Run REUSE lint + run: reuse lint diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..928602fcf --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,91 @@ +# SPDX-FileCopyrightText: 2026 Institute for Automation of Complex Power Systems, RWTH Aachen University +# SPDX-License-Identifier: Apache-2.0 + +name: CI + +on: + push: + branches: [master] + tags: ['v*'] + pull_request: + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + setup: + name: setup + runs-on: ubuntu-latest + outputs: + docker_image: ${{ steps.image.outputs.docker_image }} + steps: + - name: Compute image name for the current repository + id: image + env: + OWNER: ${{ github.repository_owner }} + run: echo "docker_image=ghcr.io/${OWNER,,}/villas/node" >> "$GITHUB_OUTPUT" + + checks: + uses: ./.github/workflows/checks.yml + secrets: inherit + + nix: + uses: ./.github/workflows/nix.yml + secrets: inherit + + prepare: + needs: [setup] + uses: ./.github/workflows/prepare.yml + with: + docker_image: ${{ needs.setup.outputs.docker_image }} + secrets: inherit + + build: + needs: [setup, prepare] + uses: ./.github/workflows/build.yml + with: + docker_image: ${{ needs.setup.outputs.docker_image }} + secrets: inherit + + test: + needs: [setup, build] + uses: ./.github/workflows/test.yml + with: + docker_image: ${{ needs.setup.outputs.docker_image }} + secrets: inherit + + packaging-docker: + if: github.event_name != 'pull_request' + needs: [setup, test] + uses: ./.github/workflows/packaging-docker.yml + with: + docker_image: ${{ needs.setup.outputs.docker_image }} + secrets: inherit + + packaging-nix: + if: github.event_name != 'pull_request' + needs: [setup, test] + uses: ./.github/workflows/packaging-nix.yml + with: + docker_image: ${{ needs.setup.outputs.docker_image }} + secrets: inherit + + deploy: + if: github.event_name != 'pull_request' + needs: [setup, packaging-docker] + uses: ./.github/workflows/deploy.yml + with: + docker_image: ${{ needs.setup.outputs.docker_image }} + is_version_tag: ${{ startsWith(github.ref, 'refs/tags/v') }} + secrets: inherit + + deploy-nix: + if: github.event_name != 'pull_request' + needs: [setup, packaging-nix] + uses: ./.github/workflows/deploy-nix.yml + with: + docker_image: ${{ needs.setup.outputs.docker_image }} + is_version_tag: ${{ startsWith(github.ref, 'refs/tags/v') }} + secrets: inherit diff --git a/.github/workflows/cleanup.yml b/.github/workflows/cleanup.yml new file mode 100644 index 000000000..3084b3f54 --- /dev/null +++ b/.github/workflows/cleanup.yml @@ -0,0 +1,32 @@ +# SPDX-FileCopyrightText: 2026 Institute for Automation of Complex Power Systems, RWTH Aachen University +# SPDX-License-Identifier: Apache-2.0 + +name: Cleanup + +on: + schedule: + - cron: "0 3 * * *" + workflow_dispatch: + +permissions: + packages: write + +jobs: + cleanup-packages: + name: cleanup:packages + runs-on: ubuntu-latest + steps: + # Deletes PR, branch, SHA and content-hash image versions older than + # 14 days. Version tags (v*), master and latest are kept forever. + # Starts in dry-run: review one run's log, then set dry-run to false. + - uses: dataaxiom/ghcr-cleanup-action@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + owner: ${{ github.repository_owner }} + packages: ^villas/node(/.*)?$ + expand-packages: true + use-regex: true + exclude-tags: ^(latest|master.*|v.*)$ + older-than: 14 days + delete-untagged: true + dry-run: true diff --git a/.github/workflows/deploy-nix.yml b/.github/workflows/deploy-nix.yml new file mode 100644 index 000000000..3e0c26037 --- /dev/null +++ b/.github/workflows/deploy-nix.yml @@ -0,0 +1,121 @@ +# SPDX-FileCopyrightText: 2026 Institute for Automation of Complex Power Systems, RWTH Aachen University +# SPDX-License-Identifier: Apache-2.0 + +name: Deploy (Nix) + +on: + workflow_call: + inputs: + docker_image: + required: true + type: string + is_version_tag: + required: false + type: boolean + default: false + +env: + REGISTRY: ghcr.io + DOCKER_IMAGE: ${{ inputs.docker_image }} + +permissions: + contents: read + packages: write + +jobs: + deploy-docker-nix: + name: deploy:docker-nix + runs-on: ubuntu-latest + steps: + - uses: docker/setup-buildx-action@v4 + + - uses: docker/login-action@v4 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Compute safe ref tag + id: ref + run: echo "tag=${GITHUB_REF_NAME//\//-}" >> "$GITHUB_OUTPUT" + + - name: Create and push multi-arch nix manifest + run: | + docker buildx imagetools create \ + -t "${{ env.DOCKER_IMAGE }}:${{ steps.ref.outputs.tag }}-nix" \ + "${{ env.DOCKER_IMAGE }}:${{ steps.ref.outputs.tag }}-nix-x86_64-linux" \ + "${{ env.DOCKER_IMAGE }}:${{ steps.ref.outputs.tag }}-nix-aarch64-linux" + + latest-docker-nix: + name: latest:docker-nix + if: ${{ inputs.is_version_tag }} + needs: [deploy-docker-nix] + runs-on: ubuntu-latest + steps: + - uses: docker/setup-buildx-action@v4 + + - uses: docker/login-action@v4 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Compute safe ref tag + id: ref + run: echo "tag=${GITHUB_REF_NAME//\//-}" >> "$GITHUB_OUTPUT" + + - name: Tag nix manifest as latest-nix + run: | + docker buildx imagetools create \ + -t "${{ env.DOCKER_IMAGE }}:latest-nix" \ + "${{ env.DOCKER_IMAGE }}:${{ steps.ref.outputs.tag }}-nix" + + latest-docker-nix-manual: + name: latest:docker-nix (manual) + if: ${{ !inputs.is_version_tag }} + needs: [deploy-docker-nix] + runs-on: ubuntu-latest + environment: latest-docker-nix-manual + steps: + - uses: docker/setup-buildx-action@v4 + + - uses: docker/login-action@v4 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Compute safe ref tag + id: ref + run: echo "tag=${GITHUB_REF_NAME//\//-}" >> "$GITHUB_OUTPUT" + + - name: Tag nix manifest as latest-nix (manual) + run: | + docker buildx imagetools create \ + -t "${{ env.DOCKER_IMAGE }}:latest-nix" \ + "${{ env.DOCKER_IMAGE }}:${{ steps.ref.outputs.tag }}-nix" + + release-nix: + name: release:nix + if: ${{ inputs.is_version_tag }} + runs-on: ubuntu-latest + permissions: + contents: write + actions: read + steps: + - uses: actions/download-artifact@v7 + with: + pattern: villas-* + path: dist + merge-multiple: true + + - name: Publish arx and rpm bundles to the release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release create "${GITHUB_REF_NAME}" dist/* \ + --repo "${GITHUB_REPOSITORY}" \ + --title "${GITHUB_REF_NAME}" \ + --generate-notes \ + || gh release upload "${GITHUB_REF_NAME}" dist/* \ + --repo "${GITHUB_REPOSITORY}" --clobber diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 000000000..5fc8dc636 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,191 @@ +# SPDX-FileCopyrightText: 2026 Institute for Automation of Complex Power Systems, RWTH Aachen University +# SPDX-License-Identifier: Apache-2.0 + +name: Deploy + +on: + workflow_call: + inputs: + docker_image: + required: true + type: string + is_version_tag: + required: false + type: boolean + default: false + +env: + REGISTRY: ghcr.io + DOCKER_IMAGE: ${{ inputs.docker_image }} + +permissions: + contents: read + packages: write + +jobs: + deploy-docker: + name: deploy:docker + runs-on: ubuntu-latest + steps: + - uses: docker/setup-buildx-action@v4 + + - uses: docker/login-action@v4 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Compute safe ref tag + id: ref + run: echo "tag=${GITHUB_REF_NAME//\//-}" >> "$GITHUB_OUTPUT" + + - name: Create and push multi-arch manifest + run: | + docker buildx imagetools create \ + -t "${{ env.DOCKER_IMAGE }}:${{ steps.ref.outputs.tag }}" \ + "${{ env.DOCKER_IMAGE }}:${{ steps.ref.outputs.tag }}-x86_64" \ + "${{ env.DOCKER_IMAGE }}:${{ steps.ref.outputs.tag }}-arm64" + + deploy-docker-dev: + name: deploy:docker-dev + runs-on: ubuntu-latest + steps: + - uses: docker/setup-buildx-action@v4 + + - uses: docker/login-action@v4 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Compute safe ref tag + id: ref + run: echo "tag=${GITHUB_REF_NAME//\//-}" >> "$GITHUB_OUTPUT" + + - name: Publish dev image for current ref + run: | + docker buildx imagetools create \ + -t "${{ env.DOCKER_IMAGE }}/dev:${{ steps.ref.outputs.tag }}" \ + "${{ env.DOCKER_IMAGE }}/dev-fedora:${{ steps.ref.outputs.tag }}" + + deploy-docker-dev-vscode: + name: deploy:docker-dev-vscode + runs-on: ubuntu-latest + steps: + - uses: docker/setup-buildx-action@v4 + + - uses: docker/login-action@v4 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Compute safe ref tag + id: ref + run: echo "tag=${GITHUB_REF_NAME//\//-}" >> "$GITHUB_OUTPUT" + + - name: Publish dev-vscode image for current ref + run: | + docker buildx imagetools create \ + -t "${{ env.DOCKER_IMAGE }}/dev-vscode:${{ steps.ref.outputs.tag }}" \ + "${{ env.DOCKER_IMAGE }}/dev-vscode:${{ github.sha }}" + + latest-docker: + name: latest:docker + if: ${{ inputs.is_version_tag }} + needs: [deploy-docker] + runs-on: ubuntu-latest + steps: + - uses: docker/setup-buildx-action@v4 + + - uses: docker/login-action@v4 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Compute safe ref tag + id: ref + run: echo "tag=${GITHUB_REF_NAME//\//-}" >> "$GITHUB_OUTPUT" + + - name: Tag and push app image as latest + run: | + docker buildx imagetools create \ + -t "${{ env.DOCKER_IMAGE }}:latest" \ + "${{ env.DOCKER_IMAGE }}:${{ steps.ref.outputs.tag }}-x86_64" \ + "${{ env.DOCKER_IMAGE }}:${{ steps.ref.outputs.tag }}-arm64" + + latest-docker-dev: + name: latest:docker-dev + if: ${{ inputs.is_version_tag }} + needs: [deploy-docker-dev] + runs-on: ubuntu-latest + steps: + - uses: docker/setup-buildx-action@v4 + + - uses: docker/login-action@v4 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Compute safe ref tag + id: ref + run: echo "tag=${GITHUB_REF_NAME//\//-}" >> "$GITHUB_OUTPUT" + + - name: Tag and push dev image as latest + run: | + docker buildx imagetools create \ + -t "${{ env.DOCKER_IMAGE }}/dev:latest" \ + "${{ env.DOCKER_IMAGE }}/dev:${{ steps.ref.outputs.tag }}" + + latest-docker-manual: + name: latest:docker (manual) + if: ${{ !inputs.is_version_tag }} + needs: [deploy-docker] + runs-on: ubuntu-latest + environment: latest-docker-manual + steps: + - uses: docker/setup-buildx-action@v4 + + - uses: docker/login-action@v4 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Compute safe ref tag + id: ref + run: echo "tag=${GITHUB_REF_NAME//\//-}" >> "$GITHUB_OUTPUT" + + - name: Tag and push app image as latest (manual) + run: | + docker buildx imagetools create \ + -t "${{ env.DOCKER_IMAGE }}:latest" \ + "${{ env.DOCKER_IMAGE }}:${{ steps.ref.outputs.tag }}-x86_64" \ + "${{ env.DOCKER_IMAGE }}:${{ steps.ref.outputs.tag }}-arm64" + + latest-docker-dev-manual: + name: latest:docker-dev (manual) + if: ${{ !inputs.is_version_tag }} + needs: [deploy-docker-dev] + runs-on: ubuntu-latest + environment: latest-docker-dev-manual + steps: + - uses: docker/setup-buildx-action@v4 + + - uses: docker/login-action@v4 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Compute safe ref tag + id: ref + run: echo "tag=${GITHUB_REF_NAME//\//-}" >> "$GITHUB_OUTPUT" + + - name: Tag and push dev image as latest (manual) + run: | + docker buildx imagetools create \ + -t "${{ env.DOCKER_IMAGE }}/dev:latest" \ + "${{ env.DOCKER_IMAGE }}/dev:${{ steps.ref.outputs.tag }}" diff --git a/.github/workflows/mirror.yaml b/.github/workflows/mirror.yaml deleted file mode 100644 index 7762b1a80..000000000 --- a/.github/workflows/mirror.yaml +++ /dev/null @@ -1,27 +0,0 @@ -# SPDX-FileCopyrightText: 2025 OPAL-RT Germany GmbH -# SPDX-License-Identifier: Apache-2.0 - ---- -name: Mirror to RWTH GitLab - -on: - push: - delete: - -jobs: - mirror: - name: Mirror - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - uses: yesolutions/mirror-action@master - with: - REMOTE: https://git.rwth-aachen.de/acs/public/villas/node.git - GIT_USERNAME: github - GIT_PASSWORD: ${{ secrets.RWTH_GITLAB_TOKEN }} - PUSH_ALL_REFS: "false" diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml new file mode 100644 index 000000000..18f5d99b7 --- /dev/null +++ b/.github/workflows/nix.yml @@ -0,0 +1,38 @@ +# SPDX-FileCopyrightText: 2026 Institute for Automation of Complex Power Systems, RWTH Aachen University +# SPDX-License-Identifier: Apache-2.0 + +name: Nix + +on: + workflow_call: + +env: + CACHIX_CACHE_NAME: ${{ vars.CACHIX_CACHE_NAME || 'villas' }} + +permissions: + contents: read + +jobs: + build-nix: + name: "build:nix [${{ matrix.system }}]" + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + system: [x86_64-linux, aarch64-linux] + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + submodules: recursive + + - uses: cachix/install-nix-action@v31 + + - uses: cachix/cachix-action@v17 + with: + name: ${{ env.CACHIX_CACHE_NAME }} + authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} + extraPullNames: villas + + - name: Build + run: nix build --print-build-logs ".#villas-node-${{ matrix.system }}" diff --git a/.github/workflows/packaging-docker.yml b/.github/workflows/packaging-docker.yml new file mode 100644 index 000000000..2be218c98 --- /dev/null +++ b/.github/workflows/packaging-docker.yml @@ -0,0 +1,100 @@ +# SPDX-FileCopyrightText: 2026 Institute for Automation of Complex Power Systems, RWTH Aachen University +# SPDX-License-Identifier: Apache-2.0 + +name: Packaging (Docker) + +on: + workflow_call: + inputs: + docker_image: + required: true + type: string + +env: + REGISTRY: ghcr.io + DOCKER_IMAGE: ${{ inputs.docker_image }} + CMAKE_EXTRA_OPTS: -DCMAKE_BUILD_TYPE=Release + DOCKER_FILE: packaging/docker/Dockerfile.fedora + +permissions: + contents: read + packages: write + +jobs: + pkg-docker-x86_64: + name: "pkg:docker [x86_64]" + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + submodules: recursive + + - uses: docker/setup-buildx-action@v4 + + - uses: docker/login-action@v4 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Compute safe ref tag + id: ref + run: echo "tag=${GITHUB_REF_NAME//\//-}" >> "$GITHUB_OUTPUT" + + - uses: docker/build-push-action@v7 + with: + context: . + file: ${{ env.DOCKER_FILE }} + target: app + push: true + pull: true + platforms: amd64 + cache-from: | + type=gha,scope=dev-fedora + type=gha,scope=app-x86_64 + cache-to: type=gha,mode=max,scope=app-x86_64 + build-args: | + ARCH=x86_64 + TRIPLET=x86_64-linux-gnu + CMAKE_OPTS=${{ env.CMAKE_EXTRA_OPTS }} + tags: ${{ env.DOCKER_IMAGE }}:${{ steps.ref.outputs.tag }}-x86_64 + + pkg-docker-arm64: + name: "pkg:docker [arm64]" + runs-on: ubuntu-24.04-arm + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + submodules: recursive + + - uses: docker/setup-buildx-action@v4 + + - uses: docker/login-action@v4 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Compute safe ref tag + id: ref + run: echo "tag=${GITHUB_REF_NAME//\//-}" >> "$GITHUB_OUTPUT" + + - uses: docker/build-push-action@v7 + with: + context: . + file: packaging/docker/Dockerfile.debian + target: app + push: true + pull: true + platforms: linux/arm64 + cache-from: type=gha,scope=app-arm64 + cache-to: type=gha,mode=max,scope=app-arm64 + build-args: | + ARCH=arm64 + TRIPLET=aarch64-linux-gnu + CMAKE_OPTS=${{ env.CMAKE_EXTRA_OPTS }} + tags: ${{ env.DOCKER_IMAGE }}:${{ steps.ref.outputs.tag }}-arm64 diff --git a/.github/workflows/packaging-nix.yml b/.github/workflows/packaging-nix.yml new file mode 100644 index 000000000..c4b9a4171 --- /dev/null +++ b/.github/workflows/packaging-nix.yml @@ -0,0 +1,138 @@ +# SPDX-FileCopyrightText: 2026 Institute for Automation of Complex Power Systems, RWTH Aachen University +# SPDX-License-Identifier: Apache-2.0 + +name: Packaging (Nix) + +on: + workflow_call: + inputs: + docker_image: + required: true + type: string + +env: + REGISTRY: ghcr.io + DOCKER_IMAGE: ${{ inputs.docker_image }} + CACHIX_CACHE_NAME: ${{ vars.CACHIX_CACHE_NAME || 'villas' }} + +permissions: + contents: read + packages: write + +jobs: + pkg-nix-arx: + name: "pkg:nix:arx [${{ matrix.system }}]" + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + system: [x86_64-linux, aarch64-linux] + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + submodules: recursive + + - uses: cachix/install-nix-action@v31 + + - uses: cachix/cachix-action@v17 + with: + name: ${{ env.CACHIX_CACHE_NAME }} + authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} + extraPullNames: villas + + - name: Bundle as arx + run: | + nix bundle --print-build-logs --out-link bundle-arx \ + --bundler github:nix-community/nix-bundle \ + ".#villas-node-${{ matrix.system }}" + mkdir -p artifacts + cp -L bundle-arx artifacts/villas-${{ matrix.system }} + + - uses: actions/upload-artifact@v7 + with: + name: villas-arx-${{ matrix.system }} + path: artifacts/ + retention-days: 90 + + pkg-nix-rpm: + name: "pkg:nix:rpm [${{ matrix.system }}]" + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + system: [x86_64-linux, aarch64-linux] + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + submodules: recursive + + - uses: cachix/install-nix-action@v31 + + - uses: cachix/cachix-action@v17 + with: + name: ${{ env.CACHIX_CACHE_NAME }} + authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} + extraPullNames: villas + + - name: Bundle as RPM + run: | + nix bundle --print-build-logs --out-link bundle-rpm \ + --bundler github:NixOS/bundlers#toRPM \ + ".#villas-node-${{ matrix.system }}" + mkdir -p artifacts + cp -L bundle-rpm/*.rpm artifacts/villas-${{ matrix.system }}.rpm + + - uses: actions/upload-artifact@v7 + with: + name: villas-rpm-${{ matrix.system }} + path: artifacts/ + retention-days: 90 + + pkg-nix-docker: + name: "pkg:nix:docker [${{ matrix.system }}]" + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + system: [x86_64-linux, aarch64-linux] + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + submodules: recursive + + - uses: cachix/install-nix-action@v31 + + - uses: cachix/cachix-action@v17 + with: + name: ${{ env.CACHIX_CACHE_NAME }} + authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} + extraPullNames: villas + + - name: Bundle as Docker image + run: | + mkdir -p /var/tmp/ + nix bundle --print-build-logs --out-link bundle-docker \ + --bundler github:NixOS/bundlers#toDockerImage \ + ".#villas-node-${{ matrix.system }}" + + - name: Compute safe ref tag + id: ref + run: echo "tag=${GITHUB_REF_NAME//\//-}" >> "$GITHUB_OUTPUT" + + - name: Push to registry + env: + CONTAINERS_REGISTRIES_CONF: ${{ runner.temp }}/registries.conf + run: | + echo 'unqualified-search-registries = []' > "$CONTAINERS_REGISTRIES_CONF" + nix run nixpkgs#skopeo -- login \ + --username "${{ github.actor }}" \ + --password "${{ secrets.GITHUB_TOKEN }}" \ + "${{ env.REGISTRY }}" + nix run nixpkgs#skopeo -- \ + --tmpdir="${{ runner.temp }}" \ + --insecure-policy \ + copy "docker-archive:./bundle-docker" \ + "docker://${{ env.DOCKER_IMAGE }}:${{ steps.ref.outputs.tag }}-nix-${{ matrix.system }}" diff --git a/.github/workflows/paper.yaml b/.github/workflows/paper.yaml index 8a0981a28..b3f9aad34 100644 --- a/.github/workflows/paper.yaml +++ b/.github/workflows/paper.yaml @@ -17,7 +17,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Build draft PDF uses: openjournals/openjournals-draft-action@v1.0 @@ -26,7 +26,7 @@ jobs: paper-path: paper/paper.md - name: Upload - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: paper path: paper/paper.pdf diff --git a/.github/workflows/prepare.yml b/.github/workflows/prepare.yml new file mode 100644 index 000000000..4d195d53d --- /dev/null +++ b/.github/workflows/prepare.yml @@ -0,0 +1,115 @@ +# SPDX-FileCopyrightText: 2026 Institute for Automation of Complex Power Systems, RWTH Aachen University +# SPDX-License-Identifier: Apache-2.0 + +name: Prepare + +on: + workflow_call: + inputs: + docker_image: + required: true + type: string + +env: + REGISTRY: ghcr.io + DOCKER_IMAGE: ${{ inputs.docker_image }} + DOCKER_TAG: ${{ github.sha }} + +permissions: + contents: read + packages: write + +jobs: + prepare-docker: + name: "prepare:docker [${{ matrix.name }}]" + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - name: ubuntu + docker_file: packaging/docker/Dockerfile.ubuntu + image_name: ubuntu + - name: debian + docker_file: packaging/docker/Dockerfile.debian + image_name: debian + - name: rocky + docker_file: packaging/docker/Dockerfile.rocky + image_name: rocky + - name: rocky9 + docker_file: packaging/docker/Dockerfile.rocky9 + image_name: rocky9 + - name: fedora + docker_file: packaging/docker/Dockerfile.fedora + image_name: fedora + - name: fedora-minimal + docker_file: packaging/docker/Dockerfile.fedora-minimal + image_name: fedora-minimal + - name: fedora-vscode + docker_file: packaging/docker/Dockerfile.fedora + image_name: vscode + target: dev-vscode + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + submodules: recursive + + - uses: docker/login-action@v4 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - uses: docker/setup-buildx-action@v4 + + - name: Compute safe ref tag + id: ref + run: echo "tag=${GITHUB_REF_NAME//\//-}" >> "$GITHUB_OUTPUT" + + - name: Compute image content hash + id: hash + run: echo "tag=${{ hashFiles(matrix.docker_file, 'packaging/deps.sh', 'packaging/patches/**', 'packaging/*.patch', 'python/**') }}" >> "$GITHUB_OUTPUT" + + - name: Check for an already-built image + id: existing + run: | + if [ "${{ github.ref_name }}" = "master" ]; then + echo "exists=false" >> "$GITHUB_OUTPUT" + elif docker buildx imagetools inspect \ + "${{ env.DOCKER_IMAGE }}/dev-${{ matrix.image_name }}:${{ steps.hash.outputs.tag }}" >/dev/null 2>&1; then + echo "exists=true" >> "$GITHUB_OUTPUT" + else + echo "exists=false" >> "$GITHUB_OUTPUT" + fi + + - name: Build and push dev image + if: steps.existing.outputs.exists != 'true' + uses: docker/build-push-action@v7 + with: + context: . + file: ${{ matrix.docker_file }} + target: ${{ matrix.target || 'dev' }} + push: true + cache-from: type=gha,scope=dev-${{ matrix.image_name }} + cache-to: type=gha,mode=max,scope=dev-${{ matrix.image_name }} + tags: | + ${{ env.DOCKER_IMAGE }}/dev-${{ matrix.image_name }}:${{ steps.hash.outputs.tag }} + ${{ env.DOCKER_IMAGE }}/dev-${{ matrix.image_name }}:${{ env.DOCKER_TAG }} + ${{ env.DOCKER_IMAGE }}/dev-${{ matrix.image_name }}:${{ steps.ref.outputs.tag }} + + - name: Tag existing image for this commit + if: steps.existing.outputs.exists == 'true' + run: | + docker buildx imagetools create \ + -t "${{ env.DOCKER_IMAGE }}/dev-${{ matrix.image_name }}:${{ env.DOCKER_TAG }}" \ + -t "${{ env.DOCKER_IMAGE }}/dev-${{ matrix.image_name }}:${{ steps.ref.outputs.tag }}" \ + "${{ env.DOCKER_IMAGE }}/dev-${{ matrix.image_name }}:${{ steps.hash.outputs.tag }}" + + - name: Publish fedora dev alias + if: matrix.name == 'fedora' + run: | + docker buildx imagetools create \ + -t "${{ env.DOCKER_IMAGE }}/dev:${{ env.DOCKER_TAG }}" \ + -t "${{ env.DOCKER_IMAGE }}/dev:${{ steps.ref.outputs.tag }}" \ + "${{ env.DOCKER_IMAGE }}/dev-fedora:${{ env.DOCKER_TAG }}" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..d3b7c956f --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,159 @@ +# SPDX-FileCopyrightText: 2026 Institute for Automation of Complex Power Systems, RWTH Aachen University +# SPDX-License-Identifier: Apache-2.0 + +name: Test + +on: + workflow_call: + inputs: + docker_image: + required: true + type: string + +env: + CMAKE_BUILD_OPTS: --parallel $(nproc) + CMAKE_OPTS: -DCMAKE_INSTALL_PREFIX=${PREFIX} + CCACHE: -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + CCACHE_DIR: ${{ github.workspace }}/.ccache + +permissions: + contents: read + packages: read + actions: read + +jobs: + test-python: + name: test:python + runs-on: ubuntu-latest + container: + image: ${{ inputs.docker_image }}/dev-fedora:${{ github.sha }} + options: --user root + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + submodules: recursive + + - name: Run Python tests + run: | + cd python + pytest --verbose . + + - name: Check formatting + run: | + cd python + black --line-length=90 --extend-exclude=".*(\.pyi|_pb2.py)$" --check . + + - name: Lint with flake8 + run: | + cd python + flake8 --max-line-length=90 --extend-exclude="*.pyi,*_pb2.py" . + + - name: Type check with mypy + run: | + cd python + mypy --namespace-packages --explicit-package-bases . + + test-cppcheck: + name: test:cppcheck + runs-on: ubuntu-latest + container: + image: ${{ inputs.docker_image }}/dev-fedora:${{ github.sha }} + options: --user root + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + submodules: recursive + + - name: Run cppcheck + run: ./tools/run-cppcheck.sh | tee cppcheck.log + + - uses: actions/upload-artifact@v7 + if: failure() + with: + name: cppcheck-log + path: cppcheck.log + + test-unit: + name: test:unit + runs-on: ubuntu-latest + container: + image: ${{ inputs.docker_image }}/dev-fedora:${{ github.sha }} + options: --user root + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + submodules: recursive + + - name: Reuse compiled tree from build:source [fedora] + uses: actions/download-artifact@v7 + with: + name: build-fedora + path: build + + - name: ccache + uses: actions/cache/restore@v6 + with: + path: ${{ github.workspace }}/.ccache + key: ccache-fedora-${{ github.sha }} + restore-keys: ccache-fedora- + + - name: Configure + run: cmake -S . -B build ${{ env.CCACHE }} + + - name: Build and run unit tests + run: LD_LIBRARY_PATH=/usr/local/lib64:$LD_LIBRARY_PATH cmake --build build ${{ env.CMAKE_BUILD_OPTS }} --target run-unit-tests run-unit-tests-common + + test-integration: + name: test:integration + runs-on: ubuntu-latest + container: + image: ${{ inputs.docker_image }}/dev-fedora:${{ github.sha }} + options: --user root + services: + rabbitmq: + image: rwthacs/rabbitmq + redis: + image: redis:6.2 + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + submodules: recursive + + - name: Reuse compiled tree from build:source [fedora] + uses: actions/download-artifact@v7 + with: + name: build-fedora + path: build + + - name: ccache + uses: actions/cache/restore@v6 + with: + path: ${{ github.workspace }}/.ccache + key: ccache-fedora-${{ github.sha }} + restore-keys: ccache-fedora- + + - name: Start mosquitto + run: | + echo "127.0.0.1 mosquitto" >> /etc/hosts + cat > /tmp/mosquitto.conf <<'EOF' + listener 1883 + allow_anonymous true + EOF + mosquitto -c /tmp/mosquitto.conf -d + + - name: Configure + run: | + cmake -S . -B build ${{ env.CCACHE }} + cmake --build build --target install + + - name: Build and run integration tests + run: cmake --build build --target run-integration-tests + - uses: actions/upload-artifact@v7 + if: always() + with: + name: integration-tests + path: build/tests/integration/ diff --git a/packaging/docker/Dockerfile.fedora b/packaging/docker/Dockerfile.fedora index 2beee634f..c78a8fca0 100644 --- a/packaging/docker/Dockerfile.fedora +++ b/packaging/docker/Dockerfile.fedora @@ -6,7 +6,8 @@ ARG DISTRO=fedora ARG FEDORA_VERSION=42 -ARG REF=unknown +ARG ARCH=x86_64 +ARG REF=villasnode FROM ${DISTRO}:${FEDORA_VERSION} AS dev @@ -15,7 +16,7 @@ ARG DISTRO # Toolchain RUN dnf -y install \ gcc-14 g++-14 \ - pkgconfig cmake make meson ninja \ + pkgconfig cmake make meson ninja ccache \ autoconf automake autogen libtool \ texinfo git awk git-svn curl tar patchutils \ flex bison \ @@ -111,6 +112,10 @@ RUN groupadd --gid $USER_GID $USERNAME \ FROM dev AS app ARG CMAKE_OPTS +ARG DISTRO +ARG FEDORA_VERSION +ARG ARCH +ARG REF COPY . /villas/ diff --git a/packaging/docker/Dockerfile.ubuntu b/packaging/docker/Dockerfile.ubuntu index 5a4a0c506..76c21e2a7 100644 --- a/packaging/docker/Dockerfile.ubuntu +++ b/packaging/docker/Dockerfile.ubuntu @@ -15,6 +15,11 @@ ARG DISTRO ENV DEBIAN_FRONTEND=noninteractive +# Prefer the Azure mirror (archive.ubuntu.com throttles from CI); default kept as fallback +RUN sed -i -E \ + 's#https?://(archive|security)\.ubuntu\.com/ubuntu/?#http://azure.archive.ubuntu.com/ubuntu/ &#g' \ + /etc/apt/sources.list.d/ubuntu.sources 2>/dev/null || true + # Toolchain RUN apt-get update && \ apt-get install -y \