From 6cb6cc60576e70ef01dbc1b6c385da66a7e2c1a5 Mon Sep 17 00:00:00 2001 From: utahta Date: Sun, 10 Jul 2011 20:49:00 +0900 Subject: [PATCH] update 0.8 --- ChangeLog | 17 ++- PKG-INFO | 174 +++++++++++++++++++++++- README.rst | 80 +++++------ pythonbrew/commands/clean.py | 11 +- pythonbrew/commands/cleanup.py | 19 +++ pythonbrew/commands/install.py | 26 +++- pythonbrew/commands/py.py | 8 +- pythonbrew/commands/update.py | 34 +++-- pythonbrew/curl.py | 6 +- pythonbrew/define.py | 5 +- pythonbrew/downloader.py | 13 +- pythonbrew/etc/config.cfg | 3 +- pythonbrew/installer/__init__.py | 4 +- pythonbrew/installer/pythoninstaller.py | 116 ++++++++-------- pythonbrew/util.py | 118 ++++++++++------ tests/test_01_update.py | 5 +- tests/test_04_install.py | 2 + tests/test_11_clean.py | 4 - tests/test_11_cleanup.py | 4 + 19 files changed, 447 insertions(+), 202 deletions(-) mode change 120000 => 100644 PKG-INFO create mode 100644 pythonbrew/commands/cleanup.py delete mode 100644 tests/test_11_clean.py create mode 100644 tests/test_11_cleanup.py diff --git a/ChangeLog b/ChangeLog index fd09df0..b6e3837 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,9 +1,20 @@ +* 0.8 + - Fixed issue #21 Added Ubuntu 11.04(Natty) support + - Fixed issue #24 non-framework python27 now defines environ properly. Thanks npinto. + - Fixed issue #27 Cleanup of OS X python build flags + - Fixed issue #28 Describe the 'symlink' command better. Thanks tgs. + - Fixed issue #30 py command does not accept arguments with a space + - Fixed bug: `pythonbrew off` need not have removed the symlink in bin directory + - Added --no-test option to the install command + - Added --verbose option to the install command + - `pythonbrew clean` has been renamed. Added `pythonbrew cleanup` instead. + * 0.7.3 - - Improved symlink command. - - support python3 + - Improved symlink command + - Added python3 support * 0.7.2 - - Bug fixed. + - Bug fixed * 0.7.1 - Enable parallel make option (--jobs). diff --git a/PKG-INFO b/PKG-INFO deleted file mode 120000 index 92cacd2..0000000 --- a/PKG-INFO +++ /dev/null @@ -1 +0,0 @@ -README.rst \ No newline at end of file diff --git a/PKG-INFO b/PKG-INFO new file mode 100644 index 0000000..d061329 --- /dev/null +++ b/PKG-INFO @@ -0,0 +1,173 @@ +Overview +======== + +pythonbrew is a program to automate the building and installation of Python in the users HOME. + +pythonbrew is inspired by `perlbrew `_ and `rvm `_. + +Installation +============ + +The recommended way to download and install pythonbrew is to run these statements in your shell:: + + curl -kLO http://github.com/utahta/pythonbrew/raw/master/pythonbrew-install + chmod +x pythonbrew-install + ./pythonbrew-install + +or more simply like this:: + + curl -kL http://github.com/utahta/pythonbrew/raw/master/pythonbrew-install | bash + +After that, pythonbrew installs itself to ~/.pythonbrew, and you should follow the instruction on screen to setup your .bashrc to put it in your PATH. + +If you need to install pythonbrew into somewhere else, you can do that by setting a PYTHONBREW_ROOT environment variable:: + + export PYTHONBREW_ROOT=/path/to/pythonbrew + ./pythonbrew-install + +Usage +===== + +pythonbrew command [options] + +Install some pythons:: + + pythonbrew install 2.6.6 + pythonbrew install --force 2.6.6 + pythonbrew install --configure="CC=gcc_4.1" 2.6.6 + pythonbrew install --no-setuptools 2.6.6 + pythonbrew install http://www.python.org/ftp/python/2.7/Python-2.6.6.tgz + pythonbrew install file:///path/to/Python-2.6.6.tgz + pythonbrew install /path/to/Python-2.6.6.tgz + pythonbrew install 2.5.5 2.6.6 + +Permanently use the specified python as default:: + + pythonbrew switch 2.6.6 + pythonbrew switch 2.5.5 + +Use the specified python in current shell:: + + pythonbrew use 2.6.6 + +Runs a named python file against specified and/or all pythons:: + + pythonbrew py test.py + pythonbrew py -v test.py # Show running python version + pythonbrew py -p 2.6.6 -p 3.1.2 test.py # Use the specified pythons + +List the installed pythons:: + + pythonbrew list + +List the available install pythons:: + + pythonbrew list -k + +Uninstall the specified python:: + + pythonbrew uninstall 2.6.6 + pythonbrew uninstall 2.5.5 2.6.6 + +Remove stale source folders and archives:: + + pythonbrew clean + +Upgrades pythonbrew to the latest version:: + + pythonbrew update + +Disable pythonbrew:: + + pythonbrew off + +Create/Remove a symbolic link to python:: + + pythonbrew symlink # Create a symbolic link, like "py2.5.5" + pythonbrew symlink -p 2.5.5 + pythonbrew symlink pip # Create a symbolic link to the specified script in bin directory + pythonbrew symlink -r # Remove a symbolic link + +Show version:: + + pythonbrew version + +COMMANDS +======== + +install + Build and install the given version of python. + + Setuptools and pip is automatically installed. + + options: --force, --no-setuptools, --configure and --as. + +switch + Permanently use the specified python as default. + +use + Use the specified python in current shell. + +py + Runs a named python file against specified and/or all pythons. + +list + List the installed all pythons. + +list -k + List the available install pythons. + +uninstall + Uninstall the given version of python. + +clean + Remove stale source folders and archives. + +update + Upgrades pythonbrew to the latest version. + +off + Disable pythonbrew. + +version + Show version. + +Options +======= + +\-f | --force + Force installation of a python. (skip `make test`) + +\-C | --configure + Custom configure options. + +\-n | --no-setuptools + Skip installation of setuptools. + +\--as + Install a python under an alias. + +LICENCE +======= + +The MIT License + +Copyright (c) <2010-2011> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.rst b/README.rst index fd9c14b..881912e 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,7 @@ Overview ======== -pythonbrew is a program to automate the building and installation of Python in the users HOME. +pythonbrew is a program to automate the building and installation of Python in the users $HOME. pythonbrew is inspired by `perlbrew `_ and `rvm `_. @@ -10,20 +10,16 @@ Installation The recommended way to download and install pythonbrew is to run these statements in your shell:: - curl -kLO http://github.com/utahta/pythonbrew/raw/master/pythonbrew-install - chmod +x pythonbrew-install - ./pythonbrew-install - -or more simply like this:: - - curl -kL http://github.com/utahta/pythonbrew/raw/master/pythonbrew-install | bash + curl -kL http://xrl.us/pythonbrewinstall | bash After that, pythonbrew installs itself to ~/.pythonbrew, and you should follow the instruction on screen to setup your .bashrc to put it in your PATH. If you need to install pythonbrew into somewhere else, you can do that by setting a PYTHONBREW_ROOT environment variable:: export PYTHONBREW_ROOT=/path/to/pythonbrew - ./pythonbrew-install + curl -kLO http://xrl.us/pythonbrewinstall + chmod +x pythonbrewinstall + ./pythonbrewinstall Usage ===== @@ -32,50 +28,54 @@ pythonbrew command [options] Install some pythons:: - pythonbrew install 2.6.6 - pythonbrew install --force 2.6.6 - pythonbrew install --configure="CC=gcc_4.1" 2.6.6 - pythonbrew install --no-setuptools 2.6.6 - pythonbrew install http://www.python.org/ftp/python/2.7/Python-2.6.6.tgz - pythonbrew install file:///path/to/Python-2.6.6.tgz - pythonbrew install /path/to/Python-2.6.6.tgz - pythonbrew install 2.5.5 2.6.6 + pythonbrew install 2.7.2 + pythonbrew install --verbose 2.7.2 + pythonbrew install --force 2.7.2 + pythonbrew install --no-test 2.7.2 + pythonbrew install --configure="CC=gcc_4.1" 2.7.2 + pythonbrew install --no-setuptools 2.7.2 + pythonbrew install http://www.python.org/ftp/python/2.7/Python-2.7.2.tgz + pythonbrew install /path/to/Python-2.7.2.tgz + pythonbrew install /path/to/Python-2.7.2 + pythonbrew install 2.7.2 3.2 -Permanently use the specified python as default:: +Permanently use the specified python:: - pythonbrew switch 2.6.6 - pythonbrew switch 2.5.5 + pythonbrew switch 2.7.2 + pythonbrew switch 3.2 Use the specified python in current shell:: - pythonbrew use 2.6.6 + pythonbrew use 2.7.2 Runs a named python file against specified and/or all pythons:: pythonbrew py test.py pythonbrew py -v test.py # Show running python version - pythonbrew py -p 2.6.6 -p 3.1.2 test.py # Use the specified pythons + pythonbrew py -p 2.7.2 -p 3.2 test.py # Use the specified pythons List the installed pythons:: pythonbrew list -List the available install pythons:: +List the available installation pythons:: pythonbrew list -k Uninstall the specified python:: - pythonbrew uninstall 2.6.6 - pythonbrew uninstall 2.5.5 2.6.6 + pythonbrew uninstall 2.7.2 + pythonbrew uninstall 2.7.2 3.2 Remove stale source folders and archives:: - pythonbrew clean + pythonbrew cleanup Upgrades pythonbrew to the latest version:: pythonbrew update + pythonbrew update --master + pythonbrew update --develop Disable pythonbrew:: @@ -83,8 +83,8 @@ Disable pythonbrew:: Create/Remove a symbolic link to python (in a directory on your $PATH):: - pythonbrew symlink # Create a symbolic link, like "py2.5.5", for each installed version - pythonbrew symlink -p 2.5.5 + pythonbrew symlink # Create a symbolic link, like "py2.7.2", for each installed version + pythonbrew symlink -p 2.7.2 pythonbrew symlink pip # Create a symbolic link to the specified script in bin directory pythonbrew symlink -r # Remove a symbolic link @@ -97,10 +97,7 @@ COMMANDS install Build and install the given version of python. - - Setuptools and pip is automatically installed. - - options: --force, --no-setuptools, --configure and --as. + Install setuptools and pip automatically. switch Permanently use the specified python as default. @@ -120,7 +117,7 @@ list -k uninstall Uninstall the given version of python. -clean +cleanup Remove stale source folders and archives. update @@ -131,21 +128,10 @@ off version Show version. + +See more details below:: -Options -======= - -\-f | --force - Force installation of a python. (skip `make test`) - -\-C | --configure - Custom configure options. - -\-n | --no-setuptools - Skip installation of setuptools. - -\--as - Install a python under an alias. + `pythonbrew help ` LICENCE ======= diff --git a/pythonbrew/commands/clean.py b/pythonbrew/commands/clean.py index 99aed07..c967e76 100644 --- a/pythonbrew/commands/clean.py +++ b/pythonbrew/commands/clean.py @@ -1,7 +1,5 @@ -import os from pythonbrew.basecommand import Command -from pythonbrew.define import PATH_BUILD, PATH_DISTS -from pythonbrew.util import rm_r +from pythonbrew.log import logger class CleanCommand(Command): name = "clean" @@ -9,11 +7,6 @@ class CleanCommand(Command): summary = "Remove stale source folders and archives" def run_command(self, options, args): - self._clean(PATH_BUILD) - self._clean(PATH_DISTS) + logger.info('\nDEPRECATION WARNING: `pythonbrew clean` has been renamed. Please run `pythonbrew cleanup` instead.\n') - def _clean(self, root): - for dir in os.listdir(root): - rm_r(os.path.join(root, dir)) - CleanCommand() diff --git a/pythonbrew/commands/cleanup.py b/pythonbrew/commands/cleanup.py new file mode 100644 index 0000000..2de8c48 --- /dev/null +++ b/pythonbrew/commands/cleanup.py @@ -0,0 +1,19 @@ +import os +from pythonbrew.basecommand import Command +from pythonbrew.define import PATH_BUILD, PATH_DISTS +from pythonbrew.util import rm_r + +class CleanupCommand(Command): + name = "cleanup" + usage = "%prog" + summary = "Remove stale source folders and archives" + + def run_command(self, options, args): + self._cleanup(PATH_BUILD) + self._cleanup(PATH_DISTS) + + def _cleanup(self, root): + for dir in os.listdir(root): + rm_r(os.path.join(root, dir)) + +CleanupCommand() diff --git a/pythonbrew/commands/install.py b/pythonbrew/commands/install.py index edbb013..34e6877 100644 --- a/pythonbrew/commands/install.py +++ b/pythonbrew/commands/install.py @@ -2,7 +2,7 @@ from pythonbrew.basecommand import Command from pythonbrew.log import logger from pythonbrew.installer.pythoninstaller import PythonInstaller,\ PythonInstallerMacOSX -from pythonbrew.util import is_macosx_snowleopard +from pythonbrew.util import is_macosx from pythonbrew.exceptions import UnknownVersionException,\ AlreadyInstalledException, NotSupportedVersionException @@ -18,7 +18,21 @@ class InstallCommand(Command): dest="force", action="store_true", default=False, - help="Force install of python.(skip make test)" + help="Force installation of python." + ) + self.parser.add_option( + "-n", "--no-test", + dest="no_test", + action="store_true", + default=False, + help="Skip `make test`." + ) + self.parser.add_option( + "-v", "--verbose", + dest="verbose", + action="store_true", + default=False, + help="Display log information on the console." ) self.parser.add_option( "-C", "--configure", @@ -28,11 +42,11 @@ class InstallCommand(Command): help="Options passed directly to configure." ) self.parser.add_option( - "-n", "--no-setuptools", + "--no-setuptools", dest="no_setuptools", action="store_true", default=False, - help="Skip install of setuptools." + help="Skip installation of setuptools." ) self.parser.add_option( "--as", @@ -50,10 +64,10 @@ class InstallCommand(Command): def run_command(self, options, args): if args: - # Install pythons + # installing python for arg in args: try: - if is_macosx_snowleopard(): + if is_macosx(): p = PythonInstallerMacOSX(arg, options) else: p = PythonInstaller(arg, options) diff --git a/pythonbrew/commands/py.py b/pythonbrew/commands/py.py index ca11af9..9390dca 100644 --- a/pythonbrew/commands/py.py +++ b/pythonbrew/commands/py.py @@ -1,10 +1,10 @@ import os import sys +import subprocess from pythonbrew.basecommand import Command from pythonbrew.define import PATH_PYTHONS from pythonbrew.util import Package from pythonbrew.log import logger -from subprocess import Popen class PyCommand(Command): name = "py" @@ -40,13 +40,11 @@ class PyCommand(Command): logger.info('*** %s ***' % d) path = os.path.join(PATH_PYTHONS, d, 'bin', args[0]) if os.path.isfile(path) and os.access(path, os.X_OK): - p = Popen([path] + args[1:]) - p.wait() + subprocess.call([path] + args[1:]) else: path = os.path.join(PATH_PYTHONS, d, 'bin', 'python') if os.path.isfile(path) and os.access(path, os.X_OK): - p = Popen([path] + args) - p.wait() + subprocess.call([path] + args) else: logger.info('%s: No such file or directory.' % path) diff --git a/pythonbrew/commands/update.py b/pythonbrew/commands/update.py index d342224..f9d760a 100644 --- a/pythonbrew/commands/update.py +++ b/pythonbrew/commands/update.py @@ -6,7 +6,7 @@ from pythonbrew.define import PATH_DISTS, VERSION, ROOT,\ from pythonbrew.log import logger from pythonbrew.downloader import Downloader, get_pythonbrew_update_url,\ get_stable_version, get_headerinfo_from_url -from pythonbrew.util import rm_r, unpack_downloadfile, Link, is_gzip, Subprocess +from pythonbrew.util import rm_r, extract_downloadfile, Link, is_gzip, Subprocess class UpdateCommand(Command): name = "update" @@ -16,11 +16,18 @@ class UpdateCommand(Command): def __init__(self): super(UpdateCommand, self).__init__() self.parser.add_option( - '--head', - dest='head', + '--master', + dest='master', action='store_true', default=False, - help='Update the pythonbrew to the github version.' + help='Update the pythonbrew to the `master` branch on github.' + ) + self.parser.add_option( + '--develop', + dest='develop', + action='store_true', + default=False, + help='Update the pythonbrew to the `develop` branch on github.' ) self.parser.add_option( '--config', @@ -61,9 +68,10 @@ class UpdateCommand(Command): logger.info("The config.cfg has been updated.") def _update_pythonbrew(self, options, args): - # pythonbrew update - if options.head: - version = 'head' + if options.master: + version = 'master' + elif options.develop: + version = 'develop' else: version = get_stable_version() # check for version @@ -77,10 +85,10 @@ class UpdateCommand(Command): sys.exit(1) headinfo = get_headerinfo_from_url(download_url) content_type = headinfo['content-type'] - # head is only for gzip. - if not options.head and not is_gzip(content_type, Link(download_url).filename): - logger.error("Invalid content-type: `%s`" % content_type) - sys.exit(1) + if not options.master and not options.develop: + if not is_gzip(content_type, Link(download_url).filename): + logger.error("content type should be gzip. content-type:`%s`" % content_type) + sys.exit(1) filename = "pythonbrew-%s" % version distname = "%s.tgz" % filename @@ -94,13 +102,13 @@ class UpdateCommand(Command): extract_dir = os.path.join(PATH_BUILD, filename) rm_r(extract_dir) - if not unpack_downloadfile(content_type, download_file, extract_dir): + if not extract_downloadfile(content_type, download_file, extract_dir): sys.exit(1) try: logger.info("Installing %s into %s" % (extract_dir, ROOT)) s = Subprocess() - s.check_call('%s %s/pythonbrew_install.py --upgrade' % (sys.executable, extract_dir)) + s.check_call([sys.executable, os.path.join(extract_dir,'pythonbrew_install.py'), '--upgrade']) except: logger.error("Failed to update pythonbrew.") sys.exit(1) diff --git a/pythonbrew/curl.py b/pythonbrew/curl.py index 62bccba..719639c 100644 --- a/pythonbrew/curl.py +++ b/pythonbrew/curl.py @@ -3,7 +3,7 @@ import re import subprocess from subprocess import Popen, PIPE from pythonbrew.log import logger -from pythonbrew.util import u +from pythonbrew.util import to_str class Curl(object): def __init__(self): @@ -26,11 +26,11 @@ class Curl(object): raise respinfo = {} for line in p.stdout: - line = u(line.strip()) + line = to_str(line.strip()) if re.match('^HTTP.*? 200 OK$', line): break for line in p.stdout: - line = u(line.strip()).split(":", 1) + line = to_str(line.strip()).split(":", 1) if len(line) == 2: respinfo[line[0].strip().lower()] = line[1].strip() return respinfo diff --git a/pythonbrew/define.py b/pythonbrew/define.py index d46b0e8..593d2a7 100644 --- a/pythonbrew/define.py +++ b/pythonbrew/define.py @@ -6,7 +6,7 @@ except: import configparser as ConfigParser # pythonbrew version -VERSION = "0.7.3" +VERSION = "0.8" # pythonbrew root path ROOT = os.environ.get("PYTHONBREW_ROOT") @@ -54,7 +54,8 @@ def _get_or_default(section, option, default=''): DISTRIBUTE_SETUP_DLSITE = _get_or_default('distribute', 'url') # pythonbrew download -PYTHONBREW_UPDATE_URL_HEAD = _get_or_default('pythonbrew', 'head') +PYTHONBREW_UPDATE_URL_MASTER = _get_or_default('pythonbrew', 'master') +PYTHONBREW_UPDATE_URL_DEVELOP = _get_or_default('pythonbrew', 'develop') PYTHONBREW_UPDATE_URL_PYPI = _get_or_default('pythonbrew', 'pypi') PYTHONBREW_UPDATE_URL_CONFIG = _get_or_default('pythonbrew', 'config') diff --git a/pythonbrew/downloader.py b/pythonbrew/downloader.py index f5266a1..ee21c02 100644 --- a/pythonbrew/downloader.py +++ b/pythonbrew/downloader.py @@ -1,8 +1,9 @@ from pythonbrew.define import PYTHON_VERSION_URL, PYTHONBREW_STABLE_VERSION_URL, \ - PYTHONBREW_UPDATE_URL_PYPI, PYTHONBREW_UPDATE_URL_HEAD + PYTHONBREW_UPDATE_URL_PYPI, PYTHONBREW_UPDATE_URL_MASTER,\ + PYTHONBREW_UPDATE_URL_DEVELOP from pythonbrew.log import logger from pythonbrew.curl import Curl -from pythonbrew.util import u +from pythonbrew.util import to_str def get_headerinfo_from_url(url): c = Curl() @@ -10,7 +11,7 @@ def get_headerinfo_from_url(url): def get_stable_version(): c = Curl() - return u(c.read(PYTHONBREW_STABLE_VERSION_URL).strip()) + return to_str(c.read(PYTHONBREW_STABLE_VERSION_URL).strip()) class Downloader(object): def download(self, msg, url, path): @@ -19,8 +20,10 @@ class Downloader(object): c.fetch(url, path) def get_pythonbrew_update_url(version): - if version == "head": - return PYTHONBREW_UPDATE_URL_HEAD + if version == "master": + return PYTHONBREW_UPDATE_URL_MASTER + elif version == 'develop': + return PYTHONBREW_UPDATE_URL_DEVELOP else: return PYTHONBREW_UPDATE_URL_PYPI % (version) diff --git a/pythonbrew/etc/config.cfg b/pythonbrew/etc/config.cfg index cf6ec26..dbdc698 100644 --- a/pythonbrew/etc/config.cfg +++ b/pythonbrew/etc/config.cfg @@ -2,7 +2,8 @@ url = http://python-distribute.org/distribute_setup.py [pythonbrew] -head = https://github.com/utahta/pythonbrew/tarball/master +master = https://github.com/utahta/pythonbrew/tarball/master +develop = https://github.com/utahta/pythonbrew/tarball/develop pypi = http://pypi.python.org/packages/source/p/pythonbrew/pythonbrew-%%s.tar.gz stable-version = https://github.com/utahta/pythonbrew/raw/master/stable-version.txt config = https://github.com/utahta/pythonbrew/raw/master/pythonbrew/etc/config.cfg diff --git a/pythonbrew/installer/__init__.py b/pythonbrew/installer/__init__.py index 2cbc24e..f4362e4 100644 --- a/pythonbrew/installer/__init__.py +++ b/pythonbrew/installer/__init__.py @@ -20,8 +20,8 @@ Please add the following line to the end of your ~/.%(yourshrc)s After that, exit this shell, start a new one, and install some fresh pythons: - pythonbrew install 2.6.6 - pythonbrew install 2.5.5 + pythonbrew install 2.7.2 + pythonbrew install 3.2 For further instructions, run: diff --git a/pythonbrew/installer/pythoninstaller.py b/pythonbrew/installer/pythoninstaller.py index 4575df4..50a6a77 100644 --- a/pythonbrew/installer/pythoninstaller.py +++ b/pythonbrew/installer/pythoninstaller.py @@ -5,8 +5,9 @@ import mimetypes from pythonbrew.util import makedirs, symlink, Package, is_url, Link,\ unlink, is_html, Subprocess, rm_r,\ is_python25, is_python24, is_python26, is_python27,\ - unpack_downloadfile, is_archive_file, path_to_fileurl, is_file,\ - fileurl_to_path, is_python30, is_python31, is_python32 + extract_downloadfile, is_archive_file, path_to_fileurl, is_file,\ + fileurl_to_path, is_python30, is_python31, is_python32,\ + get_macosx_deployment_target from pythonbrew.define import PATH_BUILD, PATH_DISTS, PATH_PYTHONS,\ ROOT, PATH_LOG, DISTRIBUTE_SETUP_DLSITE,\ PATH_PATCHES_MACOSX_PYTHON25, PATH_PATCHES_MACOSX_PYTHON24,\ @@ -22,15 +23,13 @@ class PythonInstaller(object): """ def __init__(self, arg, options): - if is_url(arg): - name = arg - elif is_archive_file(arg): + if is_archive_file(arg): name = path_to_fileurl(arg) elif os.path.isdir(arg): name = path_to_fileurl(arg) else: name = arg - + if is_url(name): self.download_url = name filename = Link(self.download_url).filename @@ -46,12 +45,23 @@ class PythonInstaller(object): self.install_dir = os.path.join(PATH_PYTHONS, pkg.name) self.build_dir = os.path.join(PATH_BUILD, pkg.name) self.download_file = os.path.join(PATH_DISTS, filename) + + # cleanup + if os.path.isdir(self.build_dir): + shutil.rmtree(self.build_dir) + + # get content type. if is_file(self.download_url): path = fileurl_to_path(self.download_url) self.content_type = mimetypes.guess_type(path)[0] else: headerinfo = get_headerinfo_from_url(self.download_url) self.content_type = headerinfo['content-type'] + if is_html(self.content_type): + # note: maybe got 404 or 503 http status code. + logger.error("Invalid content-type: `%s`" % self.content_type) + return + self.options = options self.logfile = os.path.join(PATH_LOG, 'build.log') self.configure_options = '' @@ -61,11 +71,9 @@ class PythonInstaller(object): if os.path.isdir(self.install_dir): logger.info("You are already installed `%s`" % self.pkg.name) raise AlreadyInstalledException - self.download_unpack() - logger.info("") - logger.info("This could take a while. You can run the following command on another shell to track the status:") - logger.info(" tail -f %s" % self.logfile) - logger.info("") + self.download_and_extract() + logger.info("\nThis could take a while. You can run the following command on another shell to track the status:") + logger.info(" tail -f %s\n" % self.logfile) self.patch() logger.info("Installing %s into %s" % (self.pkg.name, self.install_dir)) try: @@ -82,38 +90,32 @@ class PythonInstaller(object): logger.info("Installed %(pkgname)s successfully. Run the following command to switch to %(pkgname)s." % {"pkgname":self.pkg.name}) logger.info(" pythonbrew switch %s" % self.pkg.alias) - - def download_unpack(self): - content_type = self.content_type - if is_html(content_type): - logger.error("Invalid content-type: `%s`" % content_type) - sys.exit(1) + + def download_and_extract(self): if is_file(self.download_url): path = fileurl_to_path(self.download_url) if os.path.isdir(path): logger.info('Copying %s into %s' % (path, self.build_dir)) - if os.path.isdir(self.build_dir): - shutil.rmtree(self.build_dir) shutil.copytree(path, self.build_dir) return if os.path.isfile(self.download_file): logger.info("Use the previously fetched %s" % (self.download_file)) else: - msg = Link(self.download_url).show_msg + base_url = Link(self.download_url).base_url try: dl = Downloader() - dl.download(msg, self.download_url, self.download_file) + dl.download(base_url, self.download_url, self.download_file) except: unlink(self.download_file) logger.info("\nInterrupt to abort. `%s`" % (self.download_url)) sys.exit(1) - # unpack - if not unpack_downloadfile(self.content_type, self.download_file, self.build_dir): + # extracting + if not extract_downloadfile(self.content_type, self.download_file, self.build_dir): sys.exit(1) def patch(self): version = self.pkg.version - # ubuntu 11.04(Natty) + # for ubuntu 11.04(Natty) if is_python24(version): patch_dir = os.path.join(PATH_PATCHES_ALL, "python24") self._add_patches_to_list(patch_dir, ['patch-setup.py.diff']) @@ -142,15 +144,15 @@ class PythonInstaller(object): def _do_patch(self): try: - s = Subprocess(log=self.logfile, cwd=self.build_dir) + s = Subprocess(log=self.logfile, cwd=self.build_dir, verbose=self.options.verbose) if self.patches: logger.info("Patching %s" % self.pkg.name) for patch in self.patches: if type(patch) is dict: for (ed, source) in patch.items(): - s.check_call('ed - %s < %s' % (source, ed)) + s.shell('ed - %s < %s' % (source, ed)) else: - s.check_call("patch -p0 < %s" % patch) + s.shell("patch -p0 < %s" % patch) except: logger.error("Failed to patch `%s`" % self.build_dir) sys.exit(1) @@ -158,32 +160,35 @@ class PythonInstaller(object): def _add_patches_to_list(self, patch_dir, patch_files): for patch in patch_files: if type(patch) is dict: - for key in patch.keys(): - patch[os.path.join(patch_dir, key)] = patch[key] - del patch[key] + tmp = patch + patch = {} + for key in tmp.keys(): + patch[os.path.join(patch_dir, key)] = tmp[key] self.patches.append(patch) else: self.patches.append(os.path.join(patch_dir, patch)) def configure(self): - s = Subprocess(log=self.logfile, cwd=self.build_dir) + s = Subprocess(log=self.logfile, cwd=self.build_dir, verbose=self.options.verbose) s.check_call("./configure --prefix=%s %s %s" % (self.install_dir, self.options.configure, self.configure_options)) def make(self): jobs = self.options.jobs make = ((jobs > 0 and 'make -j%s' % jobs) or 'make') - s = Subprocess(log=self.logfile, cwd=self.build_dir) - if self.options.force: - s.check_call(make) - else: - s.check_call(make) - s.check_call("make test") + s = Subprocess(log=self.logfile, cwd=self.build_dir, verbose=self.options.verbose) + s.check_call(make) + if not self.options.no_test: + if self.options.force: + # note: ignore tests failure error. + s.call("make test") + else: + s.check_call("make test") def make_install(self): version = self.pkg.version if version == "1.5.2" or version == "1.6.1": makedirs(self.install_dir) - s = Subprocess(log=self.logfile, cwd=self.build_dir) + s = Subprocess(log=self.logfile, cwd=self.build_dir, verbose=self.options.verbose) s.check_call("make install") def symlink(self): @@ -201,7 +206,7 @@ class PythonInstaller(object): options = self.options pkgname = self.pkg.name if options.no_setuptools: - logger.info("Skip installation setuptools.") + logger.info("Skip installation of setuptools.") return download_url = DISTRIBUTE_SETUP_DLSITE filename = Link(download_url).filename @@ -213,41 +218,40 @@ class PythonInstaller(object): install_dir = os.path.join(PATH_PYTHONS, pkgname) path_python = os.path.join(install_dir,"bin","python") try: - s = Subprocess(log=self.logfile, cwd=PATH_DISTS) + s = Subprocess(log=self.logfile, cwd=PATH_DISTS, verbose=self.options.verbose) logger.info("Installing distribute into %s" % install_dir) - s.check_call("%s %s" % (path_python, filename)) - # Using easy_install install pip + s.check_call([path_python, filename]) + # installing pip easy_install = os.path.join(install_dir, 'bin', 'easy_install') if os.path.isfile(easy_install): logger.info("Installing pip into %s" % install_dir) - s.check_call("%s pip" % (easy_install)) + s.check_call([easy_install, 'pip']) except: logger.error("Failed to install setuptools. See %s/build.log to see why." % (ROOT)) - logger.info("Skip install setuptools.") + logger.info("Skip installation of setuptools.") class PythonInstallerMacOSX(PythonInstaller): - """Python installer for MacOSX SnowLeopard + """Python installer for MacOSX """ def __init__(self, arg, options): super(PythonInstallerMacOSX, self).__init__(arg, options) - version = self.pkg.version + # check for version + version = self.pkg.version if version < '2.6' and (version != '2.4.6' and version < '2.5.5'): logger.info("`%s` is not supported on MacOSX Snow Leopard" % self.pkg.name) raise NotSupportedVersionException # set configure options - if is_python24(version): - self.configure_options = '--with-universal-archs="intel" MACOSX_DEPLOYMENT_TARGET=10.6 CPPFLAGS="-D__DARWIN_UNIX03"' - elif is_python25(version): - self.configure_options = '--with-universal-archs="intel" MACOSX_DEPLOYMENT_TARGET=10.6 CPPFLAGS="-D_DARWIN_C_SOURCE"' - elif is_python26(version): - self.configure_options = '--with-universal-archs="intel" --enable-universalsdk=/ MACOSX_DEPLOYMENT_TARGET=10.6' - elif is_python27(version): - self.configure_options = '--with-universal-archs="intel" --enable-universalsdk=/ MACOSX_DEPLOYMENT_TARGET=10.6' - else: - self.configure_options = '--with-universal-archs="intel" --enable-universalsdk=/ MACOSX_DEPLOYMENT_TARGET=10.6' + target = get_macosx_deployment_target() + if target: + self.configure_options = 'MACOSX_DEPLOYMENT_TARGET=%s' % target + # note: skip `make test` to avoid hanging test_threading. + if is_python25(version) or is_python24(version): + self.options.no_test = True + def patch(self): + # note: want an interface to the source patching functionality. like a patchperl. version = self.pkg.version if is_python24(version): patch_dir = PATH_PATCHES_MACOSX_PYTHON24 diff --git a/pythonbrew/util.py b/pythonbrew/util.py index 79780d3..45dadfe 100644 --- a/pythonbrew/util.py +++ b/pythonbrew/util.py @@ -1,17 +1,17 @@ import os +import sys import errno import shutil -import subprocess import re import posixpath import tarfile import platform import urllib -from subprocess import PIPE, Popen -from pythonbrew.define import PATH_BIN, PATH_PYTHONS, PATH_ETC_CURRENT +import subprocess +import shlex +from pythonbrew.define import PATH_BIN, PATH_ETC_CURRENT from pythonbrew.exceptions import ShellCommandException from pythonbrew.log import logger -import sys def size_format(b): kb = 1000 @@ -61,9 +61,15 @@ def is_gzip(content_type, filename): return True return False -def is_macosx_snowleopard(): +def is_macosx(): mac_ver = platform.mac_ver()[0] - return mac_ver >= '10.6' and mac_ver < '10.7' + return mac_ver >= '10.6' + +def get_macosx_deployment_target(): + m = re.search('^([0-9]+\.[0-9]+)', platform.mac_ver()[0]) + if m: + return m.group(1) + return None def is_python24(version): return version >= '2.4' and version < '2.5' @@ -116,12 +122,6 @@ def rm_r(path): unlink(path) def off(): - for root, dirs, files in os.walk(PATH_BIN): - for f in files: - if f == "pythonbrew" or f == "pybrew": - continue - unlink("%s/%s" % (root, f)) - unlink("%s/current" % PATH_PYTHONS) set_current_path(PATH_BIN) def split_leading_dir(path): @@ -151,7 +151,7 @@ def has_leading_dir(paths): def untar_file(filename, location): if not os.path.exists(location): - makedirs(location) + os.makedirs(location) if filename.lower().endswith('.gz') or filename.lower().endswith('.tgz'): mode = 'r:gz' elif filename.lower().endswith('.bz2') or filename.lower().endswith('.tbz'): @@ -195,12 +195,15 @@ def untar_file(filename, location): shutil.copyfileobj(fp, destfp) finally: destfp.close() - os.chmod(path, member.mode) fp.close() + # note: configure ...etc + os.chmod(path, member.mode) + # note: the file timestamps should be such that asdl_c.py is not invoked. + os.utime(path, (member.mtime, member.mtime)) finally: tar.close() -def unpack_downloadfile(content_type, download_file, target_dir): +def extract_downloadfile(content_type, download_file, target_dir): logger.info("Extracting %s into %s" % (os.path.basename(download_file), target_dir)) if is_gzip(content_type, download_file): untar_file(download_file, target_dir) @@ -210,12 +213,10 @@ def unpack_downloadfile(content_type, download_file, target_dir): return True def get_current_python_path(): - p = Popen('command -v python', stdout=PIPE, shell=True) - p.wait() - if p.returncode == 0: - return p.stdout.read().strip() - else: - return None + """return: python path or '' + """ + p = subprocess.Popen(['command', '-v', 'python'], stdout=subprocess.PIPE) + return p.communicate()[0].strip() def set_current_path(path): fp = open(PATH_ETC_CURRENT, 'w') @@ -234,40 +235,71 @@ def fileurl_to_path(url): url = '/' + url[len('file:'):].lstrip('/') return urllib.unquote(url) -def u(val): - """to unicode - """ +def to_str(val): try: - # for python3 - if type(val) == bytes: + # python3 + if type(val) is bytes: return val.decode() except: - if type(val) == str: - return val.decode("utf-8") - return val + if type(val) is unicode: + return val.encode("utf-8") + return val + +def is_str(val): + try: + # python2 + return isinstance(val, basestring) + except: + # python3 + return isinstance(val, str) + return False class Subprocess(object): - def __init__(self, log=None, shell=True, cwd=None, print_cmd=False): + def __init__(self, log=None, cwd=None, verbose=False, debug=False): self._log = log - self._shell = shell self._cwd = cwd - self._print_cmd = print_cmd + self._verbose = verbose + self._debug = debug 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: + def shell(self, cmd): + if self._debug: logger.info(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 ShellCommandException('Failed to `%s` command' % cmd) + if self._verbose: + cmd = "(%s) 2>&1 | tee '%s'" % (cmd, self._log) + else: + cmd = "(%s) >> '%s' 2>&1" % (cmd, self._log) + returncode = subprocess.call(cmd, shell=True, cwd=self._cwd) + if returncode: + raise ShellCommandException('%s: failed to `%s`' % (returncode, cmd)) + + def call(self, cmd): + if is_str(cmd): + cmd = shlex.split(cmd) + if self._debug: + logger.info(cmd) + + fp = ((self._log and open(self._log, 'a')) or None) + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=self._cwd) + while p.returncode is None: + p.poll() + line = to_str(p.stdout.readline()) + if self._verbose: + logger.info(line.strip()) + if fp: + fp.write(line) + fp.flush() + if fp: + fp.close() + return p.returncode + + def check_call(self, cmd): + returncode = self.call(cmd) + if returncode: + raise ShellCommandException('%s: failed to `%s`' % (returncode, cmd)) class Package(object): def __init__(self, name, alias=None): @@ -302,7 +334,7 @@ class Link(object): return name @property - def show_msg(self): + def base_url(self): return posixpath.basename(self._url.split('#', 1)[0].split('?', 1)[0]) diff --git a/tests/test_01_update.py b/tests/test_01_update.py index 99e0f5f..328a86a 100644 --- a/tests/test_01_update.py +++ b/tests/test_01_update.py @@ -1,5 +1,6 @@ class UpdateOptions(object): - head = False + master = False + develop = False config = False force = False @@ -7,4 +8,4 @@ def test_update(): from pythonbrew.commands.update import UpdateCommand c = UpdateCommand() c.run_command(UpdateOptions(), None) - + \ No newline at end of file diff --git a/tests/test_04_install.py b/tests/test_04_install.py index f54e26f..e033ebe 100644 --- a/tests/test_04_install.py +++ b/tests/test_04_install.py @@ -2,6 +2,8 @@ from tests import TESTPY_VERSION class InstallOptions(object): force = True + no_test = False + verbose = False configure = "" no_setuptools = False alias = None diff --git a/tests/test_11_clean.py b/tests/test_11_clean.py deleted file mode 100644 index 7823339..0000000 --- a/tests/test_11_clean.py +++ /dev/null @@ -1,4 +0,0 @@ -def test_clean(): - from pythonbrew.commands.clean import CleanCommand - c = CleanCommand() - c.run_command(None, None) diff --git a/tests/test_11_cleanup.py b/tests/test_11_cleanup.py new file mode 100644 index 0000000..3549334 --- /dev/null +++ b/tests/test_11_cleanup.py @@ -0,0 +1,4 @@ +def test_clean(): + from pythonbrew.commands.cleanup import CleanupCommand + c = CleanupCommand() + c.run_command(None, None)