Skip to content

Commit

Permalink
Build NuGet packages in CI (#9)
Browse files Browse the repository at this point in the history
* Use Git tags for version numbers in CI
* Also run workflow on pushes & PRs to main
* Only push symbol packages for prereleases

Symbol packages include the .dll alongside the .pdb, so if you don't
need the .pdb then the only downside is a larger-than-needed download.
But since prerelease packages are what you're most likely to be trying
to debug, it's helpful to have the .pdb included.

* Explicitly request package write permission in workflow

Now that package write permission has been removed from the default
permissions for the repo, this is now required for packages to upload.

* Move version number calculations to Bash script
* Document how to write commit messages for semver bumps

---------

Co-authored-by: Kevin Hahn <kevin_hahn@sil.org>
  • Loading branch information
rmunn and hahn-kev authored Oct 15, 2024
1 parent 4200fa1 commit 28653e4
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 1 deletion.
5 changes: 4 additions & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
*.verified.txt text eol=lf working-tree-encoding=UTF-8
*.verified.xml text eol=lf working-tree-encoding=UTF-8
*.verified.json text eol=lf working-tree-encoding=UTF-8
*.verified.json text eol=lf working-tree-encoding=UTF-8

# force LF line endings on all shell scripts, even on Windows
*.sh text eol=lf
68 changes: 68 additions & 0 deletions .github/workflows/nuget-ci-cd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: Build NuGet packages

on:
push:
branches: [ develop, main ]
tags:
- v*
pull_request:
branches: [ develop, main ]

jobs:
build:
permissions:
packages: write
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4
with:
# We use `git describe` to find tags in commit history, so we need complete repo history
fetch-depth: 0

- name: Calculate version number for PR build
if: github.event_name == 'pull_request'
shell: bash
run: src/calculate-version.sh "${{ github.run_number }}" "${{ github.event.number }}"

- name: Calculate version number for non-PR build
if: github.event_name != 'pull_request'
shell: bash
run: src/calculate-version.sh "${{ github.run_number }}"

- name: Install .NET
uses: actions/setup-dotnet@v4

- name: Build & test
run: dotnet test --configuration Release --logger GitHubActions

- name: Pack
shell: bash
run: |
dotnet pack --include-symbols /p:PackageVersion="$PACKAGE_VERSION" /p:AssemblyVersion="$ASSEMBLY_VERSION" /p:FileVersion="$FILE_VERSION"
- name: Upload packages to build artifacts
uses: actions/upload-artifact@v4
with:
name: nuget-packages
path: src/artifacts/package/release/*nupkg

# Not using the GitHub package registry right now, since it doesn't allow anonymous access so it's a hassle to use

# - name: Publish package to GitHub
# if: github.event_name == 'pull_request' || (github.event_name == 'push' && startsWith(github.ref, 'refs/heads'))
# shell: bash
# run: |
# dotnet nuget push "src/artifacts/package/release/*.symbols.nupkg" -s https://nuget.pkg.github.com/sillsdev/index.json -k "$NUGET_API_KEY" --skip-duplicate
# env:
# NUGET_API_KEY: ${{ secrets.GITHUB_TOKEN }}

# - name: Publish package to NuGet.org
# if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
# shell: bash
# run: |
# echo Would run the following:
# echo dotnet nuget push "src/artifacts/package/release/*nupkg" --skip-duplicate --api-key "$NUGET_API_KEY" --source https://api.nuget.org/v3/index.json
# env:
# NUGET_API_KEY: ${{ secrets.SILLSDEV_PUBLISH_NUGET_ORG }}
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,14 @@ ISyncable remoteModel;
await dataModel.SyncWith(remoteModel);
```
It's that easy. All the heavy lifting is done by the interface which is fairly simple to implement.

## Development

### SemVer commit messages

NuGet package versions are calculated from a combination of tags and commit messages. First, the most recent Git tag matching the pattern `v\d+.\d+.\d+` is located. If that is the commit being built, then that version number is used. If there have been any commits since then, the version number will be bumped by looking for one of the following patterns in the commit messages:

* `+semver: major` or `+semver: breaking` - update major version number, reset others to 0 (so 2.3.1 would become 3.0.0)
* `+semver: minor` or `+semver: feature` - update minor version number, reset patch to 0 (so 2.3.1 would become 2.4.0)
* Anything else, including no `+semver` lines at all - update patch version number (so 2.3.1 would become 2.3.2)
* If you want to include `+semver` lines, then `+semver: patch` or `+semver: fix` are the standard ways to increment a patch version bump, but the patch version will be bumped regardless as long as there is at least one commit since the most recent tag.
9 changes: 9 additions & 0 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@
<Nullable>enable</Nullable>
<WarningsAsErrors>Nullable</WarningsAsErrors>
<UseArtifactsOutput>true</UseArtifactsOutput>
<IsPackable Condition=" '$(IsPackable)' == '' ">false</IsPackable>
</PropertyGroup>

<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<AssemblyVersion Condition=" '$(AssemblyVersion)' == '' ">$(ASSEMBLY_VERSION)</AssemblyVersion>
<AssemblyVersion Condition=" '$(AssemblyVersion)' == '' ">0.1.0.0</AssemblyVersion>
<FileVersion Condition=" '$(FileVersion)' == '' ">$(FILE_VERSION)</FileVersion>
<FileVersion Condition=" '$(FileVersion)' == '' ">0.1.0.0</FileVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
1 change: 1 addition & 0 deletions src/SIL.Harmony.Core/SIL.Harmony.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

<PropertyGroup>
<RootNamespace>SIL.Harmony.Core</RootNamespace>
<IsPackable>true</IsPackable>
</PropertyGroup>
<ItemGroup>
<InternalsVisibleTo Include="SIL.Harmony"/>
Expand Down
1 change: 1 addition & 0 deletions src/SIL.Harmony.Linq2db/SIL.Harmony.Linq2db.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

<PropertyGroup>
<RootNamespace>SIL.Harmony.Linq2db</RootNamespace>
<IsPackable>true</IsPackable>
</PropertyGroup>

<ItemGroup>
Expand Down
1 change: 1 addition & 0 deletions src/SIL.Harmony/SIL.Harmony.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

<PropertyGroup>
<RootNamespace>SIL.Harmony</RootNamespace>
<IsPackable>true</IsPackable>
</PropertyGroup>

<ItemGroup>
Expand Down
80 changes: 80 additions & 0 deletions src/calculate-version.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#!/bin/bash

# Pass build number and PR number as command-line params, e.g.:
# calculate-version.sh "${{ github.run_number }}" "${{ github.event.number }}"
# If present, command-line params override env vars

BUILD_NUMBER=$1
PR_NUMBER=$2

if [ -z "$BUILD_NUMBER" ]; then
echo "Required: pass a build number as first parameter"
echo "Optional: pass a PR number as second parameter"
exit 2
fi

# If running in CI, current commit is in GITHUB_REF env var
# If not running in CI, use git rev-parse to calculate it
GITHUB_REF=${GITHUB_REF:-$(git rev-parse --symbolic-full-name HEAD)}

# Find most recent tag
DESCRIBE=$(git describe --long --match "v*")
TAG=$(echo "$DESCRIBE" | grep -E -o '^v[0-9]+\.[0-9]+\.[0-9]+')

# Split version number from tag into major/minor/patch sections
MAJOR=$(echo "$TAG" | sed -E 's/^v([0-9]+)\.([0-9]+)\.([0-9]+)$/\1/')
MINOR=$(echo "$TAG" | sed -E 's/^v([0-9]+)\.([0-9]+)\.([0-9]+)$/\2/')
PATCH=$(echo "$TAG" | sed -E 's/^v([0-9]+)\.([0-9]+)\.([0-9]+)$/\3/')

# Calculate prerelease suffix, if any
SUFFIX=""
if [ -n "$PR_NUMBER" ]; then
SUFFIX="-PR${PR_NUMBER}.${BUILD_NUMBER}"
elif [ "$GITHUB_REF" = "refs/heads/develop" ]; then
SUFFIX="-beta.${BUILD_NUMBER}"
elif [ "$GITHUB_REF" = "refs/heads/main" ]; then
SUFFIX="-rc.${BUILD_NUMBER}"
fi

# Calculate version number bump
# Same logic as GitVersion:
# * "+semver: breaking" or "+semver: major" in commit log will produce major version bump (and reset minor and patch to 0)
# * "+semver: feature" or "+semver: minor" in commit log will produce minor version bump (and reset patch to 0)
# Default is to bump the patch version
# Git log format "%B" is the raw body with no author's email or anything else
COMMIT_COUNT=$(echo "$DESCRIBE" | sed -E 's/^[^-]+-([^-]+)-.*$/\1/')
if [ -n "$COMMIT_COUNT" -a "$COMMIT_COUNT" -gt 0 ]; then
# Calculate bump based on commit messages
RAW_LOG=$(git log --format="%B" "$TAG"..HEAD)
if grep -E '\+semver: (breaking|major)' <<< "$RAW_LOG"; then
MAJOR=$(($MAJOR + 1))
MINOR=0
PATCH=0
elif grep -E '\+semver: (feature|minor)' <<< "$RAW_LOG"; then
MINOR=$(($MINOR + 1))
PATCH=0
else
PATCH=$(($PATCH + 1))
fi
fi

# Set version number variables for MSBuild
export PACKAGE_VERSION=${MAJOR}.${MINOR}.${PATCH}${SUFFIX}
if [ $MAJOR -eq 0 ]; then
export ASSEMBLY_VERSION=0.${MINOR}.0.0
else
export ASSEMBLY_VERSION=${MAJOR}.0.0.0
fi
export FILE_VERSION=${MAJOR}.${MINOR}.${PATCH}.${BUILD_NUMBER}

# Put output variables into GITHUB_ENV if it exists
if [ -n "$GITHUB_ENV" ]; then
echo "PACKAGE_VERSION=${PACKAGE_VERSION}" >> "$GITHUB_ENV"
echo "ASSEMBLY_VERSION=${ASSEMBLY_VERSION}" >> "$GITHUB_ENV"
echo "FILE_VERSION=${FILE_VERSION}" >> "$GITHUB_ENV"
fi

# And show them on the console regardless
echo "PACKAGE_VERSION=${PACKAGE_VERSION}"
echo "ASSEMBLY_VERSION=${ASSEMBLY_VERSION}"
echo "FILE_VERSION=${FILE_VERSION}"

0 comments on commit 28653e4

Please sign in to comment.