Commit b288c4c1 authored by Gary Poster's avatar Gary Poster

basic tests and implementation of stand-alone interpreter option

parent dc34f555
...@@ -985,7 +985,7 @@ def interpreter(name, working_set, executable, dest, site_py_dest, ...@@ -985,7 +985,7 @@ def interpreter(name, working_set, executable, dest, site_py_dest,
real_sitecustomize_path = _get_module_file( real_sitecustomize_path = _get_module_file(
executable, 'sitecustomize') executable, 'sitecustomize')
if real_sitecustomize_path: if real_sitecustomize_path:
sitecustomize.write('execfile(%s)\n' % (real_sitecustomize_path,)) sitecustomize.write('execfile(%r)\n' % (real_sitecustomize_path,))
sitecustomize.close() sitecustomize.close()
generated.append(sitecustomize_path) generated.append(sitecustomize_path)
# Write site.py. # Write site.py.
...@@ -1031,7 +1031,7 @@ def interpreter(name, working_set, executable, dest, site_py_dest, ...@@ -1031,7 +1031,7 @@ def interpreter(name, working_set, executable, dest, site_py_dest,
os.chmod(script_name,0755) os.chmod(script_name,0755)
except (AttributeError, os.error): except (AttributeError, os.error):
pass pass
logger.info("Generated interpreter %r.", name) logger.info("Generated interpreter %r.", full_name)
generated.append(script_name) generated.append(script_name)
return generated return generated
......
...@@ -28,6 +28,7 @@ import socket ...@@ -28,6 +28,7 @@ import socket
import subprocess import subprocess
import sys import sys
import tempfile import tempfile
import textwrap
import threading import threading
import time import time
import urllib2 import urllib2
...@@ -202,6 +203,24 @@ def wait_until(label, func, *args, **kw): ...@@ -202,6 +203,24 @@ def wait_until(label, func, *args, **kw):
time.sleep(0.01) time.sleep(0.01)
raise ValueError('Timed out waiting for: '+label) 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): def buildoutSetUp(test):
test.globs['__tear_downs'] = __tear_downs = [] test.globs['__tear_downs'] = __tear_downs = []
...@@ -255,27 +274,7 @@ def buildoutSetUp(test): ...@@ -255,27 +274,7 @@ def buildoutSetUp(test):
sample = tmpdir('sample-buildout') sample = tmpdir('sample-buildout')
os.chdir(sample) 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): def start_server(path):
port, thread = _start_server(path, name=path) port, thread = _start_server(path, name=path)
...@@ -283,6 +282,37 @@ def buildoutSetUp(test): ...@@ -283,6 +282,37 @@ def buildoutSetUp(test):
register_teardown(lambda: stop_server(url, thread)) register_teardown(lambda: stop_server(url, thread))
return url 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( test.globs.update(dict(
sample_buildout = sample, sample_buildout = sample,
ls = ls, ls = ls,
...@@ -301,6 +331,7 @@ def buildoutSetUp(test): ...@@ -301,6 +331,7 @@ def buildoutSetUp(test):
start_server = start_server, start_server = start_server,
buildout = os.path.join(sample, 'bin', 'buildout'), buildout = os.path.join(sample, 'bin', 'buildout'),
wait_until = wait_until, wait_until = wait_until,
make_py = make_py
)) ))
zc.buildout.easy_install.prefer_final(prefer_final) zc.buildout.easy_install.prefer_final(prefer_final)
......
...@@ -76,6 +76,7 @@ setup( ...@@ -76,6 +76,7 @@ setup(
'eggs = %s:Eggs' % name, 'eggs = %s:Eggs' % name,
'custom = %s:Custom' % name, 'custom = %s:Custom' % name,
'develop = %s:Develop' % name, 'develop = %s:Develop' % name,
'interpreter = %s:Interpreter' % name,
] ]
}, },
include_package_data = True, include_package_data = True,
......
...@@ -154,6 +154,8 @@ dependent-scripts ...@@ -154,6 +154,8 @@ dependent-scripts
interpreter interpreter
The name of a script to generate that allows access to a Python The name of a script to generate that allows access to a Python
interpreter that has the path set based on the eggs installed. 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
Extra paths to include in a generated script. Extra paths to include in a generated script.
...@@ -750,3 +752,218 @@ be made to contact an index server: ...@@ -750,3 +752,218 @@ be made to contact an index server:
Uninstalling bigdemo. Uninstalling bigdemo.
Installing demo. Installing demo.
Generated script '/sample-buildout/bin/foo'. 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.
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 from zc.recipe.egg.custom import Custom, Develop
...@@ -19,6 +19,7 @@ $Id$ ...@@ -19,6 +19,7 @@ $Id$
import logging, os, re, zipfile import logging, os, re, zipfile
import zc.buildout.easy_install import zc.buildout.easy_install
class Eggs(object): class Eggs(object):
def __init__(self, buildout, name, options): def __init__(self, buildout, name, options):
...@@ -98,10 +99,11 @@ class Eggs(object): ...@@ -98,10 +99,11 @@ class Eggs(object):
update = install update = install
class Scripts(Eggs):
class ScriptBase(Eggs):
def __init__(self, buildout, name, options): def __init__(self, buildout, name, options):
super(Scripts, self).__init__(buildout, name, options) super(ScriptBase, self).__init__(buildout, name, options)
b_options = buildout['buildout'] b_options = buildout['buildout']
...@@ -135,6 +137,9 @@ class Scripts(Eggs): ...@@ -135,6 +137,9 @@ class Scripts(Eggs):
(value,)) (value,))
self.include_site_packages = (value == 'true') self.include_site_packages = (value == 'true')
class Scripts(ScriptBase):
parse_entry_point = re.compile( parse_entry_point = re.compile(
'([^=]+)=(\w+(?:[.]\w+)*):(\w+(?:[.]\w+)*)$' '([^=]+)=(\w+(?:[.]\w+)*):(\w+(?:[.]\w+)*)$'
).match ).match
...@@ -184,6 +189,50 @@ class Scripts(Eggs): ...@@ -184,6 +189,50 @@ class Scripts(Eggs):
update = install 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): def get_bool(options, name, default=False):
value = options.get(name) value = options.get(name)
if not value: if not value:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment