Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sage
Path: blob/develop/build/bin/sage-spkg
4052 views
#!/usr/bin/env bash
#
#  sage-spkg: install a Sage package
#
#  This script is typically invoked by giving the command
#      sage {-i|-p} <options> <package name>...
#
#  sage-spkg itself only accepts one <package name>.
#
#      sage-spkg <options> <package name> [<installation tree>]
#
#  Options: see usage() below.
#
#  A package may assume that the following environment
#  variables are defined:
#
#      SAGE_ROOT      -- root directory of sage distribution
#      SAGE_LOCAL     -- prefix where packages are installed (usually $SAGE_ROOT/local)
#      SAGE_INST_LOCAL-- prefix where to install this package;
#                        this is set by the optional argument <installation tree>
#                        and defaults to $SAGE_LOCAL.
#      SAGE_DISTFILES -- directory that stores upstream tarballs
#      SAGE_DESTDIR   -- temporary root the package will be installed to
#      PKG_BASE       -- the base name of the package itself (e.g. 'patch')
#      PKG_VER        -- the version number of the package
#      PKG_NAME       -- $PKG_BASE-$PKG_VER
#      LIBRARY_PATH, PYTHONPATH, LD_LIBRARY_PATH, DYLD_LIBRARY_PATH
#      CC, CXX, CFLAGS, CXXFLAGS, LDFLAGS, MAKE
#
#  Your package script should try to build using the giving CC, CXX,
#  CFLAGS, MAKE, etc, via a file spkg-install in your script.
#
#  This script does the following:
#
#      1. Set environment variables (by calling sage-env)
#      2. Extract the metadata and upstream sources into a build directory
#      3. Run the script in the package called spkg-install
#      4. Return error 1 if anything goes wrong.
#
# AUTHORS:
#
# - Robert Bradshaw, R. Andrew Ohana (2013): #14480: extend functionality to
#   support the unified git repository.
#
# - Jeroen Demeyer (2012-02-27): #12602: refactor code to find packages,
#   download them and extract them.
#
# - Jeroen Demeyer (2012-02-27): #12479: big reorganization.
#
# - Volker Braun, Jeroen Demeyer (2012-01-18): #11073: remove the
#   spkg/base repository, move this file from local/bin/sage-spkg to
#   spkg/bin/sage-spkg.
#
# - William Stein, John Palmieri and others (Sage 4.8 and earlier).
#
#*****************************************************************************
#  Distributed under the terms of the GNU General Public License (GPL)
#  as published by the Free Software Foundation; either version 2 of
#  the License, or (at your option) any later version.
#                  http://www.gnu.org/licenses/
#*****************************************************************************

# Avoid surprises with character ranges [a-z] in regular expressions
# See Issue #15791; some locales can produce different results for
# character ranges; using C.UTF-8 to ensure UTF-8 default encoding in Python
# introduces extra complications, see #30053, so we don't do it, but
# assume we are on Python3.x,  for x at least 7.
export LC_ALL=C

usage()
{
cat <<EOF
Usage: sage {-i|-p} <options> <package name>

Search Sage's list of packages (see 'sage --package list') for a
matching package, and if a match is found, install it.

Modes of operation (provide at most one):
   -d, --download-only: only download the package
   -b, --build-and-stage-only: build and install (stage) only,
       do not run post-install or check
   -p, --post-install-only: unload the staged installation
       directory and run other post-installation steps only
   -x, --check-only: exclusively run the test suite;
       this may assume that:
        * the package has been installed already and/or that
        * the build directory has not been deleted
   -e, --erase-build-directory-only: erase (delete) the
       build directory only
   --info: print information on the package only
   --help: print this help only

Other options:
   -y: automatically reply "y" for all prompts regarding
       experimental and old-style packages; warning: there
       is no guarantee that these packages will build correctly;
       use at your own risk
   -n: automatically reply "n" for all prompts regarding
       experimental and old-style packages
   -o: allow fetching the package from its upstream URL
       when it is not available from the Sage mirrors (yet)
   -c: after installing, run the test suite for the package;
       exit with an error on test suite failures
   -w: after installing, run the test suite for the package;
       print a warning on test suite failures
   -s: save (do not delete) the build directory,
       even when the installation was successful
EOF
}

