now setup LEAFHOME and LEAFTEMPDIR to /tmp/leaf-tmp/$PKG_NAME and export HOME=$LEAFHOME TEMPDIR=$LEAFTEMPDIR in this way, HOME and TEMPDIR is out of source tree, which is better also, path is shorter
1034 lines
30 KiB
Bash
Executable File
1034 lines
30 KiB
Bash
Executable File
#!/bin/bash
|
|
set -e
|
|
shopt -s inherit_errexit 2>/dev/null || true
|
|
|
|
RED_COLOR="\e[1;31m"
|
|
PURPLE_COLOR="\e[1;35m"
|
|
GREEN_COLOR="\e[1;32m"
|
|
CLEAR_COLOR="\e[0m"
|
|
|
|
_DEF_PARALLEL_JOBS=4
|
|
_ENV_PARALLEL_JOBS="${PARALLEL_JOBS}"
|
|
source /etc/leaf.conf
|
|
if [ -n "${_ENV_PARALLEL_JOBS}" ]; then
|
|
export PARALLEL_JOBS="${_ENV_PARALLEL_JOBS}"
|
|
elif [ -z "${PARALLEL_JOBS}" ]; then
|
|
export PARALLEL_JOBS="${_DEF_PARALLEL_JOBS}"
|
|
else
|
|
export PARALLEL_JOBS
|
|
fi
|
|
export MAKEFLAGS="-j${PARALLEL_JOBS}"
|
|
export NINJAJOBS="${PARALLEL_JOBS}"
|
|
export TESTSUITEFLAGS="-j${PARALLEL_JOBS}"
|
|
export LANG=C
|
|
export LC_CTYPE=C.UTF-8
|
|
export leaf_flags="CFLAGS CXXFLAGS FCFLAGS FFLAGS RUSTFLAGS"
|
|
for flag in ${leaf_flags}; do
|
|
export $flag
|
|
done
|
|
|
|
declare -A BUILD_OPTIONS
|
|
BUILD_OPTIONS=([strip]="0" [libtool]="0" [zipman]="0")
|
|
|
|
# global message file list
|
|
declare -a LEAF_MESSAGE_FILES=()
|
|
|
|
main() {
|
|
leaf_check_directories
|
|
if [ $# -le 1 ]; then
|
|
case $1 in
|
|
clean)
|
|
leaf_check_permission
|
|
rm -rf {"${BUILD_DIR}","${TEMP_DIR}"}/*
|
|
;;
|
|
list)
|
|
cat "${INSTALLED_PACKAGES}"
|
|
;;
|
|
*)
|
|
leaf_print_usage
|
|
;;
|
|
esac
|
|
else
|
|
local _item
|
|
case $1 in
|
|
prepare|build|install|force-install|remove|pack|dirct-install)
|
|
leaf_check_permission
|
|
for _item in "${@:2}"; do
|
|
if [[ x"$1" == x"remove" ]]; then
|
|
leaf_find_remove_pkgbuild "${_item}"
|
|
else
|
|
leaf_find_pkgbuild "${_item}"
|
|
fi
|
|
leaf_reset_state
|
|
leaf_message_init
|
|
if [[ x"$1" == x"remove" ]]; then
|
|
source "${TRACE_DIR}/${PKG_PREFIX}/${PKG_NAME}/PKGBUILD" || leaf_record_message "cannot find PKGBUILD, ingore."
|
|
else
|
|
source "${PKGBUILD_DIR}/${PKG_PREFIX}/${PKG_NAME}".PKGBUILD
|
|
fi
|
|
leaf_parse_options
|
|
srcdir="${BUILD_DIR}/${PKG_PREFIX}/${PKG_NAME}"
|
|
LEAFTMPDIR="/tmp/leaf-tmp/${PKG_NAME}"
|
|
LEAFHOME="/tmp/leaf-tmp/${PKG_NAME}"
|
|
export TMPDIR="${LEAFTMPDIR}"
|
|
export HOME=${LEAFHOME}
|
|
pkgdir="${srcdir}"/__pkgdir__
|
|
if [[ x"$1" != x"dirct-install" ]]; then
|
|
rm -rf "${srcdir}" && install -dm755 "${srcdir}" && cd "${srcdir}"
|
|
fi
|
|
leaf_${1}_package
|
|
#rm -rf "${LEAFTMPDIR}"
|
|
#rm -rf "${LEAFHOME}"
|
|
done
|
|
;;
|
|
upgrade)
|
|
leaf_check_permission
|
|
[ $# -eq 3 ] || leaf_error "Usage: leaf upgrade <oldpkg> <newpkg>"
|
|
leaf_upgrade_package "$2" "$3"
|
|
;;
|
|
search)
|
|
leaf_search_package "${@:2}"
|
|
;;
|
|
show)
|
|
for _item in "${@:2}"; do
|
|
leaf_find_pkgbuild "${_item}"
|
|
leaf_show_package
|
|
done
|
|
;;
|
|
unpack)
|
|
for _item in "${@:2}"; do
|
|
leaf_check_permission
|
|
leaf_reset_state
|
|
leaf_unpack_package "${_item}"
|
|
done
|
|
;;
|
|
*)
|
|
leaf_print_usage
|
|
;;
|
|
esac
|
|
leaf_print_message
|
|
fi
|
|
}
|
|
|
|
###########################################################
|
|
# Help functions that give PKGBUILD to use
|
|
###########################################################
|
|
|
|
leaf_clear_flags() {
|
|
for flag in $leaf_flags; do
|
|
unset $flag
|
|
done
|
|
}
|
|
|
|
leaf_install_hook() {
|
|
local _hook_file="$1"
|
|
local _hook_dest="${pkgdir}${HOOK_DIR}"
|
|
|
|
# check file name
|
|
[[ -f "$_hook_file" ]] || leaf_error "Hook file '$_hook_file' does not exist"
|
|
[[ -r "$_hook_file" ]] || leaf_error "Hook file '$_hook_file' is not readable"
|
|
[[ "$(basename "$_hook_file")" == *.HOOK ]] || leaf_error "Hook file must end with .HOOK extension"
|
|
|
|
# check hook file by load it in a sub shell
|
|
(
|
|
unset triggers target operation 2>/dev/null
|
|
declare -a triggers
|
|
declare -a target
|
|
source "$_hook_file" || leaf_error "Source ${_hook_file} failed."
|
|
[[ ${#triggers[@]} -gt 0 ]] || leaf_error "${_hook_file} missing 'triggers' array"
|
|
[[ ${#target[@]} -gt 0 ]] || leaf_error "${_hook_file} missing 'target' array"
|
|
declare -f operation &>/dev/null || leaf_error "${_hook_file} missing 'operation' function"
|
|
) || leaf_error "Hook file ${_hook_file} is not valid. Please check your package ${PKG_NAME}."
|
|
|
|
# install hook file
|
|
install -Dm644 -- "$_hook_file" "${_hook_dest}/$(basename "$_hook_file")" || {
|
|
leaf_error "Failed to install hook file to ${_hook_dest}"
|
|
}
|
|
|
|
echo -e "Installed hook: $(basename "$_hook_file")"
|
|
}
|
|
|
|
leaf_check_directories() {
|
|
if [ -z "${BUILD_DIR}" ]; then
|
|
leaf_error "Directory \${BUILD_DIR} must be existed"
|
|
elif [ -z "${TEMP_DIR}" ]; then
|
|
leaf_error "Directory \${TEMP_DIR} must be existed"
|
|
fi
|
|
}
|
|
|
|
leaf_check_permission() {
|
|
[ ${EUID} -eq 0 ] || leaf_error "Permission denied"
|
|
}
|
|
|
|
leaf_compress_man_pages() {
|
|
pushd "${pkgdir}" > /dev/null 2>&1
|
|
[ ! -d usr/share/man ] || {
|
|
local _item
|
|
find usr/share/man -type f,l | while read -r _item; do
|
|
case $(file -bi "${_item}") in
|
|
*text*)
|
|
/usr/bin/gzip -c -9 "${_item}" > "${_item}".gz
|
|
rm -fv "${_item}"
|
|
;;
|
|
*symlink*)
|
|
ln -srv "$(readlink -m "${_item}")".gz "$(realpath "${_item}".gz)"
|
|
rm -fv "${_item}"
|
|
;;
|
|
*)
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
popd > /dev/null 2>&1
|
|
}
|
|
|
|
leaf_error() {
|
|
leaf_print_message
|
|
echo -e "${RED_COLOR}* $1${CLEAR_COLOR}" && exit 1
|
|
}
|
|
|
|
leaf_find_pkgbuild() {
|
|
local _location="$(find "${PKGBUILD_DIR}" -type f -wholename "*$1*.PKGBUILD" -printf "%P\n")"
|
|
if [ -z "${_location}" ]; then
|
|
leaf_error "Package $1's PKGBUILD does NOT exist"
|
|
elif [[ $(echo "${_location}" | wc -l) == 1 ]]; then
|
|
PKG_PREFIX="$(echo "${_location}" | awk -F'/' '{print $1}')"
|
|
PKG_NAME="$(echo "${_location%.PKGBUILD}" | awk -F'/' '{print $2}')"
|
|
else
|
|
leaf_error "Ambiguous packages, the prefix or version of package must be specified"
|
|
fi
|
|
}
|
|
|
|
leaf_find_remove_pkgbuild() {
|
|
local _location="$(find "${TRACE_DIR}" -type d -wholename "*$1*" -printf "%P\n")"
|
|
if [ -z "${_location}" ]; then
|
|
leaf_error "Package $1 does NOT exist"
|
|
elif [[ $(echo "${_location}" | wc -l) == 1 ]]; then
|
|
PKG_PREFIX="$(echo "${_location}" | awk -F'/' '{print $1}')"
|
|
PKG_NAME="$(echo "${_location%.PKGBUILD}" | awk -F'/' '{print $2}')"
|
|
else
|
|
leaf_error "Ambiguous packages, the prefix or version of package must be specified"
|
|
fi
|
|
}
|
|
|
|
leaf_invoke_hooks() {
|
|
local _hook _target _hook_dir
|
|
local _trace_dir="${TRACE_DIR}/${PKG_PREFIX}/${PKG_NAME}"
|
|
case "$1" in
|
|
install|remove)
|
|
;;
|
|
*)
|
|
leaf_error "leaf_invoke_hooks: $1 is invalid!"
|
|
;;
|
|
esac
|
|
if [ ! -d "${HOOK_DIR}" ]; then
|
|
leaf_error "Hook dir ${HOOK_DIR} does not exist. Check your leaf installation."
|
|
fi
|
|
find "${HOOK_DIR}" -type f -name "*.HOOK" | while read -r _hook; do
|
|
unset triggers target operation
|
|
declare -a triggers
|
|
declare -a target
|
|
source "${_hook}" || leaf_error "Failed to source hook file: ${_hook}"
|
|
[[ " ${triggers[*]} " =~ " $1 " ]] || continue
|
|
for _target in "${target[@]}"; do
|
|
if grep -qe "${_target}" "${_trace_dir}"/FILES 2>/dev/null; then
|
|
echo -e "${GREEN_COLOR}*** Apply the hook: ${_hook}...${CLEAR_COLOR}"
|
|
operation
|
|
break
|
|
fi
|
|
done
|
|
done
|
|
}
|
|
|
|
leaf_merge_package() {
|
|
pushd $1 > /dev/null 2>&1
|
|
local _trace_dir="${TRACE_DIR}/${PKG_PREFIX}/${PKG_NAME}"
|
|
local _item _item_conflict _mode _owner _group
|
|
local _time="$(date +%Y%m%d%H%M%S)"
|
|
cat "${_trace_dir}"/DIRS | while read -r _item; do
|
|
_mode=$(stat -c %a ."${_item}")
|
|
_owner=$(stat -c %u ."${_item}")
|
|
_group=$(stat -c %g ."${_item}")
|
|
install -d -m ${_mode} -o ${_owner} -g ${_group} "${_item}"
|
|
done
|
|
|
|
cat "${_trace_dir}"/FILES | while read -r _item; do
|
|
_mode=$(stat -c %a ."${_item}")
|
|
_owner=$(stat -c %u ."${_item}")
|
|
_group=$(stat -c %g ."${_item}")
|
|
|
|
# Fast path: non-/etc, or /etc path that doesn't exist yet -> install/overwrite directly
|
|
if [[ "${_item}" != /etc/* || ! -e "${_item}" ]]; then
|
|
install -D -m ${_mode} -o ${_owner} -g ${_group} ."${_item}" "${_item}"
|
|
continue
|
|
fi
|
|
|
|
# Now: /etc/* and already exists on the system.
|
|
# Upgrade optimization: if OLD_TRACE_DIR is set and the current /etc file matches old baseline,
|
|
# then user didn't modify it -> overwrite with new version directly.
|
|
if [[ -n "${OLD_TRACE_DIR:-}" ]] \
|
|
&& leaf_conffile_is_unmodified "${_item}" "${OLD_TRACE_DIR}"; then
|
|
install -D -m ${_mode} -o ${_owner} -g ${_group} ."${_item}" "${_item}"
|
|
continue
|
|
fi
|
|
|
|
# Otherwise, fall back to conflict logic:
|
|
# - if content is identical -> overwrite (keeps permissions/ownership from pkg)
|
|
# - else -> write conflict file and keep user's current file
|
|
_item_conflict="$(dirname "${_item}")/._$(basename "${_item}").conflict_${PKG_NAME}_${_time}"
|
|
if diff -q ."${_item}" "${_item}" >/dev/null 2>&1; then
|
|
install -D -m ${_mode} -o ${_owner} -g ${_group} ."${_item}" "${_item}"
|
|
else
|
|
install -D -m ${_mode} -o ${_owner} -g ${_group} ."${_item}" "${_item_conflict}"
|
|
leaf_record_message "Config file confliction on ${_item}, the package provided version is installed as ${_item_conflict}."
|
|
fi
|
|
done
|
|
|
|
cat "${_trace_dir}"/LINKS | while read -r _item; do
|
|
cp -dp ."${_item}" "${_item}"
|
|
done
|
|
leaf_invoke_hooks install
|
|
popd > /dev/null 2>&1
|
|
}
|
|
|
|
leaf_parse_options() {
|
|
local _option_array=(${DEFAULT_BUILD_OPTIONS[@]} ${options[@]})
|
|
for option in ${_option_array[@]}; do
|
|
if [[ x"${option}" == x"strip" ]]; then
|
|
BUILD_OPTIONS["strip"]="1"
|
|
fi
|
|
if [[ x"${option}" == x"!strip" ]]; then
|
|
BUILD_OPTIONS["strip"]="0"
|
|
fi
|
|
if [[ x"${option}" == x"libtool" ]]; then
|
|
BUILD_OPTIONS["libtool"]="1"
|
|
fi
|
|
if [[ x"${option}" == x"!libtool" ]]; then
|
|
BUILD_OPTIONS["libtool"]="0"
|
|
fi
|
|
if [[ x"${option}" == x"zipman" ]]; then
|
|
BUILD_OPTIONS["zipman"]="1"
|
|
fi
|
|
if [[ x"${option}" == x"!zipman" ]]; then
|
|
BUILD_OPTIONS["zipman"]="0"
|
|
fi
|
|
done
|
|
}
|
|
|
|
leaf_print_usage() {
|
|
cat << EOF
|
|
Usage: leaf [option] [packages]
|
|
Option: prepare prepare packages but do not build
|
|
build build packages but do not install
|
|
install install packages
|
|
forece-install install packages even if test failed
|
|
dirct-install install packages directly from previous build
|
|
remove remove packages
|
|
upgrade upgrade oldpkg to newpkg (leaf upgrade old new)
|
|
clean clean all source folders
|
|
list list installed packages
|
|
search search packages by keyword
|
|
show show package details
|
|
pack build packages and pack
|
|
unpack unpack binary packages
|
|
This leaf does not have Super Cow Powers.
|
|
EOF
|
|
}
|
|
|
|
leaf_message_init() {
|
|
# one log per package invocation
|
|
local d="${TEMP_DIR}/leaf-messages/${PKG_PREFIX}/${PKG_NAME}"
|
|
install -dm755 -- "$d"
|
|
LEAF_MESSAGE_FILE="${d}/messages.$$.$(date +%Y%m%d%H%M%S).log"
|
|
: > "${LEAF_MESSAGE_FILE}"
|
|
export LEAF_MESSAGE_FILE
|
|
|
|
LEAF_MESSAGE_FILES+=("${LEAF_MESSAGE_FILE}")
|
|
}
|
|
|
|
leaf_record_message() {
|
|
local out
|
|
local _item
|
|
for _item in "${@:1}"; do
|
|
out+="${GREEN_COLOR}* ${_item}${CLEAR_COLOR}\n"
|
|
done
|
|
|
|
if [ -n "${LEAF_MESSAGE_FILE}" ]; then
|
|
# printf %b will explain \n 和 \e
|
|
printf '%b' "${out}" >> "${LEAF_MESSAGE_FILE}"
|
|
fi
|
|
}
|
|
|
|
leaf_print_message() {
|
|
local f prefix name
|
|
|
|
for f in "${LEAF_MESSAGE_FILES[@]}"; do
|
|
[ -n "$f" ] || continue
|
|
[ -s "$f" ] || continue
|
|
|
|
# ${TEMP_DIR}/leaf-messages/<prefix>/<name>/messages....
|
|
prefix="${f#${TEMP_DIR}/leaf-messages/}"
|
|
prefix="${prefix%%/*}"
|
|
name="${f#${TEMP_DIR}/leaf-messages/${prefix}/}"
|
|
name="${name%%/*}"
|
|
|
|
echo -e "\n${PURPLE_COLOR}* Message from ${prefix}/${name}${CLEAR_COLOR}\n"
|
|
cat -- "$f"
|
|
rm -f -- "$f"
|
|
done
|
|
|
|
LEAF_MESSAGE_FILES=()
|
|
unset LEAF_MESSAGE_FILE
|
|
}
|
|
|
|
|
|
leaf_remove_libtool_files() {
|
|
find "${pkgdir}" -name "*.la" -delete
|
|
}
|
|
|
|
leaf_noop() { :; }
|
|
|
|
leaf_reset_state() {
|
|
options=()
|
|
src_preinstall() { leaf_noop; }
|
|
src_postinstall() { leaf_noop; }
|
|
src_preremove() { leaf_noop; }
|
|
src_postremove() { leaf_noop; }
|
|
|
|
src_prepare() { leaf_noop; }
|
|
src_build() { leaf_noop; }
|
|
src_check() { leaf_noop; }
|
|
src_install() { leaf_noop; }
|
|
}
|
|
|
|
leaf_is_noop_func() {
|
|
# usage: leaf_is_noop_func <funcname>
|
|
local fn="$1" body
|
|
|
|
declare -F "$fn" >/dev/null || return 0
|
|
|
|
body="$(declare -f "$fn" \
|
|
| sed -n '0,/{/d; :a; /}/q; p; n; ba')"
|
|
|
|
body="$(printf '%s' "$body" | tr -d '[:space:]' | tr -d ';')"
|
|
|
|
# allowd no-op form: leaf_noop, :, true
|
|
case "$body" in
|
|
leaf_noop|:|true) return 0 ;;
|
|
esac
|
|
|
|
return 1
|
|
}
|
|
|
|
leaf_strip_files() {
|
|
pushd "${pkgdir}" > /dev/null 2>&1
|
|
local _file
|
|
find . -type f | while read -r _file; do
|
|
case $(file -bi "${_file}") in
|
|
*application/x-sharedlib*) # libraries(.so)
|
|
strip --strip-unneeded "${_file}"
|
|
;;
|
|
*application/x-pie-executable*) # libraries(.so)
|
|
strip --strip-unneeded "${_file}"
|
|
;;
|
|
*application/x-archive*) # libraries(.a)
|
|
strip --strip-debug "${_file}"
|
|
;;
|
|
*application/x-object*)
|
|
case "${_file}" in
|
|
*.ko) # kernel module
|
|
strip --strip-unneeded "${_file}"
|
|
;;
|
|
*)
|
|
;;
|
|
esac
|
|
;;
|
|
*application/x-executable*) # binaries
|
|
strip --strip-all "${_file}"
|
|
;;
|
|
*)
|
|
;;
|
|
esac
|
|
done
|
|
popd > /dev/null 2>&1
|
|
}
|
|
|
|
leaf_trace_package() {
|
|
pushd "$1" > /dev/null 2>&1
|
|
local _trace_dir="${TRACE_DIR}/${PKG_PREFIX}/${PKG_NAME}"
|
|
install -dm755 "${_trace_dir}"
|
|
find . -type d | sort > "${_trace_dir}"/DIRS
|
|
sed -i "1d" "$_trace_dir"/DIRS
|
|
find . -type f | sort > "${_trace_dir}"/FILES
|
|
find . -type l | sort > "${_trace_dir}"/LINKS
|
|
sed -i "s/.//" "${_trace_dir}"/{DIRS,FILES,LINKS}
|
|
install -vm644 "${PKGBUILD_DIR}/${PKG_PREFIX}/${PKG_NAME}.PKGBUILD" "${_trace_dir}/PKGBUILD"
|
|
leaf_trace_conffiles "${_trace_dir}"
|
|
popd > /dev/null 2>&1
|
|
}
|
|
|
|
leaf_trace_conffiles() {
|
|
# usage: leaf_trace_conffiles <trace_dir>
|
|
# called with CWD = pkgdir (same as leaf_trace_package)
|
|
local _trace_dir="$1"
|
|
local _out="${_trace_dir}/CONFFILES"
|
|
local _p _type _val _mode _uid _gid
|
|
|
|
[ -d ./etc ] || return 0
|
|
|
|
: > "${_out}"
|
|
|
|
# regular files
|
|
while IFS= read -r -d '' _p; do
|
|
_mode=$(stat -c %a -- "$_p")
|
|
_uid=$(stat -c %u -- "$_p")
|
|
_gid=$(stat -c %g -- "$_p")
|
|
_val=$(sha256sum -- "$_p" | awk '{print $1}')
|
|
printf '%s\tf\t%s\t%s\t%s\t%s\n' "/${_p#./}" "${_val}" "${_mode}" "${_uid}" "${_gid}" >> "${_out}"
|
|
done < <(find ./etc -type f -print0)
|
|
|
|
# symlinks
|
|
while IFS= read -r -d '' _p; do
|
|
_mode=$(stat -c %a -- "$_p")
|
|
_uid=$(stat -c %u -- "$_p")
|
|
_gid=$(stat -c %g -- "$_p")
|
|
_val=$(readlink -- "$_p")
|
|
printf '%s\tl\t%s\t%s\t%s\t%s\n' "/${_p#./}" "${_val}" "${_mode}" "${_uid}" "${_gid}" >> "${_out}"
|
|
done < <(find ./etc -type l -print0)
|
|
|
|
sort -o "${_out}" "${_out}" 2>/dev/null || true
|
|
}
|
|
|
|
leaf_conffile_is_unmodified() {
|
|
# usage: leaf_conffile_is_unmodified <abs_path> <trace_dir>
|
|
# return 0 if unmodified (safe to remove), 1 otherwise
|
|
local path="$1" tdir="$2"
|
|
local cf="${tdir}/CONFFILES"
|
|
local line type val mode uid gid
|
|
local cur_val cur_mode cur_uid cur_gid
|
|
|
|
[ -r "${cf}" ] || return 1
|
|
[ -e "${path}" ] || return 1
|
|
|
|
line="$(grep -F -m1 -- "${path}"$'\t' "${cf}" 2>/dev/null)" || return 1
|
|
|
|
IFS=$'\t' read -r _p type val mode uid gid <<< "${line}"
|
|
|
|
cur_mode="$(stat -c %a -- "${path}" 2>/dev/null)" || return 1
|
|
cur_uid="$(stat -c %u -- "${path}" 2>/dev/null)" || return 1
|
|
cur_gid="$(stat -c %g -- "${path}" 2>/dev/null)" || return 1
|
|
|
|
# metadata changed -> treat as modified
|
|
if [ "${cur_mode}" != "${mode}" ] || [ "${cur_uid}" != "${uid}" ] || [ "${cur_gid}" != "${gid}" ]; then
|
|
return 1
|
|
fi
|
|
|
|
case "${type}" in
|
|
f)
|
|
cur_val="$(sha256sum -- "${path}" 2>/dev/null | awk '{print $1}')" || return 1
|
|
[ "${cur_val}" = "${val}" ] && return 0 || return 1
|
|
;;
|
|
l)
|
|
cur_val="$(readlink -- "${path}" 2>/dev/null)" || return 1
|
|
[ "${cur_val}" = "${val}" ] && return 0 || return 1
|
|
;;
|
|
*)
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
leaf_update_package_database() {
|
|
local _item="$(echo "$2" | awk 'BEGIN{FS="/";OFS="\\/"}{print $1,$2}')"
|
|
case $1 in
|
|
add)
|
|
sed -i "/${_item}/d" "${INSTALLED_PACKAGES}"
|
|
echo "$2" >> ${INSTALLED_PACKAGES}
|
|
;;
|
|
delete)
|
|
sed -i "/${_item}/d" "${INSTALLED_PACKAGES}"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
leaf_update_environment() {
|
|
ldconfig
|
|
[ ! -r /etc/profile ] || source /etc/profile
|
|
}
|
|
|
|
leaf_run_phase() {
|
|
# usage: leaf_run_phase <phase_name> <func_name>
|
|
local phase="$1" fn="$2"
|
|
|
|
LEAF_PHASE_RC=0
|
|
|
|
declare -F "$fn" >/dev/null || {
|
|
LEAF_PHASE_RC=127
|
|
return 0
|
|
}
|
|
|
|
if leaf_is_noop_func "$fn"; then
|
|
LEAF_PHASE_RC=0
|
|
return 0
|
|
fi
|
|
|
|
echo -e "${GREEN_COLOR}>>> ${PKG_PREFIX}/${PKG_NAME}: ${phase}${CLEAR_COLOR}"
|
|
|
|
set +e
|
|
(
|
|
set -eE -o pipefail
|
|
"$fn"
|
|
)
|
|
LEAF_PHASE_RC=$?
|
|
set -e
|
|
|
|
return 0
|
|
}
|
|
|
|
leaf_phase_must() {
|
|
local phase="$1" fn="$2" msg="$3"
|
|
leaf_run_phase "$phase" "$fn"
|
|
if [ "${LEAF_PHASE_RC}" -ne 0 ]; then
|
|
leaf_error "${msg:-${fn} failed (rc=${LEAF_PHASE_RC})}"
|
|
fi
|
|
}
|
|
|
|
leaf_phase_try() {
|
|
# just run, caller checks $LEAF_PHASE_RC
|
|
leaf_run_phase "$1" "$2"
|
|
}
|
|
|
|
|
|
leaf_prepare_package() {
|
|
local url sourcefile index _md5sum local_src
|
|
|
|
distdir="${DIST_DIR}"
|
|
filedir="${PKGBUILD_DIR}/${PKG_PREFIX}/${PKG_NAME}"
|
|
mkdir -pv -- "${distdir}"
|
|
|
|
pushd "${distdir}" >/dev/null || leaf_error "cannot enter distdir: ${distdir}"
|
|
|
|
for index in "${!sources[@]}"; do
|
|
sourcefile="${sources[$index]}"
|
|
url="${urls[$index]}"
|
|
|
|
if [ -z "${url}" ]; then
|
|
url="${sourcefile}"
|
|
fi
|
|
|
|
if [ -f "${sourcefile}" ]; then
|
|
echo "${sourcefile} exists, checking..."
|
|
_md5sum="$(md5sum -- "${sourcefile}" | awk '{print $1}')"
|
|
if [ "${_md5sum}" = "${md5sums[$index]}" ]; then
|
|
echo "md5sum match, skip ${sourcefile}."
|
|
continue
|
|
fi
|
|
echo "md5sum does not match, refetching..."
|
|
rm -f -- "${sourcefile}"
|
|
fi
|
|
|
|
case "${url}" in
|
|
http://*|https://*|ftp://*)
|
|
echo "Downloading ${sourcefile} from ${url}..."
|
|
wget -O "${sourcefile}" -- "${url}" || leaf_error "wget failed: ${url}"
|
|
;;
|
|
*)
|
|
local_src="${filedir}/${url}"
|
|
echo "Copying ${sourcefile} from local file ${local_src}..."
|
|
[ -f "${local_src}" ] || leaf_error "local source not found: ${local_src}"
|
|
cp -f -- "${local_src}" "${sourcefile}" || leaf_error "cp failed: ${local_src}"
|
|
;;
|
|
esac
|
|
|
|
_md5sum="$(md5sum -- "${sourcefile}" | awk '{print $1}')"
|
|
if [ "${_md5sum}" != "${md5sums[$index]}" ]; then
|
|
leaf_error "md5sums of ${sourcefile}(${_md5sum}) does not match with ${md5sums[$index]}!"
|
|
fi
|
|
done
|
|
|
|
popd >/dev/null || true
|
|
echo "Sources ready."
|
|
leaf_phase_must "Preparing" src_prepare "Prepare failed."
|
|
}
|
|
|
|
leaf_build_package() {
|
|
rm -rf -- ${TMPDIR} ${HOME}
|
|
mkdir -pv ${TMPDIR}
|
|
mkdir -pv ${HOME}
|
|
|
|
leaf_prepare_package
|
|
leaf_phase_must "Building" src_build "Build failed."
|
|
|
|
leaf_phase_try "Checking" src_check
|
|
if [ "${LEAF_PHASE_RC}" -ne 0 ]; then
|
|
if [ "${FORCE_INSTALL:-0}" = "1" ]; then
|
|
echo -e "${RED_COLOR}* Tests failed, but is in force-install mode.${CLEAR_COLOR}"
|
|
else
|
|
leaf_error "Tests failed, please check. Aborting installation."
|
|
fi
|
|
fi
|
|
|
|
## run tests (strict: any failing command inside src_check fails the whole check)
|
|
#local _check_rc=0
|
|
#set +e
|
|
#( set -e; src_check )
|
|
#_check_rc=$?
|
|
#set -e
|
|
#
|
|
#if [ "${_check_rc}" -ne 0 ]; then
|
|
# if [ "${FORCE_INSTALL}" = "1" ]; then
|
|
# echo -e "${RED_COLOR}* Tests failed, but is in force-install mode.${CLEAR_COLOR}"
|
|
# else
|
|
# leaf_error "Tests failed, please check. Aborting installation."
|
|
# fi
|
|
#fi
|
|
|
|
# ensure pkgdir exists
|
|
rm -rf -- "${pkgdir}"
|
|
install -dm755 -- "${pkgdir}"
|
|
|
|
leaf_phase_must "Installing" src_install "Installing failed."
|
|
local _option
|
|
for _option in $(echo ${!BUILD_OPTIONS[*]}); do
|
|
if [[ x"${_option}" == x"strip" ]] && [[ x"${BUILD_OPTIONS[$_option]}" == x"1" ]]; then
|
|
leaf_strip_files
|
|
fi
|
|
if [[ x"${_option}" == x"libtool" ]] && [[ x"${BUILD_OPTIONS[$_option]}" == x"0" ]]; then
|
|
leaf_remove_libtool_files
|
|
fi
|
|
if [[ x"${_option}" == x"zipman" ]] && [[ x"${BUILD_OPTIONS[$_option]}" = x"1" ]]; then
|
|
leaf_compress_man_pages
|
|
fi
|
|
done
|
|
if [[ x"${ENABLE_DEBUG_TREE}" == x"true" ]]; then
|
|
[ ! -x /usr/bin/tree ] || /usr/bin/tree "${pkgdir}"
|
|
fi
|
|
}
|
|
|
|
leaf_install_package() {
|
|
# install need to be in fakeroot
|
|
leaf_build_package
|
|
# trace need to to in the same fakeroot
|
|
leaf_trace_package "${pkgdir}"
|
|
# ROOT starts
|
|
leaf_phase_must "Preinstall" src_preinstall "Preinstall failed."
|
|
leaf_merge_package "${pkgdir}"
|
|
leaf_phase_must "Postinstall" src_postinstall "Postinstall failed."
|
|
leaf_update_package_database add "${PKG_PREFIX}/${PKG_NAME}"
|
|
leaf_update_environment
|
|
# ROOT ends
|
|
echo -e "${GREEN_COLOR}* Package ${PKG_PREFIX}/${PKG_NAME} has been installed${CLEAR_COLOR}\n"
|
|
}
|
|
|
|
leaf_dirct-install_package() {
|
|
# install need to be in fakeroot
|
|
#leaf_build_package
|
|
# trace need to to in the same fakeroot
|
|
leaf_trace_package "${pkgdir}"
|
|
# ROOT starts
|
|
leaf_phase_must "Preinstall" src_preinstall "Preinstall failed."
|
|
leaf_merge_package "${pkgdir}"
|
|
leaf_phase_must "Postinstall" src_postinstall "Postinstall failed."
|
|
leaf_update_package_database add "${PKG_PREFIX}/${PKG_NAME}"
|
|
leaf_update_environment
|
|
# ROOT ends
|
|
echo -e "${GREEN_COLOR}* Package ${PKG_PREFIX}/${PKG_NAME} has been installed${CLEAR_COLOR}\n"
|
|
}
|
|
|
|
leaf_force-install_package(){
|
|
FORCE_INSTALL=1
|
|
leaf_install_package
|
|
}
|
|
|
|
leaf_upgrade_package() {
|
|
local old_query="$1" new_query="$2"
|
|
local old_prefix old_name new_prefix new_name
|
|
local new_srcdir new_pkgdir
|
|
local old_trace_dir new_trace_dir
|
|
local skipfile
|
|
|
|
# -------- resolve OLD (installed) --------
|
|
leaf_find_remove_pkgbuild "${old_query}"
|
|
old_prefix="${PKG_PREFIX}"
|
|
old_name="${PKG_NAME}"
|
|
old_trace_dir="${TRACE_DIR}/${old_prefix}/${old_name}"
|
|
OLD_TRACE_DIR="${old_trace_dir}"
|
|
|
|
# -------- resolve NEW (available) --------
|
|
leaf_find_pkgbuild "${new_query}"
|
|
new_prefix="${PKG_PREFIX}"
|
|
new_name="${PKG_NAME}"
|
|
|
|
# sanity
|
|
if [[ "${old_prefix}/${old_name}" == "${new_prefix}/${new_name}" ]]; then
|
|
leaf_error "upgrade: old and new refer to the same package: ${old_prefix}/${old_name}"
|
|
fi
|
|
|
|
# =========================================================
|
|
# 1) BUILD NEW (no ROOT touch)
|
|
# =========================================================
|
|
PKG_PREFIX="${new_prefix}"
|
|
PKG_NAME="${new_name}"
|
|
|
|
leaf_reset_state
|
|
leaf_message_init
|
|
source "${PKGBUILD_DIR}/${PKG_PREFIX}/${PKG_NAME}.PKGBUILD"
|
|
leaf_parse_options
|
|
|
|
srcdir="${BUILD_DIR}/${PKG_PREFIX}/${PKG_NAME}"
|
|
LEAFTMPDIR="/tmp/leaf-tmp/${PKG_NAME}"
|
|
LEAFHOME="${LEAFTMPDIR}"
|
|
export TMPDIR="${LEAFTMPDIR}"
|
|
export HOME="${LEAFHOME}"
|
|
pkgdir="${srcdir}/__pkgdir__"
|
|
|
|
rm -rf -- "${srcdir}" && install -dm755 -- "${srcdir}" && cd "${srcdir}"
|
|
mkdir -pv -- "${LEAFTMPDIR}" "${LEAFHOME}"
|
|
|
|
leaf_build_package
|
|
|
|
new_srcdir="${srcdir}"
|
|
new_pkgdir="${pkgdir}"
|
|
|
|
# =========================================================
|
|
# 2) INSTALL NEW (touch ROOT)
|
|
# Use dirct-install path so we don't rebuild.
|
|
# =========================================================
|
|
PKG_PREFIX="${new_prefix}"
|
|
PKG_NAME="${new_name}"
|
|
|
|
leaf_reset_state
|
|
leaf_message_init
|
|
source "${PKGBUILD_DIR}/${PKG_PREFIX}/${PKG_NAME}.PKGBUILD"
|
|
leaf_parse_options
|
|
|
|
srcdir="${new_srcdir}"
|
|
pkgdir="${new_pkgdir}"
|
|
LEAFTMPDIR="/tmp/leaf-tmp/${PKG_NAME}"
|
|
LEAFHOME="${LEAFTMPDIR}"
|
|
export TMPDIR="${LEAFTMPDIR}"
|
|
export HOME="${LEAFHOME}"
|
|
|
|
[ -d "${pkgdir}" ] || leaf_error "upgrade: built pkgdir missing: ${pkgdir}"
|
|
cd "${srcdir}" || leaf_error "upgrade: cannot enter build dir: ${srcdir}"
|
|
|
|
leaf_dirct-install_package
|
|
|
|
#rm -rf -- "${LEAFTMPDIR}" "${LEAFHOME}"
|
|
|
|
# new trace dir now exists (generated by leaf_dirct-install_package -> leaf_trace_package)
|
|
new_trace_dir="${TRACE_DIR}/${new_prefix}/${new_name}"
|
|
[ -d "${new_trace_dir}" ] || leaf_error "upgrade: new trace dir missing: ${new_trace_dir}"
|
|
|
|
# =========================================================
|
|
# 3) REMOVE OLD, but SKIP paths owned by NEW
|
|
# =========================================================
|
|
skipfile="$(mktemp "${TEMP_DIR}/leaf-upgrade-skip.XXXXXX")" || leaf_error "mktemp failed"
|
|
cat "${new_trace_dir}/FILES" "${new_trace_dir}/LINKS" "${new_trace_dir}/DIRS" > "${skipfile}" || {
|
|
rm -f -- "${skipfile}"
|
|
leaf_error "upgrade: cannot create skiplist"
|
|
}
|
|
|
|
PKG_PREFIX="${old_prefix}"
|
|
PKG_NAME="${old_name}"
|
|
|
|
leaf_reset_state
|
|
leaf_message_init
|
|
# old remove sources PKGBUILD from TRACE (best effort)
|
|
source "${old_trace_dir}/PKGBUILD" 2>/dev/null || true
|
|
|
|
export LEAF_REMOVE_SKIPLIST="${skipfile}"
|
|
leaf_remove_package
|
|
unset LEAF_REMOVE_SKIPLIST
|
|
|
|
rm -f -- "${skipfile}"
|
|
|
|
echo -e "${GREEN_COLOR}* Upgraded ${old_prefix}/${old_name} -> ${new_prefix}/${new_name}${CLEAR_COLOR}\n"
|
|
}
|
|
|
|
leaf_in_skiplist() {
|
|
# usage: leaf_in_skiplist <abs_path>
|
|
# return 0 if path in LEAF_REMOVE_SKIPLIST file
|
|
local p="$1"
|
|
[ -n "${LEAF_REMOVE_SKIPLIST:-}" ] || return 1
|
|
[ -r "${LEAF_REMOVE_SKIPLIST}" ] || return 1
|
|
grep -Fxq -- "$p" "${LEAF_REMOVE_SKIPLIST}" 2>/dev/null
|
|
}
|
|
|
|
leaf_remove_package() {
|
|
[ -n "$(grep "${PKG_PREFIX}/${PKG_NAME}" ${INSTALLED_PACKAGES})" ] || {
|
|
leaf_error "Package ${PKG_PREFIX}/${PKG_NAME} is NOT installed"
|
|
}
|
|
|
|
leaf_phase_must "Preremove" src_preremove "Preremove failed."
|
|
|
|
local _file _link _directory _etc_backup_path _relative_path _backup_etc=false
|
|
local _trace_dir="${TRACE_DIR}/${PKG_PREFIX}/${PKG_NAME}"
|
|
local _has_conffiles=0
|
|
|
|
[ -r "${_trace_dir}/CONFFILES" ] && _has_conffiles=1
|
|
|
|
_etc_backup_path="/etc/.leaf_backup/${PKG_NAME}_$(date +%Y%m%d%H%M%S)"
|
|
|
|
# FILES
|
|
while read -r _file; do
|
|
if leaf_in_skiplist "${_file}"; then
|
|
continue
|
|
fi
|
|
if [[ "${_file}" == /etc/* && -e "${_file}" ]]; then
|
|
if [ "${_has_conffiles}" -eq 1 ]; then
|
|
if leaf_conffile_is_unmodified "${_file}" "${_trace_dir}"; then
|
|
rm -fv -- "${_file}"
|
|
else
|
|
leaf_record_message "Preserved modified config file: ${_file}"
|
|
fi
|
|
continue
|
|
fi
|
|
|
|
# fallback for old packages without CONFFILES (keep old behavior)
|
|
if [[ -f "${_file}" ]]; then
|
|
_backup_etc=true
|
|
_relative_path="${_file#/etc/}"
|
|
mkdir -pv "${_etc_backup_path}/$(dirname "${_relative_path}")"
|
|
mv -v -- "${_file}" "${_etc_backup_path}/${_relative_path}"
|
|
else
|
|
rm -fv -- "${_file}"
|
|
fi
|
|
else
|
|
rm -fv -- "${_file}"
|
|
fi
|
|
done < "${_trace_dir}/FILES"
|
|
|
|
if [[ "${_backup_etc}" == true ]]; then
|
|
leaf_record_message "Found files in /etc, backuped to ${_etc_backup_path}."
|
|
fi
|
|
|
|
# LINKS
|
|
while read -r _link; do
|
|
if leaf_in_skiplist "${_link}"; then
|
|
continue
|
|
fi
|
|
if [[ "${_link}" == /etc/* && -e "${_link}" ]]; then
|
|
if [ "${_has_conffiles}" -eq 1 ]; then
|
|
if leaf_conffile_is_unmodified "${_link}" "${_trace_dir}"; then
|
|
rm -fv -- "${_link}"
|
|
else
|
|
leaf_record_message "Preserved modified config link: ${_link}"
|
|
fi
|
|
continue
|
|
fi
|
|
else
|
|
[ ! -L "${_link}" ] || rm -fv -- "${_link}"
|
|
fi
|
|
done < "${_trace_dir}/LINKS"
|
|
|
|
# DIRS
|
|
while read -r _directory; do
|
|
if leaf_in_skiplist "${_directory}"; then
|
|
continue
|
|
fi
|
|
if [[ -d "${_directory}" ]]; then
|
|
rmdir --ignore-fail-on-non-empty "${_directory}"
|
|
fi
|
|
done < "${_trace_dir}/DIRS"
|
|
|
|
leaf_phase_must "Postremove" src_postremove "Postremove failed."
|
|
leaf_invoke_hooks remove
|
|
rm -rf "${_trace_dir}"
|
|
leaf_update_package_database delete "${PKG_PREFIX}/${PKG_NAME}"
|
|
leaf_update_environment
|
|
echo -e "${GREEN_COLOR}<<< removed ${PKG_PREFIX}/${PKG_NAME}${CLEAR_COLOR}"
|
|
}
|
|
|
|
leaf_search_package() {
|
|
local _item _regex
|
|
for _item in "$@"; do
|
|
_regex+="${_item}\|"
|
|
done
|
|
_regex="${_regex:0:${#_regex}-2}"
|
|
find "${PKGBUILD_DIR}" -type f -regex ".*\(${_regex}\).*\.PKGBUILD" -printf "%P\n" | while read -r _item; do
|
|
echo "${_item%.PKGBUILD}"
|
|
done
|
|
}
|
|
|
|
leaf_show_package() {
|
|
source "${PKGBUILD_DIR}/${PKG_PREFIX}/${PKG_NAME}".PKGBUILD
|
|
echo "Package: ${pkgname}"
|
|
echo "Version: ${pkgver}"
|
|
echo "Description: ${pkgdesc}"
|
|
echo "Homepage: ${homepage}"
|
|
echo -e "License: ${license[@]}\n"
|
|
}
|
|
|
|
leaf_pack_package() {
|
|
leaf_build_package
|
|
pushd "${srcdir}" > /dev/null 2>&1
|
|
echo "${PKG_PREFIX}" > .PKG_PREFIX
|
|
install -m644 "${PKGBUILD_DIR}/${PKG_PREFIX}/${PKG_NAME}".PKGBUILD .PKGBUILD
|
|
install -dm755 "${BINARY_DIR}"
|
|
local _target="${BINARY_DIR}/${pkgname}-${pkgver}${BINARY_EXT}"
|
|
rm -fv "${_target}"
|
|
tar -I "${COMPRESS_PROG}" -cf "${_target}" "$(basename ${pkgdir})" .PKG{_PREFIX,BUILD}
|
|
popd > /dev/null 2>&1
|
|
}
|
|
|
|
leaf_unpack_package() {
|
|
srcdir="${TEMP_DIR}/$(basename ${1%.pkg.*})"
|
|
pkgdir="${srcdir}"/__pkgdir__
|
|
rm -rf "${srcdir}" && install -dm755 "${srcdir}" && cd "${srcdir}"
|
|
tar -xf $1 -C "${srcdir}"
|
|
source "${srcdir}"/.PKGBUILD
|
|
PKG_PREFIX="$(cat "${srcdir}"/.PKG_PREFIX)"
|
|
PKG_NAME="${pkgname}-${pkgver}"
|
|
install -Dm644 "${srcdir}"/.PKGBUILD "${PKGBUILD_DIR}/${PKG_PREFIX}/${PKG_NAME}".PKGBUILD
|
|
leaf_trace_package "${pkgdir}"
|
|
src_preinstall
|
|
leaf_merge_package "${pkgdir}"
|
|
src_postinstall
|
|
leaf_update_package_database add "${PKG_PREFIX}/${PKG_NAME}"
|
|
leaf_update_environment
|
|
echo -e "${GREEN_COLOR}* Package ${PKG_PREFIX}/${PKG_NAME} has been installed${CLEAR_COLOR}\n"
|
|
}
|
|
|
|
leaf_add_shell() {
|
|
# usage: leaf_add_shell <abs_shell_path>
|
|
local shell="$1" f="/etc/shells" tmp
|
|
|
|
# ensure file exists
|
|
if [ ! -f "$f" ]; then
|
|
install -m 0644 -o root -g root /dev/null "$f" || return 1
|
|
fi
|
|
|
|
# already present? (exact whole-line match)
|
|
grep -Fxq -- "$shell" "$f" && return 0
|
|
|
|
tmp="$(mktemp "${f}.leaf.XXXXXX")" || return 1
|
|
cat "$f" > "$tmp" || { rm -f "$tmp"; return 1; }
|
|
|
|
# append with newline
|
|
printf '%s\n' "$shell" >> "$tmp" || { rm -f "$tmp"; return 1; }
|
|
|
|
# atomic replace
|
|
install -m 0644 -o root -g root "$tmp" "$f" || { rm -f "$tmp"; return 1; }
|
|
rm -f "$tmp"
|
|
}
|
|
|
|
leaf_remove_shell() {
|
|
# usage: leaf_remove_shell <abs_shell_path>
|
|
local shell="$1" f="/etc/shells" tmp
|
|
|
|
[ -f "$f" ] || return 0
|
|
# if not present, nothing to do
|
|
grep -Fxq -- "$shell" "$f" || return 0
|
|
|
|
tmp="$(mktemp "${f}.leaf.XXXXXX")" || return 1
|
|
# delete exact matching lines only
|
|
grep -Fvx -- "$shell" "$f" > "$tmp" || true
|
|
|
|
install -m 0644 -o root -g root "$tmp" "$f" || { rm -f "$tmp"; return 1; }
|
|
rm -f "$tmp"
|
|
}
|
|
|
|
|
|
main $@
|