├── src └── github │ ├── tools │ ├── test │ │ ├── __init__.py │ │ ├── utils.py │ │ └── test_gh_pages.py │ ├── tmpl │ │ └── gh │ │ │ ├── docs │ │ │ ├── _static │ │ │ │ └── +gitignore+ │ │ │ └── _templates │ │ │ │ └── +gitignore+ │ │ │ ├── +gitignore+_tmpl │ │ │ └── bootstrap.py │ ├── sphinx.py │ ├── __init__.py │ ├── template.py │ ├── task.py │ └── gh_pages.py │ └── __init__.py ├── docs ├── source │ ├── changes.rst │ ├── gh-pages.rst │ ├── overview.rst │ ├── template.rst │ ├── task.rst │ ├── index.rst │ └── conf.py ├── Makefile └── make.bat ├── support-files └── readme.rst ├── setup.cfg ├── paver-minilib.zip ├── .gitignore ├── .gitmodules ├── dev-requirements.txt ├── setup.py ├── MANIFEST.in ├── LICENCE ├── CHANGES.rst ├── README.rst ├── pavement.py └── bootstrap.py /src/github/tools/test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/source/changes.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../../CHANGES.rst -------------------------------------------------------------------------------- /src/github/tools/tmpl/gh/docs/_static/+gitignore+: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/github/tools/tmpl/gh/docs/_templates/+gitignore+: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /support-files/readme.rst: -------------------------------------------------------------------------------- 1 | This folder contains required packages. -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [easy_install] 2 | find_links = support-files 3 | zip_ok = False -------------------------------------------------------------------------------- /docs/source/gh-pages.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: github.tools.gh_pages 2 | :members: -------------------------------------------------------------------------------- /paver-minilib.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinoboff/github-tools/HEAD/paver-minilib.zip -------------------------------------------------------------------------------- /docs/source/overview.rst: -------------------------------------------------------------------------------- 1 | ======== 2 | overview 3 | ======== 4 | 5 | .. include:: ../../README.rst -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | docs/build/doctrees/* 3 | dist/* 4 | src/*.egg-info/* 5 | virtual-env/* 6 | *.pyc 7 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "docs/build/html"] 2 | path = docs/build/html 3 | url = git@github.com:dinoboff/github-tools.git 4 | -------------------------------------------------------------------------------- /dev-requirements.txt: -------------------------------------------------------------------------------- 1 | simplejson 2 | Sphinx 3 | GitPython==0.1.7 4 | Mock>=0.5.0 5 | Nose>=0.10.4 6 | setuptools>=0.6c9 7 | virtualenv>=1.3.3 8 | -------------------------------------------------------------------------------- /src/github/tools/tmpl/gh/+gitignore+_tmpl: -------------------------------------------------------------------------------- 1 | virtual-env/* 2 | dist/* 3 | build/* 4 | docs/_build/doctrees/* 5 | ${project}.egg-info/* 6 | *.pyc 7 | -------------------------------------------------------------------------------- /docs/source/template.rst: -------------------------------------------------------------------------------- 1 | ==================== 2 | PasteScript template 3 | ==================== 4 | 5 | .. automodule:: github.tools.template 6 | :members: 7 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | if os.path.exists("paver-minilib.zip"): 3 | import sys 4 | sys.path.insert(0, "paver-minilib.zip") 5 | 6 | import paver.tasks 7 | paver.tasks.main() 8 | -------------------------------------------------------------------------------- /src/github/__init__.py: -------------------------------------------------------------------------------- 1 | # See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages 2 | try: 3 | __import__('pkg_resources').declare_namespace(__name__) 4 | except ImportError: 5 | from pkgutil import extend_path 6 | __path__ = extend_path(__path__, __name__) 7 | -------------------------------------------------------------------------------- /docs/source/task.rst: -------------------------------------------------------------------------------- 1 | ========== 2 | Paver Task 3 | ========== 4 | 5 | .. automodule:: github.tools.task 6 | :members: 7 | 8 | .. autofunction:: gh_register 9 | .. autofunction:: gh_pages_create 10 | .. autofunction:: gh_pages_update 11 | .. autofunction:: gh_pages_clean 12 | .. autofunction:: gh_pages_build 13 | -------------------------------------------------------------------------------- /src/github/tools/test/utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Created on 19 Apr 2009 3 | 4 | @author: damien 5 | """ 6 | from nose.tools import eq_, ok_ 7 | 8 | import tempfile 9 | 10 | from paver.easy import path 11 | 12 | __all__ = ['eq_', 'ok_', 'path', 'TempDir'] 13 | 14 | class TempDir(object): 15 | 16 | def __enter__(self): 17 | self.tmp_dir = path(tempfile.mkdtemp()) 18 | return self.tmp_dir 19 | 20 | def __exit__(self, type, value, traceback): 21 | self.tmp_dir.rmtree() -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | ======================================= 2 | Welcome to github-tools' documentation! 3 | ======================================= 4 | 5 | :Author: Damien Lebrun 6 | :Date: |today| 7 | :Description: PasteScript template and Paver tasks to setup a new package and easily host it on GitHub (including its documentation). 8 | 9 | 10 | .. toctree:: 11 | :maxdepth: 2 12 | 13 | overview 14 | template 15 | task 16 | changes 17 | 18 | Indices and tables 19 | ================== 20 | 21 | * :ref:`genindex` 22 | * :ref:`modindex` 23 | * :ref:`search` -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include CHANGES.rst 2 | include LICENCE 3 | include MANIFEST.in 4 | include README.rst 5 | include bootstrap.py 6 | include dev-requirements.txt 7 | include docs/Makefile 8 | include docs/make.bat 9 | include docs/source/changes.rst 10 | include docs/source/conf.py 11 | include docs/source/gh-pages.rst 12 | include docs/source/index.rst 13 | include docs/source/overview.rst 14 | include docs/source/task.rst 15 | include docs/source/template.rst 16 | include pavement.py 17 | include paver-minilib.zip 18 | include setup.cfg 19 | include setup.py 20 | include src/github/__init__.py 21 | include src/github/tools/__init__.py 22 | include src/github/tools/gh_pages.py 23 | include src/github/tools/sphinx.py 24 | include src/github/tools/task.py 25 | include src/github/tools/template.py 26 | include src/github/tools/test/__init__.py 27 | include src/github/tools/test/test_gh_pages.py 28 | include src/github/tools/test/utils.py 29 | include src/github/tools/tmpl/gh/+gitignore+_tmpl 30 | include src/github/tools/tmpl/gh/bootstrap.py 31 | include src/github/tools/tmpl/gh/docs/_static/+gitignore+ 32 | include src/github/tools/tmpl/gh/docs/_templates/+gitignore+ 33 | include support-files/readme.rst 34 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | 5 | from pkg_resources import parse_version 6 | import pkginfo 7 | 8 | 9 | def _egg_info(path_to_egg='../../src/'): 10 | path_to_egg = os.path.join( 11 | os.path.dirname(__file__), path_to_egg) 12 | egg_info = pkginfo.Develop(path_to_egg) 13 | release = egg_info.version 14 | version = '%s.%s' % tuple(map(int, parse_version(release)[0:2])) 15 | return egg_info.name, egg_info.author, version, release 16 | 17 | project, author, version, release = _egg_info() 18 | copyright = '2009, %s' % author 19 | 20 | # Extension 21 | extensions = [ 22 | 'sphinx.ext.autodoc', 23 | 'sphinx.ext.intersphinx', 24 | 'sphinx.ext.todo', 25 | ] 26 | intersphinx_mapping = {'http://docs.python.org/': None} 27 | 28 | # Source 29 | master_doc = 'index' 30 | templates_path = ['_templates'] 31 | source_suffix = '.rst' 32 | exclude_trees = [] 33 | pygments_style = 'sphinx' 34 | 35 | # html build settings 36 | html_theme = 'default' 37 | html_static_path = ['_static'] 38 | 39 | # htmlhelp settings 40 | htmlhelp_basename = '%sdoc' % project 41 | 42 | # latex build settings 43 | latex_documents = [ 44 | ('index', '%s.tex' % project, u'%s Documentation' % project, 45 | author, 'manual'), 46 | ] -------------------------------------------------------------------------------- /src/github/tools/sphinx.py: -------------------------------------------------------------------------------- 1 | """ 2 | :Description: Sphinx extension to remove leading under-scores from directories names in the html build output directory. 3 | """ 4 | import os 5 | import shutil 6 | 7 | 8 | def setup(app): 9 | """ 10 | Add a html-page-context and a build-finished event handlers 11 | """ 12 | app.connect('html-page-context', change_pathto) 13 | app.connect('build-finished', move_private_folders) 14 | 15 | def change_pathto(app, pagename, templatename, context, doctree): 16 | """ 17 | Replace pathto helper to change paths to folders with a leading underscore. 18 | """ 19 | pathto = context.get('pathto') 20 | def gh_pathto(otheruri, *args, **kw): 21 | if otheruri.startswith('_'): 22 | otheruri = otheruri[1:] 23 | return pathto(otheruri, *args, **kw) 24 | context['pathto'] = gh_pathto 25 | 26 | def move_private_folders(app, e): 27 | """ 28 | remove leading underscore from folders in in the output folder. 29 | 30 | :todo: should only affect html built 31 | """ 32 | def join(dir): 33 | return os.path.join(app.builder.outdir, dir) 34 | 35 | for item in os.listdir(app.builder.outdir): 36 | if item.startswith('_') and os.path.isdir(join(item)): 37 | shutil.move(join(item), join(item[1:])) -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009, Damien Lebrun 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 16 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /src/github/tools/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Helpers for Python package hosting at GitHub. 3 | 4 | Copyright (c) 2009, Damien Lebrun 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, 8 | are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above COPYRIGHT notice, 11 | this list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above COPYRIGHT notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 19 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 21 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | DAMAGES(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 25 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | """ 28 | 29 | __author__ = 'Damien Lebrun ' 30 | -------------------------------------------------------------------------------- /CHANGES.rst: -------------------------------------------------------------------------------- 1 | Changes 2 | ======= 3 | 4 | 0.2-rc1+1 (September 09, 2010) 5 | ------------------------------ 6 | 7 | - Bump version number to 0.2-rc1+1 8 | - require GitPython==0.1.7 (lower versions aren't available on PyPi any 9 | more) 10 | 11 | - dev-requirements.txt 12 | - pavement.py 13 | 14 | 0.2-rc1 (April 24, 2010) 15 | ------------------------ 16 | 17 | - Change GitPython requirement. Requires version 1.6 specifically (version 0.3 will track GitPython 2.x). 18 | - The gh_pages_clean task correctly checkout the gh-pages branch in the gh-pages submodule. 19 | 20 | 0.2b3 (January 30, 2010) 21 | ------------------------ 22 | 23 | - Installation of Template dependencies is now optional. 24 | 25 | 0.2b2 (January 11, 2010) 26 | ------------------------ 27 | 28 | - Fix bug with gh_package template when github.user or github.token 29 | git config values are not set. 30 | - Disable Sphinx extension and add a .nojekyll file to allow the use of 31 | folder starting with "_". 32 | 33 | 0.2b1 (August 26, 2009) 34 | ----------------------- 35 | 36 | - New Layout, without a ``src/`` folder to hold package(s) or ``docs/source`` 37 | to hold the ReST documentation - Based on `Paver-templates`_' main template. 38 | 39 | 40 | 0.1.7 (July 18, 2009) 41 | --------------------- 42 | 43 | - Fixed bug in gh_register, failing to set default gh_pages options. 44 | 45 | 46 | 0.1.6 (July 9, 2009) 47 | -------------------- 48 | 49 | - Fixed with github-tools' template. Failed when git's user.name and user.email 50 | config variables were not set 51 | - Removed distribution info from package's ``__init__.py`` file, 52 | so that pavement.py doesn't need to the package to get them. 53 | Github-tools uses pkginfo to get these info when it needs them. 54 | 55 | 56 | 0.1.4 (July 5, 2009) 57 | -------------------- 58 | 59 | - Added required files to MANIFEST.in; the gh_package template was 60 | missing from the distribution. 61 | 62 | 63 | 0.1.3 (Jun 17, 2009) 64 | -------------------- 65 | 66 | - Fixed a bug with github-tools' pavement.py. 67 | 68 | 69 | 0.1.2 (Jun 14, 2009) 70 | -------------------- 71 | 72 | - Fixed a bug in in the ``doc/source/conf.py`` created with github-tools' paste 73 | template. 74 | 75 | 76 | 0.1.0 (Jun 12, 2009): 77 | --------------------- 78 | 79 | - Initial release. 80 | 81 | 82 | .. _Paver-Templates: http://pypi.python.org/pypi/paver-templates/ -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | 9 | # Internal variables. 10 | PAPEROPT_a4 = -D latex_paper_size=a4 11 | PAPEROPT_letter = -D latex_paper_size=letter 12 | ALLSPHINXOPTS = -d build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 13 | 14 | .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest 15 | 16 | help: 17 | @echo "Please use \`make ' where is one of" 18 | @echo " html to make standalone HTML files" 19 | @echo " dirhtml to make HTML files named index.html in directories" 20 | @echo " pickle to make pickle files" 21 | @echo " json to make JSON files" 22 | @echo " htmlhelp to make HTML files and a HTML help project" 23 | @echo " qthelp to make HTML files and a qthelp project" 24 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 25 | @echo " changes to make an overview of all changed/added/deprecated items" 26 | @echo " linkcheck to check all external links for integrity" 27 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 28 | 29 | clean: 30 | -rm -rf build/* 31 | 32 | html: 33 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) build/html 34 | @echo 35 | @echo "Build finished. The HTML pages are in build/html." 36 | 37 | dirhtml: 38 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) build/dirhtml 39 | @echo 40 | @echo "Build finished. The HTML pages are in build/dirhtml." 41 | 42 | pickle: 43 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) build/pickle 44 | @echo 45 | @echo "Build finished; now you can process the pickle files." 46 | 47 | json: 48 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) build/json 49 | @echo 50 | @echo "Build finished; now you can process the JSON files." 51 | 52 | htmlhelp: 53 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) build/htmlhelp 54 | @echo 55 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 56 | ".hhp project file in build/htmlhelp." 57 | 58 | qthelp: 59 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) build/qthelp 60 | @echo 61 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 62 | ".qhcp project file in build/qthelp, like this:" 63 | @echo "# qcollectiongenerator build/qthelp/github.tools.qhcp" 64 | @echo "To view the help file:" 65 | @echo "# assistant -collectionFile build/qthelp/github.tools.qhc" 66 | 67 | latex: 68 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) build/latex 69 | @echo 70 | @echo "Build finished; the LaTeX files are in build/latex." 71 | @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ 72 | "run these through (pdf)latex." 73 | 74 | changes: 75 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) build/changes 76 | @echo 77 | @echo "The overview file is in build/changes." 78 | 79 | linkcheck: 80 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) build/linkcheck 81 | @echo 82 | @echo "Link check complete; look for any errors in the above output " \ 83 | "or in build/linkcheck/output.txt." 84 | 85 | doctest: 86 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) build/doctest 87 | @echo "Testing of doctests in the sources finished, look at the " \ 88 | "results in build/doctest/output.txt." 89 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | set SPHINXBUILD=sphinx-build 6 | set ALLSPHINXOPTS=-d build/doctrees %SPHINXOPTS% source 7 | if NOT "%PAPER%" == "" ( 8 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 9 | ) 10 | 11 | if "%1" == "" goto help 12 | 13 | if "%1" == "help" ( 14 | :help 15 | echo.Please use `make ^` where ^ is one of 16 | echo. html to make standalone HTML files 17 | echo. dirhtml to make HTML files named index.html in directories 18 | echo. pickle to make pickle files 19 | echo. json to make JSON files 20 | echo. htmlhelp to make HTML files and a HTML help project 21 | echo. qthelp to make HTML files and a qthelp project 22 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 23 | echo. changes to make an overview over all changed/added/deprecated items 24 | echo. linkcheck to check all external links for integrity 25 | echo. doctest to run all doctests embedded in the documentation if enabled 26 | goto end 27 | ) 28 | 29 | if "%1" == "clean" ( 30 | for /d %%i in (build\*) do rmdir /q /s %%i 31 | del /q /s build\* 32 | goto end 33 | ) 34 | 35 | if "%1" == "html" ( 36 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% build/html 37 | echo. 38 | echo.Build finished. The HTML pages are in build/html. 39 | goto end 40 | ) 41 | 42 | if "%1" == "dirhtml" ( 43 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% build/dirhtml 44 | echo. 45 | echo.Build finished. The HTML pages are in build/dirhtml. 46 | goto end 47 | ) 48 | 49 | if "%1" == "pickle" ( 50 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% build/pickle 51 | echo. 52 | echo.Build finished; now you can process the pickle files. 53 | goto end 54 | ) 55 | 56 | if "%1" == "json" ( 57 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% build/json 58 | echo. 59 | echo.Build finished; now you can process the JSON files. 60 | goto end 61 | ) 62 | 63 | if "%1" == "htmlhelp" ( 64 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% build/htmlhelp 65 | echo. 66 | echo.Build finished; now you can run HTML Help Workshop with the ^ 67 | .hhp project file in build/htmlhelp. 68 | goto end 69 | ) 70 | 71 | if "%1" == "qthelp" ( 72 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% build/qthelp 73 | echo. 74 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 75 | .qhcp project file in build/qthelp, like this: 76 | echo.^> qcollectiongenerator build\qthelp\github.tools.qhcp 77 | echo.To view the help file: 78 | echo.^> assistant -collectionFile build\qthelp\github.tools.ghc 79 | goto end 80 | ) 81 | 82 | if "%1" == "latex" ( 83 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% build/latex 84 | echo. 85 | echo.Build finished; the LaTeX files are in build/latex. 86 | goto end 87 | ) 88 | 89 | if "%1" == "changes" ( 90 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% build/changes 91 | echo. 92 | echo.The overview file is in build/changes. 93 | goto end 94 | ) 95 | 96 | if "%1" == "linkcheck" ( 97 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% build/linkcheck 98 | echo. 99 | echo.Link check complete; look for any errors in the above output ^ 100 | or in build/linkcheck/output.txt. 101 | goto end 102 | ) 103 | 104 | if "%1" == "doctest" ( 105 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% build/doctest 106 | echo. 107 | echo.Testing of doctests in the sources finished, look at the ^ 108 | results in build/doctest/output.txt. 109 | goto end 110 | ) 111 | 112 | :end 113 | -------------------------------------------------------------------------------- /src/github/tools/template.py: -------------------------------------------------------------------------------- 1 | """ 2 | :Description: PasteScript Template to generate a GitHub hosted python package. 3 | 4 | 5 | Let you set the package name, a one line description, the Licence (support 6 | GPL, LGPL, AGPL and BSD - GPLv3 by default) and the author name, email and 7 | organisation variables:: 8 | 9 | paster create -t gh_package 10 | 11 | 12 | The result:: 13 | 14 | / 15 | docs/ 16 | 17 | _static 18 | _templates/ 19 | conf.py 20 | index.rst 21 | / 22 | __init__.py 23 | .gitignore 24 | bootstrap.py 25 | LICENCE 26 | MANIFEST.in 27 | pavement.py 28 | README.rst 29 | 30 | * /pavement.py is the paver configuration file. All the setuptools 31 | tasks are available with paver. Paver make the creation of of new task easy. 32 | See `paver documentation `_ 33 | for more details:: 34 | 35 | paver paverdocs 36 | 37 | * /docs/ will contains your documentation source. conf.py 38 | is Sphinx' configuration file. 39 | Check `Sphinx' documentation `_ for more details. 40 | 41 | .. note:: 42 | The version number, the project name and author name(s) are set in 43 | ``pavement.py`` and shared with ``docs/conf.py``. 44 | 45 | However licence and copyright information are hard coded into ``LICENCE``, 46 | ``pavement.py``, ``docs/conf`` and ``/__init__.py``. 47 | 48 | """ 49 | 50 | import os 51 | from paste.script.templates import Template 52 | from github.tools.gh_pages import GitHubProject 53 | 54 | 55 | OPTIONAL_IMPORT = """from github.tools.task import * 56 | """ 57 | 58 | VIRTUAL_PACKAGES_TO_INSTALL = """'github-tools', 59 | """ 60 | 61 | DEVELOPMENT_HELP = """ 62 | 63 | 64 | Help and development 65 | ==================== 66 | 67 | If you'd like to help out, you can fork the project 68 | at %(project_url)s and report any bugs 69 | at %(issue_url)s. 70 | 71 | 72 | """ 73 | 74 | 75 | class GithubTemplate(Template): 76 | """Paver template for a GitHub hosted Python package.""" 77 | _template_dir = 'tmpl/gh' 78 | summary = ("A basic layout for project hosted on GitHub " 79 | "and managed with Paver") 80 | use_cheetah = True 81 | required_templates = ['paver-templates#paver_package'] 82 | 83 | def pre(self, command, output_dir, vars): 84 | """ 85 | Set extra template variables: 86 | 87 | * "year", current year. 88 | * "gitignore", set to ".gitignore". 89 | * "licence_body", licence notice of the package. 90 | * "gpl_type", for gpl licences 91 | """ 92 | vars['gitignore'] = '.gitignore' 93 | try : 94 | project = GitHubProject(name=vars['project']) 95 | vars['project_url'] = project.url.http 96 | vars['issue_url'] = project.url.issue 97 | except AttributeError: 98 | vars['project_url'] = ( 99 | 'http://github.com//%s' % vars['project'] 100 | ) 101 | vars['issue_url'] = ( 102 | 'http://github.com//%s/issues' % vars['project'] 103 | ) 104 | 105 | 106 | def post(self, command, output_dir, vars): 107 | """ 108 | Add extra settings to pavement.py 109 | """ 110 | # Edit pavement.py 111 | pavement_py = os.path.join(output_dir, 'pavement.py') 112 | command.insert_into_file( 113 | filename=pavement_py, 114 | marker_name='Optional import', 115 | text=OPTIONAL_IMPORT, 116 | indent=True 117 | ) 118 | command.insert_into_file( 119 | filename=pavement_py, 120 | marker_name='Virtualenv packages to install', 121 | text=VIRTUAL_PACKAGES_TO_INSTALL, 122 | indent=True 123 | ) 124 | 125 | # Append README.rst with development info 126 | readme = open(os.path.join(output_dir, 'README.rst'), 'a') 127 | try: 128 | readme.write(DEVELOPMENT_HELP % vars) 129 | finally: 130 | readme.close() -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Github features Git repository hosting, a download page for your Git tags 2 | (or any archive), a basic issue tracker, a wiki and static page hosting 3 | (`gh-pages `_). 4 | 5 | Github-tools defines a PasteScript template to create the basic layout, 6 | some paver tasks (``github.tools.task.*``) to host your package documentation 7 | on gh-pages and the pavement.py script to get started. 8 | 9 | 10 | Requirements 11 | ============ 12 | 13 | This extension and its dependencies require: 14 | 15 | * a GitHub user account and, 16 | * Git (tested with 1.6.2.4), 17 | * GitPython 0.1.7 18 | * Python 2.5+. 19 | 20 | It currently has only been tested on Ubuntu 8.04 (and Git built from source) 21 | with Python 2.5 and Mac OS X 10.6 with python 2.6. 22 | 23 | For Windows, It should work as long as 24 | `GitPython `_ does. However since it 25 | simply start ``git`` subprocesses to work, it might be difficult to use with 26 | Git installers like `msysgit `_ or 27 | `gitextensions `_. 28 | 29 | 30 | Installation 31 | ============ 32 | 33 | The easiest way to get github-tools is if you have setuptools / distribute installed:: 34 | 35 | easy_install github-tools[template] 36 | 37 | The current development version can be found at 38 | http://github.com/dinoboff/github-tools/tarball/master. 39 | 40 | 41 | Usage 42 | ===== 43 | 44 | Package layout 45 | -------------- 46 | 47 | If you are starting from scratch, create the basic layout with paster:: 48 | 49 | paster create -t gh_package 50 | 51 | The project name will be used for pypi and for your Github repository 52 | (``http://github.com//``). 53 | 54 | To finish your development environment setup, create a virtual environment 55 | and deploy your package in development mode:: 56 | 57 | cd 58 | python bootstrap.py --no-site-packages 59 | 60 | The basic package comes with a virtualenv boostrap script 61 | to create an isolated Python environments. To activate this environment 62 | in your shell, run:: 63 | 64 | source ./virtual-env/bin/activate 65 | # or .\virtual-env\Scripts\activate.bat on windows 66 | 67 | Finally:: 68 | 69 | paver generate_setup minilib develop 70 | 71 | Paver add a ``setup.py`` file to your package and a portable paver library 72 | (required by ``setup.py``), and deploy your application in development mode; 73 | The "src" folder which contains your package is added to the Python path. 74 | 75 | ``setup.py`` does not contain much. All ``setup()`` parameters are set in 76 | ``pavement.py``. All the distutils and setuptools task are available with paver 77 | and it is very easy to extends or add your own commands (see 78 | `paver documentation `_ 79 | for more details). 80 | 81 | You are ready to write your package and its documentation 82 | (in ``docs/``). You should probably start tracking your project now:: 83 | 84 | git init 85 | git add . 86 | git commit -m "initial import" 87 | 88 | 89 | Github project creation 90 | ----------------------- 91 | 92 | When you are ready to share your work, you will need to 93 | create a repository at GitHub and push your local repository. Paver can do it 94 | for you. Paver will need your GitHub user name and token to create 95 | the repository. You can set them with the following command:: 96 | 97 | git config --global github.user 98 | git config --global github.token 99 | 100 | You can find your token on your 101 | `Github account page `_. 102 | 103 | Then, to create the repository and upload your project:: 104 | 105 | paver gh_register 106 | 107 | 108 | Documentation hosting 109 | --------------------- 110 | 111 | Once the project is created, you can create your gh-pages branch 112 | and upload it to GitHub:: 113 | 114 | paver gh_pages_create gh_pages_build 115 | 116 | Paver will create a submodule of your project at ``docs/_build/html``, 117 | create a gh-pages root branch and push the branch to your project. 118 | It then build the html doc. To clean the html build folder, it update 119 | the submodule (you will lose changes not committed and pushed), 120 | remove every files and directory (except ``.git/``) 121 | and rebuild the documentation. 122 | 123 | When your documentation can be published, simply push your gh-pages submodule 124 | to GitHub:: 125 | 126 | paver gh_pages_build gh_pages_update -m "update docs with..." 127 | 128 | Your documentation should be available 129 | at ``http://.github.com/``. 130 | 131 | You might also want to update the submodule reference (a submodule point 132 | to specific commit on a remote repository, not to the HEAD 133 | of a specific branch):: 134 | 135 | git add docs/_build/html 136 | git commit -m "update gh-pages submodule" 137 | 138 | Help and development 139 | ==================== 140 | 141 | If you'd like to help out, you can fork the project 142 | at http://github.com/dinoboff/github-tools/ and report any bugs 143 | at http://github.com/dinoboff/github-tools/issues. 144 | -------------------------------------------------------------------------------- /pavement.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | from paver.easy import * 5 | from paver.setuputils import setup 6 | 7 | try: 8 | set 9 | except NameError: 10 | from sets import Set as set 11 | 12 | try: 13 | # Import for dev tasks 14 | from paver.virtual import bootstrap 15 | from github.tools.task import ( 16 | gh_pages_build, 17 | gh_pages_clean, 18 | gh_pages_create, 19 | gh_register) 20 | from git import Git 21 | ALL_TASKS_LOADED = True 22 | except ImportError, e: 23 | info("some tasks could not not be imported.") 24 | debug(str(e)) 25 | ALL_TASKS_LOADED = False 26 | 27 | 28 | def read_file(file_path): 29 | return open(file_path).read() 30 | 31 | def write_file(file_path, content=()): 32 | f = open(file_path, 'w') 33 | try: 34 | f.writelines(content) 35 | finally: 36 | f.close() 37 | 38 | 39 | version='0.2rc1+1' 40 | 41 | long_description = read_file('README.rst') + '\n\n' + read_file('CHANGES.rst') 42 | 43 | classifiers = [ 44 | # Get more strings from http://www.python.org/pypi?%3Aaction=list_classifiers 45 | "Programming Language :: Python :: 2.5", # not yet tested on python 2.6 46 | "Development Status :: 4 - Beta", 47 | "Environment :: Console", 48 | "Intended Audience :: Developers", 49 | "License :: OSI Approved :: BSD License", 50 | "Operating System :: POSIX :: Linux", # Not tested yet on osx or Windows 51 | "Topic :: Documentation", 52 | "Topic :: Software Development :: Version Control" 53 | ] 54 | 55 | install_requires = [ 56 | 'setuptools>=0.6c9', 57 | 'GitPython==0.1.7', 58 | 'Sphinx', 59 | 'simplejson', 60 | ] 61 | 62 | extras_require = { 63 | 'template': ['paver-templates>=0.1.0b3'], 64 | } 65 | 66 | entry_points=""" 67 | # -*- Entry points: -*- 68 | [paste.paster_create_template] 69 | gh_package = github.tools.template:GithubTemplate 70 | """ 71 | 72 | setup(name='github-tools', 73 | version=version, 74 | description='Helpers for Python package hosting at GitHub', 75 | long_description=long_description, 76 | classifiers=classifiers, 77 | keywords='sphinx github paster', 78 | author='Damien Lebrun', 79 | author_email='dinoboff@hotmail.com', 80 | url='http://dinoboff.github.com/github-tools/', 81 | license='BSD', 82 | packages = ['github', 'github.tools',], 83 | package_dir = {'': 'src'}, 84 | namespace_packages=['github'], 85 | include_package_data=True, 86 | test_suite='nose.collector', 87 | zip_safe=False, 88 | install_requires=install_requires, 89 | extras_require=extras_require, 90 | entry_points=entry_points, 91 | ) 92 | 93 | options( 94 | minilib=Bunch( 95 | extra_files=[ 96 | 'doctools', 97 | 'virtual' 98 | ] 99 | ), 100 | virtualenv=Bunch( 101 | script_name='bootstrap.py', 102 | dest_dir='./virtual-env/', 103 | packages_to_install=[ 104 | 'virtualenv>=1.3.3', 105 | 'Nose>=0.10.4', 106 | 'Mock>=0.5.0', 107 | ] 108 | ), 109 | sphinx=Bunch( 110 | docroot='docs', 111 | builddir='build', 112 | sourcedir='source', 113 | ), 114 | ) 115 | 116 | @task 117 | def test_sphinx(): 118 | sh("sphinx-build -d docs/build/doctrees -b html docs/source docs/build/html") 119 | 120 | 121 | if ALL_TASKS_LOADED: 122 | 123 | @task 124 | def pip_requirements(): 125 | """Create a pip requirement file.""" 126 | req = set() 127 | for d in ( 128 | options.virtualenv.get('packages_to_install', []) 129 | + options.setup.get('install_requires', []) 130 | ): 131 | req.add(d+'\n') 132 | write_file('dev-requirements.txt', req) 133 | 134 | @task 135 | def manifest(): 136 | """Generate a Manifest using 'git ls-files'""" 137 | includes = ( 138 | "include %s\n" % f 139 | for f in Git('.').ls_files().splitlines() 140 | if not os.path.basename(f).startswith('.') and f != 'docs/build/html' 141 | ) 142 | write_file('MANIFEST.in', includes) 143 | 144 | @task 145 | @needs('pip_requirements', 'generate_setup', 'manifest', 'minilib', 146 | 'setuptools.command.sdist') 147 | def sdist(): 148 | """Overrides sdist to make sure that our setup.py is generated.""" 149 | 150 | 151 | @task 152 | @needs('gh_pages_build', 'github.tools.task.gh_pages_update') 153 | def gh_pages_update(): 154 | """Overrides github.tools.task to rebuild the doc (with sphinx).""" 155 | 156 | 157 | tag_name = 'v%s' % version 158 | 159 | @task 160 | def tag(): 161 | """tag a new version of this distribution""" 162 | git = Git('.') 163 | git.pull('origin', 'master') 164 | git.tag(tag_name) 165 | 166 | 167 | @task 168 | @needs('sdist', 'tag', 'setuptools.command.upload',) 169 | def upload(): 170 | """Upload the distribution to pypi, the new tag and the doc to Github""" 171 | options.update( 172 | gh_pages_update=Bunch(commit_message='Update doc to %s' % version)) 173 | gh_pages_update() 174 | Git('.').push('origin', 'master', tag_name) -------------------------------------------------------------------------------- /src/github/tools/task.py: -------------------------------------------------------------------------------- 1 | """ 2 | :Description: Paver task to manage Python packages hosted at GitHub. 3 | 4 | Configuration 5 | ------------- 6 | 7 | These tasks use: 8 | 9 | * ``options.setup.name``, used for your GitHub repository name. 10 | * ``options.setup.description``, used for your GitHub repository description. 11 | * ``options.sphinx.docroot``, set by default to ``docs/``. 12 | * ``options.sphinx.build``, set by default to ``build``. 13 | * ``options.sphinx.source``, set by default to ``source``. 14 | * ``options.gh_pages.root``, local path to gh_pages root; set by default to 15 | ````/``/html`` 16 | * ``options.gh_pages.htmlroot``, local path to the html output of your doc 17 | built; set by default ``options.gh_pages.root`` (you might want to host 18 | more than your Sphinx doc there). 19 | * ``options.gh_pages.remote_name``, set by default to ``origin` and used for 20 | your github repository remote name. 21 | * ``options.gh_pages.master_branch``, set by default to ``master``. 22 | 23 | ``options.gh_pages.root`` and ``options.gh_pages.htmlroot`` will only be of 24 | any used if you are using something else than Sphinx (and don't want to use 25 | Sphinx options). 26 | """ 27 | 28 | from __future__ import with_statement 29 | import webbrowser 30 | import sys 31 | import os 32 | 33 | from paver.easy import task, options, sh, Bunch, path, needs, cmdopts, dry, info 34 | from git import Git 35 | 36 | from github.tools.gh_pages import GitHubRepo, Credentials 37 | from git.errors import GitCommandError 38 | 39 | 40 | def _adjust_options(): 41 | """ 42 | Set default sphinx and gh_pages options. 43 | """ 44 | if options.get('_github_tools_options_adjusted') is None: 45 | options.setdefault('sphinx', Bunch()) 46 | options.setdefault('gh_pages', Bunch()) 47 | options.setdefault('gh_pages_update', Bunch()) 48 | 49 | options.sphinx.docroot = docroot \ 50 | = path( options.sphinx.get('docroot', 'docs')) 51 | options.sphinx._buildir = buildir = \ 52 | docroot / options.sphinx.get('builddir', 'build') 53 | options.sphinx._sourcedir = \ 54 | docroot / options.sphinx.get('sourcedir', 'source') 55 | options.sphinx._doctrees = buildir / "doctrees" 56 | options.sphinx._htmldir = htmldir = \ 57 | buildir / 'html' 58 | 59 | gh_pages_root = options.gh_pages.get('root', None) 60 | if gh_pages_root is None: 61 | options.gh_pages.root = htmldir 62 | else: 63 | options.gh_pages.root = path(gh_pages_root) 64 | 65 | gh_pages_htmlroot = options.gh_pages.get('htmlroot', None) 66 | if gh_pages_htmlroot is None: 67 | options.gh_pages.htmlroot = options.gh_pages.root 68 | else: 69 | options.gh_pages.htmlroot = path(gh_pages_htmlroot) 70 | options.gh_pages.setdefault('remote_name', 'origin') 71 | options.gh_pages.setdefault('master_branch', 'master') 72 | 73 | options._github_tools_options_adjusted = True 74 | 75 | def _get_repo(working_copy): 76 | """ 77 | Check that a directory is a git working copy. 78 | 79 | return a GitHubRepo instance or exit. 80 | """ 81 | git_dir = os.path.join(working_copy, '.git') 82 | if os.path.exists(git_dir) and os.path.isdir(git_dir): 83 | return GitHubRepo(working_copy) 84 | sys.exit('%s is not a git directory.' % os.getcwd()) 85 | 86 | @task 87 | def gh_register(): 88 | """Create a repository at GitHub and push it your local repository.""" 89 | _adjust_options() 90 | repo = _get_repo(os.getcwd()) 91 | project_name = options.setup.name 92 | project_description = options.setup.get('description','') 93 | remote_name = options.gh_pages.remote_name 94 | master_branch = options.gh_pages.master_branch 95 | credentials = Credentials.get_credentials(repo) 96 | if not credentials: 97 | sys.exit('Your github name and token git config are not set.' 98 | 'Check http://github.com/blog/170-token-authentication' % os.getcwd()) 99 | project = dry( 100 | "Create a repository called %s at %s's GitHub account..." % ( 101 | project_name, credentials.user), 102 | repo.register, 103 | project_name, 104 | credentials, 105 | description=project_description, 106 | is_public=True, 107 | remote_name=remote_name, 108 | master_branch=master_branch) 109 | if project is not None: 110 | info('Opening your project home pages:%s', project.url.http) 111 | webbrowser.open(project.url.http) 112 | 113 | @task 114 | def gh_pages_create(): 115 | """Create a submodule to host your documentation.""" 116 | _adjust_options() 117 | repo = _get_repo(os.getcwd()) 118 | remote_name = options.gh_pages.remote_name 119 | gh_pages_root = str(options.gh_pages.root) 120 | dry( 121 | "Create a submodule at %s and a gh-pages root branch " 122 | "to host your gh-pages..." % gh_pages_root, 123 | repo.add_gh_pages_submodule, 124 | gh_pages_path=gh_pages_root, 125 | remote_name=remote_name) 126 | 127 | @task 128 | @cmdopts([ 129 | ('commit-message=', 'm', 'commit message for the doc update') 130 | ]) 131 | def gh_pages_update(): 132 | """Push your documentation it to GitHub.""" 133 | _adjust_options() 134 | 135 | 136 | repo = _get_repo(os.getcwd()) 137 | remote_name = options.gh_pages.remote_name 138 | gh_pages_root = options.gh_pages.root 139 | 140 | # The gh_pages submodule need to checkout the 141 | try: 142 | gh_pages = repo.validate_gh_pages_submodule(gh_pages_root) 143 | except ValueError, e: 144 | sys.exit('You gh-pages is either not set or set on the wrong branch. \n' 145 | 'Update your submodule, checkout the gh-pages branch ' 146 | '(cd %s; git checkout -t origin/gh-pages) ' 147 | 'and rebuild the documentation.' % gh_pages_root) 148 | 149 | dry("Add modified and untracked content to git index", gh_pages.git.add, '.') 150 | if options.gh_pages_update.get('commit_message') is None: 151 | info("No commit message set... " 152 | "You will have to commit the last changes " 153 | "and push them to GitHub") 154 | return 155 | msg = options.gh_pages_update.commit_message 156 | dry('"Commit any changes with message "%s".' % msg, 157 | gh_pages.git.commit, '-m', msg) 158 | dry("Push any changes on the gh-pages branch.", 159 | gh_pages.git.push, remote_name, 'gh-pages') 160 | info('You might want to update your submodule reference:\n\t' 161 | 'git add %s\n\tgit commit -m "built html doc updated"' 162 | % options.gh_pages.root) 163 | 164 | @task 165 | def gh_pages_clean(): 166 | """Clean your documentation. 167 | 168 | Update the submodule (every changes not committed and pushed will be lost), 169 | pull any changes and remove any file in options.gh_pages.docroot. 170 | """ 171 | _adjust_options() 172 | remote_name = options.gh_pages.remote_name 173 | repo = _get_repo(os.getcwd()) 174 | module = repo.submodules.get(options.gh_pages.root, None) 175 | if module is None: 176 | info("You have not yet created the gh-pages submodule.") 177 | return 178 | 179 | module.update() 180 | try: 181 | dry('Checkout the gh-pages branch', module.git.checkout, 'gh-pages') 182 | except GitCommandError: 183 | dry('Checkout the gh-pages remote branch', 184 | module.git.checkout, '-t', '%s/gh-pages' % remote_name) 185 | else: 186 | dry('Fetch any changes on the gh-pages remote branch', 187 | module.git.pull, remote_name, 'gh-pages') 188 | 189 | for dir_entry in options.gh_pages.htmlroot.listdir(): 190 | if dir_entry.isdir(): 191 | if dir_entry.basename() != '.git': 192 | dry("Remove %s" % dir_entry, dir_entry.rmtree) 193 | else: 194 | dry('Remove %s' % dir_entry, dir_entry.unlink) 195 | 196 | @task 197 | @needs('github.tools.task.gh_pages_clean', 'setuptools.command.egg_info') 198 | def gh_pages_build(): 199 | """Build your documentation with sphinx.""" 200 | _adjust_options() 201 | sh('sphinx-build -d %s -b html %s %s' % ( 202 | options.sphinx._doctrees, 203 | options.sphinx._sourcedir, 204 | options.sphinx._htmldir)) 205 | # a .nojekyll file at the root of the gh-pages repository disable 206 | # Jekyll (http://github.com/blog/572-bypassing-jekyll-on-github-pages) 207 | no_jekyll = options.gh_pages.htmlroot / '.nojekyll' 208 | no_jekyll.touch() 209 | -------------------------------------------------------------------------------- /src/github/tools/test/test_gh_pages.py: -------------------------------------------------------------------------------- 1 | """ 2 | Created on 19 Apr 2009 3 | 4 | @author: damien 5 | """ 6 | from __future__ import with_statement 7 | from StringIO import StringIO 8 | from ConfigParser import RawConfigParser 9 | import os 10 | import unittest 11 | import cgi 12 | 13 | from mock import patch, Mock 14 | 15 | from github.tools.test.utils import eq_, ok_,TempDir, path 16 | from github.tools.gh_pages import Credentials, GitHubRepo, GitHubProject,\ 17 | GitmoduleReader, Submodule, Repo 18 | 19 | 20 | class TestCredentials(unittest.TestCase): 21 | 22 | def test_new(self): 23 | c = Credentials(user='damien', token='xyz') 24 | eq_('damien', c.user) 25 | eq_('xyz', c.token) 26 | 27 | def test_get_credential(self): 28 | with TempDir() as tmp: 29 | repo = Repo.init_bare(path(tmp) / 'repo.git') 30 | repo.git.config('github.user', 'damien') 31 | repo.git.config('github.token', 'xyz') 32 | c = Credentials.get_credentials(repo) 33 | eq_('damien', c.user) 34 | eq_('xyz', c.token) 35 | 36 | 37 | class TestProject(unittest.TestCase): 38 | GITHUB_JSON_RESPONSE = ('{"repository": ' 39 | '{"description": "just a test", ' 40 | '"name": "foo", "private": false, ' 41 | '"url": "http://github.com/damien/foo", ' 42 | '"watchers": 0, "forks": 0, "fork": false, ' 43 | '"owner": "damien", "homepage": ""}}') 44 | 45 | def test_new(self): 46 | project = GitHubProject( 47 | name='foo', 48 | owner='damien', 49 | description='just a test', 50 | is_public=True) 51 | 52 | eq_('foo', project.name) 53 | eq_('damien', project.owner) 54 | eq_('just a test' , project.description) 55 | eq_(True, project.is_public) 56 | eq_('http://github.com/damien/foo', str(project.url)) 57 | eq_('http://github.com/damien/foo', project.url.http) 58 | eq_('git@github.com:damien/foo.git',project.url.ssh) 59 | eq_('git://github.com/damien/foo.git',project.url.git) 60 | eq_('http://github.com/damien/foo/issues',project.url.issue) 61 | 62 | def test_get_project_from_json(self): 63 | project = GitHubProject.get_project_from_json( 64 | StringIO(self.GITHUB_JSON_RESPONSE)) 65 | eq_('foo', project.name) 66 | eq_('damien', project.owner) 67 | eq_('just a test' , project.description) 68 | eq_(True, project.is_public) 69 | eq_('git@github.com:damien/foo.git',project.url.ssh) 70 | 71 | @patch('urllib2.urlopen') 72 | def test_get_project(self, urlopen_mock): 73 | urlopen_mock.return_value = StringIO(self.GITHUB_JSON_RESPONSE) 74 | 75 | project = GitHubProject.get_project('foo', 'damien') 76 | urlopen_mock.assert_called_with('http://github.com/api/v2/json/repos/show/damien/foo') 77 | eq_('foo', project.name) 78 | eq_('damien', project.owner) 79 | eq_('just a test' , project.description) 80 | eq_(True, project.is_public) 81 | eq_('git@github.com:damien/foo.git',project.url.ssh) 82 | 83 | @patch('urllib2.urlopen') 84 | def test_create(self, urlopen_mock): 85 | urlopen_mock.return_value = StringIO(self.GITHUB_JSON_RESPONSE) 86 | credentials = Credentials('damien', 'xyz') 87 | project = GitHubProject.create( 88 | 'foo', credentials, description='just a test', is_public=True) 89 | 90 | # test request to github 91 | url, data = urlopen_mock.call_args[0] 92 | data_dict = dict(cgi.parse_qsl(data)) 93 | eq_('http://github.com/api/v2/json/repos/create', url) 94 | eq_('foo', data_dict['name']) 95 | eq_('just a test', data_dict['description']) 96 | eq_('1', data_dict['public']) 97 | eq_('damien', data_dict['login']) 98 | eq_('xyz', data_dict['token']) 99 | 100 | # test returned project 101 | eq_('foo', project.name) 102 | eq_('damien', project.owner) 103 | eq_('just a test' , project.description) 104 | eq_(True, project.is_public) 105 | eq_('git@github.com:damien/foo.git',project.url.ssh) 106 | 107 | 108 | class TestRepo(unittest.TestCase): 109 | 110 | def test_create(self): 111 | with TempDir() as tmp: 112 | repo = GitHubRepo.create(tmp) 113 | git_dir = tmp / '.git' 114 | ok_(git_dir.exists()) 115 | eq_(str(git_dir), repo.path) 116 | 117 | def test_create_no_folder(self): 118 | with TempDir() as tmp: 119 | repo_path = tmp / 'no-folder' 120 | repo = GitHubRepo.create(repo_path, mk_dir=True) 121 | git_dir = repo_path / '.git' 122 | ok_(git_dir.exists()) 123 | eq_(str(git_dir), repo.path) 124 | 125 | 126 | class TestGitHubRepo(): 127 | 128 | @patch('urllib2.urlopen') 129 | def test_register(self, urlopen_mock): 130 | urlopen_mock.return_value = \ 131 | StringIO(TestProject.GITHUB_JSON_RESPONSE) 132 | with TempDir() as tmp: 133 | repo = GitHubRepo.create(tmp) 134 | git_mock = Mock() 135 | repo.git = git_mock 136 | credentials = Credentials('damien', 'xyz') 137 | repo.register( 138 | 'foo', credentials=credentials, 139 | description='just a test', is_public=True) 140 | 141 | # test project creation 142 | url, data = urlopen_mock.call_args[0] 143 | data_dict = dict(cgi.parse_qsl(data)) 144 | eq_('http://github.com/api/v2/json/repos/create', url) 145 | eq_('foo', data_dict['name']) 146 | eq_('just a test', data_dict['description']) 147 | eq_('1', data_dict['public']) 148 | eq_('damien', data_dict['login']) 149 | eq_('xyz', data_dict['token']) 150 | 151 | # test remote added 152 | eq_(('add', 'origin', 'git@github.com:damien/foo.git'), 153 | git_mock.remote.call_args[0]) 154 | eq_(('origin', 'master'), git_mock.push.call_args[0]) 155 | 156 | def test_add_gh_pages_submodule(self): 157 | #assert False 158 | pass 159 | 160 | 161 | 162 | 163 | class TestSubmodule(unittest.TestCase): 164 | 165 | def test_new(self): 166 | with TempDir() as tmp: 167 | local_path = tmp / 'local-repo' 168 | local_repo = Repo.create(local_path, mk_dir=True) 169 | module = Submodule( 170 | local_repo, 171 | 'file://%s' % local_path, 172 | 'test', sha='a'*40, status=' ') 173 | eq_('file://%s' % local_path, module.url) 174 | eq_('test', module.path) 175 | eq_('a'*40, module.sha) 176 | eq_(' ', module.status) 177 | 178 | def test_init(self): 179 | with TempDir() as tmp: 180 | remote_path = tmp / 'remote-repo' 181 | remote_repo = Repo.create(remote_path, mk_dir=True) 182 | with open(remote_path / 'test.txt', 'w') as f: 183 | f.write('testing...') 184 | remote_repo.git.add('test.txt') 185 | remote_repo.git.commit('-m', 'testing...') 186 | 187 | # set a local repository and add the module 188 | local_path = tmp / 'local-repo' 189 | local_repo = Repo.create(local_path, mk_dir=True) 190 | module = Submodule(local_repo, 191 | 'file://%s' % remote_path, 'test') 192 | module.init('testing') 193 | ok_(os.path.exists(local_path / 'test/.git')) 194 | ok_(os.path.exists(local_path / 'test/test.txt')) 195 | ok_(os.path.exists(local_path / '.gitmodules')) 196 | 197 | class TestSubmoduleDict(unittest.TestCase): 198 | 199 | def test_add(self): 200 | with TempDir() as tmp: 201 | # set a remote repository 202 | remote_path = tmp / 'remote-repo' 203 | remote_repo = Repo.create(remote_path, mk_dir=True) 204 | with open(remote_path / 'test.txt', 'w') as f: 205 | f.write('testing...') 206 | remote_repo.git.add('test.txt') 207 | remote_repo.git.commit('-m', 'testing...') 208 | 209 | # set a local repository and add the module 210 | local_path = tmp / 'local-repo' 211 | local_repo = Repo.create(local_path, mk_dir=True) 212 | eq_(0, len(local_repo.submodules)) 213 | local_repo.submodules.add('file://%s' % remote_path, 'test') 214 | ok_(os.path.exists(local_path / 'test/.git')) 215 | ok_(os.path.exists(local_path / 'test/test.txt')) 216 | ok_(os.path.exists(local_path / '.gitmodules')) 217 | eq_(1, len(local_repo.submodules)) 218 | eq_('file://%s' % remote_path, local_repo.submodules['test'].url) 219 | 220 | class TestGitmoduleReader(unittest.TestCase): 221 | 222 | def test_readline(self): 223 | cfg_txt = ( 224 | '[submodule "docs/build/html"]\n' 225 | '\tpath = docs/build/html\n' 226 | '\turl = git@github.com:damien/foo.git\n' 227 | ) 228 | with TempDir() as tmp: 229 | cfg_path = os.path.join(tmp, 'config') 230 | with open(cfg_path, 'w') as f: 231 | f.write(cfg_txt) 232 | fp = GitmoduleReader(cfg_path) 233 | cfg = RawConfigParser() 234 | cfg.readfp(fp) 235 | section_name = 'submodule "docs/build/html"' 236 | eq_(section_name, cfg.sections()[0]) 237 | eq_('docs/build/html', cfg.get(section_name, 'path')) 238 | eq_('git@github.com:damien/foo.git', 239 | cfg.get(section_name, 'url')) -------------------------------------------------------------------------------- /src/github/tools/gh_pages.py: -------------------------------------------------------------------------------- 1 | """ 2 | Github-tools Models. 3 | """ 4 | from __future__ import with_statement 5 | from urllib import urlencode 6 | from ConfigParser import RawConfigParser 7 | import urllib2 8 | import os 9 | from git.errors import GitCommandError 10 | 11 | try: 12 | import json #@UnresolvedImport 13 | except ImportError: 14 | import simplejson as json 15 | 16 | from git import Git, Repo as _Repo 17 | 18 | 19 | class Credentials(object): 20 | """ 21 | User credential for Github API. 22 | """ 23 | 24 | def __init__(self, user=None, token=None): 25 | self.user = user 26 | self.token = token 27 | 28 | def __len__(self): 29 | return bool(self.user and self.token) 30 | 31 | @classmethod 32 | def get_credentials(cls, repo=None): 33 | """ 34 | Get credentials from the github.user and github.token config values 35 | """ 36 | if repo: 37 | _git = repo.git 38 | else: 39 | _git = Git(os.getcwd()) 40 | return cls( 41 | user=_git.config('github.user', with_exceptions=False), 42 | token=_git.config('github.token', with_exceptions=False) 43 | ) 44 | 45 | 46 | class GitHubProject(object): 47 | """ 48 | GitHub project class 49 | """ 50 | 51 | def __init__(self, 52 | name= None, 53 | owner=None, 54 | description=None, 55 | is_public=None): 56 | self.name = name 57 | self.owner = owner 58 | self.description = description 59 | self.is_public = is_public 60 | self._url = ProjectUrl(self) 61 | 62 | @property 63 | def url(self): 64 | return self._url 65 | 66 | @classmethod 67 | def create(cls, 68 | project_name, credentials, 69 | description='', is_public=True): 70 | """ 71 | Create a new GitHub project. 72 | """ 73 | data = dict( 74 | login=credentials.user, 75 | token=credentials.token, 76 | name=project_name, 77 | description=description, 78 | public=int(is_public)) 79 | url = 'http://github.com/api/v2/json/repos/create' 80 | json_details = urllib2.urlopen(url, urlencode(data)) 81 | return cls.get_project_from_json(json_details) 82 | 83 | @classmethod 84 | def get_project(cls, project_name, owner): 85 | """Fetch the project details from GitHub""" 86 | url = "http://github.com/api/v2/json/repos/show/%s/%s" % ( 87 | owner, project_name) 88 | json_details = urllib2.urlopen(url) 89 | return cls.get_project_from_json(json_details) 90 | 91 | @classmethod 92 | def get_project_from_json(cls, json_details): 93 | details = json.load(json_details)['repository'] 94 | return GitHubProject( 95 | name=details['name'], 96 | owner=details['owner'], 97 | description=details['description'], 98 | is_public= not details['private']) 99 | 100 | 101 | class ProjectUrl(object): 102 | """ 103 | Holds the different GitHub urls of a project. 104 | """ 105 | _tmpls = dict( 106 | ssh='git@github.com:%s/%s.git', 107 | git='git://github.com/%s/%s.git', 108 | http='http://github.com/%s/%s', 109 | gh_pages='http://%s.github.com/%s', 110 | issue='http://github.com/%s/%s/issues' 111 | ) 112 | 113 | def __init__(self, project): 114 | self.project = project 115 | 116 | @property 117 | def ssh(self): 118 | """SSH url to of the repository for read/write access.""" 119 | return self._url('ssh') 120 | 121 | @property 122 | def git(self): 123 | """Git url of the repository for read access.""" 124 | return self._url('git') 125 | 126 | @property 127 | def http(self): 128 | """Url to project home page.""" 129 | return self._url('http') 130 | 131 | @property 132 | def issue(self): 133 | """Url to issue tracker""" 134 | return self._url('issue') 135 | 136 | @property 137 | def gh_pages(self): 138 | """Url for the project's gh-pages.""" 139 | return self._url('gh_pages') 140 | 141 | def _url(self, protocol='ssh'): 142 | if self.project.name is None: 143 | raise AttributeError('Project name not defined') 144 | if self.project.owner is None: 145 | self.project.owner = Credentials.get_credentials().user 146 | if not self.project.owner: 147 | raise AttributeError( 148 | 'The project owner or the github user need to be set.') 149 | return self._tmpls[protocol] % (self.project.owner, self.project.name) 150 | 151 | def __str__(self): 152 | return self.http 153 | 154 | 155 | class Repo(_Repo): 156 | """ 157 | Overwrite git.Repo to add submodule support 158 | and create repository with working copy. 159 | """ 160 | 161 | def __init__(self, path=None): 162 | super(Repo, self).__init__(path) 163 | self.submodules = SubmoduleDict(self) 164 | 165 | @classmethod 166 | def create(cls, path=None, mk_dir=False): 167 | """ 168 | Initialise the repository WITH a working copy 169 | """ 170 | if not os.path.exists(path) and mk_dir: 171 | os.mkdir(path) 172 | _git = Git(path or os.curdir) 173 | _git.init() 174 | return cls(path=path) 175 | 176 | 177 | class GitHubRepo(Repo): 178 | """ 179 | GitHubRepo instance for a repository cloned to/from GitHub. 180 | 181 | Allow to manage gh-pages as a submodule. 182 | """ 183 | 184 | def __init__(self, path=None): 185 | super(GitHubRepo, self).__init__(path) 186 | self.submodules = SubmoduleDict(self) 187 | 188 | def register(self, 189 | project_name, credentials=None, 190 | description='', is_public=True, 191 | remote_name='origin', 192 | master_branch='master'): 193 | """ 194 | Create a repository on GitHub, push your local copy to it, 195 | and add a remote alias name for it to your local copy. 196 | 197 | :param project_name: Name of the repository to create at GitHub. 198 | :param credentials: GitHub API credentials, used to create 199 | a new GitHub repository. 200 | :param description: Repository description. 201 | :param is_public: Should the GitHub repository be public. 202 | :param remote_name: GitHub repository remote name for your local copy 203 | (default to "origin"). 204 | :param master_branch: Name of the branch to push to GitHub 205 | (default to "master") 206 | """ 207 | if credentials is None: 208 | credentials = Credentials.get_credentials(self) 209 | project = GitHubProject.create( 210 | project_name, credentials=credentials, 211 | description=description, is_public=is_public) 212 | self.git.remote('add', remote_name, project.url.ssh) 213 | self.git.push(remote_name, master_branch) 214 | return project 215 | 216 | def add_gh_pages_submodule(self, gh_pages_path, remote_name='origin'): 217 | """ 218 | Add the gh-pages submodule, as a root branch of the GitHub repository. 219 | 220 | :param gh_pages_path: Path to gh-pages submodule. 221 | :param remote_name: Remote name of the GitHub repository. 222 | """ 223 | project_url = self.git.config('remote.%s.url' % remote_name).strip() 224 | self.submodules.add(project_url, gh_pages_path) 225 | 226 | #create gh-pages branch 227 | gh_pages = Repo(gh_pages_path) 228 | gh_pages.git.symbolic_ref('HEAD', 'refs/heads/gh-pages') 229 | index = os.path.join(gh_pages.path, 'index') 230 | if os.path.exists(index): 231 | os.unlink(index) 232 | with open(os.path.join(gh_pages_path, 'index.html'), 'w') as f: 233 | f.write('Documentation coming soon...') 234 | gh_pages.git.add('index.html') 235 | gh_pages.git.commit('-m', 'initial commit') 236 | gh_pages.git.push('origin', 'gh-pages') 237 | 238 | # update submodule 239 | self.git.add(gh_pages_path) 240 | self.git.commit('-m', 'update gh-pages submodule') 241 | 242 | def validate_gh_pages_submodule(self, gh_pages_path): 243 | module = self.submodules[gh_pages_path] 244 | gh_pages_repo = Repo(module.path) 245 | 246 | try: 247 | active_branch = gh_pages_repo.active_branch 248 | except GitCommandError: 249 | active_branch = 'no active branch' 250 | 251 | if active_branch != 'gh-pages': 252 | raise ValueError('"gh-pages" is not the current branch of the "%s" submodule.' % gh_pages_path) 253 | 254 | return module 255 | 256 | 257 | class Submodule(object): 258 | """ 259 | A repository submodule. 260 | 261 | Hold its details: the module path, its repository url, sha and status. 262 | """ 263 | 264 | def __init__(self, repo, url, module_path, sha=None, status=None): 265 | self.repo = repo 266 | self.path = module_path.rstrip('/') 267 | self.url = url 268 | self.sha = sha 269 | self.status = status 270 | self.git = Git(self.path) 271 | 272 | def init(self, msg=None): 273 | if msg is None: 274 | msg = 'Add a submodule for "%s" at "%s' % (self.url, self.path,) 275 | if self.status in (None, '-'): 276 | self.repo.git.submodule('add', self.url, self.path) 277 | self.repo.git.submodule('init', self.path) 278 | self.repo.git.commit('-m', msg) 279 | 280 | def update(self): 281 | if self.status == '-': 282 | self.repo.git.submodule('init', self.path) 283 | self.repo.git.submodule('update', self.path) 284 | 285 | 286 | class SubmoduleDict(dict): 287 | """ 288 | List the submodules of a repository. 289 | 290 | The keys are submodules name (and path). 291 | """ 292 | 293 | def __init__(self, repo): 294 | self.repo = repo 295 | self._get_submodules() 296 | 297 | def __getitem__(self, path): 298 | self._get_submodules() 299 | return super(SubmoduleDict, self).__getitem__(path.rstrip('/')) 300 | 301 | def __setitem__(self, path, module): 302 | if module.path not in self: 303 | module.init() 304 | self._get_submodules() 305 | 306 | def __delitem__(self, name): 307 | raise AttributeError('Not implemented. You need to remove the module yourself.') 308 | 309 | def add(self,url, path): 310 | module = Submodule(self.repo, url, path) 311 | self.__setitem__(path, module) 312 | 313 | def clear(self): 314 | self._get_submodules() 315 | 316 | def _get_submodules(self): 317 | """ 318 | Parse .gitmodule to get the list of submodules. 319 | """ 320 | super(SubmoduleDict, self).clear() 321 | gitmodule = os.path.join(self.repo.git.get_dir, '.gitmodules') 322 | if not os.path.exists(gitmodule): 323 | return 324 | cfg = RawConfigParser() 325 | cfg.readfp(GitmoduleReader(gitmodule), gitmodule) 326 | for section in cfg.sections(): 327 | path = cfg.get(section, 'path') 328 | url = cfg.get(section, 'url') 329 | info = self.repo.git.submodule('status', path) 330 | status = None 331 | sha = None 332 | if info: 333 | status = info[0] 334 | sha = info[1:41] 335 | module = Submodule(self.repo, url, path, sha=sha, status=status) 336 | super(SubmoduleDict, self).__setitem__( 337 | module.path, 338 | module) 339 | 340 | 341 | class GitmoduleReader(file): 342 | """ 343 | Extends file to trim the start of each read line (with readline()). 344 | 345 | Used by SubmoduleDict to parse .gitmodule files. RawConfigParser doesn't seems 346 | to be able to parse those .gitmodule lines that start a tab. 347 | """ 348 | def readline(self,*args, **kw): 349 | return file.readline(self,*args, **kw).lstrip() -------------------------------------------------------------------------------- /bootstrap.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ## WARNING: This file is generated 3 | #!/usr/bin/env python 4 | """Create a "virtual" Python installation 5 | """ 6 | 7 | import sys 8 | import os 9 | import optparse 10 | import shutil 11 | import logging 12 | import distutils.sysconfig 13 | try: 14 | import subprocess 15 | except ImportError, e: 16 | if sys.version_info <= (2, 3): 17 | print 'ERROR: %s' % e 18 | print 'ERROR: this script requires Python 2.4 or greater; or at least the subprocess module.' 19 | print 'If you copy subprocess.py from a newer version of Python this script will probably work' 20 | sys.exit(101) 21 | else: 22 | raise 23 | try: 24 | set 25 | except NameError: 26 | from sets import Set as set 27 | 28 | join = os.path.join 29 | py_version = 'python%s.%s' % (sys.version_info[0], sys.version_info[1]) 30 | is_jython = sys.platform.startswith('java') 31 | expected_exe = is_jython and 'jython' or 'python' 32 | 33 | REQUIRED_MODULES = ['os', 'posix', 'posixpath', 'ntpath', 'genericpath', 34 | 'fnmatch', 'locale', 'encodings', 'codecs', 35 | 'stat', 'UserDict', 'readline', 'copy_reg', 'types', 36 | 're', 'sre', 'sre_parse', 'sre_constants', 'sre_compile', 37 | 'lib-dynload', 'config', 'zlib'] 38 | 39 | if sys.version_info[:2] == (2, 6): 40 | REQUIRED_MODULES.extend(['warnings', 'linecache', '_abcoll', 'abc']) 41 | if sys.version_info[:2] <= (2, 3): 42 | REQUIRED_MODULES.extend(['sets', '__future__']) 43 | 44 | class Logger(object): 45 | 46 | """ 47 | Logging object for use in command-line script. Allows ranges of 48 | levels, to avoid some redundancy of displayed information. 49 | """ 50 | 51 | DEBUG = logging.DEBUG 52 | INFO = logging.INFO 53 | NOTIFY = (logging.INFO+logging.WARN)/2 54 | WARN = WARNING = logging.WARN 55 | ERROR = logging.ERROR 56 | FATAL = logging.FATAL 57 | 58 | LEVELS = [DEBUG, INFO, NOTIFY, WARN, ERROR, FATAL] 59 | 60 | def __init__(self, consumers): 61 | self.consumers = consumers 62 | self.indent = 0 63 | self.in_progress = None 64 | self.in_progress_hanging = False 65 | 66 | def debug(self, msg, *args, **kw): 67 | self.log(self.DEBUG, msg, *args, **kw) 68 | def info(self, msg, *args, **kw): 69 | self.log(self.INFO, msg, *args, **kw) 70 | def notify(self, msg, *args, **kw): 71 | self.log(self.NOTIFY, msg, *args, **kw) 72 | def warn(self, msg, *args, **kw): 73 | self.log(self.WARN, msg, *args, **kw) 74 | def error(self, msg, *args, **kw): 75 | self.log(self.WARN, msg, *args, **kw) 76 | def fatal(self, msg, *args, **kw): 77 | self.log(self.FATAL, msg, *args, **kw) 78 | def log(self, level, msg, *args, **kw): 79 | if args: 80 | if kw: 81 | raise TypeError( 82 | "You may give positional or keyword arguments, not both") 83 | args = args or kw 84 | rendered = None 85 | for consumer_level, consumer in self.consumers: 86 | if self.level_matches(level, consumer_level): 87 | if (self.in_progress_hanging 88 | and consumer in (sys.stdout, sys.stderr)): 89 | self.in_progress_hanging = False 90 | sys.stdout.write('\n') 91 | sys.stdout.flush() 92 | if rendered is None: 93 | if args: 94 | rendered = msg % args 95 | else: 96 | rendered = msg 97 | rendered = ' '*self.indent + rendered 98 | if hasattr(consumer, 'write'): 99 | consumer.write(rendered+'\n') 100 | else: 101 | consumer(rendered) 102 | 103 | def start_progress(self, msg): 104 | assert not self.in_progress, ( 105 | "Tried to start_progress(%r) while in_progress %r" 106 | % (msg, self.in_progress)) 107 | if self.level_matches(self.NOTIFY, self._stdout_level()): 108 | sys.stdout.write(msg) 109 | sys.stdout.flush() 110 | self.in_progress_hanging = True 111 | else: 112 | self.in_progress_hanging = False 113 | self.in_progress = msg 114 | 115 | def end_progress(self, msg='done.'): 116 | assert self.in_progress, ( 117 | "Tried to end_progress without start_progress") 118 | if self.stdout_level_matches(self.NOTIFY): 119 | if not self.in_progress_hanging: 120 | # Some message has been printed out since start_progress 121 | sys.stdout.write('...' + self.in_progress + msg + '\n') 122 | sys.stdout.flush() 123 | else: 124 | sys.stdout.write(msg + '\n') 125 | sys.stdout.flush() 126 | self.in_progress = None 127 | self.in_progress_hanging = False 128 | 129 | def show_progress(self): 130 | """If we are in a progress scope, and no log messages have been 131 | shown, write out another '.'""" 132 | if self.in_progress_hanging: 133 | sys.stdout.write('.') 134 | sys.stdout.flush() 135 | 136 | def stdout_level_matches(self, level): 137 | """Returns true if a message at this level will go to stdout""" 138 | return self.level_matches(level, self._stdout_level()) 139 | 140 | def _stdout_level(self): 141 | """Returns the level that stdout runs at""" 142 | for level, consumer in self.consumers: 143 | if consumer is sys.stdout: 144 | return level 145 | return self.FATAL 146 | 147 | def level_matches(self, level, consumer_level): 148 | """ 149 | >>> l = Logger() 150 | >>> l.level_matches(3, 4) 151 | False 152 | >>> l.level_matches(3, 2) 153 | True 154 | >>> l.level_matches(slice(None, 3), 3) 155 | False 156 | >>> l.level_matches(slice(None, 3), 2) 157 | True 158 | >>> l.level_matches(slice(1, 3), 1) 159 | True 160 | >>> l.level_matches(slice(2, 3), 1) 161 | False 162 | """ 163 | if isinstance(level, slice): 164 | start, stop = level.start, level.stop 165 | if start is not None and start > consumer_level: 166 | return False 167 | if stop is not None or stop <= consumer_level: 168 | return False 169 | return True 170 | else: 171 | return level >= consumer_level 172 | 173 | #@classmethod 174 | def level_for_integer(cls, level): 175 | levels = cls.LEVELS 176 | if level < 0: 177 | return levels[0] 178 | if level >= len(levels): 179 | return levels[-1] 180 | return levels[level] 181 | 182 | level_for_integer = classmethod(level_for_integer) 183 | 184 | def mkdir(path): 185 | if not os.path.exists(path): 186 | logger.info('Creating %s', path) 187 | os.makedirs(path) 188 | else: 189 | logger.info('Directory %s already exists', path) 190 | 191 | def copyfile(src, dest, symlink=True): 192 | if not os.path.exists(src): 193 | # Some bad symlink in the src 194 | logger.warn('Cannot find file %s (bad symlink)', src) 195 | return 196 | if os.path.exists(dest): 197 | logger.debug('File %s already exists', dest) 198 | return 199 | if not os.path.exists(os.path.dirname(dest)): 200 | logger.info('Creating parent directories for %s' % os.path.dirname(dest)) 201 | os.makedirs(os.path.dirname(dest)) 202 | if symlink and hasattr(os, 'symlink'): 203 | logger.info('Symlinking %s', dest) 204 | os.symlink(os.path.abspath(src), dest) 205 | else: 206 | logger.info('Copying to %s', dest) 207 | if os.path.isdir(src): 208 | shutil.copytree(src, dest, True) 209 | else: 210 | shutil.copy2(src, dest) 211 | 212 | def writefile(dest, content, overwrite=True): 213 | if not os.path.exists(dest): 214 | logger.info('Writing %s', dest) 215 | f = open(dest, 'wb') 216 | f.write(content) 217 | f.close() 218 | return 219 | else: 220 | f = open(dest, 'rb') 221 | c = f.read() 222 | f.close() 223 | if c != content: 224 | if not overwrite: 225 | logger.notify('File %s exists with different content; not overwriting', dest) 226 | return 227 | logger.notify('Overwriting %s with new content', dest) 228 | f = open(dest, 'wb') 229 | f.write(content) 230 | f.close() 231 | else: 232 | logger.info('Content %s already in place', dest) 233 | 234 | def rmtree(dir): 235 | if os.path.exists(dir): 236 | logger.notify('Deleting tree %s', dir) 237 | shutil.rmtree(dir) 238 | else: 239 | logger.info('Do not need to delete %s; already gone', dir) 240 | 241 | def make_exe(fn): 242 | if hasattr(os, 'chmod'): 243 | oldmode = os.stat(fn).st_mode & 07777 244 | newmode = (oldmode | 0555) & 07777 245 | os.chmod(fn, newmode) 246 | logger.info('Changed mode of %s to %s', fn, oct(newmode)) 247 | 248 | def install_setuptools(py_executable, unzip=False): 249 | setup_fn = 'setuptools-0.6c9-py%s.egg' % sys.version[:3] 250 | search_dirs = ['.', os.path.dirname(__file__), join(os.path.dirname(__file__), 'support-files')] 251 | if os.path.splitext(os.path.dirname(__file__))[0] != 'virtualenv': 252 | # Probably some boot script; just in case virtualenv is installed... 253 | try: 254 | import virtualenv 255 | except ImportError: 256 | pass 257 | else: 258 | search_dirs.append(os.path.join(os.path.dirname(virtualenv.__file__), 'support-files')) 259 | for dir in search_dirs: 260 | if os.path.exists(join(dir, setup_fn)): 261 | setup_fn = join(dir, setup_fn) 262 | break 263 | if is_jython and os._name == 'nt': 264 | # Jython's .bat sys.executable can't handle a command line 265 | # argument with newlines 266 | import tempfile 267 | fd, ez_setup = tempfile.mkstemp('.py') 268 | os.write(fd, EZ_SETUP_PY) 269 | os.close(fd) 270 | cmd = [py_executable, ez_setup] 271 | else: 272 | cmd = [py_executable, '-c', EZ_SETUP_PY] 273 | if unzip: 274 | cmd.append('--always-unzip') 275 | env = {} 276 | if logger.stdout_level_matches(logger.DEBUG): 277 | cmd.append('-v') 278 | if os.path.exists(setup_fn): 279 | logger.info('Using existing Setuptools egg: %s', setup_fn) 280 | cmd.append(setup_fn) 281 | if os.environ.get('PYTHONPATH'): 282 | env['PYTHONPATH'] = setup_fn + os.path.pathsep + os.environ['PYTHONPATH'] 283 | else: 284 | env['PYTHONPATH'] = setup_fn 285 | else: 286 | logger.info('No Setuptools egg found; downloading') 287 | cmd.extend(['--always-copy', '-U', 'setuptools']) 288 | logger.start_progress('Installing setuptools...') 289 | logger.indent += 2 290 | cwd = None 291 | if not os.access(os.getcwd(), os.W_OK): 292 | cwd = '/tmp' 293 | try: 294 | call_subprocess(cmd, show_stdout=False, 295 | filter_stdout=filter_ez_setup, 296 | extra_env=env, 297 | cwd=cwd) 298 | finally: 299 | logger.indent -= 2 300 | logger.end_progress() 301 | if is_jython and os._name == 'nt': 302 | os.remove(ez_setup) 303 | 304 | def filter_ez_setup(line): 305 | if not line.strip(): 306 | return Logger.DEBUG 307 | for prefix in ['Reading ', 'Best match', 'Processing setuptools', 308 | 'Copying setuptools', 'Adding setuptools', 309 | 'Installing ', 'Installed ']: 310 | if line.startswith(prefix): 311 | return Logger.DEBUG 312 | return Logger.INFO 313 | 314 | def main(): 315 | parser = optparse.OptionParser( 316 | version="1.3.3", 317 | usage="%prog [OPTIONS] DEST_DIR") 318 | 319 | parser.add_option( 320 | '-v', '--verbose', 321 | action='count', 322 | dest='verbose', 323 | default=0, 324 | help="Increase verbosity") 325 | 326 | parser.add_option( 327 | '-q', '--quiet', 328 | action='count', 329 | dest='quiet', 330 | default=0, 331 | help='Decrease verbosity') 332 | 333 | parser.add_option( 334 | '-p', '--python', 335 | dest='python', 336 | metavar='PYTHON_EXE', 337 | help='The Python interpreter to use, e.g., --python=python2.5 will use the python2.5 ' 338 | 'interpreter to create the new environment. The default is the interpreter that ' 339 | 'virtualenv was installed with (%s)' % sys.executable) 340 | 341 | parser.add_option( 342 | '--clear', 343 | dest='clear', 344 | action='store_true', 345 | help="Clear out the non-root install and start from scratch") 346 | 347 | parser.add_option( 348 | '--no-site-packages', 349 | dest='no_site_packages', 350 | action='store_true', 351 | help="Don't give access to the global site-packages dir to the " 352 | "virtual environment") 353 | 354 | parser.add_option( 355 | '--unzip-setuptools', 356 | dest='unzip_setuptools', 357 | action='store_true', 358 | help="Unzip Setuptools when installing it") 359 | 360 | parser.add_option( 361 | '--relocatable', 362 | dest='relocatable', 363 | action='store_true', 364 | help='Make an EXISTING virtualenv environment relocatable. ' 365 | 'This fixes up scripts and makes all .pth files relative') 366 | 367 | if 'extend_parser' in globals(): 368 | extend_parser(parser) 369 | 370 | options, args = parser.parse_args() 371 | 372 | global logger 373 | 374 | if 'adjust_options' in globals(): 375 | adjust_options(options, args) 376 | 377 | verbosity = options.verbose - options.quiet 378 | logger = Logger([(Logger.level_for_integer(2-verbosity), sys.stdout)]) 379 | 380 | if options.python and not os.environ.get('VIRTUALENV_INTERPRETER_RUNNING'): 381 | env = os.environ.copy() 382 | interpreter = resolve_interpreter(options.python) 383 | if interpreter == sys.executable: 384 | logger.warn('Already using interpreter %s' % interpreter) 385 | else: 386 | logger.notify('Running virtualenv with interpreter %s' % interpreter) 387 | env['VIRTUALENV_INTERPRETER_RUNNING'] = 'true' 388 | file = __file__ 389 | if file.endswith('.pyc'): 390 | file = file[:-1] 391 | os.execvpe(interpreter, [interpreter, file] + sys.argv[1:], env) 392 | 393 | if not args: 394 | print 'You must provide a DEST_DIR' 395 | parser.print_help() 396 | sys.exit(2) 397 | if len(args) > 1: 398 | print 'There must be only one argument: DEST_DIR (you gave %s)' % ( 399 | ' '.join(args)) 400 | parser.print_help() 401 | sys.exit(2) 402 | 403 | home_dir = args[0] 404 | 405 | if os.environ.get('WORKING_ENV'): 406 | logger.fatal('ERROR: you cannot run virtualenv while in a workingenv') 407 | logger.fatal('Please deactivate your workingenv, then re-run this script') 408 | sys.exit(3) 409 | 410 | if os.environ.get('PYTHONHOME'): 411 | if sys.platform == 'win32': 412 | name = '%PYTHONHOME%' 413 | else: 414 | name = '$PYTHONHOME' 415 | logger.warn('%s is set; this can cause problems creating environments' % name) 416 | 417 | if options.relocatable: 418 | make_environment_relocatable(home_dir) 419 | return 420 | 421 | create_environment(home_dir, site_packages=not options.no_site_packages, clear=options.clear, 422 | unzip_setuptools=options.unzip_setuptools) 423 | if 'after_install' in globals(): 424 | after_install(options, home_dir) 425 | 426 | def call_subprocess(cmd, show_stdout=True, 427 | filter_stdout=None, cwd=None, 428 | raise_on_returncode=True, extra_env=None): 429 | cmd_parts = [] 430 | for part in cmd: 431 | if len(part) > 40: 432 | part = part[:30]+"..."+part[-5:] 433 | if ' ' in part or '\n' in part or '"' in part or "'" in part: 434 | part = '"%s"' % part.replace('"', '\\"') 435 | cmd_parts.append(part) 436 | cmd_desc = ' '.join(cmd_parts) 437 | if show_stdout: 438 | stdout = None 439 | else: 440 | stdout = subprocess.PIPE 441 | logger.debug("Running command %s" % cmd_desc) 442 | if extra_env: 443 | env = os.environ.copy() 444 | env.update(extra_env) 445 | else: 446 | env = None 447 | try: 448 | proc = subprocess.Popen( 449 | cmd, stderr=subprocess.STDOUT, stdin=None, stdout=stdout, 450 | cwd=cwd, env=env) 451 | except Exception, e: 452 | logger.fatal( 453 | "Error %s while executing command %s" % (e, cmd_desc)) 454 | raise 455 | all_output = [] 456 | if stdout is not None: 457 | stdout = proc.stdout 458 | while 1: 459 | line = stdout.readline() 460 | if not line: 461 | break 462 | line = line.rstrip() 463 | all_output.append(line) 464 | if filter_stdout: 465 | level = filter_stdout(line) 466 | if isinstance(level, tuple): 467 | level, line = level 468 | logger.log(level, line) 469 | if not logger.stdout_level_matches(level): 470 | logger.show_progress() 471 | else: 472 | logger.info(line) 473 | else: 474 | proc.communicate() 475 | proc.wait() 476 | if proc.returncode: 477 | if raise_on_returncode: 478 | if all_output: 479 | logger.notify('Complete output from command %s:' % cmd_desc) 480 | logger.notify('\n'.join(all_output) + '\n----------------------------------------') 481 | raise OSError( 482 | "Command %s failed with error code %s" 483 | % (cmd_desc, proc.returncode)) 484 | else: 485 | logger.warn( 486 | "Command %s had error code %s" 487 | % (cmd_desc, proc.returncode)) 488 | 489 | 490 | def create_environment(home_dir, site_packages=True, clear=False, 491 | unzip_setuptools=False): 492 | """ 493 | Creates a new environment in ``home_dir``. 494 | 495 | If ``site_packages`` is true (the default) then the global 496 | ``site-packages/`` directory will be on the path. 497 | 498 | If ``clear`` is true (default False) then the environment will 499 | first be cleared. 500 | """ 501 | home_dir, lib_dir, inc_dir, bin_dir = path_locations(home_dir) 502 | 503 | py_executable = install_python( 504 | home_dir, lib_dir, inc_dir, bin_dir, 505 | site_packages=site_packages, clear=clear) 506 | 507 | install_distutils(lib_dir, home_dir) 508 | 509 | install_setuptools(py_executable, unzip=unzip_setuptools) 510 | 511 | install_activate(home_dir, bin_dir) 512 | 513 | def path_locations(home_dir): 514 | """Return the path locations for the environment (where libraries are, 515 | where scripts go, etc)""" 516 | # XXX: We'd use distutils.sysconfig.get_python_inc/lib but its 517 | # prefix arg is broken: http://bugs.python.org/issue3386 518 | if sys.platform == 'win32': 519 | # Windows has lots of problems with executables with spaces in 520 | # the name; this function will remove them (using the ~1 521 | # format): 522 | mkdir(home_dir) 523 | import win32api 524 | home_dir = win32api.GetShortPathName(home_dir) 525 | lib_dir = join(home_dir, 'Lib') 526 | inc_dir = join(home_dir, 'Include') 527 | bin_dir = join(home_dir, 'Scripts') 528 | elif is_jython: 529 | lib_dir = join(home_dir, 'Lib') 530 | inc_dir = join(home_dir, 'Include') 531 | bin_dir = join(home_dir, 'bin') 532 | else: 533 | lib_dir = join(home_dir, 'lib', py_version) 534 | inc_dir = join(home_dir, 'include', py_version) 535 | bin_dir = join(home_dir, 'bin') 536 | return home_dir, lib_dir, inc_dir, bin_dir 537 | 538 | def install_python(home_dir, lib_dir, inc_dir, bin_dir, site_packages, clear): 539 | """Install just the base environment, no distutils patches etc""" 540 | if sys.executable.startswith(bin_dir): 541 | print 'Please use the *system* python to run this script' 542 | return 543 | 544 | if clear: 545 | rmtree(lib_dir) 546 | ## FIXME: why not delete it? 547 | ## Maybe it should delete everything with #!/path/to/venv/python in it 548 | logger.notify('Not deleting %s', bin_dir) 549 | 550 | if hasattr(sys, 'real_prefix'): 551 | logger.notify('Using real prefix %r' % sys.real_prefix) 552 | prefix = sys.real_prefix 553 | else: 554 | prefix = sys.prefix 555 | mkdir(lib_dir) 556 | fix_lib64(lib_dir) 557 | stdlib_dirs = [os.path.dirname(os.__file__)] 558 | if sys.platform == 'win32': 559 | stdlib_dirs.append(join(os.path.dirname(stdlib_dirs[0]), 'DLLs')) 560 | elif sys.platform == 'darwin': 561 | stdlib_dirs.append(join(stdlib_dirs[0], 'site-packages')) 562 | for stdlib_dir in stdlib_dirs: 563 | if not os.path.isdir(stdlib_dir): 564 | continue 565 | if hasattr(os, 'symlink'): 566 | logger.info('Symlinking Python bootstrap modules') 567 | else: 568 | logger.info('Copying Python bootstrap modules') 569 | logger.indent += 2 570 | try: 571 | for fn in os.listdir(stdlib_dir): 572 | if fn != 'site-packages' and os.path.splitext(fn)[0] in REQUIRED_MODULES: 573 | copyfile(join(stdlib_dir, fn), join(lib_dir, fn)) 574 | finally: 575 | logger.indent -= 2 576 | mkdir(join(lib_dir, 'site-packages')) 577 | writefile(join(lib_dir, 'site.py'), SITE_PY) 578 | writefile(join(lib_dir, 'orig-prefix.txt'), prefix) 579 | site_packages_filename = join(lib_dir, 'no-global-site-packages.txt') 580 | if not site_packages: 581 | writefile(site_packages_filename, '') 582 | else: 583 | if os.path.exists(site_packages_filename): 584 | logger.info('Deleting %s' % site_packages_filename) 585 | os.unlink(site_packages_filename) 586 | 587 | stdinc_dir = join(prefix, 'include', py_version) 588 | if os.path.exists(stdinc_dir): 589 | copyfile(stdinc_dir, inc_dir) 590 | else: 591 | logger.debug('No include dir %s' % stdinc_dir) 592 | 593 | if sys.exec_prefix != prefix: 594 | if sys.platform == 'win32': 595 | exec_dir = join(sys.exec_prefix, 'lib') 596 | elif is_jython: 597 | exec_dir = join(sys.exec_prefix, 'Lib') 598 | else: 599 | exec_dir = join(sys.exec_prefix, 'lib', py_version) 600 | for fn in os.listdir(exec_dir): 601 | copyfile(join(exec_dir, fn), join(lib_dir, fn)) 602 | 603 | if is_jython: 604 | # Jython has either jython-dev.jar and javalib/ dir, or just 605 | # jython.jar 606 | for name in 'jython-dev.jar', 'javalib', 'jython.jar': 607 | src = join(prefix, name) 608 | if os.path.exists(src): 609 | copyfile(src, join(home_dir, name)) 610 | copyfile(join(prefix, 'cachedir'), join(home_dir, 'cachedir'), 611 | symlink=False) 612 | 613 | mkdir(bin_dir) 614 | py_executable = join(bin_dir, os.path.basename(sys.executable)) 615 | if 'Python.framework' in prefix: 616 | if py_executable.endswith('/Python'): 617 | # The name of the python executable is not quite what 618 | # we want, rename it. 619 | py_executable = os.path.join( 620 | os.path.dirname(py_executable), 'python') 621 | 622 | logger.notify('New %s executable in %s', expected_exe, py_executable) 623 | if sys.executable != py_executable: 624 | ## FIXME: could I just hard link? 625 | executable = sys.executable 626 | if sys.platform == 'cygwin' and os.path.exists(executable + '.exe'): 627 | # Cygwin misreports sys.executable sometimes 628 | executable += '.exe' 629 | py_executable += '.exe' 630 | logger.info('Executable actually exists in %s' % executable) 631 | shutil.copyfile(executable, py_executable) 632 | make_exe(py_executable) 633 | if os.path.splitext(os.path.basename(py_executable))[0] != expected_exe: 634 | secondary_exe = os.path.join(os.path.dirname(py_executable), 635 | expected_exe) 636 | py_executable_ext = os.path.splitext(py_executable)[1] 637 | if py_executable_ext == '.exe': 638 | # python2.4 gives an extension of '.4' :P 639 | secondary_exe += py_executable_ext 640 | if os.path.exists(secondary_exe): 641 | logger.warn('Not overwriting existing %s script %s (you must use %s)' 642 | % (expected_exe, secondary_exe, py_executable)) 643 | else: 644 | logger.notify('Also creating executable in %s' % secondary_exe) 645 | shutil.copyfile(sys.executable, secondary_exe) 646 | make_exe(secondary_exe) 647 | 648 | if 'Python.framework' in prefix: 649 | logger.debug('MacOSX Python framework detected') 650 | 651 | # Copy the framework's dylib into the virtual 652 | # environment 653 | virtual_lib = os.path.join(home_dir, '.Python') 654 | 655 | if os.path.exists(virtual_lib): 656 | os.unlink(virtual_lib) 657 | copyfile( 658 | os.path.join(prefix, 'Python'), 659 | virtual_lib) 660 | 661 | # And then change the install_name of the copied python executable 662 | try: 663 | call_subprocess( 664 | ["install_name_tool", "-change", 665 | os.path.join(prefix, 'Python'), 666 | '@executable_path/../.Python', 667 | py_executable]) 668 | except: 669 | logger.fatal( 670 | "Could not call install_name_tool -- you must have Apple's development tools installed") 671 | raise 672 | 673 | # Some tools depend on pythonX.Y being present 674 | pth = py_executable + '%s.%s' % ( 675 | sys.version_info[0], sys.version_info[1]) 676 | if os.path.exists(pth): 677 | os.unlink(pth) 678 | os.symlink('python', pth) 679 | 680 | if sys.platform == 'win32' and ' ' in py_executable: 681 | # There's a bug with subprocess on Windows when using a first 682 | # argument that has a space in it. Instead we have to quote 683 | # the value: 684 | py_executable = '"%s"' % py_executable 685 | cmd = [py_executable, '-c', 'import sys; print sys.prefix'] 686 | logger.info('Testing executable with %s %s "%s"' % tuple(cmd)) 687 | proc = subprocess.Popen(cmd, 688 | stdout=subprocess.PIPE) 689 | proc_stdout, proc_stderr = proc.communicate() 690 | proc_stdout = os.path.normcase(os.path.abspath(proc_stdout.strip())) 691 | if proc_stdout != os.path.normcase(os.path.abspath(home_dir)): 692 | logger.fatal( 693 | 'ERROR: The executable %s is not functioning' % py_executable) 694 | logger.fatal( 695 | 'ERROR: It thinks sys.prefix is %r (should be %r)' 696 | % (proc_stdout, os.path.normcase(os.path.abspath(home_dir)))) 697 | logger.fatal( 698 | 'ERROR: virtualenv is not compatible with this system or executable') 699 | sys.exit(100) 700 | else: 701 | logger.info('Got sys.prefix result: %r' % proc_stdout) 702 | 703 | pydistutils = os.path.expanduser('~/.pydistutils.cfg') 704 | if os.path.exists(pydistutils): 705 | logger.notify('Please make sure you remove any previous custom paths from ' 706 | 'your %s file.' % pydistutils) 707 | ## FIXME: really this should be calculated earlier 708 | return py_executable 709 | 710 | def install_activate(home_dir, bin_dir): 711 | if sys.platform == 'win32' or is_jython and os._name == 'nt': 712 | files = {'activate.bat': ACTIVATE_BAT, 713 | 'deactivate.bat': DEACTIVATE_BAT} 714 | if os.environ.get('OS') == 'Windows_NT' and os.environ.get('OSTYPE') == 'cygwin': 715 | files['activate'] = ACTIVATE_SH 716 | else: 717 | files = {'activate': ACTIVATE_SH} 718 | files['activate_this.py'] = ACTIVATE_THIS 719 | for name, content in files.items(): 720 | content = content.replace('__VIRTUAL_ENV__', os.path.abspath(home_dir)) 721 | content = content.replace('__VIRTUAL_NAME__', os.path.basename(os.path.abspath(home_dir))) 722 | content = content.replace('__BIN_NAME__', os.path.basename(bin_dir)) 723 | writefile(os.path.join(bin_dir, name), content) 724 | 725 | def install_distutils(lib_dir, home_dir): 726 | distutils_path = os.path.join(lib_dir, 'distutils') 727 | mkdir(distutils_path) 728 | ## FIXME: maybe this prefix setting should only be put in place if 729 | ## there's a local distutils.cfg with a prefix setting? 730 | home_dir = os.path.abspath(home_dir) 731 | ## FIXME: this is breaking things, removing for now: 732 | #distutils_cfg = DISTUTILS_CFG + "\n[install]\nprefix=%s\n" % home_dir 733 | writefile(os.path.join(distutils_path, '__init__.py'), DISTUTILS_INIT) 734 | writefile(os.path.join(distutils_path, 'distutils.cfg'), DISTUTILS_CFG, overwrite=False) 735 | 736 | def fix_lib64(lib_dir): 737 | """ 738 | Some platforms (particularly Gentoo on x64) put things in lib64/pythonX.Y 739 | instead of lib/pythonX.Y. If this is such a platform we'll just create a 740 | symlink so lib64 points to lib 741 | """ 742 | if [p for p in distutils.sysconfig.get_config_vars().values() 743 | if isinstance(p, basestring) and 'lib64' in p]: 744 | logger.debug('This system uses lib64; symlinking lib64 to lib') 745 | assert os.path.basename(lib_dir) == 'python%s' % sys.version[:3], ( 746 | "Unexpected python lib dir: %r" % lib_dir) 747 | lib_parent = os.path.dirname(lib_dir) 748 | assert os.path.basename(lib_parent) == 'lib', ( 749 | "Unexpected parent dir: %r" % lib_parent) 750 | copyfile(lib_parent, os.path.join(os.path.dirname(lib_parent), 'lib64')) 751 | 752 | def resolve_interpreter(exe): 753 | """ 754 | If the executable given isn't an absolute path, search $PATH for the interpreter 755 | """ 756 | if os.path.abspath(exe) != exe: 757 | paths = os.environ.get('PATH', '').split(os.pathsep) 758 | for path in paths: 759 | if os.path.exists(os.path.join(path, exe)): 760 | exe = os.path.join(path, exe) 761 | break 762 | if not os.path.exists(exe): 763 | logger.fatal('The executable %s (from --python=%s) does not exist' % (exe, exe)) 764 | sys.exit(3) 765 | return exe 766 | 767 | ############################################################ 768 | ## Relocating the environment: 769 | 770 | def make_environment_relocatable(home_dir): 771 | """ 772 | Makes the already-existing environment use relative paths, and takes out 773 | the #!-based environment selection in scripts. 774 | """ 775 | activate_this = os.path.join(home_dir, 'bin', 'activate_this.py') 776 | if not os.path.exists(activate_this): 777 | logger.fatal( 778 | 'The environment doesn\'t have a file %s -- please re-run virtualenv ' 779 | 'on this environment to update it' % activate_this) 780 | fixup_scripts(home_dir) 781 | fixup_pth_and_egg_link(home_dir) 782 | ## FIXME: need to fix up distutils.cfg 783 | 784 | OK_ABS_SCRIPTS = ['python', 'python%s' % sys.version[:3], 785 | 'activate', 'activate.bat', 'activate_this.py'] 786 | 787 | def fixup_scripts(home_dir): 788 | # This is what we expect at the top of scripts: 789 | shebang = '#!%s/bin/python' % os.path.normcase(os.path.abspath(home_dir)) 790 | # This is what we'll put: 791 | new_shebang = '#!/usr/bin/env python%s' % sys.version[:3] 792 | activate = "import os; activate_this=os.path.join(os.path.dirname(__file__), 'activate_this.py'); execfile(activate_this, dict(__file__=activate_this)); del os, activate_this" 793 | bin_dir = os.path.join(home_dir, 'bin') 794 | for filename in os.listdir(bin_dir): 795 | filename = os.path.join(bin_dir, filename) 796 | f = open(filename, 'rb') 797 | lines = f.readlines() 798 | f.close() 799 | if not lines: 800 | logger.warn('Script %s is an empty file' % filename) 801 | continue 802 | if lines[0].strip() != shebang: 803 | if os.path.basename(filename) in OK_ABS_SCRIPTS: 804 | logger.debug('Cannot make script %s relative' % filename) 805 | elif lines[0].strip() == new_shebang: 806 | logger.info('Script %s has already been made relative' % filename) 807 | else: 808 | logger.warn('Script %s cannot be made relative (it\'s not a normal script that starts with %s)' 809 | % (filename, shebang)) 810 | continue 811 | logger.notify('Making script %s relative' % filename) 812 | lines = [new_shebang+'\n', activate+'\n'] + lines[1:] 813 | f = open(filename, 'wb') 814 | f.writelines(lines) 815 | f.close() 816 | 817 | def fixup_pth_and_egg_link(home_dir): 818 | """Makes .pth and .egg-link files use relative paths""" 819 | home_dir = os.path.normcase(os.path.abspath(home_dir)) 820 | for path in sys.path: 821 | if not path: 822 | path = '.' 823 | if not os.path.isdir(path): 824 | continue 825 | path = os.path.normcase(os.path.abspath(path)) 826 | if not path.startswith(home_dir): 827 | logger.debug('Skipping system (non-environment) directory %s' % path) 828 | continue 829 | for filename in os.listdir(path): 830 | filename = os.path.join(path, filename) 831 | if filename.endswith('.pth'): 832 | if not os.access(filename, os.W_OK): 833 | logger.warn('Cannot write .pth file %s, skipping' % filename) 834 | else: 835 | fixup_pth_file(filename) 836 | if filename.endswith('.egg-link'): 837 | if not os.access(filename, os.W_OK): 838 | logger.warn('Cannot write .egg-link file %s, skipping' % filename) 839 | else: 840 | fixup_egg_link(filename) 841 | 842 | def fixup_pth_file(filename): 843 | lines = [] 844 | prev_lines = [] 845 | f = open(filename) 846 | prev_lines = f.readlines() 847 | f.close() 848 | for line in prev_lines: 849 | line = line.strip() 850 | if (not line or line.startswith('#') or line.startswith('import ') 851 | or os.path.abspath(line) != line): 852 | lines.append(line) 853 | else: 854 | new_value = make_relative_path(filename, line) 855 | if line != new_value: 856 | logger.debug('Rewriting path %s as %s (in %s)' % (line, new_value, filename)) 857 | lines.append(new_value) 858 | if lines == prev_lines: 859 | logger.info('No changes to .pth file %s' % filename) 860 | return 861 | logger.notify('Making paths in .pth file %s relative' % filename) 862 | f = open(filename, 'w') 863 | f.write('\n'.join(lines) + '\n') 864 | f.close() 865 | 866 | def fixup_egg_link(filename): 867 | f = open(filename) 868 | link = f.read().strip() 869 | f.close() 870 | if os.path.abspath(link) != link: 871 | logger.debug('Link in %s already relative' % filename) 872 | return 873 | new_link = make_relative_path(filename, link) 874 | logger.notify('Rewriting link %s in %s as %s' % (link, filename, new_link)) 875 | f = open(filename, 'w') 876 | f.write(new_link) 877 | f.close() 878 | 879 | def make_relative_path(source, dest, dest_is_directory=True): 880 | """ 881 | Make a filename relative, where the filename is dest, and it is 882 | being referred to from the filename source. 883 | 884 | >>> make_relative_path('/usr/share/something/a-file.pth', 885 | ... '/usr/share/another-place/src/Directory') 886 | '../another-place/src/Directory' 887 | >>> make_relative_path('/usr/share/something/a-file.pth', 888 | ... '/home/user/src/Directory') 889 | '../../../home/user/src/Directory' 890 | >>> make_relative_path('/usr/share/a-file.pth', '/usr/share/') 891 | './' 892 | """ 893 | source = os.path.dirname(source) 894 | if not dest_is_directory: 895 | dest_filename = os.path.basename(dest) 896 | dest = os.path.dirname(dest) 897 | dest = os.path.normpath(os.path.abspath(dest)) 898 | source = os.path.normpath(os.path.abspath(source)) 899 | dest_parts = dest.strip(os.path.sep).split(os.path.sep) 900 | source_parts = source.strip(os.path.sep).split(os.path.sep) 901 | while dest_parts and source_parts and dest_parts[0] == source_parts[0]: 902 | dest_parts.pop(0) 903 | source_parts.pop(0) 904 | full_parts = ['..']*len(source_parts) + dest_parts 905 | if not dest_is_directory: 906 | full_parts.append(dest_filename) 907 | if not full_parts: 908 | # Special case for the current directory (otherwise it'd be '') 909 | return './' 910 | return os.path.sep.join(full_parts) 911 | 912 | 913 | 914 | ############################################################ 915 | ## Bootstrap script creation: 916 | 917 | def create_bootstrap_script(extra_text, python_version=''): 918 | """ 919 | Creates a bootstrap script, which is like this script but with 920 | extend_parser, adjust_options, and after_install hooks. 921 | 922 | This returns a string that (written to disk of course) can be used 923 | as a bootstrap script with your own customizations. The script 924 | will be the standard virtualenv.py script, with your extra text 925 | added (your extra text should be Python code). 926 | 927 | If you include these functions, they will be called: 928 | 929 | ``extend_parser(optparse_parser)``: 930 | You can add or remove options from the parser here. 931 | 932 | ``adjust_options(options, args)``: 933 | You can change options here, or change the args (if you accept 934 | different kinds of arguments, be sure you modify ``args`` so it is 935 | only ``[DEST_DIR]``). 936 | 937 | ``after_install(options, home_dir)``: 938 | 939 | After everything is installed, this function is called. This 940 | is probably the function you are most likely to use. An 941 | example would be:: 942 | 943 | def after_install(options, home_dir): 944 | subprocess.call([join(home_dir, 'bin', 'easy_install'), 945 | 'MyPackage']) 946 | subprocess.call([join(home_dir, 'bin', 'my-package-script'), 947 | 'setup', home_dir]) 948 | 949 | This example immediately installs a package, and runs a setup 950 | script from that package. 951 | 952 | If you provide something like ``python_version='2.4'`` then the 953 | script will start with ``#!/usr/bin/env python2.4`` instead of 954 | ``#!/usr/bin/env python``. You can use this when the script must 955 | be run with a particular Python version. 956 | """ 957 | filename = __file__ 958 | if filename.endswith('.pyc'): 959 | filename = filename[:-1] 960 | f = open(filename, 'rb') 961 | content = f.read() 962 | f.close() 963 | py_exe = 'python%s' % python_version 964 | content = (('#!/usr/bin/env %s\n' % py_exe) 965 | + '## WARNING: This file is generated\n' 966 | + content) 967 | return content.replace('##EXT' 'END##', extra_text) 968 | 969 | def adjust_options(options, args): 970 | args[:] = ['./virtual-env/'] 971 | 972 | def after_install(options, home_dir): 973 | if sys.platform == 'win32': 974 | bin_dir = join(home_dir, 'Scripts') 975 | else: 976 | bin_dir = join(home_dir, 'bin') 977 | subprocess.call([join(bin_dir, 'easy_install'), 'paver==1.0.1']) 978 | subprocess.call([join(bin_dir, 'easy_install'), 'virtualenv']) 979 | subprocess.call([join(bin_dir, 'easy_install'), 'Nose']) 980 | subprocess.call([join(bin_dir, 'easy_install'), 'Mock']) 981 | 982 | 983 | ##file site.py 984 | SITE_PY = """ 985 | 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 1106 | eP8HyeK9cQ== 1107 | """.decode("base64").decode("zlib") 1108 | 1109 | ##file ez_setup.py 1110 | EZ_SETUP_PY = """ 1111 | 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 1173 | woheJi7S138BSGV4QQ== 1174 | """.decode("base64").decode("zlib") 1175 | 1176 | ##file activate.sh 1177 | ACTIVATE_SH = """ 1178 | eJytU99P2zAQfvdfcaQ8ABqN+srUh6IhUYmViXSdNECum1waS6ld2U6zgva/75ykNP0xpGnkIYl9 1179 | n8/fffddB8aZtJDKHGFRWAczhMJiAqV0GQRWFyZGmEkVitjJlXAYwEVq9AJmwmYXrANrXUAslNIO 1180 | TKFAOkikwdjla8YS3JyCs3N4ZUCPTOERLhUEp/z+7gufDB/G3wd3/NtgfBvAM3wGl6GqkP7x2/1j 1181 | 0DcE/lpq4yrg216hLDo4OFTFU8mqb6eu3Ga6yBNI0BHnqigQKoEXm32CMpNxBplYIQj6UCjWi4UP 1182 | u0y4Sq8mFakWizwn3ZyGOd1NMtBfqo1fLAUJ2xy1XYAfpK0uXBN2Us2bNDtALwScet4QZ0LN0UJJ 1183 | TRKJf63BC07XGrRLYo7JnrjXg4j0vNT16md0yyc3D9HwfnRE5Kq0S7Mjz9/aFPWOdSnqHTSJgAc9 1184 | inrvtqgJbyjUkE30ZjTZEjshXkSkD4HSKkHrTOGNhnvcOhBhnsIGcLJ3+9aem3t/M3J0HZTGYE6t 1185 | Vw5Wwkgxy9G2Db17MWMtnv2A89aS84A1CrSLYQf+JA1rbzeLFjrk/Ho44qPB1xvOrxpY2/psX0qf 1186 | zPeg0iuYkrNRiQXC007ep2BayUgc96XzvpIiJ2Nb9FaFAe0o8t5cxs2MayNJlAaOCJlzy6swLMuy 1187 | +4KOnLrqkptDq1NXCoOh8BlC9maZxxatKaU8SvBpOn2GuhbMLW5Pn71T1Hl9gFra8h77oJn/gHn/ 1188 | z1n/9znfzDgp8gduuMqz 1189 | """.decode("base64").decode("zlib") 1190 | 1191 | ##file activate.bat 1192 | ACTIVATE_BAT = """ 1193 | eJx9kMsOgjAQRfdN+g+zoAn8goZEDESJPBpEViSzkFbZ0IX8f+RRaVW0u5mee3PanbjeFSgpKXmI 1194 | Hqq4KC9BglFW+YjWhEgJJa2ETvXQCNl2ogFe5CkvwaUEhjPm543vcOdAiacjLxzzJFw6f2bZCsZ0 1195 | 2YitXPtswawi1zwgC9II0QPD/RELyuOb1jB/Sg0rNhM31Ss4n2I+7ibLb8epQGco2Rja1Fs/zeoa 1196 | cR9nWnprJaMspOQJdBR1/g== 1197 | """.decode("base64").decode("zlib") 1198 | 1199 | ##file deactivate.bat 1200 | DEACTIVATE_BAT = """ 1201 | eJxzSE3OyFfIT0vj4spMU0hJTcvMS01RiPf3cYkP8wwKCXX0iQ8I8vcNCFHQ4FIAguLUEgWIgK0q 1202 | FlWqXJpcICVYpGzx2OAY4oFsPpCLbjpQCLvZILVcXFaufi5cACHzOrI= 1203 | """.decode("base64").decode("zlib") 1204 | 1205 | ##file distutils-init.py 1206 | DISTUTILS_INIT = """ 1207 | eJytVsuq3DAM3fsrxAwlCUxD1xeGu+gD7qKl0EK7M76JMnGbsYPtzOPvK9uZPGdoF80iONKRLB1J 1208 | duSx1caBtkzGlb0Oy7MwSqqDhZvgvVaVPHwVxqKB7fxTWlDagYCTNK4TDaoTHHXZNbgDq+GMUAgF 1209 | nUWQDpyGSqoSXI1gXdnIV8ZKaZQ4IuwpmLwVrs57iVdZ1znZWO7lE8QvLVW6gKfTsHLOK9kg59kO 1210 | ksFNkjFZDU6UNke/SOfbZLBfhZKubAb/2RMDem6c5X6RBpF/Nt8p0wkzw1bQiuK3OCAIB28siLZF 1211 | CtwT9EpMqciQp6XRhXBSKxA2Cq/W4XF09LzJGDYWYxg8pMB5LhWx4NJ3O1hkF2B4wQJ0iyOJgdE5 1212 | lJjjXCrpyF17TbIsNyjKNGO3tvDwSfsUgX/Gtttxz9yvKFdX1GifGNNNyX0H8AgOJFoqrIflH+hl 1213 | 5Gvn081XKFZiBStparGp+hpUuqPeouLd2yQCAy5SyMdaLBprRcOYTlEdkuhkO+kkvBCAdlj47cPa 1214 | DrFNqrLCDi2zhR+1CKNSiKYJNW/RvO38sMWEwCcU8DGGOD57SFpt5SV5Glx5m5B9P2AbquMsl03s 1215 | hqF97hrdtVmiZgRScnlrsJKX3RyYTaIOcGm9Kp2DxlgqTQeMb3eaiIaCSAONE0DvzmNyVKU9S5rN 1216 | ZBFxsjAY62HwqE+YevOMzVV+IlWZ3gnfoOuMijD2D41L7KybeT4lw/QsRuWAjrdXV2tFg1iQowGY 1217 | z1VhOAblwi5tG+G4bbGQlSz21H2xOPsvWt3YJhKj0B/oXj5S1svD5v4IaHiUTMlYBzvf9LZlxh4F 1218 | SSd2qQvO+/m9r2SP8p9Ss7BdMUm3ziMm/YX0kElSrpm0TqhSmNJrHzI7BQEt/yfVq6jmMf2FeEI8 1219 | Jn6ivE/8gsmFre/xTy8/P398gm+17poSXmJ7L7jvjU/+/nv2cxGfFwc13XmCbkD6T/EkdlW1o38L 1220 | Gz7PtSRPpUarErp+kA6JeHnSxJY5+wM7qxSa 1221 | """.decode("base64").decode("zlib") 1222 | 1223 | ##file distutils.cfg 1224 | DISTUTILS_CFG = """ 1225 | eJxNj00KwkAMhfc9xYNuxe4Ft57AjYiUtDO1wXSmNJnK3N5pdSEEAu8nH6lxHVlRhtDHMPATA4uH 1226 | xJ4EFmGbvfJiicSHFRzUSISMY6hq3GLCRLnIvSTnEefN0FIjw5tF0Hkk9Q5dRunBsVoyFi24aaLg 1227 | 9FDOlL0FPGluf4QjcInLlxd6f6rqkgPu/5nHLg0cXCscXoozRrP51DRT3j9QNl99AP53T2Q= 1228 | """.decode("base64").decode("zlib") 1229 | 1230 | ##file activate_this.py 1231 | ACTIVATE_THIS = """ 1232 | eJx1UsGOnDAMvecrIlYriDRlKvU20h5aaY+teuilGo1QALO4CwlKAjP8fe1QGGalRoLEefbzs+Mk 1233 | Sb7NcvRo3iTcoGqwgyy06As+HWSNVciKaBTFywYoJWc7yit2ndBVwEkHkIzKCV0YdQdmkvShs6YH 1234 | E3IhfjFaaSNLoHxQy2sLJrL0ow98JQmEG/rAYn7OobVGogngBgf0P0hjgwgt7HOUaI5DdBVJkggR 1235 | 3HwSktaqWcCtgiHIH7qHV+esW2CnkRJ+9R5cQGsikkWEV/J7leVGs9TV4TvcO5QOOrTHYI+xeCjY 1236 | JR/m9GPDHv2oSZunUokS2A/WBelnvx6tF6LUJO2FjjlH5zU6Q+Kz/9m69LxvSZVSwiOlGnT1rt/A 1237 | 77j+WDQZ8x9k2mFJetOle88+lc8sJJ/AeerI+fTlQigTfVqJUiXoKaaC3AqmI+KOnivjMLbvBVFU 1238 | 1JDruuadNGcPmkgiBTnQXUGUDd6IK9JEQ9yPdM96xZP8bieeMRqTuqbxIbbey2DjVUNzRs1rosFS 1239 | TsLAdS/0fBGNdTGKhuqD7mUmsFlgGjN2eSj1tM3GnjfXwwCmzjhMbR4rLZXXk+Z/6Hp7Pn2+kJ49 1240 | jfgLHgI4Jg== 1241 | """.decode("base64").decode("zlib") 1242 | 1243 | if __name__ == '__main__': 1244 | main() 1245 | 1246 | ## TODO: 1247 | ## Copy python.exe.manifest 1248 | ## Monkeypatch distutils.sysconfig 1249 | -------------------------------------------------------------------------------- /src/github/tools/tmpl/gh/bootstrap.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ## WARNING: This file is generated 3 | #!/usr/bin/env python 4 | """Create a "virtual" Python installation 5 | """ 6 | 7 | import sys 8 | import os 9 | import optparse 10 | import shutil 11 | import logging 12 | import distutils.sysconfig 13 | try: 14 | import subprocess 15 | except ImportError, e: 16 | if sys.version_info <= (2, 3): 17 | print 'ERROR: %s' % e 18 | print 'ERROR: this script requires Python 2.4 or greater; or at least the subprocess module.' 19 | print 'If you copy subprocess.py from a newer version of Python this script will probably work' 20 | sys.exit(101) 21 | else: 22 | raise 23 | try: 24 | set 25 | except NameError: 26 | from sets import Set as set 27 | 28 | join = os.path.join 29 | py_version = 'python%s.%s' % (sys.version_info[0], sys.version_info[1]) 30 | is_jython = sys.platform.startswith('java') 31 | expected_exe = is_jython and 'jython' or 'python' 32 | 33 | REQUIRED_MODULES = ['os', 'posix', 'posixpath', 'ntpath', 'genericpath', 34 | 'fnmatch', 'locale', 'encodings', 'codecs', 35 | 'stat', 'UserDict', 'readline', 'copy_reg', 'types', 36 | 're', 'sre', 'sre_parse', 'sre_constants', 'sre_compile', 37 | 'lib-dynload', 'config', 'zlib'] 38 | 39 | if sys.version_info[:2] == (2, 6): 40 | REQUIRED_MODULES.extend(['warnings', 'linecache', '_abcoll', 'abc']) 41 | if sys.version_info[:2] <= (2, 3): 42 | REQUIRED_MODULES.extend(['sets', '__future__']) 43 | 44 | class Logger(object): 45 | 46 | """ 47 | Logging object for use in command-line script. Allows ranges of 48 | levels, to avoid some redundancy of displayed information. 49 | """ 50 | 51 | DEBUG = logging.DEBUG 52 | INFO = logging.INFO 53 | NOTIFY = (logging.INFO+logging.WARN)/2 54 | WARN = WARNING = logging.WARN 55 | ERROR = logging.ERROR 56 | FATAL = logging.FATAL 57 | 58 | LEVELS = [DEBUG, INFO, NOTIFY, WARN, ERROR, FATAL] 59 | 60 | def __init__(self, consumers): 61 | self.consumers = consumers 62 | self.indent = 0 63 | self.in_progress = None 64 | self.in_progress_hanging = False 65 | 66 | def debug(self, msg, *args, **kw): 67 | self.log(self.DEBUG, msg, *args, **kw) 68 | def info(self, msg, *args, **kw): 69 | self.log(self.INFO, msg, *args, **kw) 70 | def notify(self, msg, *args, **kw): 71 | self.log(self.NOTIFY, msg, *args, **kw) 72 | def warn(self, msg, *args, **kw): 73 | self.log(self.WARN, msg, *args, **kw) 74 | def error(self, msg, *args, **kw): 75 | self.log(self.WARN, msg, *args, **kw) 76 | def fatal(self, msg, *args, **kw): 77 | self.log(self.FATAL, msg, *args, **kw) 78 | def log(self, level, msg, *args, **kw): 79 | if args: 80 | if kw: 81 | raise TypeError( 82 | "You may give positional or keyword arguments, not both") 83 | args = args or kw 84 | rendered = None 85 | for consumer_level, consumer in self.consumers: 86 | if self.level_matches(level, consumer_level): 87 | if (self.in_progress_hanging 88 | and consumer in (sys.stdout, sys.stderr)): 89 | self.in_progress_hanging = False 90 | sys.stdout.write('\n') 91 | sys.stdout.flush() 92 | if rendered is None: 93 | if args: 94 | rendered = msg % args 95 | else: 96 | rendered = msg 97 | rendered = ' '*self.indent + rendered 98 | if hasattr(consumer, 'write'): 99 | consumer.write(rendered+'\n') 100 | else: 101 | consumer(rendered) 102 | 103 | def start_progress(self, msg): 104 | assert not self.in_progress, ( 105 | "Tried to start_progress(%r) while in_progress %r" 106 | % (msg, self.in_progress)) 107 | if self.level_matches(self.NOTIFY, self._stdout_level()): 108 | sys.stdout.write(msg) 109 | sys.stdout.flush() 110 | self.in_progress_hanging = True 111 | else: 112 | self.in_progress_hanging = False 113 | self.in_progress = msg 114 | 115 | def end_progress(self, msg='done.'): 116 | assert self.in_progress, ( 117 | "Tried to end_progress without start_progress") 118 | if self.stdout_level_matches(self.NOTIFY): 119 | if not self.in_progress_hanging: 120 | # Some message has been printed out since start_progress 121 | sys.stdout.write('...' + self.in_progress + msg + '\n') 122 | sys.stdout.flush() 123 | else: 124 | sys.stdout.write(msg + '\n') 125 | sys.stdout.flush() 126 | self.in_progress = None 127 | self.in_progress_hanging = False 128 | 129 | def show_progress(self): 130 | """If we are in a progress scope, and no log messages have been 131 | shown, write out another '.'""" 132 | if self.in_progress_hanging: 133 | sys.stdout.write('.') 134 | sys.stdout.flush() 135 | 136 | def stdout_level_matches(self, level): 137 | """Returns true if a message at this level will go to stdout""" 138 | return self.level_matches(level, self._stdout_level()) 139 | 140 | def _stdout_level(self): 141 | """Returns the level that stdout runs at""" 142 | for level, consumer in self.consumers: 143 | if consumer is sys.stdout: 144 | return level 145 | return self.FATAL 146 | 147 | def level_matches(self, level, consumer_level): 148 | """ 149 | >>> l = Logger() 150 | >>> l.level_matches(3, 4) 151 | False 152 | >>> l.level_matches(3, 2) 153 | True 154 | >>> l.level_matches(slice(None, 3), 3) 155 | False 156 | >>> l.level_matches(slice(None, 3), 2) 157 | True 158 | >>> l.level_matches(slice(1, 3), 1) 159 | True 160 | >>> l.level_matches(slice(2, 3), 1) 161 | False 162 | """ 163 | if isinstance(level, slice): 164 | start, stop = level.start, level.stop 165 | if start is not None and start > consumer_level: 166 | return False 167 | if stop is not None or stop <= consumer_level: 168 | return False 169 | return True 170 | else: 171 | return level >= consumer_level 172 | 173 | #@classmethod 174 | def level_for_integer(cls, level): 175 | levels = cls.LEVELS 176 | if level < 0: 177 | return levels[0] 178 | if level >= len(levels): 179 | return levels[-1] 180 | return levels[level] 181 | 182 | level_for_integer = classmethod(level_for_integer) 183 | 184 | def mkdir(path): 185 | if not os.path.exists(path): 186 | logger.info('Creating %s', path) 187 | os.makedirs(path) 188 | else: 189 | logger.info('Directory %s already exists', path) 190 | 191 | def copyfile(src, dest, symlink=True): 192 | if not os.path.exists(src): 193 | # Some bad symlink in the src 194 | logger.warn('Cannot find file %s (bad symlink)', src) 195 | return 196 | if os.path.exists(dest): 197 | logger.debug('File %s already exists', dest) 198 | return 199 | if not os.path.exists(os.path.dirname(dest)): 200 | logger.info('Creating parent directories for %s' % os.path.dirname(dest)) 201 | os.makedirs(os.path.dirname(dest)) 202 | if symlink and hasattr(os, 'symlink'): 203 | logger.info('Symlinking %s', dest) 204 | os.symlink(os.path.abspath(src), dest) 205 | else: 206 | logger.info('Copying to %s', dest) 207 | if os.path.isdir(src): 208 | shutil.copytree(src, dest, True) 209 | else: 210 | shutil.copy2(src, dest) 211 | 212 | def writefile(dest, content, overwrite=True): 213 | if not os.path.exists(dest): 214 | logger.info('Writing %s', dest) 215 | f = open(dest, 'wb') 216 | f.write(content) 217 | f.close() 218 | return 219 | else: 220 | f = open(dest, 'rb') 221 | c = f.read() 222 | f.close() 223 | if c != content: 224 | if not overwrite: 225 | logger.notify('File %s exists with different content; not overwriting', dest) 226 | return 227 | logger.notify('Overwriting %s with new content', dest) 228 | f = open(dest, 'wb') 229 | f.write(content) 230 | f.close() 231 | else: 232 | logger.info('Content %s already in place', dest) 233 | 234 | def rmtree(dir): 235 | if os.path.exists(dir): 236 | logger.notify('Deleting tree %s', dir) 237 | shutil.rmtree(dir) 238 | else: 239 | logger.info('Do not need to delete %s; already gone', dir) 240 | 241 | def make_exe(fn): 242 | if hasattr(os, 'chmod'): 243 | oldmode = os.stat(fn).st_mode & 07777 244 | newmode = (oldmode | 0555) & 07777 245 | os.chmod(fn, newmode) 246 | logger.info('Changed mode of %s to %s', fn, oct(newmode)) 247 | 248 | def install_setuptools(py_executable, unzip=False): 249 | setup_fn = 'setuptools-0.6c9-py%s.egg' % sys.version[:3] 250 | search_dirs = ['.', os.path.dirname(__file__), join(os.path.dirname(__file__), 'support-files')] 251 | if os.path.splitext(os.path.dirname(__file__))[0] != 'virtualenv': 252 | # Probably some boot script; just in case virtualenv is installed... 253 | try: 254 | import virtualenv 255 | except ImportError: 256 | pass 257 | else: 258 | search_dirs.append(os.path.join(os.path.dirname(virtualenv.__file__), 'support-files')) 259 | for dir in search_dirs: 260 | if os.path.exists(join(dir, setup_fn)): 261 | setup_fn = join(dir, setup_fn) 262 | break 263 | if is_jython and os._name == 'nt': 264 | # Jython's .bat sys.executable can't handle a command line 265 | # argument with newlines 266 | import tempfile 267 | fd, ez_setup = tempfile.mkstemp('.py') 268 | os.write(fd, EZ_SETUP_PY) 269 | os.close(fd) 270 | cmd = [py_executable, ez_setup] 271 | else: 272 | cmd = [py_executable, '-c', EZ_SETUP_PY] 273 | if unzip: 274 | cmd.append('--always-unzip') 275 | env = {} 276 | if logger.stdout_level_matches(logger.DEBUG): 277 | cmd.append('-v') 278 | if os.path.exists(setup_fn): 279 | logger.info('Using existing Setuptools egg: %s', setup_fn) 280 | cmd.append(setup_fn) 281 | if os.environ.get('PYTHONPATH'): 282 | env['PYTHONPATH'] = setup_fn + os.path.pathsep + os.environ['PYTHONPATH'] 283 | else: 284 | env['PYTHONPATH'] = setup_fn 285 | else: 286 | logger.info('No Setuptools egg found; downloading') 287 | cmd.extend(['--always-copy', '-U', 'setuptools']) 288 | logger.start_progress('Installing setuptools...') 289 | logger.indent += 2 290 | cwd = None 291 | if not os.access(os.getcwd(), os.W_OK): 292 | cwd = '/tmp' 293 | try: 294 | call_subprocess(cmd, show_stdout=False, 295 | filter_stdout=filter_ez_setup, 296 | extra_env=env, 297 | cwd=cwd) 298 | finally: 299 | logger.indent -= 2 300 | logger.end_progress() 301 | if is_jython and os._name == 'nt': 302 | os.remove(ez_setup) 303 | 304 | def filter_ez_setup(line): 305 | if not line.strip(): 306 | return Logger.DEBUG 307 | for prefix in ['Reading ', 'Best match', 'Processing setuptools', 308 | 'Copying setuptools', 'Adding setuptools', 309 | 'Installing ', 'Installed ']: 310 | if line.startswith(prefix): 311 | return Logger.DEBUG 312 | return Logger.INFO 313 | 314 | def main(): 315 | parser = optparse.OptionParser( 316 | version="1.3.3", 317 | usage="%prog [OPTIONS] DEST_DIR") 318 | 319 | parser.add_option( 320 | '-v', '--verbose', 321 | action='count', 322 | dest='verbose', 323 | default=0, 324 | help="Increase verbosity") 325 | 326 | parser.add_option( 327 | '-q', '--quiet', 328 | action='count', 329 | dest='quiet', 330 | default=0, 331 | help='Decrease verbosity') 332 | 333 | parser.add_option( 334 | '-p', '--python', 335 | dest='python', 336 | metavar='PYTHON_EXE', 337 | help='The Python interpreter to use, e.g., --python=python2.5 will use the python2.5 ' 338 | 'interpreter to create the new environment. The default is the interpreter that ' 339 | 'virtualenv was installed with (%s)' % sys.executable) 340 | 341 | parser.add_option( 342 | '--clear', 343 | dest='clear', 344 | action='store_true', 345 | help="Clear out the non-root install and start from scratch") 346 | 347 | parser.add_option( 348 | '--no-site-packages', 349 | dest='no_site_packages', 350 | action='store_true', 351 | help="Don't give access to the global site-packages dir to the " 352 | "virtual environment") 353 | 354 | parser.add_option( 355 | '--unzip-setuptools', 356 | dest='unzip_setuptools', 357 | action='store_true', 358 | help="Unzip Setuptools when installing it") 359 | 360 | parser.add_option( 361 | '--relocatable', 362 | dest='relocatable', 363 | action='store_true', 364 | help='Make an EXISTING virtualenv environment relocatable. ' 365 | 'This fixes up scripts and makes all .pth files relative') 366 | 367 | if 'extend_parser' in globals(): 368 | extend_parser(parser) 369 | 370 | options, args = parser.parse_args() 371 | 372 | global logger 373 | 374 | if 'adjust_options' in globals(): 375 | adjust_options(options, args) 376 | 377 | verbosity = options.verbose - options.quiet 378 | logger = Logger([(Logger.level_for_integer(2-verbosity), sys.stdout)]) 379 | 380 | if options.python and not os.environ.get('VIRTUALENV_INTERPRETER_RUNNING'): 381 | env = os.environ.copy() 382 | interpreter = resolve_interpreter(options.python) 383 | if interpreter == sys.executable: 384 | logger.warn('Already using interpreter %s' % interpreter) 385 | else: 386 | logger.notify('Running virtualenv with interpreter %s' % interpreter) 387 | env['VIRTUALENV_INTERPRETER_RUNNING'] = 'true' 388 | file = __file__ 389 | if file.endswith('.pyc'): 390 | file = file[:-1] 391 | os.execvpe(interpreter, [interpreter, file] + sys.argv[1:], env) 392 | 393 | if not args: 394 | print 'You must provide a DEST_DIR' 395 | parser.print_help() 396 | sys.exit(2) 397 | if len(args) > 1: 398 | print 'There must be only one argument: DEST_DIR (you gave %s)' % ( 399 | ' '.join(args)) 400 | parser.print_help() 401 | sys.exit(2) 402 | 403 | home_dir = args[0] 404 | 405 | if os.environ.get('WORKING_ENV'): 406 | logger.fatal('ERROR: you cannot run virtualenv while in a workingenv') 407 | logger.fatal('Please deactivate your workingenv, then re-run this script') 408 | sys.exit(3) 409 | 410 | if os.environ.get('PYTHONHOME'): 411 | if sys.platform == 'win32': 412 | name = '%PYTHONHOME%' 413 | else: 414 | name = '$PYTHONHOME' 415 | logger.warn('%s is set; this can cause problems creating environments' % name) 416 | 417 | if options.relocatable: 418 | make_environment_relocatable(home_dir) 419 | return 420 | 421 | create_environment(home_dir, site_packages=not options.no_site_packages, clear=options.clear, 422 | unzip_setuptools=options.unzip_setuptools) 423 | if 'after_install' in globals(): 424 | after_install(options, home_dir) 425 | 426 | def call_subprocess(cmd, show_stdout=True, 427 | filter_stdout=None, cwd=None, 428 | raise_on_returncode=True, extra_env=None): 429 | cmd_parts = [] 430 | for part in cmd: 431 | if len(part) > 40: 432 | part = part[:30]+"..."+part[-5:] 433 | if ' ' in part or '\n' in part or '"' in part or "'" in part: 434 | part = '"%s"' % part.replace('"', '\\"') 435 | cmd_parts.append(part) 436 | cmd_desc = ' '.join(cmd_parts) 437 | if show_stdout: 438 | stdout = None 439 | else: 440 | stdout = subprocess.PIPE 441 | logger.debug("Running command %s" % cmd_desc) 442 | if extra_env: 443 | env = os.environ.copy() 444 | env.update(extra_env) 445 | else: 446 | env = None 447 | try: 448 | proc = subprocess.Popen( 449 | cmd, stderr=subprocess.STDOUT, stdin=None, stdout=stdout, 450 | cwd=cwd, env=env) 451 | except Exception, e: 452 | logger.fatal( 453 | "Error %s while executing command %s" % (e, cmd_desc)) 454 | raise 455 | all_output = [] 456 | if stdout is not None: 457 | stdout = proc.stdout 458 | while 1: 459 | line = stdout.readline() 460 | if not line: 461 | break 462 | line = line.rstrip() 463 | all_output.append(line) 464 | if filter_stdout: 465 | level = filter_stdout(line) 466 | if isinstance(level, tuple): 467 | level, line = level 468 | logger.log(level, line) 469 | if not logger.stdout_level_matches(level): 470 | logger.show_progress() 471 | else: 472 | logger.info(line) 473 | else: 474 | proc.communicate() 475 | proc.wait() 476 | if proc.returncode: 477 | if raise_on_returncode: 478 | if all_output: 479 | logger.notify('Complete output from command %s:' % cmd_desc) 480 | logger.notify('\n'.join(all_output) + '\n----------------------------------------') 481 | raise OSError( 482 | "Command %s failed with error code %s" 483 | % (cmd_desc, proc.returncode)) 484 | else: 485 | logger.warn( 486 | "Command %s had error code %s" 487 | % (cmd_desc, proc.returncode)) 488 | 489 | 490 | def create_environment(home_dir, site_packages=True, clear=False, 491 | unzip_setuptools=False): 492 | """ 493 | Creates a new environment in ``home_dir``. 494 | 495 | If ``site_packages`` is true (the default) then the global 496 | ``site-packages/`` directory will be on the path. 497 | 498 | If ``clear`` is true (default False) then the environment will 499 | first be cleared. 500 | """ 501 | home_dir, lib_dir, inc_dir, bin_dir = path_locations(home_dir) 502 | 503 | py_executable = install_python( 504 | home_dir, lib_dir, inc_dir, bin_dir, 505 | site_packages=site_packages, clear=clear) 506 | 507 | install_distutils(lib_dir, home_dir) 508 | 509 | install_setuptools(py_executable, unzip=unzip_setuptools) 510 | 511 | install_activate(home_dir, bin_dir) 512 | 513 | def path_locations(home_dir): 514 | """Return the path locations for the environment (where libraries are, 515 | where scripts go, etc)""" 516 | # XXX: We'd use distutils.sysconfig.get_python_inc/lib but its 517 | # prefix arg is broken: http://bugs.python.org/issue3386 518 | if sys.platform == 'win32': 519 | # Windows has lots of problems with executables with spaces in 520 | # the name; this function will remove them (using the ~1 521 | # format): 522 | mkdir(home_dir) 523 | import win32api 524 | home_dir = win32api.GetShortPathName(home_dir) 525 | lib_dir = join(home_dir, 'Lib') 526 | inc_dir = join(home_dir, 'Include') 527 | bin_dir = join(home_dir, 'Scripts') 528 | elif is_jython: 529 | lib_dir = join(home_dir, 'Lib') 530 | inc_dir = join(home_dir, 'Include') 531 | bin_dir = join(home_dir, 'bin') 532 | else: 533 | lib_dir = join(home_dir, 'lib', py_version) 534 | inc_dir = join(home_dir, 'include', py_version) 535 | bin_dir = join(home_dir, 'bin') 536 | return home_dir, lib_dir, inc_dir, bin_dir 537 | 538 | def install_python(home_dir, lib_dir, inc_dir, bin_dir, site_packages, clear): 539 | """Install just the base environment, no distutils patches etc""" 540 | if sys.executable.startswith(bin_dir): 541 | print 'Please use the *system* python to run this script' 542 | return 543 | 544 | if clear: 545 | rmtree(lib_dir) 546 | ## FIXME: why not delete it? 547 | ## Maybe it should delete everything with #!/path/to/venv/python in it 548 | logger.notify('Not deleting %s', bin_dir) 549 | 550 | if hasattr(sys, 'real_prefix'): 551 | logger.notify('Using real prefix %r' % sys.real_prefix) 552 | prefix = sys.real_prefix 553 | else: 554 | prefix = sys.prefix 555 | mkdir(lib_dir) 556 | fix_lib64(lib_dir) 557 | stdlib_dirs = [os.path.dirname(os.__file__)] 558 | if sys.platform == 'win32': 559 | stdlib_dirs.append(join(os.path.dirname(stdlib_dirs[0]), 'DLLs')) 560 | elif sys.platform == 'darwin': 561 | stdlib_dirs.append(join(stdlib_dirs[0], 'site-packages')) 562 | for stdlib_dir in stdlib_dirs: 563 | if not os.path.isdir(stdlib_dir): 564 | continue 565 | if hasattr(os, 'symlink'): 566 | logger.info('Symlinking Python bootstrap modules') 567 | else: 568 | logger.info('Copying Python bootstrap modules') 569 | logger.indent += 2 570 | try: 571 | for fn in os.listdir(stdlib_dir): 572 | if fn != 'site-packages' and os.path.splitext(fn)[0] in REQUIRED_MODULES: 573 | copyfile(join(stdlib_dir, fn), join(lib_dir, fn)) 574 | finally: 575 | logger.indent -= 2 576 | mkdir(join(lib_dir, 'site-packages')) 577 | writefile(join(lib_dir, 'site.py'), SITE_PY) 578 | writefile(join(lib_dir, 'orig-prefix.txt'), prefix) 579 | site_packages_filename = join(lib_dir, 'no-global-site-packages.txt') 580 | if not site_packages: 581 | writefile(site_packages_filename, '') 582 | else: 583 | if os.path.exists(site_packages_filename): 584 | logger.info('Deleting %s' % site_packages_filename) 585 | os.unlink(site_packages_filename) 586 | 587 | stdinc_dir = join(prefix, 'include', py_version) 588 | if os.path.exists(stdinc_dir): 589 | copyfile(stdinc_dir, inc_dir) 590 | else: 591 | logger.debug('No include dir %s' % stdinc_dir) 592 | 593 | if sys.exec_prefix != prefix: 594 | if sys.platform == 'win32': 595 | exec_dir = join(sys.exec_prefix, 'lib') 596 | elif is_jython: 597 | exec_dir = join(sys.exec_prefix, 'Lib') 598 | else: 599 | exec_dir = join(sys.exec_prefix, 'lib', py_version) 600 | for fn in os.listdir(exec_dir): 601 | copyfile(join(exec_dir, fn), join(lib_dir, fn)) 602 | 603 | if is_jython: 604 | # Jython has either jython-dev.jar and javalib/ dir, or just 605 | # jython.jar 606 | for name in 'jython-dev.jar', 'javalib', 'jython.jar': 607 | src = join(prefix, name) 608 | if os.path.exists(src): 609 | copyfile(src, join(home_dir, name)) 610 | copyfile(join(prefix, 'cachedir'), join(home_dir, 'cachedir'), 611 | symlink=False) 612 | 613 | mkdir(bin_dir) 614 | py_executable = join(bin_dir, os.path.basename(sys.executable)) 615 | if 'Python.framework' in prefix: 616 | if py_executable.endswith('/Python'): 617 | # The name of the python executable is not quite what 618 | # we want, rename it. 619 | py_executable = os.path.join( 620 | os.path.dirname(py_executable), 'python') 621 | 622 | logger.notify('New %s executable in %s', expected_exe, py_executable) 623 | if sys.executable != py_executable: 624 | ## FIXME: could I just hard link? 625 | executable = sys.executable 626 | if sys.platform == 'cygwin' and os.path.exists(executable + '.exe'): 627 | # Cygwin misreports sys.executable sometimes 628 | executable += '.exe' 629 | py_executable += '.exe' 630 | logger.info('Executable actually exists in %s' % executable) 631 | shutil.copyfile(executable, py_executable) 632 | make_exe(py_executable) 633 | if os.path.splitext(os.path.basename(py_executable))[0] != expected_exe: 634 | secondary_exe = os.path.join(os.path.dirname(py_executable), 635 | expected_exe) 636 | py_executable_ext = os.path.splitext(py_executable)[1] 637 | if py_executable_ext == '.exe': 638 | # python2.4 gives an extension of '.4' :P 639 | secondary_exe += py_executable_ext 640 | if os.path.exists(secondary_exe): 641 | logger.warn('Not overwriting existing %s script %s (you must use %s)' 642 | % (expected_exe, secondary_exe, py_executable)) 643 | else: 644 | logger.notify('Also creating executable in %s' % secondary_exe) 645 | shutil.copyfile(sys.executable, secondary_exe) 646 | make_exe(secondary_exe) 647 | 648 | if 'Python.framework' in prefix: 649 | logger.debug('MacOSX Python framework detected') 650 | 651 | # Copy the framework's dylib into the virtual 652 | # environment 653 | virtual_lib = os.path.join(home_dir, '.Python') 654 | 655 | if os.path.exists(virtual_lib): 656 | os.unlink(virtual_lib) 657 | copyfile( 658 | os.path.join(prefix, 'Python'), 659 | virtual_lib) 660 | 661 | # And then change the install_name of the copied python executable 662 | try: 663 | call_subprocess( 664 | ["install_name_tool", "-change", 665 | os.path.join(prefix, 'Python'), 666 | '@executable_path/../.Python', 667 | py_executable]) 668 | except: 669 | logger.fatal( 670 | "Could not call install_name_tool -- you must have Apple's development tools installed") 671 | raise 672 | 673 | # Some tools depend on pythonX.Y being present 674 | pth = py_executable + '%s.%s' % ( 675 | sys.version_info[0], sys.version_info[1]) 676 | if os.path.exists(pth): 677 | os.unlink(pth) 678 | os.symlink('python', pth) 679 | 680 | if sys.platform == 'win32' and ' ' in py_executable: 681 | # There's a bug with subprocess on Windows when using a first 682 | # argument that has a space in it. Instead we have to quote 683 | # the value: 684 | py_executable = '"%s"' % py_executable 685 | cmd = [py_executable, '-c', 'import sys; print sys.prefix'] 686 | logger.info('Testing executable with %s %s "%s"' % tuple(cmd)) 687 | proc = subprocess.Popen(cmd, 688 | stdout=subprocess.PIPE) 689 | proc_stdout, proc_stderr = proc.communicate() 690 | proc_stdout = os.path.normcase(os.path.abspath(proc_stdout.strip())) 691 | if proc_stdout != os.path.normcase(os.path.abspath(home_dir)): 692 | logger.fatal( 693 | 'ERROR: The executable %s is not functioning' % py_executable) 694 | logger.fatal( 695 | 'ERROR: It thinks sys.prefix is %r (should be %r)' 696 | % (proc_stdout, os.path.normcase(os.path.abspath(home_dir)))) 697 | logger.fatal( 698 | 'ERROR: virtualenv is not compatible with this system or executable') 699 | sys.exit(100) 700 | else: 701 | logger.info('Got sys.prefix result: %r' % proc_stdout) 702 | 703 | pydistutils = os.path.expanduser('~/.pydistutils.cfg') 704 | if os.path.exists(pydistutils): 705 | logger.notify('Please make sure you remove any previous custom paths from ' 706 | 'your %s file.' % pydistutils) 707 | ## FIXME: really this should be calculated earlier 708 | return py_executable 709 | 710 | def install_activate(home_dir, bin_dir): 711 | if sys.platform == 'win32' or is_jython and os._name == 'nt': 712 | files = {'activate.bat': ACTIVATE_BAT, 713 | 'deactivate.bat': DEACTIVATE_BAT} 714 | if os.environ.get('OS') == 'Windows_NT' and os.environ.get('OSTYPE') == 'cygwin': 715 | files['activate'] = ACTIVATE_SH 716 | else: 717 | files = {'activate': ACTIVATE_SH} 718 | files['activate_this.py'] = ACTIVATE_THIS 719 | for name, content in files.items(): 720 | content = content.replace('__VIRTUAL_ENV__', os.path.abspath(home_dir)) 721 | content = content.replace('__VIRTUAL_NAME__', os.path.basename(os.path.abspath(home_dir))) 722 | content = content.replace('__BIN_NAME__', os.path.basename(bin_dir)) 723 | writefile(os.path.join(bin_dir, name), content) 724 | 725 | def install_distutils(lib_dir, home_dir): 726 | distutils_path = os.path.join(lib_dir, 'distutils') 727 | mkdir(distutils_path) 728 | ## FIXME: maybe this prefix setting should only be put in place if 729 | ## there's a local distutils.cfg with a prefix setting? 730 | home_dir = os.path.abspath(home_dir) 731 | ## FIXME: this is breaking things, removing for now: 732 | #distutils_cfg = DISTUTILS_CFG + "\n[install]\nprefix=%s\n" % home_dir 733 | writefile(os.path.join(distutils_path, '__init__.py'), DISTUTILS_INIT) 734 | writefile(os.path.join(distutils_path, 'distutils.cfg'), DISTUTILS_CFG, overwrite=False) 735 | 736 | def fix_lib64(lib_dir): 737 | """ 738 | Some platforms (particularly Gentoo on x64) put things in lib64/pythonX.Y 739 | instead of lib/pythonX.Y. If this is such a platform we'll just create a 740 | symlink so lib64 points to lib 741 | """ 742 | if [p for p in distutils.sysconfig.get_config_vars().values() 743 | if isinstance(p, basestring) and 'lib64' in p]: 744 | logger.debug('This system uses lib64; symlinking lib64 to lib') 745 | assert os.path.basename(lib_dir) == 'python%s' % sys.version[:3], ( 746 | "Unexpected python lib dir: %r" % lib_dir) 747 | lib_parent = os.path.dirname(lib_dir) 748 | assert os.path.basename(lib_parent) == 'lib', ( 749 | "Unexpected parent dir: %r" % lib_parent) 750 | copyfile(lib_parent, os.path.join(os.path.dirname(lib_parent), 'lib64')) 751 | 752 | def resolve_interpreter(exe): 753 | """ 754 | If the executable given isn't an absolute path, search $PATH for the interpreter 755 | """ 756 | if os.path.abspath(exe) != exe: 757 | paths = os.environ.get('PATH', '').split(os.pathsep) 758 | for path in paths: 759 | if os.path.exists(os.path.join(path, exe)): 760 | exe = os.path.join(path, exe) 761 | break 762 | if not os.path.exists(exe): 763 | logger.fatal('The executable %s (from --python=%s) does not exist' % (exe, exe)) 764 | sys.exit(3) 765 | return exe 766 | 767 | ############################################################ 768 | ## Relocating the environment: 769 | 770 | def make_environment_relocatable(home_dir): 771 | """ 772 | Makes the already-existing environment use relative paths, and takes out 773 | the #!-based environment selection in scripts. 774 | """ 775 | activate_this = os.path.join(home_dir, 'bin', 'activate_this.py') 776 | if not os.path.exists(activate_this): 777 | logger.fatal( 778 | 'The environment doesn\'t have a file %s -- please re-run virtualenv ' 779 | 'on this environment to update it' % activate_this) 780 | fixup_scripts(home_dir) 781 | fixup_pth_and_egg_link(home_dir) 782 | ## FIXME: need to fix up distutils.cfg 783 | 784 | OK_ABS_SCRIPTS = ['python', 'python%s' % sys.version[:3], 785 | 'activate', 'activate.bat', 'activate_this.py'] 786 | 787 | def fixup_scripts(home_dir): 788 | # This is what we expect at the top of scripts: 789 | shebang = '#!%s/bin/python' % os.path.normcase(os.path.abspath(home_dir)) 790 | # This is what we'll put: 791 | new_shebang = '#!/usr/bin/env python%s' % sys.version[:3] 792 | activate = "import os; activate_this=os.path.join(os.path.dirname(__file__), 'activate_this.py'); execfile(activate_this, dict(__file__=activate_this)); del os, activate_this" 793 | bin_dir = os.path.join(home_dir, 'bin') 794 | for filename in os.listdir(bin_dir): 795 | filename = os.path.join(bin_dir, filename) 796 | f = open(filename, 'rb') 797 | lines = f.readlines() 798 | f.close() 799 | if not lines: 800 | logger.warn('Script %s is an empty file' % filename) 801 | continue 802 | if lines[0].strip() != shebang: 803 | if os.path.basename(filename) in OK_ABS_SCRIPTS: 804 | logger.debug('Cannot make script %s relative' % filename) 805 | elif lines[0].strip() == new_shebang: 806 | logger.info('Script %s has already been made relative' % filename) 807 | else: 808 | logger.warn('Script %s cannot be made relative (it\'s not a normal script that starts with %s)' 809 | % (filename, shebang)) 810 | continue 811 | logger.notify('Making script %s relative' % filename) 812 | lines = [new_shebang+'\n', activate+'\n'] + lines[1:] 813 | f = open(filename, 'wb') 814 | f.writelines(lines) 815 | f.close() 816 | 817 | def fixup_pth_and_egg_link(home_dir): 818 | """Makes .pth and .egg-link files use relative paths""" 819 | home_dir = os.path.normcase(os.path.abspath(home_dir)) 820 | for path in sys.path: 821 | if not path: 822 | path = '.' 823 | if not os.path.isdir(path): 824 | continue 825 | path = os.path.normcase(os.path.abspath(path)) 826 | if not path.startswith(home_dir): 827 | logger.debug('Skipping system (non-environment) directory %s' % path) 828 | continue 829 | for filename in os.listdir(path): 830 | filename = os.path.join(path, filename) 831 | if filename.endswith('.pth'): 832 | if not os.access(filename, os.W_OK): 833 | logger.warn('Cannot write .pth file %s, skipping' % filename) 834 | else: 835 | fixup_pth_file(filename) 836 | if filename.endswith('.egg-link'): 837 | if not os.access(filename, os.W_OK): 838 | logger.warn('Cannot write .egg-link file %s, skipping' % filename) 839 | else: 840 | fixup_egg_link(filename) 841 | 842 | def fixup_pth_file(filename): 843 | lines = [] 844 | prev_lines = [] 845 | f = open(filename) 846 | prev_lines = f.readlines() 847 | f.close() 848 | for line in prev_lines: 849 | line = line.strip() 850 | if (not line or line.startswith('#') or line.startswith('import ') 851 | or os.path.abspath(line) != line): 852 | lines.append(line) 853 | else: 854 | new_value = make_relative_path(filename, line) 855 | if line != new_value: 856 | logger.debug('Rewriting path %s as %s (in %s)' % (line, new_value, filename)) 857 | lines.append(new_value) 858 | if lines == prev_lines: 859 | logger.info('No changes to .pth file %s' % filename) 860 | return 861 | logger.notify('Making paths in .pth file %s relative' % filename) 862 | f = open(filename, 'w') 863 | f.write('\n'.join(lines) + '\n') 864 | f.close() 865 | 866 | def fixup_egg_link(filename): 867 | f = open(filename) 868 | link = f.read().strip() 869 | f.close() 870 | if os.path.abspath(link) != link: 871 | logger.debug('Link in %s already relative' % filename) 872 | return 873 | new_link = make_relative_path(filename, link) 874 | logger.notify('Rewriting link %s in %s as %s' % (link, filename, new_link)) 875 | f = open(filename, 'w') 876 | f.write(new_link) 877 | f.close() 878 | 879 | def make_relative_path(source, dest, dest_is_directory=True): 880 | """ 881 | Make a filename relative, where the filename is dest, and it is 882 | being referred to from the filename source. 883 | 884 | >>> make_relative_path('/usr/share/something/a-file.pth', 885 | ... '/usr/share/another-place/src/Directory') 886 | '../another-place/src/Directory' 887 | >>> make_relative_path('/usr/share/something/a-file.pth', 888 | ... '/home/user/src/Directory') 889 | '../../../home/user/src/Directory' 890 | >>> make_relative_path('/usr/share/a-file.pth', '/usr/share/') 891 | './' 892 | """ 893 | source = os.path.dirname(source) 894 | if not dest_is_directory: 895 | dest_filename = os.path.basename(dest) 896 | dest = os.path.dirname(dest) 897 | dest = os.path.normpath(os.path.abspath(dest)) 898 | source = os.path.normpath(os.path.abspath(source)) 899 | dest_parts = dest.strip(os.path.sep).split(os.path.sep) 900 | source_parts = source.strip(os.path.sep).split(os.path.sep) 901 | while dest_parts and source_parts and dest_parts[0] == source_parts[0]: 902 | dest_parts.pop(0) 903 | source_parts.pop(0) 904 | full_parts = ['..']*len(source_parts) + dest_parts 905 | if not dest_is_directory: 906 | full_parts.append(dest_filename) 907 | if not full_parts: 908 | # Special case for the current directory (otherwise it'd be '') 909 | return './' 910 | return os.path.sep.join(full_parts) 911 | 912 | 913 | 914 | ############################################################ 915 | ## Bootstrap script creation: 916 | 917 | def create_bootstrap_script(extra_text, python_version=''): 918 | """ 919 | Creates a bootstrap script, which is like this script but with 920 | extend_parser, adjust_options, and after_install hooks. 921 | 922 | This returns a string that (written to disk of course) can be used 923 | as a bootstrap script with your own customizations. The script 924 | will be the standard virtualenv.py script, with your extra text 925 | added (your extra text should be Python code). 926 | 927 | If you include these functions, they will be called: 928 | 929 | ``extend_parser(optparse_parser)``: 930 | You can add or remove options from the parser here. 931 | 932 | ``adjust_options(options, args)``: 933 | You can change options here, or change the args (if you accept 934 | different kinds of arguments, be sure you modify ``args`` so it is 935 | only ``[DEST_DIR]``). 936 | 937 | ``after_install(options, home_dir)``: 938 | 939 | After everything is installed, this function is called. This 940 | is probably the function you are most likely to use. An 941 | example would be:: 942 | 943 | def after_install(options, home_dir): 944 | subprocess.call([join(home_dir, 'bin', 'easy_install'), 945 | 'MyPackage']) 946 | subprocess.call([join(home_dir, 'bin', 'my-package-script'), 947 | 'setup', home_dir]) 948 | 949 | This example immediately installs a package, and runs a setup 950 | script from that package. 951 | 952 | If you provide something like ``python_version='2.4'`` then the 953 | script will start with ``#!/usr/bin/env python2.4`` instead of 954 | ``#!/usr/bin/env python``. You can use this when the script must 955 | be run with a particular Python version. 956 | """ 957 | filename = __file__ 958 | if filename.endswith('.pyc'): 959 | filename = filename[:-1] 960 | f = open(filename, 'rb') 961 | content = f.read() 962 | f.close() 963 | py_exe = 'python%s' % python_version 964 | content = (('#!/usr/bin/env %s\n' % py_exe) 965 | + '## WARNING: This file is generated\n' 966 | + content) 967 | return content.replace('##EXT' 'END##', extra_text) 968 | 969 | def adjust_options(options, args): 970 | if len(args) == 0: 971 | args[:] = ['./virtual-env'] 972 | 973 | def after_install(options, home_dir): 974 | if sys.platform == 'win32': 975 | bin_dir = join(home_dir, 'Scripts') 976 | else: 977 | bin_dir = join(home_dir, 'bin') 978 | subprocess.call([join(bin_dir, 'easy_install'), 'paver>=1.0']) 979 | subprocess.call([join(bin_dir, 'easy_install'), 'github-tools>=0.1.7']) 980 | subprocess.call([join(bin_dir, 'easy_install'), 'virtualenv']) 981 | subprocess.call([join(bin_dir, 'easy_install'), 'Nose']) 982 | 983 | 984 | ##file site.py 985 | SITE_PY = """ 986 | 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 1107 | eP8HyeK9cQ== 1108 | """.decode("base64").decode("zlib") 1109 | 1110 | ##file ez_setup.py 1111 | EZ_SETUP_PY = """ 1112 | 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 1174 | woheJi7S138BSGV4QQ== 1175 | """.decode("base64").decode("zlib") 1176 | 1177 | ##file activate.sh 1178 | ACTIVATE_SH = """ 1179 | eJytU99P2zAQfvdfcaQ8ABqN+srUh6IhUYmViXSdNECum1waS6ld2U6zgva/75ykNP0xpGnkIYl9 1180 | n8/fffddB8aZtJDKHGFRWAczhMJiAqV0GQRWFyZGmEkVitjJlXAYwEVq9AJmwmYXrANrXUAslNIO 1181 | TKFAOkikwdjla8YS3JyCs3N4ZUCPTOERLhUEp/z+7gufDB/G3wd3/NtgfBvAM3wGl6GqkP7x2/1j 1182 | 0DcE/lpq4yrg216hLDo4OFTFU8mqb6eu3Ga6yBNI0BHnqigQKoEXm32CMpNxBplYIQj6UCjWi4UP 1183 | u0y4Sq8mFakWizwn3ZyGOd1NMtBfqo1fLAUJ2xy1XYAfpK0uXBN2Us2bNDtALwScet4QZ0LN0UJJ 1184 | TRKJf63BC07XGrRLYo7JnrjXg4j0vNT16md0yyc3D9HwfnRE5Kq0S7Mjz9/aFPWOdSnqHTSJgAc9 1185 | inrvtqgJbyjUkE30ZjTZEjshXkSkD4HSKkHrTOGNhnvcOhBhnsIGcLJ3+9aem3t/M3J0HZTGYE6t 1186 | Vw5Wwkgxy9G2Db17MWMtnv2A89aS84A1CrSLYQf+JA1rbzeLFjrk/Ho44qPB1xvOrxpY2/psX0qf 1187 | zPeg0iuYkrNRiQXC007ep2BayUgc96XzvpIiJ2Nb9FaFAe0o8t5cxs2MayNJlAaOCJlzy6swLMuy 1188 | +4KOnLrqkptDq1NXCoOh8BlC9maZxxatKaU8SvBpOn2GuhbMLW5Pn71T1Hl9gFra8h77oJn/gHn/ 1189 | z1n/9znfzDgp8gduuMqz 1190 | """.decode("base64").decode("zlib") 1191 | 1192 | ##file activate.bat 1193 | ACTIVATE_BAT = """ 1194 | eJx9kMsOgjAQRfdN+g+zoAn8goZEDESJPBpEViSzkFbZ0IX8f+RRaVW0u5mee3PanbjeFSgpKXmI 1195 | Hqq4KC9BglFW+YjWhEgJJa2ETvXQCNl2ogFe5CkvwaUEhjPm543vcOdAiacjLxzzJFw6f2bZCsZ0 1196 | 2YitXPtswawi1zwgC9II0QPD/RELyuOb1jB/Sg0rNhM31Ss4n2I+7ibLb8epQGco2Rja1Fs/zeoa 1197 | cR9nWnprJaMspOQJdBR1/g== 1198 | """.decode("base64").decode("zlib") 1199 | 1200 | ##file deactivate.bat 1201 | DEACTIVATE_BAT = """ 1202 | eJxzSE3OyFfIT0vj4spMU0hJTcvMS01RiPf3cYkP8wwKCXX0iQ8I8vcNCFHQ4FIAguLUEgWIgK0q 1203 | FlWqXJpcICVYpGzx2OAY4oFsPpCLbjpQCLvZILVcXFaufi5cACHzOrI= 1204 | """.decode("base64").decode("zlib") 1205 | 1206 | ##file distutils-init.py 1207 | DISTUTILS_INIT = """ 1208 | eJytVsuq3DAM3fsrxAwlCUxD1xeGu+gD7qKl0EK7M76JMnGbsYPtzOPvK9uZPGdoF80iONKRLB1J 1209 | duSx1caBtkzGlb0Oy7MwSqqDhZvgvVaVPHwVxqKB7fxTWlDagYCTNK4TDaoTHHXZNbgDq+GMUAgF 1210 | nUWQDpyGSqoSXI1gXdnIV8ZKaZQ4IuwpmLwVrs57iVdZ1znZWO7lE8QvLVW6gKfTsHLOK9kg59kO 1211 | ksFNkjFZDU6UNke/SOfbZLBfhZKubAb/2RMDem6c5X6RBpF/Nt8p0wkzw1bQiuK3OCAIB28siLZF 1212 | CtwT9EpMqciQp6XRhXBSKxA2Cq/W4XF09LzJGDYWYxg8pMB5LhWx4NJ3O1hkF2B4wQJ0iyOJgdE5 1213 | lJjjXCrpyF17TbIsNyjKNGO3tvDwSfsUgX/Gtttxz9yvKFdX1GifGNNNyX0H8AgOJFoqrIflH+hl 1214 | 5Gvn081XKFZiBStparGp+hpUuqPeouLd2yQCAy5SyMdaLBprRcOYTlEdkuhkO+kkvBCAdlj47cPa 1215 | DrFNqrLCDi2zhR+1CKNSiKYJNW/RvO38sMWEwCcU8DGGOD57SFpt5SV5Glx5m5B9P2AbquMsl03s 1216 | hqF97hrdtVmiZgRScnlrsJKX3RyYTaIOcGm9Kp2DxlgqTQeMb3eaiIaCSAONE0DvzmNyVKU9S5rN 1217 | ZBFxsjAY62HwqE+YevOMzVV+IlWZ3gnfoOuMijD2D41L7KybeT4lw/QsRuWAjrdXV2tFg1iQowGY 1218 | z1VhOAblwi5tG+G4bbGQlSz21H2xOPsvWt3YJhKj0B/oXj5S1svD5v4IaHiUTMlYBzvf9LZlxh4F 1219 | SSd2qQvO+/m9r2SP8p9Ss7BdMUm3ziMm/YX0kElSrpm0TqhSmNJrHzI7BQEt/yfVq6jmMf2FeEI8 1220 | Jn6ivE/8gsmFre/xTy8/P398gm+17poSXmJ7L7jvjU/+/nv2cxGfFwc13XmCbkD6T/EkdlW1o38L 1221 | Gz7PtSRPpUarErp+kA6JeHnSxJY5+wM7qxSa 1222 | """.decode("base64").decode("zlib") 1223 | 1224 | ##file distutils.cfg 1225 | DISTUTILS_CFG = """ 1226 | eJxNj00KwkAMhfc9xYNuxe4Ft57AjYiUtDO1wXSmNJnK3N5pdSEEAu8nH6lxHVlRhtDHMPATA4uH 1227 | xJ4EFmGbvfJiicSHFRzUSISMY6hq3GLCRLnIvSTnEefN0FIjw5tF0Hkk9Q5dRunBsVoyFi24aaLg 1228 | 9FDOlL0FPGluf4QjcInLlxd6f6rqkgPu/5nHLg0cXCscXoozRrP51DRT3j9QNl99AP53T2Q= 1229 | """.decode("base64").decode("zlib") 1230 | 1231 | ##file activate_this.py 1232 | ACTIVATE_THIS = """ 1233 | eJx1UsGOnDAMvecrIlYriDRlKvU20h5aaY+teuilGo1QALO4CwlKAjP8fe1QGGalRoLEefbzs+Mk 1234 | Sb7NcvRo3iTcoGqwgyy06As+HWSNVciKaBTFywYoJWc7yit2ndBVwEkHkIzKCV0YdQdmkvShs6YH 1235 | E3IhfjFaaSNLoHxQy2sLJrL0ow98JQmEG/rAYn7OobVGogngBgf0P0hjgwgt7HOUaI5DdBVJkggR 1236 | 3HwSktaqWcCtgiHIH7qHV+esW2CnkRJ+9R5cQGsikkWEV/J7leVGs9TV4TvcO5QOOrTHYI+xeCjY 1237 | JR/m9GPDHv2oSZunUokS2A/WBelnvx6tF6LUJO2FjjlH5zU6Q+Kz/9m69LxvSZVSwiOlGnT1rt/A 1238 | 77j+WDQZ8x9k2mFJetOle88+lc8sJJ/AeerI+fTlQigTfVqJUiXoKaaC3AqmI+KOnivjMLbvBVFU 1239 | 1JDruuadNGcPmkgiBTnQXUGUDd6IK9JEQ9yPdM96xZP8bieeMRqTuqbxIbbey2DjVUNzRs1rosFS 1240 | TsLAdS/0fBGNdTGKhuqD7mUmsFlgGjN2eSj1tM3GnjfXwwCmzjhMbR4rLZXXk+Z/6Hp7Pn2+kJ49 1241 | jfgLHgI4Jg== 1242 | """.decode("base64").decode("zlib") 1243 | 1244 | if __name__ == '__main__': 1245 | main() 1246 | 1247 | ## TODO: 1248 | ## Copy python.exe.manifest 1249 | ## Monkeypatch distutils.sysconfig 1250 | --------------------------------------------------------------------------------