[Checkins] SVN: zc.buildout/branches/gary-4/ basic tests and implementation of stand-alone interpreter option

Gary Poster gary.poster at canonical.com
Thu Dec 17 21:33:09 EST 2009


Log message for revision 106736:
  basic tests and implementation of stand-alone interpreter option

Changed:
  U   zc.buildout/branches/gary-4/src/zc/buildout/easy_install.py
  U   zc.buildout/branches/gary-4/src/zc/buildout/testing.py
  U   zc.buildout/branches/gary-4/zc.recipe.egg_/setup.py
  U   zc.buildout/branches/gary-4/zc.recipe.egg_/src/zc/recipe/egg/README.txt
  U   zc.buildout/branches/gary-4/zc.recipe.egg_/src/zc/recipe/egg/__init__.py
  U   zc.buildout/branches/gary-4/zc.recipe.egg_/src/zc/recipe/egg/egg.py

-=-
Modified: zc.buildout/branches/gary-4/src/zc/buildout/easy_install.py
===================================================================
--- zc.buildout/branches/gary-4/src/zc/buildout/easy_install.py	2009-12-18 01:40:41 UTC (rev 106735)
+++ zc.buildout/branches/gary-4/src/zc/buildout/easy_install.py	2009-12-18 02:33:08 UTC (rev 106736)
@@ -985,7 +985,7 @@
         real_sitecustomize_path = _get_module_file(
             executable, 'sitecustomize')
         if real_sitecustomize_path:
-            sitecustomize.write('execfile(%s)\n' % (real_sitecustomize_path,))
+            sitecustomize.write('execfile(%r)\n' % (real_sitecustomize_path,))
     sitecustomize.close()
     generated.append(sitecustomize_path)
     # Write site.py.
@@ -1031,7 +1031,7 @@
             os.chmod(script_name,0755)
         except (AttributeError, os.error):
             pass
-        logger.info("Generated interpreter %r.", name)
+        logger.info("Generated interpreter %r.", full_name)
     generated.append(script_name)
     return generated
 

Modified: zc.buildout/branches/gary-4/src/zc/buildout/testing.py
===================================================================
--- zc.buildout/branches/gary-4/src/zc/buildout/testing.py	2009-12-18 01:40:41 UTC (rev 106735)
+++ zc.buildout/branches/gary-4/src/zc/buildout/testing.py	2009-12-18 02:33:08 UTC (rev 106736)
@@ -28,6 +28,7 @@
 import subprocess
 import sys
 import tempfile
+import textwrap
 import threading
 import time
 import urllib2
@@ -202,6 +203,24 @@
         time.sleep(0.01)
     raise ValueError('Timed out waiting for: '+label)
 
+def make_buildout():
+    # Create a basic buildout.cfg to avoid a warning from buildout:
+    open('buildout.cfg', 'w').write(
+        "[buildout]\nparts =\n"
+        )
+    # Use the buildout bootstrap command to create a buildout
+    zc.buildout.buildout.Buildout(
+        'buildout.cfg',
+        [('buildout', 'log-level', 'WARNING'),
+         # trick bootstrap into putting the buildout develop egg
+         # in the eggs dir.
+         ('buildout', 'develop-eggs-directory', 'eggs'),
+         ]
+        ).bootstrap([])
+    # Create the develop-eggs dir, which didn't get created the usual
+    # way due to the trick above:
+    os.mkdir('develop-eggs')
+
 def buildoutSetUp(test):
 
     test.globs['__tear_downs'] = __tear_downs = []
@@ -255,34 +274,45 @@
     sample = tmpdir('sample-buildout')
 
     os.chdir(sample)
+    make_buildout()
 
-    # Create a basic buildout.cfg to avoid a warning from buildout:
-    open('buildout.cfg', 'w').write(
-        "[buildout]\nparts =\n"
-        )
-
-    # Use the buildout bootstrap command to create a buildout
-    zc.buildout.buildout.Buildout(
-        'buildout.cfg',
-        [('buildout', 'log-level', 'WARNING'),
-         # trick bootstrap into putting the buildout develop egg
-         # in the eggs dir.
-         ('buildout', 'develop-eggs-directory', 'eggs'),
-         ]
-        ).bootstrap([])
-
-
-
-    # Create the develop-eggs dir, which didn't get created the usual
-    # way due to the trick above:
-    os.mkdir('develop-eggs')
-
     def start_server(path):
         port, thread = _start_server(path, name=path)
         url = 'http://localhost:%s/' % port
         register_teardown(lambda: stop_server(url, thread))
         return url
 
