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