#!/bin/sh # # scratchpkg # # Copyright (c) 2018 by Emmett1 (emmett1.2miligrams@gmail.com) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # msg() { echo "==> $1" } msg2() { echo " -> $1" } msgerr() { echo "==> ERROR: $1" } msgwarn() { echo "==> WARNING: $1" } updatemdsum() { msg "Generating .checksums..." generatemdsum > .checksums } generatemdsum() { if [ -z "$source" ]; then echo SKIP return 0 fi for src in $source; do if echo $src | grep -qE "::(ftp|http|https)://"; then FILENAME=$SOURCE_DIR/$(echo $src | awk -F '::' '{print $1}') elif echo $src | grep -qE "(ftp|http|https)://"; then FILENAME=$SOURCE_DIR/$(basename $src) else FILENAME=$src fi needupdatechecksum="$needupdatechecksum $FILENAME" done md5sum $needupdatechecksum | sed -e 's| .*/| |' | sort -k 2 } checkmdsum() { TMPCHECKSUM=$WORK_DIR/checksumstmp.$$ ORICHECKSUM=$WORK_DIR/checksumsori.$$ DIFCHECKSUM=$WORK_DIR/checksumsdiff.$$ if [ ! -f .checksums ]; then msg "Generating .checksums..." generatemdsum > .checksums else msg "Checking checksums..." generatemdsum > "$TMPCHECKSUM" sort -k2 .checksums > "$ORICHECKSUM" diff -w -t -U 0 "$ORICHECKSUM" "$TMPCHECKSUM" > "$DIFCHECKSUM" fi [ -s "$DIFCHECKSUM" ] && { mismatch=1 cat "$DIFCHECKSUM" \ | sed '/---/d' \ | sed '/+++/d' \ | sed '/@@/d' \ | sed 's/^-/ -> missing: /' \ | sed 's/^+/ -> new : /' } rm -f "$TMPCHECKSUM" "$DIFCHECKSUM" "$ORICHECKSUM" [ "$mismatch" = 1 ] && abort 1 } download_src() { for FILE in $source; do if echo $FILE | grep -Eq '::(http|https|ftp)://'; then FILENAME=$(echo $FILE | awk -F '::' '{print $1}') SRCURL=$(echo $FILE | awk -F '::' '{print $2}') else FILENAME=$(basename $FILE) SRCURL=$FILE fi [ "$DOWNLOAD_PROG" = "auto" ] && { command -v curl >/dev/null && DOWNLOAD_PROG=curl command -v wget >/dev/null && DOWNLOAD_PROG=wget } case $DOWNLOAD_PROG in curl) DLCMD="curl -C - -L --fail --ftp-pasv --retry 3 --retry-delay 3 -o $SOURCE_DIR/$FILENAME.partial $CURL_OPTS" ;; wget) DLCMD="wget -c --passive-ftp --no-directories --tries=3 --waitretry=3 --output-document=$SOURCE_DIR/$FILENAME.partial $WGET_OPTS" ;; *) msgerr "No download agent found"; abort 1;; esac if [ "$FILENAME" != "$FILE" ]; then if [ ! -f "$SOURCE_DIR/$FILENAME" ] || [ "$REDOWNLOAD_SOURCE" ]; then [ "$REDOWNLOAD_SOURCE" ] && rm -f "$SOURCE_DIR/$FILENAME.partial" if [ -f "$SOURCE_DIR/$FILENAME.partial" ]; then msg "Resuming '$SRCURL'." else msg "Downloading '$SRCURL'." fi $DLCMD $SRCURL if [ $? = 0 ]; then [ "$REDOWNLOAD_SOURCE" ] && rm -f "$SOURCE_DIR/$FILENAME" mv $SOURCE_DIR/$FILENAME.partial $SOURCE_DIR/$FILENAME else msgerr "Failed downloading '$FILENAME'." abort 1 fi fi else if [ ! -f "$FILENAME" ]; then msgerr "Source '$FILENAME' not found." abort 1 fi fi done } prepare_src() { [ "$IGNORE_MDSUM" -o "$CHECK_MDSUM" = 0 ] || checkmdsum SRC=$WORK_DIR/$name/src PKG=$WORK_DIR/$name/pkg umask 022 rm -fr $WORK_DIR/$name mkdir -p $SRC $PKG if [ "$source" ]; then for FILE in $source; do if echo $FILE | grep -Eq '::(http|https|ftp)://'; then FILENAME=$SOURCE_DIR/$(echo $FILE | awk -F '::' '{print $1}') elif echo $FILE | grep -Eq '^(http|https|ftp)://'; then FILENAME=$SOURCE_DIR/$(basename $FILE) else FILENAME=$PWD/$(basename $FILE) fi for NOEXT in $noextract; do if [ "$NOEXT" = "$(basename $FILENAME)" ]; then nxt=1 break fi done if [ "$FILENAME" != "$FILE" ] && [ "$nxt" != 1 ]; then case $FILENAME in *.tar|*.tar.gz|*.tar.Z|*.tgz|*.tar.bz2|*.tbz2|*.tar.xz|*.txz|*.tar.lzma|*.zip|*.rpm) if [ $(command -v bsdtar) ]; then COMMAND="bsdtar -p -o -C $SRC -xf $FILENAME" else COMMAND="tar -p -o -C $SRC -xf $FILENAME" fi MODE="Unpacking" ;; *) COMMAND="cp $FILENAME $SRC" MODE="Preparing" ;; esac msg "$MODE '$(basename $FILENAME)'..." $COMMAND || { msgerr "$MODE '$FILENAME' failed." abort 1 } else MODE="Preparing" msg "$MODE '$(basename $FILENAME)'..." cp "$FILENAME" "$SRC" || { msgerr "$MODE '$FILENAME' failed." abort 1 } fi unset nxt done fi } run_build() { [ "$(id -u)" = 0 ] || { msgerr "You must build package as root, or use fakeroot." abort 1 } msg "Start build '$name-$version-$release'." [ "$MAKE_FLAGS" = 1 ] && export MAKEFLAGS || unset MAKEFLAGS [ "$BUILD_FLAGS" = 1 ] && export CFLAGS CXXFLAGS || unset CFLAGS CXXFLAGS cd $SRC >/dev/null (set -e -x; build 2>&1) if [ $? != 0 ]; then msgerr "Build '$name-$version-$release' failed." abort 1 else msg "Build '$name-$version-$release' success." fi cd - >/dev/null } removeemptydirs() { find . -type d -empty -delete } removelibtool() { find . ! -type d -name "*.la" -delete } strip_files() { if [ "$nostrip" ]; then for i in $nostrip; do xstrip="$xstrip -e $i" done FILTER="grep -v $xstrip" else FILTER="cat" fi find . -type f -printf "%P\n" 2>/dev/null | $FILTER | while read -r binary ; do case "$(file -bi "$binary")" in *application/x-sharedlib*) # Libraries (.so) strip --strip-unneeded "$binary" 2>/dev/null ;; *application/x-pie-executable*) # Libraries (.so) strip --strip-unneeded "$binary" 2>/dev/null ;; *application/x-archive*) # Libraries (.a) strip --strip-debug "$binary" 2>/dev/null ;; *application/x-object*) case "$binary" in *.ko) # Kernel module strip --strip-unneeded "$binary" 2>/dev/null ;; *) continue;; esac;; *application/x-executable*) # Binaries strip --strip-all "$binary" 2>/dev/null ;; *) continue ;; esac done } compressinfomanpages() { find . -type f -path "*/man/man*/*" | while read -r file; do if [ "$file" = "${file%%.gz}" ]; then gzip -9 -f "$file" fi done find . -type l -path "*/man/man*/*" | while read -r file; do FILE="${file%%.gz}.gz" TARGET="$(readlink $file)" TARGET="${TARGET##*/}" TARGET="${TARGET%%.gz}.gz" DIR=$(dirname "$FILE") rm -f $file if [ -e "$DIR/$TARGET" ]; then ln -sf $TARGET $FILE fi done if [ -d usr/share/info ]; then (cd usr/share/info for file in $(find . -type f); do if [ "$file" = "${file%%.gz}" ]; then gzip -9 "$file" fi done ) fi } backupconf() { for FILE in $backup; do if [ ! -f $FILE ]; then msgerr "File '$FILE' not exist!" abort 1 else mv $FILE $FILE.spkgnew fi done } removedocs() { for i in doc gtk-doc info; do rm -fr \ usr/share/$i \ usr/$i \ usr/local/$i \ usr/local/share/$i done } removelocales() { rm -fr \ usr/share/locale \ usr/locale \ usr/local/locale \ usr/local/share/locale \ usr/lib/locale } packaging() { for FILE in $INCLUDEINPKG; do if [ -f $FILE ]; then install -m644 $FILE $PKG/.pkg$FILE addtotar="$addtotar .pkg$FILE" fi done cd $PKG >/dev/null # remove possible conflict junks rm -f usr/share/info/dir usr/info/dir rm -f usr/lib/charset.alias # on musl system find . \( -name perllocal.pod -o -name .packlist \) -delete [ -d usr/share/fonts ] && { find usr/share/fonts \( -name fonts.dir -o -name fonts.scale \) -delete } # remove pseudo directories #rm -fr dev sys proc run tmp [ "$KEEP_EMPTYDIR" = 0 ] && removeemptydirs [ "$KEEP_LIBTOOL" = 0 ] && removelibtool [ "$STRIP_BINARY" = 1 ] && strip_files [ "$ZIP_MAN" = 1 ] && compressinfomanpages [ "$KEEP_DOCS" = 0 ] && removedocs [ "$KEEP_LOCALES" = 0 ] && removelocales if [ "${#backup[@]}" -gt 0 ]; then backupconf fi [ "$FORCE_REBUILD" ] && rm -f "$PACKAGE_DIR/$PKGNAME" case $COMPRESSION_MODE in xz) COMPRESS="-J" ;; gz) COMPRESS="-z" ;; bz2) COMPRESS="-j" ;; esac tar -c $COMPRESS -f $PACKAGE_DIR/$PKGNAME * $addtotar || { rm -f $PACKAGE_DIR/$PKGNAME msgerr "Packaging '$PKGNAME' failed." abort 1 } tar -tvf $PACKAGE_DIR/$PKGNAME | sort -k 6 msg "Successfully created package '$PKGNAME'. ($(ls -lh $PACKAGE_DIR/$PKGNAME | awk '{print $5}'))" cd - >/dev/null [ -f .pkgfiles ] || generate_pkgfiles } generate_pkgfiles() { [ -f "$PACKAGE_DIR/$PKGNAME" ] || { msgerr "Package '$PKGNAME' not found." exit 1 } for i in $INCLUDEINPKG; do excludefile="$excludefile --exclude=.pkg$i" done msg "Generating .pkgfiles..." tar -tvf "$PACKAGE_DIR/$PKGNAME" $excludefile \ | awk '{$3=$4=$5=""; print $0}' \ | sed "s,lib/modules/$(uname -r),lib/modules/,g" \ | sort -k 3 > .pkgfiles } cleanup() { [ -e "$PACKAGE_DIR/$PKGNAME" ] && { msg "Removing $PACKAGE_DIR/$PKGNAME" rm -f "$PACKAGE_DIR/$PKGNAME" } for src in $source; do if echo $src | grep -qE "::(ftp|http|https)://"; then FILENAME=$SOURCE_DIR/$(echo $src | awk -F '::' '{print $1}') elif echo $src | grep -qE "^(ftp|http|https)://"; then FILENAME=$SOURCE_DIR/$(basename $src) else FILENAME=$src fi if [ -e "$FILENAME" ] && [ "$FILENAME" != "$src" ]; then msg "Removing $FILENAME" rm -f "$FILENAME" fi done } check_buildscript() { # check the required field in buildscript if [ -z "$name" ]; then msgerr "'name' is empty!" exit 1 elif [ "$(basename $(pwd))" != "$name" ]; then msgerr "Port name and Directory name is different!" exit 1 elif [ -z "$version" ]; then msgerr "'version' is empty!" exit 1 elif [ -z "$release" ]; then msgerr "'release' is empty!" exit 1 elif [ ! "$(command -v build)" ]; then msgerr "'build' function not exist!" exit 1 elif echo "$version" | grep -q '-'; then msgerr "'version' should not contain '-'." exit 1 elif echo "$release" | grep -q '-'; then msgerr "'release' should not contain '-'." exit 1 elif [ -z "$description" ]; then msgerr "'description' cant empty." exit 1 fi } set_options() { for OPT in $OPTIONS $options; do case $OPT in libtool) KEEP_LIBTOOL=1 ;; !libtool) KEEP_LIBTOOL=0 ;; emptydirs) KEEP_EMPTYDIR=1;; !emptydirs) KEEP_EMPTYDIR=0;; strip) STRIP_BINARY=1 ;; !strip) STRIP_BINARY=0 ;; zipman) ZIP_MAN=1 ;; !zipman) ZIP_MAN=0 ;; buildflags) BUILD_FLAGS=1 ;; !buildflags) BUILD_FLAGS=0 ;; makeflags) MAKE_FLAGS=1 ;; !makeflags) MAKE_FLAGS=0 ;; checksum) CHECK_MDSUM=1 ;; !checksum) CHECK_MDSUM=0 ;; docs) KEEP_DOCS=1 ;; !docs) KEEP_DOCS=0 ;; locales) KEEP_LOCALES=1 ;; !locales) KEEP_LOCALES=0 ;; esac done } checkdir() { for DIR in "$@"; do if [ ! -d $DIR ]; then msgerr "Directory '$DIR' not exist." exit 1 elif [ ! -w $DIR ]; then msgerr "Directory '$DIR' not writable." exit 1 elif [ ! -x $DIR ] || [ ! -r $1 ]; then msgerr "Directory '$DIR' not readable." exit 1 fi done } clearworkdir() { if [ ! "$KEEP_WORK" ]; then if [ "$name" ]; then rm -fr $WORK_DIR/$name fi fi } interrupted() { echo abort 100 } abort() { rm -f "$LOCK_FILE" clearworkdir exit $1 } parse_opts() { while [ "$1" ]; do case $1 in -i | --install) INSTALL_PKG=yes ;; -u | --upgrade) UPGRADE_PKG=yes; OPTS="$OPTS $1" ;; -r | --reinstall) REINSTALL_PKG=yes; OPTS="$OPTS $1" ;; -c | --ignore-conflict) OPTS="$OPTS $1" ;; -v | --verbose) OPTS="$OPTS $1" ;; -f | --force-rebuild) FORCE_REBUILD=yes ;; -m | --skip-mdsum) IGNORE_MDSUM=yes ;; -g | --genmdsum) UPDATE_MDSUM=yes ;; -o | --download) DOWNLOAD_ONLY=yes ;; -x | --extract) EXTRACT_ONLY=yes ;; -w | --keep-work) KEEP_WORK=yes ;; -p | --pkgfiles) GENPKGFILES=yes ;; -h | --help) SHOWHELP=yes ;; --clean) CLEANUP=yes ;; --root=*) OPTS="$OPTS $1" ;; --no-preinstall) OPTS="$OPTS $1" ;; --no-postinstall) OPTS="$OPTS $1" ;; --no-preupgrade) OPTS="$OPTS $1" ;; --no-postupgrade) OPTS="$OPTS $1" ;; --no-backup) OPTS="$OPTS $1" ;; --redownload) REDOWNLOAD_SOURCE=yes ;; --config=*) PKGBUILD_CONF="${1#*=}" ;; --srcdir=*) CUSTOM_SOURCE_DIR="${1#*=}" ;; --pkgdir=*) CUSTOM_PACKAGE_DIR="${1#*=}" ;; --workdir=*) CUSTOM_WORK_DIR="${1#*=}" ;; *) msg "Invalid $(basename $0) option! ($1)"; exit 1 ;; esac shift done } help() { cat << EOF Usage: $(basename $0) [ ] Options: -i, --install install package into system -u, --upgrade upgrade package -r, --reinstall reinstall package -c, --ignore-conflict ignore conflict when installing package -v, --verbose verbose install process -f, --force-rebuild rebuild package -m, --skip-mdsum skip md5sum checking -g, --genmdsum generate md5sum -o, --download download only source file -x, --extract extract only source file -p, --pkgfiles generate list files in package -w, --keep-work keep working directory -h, --help show this help message --clean remove downloaded sources and prebuilt packages --config= use custom config file --srcdir= override directory path for sources --pkgdir= override directory path for compiled package --workdir= override directory path for working dir --no-preinstall skip preinstall script before install package --no-postinstall skip postinstall script after install package --no-preupgrade skip preupgrade script before upgrade package --no-postupgrade skip postupgrade script after upgrade package --no-backup skip backup configuration file when upgrading package --redownload re-download source file EOF } extract_opts() { while [ "$1" ]; do case $1 in --*) opts="$opts $1";; -*) char=${#1}; count=1 while [ "$count" != "$char" ]; do count=$((count+1)) opts="$opts -$(echo $1 | cut -c $count)" done;; *) opts="$opts $1" esac shift done echo $opts } main() { parse_opts $(extract_opts "$@") if [ -f "$PKGBUILD_CONF" ]; then . "$PKGBUILD_CONF" else msgerr "Config file not found ('$PKGBUILD_CONF')" exit 1 fi [ "$CUSTOM_SOURCE_DIR" ] && SOURCE_DIR="$CUSTOM_SOURCE_DIR" [ "$CUSTOM_PACKAGE_DIR" ] && PACKAGE_DIR="$CUSTOM_PACKAGE_DIR" [ "$CUSTOM_WORK_DIR" ] && WORK_DIR="$CUSTOM_WORK_DIR" checkdir "$SOURCE_DIR" "$PACKAGE_DIR" "$WORK_DIR" # show usage [ "$SHOWHELP" ] && { help exit 0 } if [ -f "$PKGBUILD_BSCRIPT" ]; then description=$(grep "^# description[[:blank:]]*:" $PKGBUILD_BSCRIPT | sed 's/^# description[[:blank:]]*:[[:blank:]]*//') . ./$PKGBUILD_BSCRIPT else msgerr "'$PKGBUILD_BSCRIPT' file not found." exit 1 fi check_buildscript set_options case $COMPRESSION_MODE in gz|bz2|xz) PKGNAME="$name-$version-$release.spkg.tar.$COMPRESSION_MODE" ;; *) msgerr "Invalid compression mode. ($COMPRESSION_MODE)"; exit 1 ;; esac # generate .pkgfiles [ "$GENPKGFILES" ] && { generate_pkgfiles exit 0 } # download source only [ "$DOWNLOAD_ONLY" ] && { download_src exit 0 } # extract source only [ "$EXTRACT_ONLY" ] && { download_src prepare_src exit 0 } # update md5sum [ "$UPDATE_MDSUM" ] && { download_src updatemdsum exit 0 } # remove source and package [ "$CLEANUP" ] && { cleanup exit 0 } LOCK_FILE="/tmp/pkgbuild.$name.lock" # check for lock file [ -f "$LOCK_FILE" ] && { msgerr "Cant build same package simultaneously." msgerr "remove '$LOCK_FILE' if no build process for '$name'." exit 1 } # create lock file touch "$LOCK_FILE" 2>/dev/null || { msgerr "Cant create lock file in '$LOCK_FILE'." exit 1 } # build package if [ -f "$PACKAGE_DIR/$PKGNAME" ] && [ ! "$FORCE_REBUILD" ]; then if [ ! "$INSTALL_PKG" ] && [ ! "$REINSTALL_PKG" ] && [ ! "$UPGRADE_PKG" ]; then echo "Package '$PKGNAME' is up-to-date." abort 0 fi else msg "Building '$name-$version-$release'..." download_src prepare_src run_build packaging clearworkdir fi # install package if [ "$INSTALL_PKG" ] || [ "$REINSTALL_PKG" ] || [ "$UPGRADE_PKG" ]; then pkgadd $PACKAGE_DIR/$PKGNAME $INST_OPT $OPTS || abort 1 fi abort 0 } trap "interrupted" 1 2 3 15 export LC_ALL=C PKGBUILD_CONF="/etc/scratchpkg.conf" PKGBUILD_BSCRIPT="spkgbuild" SOURCE_DIR="/var/cache/scratchpkg/sources" PACKAGE_DIR="/var/cache/scratchpkg/packages" WORK_DIR="/var/cache/scratchpkg/work" DOWNLOAD_PROG="auto" COMPRESSION_MODE="xz" OPTIONS="!libtool emptydirs strip zipman buildflags makeflags checksum !docs !locales" INCLUDEINPKG="install readme mkdirs" main "$@"