1#!/usr/bin/env nix-shell
2#!nix-shell -I nixpkgs=./. -i bash -p curl jq nix gnused
3# shellcheck shell=bash
4
5set -Eeuo pipefail
6shopt -s inherit_errexit
7
8trap 'exit 1' ERR
9
10rids=({linux-{,musl-}{arm,arm64,x64},osx-{arm64,x64},win-{arm64,x64,x86}})
11
12release () {
13 local content="$1"
14 local version="$2"
15
16 jq -er '.releases[] | select(."release-version" == "'"$version"'")' <<< "$content"
17}
18
19release_files () {
20 local release="$1"
21 local expr="$2"
22
23 jq -er '[('"$expr"').files[] | select(.name | test("^.*.tar.gz$"))]' <<< "$release"
24}
25
26release_platform_attr () {
27 local release_files="$1"
28 local platform="$2"
29 local attr="$3"
30
31 jq -r '.[] | select((.rid == "'"$platform"'") and (.name | contains("-composite-") or contains("-pack-") | not)) | ."'"$attr"'"' <<< "$release_files"
32}
33
34platform_sources () {
35 local release_files="$1"
36
37 echo "srcs = {"
38 for rid in "${rids[@]}"; do
39 local url hash
40
41 url=$(release_platform_attr "$release_files" "$rid" url)
42 hash=$(release_platform_attr "$release_files" "$rid" hash)
43
44 [[ -z "$url" || -z "$hash" ]] && continue
45
46 hash=$(nix --extra-experimental-features nix-command hash convert --to sri --hash-algo sha512 "$hash")
47
48 echo " $rid = {
49 url = \"$url\";
50 hash = \"$hash\";
51 };"
52 done
53 echo " };"
54}
55
56nuget_index="$(curl -fsSL "https://api.nuget.org/v3/index.json")"
57
58get_nuget_resource() {
59 jq -er '.resources[] | select(."@type" == "'"$1"'")."@id"' <<<"$nuget_index"
60}
61
62nuget_package_base_url="$(get_nuget_resource "PackageBaseAddress/3.0.0")"
63nuget_registration_base_url="$(get_nuget_resource "RegistrationsBaseUrl/3.6.0")"
64
65generate_package_list() {
66 local version="$1" indent="$2"
67 shift 2
68 local pkgs=( "$@" ) pkg url hash catalog_url catalog hash_algorithm
69
70 for pkg in "${pkgs[@]}"; do
71 url=${nuget_package_base_url}${pkg,,}/${version,,}/${pkg,,}.${version,,}.nupkg
72
73 if hash=$(curl -s --head "$url" -o /dev/null -w '%header{x-ms-meta-sha512}') && [[ -n "$hash" ]]; then
74 # Undocumented fast path for nuget.org
75 # https://github.com/NuGet/NuGetGallery/issues/9433#issuecomment-1472286080
76 hash=$(nix --extra-experimental-features nix-command hash convert --to sri --hash-algo sha512 "$hash")
77 elif {
78 catalog_url=$(curl -sL --compressed "${nuget_registration_base_url}${pkg,,}/${version,,}.json" | jq -r ".catalogEntry") && [[ -n "$catalog_url" ]] &&
79 catalog=$(curl -sL "$catalog_url") && [[ -n "$catalog" ]] &&
80 hash_algorithm="$(jq -er '.packageHashAlgorithm' <<<"$catalog")"&& [[ -n "$hash_algorithm" ]] &&
81 hash=$(jq -er '.packageHash' <<<"$catalog") && [[ -n "$hash" ]]
82 }; then
83 # Documented but slower path (requires 2 requests)
84 hash=$(nix --extra-experimental-features nix-command hash convert --to sri --hash-algo "${hash_algorithm,,}" "$hash")
85 elif hash=$(nix-prefetch-url "$url" --type sha512); then
86 # Fallback to downloading and hashing locally
87 echo "Failed to fetch hash from nuget for $url, falling back to downloading locally" >&2
88 hash=$(nix --extra-experimental-features nix-command hash convert --to sri --hash-algo sha512 "$hash")
89 else
90 echo "Failed to fetch hash for $url" >&2
91 exit 1
92 fi
93
94 echo "$indent(fetchNupkg { pname = \"${pkg}\"; version = \"${version}\"; hash = \"${hash}\"; })"
95 done
96}
97
98versionAtLeast () {
99 local cur_version=$1 min_version=$2
100 printf "%s\0%s" "$min_version" "$cur_version" | sort -zVC
101}
102
103# These packages are implicitly references by the build process,
104# based on the specific project configurations (RIDs, used features, etc.)
105# They are always referenced with the same version as the SDK used for building.
106# Since we lock nuget dependencies, when these packages are included in the generated
107# lock files (deps.nix), every update of SDK required those lock files to be
108# updated to reflect the new versions of these packages - otherwise, the build
109# would fail due to missing dependencies.
110#
111# Moving them to a separate list stored alongside the SDK package definitions,
112# and implicitly including them along in buildDotnetModule allows us
113# to make updating .NET SDK packages a lot easier - we now just update
114# the versions of these packages in one place, and all packages that
115# use buildDotnetModule continue building with the new .NET version without changes.
116#
117# Keep in mind that there is no canonical list of these implicitly
118# referenced packages - this list was created based on looking into
119# the deps.nix files of existing packages, and which dependencies required
120# updating after a SDK version bump.
121#
122# Due to this, make sure to check if new SDK versions introduce any new packages.
123# This should not happend in minor or bugfix updates, but probably happens
124# with every new major .NET release.
125aspnetcore_packages () {
126 local version=$1
127 local pkgs=(
128 Microsoft.AspNetCore.App.Ref
129 )
130
131 generate_package_list "$version" ' ' "${pkgs[@]}"
132}
133
134aspnetcore_target_packages () {
135 local version=$1
136 local rid=$2
137 local pkgs=(
138 "Microsoft.AspNetCore.App.Runtime.$rid"
139 )
140
141 generate_package_list "$version" ' ' "${pkgs[@]}"
142}
143
144netcore_packages () {
145 local version=$1
146 local pkgs=(
147 Microsoft.NETCore.DotNetAppHost
148 Microsoft.NETCore.App.Ref
149 )
150
151 if ! versionAtLeast "$version" 9; then
152 pkgs+=(
153 Microsoft.NETCore.DotNetHost
154 Microsoft.NETCore.DotNetHostPolicy
155 Microsoft.NETCore.DotNetHostResolver
156 )
157 fi
158
159 if versionAtLeast "$version" 7; then
160 pkgs+=(
161 Microsoft.DotNet.ILCompiler
162 )
163 fi
164
165 if versionAtLeast "$version" 8; then
166 pkgs+=(
167 Microsoft.NET.ILLink.Tasks
168 )
169 fi
170
171 generate_package_list "$version" ' ' "${pkgs[@]}"
172}
173
174netcore_host_packages () {
175 local version=$1
176 local rid=$2
177 local pkgs=(
178 "Microsoft.NETCore.App.Crossgen2.$rid"
179 )
180
181 local min_ilcompiler=
182 case "$rid" in
183 linux-musl-arm) ;;
184 linux-arm) ;;
185 win-x86) ;;
186 osx-arm64) min_ilcompiler=8 ;;
187 *) min_ilcompiler=7 ;;
188 esac
189
190 if [[ -n "$min_ilcompiler" ]] && versionAtLeast "$version" "$min_ilcompiler"; then
191 pkgs+=(
192 "runtime.$rid.Microsoft.DotNet.ILCompiler"
193 )
194 fi
195
196 generate_package_list "$version" ' ' "${pkgs[@]}"
197}
198
199netcore_target_packages () {
200 local version=$1
201 local rid=$2
202 local pkgs=(
203 "Microsoft.NETCore.App.Host.$rid"
204 "Microsoft.NETCore.App.Runtime.$rid"
205 "runtime.$rid.Microsoft.NETCore.DotNetAppHost"
206 )
207
208 if ! versionAtLeast "$version" 9; then
209 pkgs+=(
210 "runtime.$rid.Microsoft.NETCore.DotNetHost"
211 "runtime.$rid.Microsoft.NETCore.DotNetHostPolicy"
212 "runtime.$rid.Microsoft.NETCore.DotNetHostResolver"
213 )
214 case "$rid" in
215 linux-musl-arm*) ;;
216 win-arm64) ;;
217 *) pkgs+=(
218 "Microsoft.NETCore.App.Runtime.Mono.$rid"
219 ) ;;
220 esac
221 fi
222
223 generate_package_list "$version" ' ' "${pkgs[@]}"
224}
225
226usage () {
227 echo "Usage: $pname [[--sdk] [-o output] sem-version] ...
228Get updated dotnet src (platform - url & sha512) expressions for specified versions
229
230Exit codes:
231 0 Success
232 1 Failure
233 2 Release not found
234
235Examples:
236 $pname 6.0.14 7.0.201 - specific x.y.z versions
237 $pname 6.0 7.0 - latest x.y versions
238" >&2
239}
240
241update() {
242 local -r sem_version=$1 sdk=$2
243 local output=$3
244
245 local patch_specified=false
246 # Check if a patch was specified as an argument.
247 # If so, generate file for the specific version.
248 # If only x.y version was provided, get the latest patch
249 # version of the given x.y version.
250 if [[ "$sem_version" =~ ^[0-9]{1,}\.[0-9]{1,}\.[0-9]{1,} ]]; then
251 patch_specified=true
252 elif [[ ! "$sem_version" =~ ^[0-9]{1,}\.[0-9]{1,}$ ]]; then
253 usage
254 return 1
255 fi
256
257 : ${output:="$(realpath "$(dirname "${BASH_SOURCE[0]}")")"/versions/$sem_version.nix}
258 echo "Generating $output"
259
260 # Make sure the x.y version is properly passed to .NET release metadata url.
261 # Then get the json file and parse it to find the latest patch release.
262 local major_minor content major_minor_patch
263 major_minor=$(sed 's/^\([0-9]*\.[0-9]*\).*$/\1/' <<< "$sem_version")
264 content=$(curl -fsSL https://dotnetcli.blob.core.windows.net/dotnet/release-metadata/"$major_minor"/releases.json)
265 if [[ -n $sdk ]]; then
266 trap '' ERR
267 major_minor_patch=$(
268 jq -er --arg version "$sem_version" '
269 .releases[] |
270 select(.sdks[].version == $version) |
271 ."release-version"' <<< "$content" || if [[ $? == 4 ]]; then exit 2; else exit 1; fi)
272 trap 'exit 1' ERR
273 else
274 major_minor_patch=$([ "$patch_specified" == true ] && echo "$sem_version" || jq -er '."latest-release"' <<< "$content")
275 fi
276 local major_minor_underscore=${major_minor/./_}
277
278 local release_content aspnetcore_version runtime_version
279 local -a sdk_versions
280
281 release_content=$(release "$content" "$major_minor_patch")
282 aspnetcore_version=$(jq -er '."aspnetcore-runtime".version' <<< "$release_content")
283 runtime_version=$(jq -er '.runtime.version' <<< "$release_content")
284
285 if [[ -n $sdk ]]; then
286 sdk_versions=("$sem_version")
287 else
288 mapfile -t sdk_versions < <(jq -er '.sdks[] | .version' <<< "$release_content" | sort -rn)
289 fi
290
291 # If patch was not specified, check if the package is already the latest version
292 # If it is, exit early
293 if [ "$patch_specified" == false ] && [ -f "$output" ]; then
294 local -a versions
295 IFS= readarray -d '' versions < <(
296 nix-instantiate --eval --json -E "{ output }: with (import output {
297 buildAspNetCore = { ... }: {};
298 buildNetSdk = { version, ... }: { inherit version; };
299 buildNetRuntime = { version, ... }: { inherit version; };
300 fetchNupkg = { ... }: {};
301 }); (x: builtins.deepSeq x x) [
302 runtime_${major_minor_underscore}.version
303 sdk_${major_minor_underscore}.version
304 ]" --argstr output "$output" | jq -e --raw-output0 .[])
305 if [[ "${versions[0]}" == "$major_minor_patch" && "${versions[1]}" == "${sdk_versions[0]}" ]]; then
306 echo "Nothing to update."
307 return
308 fi
309 fi
310
311 local aspnetcore_files runtime_files
312 aspnetcore_files="$(release_files "$release_content" .\"aspnetcore-runtime\")"
313 runtime_files="$(release_files "$release_content" .runtime)"
314
315 local channel_version support_phase
316 channel_version=$(jq -er '."channel-version"' <<< "$content")
317 support_phase=$(jq -er '."support-phase"' <<< "$content")
318
319 local aspnetcore_sources runtime_sources
320 aspnetcore_sources="$(platform_sources "$aspnetcore_files")"
321 runtime_sources="$(platform_sources "$runtime_files")"
322
323 result=$(mktemp -t dotnet-XXXXXX.nix)
324 trap "rm -f $result" TERM INT EXIT
325
326 (
327 echo "{ buildAspNetCore, buildNetRuntime, buildNetSdk, fetchNupkg }:
328
329# v$channel_version ($support_phase)
330
331let
332 commonPackages = ["
333 aspnetcore_packages "${aspnetcore_version}"
334 netcore_packages "${runtime_version}"
335 echo " ];
336
337 hostPackages = {"
338 for rid in "${rids[@]}"; do
339 echo " $rid = ["
340 netcore_host_packages "${runtime_version}" "$rid"
341 echo " ];"
342 done
343 echo " };
344
345 targetPackages = {"
346 for rid in "${rids[@]}"; do
347 echo " $rid = ["
348 aspnetcore_target_packages "${aspnetcore_version}" "$rid"
349 netcore_target_packages "${runtime_version}" "$rid"
350 echo " ];"
351 done
352 echo " };
353
354in rec {
355 release_$major_minor_underscore = \"$major_minor_patch\";
356
357 aspnetcore_$major_minor_underscore = buildAspNetCore {
358 version = \"${aspnetcore_version}\";
359 $aspnetcore_sources
360 };
361
362 runtime_$major_minor_underscore = buildNetRuntime {
363 version = \"${runtime_version}\";
364 $runtime_sources
365 };"
366
367 local -A feature_bands
368 unset latest_sdk
369
370 for sdk_version in "${sdk_versions[@]}"; do
371 local sdk_base_version=${sdk_version%-*}
372 local feature_band=${sdk_base_version:0:-2}xx
373 # sometimes one release has e.g. both 8.0.202 and 8.0.203
374 [[ ! ${feature_bands[$feature_band]+true} ]] || continue
375 feature_bands[$feature_band]=$sdk_version
376 local sdk_files sdk_sources
377 sdk_files="$(release_files "$release_content" ".sdks[] | select(.version == \"$sdk_version\")")"
378 sdk_sources="$(platform_sources "$sdk_files")"
379 local sdk_attrname=sdk_${feature_band//./_}
380 [[ -v latest_sdk ]] || local latest_sdk=$sdk_attrname
381
382 echo "
383 $sdk_attrname = buildNetSdk {
384 version = \"${sdk_version}\";
385 $sdk_sources
386 inherit commonPackages hostPackages targetPackages;
387 runtime = runtime_$major_minor_underscore;
388 aspnetcore = aspnetcore_$major_minor_underscore;
389 };"
390 done
391
392 if [[ -n $sdk ]]; then
393 echo "
394 sdk = sdk_$major_minor_underscore;
395"
396 fi
397
398 echo "
399 sdk_$major_minor_underscore = $latest_sdk;
400}"
401 )> "$result"
402
403 nixfmt "$result"
404 cp "$result" "$output"
405 echo "Generated $output"
406}
407
408main () {
409 local pname sdk output
410 pname=$(basename "$0")
411
412 sdk=
413 output=
414
415 while [ $# -gt 0 ]; do
416 case $1 in
417 --sdk)
418 shift
419 sdk=1
420 ;;
421 -o)
422 shift
423 output=$1
424 shift
425 ;;
426 *)
427 update "$1" "$sdk" "$output"
428 shift
429 ;;
430 esac
431 done
432}
433
434main "$@"