# error_msg(header, command)
# This is for printing an error message if something went wrong.
# The first argument is the header to print. If given, the second
# argument should be some proposed command to run in the subshell
# such as "make".
error_msg()
{
cat >&2 <<MESSAGE
************************************************************************
$1
************************************************************************
Please email sage-devel (http://groups.google.com/group/sage-devel)
explaining the problem and including the log files
  $SAGE_LOGS/$PKG_NAME.log
and
  $SAGE_ROOT/config.log
Describe your computer, operating system, etc.
MESSAGE

if [ -n "$2" ]; then
cat >&2 <<MESSAGE
If you want to try to fix the problem yourself, *don't* just cd to
`pwd` and type '$2' or whatever is appropriate.
Instead, the following commands setup all environment variables
correctly and load a subshell for you to debug the error:
  (cd '`pwd`' && '$SAGE_ROOT/sage' --buildsh)
When you are done debugging, you can type "exit" to leave the subshell.
MESSAGE
fi

cat >&2 <<MESSAGE
************************************************************************
MESSAGE
}

exit_with_error_msg()
{
    error_msg "$@"
    exit 1
}

# Handle -n, -t, -q options for recursive make
# See Issue #12016.
if echo "$MAKE $MAKEFLAGS -$MAKEFLAGS" |grep '[ ]-[A-Za-z]*[qnt]' >/dev/null; then
    if echo "$MAKE $MAKEFLAGS -$MAKEFLAGS" |grep '[ ]-[A-Za-z]*q' >/dev/null; then
        # Pretend the target is *not* up-to-date
        exit 1
    else
        exit 0
    fi
fi

if [ $# -eq 0 ]; then
   usage
   exit 0
fi

##################################################################
# Set environment variables
##################################################################

# The following sets environment variables for building packages.
# Since this is sourced, it returns a non-zero value on errors rather
# than exiting.  Using dot suggested by W. Cheung.
. sage-env-config
. sage-env || exit_with_error_msg "Error setting environment variables by sourcing sage-env"
. sage-build-env-config
. sage-build-env || exit_with_error_msg "Error setting environment variables by sourcing sage-build-env"

# Remove '.' from PYTHONPATH, to avoid trouble with setuptools / easy_install
# (cf. #10192, #10176):
if [ -n "$PYTHONPATH" ]; then
    # We also collapse multiple slashs into a single one (first substitution),
    # remove leading './'s and trailing '/.'s (second and third), and
    # remove leading, trailing and redundant ':'s (last three substitutions):
    new_pp=`echo ":$PYTHONPATH:" \
        | sed \
        -e 's|//*|/|g' \
        -e 's|:\(\./\)\{1,\}|:|g' \
        -e 's|\(/\.\)\{1,\}:|:|g' \
        -e 's|\(:\.\)\{1,\}:|:|g' \
        -e 's|::*|:|g' -e 's|^::*||' -e 's|::*$||'`

    if [ "$PYTHONPATH" != "$new_pp" ]; then
        echo "Cleaning up PYTHONPATH:"
        echo "  Old: \"$PYTHONPATH\""
        echo "  New: \"$new_pp\""
        PYTHONPATH=$new_pp
        export PYTHONPATH # maybe redundant, but in any case safe
    fi
fi

##################################################################
# Handle special command-line options
##################################################################

# Parse options
INFO=0
YES=0
KEEP_EXISTING=0
INSTALL=1
POST_INSTALL=1
ERASE_ONLY=0
MODE_SWITCHES=
while true; do
    case "$1" in
        --info)
            MODE_SWITCHES+=", $1"
            INFO=1;;
        --help)
            usage
            exit 0;;
        -y)
            YES=1;;
        -n)
            YES=-1;;
        -d|--download-only)
            MODE_SWITCHES+=", $1"
            SAGE_INSTALL_FETCH_ONLY=1;;
        -s)
            export SAGE_KEEP_BUILT_SPKGS=yes;;
        -c|--check)
            export SAGE_CHECK=yes;;
        -w|--check-warning-only)
            export SAGE_CHECK=warn;;
        -b|--build-and-stage-only)
            MODE_SWITCHES+=", $1"
            POST_INSTALL=0; export SAGE_CHECK=no;;
        -p|--post-install-only)
            MODE_SWITCHES+=", $1"
            INSTALL=0; export SAGE_CHECK=no;;
        -x|--check-only)
            MODE_SWITCHES+=", $1"
            INSTALL=0; POST_INSTALL=0; export SAGE_CHECK=yes;;
        -k|--keep-existing)
            KEEP_EXISTING=yes;;
        -e|--erase-build-directory-only)
            MODE_SWITCHES+=", $1"
            ERASE_ONLY=1;;
        -o|--allow-upstream)
            SAGE_DOWNLOAD_FILE_OPTIONS+=" --allow-upstream";;
        -*)
            echo >&2 "Error: unknown option '$1'"
            exit 2;;
        *) break;;
    esac
    shift
