#!/bin/sh # script to build apk packages (light version og makepkg) # Copyright (c) 2008 Natanael Copa # # Distributed under GPL-2 # # Depends on: busybox utilities, fakeroot, # abuild_ver=1.10 startdir="$PWD" srcdir=${srcdir:-"$startdir/src"} pkgdir=${pkgdir:-"$startdir/pkg"} pkgrel=0 repo=${startdir%/*} repo=${repo##*/} # defaults SRCDEST=${SRCDEST:-$startdir} PKGDEST=${PKGDEST:-$startdir} BUILD_BASE="binutils gcc make patch uclibc-dev" SUDO=${SUDO:-"sudo"} default_cmds="sanitycheck builddeps clean fetch unpack rootpkg" # read config ABUILD_CONF=${ABUILD_CONF:-"/etc/abuild.conf"} [ -f "$ABUILD_CONF" ] && . "$ABUILD_CONF" # source functions datadir=/usr/share/abuild # if abuild was not run from PATH, then look for func lib at same location if [ -z "$FUNCLIB" ]; then FUNCLIB="${0##/*}/functions.sh" [ -f "$FUNCLIB" ] || FUNCLIB=$datadir/functions.sh fi if ! [ -f "$FUNCLIB" ]; then echo "$FUNCLIB: not found" >&2 exit 1 fi . "$FUNCLIB" set_xterm_title() { if [ "$TERM" = xterm ]; then printf "\033]0;$1\007" >&2 fi } cleanup() { set_xterm_title "" if [ -z "$install_after" ] && [ -n "$uninstall_after" ]; then $SUDO apk del $uninstall_after fi } die() { error "$@" cleanup exit 1 } # check if apkbuild is basicly sane sanitycheck() { local i msg "Checking sanity of $APKBUILD..." [ -z "$pkgname" ] && die "Missing pkgname in APKBUILD" [ -z "${pkgname##* *}" ] && die "pkgname contains spaces" [ -z "$pkgver" ] && die "Missing pkgver in APKBUILD" [ "${pkgver##[0-9]}" == "$pkgver" ] && \ die "pkgver does not start with a digit" [ -z "$pkgrel" ] && warning "Missing pkgrel in APKBUILD. Using pkgrel=0" [ -z "$pkgdesc" ] && die "Missing pkgdesc in APKBUILD" [ -z "$url" ] && die "Missing url in APKBUILD" [ -z "$license" ] && die "Missing license in APKBULID" if [ -n "$source" ]; then for i in $source; do md5sums_has ${i##*/} || die "${i##*/} is missing in md5sums" done fi if [ -n "$md5sums" ]; then for i in $(echo "$md5sums" | awk '{ print $2 }'); do source_has $i || die "$i is missing in source" done fi # common spelling errors [ -n "$depend" ] && die "APKBUILD contains 'depend'. It should be depends" [ -n "$makedepend" ] && die "APKBUILD contains 'makedepend'. It should be makedepends" grep '^# Maintainer:' $APKBUILD >/dev/null || warning "No maintainer" return 0 } md5check() { local dummy f if [ -z "$source" ]; then return 0 fi if [ -z "$md5sums" ]; then die "Use 'abuild checksum' to generate/update the checksum(s)" fi if [ "$(echo $source | wc -l)" -ne "$(echo $md5sums | wc -l)" ]; then die "Number of md5sums does not correspond to number of sources" fi fetch || return 1 msg "Checking md5sums..." cd "$srcdir" && echo "$md5sums" | md5sum -c } uri_fetch() { local uri="$1" local d="${s##*/}" # $(basename $s) local opts [ -n "$quiet" ] && opts="-q" [ -f "$SRCDEST/$d" ] && return 0 mkdir -p "$SRCDEST" if [ -f "$SRCDEST/$d.part" ]; then msg "Partial download found. Trying to resume" opts="$opts -c" fi msg "Fetching $uri" wget $opts -O "$SRCDEST/$d.part" "$uri" \ && mv "$SRCDEST/$d.part" "$SRCDEST/$d" } is_remote() { case "$1" in http://*|ftp://*) return 0;; esac return 1 } fetch() { local s mkdir -p "$srcdir" for s in $source; do if is_remote "$s"; then uri_fetch "$s" || return 1 ln -sf "$SRCDEST/${s##*/}" "$srcdir"/ else ln -sf "$startdir/$s" "$srcdir/" fi done } # unpack the sources unpack() { local u md5check || return 1 mkdir -p "$srcdir" for u in $source; do local s="$SRCDEST/${u##*/}" # $(basename $s) case "$s" in *.tar.gz|*.tgz) msg "Unpacking $s..." tar -C "$srcdir" -zxf "$s" || return 1;; *.tar.bz2) msg "Unpacking $s..." tar -C "$srcdir" -jxf "$s" || return 1;; *.tar.lzma) msg "Unpacking $s..." unlzma -c "$s" | tar -C "$srcdir" -x \ || return 1;; *.zip) msg "Unpacking $s..." unzip "$s" -d "$srcdir" || return 1;; esac done } # cleanup source and package dir clean() { msg "Cleaning temporary build dirs..." rm -rf "$srcdir" rm -rf "$pkgdir" local i for i in $subpackages; do rm -rf "$pkgdir-$(get_split_func $i)" done } # cleanup fetched sources cleancache() { local s for s in $source; do if is_remote "$s"; then msg "Cleaning downloaded ${s##*/}..." rm -f "$SRCDEST/${s##*/}" fi done } cleanpkg() { local i msg "Cleaning built packages..." for i in $pkgname $subpackages; do local p="$i-$pkgver-r$pkgrel" rm -f "$PKGDEST/$p.apk" "$PKGDEST/$p.src.tar.gz" done } # clean all packages except current cleanoldpkg() { local i j msg "Cleaning all packages except $pkgver-r$pkgrel..." for i in $pkgname $subpackages; do for j in "$PKGDEST"/${i%:*}-[0-9]*.apk; do [ "$j" != "$PKGDEST/${i%:*}-$pkgver-r$pkgrel.apk" ] \ && rm -f "$j" done done return 0 } runpart() { local part=$1 [ -n "$DEBUG" ] && msg "$part" $part || die "$part failed" } # override those in your build script build() { die "No build() function found in $APKBUILD" } get_split_func() { # get the 'func' from "sub-pkg:func" local func=${1##*:} # get 'func' from "sub-pkg-func" if there was no :func [ "$func" = "$1" ] && func=${func##*-} echo $func } subpkg() { if [ -z "$subpackages" ]; then return 0 fi local i cd "$startdir" for i in $subpackages; do local func=$(get_split_func $i) # call abuild recursively, setting subpkg{dir,name} msg "Running split function $func..." subpkgdir="$startdir/pkg-$func" subpkgname="${i%:*}" \ $0 $func package || return 1 done } package_apk() { local name=${subpkgname:-$pkgname} [ -z "${name##* *}" ] && die "package name contains spaces" local dir=${subpkgdir:-$pkgdir} local pkg="$name-$pkgver-r$pkgrel.apk" local sub [ ! -d "$dir" ] && die "Missing $dir" cd "$dir" msg "Creating ${subpkgname:+sub}package $pkg..." local builddate=$(date -u "+%s") local size=$(du -sk | awk '{print $1 * 1024}') echo "# Generated by $(basename $0) $abuild_ver" >.PKGINFO if [ -n "$FAKEROOTKEY" ]; then echo "# using $(fakeroot -v)" >> .PKGINFO fi echo "# $(date -u)" >> .PKGINFO cat >> .PKGINFO </dev/null && ! depends_has busybox ; then msg "Adding busybox to depends since we have an install script" deps="$deps busybox" fi for i in $license; do echo "license = $i" >>.PKGINFO done for i in $replaces; do echo "replaces = $i" >>.PKGINFO done for i in $deps; do echo "depend = $i" >>.PKGINFO done for i in $conflicts; do echo "conflict = $i" >>.PKGINFO done for i in $provides; do echo "provides = $i" >>.PKGINFO done for i in $backup; do echo "backup = $i" >>.PKGINFO done local metafiles=".PKGINFO" if [ -n "$install" ]; then cp "$srcdir/$install" "$dir/.INSTALL" || return 1 chmod +x "$dir/.INSTALL" metafiles="$metafiles .INSTALL" fi # for i in pre-install post-install pre-deinstall post-deinstall; do # [ -f ../$i ] && cp ../$i "$db"/ # done set * [ "$1" = '*' ] && set -- ( cd "$dir" && tar -zcf "$PKGDEST/$pkg" $metafiles $@ ) } package() { options_has "!strip" || stripbin package_apk } # predefined splitfunc doc default_doc() { depends="$depends_doc" local i for i in doc man info html sgml licenses; do if [ -d "$pkgdir/usr/share/$i" ]; then mkdir -p "$subpkgdir/usr/share" mv "$pkgdir/usr/share/$i" "$subpkgdir/usr/share/" fi done rm -f "$subpkgdir/usr/share/info/dir" # # compress info and man pages # find "$subpkgdir/usr/share" \( -name '*.info' -o -name '*.info-[1-9]' \ # -o -name '*.[1-9]' \) -exec gzip {} \; # remove if empty, ignore error (not empty) rmdir "$pkgdir/usr/share" "$pkgdir/usr" 2>/dev/null # [ -d "$subpkgdir/usr/share/man" ] && depends="man" return 0 } doc() { default_doc } # predefined splitfunc mod default_mod() { depends="$kernel $depends_mod" for i in firmware modules; do if [ -d "$pkgdir/lib/$i" ]; then rm -rf "$subpkgdir/lib" mkdir -p "$subpkgdir/lib" mv "$pkgdir/lib/$i" "$subpkgdir/lib" fi done } mod() { default_mod } # predefined splitfunc dev default_dev() { depends="$pkgname $depends_dev" cd "$pkgdir" || return 0 for i in usr/include usr/lib/pkgconfig usr/share/aclocal\ usr/share/gettext usr/bin/*-config \ $(find -name include -type d) \ $(find usr/ -name '*.[acho]' -o -name '*.la' \ 2>/dev/null); do if [ -e "$pkgdir/$i" ] || [ -L "$pkgdir/$i" ]; then d="$subpkgdir/${i%/*}" # dirname $i mkdir -p "$d" mv "$pkgdir/$i" "$d" rmdir "$pkgdir/${i%/*}" 2>/dev/null fi done return 0 } dev() { default_dev } # build and package in fakeroot rootpkg() { cd "$startdir" msg "Entering fakeroot..." fakeroot $0 build subpkg package } srcpkg() { local p="$pkgname-$pkgver-$pkgrel" local prefix="${startdir##*/}" local i files="$prefix/APKBUILD" for i in $source; do files="$files $prefix/${i##*/}" done mkdir -p "$PKGDEST" msg "Creating source package $p.src.tar.gz..." (cd .. && tar -zcf "$PKGDEST/$p.src.tar.gz" $files) } # check if package is up to date up2date() { local pkg="$PKGDEST/$pkgname-$pkgver-r$pkgrel.apk" local i s cd "$startdir" for i in $pkgname $subpackages; do [ -f "$PKGDEST/$pkgname-$pkgver-r$pkgrel.apk" ] || return 1 done [ -n "$keep" ] && return 0 for i in $source APKBUILD; do local s if is_remote "$i"; then s="$SRCDEST/${i##*/}" # $(basename $i) else s="$startdir/${i##*/}" fi if [ "$s" -nt "$pkg" ]; then return 1 fi done return 0 } # source all APKBUILDs and output: # 1) origin of package # 2) all dependencies # the output is i in a format easy parseable for awk depparse_aports() { # lets run this in a subshell since we source all APKBUILD here ( aportsdir=$(realpath ${APKBUILD%/APKBUILD}/../..) for i in $aportsdir/*/*/APKBUILD; do pkgname= subpackages= depends= makedepends= . $i dir=${i%/APKBUILD} for j in $pkgname $subpackages; do echo "o ${j%%:*} $dir" set -- $depends $makedepends echo -n "d ${j%%:*} $1" shift while [ $# -gt 0 ]; do echo -n ",$1" shift done echo done done ) } deptrace() { local deps="$@" [ -z "$deps" ] && deps="$BUILD_BASE $depends $makedepends" ( depparse_aports if [ -z "$upgrade" ]; then # list installed pkgs and prefix with 'i ' apk info -q | sort | sed 's/^/i /' fi ) | awk -v pkgs="$deps" ' function depgraph(pkg, a, i) { if (visited[pkg]) return 0; visited[pkg] = 1; split(deps[pkg], a, ","); for (i in a) depgraph(a[i]); print pkg ":" origin[pkg]; } $1 == "i" { visited[$2] = 1 } $1 == "o" { origin[$2] = $3 } $1 == "d" { deps[$2] = $3 } END { split(pkgs, pkgarray); for (i in pkgarray) depgraph(pkgarray[i]); } ' } # build and install dependencies builddeps() { local deps alldeps pkg i dir ver missing msg "Building dependencies..." deps="$BUILD_BASE $makedepends" # add depends unless it is a subpackage for i in $depends; do subpackages_has $i || deps="$deps $i" done # find which deps are missing for i in $deps; do if ! apk info -e $i; then if [ -z "$install_deps" ] && [ -z "$recursive" ]; then die "Missing dependency $i. Use -r to autoinstall or -R to build" fi missing="$missing $i" fi done [ -z "$missing" ] && return 0 if [ -n "$install_deps" ] && $SUDO apk add $missing; then uninstall_after="$missing $uninstall_after" return 0 fi [ -z "$recursive" ] && return 1 for i in $(deptrace $missing); do # i = pkg:dir local dir=${i#*:} local pkg=${i%:*} msg "Entering $dir" cd "$dir" || return 1 $0 -k -i $pkg || return 1 uninstall_after="$pkg $uninstall_after" done } # replace the md5sums in the APKBUILD checksum() { local s files [ -z "$source" ] && return 0 fetch msg "Updating the md5sums in APKBUILD..." for s in $source; do files="$files ${s##*/}" done md5sums="$(cd "$srcdir" && md5sum $files)" || die "md5sum failed" sed -i -e '/^md5sums="/,/"\$/d; /^md5sums=''/,/''\$/d' "$APKBUILD" echo "md5sums=\"$md5sums\"" >>"$APKBUILD" } stripbin() { local bin cd "${subpkgdir:-$pkgdir}" || return 1 msg "Stripping binaries" find . -type f 2>/dev/null | while read bin; do local opt= case "$(file -biz "$bin")" in */x-sharedlib*|*/x-archive*) strip --strip-debug "$bin";; */x-executable*) strip "$bin";; esac done return 0 } # simply list target apks listpkg() { local i for i in $pkgname $subpackages; do echo "${i%:*}-$pkgver-r$pkgrel.apk" done } source_has() { local i for i in $source; do [ "$1" = "${i##*/}" ] && return 0 done return 1 } subpackages_has() { local i for i in $subpackages; do [ "$1" = "${i%:*}" ] && return 0 done return 1 } list_has() { local needle="$1" local i shift for i in $@; do [ "$needle" = "$i" ] && return 0 [ "$needle" = "!$i" ] && return 1 done return 1 } options_has() { list_has "$1" $options } depends_has() { list_has "$1" $depends } md5sums_has() { list_has "$1" $md5sums } # install package after build post_add() { local pkgf="$PKGDEST/$1-$pkgver-r$pkgrel.apk" local deps i if ! subpackages_has $1 && [ "$1" != "$pkgname" ]; then die "$1 is not built by this APKBUILD" fi # recursively install dependencies that are provided by this APKBUILD deps=$(apk index "$pkgf" 2>/dev/null | awk -F: '$1=="D" { print $2 }') for i in $deps; do if subpackages_has $i || [ "$i" = "$pkgname" ]; then post_add $i || return 1 fi done $SUDO apk add -u "$pkgf" || die "Failed to install $1" } # create new aport from templates newaport() { local pn=${newname%-[0-9]*} local pv if [ "$pn" != "$newname" ]; then pv=${newname#$pn-} fi if [ -e "$pn"/APKBUILD ]; then error "$pn/APKBUILD already exist" return 1 fi mkdir -p "$pn" cd "$pn" sed -e '1,/^\#*$/d' \ -e "s/^\(# Contributor: \).*/\1$PACKAGER/" \ -e "s/^\(# Maintainer: \).*/\1$PACKAGER/" \ -e "s/^pkgname=.*/pkgname=$pn/" \ -e "s/^pkgver=.*/pkgver=$pv/" \ "$datadir"/sample.APKBUILD > APKBUILD || return 1 #-e '1,/^\#$/d' \ if [ -n "$cpinitd" ]; then cp "$datadir"/sample.initd $pn.initd cp "$datadir"/sample.confd $pn.confd cp "$datadir"/sample.install $pn.install sed -i -e "s/^install=.*/install=\"$pn.install\"/" \ -e "s/^source=\"\(.*\)\"/source=\"\1\n\t$pn.initd\n\t$pn.confd\n\t\$install\n\t\"/" \ APKBUILD fi } usage() { echo "$(basename $0) $abuild_ver" echo "usage: ${0##*/} [options] [-i PKG] [-P REPODEST] [-p PKGDEST]" echo " [-s SRCDEST] [cmd] ..." echo " ${0##*/} [-c] -n PKGNAME[-PKGVER]" echo "Options:" echo " -f Force specified cmd, even if they are already done" echo " -h Show this help" echo " -i Install PKG after successul build" echo " -k Keep built packages, even if APKBUILD or sources are newer" echo " -p Set package destination directory" echo " -P Set PKGDEST to REPODEST/, where repo is the parents dir name" echo " -q Quiet" echo " -r Install missing dependencies from system repository (using sudo)" echo " -R Recursively build and install missing dependencies (using sudo)" echo " -s Set source package destination directory" echo " -u Recursively build and upgrade all dependencies (using sudo)" echo "" echo " -n Create a new APKBUILD in a directory named PKGNAME" echo " -c Copy a sample init.d, conf.d and install script to new directory" echo "" echo "Commands:" echo " checksum Generate checksum to be included in APKBUILD" echo " fetch Fetch sources to \$SRCDEST and verify checksums" echo " sanitycheck Basic sanity check of APKBUILD" echo " md5check Check md5sums" echo " unpack Unpack sources to \$srcdir" echo " build Compile and install package into \$pkgdir" echo " listpkg List target packages" echo " package Create package in \$PKGDEST" echo " rootpkg Run '$0 build package' as fakeroot" echo " clean Remove temp build and install dirs" echo " cleanoldpkg Remove binary packages except current version" echo " cleanpkg Remove already built binary and source package" echo " cleancache Remove downloaded files from \$SRCDEST" echo " srcpkg Make a source package" echo " up2date Compare target and sources dates" echo "" exit 0 } APKBUILD="${APKBUILD:-./APKBUILD}" unset force unset recursive while getopts "cfhi:kin:p:P:qrRs:u" opt; do case $opt in 'c') cpinitd=1;; 'f') force=1;; 'h') usage;; 'i') install_after="$install_after $OPTARG";; 'k') keep=1;; 'n') newname=$OPTARG;; 'p') PKGDEST=$OPTARG;; 'P') REPODEST=$OPTARG;; 'q') quiet=1;; 'r') install_deps=1;; 'R') recursive=1;; 's') SRCDEST=$OPTARG;; 'u') upgrade=1 recursive=1;; esac done shift $(( $OPTIND - 1 )) # If REPODEST is set then it will override the PKGDEST if [ -n "$REPODEST" ]; then PKGDEST="$REPODEST/$repo" fi # source the buildfile if [ -z "$newname" ]; then [ -f "$APKBUILD" ] || die "Could not find $APKBUILD (PWD=$PWD)" . "$APKBUILD" fi # If we are handling a sub package then reset subpackages if [ -n "$subpkgname" ]; then subpackages= fi trap 'die "Aborted by user"' INT set_xterm_title "abuild: $pkgname" if [ -z "$1" ] && [ -n "$newname" ]; then set "newaport" fi if [ -z "$1" ]; then if up2date && [ -z "$force" ]; then msg "Package is up to date" else set $default_cmds fi fi while [ $# -gt 0 ]; do runpart $1 shift done for i in $install_after; do post_add $i done cleanup