summaryrefslogtreecommitdiff
path: root/src/dagpkg
diff options
context:
space:
mode:
Diffstat (limited to 'src/dagpkg')
-rwxr-xr-xsrc/dagpkg222
1 files changed, 222 insertions, 0 deletions
diff --git a/src/dagpkg b/src/dagpkg
new file mode 100755
index 0000000..e1487d5
--- /dev/null
+++ b/src/dagpkg
@@ -0,0 +1,222 @@
+#!/usr/bin/env bash
+#
+# dagpkg - create a directed graph of package dependencies and build
+# them in topological order
+
+# Copyright (C) 2014 Nicolás Reynolds <fauno@parabola.nu>
+# Copyright (C) 2014 Michał Masłowski <mtjm@mtjm.eu>
+#
+# License: GNU GPLv3+
+#
+# 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 Affero 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 <http://www.gnu.org/licenses/>.
+set -e
+
+source libremessages
+source $(librelib conf)
+
+# Source variables from libretools
+load_files libretools
+check_vars libretools FULLBUILDCMD || exit 1
+#check_vars libretools HOOKPREBUILD HOOKLOCALRELEASE || exit 1 # optional
+
+# Source variables from makepkg
+load_files makepkg
+check_vars makepkg CARCH || exit 1
+
+# Globals:
+# - temp_dir
+# - log
+# - name (sort of, it's also local to visit_pkgbuild() )
+# - prev
+# - I
+# - marks
+# - various PKGBUILD variables:
+# - pkgbase/pkgname
+# - epoch/pkgver/pkgrel
+# - arch
+# - {,make,check}depends
+
+# End inmediately but print an useful message
+trap_exit() {
+ local signal=$1; shift
+ local msg=("$@")
+ term_title "error!"
+ echo
+ error "(%s) %s (leftovers on %s)" \
+ "${0##*/}" "$(print "${msg[@]}")" "${temp_dir}"
+ trap -- "$signal"
+ kill "-$signal" "$$"
+}
+
+setup_traps trap_exit
+
+source_pkgbuild() {
+ # Source this PKGBUILD, if it doesn't exist, exit
+ if ! load_PKGBUILD &>/dev/null; then
+ error "No PKGBUILD in %s" "$PWD"
+ exit 1
+ fi
+
+ # Save resources
+ # This is intentionally less exhaustive than unset_PKGBUILD()
+ # XXX: document which things we actually *want* to not be unset.
+ unset pkgdesc license groups backup install md5sums sha1sums \
+ sha256sums source options &>/dev/null
+
+ unset build package &>/dev/null
+
+ local _pkg
+ for _pkg in "${pkgname[@]}"; do
+ unset "package_${_pkg}" &>/dev/null || true
+ done
+
+ # This is the name of the package
+ name="${pkgbase:-${pkgname[0]}}"
+}
+
+source_pkgbuild
+
+# A temporary work dir and log file
+temp_dir="${1:-$(mktemp -dt ${name}-testpkg-XXXX)}"
+log="${temp_dir}/buildorder"
+
+# Mark array for DFS-based topological sort. See
+# https://en.wikipedia.org/wiki/Topological_sort for an explanation of
+# the algorithm. Key: package name, value: 0 for unvisited package, 1
+# during visit, 2 after visit.
+declare -A marks
+
+# Visit a PKGBUILD for graph building.
+visit_pkgbuild() {
+ # The name of the previous package
+ prev="${1}"
+
+ local name
+ source_pkgbuild
+
+ # If it's already built we don't bother
+ if is_built "${pkgname[0]}" "$(get_full_version "${pkgname[0]}")"; then
+ return
+ fi
+
+ # Detect cycle or already visited package
+ case "${marks[$name]:-0}" in
+ 1) msg2 "cycle found with %s depending on %s" $prev $name
+ exit 1;;
+ 2) return;;
+ esac
+
+ msg "%s (%s)" ${name} ${prev}
+
+ if ! in_array "${CARCH}" "${arch[@]}"; then
+ warning "%s isn't ported to %s yet" ${name} ${CARCH}
+ fi
+
+ # If the envvar I contains this package, ignore it and exit
+ if in_array "$name" $I; then
+ msg2 "%s ignored" ${name}
+ return
+ fi
+
+ # Mark the package as being visited
+ marks[$name]=1
+
+ # Recurse into dependencies
+ local d
+ for d in "${depends[@]}" "${makedepends[@]}" "${checkdepends[@]}"; do
+ # Cleanup dependency versions
+ d=$(echo $d | sed "s/[<>=].*//")
+
+ # Where's the pkgbuild?
+ local w=$(toru-where $d)
+
+ # Skip if not available
+ test -z "$w" && continue
+
+ # Go to this dir
+ pushd $w &>/dev/null
+
+ visit_pkgbuild "$name"
+
+ popd &>/dev/null
+ done
+
+ # Mark the package as finished
+ marks[$name]=2
+ # Append it to the reversed list of packages to build.
+ echo "$name" >> "${log}"
+}
+
+# If we specified a work dir on the cli it means we want to skip
+# dependency graph creation and jump to build whatever is there
+if [ -z "${1}" ]; then
+ # Visit the root PKGBUILD to make the graph.
+ visit_pkgbuild ""
+else
+ msg "Resuming build..."
+fi
+
+# enter work dir
+pushd "${temp_dir}" &>/dev/null
+nl ${log} | while read order pkg; do
+ # skip if already built
+ if test -f "${pkg}/built_ok"; then
+ warning "tried to build %s twice" "%{pkg}"
+ continue
+ fi
+
+ # where's this package?
+ local w="$(toru-where "$pkg")"
+ test -z "$w" && continue
+
+ # copy to work dir if not already
+ # this means you can make modifications to the pkgbuild during the
+ # graph build or remove the dir after a build failure and let dagpkg
+ # copy a new version
+ test -d "$pkg" || cp -r "$w" "$pkg"
+ pushd "$pkg" &>/dev/null
+
+ term_title "$pkg($order)"
+
+ msg "Building %s" ${pkg}
+
+ # upgrade the system
+ # this would probably have to go on HOOKPREBUILD if you're working
+ # outside chroots
+ sudo -E pacman -Syu --noconfirm
+
+ # run the pre build command from libretools.conf
+ if [[ -n "$HOOKPREBUILD" ]]; then
+ ${HOOKPREBUILD}
+ fi
+
+ # run the build command
+ ${FULLBUILDCMD}
+
+ # Run local release hook with $1 = $repo
+ if [[ -n "$HOOKLOCALRELEASE" ]]; then
+ ${HOOKLOCALRELEASE} "$(basename "$(dirname "$w")")"
+ fi
+
+ # it's built!
+ touch built_ok
+
+ popd &>/dev/null
+done
+
+popd &>/dev/null
+# cleanup
+rm -rf ${log} "${temp_dir}"
+
+term_title "done"