done
MODE_SWITCHES=${MODE_SWITCHES#, }
case "$MODE_SWITCHES" in
    *,*)
        echo >&2 "Error: at most one of the mode switches may be given, got $MODE_SWITCHES"
        echo >&2
        usage
        exit 1
        ;;
esac
# One should be able to install a package using
# sage -i <package-name>

PKG_SRC="$1"
# Does PKG_SRC contain a slash?
if echo "$PKG_SRC" | grep / >/dev/null; then
    echo >&2 "Error: Installing old-style SPKGs is no longer supported"
    exit 1
fi
PKG_NAME="$PKG_SRC"
export PKG_BASE=`echo "$PKG_NAME" | sed 's/-.*//'` # strip version number

case $# in
    1)
        SAGE_INST_LOCAL="$SAGE_LOCAL"
        ;;
    2)
        SAGE_INST_LOCAL="$2"
        ;;
    *)
        usage
        exit 1
        ;;
esac
export SAGE_INST_LOCAL

if [ -z "$SAGE_BUILD_DIR" ]; then
    export SAGE_BUILD_DIR="$SAGE_INST_LOCAL/var/tmp/sage/build"
fi

export SAGE_SPKG_INST="$SAGE_INST_LOCAL/var/lib/sage/installed"
export SAGE_SPKG_SCRIPTS="$SAGE_INST_LOCAL/var/lib/sage/scripts"
export SAGE_SPKG_WHEELS="$SAGE_INST_LOCAL/var/lib/sage/wheels"

# PKG_SRC should look like "package-VERSION" or just "package".
# VERSION, if provided, must match the version in build/pkgs.
PKG_VER="${PKG_NAME#${PKG_BASE}}"
export PKG_VER="${PKG_VER#-}"
eval $(sage-package properties --format=shell $PKG_BASE)
eval PKG_SCRIPTS=\$path_$PKG_BASE \
     PKG_TYPE=\$type_$PKG_BASE \
     PKG_SRC_TYPE=\$source_$PKG_BASE \
     LOCAL_PKG_VER=\$version_with_patchlevel_$PKG_BASE
if [ -n "$PKG_VER" -a "$PKG_VER" != "$LOCAL_PKG_VER" ]; then
    echo >&2 "Error: Selecting a different version of a package is no longer supported"
    exit 1
fi
if [ -z "$PKG_VER" ]; then
    export PKG_NAME="${PKG_BASE}"
else
    export PKG_NAME="${PKG_BASE}-${PKG_VER}"
fi

# Set the $SAGE_DESTDIR variable to be passed to the spkg-install
# script (the script itself could set this, but better to standardize
# this in one place)
export SAGE_DESTDIR="${SAGE_BUILD_DIR}/${PKG_NAME}/inst"

# The actual prefix where the installation will be staged. This is the
# directory that you need to work in if you want to change the staged
# installation tree (before final installation to $SAGE_INST_LOCAL) at the
# end of spkg-install.
export SAGE_DESTDIR_LOCAL="${SAGE_DESTDIR}${SAGE_INST_LOCAL}"

INSTALLED_SCRIPTS_DEST="$SAGE_SPKG_SCRIPTS/$PKG_BASE"
INSTALLED_SCRIPTS="prerm piprm postrm check"
WRAPPED_SCRIPTS="build install preinst pipinst postinst $INSTALLED_SCRIPTS"


