1{ lib
2, callPackage
3, runCommand
4, makeWrapper
5, wrapGAppsHook3
6, buildDartApplication
7, cacert
8, glib
9, flutter
10, pkg-config
11, jq
12, yq
13, moreutils
14}:
15
16# absolutely no mac support for now
17
18{ pubGetScript ? "flutter pub get"
19, flutterBuildFlags ? [ ]
20, targetFlutterPlatform ? "linux"
21, extraWrapProgramArgs ? ""
22, ...
23}@args:
24
25let
26 builderArgs = rec {
27 universal = args // {
28 sdkSetupScript = ''
29 # Pub needs SSL certificates. Dart normally looks in a hardcoded path.
30 # https://github.com/dart-lang/sdk/blob/3.1.0/runtime/bin/security_context_linux.cc#L48
31 #
32 # Dart does not respect SSL_CERT_FILE...
33 # https://github.com/dart-lang/sdk/issues/48506
34 # ...and Flutter does not support --root-certs-file, so the path cannot be manually set.
35 # https://github.com/flutter/flutter/issues/56607
36 # https://github.com/flutter/flutter/issues/113594
37 #
38 # libredirect is of no use either, as Flutter does not pass any
39 # environment variables (including LD_PRELOAD) to the Pub process.
40 #
41 # Instead, Flutter is patched to allow the path to the Dart binary used for
42 # Pub commands to be overriden.
43 export NIX_FLUTTER_PUB_DART="${runCommand "dart-with-certs" { nativeBuildInputs = [ makeWrapper ]; } ''
44 mkdir -p "$out/bin"
45 makeWrapper ${flutter.dart}/bin/dart "$out/bin/dart" \
46 --add-flags "--root-certs-file=${cacert}/etc/ssl/certs/ca-bundle.crt"
47 ''}/bin/dart"
48
49 export HOME="$NIX_BUILD_TOP"
50 flutter config --no-analytics &>/dev/null # mute first-run
51 flutter config --enable-linux-desktop >/dev/null
52 '';
53
54 inherit pubGetScript;
55
56 sdkSourceBuilders = {
57 # https://github.com/dart-lang/pub/blob/68dc2f547d0a264955c1fa551fa0a0e158046494/lib/src/sdk/flutter.dart#L81
58 "flutter" = name: runCommand "flutter-sdk-${name}" { passthru.packageRoot = "."; } ''
59 for path in '${flutter}/packages/${name}' '${flutter}/bin/cache/pkg/${name}'; do
60 if [ -d "$path" ]; then
61 ln -s "$path" "$out"
62 break
63 fi
64 done
65
66 if [ ! -e "$out" ]; then
67 echo 1>&2 'The Flutter SDK does not contain the requested package: ${name}!'
68 exit 1
69 fi
70 '';
71 };
72
73 extraPackageConfigSetup = ''
74 # https://github.com/flutter/flutter/blob/3.13.8/packages/flutter_tools/lib/src/dart/pub.dart#L755
75 if [ "$('${yq}/bin/yq' '.flutter.generate // false' pubspec.yaml)" = "true" ]; then
76 '${jq}/bin/jq' '.packages |= . + [{
77 name: "flutter_gen",
78 rootUri: "flutter_gen",
79 languageVersion: "2.12",
80 }]' "$out" | '${moreutils}/bin/sponge' "$out"
81 fi
82 '';
83 };
84
85 linux = universal // {
86 outputs = universal.outputs or [ ] ++ [ "debug" ];
87
88 nativeBuildInputs = (universal.nativeBuildInputs or [ ]) ++ [
89 wrapGAppsHook3
90
91 # Flutter requires pkg-config for Linux desktop support, and many plugins
92 # attempt to use it.
93 #
94 # It is available to the `flutter` tool through its wrapper, but it must be
95 # added here as well so the setup hook adds plugin dependencies to the
96 # pkg-config search paths.
97 pkg-config
98 ];
99
100 buildInputs = (universal.buildInputs or [ ]) ++ [ glib ];
101
102 dontDartBuild = true;
103 buildPhase = universal.buildPhase or ''
104 runHook preBuild
105
106 mkdir -p build/flutter_assets/fonts
107
108 flutter build linux -v --release --split-debug-info="$debug" ${builtins.concatStringsSep " " (map (flag: "\"${flag}\"") flutterBuildFlags)}
109
110 runHook postBuild
111 '';
112
113 dontDartInstall = true;
114 installPhase = universal.installPhase or ''
115 runHook preInstall
116
117 built=build/linux/*/release/bundle
118
119 mkdir -p $out/bin
120 mv $built $out/app
121
122 for f in $(find $out/app -iname "*.desktop" -type f); do
123 install -D $f $out/share/applications/$(basename $f)
124 done
125
126 for f in $(find $out/app -maxdepth 1 -type f); do
127 ln -s $f $out/bin/$(basename $f)
128 done
129
130 # make *.so executable
131 find $out/app -iname "*.so" -type f -exec chmod +x {} +
132
133 # remove stuff like /build/source/packages/ubuntu_desktop_installer/linux/flutter/ephemeral
134 for f in $(find $out/app -executable -type f); do
135 if patchelf --print-rpath "$f" | grep /build; then # this ignores static libs (e,g. libapp.so) also
136 echo "strip RPath of $f"
137 newrp=$(patchelf --print-rpath $f | sed -r "s|/build.*ephemeral:||g" | sed -r "s|/build.*profile:||g")
138 patchelf --set-rpath "$newrp" "$f"
139 fi
140 done
141
142 runHook postInstall
143 '';
144
145 dontWrapGApps = true;
146 extraWrapProgramArgs = ''
147 ''${gappsWrapperArgs[@]} \
148 ${extraWrapProgramArgs}
149 '';
150 };
151
152 web = universal // {
153 dontDartBuild = true;
154 buildPhase = universal.buildPhase or ''
155 runHook preBuild
156
157 mkdir -p build/flutter_assets/fonts
158
159 flutter build web -v --release ${builtins.concatStringsSep " " (map (flag: "\"${flag}\"") flutterBuildFlags)}
160
161 runHook postBuild
162 '';
163
164 dontDartInstall = true;
165 installPhase = universal.installPhase or ''
166 runHook preInstall
167
168 cp -r build/web "$out"
169
170 runHook postInstall
171 '';
172 };
173 }.${targetFlutterPlatform} or (throw "Unsupported Flutter host platform: ${targetFlutterPlatform}");
174
175 minimalFlutter = flutter.override { supportedTargetFlutterPlatforms = [ "universal" targetFlutterPlatform ]; };
176
177 buildAppWith = flutter: buildDartApplication.override { dart = flutter; };
178in
179buildAppWith minimalFlutter (builderArgs // { passthru = builderArgs.passthru or { } // { multiShell = buildAppWith flutter builderArgs; }; })