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