warning_for_experimental_packages() { ############################
case "$PKG_TYPE:$PKG_SRC_TYPE" in
  experimental:normal|experimental:wheel)
    if [ $YES != 1 ]; then
        echo "Error: The package $PKG_NAME is marked as experimental."
        echo "Use 'sage -i -y $PKG_BASE' to force installation of this package"
        echo "or use the configure option --enable-experimental-packages"
        exit 1
    fi;;
esac
} ############################## warning_for_experimental_packages

ensure_pkg_src() { ###############################################
case "$PKG_SRC_TYPE" in
    normal|wheel)
        PKG_SRC=$(sage-package download $SAGE_DOWNLOAD_FILE_OPTIONS $PKG_BASE) || exit_with_error_msg "Error downloading tarball of $PKG_BASE"
        # Do a final check that PKG_SRC is a file with an absolute path
        cd /
        if [ ! -f "$PKG_SRC" ]; then
            echo >&2 "Error: spkg file '$PKG_SRC' not found."
            echo >&2 "This shouldn't happen, it is a bug in the sage-spkg script."
            exit 1
        fi

        # Go back to SAGE_ROOT where we have less chance of completely messing
        # up the system if we do something wrong.
        cd "$SAGE_ROOT" || exit $?

        # If SAGE_SPKG_COPY_UPSTREAM is set, it should be the name of a directory
        # to which all upstream files are copied. This is used in sage-sdist.
        if [ -n "$SAGE_SPKG_COPY_UPSTREAM" ]; then
            mkdir -p "$SAGE_SPKG_COPY_UPSTREAM" && cp -p "$PKG_SRC" "$SAGE_SPKG_COPY_UPSTREAM"
            if [ $? -ne 0 ]; then
                error_msg "Error copying upstream tarball to directory '$SAGE_SPKG_COPY_UPSTREAM'"
                exit 1
            fi
        fi
        ;;
    none)
        echo >&2
        echo >&2 "Note: $PKG_BASE is a dummy package that the Sage distribution uses"
        echo >&2 "to provide information about equivalent system packages."
        echo >&2 "It cannot be installed using the Sage distribution."
        echo >&2 "Please install it manually, for example using the system packages"
        echo >&2 "recommended at the end of a run of './configure'"
        echo >&2 "See below for package-specific information."
        echo >&2
        sage-spkg-info $PKG_BASE
        echo >&2
        echo >&2 "Error: $PKG_BASE is a dummy package and "
        echo >&2 "cannot be installed using the Sage distribution."
        exit 1
        ;;
esac
} ################################################# ensure_pkg_src

setup_directories() { ############################################

for dir in "$SAGE_SPKG_INST" "$SAGE_SPKG_SCRIPTS" "$SAGE_BUILD_DIR"; do
    mkdir -p "$dir" || exit_with_error_msg "Error creating directory $dir"
done

# Issue #5852: check write permissions
if [ ! -w "$SAGE_BUILD_DIR" ]; then
    exit_with_error_msg "Error: no write access to build directory $SAGE_BUILD_DIR"
fi
if [ ! -d "$SAGE_INST_LOCAL" ]; then
    # If you just unpack Sage and run "sage -p <pkg>" then local does not yet exist
    mkdir "$SAGE_INST_LOCAL"
fi
if [ ! -w "$SAGE_INST_LOCAL" ]; then
    exit_with_error_msg "Error: no write access to installation directory $SAGE_INST_LOCAL"
fi

# Make absolutely sure that we are in the build directory before doing
# a scary "rm -rf" below.
cd "$SAGE_BUILD_DIR" || exit $?


if [ "x$SAGE_KEEP_BUILT_SPKGS" != "xyes" ]; then
    rm -rf "$PKG_NAME"
else
    if [ -e "$PKG_NAME" ]; then
        echo "Moving old directory $PKG_NAME to $SAGE_BUILD_DIR/old..."
        mkdir -p old || exit_with_error_msg "Error creating directory $SAGE_BUILD_DIR/old"
        rm -rf old/"$PKG_NAME"
        mv "$PKG_NAME" old/
    fi
fi

if [ -e "$PKG_NAME" ]; then
    exit_with_error_msg "Error (re)moving $PKG_NAME"
fi
} ############################################## setup_directories

