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