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 [
43 ./dotnet-setup-hook.sh
44 ]
45 ++ lib.optional (type == "sdk") (
46 replaceVars ./dotnet-sdk-setup-hook.sh {
47 inherit lndir xmlstarlet;
48 }
49 );
50
51 propagatedSandboxProfile = toString unwrapped.__propagatedSandboxProfile;
52
53 propagatedBuildInputs = lib.optional (type == "sdk") nugetPackageHook;
54
55 nativeBuildInputs = [ installShellFiles ];
56
57 outputs = [ "out" ] ++ lib.optional (unwrapped ? man) "man";
58
59 installPhase = ''
60 runHook preInstall
61 mkdir -p "$out"/bin "$out"/share
62 ln -s "$src"/bin/* "$out"/bin
63 ln -s "$src"/share/dotnet "$out"/share
64 runHook postInstall
65 '';
66
67 postInstall = ''
68 # completions snippets taken from https://learn.microsoft.com/en-us/dotnet/core/tools/enable-tab-autocomplete
69 installShellCompletion --cmd dotnet \
70 --bash ${./completions/dotnet.bash} \
71 --zsh ${./completions/dotnet.zsh} \
72 --fish ${./completions/dotnet.fish}
73 '';
74
75 doInstallCheck = true;
76
77 installCheckPhase = ''
78 runHook preInstallCheck
79 HOME=$(mktemp -d) $out/bin/dotnet --info
80 runHook postInstallCheck
81 '';
82
83 postFixup = lib.optionalString (unwrapped ? man) ''
84 ln -s ${unwrapped.man} "$man"
85 '';
86
87 passthru = unwrapped.passthru // {
88 inherit unwrapped;
89 tests =
90 let
91 mkDotnetTest =
92 {
93 name,
94 stdenv ? stdenvNoCC,
95 template,
96 lang ? null,
97 usePackageSource ? false,
98 build,
99 buildInputs ? [ ],
100 runtime ? finalAttrs.finalPackage.runtime,
101 runInputs ? [ ],
102 run ? null,
103 runAllowNetworking ? false,
104 }:
105 let
106 sdk = finalAttrs.finalPackage;
107 built = stdenv.mkDerivation {
108 name = "${sdk.name}-test-${name}";
109 buildInputs = [ sdk ] ++ buildInputs ++ lib.optional (usePackageSource) sdk.packages;
110 # make sure ICU works in a sandbox
111 propagatedSandboxProfile = toString sdk.__propagatedSandboxProfile;
112 unpackPhase =
113 let
114 unpackArgs =
115 [ template ]
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 [
223 zlib
224 ]
225 ++ lib.optional stdenv.hostPlatform.isDarwin [
226 swiftPackages.swift
227 darwin.ICU
228 ];
229 build = ''
230 dotnet restore -p:PublishAot=true
231 dotnet publish -p:PublishAot=true -o $out/bin
232 '';
233 runtime = null;
234 run = checkConsoleOutput "$src/bin/test";
235 };
236 };
237
238 mkWebTest =
239 lang: suffix:
240 mkDotnetTest {
241 name = "web-${suffix}";
242 template = "web";
243 inherit lang;
244 build = "dotnet publish -o $out/bin";
245 runtime = finalAttrs.finalPackage.aspnetcore;
246 runInputs = [
247 expect
248 curl
249 ];
250 run = ''
251 expect <<"EOF"
252 set status 1
253 spawn $env(src)/bin/test
254 proc abort { } { exit 2 }
255 expect_before default abort
256 expect -re {Now listening on: ([^\r]+)\r} {
257 set url $expect_out(1,string)
258 }
259 expect "Application started. Press Ctrl+C to shut down."
260 set output [exec curl -sSf $url]
261 if {$output != "Hello World!"} {
262 send_error "Unexpected output: $output\n"
263 exit 1
264 }
265 send \x03
266 expect_before timeout abort
267 expect eof
268 catch wait result
269 exit [lindex $result 3]
270 EOF
271 touch $out
272 '';
273 runAllowNetworking = true;
274 };
275 in
276 unwrapped.passthru.tests or { }
277 // {
278 version = testers.testVersion {
279 package = finalAttrs.finalPackage;
280 command = "HOME=$(mktemp -d) dotnet " + (if type == "sdk" then "--version" else "--info");
281 };
282 }
283 // lib.optionalAttrs (type == "sdk") ({
284 console = lib.recurseIntoAttrs {
285 # yes, older SDKs omit the comma
286 cs = mkConsoleTests "C#" "cs" "Hello,?\\ World!";
287 fs = mkConsoleTests "F#" "fs" "Hello\\ from\\ F#";
288 vb = mkConsoleTests "VB" "vb" "Hello,?\\ World!";
289 };
290
291 web = lib.recurseIntoAttrs {
292 cs = mkWebTest "C#" "cs";
293 fs = mkWebTest "F#" "fs";
294 };
295 });
296 };
297})