1{
2 stdenv,
3 stdenvNoCC,
4 lib,
5 writeText,
6 testers,
7 runCommand,
8 runCommandWith,
9 darwin,
10 expect,
11 curl,
12 installShellFiles,
13 callPackage,
14 zlib,
15 swiftPackages,
16 icu,
17 lndir,
18 replaceVars,
19 nugetPackageHook,
20 xmlstarlet,
21}:
22type: unwrapped:
23stdenvNoCC.mkDerivation (finalAttrs: {
24 pname = "${unwrapped.pname}-wrapped";
25 inherit (unwrapped) version;
26
27 meta = {
28 description = "${unwrapped.meta.description or "dotnet"} (wrapper)";
29 mainProgram = "dotnet";
30 inherit (unwrapped.meta)
31 homepage
32 license
33 maintainers
34 platforms
35 ;
36 };
37
38 src = unwrapped;
39 dontUnpack = true;
40
41 setupHooks = [
42 ./dotnet-setup-hook.sh
43 ]
44 ++ lib.optional (type == "sdk") (
45 replaceVars ./dotnet-sdk-setup-hook.sh {
46 inherit lndir xmlstarlet;
47 }
48 );
49
50 propagatedSandboxProfile = toString unwrapped.__propagatedSandboxProfile;
51
52 propagatedBuildInputs = lib.optional (type == "sdk") nugetPackageHook;
53
54 nativeBuildInputs = [ installShellFiles ];
55
56 outputs = [ "out" ] ++ lib.optional (unwrapped ? man) "man";
57
58 installPhase = ''
59 runHook preInstall
60 mkdir -p "$out"/bin "$out"/share
61 ln -s "$src"/bin/* "$out"/bin
62 ln -s "$src"/share/dotnet "$out"/share
63 runHook postInstall
64 '';
65
66 postInstall = ''
67 # completions snippets taken from https://learn.microsoft.com/en-us/dotnet/core/tools/enable-tab-autocomplete
68 installShellCompletion --cmd dotnet \
69 --bash ${./completions/dotnet.bash} \
70 --zsh ${./completions/dotnet.zsh} \
71 --fish ${./completions/dotnet.fish}
72 '';
73
74 doInstallCheck = true;
75
76 installCheckPhase = ''
77 runHook preInstallCheck
78 HOME=$(mktemp -d) $out/bin/dotnet --info
79 runHook postInstallCheck
80 '';
81
82 postFixup = lib.optionalString (unwrapped ? man) ''
83 ln -s ${unwrapped.man} "$man"
84 '';
85
86 passthru = unwrapped.passthru // {
87 inherit unwrapped;
88 tests =
89 let
90 mkDotnetTest =
91 {
92 name,
93 stdenv ? stdenvNoCC,
94 template,
95 lang ? null,
96 usePackageSource ? false,
97 build,
98 buildInputs ? [ ],
99 runtime ? finalAttrs.finalPackage.runtime,
100 runInputs ? [ ],
101 run ? null,
102 runAllowNetworking ? false,
103 }:
104 let
105 sdk = finalAttrs.finalPackage;
106 built = stdenv.mkDerivation {
107 name = "${sdk.name}-test-${name}";
108 buildInputs = [ sdk ] ++ buildInputs ++ lib.optional (usePackageSource) sdk.packages;
109 # make sure ICU works in a sandbox
110 propagatedSandboxProfile = toString sdk.__propagatedSandboxProfile;
111 unpackPhase =
112 let
113 unpackArgs = [
114 template
115 ]
116 ++ lib.optionals (lang != null) [
117 "-lang"
118 lang
119 ];
120 in
121 ''
122 mkdir test
123 cd test
124 dotnet new ${lib.escapeShellArgs unpackArgs} -o . --no-restore
125 '';
126 buildPhase = build;
127 dontPatchELF = true;
128 };
129 in
130 # older SDKs don't include an embedded FSharp.Core package
131 if lang == "F#" && lib.versionOlder sdk.version "6.0.400" then
132 null
133 else if run == null then
134 built
135 else
136 runCommand "${built.name}-run"
137 (
138 {
139 src = built;
140 nativeBuildInputs = [ built ] ++ runInputs;
141 passthru = {
142 inherit built;
143 };
144 }
145 // lib.optionalAttrs (stdenv.hostPlatform.isDarwin && runAllowNetworking) {
146 sandboxProfile = ''
147 (allow network-inbound (local ip))
148 (allow mach-lookup (global-name "com.apple.FSEvents"))
149 '';
150 __darwinAllowLocalNetworking = true;
151 }
152 )
153 (
154 lib.optionalString (runtime != null) ''
155 export DOTNET_ROOT=${runtime}/share/dotnet
156 ''
157 + run
158 );
159
160 mkConsoleTests =
161 lang: suffix: output:
162 let
163 # Setting LANG to something other than 'C' forces the runtime to search
164 # for ICU, which will be required in most user environments.
165 checkConsoleOutput = command: ''
166 output="$(LANG=C.UTF-8 ${command})"
167 [[ "$output" =~ ${output} ]] && touch "$out"
168 '';
169
170 mkConsoleTest =
171 { name, ... }@args:
172 mkDotnetTest (
173 args
174 // {
175 name = "console-${name}-${suffix}";
176 template = "console";
177 inherit lang;
178 }
179 );
180 in
181 lib.recurseIntoAttrs {
182 run = mkConsoleTest {
183 name = "run";
184 build = checkConsoleOutput "dotnet run";
185 };
186
187 publish = mkConsoleTest {
188 name = "publish";
189 build = "dotnet publish -o $out/bin";
190 run = checkConsoleOutput "$src/bin/test";
191 };
192
193 self-contained = mkConsoleTest {
194 name = "self-contained";
195 usePackageSource = true;
196 build = "dotnet publish --use-current-runtime --sc -o $out";
197 runtime = null;
198 run = checkConsoleOutput "$src/test";
199 };
200
201 single-file = mkConsoleTest {
202 name = "single-file";
203 usePackageSource = true;
204 build = "dotnet publish --use-current-runtime -p:PublishSingleFile=true -o $out/bin";
205 runtime = null;
206 run = checkConsoleOutput "$src/bin/test";
207 };
208
209 ready-to-run = mkConsoleTest {
210 name = "ready-to-run";
211 usePackageSource = true;
212 build = "dotnet publish --use-current-runtime -p:PublishReadyToRun=true -o $out/bin";
213 run = checkConsoleOutput "$src/bin/test";
214 };
215 }
216 // lib.optionalAttrs finalAttrs.finalPackage.hasILCompiler {
217 aot = mkConsoleTest {
218 name = "aot";
219 stdenv = if stdenv.hostPlatform.isDarwin then swiftPackages.stdenv else stdenv;
220 usePackageSource = true;
221 buildInputs = [
222 zlib
223 ]
224 ++ lib.optional stdenv.hostPlatform.isDarwin [
225 swiftPackages.swift
226 darwin.ICU
227 ];
228 build = ''
229 dotnet restore -p:PublishAot=true
230 dotnet publish -p:PublishAot=true -o $out/bin
231 '';
232 runtime = null;
233 run = checkConsoleOutput "$src/bin/test";
234 };
235 };
236
237 mkWebTest =
238 lang: suffix:
239 mkDotnetTest {
240 name = "web-${suffix}";
241 template = "web";
242 inherit lang;
243 build = "dotnet publish -o $out/bin";
244 runtime = finalAttrs.finalPackage.aspnetcore;
245 runInputs = [
246 expect
247 curl
248 ];
249 run = ''
250 expect <<"EOF"
251 set status 1
252 spawn $env(src)/bin/test
253 proc abort { } { exit 2 }
254 expect_before default abort
255 expect -re {Now listening on: ([^\r]+)\r} {
256 set url $expect_out(1,string)
257 }
258 expect "Application started. Press Ctrl+C to shut down."
259 set output [exec curl -sSf $url]
260 if {$output != "Hello World!"} {
261 send_error "Unexpected output: $output\n"
262 exit 1
263 }
264 send \x03
265 expect_before timeout abort
266 expect eof
267 catch wait result
268 exit [lindex $result 3]
269 EOF
270 touch $out
271 '';
272 runAllowNetworking = true;
273 };
274 in
275 unwrapped.passthru.tests or { }
276 // {
277 version = testers.testVersion {
278 package = finalAttrs.finalPackage;
279 command = "HOME=$(mktemp -d) dotnet " + (if type == "sdk" then "--version" else "--info");
280 };
281 }
282 // lib.optionalAttrs (type == "sdk") ({
283 console = lib.recurseIntoAttrs {
284 # yes, older SDKs omit the comma
285 cs = mkConsoleTests "C#" "cs" "Hello,?\\ World!";
286 fs = mkConsoleTests "F#" "fs" "Hello\\ from\\ F#";
287 vb = mkConsoleTests "VB" "vb" "Hello,?\\ World!";
288 };
289
290 web = lib.recurseIntoAttrs {
291 cs = mkWebTest "C#" "cs";
292 fs = mkWebTest "F#" "fs";
293 };
294 });
295 };
296})