Permalink
Switch branches/tags
Nothing to show
Find file
Fetching contributors…
Cannot retrieve contributors at this time
executable file 509 lines (406 sloc) 12.4 KB
#!/bin/sh
#
# "multipy" is free software; you can redistribute it and/or modify
# it under the terms of the MIT license. See LICENSE for details.
#
PYTHON_VERSIONS="2.4 2.5 2.6 2.7 3.0 3.1 3.2 3.3 3.4"
PYTHON_URL_TEMPLATE="http://www.python.org/ftp/python/%s/Python-%s.tgz"
SETUPTOOLS_BASEURL="http://pypi.python.org/packages/source/s/setuptools/"
# From virtualenv, with slight modifications
ACTIVATE_SCRIPT=$(cat <<EOF
# This file must be used with "source bin/activate" *from the shell*
# you cannot run it directly
deactivate () {
if [ -n "\$_OLD_MULTIPY_PATH" ] ; then
PATH="\$_OLD_MULTIPY_PATH"
export PATH
unset _OLD_MULTIPY_PATH
fi
# This should detect bash and zsh, which have a hash command that must
# be called to get it to forget past commands. Without forgetting
# past commands the \$PATH changes we made may not be respected
if [ -n "\$BASH" -o -n "\$ZSH_VERSION" ] ; then
hash -r
fi
if [ -n "\$_OLD_MULTIPY_PS1" ] ; then
PS1="\$_OLD_MULTIPY_PS1"
export PS1
unset _OLD_MULTIPY_PS1
fi
unset MULTIPY_DIR
if [ ! "\$1" = "nondestructive" ] ; then
# Self destruct!
unset -f deactivate
fi
}
# unset irrelavent variables
deactivate nondestructive
MULTIPY_DIR="%s"
_OLD_MULTIPY_PATH="\$PATH"
PATH="\$MULTIPY_DIR/bin:\$PATH"
export PATH
_OLD_MULTIPY_PS1="\$PS1"
if [ "\`basename \"\$MULTIPY_DIR\"\`" = "__" ] ; then
# special case for Aspen magic directories
# see http://www.zetadev.com/software/aspen/
PS1="[\`basename \\\`dirname \"\$MULTIPY_DIR\"\\\`\`] \$PS1"
else
PS1="(\`basename \\\"\$MULTIPY_DIR\\\"\`)\$PS1"
fi
export PS1
# This should detect bash and zsh, which have a hash command that must
# be called to get it to forget past commands. Without forgetting
# past commands the \$PATH changes we made may not be respected
if [ -n "\$BASH" -o -n "\$ZSH_VERSION" ] ; then
hash -r
fi
EOF
)
### utils ###
error() {
echo "Error: $1" >&2
}
die() {
[ -n "$1" ] && error "$1"
[ -n "$2" ] && echo "For logs, see $tempdir/*.log" >&2
exit 1
}
# Usage: append VAR STRING
append() {
eval local tmp=\$$1
if [ -n "$tmp" ]; then
tmp="$tmp $2"
else
tmp=$2
fi
eval $1=\$tmp
}
# http://rubinium.org/blog/archives/2010/04/05/shell-script-version-compare-vercmp/
version_cmp() {
expr '(' "$1" : '\([^.]*\)' ')' '-' '(' "$2" : '\([^.]*\)' ')' '|' \
'(' "$1.0" : '[^.]*[.]\([^.]*\)' ')' '-' '(' "$2.0" : '[^.]*[.]\([^.]*\)' ')' '|' \
'(' "$1.0.0" : '[^.]*[.][^.]*[.]\([^.]*\)' ')' '-' '(' "$2.0.0" : '[^.]*[.][^.]*[.]\([^.]*\)' ')' '|' \
'(' "$1.0.0.0" : '[^.]*[.][^.]*[.][^.]*[.]\([^.]*\)' ')' '-' '(' "$2.0.0.0" : '[^.]*[.][^.]*[.][^.]*[.]\([^.]*\)' ')'
}
# X.Y
is_minor_version() {
echo $1 | grep -E -q '^[0-9]+.[0-9]+$'
}
# X.Y.Z
is_micro_version() {
echo $1 | grep -E -q '^[0-9]+.[0-9]+.[0-9]+$'
}
get_minor_version() {
echo "$1" | sed -r -e 's/^([0-9]+\.[0-9]+)(\.[0-9]+)?$/\1/'
}
is_supported_version() {
local minor_version v
minor_version=$(get_minor_version $1)
for v in $PYTHON_VERSIONS; do
[ "$minor_version" = "$v" ] && return
done
# Not found
return 1
}
# Assigns variables: versions
get_version_args() {
local version invalid
if [ -z "$1" ]; then
error "No Python versions specified!"
return 1
fi
if [ "$*" = "all" ]; then
versions=$PYTHON_VERSIONS
else
versions=$*
for version in $versions; do
is_supported_version $version || append invalid "$version"
done
if [ -n "$invalid" ]; then
error "Invalid/unsupported Python version(s): $invalid"
return 1
fi
fi
}
# Assigns variables: version
get_version_arg() {
local v
if [ -z "$1" ]; then
error "No Python version specified!"
return 1
fi
for v in $*; do
if [ -n "$version" ]; then
error "Single version expected"
return 1
fi
if ! is_supported_version $v; then
error "Invalid/unsupported Python version: $v"
return 1
fi
version=$v
done
}
try_cmd() {
local result
result=$(eval "$1" 2>/dev/null)
if [ $? -eq 0 -a -n "$result" ]; then
echo $result
else
return 1
fi
}
# Prints the number of CPUs, or 1 if unable to find out
cpu_count() {
try_cmd "nproc" && return
try_cmd "[ -e /proc/cpuinfo ] && grep ^processor /proc/cpuinfo | wc -l" && return
try_cmd "sysctl -n hw.ncpu" && return
echo 1
}
is_installed() {
[ -e $pythondir/$1 ]
}
# Assigns variables: latest_python_version
find_latest_python() {
echo "Checking for latest version of Python $1..."
for micro in $(seq 9 -1 -1); do
[ "$micro" != "-1" ] && v=$1.$micro || v=$1
url=$(printf "$PYTHON_URL_TEMPLATE" $v $v)
if wget -q --spider "$url"; then
echo "Found latest version: $v"
latest_python_version=$v
return 0
fi
done
return 1
}
# Assigns variables: tarball
download_python() {
local url
mkdir -p $sourcedir
url=$(printf "$PYTHON_URL_TEMPLATE" $1 $1)
tarball=$sourcedir/Python-$1.tgz
if [ -e $tarball ]; then
echo " Python-$1.tgz already downloaded"
else
echo " Downloading..."
wget -q -P $sourcedir "$url" || return 1
fi
}
# Assigns variables: latest_setuptools
install_setuptools() {
local pyversion=$1 pyminor
local version versions tarball srcdir python
pyminor=$(get_minor_version $pyversion)
# Setuptools doesn't support 2.4 or 2.5
if [ "$pyminor" = "2.4" -o "$pyminor" = "2.5" ]; then
echo " Setuptools does not support Python $pyminor, skip"
return
fi
if [ -z "$latest_setuptools" ]; then
echo " Checking for latest version of setuptools..."
wget -q -O $tempdir/index.html $SETUPTOOLS_BASEURL || return 1
versions=$(grep -E '>setuptools-[0-9.]+.tar.gz</a>' $tempdir/index.html | sed -r -e 's%.*>setuptools-([0-9.]+).tar.gz</a>.*%\1%')
latest_setuptools=0
for version in $versions; do
if [ "$(version_cmp $version $latest_setuptools)" -gt 0 ]; then
latest_setuptools=$version
fi
done
echo " Found latest version: $latest_setuptools"
tarball=setuptools-$latest_setuptools.tar.gz
if [ ! -e "$tarball" ]; then
echo " Downloading setuptools..."
wget -q -P $sourcedir "$SETUPTOOLS_BASEURL/$tarball" || return 1
else
echo " $tarball already downloaded"
fi
echo " Unpacking setuptools..."
rm -rf $tempdir/$tarball
gunzip -c $sourcedir/$tarball | tar -C $tempdir -xf - || return 1
fi
echo " Installing setuptools..."
srcdir=$tempdir/setuptools-$latest_setuptools
python=$pythondir/$pyversion/bin/python$pyminor
(cd $srcdir && $python setup.py install) \
> $tempdir/install-setuptools.$pyversion.log 2>&1 \
|| return 1
}
### commands ###
install() {
local versions version latest_python_version installed=0
get_version_args "$@" || die # -> versions
mkdir -p $tempdir
for version in $versions; do
if is_minor_version "$version"; then
if find_latest_python "$version"; then # -> latest_python_version
version=$latest_python_version
else
die "Nothing found"
fi
fi
if is_installed $version; then
echo "Python $version already installed, skipping"
continue
fi
echo "Installing Python $version..."
download_python $version || die "Unable to download Python $version"
srcdir=$tempdir/${tarball##*/}
srcdir=${srcdir%.tgz}
echo " Unpacking..."
rm -rf $srcdir
gunzip -c $tarball | tar -C $tempdir -xf - || die "Unpack failed"
echo " Configuring..."
(cd $srcdir && ./configure --prefix=$pythondir/$version) \
>$tempdir/configure.$version.log 2>&1 \
|| die "Configure failed" 1
echo " Compiling..."
(cd $srcdir && make -j "$jobs") \
>$tempdir/compile.$version.log 2>&1 \
|| die "Compile failed" 1
sed -n '/necessary bits/,/detect_modules/p' \
< $tempdir/compile.$version.log
echo " Installing..."
(cd $srcdir && make install) \
>$tempdir/install.$version.log 2>&1 \
|| die "Install failed" 1
if [ $no_setuptools -eq 0 ]; then
install_setuptools $version || die "Unable to install setuptools" 1
fi
# Write the activate script
printf "$ACTIVATE_SCRIPT" "$pythondir/$version" \
>$pythondir/$version/bin/activate
installed=1
done
if [ $installed -eq 1 ]; then
# Remove temporary files (disable with -k)
if [ $keep_tmp -eq 0 ]; then
echo "Cleaning up..."
rm -rf $tempdir/*
fi
echo "All done."
fi
}
remove() {
local version
for version in $(list); do
if [ "$1" = "all" -o "$1" = "$version" ]; then
echo "Removing Python $version..."
rm -rf $pythondir/$version
fi
done
echo "All done."
}
list() {
local version
for version in $pythondir/*; do
version=$(basename $version)
is_micro_version $version && echo $version
# Backwards compatibility
is_minor_version $version && echo $version
done
return 0
}
path() {
local version
get_version_arg "$@" || die # -> version
is_installed $version || die "Python $version is not installed"
echo $pythondir/$version
}
activate() {
local version
get_version_arg "$@" || die # -> version
is_installed $version || die "Python $version is not installed"
echo $pythondir/$version/bin/activate
}
### main ###
usage() {
cat >&2 <<EOF
Usage: $0 [options] command [args...]
Commands:
install version...|all
Installs the given Python versions or all supported versions.
If version is of the form X.Y (e.g. 3.2), install the latest
release of that series.
remove version...|all
Removes the given Python version or all installed versions.
list
Lists installed Python versions
path version
Print the path to the top directory of the given Python
version, e.g. /home/user/multipy/pythons/2.6.
activate version
Print the path of the activate script of the given Python
version, e.g. /home/user/multipy/python/2.6/bin/activate.
This is meant to be used to easily source the activate script
in the shell:
. \$(multipy activate 2.6)
Options:
-b BASEDIR The base directory [default: ~/multipy]
-k Keep temporary files and logs after installation
-n Don't install setuptools
-j N Compile with N jobs in parallel [default: $jobs_default]
Upon startup, ~/.multipyrc and ~/.config/multipyrc are loaded. The
following variables can be assigned in these files:
Assignment... has the same effect as:
basedir=BASEDIR -b BASEDIR
keep_tmp=1 -k
no_setuptools=1 -n
no_distribute=1 -n (for backwards compatibility)
jobs=2 -j 2
Copyright (C) 2011-2014 Petri Lehtinen. Licenced under the MIT license.
EOF
exit 2
}
basedir=$HOME/multipy
no_setuptools=0
keep_tmp=0
jobs_default=$(cpu_count)
# Load config files
: ${XDG_CONFIG_HOME:=$HOME/.config}
[ -f $HOME/.multipyrc ] && . $HOME/.multipyrc
[ -f $XDG_CONFIG_HOME/multipyrc ] && . $XDG_CONFIG_HOME/multipyrc
# Backwards compatibility
if [ -n "$no_distribute" ]; then
$no_setuptools=$no_distribute
fi
while getopts "b:knj:" opt; do
case $opt in
b)
basedir=$OPTARG
;;
n)
no_setuptools=1
;;
k)
keep_tmp=1
;;
j)
jobs=$OPTARG
;;
\?)
echo >&2
usage
;;
esac
done
[ -z "$jobs" ] && jobs=$jobs_default
# Make basedir absolute
[ "${basedir#/}" = "$basedir" ] && basedir=$(pwd)/$basedir
pythondir=$basedir/pythons
sourcedir=$basedir/sources
tempdir=$basedir/tmp
# No more arguments
[ "$opt" = "--" ] && usage
shift $(expr $OPTIND - 1)
cmd=$1
[ -z "$1" ] && usage
shift
case $cmd in
install|remove|list|path|activate)
;;
*)
usage
;;
esac
$cmd "$@"