flutter.buildFlutterApplication: Refactor dependency setup as a standalone derivation with an output hook

This allows anything that needs to build a Dart application (such as a future `buildDartApplication` function) to use the same setup.
This commit is contained in:
hacker1024 2023-04-16 00:17:41 +10:00
parent 607a57d48e
commit 915a6779fc
3 changed files with 195 additions and 119 deletions

View File

@ -0,0 +1,133 @@
{ stdenvNoCC
, lib
, makeSetupHook
, dart
, git
, cacert
}:
{
# The output hash of the dependencies for this project.
vendorHash ? ""
# Commands to run once before using Dart or pub.
, sdkSetupScript ? ""
# Commands to run to populate the pub cache.
, pubGetScript ? "dart pub get"
# Arguments used in the derivation that builds the Dart package.
# Passing these is recommended to ensure that the same steps are made to prepare the sources in both this
# derivation and the one that builds the Dart package.
, buildDrvArgs ? { }
, ...
}@args:
# This is a fixed-output derivation and setup hook that can be used to fetch dependencies for Dart projects.
# It is designed to be placed in the nativeBuildInputs of a derivation that builds a Dart package.
# Providing the buildDrvArgs argument is highly recommended.
let
buildDrvInheritArgNames = [
"name"
"pname"
"version"
"src"
"sourceRoot"
"setSourceRoot"
"preUnpack"
"unpackPhase"
"unpackCmd"
"postUnpack"
"prePatch"
"patchPhase"
"patches"
"patchFlags"
"postPatch"
];
buildDrvInheritArgs = builtins.foldl'
(attrs: arg:
if buildDrvArgs ? ${arg}
then attrs // { ${arg} = buildDrvArgs.${arg}; }
else attrs)
{ }
buildDrvInheritArgNames;
drvArgs = buildDrvInheritArgs // (removeAttrs args [ "buildDrvArgs" ]);
name = (if drvArgs ? name then drvArgs.name else "${drvArgs.pname}-${drvArgs.version}");
deps =
# This
stdenvNoCC.mkDerivation ({
name = "${name}-dart-deps";
nativeBuildInputs = [
dart
git
];
# avoid pub phase
dontBuild = true;
configurePhase = ''
# Configure the package cache
export PUB_CACHE="$out/cache/.pub-cache"
mkdir -p "$PUB_CACHE"
${sdkSetupScript}
'';
installPhase = ''
${pubGetScript}
# so we can use lock, diff yaml
mkdir -p "$out/pubspec"
cp "pubspec.yaml" "$out/pubspec"
cp "pubspec.lock" "$out/pubspec"
# nuke nondeterminism
# Remove Git directories in the Git package cache - these are rarely used by Pub,
# which instead maintains a corresponsing mirror and clones cached packages through it.
find "$PUB_CACHE" -name .git -type d -prune -exec rm -rf {} +
# Remove continuously updated package metadata caches
rm -rf "$PUB_CACHE"/hosted/*/.cache # Not pinned by pubspec.lock
rm -rf "$PUB_CACHE"/git/cache/*/* # Recreate this on the other end. See: https://github.com/dart-lang/pub/blob/c890afa1d65b340fa59308172029680c2f8b0fc6/lib/src/source/git.dart#L531
# Miscelaneous transient package cache files
rm -f "$PUB_CACHE"/README.md # May change with different Dart versions
rm -rf "$PUB_CACHE"/_temp # https://github.com/dart-lang/pub/blob/c890afa1d65b340fa59308172029680c2f8b0fc6/lib/src/system_cache.dart#L131
rm -rf "$PUB_CACHE"/log # https://github.com/dart-lang/pub/blob/c890afa1d65b340fa59308172029680c2f8b0fc6/lib/src/command.dart#L348
'';
GIT_SSL_CAINFO = "${cacert}/etc/ssl/certs/ca-bundle.crt";
SSL_CERT_FILE = "${cacert}/etc/ssl/certs/ca-bundle.crt";
impureEnvVars = lib.fetchers.proxyImpureEnvVars ++ [
"GIT_PROXY_COMMAND"
"NIX_GIT_SSL_CAINFO"
"SOCKS_SERVER"
];
# Patching shebangs introduces input references to this fixed-output derivation.
# This triggers a bug in Nix, causing the output path to change unexpectedly.
# https://github.com/NixOS/nix/issues/6660
dontPatchShebangs = true;
# The following operations are not generally useful for this derivation.
# If a package does contain some native components used at build time,
# please file an issue.
dontStrip = true;
dontMoveSbin = true;
dontPatchELF = true;
outputHashAlgo = "sha256";
outputHashMode = "recursive";
outputHash = if vendorHash != "" then vendorHash else lib.fakeSha256;
} // drvArgs);
in
(makeSetupHook {
# The setup hook should not be part of the fixed-output derivation.
# Updates to the hook script should not change vendor hashes, and it won't
# work at all anyway due to https://github.com/NixOS/nix/issues/6660.
name = "${name}-dart-deps-setup-hook";
substitutions = { inherit deps; };
}) ./setup-hook.sh

View File

@ -0,0 +1,42 @@
preConfigureHooks+=(_setupPubCache)
_setupPubCache() {
deps="@deps@"
# Configure the package cache.
export PUB_CACHE="$(mktemp -d)"
mkdir -p "$PUB_CACHE"
if [ -d "$deps/cache/.pub-cache/git" ]; then
# Link the Git package cache.
mkdir -p "$PUB_CACHE/git"
ln -s "$deps/cache/.pub-cache/git"/* "$PUB_CACHE/git"
# Recreate the internal Git cache subdirectory.
# See: https://github.com/dart-lang/pub/blob/c890afa1d65b340fa59308172029680c2f8b0fc6/lib/src/source/git.dart#L339)
# Blank repositories are created instead of attempting to match the cache mirrors to checkouts.
# This is not an issue, as pub does not need the mirrors in the Flutter build process.
rm "$PUB_CACHE/git/cache" && mkdir "$PUB_CACHE/git/cache"
for mirror in $(ls -A "$deps/cache/.pub-cache/git/cache"); do
git --git-dir="$PUB_CACHE/git/cache/$mirror" init --bare --quiet
done
fi
# Link the remaining package cache directories.
# At this point, any subdirectories that must be writable must have been taken care of.
for file in $(comm -23 <(ls -A "$deps/cache/.pub-cache") <(ls -A "$PUB_CACHE")); do
ln -s "$deps/cache/.pub-cache/$file" "$PUB_CACHE/$file"
done
# ensure we're using a lockfile for the right package version
if [ -e pubspec.lock ]; then
# FIXME: currently this is broken. in theory this should not break, but flutter has it's own way of doing things.
# diff -u pubspec.lock "$deps/pubspec/pubspec.lock"
true
else
cp -v "$deps/pubspec/pubspec.lock" .
# Sometimes the pubspec.lock will get opened in write mode, even when offline.
chmod u+w pubspec.lock
fi
diff -u pubspec.yaml "$deps/pubspec/pubspec.yaml"
}

View File

@ -1,4 +1,5 @@
{ lib
, callPackage
, stdenvNoCC
, llvmPackages_13
, cacert
@ -8,135 +9,35 @@
# absolutely no mac support for now
args:
{ pubGetScript ? "flutter pub get"
, vendorHash
, nativeBuildInputs ? [ ]
, ...
}@args:
let
pl = n: "##FLUTTER_${n}_PLACEHOLDER_MARKER##";
placeholder_deps = pl "DEPS";
placeholder_flutter = pl "FLUTTER";
fetchAttrs = [ "src" "sourceRoot" "setSourceRoot" "unpackPhase" "patches" ];
getAttrsOrNull = names: attrs: lib.genAttrs names (name: if attrs ? ${name} then attrs.${name} else null);
flutterSetupScript = ''
export HOME="$NIX_BUILD_TOP"
flutter config --no-analytics &>/dev/null # mute first-run
flutter config --enable-linux-desktop >/dev/null
'';
deps = callPackage ../dart/fetch-dart-deps { dart = flutter; } {
sdkSetupScript = flutterSetupScript;
inherit pubGetScript vendorHash;
buildDrvArgs = args;
};
self =
(self: llvmPackages_13.stdenv.mkDerivation (args // {
deps = stdenvNoCC.mkDerivation (lib.recursiveUpdate (getAttrsOrNull fetchAttrs args) {
name = "${self.name}-deps-flutter";
nativeBuildInputs = [
flutter
git
];
# avoid pub phase
dontBuild = true;
installPhase = ''
TMP=$(mktemp -d)
export HOME="$TMP"
# Configure the package cache
export PUB_CACHE="$out/cache/.pub-cache"
mkdir -p "$PUB_CACHE"
flutter config --no-analytics &>/dev/null # mute first-run
flutter config --enable-linux-desktop
flutter packages get
${lib.optionalString (args ? flutterExtraFetchCommands) args.flutterExtraFetchCommands}
# so we can use lock, diff yaml
mkdir -p "$out/pubspec"
cp "pubspec.yaml" "$out/pubspec"
cp "pubspec.lock" "$out/pubspec"
# nuke nondeterminism
# Remove Git directories in the Git package cache - these are rarely used by Pub,
# which instead maintains a corresponsing mirror and clones cached packages through it.
find "$PUB_CACHE" -name .git -type d -prune -exec rm -rf {} +
# Remove continuously updated package metadata caches
rm -rf "$PUB_CACHE"/hosted/*/.cache # Not pinned by pubspec.lock
rm -rf "$PUB_CACHE"/git/cache/*/* # Recreate this on the other end. See: https://github.com/dart-lang/pub/blob/c890afa1d65b340fa59308172029680c2f8b0fc6/lib/src/source/git.dart#L531
# Miscelaneous transient package cache files
rm -f "$PUB_CACHE"/README.md # May change with different Dart versions
rm -rf "$PUB_CACHE"/_temp # https://github.com/dart-lang/pub/blob/c890afa1d65b340fa59308172029680c2f8b0fc6/lib/src/system_cache.dart#L131
rm -rf "$PUB_CACHE"/log # https://github.com/dart-lang/pub/blob/c890afa1d65b340fa59308172029680c2f8b0fc6/lib/src/command.dart#L348
'';
GIT_SSL_CAINFO = "${cacert}/etc/ssl/certs/ca-bundle.crt";
SSL_CERT_FILE = "${cacert}/etc/ssl/certs/ca-bundle.crt";
impureEnvVars = lib.fetchers.proxyImpureEnvVars ++ [
"GIT_PROXY_COMMAND" "NIX_GIT_SSL_CAINFO" "SOCKS_SERVER"
];
# unnecesarry
dontFixup = true;
# Patching shebangs introduces input references to this fixed-output derivation.
# This triggers a bug in Nix, causing the output path to change unexpectedly.
# https://github.com/NixOS/nix/issues/6660
dontPatchShebangs = true;
outputHashAlgo = if self ? vendorHash then null else "sha256";
outputHashMode = "recursive";
outputHash = if self ? vendorHash then
self.vendorHash
else if self ? vendorSha256 then
self.vendorSha256
else
lib.fakeSha256;
});
nativeBuildInputs = [
deps
flutter
git
] ++ lib.optionals (args ? nativeBuildInputs) args.nativeBuildInputs;
buildInputs = lib.optionals (args ? buildInputs) args.buildInputs;
] ++ nativeBuildInputs;
configurePhase = ''
runHook preConfigure
TMP=$(mktemp -d)
export HOME="$TMP"
flutter config --no-analytics &>/dev/null # mute first-run
flutter config --enable-linux-desktop
# Configure the package cache
export PUB_CACHE="$TMP/.pub-cache"
mkdir -p "$PUB_CACHE"
# Link the Git package cache.
mkdir -p "$PUB_CACHE/git"
ln -s "$deps/cache/.pub-cache/git"/* "$PUB_CACHE/git"
# Recreate the internal Git cache subdirectory.
# See: https://github.com/dart-lang/pub/blob/c890afa1d65b340fa59308172029680c2f8b0fc6/lib/src/source/git.dart#L339)
# Blank repositories are created instead of attempting to match the cache mirrors to checkouts.
# This is not an issue, as pub does not need the mirrors in the Flutter build process.
rm "$PUB_CACHE/git/cache" && mkdir "$PUB_CACHE/git/cache"
for mirror in $(ls -A "$deps/cache/.pub-cache/git/cache"); do
git --git-dir="$PUB_CACHE/git/cache/$mirror" init --bare --quiet
done
# Link the remaining package cache directories.
# At this point, any subdirectories that must be writable must have been taken care of.
for file in $(comm -23 <(ls -A "$deps/cache/.pub-cache") <(ls -A "$PUB_CACHE")); do
ln -s "$deps/cache/.pub-cache/$file" "$PUB_CACHE/$file"
done
# ensure we're using a lockfile for the right package version
if [ -e pubspec.lock ]; then
# FIXME: currently this is broken. in theory this should not break, but flutter has it's own way of doing things.
# diff -u pubspec.lock "$deps/pubspec/pubspec.lock"
true
else
cp -v "$deps/pubspec/pubspec.lock" .
# Sometimes the pubspec.lock will get opened in write mode, even when offline.
chmod u+w pubspec.lock
fi
diff -u pubspec.yaml "$deps/pubspec/pubspec.yaml"
${flutterSetupScript}
runHook postConfigure
'';