572 lines
15 KiB
Bash
Executable file
572 lines
15 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
|
|
workspace="$(pwd -P)"
|
|
buildfile="${workspace}/DEBBUILD"
|
|
|
|
srcdir="${workspace}/src"
|
|
pkgdir="${workspace}/pkg"
|
|
|
|
_IN_FAKEROOT=0
|
|
_BACKUP_STDOUT=3 # use file descriptor 3 to backup stdout
|
|
_BACKUP_STDERR=4 # use file descriptor 4 to backup stderr
|
|
_ARGLIST=("$@")
|
|
OVERRIDE_SOURCE=0
|
|
QUIET=0
|
|
PACKAGELIST=0
|
|
IS_DYNAMICV_PKGVER=0
|
|
|
|
_STEP_CLEAN=1
|
|
_STEP_RETRIEVE_SOURCE=1
|
|
_STEP_EXTRACT_SOURCE=1
|
|
_STEP_UPDATE_PKGVER=1
|
|
_STEP_CHCECK_PACKAGE_EXISTS=1
|
|
_STEP_BUILD=1
|
|
_STEP_PACKAGE=1
|
|
_STEP_CREATE_ARCHIVE=1
|
|
|
|
_STOP_AFTER_CLEAN=0
|
|
_STOP_AFTER_RETRIEVE_SOURCE=0
|
|
_STOP_AFTER_EXTRACT_SOURCE=0
|
|
_STOP_AFTER_UPDATE_PKGVER=0
|
|
#_STEP_CHCECK_PACKAGE_EXISTS=0 # stop after checking exists is useless
|
|
_STOP_AFTER_BUILD=0
|
|
_STOP_AFTER_PACKAGE=0
|
|
_STOP_AFTER_CREATE_ARCHIVE=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 warn {
|
|
(( QUIET )) && return 0
|
|
local mesg=$1; shift
|
|
printf "${YELLOW}==>${ALL_OFF}${BOLD}${YELLOW} ${mesg}${ALL_OFF}\n" "$@" >&2
|
|
}
|
|
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)"
|
|
|
|
echo "Package: ${pkgname}"
|
|
echo "Version: ${fv}"
|
|
echo "Architecture: ${arch}"
|
|
echo "Maintainer: ${maintainer}"
|
|
echo "Installed-Size: ${install_size}"
|
|
echo "Homepage: ${url}"
|
|
|
|
[[ -n "${section:=misc}" ]] && echo "Section: ${section}"
|
|
[[ -n "${priority:=optional}" ]] && echo "Priority: ${priority}"
|
|
|
|
echo "Description: ${pkgdesc}"
|
|
}
|
|
|
|
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 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 --gzip --exclude="./DEBIAN" -C "${pkgdir}" -cf "${data_tgz}" .
|
|
|
|
tar --gzip -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 step_create_archive {
|
|
generate_control
|
|
generate_deb
|
|
}
|
|
|
|
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}"
|
|
return 1
|
|
fi
|
|
|
|
if [[ -e "${tgt}" ]] ; then
|
|
err "soft_link tgt %s already exist" "${tgt}"
|
|
return 1
|
|
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 retrieve_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 "Retrieving ${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 "Retrieving 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/remotes/origin/${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 step_clean_dir {
|
|
msg "Cleaning \$srcdir and \$pkgdir..."
|
|
rm -rf "${srcdir}" "${pkgdir}"
|
|
mkdir -p "${srcdir}" "${pkgdir}"
|
|
}
|
|
|
|
function step_retrieve_source {
|
|
msg "Retrieving source..."
|
|
for s in "${source[@]}"; do
|
|
file_name="${s%%::*}"
|
|
url="${s##*::}"
|
|
|
|
retrieve_source_single "${file_name}" "${url}"
|
|
done
|
|
}
|
|
|
|
function step_extract_source {
|
|
msg "Extracting source..."
|
|
for s in "${source[@]}"; do
|
|
file_name="${s%%::*}"
|
|
url="${s##*::}"
|
|
|
|
extract_source_single "${file_name}" "${url}"
|
|
done
|
|
}
|
|
|
|
function step_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 run_function {
|
|
if is_function "$1" ; then
|
|
msg "Run function: $1"
|
|
run_function_safe "$1"
|
|
fi
|
|
}
|
|
|
|
function disable_stdout {
|
|
exec {_BACKUP_STDOUT}>&1
|
|
exec 1>/dev/null
|
|
}
|
|
|
|
function enable_stdout {
|
|
exec 1>&"$_BACKUP_STDOUT"
|
|
}
|
|
|
|
function show_help {
|
|
echo "$0 [OPTIONS]"
|
|
echo " -Q quiet, disable log"
|
|
echo " -f|--force don't check if package already exists, will"
|
|
echo " --nobuild retrieve source, update pkgver and stop"
|
|
echo " override the existed package"
|
|
echo " --noextract use the current srcdir to build package, skip"
|
|
echo " those retrieve source steps"
|
|
echo " --noarchive retrieve and build, but don't package to .deb"
|
|
echo " --packagelist show file to be generated by current DEBBUILD"
|
|
echo " --is-dynamic-pkgver exit with 0 if DEBBUILD will update pkgver later"
|
|
echo " -h|--help show this message"
|
|
}
|
|
|
|
##
|
|
## Here start the build logic
|
|
##
|
|
|
|
while (( "$#" >= 1 )); do
|
|
case "$1" in
|
|
-F) _IN_FAKEROOT=1 ;;
|
|
-Q) QUIET=1 ;;
|
|
-f|--force) _STEP_CHCECK_PACKAGE_EXISTS=0 ;;
|
|
--noextract)
|
|
_STEP_CLEAN=0
|
|
_STEP_RETRIEVE_SOURCE=0
|
|
_STEP_EXTRACT_SOURCE=0
|
|
_STEP_UPDATE_PKGVER=0
|
|
;;
|
|
--nobuild)
|
|
_STEP_CHCECK_PACKAGE_EXISTS=0
|
|
_STEP_BUILD=0
|
|
_STEP_PACKAGE=0
|
|
_STEP_CREATE_ARCHIVE=0
|
|
|
|
_STOP_AFTER_UPDATE_PKGVER=1
|
|
;;
|
|
--noarchive)
|
|
_STEP_CHCECK_PACKAGE_EXISTS=0
|
|
_STEP_CREATE_ARCHIVE=0
|
|
|
|
_STOP_AFTER_BUILD=1
|
|
;;
|
|
--packagelist) PACKAGELIST=1 ;;
|
|
--is-dynamic-pkgver) IS_DYNAMICV_PKGVER=1 ;;
|
|
-h|--help) show_help ; exit 0;;
|
|
|
|
*) err "Unkown option $1"
|
|
err "Use $0 --help for help"
|
|
exit 1 ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
source "${buildfile}"
|
|
|
|
|
|
if (( PACKAGELIST )) ; then
|
|
echo "$(get_deb_name)"
|
|
exit 0
|
|
fi
|
|
|
|
if (( IS_DYNAMICV_PKGVER )) ; then
|
|
if is_function pkgver ; then
|
|
exit 0
|
|
else
|
|
exit 4
|
|
fi
|
|
fi
|
|
|
|
if (( QUIET )) ; then
|
|
disable_stdout
|
|
fi
|
|
|
|
if (( ! _IN_FAKEROOT )) ; then
|
|
check_source_validation
|
|
|
|
if (( _STEP_CLEAN )) ; then
|
|
step_clean_dir
|
|
fi
|
|
|
|
if (( _STEP_RETRIEVE_SOURCE )) ; then
|
|
step_retrieve_source
|
|
fi
|
|
|
|
if (( _STEP_EXTRACT_SOURCE )) ; then
|
|
step_extract_source
|
|
fi
|
|
|
|
if (( _STEP_UPDATE_PKGVER )) ; then
|
|
step_update_pkgver
|
|
(( _STOP_AFTER_UPDATE_PKGVER )) && msg "Sources are ready."
|
|
fi
|
|
|
|
# Check if the package already exists
|
|
if (( _STEP_CHCECK_PACKAGE_EXISTS )) ; then
|
|
if [[ -f "$(get_deb_name)" ]] ; then
|
|
warn "%s %s" \
|
|
"The package $(get_deb_name) already exists, skipping build." \
|
|
"(use -f to force re-build)"
|
|
exit 0
|
|
fi
|
|
fi
|
|
|
|
if (( _STEP_BUILD )) ; then
|
|
run_function build
|
|
(( _STOP_AFTER_BUILD )) && msg "Package directory is ready."
|
|
fi
|
|
|
|
if (( _STEP_PACKAGE || _STEP_CREATE_ARCHIVE )) ; then
|
|
# 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..."
|
|
fi
|
|
else
|
|
if (( _STEP_PACKAGE )) ; then
|
|
run_function package
|
|
fi
|
|
|
|
if (( _STEP_CREATE_ARCHIVE )) ; then
|
|
step_create_archive
|
|
msg "Finish making: $(get_deb_name) $(date)"
|
|
fi
|
|
fi
|
|
|
|
if (( QUIET )) ; then
|
|
enable_stdout
|
|
fi
|