Compare commits

..
20 Commits
Author SHA1 Message Date
wyj 96e0ccfbdd fix: clean the message file
Fixes #3
2026-02-06 16:44:42 -05:00
wyj 4b895a0591 fix: clean the home and temdir 2026-02-06 16:28:33 -05:00
wyj 94379cb720 feat: set terminal title 2026-02-06 15:46:51 -05:00
wyj 7587d72578 update: fix PATH 2026-01-19 17:31:33 -05:00
wyj cfe99f928a update: change filedir 2026-01-19 17:30:10 -05:00
wyj 2fc05ba7ea update: safe leaf_update_package_datebase 2026-01-17 16:18:42 -05:00
wyj 30fed4b734 update: add leaf query 2026-01-17 15:17:42 -05:00
wyj caf66028e6 update: add leaf_info to print 2026-01-14 00:27:18 -05:00
wyj d5188a3385 update: update readme 2026-01-11 05:10:21 -05:00
wyj 618ad9222a feat: allow sha512sums ans sha256sums 2026-01-11 05:08:00 -05:00
wyj 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
wyj 1386633baf update: add remove output 2025-12-29 17:49:34 -05:00
wyj 24c9bc3758 feat: add leaf upgrade old new for upgrading 2025-12-29 17:40:55 -05:00
wyj d0600c4b07 feat: add CONFFILES to record /etc files 2025-12-29 01:40:45 -05:00
wyj db903ca802 fix: fix a bug in leaf_remove_package
the _directory varibale lost $
2025-12-28 13:38:40 -05:00
wyj 57ea39b406 feat: check dummy function, do not echo 2025-12-26 23:32:34 -05:00
wyj f2cab01e36 fix: update envs 2025-12-26 19:05:20 -05:00
wyj a11405d93f fix: fix message by write to a temp message file
fixes #1
2025-12-26 18:02:02 -05:00
wyj c682ef1049 fix: errexit fix 2025-12-26 17:10:38 -05:00
wyj d44c761978 update: use filedir 2025-12-26 14:32:11 -05:00
2 changed files with 583 additions and 92 deletions
+3
View File
@@ -65,6 +65,9 @@
- license 数组, 软件许可证 - license 数组, 软件许可证
- sources 数组, 构建过程使用到的所有源码包和补丁,可以是 `https://...` 也可以是文件相对路径 - sources 数组, 构建过程使用到的所有源码包和补丁,可以是 `https://...` 也可以是文件相对路径
- urls 数组, `sources` 各项对应的地址, 当前仅限使用 `wget` 获取或复制文件 - urls 数组, `sources` 各项对应的地址, 当前仅限使用 `wget` 获取或复制文件
- 以下三个数组有一个即可
- sha512sums 数组, `sources` 各项对应的 sha512 checksum
- sha256sums 数组, `sources` 各项对应的 sha256 checksum
- md5sums 数组, `sources` 各项对应的 md5 checksum - md5sums 数组, `sources` 各项对应的 md5 checksum
- options 数组, 构建选项, 详见第4小节 - options 数组, 构建选项, 详见第4小节
- srcdir 字符串, 构建路径(在leaf主程序中定义) - srcdir 字符串, 构建路径(在leaf主程序中定义)
+580 -92
View File
@@ -1,14 +1,16 @@
#!/bin/bash #!/bin/bash
set -e set -e
shopt -s inherit_errexit 2>/dev/null || true
RED_COLOR="\e[1;31m" RED_COLOR=$"\e[1;31m"
PURPLE_COLOR="\e[1;35m" PURPLE_COLOR=$"\e[1;35m"
GREEN_COLOR="\e[1;32m" GREEN_COLOR=$'\e[1;32m'
CLEAR_COLOR="\e[0m" CLEAR_COLOR=$'\e[0m'
_DEF_PARALLEL_JOBS=4 _DEF_PARALLEL_JOBS=4
_ENV_PARALLEL_JOBS="${PARALLEL_JOBS}" _ENV_PARALLEL_JOBS="${PARALLEL_JOBS}"
source /etc/leaf.conf source /etc/leaf.conf
export PATH="/usr/bin:/usr/sbin"
if [ -n "${_ENV_PARALLEL_JOBS}" ]; then if [ -n "${_ENV_PARALLEL_JOBS}" ]; then
export PARALLEL_JOBS="${_ENV_PARALLEL_JOBS}" export PARALLEL_JOBS="${_ENV_PARALLEL_JOBS}"
elif [ -z "${PARALLEL_JOBS}" ]; then elif [ -z "${PARALLEL_JOBS}" ]; then
@@ -18,8 +20,11 @@ else
fi fi
export MAKEFLAGS="-j${PARALLEL_JOBS}" export MAKEFLAGS="-j${PARALLEL_JOBS}"
export NINJAJOBS="${PARALLEL_JOBS}" export NINJAJOBS="${PARALLEL_JOBS}"
export LC_ALL=C export TESTSUITEFLAGS="-j${PARALLEL_JOBS}"
export LANG=C
export LC_CTYPE=C.UTF-8
export leaf_flags="CFLAGS CXXFLAGS FCFLAGS FFLAGS RUSTFLAGS" export leaf_flags="CFLAGS CXXFLAGS FCFLAGS FFLAGS RUSTFLAGS"
export LEAF_MESSAGE_DIR="${TEMP_DIR:-/tmp}/leaf-messages"
for flag in ${leaf_flags}; do for flag in ${leaf_flags}; do
export $flag export $flag
done done
@@ -27,7 +32,15 @@ done
declare -A BUILD_OPTIONS declare -A BUILD_OPTIONS
BUILD_OPTIONS=([strip]="0" [libtool]="0" [zipman]="0") BUILD_OPTIONS=([strip]="0" [libtool]="0" [zipman]="0")
# global message file list
declare -a LEAF_MESSAGE_FILES=()
leaf_set_title() {
printf '\e]2;%s\a' "$*"
}
main() { main() {
leaf_set_title "leaf"
leaf_check_directories leaf_check_directories
if [ $# -le 1 ]; then if [ $# -le 1 ]; then
case $1 in case $1 in
@@ -54,6 +67,7 @@ main() {
leaf_find_pkgbuild "${_item}" leaf_find_pkgbuild "${_item}"
fi fi
leaf_reset_state leaf_reset_state
leaf_message_init
if [[ x"$1" == x"remove" ]]; then if [[ x"$1" == x"remove" ]]; then
source "${TRACE_DIR}/${PKG_PREFIX}/${PKG_NAME}/PKGBUILD" || leaf_record_message "cannot find PKGBUILD, ingore." source "${TRACE_DIR}/${PKG_PREFIX}/${PKG_NAME}/PKGBUILD" || leaf_record_message "cannot find PKGBUILD, ingore."
else else
@@ -61,13 +75,24 @@ main() {
fi fi
leaf_parse_options leaf_parse_options
srcdir="${BUILD_DIR}/${PKG_PREFIX}/${PKG_NAME}" 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__ pkgdir="${srcdir}"/__pkgdir__
if [[ x"$1" != x"dirct-install" ]]; then if [[ x"$1" != x"dirct-install" ]]; then
rm -rf "${srcdir}" && install -dm755 "${srcdir}" && cd "${srcdir}" rm -rf "${srcdir}" && install -dm755 "${srcdir}" && cd "${srcdir}"
fi fi
leaf_${1}_package leaf_${1}_package
#rm -rf "${LEAFTMPDIR}"
#rm -rf "${LEAFHOME}"
done done
;; ;;
upgrade)
leaf_check_permission
[ $# -eq 3 ] || leaf_error "Usage: leaf upgrade <oldpkg> <newpkg>"
leaf_upgrade_package "$2" "$3"
;;
search) search)
leaf_search_package "${@:2}" leaf_search_package "${@:2}"
;; ;;
@@ -84,13 +109,16 @@ main() {
leaf_unpack_package "${_item}" leaf_unpack_package "${_item}"
done done
;; ;;
query)
shift
[[ $# -eq 1 ]] || leaf_error "usage: leaf query <atom-or-name>"
leaf_query "$1"
;;
*) *)
leaf_print_usage leaf_print_usage
;; ;;
esac esac
if [ -n "${MESSAGE}" ]; then leaf_print_message
echo -e "${MESSAGE}"
fi
fi fi
} }
@@ -98,6 +126,10 @@ main() {
# Help functions that give PKGBUILD to use # Help functions that give PKGBUILD to use
########################################################### ###########################################################
leaf_info() {
echo -e "${GREEN_COLOR} * $1${CLEAR_COLOR}"
}
leaf_clear_flags() { leaf_clear_flags() {
for flag in $leaf_flags; do for flag in $leaf_flags; do
unset $flag unset $flag
@@ -167,9 +199,7 @@ leaf_compress_man_pages() {
} }
leaf_error() { leaf_error() {
if [ -n "${MESSAGE}" ]; then leaf_print_message
echo -e "${MESSAGE}"
fi
echo -e "${RED_COLOR}* $1${CLEAR_COLOR}" && exit 1 echo -e "${RED_COLOR}* $1${CLEAR_COLOR}" && exit 1
} }
@@ -228,6 +258,7 @@ leaf_invoke_hooks() {
leaf_merge_package() { leaf_merge_package() {
pushd $1 > /dev/null 2>&1 pushd $1 > /dev/null 2>&1
leaf_set_title "leaf: ${PKG_PREFIX}/${PKG_NAME} (Merging)"
local _trace_dir="${TRACE_DIR}/${PKG_PREFIX}/${PKG_NAME}" local _trace_dir="${TRACE_DIR}/${PKG_PREFIX}/${PKG_NAME}"
local _item _item_conflict _mode _owner _group local _item _item_conflict _mode _owner _group
local _time="$(date +%Y%m%d%H%M%S)" local _time="$(date +%Y%m%d%H%M%S)"
@@ -237,27 +268,46 @@ leaf_merge_package() {
_group=$(stat -c %g ."${_item}") _group=$(stat -c %g ."${_item}")
install -d -m ${_mode} -o ${_owner} -g ${_group} "${_item}" install -d -m ${_mode} -o ${_owner} -g ${_group} "${_item}"
done done
cat "${_trace_dir}"/FILES | while read -r _item; do
cat "${_trace_dir}"/FILES | while read -r _item; do
_mode=$(stat -c %a ."${_item}") _mode=$(stat -c %a ."${_item}")
_owner=$(stat -c %u ."${_item}") _owner=$(stat -c %u ."${_item}")
_group=$(stat -c %g ."${_item}") _group=$(stat -c %g ."${_item}")
if [[ "${_item}" == /etc/* && -e "${_item}" ]]; then
_item_conflict="$(dirname "${_item}")/._$(basename "${_item}").conflict_${PKG_NAME}_${_time}" # Fast path: non-/etc, or /etc path that doesn't exist yet -> install/overwrite directly
if (diff ."${_item}" "${_item}" > /dev/null); then if [[ "${_item}" != /etc/* || ! -e "${_item}" ]]; 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
else
install -D -m ${_mode} -o ${_owner} -g ${_group} ."${_item}" "${_item}" 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 fi
done done
cat "${_trace_dir}"/LINKS | while read -r _item; do cat "${_trace_dir}"/LINKS | while read -r _item; do
cp -dp ."${_item}" "${_item}" cp -dp ."${_item}" "${_item}"
done done
leaf_invoke_hooks install leaf_invoke_hooks install
popd > /dev/null 2>&1 popd > /dev/null 2>&1
rm -rf "${LEAFHOME}"
rm -rf "${LEAFTMPDIR}"
} }
leaf_parse_options() { leaf_parse_options() {
@@ -290,9 +340,10 @@ Usage: leaf [option] [packages]
Option: prepare prepare packages but do not build Option: prepare prepare packages but do not build
build build packages but do not install build build packages but do not install
install install packages install install packages
forece-install install packages even if test faild forece-install install packages even if test failed
dirct-install install packages directly from previous build dirct-install install packages directly from previous build
remove remove packages remove remove packages
upgrade upgrade oldpkg to newpkg (leaf upgrade old new)
clean clean all source folders clean clean all source folders
list list installed packages list list installed packages
search search packages by keyword search search packages by keyword
@@ -303,41 +354,90 @@ Option: prepare prepare packages but do not build
EOF EOF
} }
leaf_message_init() {
# one log per package invocation
local d="${LEAF_MESSAGE_DIR}/${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() { leaf_record_message() {
MESSAGE+="\n${PURPLE_COLOR}* Message from ${PKG_PREFIX}/${PKG_NAME}${CLEAR_COLOR}\n" local out
local _item local _item
for _item in "${@:1}"; do for _item in "${@:1}"; do
MESSAGE+="${GREEN_COLOR}* ${_item}${CLEAR_COLOR}\n" out+="${GREEN_COLOR}* ${_item}${CLEAR_COLOR}\n"
done 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"
done
rm -rf -- "${LEAF_MESSAGE_DIR}"/*
LEAF_MESSAGE_FILES=()
unset LEAF_MESSAGE_FILE
}
leaf_remove_libtool_files() { leaf_remove_libtool_files() {
find "${pkgdir}" -name "*.la" -delete find "${pkgdir}" -name "*.la" -delete
} }
leaf_noop() { :; }
leaf_reset_state() { leaf_reset_state() {
options=() options=()
src_preinstall() { src_preinstall() { leaf_noop; }
: src_postinstall() { leaf_noop; }
} src_preremove() { leaf_noop; }
src_postinstall() { src_postremove() { leaf_noop; }
:
} src_prepare() { leaf_noop; }
src_preremove() { src_build() { leaf_noop; }
: src_check() { leaf_noop; }
} src_install() { leaf_noop; }
src_postremove() { }
:
} leaf_is_noop_func() {
src_prepare() { # usage: leaf_is_noop_func <funcname>
: local fn="$1" body
}
src_build() { declare -F "$fn" >/dev/null || return 0
:
} body="$(declare -f "$fn" \
src_check() { | 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() { leaf_strip_files() {
@@ -383,35 +483,187 @@ leaf_trace_package() {
find . -type l | sort > "${_trace_dir}"/LINKS find . -type l | sort > "${_trace_dir}"/LINKS
sed -i "s/.//" "${_trace_dir}"/{DIRS,FILES,LINKS} sed -i "s/.//" "${_trace_dir}"/{DIRS,FILES,LINKS}
install -vm644 "${PKGBUILD_DIR}/${PKG_PREFIX}/${PKG_NAME}.PKGBUILD" "${_trace_dir}/PKGBUILD" install -vm644 "${PKGBUILD_DIR}/${PKG_PREFIX}/${PKG_NAME}.PKGBUILD" "${_trace_dir}/PKGBUILD"
leaf_trace_conffiles "${_trace_dir}"
popd > /dev/null 2>&1 popd > /dev/null 2>&1
} }
leaf_update_package_database() { leaf_trace_conffiles() {
local _item="$(echo "$2" | awk 'BEGIN{FS="/";OFS="\\/"}{print $1,$2}')" # usage: leaf_trace_conffiles <trace_dir>
case $1 in # called with CWD = pkgdir (same as leaf_trace_package)
add) local _trace_dir="$1"
sed -i "/${_item}/d" "${INSTALLED_PACKAGES}" local _out="${_trace_dir}/CONFFILES"
echo "$2" >> ${INSTALLED_PACKAGES} 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
;; ;;
delete) l)
sed -i "/${_item}/d" "${INSTALLED_PACKAGES}" cur_val="$(readlink -- "${path}" 2>/dev/null)" || return 1
[ "${cur_val}" = "${val}" ] && return 0 || return 1
;;
*)
return 1
;; ;;
esac esac
} }
leaf_update_package_database() {
local action=$1 atom=$2 db="${INSTALLED_PACKAGES}"
local tmp="${db}.tmp.$$"
case "$action" in
add)
{ [ -f "$db" ] && grep -Fvx -- "$atom" "$db" || :; printf '%s\n' "$atom"; } >"$tmp" &&
mv -f -- "$tmp" "$db"
;;
delete)
grep -Fvx -- "$atom" "$db" >"$tmp" &&
mv -f -- "$tmp" "$db"
;;
*)
leaf_error "unknown operation leaf_update_package_database ${action}."
;;
esac
}
leaf_update_environment() { leaf_update_environment() {
ldconfig ldconfig
[ ! -r /etc/profile ] || source /etc/profile [ ! -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
leaf_set_title "leaf: ${PKG_PREFIX}/${PKG_NAME} (${phase})"
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() { leaf_prepare_package() {
local url sourcefile index _md5sum local_src pkg_src_root local url sourcefile index _sum local_src
local checksum_algo checksum_arr_name
local decl
leaf_set_title "leaf: ${PKG_PREFIX}/${PKG_NAME} (Fetching)"
distdir="${DIST_DIR}" distdir="${DIST_DIR}"
filedir="${PKGBUILD_DIR}/${PKG_PREFIX}/${pkgname}"
mkdir -pv -- "${distdir}" mkdir -pv -- "${distdir}"
pkg_src_root="${PKGBUILD_DIR}/${PKG_PREFIX}/${PKG_NAME}" # choose checksum type (sha512 -> sha256 -> md5)
if declare -p sha512sums &>/dev/null; then
decl="$(declare -p sha512sums 2>/dev/null)"
[[ "${decl}" == "declare -a "* || "${decl}" == "declare -a"* ]] \
|| leaf_error "sha512sums is defined but is not an array"
checksum_algo="sha512"
checksum_arr_name="sha512sums"
elif declare -p sha256sums &>/dev/null; then
decl="$(declare -p sha256sums 2>/dev/null)"
[[ "${decl}" == "declare -a "* || "${decl}" == "declare -a"* ]] \
|| leaf_error "sha256sums is defined but is not an array"
checksum_algo="sha256"
checksum_arr_name="sha256sums"
elif declare -p md5sums &>/dev/null; then
decl="$(declare -p md5sums 2>/dev/null)"
[[ "${decl}" == "declare -a "* || "${decl}" == "declare -a"* ]] \
|| leaf_error "md5sums is defined but is not an array"
checksum_algo="md5"
checksum_arr_name="md5sums"
else
leaf_error "no checksum list defined: need one of sha512sums/sha256sums/md5sums"
fi
local -n checksums_ref="${checksum_arr_name}"
# enter ${distdir}
pushd "${distdir}" >/dev/null || leaf_error "cannot enter distdir: ${distdir}" pushd "${distdir}" >/dev/null || leaf_error "cannot enter distdir: ${distdir}"
for index in "${!sources[@]}"; do for index in "${!sources[@]}"; do
@@ -424,12 +676,17 @@ leaf_prepare_package() {
if [ -f "${sourcefile}" ]; then if [ -f "${sourcefile}" ]; then
echo "${sourcefile} exists, checking..." echo "${sourcefile} exists, checking..."
_md5sum="$(md5sum -- "${sourcefile}" | awk '{print $1}')" case "${checksum_algo}" in
if [ "${_md5sum}" = "${md5sums[$index]}" ]; then sha512) _sum="$(sha512sum -- "${sourcefile}" | awk '{print $1}')" ;;
echo "md5sum match, skip ${sourcefile}." sha256) _sum="$(sha256sum -- "${sourcefile}" | awk '{print $1}')" ;;
md5) _sum="$(md5sum -- "${sourcefile}" | awk '{print $1}')" ;;
*) leaf_error "internal error: unknown checksum algo: ${checksum_algo}" ;;
esac
if [ "${_sum}" = "${checksums_ref[$index]}" ]; then
echo "${checksum_algo}sum match, skip ${sourcefile}."
continue continue
fi fi
echo "md5sum does not match, refetching..." echo "${checksum_algo}sum does not match, refetching..."
rm -f -- "${sourcefile}" rm -f -- "${sourcefile}"
fi fi
@@ -439,37 +696,66 @@ leaf_prepare_package() {
wget -O "${sourcefile}" -- "${url}" || leaf_error "wget failed: ${url}" wget -O "${sourcefile}" -- "${url}" || leaf_error "wget failed: ${url}"
;; ;;
*) *)
local_src="${pkg_src_root}/${url}" local_src="${filedir}/${url}"
echo "Copying ${sourcefile} from local file ${local_src}..." echo "Copying ${sourcefile} from local file ${local_src}..."
[ -f "${local_src}" ] || leaf_error "local source not found: ${local_src}" [ -f "${local_src}" ] || leaf_error "local source not found: ${local_src}"
cp -f -- "${local_src}" "${sourcefile}" || leaf_error "cp failed: ${local_src}" cp -f -- "${local_src}" "${sourcefile}" || leaf_error "cp failed: ${local_src}"
;; ;;
esac esac
_md5sum="$(md5sum -- "${sourcefile}" | awk '{print $1}')" case "${checksum_algo}" in
if [ "${_md5sum}" != "${md5sums[$index]}" ]; then sha512) _sum="$(sha512sum -- "${sourcefile}" | awk '{print $1}')" ;;
leaf_error "md5sums of ${sourcefile}(${_md5sum}) does not match with ${md5sums[$index]}!" sha256) _sum="$(sha256sum -- "${sourcefile}" | awk '{print $1}')" ;;
md5) _sum="$(md5sum -- "${sourcefile}" | awk '{print $1}')" ;;
*) leaf_error "internal error: unknown checksum algo: ${checksum_algo}" ;;
esac
if [ "${_sum}" != "${checksums_ref[$index]}" ]; then
leaf_error "${checksum_algo}sum of ${sourcefile}(${_sum}) does not match with ${checksums_ref[$index]}!"
fi fi
done done
popd >/dev/null || true popd >/dev/null || true
echo "Sources ready." echo "Sources ready."
src_prepare leaf_phase_must "Preparing" src_prepare "Prepare failed."
} }
leaf_build_package() { leaf_build_package() {
leaf_prepare_package rm -rf -- ${TMPDIR} ${HOME}
src_build mkdir -pv ${TMPDIR}
mkdir -pv ${HOME}
if ! src_check; then leaf_prepare_package
if [ "${FORCE_INSTALL}" == "1" ]; then 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}" echo -e "${RED_COLOR}* Tests failed, but is in force-install mode.${CLEAR_COLOR}"
else else
leaf_error "Tests failed, please check. Aborting installation." leaf_error "Tests failed, please check. Aborting installation."
fi fi
fi fi
src_install ## 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 local _option
for _option in $(echo ${!BUILD_OPTIONS[*]}); do for _option in $(echo ${!BUILD_OPTIONS[*]}); do
if [[ x"${_option}" == x"strip" ]] && [[ x"${BUILD_OPTIONS[$_option]}" == x"1" ]]; then if [[ x"${_option}" == x"strip" ]] && [[ x"${BUILD_OPTIONS[$_option]}" == x"1" ]]; then
@@ -493,9 +779,9 @@ leaf_install_package() {
# trace need to to in the same fakeroot # trace need to to in the same fakeroot
leaf_trace_package "${pkgdir}" leaf_trace_package "${pkgdir}"
# ROOT starts # ROOT starts
src_preinstall leaf_phase_must "Preinstall" src_preinstall "Preinstall failed."
leaf_merge_package "${pkgdir}" leaf_merge_package "${pkgdir}"
src_postinstall leaf_phase_must "Postinstall" src_postinstall "Postinstall failed."
leaf_update_package_database add "${PKG_PREFIX}/${PKG_NAME}" leaf_update_package_database add "${PKG_PREFIX}/${PKG_NAME}"
leaf_update_environment leaf_update_environment
# ROOT ends # ROOT ends
@@ -508,9 +794,9 @@ leaf_dirct-install_package() {
# trace need to to in the same fakeroot # trace need to to in the same fakeroot
leaf_trace_package "${pkgdir}" leaf_trace_package "${pkgdir}"
# ROOT starts # ROOT starts
src_preinstall leaf_phase_must "Preinstall" src_preinstall "Preinstall failed."
leaf_merge_package "${pkgdir}" leaf_merge_package "${pkgdir}"
src_postinstall leaf_phase_must "Postinstall" src_postinstall "Postinstall failed."
leaf_update_package_database add "${PKG_PREFIX}/${PKG_NAME}" leaf_update_package_database add "${PKG_PREFIX}/${PKG_NAME}"
leaf_update_environment leaf_update_environment
# ROOT ends # ROOT ends
@@ -522,39 +808,207 @@ leaf_force-install_package(){
leaf_install_package 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}"
rm -rf "${LEAFHOME}"
rm -rf "${LEAFTMPDIR}"
# =========================================================
# 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() { leaf_remove_package() {
[ -n "$(grep "${PKG_PREFIX}/${PKG_NAME}" ${INSTALLED_PACKAGES})" ] || { [ -n "$(grep "${PKG_PREFIX}/${PKG_NAME}" ${INSTALLED_PACKAGES})" ] || {
leaf_error "Package ${PKG_PREFIX}/${PKG_NAME} is NOT installed" leaf_error "Package ${PKG_PREFIX}/${PKG_NAME} is NOT installed"
} }
src_preremove
leaf_phase_must "Preremove" src_preremove "Preremove failed."
local _file _link _directory _etc_backup_path _relative_path _backup_etc=false local _file _link _directory _etc_backup_path _relative_path _backup_etc=false
_etc_backup_path="/etc/.leaf_backup/${PKGNAME}_$(date +%Y%m%d%H%M%S)" local _trace_dir="${TRACE_DIR}/${PKG_PREFIX}/${PKG_NAME}"
cat "${TRACE_DIR}/${PKG_PREFIX}/${PKG_NAME}/FILES" | while read -r _file; do local _has_conffiles=0
if [[ "${_file}" == /etc/* && -f "${_file}" ]]; then
_backup_etc=true [ -r "${_trace_dir}/CONFFILES" ] && _has_conffiles=1
_relative_path="${_file#/etc/}"
mkdir -pv "${_etc_backup_path}/$(dirname "${_relative_path}")" _etc_backup_path="/etc/.leaf_backup/${PKG_NAME}_$(date +%Y%m%d%H%M%S)"
mv -v "${_file}" "${_etc_backup_path}/${_relative_path}"
else # FILES
rm -fv "${_file}" while read -r _file; do
if leaf_in_skiplist "${_file}"; then
continue
fi fi
done 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 if [[ "${_backup_etc}" == true ]]; then
leaf_record_message "Found files in /etc, backuped to ${_etc_backup_path}." leaf_record_message "Found files in /etc, backuped to ${_etc_backup_path}."
fi fi
cat "${TRACE_DIR}/${PKG_PREFIX}/${PKG_NAME}/LINKS" | while read -r _link; do
[ ! -L "${_link}" ] || rm -fv "${_link}" # LINKS
done while read -r _link; do
cat "${TRACE_DIR}/${PKG_PREFIX}/${PKG_NAME}/DIRS" | while read -r _directory; do if leaf_in_skiplist "${_link}"; then
if [[ -d _directory ]]; 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}" rmdir --ignore-fail-on-non-empty "${_directory}"
fi fi
done done < "${_trace_dir}/DIRS"
src_postremove
leaf_phase_must "Postremove" src_postremove "Postremove failed."
leaf_invoke_hooks remove leaf_invoke_hooks remove
rm -rf "${TRACE_DIR}/${PKG_PREFIX}/${PKG_NAME}" rm -rf "${_trace_dir}"
leaf_update_package_database delete "${PKG_PREFIX}/${PKG_NAME}" leaf_update_package_database delete "${PKG_PREFIX}/${PKG_NAME}"
leaf_update_environment leaf_update_environment
echo -e "${GREEN_COLOR}<<< removed ${PKG_PREFIX}/${PKG_NAME}${CLEAR_COLOR}"
} }
leaf_search_package() { leaf_search_package() {
@@ -577,6 +1031,40 @@ leaf_show_package() {
echo -e "License: ${license[@]}\n" echo -e "License: ${license[@]}\n"
} }
leaf_query() {
local q="$1"
local trace_root="${TRACE_DIR:-/var/lib/leaf/trace}"
if ! leaf_find_remove_pkgbuild "$q"; then
leaf_error "${q} cannot found in the leaf installed database."
fi
local pkgbuild_path="${trace_root}/${PKG_PREFIX}/${PKG_NAME}/PKGBUILD"
[[ -f "$pkgbuild_path" ]] || leaf_error "query: missing PKGBUILD: $pkgbuild_path"
[[ -r "$pkgbuild_path" ]] || leaf_error "query: PKGBUILD not readable: $pkgbuild_path"
(
set -euo pipefail
unset pkgname pkgver
# shellcheck disable=SC1090
source "$pkgbuild_path"
local full="${PKG_PREFIX}/${PKG_NAME}"
if [[ -t 1 && -z "${NO_COLOR-}" ]]; then
local BOLD=$'\e[1m'
printf '%s%s%s %s %s%s%s\n' \
"${BOLD}" "${full}" "${CLEAR_COLOR}" \
"${pkgname}" \
"${GREEN_COLOR}" "${pkgver}" "${CLEAR_COLOR}"
else
printf '%s %s %s\n' "${full}" "${pkgname}" "${pkgver}"
fi
)
}
leaf_pack_package() { leaf_pack_package() {
leaf_build_package leaf_build_package
pushd "${srcdir}" > /dev/null 2>&1 pushd "${srcdir}" > /dev/null 2>&1