diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 649a694..87034f7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,15 +10,18 @@ jobs: Build: name: 'Build Package' runs-on: ubuntu-latest + permissions: + contents: 'read' + id-token: 'write' env: RELEASE_VERSION: ${{ inputs.RELEASE_VERSION }} steps: - name: Checkout Repository uses: actions/checkout@v4 with: - token: ${{ secrets.ULTIMAKER_CI_PAT }} + token: ${{ secrets.ULTIMAKER_CI_PAT }} # TODO: Replace with GITHUB_TOKEN - See https://ultimaker.atlassian.net/browse/EMB-120 submodules: recursive - + - name: Build Package id: build run: | @@ -27,30 +30,14 @@ jobs: # an encrypted firmware module using Ultimaker/umlicensemanager. # Other firmware components ignore this. export BUILDTYPE=PRODUCTION - # Encryption key for UMMOD file in "PRODUCTION" build mode. - # See umlicensemanager documentation - export UMLM_ENCRYPTION_KEY="${{ secrets.UMLM_ENCRYPTION_KEY }}" - export RELEASE_VERSION="${{ env.RELEASE_VERSION }}" echo "RELEASE_VERSION: ${RELEASE_VERSION}" - ./build_for_ultimaker.sh -a build - + # Pass encryption key directly to the build script, not as global export + UMLM_ENCRYPTION_KEY="${{ secrets.UMLM_ENCRYPTION_KEY }}" ./build_for_ultimaker.sh -a build + - name: Upload Artifact (Built package) uses: actions/upload-artifact@v4 with: name: build-package path: "./*.deb" - - - name: Dump GitHub context - if: ${{ always() }} - env: - GITHUB_CONTEXT: ${{ toJson(github) }} - JOB_CONTEXT: ${{ toJson(job) }} - STEPS_CONTEXT: ${{ toJson(steps) }} - RUNNER_CONTEXT: ${{ toJson(runner) }} - run: | - echo "${GITHUB_CONTEXT}" - echo "${JOB_CONTEXT}" - echo "${STEPS_CONTEXT}" - echo "${RUNNER_CONTEXT}" diff --git a/.github/workflows/prepare_env.yml b/.github/workflows/prepare_env.yml index d38504d..e3d0bd5 100644 --- a/.github/workflows/prepare_env.yml +++ b/.github/workflows/prepare_env.yml @@ -2,130 +2,87 @@ on: workflow_call: inputs: BUILD_DOCKER_CACHE: - description: "If true, will build the docker image cache" + description: 'If true, will build the docker image cache' type: boolean required: false default: false + EMBEDDED_WORKFLOWS_BRANCH: + description: 'Branch to checkout embedded-workflows repo' + type: string + required: false + default: 'EMB-115_migrate_to_gar' # TODO: Change to `main` once merged outputs: RELEASE_VERSION: description: "The package version for this build" value: ${{ jobs.Prepare.outputs.RELEASE_VERSION }} RELEASE_REPO: - description: "The Cloudsmith repository to release the package" + description: "The GAR repository to release the package" value: ${{ jobs.Prepare.outputs.RELEASE_REPO }} env: # All branches starting with any of the items in the list below will be # considered a master branch, e.g. "master/s-line" starts with "master" # from the list below and it is then a master branch. MASTER_BRANCH_LIST: "main master stable" - VERSION_REGEX: 'v[0-9]{1,4}\.[0-9]{1,4}\.[0-9]{1,9}(-(dev|[0-9]{1,3}))?' - + # Regex to capture full prerelease identifiers including numeric suffixes + # Examples: v1.2.3, v1.2.3-alpha, v1.2.3-beta.1, v1.2.3-rc.2 + VERSION_REGEX: 'v[0-9]{1,4}\.[0-9]{1,4}\.[0-9]{1,9}(-(alpha|beta|rc)(\.?[0-9]+)?)?' + jobs: Prepare: name: 'Prepare Environment' runs-on: ubuntu-latest + permissions: + contents: 'read' + id-token: 'write' outputs: RELEASE_VERSION: ${{ steps.vars.outputs.RELEASE_VERSION }} RELEASE_REPO: ${{ steps.vars.outputs.RELEASE_REPO }} - env: - CURRENT_BRANCH_REF: ${{ github.ref_type == 'branch' && github.ref || github.event.base_ref }} steps: + - name: Checkout Repository + uses: actions/checkout@v4 + with: + token: ${{ secrets.ULTIMAKER_CI_PAT }} # TODO: Replace with GITHUB_TOKEN - See https://ultimaker.atlassian.net/browse/EMB-120 + fetch-depth: 0 # Full history needed for git describe + submodules: recursive # Needed for docker cache build + + - name: Checkout Embedded-workflows repo + uses: actions/checkout@v4 + with: + repository: Ultimaker/embedded-workflows + path: embedded-workflows + ref: ${{ inputs.EMBEDDED_WORKFLOWS_BRANCH }} + - name: Generate Variables id: vars run: | - echo "GITHUB_REF ${GITHUB_REF}" - echo "GITHUB_REF_NAME ${GITHUB_REF_NAME}" - echo "GITHUB_REF_TYPE: ${GITHUB_REF_TYPE}" - RELEASE_VERSION="999.999.999" - RELEASE_REPO="none" - - # Check if this is a commit/tag in the master branch - IS_MASTER_BRANCH="no" - CURRENT_BRANCH="${CURRENT_BRANCH_REF##refs/heads/}" - for master_branch in $MASTER_BRANCH_LIST; do - # Try to remove "master_branch" from "CURRENT_BRANCH" and check if it changes - if [[ "${CURRENT_BRANCH##${master_branch}}" != "${CURRENT_BRANCH}" ]]; then - IS_MASTER_BRANCH="yes" - break - fi; - done; - echo "CURRENT_BRANCH: ${CURRENT_BRANCH}" - echo "IS_MASTER_BRANCH: ${IS_MASTER_BRANCH}" + set -euo pipefail - # Check what triggered this action: A Pull Request, a tag push or branch push - TRIGGER="pull_request" - if [[ "${GITHUB_EVENT_NAME}" == "push" ]]; then - if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then - TRIGGER="tag" - else - TRIGGER="branch" - fi - fi + # Use dedicated script for version generation + # This script can be tested locally and reused across workflows + VERSION_OUTPUT=$(./embedded-workflows/scripts/generate_semver_version.sh) - echo "TRIGGER: ${TRIGGER}" + # Parse output and set GitHub outputs + RELEASE_VERSION=$(echo "$VERSION_OUTPUT" | grep "^RELEASE_VERSION=" | cut -d= -f2) + RELEASE_REPO=$(echo "$VERSION_OUTPUT" | grep "^RELEASE_REPO=" | cut -d= -f2) - if [[ "${TRIGGER}" == "branch" && "${IS_MASTER_BRANCH}" == "yes" ]]; then - echo "This is a merge to master, lets make the Nightly Release" - - # Lets prepare the package name from the branch name. There are a few rules for debian package version and we must follow them: - # - Only lowercase letters: So lets convert all uppercase letter to lower case: - NIGHTLY_PACKAGE_VERSION_SUFFIX="${GITHUB_REF_NAME@L}" - - # - Other accepted chars: numbers (0-9), '.', '+' and '-'. Lets convert all other chars to '-': - NIGHTLY_PACKAGE_VERSION_SUFFIX=${NIGHTLY_PACKAGE_VERSION_SUFFIX//[^0-9a-z+.-]/-} - - echo "Nightly Package Version Suffix: '${NIGHTLY_PACKAGE_VERSION_SUFFIX}'" - RELEASE_REPO="nightly-builds" - RELEASE_VERSION="$(date +%Y.%m.%d)-merge-${NIGHTLY_PACKAGE_VERSION_SUFFIX}" - - elif [[ "${TRIGGER}" == "tag" ]]; then - echo "This is a tag push, lets parse the tag >${GITHUB_REF_NAME}< and check if we should release" - VERSION=$(echo "${GITHUB_REF_NAME}" | grep -o -E -e "${VERSION_REGEX}") || true # Return true if grep finds nothing - RELEASE_VERSION="${VERSION#v}" # Remove the initial "v" leaving only the numbers and optional "-dev" - - if [[ -z "${RELEASE_VERSION}" ]]; then - echo "Failed to parse the tag, it does not follow the standard" - elif [[ "${RELEASE_VERSION//dev/}" == "${RELEASE_VERSION}" ]]; then ## True if there is no "dev" in the tag - echo "Success, this is a official release" - RELEASE_REPO="packages-released" - else - echo "Success, this is a development release" - RELEASE_REPO="packages-dev" - fi - fi - - if [[ "${RELEASE_REPO}" == "none" ]]; then - echo "This is an ordinary commit, do not release" - RELEASE_VERSION="999.999.999" - fi - - echo "RELEASE_VERSION: ${RELEASE_VERSION}" - echo "RELEASE_REPO: ${RELEASE_REPO}" echo RELEASE_VERSION="${RELEASE_VERSION}" >> $GITHUB_OUTPUT echo RELEASE_REPO="${RELEASE_REPO}" >> $GITHUB_OUTPUT - - name: Checkout Repository - if: ${{ inputs.BUILD_DOCKER_CACHE }} - uses: actions/checkout@v4 - with: - token: ${{ secrets.ULTIMAKER_CI_PAT }} - submodules: recursive + - name: Generate Summary + run: | + set -euo pipefail + + echo "# Environment Preparation Summary" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "## Release Information" >> "$GITHUB_STEP_SUMMARY" + echo "- **Version**: \`${{ steps.vars.outputs.RELEASE_VERSION }}\`" >> "$GITHUB_STEP_SUMMARY" + echo "- **Repository**: \`${{ steps.vars.outputs.RELEASE_REPO }}\`" >> "$GITHUB_STEP_SUMMARY" + echo "- **Trigger**: \`${GITHUB_EVENT_NAME}\`" >> "$GITHUB_STEP_SUMMARY" + echo "- **Ref Type**: \`${GITHUB_REF_TYPE}\`" >> "$GITHUB_STEP_SUMMARY" + echo "- **Ref Name**: \`${GITHUB_REF_NAME}\`" >> "$GITHUB_STEP_SUMMARY" - name: "Build Docker Image Cache" if: ${{ inputs.BUILD_DOCKER_CACHE }} run: | echo "${{ secrets.ULTIMAKER_CI_PAT }}" | docker login ghcr.io -u $ --password-stdin ./build_for_ultimaker.sh -a build_docker_cache - - - name: Dump GitHub context - if: ${{ always() }} - env: - GITHUB_CONTEXT: ${{ toJson(github) }} - JOB_CONTEXT: ${{ toJson(job) }} - STEPS_CONTEXT: ${{ toJson(steps) }} - RUNNER_CONTEXT: ${{ toJson(runner) }} - run: | - echo "${GITHUB_CONTEXT}" - echo "${JOB_CONTEXT}" - echo "${STEPS_CONTEXT}" - echo "${RUNNER_CONTEXT}" diff --git a/.github/workflows/release_docker_img.yml b/.github/workflows/release_docker_img.yml index bee968a..39dfa70 100644 --- a/.github/workflows/release_docker_img.yml +++ b/.github/workflows/release_docker_img.yml @@ -17,23 +17,26 @@ on: env: DOCKER_IMAGE_NAME: ghcr.io/ultimaker/${{ inputs.DOCKER_IMAGE_NAME }} DOCKER_TAG_PREFIX: ${{ inputs.DOCKER_TAG_PREFIX }} - + jobs: Build: name: 'Build and Release Docker Image' runs-on: ubuntu-latest + permissions: + contents: 'read' + id-token: 'write' steps: - name: Checkout Repository uses: actions/checkout@v4 with: - token: ${{ secrets.ULTIMAKER_CI_PAT }} + token: ${{ secrets.ULTIMAKER_CI_PAT }} # TODO: Replace with GITHUB_TOKEN - See https://ultimaker.atlassian.net/browse/EMB-120 submodules: recursive - name: Login in GitHub Container Registry id: ghcr_login run: | echo "${{ secrets.ULTIMAKER_CI_PAT }}" | docker login ghcr.io -u $ --password-stdin - + - name: Build Docker Image id: build run: | @@ -55,11 +58,3 @@ jobs: echo "DOCKER_IMAGE_NAME: ${DOCKER_IMAGE_NAME}" echo "DOCKER_IMAGE_VERSION: ${DOCKER_IMAGE_VERSION}" docker push "${DOCKER_IMAGE_NAME}:${DOCKER_IMAGE_VERSION}" - - - name: Dump GitHub context - if: ${{ always() }} - run: | - echo "${{ toJson(github) }}" - echo "${{ toJson(steps) }}" - echo "${{ toJson(runner) }}" - diff --git a/.github/workflows/release_pkg.yml b/.github/workflows/release_pkg.yml index aa156d0..a8c97b9 100644 --- a/.github/workflows/release_pkg.yml +++ b/.github/workflows/release_pkg.yml @@ -2,7 +2,7 @@ on: workflow_call: inputs: RELEASE_REPO: - description: "The Cloudsmith repository to release the package" + description: "The GAR repository to release the package" required: true type: string @@ -10,51 +10,137 @@ jobs: Release_Package: name: 'Release Package' runs-on: ubuntu-latest + permissions: + contents: 'read' + id-token: 'write' env: - RELEASE_REPO: ${{ inputs.RELEASE_REPO }} + EMB_APT_REPO: ${{ inputs.RELEASE_REPO }} + EMB_APT_SERVICE_ACCOUNT: ${{ vars.EMB_APT_SERVICE_ACCOUNT }} + EMB_GCP_LOCATION: ${{ vars.EMB_GCP_LOCATION }} + EMB_GCP_PROJECT: ${{ vars.EMB_GCP_PROJECT }} + EMB_WI_PROVIDER: ${{ vars.EMB_WI_PROVIDER }} + steps: - - name: Cloning Embedded-Workflows on default branch - uses: actions/checkout@v4 - with: - repository: Ultimaker/embedded-workflows - token: ${{ secrets.ULTIMAKER_CI_PAT }} - fetch-depth: 0 - path: embedded-workflows - - uses: actions/download-artifact@v4 with: name: build-package - - - name: Display structure of downloaded files - shell: bash - run: ls -lh - - - name: Build Cloudsmith Docker image - shell: bash + + - id: auth + name: 'Authenticate to Google Cloud' + uses: 'google-github-actions/auth@v2' + with: + token_format: 'access_token' + workload_identity_provider: ${{ env.EMB_WI_PROVIDER }} + service_account: ${{ env.EMB_APT_SERVICE_ACCOUNT }} + + - name: 'Set up Cloud SDK' + uses: 'google-github-actions/setup-gcloud@v2' + + - name: 'Extract package metadata' + id: meta run: | - docker build embedded-workflows/Dockerfiles/ -f embedded-workflows/Dockerfiles/cloudsmith.Dockerfile -t cloudsmith-cli:latest - - - name: Upload Debian Packages - shell: bash + set -euo pipefail + # Find all .deb files and store the first one for summary + DEB_FILE=$(find . -name "*.deb" -print -quit) + if [[ -z "${DEB_FILE:-}" ]]; then + echo "No .deb file found in ." >&2 + exit 1 + fi + echo "deb_file=${DEB_FILE}" >> "$GITHUB_OUTPUT" + + # Extract package name and version from filename: _-.deb or _.deb + DEB_BASENAME=$(basename "$DEB_FILE") + PKG_NAME=${DEB_BASENAME%%_*} + PKG_VERSION_PART=${DEB_BASENAME#*_} + PKG_VERSION=${PKG_VERSION_PART%%.deb} + echo "pkg_name=${PKG_NAME}" >> "$GITHUB_OUTPUT" + echo "pkg_version=${PKG_VERSION}" >> "$GITHUB_OUTPUT" + + - name: 'Publish to GAR' + id: publish run: | + set -euo pipefail + # Upload all .deb packages found in . for package in *.deb; do - echo " " - echo "########## Uploading ${package} ##########" - echo " " - docker run -v "$(pwd):/build" cloudsmith-cli:latest cloudsmith push deb ultimaker/${{ env.RELEASE_REPO }}/debian/buster /build/${package} -k ${{ secrets.CLOUDSMITH_API_KEY }} - docker run -v "$(pwd):/build" cloudsmith-cli:latest cloudsmith push deb ultimaker/${{ env.RELEASE_REPO }}/debian/bookworm /build/${package} -k ${{ secrets.CLOUDSMITH_API_KEY }} - done; - - - name: Dump GitHub context - if: ${{ always() }} + if [[ -f "$package" ]]; then + echo "Uploading ${package} to GAR..." + gcloud artifacts apt upload "$EMB_APT_REPO" \ + --project="$EMB_GCP_PROJECT" \ + --location="$EMB_GCP_LOCATION" \ + --source="$package" + fi + done + + - name: 'Publish summary' + if: ${{ success() }} env: - GITHUB_CONTEXT: ${{ toJson(github) }} - JOB_CONTEXT: ${{ toJson(job) }} - STEPS_CONTEXT: ${{ toJson(steps) }} - RUNNER_CONTEXT: ${{ toJson(runner) }} - shell: bash + DEB_FILE: ${{ steps.meta.outputs.deb_file }} + PKG_NAME: ${{ steps.meta.outputs.pkg_name }} + PKG_VERSION: ${{ steps.meta.outputs.pkg_version }} run: | - echo "${GITHUB_CONTEXT}" - echo "${JOB_CONTEXT}" - echo "${STEPS_CONTEXT}" - echo "${RUNNER_CONTEXT}" + set -euo pipefail + + echo "# Release summary" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "Published .deb package to Google Artifact Registry (APT):" >> "$GITHUB_STEP_SUMMARY" + echo "- File: \`${DEB_FILE}\`" >> "$GITHUB_STEP_SUMMARY" + echo "- Package: \`${PKG_NAME}\`" >> "$GITHUB_STEP_SUMMARY" + echo "- Version: \`${PKG_VERSION}\`" >> "$GITHUB_STEP_SUMMARY" + echo "- Repository: \`${EMB_APT_REPO}\`" >> "$GITHUB_STEP_SUMMARY" + echo "- Project: \`${EMB_GCP_PROJECT}\`" >> "$GITHUB_STEP_SUMMARY" + echo "- Location: \`${EMB_GCP_LOCATION}\`" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "## Distribution Support" >> "$GITHUB_STEP_SUMMARY" + echo "This package supports multiple Debian distributions. The distribution is determined by the package metadata set during the build process. Supported distributions are:" >> "$GITHUB_STEP_SUMMARY" + echo "- \`buster\` (Debian 10)" >> "$GITHUB_STEP_SUMMARY" + echo "- \`bookworm\` (Debian 12)" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "The GAR repository automatically organizes packages by distribution based on the metadata in the .deb file. See **Build Configuration** below for how to set distribution metadata." >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "## How to use" >> "$GITHUB_STEP_SUMMARY" + echo "You can install from the GAR APT repository using the following steps on Ubuntu/Debian:" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "1. Install the Artifact Registry APT transport helper:" >> "$GITHUB_STEP_SUMMARY" + echo '```bash' >> "$GITHUB_STEP_SUMMARY" + echo "sudo apt-get update" >> "$GITHUB_STEP_SUMMARY" + echo "sudo apt-get install -y artifact-registry-apt-transport" >> "$GITHUB_STEP_SUMMARY" + echo '```' >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "2. Authenticate with gcloud and configure your project (if needed):" >> "$GITHUB_STEP_SUMMARY" + echo '```bash' >> "$GITHUB_STEP_SUMMARY" + echo "gcloud auth login" >> "$GITHUB_STEP_SUMMARY" + echo "gcloud config set project ${EMB_GCP_PROJECT}" >> "$GITHUB_STEP_SUMMARY" + echo '```' >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "3. Add the GAR APT repository (replace \`buster\` with your target distribution):" >> "$GITHUB_STEP_SUMMARY" + echo '```bash' >> "$GITHUB_STEP_SUMMARY" + echo "echo 'deb [signed-by=/usr/share/keyrings/artifact-registry-keyring.gpg] https://${EMB_GCP_LOCATION}-apt.pkg.dev/${EMB_GCP_PROJECT}/${EMB_APT_REPO} buster main' | sudo tee /etc/apt/sources.list.d/${EMB_APT_REPO}.list" >> "$GITHUB_STEP_SUMMARY" + echo '```' >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "4. Update and install:" >> "$GITHUB_STEP_SUMMARY" + echo '```bash' >> "$GITHUB_STEP_SUMMARY" + echo "sudo apt-get update" >> "$GITHUB_STEP_SUMMARY" + echo "sudo apt-get install -y ${PKG_NAME}" >> "$GITHUB_STEP_SUMMARY" + echo '```' >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "5. Alternatively, install directly from the downloaded .deb file:" >> "$GITHUB_STEP_SUMMARY" + echo '```bash' >> "$GITHUB_STEP_SUMMARY" + echo "sudo apt-get install -y ./${PKG_NAME}_${PKG_VERSION}.deb" >> "$GITHUB_STEP_SUMMARY" + echo '```' >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "## Build Configuration" >> "$GITHUB_STEP_SUMMARY" + echo "The distribution metadata for the Debian package is set during the build process. This ensures the package is automatically organized by distribution in the GAR repository." >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "### Setting Distribution in CMake (CPack)" >> "$GITHUB_STEP_SUMMARY" + echo "If using CMake with CPack, set the distribution in your \`CMakeLists.txt\`:" >> "$GITHUB_STEP_SUMMARY" + echo '```cmake' >> "$GITHUB_STEP_SUMMARY" + echo 'set(CPACK_DEBIAN_PACKAGE_DISTRIBUTION "buster") # or "bookworm"' >> "$GITHUB_STEP_SUMMARY" + echo '```' >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "### Setting Distribution in Debian Control File" >> "$GITHUB_STEP_SUMMARY" + echo "Add the distribution to your \`debian/control\` file:" >> "$GITHUB_STEP_SUMMARY" + echo '```' >> "$GITHUB_STEP_SUMMARY" + echo "Distribution: buster" >> "$GITHUB_STEP_SUMMARY" + echo '```' >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "The package metadata is embedded in the .deb file and used by GAR to automatically organize packages by distribution, eliminating the need for manual distribution tagging during upload." >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml index ccf4faa..e1148da 100644 --- a/.github/workflows/shellcheck.yml +++ b/.github/workflows/shellcheck.yml @@ -1,6 +1,6 @@ on: workflow_call: - + jobs: Shellcheck: name: 'Shell Check' @@ -9,10 +9,10 @@ jobs: - name: Checkout Repository uses: actions/checkout@v4 with: - token: ${{ secrets.ULTIMAKER_CI_PAT }} + token: ${{ secrets.ULTIMAKER_CI_PAT }} # TODO: Replace with GITHUB_TOKEN - See https://ultimaker.atlassian.net/browse/EMB-120 submodules: recursive fetch-depth: 0 - + - name: Shell Check id: shellcheck run: | diff --git a/.github/workflows/unit_test.yml b/.github/workflows/unit_test.yml index 7b44ae8..43e045e 100644 --- a/.github/workflows/unit_test.yml +++ b/.github/workflows/unit_test.yml @@ -5,27 +5,18 @@ jobs: Unit_Test: name: 'Unit Test' runs-on: ubuntu-latest + permissions: + contents: 'read' + id-token: 'write' + steps: - name: Checkout Repository uses: actions/checkout@v4 with: token: ${{ secrets.ULTIMAKER_CI_PAT }} submodules: recursive - + - name: Unit Test id: unittest run: | ./build_for_ultimaker.sh -a unittest - - - name: Dump GitHub context - if: ${{ always() }} - env: - GITHUB_CONTEXT: ${{ toJson(github) }} - JOB_CONTEXT: ${{ toJson(job) }} - STEPS_CONTEXT: ${{ toJson(steps) }} - RUNNER_CONTEXT: ${{ toJson(runner) }} - run: | - echo "${GITHUB_CONTEXT}" - echo "${JOB_CONTEXT}" - echo "${STEPS_CONTEXT}" - echo "${RUNNER_CONTEXT}" diff --git a/Dockerfiles/clang-format/13/Dockerfile b/Dockerfiles/clang-format/13/Dockerfile index 2d8f778..d2525f4 100644 --- a/Dockerfiles/clang-format/13/Dockerfile +++ b/Dockerfiles/clang-format/13/Dockerfile @@ -16,4 +16,10 @@ RUN apt-get update && \ # clang-format with pip RUN pip3 install clang-format==13.0.0 +# Create non-privileged user for security +RUN useradd -m -u 1000 -s /bin/bash builder && \ + chown -R builder:builder /work + +USER builder + COPY Dockerfile Dockerfile diff --git a/Dockerfiles/clang-tidy/11/Dockerfile b/Dockerfiles/clang-tidy/11/Dockerfile index 465eb97..994d91f 100644 --- a/Dockerfiles/clang-tidy/11/Dockerfile +++ b/Dockerfiles/clang-tidy/11/Dockerfile @@ -11,4 +11,10 @@ RUN apt-get update && \ rm -rf /var/lib/apt/* && \ ln -s /usr/bin/clang-tidy-11 /usr/bin/clang-tidy +# Create non-privileged user for security +RUN useradd -m -u 1000 -s /bin/bash builder && \ + chown -R builder:builder /work + +USER builder + COPY Dockerfile Dockerfile diff --git a/Dockerfiles/clang-tidy/14/Dockerfile b/Dockerfiles/clang-tidy/14/Dockerfile index 2af4230..d79dab0 100644 --- a/Dockerfiles/clang-tidy/14/Dockerfile +++ b/Dockerfiles/clang-tidy/14/Dockerfile @@ -11,4 +11,10 @@ RUN apt-get update && \ rm -rf /var/lib/apt/* && \ ln -s /usr/bin/clang-tidy-14 /usr/bin/clang-tidy +# Create non-privileged user for security +RUN useradd -m -u 1000 -s /bin/bash builder && \ + chown -R builder:builder /work + +USER builder + COPY Dockerfile Dockerfile diff --git a/Dockerfiles/cloudsmith.Dockerfile b/Dockerfiles/cloudsmith.Dockerfile deleted file mode 100644 index 1c88b41..0000000 --- a/Dockerfiles/cloudsmith.Dockerfile +++ /dev/null @@ -1,9 +0,0 @@ -FROM python:3.12.2-slim-bookworm - -LABEL Description="Image with Cloudsmith CLI tool" - -ENV PIP_REQUEREMENTS \ - cloudsmith-cli - -# install any python requirements -RUN pip3 install ${PIP_REQUEREMENTS} diff --git a/Dockerfiles/cppcheck/2.3/Dockerfile b/Dockerfiles/cppcheck/2.3/Dockerfile index 4d5963b..7530a99 100644 --- a/Dockerfiles/cppcheck/2.3/Dockerfile +++ b/Dockerfiles/cppcheck/2.3/Dockerfile @@ -10,4 +10,10 @@ RUN apt-get update && \ rm -rf /var/cache/apt/* && \ rm -rf /var/lib/apt/* +# Create non-privileged user for security +RUN useradd -m -u 1000 -s /bin/bash builder && \ + chown -R builder:builder /work + +USER builder + COPY Dockerfile Dockerfile diff --git a/scripts/generate_semver_version.md b/scripts/generate_semver_version.md new file mode 100644 index 0000000..9c7b642 --- /dev/null +++ b/scripts/generate_semver_version.md @@ -0,0 +1,446 @@ +# SemVer Version Generation Scripts + +This directory contains scripts for generating Semantic Versioning 2.0 compliant version numbers based on git history and workflow context. + +## Understanding Semantic Versioning 2.0 + +[Semantic Versioning 2.0](https://semver.org/) is a versioning scheme that conveys meaning about the underlying changes. + +### Version Format + +``` +major.minor.patch-prerelease+build_metadata +``` + +**Core Version** (`major.minor.patch`): +- **MAJOR**: Incremented for incompatible API changes +- **MINOR**: Incremented for backward-compatible new functionality +- **PATCH**: Incremented for backward-compatible bug fixes + +**Prerelease** (`-alpha`, `-beta`, `-rc`): +- Optional identifier indicating the software is not yet stable +- Prerelease versions have **lower precedence** than the associated release +- Examples: `1.0.0-alpha.1`, `1.0.0-beta.2`, `1.0.0-rc.1` + +**Build Metadata** (`+sha`, `+date.branch.sha`): +- Optional metadata for traceability (commit SHA, build date, etc.) +- Ignored when determining version precedence +- Examples: `1.0.0+abc1234`, `1.0.0-beta.1+20260118.main.abc1234` + +### Prerelease Types and When to Use Them + +This project follows a standard prerelease progression: + +**`alpha`** → **`beta`** → **`rc`** (release candidate) → **release** + +#### Alpha (α) +- **Purpose**: Early testing, unstable, may have incomplete features +- **Audience**: Internal developers, early testers, continuous integration +- **Changes**: Frequent breaking changes expected +- **Example**: `1.2.0-alpha.0+abc1234` or `1.2.4-alpha.0+20260118.main.abc1234` +- **When generated**: + - Pull requests and feature branch commits + - **Automatic nightly builds from master/main branch merges** (with date and branch metadata) + - Invalid or unparseable tags + - Default for any commit not on a release branch +- **Note**: Auto-generated alpha versions use a naive patch bump (e.g., `v1.2.3` → `1.2.4-alpha.0`), regardless of whether the actual changes warrant a major or minor version increment. This is intentional - nightly builds from master represent ongoing development, not stabilization. For intentional version control or beta/RC releases, create an explicit git tag. + +#### Beta (β) +- **Purpose**: Feature-complete but may have bugs, ready for wider testing +- **Audience**: Beta testers, QA teams +- **Changes**: API should be stable, only bug fixes and minor tweaks +- **Example**: `1.2.0-beta.1` +- **When generated**: + - **Explicit release branches only**: Tags like `v1.2.0-beta` or `v1.2.0-beta.1` + - **NOT from automatic master/main merges** - Those use alpha instead +- **Note**: Beta versions require explicit git tags on release branches. Auto-generated builds from master/main branch merges use alpha (see below) to indicate ongoing development, not stabilization. + +#### RC (Release Candidate) +- **Purpose**: Potentially final version, undergoing final validation +- **Audience**: QA teams, staging environments +- **Changes**: Only critical bug fixes, no new features +- **Example**: `1.2.0-rc.1+abc1234` +- **When generated**: + - Tags like `v1.2.0-rc.1`, `v1.2.0-rc.2` + - Indicates release is imminent + +#### Release +- **Purpose**: Stable, production-ready version +- **Audience**: All users, production environments +- **Changes**: No breaking changes within same major version +- **Example**: `1.2.0+abc1234` +- **When generated**: + - Tags like `v1.2.0`, `v2.0.0` (no prerelease suffix) + +### Version Precedence Examples + +According to SemVer 2.0, versions are compared as follows: + +``` +1.0.0-alpha.0 < 1.0.0-alpha.1 < 1.0.0-beta.0 < 1.0.0-beta.1 < 1.0.0-rc.1 < 1.0.0 < 1.0.1 +``` + +Key points: +- Prerelease versions always sort **before** the associated release +- Numeric suffixes are compared numerically: `alpha.0` < `alpha.1` < `alpha.10` +- Build metadata (`+...`) does **not** affect precedence + +## Scripts + +### `generate_semver_version.sh` + +Main script that generates version numbers following SemVer 2.0 format. + +#### Usage + +```bash +./generate_semver_version.sh +``` + +The script reads environment variables and outputs version information to stdout in `key=value` format: + +``` +RELEASE_VERSION=1.2.4-alpha.0+abc1234 +RELEASE_REPO=packages-dev +``` + +#### Environment Variables + +Required for GitHub Actions context: +- `GITHUB_EVENT_NAME` - Type of GitHub event (push, pull_request, etc.) +- `GITHUB_REF_TYPE` - Type of ref (tag, branch) +- `GITHUB_REF_NAME` - Name of the ref (tag name or branch name) +- `GITHUB_REF` - Full ref (refs/heads/main, refs/tags/v1.0.0) +- `GITHUB_EVENT_BASE_REF` - Base ref for pull requests + +Optional: +- `MASTER_BRANCH_LIST` - Space-separated list of master branch prefixes (default: "main master stable") + +#### Version Generation Logic + +The script automatically determines the appropriate version based on the Git context: + +| Trigger | Condition | Version Format | Example | Repository | Use Case | +|---------|-----------|----------------|---------|------------|----------| +| Pull Request | Any feature branch | `X.Y.Z-alpha.0+sha` | `1.2.4-alpha.0+abc1234` | `packages-dev` | Development testing | +| Push | Master/main branch | `X.Y.Z-alpha.0+YYYYMMDD.branch.sha` | `1.2.4-alpha.0+20260118.main.abc1234` | `nightly-builds` | Nightly/continuous builds | +| Tag | `vX.Y.Z-alpha` | `X.Y.Z-alpha` | `1.2.3-alpha` | `packages-dev` | Early alpha release | +| Tag | `vX.Y.Z-alpha.N` | `X.Y.Z-alpha.N` | `1.2.3-alpha.2` | `packages-dev` | Specific alpha iteration | +| Tag | `vX.Y.Z-beta` | `X.Y.Z-beta` | `1.2.3-beta` | `packages-dev` | Beta release (release branch) | +| Tag | `vX.Y.Z-beta.N` | `X.Y.Z-beta.N` | `1.2.3-beta.1` | `packages-dev` | Specific beta iteration (release branch) | +| Tag | `vX.Y.Z-rc.N` | `X.Y.Z-rc.N` | `1.2.3-rc.1` | `packages-dev` | Release candidate | +| Tag | `vX.Y.Z` | `X.Y.Z` | `1.2.3` | `packages-released` | Official release | +| Invalid tag | Any unparseable tag | `X.Y.Z-alpha.0+sha` | `1.2.4-alpha.0+abc1234` | `packages-dev` | Fallback for invalid tags | + +**Note**: `X.Y.Z` is automatically determined by finding the latest reachable semver tag and bumping the patch version. + +⚠️ **Version Bumping Strategy**: During development (PRs, feature branches, and master merges), this script performs a **naive patch version bump** from the latest tag. This means even if you're working on a major or minor feature release, the auto-generated version will only bump the patch number (e.g., `v1.2.3` → `1.2.4-alpha.0+sha`). This is a limitation of the automated approach - the script cannot determine the context or scope of the work being done. For proper major/minor version increments, you must create an explicit git tag with the desired version number. + +## Release Workflow: When to Use Alpha vs Beta + +To understand when versions are alpha vs beta, think about the **development stage**: + +``` +Development Branch/PR → Nightly Master Merge → Release Branch/Tag + (alpha) (alpha) (beta/rc/release) +``` + +### Development Stage Progression + +1. **Feature Development** (`alpha`) + - Feature branches, pull requests + - Any commit not on master/release branch + - Version: `X.Y.Z-alpha.0+sha` + - Repository: `packages-dev` + - When to use: Normal development work + +2. **Nightly/Continuous Builds** (`alpha`) + - Automatic builds when PR is merged to master/main + - Represents latest development state + - Version: `X.Y.Z-alpha.0+YYYYMMDD.branch.sha` + - Repository: `nightly-builds` + - When to use: Integration testing, CI validation + - **Note**: Still alpha because master is the development branch, not a release candidate + +3. **Beta/Release Preparation** (`beta`, `rc`) + - **Only happens via explicit git tags** on release branches + - Beta: `vX.Y.Z-beta.1` → `X.Y.Z-beta.1` + - RC: `vX.Y.Z-rc.1` → `X.Y.Z-rc.1` + - Repository: `packages-dev` + - When to use: When preparing for a release, create a release branch and tag it with the desired version + +4. **Official Release** (`release`) + - **Only happens via explicit git tag** with no prerelease suffix + - Version: `vX.Y.Z` → `X.Y.Z` + - Repository: `packages-released` + - When to use: When release is validated and approved + +#### Workflow Decision Tree + +```mermaid +graph TD + A[Git Event: push/PR/tag] --> B{Event Type?} + + B -->|Pull Request| C[PR/Feature Branch] + B -->|Push to Branch| D{Master Branch?} + B -->|Tag Push| E{Valid Tag?} + + C --> F[Generate: X.Y.Z-alpha.0+sha] + F --> G[Repository: packages-dev] + + D -->|Yes| H[Master Merge] + D -->|No| C + + H --> I[Generate: X.Y.Z-alpha.0+YYYYMMDD.branch.sha] + I --> J[Repository: nightly-builds] + + E -->|No - Invalid| K[Fallback to alpha] + K --> F + + E -->|Yes| L{Tag Type?} + + L -->|vX.Y.Z| M[Official Release] + L -->|vX.Y.Z-alpha
vX.Y.Z-beta| N[Prerelease] + L -->|vX.Y.Z-rc.N| O[Release Candidate] + + M --> P[Generate: X.Y.Z] + P --> Q[Repository: packages-released] + + N --> R[Generate: X.Y.Z-prerelease] + R --> S[Repository: packages-dev] + + O --> T[Generate: X.Y.Z-rc.N] + T --> S + + style F fill:#fff3cd + style I fill:#fff3cd + style P fill:#d1e7dd + style R fill:#fff3cd + style T fill:#f8d7da +``` + +**Legend:** +- 🟡 Alpha versions (development & nightly builds) +- 🟢 Release versions (stable) +- 🔴 RC versions (release candidates) + +#### Target Repositories + +The `RELEASE_REPO` output determines which Google Artifact Registry repository the package will be published to: + +- **`packages-dev`**: Development and prerelease versions (alpha, beta, rc) +- **`nightly-builds`**: Automated builds from master branch merges +- **`packages-released`**: Official stable releases (no prerelease suffix) + +Combined with `EMB_APT_REPO` organization variable, the full repository name becomes: +``` +${EMB_APT_REPO}-${RELEASE_REPO} +``` +Example: `um-embedded-apt-packages-dev`, `um-embedded-apt-packages-released` + +### `test_generate_semver_version.sh` + +Test script that demonstrates various scenarios and validates the version generation logic. + +#### Usage + +```bash +cd /path/to/embedded-workflows/scripts +./test_generate_semver_version.sh +``` + +The script will run through 7 different test scenarios: +1. Pull Request / Regular Commit (alpha version) +2. Master Branch Merge (beta version) +3. Official Release Tag +4. Alpha Release Tag +5. Beta Release Tag +6. Release Candidate Tag +7. Invalid Tag Format (fallback to alpha) + +#### Example Output + +``` +========================================== +Testing SemVer Version Generation Script +========================================== + +Test 1: Pull Request / Regular Commit +Expected: X.Y.Z-alpha.0+sha +RELEASE_VERSION=1.2.4-alpha.0+abc1234 +RELEASE_REPO=packages-dev + +Test 2: Master Branch Merge +Expected: X.Y.Z-beta.0+YYYYMMDD.main.sha +RELEASE_VERSION=1.2.4-beta.0+20260118.main.abc1234 +RELEASE_REPO=nightly-builds + +... +``` + +## Local Testing + +To test the version generation locally outside of GitHub Actions: + +```bash +# Navigate to the scripts directory +cd /path/to/embedded-workflows/scripts + +# Set up minimal environment +export GITHUB_EVENT_NAME="pull_request" +export GITHUB_REF_TYPE="branch" +export GITHUB_REF_NAME="feature/my-feature" +export GITHUB_REF="refs/heads/feature/my-feature" + +# Run the script +./generate_semver_version.sh + +# Or run the full test suite +./test_generate_semver_version.sh +``` + +## Practical Examples + +### Example 1: Feature Development Workflow + +```bash +# Developer working on feature branch +git checkout -b feature/new-api +git commit -m "Add new API endpoint" +git push origin feature/new-api + +# Opens PR → Triggers workflow +# Generated version: 1.2.4-alpha.0+abc1234 +# Published to: um-embedded-apt-packages-dev +``` + +### Example 2: Master Branch Integration (Nightly Build) + +```bash +# PR merged to main +git checkout main +git merge feature/new-api +git push origin main + +# Push to main → Triggers workflow (nightly build) +# Generated version: 1.2.4-alpha.0+20260118.main.def5678 +# Published to: um-embedded-apt-nightly-builds +# Note: Uses alpha, not beta - nightly builds from master represent ongoing development +``` + +### Example 3: Releasing a Beta + +```bash +# Team decides to create a beta release +git tag v1.3.0-beta.1 +git push origin v1.3.0-beta.1 + +# Tag push → Triggers workflow +# Generated version: 1.3.0-beta.1 +# Published to: um-embedded-apt-packages-dev +``` + +### Example 4: Release Candidate + +```bash +# Beta testing complete, creating RC +git tag v1.3.0-rc.1 +git push origin v1.3.0-rc.1 + +# Tag push → Triggers workflow +# Generated version: 1.3.0-rc.1 +# Published to: um-embedded-apt-packages-dev +``` + +### Example 5: Official Release + +```bash +# RC validated, creating official release +git tag v1.3.0 +git push origin v1.3.0 + +# Tag push → Triggers workflow +# Generated version: 1.3.0 +# Published to: um-embedded-apt-packages-released +``` + +### Example 6: Hotfix Release + +```bash +# Critical bug found in production +git checkout v1.3.0 +git checkout -b hotfix/critical-bug +git commit -m "Fix critical security issue" +git tag v1.3.1 +git push origin v1.3.1 + +# Tag push → Triggers workflow +# Generated version: 1.3.1 +# Published to: um-embedded-apt-packages-released +``` + +## Local Testing + +To test the version generation locally outside of GitHub Actions: + +```bash +# Navigate to the scripts directory +cd /path/to/embedded-workflows/scripts + +# Set up minimal environment +export GITHUB_EVENT_NAME="pull_request" +export GITHUB_REF_TYPE="branch" +export GITHUB_REF_NAME="feature/my-feature" +export GITHUB_REF="refs/heads/feature/my-feature" + +# Run the script +./generate_semver_version.sh + +# Or run the full test suite +./test_generate_semver_version.sh +``` + +## Integration with Workflows + +The script is used in GitHub Actions workflows via: + +```yaml +- name: Generate Variables + id: vars + run: | + set -euo pipefail + + # Use dedicated script for version generation + VERSION_OUTPUT=$(./embedded-workflows/scripts/generate_semver_version.sh) + + # Parse output and set GitHub outputs + RELEASE_VERSION=$(echo "$VERSION_OUTPUT" | grep "^RELEASE_VERSION=" | cut -d= -f2) + RELEASE_REPO=$(echo "$VERSION_OUTPUT" | grep "^RELEASE_REPO=" | cut -d= -f2) + + echo RELEASE_VERSION="${RELEASE_VERSION}" >> $GITHUB_OUTPUT + echo RELEASE_REPO="${RELEASE_REPO}" >> $GITHUB_OUTPUT +``` + +## SemVer 2.0 Compliance + +All versions follow [Semantic Versioning 2.0](https://semver.org/): + +- **Major.Minor.Patch**: Core version numbers +- **Prerelease identifiers**: `-alpha`, `-beta`, `-rc` (with numeric suffixes like `.0`, `.1`) +- **Build metadata**: `+sha` or `+date.branch.sha` for traceability + +### Version Precedence + +``` +1.2.3-alpha.0 < 1.2.3-alpha.1 < 1.2.3-beta.0 < 1.2.3-rc.1 < 1.2.3 +``` + +Prerelease versions have lower precedence than the associated normal version. + +## Benefits + +1. **Testable**: Can be run and validated locally without GitHub Actions +2. **Reusable**: Same script can be used across multiple workflows +3. **Maintainable**: Logic is centralized in one place +4. **Standards-compliant**: Strictly follows SemVer 2.0 +5. **Traceable**: Every version includes commit SHA in build metadata diff --git a/scripts/generate_semver_version.sh b/scripts/generate_semver_version.sh new file mode 100755 index 0000000..1157a39 --- /dev/null +++ b/scripts/generate_semver_version.sh @@ -0,0 +1,156 @@ +#!/bin/bash +# Generate SemVer 2.0 compliant version based on git history and context +# Usage: generate_semver_version.sh [options] +# +# This script generates semantic versions following SemVer 2.0: +# Format: major.minor.patch-prerelease.0+build_metadata +# +# Prerelease progression: alpha → beta → rc → release +# +# Environment variables: +# GITHUB_EVENT_NAME - Type of GitHub event (push, pull_request, etc.) +# GITHUB_REF_TYPE - Type of ref (tag, branch) +# GITHUB_REF_NAME - Name of the ref (tag name or branch name) +# GITHUB_REF - Full ref (refs/heads/main, refs/tags/v1.0.0) +# GITHUB_EVENT_BASE_REF - Base ref for pull requests +# MASTER_BRANCH_LIST - Space-separated list of master branch prefixes +# +# Outputs (printed to stdout in key=value format): +# RELEASE_VERSION=X.Y.Z-pre.0+metadata +# RELEASE_REPO=repository-name + +set -euo pipefail + +# Default values +MASTER_BRANCH_LIST="${MASTER_BRANCH_LIST:-main master stable}" +# Regex to capture full prerelease identifiers including numeric suffixes +# Examples: v1.2.3, v1.2.3-alpha, v1.2.3-beta.1, v1.2.3-rc.2 +VERSION_REGEX='v[0-9]{1,4}\.[0-9]{1,4}\.[0-9]{1,9}(-(alpha|beta|rc)(\.?[0-9]+)?)?' + +# Function to get latest version from git tags and bump patch +# Returns version in format: major.minor.patch +get_bumped_version() { + # Find the latest semver tag reachable from current branch + LATEST_TAG=$(git describe --tags --abbrev=0 --match "v[0-9]*.[0-9]*.[0-9]*" 2>/dev/null || echo "v0.0.0") + echo "Latest reachable tag: ${LATEST_TAG}" >&2 + + # Parse the version (remove 'v' prefix and any suffix) + VERSION_ONLY="${LATEST_TAG#v}" + VERSION_ONLY="${VERSION_ONLY%%-*}" # Remove anything after first dash + + # Split into major.minor.patch + IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION_ONLY" + MAJOR=${MAJOR:-0} + MINOR=${MINOR:-0} + PATCH=${PATCH:-0} + + # Bump patch version + PATCH=$((PATCH + 1)) + + echo "${MAJOR}.${MINOR}.${PATCH}" +} + +# Main logic +main() { + echo "=== SemVer Version Generator ===" >&2 + echo "GITHUB_REF: ${GITHUB_REF:-}" >&2 + echo "GITHUB_REF_NAME: ${GITHUB_REF_NAME:-}" >&2 + echo "GITHUB_REF_TYPE: ${GITHUB_REF_TYPE:-}" >&2 + echo "GITHUB_EVENT_NAME: ${GITHUB_EVENT_NAME:-}" >&2 + + # Get short commit SHA for build metadata + COMMIT_SHA=$(git rev-parse --short HEAD) + echo "COMMIT_SHA: ${COMMIT_SHA}" >&2 + + # Determine current branch for master branch detection + if [[ "${GITHUB_REF_TYPE:-branch}" == "branch" ]]; then + CURRENT_BRANCH_REF="${GITHUB_REF:-refs/heads/main}" + else + CURRENT_BRANCH_REF="${GITHUB_EVENT_BASE_REF:-refs/heads/main}" + fi + + # Check if this is a commit/tag in the master branch + IS_MASTER_BRANCH="no" + CURRENT_BRANCH="${CURRENT_BRANCH_REF##refs/heads/}" + for master_branch in $MASTER_BRANCH_LIST; do + # Try to remove "master_branch" from "CURRENT_BRANCH" and check if it changes + if [[ "${CURRENT_BRANCH##${master_branch}}" != "${CURRENT_BRANCH}" ]]; then + IS_MASTER_BRANCH="yes" + break + fi + done + echo "CURRENT_BRANCH: ${CURRENT_BRANCH}" >&2 + echo "IS_MASTER_BRANCH: ${IS_MASTER_BRANCH}" >&2 + + # Check what triggered this action: A Pull Request, a tag push or branch push + TRIGGER="pull_request" + if [[ "${GITHUB_EVENT_NAME:-pull_request}" == "push" ]]; then + if [[ "${GITHUB_REF_TYPE:-branch}" == "tag" ]]; then + TRIGGER="tag" + else + TRIGGER="branch" + fi + fi + echo "TRIGGER: ${TRIGGER}" >&2 + + RELEASE_VERSION="" + RELEASE_REPO="packages-dev" # Default to packages-dev, always release at minimum here + + if [[ "${TRIGGER}" == "branch" && "${IS_MASTER_BRANCH}" == "yes" ]]; then + echo "This is a merge to master, lets make the Alpha nightly build" >&2 + + # Get base version and prepare alpha nightly build following SemVer 2.0 + BASE_VERSION=$(get_bumped_version) + + # Prepare the branch name suffix following Debian and SemVer rules + # - Only lowercase letters, numbers, '.', '+' and '-' + BRANCH_SUFFIX="${GITHUB_REF_NAME@L}" + BRANCH_SUFFIX=${BRANCH_SUFFIX//[^0-9a-z+.-]/-} + + # SemVer 2.0 format: major.minor.patch-prerelease.0+build_metadata + # Nightly alpha format: X.Y.Z-alpha.0+YYYYMMDD.branch.sha + BUILD_DATE=$(date +%Y%m%d) + RELEASE_REPO="nightly-builds" + RELEASE_VERSION="${BASE_VERSION}-alpha.0+${BUILD_DATE}.${BRANCH_SUFFIX}.${COMMIT_SHA}" + + echo "Alpha nightly build version: '${RELEASE_VERSION}'" >&2 + + elif [[ "${TRIGGER}" == "tag" ]]; then + echo "This is a tag push, lets parse the tag >${GITHUB_REF_NAME}< and check if we should release" >&2 + VERSION=$(echo "${GITHUB_REF_NAME}" | grep -o -E -e "${VERSION_REGEX}") || true # Return true if grep finds nothing + RELEASE_VERSION="${VERSION#v}" # Remove the initial "v" leaving only the numbers and optional "-alpha", "-beta", "-rc" + + if [[ -z "${RELEASE_VERSION}" ]]; then + echo "Failed to parse the tag, it does not follow the standard" >&2 + # Fall through to generate version from git tags + elif [[ "${RELEASE_VERSION}" =~ (alpha|beta|rc) ]]; then + echo "Success, this is a pre-release (alpha/beta/rc)" >&2 + RELEASE_REPO="packages-dev" + # Tag-based releases use the tag as-is, no build metadata added + else + echo "Success, this is an official release" >&2 + RELEASE_REPO="packages-released" + # Tag-based releases use the tag as-is, no build metadata added + fi + fi + + # If no valid version yet, generate one from git history + # SemVer 2.0 format: major.minor.patch-pre.0+build_metadata + if [[ -z "${RELEASE_VERSION}" ]]; then + echo "Generating version from git tags following SemVer 2.0" >&2 + BASE_VERSION=$(get_bumped_version) + # Format: X.Y.Z-alpha.0+sha (alpha is earliest pre-release stage) + RELEASE_VERSION="${BASE_VERSION}-alpha.0+${COMMIT_SHA}" + echo "Generated version: ${RELEASE_VERSION}" >&2 + fi + + echo "RELEASE_VERSION: ${RELEASE_VERSION}" >&2 + echo "RELEASE_REPO: ${RELEASE_REPO}" >&2 + + # Output in format that can be sourced or parsed + echo "RELEASE_VERSION=${RELEASE_VERSION}" + echo "RELEASE_REPO=${RELEASE_REPO}" +} + +# Run main function +main "$@" diff --git a/scripts/test_generate_semver_version.sh b/scripts/test_generate_semver_version.sh new file mode 100755 index 0000000..1df3beb --- /dev/null +++ b/scripts/test_generate_semver_version.sh @@ -0,0 +1,95 @@ +#!/bin/bash +# Test script for generate_semver_version.sh +# Demonstrates different scenarios and how to test locally + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +VERSION_SCRIPT="${SCRIPT_DIR}/generate_semver_version.sh" + +echo "==========================================" +echo "Testing SemVer Version Generation Script" +echo "==========================================" +echo "" + +# Test 1: Pull Request / Regular Commit (alpha version) +echo "Test 1: Pull Request / Regular Commit" +echo "Expected: X.Y.Z-alpha.0+sha" +export GITHUB_EVENT_NAME="pull_request" +export GITHUB_REF_TYPE="branch" +export GITHUB_REF_NAME="feature/test" +export GITHUB_REF="refs/heads/feature/test" +unset GITHUB_EVENT_BASE_REF +OUTPUT=$("$VERSION_SCRIPT") +echo "$OUTPUT" +echo "" + +# Test 2: Master Branch Merge (nightly build - alpha) +echo "Test 2: Master Branch Merge (nightly build - alpha)" +echo "Expected: X.Y.Z-alpha.0+YYYYMMDD.main.sha" +export GITHUB_EVENT_NAME="push" +export GITHUB_REF_TYPE="branch" +export GITHUB_REF_NAME="main" +export GITHUB_REF="refs/heads/main" +OUTPUT=$("$VERSION_SCRIPT") +echo "$OUTPUT" +echo "" + +# Test 3: Official Release Tag +echo "Test 3: Official Release Tag" +echo "Expected: X.Y.Z" +export GITHUB_EVENT_NAME="push" +export GITHUB_REF_TYPE="tag" +export GITHUB_REF_NAME="v1.2.3" +export GITHUB_REF="refs/tags/v1.2.3" +OUTPUT=$("$VERSION_SCRIPT") +echo "$OUTPUT" +echo "" + +# Test 4: Alpha Release Tag +echo "Test 4: Alpha Release Tag" +echo "Expected: X.Y.Z-alpha" +export GITHUB_EVENT_NAME="push" +export GITHUB_REF_TYPE="tag" +export GITHUB_REF_NAME="v1.2.3-alpha" +export GITHUB_REF="refs/tags/v1.2.3-alpha" +OUTPUT=$("$VERSION_SCRIPT") +echo "$OUTPUT" +echo "" + +# Test 5: Beta Release Tag +echo "Test 5: Beta Release Tag" +echo "Expected: X.Y.Z-beta.1" +export GITHUB_EVENT_NAME="push" +export GITHUB_REF_TYPE="tag" +export GITHUB_REF_NAME="v1.2.3-beta.1" +export GITHUB_REF="refs/tags/v1.2.3-beta.1" +OUTPUT=$("$VERSION_SCRIPT") +echo "$OUTPUT" +echo "" + +# Test 6: Release Candidate Tag +echo "Test 6: Release Candidate Tag" +echo "Expected: X.Y.Z-rc.1" +export GITHUB_EVENT_NAME="push" +export GITHUB_REF_TYPE="tag" +export GITHUB_REF_NAME="v1.2.3-rc.1" +export GITHUB_REF="refs/tags/v1.2.3-rc.1" +OUTPUT=$("$VERSION_SCRIPT") +echo "$OUTPUT" +echo "" + +# Test 7: Invalid Tag Format +echo "Test 7: Invalid Tag Format (should fall back to alpha)" +echo "Expected: X.Y.Z-alpha.0+sha" +export GITHUB_EVENT_NAME="push" +export GITHUB_REF_TYPE="tag" +export GITHUB_REF_NAME="random-tag" +export GITHUB_REF="refs/tags/random-tag" +OUTPUT=$("$VERSION_SCRIPT") +echo "$OUTPUT" +echo "" + +echo "==========================================" +echo "All tests completed!" +echo "=========================================="