#!/usr/bin/env bash # # dagpkg - create a directed graph of package dependencies and build # them in topological order # Copyright (C) 2014 Nicolás Reynolds # Copyright (C) 2014 Michał Masłowski # Copyright (C) 2017 Luke Shumaker # # 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 . set -e . "$(librelib messages)" . "$(librelib conf)" # Globals: # - temp_dir # - 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" "$$" } source_pkgbuild() { # Source this PKGBUILD, if it doesn't exist, exit if ! load_PKGBUILD &>/dev/null; then error "No PKGBUILD in %s" "$PWD" exit $EXIT_FAILURE 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]}}" } # Visit a PKGBUILD for graph building. visit_pkgbuild() { log=$1 # The file to appund our results to prev=$2 # The name of the previous package 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 $EXIT_FAILURE ;; 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 w for d in "${depends[@]}" "${makedepends[@]}" "${checkdepends[@]}"; do # Cleanup dependency versions d=${d%%[<>=]*} # Where's the pkgbuild? w=$(toru-where "$d") # Skip if not available test -z "$w" && continue # Go to this dir pushd "$w" &>/dev/null visit_pkgbuild "$log" "$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}" } main() { # Source variables from libretools declare -i ret=0 load_conf libretools.conf FULLBUILDCMD || ret=$ # and HOOK{PRE,POST}BUILD, which are optional load_conf makepkg.conf CARCH || ret=$? [[ $ret = 0 ]] || exit $ret setup_traps trap_exit source_pkgbuild # A temporary work dir and log file temp_dir="${1:-$(mktemp -dt "${name}-testpkg-XXXX")}" local 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 -gA marks # 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 "$log" "" else msg "Resuming build..." fi # enter work dir pushd "${temp_dir}" &>/dev/null local w while read -r 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? 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 "%s(%s)" "$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 < <(nl "$log") popd &>/dev/null # cleanup rm -rf "${log}" "${temp_dir}" term_title "done" } main "$@"