Files
leaf/leaf
2025-12-21 15:38:12 -05:00

563 lines
16 KiB
Bash
Executable File

#!/bin/bash
set -e
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 LC_ALL=C
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")
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
leaf_find_pkgbuild "${_item}"
leaf_reset_state
source "${PKGBUILD_DIR}/${PKG_PREFIX}/${PKG_NAME}".PKGBUILD
leaf_parse_options
srcdir="${BUILD_DIR}/${PKG_PREFIX}/${PKG_NAME}"
pkgdir="${srcdir}"/__pkgdir__
if [[ x"$1" != x"dirct-install" ]]; then
rm -rf "${srcdir}" && install -dm755 "${srcdir}" && cd "${srcdir}"
fi
leaf_${1}_package
done
;;
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
if [ -n "${MESSAGE}" ]; then
echo -e "${MESSAGE}"
fi
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() {
if [ -n "${MESSAGE}" ]; then
echo -e "${MESSAGE}"
fi
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_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
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}")
if [[ "${_item}" == /etc/* && -e "${_item}" ]]; then
_item_conflict="$(dirname "${_item}")/._$(basename "${_item}").conflict_${PKG_NAME}_${_time}"
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}."
else
install -D -m ${_mode} -o ${_owner} -g ${_group} ."${_item}" "${_item}"
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 faild
dirct-install install packages directly from previous build
remove remove packages
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_record_message() {
MESSAGE+="\n${PURPLE_COLOR}* Message from ${PKG_PREFIX}/${PKG_NAME}${CLEAR_COLOR}\n"
local _item
for _item in "${@:1}"; do
MESSAGE+="${GREEN_COLOR}* ${_item}${CLEAR_COLOR}\n"
done
}
leaf_remove_libtool_files() {
find "${pkgdir}" -name "*.la" -delete
}
leaf_reset_state() {
options=()
src_preinstall() {
:
}
src_postinstall() {
:
}
src_preremove() {
:
}
src_postremove() {
:
}
src_prepare() {
:
}
src_build() {
:
}
src_check() {
:
}
}
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}
popd > /dev/null 2>&1
}
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_prepare_package() {
local url sourcefile index _md5sum
distdir="${DIST_DIR}"
mkdir -pv "${distdir}"
pushd "${distdir}"
for index in "${!sources[@]}"; do
sourcefile="${sources[$index]}"
url="${urls[$index]}"
if [ -f "${sourcefile}" ]; then
echo "${sourcefile} exits, checking..."
_md5sum=$(md5sum ${sourcefile} | awk '{print $1}')
if [ "${_md5sum}" == "${md5sums[$index]}" ]; then
echo "md5sum match, skip ${sourcefile}."
else
echo "md5sum does not match, redownloading..."
rm -rf "${sourcefile}"
wget -O "${sourcefile}" "${url}"
fi
else
echo "Downloading ${sourcefile}..."
wget -O "${sourcefile}" "${url}"
fi
_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
echo "Sources ready."
src_prepare
}
leaf_build_package() {
leaf_prepare_package
src_build
if ! src_check; 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
src_install
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
src_preinstall
leaf_merge_package "${pkgdir}"
src_postinstall
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
src_preinstall
leaf_merge_package "${pkgdir}"
src_postinstall
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_remove_package() {
[ -n "$(grep "${PKG_PREFIX}/${PKG_NAME}" ${INSTALLED_PACKAGES})" ] || {
leaf_error "Package ${PKG_PREFIX}/${PKG_NAME} is NOT installed"
}
src_preremove
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)"
cat "${TRACE_DIR}/${PKG_PREFIX}/${PKG_NAME}/FILES" | while read -r _file; do
if [[ "${_file}" == /etc/* && -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
done
if [[ "${_backup_etc}" == true ]]; then
leaf_record_message "Found files in /etc, backuped to ${_etc_backup_path}."
fi
cat "${TRACE_DIR}/${PKG_PREFIX}/${PKG_NAME}/LINKS" | while read -r _link; do
[ ! -L "${_link}" ] || rm -fv "${_link}"
done
cat "${TRACE_DIR}/${PKG_PREFIX}/${PKG_NAME}/DIRS" | while read -r _directory; do
rmdir --ignore-fail-on-non-empty "${_directory}"
done
src_postremove
leaf_invoke_hooks remove
rm -rf "${TRACE_DIR}/${PKG_PREFIX}/${PKG_NAME}"
leaf_update_package_database delete "${PKG_PREFIX}/${PKG_NAME}"
leaf_update_environment
}
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"
}
main $@