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