#!/bin/sh # ______ ______ __ __ ______ # /\ __ \ /\ == \/\ \/ / /\ ___\ # \ \ __ \\ \ _-/\ \ _"-.\ \ \__ \ # \ \_\ \_\\ \_\ \ \_\ \_\\ \_____\ # \/_/\/_/ \/_/ \/_/\/_/ \/_____/ # # APKG - Alice Package Manager (C) 2023-2025 Emmett1 # fetch_src() { [ "$source" ] || return 0 for i in $source; do i=${i%::noextract} case $i in *::*) sn=${i%::*};; *) sn=${i##*/};; esac case ${i#*::} in http://*|https://*|ftp://*) if [ ! -f $APKG_SOURCE_DIR/$sn ]; then [ -f $APKG_SOURCE_DIR/$sn.tmp ] && resume="-C -" msg "fetching ${i#*::}" curl $resume --fail --retry 3 --retry-delay 3 -L -o $APKG_SOURCE_DIR/$sn.tmp ${i#*::} || { msg "Failed downloading source '$sn'." exit 1 } mv $APKG_SOURCE_DIR/$sn.tmp $APKG_SOURCE_DIR/$sn fi;; esac done } extract_src() { prepare_workdir [ "$source" ] || return 0 checksource for i in $source; do case $i in */*) S=$APKG_SOURCE_DIR;; *) S=$HERE;; esac case ${i%::noextract} in *::*) sn=${i%%::*};; *) sn=${i##*/}; sn=${sn%::noextract};; esac case $i in *::noextract) i=${i%::noextract} cp $S/$sn $SRC || { msg "Failed copy '$S/$sn' to '$SRC'." ret 1 };; *.tar.bz2|*.tar.gz|*.tar.lz|*.tar.lzma|*.tar.lzo|*.tar.xz|*.tar.Z|*.tb2|*.tbz|*.tbz2|*.tz2|*.taz|*.tgz|*.tlz|*.txz|*.tZ|*.taZ) msg "Unpacking '$S/$sn'..." tar -p -o -C $SRC -xf $S/$sn || { msg "Failed unpacking source '${i##*/}'." ret 1 };; *) i=${i%::noextract} cp $S/$sn $SRC || { msg "Failed copy '$S/$sn' to '$SRC'." ret 1 };; esac done } prepare_workdir() { export SRC=$APKG_WORK_DIR/apkg-src-${PWD##*/} export PKG=$APKG_WORK_DIR/apkg-pkg-${PWD##*/} remove_workdir mkdir -p "$PKG" "$SRC" } remove_workdir() { rm -rf "$PKG" "$SRC" } ret() { [ "$APKG_KEEP_WORKDIR" ] || remove_workdir exit $1 } detect_buildtype() { if [ -f meson.build ]; then build_type=meson_build elif [ -f configure ]; then build_type=configure_build elif [ -f CMakeLists.txt ]; then build_type=cmake_build elif [ -f setup.py ]; then build_type=python_build elif [ -f Makefile.PL ]; then build_type=perlmodule_build elif [ -f Makefile ] || [ -f makefile ] || [ -f GNUmakefile ]; then build_type=makefile_build else msg "failed to detect buildtype" ret 1 fi } _makefile_build() { make \ PREFIX=/usr \ prefix=/usr \ SYSCONFDIR=/etc \ sysconfdir=/etc \ MANDIR=/usr/share/man \ mandir=/usr/share/man \ LIBDIR=/usr/lib \ PKGCONFIGDIR=/usr/lib/pkgconfig make \ PREFIX=/usr \ prefix=/usr \ SYSCONFDIR=/etc \ sysconfdir=/etc \ MANDIR=/usr/share/man \ mandir=/usr/share/man \ LIBDIR=/usr/lib \ PKGCONFIGDIR=/usr/lib/pkgconfig \ DESTDIR=$PKG install } _perlmodule_build() { perl Makefile.PL make make DESTDIR=$PKG install } _cmake_build() { mkdir -p cmakebuild cd cmakebuild cmake \ -DCMAKE_INSTALL_PREFIX=/usr \ -DCMAKE_INSTALL_SYSCONFDIR=/etc \ -DCMAKE_INSTALL_LIBDIR=lib \ -DCMAKE_BUILD_TYPE=Release \ -DFETCHCONTENT_FULLY_DISCONNECTED=ON \ -DCMAKE_C_FLAGS_RELEASE="$CFLAGS" \ -DCMAKE_CXX_FLAGS_RELEASE="$CXXFLAGS" \ $build_opt \ -B build .. if [ -f build.ninja ]; then ninja DESTDIR=$PKG ninja install else cmake --build build DESTDIR=$PKG cmake --install build fi } _python_build() { python3 setup.py build python3 setup.py install --prefix=/usr --root=$PKG --optimize=1 $build_opt } _configure_build() { ./configure \ --prefix=/usr \ --sysconfdir=/etc \ --localstatedir=/var \ --libdir=/usr/lib \ --infodir=/usr/share/info \ --mandir=/usr/share/man \ $build_opt make make DESTDIR=$PKG install } _meson_build() { meson setup \ -Dprefix=/usr \ -Dlibdir=/usr/lib \ -Dincludedir=/usr/include \ -Ddatadir=/usr/share \ -Dmandir=/usr/share/man \ -Dinfodir=/usr/share/info \ -Dlocaledir=/usr/share/locale \ -Dsysconfdir=/etc \ -Dlocalstatedir=/var \ -Dsharedstatedir=/var/lib \ -Dbuildtype=plain \ -Dauto_features=auto \ -Dwrap_mode=nodownload \ -Db_lto=true \ -Db_pie=true \ $build_opt \ build ninja -C build DESTDIR=$PKG meson install -C build } apply_patch() { [ "$skip_patch" ] && { unset skip_patch # allowing manual patch in build() using apply_patch return 0 } if [ "$source" ]; then for p in $source; do case ${p%::noextract} in *::*) pn=${p%::*};; *) pn=${p##*/};; esac case $pn in *.patch|*.diff) msg "applying patch '$SRC/$pn'." patch ${patch_opt:--p1} -i $SRC/$pn || ret 1;; esac done fi } msg() { # Don't bother if we're not in a terminal if [ -t 1 ]; then ttysize=$(stty size 2>/dev/null | awk '{print $2}' || echo "80") fi printf "%s\n" "[${name:-...}] $@" | fold -sw ${ttysize:-80} } buildstatus() { [ "$?" = 0 ] || { msg "build package '$name-$version-$release' failed on '$1'." ret 1 } } build_src() { cd $SRC if [ "$build_dir" ]; then if [ ! -d "$build_dir" ]; then msg "Source directory '$build_dir' not found." ret 1 fi elif [ -d $name-$version ]; then build_dir=$name-$version elif [ -d "$(ls -1 --group-directories-first | head -n1)" ]; then build_dir=$(ls -1 --group-directories-first | head -n1) fi # cd into extracted source directory if [ "$build_dir" ]; then cd $build_dir fi apply_patch export CARGO_HOME=${CARGO_HOME:-$SRC/.cargo} export GOCACHE=${GOCACHE:-$SRC/.go} export DESTDIR=$PKG export DEST_DIR=$PKG # p7zip export INSTALLROOT=$PKG # syslinux export install_root=$PKG # glibc export INSTALL_ROOT=$PKG # qt5 if [ ! "$source" ]; then # dummy pkg mkdir -p $PKG/usr elif [ "$(command -v build)" ]; then (set -e -x; build) buildstatus build else if [ "$(command -v prebuild)" ]; then (set -e -x; prebuild) buildstatus prebuild fi if [ ! "${build_type}" ]; then detect_buildtype fi (set -e -x; _${build_type}) buildstatus ${build_type} if [ "$(command -v postbuild)" ]; then (set -e -x; postbuild) buildstatus postbuild fi fi msg "build $name-$version-$release success" # compress man pages if [ -d $PKG/usr/share/man ]; then # only keep man[1-8] man pages for m in $PKG/usr/share/man/*; do case ${m##*/} in man[1-8]) continue;; esac rm -rf $m done for i in $(find $PKG/usr/share/man -type f); do [ $i = ${i%%.gz} ] || continue gzip -9 $i done for i in $(find $PKG/usr/share/man -type l) ; do ln -s $(readlink $i).gz ${i%.gz}.gz rm $i done fi # compress info pages if [ -d $PKG/usr/share/info ]; then rm -f $PKG/usr/share/info/dir find $PKG/usr/share/info -type f -exec gzip -9 {} \; fi # possible conflicts find $PKG -name "fonts.dir" -exec rm {} \; find $PKG -name "fonts.scale" -exec rm {} \; find $PKG -name "perllocal.pod" -exec rm {} \; find $PKG -name "charset.alias" -exec rm {} \; if [ ! "$keep_static" ]; then find $PKG -name "*.a" -exec rm {} \; fi if [ ! "$keep_libtool" ]; then find $PKG -name "*.la" -exec rm {} \; fi if [ ! "$keep_locale" ]; then rm -rf $PKG/usr/share/locale rm -rf $PKG/usr/lib/locale fi if [ ! "$keep_doc" ]; then rm -rf $PKG/usr/share/doc rm -rf $PKG/usr/doc fi # strip binaries and libraries if [ ! "$no_strip" ]; then find $PKG | xargs file | grep "executable" | grep ELF | cut -f 1 -d : | xargs ${CROSS_COMPILE}strip --strip-all 2>/dev/null find $PKG | xargs file | grep "shared object" | grep ELF | cut -f 1 -d : | xargs ${CROSS_COMPILE}strip --strip-unneeded 2>/dev/null find $PKG | xargs file | grep "current ar archive" | cut -f 1 -d : | xargs ${CROSS_COMPILE}strip --strip-debug 2>/dev/null fi # usrmerge if [ "$APKG_BINMERGE" ]; then for b in bin sbin usr/sbin; do if [ -d $PKG/$b ]; then mkdir -p $PKG/usr/bin mv $PKG/$b/* $PKG/usr/bin rm -rf $PKG/$b fi done fi # runit service for s in $sv; do [ -f $SRC/$s ] || ret 1 case $s in run) install -Dm755 $SRC/$s $PKG/etc/sv/$name/run ln -s ../../../run/runit/supervise.$name $PKG/etc/sv/$name/supervise;; finish) install -Dm755 $SRC/$s $PKG/etc/sv/$name/finish;; *.run) install -Dm755 $SRC/$s $PKG/etc/sv/${s%.*}/run ln -s ../../../run/runit/supervise.${s%.*} $PKG/etc/sv/${s%.*}/supervise;; *.finish) install -Dm755 $SRC/$s $PKG/etc/sv/${s%.*}/finish;; *.*) install -Dm644 $SRC/$s $PKG/etc/sv/${s%%.*}/${s#*.};; *) install -Dm644 $SRC/$s $PKG/etc/sv/$name/$s;; esac done if [ ! "$(ls -1 $PKG)" ]; then msg "\$PKG is empty" ret 1 fi # temporary: for backward compatibility if [ "$(grep '#MARKER' /usr/bin/spm)" ]; then cd $PKG spm -b $packagefile || { msg "Failed packaging $packagefile" ret 1 } else cd $APKG_PACKAGE_DIR spm -b $PKG && { mv package.spm $packagefile } || { msg "Failed packaging $packagefile" ret 1 } fi if [ ! -f "$HERE"/.files ] || [ "$HERE"/abuild -nt "$HERE"/.files ]; then pkg_updatefiles fi cd $HERE remove_workdir } pkg_updatefiles() { if [ ! -f "$packagefile" ]; then msg "Package '$packagefile' not found." ret 1 fi msg "filelist updated." tar -tvf $packagefile | awk '{$3=$4=$5=""; print $0}' | sort -k 3 > "$HERE"/.files } pkg_path() { for p in $@; do for r in $APKG_REPO; do [ -f $r/$p/abuild ] && { printf "%s\n" $r/$p break } done done } die() { [ "$1" ] && printf "%s\n" "error: $1" ret 1 } checkdep() { [ "$(pkg_path $1)" ] || return # track processed pkg to avoid cycle deps process="$process $1" for ii in $(pkg_depends $1); do # if already installed, skip if [ "$skip_installed" ]; then [ -s $SPM_PKGDB/$ii ] && continue fi # if deps already in process list, skip, cycle deps detected printf "%s\n" $process | tr ' ' '\n' | grep -qx $ii && continue # skip if itself in depends list [ "$ii" = "$1" ] && continue # skip if pkg already in deps list printf "%s\n" $DEPS | tr ' ' '\n' | grep -x $ii && continue # check deps checkdep $ii done # will go here if no deps anymore to check, add it to list deps DEPS="$DEPS $1" } pkg_deplist() { for i in $@; do # if already have in list deps, dont check again if [ ! $(printf "%s\n" $DEPS | tr ' ' '\n' | grep -x $i) ]; then checkdep $i fi done printf "%s\n" $DEPS | tr ' ' '\n' } pkg_fsearch() { for r in $APKG_REPO; do rr="$rr $r/*/.files" done grep $1 $rr 2>/dev/null | tr ':' ' ' | awk '{print $1,$4}' | sed 's,/.files,,g' } pkg_dependents() { for r in $APKG_REPO; do rr="$rr $r/*/depends" done grep -x $1 $rr 2>/dev/null | rev | cut -d / -f 2 | rev } pkg_depends() { _path=$(pkg_path $1) || return 1 [ -s $_path/depends ] || return solve_alias $(grep -Ev ^'(#|$)' $_path/depends | awk '{print $1}') } solve_alias() { [ "$APKG_ALIAS" ] || { printf "%s\n" $@ return } while [ "$1" ]; do d=$(printf "%s\n" $APKG_ALIAS | tr ' ' '\n' | grep ^$1:* | head -n1 | awk -F : '{print $2}') printf "%s\n" ${d:-$1} shift done } pkg_outdate() { verbose=1 pkg_allinstalled | while read -r n v; do if [ "$mask" ]; then [ "$APKG_MASK" ] && { printf "%s\n" "$APKG_MASK" | tr ' ' '\n' | grep -xq $n && continue } fi [ "$(pkg_path $n)" ] || continue nv="$(grep ^version= $(pkg_path $n)/abuild | tail -n1 | awk -F = '{print $2}')-$(grep ^release= $(pkg_path $n)/abuild | awk -F = '{print $2}')" [ "$nv" ] || continue [ "$v" = "$nv" ] || printf "%s\n" "$n $v -> $nv" done } pkg_remove() { needroot for i in $@; do SPM_ROOT=$APKG_ROOT spm -r $i done } pkg_sysup() { needroot msg "Checking for outdated packages..." od=$(mask=1 pkg_outdate | awk '{print $1}') if [ ! "$od" ]; then msg "No outdated packages." exit 0 else msg "Solving dependencies..." for p in $(pkg_deplist $od); do [ -s $SPM_PKGDB/$p ] || ni="$ni $p" printf "%s\n" $od | tr ' ' '\n' | grep -qx $p && pu="$pu $p" done if [ "$ni" ]; then newpkg=$(printf "%s\n" $ni | tr ' ' '\n' | wc -l) msg "Installing $newpkg new package(s): $ni" fi totalpkg=$(printf "%s\n" $pu | tr ' ' '\n' | wc -l) msg "Upgrading $totalpkg package(s): $pu" prompt_user if [ "$ni" ]; then $APKG -i $ni #|| die fi $APKG -u $pu #|| die fi } pkg_search() { if [ ! "$APKG_REPO" ]; then msg "No repo configured." exit 1 fi if [ "$pkg" ]; then for p in $pkg; do grep="$grep -e $p" done grep="grep $grep" else grep=cat fi [ "$verbose" ] && { find $APKG_REPO -type f -name abuild 2>/dev/null | sed 's|/abuild||' | rev | awk -F / '{print $1}' | rev | $grep | while read line; do printf "$line "; awk -F'=' '/^version/ {print $2}' $($APKG -p $line)/abuild ; done } || { find $APKG_REPO -type f -name abuild 2>/dev/null | sed 's|/abuild||' | rev | awk -F / '{print $1}' | rev | $grep } } pkg_depinstalll() { needroot for p in $@; do if [ -s $SPM_PKGDB/$p ]; then msg "Package '$p' is installed" continue fi if [ ! "$(pkg_path $p)" ]; then msg "Package '$p' not found" continue fi pkg="$pkg $p" done [ "$pkg" ] || exit 0 set -- $pkg msg "Solving dependencies..." for p in $(skip_installed=1 pkg_deplist $@); do [ -s $SPM_PKGDB/$p ] && continue installthis="$installthis $p" done if [ ! "$installthis" ]; then msg "Nothing to install. Exiting..." exit 0 fi totalpkg=$(printf "%s\n" $installthis | tr ' ' '\n' | wc -l) msg "Installing $totalpkg package(s): $installthis" prompt_user $APKG -i $installthis || die } _trigger_fccache() { command -v fc-cache >/dev/null || return 0 spm -l $1 | grep -q ^usr/share/fonts/$ && { msg "Updating fontconfig cache..." fc-cache -sf } } _trigger_gdkpixbufcache() { command -v gdk-pixbuf-query-loaders >/dev/null || return 0 spm -l $1 | grep -q ^usr/lib/gdk-pixbuf-2.0/2.10.0/loaders/$ && { msg "Probing GDK-Pixbuf loader modules..." gdk-pixbuf-query-loaders --update-cache } } _trigger_giomodules() { command -v gio-querymodules >/dev/null || return 0 spm -l $1 | grep -q ^usr/lib/gio/modules/$ && { msg "Updating GIO module cache..." gio-querymodules /usr/lib/gio/modules } } _trigger_glibschema() { command -v glib-compile-schemas >/dev/null || return 0 spm -l $1 | grep -q ^usr/share/glib-2.0/schemas/$ && { msg "Compiling GSettings XML schema files..." glib-compile-schemas /usr/share/glib-2.0/schemas } } _trigger_gtk2immodules() { command -v gtk-query-immodules-2.0 >/dev/null || return 0 spm -l $1 | grep -q ^usr/lib/gtk-2.0/2.10.0/immodules/$ && { msg "Probing GTK2 input method modules..." gtk-query-immodules-2.0 --update-cache } } _trigger_gtk3immodules() { command -v gtk-query-immodules-3.0 >/dev/null || return 0 spm -l $1 | grep -q ^usr/lib/gtk-3.0/3.0.0/immodules/$ && { msg "Probing GTK3 input method modules..." gtk-query-immodules-3.0 --update-cache } } _trigger_iconcache() { command -v gtk-update-icon-cache >/dev/null || return 0 spm -l $1 | grep -q ^usr/share/icons/$ && { msg "Updating icon theme caches..." for dir in /usr/share/icons/* ; do if [ -e $dir/index.theme ]; then gtk-update-icon-cache -q $dir 2>/dev/null else rm -f $dir/icon-theme.cache rmdir --ignore-fail-on-non-empty $dir fi done } } _trigger_udevdb() { command -v udevadm >/dev/null || return 0 spm -l $1 | grep -q ^etc/udev/hwdb.d/$ && { msg "Updating hardware database..." udevadm hwdb --update } } _trigger_fontcache() { command -v mkfontdir >/dev/null || return 0 command -v mkfontscale >/dev/null || return 0 spm -l $1 | grep -q ^usr/share/fonts/$ && { msg "Updating X fontdir indices..." for dir in $(find /usr/share/fonts -maxdepth 1 -type d \( ! -path /usr/share/fonts \)); do rm -f $dir/fonts.scale $dir/fonts.dir $dir/.uuid rmdir --ignore-fail-on-non-empty $dir [ -d "$dir" ] || continue mkfontdir $dir mkfontscale $dir done } } _trigger_desktopdb() { command -v update-desktop-database >/dev/null || return 0 spm -l $1 | grep -q ^usr/share/applications/$ && { msg "Updating desktop file MIME type cache..." update-desktop-database --quiet } } _trigger_mimedb() { command -v update-mime-database >/dev/null || return 0 spm -l $1 | grep -q ^usr/share/mime/$ && { msg "Updating the MIME type database..." update-mime-database /usr/share/mime } } pkg_trigger() { needroot [ "$@" ] || set -- $($APKG -a) for t in mimedb desktopdb fontcache fccache udevdb iconcache gtk3immodules gtk2immodules glibschema giomodules gdkpixbufcache; do for p in $@; do _trigger_$t $p && break done done } pkg_allinstalled() { if [ "$pkg" ]; then for p in $pkg; do grep="$grep -e $p" done grep="grep $grep" else grep=cat fi [ "$verbose" ] && { ls -1 $SPM_PKGDB | $grep | while read line; do printf "$line "; head -n1 $SPM_PKGDB/$line ; done } || { ls -1 $SPM_PKGDB | $grep } } checksum_src() { if [ ! -s "$checksum_file" ]; then gencsum else b3sumcheck || return 0 printcsum > ${checksum_file}.tmp diff -U 0 ${checksum_file} ${checksum_file}.tmp | \ sed '/---/d;/+++/d;/@@/d' | \ sed 's/^+/ new : /' | \ sed 's/^-/ missing: /' > ${checksum_file}.diff [ -s "${checksum_file}.diff" ] && { cat ${checksum_file}.diff msg "Checksum failed." failed=1 } rm -f ${checksum_file}.tmp ${checksum_file}.diff [ "$failed" ] && ret 1 fi } printsource() { # print out source full path for printsource in $source; do printsource=${printsource%::noextract} case $printsource in *::*) sourcename=${printsource%::*};; *) sourcename=${printsource##*/};; esac case ${printsource#*::} in */*) sourcenamefullpath=${APKG_SOURCE_DIR}/$sourcename;; *) sourcenamefullpath=${HERE}/$sourcename;; esac printf "%s\n" $sourcenamefullpath done } checksource() { # check for source existence, exit non-zero if missing for checksource in $(printsource); do [ -f "$checksource" ] || { msg "Source file '$checksource' not found." failed=1 } done [ "$failed" ] && ret 1 } b3sumcheck() { command -v b3sum >/dev/null || { msg "'b3sum' not installed."; return 1; } } gencsum() { checksource b3sumcheck || return 0 rm -f .checksum printcsum > .checksum msg "Checksum file updated." } printcsum() { for printcsum in $(printsource); do [ -f "$printcsum" ] || continue b3sum $printcsum | sed "s,$APKG_SOURCE_DIR/,,;s,$HERE/,," done | sort -k 2 } prompt_user() { if [ ! "$APKG_NOPROMPT" ]; then msg "Press ENTER to continue operation." msg "Press Ctrl + C to abort." read -r null fi } runscript() { if [ -x ./${1}install ]; then msg "Running ${1}install script..." if [ "$APKG_ROOT" ]; then cat ./${1}install > "$APKG_ROOT"/.runscript chmod +x "$APKG_ROOT"/.runscript chroot "$APKG_ROOT" /.runscript rm -f "$APKG_ROOT"/.runscript else ./${1}install fi fi } needroot() { [ $(id -u) = 0 ] || { msg 'This operation need root access' exit 1 } } parsesubopt() { # loop opts until found '-*' shift; while [ "$1" ]; do case $1 in -*) break;; *) printf "%s\n" "$1";; esac shift done } updateopts() { while [ "$1" ]; do case $1 in -*) newopt="$newopt $1";; esac shift done printf "%s\n" "$newopt" } parseopts() { while [ "$1" ]; do case $1 in -I) pkg_depinstalll "$(parsesubopt "$@")"; exit 0;; -D) pkg_deplist "$(parsesubopt "$@")"; exit 0;; -r) pkg_remove "$(parsesubopt "$@")"; exit 0;; -p) pkg_path "$(parsesubopt "$@")"; exit 0;; -t) pkg_trigger "$(parsesubopt "$@")"; exit 0;; -U) pkg_sysup; exit 0;; -d) pkg_depends "$2"; exit 0;; -s) search=1;; -S) pkg_fsearch "$2"; exit 0;; -j) pkg_dependents "$2"; exit 0;; -a) allinstalled=1;; -h) apkg_help; exit 0;; -l) pkg_outdate; exit 0;; -g) updatecsum=1;; -k) updatefiles=1;; -f) forcerebuild=1;; -i) install=1;; -u) upgrade=1;; -o) downloadonly=1;; -v) verbose=1;; -*) msg "invalid option '$1'"; exit 1;; *) pkg="$pkg $1";; esac shift done } apkg_help() { cat << EOF usage: ${0##*/}