makedeb/makedeb
2023-07-17 22:26:56 +08:00

452 lines
11 KiB
Bash
Executable file

#!/usr/bin/env bash
workspace="$(pwd -P)"
buildfile="${workspace}/DEBBUILD"
srcdir="${workspace}/src"
pkgdir="${workspace}/pkg"
FAKE_PACKAGE=0
OVERRIDE_SOURCE=0
SHOW_TARGET_FILE=0
QUIET=0
set -o functrace
set -o nounset
set -o errtrace
set -o errexit
function err_occur {
err "Failed at $1: ${BASH_COMMAND}"
err "Trace line number: %s" "$*"
}
trap 'err_occur "${LINENO}" "${BASH_LINENO[@]}"' ERR
# prefer terminal safe colored and bold text when tput is supported
if tput setaf 0 &>/dev/null; then
ALL_OFF="$(tput sgr0)"
BOLD="$(tput bold)"
BLUE="${BOLD}$(tput setaf 4)"
GREEN="${BOLD}$(tput setaf 2)"
RED="${BOLD}$(tput setaf 1)"
YELLOW="${BOLD}$(tput setaf 3)"
else
ALL_OFF="\e[0m"
BOLD="\e[1m"
BLUE="${BOLD}\e[34m"
GREEN="${BOLD}\e[32m"
RED="${BOLD}\e[31m"
YELLOW="${BOLD}\e[33m"
fi
readonly ALL_OFF BOLD BLUE GREEN RED YELLOW
function msg {
(( QUIET )) && return 0
local mesg=$1; shift
printf "${GREEN}==>${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@"
}
function msg2 {
(( QUIET )) && return 0
local mesg=$1; shift
printf "${BLUE} ->${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@"
}
function err {
(( QUIET )) && return 0
local mesg=$1; shift
printf "${RED} ->${ALL_OFF}${BOLD}${RED} ${mesg}${ALL_OFF}\n" "$@" >&2
}
function get_full_version {
local result="${pkgver//-/.}"
if [[ -n "${pkgrel:-}" ]] ; then
result="${result}-${pkgrel}"
fi
if [[ -n "${pkgcommit:-}" ]] ; then
result="${result}+${pkgcommit}"
fi
echo "${result}"
}
function get_deb_name {
echo "${pkgname}_$(get_full_version).deb"
}
function debian_control {
local install_size="$(du --block-size=1K --summarize ${pkgdir} | cut -d $'\t' -f 1)"
local fv="$(get_full_version)"
cat << EOF | envsubst
Package: ${pkgname}
Version: ${fv}
Architecture: all
Maintainer: ${maintainer}
Installed-Size: ${install_size}
Description: ${pkgdesc}
EOF
}
function generate_deb {
msg "generating deb package..."
local tmpdir="$(mktemp --directory)"
local data_tgz="${tmpdir}/data.tar.gz"
local control_tgz="${tmpdir}/control.tar.gz"
local debian_binary="${tmpdir}/debian-binary"
tar --exclude="./DEBIAN" -C "${pkgdir}" -cf "${data_tgz}" .
tar -C "${pkgdir}/DEBIAN" -cf "${control_tgz}" .
echo 2.0 > "${debian_binary}"
ar r "$(get_deb_name)" "${debian_binary}" "${control_tgz}" "${data_tgz}"
rm -rf "${tmpdir}"
msg2 "generated deb: %s" "$(get_deb_name)"
}
function url_type {
if [[ "${url}" == git+* ]] ; then
echo "git"
elif [[ "${url}" == http://* ]] ; then
echo "http"
elif [[ "${url}" == https://* ]] ; then
echo "https"
elif [[ "${url}" == ftp://* ]] ; then
echo "ftp"
elif [[ "${url}" == ftps://* ]] ; then
echo "ftps"
elif [[ "${url}" == file//* ]]; then
echo "file"
elif [[ "${url}" != *://* ]] ; then
echo "file"
else
err "Unkown url schema: %s" "${url}"
err "Aborting..."
exit 1
fi
}
function create_soft_link {
local src="$1"
local tgt="$2"
if [[ ! -e "${src}" ]] ; then
err "soft_link src %s not exist" "${src}"
fi
if [[ -e "${tgt}" ]] ; then
err "soft_link tgt %s already exist" "${tgt}"
fi
ln --symbolic "${src}" "${tgt}"
}
function url_fragment {
local url="$1"
local fragment="${url#*#}"
if [[ "${url}" == "${fragment}" ]] ; then
return
fi
echo "${fragment}"
}
# $1 is relative path to file
# $2 is url/(relative path to materials)
function retrive_source_single {
local file_name="$1"
local url="$2"
case "$(url_type "${url}")" in
"git")
if [[ -d "${file_name}" ]]; then
msg2 "updating ${file_name} from ${url} with git..."
git --git-dir="${workspace}/${file_name}" fetch --all
else
msg2 "cloning ${file_name} from ${url} with git..."
local git_source="${url##git+}"
git_source="${git_source%%#*}"
git clone --mirror "${git_source}" "${workspace}/${file_name}"
fi
;;
"http"|"https"|"ftp"|"ftps")
if (( ! OVERRIDE_SOURCE )) && [[ -f "${workspace}/${file_name}" ]] ; then
msg2 "${file_name} already exists, skip download"
else
msg2 "retriving ${file_name} from ${url} with curl..."
curl --location "${url}" --output "${workspace}/${file_name}"
fi
;;
"file")
if [[ -e "${workspace}/${url}" ]] ; then
msg2 "found local file: ${url}"
else
err "local file not found: ${url}"
err "Aborting..."
exit 1
fi
;;
*)
err "retriving url as type ${url_type} not supported"
err "Aborting..."
exit 1
esac
}
# $1 is relative path to file
# $2 is url/(relative path to materials)
function extract_source_single {
local file_name="$1"
local url="$2"
local url_type="$(url_type "${url}")"
case "${url_type}" in
"git")
[[ -d "${srcdir}/${file_name}" ]] || mkdir -p "${srcdir}/${file_name}"
local ref=HEAD
local frag=$(url_fragment "${url}")
if [[ -n "${frag}" ]] ; then
case "${frag%%=*}" in
"branch")
ref="refs/heads/${frag##*=}"
;;
"tag")
ref="refs/tags/${frag##*=}"
;;
"commit")
ref="${frag##*=}"
;;
*)
err "unrecognized reference in git url: ${frag}"
exit 1
;;
esac
fi
msg2 "extracting git ${workspace}/${file_name} with reference ${ref}"
git clone --shared "${workspace}/${file_name}" "${srcdir}/${file_name}"
git -C "${srcdir}/${file_name}" switch --force-create makedeb --no-track "${ref}"
;;
"file") # for material files, just soft-link under src
create_soft_link $(realpath "${workspace}/${url}") "${srcdir}/${file_name}"
decompress_source_file "${srcdir}/${file_name}"
;;
*) # for downloaded files, just soft-link downloaded file under src
create_soft_link $(realpath "${workspace}/${file_name}") "${srcdir}/${file_name}"
decompress_source_file "${srcdir}/${file_name}"
;;
esac
}
# $1 is real path to file
function decompress_source_file {
local file="$1"
case "${file}" in
*.tar|*.tar.gz|*.tgz|*.tar.Z|*.tar.bz2|*.tbz2|*.tar.lz|*.tlz|*.tar.xz|*.txz|*.tar.zst)
cmd="tar" ;;
*.gz|*.z|*.Z)
cmd="gzip" ;;
*.bz2|*.bz)
cmd="bzip2" ;;
*.xz)
cmd="xz" ;;
*.zst|*.zstd)
cmd="zstd" ;;
*.zip)
cmd="unzip" ;;
*)
msg2 "no need to decompress, skip %s" "${file}"
return
esac
msg2 "decompressing %s with %s" "${file}" "${cmd}"
local res=0
case "$cmd" in
"tar")
$cmd -xf "$file" --directory="${srcdir}" || res=$?
;;
"unzip")
$cmd "$file" -d "${srcdir}" || res=$?
;;
*)
rm -f -- "${file%.*}"
$cmd -dcf -- "$file" > "${file%.*}" || res=$?
;;
esac
}
error_function() {
# first exit all subshells, then print the error
if (( ! BASH_SUBSHELL )); then
err "A failure occurred in %s()." "$1"
err "$(gettext "Aborting...")"
fi
exit 1
}
# use less strict shell for custom functions
function run_function_safe {
local restoretrap
set +o errtrace
set +o errexit
restoretrap=$(trap -p ERR)
trap "error_function '$1'" ERR
"$1"
set -o errtrace
set -o errexit
trap - ERR
eval "$restoretrap"
}
function is_function {
declare -F "$1" > /dev/null
}
function check_source_validation {
# check if all source is valid
for s in "${source[@]}"; do
if ! grep "::" <<< "$s" > /dev/null; then
err "source must contain \"::\" to specify file name"
exit 1
fi
done
}
function clean_dir {
msg "cleaning \$srcdir and \$pkgdir..."
rm -rf "${srcdir}" "${pkgdir}"
mkdir -p "${srcdir}" "${pkgdir}"
}
function retrieve_source {
msg "retrieving source..."
for s in "${source[@]}"; do
file_name="${s%%::*}"
url="${s##*::}"
retrive_source_single "${file_name}" "${url}"
done
}
function extract_source {
msg "extracting source..."
for s in "${source[@]}"; do
file_name="${s%%::*}"
url="${s##*::}"
extract_source_single "${file_name}" "${url}"
done
}
function update_pkgver {
if ! is_function pkgver; then
return
fi
msg "updating pkgver..."
newpkgver="$(run_function_safe pkgver)"
if [[ "${newpkgver}" != "${pkgver:-}" ]] ; then
mapfile -t bfcontent < "${buildfile}"
shopt -s extglob
bfcontent=("${bfcontent[@]/#pkgver=*?(")([^ ])?(")/pkgver=$newpkgver}")
bfcontent=("${bfcontent[@]/#pkgrel=*([^ ])/pkgrel=1}")
shopt -u extglob
if ! printf '%s\n' "${bfcontent[@]}" > "${buildfile}"; then
err "Failed to update %s from %s to %s" "pkgver" "$pkgver" "$newpkgver"
exit 1
fi
source "${buildfile}"
msg2 "updated version: ${newpkgver}"
fi
pkgver="${newpkgver}"
}
function generate_control {
msg "generating control info..."
mkdir -p "${pkgdir}/DEBIAN"
echo 2 > "${pkgdir}/DEBIAN/compat"
debian_control > "${pkgdir}/DEBIAN/control"
function debian_hooks_warpper { is_function "$1" && "$1" > "$2" && chmod +x "$2" || true; }
debian_hooks_warpper debian_preinst ${pkgdir}/DEBIAN/preinst
debian_hooks_warpper debian_postinst ${pkgdir}/DEBIAN/postinst
debian_hooks_warpper debian_prerm ${pkgdir}/DEBIAN/prerm
debian_hooks_warpper debian_postrm ${pkgdir}/DEBIAN/postrm
}
function run_function {
if is_function "$1" ; then
msg "run function: $1"
run_function_safe "$1"
fi
}
##
## Here start the build logic
##
while (( "$#" >= 1 )); do
case "$1" in
-F) FAKE_PACKAGE=1 ;;
-OS) OVERRIDE_SOURCE=1 ;;
-STF) SHOW_TARGET_FILE=1 ; QUIET=1 ;;
-Q) QUIET=1 ;;
*) err "Unkown option $1"; break ;;
esac
shift
done
source "${buildfile}"
if (( ! FAKE_PACKAGE )) ; then
if (( SHOW_TARGET_FILE )) && ! is_function pkgver ; then
echo "$(get_deb_name)"
exit 0
fi
check_source_validation
clean_dir
retrieve_source
extract_source
update_pkgver
if (( SHOW_TARGET_FILE )) && is_function pkgver ; then
echo "$(get_deb_name)"
exit 0
fi
run_function build
# recursive call self to run rest task in fakeroot
msg "entering fakeroot environment..."
fakeroot -- bash -$- "${BASH_SOURCE[0]}" -F "${ARGLIST[@]}" || exit $?
msg "leaving fakeroot environment..."
msg "builds has done"
else
run_function package
generate_control
generate_deb
fi