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
11 changes: 6 additions & 5 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,13 +141,14 @@ After the final push, sweep-resolve stale older threads for removed code paths.
- **LanguageData/**
- Contains downloaded language data files
- JSON converted data files
- Updated weekly via GitHub Actions
- Refreshed by daily codegen PRs; published with the weekly release

- **.github/workflows/**
- `run-periodic-codegen-pull-request.yml`: Weekly scheduled job to update language data
- `publish-release.yml`: Release and NuGet publishing workflow
- `run-periodic-codegen-pull-request.yml`: Daily scheduled job that opens codegen PRs to update language data
- `publish-release.yml`: Sole publisher — weekly scheduled (Mon 02:00 UTC) + manual dispatch full build/publish of both branches; pushes only publish when `PUBLISH_ON_MERGE` is set (two-phase model)
- `test-pull-request.yml`: PR smoke test — unit tests + a reduced, never-published library build gated by `dorny/paths-filter`
- `merge-bot-pull-request.yml`: Automated PR merge workflow
- `build-release-task.yml`, `build-library-task.yml`: Build tasks
- `build-release-task.yml`, `build-nugetlibrary-task.yml`: Build tasks
- `get-version-task.yml`, `build-datebadge-task.yml`: Version and badge generation

### Project Configuration
Expand Down Expand Up @@ -335,7 +336,7 @@ Examples:

### Data Updates

- Language data is updated weekly via GitHub Actions workflow
- Language data is refreshed by daily codegen PRs and shipped with the weekly release (GitHub Actions)
- The `LanguageTagsCreate` tool downloads data from:
- ISO 639-2: Library of Congress
- ISO 639-3: SIL International
Expand Down
10 changes: 9 additions & 1 deletion .github/workflows/build-datebadge-task.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@ name: Build BYOB date badge task

on:
workflow_call:
inputs:
# Logical branch this badge run is for. The badge only updates on
# `main`; the publisher passes the branch explicitly so a scheduled
# run building `develop` doesn't try to write the main badge. Required
# (no `github.ref_name` fallback) so the gate can't silently misfire.
branch:
required: true
type: string

jobs:

Expand All @@ -18,7 +26,7 @@ jobs:
echo "date=$(date)" >> $GITHUB_OUTPUT

- name: Build BYOB date badge step
if: ${{ github.ref_name == 'main' }}
if: ${{ inputs.branch == 'main' }}
uses: RubbaBoy/BYOB@a4919104bc0ec7cfd7f113e42c405cc45246f2a4 # v1
with:
name: lastbuild
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Build library task
name: Build NuGet library task

env:
PROJECT_FILE: ./LanguageTags/LanguageTags.csproj
Expand All @@ -7,25 +7,38 @@ env:
on:
workflow_call:
inputs:
# Input to control whether to push the library to NuGet.org
# Input to control whether to push the NuGet library to NuGet.org
push:
required: false
type: boolean
default: false
# Git ref to check out / version (empty = default checkout ref).
ref:
required: false
type: string
default: ''
# Logical branch driving build configuration (`main` => Release, else
# Debug). Required (no `github.ref_name` fallback, which would mislabel
# the develop leg of the publisher's matrix); the orchestrator passes it.
branch:
required: true
type: string
outputs:
# Output of the uploaded artifact id
artifact-id:
value: ${{ jobs.build-library.outputs.artifact-id }}
value: ${{ jobs.build-nugetlibrary.outputs.artifact-id }}

jobs:

get-version:
name: Get version information job
uses: ./.github/workflows/get-version-task.yml
secrets: inherit
with:
ref: ${{ inputs.ref }}

build-library:
name: Build library project job
build-nugetlibrary:
name: Build NuGet library project job
runs-on: ubuntu-latest
outputs:
artifact-id: ${{ steps.artifact-upload-step.outputs.artifact-id }}
Expand All @@ -40,14 +53,16 @@ jobs:

- name: Checkout code step
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.ref }}

- name: Build library project step
- name: Build NuGet library project step
run: |
set -euo pipefail
dotnet build ${{ env.PROJECT_FILE }} \
-property:OutputPath=${{ runner.temp }}/publish/ \
-property:PackageOutputPath=${{ runner.temp }}/publish/ \
--configuration ${{ inputs.push && 'Release' || 'Debug' }} \
--configuration ${{ inputs.branch == 'main' && 'Release' || 'Debug' }} \
-property:Version=${{ needs.get-version.outputs.AssemblyVersion }} \
-property:FileVersion=${{ needs.get-version.outputs.AssemblyFileVersion }} \
-property:AssemblyVersion=${{ needs.get-version.outputs.AssemblyVersion }} \
Expand All @@ -68,9 +83,11 @@ jobs:
set -euo pipefail
7z a -t7z ${{ runner.temp }}/${{ env.PROJECT_ARTIFACT }} ${{ runner.temp }}/publish/*

# Branch-suffixed so the publisher's branch matrix can build both
# branches in one run without colliding on the artifact name.
- name: Upload build artifacts step
id: artifact-upload-step
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: library-build
name: nugetlibrary-build-${{ inputs.branch }}
path: ${{ runner.temp }}/${{ env.PROJECT_ARTIFACT }}
103 changes: 89 additions & 14 deletions .github/workflows/build-release-task.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,52 +13,127 @@ on:
required: false
type: boolean
default: false
# Git ref to check out / version (empty = default checkout ref).
ref:
required: false
type: string
default: ''
# Logical branch driving config / tags / prerelease for every target.
# Required (no `github.ref_name` fallback): the publisher builds both
# `main` and `develop` from one run whose `github.ref_name` is `main`,
# so a silent fallback would mislabel the develop leg. Every caller
# passes it explicitly; a missing value should fail loudly.
branch:
required: true
type: string
# Smoke mode: reduced, never-published build for fast PR feedback.
# Forwarded to every target; also hard-disables every push below so a
# smoke run can never publish regardless of the publish flags.
smoke:
required: false
type: boolean
default: false
# Per-target presence gate. Default true (build everything). A PR smoke
# run sets this from the paths-filter so the library only builds when it
# actually changed.
enable_nuget:
required: false
type: boolean
default: true

jobs:

get-version:
name: Get version information job
uses: ./.github/workflows/get-version-task.yml
secrets: inherit
with:
ref: ${{ inputs.ref }}

build-library:
name: Build library job
uses: ./.github/workflows/build-library-task.yml
build-nugetlibrary:
name: Build NuGet library job
if: ${{ inputs.enable_nuget }}
needs: [get-version]
uses: ./.github/workflows/build-nugetlibrary-task.yml
secrets: inherit
with:
# Conditional push to NuGet.org
push: ${{ inputs.nuget }}
# Pin to the exact commit get-version resolved (immutable), not the
# possibly-moving branch ref: the publisher passes a branch name, and a
# commit landing mid-run could otherwise build artifacts from a different
# commit than the one the release tag (also GitCommitId) points at.
ref: ${{ needs.get-version.outputs.GitCommitId }}
branch: ${{ inputs.branch }}
# Conditional push to NuGet.org — never on a smoke build.
push: ${{ inputs.nuget && !inputs.smoke }}

github-release:
name: Publish GitHub release job
if: ${{ inputs.github }}
# `&& !inputs.smoke` enforces the "smoke never publishes" guarantee at the
# job level too (matching the `&& !inputs.smoke` push gate above), so a
# smoke caller that also set `github: true` still can't create a release.
if: ${{ inputs.github && !inputs.smoke }}
runs-on: ubuntu-latest
needs: [get-version, build-library]
needs: [get-version, build-nugetlibrary]

steps:

# Check out the exact built commit (NBGV `GitCommitId`), not the
# possibly-moving `inputs.ref` branch, so the uploaded release files
# match the tag even if the branch advances mid-run.
- name: Checkout code step
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ needs.get-version.outputs.GitCommitId }}

- name: Download library build artifacts step
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
artifact-ids: ${{ needs.build-library.outputs.artifact-id }}
artifact-ids: ${{ needs.build-nugetlibrary.outputs.artifact-id }}
path: ./Publish

# The weekly publisher re-runs even when a branch has no new commits, so
# NBGV can produce a SemVer2 that was already released. GitHub release
# creation has no built-in skip-duplicate (unlike NuGet's
# `--skip-duplicate`), and re-publishing an unchanged version is exactly
# the churn the two-phase model avoids — so skip the release step when a
# release for this tag already exists.
- name: Check for existing release step
id: release-exists
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ needs.get-version.outputs.SemVer2 }}
run: |
set -euo pipefail
if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
echo "exists=true" >> "$GITHUB_OUTPUT"
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
echo "Release $TAG already exists; workflow_dispatch will refresh it."
else
echo "Release $TAG already exists; skipping release creation (no-op republish)."
fi
else
echo "exists=false" >> "$GITHUB_OUTPUT"
fi

# `target_commitish` MUST be set explicitly: softprops doesn't pass a
# default through, and GitHub's REST API then defaults the new tag to
# the repository's default branch (main). On `push: develop` runs the
# tag would land on main's tip instead of the develop commit that
# built the artifact, leaving "Browse files" and `git checkout <tag>`
# pointing at unrelated code.
# the repository's default branch (main). We pin it to NBGV's
# `GitCommitId` — the exact commit the version was computed from. This
# avoids two bugs: `github.sha` would be wrong (the publisher's branch
# matrix builds `develop` from a run whose `github.sha` is main's tip),
# and `inputs.branch` would be a moving ref (a commit landing mid-run
# could tag the release on a newer commit than the one that was built).
# Skip the no-op weekly republish when the tag already exists, but always
# allow a manual `workflow_dispatch` through so it can repair/refresh a
# partially-created release for the same tag.
- name: Create GitHub release step
if: ${{ steps.release-exists.outputs.exists == 'false' || github.event_name == 'workflow_dispatch' }}
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
with:
generate_release_notes: true
tag_name: ${{ needs.get-version.outputs.SemVer2 }}
target_commitish: ${{ github.sha }}
prerelease: ${{ github.ref_name != 'main' }}
target_commitish: ${{ needs.get-version.outputs.GitCommitId }}
prerelease: ${{ inputs.branch != 'main' }}
files: |
LICENSE
README.md
Expand Down
17 changes: 17 additions & 0 deletions .github/workflows/get-version-task.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@ name: Get version information task

on:
workflow_call:
inputs:
# Git ref to check out and version. Empty string falls back to the
# caller's default checkout ref (`github.ref`), preserving the original
# behavior. The publisher passes an explicit branch so a scheduled run —
# which always reports `github.ref` as the default branch — can still
# compute NBGV versions for `develop` too.
ref:
required: false
type: string
default: ''
outputs:
# Version information outputs
SemVer2:
Expand All @@ -12,6 +22,11 @@ on:
value: ${{ jobs.get-version.outputs.AssemblyFileVersion }}
AssemblyInformationalVersion:
value: ${{ jobs.get-version.outputs.AssemblyInformationalVersion }}
# Full SHA of the commit NBGV computed the version from. Used to pin the
# GitHub release tag and the built artifacts to the exact built commit
# (immutable), rather than a moving branch ref.
GitCommitId:
value: ${{ jobs.get-version.outputs.GitCommitId }}

jobs:

Expand All @@ -23,6 +38,7 @@ jobs:
AssemblyVersion: ${{ steps.nbgv.outputs.AssemblyVersion }}
AssemblyFileVersion: ${{ steps.nbgv.outputs.AssemblyFileVersion }}
AssemblyInformationalVersion: ${{ steps.nbgv.outputs.AssemblyInformationalVersion }}
GitCommitId: ${{ steps.nbgv.outputs.GitCommitId }}

steps:

Expand All @@ -34,6 +50,7 @@ jobs:
- name: Checkout code step
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.ref }}
fetch-depth: 0

# nbgv is intentionally NOT SHA-pinned: the upstream tag stream lags
Expand Down
Loading
Loading