Commit c85a590f authored by jim's avatar jim

Added support for extra paths in generated scripts.

Added ability to supply entry points directly. This is useful for
packages that don't declare their entry points.

No longer generate "py-" scripts implicitly.  Added a new option,
interpreter, to request such scripts and specifu their names.


git-svn-id: http://svn.zope.org/repos/main/zc.buildout/trunk@69991 62d5b8a3-27da-0310-9561-8e5933582275
parent cd3eca20
...@@ -24,10 +24,7 @@ import logging, os, re, tempfile, sys ...@@ -24,10 +24,7 @@ import logging, os, re, tempfile, sys
import pkg_resources, setuptools.command.setopt, setuptools.package_index import pkg_resources, setuptools.command.setopt, setuptools.package_index
import zc.buildout import zc.buildout
# XXX we could potentially speed this up quite a bit by keeping our default_index_url = os.environ.get('buildout-testing-index-url')
# own PackageIndex to analyse whether there are newer dists. A hitch
# is that the package index seems to go out of its way to only handle
# one Python version at a time. :(
logger = logging.getLogger('zc.buildout.easy_install') logger = logging.getLogger('zc.buildout.easy_install')
...@@ -69,6 +66,9 @@ def _get_index(executable, index_url, find_links): ...@@ -69,6 +66,9 @@ def _get_index(executable, index_url, find_links):
if index is not None: if index is not None:
return index return index
if index_url is None:
index_url = default_index_url
if index_url is None: if index_url is None:
index = setuptools.package_index.PackageIndex( index = setuptools.package_index.PackageIndex(
python=_get_version(executable) python=_get_version(executable)
...@@ -151,7 +151,7 @@ def _satisfied(req, env, dest, executable, index, links): ...@@ -151,7 +151,7 @@ def _satisfied(req, env, dest, executable, index, links):
return best_we_have return best_we_have
else: else:
# Let's find out if we already have the best available: # Let's find out if we already have the best available:
if best_we_have >= best_available: if best_we_have.parsed_version >= best_available.parsed_version:
# Yup. Use it. # Yup. Use it.
logger.debug('We have the best distributon that satisfies\n%s', req) logger.debug('We have the best distributon that satisfies\n%s', req)
return best_we_have return best_we_have
...@@ -218,6 +218,8 @@ def _get_dist(requirement, env, ws, ...@@ -218,6 +218,8 @@ def _get_dist(requirement, env, ws,
if dist is None: if dist is None:
if dest is not None: if dest is not None:
logger.info("Getting new distribution for %s", requirement)
# May need a new one. Call easy_install # May need a new one. Call easy_install
_call_easy_install(str(requirement), dest, links, index, _call_easy_install(str(requirement), dest, links, index,
executable, always_unzip) executable, always_unzip)
...@@ -228,8 +230,10 @@ def _get_dist(requirement, env, ws, ...@@ -228,8 +230,10 @@ def _get_dist(requirement, env, ws,
# and either firgure out the distribution added, or # and either firgure out the distribution added, or
# only rescan if any files have been added. # only rescan if any files have been added.
env.scan([dest]) env.scan([dest])
dist = env.best_match(requirement, ws)
dist = env.best_match(requirement, ws) logger.info("Got %s", dist)
else:
dist = env.best_match(requirement, ws)
if dist is None: if dist is None:
raise ValueError("Couldn't find", requirement) raise ValueError("Couldn't find", requirement)
...@@ -246,12 +250,12 @@ def _get_dist(requirement, env, ws, ...@@ -246,12 +250,12 @@ def _get_dist(requirement, env, ws,
def install(specs, dest, def install(specs, dest,
links=(), index=None, links=(), index=None,
executable=sys.executable, always_unzip=False, executable=sys.executable, always_unzip=False,
path=None): path=None, working_set=None):
logger.debug('Installing %r', specs) logger.debug('Installing %r', specs)
path = path and path[:] or [] path = path and path[:] or []
if dest is not None: if dest is not None and dest not in path:
path.insert(0, dest) path.insert(0, dest)
path += buildout_and_setuptools_path path += buildout_and_setuptools_path
...@@ -265,7 +269,10 @@ def install(specs, dest, ...@@ -265,7 +269,10 @@ def install(specs, dest,
env = pkg_resources.Environment(path, python=_get_version(executable)) env = pkg_resources.Environment(path, python=_get_version(executable))
requirements = [pkg_resources.Requirement.parse(spec) for spec in specs] requirements = [pkg_resources.Requirement.parse(spec) for spec in specs]
ws = pkg_resources.WorkingSet([]) if working_set is None:
ws = pkg_resources.WorkingSet([])
else:
ws = working_set
for requirement in requirements: for requirement in requirements:
ws.add(_get_dist(requirement, env, ws, ws.add(_get_dist(requirement, env, ws,
...@@ -368,46 +375,51 @@ def scripts(reqs, working_set, executable, dest, ...@@ -368,46 +375,51 @@ def scripts(reqs, working_set, executable, dest,
scripts=None, scripts=None,
extra_paths=(), extra_paths=(),
arguments='', arguments='',
interpreter=None,
): ):
reqs = [pkg_resources.Requirement.parse(r) for r in reqs]
projects = [r.project_name for r in reqs]
path = [dist.location for dist in working_set] path = [dist.location for dist in working_set]
path.extend(extra_paths) path.extend(extra_paths)
path = repr(path)[1:-1].replace(', ', ',\n ') path = repr(path)[1:-1].replace(', ', ',\n ')
generated = [] generated = []
for dist in working_set: if isinstance(reqs, str):
if dist.project_name in projects: raise TypeError('Expected iterable of requirements or entry points,'
' got string.')
entry_points = []
for req in reqs:
if isinstance(req, str):
req = pkg_resources.Requirement.parse(req)
dist = working_set.find(req)
for name in pkg_resources.get_entry_map(dist, 'console_scripts'): for name in pkg_resources.get_entry_map(dist, 'console_scripts'):
if scripts is not None: entry_point = dist.get_entry_info('console_scripts', name)
sname = scripts.get(name) entry_points.append(
if sname is None: (name, entry_point.module_name, '.'.join(entry_point.attrs))
continue
else:
sname = name
sname = os.path.join(dest, sname)
generated.extend(
_script(dist, 'console_scripts', name, path, sname,
executable, arguments)
) )
else:
entry_points.append(req)
for name, module_name, attrs in entry_points:
if scripts is not None:
sname = scripts.get(name)
if sname is None:
continue
else:
sname = name
name = 'py-'+dist.project_name sname = os.path.join(dest, sname)
if scripts is not None: generated.extend(
sname = scripts.get(name) _script(module_name, attrs, path, sname, executable, arguments)
else: )
sname = name
if sname is not None: if interpreter:
sname = os.path.join(dest, sname) sname = os.path.join(dest, interpreter)
generated.extend( generated.extend(_pyscript(path, sname, executable))
_pyscript(path, sname, executable)
)
return generated return generated
def _script(dist, group, name, path, dest, executable, arguments): def _script(module_name, attrs, path, dest, executable, arguments):
entry_point = dist.get_entry_info(group, name)
generated = [] generated = []
if sys.platform == 'win32': if sys.platform == 'win32':
# generate exe file and give the script a magic name: # generate exe file and give the script a magic name:
...@@ -420,10 +432,8 @@ def _script(dist, group, name, path, dest, executable, arguments): ...@@ -420,10 +432,8 @@ def _script(dist, group, name, path, dest, executable, arguments):
open(dest, 'w').write(script_template % dict( open(dest, 'w').write(script_template % dict(
python = executable, python = executable,
path = path, path = path,
project = dist.project_name, module_name = module_name,
name = name, attrs = attrs,
module_name = entry_point.module_name,
attrs = '.'.join(entry_point.attrs),
arguments = arguments, arguments = arguments,
)) ))
try: try:
...@@ -438,7 +448,7 @@ script_template = '''\ ...@@ -438,7 +448,7 @@ script_template = '''\
import sys import sys
sys.path[0:0] = [ sys.path[0:0] = [
%(path)s %(path)s,
] ]
import %(module_name)s import %(module_name)s
...@@ -474,7 +484,7 @@ py_script_template = '''\ ...@@ -474,7 +484,7 @@ py_script_template = '''\
import sys import sys
sys.path[0:0] = [ sys.path[0:0] = [
%(path)s %(path)s,
] ]
_interactive = True _interactive = True
......
...@@ -188,7 +188,7 @@ Now, we'll use the scripts method to generate scripts in this directory ...@@ -188,7 +188,7 @@ Now, we'll use the scripts method to generate scripts in this directory
from the demo egg: from the demo egg:
>>> scripts = zc.buildout.easy_install.scripts( >>> scripts = zc.buildout.easy_install.scripts(
... ['demo==0.1'], ws, python2_4_executable, bin) ... ['demo'], ws, python2_4_executable, bin)
the four arguments we passed were: the four arguments we passed were:
...@@ -202,25 +202,26 @@ the four arguments we passed were: ...@@ -202,25 +202,26 @@ the four arguments we passed were:
3. The destination directory. 3. The destination directory.
The bin directory now contains 2 generated scripts: The bin directory now contains a generated script:
>>> ls(bin) >>> ls(bin)
- demo - demo
- py-demo
The return value is a list of the scripts generated: The return value is a list of the scripts generated:
>>> import os, sys >>> import os, sys
>>> if sys.platform == 'win32': >>> if sys.platform == 'win32':
... scripts == [os.path.join(bin, 'demo.exe'), ... scripts == [os.path.join(bin, 'demo.exe'),
... os.path.join(bin, 'demo-script.py'), ... os.path.join(bin, 'demo-script.py')]
... os.path.join(bin, 'py-demo.exe'),
... os.path.join(bin, 'py-demo-script.py')]
... else: ... else:
... scripts == [os.path.join(bin, 'demo'), ... scripts == [os.path.join(bin, 'demo')]
... os.path.join(bin, 'py-demo')]
True True
Note that in Windows, 2 files are generated for each script. A script
file, ending in '-script.py', and an exe file that allows the script
to be invoked directly without having to specify the Python
interpreter and without having to provide a '.py' suffix.
The demo script run the entry point defined in the demo egg: The demo script run the entry point defined in the demo egg:
>>> cat(bin, 'demo') # doctest: +NORMALIZE_WHITESPACE >>> cat(bin, 'demo') # doctest: +NORMALIZE_WHITESPACE
...@@ -229,7 +230,7 @@ The demo script run the entry point defined in the demo egg: ...@@ -229,7 +230,7 @@ The demo script run the entry point defined in the demo egg:
import sys import sys
sys.path[0:0] = [ sys.path[0:0] = [
'/tmp/xyzsample-install/demo-0.3-py2.3.egg', '/tmp/xyzsample-install/demo-0.3-py2.3.egg',
'/tmp/xyzsample-install/demoneeded-1.1-py2.3.egg' '/tmp/xyzsample-install/demoneeded-1.1-py2.3.egg',
] ]
<BLANKLINE> <BLANKLINE>
import eggrecipedemo import eggrecipedemo
...@@ -244,16 +245,73 @@ Some things to note: ...@@ -244,16 +245,73 @@ Some things to note:
- The module for the script entry point is imported and the entry - The module for the script entry point is imported and the entry
point, in this case, 'main', is run. point, in this case, 'main', is run.
The py-demo script simply run the Python interactive interpreter with Rather than requirement strings, you can pass tuples containing 3
strings:
- A script name,
- A module,
- An attribute expression for an entry point within the module.
For example, we could have passed antry point information directly
rather than passing a requirement:
>>> scripts = zc.buildout.easy_install.scripts(
... [('demo', 'eggrecipedemo', 'main')],
... ws, python2_4_executable, bin)
>>> cat(bin, 'demo') # doctest: +NORMALIZE_WHITESPACE
#!/usr/local/bin/python2.3
<BLANKLINE>
import sys
sys.path[0:0] = [
'/tmp/xyzsample-install/demo-0.3-py2.3.egg',
'/tmp/xyzsample-install/demoneeded-1.1-py2.3.egg',
]
<BLANKLINE>
import eggrecipedemo
<BLANKLINE>
if __name__ == '__main__':
eggrecipedemo.main()
Passing entry-point information directly is handy when using eggs (or
distributions) that don't declare their entry points, such as
distributions that aren't based on setuptools.
The interpreter keyword argument can be used to generate a script that can
be used to invoke the Python interactive interpreter with the path set
based on the working set. This generated script can also be used to
run other scripts with the path set on the working set:
>>> scripts = zc.buildout.easy_install.scripts(
... ['demo'], ws, python2_4_executable, bin, interpreter='py')
>>> ls(bin)
- demo
- py
>>> if sys.platform == 'win32':
... scripts == [os.path.join(bin, 'demo.exe'),
... os.path.join(bin, 'demo-script.py'),
... os.path.join(bin, 'py.exe'),
... os.path.join(bin, 'py-script.py')]
... else:
... scripts == [os.path.join(bin, 'demo'),
... os.path.join(bin, 'py')]
True
The py script simply runs the Python interactive interpreter with
the path set: the path set:
>>> cat(bin, 'py-demo') # doctest: +NORMALIZE_WHITESPACE >>> cat(bin, 'py') # doctest: +NORMALIZE_WHITESPACE
#!/usr/local/bin/python2.4 #!/usr/local/bin/python2.4
import sys import sys
<BLANKLINE> <BLANKLINE>
sys.path[0:0] = [ sys.path[0:0] = [
'/tmp/tmp5zS2Afsample-install/demo-0.3-py2.4.egg', '/tmp/tmp5zS2Afsample-install/demo-0.3-py2.4.egg',
'/tmp/tmp5zS2Afsample-install/demoneeded-1.1-py2.4.egg' '/tmp/tmp5zS2Afsample-install/demoneeded-1.1-py2.4.egg',
] ]
<BLANKLINE> <BLANKLINE>
_interactive = True _interactive = True
...@@ -278,12 +336,12 @@ the path set: ...@@ -278,12 +336,12 @@ the path set:
If invoked with a script name and arguments, it will run that script, instead. If invoked with a script name and arguments, it will run that script, instead.
An additional argumnet can be passed to define which scripts to install An additional argumnet can be passed to define which scripts to install
and to provie script names. The argument is a dictionary mapping and to provide script names. The argument is a dictionary mapping
original script names to new script names. original script names to new script names.
>>> bin = mkdtemp() >>> bin = mkdtemp()
>>> scripts = zc.buildout.easy_install.scripts( >>> scripts = zc.buildout.easy_install.scripts(
... ['demo==0.1'], ws, python2_4_executable, bin, dict(demo='run')) ... ['demo'], ws, python2_4_executable, bin, dict(demo='run'))
>>> if sys.platform == 'win32': >>> if sys.platform == 'win32':
... scripts == [os.path.join(bin, 'run.exe'), ... scripts == [os.path.join(bin, 'run.exe'),
...@@ -304,7 +362,7 @@ We can pass a keyword argument, extra paths, to caue additional paths ...@@ -304,7 +362,7 @@ We can pass a keyword argument, extra paths, to caue additional paths
to be included in the a generated script: to be included in the a generated script:
>>> scripts = zc.buildout.easy_install.scripts( >>> scripts = zc.buildout.easy_install.scripts(
... ['demo==0.1'], ws, python2_4_executable, bin, dict(demo='run'), ... ['demo'], ws, python2_4_executable, bin, dict(demo='run'),
... extra_paths=['/foo/bar']) ... extra_paths=['/foo/bar'])
>>> cat(bin, 'run') # doctest: +NORMALIZE_WHITESPACE >>> cat(bin, 'run') # doctest: +NORMALIZE_WHITESPACE
...@@ -314,7 +372,7 @@ to be included in the a generated script: ...@@ -314,7 +372,7 @@ to be included in the a generated script:
sys.path[0:0] = [ sys.path[0:0] = [
'/tmp/xyzsample-install/demo-0.3-py2.3.egg', '/tmp/xyzsample-install/demo-0.3-py2.3.egg',
'/tmp/xyzsample-install/demoneeded-1.1-py2.3.egg', '/tmp/xyzsample-install/demoneeded-1.1-py2.3.egg',
'/foo/bar' '/foo/bar',
] ]
<BLANKLINE> <BLANKLINE>
import eggrecipedemo import eggrecipedemo
...@@ -330,7 +388,7 @@ entry point. The value passed source string to be placed between the ...@@ -330,7 +388,7 @@ entry point. The value passed source string to be placed between the
parentheses in the call: parentheses in the call:
>>> scripts = zc.buildout.easy_install.scripts( >>> scripts = zc.buildout.easy_install.scripts(
... ['demo==0.1'], ws, python2_4_executable, bin, dict(demo='run'), ... ['demo'], ws, python2_4_executable, bin, dict(demo='run'),
... arguments='1, 2') ... arguments='1, 2')
>>> cat(bin, 'run') # doctest: +NORMALIZE_WHITESPACE >>> cat(bin, 'run') # doctest: +NORMALIZE_WHITESPACE
...@@ -338,7 +396,7 @@ parentheses in the call: ...@@ -338,7 +396,7 @@ parentheses in the call:
import sys import sys
sys.path[0:0] = [ sys.path[0:0] = [
'/tmp/xyzsample-install/demo-0.3-py2.3.egg', '/tmp/xyzsample-install/demo-0.3-py2.3.egg',
'/tmp/xyzsample-install/demoneeded-1.1-py2.3.egg' '/tmp/xyzsample-install/demoneeded-1.1-py2.3.egg',
] ]
<BLANKLINE> <BLANKLINE>
import eggrecipedemo import eggrecipedemo
......
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