Files
leaf/leaf
Yingjie Wang 4ff79cfdd6 update: seperate HOME and TEMPDIR
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
2026-01-02 01:30:13 -05:00

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 $@