extract_the_package() { ##########################################

cd "$SAGE_BUILD_DIR" || exit $?
echo "Setting up build directory $SAGE_BUILD_DIR/$PKG_NAME"
case "$PKG_SRC_TYPE" in
  script)
    # Transplant the 'src' symlink, copy scripts.
    mkdir -p "$PKG_NAME"
    if [ -d "$PKG_SCRIPTS"/src ]; then
        ln -s $(cd "$PKG_SCRIPTS"/src && pwd -P) "$PKG_NAME"/src
    fi
    for a in $WRAPPED_SCRIPTS; do
        if [ -r "$PKG_SCRIPTS"/spkg-$a.in ]; then
            cp "$PKG_SCRIPTS"/spkg-$a.in "$PKG_NAME"/
        elif [ -x "$PKG_SCRIPTS"/spkg-$a ]; then
            cp "$PKG_SCRIPTS"/spkg-$a "$PKG_NAME"/
        fi
    done
    cd "$PKG_NAME" || exit $?
    ;;
  normal|wheel)
    # Copy whole directory, resolving symlinks
    cp -RLp "$PKG_SCRIPTS" "$PKG_NAME"
    cd "$PKG_NAME" || exit $?
    case "$PKG_SRC" in
    *.whl)
        # (Platform-independent) wheel
        # Do not extract, do not create a src directory,
        # just copy to dist/ and create a simple install script.
        mkdir -p dist
        cp "$PKG_SRC" dist/
        if [ ! -f spkg-install.in ]; then
            echo "sdh_store_and_pip_install_wheel ." > spkg-install.in
        fi
        ;;
    *)
        # Source tarball
        sage-uncompress-spkg -d src "$PKG_SRC" || exit_with_error_msg "Error: failed to extract $PKG_SRC"
        cd src
        sage-apply-patches || exit_with_error_msg "Error applying patches"
        cd ..
        ;;
    esac
    ;;
  *)
    error_msg "Unhandled source type $PKG_SRC_TYPE"
    ;;
esac
if [ "$SAGE_KEEP_BUILT_SPKGS" = "yes" ]; then
    touch .keep
fi
} ############################################ extract_the_package