+    def make_py(initialization='', site_packages_dir=None):
+        """Returns paths to new executable and to its site-packages.
+        """
+        buildout = tmpdir('executable_buildout')
+        if site_packages_dir is None:
+            site_packages_dir = mkdir(buildout, 'site-packages')
+        old_wd = os.getcwd()
+        os.chdir(buildout)
+        make_buildout()
+        initialization = '\n'.join(
+            '  ' + line for line in initialization.split('\n'))
+        install_develop(
+            'zc.recipe.egg', os.path.join(buildout, 'develop-eggs'))
+        write('buildout.cfg', textwrap.dedent('''\
+            [buildout]
+            parts = py
+
+            [py]
+            recipe = zc.recipe.egg:interpreter
+            initialization =
+            %(initialization)s
+            extra-paths = %(site-packages)s
+            eggs = setuptools
+            ''') % {
+                'initialization': initialization,
+                'site-packages': site_packages_dir})
+        system(os.path.join(buildout, 'bin', 'buildout'))
+        os.chdir(old_wd)
+        return (
+            os.path.join(buildout, 'bin', 'py'), site_packages_dir)
+
     test.globs.update(dict(
         sample_buildout = sample,
         ls = ls,
@@ -301,6 +331,7 @@
         start_server = start_server,
         buildout = os.path.join(sample, 'bin', 'buildout'),
         wait_until = wait_until,
+        make_py = make_py
         ))
 
     zc.buildout.easy_install.prefer_final(prefer_final)

Modified: zc.buildout/branches/gary-4/zc.recipe.egg_/setup.py
===================================================================
--- zc.buildout/branches/gary-4/zc.recipe.egg_/setup.py	2009-12-18 01:40:41 UTC (rev 106735)
+++ zc.buildout/branches/gary-4/zc.recipe.egg_/setup.py	2009-12-18 02:33:08 UTC (rev 106736)
@@ -76,6 +76,7 @@
                                     'eggs = %s:Eggs' % name,
                                     'custom = %s:Custom' % name,
                                     'develop = %s:Develop' % name,
+                                    'interpreter = %s:Interpreter' % name,
                                     ]
                     },
     include_package_data = True,

Modified: zc.buildout/branches/gary-4/zc.recipe.egg_/src/zc/recipe/egg/README.txt
===================================================================
--- zc.buildout/branches/gary-4/zc.recipe.egg_/src/zc/recipe/egg/README.txt	2009-12-18 01:40:41 UTC (rev 106735)
+++ zc.buildout/branches/gary-4/zc.recipe.egg_/src/zc/recipe/egg/README.txt	2009-12-18 02:33:08 UTC (rev 106736)
@@ -154,6 +154,8 @@
 interpreter
    The name of a script to generate that allows access to a Python
    interpreter that has the path set based on the eggs installed.
+   See the ``interpreter`` (or ``py``) recipe, below, for a more
+   full-featured interpreter.
 
 extra-paths
    Extra paths to include in a generated script.
@@ -750,3 +752,218 @@
     Uninstalling bigdemo.
     Installing demo.
     Generated script '/sample-buildout/bin/foo'.
