Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .github/RELEASE_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,43 @@ The work products compiled in the safety package are created with care according

For details on the features, see https://eclipse-score.github.io/score/main/features/lifecycle/index.html

---

## Changes to the Module

### New Features

$NEW_FEATURES

### Improvements

$IMPROVEMENTS

### Bug Fixes

$BUG_FIXES

### Other Changes by Label

$OTHER_CHANGES

### Compatibility

$COMPATIBILITY

### Performed Verification

$PERFORMED_VERIFICATION

### Known Issues

$KNOWN_ISSUES

### Known Vulnerabilities

$KNOWN_VULNERABILITIES

### Upgrade Instructions

$UPGRADE_INSTRUCTIONS

1 change: 1 addition & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
name: Bazel Build

on:
workflow_call:
pull_request:
types: [opened, reopened, synchronize]
merge_group:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/code_coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# *******************************************************************************
name: Code Coverage
on:
workflow_call:
pull_request:
types: [opened, reopened, synchronize]
push:
Expand Down
110 changes: 86 additions & 24 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,44 @@
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************
#
# This workflow automates the release process for the lifecycle repository.
# It is triggered manually to build, test, and validate release artifacts.
# The workflow creates a draft release with a filled-out description for
# maintainers to approve and publish.
# GitHub Actions Workflow: Release
#
# Purpose:
# Automates the complete release process for the lifecycle module.
# Orchestrates builds on all supported platforms, collects coverage reports,
# generates release notes from RST templates, and creates a draft release.
#
# Trigger:
# Manual workflow dispatch with required release tag input (e.g., v1.0.0)
# Input tag shall create the tag for the repo even if release is not published yet.
# In case Input tag already exist only the relevant github release page will be updated.
#
# Process:
# 1. Run parallel build jobs:
# - build-linux: Build and test on Linux (x86_64, arm64)
# - build-qnx: Build and test on QNX (x86_64, arm64)
# - coverage-report: Generate C++ code coverage report
# 2. After all builds pass, create-release job:
# - Generates release body from template (Markdown)
# - Extracts latest release notes from RST file
# - Converts RST markup to Markdown (links only)
# - Downloads and archives coverage report
# - Creates draft GitHub release for maintainer review
#
# Release Notes Format:
# - Documentation: .rst format (Sphinx compatible, stored in docs/release/)
# - GitHub Release: .md format (generated from .rst template)
# - Conversion: Only RST links converted; structure preserved
#
# Notes:
# - Concurrent runs prevented with concurrency.group: release
# - Draft releases allow maintainers to review before publishing
# - All build artifacts available for debugging

name: Release
run-name: Release ${{ inputs.tag }}

# Trigger: Manual workflow dispatch with a required tag input parameter
permissions:
contents: read
on:
Expand All @@ -27,76 +58,103 @@ on:
description: 'Release tag (e.g., v1.0.0)'
required: true
type: string

# Prevent concurrent release runs to avoid conflicts and race conditions
concurrency:
group: release

jobs:
# Parallel build jobs (all must succeed before creating release)

build-linux:
# Build and test on Linux platforms: x86_64, aarch64
uses: ./.github/workflows/build.yml

build-qnx:
# Build and test on QNX platforms: x86_64, aarch64
uses: ./.github/workflows/qnx8.yml
permissions:
contents: read
pull-requests: read
secrets: inherit

coverage-report:
# Generate C++ code coverage report; artifact downloaded by create-release job
uses: ./.github/workflows/code_coverage.yml

# lint:
# uses: ./.github/workflows/lint.yml
# sanitizers:
# uses: ./.github/workflows/sanitizers_linux.yml

# Release orchestration: runs after all builds pass
create-release:
needs: [build-linux, build-qnx, coverage-report]
runs-on: ubuntu-latest
permissions:
contents: write
contents: write # Needed to create releases
env:
COVERAGE_ARCHIVE: ${{ github.event.repository.name }}-${{ inputs.tag }}-cpp-coverage
steps:
- name: Checkout Repository
# Access source files, templates, and release notes
uses: actions/checkout@v6
- name: Get Current Date and Previous Release Tag

