diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 76d175a1..4e36b136 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,15 +1,14 @@ name: ci -# GitHub Actions minutes are limited, so this build runs automatically only where -# it matters: a PR into `main` (the develop -> main release gate) and pushes to -# `main` (the release itself). For PRs into `develop`, day-to-day validation is -# done with scripts/local-ci.sh; a run here happens only when an admin adds the -# `run-ci` label (re-add it to trigger each run) or dispatches the workflow -# manually. See the `if:` on the build job below. +# Build validation. GitHub Actions minutes are limited, so this runs automatically +# only for PRs into `main` (the develop -> main gate). For PRs into `develop`, +# day-to-day validation is done with scripts/local-ci.sh; a run here happens only +# when an admin adds the `run-ci` label (re-add it to trigger each run) or +# dispatches the workflow manually. See the `if:` on the build job below. +# +# This workflow does NOT publish releases — that is handled by release.yml +# (manual dispatch + a monthly check for new commits on main). on: - push: - branches: [main] - paths-ignore: ['**.md', 'docs/**', 'LICENSE*'] pull_request: branches: [main, develop] types: [opened, synchronize, reopened, labeled] @@ -22,11 +21,10 @@ concurrency: jobs: build: - # Auto for main (PRs into it + release pushes) and manual dispatch; for develop - # PRs only when an admin adds the `run-ci` label. + # Auto for PRs into main and manual dispatch; for develop PRs only when an + # admin adds the `run-ci` label. if: >- github.event_name == 'workflow_dispatch' || - github.event_name == 'push' || github.base_ref == 'main' || (github.event.action == 'labeled' && github.event.label.name == 'run-ci') runs-on: windows-2025 @@ -103,101 +101,3 @@ jobs: ConfuserEx-GUI.zip ConfuserEx.zip Confuser.MSBuild.Tasks/bin/Release/*.nupkg - - # Dev build: on push to develop branch - dev-release: - needs: build - if: github.event_name == 'push' && github.ref == 'refs/heads/develop' - runs-on: windows-2025 - timeout-minutes: 5 - permissions: - contents: write - steps: - - uses: actions/checkout@v5 - with: - fetch-depth: 0 - - - name: Install nbgv - run: dotnet tool install -g nbgv - - - name: Compute version - id: version - shell: pwsh - run: | - $ver = nbgv get-version -v NuGetPackageVersion - echo "VERSION=$ver" >> $env:GITHUB_OUTPUT - - - name: Download artifacts - uses: actions/download-artifact@v5 - with: - name: confuserex-packages - - - name: Create or update dev release - uses: softprops/action-gh-release@v2 - with: - tag_name: dev-latest - name: "Dev build v${{ steps.version.outputs.VERSION }}" - prerelease: true - make_latest: false - body: | - **Development build** — for testing only, not production use. - - Version: `${{ steps.version.outputs.VERSION }}` - Branch: `develop` - Commit: ${{ github.sha }} - - Download the binaries below to test recent fixes and features before they are included in a stable release. - files: | - ConfuserEx-CLI.zip - ConfuserEx-GUI.zip - ConfuserEx.zip - *.nupkg - - # Release: only on PR merge to main - release: - needs: build - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - runs-on: windows-2025 - timeout-minutes: 5 - permissions: - contents: write - steps: - - uses: actions/checkout@v5 - with: - fetch-depth: 0 - - - name: Install nbgv - run: dotnet tool install -g nbgv - - - name: Compute version - id: version - shell: pwsh - run: | - $ver = nbgv get-version -v NuGetPackageVersion - echo "VERSION=$ver" >> $env:GITHUB_OUTPUT - - - name: Configure git identity - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - - - name: Create release tag - run: | - git tag -a "v${{ steps.version.outputs.VERSION }}" -m "Release ${{ steps.version.outputs.VERSION }}" - git push origin "v${{ steps.version.outputs.VERSION }}" - - - name: Download artifacts - uses: actions/download-artifact@v5 - with: - name: confuserex-packages - - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 - with: - tag_name: v${{ steps.version.outputs.VERSION }} - name: v${{ steps.version.outputs.VERSION }} - files: | - ConfuserEx-CLI.zip - ConfuserEx-GUI.zip - ConfuserEx.zip - *.nupkg diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..abdef97f --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,161 @@ +name: release + +# GitHub Actions minutes are limited, so releases are NOT cut automatically on +# every push to main. Instead: +# - Run this workflow manually (Actions tab -> release -> Run workflow) to build +# and publish a release from main on demand. +# - On the 1st of each month it checks main for commits since the last release +# tag and only spends the (expensive) Windows build when there are new changes. +# +# The check job runs on a cheap Ubuntu runner, so a monthly run with nothing new +# costs only a few seconds and publishes nothing. +on: + workflow_dispatch: + inputs: + force: + description: "Release even if there are no new commits since the last release tag" + type: boolean + default: false + schedule: + - cron: "0 6 1 * *" # 06:00 UTC on the 1st of every month + +concurrency: + group: release + cancel-in-progress: false + +permissions: + contents: write + +jobs: + check: + name: Check main for new commits + runs-on: ubuntu-latest + outputs: + should_release: ${{ steps.decide.outputs.should_release }} + steps: + - uses: actions/checkout@v5 + with: + ref: main + fetch-depth: 0 + fetch-tags: true + + - name: Decide whether to release + id: decide + shell: bash + run: | + last_tag=$(git tag --list 'v*' --sort=-v:refname | head -n1) + if [ -z "$last_tag" ]; then + echo "No release tag found — treating as first release." + echo "should_release=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + + behind=$(git rev-list "$last_tag"..HEAD --count) + echo "Last release: $last_tag — $behind new commit(s) on main since then." + + if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ inputs.force }}" = "true" ]; then + echo "Manual dispatch with force=true — releasing." + echo "should_release=true" >> "$GITHUB_OUTPUT" + elif [ "$behind" -gt 0 ]; then + echo "New commits present — releasing." + echo "should_release=true" >> "$GITHUB_OUTPUT" + else + echo "No new commits since $last_tag — skipping. (Use force=true to release anyway.)" + echo "should_release=false" >> "$GITHUB_OUTPUT" + fi + + release: + name: Build and publish release + needs: check + if: needs.check.outputs.should_release == 'true' + runs-on: windows-2025 + timeout-minutes: 15 + permissions: + contents: write + env: + NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages + steps: + - uses: actions/checkout@v5 + with: + ref: main + fetch-depth: 0 + + - name: Setup MSBuild + uses: microsoft/setup-msbuild@v2 + + - name: Cache NuGet packages + uses: actions/cache@v4 + with: + path: ${{ github.workspace }}/.nuget/packages + key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.vcxproj') }} + restore-keys: | + ${{ runner.os }}-nuget- + + - name: Install nbgv + run: dotnet tool install -g nbgv + + - name: Compute version + id: version + shell: pwsh + run: | + $ver = nbgv get-version -v NuGetPackageVersion + echo "VERSION=$ver" >> $env:GITHUB_OUTPUT + Write-Host "Version: $ver" + + - name: Restore + run: msbuild Confuser2.sln -t:Restore -verbosity:minimal + + - name: Build + run: msbuild Confuser2.sln -p:Configuration=Release -verbosity:minimal + + - name: Package CLI + shell: pwsh + run: | + $src = 'Confuser.CLI/bin/Release/net10.0' + Get-ChildItem $src -Exclude '*.pdb','*.xml' | Compress-Archive -DestinationPath 'ConfuserEx-CLI.zip' + Write-Host "Created ConfuserEx-CLI.zip" + + - name: Package GUI + shell: pwsh + run: | + $src = 'ConfuserEx/bin/Release/net10.0-windows' + Get-ChildItem $src -Exclude '*.pdb','*.xml' | Compress-Archive -DestinationPath 'ConfuserEx-GUI.zip' + Write-Host "Created ConfuserEx-GUI.zip" + + - name: Package combined + shell: pwsh + run: | + $tmp = 'combined' + New-Item -ItemType Directory -Path $tmp -Force | Out-Null + Copy-Item 'Confuser.CLI/bin/Release/net10.0/*' $tmp -Exclude '*.pdb','*.xml' -Recurse + Copy-Item 'ConfuserEx/bin/Release/net10.0-windows/*' $tmp -Exclude '*.pdb','*.xml' -Recurse -Force + Get-ChildItem $tmp | Compress-Archive -DestinationPath 'ConfuserEx.zip' + Remove-Item $tmp -Recurse -Force + Write-Host "Created ConfuserEx.zip" + + - name: Configure git identity + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Create release tag + shell: bash + run: | + tag="v${{ steps.version.outputs.VERSION }}" + if git rev-parse "$tag" >/dev/null 2>&1; then + echo "Tag $tag already exists — skipping tag creation." + else + git tag -a "$tag" -m "Release ${{ steps.version.outputs.VERSION }}" + git push origin "$tag" + fi + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: v${{ steps.version.outputs.VERSION }} + name: v${{ steps.version.outputs.VERSION }} + files: | + ConfuserEx-CLI.zip + ConfuserEx-GUI.zip + ConfuserEx.zip + Confuser.MSBuild.Tasks/bin/Release/*.nupkg diff --git a/docs/building.md b/docs/building.md index 4e1a67c9..391c0e30 100644 --- a/docs/building.md +++ b/docs/building.md @@ -72,9 +72,10 @@ which mirrors the same build, test and coverage steps offline. | Workflow | Trigger | Purpose | |----------|---------|---------| -| `ci.yml` | PR into `main` + push to `main` (release). Manual: `run-ci` label on a `develop` PR, or `workflow_dispatch` | Build, package, create releases | +| `ci.yml` | PR into `main`. Manual: `run-ci` label on a `develop` PR, or `workflow_dispatch` | Build + package (validation only) | | `test.yml` | PR into `main`. Manual: `run-ci` label on a `develop` PR, or `workflow_dispatch` | Build, test, coverage report | | `lint.yml` | PR into `main`. Manual: `run-ci` label on a `develop` PR, or `workflow_dispatch` | Whitespace, style, Roslyn analyzers | +| `release.yml` | Manual (`workflow_dispatch`) + monthly (1st of month) | Build, tag and publish a GitHub Release from `main` | | `codeql-analysis.yml` | Weekly + manual | Security analysis | **PRs into `develop` do not run the cloud pipeline automatically** — validate them @@ -82,5 +83,14 @@ with `scripts/local-ci.sh`. To run a workflow on a specific `develop` PR anyway, admin adds the `run-ci` label (re-add it to trigger each subsequent run) or dispatches the workflow manually from the Actions tab. -Releases are created by `ci.yml` when `develop` is merged into `main`. Versioning is -handled by [Nerdbank.GitVersioning](https://github.com/dotnet/Nerdbank.GitVersioning) from `version.json`. +### Releases + +Releases are **not** cut automatically on push to `main`. `release.yml` handles them: + +- **Manual** — Actions tab → **release** → **Run workflow** (on `main`). Builds, tags + `v`, and publishes a GitHub Release with the CLI/GUI/combined zips and the + MSBuild-tasks nupkg. Use the `force` input to release even with no new commits. +- **Monthly** — on the 1st of each month a cheap check compares `main` to the last + `v*` tag and only runs the (expensive) build+publish when there are new commits. + +Versioning is handled by [Nerdbank.GitVersioning](https://github.com/dotnet/Nerdbank.GitVersioning) from `version.json`.