+
+Interpreter generation
+----------------------
+
+What if you want a more full-featured interpreter than the one described
+above?  That one is a script that mimics an interpreter--it has support
+for only a limited number of command-line options.
+
+The interpreter recipe generates a full-fledged version.  Here's an example.
+
+    >>> write(sample_buildout, 'buildout.cfg',
+    ... """
+    ... [buildout]
+    ... parts = py
+    ...
+    ... [py]
+    ... recipe = zc.recipe.egg:interpreter
+    ... eggs = demo<0.3
+    ... find-links = %(server)s
+    ... index = %(server)s/index
+    ... """ % dict(server=link_server))
+
+    >>> print system(buildout),
+    Uninstalling demo.
+    Installing py.
+    Generated interpreter '/sample-buildout/bin/py'.
+
+Notice that the recipe took the name of the recipe from the name of the
+section.
+
+The bin/py script now just restarts Python after specifying a special
+path in PYTHONPATH.
+
+    >>> cat(sample_buildout, 'bin', 'py') # doctest: +NORMALIZE_WHITESPACE
+    #!/usr/bin/python2.4 -S
+    <BLANKLINE>
+    import os
+    import sys
+    <BLANKLINE>
+    argv = [sys.executable] + sys.argv[1:]
+    environ = os.environ.copy()
+    path = '/sample-buildout/parts/py'
+    if environ.get('PYTHONPATH'):
+        path = os.pathsep.join([path, environ['PYTHONPATH']])
+    environ['PYTHONPATH'] = path
+    os.execve(sys.executable, argv, environ)
+
+The path is a directory that contains two files: our own site.py and
+sitecustomize.py.
+
+    >>> ls(sample_buildout, 'parts', 'py')
+    -  site.py
+    -  sitecustomize.py
+
+    >>> cat(sample_buildout, 'parts', 'py', 'site.py')
+    ... # doctest: +NORMALIZE_WHITESPACE
+    import sys
+    sys.path[0:0] = [
+      '/sample-buildout/eggs/demo-0.2-py2.4.egg',
+      '/sample-buildout/eggs/demoneeded-1.2c1-py2.4.egg',
+      ]
+    import sitecustomize
+
+    >>> cat(sample_buildout, 'parts', 'py', 'sitecustomize.py')
+
+Here's an example of using the generated interpreter.
+
+    >>> print system(join(sample_buildout, 'bin', 'py') +
+    ...              ' -c "import sys, pprint; pprint.pprint(sys.path[:3])"')
+    ['',
+     '/sample-buildout/eggs/demo-0.2-py2.4.egg',
+     '/sample-buildout/eggs/demoneeded-1.2c1-py2.4.egg']
+    <BLANKLINE>
+
+The interpreter recipe takes several options.  First, here's the list of the
+options that overlap from the scripts recipe.  After this, we'll list the new
+options and describe them.
+
+* eggs
+* find-links
+* index
+* python
+* extra-paths
+* initialization
+* relative-paths
+* include-site-packages
+
+In addition to these, the interpreter script offers these three new options.
+
+include-site-customization
+    Normally the Python's real sitecustomize module is not processed.
+    If you want it to be processed, set this value to 'true'.  This will
+    be honored irrespective of the setting for include-site-paths.
+
+extends
+    You can extend another section using this value.  It is intended to be
+    used by extending a section that uses this package's scripts recipe.
+    In this manner, you can avoid repeating yourself.
+
+name
+    If you do not want to have the interpreter have the same name as the
+    section, you can set it explicitly with this option.
+
+Let's look at the ``extends`` option first.
+
+    >>> write(sample_buildout, 'buildout.cfg',
+    ... """
+    ... [buildout]
+    ... parts = demo python
+    ...
+    ... [demo]
+    ... recipe = zc.recipe.egg
+    ... eggs = demo<0.3
+    ... find-links = %(server)s
+    ... index = %(server)s/index
+    ...
+    ... [python]
+    ... recipe = zc.recipe.egg:interpreter
+    ... extends = demo
+    ... """ % dict(server=link_server))
+
+That's not quite as short as adding an "interpreter = py" option to the
+[demo] section, but an improvement over what it could be.
+
+Now let's put it in action.
+
+    >>> print system(buildout),
+    Uninstalling py.
+    Installing demo.
+    Generated script '/sample-buildout/bin/demo'.
+    Installing python.
+    Generated interpreter '/sample-buildout/bin/python'.
+
+    >>> print system(join(sample_buildout, 'bin', 'python') +
+    ...              ' -c "import sys, pprint; pprint.pprint(sys.path[:3])"')
+    ['',
+     '/sample-buildout/eggs/demo-0.2-py2.4.egg',
+     '/sample-buildout/eggs/demoneeded-1.2c1-py2.4.egg']
+    <BLANKLINE>
+
+Note that the parts/py directory has been cleaned up, and parts/python has
+been created.
+
+    >>> ls(sample_buildout, 'parts')
+    d  python
+
+Now let's use the include-site-customization option.  It simply lets Python's
+underlying sitecustomize module, if it exists, be executed.
+
+To show this, we need a Python executable guaranteed to have a sitecustomize
+module.  We'll make one.  The os.environ change below will go into the
+sitecustomize.  We'll be able to use that as a flag.
+
+    >>> py_path, site_packages_path = make_py(initialization='''\
+    ... import os
+    ... os.environ['zc.buildout'] = 'foo bar baz shazam'
+    ... ''')
+
+    >>> write(sample_buildout, 'buildout.cfg',
+    ... """
+    ... [buildout]
+    ... parts = py
+    ... executable = %(py_path)s
+    ...
+    ... [py]
+    ... recipe = zc.recipe.egg:interpreter
+    ... include-site-customization = true
+    ... eggs = demo<0.3
+    ... find-links = %(server)s
+    ... index = %(server)s/index
+    ... """ % dict(server=link_server, py_path=py_path))
+
+    >>> print system(buildout),
+    Uninstalling python.
+    Uninstalling demo.
+    Installing py.
+    Generated interpreter '/sample-buildout/bin/py'.
+
+    >>> cat(sample_buildout, 'parts', 'py', 'sitecustomize.py')
+    ... # doctest: +NORMALIZE_WHITESPACE
+    execfile('/executable_buildout/parts/py/sitecustomize.py')
+    >>> print system(join(sample_buildout, 'bin', 'py') +
+    ...              ''' -c "import os; print os.environ['zc.buildout']"''')
+    foo bar baz shazam
+    <BLANKLINE>
+
+The last new option is ``name``.  This simply changes the name of the
+interpreter, so that you are not forced to use the name of the section.
+
+    >>> write(sample_buildout, 'buildout.cfg',
+    ... """
+    ... [buildout]
+    ... parts = interpreter
+    ...
+    ... [interpreter]
+    ... name = python2
+    ... recipe = zc.recipe.egg:interpreter
+    ... include-site-customization = true
+    ... eggs = demo<0.3
+    ... find-links = %(server)s
+    ... index = %(server)s/index
+    ... """ % dict(server=link_server))
+
+    >>> print system(buildout),
+    Uninstalling py.
+    Installing interpreter.
+    Generated interpreter '/sample-buildout/bin/python2'.
+
+    >>> print system(join(sample_buildout, 'bin', 'python2') +
+    ...              ' -c "print 42"')
+    42
+    <BLANKLINE>
+
+The other options have been described before for the scripts recipe, and so
+they will not be repeated here.

