termux-packages/scripts/build-bootstraps.sh
2022-05-11 18:20:56 +02:00

467 lines
13 KiB
Bash
Executable File

#!/usr/bin/env bash
# shellcheck disable=SC2039,SC2059
# Title: build-bootstrap.sh
# Description: A script to build bootstrap archives for the termux-app
# from local package sources instead of debs published in
# apt repo like done by generate-bootstrap.sh. It allows
# bootstrap archives to be easily built for (forked) termux
# apps without having to publish an apt repo first.
# Usage: run "build-bootstrap.sh --help"
version=0.1.0
set -e
. $(dirname "$(realpath "$0")")/properties.sh
BOOTSTRAP_TMPDIR=$(mktemp -d "${TMPDIR:-/tmp}/bootstrap-tmp.XXXXXXXX")
# By default, bootstrap archives are compatible with Android >=7.0
# and <10.
BOOTSTRAP_ANDROID10_COMPATIBLE=false
# By default, bootstrap archives will be built for all architectures
# supported by Termux application.
# Override with option '--architectures'.
TERMUX_DEFAULT_ARCHITECTURES=("aarch64" "arm" "i686" "x86_64")
TERMUX_ARCHITECTURES=("${TERMUX_DEFAULT_ARCHITECTURES[@]}")
TERMUX_PACKAGES_DIRECTORY="/home/builder/termux-packages"
TERMUX_BUILT_DEBS_DIRECTORY="$TERMUX_PACKAGES_DIRECTORY/output"
TERMUX_BUILT_PACKAGES_DIRECTORY="/data/data/.built-packages"
IGNORE_BUILD_SCRIPT_NOT_FOUND_ERROR=1
FORCE_BUILD_PACKAGES=0
# A list of packages to build
declare -a PACKAGES=()
# A list of non-essential packages to build.
# By default it is empty, but can be filled with option '--add'.
declare -a ADDITIONAL_PACKAGES=()
# A list of already extracted packages
declare -a EXTRACTED_PACKAGES=()
# A list of options to pass to build-package.sh
declare -a BUILD_PACKAGE_OPTIONS=()
# Check for some important utilities that may not be available for
# some reason.
for cmd in ar awk curl grep gzip find sed tar xargs xz zip; do
if [ -z "$(command -v $cmd)" ]; then
echo "[!] Utility '$cmd' is not available in PATH."
exit 1
fi
done
# Build deb files for package and its dependencies deb from source for arch
build_package() {
local return_value
local package_arch="$1"
local package_name="$2"
local build_output
# Build package from source
# stderr will be redirected to stdout and both will be captured into variable and printed on screen
cd "$TERMUX_PACKAGES_DIRECTORY"
echo $'\n\n\n'"[*] Building '$package_name'..."
exec 99>&1
build_output="$("$TERMUX_PACKAGES_DIRECTORY"/build-package.sh "${BUILD_PACKAGE_OPTIONS[@]}" -a "$package_arch" "$package_name" 2>&1 | tee >(cat - >&99); exit ${PIPESTATUS[0]})";
return_value=$?
echo "[*] Building '$package_name' exited with exit code $return_value"
exec 99>&-
if [ $return_value -ne 0 ]; then
echo "Failed to build package '$package_name' for arch '$package_arch'" 1>&2
# Dependency packages may not have a build.sh, so we ignore the error.
# A better way should be implemented to validate if its actually a dependency
# and not a required package itself, by removing dependencies from PACKAGES array.
if [[ $IGNORE_BUILD_SCRIPT_NOT_FOUND_ERROR == "1" ]] && [[ "$build_output" == *"No build.sh script at package dir"* ]]; then
echo "Ignoring error 'No build.sh script at package dir'" 1>&2
return 0
fi
fi
return $return_value
}
# Extract *.deb files to the bootstrap root.
extract_debs() {
local current_package_name
local data_archive
local control_archive
local package_tmpdir
local deb
local file
cd "$TERMUX_BUILT_DEBS_DIRECTORY"
if [ -z "$(ls -A)" ]; then
echo $'\n\n\n'"No debs found"
return 1
else
echo $'\n\n\n'"Deb Files:"
echo "\""
ls
echo "\""
fi
for deb in *.deb; do
current_package_name="$(echo "$deb" | sed -E 's/^([^_]+).*/\1/' )"
echo "current_package_name: '$current_package_name'"
if [[ "$current_package_name" == *"-static" ]]; then
echo "[*] Skipping static package '$deb'..."
continue
fi
if [[ " ${EXTRACTED_PACKAGES[*]} " == *" $current_package_name "* ]]; then
echo "[*] Skipping already extracted package '$current_package_name'..."
continue
fi
EXTRACTED_PACKAGES+=("$current_package_name")
package_tmpdir="${BOOTSTRAP_PKGDIR}/${current_package_name}"
mkdir -p "$package_tmpdir"
rm -rf "$package_tmpdir"/*
echo "[*] Extracting '$deb'..."
(cd "$package_tmpdir"
ar x "$TERMUX_BUILT_DEBS_DIRECTORY/$deb"
# data.tar may have extension different from .xz
if [ -f "./data.tar.xz" ]; then
data_archive="data.tar.xz"
elif [ -f "./data.tar.gz" ]; then
data_archive="data.tar.gz"
else
echo "No data.tar.* found in '$deb'."
return 1
fi
# Do same for control.tar.
if [ -f "./control.tar.xz" ]; then
control_archive="control.tar.xz"
elif [ -f "./control.tar.gz" ]; then
control_archive="control.tar.gz"
else
echo "No control.tar.* found in '$deb'."
return 1
fi
# Extract files.
tar xf "$data_archive" -C "$BOOTSTRAP_ROOTFS"
if ! ${BOOTSTRAP_ANDROID10_COMPATIBLE}; then
# Register extracted files.
tar tf "$data_archive" | sed -E -e 's@^\./@/@' -e 's@^/$@/.@' -e 's@^([^./])@/\1@' > "${BOOTSTRAP_ROOTFS}/${TERMUX_PREFIX}/var/lib/dpkg/info/${current_package_name}.list"
# Generate checksums (md5).
tar xf "$data_archive"
find data -type f -print0 | xargs -0 -r md5sum | sed 's@^\.$@@g' > "${BOOTSTRAP_ROOTFS}/${TERMUX_PREFIX}/var/lib/dpkg/info/${current_package_name}.md5sums"
# Extract metadata.
tar xf "$control_archive"
{
cat control
echo "Status: install ok installed"
echo
} >> "${BOOTSTRAP_ROOTFS}/${TERMUX_PREFIX}/var/lib/dpkg/status"
# Additional data: conffiles & scripts
for file in conffiles postinst postrm preinst prerm; do
if [ -f "${PWD}/${file}" ]; then
cp "$file" "${BOOTSTRAP_ROOTFS}/${TERMUX_PREFIX}/var/lib/dpkg/info/${current_package_name}.${file}"
fi
done
fi
)
done
}
# Final stage: generate bootstrap archive and place it to current
# working directory.
# Information about symlinks is stored in file SYMLINKS.txt.
create_bootstrap_archive() {
echo $'\n\n\n'"[*] Creating 'bootstrap-${1}.zip'..."
(cd "${BOOTSTRAP_ROOTFS}/${TERMUX_PREFIX}"
# Do not store symlinks in bootstrap archive.
# Instead, put all information to SYMLINKS.txt
while read -r -d '' link; do
echo "$(readlink "$link")${link}" >> SYMLINKS.txt
rm -f "$link"
done < <(find . -type l -print0)
zip -r9 "${BOOTSTRAP_TMPDIR}/bootstrap-${1}.zip" ./*
)
mv -f "${BOOTSTRAP_TMPDIR}/bootstrap-${1}.zip" "$TERMUX_PACKAGES_DIRECTORY/"
echo "[*] Finished successfully (${1})."
}
set_build_bootstrap_traps() {
#set traps for the build_bootstrap_trap itself
trap 'build_bootstrap_trap' EXIT
trap 'build_bootstrap_trap TERM' TERM
trap 'build_bootstrap_trap INT' INT
trap 'build_bootstrap_trap HUP' HUP
trap 'build_bootstrap_trap QUIT' QUIT
return 0
}
build_bootstrap_trap() {
local build_bootstrap_trap_exit_code=$?
trap - EXIT
[ -h "$TERMUX_BUILT_PACKAGES_DIRECTORY" ] && rm -f "$TERMUX_BUILT_PACKAGES_DIRECTORY"
[ -d "$BOOTSTRAP_TMPDIR" ] && rm -rf "$BOOTSTRAP_TMPDIR"
[ -n "$1" ] && trap - "$1"; exit $build_bootstrap_trap_exit_code
}
show_usage() {
cat <<'HELP_EOF'
build-bootstraps.sh is a script to build bootstrap archives for the
termux-app from local package sources instead of debs published in
apt repo like done by generate-bootstrap.sh. It allows bootstrap archives
to be easily built for (forked) termux apps without having to publish
an apt repo first.
Usage:
build-bootstraps.sh [command_options]
Available command_options:
[ -h | --help ] Display this help screen
[ -f ] Force build even if packages have already been built.
[ --android10 ]
Generate bootstrap archives for Android 10+ for
apk packaging system.
[ -a | --add <packages> ]
Additional packages to include into bootstrap archive.
Multiple packages should be passed as comma-separated list.
[ --architectures <architectures> ]
Override default list of architectures for which bootstrap
archives will be created. Multiple architectures should be
passed as comma-separated list.
The package name/prefix that the bootstrap is built for is defined by
TERMUX_APP_PACKAGE in 'scrips/properties.sh'. It defaults to 'me.sergiotarxz.openmg.x11'.
If package name is changed, make sure to run
`./scripts/run-docker.sh ./clean.sh` or pass '-f' to force rebuild of packages.
### Examples
Build default bootstrap archives for all supported archs:
./scripts/run-docker.sh ./scripts/build-bootstraps.sh &> build.log
Build default bootstrap archive for aarch64 arch only:
./scripts/run-docker.sh ./scripts/build-bootstraps.sh --architectures aarch64 &> build.log
Build bootstrap archive with additionall openssh package for aarch64 arch only:
./scripts/run-docker.sh ./scripts/build-bootstraps.sh --architectures aarch64 --add openssh &> build.log
HELP_EOF
echo $'\n'"TERMUX_APP_PACKAGE: \"$TERMUX_APP_PACKAGE\""
echo "TERMUX_PREFIX: \"${TERMUX_PREFIX[*]}\""
echo "TERMUX_ARCHITECTURES: \"${TERMUX_ARCHITECTURES[*]}\""
}
main() {
local return_value
while (($# > 0)); do
case "$1" in
-h|--help)
show_usage
return 0
;;
--android10)
BOOTSTRAP_ANDROID10_COMPATIBLE=true
;;
-a|--add)
if [ $# -gt 1 ] && [ -n "$2" ] && [[ $2 != -* ]]; then
for pkg in $(echo "$2" | tr ',' ' '); do
ADDITIONAL_PACKAGES+=("$pkg")
done
unset pkg
shift 1
else
echo "[!] Option '--add' requires an argument." 1>&2
show_usage
return 1
fi
;;
--architectures)
if [ $# -gt 1 ] && [ -n "$2" ] && [[ $2 != -* ]]; then
TERMUX_ARCHITECTURES=()
for arch in $(echo "$2" | tr ',' ' '); do
TERMUX_ARCHITECTURES+=("$arch")
done
unset arch
shift 1
else
echo "[!] Option '--architectures' requires an argument." 1>&2
show_usage
return 1
fi
;;
-f)
BUILD_PACKAGE_OPTIONS+=("-f")
FORCE_BUILD_PACKAGES=1
;;
*)
echo "[!] Got unknown option '$1'" 1>&2
show_usage
return 1
;;
esac
shift 1
done
set_build_bootstrap_traps
for package_arch in "${TERMUX_ARCHITECTURES[@]}"; do
if [[ " ${TERMUX_DEFAULT_ARCHITECTURES[*]} " != *" $package_arch "* ]]; then
echo "Unsupported architecture '$package_arch' for in architectures list: '${TERMUX_ARCHITECTURES[*]}'" 1>&2
echo "Supported architectures: '${TERMUX_DEFAULT_ARCHITECTURES[*]}'" 1>&2
return 1
fi
done
for package_arch in "${TERMUX_ARCHITECTURES[@]}"; do
# The termux_step_finish_build stores package version in .built-packages directory, but
# its not arch independent. So instead we create an arch specific one and symlink it
# to the .built-packages directory so that users can easily switch arches without having
# to rebuild packages
TERMUX_BUILT_PACKAGES_DIRECTORY_FOR_ARCH="$TERMUX_BUILT_PACKAGES_DIRECTORY-$package_arch"
mkdir -p "$TERMUX_BUILT_PACKAGES_DIRECTORY_FOR_ARCH"
if [ -f "$TERMUX_BUILT_PACKAGES_DIRECTORY" ] || [ -d "$TERMUX_BUILT_PACKAGES_DIRECTORY" ]; then
rm -rf "$TERMUX_BUILT_PACKAGES_DIRECTORY"
fi
ln -sf "$TERMUX_BUILT_PACKAGES_DIRECTORY_FOR_ARCH" "$TERMUX_BUILT_PACKAGES_DIRECTORY"
if [[ $FORCE_BUILD_PACKAGES == "1" ]]; then
rm -f "$TERMUX_BUILT_PACKAGES_DIRECTORY_FOR_ARCH"/*
rm -f "$TERMUX_BUILT_DEBS_DIRECTORY"/*
fi
BOOTSTRAP_ROOTFS="$BOOTSTRAP_TMPDIR/rootfs-${package_arch}"
BOOTSTRAP_PKGDIR="$BOOTSTRAP_TMPDIR/packages-${package_arch}"
# Create initial directories for $TERMUX_PREFIX
if ! ${BOOTSTRAP_ANDROID10_COMPATIBLE}; then
mkdir -p "${BOOTSTRAP_ROOTFS}/${TERMUX_PREFIX}/etc/apt/apt.conf.d"
mkdir -p "${BOOTSTRAP_ROOTFS}/${TERMUX_PREFIX}/etc/apt/preferences.d"
mkdir -p "${BOOTSTRAP_ROOTFS}/${TERMUX_PREFIX}/var/lib/dpkg/info"
mkdir -p "${BOOTSTRAP_ROOTFS}/${TERMUX_PREFIX}/var/lib/dpkg/triggers"
mkdir -p "${BOOTSTRAP_ROOTFS}/${TERMUX_PREFIX}/var/lib/dpkg/updates"
mkdir -p "${BOOTSTRAP_ROOTFS}/${TERMUX_PREFIX}/var/log/apt"
touch "${BOOTSTRAP_ROOTFS}/${TERMUX_PREFIX}/var/lib/dpkg/available"
touch "${BOOTSTRAP_ROOTFS}/${TERMUX_PREFIX}/var/lib/dpkg/status"
fi
mkdir -p "${BOOTSTRAP_ROOTFS}/${TERMUX_PREFIX}/tmp"
PACKAGES=()
EXTRACTED_PACKAGES=()
# Package manager.
if ! ${BOOTSTRAP_ANDROID10_COMPATIBLE}; then
PACKAGES+=("apt")
fi
# Core utilities.
PACKAGES+=("bash")
PACKAGES+=("bzip2")
if ! ${BOOTSTRAP_ANDROID10_COMPATIBLE}; then
PACKAGES+=("command-not-found")
else
PACKAGES+=("proot")
fi
PACKAGES+=("coreutils")
PACKAGES+=("curl")
PACKAGES+=("dash")
PACKAGES+=("diffutils")
PACKAGES+=("findutils")
PACKAGES+=("gawk")
PACKAGES+=("grep")
PACKAGES+=("gzip")
PACKAGES+=("less")
PACKAGES+=("procps")
PACKAGES+=("psmisc")
PACKAGES+=("sed")
PACKAGES+=("tar")
PACKAGES+=("termux-exec")
PACKAGES+=("termux-keyring")
PACKAGES+=("termux-tools")
PACKAGES+=("util-linux")
PACKAGES+=("xz-utils")
# Additional.
PACKAGES+=("ed")
PACKAGES+=("debianutils")
PACKAGES+=("dos2unix")
PACKAGES+=("inetutils")
PACKAGES+=("lsof")
PACKAGES+=("nano")
PACKAGES+=("net-tools")
PACKAGES+=("patch")
PACKAGES+=("unzip")
# Handle additional packages.
for add_pkg in "${ADDITIONAL_PACKAGES[@]}"; do
if [[ " ${PACKAGES[*]} " != *" $add_pkg "* ]]; then
PACKAGES+=("$add_pkg")
fi
done
unset add_pkg
# Build packages.
for package_name in "${PACKAGES[@]}"; do
set +e
build_package "$package_arch" "$package_name" || return $?
set -e
done
# Extract all debs.
extract_debs || return $?
# Create bootstrap archive.
create_bootstrap_archive "$package_arch" || return $?
done
}
main "$@"