This commit is contained in:
utahta
2010-10-21 01:58:22 +09:00
parent fb019179db
commit 8a971f7c85
-1
View File
@@ -1 +0,0 @@
pythonbrew.py
Executable
+595
View File
@@ -0,0 +1,595 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
import os
import sys
import urllib
import errno
import re
import shutil
import filecmp
import subprocess
from HTMLParser import HTMLParser
from optparse import OptionParser
VERSION = "0.3"
if os.environ.has_key("PYTHONBREW_ROOT"):
ROOT = os.environ["PYTHONBREW_ROOT"]
else:
ROOT = "%s/python/pythonbrew" % os.environ["HOME"]
PYTHONDLSITE = "http://www.python.org/ftp/python/%s/%s"
EZSETUPDLSITE = "http://peak.telecommunity.com/dist/ez_setup.py"
PATH_PYTHONS = "%s/pythons" % ROOT
PATH_BUILD = "%s/build" % ROOT
PATH_DISTS = "%s/dists" % ROOT
PATH_ETC = "%s/etc" % ROOT
parser = OptionParser(usage="%prog COMMAND [OPTIONS]",
version=VERSION,
add_help_option=False)
parser.add_option(
'-h', '--help',
dest='help',
action='store_true',
help='Show help')
parser.disable_interspersed_args()
command_dict = {}
def add_command(command):
command_dict[command.name] = command
#----------------------------------------------------
# exception
#----------------------------------------------------
class BuildingException(Exception):
"""General exception during building"""
#----------------------------------------------------
# util
#----------------------------------------------------
def size_format(b):
kb = 1000
mb = kb*kb
b = float(b)
if b >= mb:
return "%.1fMb" % (b/mb)
if b >= kb:
return "%.1fKb" % (b/kb)
return "%.0fbytes" % (b)
def is_url(name):
if ':' not in name:
return False
scheme = name.split(':', 1)[0].lower()
return scheme in ['http', 'https', 'file', 'ftp']
def splitext(name):
base, ext = os.path.splitext(name)
if base.lower().endswith('.tar'):
ext = base[-4:] + ext
base = base[:-4]
return base, ext
def is_archive_file(name):
ext = splitext(name)[1].lower()
archives = ('.zip', '.tar.gz', '.tar.bz2', '.tgz', '.tar')
if ext in archives:
return True
return False
def makedirs(name):
try:
os.makedirs(name)
except OSError, (e,es):
if errno.EEXIST != e:
raise
def symlink(src, dst):
try:
os.symlink(src, dst)
except:
pass
def unlink(name):
try:
os.unlink(name)
except OSError, (e,es):
if errno.ENOENT != e:
raise
def clean_switch_symlink():
for root, dirs, files in os.walk("%s/bin/" % ROOT):
for f in files:
if f == "pythonbrew":
continue
unlink("%s%s" % (root, f))
#----------------------------------------------------
# classes
#----------------------------------------------------
class Downloader(object):
def __init__(self):
self._msg = ""
self._last_msg = ""
self._bytes = 0.0
def download(self, msg, url, path):
self._msg = msg
self._bytes = 0
urllib.urlretrieve(url, path, self._download_progress)
print " downloaded."
def _download_progress(self, block, blockbytes, maxbytes):
self._bytes += float(blockbytes)
if self._bytes >= maxbytes:
self._bytes = maxbytes
percent = (self._bytes / maxbytes) * 100
max_size = size_format(maxbytes)
now_size = size_format(self._bytes)
now_msg = "\rDownloading %s (%s): %3i%% %s" % (self._msg, max_size, percent, now_size)
padding = " " * (len(self._last_msg) - len(now_msg))
sys.stdout.write("%s%s" % (now_msg, padding))
sys.stdout.flush()
self._last_msg = now_msg
class Subprocess(object):
def __init__(self, log=None, shell=False, cwd=None, print_cmd=True):
self._log = log
self._shell = shell
self._cwd = cwd
self._print_cmd = print_cmd
def chdir(self, cwd):
self._cwd = cwd
def check_call(self, cmd, shell=None, cwd=None):
if shell:
self._shell = shell
if cwd:
self._cwd = cwd
if self._print_cmd:
print cmd
if self._log:
cmd = "(%s) >> '%s' 2>&1" % (cmd, self._log)
retcode = subprocess.call(cmd, shell=self._shell, cwd=self._cwd)
if retcode != 0:
raise BuildingException()
class PythonVersionParser(HTMLParser):
def __init__(self):
HTMLParser.__init__(self)
self._versions = []
self._re = re.compile("^(\d+\.\d+(\..*)?)/$")
def handle_starttag(self, tag, attrs):
if tag == "a":
attrs = dict(attrs)
if "href" in attrs:
m = self._re.search(attrs["href"])
if m:
self._versions.append(m.group(1))
def get_sorted_versions(self):
return sorted(self._versions)
class PythonPackages(object):
def __init__(self):
parser = PythonVersionParser()
fp = urllib.urlopen("http://www.python.org/ftp/python/")
parser.feed(fp.read())
fp.close()
parser.close()
self._versions = parser.get_sorted_versions()
def has_version(self, version):
return version in self._versions
def get_packages(self):
return ["Python-%s" % v for v in self._versions]
#----------------------------------------------------
# commands
#----------------------------------------------------
class Command(object):
name = None
usage = None
summary = ""
def __init__(self):
self.parser = OptionParser(usage=self.usage,
prog='%s %s' % (os.path.basename(sys.argv[0]), self.name))
def run(self, args):
options, args = self.parser.parse_args(args)
self.run_command(options, args[1:])
class HelpCommand(Command):
name = "help"
usage = "%prog [COMMAND]"
summary = "Show available commands"
def run_command(self, options, args):
if args:
command = args[0]
if command not in command_dict:
parser.error("Unknown command: `%s`" % command)
return
command = command_dict[command]
command.parser.print_help()
return
parser.print_help()
print
print "Commands available:"
commands = [command_dict[key] for key in sorted(command_dict.keys())]
for command in commands:
print " %s: %s" % (command.name, command.summary)
print
print "Further Instructions:"
print " http://github.com/utahta/pythonbrew"
class InitCommand(Command):
name = "init"
usage = "%prog"
summary = "Run this once to setup the pythonbrew directory ready for installing pythons into"
def run_command(self, options, args):
makedirs(PATH_PYTHONS)
makedirs(PATH_BUILD)
makedirs(PATH_DISTS)
makedirs(PATH_ETC)
os.system("echo 'export PATH=%s/bin:%s/current/bin:${PATH}' > %s/bashrc" % (ROOT, PATH_PYTHONS, PATH_ETC))
os.system("echo 'setenv PATH %s/bin:%s/current/bin:$PATH' > %s/cshrc" % (ROOT, PATH_PYTHONS, PATH_ETC))
m = re.search("(t?csh)", os.environ.get("SHELL"))
if m:
shrc = "cshrc"
yourshrc = m.group(1)+"rc"
else:
shrc = yourshrc = "bashrc"
print """
Pythonbrew environment initiated, required directories are created under
"""+ROOT+"""
Well-done! Congratulations! Please add the following line to the end
of your ~/."""+yourshrc+"""
source """+PATH_ETC+"""/"""+shrc+"""
After that, exit this shell, start a new one, and install some fresh
pythons:
pythonbrew install Python-2.6.6
pythonbrew install Python-2.5.5
For further instructions, simply run:
pythonbrew
The default help messages will popup and tell you what to do!
Enjoy pythonbrew at $HOME!!
INSTRUCTION"""
class InstallCommand(Command):
name = "install"
usage = "%prog [OPTIONS] PACKAGE_NAMES"
summary = "Build and install the given version of python"
def __init__(self):
super(InstallCommand, self).__init__()
self.parser.add_option(
"-f", "--force",
dest="force",
action="store_true",
default=False,
help="Force installation of a Python."
)
self.parser.add_option(
"-b", "--build-options",
dest="build_options",
default="",
help="Set configure options."
)
self.parser.add_option(
"-n", "--no-setuptools",
dest="no_setuptools",
action="store_true",
default=False,
help="Skip installation of setuptools."
)
self._logfile = "%s/build.log" % ROOT
def run_command(self, options, args):
if args:
# Install Python
self._install_python(args[0], options)
else:
# Install pythonbrew
executable = os.path.abspath(sys.argv[0])
target = "%s/bin/pythonbrew" % ROOT
if os.path.isfile(executable) and os.path.isfile(target):
if filecmp.cmp(executable, target):
print """You are already running the installed pythonbrew:
""" + executable;
return
makedirs("%s/bin" % ROOT)
shutil.copy(executable, target)
os.chmod(target, 0755)
print """The pythonbrew is installed as:
"""+target+"""
You may trash the downloaded """+executable+""" from now on.
Next, if this is the first time you've run pythonbrew installation, run:
"""+target+""" init
And follow the instruction on screen."""
def _get_package(self, name):
if not os.path.isfile(name) and not os.path.isdir(name):
if is_url(name):
basename = os.path.basename(name)
download_url = name
download_path = "%s/%s" % (PATH_DISTS, basename)
else:
m = re.search("^Python-(\d+\.\d+(\..*)?)$", name)
if not m:
print "Unknown package: `%s`" % name
sys.exit(1)
dist_version = m.group(1)
pkgs = PythonPackages()
if not pkgs.has_version(dist_version):
print "Package not found: `%s`" % name
sys.exit(1)
basename = "%s.tgz" % name
download_url = PYTHONDLSITE % (dist_version, basename)
download_path = "%s/%s" % (PATH_DISTS, basename)
if os.path.isfile(download_path):
print "Use the previously fetched %s" % (download_path)
else:
try:
dl = Downloader()
dl.download(
basename,
download_url,
download_path
)
except:
os.remove(download_path)
print "\nInterrupt to abort. `%s`" % (download_url)
sys.exit(1)
# iffy
if os.path.getsize(download_path) < 1000000:
print "Invalid file downloaded. (maybe 404 not found?) `%s`" % (download_url)
os.remove(download_path)
sys.exit(1)
else:
if os.path.isfile(name):
basename = os.path.basename(name)
print "Copy the file %s to %s/%s" % (name, PATH_DISTS, basename)
shutil.copy(name, "%s/%s" % (PATH_DISTS, basename))
elif os.path.isdir(name):
basename = name
print "Copy the directory %s to %s/%s" % (name, PATH_DISTS, basename)
shutil.copytree(name, "%s/%s" % (PATH_DISTS, basename))
else:
print "Unknown object. `%s`" % name
sys.exit(1)
return basename
def _get_uncompress_command(self, basename):
distpath = "%s/%s" % (PATH_DISTS, basename)
if os.path.isfile(distpath):
ext = splitext(basename)[1]
if ext == ".tar.gz" or ext == ".tgz":
return "tar zxf %s" % (distpath)
elif ext == ".tar.bz2":
return "tar jxf %s" % (distpath)
elif ext == ".tar":
return "tar xf %s" % (distpath)
elif ext == ".zip":
return "unzip %s" % (distpath)
elif os.path.isdir(distpath):
return "mv %s %s/%s" % (distpath, PATH_BUILD, basename)
else:
print "Unknown object. `%s`" % (basename)
return ""
def _install_python(self, dist, options):
basename = self._get_package(dist)
pkgname = splitext(basename)[0]
install_dir = "%s/%s" % (PATH_PYTHONS, pkgname)
build_options = "--prefix=%s %s" % (install_dir, options.build_options)
print "Installing %s into %s" % (pkgname, install_dir);
print """This could take a while. You can run the following command on another shell to track the status:
tail -f %s
""" % (self._logfile)
try:
s = Subprocess(log=self._logfile, shell=True, cwd=PATH_BUILD)
s.check_call(self._get_uncompress_command(basename))
s.chdir("%s/%s" % (PATH_BUILD, pkgname))
s.check_call("./configure %s" % (build_options))
if options.force:
s.check_call("make")
s.check_call("make install")
else:
s.check_call("make")
s.check_call("make test")
s.check_call("make install")
except:
print """Installing %(pkgname)s failed. See %(ROOT)s/build.log to see why.
pythonbrew install --force %(pkgname)s""" % {"pkgname":pkgname, "ROOT":ROOT}
sys.exit(1)
# install ez_setup
self._install_ez_setup(pkgname, options.no_setuptools)
print """Installed """+pkgname+""" successfully. Run the following command to switch to it.
pythonbrew switch """+pkgname
def _install_ez_setup(self, pydist, no_setuptools):
if no_setuptools:
print "Skip installation setuptools."
return
basename = os.path.basename(EZSETUPDLSITE)
dl = Downloader()
dl.download(basename, EZSETUPDLSITE, "%s/%s" % (PATH_DISTS, basename))
os.system("%s/%s/bin/python %s/%s" % (PATH_PYTHONS, pydist, PATH_DISTS, basename))
if os.path.isfile("%s/%s/bin/easy_install" % (PATH_PYTHONS, pydist)):
os.system("%s/%s/bin/easy_install pip" % (PATH_PYTHONS, pydist))
class InstalledCommand(Command):
name = "installed"
usage = "%prog"
summary = "List the installed versions of python"
def run_command(self, options, args):
if os.path.islink("%s/current" % PATH_PYTHONS):
if os.path.realpath("%s/current" % PATH_PYTHONS) == ROOT:
cur = os.path.realpath("%s/bin/python" % ROOT)
else:
cur = os.path.basename(os.path.realpath("%s/current" % PATH_PYTHONS))
print "%s (*)" % cur
else:
cur = ""
for d in os.listdir("%s/" % PATH_PYTHONS):
if d == "current" or cur == "%s" % (d):
continue
print "%s" % (d)
class SwitchCommand(Command):
name = "switch"
usage = "%prog PACKAGE"
summary = "Switch to the given version"
def run_command(self, options, args):
if args:
dist = args[0]
distdir = dist
if os.path.isfile( dist ) and os.access( dist, os.X_OK ):
if re.search( ".*python(\d(\.\d)?)?$", dist ):
self._switch_file(dist)
else:
print "Invalid binary: `%s`" % dist
return
elif os.path.isdir( dist ):
if os.path.isdir("%s/bin" % dist):
if os.path.isfile("%s/bin/python" % dist):
self._switch_file("%s/bin/python" % dist)
if os.path.isfile("%s/bin/python3" % dist):
self._switch_file("%s/bin/python3" % dist)
return
elif os.path.isfile("%s/python" % dist) and os.access("%s/python" % dist, os.X_OK):
self._switch_file("%s/python" % dist)
return
elif os.path.isfile("%s/python3" % dist) and os.access("%s/python3" % dist, os.X_OK):
self._switch_file("%s/python3" % dist)
return
else:
print "Invalid directory: `%s`" % dist
return
elif not os.path.isdir( "%s/%s" % (PATH_PYTHONS, dist) ):
print "Unknown package: `%s`" % dist
return
self._switch_dir( distdir )
def _switch_file(self, dist):
unlink("%s/current" % PATH_PYTHONS)
unlink("%s/bin/python" % ROOT)
clean_switch_symlink()
symlink(dist, "%s/bin/python" % ROOT)
symlink(ROOT, "%s/current" % PATH_PYTHONS)
print "Switched to "+dist
def _switch_dir(self, dist):
unlink("%s/current" % PATH_PYTHONS)
unlink("%s/bin/python" % ROOT)
symlink(dist, "%s/current" % PATH_PYTHONS)
clean_switch_symlink()
for root, dirs, files in os.walk("%s/pythons/current/bin/" % ROOT):
for f in files:
symlink("%s%s" % (root, f), "%s/bin/%s" % (ROOT, f))
print "Switched to "+dist
class OffCommand(Command):
name = "off"
usage = "%prog"
summary = "Disable pythonbrew"
def run_command(self, options, args):
unlink("%s/current" % PATH_PYTHONS)
clean_switch_symlink()
class VersionCommand(Command):
name = "version"
usage = "%prog"
summary = "Show version"
def run_command(self, options, args):
print VERSION
class SearchCommand(Command):
name = "search"
usage = "%prog [Python-<VERSION>]"
summary = "Search Python packages"
def run_command(self, options, args):
pkgs = PythonPackages()
if args:
pattern = args[0]
_re = re.compile(r"%s" % pattern)
pkgnames = []
for pkgname in pkgs.get_packages():
if _re.match(pkgname):
pkgnames.append(pkgname)
if pkgnames:
for pkgname in pkgnames:
print pkgname
else:
print "Package not found. `%s`" % pattern
else:
for pkgname in pkgs.get_packages():
print pkgname
class Pythonbrew(object):
def run(self):
options, args = parser.parse_args(sys.argv[1:])
if options.help and not args:
args = ["help"]
if not args:
parser.error('You must give a command (use "pythonbrew help" to see a list of commands)')
return
add_command(HelpCommand())
add_command(InitCommand())
add_command(InstallCommand())
add_command(InstalledCommand())
add_command(SwitchCommand())
add_command(OffCommand())
add_command(VersionCommand())
add_command(SearchCommand())
command = args[0].lower()
if command not in command_dict:
parser.error("Unknown command: `%s`" % command)
return
command = command_dict[command]
command.run(args)
def main():
p = Pythonbrew()
p.run()
if __name__ == "__main__":
main()