1{
2 alsa-lib,
3 autoPatchelfHook,
4 buildPackages,
5 callPackage,
6 dbus,
7 dotnetCorePackages,
8 exportTemplatesHash,
9 fetchFromGitHub,
10 fetchpatch,
11 fontconfig,
12 glib,
13 hash,
14 installShellFiles,
15 lib,
16 libdecor,
17 libGL,
18 libpulseaudio,
19 libX11,
20 libXcursor,
21 libXext,
22 libXfixes,
23 libXi,
24 libXinerama,
25 libxkbcommon,
26 libXrandr,
27 libXrender,
28 makeWrapper,
29 perl,
30 pkg-config,
31 runCommand,
32 scons,
33 speechd-minimal,
34 stdenv,
35 stdenvNoCC,
36 testers,
37 udev,
38 updateScript,
39 version,
40 vulkan-loader,
41 wayland,
42 wayland-scanner,
43 withAlsa ? true,
44 withDbus ? true,
45 withFontconfig ? true,
46 withMono ? false,
47 nugetDeps ? null,
48 withPlatform ? "linuxbsd",
49 withPrecision ? "single",
50 withPulseaudio ? true,
51 withSpeechd ? true,
52 withTouch ? true,
53 withUdev ? true,
54 # Wayland in Godot requires X11 until upstream fix is merged
55 # https://github.com/godotengine/godot/pull/73504
56 withWayland ? true,
57 withX11 ? true,
58}:
59assert lib.asserts.assertOneOf "withPrecision" withPrecision [
60 "single"
61 "double"
62];
63let
64 mkSconsFlagsFromAttrSet = lib.mapAttrsToList (
65 k: v: if builtins.isString v then "${k}=${v}" else "${k}=${builtins.toJSON v}"
66 );
67
68 arch = stdenv.hostPlatform.linuxArch;
69
70 dotnet-sdk = if withMono then dotnetCorePackages.sdk_8_0-source else null;
71 dotnet-sdk_alt = if withMono then dotnetCorePackages.sdk_9_0-source else null;
72
73 dottedVersion = lib.replaceStrings [ "-" ] [ "." ] version + lib.optionalString withMono ".mono";
74
75 mkTarget =
76 target:
77 let
78 editor = target == "editor";
79 suffix = lib.optionalString withMono "-mono" + lib.optionalString (!editor) "-template";
80 binary = lib.concatStringsSep "." (
81 [
82 "godot"
83 withPlatform
84 target
85 ]
86 ++ lib.optional (withPrecision != "single") withPrecision
87 ++ [ arch ]
88 ++ lib.optional withMono "mono"
89 );
90
91 mkTests =
92 pkg: dotnet-sdk:
93 {
94 version = testers.testVersion {
95 package = pkg;
96 version = dottedVersion;
97 };
98 }
99 // lib.optionalAttrs (editor) (
100 let
101 project-src =
102 runCommand "${pkg.name}-project-src"
103 {
104 nativeBuildInputs = [ pkg ] ++ lib.optional (dotnet-sdk != null) dotnet-sdk;
105 }
106 (
107 ''
108 mkdir "$out"
109 cd "$out"
110 touch project.godot
111
112 cat >create-scene.gd <<'EOF'
113 extends SceneTree
114
115 func _initialize():
116 var node = Node.new()
117 var script = ResourceLoader.load("res://test.gd")
118 node.set_script(script)
119 ''
120 + lib.optionalString withMono ''
121 ${""}
122 var monoNode = Node.new()
123 var monoScript = ResourceLoader.load("res://Test.cs")
124 monoNode.set_script(monoScript)
125 node.add_child(monoNode)
126 monoNode.owner = node
127 ''
128 + ''
129 var scene = PackedScene.new()
130 var scenePath = "res://test.tscn"
131 scene.pack(node)
132 node.free()
133 var x = ResourceSaver.save(scene, scenePath)
134 ProjectSettings["application/run/main_scene"] = scenePath
135 ProjectSettings.save()
136 quit()
137 EOF
138
139 cat >test.gd <<'EOF'
140 extends Node
141 func _ready():
142 print("Hello, World!")
143 get_tree().quit()
144 EOF
145
146 cat >export_presets.cfg <<'EOF'
147 [preset.0]
148 name="build"
149 platform="Linux"
150 runnable=true
151 export_filter="all_resources"
152 include_filter=""
153 exclude_filter=""
154 [preset.0.options]
155 binary_format/architecture="${arch}"
156 EOF
157 ''
158 + lib.optionalString withMono ''
159 cat >Test.cs <<'EOF'
160 using Godot;
161 using System;
162
163 public partial class Test : Node
164 {
165 public override void _Ready()
166 {
167 GD.Print("Hello, Mono!");
168 GetTree().Quit();
169 }
170 }
171 EOF
172
173 sdk_version=$(basename ${pkg}/share/nuget/packages/godot.net.sdk/*)
174 cat >UnnamedProject.csproj <<EOF
175 <Project Sdk="Godot.NET.Sdk/$sdk_version">
176 <PropertyGroup>
177 <TargetFramework>net${lib.versions.majorMinor (lib.defaultTo pkg.dotnet-sdk dotnet-sdk).version}</TargetFramework>
178 <EnableDynamicLoading>true</EnableDynamicLoading>
179 </PropertyGroup>
180 </Project>
181 EOF
182
183 configureNuget
184
185 dotnet new sln -n UnnamedProject
186 message=$(dotnet sln add UnnamedProject.csproj)
187 echo "$message"
188 # dotnet sln doesn't return an error when it fails to add the project
189 [[ $message == "Project \`UnnamedProject.csproj\` added to the solution." ]]
190
191 rm nuget.config
192 ''
193 );
194
195 export-tests = lib.makeExtensible (final: {
196 inherit (pkg) export-template;
197
198 export = stdenvNoCC.mkDerivation {
199 name = "${final.export-template.name}-export";
200
201 nativeBuildInputs = [ pkg ] ++ lib.optional (dotnet-sdk != null) dotnet-sdk;
202
203 src = project-src;
204
205 buildPhase = ''
206 runHook preBuild
207
208 export HOME=$(mktemp -d)
209 mkdir -p $HOME/.local/share/godot/
210 ln -s "${final.export-template}"/share/godot/export_templates "$HOME"/.local/share/godot/
211
212 godot${suffix} --headless --build-solutions -s create-scene.gd
213
214 runHook postBuild
215 '';
216
217 installPhase = ''
218 runHook preInstall
219
220 mkdir -p "$out"/bin
221 godot${suffix} --headless --export-release build "$out"/bin/test
222
223 runHook postInstall
224 '';
225 };
226
227 run = runCommand "${final.export.name}-runs" { passthru = { inherit (final) export; }; } (
228 ''
229 (
230 set -eo pipefail
231 HOME=$(mktemp -d)
232 "${final.export}"/bin/test --headless | tail -n+3 | (
233 ''
234 + lib.optionalString withMono ''
235 # indent
236 read output
237 if [[ "$output" != "Hello, Mono!" ]]; then
238 echo "unexpected output: $output" >&2
239 exit 1
240 fi
241 ''
242 + ''
243 read output
244 if [[ "$output" != "Hello, World!" ]]; then
245 echo "unexpected output: $output" >&2
246 exit 1
247 fi
248 )
249 touch "$out"
250 )
251 ''
252 );
253 });
254
255 in
256 {
257 export-runs = export-tests.run;
258
259 export-bin-runs =
260 (export-tests.extend (
261 final: prev: {
262 export-template = pkg.export-templates-bin;
263
264 export = prev.export.overrideAttrs (prev: {
265 nativeBuildInputs = prev.nativeBuildInputs or [ ] ++ [
266 autoPatchelfHook
267 ];
268
269 # stripping dlls results in:
270 # Failed to load System.Private.CoreLib.dll (error code 0x8007000B)
271 stripExclude = lib.optional withMono [ "*.dll" ];
272
273 runtimeDependencies =
274 prev.runtimeDependencies or [ ]
275 ++ map lib.getLib [
276 alsa-lib
277 libpulseaudio
278 libX11
279 libXcursor
280 libXext
281 libXi
282 libXrandr
283 udev
284 vulkan-loader
285 ];
286 });
287 }
288 )).run;
289 }
290 );
291
292 attrs = finalAttrs: rec {
293 pname = "godot${suffix}";
294 inherit version;
295
296 src = fetchFromGitHub {
297 owner = "godotengine";
298 repo = "godot";
299 tag = version;
300 inherit hash;
301 # Required for the commit hash to be included in the version number.
302 #
303 # `methods.py` reads the commit hash from `.git/HEAD` and manually follows
304 # refs.
305 #
306 # See also 'hash' in
307 # https://docs.godotengine.org/en/stable/classes/class_engine.html#class-engine-method-get-version-info
308 leaveDotGit = true;
309 # Only keep HEAD, because leaveDotGit is non-deterministic:
310 # https://github.com/NixOS/nixpkgs/issues/8567
311 postFetch = ''
312 hash=$(git -C "$out" rev-parse HEAD)
313 rm -r "$out"/.git
314 mkdir "$out"/.git
315 echo "$hash" > "$out"/.git/HEAD
316 '';
317 };
318
319 outputs = [
320 "out"
321 ]
322 ++ lib.optional (editor) "man";
323 separateDebugInfo = true;
324
325 # Set the build name which is part of the version. In official downloads, this
326 # is set to 'official'. When not specified explicitly, it is set to
327 # 'custom_build'. Other platforms packaging Godot (Gentoo, Arch, Flatpack
328 # etc.) usually set this to their name as well.
329 #
330 # See also 'methods.py' in the Godot repo and 'build' in
331 # https://docs.godotengine.org/en/stable/classes/class_engine.html#class-engine-method-get-version-info
332 BUILD_NAME = "nixpkgs";
333
334 preConfigure = lib.optionalString (editor && withMono) ''
335 # TODO: avoid pulling in dependencies of windows-only project
336 dotnet sln modules/mono/editor/GodotTools/GodotTools.sln \
337 remove modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj
338
339 dotnet restore modules/mono/glue/GodotSharp/GodotSharp.sln
340 dotnet restore modules/mono/editor/GodotTools/GodotTools.sln
341 dotnet restore modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln
342 '';
343
344 # From: https://github.com/godotengine/godot/blob/4.2.2-stable/SConstruct
345 sconsFlags = mkSconsFlagsFromAttrSet {
346 # Options from 'SConstruct'
347 precision = withPrecision; # Floating-point precision level
348 production = true; # Set defaults to build Godot for use in production
349 platform = withPlatform;
350 inherit target;
351 debug_symbols = true;
352
353 # Options from 'platform/linuxbsd/detect.py'
354 alsa = withAlsa;
355 dbus = withDbus; # Use D-Bus to handle screensaver and portal desktop settings
356 fontconfig = withFontconfig; # Use fontconfig for system fonts support
357 pulseaudio = withPulseaudio; # Use PulseAudio
358 speechd = withSpeechd; # Use Speech Dispatcher for Text-to-Speech support
359 touch = withTouch; # Enable touch events
360 udev = withUdev; # Use udev for gamepad connection callbacks
361 wayland = withWayland; # Compile with Wayland support
362 x11 = withX11; # Compile with X11 support
363
364 module_mono_enabled = withMono;
365
366 # aliasing bugs exist with hardening+LTO
367 # https://github.com/godotengine/godot/pull/104501
368 ccflags = "-fno-strict-aliasing";
369 linkflags = "-Wl,--build-id";
370
371 use_sowrap = false;
372 };
373
374 enableParallelBuilding = true;
375
376 strictDeps = true;
377
378 patches = lib.optionals (lib.versionOlder version "4.4") [
379 (fetchpatch {
380 name = "wayland-header-fix.patch";
381 url = "https://github.com/godotengine/godot/commit/6ce71f0fb0a091cffb6adb4af8ab3f716ad8930b.patch";
382 hash = "sha256-hgAtAtCghF5InyGLdE9M+9PjPS1BWXWGKgIAyeuqkoU=";
383 })
384 # Fix a crash in the mono test project build. It no longer seems to
385 # happen in 4.4, but an existing fix couldn't be identified.
386 ./CSharpLanguage-fix-crash-in-reload_assemblies-after-.patch
387 ];
388
389 postPatch = ''
390 # this stops scons from hiding e.g. NIX_CFLAGS_COMPILE
391 perl -pi -e '{ $r += s:(env = Environment\(.*):\1\nenv["ENV"] = os.environ: } END { exit ($r != 1) }' SConstruct
392
393 substituteInPlace thirdparty/glad/egl.c \
394 --replace-fail \
395 'static const char *NAMES[] = {"libEGL.so.1", "libEGL.so"}' \
396 'static const char *NAMES[] = {"${lib.getLib libGL}/lib/libEGL.so"}'
397
398 substituteInPlace thirdparty/glad/gl.c \
399 --replace-fail \
400 'static const char *NAMES[] = {"libGLESv2.so.2", "libGLESv2.so"}' \
401 'static const char *NAMES[] = {"${lib.getLib libGL}/lib/libGLESv2.so"}' \
402
403 substituteInPlace thirdparty/glad/gl{,x}.c \
404 --replace-fail \
405 '"libGL.so.1"' \
406 '"${lib.getLib libGL}/lib/libGL.so"'
407
408 substituteInPlace thirdparty/volk/volk.c \
409 --replace-fail \
410 'dlopen("libvulkan.so.1"' \
411 'dlopen("${lib.getLib vulkan-loader}/lib/libvulkan.so"'
412 '';
413
414 depsBuildBuild = lib.optionals (stdenv.buildPlatform != stdenv.hostPlatform) [
415 buildPackages.stdenv.cc
416 pkg-config
417 ];
418
419 buildInputs =
420 lib.optionals (editor && withMono) dotnet-sdk.packages
421 ++ lib.optional withAlsa alsa-lib
422 ++ lib.optional (withX11 || withWayland) libxkbcommon
423 ++ lib.optionals withX11 [
424 libX11
425 libXcursor
426 libXext
427 libXfixes
428 libXi
429 libXinerama
430 libXrandr
431 libXrender
432 ]
433 ++ lib.optionals withWayland [
434 # libdecor
435 wayland
436 ]
437 ++ lib.optionals withDbus [
438 dbus
439 ]
440 ++ lib.optionals withFontconfig [
441 fontconfig
442 ]
443 ++ lib.optional withPulseaudio libpulseaudio
444 ++ lib.optionals withSpeechd [
445 speechd-minimal
446 glib
447 ]
448 ++ lib.optional withUdev udev;
449
450 nativeBuildInputs = [
451 installShellFiles
452 perl
453 pkg-config
454 scons
455 ]
456 ++ lib.optionals withWayland [ wayland-scanner ]
457 ++ lib.optional (editor && withMono) [
458 makeWrapper
459 dotnet-sdk
460 ];
461
462 postBuild = lib.optionalString (editor && withMono) ''
463 echo "Generating Glue"
464 bin/${binary} --headless --generate-mono-glue modules/mono/glue
465 echo "Building C#/.NET Assemblies"
466 python modules/mono/build_scripts/build_assemblies.py --godot-output-dir bin --precision=${withPrecision}
467 '';
468
469 installPhase = ''
470 runHook preInstall
471
472 mkdir -p "$out"/{bin,libexec}
473 cp -r bin/* "$out"/libexec
474
475 cd "$out"/bin
476 ln -s ../libexec/${binary} godot${lib.versions.majorMinor version}${suffix}
477 ln -s godot${lib.versions.majorMinor version}${suffix} godot${lib.versions.major version}${suffix}
478 ln -s godot${lib.versions.major version}${suffix} godot${suffix}
479 cd -
480 ''
481 + (
482 if editor then
483 ''
484 installManPage misc/dist/linux/godot.6
485
486 mkdir -p "$out"/share/{applications,icons/hicolor/scalable/apps}
487 cp misc/dist/linux/org.godotengine.Godot.desktop \
488 "$out/share/applications/org.godotengine.Godot${lib.versions.majorMinor version}${suffix}.desktop"
489
490 substituteInPlace "$out/share/applications/org.godotengine.Godot${lib.versions.majorMinor version}${suffix}.desktop" \
491 --replace-fail "Exec=godot" "Exec=$out/bin/godot${suffix}" \
492 --replace-fail "Godot Engine" "Godot Engine ${
493 lib.versions.majorMinor version + lib.optionalString withMono " (Mono)"
494 }"
495 cp icon.svg "$out/share/icons/hicolor/scalable/apps/godot.svg"
496 cp icon.png "$out/share/icons/godot.png"
497 ''
498 + lib.optionalString withMono ''
499 mkdir -p "$out"/share/nuget
500 mv "$out"/libexec/GodotSharp/Tools/nupkgs "$out"/share/nuget/source
501
502 wrapProgram "$out"/libexec/${binary} \
503 --prefix NUGET_FALLBACK_PACKAGES ';' "$out"/share/nuget/packages/
504 ''
505 else
506 let
507 template =
508 (lib.replaceStrings
509 [ "template" ]
510 [
511 {
512 linuxbsd = "linux";
513 }
514 .${withPlatform}
515 ]
516 target
517 )
518 + "."
519 + arch;
520 in
521 ''
522 templates="$out"/share/godot/export_templates/${dottedVersion}
523 mkdir -p "$templates"
524 ln -s "$out"/libexec/${binary} "$templates"/${template}
525 ''
526 )
527 + ''
528 runHook postInstall
529 '';
530
531 passthru = {
532 inherit updateScript;
533 tests =
534 mkTests finalAttrs.finalPackage dotnet-sdk
535 // lib.optionalAttrs (editor && withMono) {
536 sdk-override = mkTests finalAttrs.finalPackage dotnet-sdk_alt;
537 };
538 }
539 // lib.optionalAttrs editor {
540 export-template = mkTarget "template_release";
541 export-templates-bin = (
542 callPackage ./export-templates-bin.nix {
543 inherit version withMono;
544 godot = finalAttrs.finalPackage;
545 hash = exportTemplatesHash;
546 }
547 );
548 };
549
550 requiredSystemFeatures = [
551 # fixes: No space left on device
552 "big-parallel"
553 ];
554
555 meta = {
556 changelog = "https://github.com/godotengine/godot/releases/tag/${version}";
557 description = "Free and Open Source 2D and 3D game engine";
558 homepage = "https://godotengine.org";
559 license = lib.licenses.mit;
560 platforms = [
561 "x86_64-linux"
562 "aarch64-linux"
563 ]
564 ++ lib.optional (!withMono) "i686-linux";
565 maintainers = with lib.maintainers; [
566 shiryel
567 corngood
568 ];
569 mainProgram = "godot${suffix}";
570 };
571 };
572
573 unwrapped = stdenv.mkDerivation (
574 if (editor && withMono) then
575 dotnetCorePackages.addNuGetDeps {
576 inherit nugetDeps;
577 overrideFetchAttrs = old: rec {
578 runtimeIds = map (system: dotnetCorePackages.systemToDotnetRid system) old.meta.platforms;
579 buildInputs =
580 old.buildInputs
581 ++ lib.concatLists (lib.attrValues (lib.getAttrs runtimeIds dotnet-sdk.targetPackages));
582 };
583 } attrs
584 else
585 attrs
586 );
587
588 wrapper =
589 if (editor && withMono) then
590 stdenv.mkDerivation (finalAttrs: {
591 __structuredAttrs = true;
592
593 pname = finalAttrs.unwrapped.pname + "-wrapper";
594 inherit (finalAttrs.unwrapped) version outputs meta;
595 inherit unwrapped dotnet-sdk;
596
597 dontUnpack = true;
598 dontConfigure = true;
599 dontBuild = true;
600
601 nativeBuildInputs = [ makeWrapper ];
602 strictDeps = true;
603
604 installPhase = ''
605 runHook preInstall
606
607 mkdir -p "$out"/{bin,libexec,share/applications,nix-support}
608
609 cp -d "$unwrapped"/bin/* "$out"/bin/
610 ln -s "$unwrapped"/libexec/* "$out"/libexec/
611 ln -s "$unwrapped"/share/nuget "$out"/share/
612 cp "$unwrapped/share/applications/org.godotengine.Godot${lib.versions.majorMinor version}${suffix}.desktop" \
613 "$out/share/applications/org.godotengine.Godot${lib.versions.majorMinor version}${suffix}.desktop"
614
615 substituteInPlace "$out/share/applications/org.godotengine.Godot${lib.versions.majorMinor version}${suffix}.desktop" \
616 --replace-fail "Exec=$unwrapped/bin/godot${suffix}" "Exec=$out/bin/godot${suffix}"
617 ln -s "$unwrapped"/share/icons $out/share/
618
619 # ensure dotnet hooks get run
620 echo "${finalAttrs.dotnet-sdk}" >> "$out"/nix-support/propagated-build-inputs
621
622 wrapProgram "$out"/libexec/${binary} \
623 --prefix PATH : "${lib.makeBinPath [ finalAttrs.dotnet-sdk ]}"
624
625 runHook postInstall
626 '';
627
628 postFixup = lib.concatMapStringsSep "\n" (output: ''
629 [[ -e "''$${output}" ]] || ln -s "${unwrapped.${output}}" "''$${output}"
630 '') finalAttrs.unwrapped.outputs;
631
632 passthru = unwrapped.passthru // {
633 tests = mkTests finalAttrs.finalPackage null // {
634 unwrapped = lib.recurseIntoAttrs unwrapped.tests;
635 sdk-override = lib.recurseIntoAttrs (
636 mkTests (finalAttrs.finalPackage.overrideAttrs { dotnet-sdk = dotnet-sdk_alt; }) null
637 );
638 };
639 };
640 })
641 else
642 unwrapped;
643 in
644 wrapper;
645in
646mkTarget "editor"