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