- name: Get Previous Release Tag and Current Date
# Query: Previous release tag (for version comparison in release notes)
# Compute: Current date (added to release metadata)
env:
GH_TOKEN: ${{ github.token }}
run: |
PREVIOUS_TAG=$(gh release view --json tagName --jq '.tagName' 2>/dev/null || echo "N/A")
echo "RELEASE_DATE=$(date --rfc-3339=date)" >> $GITHUB_ENV
echo "PREVIOUS_RELEASE_TAG=$PREVIOUS_TAG" >> $GITHUB_ENV
- name: Generate Release Body

- name: Generate Release Body from Template
# Substitute variables into .md template
# Output: release_body.md with placeholders replaced ($RELEASE_TAG, $COMMIT_SHA, etc.)
env:
RELEASE_TAG: ${{ inputs.tag }}
COMMIT_SHA: ${{ github.sha }}
ACTION_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
envsubst '$RELEASE_TAG $PREVIOUS_RELEASE_TAG $COMMIT_SHA $RELEASE_DATE $ACTION_RUN_URL' < .github/RELEASE_TEMPLATE.md > release_body.md

- name: Find Latest Release Note
# Searches docs/release/release_note_v_X_Y_Z.rst files
# Sorts versions numerically, returns the highest version found
run: |
LATEST_RELEASE_NOTE=$(
LATEST=$(
for f in docs/release/release_note_v_*.rst; do
[ -e "$f" ] || continue
ver=$(basename "$f" .rst | sed 's/release_note_v_//' | tr '_' '.')
echo "$ver $f"
done | sort -t '.' -k1,1n -k2,2n -k3,3n | tail -1 | awk '{print $2}'
)
echo "LATEST_RELEASE_NOTE=$LATEST_RELEASE_NOTE" >> "$GITHUB_ENV"
- name: Append Release Note to Release Body
if: env.LATEST_RELEASE_NOTE != ''
run: |
printf '\n---\n\n## Changes to the Module\n\n' >> release_body.md
sed -n '/^Changes to the Module$/,$p' "$LATEST_RELEASE_NOTE" | sed '1,2d' >> release_body.md
echo "LATEST_RELEASE_NOTE=${LATEST:--}" >> "$GITHUB_ENV"

- name: Populate Release Note Sections
# Extracts release note sections from .rst file and converts to .md format
# Helper script: scripts/prepare_release_notes.sh
# Conversion: RST links (`text <url>`_) → Markdown links ([text](url))
if: env.LATEST_RELEASE_NOTE != '-'
run: ./scripts/prepare_release_notes.sh release_body.md "${{ env.LATEST_RELEASE_NOTE }}"

- name: Download Coverage Report Artifact
# Retrieves C++ coverage report generated by coverage-report job
# Artifact name format: {repo-name}_cpp_coverage_cpp
uses: actions/download-artifact@v7
with:
name: ${{ format('{0}_cpp_coverage{1}', github.event.repository.name, '_cpp') }}
path: ${{ env.COVERAGE_ARCHIVE }}

- name: Create Coverage Report Archive
# Compresses coverage report into .tar.xz for attachment
# Reproducible flags: sorted names, fixed ownership (0:0), fixed timestamps
run: |
tar --sort=name --owner=0 --group=0 --numeric-owner \
-cJf "$COVERAGE_ARCHIVE.tar.xz" "$COVERAGE_ARCHIVE"

- name: Create Draft Release
# Creates GitHub release with:
# - Generated release body (template + extracted sections from .rst)
# - Coverage report archive as downloadable attachment
# - Draft status: maintainers review and publish when ready
uses: softprops/action-gh-release@v2
id: draft_release
with:
Expand All @@ -107,10 +165,14 @@ jobs:
target_commitish: ${{ github.sha }}
files: |
${{ env.COVERAGE_ARCHIVE }}.tar.xz

- name: Summary
# Output release details to GitHub Actions UI for maintainer review
# Includes: tag, branch, commit hash, link to draft release
run: |
echo "### Draft Release Created :rocket:" >> $GITHUB_STEP_SUMMARY
echo "Tag: **${{ inputs.tag }}**" >> $GITHUB_STEP_SUMMARY
echo "Branch: \`${{ github.ref_name }}\`" >> $GITHUB_STEP_SUMMARY
echo "Commit: \`${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY
echo "Maintainers can review at: ${{ steps.draft_release.outputs.url }}" >> $GITHUB_STEP_SUMMARY
echo "### Draft Release Created 🚀" >> $GITHUB_STEP_SUMMARY
echo "**Tag:** \`${{ inputs.tag }}\`" >> $GITHUB_STEP_SUMMARY
echo "**Branch:** \`${{ github.ref_name }}\`" >> $GITHUB_STEP_SUMMARY
echo "**Commit:** \`${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "👉 [Review draft release](${{ steps.draft_release.outputs.url }})" >> $GITHUB_STEP_SUMMARY
114 changes: 114 additions & 0 deletions scripts/prepare_release_notes.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#!/usr/bin/env bash
# *******************************************************************************
# Copyright (c) 2026 Contributors to the Eclipse Foundation
#
# See the NOTICE file(s) distributed with this work for additional
# information regarding copyright ownership.
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************

set -euo pipefail

# Script: prepare_release_notes.sh
# Purpose: Extract release note sections from RST file and convert to Markdown format
# Usage: ./scripts/prepare_release_notes.sh <release_body.md> <release_note.rst>
# Environment: Expects release_body.md to contain $SECTION_NAME placeholders

if [[ $# -ne 2 ]]; then
echo "Usage: $0 <release_body_file> <release_note_rst_file>" >&2
exit 1
fi

RELEASE_BODY="$1"
RELEASE_NOTE="$2"

if [[ ! -f "$RELEASE_BODY" ]]; then
echo "Error: Release body file not found: $RELEASE_BODY" >&2
exit 1
fi

if [[ ! -f "$RELEASE_NOTE" ]]; then
echo "Warning: Release note file not found: $RELEASE_NOTE" >&2
echo "Skipping release note extraction (sections will remain as placeholders)"
exit 0
fi

# ---------------------
# Helper Functions
# ---------------------

# Extract a section between two RST headings (underline style)
# Uses sed to find the section and remove heading/underline lines
extract_section() {
local heading="$1"
local next_heading="${2:-}"
local file="$3"

if [[ -z "$next_heading" ]]; then
# Extract from heading to next heading at same level
sed -n "/^${heading}$/,/^[=-]\{1,\}$/p" "$file" | sed '1,2d;$d'
else
# Extract from heading to specific next heading
sed -n "/^${heading}$/,/^${next_heading}$/p" "$file" | sed '1,2d;$d'
fi
}

# Convert RST inline links to Markdown format
# RST: `link text <https://example.com>`_
# Markdown: [link text](https://example.com)
convert_rst_links() {
sed "s/\`\([^<]*\) <\([^>]*\)>\`_/[\1](\2)/g"
}

# ---------------------
# Extract Sections
# ---------------------

declare -A SECTIONS

# Define all release note sections in order
# Key: section name (used in template)
# Value: "heading_in_rst" "next_heading_in_rst" (or "" for last section)
declare -A SECTION_MAP=(
[NEW_FEATURES]="New Features|Improvements"
[IMPROVEMENTS]="Improvements|Bug Fixes"
[BUG_FIXES]="Bug Fixes|Other Changes by Label"
[OTHER_CHANGES]="Other Changes by Label|Compatibility"
[COMPATIBILITY]="Compatibility|Performed Verification"
[PERFORMED_VERIFICATION]="Performed Verification|Known Issues"
[KNOWN_ISSUES]="Known Issues|Known Vulnerabilities"
[KNOWN_VULNERABILITIES]="Known Vulnerabilities|Upgrade Instructions"
[UPGRADE_INSTRUCTIONS]="Upgrade Instructions|"
)

# Extract each section and convert links
for section_var in "${!SECTION_MAP[@]}"; do
IFS='|' read -r heading next_heading <<< "${SECTION_MAP[$section_var]}"

if [[ -z "$next_heading" ]]; then
SECTIONS[$section_var]=$(extract_section "$heading" "" "$RELEASE_NOTE" | convert_rst_links)
else
SECTIONS[$section_var]=$(extract_section "$heading" "$next_heading" "$RELEASE_NOTE" | convert_rst_links)
fi
done

# ---------------------
# Update Release Body
# ---------------------

# Replace all section placeholders using envsubst
# Must export all variables for envsubst to find them
for section_var in "${!SECTIONS[@]}"; do
export "$section_var"="${SECTIONS[$section_var]}"
done

# Create temporary file with all sections substituted
envsubst "$(printf '$%s ' "${!SECTIONS[@]}")" < "$RELEASE_BODY" > "${RELEASE_BODY}.tmp"
mv "${RELEASE_BODY}.tmp" "$RELEASE_BODY"

echo "✅ Release notes prepared successfully"
Loading