Modified: zc.buildout/branches/gary-4/zc.recipe.egg_/src/zc/recipe/egg/__init__.py
===================================================================
--- zc.buildout/branches/gary-4/zc.recipe.egg_/src/zc/recipe/egg/__init__.py	2009-12-18 01:40:41 UTC (rev 106735)
+++ zc.buildout/branches/gary-4/zc.recipe.egg_/src/zc/recipe/egg/__init__.py	2009-12-18 02:33:08 UTC (rev 106736)
@@ -1,2 +1,2 @@
-from zc.recipe.egg.egg import Egg, Scripts, Eggs
+from zc.recipe.egg.egg import Egg, Scripts, Eggs, Interpreter
 from zc.recipe.egg.custom import Custom, Develop

Modified: zc.buildout/branches/gary-4/zc.recipe.egg_/src/zc/recipe/egg/egg.py
===================================================================
--- zc.buildout/branches/gary-4/zc.recipe.egg_/src/zc/recipe/egg/egg.py	2009-12-18 01:40:41 UTC (rev 106735)
+++ zc.buildout/branches/gary-4/zc.recipe.egg_/src/zc/recipe/egg/egg.py	2009-12-18 02:33:08 UTC (rev 106736)
@@ -19,6 +19,7 @@
 import logging, os, re, zipfile
 import zc.buildout.easy_install
 
+
 class Eggs(object):
 
     def __init__(self, buildout, name, options):
@@ -98,10 +99,11 @@
 
     update = install
 
-class Scripts(Eggs):
 
+class ScriptBase(Eggs):
+
     def __init__(self, buildout, name, options):
-        super(Scripts, self).__init__(buildout, name, options)
+        super(ScriptBase, self).__init__(buildout, name, options)
 
         b_options = buildout['buildout']
 
@@ -135,6 +137,9 @@
                 (value,))
         self.include_site_packages = (value == 'true')
 
+
+class Scripts(ScriptBase):
+
     parse_entry_point = re.compile(
         '([^=]+)=(\w+(?:[.]\w+)*):(\w+(?:[.]\w+)*)$'
         ).match
@@ -184,6 +189,50 @@
 
     update = install
 
+
+class Interpreter(ScriptBase):
+
+    def __init__(self, buildout, name, options):
+        if 'extends' in options:
+            options.update(buildout[options['extends']])
+        super(Interpreter, self).__init__(buildout, name, options)
+        b_options = buildout['buildout']
+        options['parts-directory'] = os.path.join(
+            b_options['parts-directory'], self.name)
+
+        value = options.setdefault(
+            'include-site-customization',
+            b_options.get('include-site-customization', 'false'))
+        if value not in ('true', 'false'):
+            raise zc.buildout.UserError(
+                "Invalid value for include-site-customization option: %s" %
+                (value,))
+        self.include_site_customization = (value == 'true')
+
+        options.setdefault('name', name)
+
+    def install(self):
+        reqs, ws = self.working_set()
+        options = self.options
+        if not os.path.exists(options['parts-directory']):
+            os.mkdir(options['parts-directory'])
+            dir_made = True
+        else:
+            dir_made = False
+        generated = zc.buildout.easy_install.interpreter(
+            options['name'], ws, options['executable'],
+            options['bin-directory'], options['parts-directory'],
+            extra_paths=self.extra_paths,
+            initialization=options.get('initialization', ''),
+            relative_paths=self._relative_paths,
+            import_site=self.include_site_packages,
+            import_sitecustomize=self.include_site_customization,
+            )
+        if dir_made:
+            generated.append(options['parts-directory'])
+        return generated
+
+
 def get_bool(options, name, default=False):
     value = options.get(name)
     if not value:



More information about the checkins mailing list