I'm siGit, running on siGit Code & Deploy platform, browsing siGit Code coding agent repo.

1 name: Release NuGet
2
3 on:
4 push:
5 tags:
6 - "v*.*.*"
7 workflow_dispatch:
8 inputs:
9 tag:
10 description: "Release tag (e.g. v1.5.2)"
11 required: true
12
13 concurrency:
14 # A re-pushed or re-dispatched tag fires this a second time. Queue the
15 # duplicate behind the original instead of cancelling it: cancelling a run
16 # mid-publish can leave a channel half-uploaded, whereas a queued duplicate
17 # just hits the "already published" check and exits clean.
18 group: ${{ github.workflow }}-${{ github.event.inputs.tag || github.ref_name }}
19 cancel-in-progress: false
20
21 permissions:
22 contents: read
23
24 env:
25 CARGO_TERM_COLOR: always
26 DOTNET_VERSION: 8.0.x
27 NUGET_PACKAGE_ID: SiGit.Code
28
29 jobs:
30 build-native-binaries:
31 name: Build native binary (${{ matrix.build.NAME }})
32 runs-on: ${{ matrix.build.OS }}
33 strategy:
34 fail-fast: false
35 matrix:
36 build:
37 - {
38 NAME: linux-x64,
39 OS: ubuntu-latest,
40 TARGET: x86_64-unknown-linux-gnu,
41 }
42 - {
43 NAME: linux-arm64,
44 OS: ubuntu-24.04-arm,
45 TARGET: aarch64-unknown-linux-gnu,
46 }
47 - {
48 NAME: windows-x64,
49 OS: windows-2022,
50 TARGET: x86_64-pc-windows-msvc,
51 }
52 - {
53 NAME: windows-arm64,
54 OS: windows-2022,
55 TARGET: aarch64-pc-windows-msvc,
56 }
57 - { NAME: darwin-x64, OS: macos-26, TARGET: x86_64-apple-darwin }
58 - { NAME: darwin-arm64, OS: macos-26, TARGET: aarch64-apple-darwin }
59 steps:
60 - name: Checkout
61 uses: actions/checkout@v6
62 with:
63 ref: ${{ github.event.inputs.tag || github.ref }}
64
65 - name: Read Rust toolchain
66 shell: bash
67 run: |
68 rust_toolchain="$(sed -n 's/^channel = "\(.*\)"/\1/p' rust-toolchain.toml | head -n 1)"
69 if [ -z "$rust_toolchain" ]; then
70 echo "Failed to read Rust toolchain from rust-toolchain.toml" >&2
71 exit 1
72 fi
73 echo "RUST_TOOLCHAIN=${rust_toolchain}" >> "$GITHUB_ENV"
74
75 - name: Install Rust toolchain
76 uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9
77 with:
78 toolchain: ${{ env.RUST_TOOLCHAIN }}
79
80 - name: Install Rust target
81 shell: bash
82 run: |
83 rustup target add ${{ matrix.build.TARGET }} --toolchain ${{ env.RUST_TOOLCHAIN }}
84 rustup target list --installed
85
86 - name: Setup Rust cache
87 uses: Swatinem/rust-cache@v2
88 with:
89 key: nuget-${{ matrix.build.TARGET }}
90
91 - name: Build binary
92 if: ${{ !contains(matrix.build.TARGET, 'linux') }}
93 shell: bash
94 run: cargo build --locked --release -p sigit --target ${{ matrix.build.TARGET }}
95
96 # Built in manylinux2014 so the binary runs on glibc 2.17 and later, whatever the runner
97 # has. See packaging/linux/build-manylinux.sh.
98 - name: Build Linux binary (glibc 2.17)
99 if: contains(matrix.build.TARGET, 'linux')
100 shell: bash
101 run: ./packaging/linux/build-manylinux.sh ${{ matrix.build.TARGET }}
102
103 - name: Check Linux binary against the glibc 2.17 floor
104 if: contains(matrix.build.TARGET, 'linux')
105 shell: bash
106 run: ./packaging/linux/check-glibc.sh target/${{ matrix.build.TARGET }}/release/sigit 2.17
107
108 - name: Prepare artifact
109 shell: bash
110 run: |
111 executable_name="sigit"
112 if [[ "${{ matrix.build.OS }}" == "windows-2022" ]]; then
113 executable_name="sigit.exe"
114 fi
115
116 mkdir -p "nuget-artifacts/native/${{ matrix.build.NAME }}"
117 cp "target/${{ matrix.build.TARGET }}/release/${executable_name}" \
118 "nuget-artifacts/native/${{ matrix.build.NAME }}/${executable_name}"
119
120 - name: Upload native artifact
121 uses: actions/upload-artifact@v4
122 with:
123 name: nuget-native-${{ matrix.build.NAME }}
124 path: nuget-artifacts/native
125
126 pack-nuget:
127 name: Pack NuGet .NET tool
128 runs-on: ubuntu-latest
129 needs: build-native-binaries
130 steps:
131 - name: Checkout
132 uses: actions/checkout@v6
133 with:
134 ref: ${{ github.event.inputs.tag || github.ref }}
135
136 - name: Set the release version
137 shell: bash
138 run: |
139 if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then
140 release_version="${GITHUB_REF_NAME#v}"
141 else
142 release_version="${{ github.event.inputs.tag }}"
143 release_version="${release_version#v}"
144 fi
145 echo "RELEASE_VERSION=${release_version}" >> "$GITHUB_ENV"
146
147 - name: Download native artifacts
148 uses: actions/download-artifact@v4
149 with:
150 pattern: nuget-native-*
151 path: packaging/nuget/sigit/native
152 merge-multiple: true
153
154 - name: Setup .NET SDK
155 uses: actions/setup-dotnet@v4
156 with:
157 dotnet-version: ${{ env.DOTNET_VERSION }}
158
159 - name: Pack .NET tool
160 shell: bash
161 run: |
162 dotnet pack packaging/nuget/sigit/SiGit.Code.csproj \
163 --configuration Release \
164 --output packaging/nuget/dist \
165 -p:PackageVersion=${RELEASE_VERSION}
166
167 # All six binaries ship in one package, so this sits closer to nuget.org's
168 # 250 MB ceiling than the single-platform npm and PyPI artifacts do. Fail
169 # here with a clear message rather than at `dotnet nuget push`, where the
170 # error is just "the package file exceeds the size limit".
171 - name: Check package size against the NuGet limit
172 shell: bash
173 run: |
174 package="$(ls packaging/nuget/dist/*.nupkg | head -n 1)"
175 size_bytes="$(wc -c < "${package}")"
176 limit_bytes=$((250 * 1024 * 1024))
177
178 echo "Package: ${package}"
179 echo "Size: $((size_bytes / 1024 / 1024)) MiB"
180
181 if [ "${size_bytes}" -gt "${limit_bytes}" ]; then
182 echo "::error::${package} is over nuget.org's 250 MB package limit. Split the tool into RID-specific packages (.NET 10 DotnetToolRidPackage) instead of bundling every target." >&2
183 exit 1
184 fi
185
186 - name: Upload package artifact
187 uses: actions/upload-artifact@v4
188 with:
189 name: nuget-package
190 path: packaging/nuget/dist/*.nupkg
191
192 smoke-test:
193 name: Smoke test (${{ matrix.os }})
194 runs-on: ${{ matrix.os }}
195 needs: pack-nuget
196 strategy:
197 fail-fast: false
198 matrix:
199 os:
200 - ubuntu-latest
201 - windows-2022
202 - macos-26
203 steps:
204 - name: Checkout
205 uses: actions/checkout@v6
206 with:
207 ref: ${{ github.event.inputs.tag || github.ref }}
208
209 - name: Set the release version
210 shell: bash
211 run: |
212 if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then
213 release_version="${GITHUB_REF_NAME#v}"
214 else
215 release_version="${{ github.event.inputs.tag }}"
216 release_version="${release_version#v}"
217 fi
218 echo "RELEASE_VERSION=${release_version}" >> "$GITHUB_ENV"
219
220 - name: Download package artifact
221 uses: actions/download-artifact@v4
222 with:
223 name: nuget-package
224 path: packaging/nuget/dist
225
226 - name: Setup .NET SDK
227 uses: actions/setup-dotnet@v4
228 with:
229 dotnet-version: ${{ env.DOTNET_VERSION }}
230
231 - name: Install tool from local package
232 shell: pwsh
233 run: |
234 $packageSource = (Resolve-Path "packaging/nuget/dist").Path
235 @"
236 <?xml version="1.0" encoding="utf-8"?>
237 <configuration>
238 <packageSources>
239 <clear />
240 <add key="local" value="$packageSource" />
241 </packageSources>
242 </configuration>
243 "@ | Set-Content -Path "packaging/nuget/NuGet.Config"
244
245 dotnet tool install `
246 --tool-path ".tool" `
247 --configfile "packaging/nuget/NuGet.Config" `
248 --version "$env:RELEASE_VERSION" `
249 "$env:NUGET_PACKAGE_ID"
250
251 - name: Verify bundled native binary
252 shell: pwsh
253 run: |
254 $runtimeIdentifier = if ($IsWindows) {
255 if ([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture -eq [System.Runtime.InteropServices.Architecture]::Arm64) { "windows-arm64" } else { "windows-x64" }
256 } elseif ($IsMacOS) {
257 if ([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture -eq [System.Runtime.InteropServices.Architecture]::Arm64) { "darwin-arm64" } else { "darwin-x64" }
258 } else {
259 if ([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture -eq [System.Runtime.InteropServices.Architecture]::Arm64) { "linux-arm64" } else { "linux-x64" }
260 }
261
262 $binaryName = if ($IsWindows) { "sigit.exe" } else { "sigit" }
263 $nativeBinary = Get-ChildItem ".tool/.store" -Recurse -File | Where-Object {
264 $_.FullName -like "*native*${runtimeIdentifier}*${binaryName}"
265 } | Select-Object -First 1
266
267 if (-not $nativeBinary) {
268 Write-Error "The sigit native binary for ${runtimeIdentifier} was not bundled in the installed .NET tool package."
269 }
270
271 Write-Host "Found bundled native binary at $($nativeBinary.FullName)"
272
273 publish-nuget:
274 name: Publish to NuGet
275 runs-on: ubuntu-latest
276 needs: smoke-test
277 # Trusted Publishing exchanges a GitHub OIDC token for a short-lived NuGet
278 # API key, so no long-lived NUGET_API_KEY secret is stored. Requires the
279 # `sigit-nuget` trusted publisher policy on nuget.org (owner getsigit, repo
280 # sigit, workflow release-nuget.yml).
281 permissions:
282 contents: read
283 id-token: write
284 steps:
285 - name: Set the release version
286 shell: bash
287 run: |
288 if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then
289 release_version="${GITHUB_REF_NAME#v}"
290 else
291 release_version="${{ github.event.inputs.tag }}"
292 release_version="${release_version#v}"
293 fi
294 echo "RELEASE_VERSION=${release_version}" >> "$GITHUB_ENV"
295
296 - name: Download package artifact
297 uses: actions/download-artifact@v4
298 with:
299 name: nuget-package
300 path: packaging/nuget/dist
301
302 - name: Setup .NET SDK
303 uses: actions/setup-dotnet@v4
304 with:
305 dotnet-version: ${{ env.DOTNET_VERSION }}
306
307 - name: Check whether release already exists on NuGet
308 id: nuget-check
309 shell: bash
310 run: |
311 package_index_url="https://api.nuget.org/v3-flatcontainer/sigit.code/index.json"
312
313 # Keep the fetch and the version match separate. Piping curl into
314 # grep hides a failed fetch behind an empty match, so an outage or a
315 # blocked request would read as "not published yet" and send the
316 # publish below into a version that is already there. A 404 here
317 # means the package itself is not on NuGet, which is the first
318 # release and a real "not published".
319 index=$(mktemp)
320 STATUS=$(curl -sS -o "${index}" -w '%{http_code}' \
321 --retry 3 --retry-delay 2 --retry-all-errors "${package_index_url}")
322
323 case "$STATUS" in
324 200)
325 if grep -F "\"${RELEASE_VERSION}\"" "${index}" >/dev/null; then
326 echo "${NUGET_PACKAGE_ID} ${RELEASE_VERSION} already exists on NuGet, skipping publish"
327 echo "exists=true" >> "$GITHUB_OUTPUT"
328 else
329 echo "exists=false" >> "$GITHUB_OUTPUT"
330 fi
331 ;;
332 404)
333 echo "${NUGET_PACKAGE_ID} is not on NuGet yet, publishing its first version"
334 echo "exists=false" >> "$GITHUB_OUTPUT"
335 ;;
336 *)
337 echo "Unexpected ${STATUS} from NuGet while checking whether ${NUGET_PACKAGE_ID} ${RELEASE_VERSION} is published" >&2
338 exit 1
339 ;;
340 esac
341
342 # Exchange the workflow's OIDC token for a short-lived NuGet API key. The
343 # key is written to the environment for the subsequent `dotnet nuget push`
344 # via the NUGET_API_KEY variable this action sets.
345 - name: NuGet trusted-publishing login
346 if: steps.nuget-check.outputs.exists != 'true'
347 uses: NuGet/login@v1
348 id: nuget-login
349 with:
350 user: setoelkahfi
351
352 - name: Publish package
353 if: steps.nuget-check.outputs.exists != 'true'
354 shell: bash
355 run: |
356 dotnet nuget push packaging/nuget/dist/*.nupkg \
357 --api-key "${{ steps.nuget-login.outputs.NUGET_API_KEY }}" \
358 --source https://api.nuget.org/v3/index.json \
359 --skip-duplicate