#!/bin/sh # script to build apk packages (light version of makepkg) # Copyright (c) 2008 Natanael Copa # # Distributed under GPL-2 # # Depends on: busybox utilities, fakeroot, # abuild_ver=@VERSION@ sysconfdir=@sysconfdir@ abuildrepo_base=@abuildrepo@ datadir=@datadir@ program=${0##*/} abuild_path=$(readlink -f $0) # defaults BUILD_BASE="build-base" SUDO=${SUDO:-"sudo"} FAKEROOT=${FAKEROOT:-"fakeroot"} APK=${APK:-apk} apk_opt_wait="--wait 30" # read config ABUILD_CONF=${ABUILD_CONF:-"$sysconfdir/abuild.conf"} [ -f "$ABUILD_CONF" ] && . "$ABUILD_CONF" default_colors() { NORMAL="\033[1;0m" STRONG="\033[1;1m" RED="\033[1;31m" GREEN="\033[1;32m" YELLOW="\033[1;33m" BLUE="\033[1;34m" } monochrome() { NORMAL="" STRONG="" RED="" GREEN="" YELLOW="" BLUE="" } #colors if [ -n "$USE_COLORS" ]; then default_colors fi # functions msg() { local prompt="$GREEN>>>${NORMAL}" local fake="${FAKEROOTKEY:+${BLUE}*${NORMAL}}" local name="${STRONG}${subpkgname:-$pkgname}${NORMAL}" [ -z "$quiet" ] && printf "${prompt} ${name}${fake}: $@\n" >&2 } warning() { local prompt="${YELLOW}>>> WARNING:${NORMAL}" local fake="${FAKEROOTKEY:+${BLUE}*${NORMAL}}" local name="${STRONG}${subpkgname:-$pkgname}${NORMAL}" printf "${prompt} ${name}${fake}: $@\n" >&2 } error() { local prompt="${RED}>>> ERROR:${NORMAL}" local fake="${FAKEROOTKEY:+${BLUE}*${NORMAL}}" local name="${STRONG}${subpkgname:-$pkgname}${NORMAL}" printf "${prompt} ${name}${fake}: $@\n" >&2 } set_xterm_title() { if [ "$TERM" = xterm ] && [ -n "$USE_COLORS" ]; then printf "\033]0;$1\007" >&2 fi } cleanup() { set_xterm_title "" if [ -z "$install_after" ] && [ -n "$uninstall_after" ]; then $SUDO $APK del $apk_opt_wait $uninstall_after fi } die() { error "$@" cleanup exit 1 } # check if apkbuild is basicly sane sanitycheck() { local i suggestion 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" if [ "$pkgver" != "volatile" ] && [ -z "$nodeps" ]; then $APK version --check -q "$pkgver" ||\ die "$pkgver is not a valid version" fi [ -z "$pkgrel" ] && die "Missing pkgrel in APKBUILD" [ -z "$pkgdesc" ] && die "Missing pkgdesc in APKBUILD" [ -z "$url" ] && die "Missing url in APKBUILD" [ -z "$license" ] && die "Missing license in APKBULID" # check if CARCH, CBUILD, CHOST and CTARGET is set if [ -z "$CARCH" ]; then case "$(uname -m)" in i[0-9]86) suggestion=" (Suggestion: CARCH=x86)";; x86_64) suggestion=" (Suggestion: CARCH=x86_64)";; esac die "Please set CARCH in /etc/abuild.conf$suggestion" fi [ -z "$CHOST" ] && die "Please set CHOST in /etc/abuild.conf" for i in $install; do [ -e "$startdir/$i" ] || die "install script $startdir/$i is missing" done [ -n "${triggers%%:*}" ] && [ ! -e "$startdir"/${triggers%%:*} ] \ && die "trigger script $startdir/${triggers%%:*} is missing" if [ -n "$source" ]; then for i in $source; do if install_has "$i"; then warning "You should not have \$install in source" continue fi md5sums_has ${i##*/} || die "${i##*/} is missing in md5sums" case "$i" in https://*) makedepends_has wget || die "wget must be in makedepends when source has https://" ;; esac done fi if [ -n "$md5sums" ]; then for i in $(echo "$md5sums" | awk '{ print $2 }'); do source_has $i || die "$i exists in md5sums but 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" makedepends_has 'g++' && warning "g++ should not be in makedepends" return 0 } md5check() { local dummy f endreturnval originalparams origin file 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" || return 1 IFS=$'\n' endreturnval=0 originalparams=$@ set -- $source for src in $md5sums; do origin=$1; shift echo "$src" | md5sum -c if [ $? -ne 0 ]; then endreturnval=1 is_remote $origin || continue echo "Because the remote file above failed the md5sum check it will be deleted." echo "Rebuilding will cause it to re-download which in some cases may fix the problem." file=`echo "$src" | sed 's/.*[ \t\n]\(.*\)/\1/'` echo "Deleting: $file" rm $file fi done unset IFS set -- $originalparams return $endreturnval } # verify upstream sources sourcecheck() { local uri for uri in $source; do is_remote $uri || continue case "$uri" in saveas-*://*) uri=${uri#saveas-} uri=${uri%/*} ;; esac wget -q -s "$uri" || return 1 done return 0 } uri_fetch() { local uri="$1" local d="${uri##*/}" # $(basename $uri) local opts [ -n "$quiet" ] && opts="-q" [ -f "$SRCDEST/$d" ] && return 0 # fix saveas-*://* URIs case "$uri" in # remove 'saveas-' from beginning and # '/filename' from end of URI saveas-*://*) uri="${uri:7:$(expr ${#uri} - 7 - ${#d} - 1)}";; esac # we need GNU wget for this case "$uri" in https://*) opts="--no-check-certificate";; esac 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://*|https://*|saveas-*://*) return 0;; esac return 1 } # try download from file from mirror first uri_fetch_mirror() { local uri="$1" local d="${uri##*/}" # $(basename $uri) if [ -n "$DISTFILES_MIRROR" ]; then if is_remote "$DISTFILES_MIRROR"; then uri_fetch "$DISTFILES_MIRROR"/$d && return 0 else cp "$DISTFILES_MIRROR"/$d "$SRCDEST" && return 0 fi fi uri_fetch "$uri" } default_fetch() { local s mkdir -p "$srcdir" for s in $source; do if is_remote "$s"; then uri_fetch_mirror "$s" || return 1 ln -sf "$SRCDEST/${s##*/}" "$srcdir"/ else ln -sf "$startdir/$s" "$srcdir/" fi done } fetch() { default_fetch } # verify that all init.d scripts are openrc runscripts initdcheck() { local i for i in $source; do case $i in *.initd) head -n 1 "$srcdir"/$i | grep -q '/sbin/runscript' \ && continue error "$i is not an openrc #!/sbin/runscript" return 1 ;; esac done } # unpack the sources default_unpack() { local u if [ -z "$force" ]; then md5check || return 1 initdcheck || return 1 fi 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;; *.tar.xz) msg "Unpacking $s..." unxz -c "$s" | tar -C "$srcdir" -x || return 1;; *.zip) msg "Unpacking $s..." unzip "$s" -d "$srcdir" || return 1;; esac done } unpack() { default_unpack } # cleanup source and package dir clean() { msg "Cleaning temporary build dirs..." rm -rf "$srcdir" rm -rf "$pkgbasedir" } # 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 } listpkgnames() { local i for i in $pkgname $subpackages; do echo ${i%:*} done for i in $linguas; do echo $pkgname-lang-$i done } cleanpkg() { local i getpkgver || return 1 msg "Cleaning built packages..." for i in $(listpkgnames); do local p="${i%:*}-$pkgver-r$pkgrel" rm -f "$PKGDEST/$p.apk" "$PKGDEST/$p.src.tar.gz" \ "$abuildrepo"/$p.apk "$abuildrepo"/*/$p.apk done # remove given packages from index } # clean all packages except current cleanoldpkg() { local i j getpkgver || return 1 msg "Cleaning all packages except $pkgver-r$pkgrel..." for i in $(listpkgnames); do local pn=${i%:*} for j in "$PKGDEST"/$pn-[0-9]*.apk ; do [ "$j" = "$PKGDEST/$pn-$pkgver-r$pkgrel.apk" ] \ && continue rm -f "$j" "$abuildrepo"/*/${j##*/} done done return 0 } mkusers() { local i for i in $pkgusers; do if ! getent passwd $i >/dev/null; then local gopt= msg "Creating user $i" if getent group $i >/dev/null; then gopt="-G $i" fi $SUDO adduser -D -H $gopt $i || return 1 fi done for i in $pkggroups; do if ! getent group $i >/dev/null; then msg "Creating group $i" $SUDO addgroup $i || return 1 fi done } runpart() { local part=$1 [ -n "$DEBUG" ] && msg "$part" $part || die "$part failed" } # override those in your build script getpkgver() { # this func is supposed to be overridden by volatile packages if [ "$pkgver" = "volatile" ]; then error "Please provide a getpkgver() function in your APKBUILD" return 1 fi } prepare() { : } build() { : } # generate a simple tar.gz package of pkgdir targz() { cd "$pkgdir" || return 1 mkdir -p "$PKGDEST" tar -czf "$PKGDEST"/$pkgname-$pkgver-r$pkgrel.tar.gz * } 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 } prepare_subpackages() { 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="$pkgbasedir/${i%:*}" subpkgname="${i%:*}" \ $0 $func prepare_package || return 1 done } lang_subpkg() { if [ -z "$lang" ]; then error "lang is not set" return 1 fi arch="noarch" install_if="$pkgname=$pkgver-r$pkgrel lang-$lang" mkdir -p "$subpkgdir"/usr/share/locale mv "$pkgdir"/usr/share/locale/$lang* \ "$subpkgdir"/usr/share/locale/ \ || return 1 } prepare_language_packs() { for lang in $linguas; do lang="$lang" \ subpkgname="$pkgname-lang-$lang" \ subpkgdir="$pkgbasedir"/$subpkgname \ $0 lang_subpkg prepare_package || return 1 done } # echo '-dirty' if git is not clean git_dirty() { if [ $(git status -s "$startdir" | wc -l) -ne 0 ]; then echo "-dirty" fi } # echo last commit hash id git_last_commit() { git log --format=oneline -n 1 "$startdir" | awk '{print $1}' } get_maintainer() { if [ -z "$maintainer" ]; then maintainer=$(awk -F': ' '/\# *Maintainer/ {print $2}' "$APKBUILD") fi } prepare_metafiles() { getpkgver || return 1 local name=${subpkgname:-$pkgname} [ -z "${name##* *}" ] && die "package name contains spaces" local dir=${subpkgdir:-$pkgdir} local pkg="$name-$pkgver-r$pkgrel.apk" local pkginfo="$controldir"/.PKGINFO local sub [ ! -d "$dir" ] && die "Missing $dir" cd "$dir" mkdir -p "$controldir" local builddate=$(date -u "+%s") local size=$(du -sk | awk '{print $1 * 1024}') local parch="$CARCH" # we need to wait with setting noarch til our build infra can handle it # if [ "$arch" = "noarch" ]; then # parch="noarch" # fi 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 ; then msg "Script found. busybox added as a dependency for $pkg" deps="$deps busybox" break fi done fi local last_commit="$(git_last_commit)$(git_dirty)" if [ -n "$last_commit" ]; then echo "commit = $last_commit" >> "$pkginfo" fi get_maintainer if [ -n "$maintainer" ]; then echo "maintainer = $maintainer" >> "$pkginfo" 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 if [ -n "$triggers" ]; then echo "triggers = ${triggers#*:}" >> "$pkginfo" fi if [ -n "$install_if" ]; then echo "install_if = $(echo $install_if)" >> "$pkginfo" fi local metafiles=".PKGINFO" for i in $install ${triggers%%:*}; do script=${i#$name} case "$script" in .pre-install|.post-install|.pre-upgrade|.post-upgrade|.pre-deinstall|.post-deinstall|.trigger) msg "Adding $script" ;; *) error "$script: Invalid install/trigger script" return 1 ;; esac cp "$startdir/$i" "$controldir/$script" || return 1 chmod +x "$controldir/$script" metafiles="$metafiles $script" done echo $metafiles | tr ' ' '\n' > "$controldir"/.metafiles } prepare_tracedeps() { local dir=${subpkgdir:-$pkgdir} [ "$arch" = "noarch" ] && return 0 options_has "!tracedeps" && return 0 # lets tell all the .so files this package provides in .provides-so find -name '*.so' -o -name '*.so.[0-9]*' | sed 's:.*/::' \ >"$controldir"/.provides-so # lets tell all the places we should look for .so files - all rpaths scanelf -q -Rr "$dir" | sed -e 's/[[:space:]].*//' -e 's/:/\n/' \ | sort | uniq \ >"$controldir"/.rpaths # now find the so dependencies scanelf -Rn "$dir" | tr ' ' ':' | awk -F ":" '$1 == "ET_DYN" || $1 == "ET_EXEC" {print $2}' \ | sed 's:,:\n:g' | sort | uniq \ | while read i; do # only add files that are not self provided grep "^$i$" "$controldir"/.provides-so >/dev/null \ || echo $i >> "$controldir"/.needs-so done } # check if dir has arch specific binaries dir_has_arch_binaries() { local dir="$1" # if scanelf returns something, then we have binaries [ -n "$(scanelf -R "$dir" | head -n 1)" ] && return 0 # look for static *.a [ -n "$(find "$dir" -type f -name '*.a' | head -n 1)" ] && return 0 return 1 } # returns true if this is the -dev package is_dev_pkg() { test "${subpkgname%-dev}" != "$subpkgname" } # check that noarch is set if needed archcheck() { options_has "!archcheck" && return 0 if dir_has_arch_binaries "${subpkgdir:-$pkgdir}"; then [ "$arch" != "noarch" ] && return 0 error "Arch specific binaries found so arch must not be set to \"noarch\"" return 1 elif [ "$arch" != "noarch" ] && ! is_dev_pkg; then # we dont want -dev package go to noarch warning "No arch specific binaries found so arch should probably be set to \"noarch\"" fi return 0 } prepare_package() { msg "Preparing ${subpkgname:+sub}package ${subpkgname:-$pkgname}..." stripbin prepare_metafiles && prepare_tracedeps || return 1 archcheck } pkginfo_val() { local key="$1" local file="$2" awk -F ' = ' "\$1 == \"$key\" {print \$2}" "$file" } # find real path to so files real_so_path() { local so="$1" shift while [ $# -gt 0 ]; do [ -e "$1"/$so ] && realpath "$1/$so" && return 0 shift done error "$so: path not found" return 1 } # search rpaths and /usr/lib /lib for given so files find_so_files() { local rpaths=$(cat "$1") shift while [ $# -gt 0 ]; do real_so_path "$1" /usr/lib /lib $rpaths || return 1 shift done return 0 } trace_apk_deps() { local name="$1" local dir="$2" local i= j= found= autodeps= deppkgs= missing= so_paths= self_provided= msg "Tracing dependencies for $name..." # add pkgconfig if usr/lib/pkgconfig is found if [ -d "$pkgbasedir"/$name/usr/lib/pkgconfig ] \ && ! grep -q '^depend = pkgconfig' "$dir"/.PKGINFO; then msg " added pkgconfig (found /usr/lib/pkgconfig)" autodeps="$autodeps pkgconfig" fi # special case for libpthread: we need depend on libgcc if [ -f "$dir"/.needs-so ] && grep -q -w '^libpthread.so.*' "$dir"/.needs-so \ && ! grep -q -w "^depend = libgcc" "$dir"/.PKGINFO; then autodeps="$autodeps libgcc" msg " added libgcc (due to libpthread)" fi [ -f "$dir"/.needs-so ] && for i in $(cat "$dir"/.needs-so); do found= # first check if its provide by same apkbuild for j in "$dir"/../.control.*/.provides-so; do grep -w "$i" "$j" >/dev/null || continue found=${j%/.provides-so} found=${found##*/.control.} break done if [ -n "$found" ]; then if ! list_has "$found" $self_provided; then self_provided="$self_provided $found" fi else missing="$missing $i" fi done # find all packages that holds the so files if [ -f "$dir"/.rpaths ]; then so_files=$(find_so_files "$dir"/.rpaths $missing) || return 1 deppkgs=$($APK info -q -W $so_files) || return 1 fi for found in $self_provided $deppkgs; do if grep -w "^depend = ${found}$" "$dir"/.PKGINFO >/dev/null ; then warning "You can remove '$found' from depends" continue fi if [ "$found" != "$name" ] && ! list_has "$found" $autodeps; then autodeps="$autodeps $found" msg " added $found" fi done [ -z "$autodeps" ] && return 0 echo "# automatically detected:" >> "$dir"/.PKGINFO for i in $autodeps; do echo "depend = $i" >> "$dir"/.PKGINFO done } create_apks() { local file getpkgver || return 1 mkdir -p "$PKGDEST" for file in "$pkgbasedir"/.control.*/.PKGINFO; do local dir="${file%/.PKGINFO}" local name=$(pkginfo_val pkgname $file) local ver=$(pkginfo_val pkgver $file) local apk=$name-$ver.apk local datadir="$pkgbasedir"/$name trace_apk_deps "$name" "$dir" || return 1 msg "Creating $apk..." ( cd "$datadir" # data.tar.gz set -- * if [ "$1" = '*' ]; then touch .dummy set -- .dummy fi tar -c "$@" | abuild-tar --hash | gzip -9 >"$dir"/data.tar.gz # append the hash for data.tar.gz local sha256=$(sha256sum "$dir"/data.tar.gz | cut -f1 -d' ') echo "datahash = $sha256" >> "$dir"/.PKGINFO # control.tar.gz cd "$dir" tar -c $(cat "$dir"/.metafiles) | abuild-tar --cut \ | gzip -9 > control.tar.gz abuild-sign -q control.tar.gz || exit 1 # create the final apk cat control.tar.gz data.tar.gz > "$PKGDEST"/$apk ) done } clean_abuildrepo() { local apk cd "$abuildrepo" || return 1 # remove compat symlink for d in "$abuildrepo/$CARCH"; do [ -L "$d" ] && rm "$d" done # remove broken links from abuildrepo for apk in *.apk */*.apk; do if [ -L "$apk" ] && [ ! -f "$apk" ]; then rm -f "$apk" fi done } mklinks_abuildrepo() { local apk mkdir -p "$abuildrepo"/$CARCH cd "$abuildrepo" || return 1 # create links for this package for apk in $(listpkg); do [ -f "$PKGDEST"/$apk ] || continue ln -sf "$PKGDEST"/$apk "$abuildrepo"/$CARCH/$apk done } update_abuildrepo() { local d apk if ! apk_up2date || [ -n "$force" ]; then sanitycheck && builddeps && clean && fetch && unpack \ && prepare && mkusers && rootpkg || return 1 fi clean_abuildrepo mklinks_abuildrepo cd "$abuildrepo" local index=$CARCH/APKINDEX.tar.gz msg "Updating the cached abuild repository index..." local sign=".SIGN.RSA.${SIGN_PUBLIC_KEY##*/}" local oldindex= if [ -f "$index" ]; then oldindex="--index $index" fi $APK index --quiet $oldindex --output "$index".unsigned \ --description "$repo $(cd $startdir && git describe)" \ --rewrite-arch $CARCH \ $CARCH/*.apk || exit 1 msg "Signing the index..." abuild-sign -q "$index".unsigned || exit 1 mv "$index".unsigned "$index" chmod 644 "$index" } # predefined splitfunc doc default_doc() { depends="$depends_doc" install="$install_doc" triggers="$triggers_doc" pkgdesc="$pkgdesc (documentation)" arch=${arch_doc:-"noarch"} local i for i in doc man info html sgml licenses gtk-doc; 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" install="$install_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() { local i= j= depends="$pkgname $depends_dev" install="$install_dev" triggers="$triggers_dev" pkgdesc="$pkgdesc (development files)" for i in $origsubpackages; do [ "${i%:*}" = "$subpkgname" ] || depends="$depends ${i%:*}" done cd "$pkgdir" || return 0 for i in usr/include usr/lib/pkgconfig usr/share/aclocal\ usr/share/gettext usr/bin/*-config \ usr/share/vala/vapi usr/share/gir-[0-9]*\ $(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 # move *.so links needed when linking the apps to -dev packages for i in lib/*.so usr/lib/*.so; do if [ -L "$i" ]; then mkdir -p "$subpkgdir"/"${i%/*}" mv "$i" "$subpkgdir/$i" || return 1 fi done return 0 } dev() { default_dev } is_function() { type "$1" 2>&1 | head -n 1 | egrep -q "is a (shell )?function" } do_fakeroot() { if [ -n "$FAKEROOT" ]; then $FAKEROOT -- "$@" else "$@" fi } # build and package in fakeroot rootpkg() { local do_build=build cd "$startdir" if is_function package; then build || return 1 do_build=package fi cd "$startdir" [ -n "$FAKEROOT" ] && msg "Entering fakeroot..." do_fakeroot "$abuild_path" $color_opt $do_build \ prepare_subpackages \ prepare_language_packs \ prepare_package \ create_apks } srcpkg() { getpkgver || return 1 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) } # return true if arch is supported or noarch check_arch() { list_has $CARCH $arch || [ "$arch" = "noarch" ] || [ "$arch" = "all" ] } # check if package is up to date apk_up2date() { getpkgver || return 1 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 } abuildindex_up2date() { local i getpkgver || return 1 local dir="$abuildrepo"/$CARCH local apk="${pkgname%:*}-$pkgver-r$pkgrel.apk" local idx="$dir"/APKINDEX.tar.gz local file="$dir"/$apk # check if index is missing [ -f "$idx" ] || return 1 # if link or file is missing, then we need update abuildrepo index [ -f "$file" ] || return 1 # if file exists and is newer than index, then we need update index [ "$file" -nt "$idx" ] && return 1 return 0 } up2date() { check_arch || return 0 apk_up2date && abuildindex_up2date } # rebuild package and abuildrepo index if needed abuildindex() { up2date && return 0 update_abuildrepo } # 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} deps= # filter out conflicts from deps and version info for j in $depends $makedepends; do case "$j" in !*) continue;; esac deps="$deps ${j%%[<>=]*}" done for j in $pkgname $subpackages; do echo "o ${j%%:*} $dir" set -- $deps echo -n "d ${j%%:*} $1" shift while [ $# -gt 0 ]; do echo -n ",$1" shift done echo done done ) } deptrace() { local deps= i= # strip versions from deps for i in "$@"; do deps="$deps ${i%%[<>=]*}" done [ -z "$deps" ] && return 0 ( 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= installed_deps= local filtered_deps= conflicts= [ -n "$nodeps" ] && return 0 msg "Analyzing dependencies..." # add depends unless it is a subpackage or package itself for i in $BUILD_BASE $depends $makedepends; do [ "$pkgname" = "${i%%[<>=]*}" ] && continue subpackages_has ${i%%[<>=]*} || deps="$deps $i" done installed_deps=$($APK info -e $deps) # find which deps are missing for i in $deps; do if [ "${i#\!}" != "$i" ]; then $APK info -q -e "${i#\!}" \ && conflicts="$conflicts ${i#\!}" elif ! deplist_has $i $installed_deps || [ -n "$upgrade" ]; then missing="$missing $i" fi done if [ -n "$conflicts" ]; then error "Conflicting package(s) installed:$conflicts" return 1 fi if [ -z "$install_deps" ] && [ -z "$recursive" ]; then # if we dont have any missing deps we are done now [ -z "$missing" ] && return 0 error "Missing dependencies: $missing Use -r to autoinstall or -R to build" return 1 fi uninstall_after=".makedepends-$pkgname $uninstall_after" if [ -n "$install_deps" ] && [ -z "$recursive" ] && [ -n "$deps" ]; then # make a --simluate run first to detect missing deps # apk-tools --virtual is no goot at reporting those. $SUDO $APK add --repository "$abuildrepo" \ $apk_opt_wait \ --simulate --quiet $deps || return 1 $SUDO $APK add --repository "$abuildrepo" \ $apk_opt_wait \ --virtual .makedepends-$pkgname $deps \ && return 0 fi [ -z "$recursive" ] && return 1 # find dependencies that are installed but missing in repo. for i in $deps; do local m=$($APK search --repository "$abuildrepo" ${i%%[<>=]*}) if [ -z "$m" ]; then missing="$missing $i" fi done for i in $(deptrace $missing); do # i = pkg:dir local dir=${i#*:} local pkg=${i%:*} # ignore if dependency is in other repo [ -d "$dir" ] || continue # break cricular deps list_has $pkg $ABUILD_VISITED && continue export ABUILD_VISITED="$ABUILD_VISITED $pkg" msg "Entering $dir" cd "$dir" && $0 $forceroot $keep $quiet $install_deps \ $recursive $upgrade $color_opt abuildindex || return 1 done $SUDO $APK add -u --repository "$abuildrepo" \ $apk_opt_wait \ --virtual .makedepends-$pkgname $deps } # 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 if options_has "!strip" || [ "$arch" = "noarch" ]; then return 0 fi cd "${subpkgdir:-$pkgdir}" || return 1 msg "Stripping binaries" scanelf --recursive --nobanner --etype "ET_DYN,ET_EXEC" . \ | sed -e 's:^ET_DYN ::' -e 's:^ET_EXEC ::' \ | xargs -r strip } # simply list target apks listpkg() { local name getpkgver || return 1 for name in $(listpkgnames) ; do echo "$name-$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 } # same as list_has but we filter version info deplist_has() { local needle="$1" local i shift for i in $@; do i=${i%%[<>=]*} [ "$needle" = "$i" ] && return 0 [ "$needle" = "!$i" ] && return 1 done return 1 } options_has() { list_has "$1" $options } depends_has() { deplist_has "$1" $depends } makedepends_has() { deplist_has "$1" $makedepends } md5sums_has() { list_has "$1" $md5sums } install_has() { list_has "$1" $install } # install package after build post_add() { getpkgver || return 1 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 $apk_opt_wait -u "$pkgf" || die "Failed to install $1" } installdeps() { local deps i $SUDO $APK add $apk_opt_wait --repository "$abuildrepo" \ --virtual .makedepends-$pkgname \ $makedepends } uninstalldeps (){ $SUDO $APK del $apk_opt_wait .makedepends-$pkgname } all() { if ! [ -n "$force" ]; then check_arch || return 0 fi if up2date && [ -z "$force" ]; then msg "Package is up to date" else update_abuildrepo fi } usage() { echo "$program $abuild_ver" echo "usage: $program [options] [-i PKG] [-P REPODEST] [-p PKGDEST]" echo " [-s SRCDEST] [cmd] ..." echo " $program [-c] -n PKGNAME[-PKGVER]" echo "Options:" echo " -c Enable colored output" echo " -d Disable dependency checking" echo " -f Force specified cmd, even if they are already done" echo " -F Force run as root" 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 " -m Disable colors (monochrome)" echo " -p Set package destination directory" echo " -P Set PKGDEST to REPODEST//\$CARCH, 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 "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 " sourcecheck Check if remote source package exists upstream" echo " up2date Compare target and sources dates" echo " installdeps Install packages listed in makedepends and depends" echo " uninstalldeps Uninstall packages listed in makedepends and depends" echo "" exit 0 } APKBUILD="${APKBUILD:-./APKBUILD}" unset force unset recursive while getopts "cdfFhi:kimnp:P:qrRs:u" opt; do case $opt in 'c') default_colors color_opt="-c";; 'd') nodeps=1;; 'f') force="-f";; 'F') forceroot="-F";; 'h') usage;; 'i') install_after="$install_after $OPTARG";; 'k') keep="-k";; 'm') monochrome color_opt="-m";; 'n') die "Use newapkbuild to create new aports";; 'p') PKGDEST=$OPTARG;; 'P') REPODEST=$OPTARG;; 'q') quiet="-q";; 'r') install_deps="-r";; 'R') recursive="-R";; 's') SRCDEST=$OPTARG;; 'u') upgrade="-u" recursive="-R";; esac done shift $(( $OPTIND - 1 )) # check so we are not root if [ "$(whoami)" = "root" ] && [ -z "$FAKEROOTKEY" ]; then [ -z "$forceroot" ] && die "Do not run abuild as root" SUDO= FAKEROOT= fi # find startdir [ -f "$APKBUILD" ] || die "Could not find $APKBUILD (PWD=$PWD)" APKBUILD=$(readlink -f "$APKBUILD") startdir="${APKBUILD%/*}" srcdir=${srcdir:-"$startdir/src"} pkgbasedir=${pkgbasedir:-"$startdir/pkg"} pkgrel=0 repo=${startdir%/*} repo=${repo##*/} abuildrepo="$abuildrepo_base"/$repo SRCDEST=${SRCDEST:-$startdir} PKGDEST=${PKGDEST:-$startdir} cd "$startdir" || die . "$APKBUILD" # If REPODEST is set then it will override the PKGDEST if [ -n "$REPODEST" ]; then PKGDEST="$REPODEST/$repo/$CARCH" # for recursive action export REPODEST fi # If we are handling a sub package then reset subpackages and install if [ -n "$subpkgname" ]; then origsubpackages="$subpackages" subpackages= install= triggers= fi pkgdir="$pkgbasedir/$pkgname" controldir="$pkgbasedir"/.control.${subpkgname:-$pkgname} trap 'die "Aborted by user"' INT set_xterm_title "abuild: $pkgname" if [ -z "$1" ]; then set all fi while [ $# -gt 0 ]; do runpart $1 shift done for i in $install_after; do post_add $i done cleanup