diff --git a/.gitattributes b/.gitattributes index 0c0b43f..75c9d16 100644 --- a/.gitattributes +++ b/.gitattributes @@ -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 \ No newline at end of file +*.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 \ No newline at end of file diff --git a/.github/workflows/nuget-ci-cd.yml b/.github/workflows/nuget-ci-cd.yml new file mode 100644 index 0000000..eee0605 --- /dev/null +++ b/.github/workflows/nuget-ci-cd.yml @@ -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 }} diff --git a/README.md b/README.md index 2b0590d..f1c7a6e 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 36c1bcc..4a44902 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -6,6 +6,15 @@ enable Nullable true + false + + + + Debug + $(ASSEMBLY_VERSION) + 0.1.0.0 + $(FILE_VERSION) + 0.1.0.0 diff --git a/src/SIL.Harmony.Core/SIL.Harmony.Core.csproj b/src/SIL.Harmony.Core/SIL.Harmony.Core.csproj index d0ac008..acdee42 100644 --- a/src/SIL.Harmony.Core/SIL.Harmony.Core.csproj +++ b/src/SIL.Harmony.Core/SIL.Harmony.Core.csproj @@ -2,6 +2,7 @@ SIL.Harmony.Core + true diff --git a/src/SIL.Harmony.Linq2db/SIL.Harmony.Linq2db.csproj b/src/SIL.Harmony.Linq2db/SIL.Harmony.Linq2db.csproj index 7338ee0..753f30e 100644 --- a/src/SIL.Harmony.Linq2db/SIL.Harmony.Linq2db.csproj +++ b/src/SIL.Harmony.Linq2db/SIL.Harmony.Linq2db.csproj @@ -2,6 +2,7 @@ SIL.Harmony.Linq2db + true diff --git a/src/SIL.Harmony/SIL.Harmony.csproj b/src/SIL.Harmony/SIL.Harmony.csproj index 2844de2..6bb7e63 100644 --- a/src/SIL.Harmony/SIL.Harmony.csproj +++ b/src/SIL.Harmony/SIL.Harmony.csproj @@ -2,6 +2,7 @@ SIL.Harmony + true diff --git a/src/calculate-version.sh b/src/calculate-version.sh new file mode 100755 index 0000000..f57334e --- /dev/null +++ b/src/calculate-version.sh @@ -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}"