{ lib, callPackage, runCommand, makeWrapper, wrapGAppsHook3, buildDartApplication, cacert, glib, flutter, pkg-config, buildPackages, }: # absolutely no mac support for now { pubGetScript ? null, flutterBuildFlags ? [ ], targetFlutterPlatform ? "linux", extraWrapProgramArgs ? "", flutterMode ? null, ... }@args: let hasEngine = flutter ? engine && flutter.engine != null && flutter.engine.meta.available; flutterMode = args.flutterMode or (if hasEngine then flutter.engine.runtimeMode else "release"); flutterFlags = lib.optional hasEngine "--local-engine host_${flutterMode}${ lib.optionalString (!flutter.engine.isOptimized) "_unopt" }"; flutterBuildFlags = [ "--${flutterMode}" ] ++ (args.flutterBuildFlags or [ ]) ++ flutterFlags; builderArgs = let universal = args // { inherit flutterMode flutterFlags flutterBuildFlags; sdkSetupScript = '' # Pub needs SSL certificates. Dart normally looks in a hardcoded path. # https://github.com/dart-lang/sdk/blob/3.1.0/runtime/bin/security_context_linux.cc#L48 # # Dart does not respect SSL_CERT_FILE... # https://github.com/dart-lang/sdk/issues/48506 # ...and Flutter does not support --root-certs-file, so the path cannot be manually set. # https://github.com/flutter/flutter/issues/56607 # https://github.com/flutter/flutter/issues/113594 # # libredirect is of no use either, as Flutter does not pass any # environment variables (including LD_PRELOAD) to the Pub process. # # Instead, Flutter is patched to allow the path to the Dart binary used for # Pub commands to be overriden. export NIX_FLUTTER_PUB_DART="${ runCommand "dart-with-certs" { nativeBuildInputs = [ makeWrapper ]; } '' mkdir -p "$out/bin" makeWrapper ${flutter.dart}/bin/dart "$out/bin/dart" \ --add-flags "--root-certs-file=${cacert}/etc/ssl/certs/ca-bundle.crt" '' }/bin/dart" export HOME="$NIX_BUILD_TOP" flutter config $flutterFlags --no-analytics &>/dev/null # mute first-run flutter config $flutterFlags --enable-linux-desktop >/dev/null ''; pubGetScript = args.pubGetScript or "flutter${lib.optionalString hasEngine " --local-engine $flutterMode"} pub get"; sdkSourceBuilders = { # https://github.com/dart-lang/pub/blob/68dc2f547d0a264955c1fa551fa0a0e158046494/lib/src/sdk/flutter.dart#L81 "flutter" = name: runCommand "flutter-sdk-${name}" { passthru.packageRoot = "."; } '' for path in '${flutter}/packages/${name}' '${flutter}/bin/cache/pkg/${name}'; do if [ -d "$path" ]; then ln -s "$path" "$out" break fi done if [ ! -e "$out" ]; then echo 1>&2 'The Flutter SDK does not contain the requested package: ${name}!' exit 1 fi ''; # https://github.com/dart-lang/pub/blob/e1fbda73d1ac597474b82882ee0bf6ecea5df108/lib/src/sdk/dart.dart#L80 "dart" = name: runCommand "dart-sdk-${name}" { passthru.packageRoot = "."; } '' for path in '${flutter.dart}/pkg/${name}'; do if [ -d "$path" ]; then ln -s "$path" "$out" break fi done if [ ! -e "$out" ]; then echo 1>&2 'The Dart SDK does not contain the requested package: ${name}!' exit 1 fi ''; }; # https://github.com/flutter/flutter/blob/edada7c56edf4a183c1735310e123c7f923584f1/packages/flutter_tools/lib/src/dart/pub.dart#L804 extraPackageConfigSetup = lib.optionalString (lib.versionOlder flutter.version "3.34.0") '' if [ "$("${lib.getExe buildPackages.yq}" '.flutter.generate // false' pubspec.yaml)" = "true" ]; then if ! "${lib.getExe buildPackages.jq}" -e '.packages[] | select(.name == "flutter_gen")' "$out" >/dev/null 2>&1; then export TEMP_PACKAGES=$(mktemp) "${lib.getExe buildPackages.jq}" '.packages |= . + [{ name: "flutter_gen", rootUri: "flutter_gen", languageVersion: "2.12" }]' "$out" > "$TEMP_PACKAGES" cp "$TEMP_PACKAGES" "$out" rm "$TEMP_PACKAGES" unset TEMP_PACKAGES fi fi ''; }; in { inherit universal; linux = universal // { outputs = universal.outputs or [ ] ++ [ "debug" ]; nativeBuildInputs = (universal.nativeBuildInputs or [ ]) ++ [ wrapGAppsHook3 # Flutter requires pkg-config for Linux desktop support, and many plugins # attempt to use it. # # It is available to the `flutter` tool through its wrapper, but it must be # added here as well so the setup hook adds plugin dependencies to the # pkg-config search paths. pkg-config ]; buildInputs = (universal.buildInputs or [ ]) ++ [ glib ]; dontDartBuild = true; buildPhase = universal.buildPhase or '' runHook preBuild mkdir -p build/flutter_assets/fonts flutter build linux -v --split-debug-info="$debug" $flutterBuildFlags runHook postBuild ''; dontDartInstall = true; installPhase = universal.installPhase or '' runHook preInstall built=build/linux/*/$flutterMode/bundle mkdir -p $out/bin mkdir -p $out/app mv $built $out/app/$pname for f in $(find $out/app/$pname -iname "*.desktop" -type f); do install -D $f $out/share/applications/$(basename $f) done for f in $(find $out/app/$pname -maxdepth 1 -type f); do ln -s $f $out/bin/$(basename $f) done # make *.so executable find $out/app/$pname -iname "*.so" -type f -exec chmod +x {} + # remove stuff like /build/source/packages/ubuntu_desktop_installer/linux/flutter/ephemeral for f in $(find $out/app/$pname -executable -type f); do if patchelf --print-rpath "$f" | grep /build; then # this ignores static libs (e,g. libapp.so) also echo "strip RPath of $f" newrp=$(patchelf --print-rpath $f | sed -r "s|/build.*ephemeral:||g" | sed -r "s|/build.*profile:||g") patchelf --set-rpath "$newrp" "$f" fi done runHook postInstall ''; dontWrapGApps = true; extraWrapProgramArgs = '' ''${gappsWrapperArgs[@]} \ ${extraWrapProgramArgs} ''; }; web = universal // { dontDartBuild = true; buildPhase = universal.buildPhase or '' runHook preBuild mkdir -p build/flutter_assets/fonts flutter build web -v $flutterBuildFlags runHook postBuild ''; dontDartInstall = true; installPhase = universal.installPhase or '' runHook preInstall cp -r build/web "$out" runHook postInstall ''; }; } .${targetFlutterPlatform} or (throw "Unsupported Flutter host platform: ${targetFlutterPlatform}"); minimalFlutter = flutter.override { supportedTargetFlutterPlatforms = [ "universal" targetFlutterPlatform ]; }; buildAppWith = flutter: buildDartApplication.override { dart = flutter; }; in buildAppWith minimalFlutter ( builderArgs // { passthru = builderArgs.passthru or { } // { multiShell = buildAppWith flutter builderArgs; }; } )