feat: Add flutter-build.sh

This commit is contained in:
PapaTutuWawa 2023-07-22 00:25:37 +02:00
commit 92687b6513
6 changed files with 361 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
# NixOS artifacts
result

22
LICENSE Normal file
View File

@ -0,0 +1,22 @@
MIT License
Copyright (c) 2023 Alexander "PapaTutuWawa"
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

12
README.md Normal file
View File

@ -0,0 +1,12 @@
# bits-and-bytes
A collection of small scripts (and other stuff) that I want to reuse across
my personal projects.
## Nix
For users of Nix, I also provide a Flake (see `flake.nix`).
## License
See `LICENSE`.

61
flake.lock Normal file
View File

@ -0,0 +1,61 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1689068808,
"narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1689935543,
"narHash": "sha256-6GQ9ib4dA/r1leC5VUpsBo0BmDvNxLjKrX1iyL+h8mc=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "e43e2448161c0a2c4928abec4e16eae1516571bc",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

36
flake.nix Normal file
View File

@ -0,0 +1,36 @@
{
description = "A collection of reusable scripts for various use-cases";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }: flake-utils.lib.eachDefaultSystem (system: let
pkgs = import nixpkgs { inherit system; };
flutterBuildRaw = {stdenv}: stdenv.mkDerivation {
pname = "flutter-build";
version = "0.1.0";
src = ./.;
installPhase = ''
mkdir -p $out/bin
install --mode 555 src/flutter-build.sh $out/bin/flutter-build
'';
};
flutter-build-raw = pkgs.callPackage flutterBuildRaw {};
in {
packages = {
# The raw flutter-build script
inherit flutter-build-raw;
# A wrapper around the script that already provides the notify-send option
flutter-build = pkgs.writeScriptBin "flutter-build" ''
${flutter-build-raw}/bin/flutter-build \
--notify-send ${pkgs.libnotify}/bin/notify-send \
$@
'';
};
});
}

228
src/flutter-build.sh Normal file
View File

