Skip to content
Merged
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
120 changes: 10 additions & 110 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -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]
Expand All @@ -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
Expand Down Expand Up @@ -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
161 changes: 161 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -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
16 changes: 13 additions & 3 deletions docs/building.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,25 @@ 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
with `scripts/local-ci.sh`. To run a workflow on a specific `develop` PR anyway, an
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<version>`, 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`.