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