# The package has been extracted,
prepare_for_installation() { #####################################

# Rewrites the given bash pseudo-script with a boilerplate header that includes
# the shebang line and sourcing sage-env.  Make sure the name of the script is
# passed in as an absolute path.
write_script_wrapper() {
    local script="$1"
    local script_dir="$2"
    local fallback_script_dir="$3"

    trap "echo >&2 Error: Unexpected error writing wrapper script for $script; exit \$_" ERR

    if head -1 "$script.in" | grep '^#!.*$' >/dev/null; then
        echo >&2 "Error: ${script##*/} should not contain a shebang line; it will be prepended automatically."
        exit 1
    fi

    local tmpscript="$(dirname "$script")/.tmp-${script##*/}"

    cat > "$tmpscript" <<__EOF__
#!/usr/bin/env bash

export SAGE_ROOT="$SAGE_ROOT"
export SAGE_SRC="$SAGE_SRC"
export SAGE_PKG_DIR="$script_dir"
export SAGE_SPKG_SCRIPTS="$SAGE_SPKG_SCRIPTS"
export SAGE_SPKG_WHEELS="$SAGE_SPKG_WHEELS"

export PKG_NAME="$PKG_NAME"
export PKG_BASE="$PKG_BASE"
export PKG_VER="$PKG_VER"

for lib in "\$SAGE_ROOT/build/bin/sage-dist-helpers" "\$SAGE_SRC/bin/sage-src-env-config" "\$SAGE_SRC/bin/sage-env-config" "\$SAGE_SRC/bin/sage-env" "\$SAGE_ROOT/build/bin/sage-build-env-config" "\$SAGE_ROOT/build/bin/sage-build-env"; do
    source "\$lib"
    if [ \$? -ne 0 ]; then
        echo >&2 "Error: failed to source \$lib"
        echo >&2 "Is \$SAGE_ROOT the correct SAGE_ROOT?"
        exit 1
    fi
done

export SAGE_INST_LOCAL="$SAGE_INST_LOCAL"

sdh_guard
if [ \$? -ne 0 ]; then
    echo >&2 "Error: sdh_guard not found; Sage environment was not set up properly"
    exit 1
fi

__EOF__
    if [ -n "$fallback_script_dir" ]; then
        cat >> "$tmpscript" <<__EOF__
cd "\$SAGE_PKG_DIR" 2>/dev/null || cd "$fallback_script_dir"

__EOF__
    else
        cat >> "$tmpscript" <<__EOF__
cd "\$SAGE_PKG_DIR"
if [ \$? -ne 0 ]; then
    echo >&2 "Error: could not cd to the package build directory \$SAGE_PKG_DIR"
    exit 1
fi

__EOF__
    fi
    cat "$script.in" >> "$tmpscript"
    mv "$tmpscript" "$script"
    chmod +x "$script"

    trap - ERR
}

# Prepare script for uninstallation of packages that use sdh_pip_install
# or sdh_store_and_pip_install_wheel.
touch spkg-piprm.in

# Prepare script for deferred installation of packages that use sdh_pip_install
# or sdh_store_and_pip_install_wheel.
touch spkg-pipinst.in

for script in $WRAPPED_SCRIPTS; do
    # 'Installed' scripts are not run immediately out of the package build
    # directory, and may be run later.
    # For the installed *rm scripts, set their root directory to $SAGE_ROOT.
    # For the installed check scripts, some need the temporary build directory,
    # others are OK with $PKG_SCRIPTS. So try to run out of the temporary
    # build directory but fall back to the latter.
    case $script in
        check) script_dir="$(pwd)"
               fallback_script_dir="$PKG_SCRIPTS"
               ;;
        *rm)   script_dir="\$SAGE_ROOT"
               fallback_script_dir=
               ;;
        *)     script_dir="$(pwd)"
               fallback_script_dir=
               ;;
    esac

    script="spkg-$script"

    if [ -f "$script.in" ]; then
        write_script_wrapper "$(pwd)/$script" "$script_dir" "$fallback_script_dir"
    fi
done
} ####################################### prepare_for_installation

actually_build_and_install() { ###################################

case "$PKG_SRC" in
    *.whl)
        # Silence is golden.
        ;;
    *)
        echo "Host system: $(uname -a)"
        echo "C compiler: $CC$($CC -v 2>&1 | while read -r line; do echo -n ", $line"; done)"
        ;;
esac

# Poison the proxy variable to forbid downloads in spkg-install
# for normal/wheel standard packages
case "$PKG_TYPE:$PKG_SRC_TYPE" in
    standard:normal|standard:wheel)
        export http_proxy=http://192.0.2.0:5187/
        export https_proxy=$http_proxy
        export ftp_proxy=$http_proxy
        export rsync_proxy=$http_proxy
        ;;
esac

# Make sage-logger show the full logs
unset SAGE_SILENT_BUILD
unset V

# First uninstall the previous version of this package, if any
if [ "$KEEP_EXISTING" != "yes" ]; then
    sage-spkg-uninstall "$PKG_BASE" "$SAGE_INST_LOCAL"
fi

# To work around #26996: Create lib and set a symlink so that writes into lib64/ end up in lib/
(mkdir -p "$SAGE_DESTDIR_LOCAL/lib" && cd "$SAGE_DESTDIR_LOCAL" && ln -sf lib lib64)

# Run the pre-install script, if any
if [ -f spkg-preinst ]; then
    sage-logger -P spkg-preinst "$SAGE_SUDO ./spkg-preinst" || exit_with_error_msg "Error running the preinst script for $PKG_NAME."
fi

if [ -f spkg-build ]; then
    # Package has both spkg-build and spkg-install; execute the latter with SAGE_SUDO
    sage-logger -P spkg-build "./spkg-build" || exit_with_error_msg "Error building package $PKG_NAME" "make"
    sage-logger -P spkg-install "$SAGE_SUDO ./spkg-install" || exit_with_error_msg "Error installing package $PKG_NAME" "make"
else
    # Package only has spkg-install
    sage-logger -P spkg-install "./spkg-install" || exit_with_error_msg "Error installing package $PKG_NAME" "make"
