[Checkins] SVN: zc.buildout/trunk/src/zc/buildout/easy_install. Added support for extra paths in generated scripts.

Jim Fulton jim at zope.com
Tue Sep 5 18:55:56 EDT 2006


Log message for revision 69991:
  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.
  

Changed:
  U   zc.buildout/trunk/src/zc/buildout/easy_install.py
  U   zc.buildout/trunk/src/zc/buildout/easy_install.txt

-=-
Modified: zc.buildout/trunk/src/zc/buildout/easy_install.py
===================================================================
--- zc.buildout/trunk/src/zc/buildout/easy_install.py	2006-09-05 22:55:53 UTC (rev 69990)
+++ zc.buildout/trunk/src/zc/buildout/easy_install.py	2006-09-05 22:55:55 UTC (rev 69991)
@@ -24,10 +24,7 @@
 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')
 
@@ -70,6 +67,9 @@
         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 @@
         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 @@
 
     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,8 +230,10 @@
             # 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)
+            dist = env.best_match(requirement, ws)
+            logger.info("Got %s", dist)            
+        else:
+            dist = env.best_match(requirement, ws)
 
     if dist is None:
         raise ValueError("Couldn't find", requirement)
@@ -246,12 +250,12 @@
 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 @@
     env = pkg_resources.Environment(path, python=_get_version(executable))
     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:
         ws.add(_get_dist(requirement, env, ws,
@@ -368,46 +375,51 @@
             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'):
-                if scripts is not None:
-                    sname = scripts.get(name)
-                    if sname is None:
-                        continue
-                else:
-                    sname = name
-
-                sname = os.path.join(dest, sname)
-                generated.extend(
-                    _script(dist, 'console_scripts', name, path, sname,
-                            executable, arguments)
+                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:
+                continue
+        else:
+            sname = name
 
-            name = 'py-'+dist.project_name
-            if scripts is not None:
-                sname = scripts.get(name)
-            else:
-                sname = name
+        sname = os.path.join(dest, sname)
+        generated.extend(
+            _script(module_name, attrs, path, sname, executable, arguments)
+            )
 
-            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 @@
     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 @@
 
 import sys
 sys.path[0:0] = [
-  %(path)s
+  %(path)s,
   ]
 
 import %(module_name)s
@@ -474,7 +484,7 @@
 import sys
     
 sys.path[0:0] = [
-  %(path)s
+  %(path)s,
   ]
 
 _interactive = True

Modified: zc.buildout/trunk/src/zc/buildout/easy_install.txt
===================================================================
--- zc.buildout/trunk/src/zc/buildout/easy_install.txt	2006-09-05 22:55:53 UTC (rev 69990)
+++ zc.buildout/trunk/src/zc/buildout/easy_install.txt	2006-09-05 22:55:55 UTC (rev 69991)
@@ -188,7 +188,7 @@
 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 @@
 
 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 @@
     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 @@
 - 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 @@
 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 @@
 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 @@
     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 @@
 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 @@
     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



More information about the Checkins mailing list