Commit 83ea1690 authored by Jim Fulton's avatar Jim Fulton

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.
parent e67d9ea8
......@@ -24,10 +24,7 @@ import logging, os, re, tempfile, sys
import pkg_resources, setuptools.command.setopt, setuptools.package_index
import zc.buildout
# XXX we could potentially speed this up quite a bit by keeping our
# 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. :(
default_index_url = os.environ.get('buildout-testing-index-url')
logger = logging.getLogger('zc.buildout.easy_install')
......@@ -69,6 +66,9 @@ def _get_index(executable, index_url, find_links):
if index is not None:
return index
if index_url is None:
index_url = default_index_url
if index_url is None:
index = setuptools.package_index.PackageIndex(
python=_get_version(executable)
......@@ -151,7 +151,7 @@ def _satisfied(req, env, dest, executable, index, links):
return best_we_have
else:
# 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.
logger.debug('We have the best distributon that satisfies\n%s', req)
return best_we_have
......@@ -218,6 +218,8 @@ def _get_dist(requirement, env, ws,
if dist is None:
if dest is not None:
logger.info("Getting new distribution for %s", requirement)
# May need a new one. Call easy_install
_call_easy_install(str(requirement), dest, links, index,
executable, always_unzip)
......@@ -228,7 +230,9 @@ def _get_dist(requirement, env, ws,
# and either firgure out the distribution added, or
# only rescan if any files have been added.
env.scan([dest])
dist = env.best_match(requirement, ws)
logger.info("Got %s", dist)
else:
dist = env.best_match(requirement, ws)
if dist is None:
......@@ -246,12 +250,12 @@ def _get_dist(requirement, env, ws,
def install(specs, dest,
links=(), index=None,
executable=sys.executable, always_unzip=False,
path=None):
path=None, working_set=None):
logger.debug('Installing %r', specs)
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 += buildout_and_setuptools_path
......@@ -265,7 +269,10 @@ def install(specs, dest,
env = pkg_resources.Environment(path, python=_get_version(executable))
requirements = [pkg_resources.Requirement.parse(spec) for spec in specs]
if working_set is None:
ws = pkg_resources.WorkingSet([])
else:
ws = working_set
for requirement in requirements:
ws.add(_get_dist(requirement, env, ws,
......@@ -368,17 +375,32 @@ def scripts(reqs, working_set, executable, dest,
scripts=None,
extra_paths=(),
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.extend(extra_paths)
path = repr(path)[1:-1].replace(', ', ',\n ')
generated = []
for dist in working_set:
if dist.project_name in projects:
if isinstance(reqs, str):
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'):
entry_point = dist.get_entry_info('console_scripts', name)
entry_points.append(
(name, entry_point.module_name, '.'.join(entry_point.attrs))
)
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:
......@@ -388,26 +410,16 @@ def scripts(reqs, working_set, executable, dest,
sname = os.path.join(dest, sname)
generated.extend(
_script(dist, 'console_scripts', name, path, sname,
executable, arguments)
_script(module_name, attrs, path, sname, executable, arguments)
)
name = 'py-'+dist.project_name
if scripts is not None:
sname = scripts.get(name)
else:
sname = name
if sname is not None:
sname = os.path.join(dest, sname)
generated.extend(
_pyscript(path, sname, executable)
)
if interpreter:
sname = os.path.join(dest, interpreter)
generated.extend(_pyscript(path, sname, executable))
return generated
def _script(dist, group, name, path, dest, executable, arguments):
entry_point = dist.get_entry_info(group, name)
def _script(module_name, attrs, path, dest, executable, arguments):
generated = []
if sys.platform == 'win32':
# generate exe file and give the script a magic name:
......@@ -420,10 +432,8 @@ def _script(dist, group, name, path, dest, executable, arguments):
open(dest, 'w').write(script_template % dict(
python = executable,
path = path,
project = dist.project_name,
name = name,
module_name = entry_point.module_name,
attrs = '.'.join(entry_point.attrs),
module_name = module_name,
attrs = attrs,
arguments = arguments,
))
try:
......@@ -438,7 +448,7 @@ script_template = '''\
import sys
sys.path[0:0] = [
%(path)s
%(path)s,
]
import %(module_name)s
......@@ -474,7 +484,7 @@ py_script_template = '''\
import sys
sys.path[0:0] = [
%(path)s
%(path)s,
]
_interactive = True
......
......@@ -188,7 +188,7 @@ Now, we'll use the scripts method to generate scripts in this directory
from the demo egg:
>>> 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:
......@@ -202,25 +202,26 @@ the four arguments we passed were:
3. The destination directory.
The bin directory now contains 2 generated scripts:
The bin directory now contains a generated script:
>>> ls(bin)
- demo
- py-demo
The return value is a list of the scripts generated:
>>> import os, sys
>>> if sys.platform == 'win32':
... scripts == [os.path.join(bin, 'demo.exe'),
... os.path.join(bin, 'demo-script.py'),
... os.path.join(bin, 'py-demo.exe'),
... os.path.join(bin, 'py-demo-script.py')]
... os.path.join(bin, 'demo-script.py')]
... else:
... scripts == [os.path.join(bin, 'demo'),
... os.path.join(bin, 'py-demo')]
... scripts == [os.path.join(bin, 'demo')]
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:
>>> cat(bin, 'demo') # doctest: +NORMALIZE_WHITESPACE
......@@ -229,7 +230,7 @@ The demo script run the entry point defined in the demo egg:
import sys
sys.path[0:0] = [
'/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>
import eggrecipedemo
......@@ -244,16 +245,73 @@ Some things to note:
- The module for the script entry point is imported and the entry
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:
>>> cat(bin, 'py-demo') # doctest: +NORMALIZE_WHITESPACE
>>> cat(bin, 'py') # doctest: +NORMALIZE_WHITESPACE
#!/usr/local/bin/python2.4
import sys
<BLANKLINE>
sys.path[0:0] = [
'/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>
_interactive = True
......@@ -278,12 +336,12 @@ the path set:
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
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.
>>> bin = mkdtemp()
>>> 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':
... scripts == [os.path.join(bin, 'run.exe'),
......@@ -304,7 +362,7 @@ We can pass a keyword argument, extra paths, to caue additional paths
to be included in the a generated script:
>>> 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'])
>>> cat(bin, 'run') # doctest: +NORMALIZE_WHITESPACE
......@@ -314,7 +372,7 @@ to be included in the a generated script:
sys.path[0:0] = [
'/tmp/xyzsample-install/demo-0.3-py2.3.egg',
'/tmp/xyzsample-install/demoneeded-1.1-py2.3.egg',
'/foo/bar'
'/foo/bar',
]
<BLANKLINE>
import eggrecipedemo
......@@ -330,7 +388,7 @@ entry point. The value passed source string to be placed between the
parentheses in the call:
>>> 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')
>>> cat(bin, 'run') # doctest: +NORMALIZE_WHITESPACE
......@@ -338,7 +396,7 @@ parentheses in the call:
import sys
sys.path[0:0] = [
'/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>
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