fi
} ##################################### actually_build_and_install

unload_destdir() { ###############################################
# To work around #26996: Remove the symlink set,
# or we get "cp: cannot overwrite directory"
rm -f "$SAGE_DESTDIR_LOCAL/lib64"

# All spkgs should eventually support this, but fall back on old behavior in
# case DESTDIR=$SAGE_DESTDIR installation was not used
if [ -d "$SAGE_DESTDIR" ]; then
    echo "Moving package files from temporary location $SAGE_DESTDIR to $SAGE_INST_LOCAL"
    # Some `find` implementations will put superfluous slashes in the
    # output if we give them a directory name with a slash; so make sure
    # any trailing slash is removed; https://github.com/sagemath/sage/issues/26013
    PREFIX="${SAGE_DESTDIR_LOCAL%/}"

    rm -f "$PREFIX"/lib/*.la || exit_with_error_msg "Error deleting unnecessary libtool archive files"

    # Generate installed file manifest
    FILE_LIST="$(cd "$PREFIX" && find . -type f -o -type l | sed 's|^\./||' | sort)"

    # Copy files into $SAGE_INST_LOCAL
    $SAGE_SUDO cp -Rp "$PREFIX/." "$SAGE_INST_LOCAL" || exit_with_error_msg "Error moving files for $PKG_NAME."

    # Remove the $SAGE_DESTDIR entirely once all files have been moved to their
    # final location.
    rm -rf "$SAGE_DESTDIR"
else
    echo "The temporary location $SAGE_DESTDIR does not exist; has it been unloaded already?"
    exit 1
fi

# At this stage the path in $SAGE_DESTDIR no longer exists, so the variable
# should be unset
unset SAGE_DESTDIR
unset SAGE_DESTDIR_LOCAL
} ################################################# unload_destdir

install_scripts() { ##############################################
# Some spkg scripts, if they exist, should also be installed to
# $SAGE_SPKG_SCRIPTS; they are not included in the package's manifest, but are
# removed by sage-spkg-uninstall

if [ ! -f "$INSTALLED_SCRIPTS_DEST"/spkg-requirements.txt ]; then
    # No packages to uninstall with pip, so remove the prepared uninstall script
    # and the prepared deferred installation script
    rm -f spkg-piprm spkg-piprm.in spkg-pipinst spkg-pipinst.in
fi

for script in $INSTALLED_SCRIPTS; do
    script="spkg-$script"

    if [ -f "$script" ]; then
        mkdir -p "$INSTALLED_SCRIPTS_DEST" || exit_with_error_msg "Error creating the spkg scripts directory $INSTALLED_SCRIPTS_DEST."
        cp -a "$script" "$INSTALLED_SCRIPTS_DEST" || exit_with_error_msg "Error copying the $script script to $INSTALLED_SCRIPTS_DEST."
    fi
done
} ################################################ install_scripts

post_install() { #################################################
# Run the post-install script, if any
# But first complete the delayed installation of wheels.
if [ -f spkg-pipinst ]; then
    sage-logger -P spkg-pipinst "$SAGE_SUDO ./spkg-pipinst" || exit_with_error_msg "Error running the pipinst script for $PKG_NAME."
fi
if [ -f spkg-postinst ]; then
    sage-logger -P spkg-postinst "$SAGE_SUDO ./spkg-postinst" || exit_with_error_msg "Error running the postinst script for $PKG_NAME."
fi
} ################################################### post_install

run_test_suite() { ###############################################
# Note: spkg-check tests are run after the package has been copied into
# SAGE_INST_LOCAL.  It might make more sense to run the tests before, but the
# spkg-check scripts were written before use of DESTDIR installs, and so
# fail in many cases.  This might be good to change later.
    if ! cd "$SAGE_BUILD_DIR/$PKG_NAME" 2>/dev/null; then
        cd "$PKG_SCRIPTS" || exit $?
    fi

    if [ -f "$INSTALLED_SCRIPTS_DEST"/spkg-check ]; then
        echo "Running the test suite for $PKG_NAME..."
        export PKG_BASE
        sage-logger -P spkg-check "$INSTALLED_SCRIPTS_DEST"/spkg-check
        if [ $? -ne 0 ]; then
            TEST_SUITE_RESULT="failed"
            if [ "$SAGE_CHECK" = "warn" ]; then
                # The following warning message must be consistent
                # with SAGE_ROOT/build/make/install (see #32781)
                error_msg "Warning: Failures testing package $PKG_NAME (ignored)" "make check"
            else
                exit_with_error_msg "Error testing package $PKG_NAME" "make check"
            fi
        else
            TEST_SUITE_RESULT="passed"
            echo "Passed the test suite for $PKG_NAME."
        fi
    elif [ -f "$PKG_SCRIPTS"/spkg-check.in -o -f "$PKG_SCRIPTS"/spkg-check ]; then
        echo "The test suite for $PKG_NAME cannot be run because the script"
        echo "$INSTALLED_SCRIPTS_DEST/spkg-check"
        echo "is missing. Install/re-install package $PKG_NAME to run the test suite."
        exit 1
    else
        echo "Package $PKG_NAME has no test suite."
        TEST_SUITE_RESULT="not available"
    fi
} ################################################# run_test_suite

write_installation_record() { ####################################
# For each line in $FILE_LIST, enclose in double quotes:
NEW_LIST=""
for f in $FILE_LIST; do
         NEW_LIST+="\"$f\"
"
done
# Now remove the last line (it's blank), indent each line (skipping
# the first) and append a comma (skipping the last).
FILE_LIST="$(echo "$NEW_LIST" | sed '$d' | sed '2,$s/^/        /; $!s/$/,/')"

# Mark that the new package has been installed (and tested, if
# applicable).
PKG_NAME_INSTALLED="$SAGE_SPKG_INST/$PKG_NAME"
cat > "$PKG_NAME_INSTALLED" << __EOF__
{
    "package_name": "$PKG_BASE",
    "package_version": "$PKG_VER",
    "install_date": "$(date)",
    "system_uname": "$(uname -a)",
    "sage_version": "$(cat "${SAGE_ROOT}/VERSION.txt")",
    "test_result": "$TEST_SUITE_RESULT",
    "files": [
        $FILE_LIST
    ]
}
__EOF__
} ###################################### write_installation_record

delete_the_temporary_build_directory() { #########################
    echo "Deleting build directory $SAGE_BUILD_DIR/$PKG_NAME"
    # On Solaris, the current working directory cannot be deleted,
    # so we "cd" out of $SAGE_BUILD_DIR/$PKG_NAME. See #12637.
    cd "$SAGE_BUILD_DIR"
    rm -rf "$SAGE_BUILD_DIR/$PKG_NAME"
} ########################### delete_the_temporary_build_directory

delete_the_temporary_build_directory_if_required() { #############
if [ ! -e "$SAGE_BUILD_DIR/$PKG_NAME/.keep" ]; then
    delete_the_temporary_build_directory
else
    echo "You can safely delete the build directory $SAGE_BUILD_DIR/$PKG_NAME"
fi
} ############### delete_the_temporary_build_directory_if_required


##################################################################
# MAIN
##################################################################

if [ $INFO -ne 0 ]; then
    exec sage-spkg-info $PKG_BASE
fi

if [ $ERASE_ONLY = 1 ]; then
    delete_the_temporary_build_directory
    exit 0
fi

if [ $INSTALL = 1 ]; then
    warning_for_experimental_packages
    ensure_pkg_src
fi

if [ -n "$SAGE_INSTALL_FETCH_ONLY" ]; then
    exit 0
fi

if [ $INSTALL = 1 ]; then
    setup_directories
    extract_the_package
    prepare_for_installation
    actually_build_and_install
fi

if [ $POST_INSTALL = 1 ]; then
    cd "$SAGE_BUILD_DIR/$PKG_NAME" || exit $?
    unload_destdir
    install_scripts
    post_install
fi

if [ "$SAGE_CHECK" = "yes" -o "$SAGE_CHECK" = "warn" ]; then
    run_test_suite
fi

if [ $POST_INSTALL = 1 ]; then
    write_installation_record
    delete_the_temporary_build_directory_if_required
    echo "Finished installing $PKG_NAME"
fi