@ -0,0 +1,228 @@
#!/bin/bash
set -e
# Parse CLI arguments
# (Thanks StackOverflow https://stackoverflow.com/questions/192249/how-do-i-parse-command-line-arguments-in-bash)
while [[ $# -gt 0 ]]; do
case $1 in
--name)
# The name of the application. Only useful for displaying in the script.
# If not specified, will use the "name: " attribute from the pubspec.yaml
NAME=$2
shift
shift
;;
--not-signed)
# Tells the script that the APKs are *NOT* signed and have to be manually
# signed.
ALREADY_SIGNED=n
shift
;;
--zipalign)
# The path to the zipalign binary. If not specified, lets the shell resolve the path
# itself.
# Only useful with --not-signed.
ZIPALIGN=$2
shift
shift
;;
--apksigner)
# The path to the apksigner binary. If not specified, lets the shell resolve the path
# itself.
# Only useful with --not-signed.
APKSIGNER=$2
shift
shift
;;
--notify-send)
# The path to the notify-send binary. If not specified, lets the shell resolve the path
# itself.
# Only useful with --not-signed.
NOTIFY_SEND=$2
shift
shift
;;
--provider-config)
# The path to the provider config passed to apksigner via --provider-arg.
# Only useful with --not-signed.
PROVIDER_CONFIG=$2
shift
shift
;;
--no-clean)
# Does not run "flutter clean" before running the build.
CLEAN_BUILD=n
shift
;;
--min-sdk-version)
# The minimum SDK version that should be able to validate the APK signature.
# Only useful with --not-signed.
MIN_SDK_VERSION=$2
shift
shift
;;
--check)
# Prints all parsed options and exits.
JUST_CHECK=y
shift
;;
--skip-build)
# Skips the entire build but not the signing, if --not-signed is specified. If --not-signed
# is not specified, then the build is skipped, but the APKs are copied into the correct folder.
# This flag expects that the build has run at least once and the correct release directory
# already exists.
SKIP_BUILD=y
shift
;;
--no-notification)
# Does not create a notification when asking for the PIN of the Yubikey.
# Only useful with --not-signed.
SEND_NOTIFICATION=n
shift
;;
*)
echo "Unknown argument: $1"
shift
;;
esac
done
# Extract the name, if not passed
if [[ -z "$NAME" ]]; then
NAME=$(grep -E "^name: " pubspec.yaml | cut -b 6-)
fi
# Default values
APKSIGNER=${APKSIGNER:-apksigner}
ZIPALIGN=${ZIPALIGN:-zipalign}
ALREADY_SIGNED=${ALREADY_SIGNED:-y}
MIN_SDK_VERSION=${MIN_SDK_VERSION:-24}
CLEAN_BUILD=${CLEAN_BUILD:-y}
SKIP_BUILD=${SKIP_BUILD:-n}
NOTIFY_SEND=${NOTIFY_SEND:-notify-send}
SEND_NOTIFICATION=${SEND_NOTIFICATION:-y}
# Parse version info
version=$(grep -E "^version: " pubspec.yaml | cut -b 10-)
IFS="+" read -ra version_parts <<< "$version"
version_code="${version_parts[1]}"
release_dir="./release-${version}"
# Print a header
echo "===== ${NAME} ====="
echo "Building version ${version}"
echo "Moving APKs into ${release_dir} after build"
echo "Clean build: ${CLEAN_BUILD}"
echo "Skipping build: ${SKIP_BUILD}"
echo "Sending notification: ${SEND_NOTIFICATION}"
echo "APKs already signed: ${ALREADY_SIGNED}"
if [[ "$ALREADY_SIGNED" = "n" ]]; then
echo "Used zipalign: ${ZIPALIGN}"
echo "Used apksigner: ${APKSIGNER}"
echo "Provider config: ${PROVIDER_CONFIG}"
echo "Minimum SDK verifiability: ${MIN_SDK_VERSION}"
fi
if [[ "${JUST_CHECK}" = "y" ]]; then
echo
echo "Terminating here because --check was used"
exit
fi
# For better readability, print an empty line
echo
# Check if we have a changelog file for that version
if [[ ! -f "./fastlane/metadata/android/en-US/changelogs/$version_code.txt" ]]; then
echo "Warning: No changelog item for $version_code"
fi
if [[ "${SKIP_BUILD}" = "y" ]]; then
echo "Skipping build because of --skip-build"
else
# Create the directory
[[ -d "${release_dir}" ]] && echo "Warning: Release directory ${release_dir} already exists"
mkdir "${release_dir}" || true
if [[ "${CLEAN_BUILD}" = "y" ]]; then
# Clean flutter build
flutter clean
fi
# Get dependencies
flutter pub get
# Build everything again
flutter pub run build_runner build --delete-conflicting-outputs
# Build the release apk
flutter build apk \
--release \
--split-per-abi \
--split-debug-info="${release_dir}/debug-info"
fi
# If we have to sign, ask for the PIN upfront
if [[ "$ALREADY_SIGNED" = "n" ]]; then
[[ "${SEND_NOTIFICATION}" = "y" ]] && ${NOTIFY_SEND} \
--urgency normal \
--app-name "Flutter Build" \
--icon dialog-password \
"Signing PIN required" \
"The build is done and the PIN for manually signing the APKs must be entered"
# Thanks https://geoffreymetais.github.io/code/key-signing/#scripting
echo "Please enter Yubikey PIN code "
stty -echo
trap 'stty echo' EXIT
read -p 'PIN: ' YUBI_PIN
stty echo
trap - EXIT
fi
# Move everything
for platform in arm64-v8a armeabi-v7a x86_64; do
echo "Processing the $platform release..."
raw_apk="build/app/outputs/flutter-apk/app-${platform}-release.apk"
if [[ "$ALREADY_SIGNED" = "y" ]]; then
# Simply copy artifacts
cp -f "$raw_apk" "${release_dir}/${NAME}-${platform}-release.apk"
else
# https://developer.android.com/build/building-cmdline#sign_cmdline
aligned_apk="${release_dir}/app-${platform}-release-aligned.apk"
signed_apk="${release_dir}/app-${platform}-release.apk"
${ZIPALIGN} -p 4 "$raw_apk" "${aligned_apk}"
# NOTE: "-J-add-exports jdk.crypto.cryptoki/sun.security.pkcs11=ALL-UNNAMED" tells the wrapper that is
# `apksigner` to append "--add-exports jdk.[...]" as a parameter to the Java CLI. It is in
# quotation marks such that it is passed with only one (1) dash (it would otherwise be "--add-exports -jdk.[...]",
# which is wrong).
# At least on NixOS, apksigner refuses to use the sun.security.pkcs11.SunPKCS11 provider because
# it is not exported for its unnamed class. So, we have to do a little trickery to "export" the
# class to apksigner.
${APKSIGNER} "-J-add-exports jdk.crypto.cryptoki/sun.security.pkcs11=ALL-UNNAMED" sign \
--ks NONE \
--ks-pass "pass:$YUBI_PIN" \
--provider-class sun.security.pkcs11.SunPKCS11 \
--provider-arg "${PROVIDER_CONFIG}" \
--ks-type PKCS11 \
--min-sdk-version "${MIN_SDK_VERSION}" \
--max-sdk-version 34 \
--in "${aligned_apk}" \
--out "${signed_apk}" \
--v1-signing-enabled \
--v2-signing-enabled \
--v3-signing-enabled \
--v4-signing-enabled
# Safety check
${APKSIGNER} verify --min-sdk-version "${MIN_SDK_VERSION}" "${signed_apk}"
# Remove temporary artifact
rm "${aligned_apk}"
fi
done
# Prevent leaking the PIN
unset YUBI_PIN