[Checkins] SVN: zc.buildout/branches/use_distribute/ merge changes from r102547 to HEAD of trunk.

Chris Withers chris at simplistix.co.uk
Tue Sep 8 06:52:14 EDT 2009


Log message for revision 103642:
  merge changes from r102547 to HEAD of trunk.

Changed:
  U   zc.buildout/branches/use_distribute/CHANGES.txt
  A   zc.buildout/branches/use_distribute/DEVELOPERS.txt
  U   zc.buildout/branches/use_distribute/README.txt
  U   zc.buildout/branches/use_distribute/setup.py
  U   zc.buildout/branches/use_distribute/src/zc/buildout/buildout.py
  U   zc.buildout/branches/use_distribute/src/zc/buildout/buildout.txt
  U   zc.buildout/branches/use_distribute/src/zc/buildout/download.py
  U   zc.buildout/branches/use_distribute/src/zc/buildout/download.txt
  U   zc.buildout/branches/use_distribute/src/zc/buildout/easy_install.py
  U   zc.buildout/branches/use_distribute/src/zc/buildout/easy_install.txt
  U   zc.buildout/branches/use_distribute/src/zc/buildout/extends-cache.txt
  U   zc.buildout/branches/use_distribute/src/zc/buildout/testing.py
  A   zc.buildout/branches/use_distribute/src/zc/buildout/testrecipes.py
  U   zc.buildout/branches/use_distribute/src/zc/buildout/tests.py
  U   zc.buildout/branches/use_distribute/zc.recipe.egg_/src/zc/recipe/egg/README.txt
  U   zc.buildout/branches/use_distribute/zc.recipe.egg_/src/zc/recipe/egg/selecting-python.txt

-=-
Modified: zc.buildout/branches/use_distribute/CHANGES.txt
===================================================================
--- zc.buildout/branches/use_distribute/CHANGES.txt	2009-09-08 10:46:18 UTC (rev 103641)
+++ zc.buildout/branches/use_distribute/CHANGES.txt	2009-09-08 10:52:14 UTC (rev 103642)
@@ -1,9 +1,49 @@
 Change History
 **************
 
-1.4.0 (unreleased)
+1.?.? (2008-0?-??)
 ==================
 
+Bugs fixed:
+
+- Incrementing didn't work properly when extending multiple files.
+  https://bugs.launchpad.net/zc.buildout/+bug/421022
+
+1.4.1 (2009-08-27)
+==================
+
+New Feature:
+
+- Added a debug built-in recipe to make writing some tests easier.
+
+Bugs fixed:
+
+- (introduced in 1.4.0) option incrementing (-=) and decrementing (-=)
+  didn't work in the buildout section.
+  https://bugs.launchpad.net/zc.buildout/+bug/420463
+
+- Option incrementing and decrementing didn't work for options
+  specified on the command line.
+
+- Scripts generated with relative-paths enabled couldn't be
+  symbolically linked to other locations and still work.
+
+- Scripts run using generated interpreters didn't have __file__ set correctly.
+
+- The standard Python -m option didn't work for custom interpreters.
+
+1.4.0 (2009-08-26)
+==================
+
+- When doing variable substitutions, you can omit the section name to
+  refer to a variable in the same section (e.g. ${:foo}).
+
+- When doing variable substitution, you can use the special option,
+  ``_buildout_section_name_`` to get the section name.  This is most handy
+  for getting the current section name (e.g. ${:_buildout_section_name_}).
+
+- A new special option, ``<`` allows sections to be used as macros.
+
 - Added annotate command for annotated sections. Displays sections
   key-value pairs along with the value origin.
 
@@ -13,6 +53,11 @@
 - Used the download API to allow caching of base configurations (specified by
   the buildout section's 'extends' option).
 
+1.3.1 (2009-08-12)
+==================
+
+- Bug fixed: extras were ignored in some cases when versions were specified.
+
 1.3.0 (2009-06-22)
 ==================
 

Copied: zc.buildout/branches/use_distribute/DEVELOPERS.txt (from rev 103641, zc.buildout/trunk/DEVELOPERS.txt)
===================================================================
--- zc.buildout/branches/use_distribute/DEVELOPERS.txt	                        (rev 0)
+++ zc.buildout/branches/use_distribute/DEVELOPERS.txt	2009-09-08 10:52:14 UTC (rev 103642)
@@ -0,0 +1,11 @@
+Developing buildout itself
+**************************
+
+When you're developing buildout itself, you need to know two things:
+
+- Use a clean python *without* setuptools installed.  Otherwise many tests
+  will find your already-installed setuptools, leading to test differences
+  when setuptools' presence is explicitly tested.
+
+- Don't bootstrap with ``python bootstrap/bootstrap.py`` but with ``python
+  dev.py``.

Modified: zc.buildout/branches/use_distribute/README.txt
===================================================================
--- zc.buildout/branches/use_distribute/README.txt	2009-09-08 10:46:18 UTC (rev 103641)
+++ zc.buildout/branches/use_distribute/README.txt	2009-09-08 10:52:14 UTC (rev 103642)
@@ -123,7 +123,7 @@
 This is probably the most common type of buildout.
 
 If I need to run a previous version of zc.buildout, I use the
-`--version` option of the buildout.py script::
+`--version` option of the bootstrap.py script::
 
     $ python bootstrap.py --version 1.1.3
     

Modified: zc.buildout/branches/use_distribute/setup.py
===================================================================
--- zc.buildout/branches/use_distribute/setup.py	2009-09-08 10:46:18 UTC (rev 103641)
+++ zc.buildout/branches/use_distribute/setup.py	2009-09-08 10:52:14 UTC (rev 103642)
@@ -11,11 +11,11 @@
 # FOR A PARTICULAR PURPOSE.
 #
 ##############################################################################
-
+name = "zc.buildout"
 version = "1.4.0-distribute-dev"
 
 import os
-from setuptools import setup, find_packages
+from setuptools import setup
 
 def read(*rnames):
     return open(os.path.join(os.path.dirname(__file__), *rnames)).read()
@@ -54,9 +54,15 @@
         '**********************\n'
         )
 
-open('doc.txt', 'w').write(long_description)
+entry_points = """
+[console_scripts]
+buildout = %(name)s.buildout:main
 
-name = "zc.buildout"
+[zc.buildout]
+debug = %(name)s.testrecipes:Debug
+
+""" % dict(name=name)
+
 setup(
     name = name,
     version = version,
@@ -66,7 +72,7 @@
     long_description=long_description,
     license = "ZPL 2.1",
     keywords = "development build",
-    url='http://www.python.org/pypi/zc.buildout',
+    url='http://pypi.python.org/pypi/zc.buildout',
 
     data_files = [('.', ['README.txt'])],
     packages = ['zc', 'zc.buildout'],
@@ -74,8 +80,7 @@
     namespace_packages = ['zc'],
     install_requires = 'distribute',
     include_package_data = True,
-    entry_points = {'console_scripts':
-                    ['buildout = %s.buildout:main' % name]}, 
+    entry_points = entry_points,
     zip_safe=False,
     classifiers = [
        'Intended Audience :: Developers',

Modified: zc.buildout/branches/use_distribute/src/zc/buildout/buildout.py
===================================================================
--- zc.buildout/branches/use_distribute/src/zc/buildout/buildout.py	2009-09-08 10:46:18 UTC (rev 103641)
+++ zc.buildout/branches/use_distribute/src/zc/buildout/buildout.py	2009-09-08 10:52:14 UTC (rev 103642)
@@ -12,38 +12,33 @@
 #
 ##############################################################################
 """Buildout main script
-
-$Id$
 """
 
+from rmtree import rmtree
+try:
+    from hashlib import md5
+except ImportError:
+    # Python 2.4 and older
+    from md5 import md5
+
+import ConfigParser
+import copy
 import distutils.errors
+import glob
+import itertools
 import logging
 import os
-import pprint
+import pkg_resources
 import re
 import shutil
-import cStringIO
 import sys
 import tempfile
-import ConfigParser
 import UserDict
-import glob
-import copy
-
-
-import pkg_resources
 import zc.buildout
 import zc.buildout.download
 import zc.buildout.easy_install
 
-from rmtree import rmtree
 
-try:
-    from hashlib import md5
-except ImportError:
-    # Python 2.4 and older
-    from md5 import md5
-
 realpath = zc.buildout.easy_install.realpath
 
 pkg_resources_loc = pkg_resources.working_set.find(
@@ -164,10 +159,15 @@
         else:
             base = None
 
-        override = dict((option, (value, 'COMMAND_LINE_VALUE'))
-                        for section, option, value in cloptions
-                        if section == 'buildout')
 
+        cloptions = dict(
+            (section, dict((option, (value, 'COMMAND_LINE_VALUE'))
+                           for (_, option, value) in v))
+            for (section, v) in itertools.groupby(sorted(cloptions),
+                                                  lambda v: v[0])
+            )
+        override = cloptions.get('buildout', {}).copy()
+
         # load user defaults, which override defaults
         if user_defaults:
             user_config = os.path.join(os.path.expanduser('~'),
@@ -182,11 +182,7 @@
                                 data['buildout'].copy(), override))
 
         # apply command-line options
-        for (section, option, value) in cloptions:
-            options = data.get(section)
-            if options is None:
-                options = data[section] = {}
-            options[option] = value, "COMMAND_LINE_VALUE"
+        _update(data, cloptions)
 
         self._annotated = copy.deepcopy(data)
         self._raw = _unannotate(data)
@@ -929,10 +925,13 @@
                 arg_list = list()
 
                 for a in args:
-                    add_args.append(zc.buildout.easy_install._safe_arg(a))
+                    arg_list.append(zc.buildout.easy_install._safe_arg(a))
 
-                subprocess.Popen([zc.buildout.easy_install._safe_arg(sys.executable)] + list(tsetup) +
-                                arg_list).wait()
+                subprocess.Popen(
+                    [zc.buildout.easy_install._safe_arg(sys.executable)]
+                    + list(tsetup)
+                    + arg_list
+                    ).wait()
 
             else:
                 os.spawnl(os.P_WAIT, sys.executable, zc.buildout.easy_install._safe_arg (sys.executable), tsetup,
@@ -1028,6 +1027,9 @@
         name = self.name
         __doing__ = 'Initializing section %s.', name
 
+        if '<' in self._raw:
+            self._raw = self._do_extend_raw(name, self._raw, [])
+
         # force substitutions
         for k, v in self._raw.items():
             if '${' in v:
@@ -1048,6 +1050,33 @@
         self.recipe = recipe_class(buildout, name, self)
         buildout._parts.append(name)
 
+    def _do_extend_raw(self, name, data, doing):
+        if name == 'buildout':
+            return data
+        if name in doing:
+            raise zc.buildout.UserError("Infinite extending loop %r" % name)
+        doing.append(name)
+        try:
+            to_do = data.pop('<', None)
+            if to_do is None:
+                return data
+            __doing__ = 'Loading input sections for %r', name
+
+            result = {}
+            for iname in to_do.split('\n'):
+                iname = iname.strip()
+                if not iname:
+                    continue
+                raw = self.buildout._raw.get(iname)
+                if raw is None:
+                    raise zc.buildout.UserError("No section named %r" % iname)
+                result.update(self._do_extend_raw(iname, raw, doing))
+
+            result.update(data)
+            return result
+        finally:
+            assert doing.pop() == name
+
     def _dosub(self, option, v):
         __doing__ = 'Getting option %s:%s.', self.name, option
         seen = [(self.name, option)]
@@ -1086,7 +1115,7 @@
 
     _template_split = re.compile('([$]{[^}]*})').split
     _simple = re.compile('[-a-zA-Z0-9 ._]+$').match
-    _valid = re.compile('\${[-a-zA-Z0-9 ._]+:[-a-zA-Z0-9 ._]+}$').match
+    _valid = re.compile('\${[-a-zA-Z0-9 ._]*:[-a-zA-Z0-9 ._]+}$').match
     def _sub(self, template, seen):
         value = self._template_split(template)
         subs = []
@@ -1112,9 +1141,16 @@
                         "has invalid characters."
                         % ref)
 
-            v = self.buildout[s[0]].get(s[1], None, seen)
+            section, option = s
+            if not section:
+                section = self.name
+            v = self.buildout[section].get(option, None, seen)
             if v is None:
-                raise MissingOption("Referenced option does not exist:", *s)
+                if option == '_buildout_section_name_':
+                    v = self.name
+                else:
+                    raise MissingOption("Referenced option does not exist:",
+                                        section, option)
             subs.append(v)
         subs.append('')
 
@@ -1172,7 +1208,7 @@
                     elif os.path.isfile(p):
                         os.remove(p)
                     else:
-                        self._buildout._logger.warn("Couldn't clean up %r.", p)
+                        self.buildout._logger.warn("Couldn't clean up %r.", p)
                 raise
         finally:
             self._created = None
@@ -1238,11 +1274,13 @@
     """
     _update_section(dl_options, override)
     _dl_options = _unannotate_section(dl_options.copy())
+    is_temp = False
     download = zc.buildout.download.Download(
         _dl_options, cache=_dl_options.get('extends-cache'), fallback=True,
         hash_name=True)
     if _isurl(filename):
-        fp = open(download(filename))
+        path, is_temp = download(filename)
+        fp = open(path)
         base = filename[:filename.rfind('/')]
     elif _isurl(base):
         if os.path.isabs(filename):
@@ -1250,7 +1288,8 @@
             base = os.path.dirname(filename)
         else:
             filename = base + '/' + filename
-            fp = open(download(filename))
+            path, is_temp = download(filename)
+            fp = open(path)
             base = filename[:filename.rfind('/')]
     else:
         filename = os.path.join(base, filename)
@@ -1258,6 +1297,9 @@
         base = os.path.dirname(filename)
 
     if filename in seen:
+        if is_temp:
+            fp.close()
+            os.remove(path)
         raise zc.buildout.UserError("Recursive file include", seen, filename)
 
     root_config_file = not seen
@@ -1268,6 +1310,10 @@
     parser = ConfigParser.RawConfigParser()
     parser.optionxform = lambda s: s
     parser.readfp(fp)
+    if is_temp:
+        fp.close()
+        os.remove(path)
+
     extends = extended_by = None
     for section in parser.sections():
         options = dict(parser.items(section))
@@ -1283,10 +1329,10 @@
 
     if extends:
         extends = extends.split()
-        extends.reverse()
+        eresult = _open(base, extends.pop(0), seen, dl_options, override)
         for fname in extends:
-            result = _update(_open(base, fname, seen, dl_options, override),
-                             result)
+            _update(eresult, _open(base, fname, seen, dl_options, override))
+        result = _update(eresult, result)
 
     if extended_by:
         self._logger.warn(
@@ -1326,6 +1372,7 @@
     return result
 
 def _update_section(s1, s2):
+    s2 = s2.copy() # avoid mutating the second argument, which is unexpected
     for k, v in s2.items():
         v2, note2 = v
         if k.endswith('+'):

Modified: zc.buildout/branches/use_distribute/src/zc/buildout/buildout.txt
===================================================================
--- zc.buildout/branches/use_distribute/src/zc/buildout/buildout.txt	2009-09-08 10:46:18 UTC (rev 103641)
+++ zc.buildout/branches/use_distribute/src/zc/buildout/buildout.txt	2009-09-08 10:52:14 UTC (rev 103642)
@@ -866,7 +866,38 @@
 contain alphanumeric characters, hyphens, periods and spaces. This
 restriction might be relaxed in future releases.
 
+We can ommit the section name in a variable substitution to refer to
+the current section.  We can also use the special option,
+_buildout_section_name_ to get the current section name.
 
+    >>> write(sample_buildout, 'buildout.cfg',
+    ... """
+    ... [buildout]
+    ... develop = recipes
+    ... parts = data-dir debug
+    ... log-level = INFO
+    ...
+    ... [debug]
+    ... recipe = recipes:debug
+    ... File 1 = ${data-dir:path}/file
+    ... File 2 = ${:File 1}/log
+    ... my_name = ${:_buildout_section_name_}
+    ...
+    ... [data-dir]
+    ... recipe = recipes:mkdir
+    ... path = mydata
+    ... """)
+
+    >>> print system(buildout),
+    Develop: '/sample-buildout/recipes'
+    Uninstalling debug.
+    Updating data-dir.
+    Installing debug.
+    File 1 /sample-buildout/mydata/file
+    File 2 /sample-buildout/mydata/file/log
+    my_name debug
+    recipe recipes:debug
+
 Automatic part selection and ordering
 -------------------------------------
 
@@ -897,8 +928,9 @@
 
     >>> print system(buildout),
     Develop: '/sample-buildout/recipes'
+    Uninstalling debug.
     Updating data-dir.
-    Updating debug.
+    Installing debug.
     File 1 /sample-buildout/mydata/file
     File 2 /sample-buildout/mydata/file/log
     recipe recipes:debug
@@ -947,7 +979,56 @@
     parts = data-dir debug
     ...
 
+Extending sections (macros)
+---------------------------
 
+A section (other than the buildout section) can extend one or more
+other sections using the ``<`` option.  Options from the referenced
+sections are copied to the refering section *before* variable
+substitution.  This, together with the ability to refer to variables
+of the current section allows sections to be used as macros.
+
+    >>> write(sample_buildout, 'buildout.cfg',
+    ... """
+    ... [buildout]
+    ... develop = recipes
+    ... parts = myfiles
+    ... log-level = INFO
+    ...
+    ... [debug]
+    ... recipe = recipes:debug
+    ...
+    ... [with_file1]
+    ... <= debug
+    ... file1 = ${:path}/file1
+    ... color = red
+    ...
+    ... [with_file2]
+    ... <= debug
+    ... file2 = ${:path}/file2
+    ... color = blue
+    ...
+    ... [myfiles]
+    ... <= with_file1
+    ...    with_file2
+    ... path = mydata
+    ... """)
+
+    >>> print system(buildout),
+    Develop: '/sample-buildout/recipes'
+    Uninstalling debug.
+    Uninstalling data-dir.
+    Installing myfiles.
+    color blue
+    file1 mydata/file1
+    file2 mydata/file2
+    path mydata
+    recipe recipes:debug
+
+In this example, the debug, with_file1 and with_file2 sections act as
+macros. In particular, the variable substitutions are performed
+relative to the myfiles section.
+
 Adding and removing options
 ---------------------------
 
@@ -1051,11 +1132,10 @@
     >>> os.chdir(sample_buildout)
     >>> print system(os.path.join(sample_buildout, 'bin', 'buildout')),
     Develop: '/sample-buildout/demo'
-    Uninstalling debug.
+    Uninstalling myfiles.
     Getting distribution for 'recipes'.
     zip_safe flag not set; analyzing archive contents...
     Got recipes 0.0.0.
-    Uninstalling data-dir.
     warning: install_lib: 'build/lib' does not exist -- no Python modules to install
 
 Verify option values.
@@ -1075,7 +1155,8 @@
 Annotated sections output shows which files are responsible for which
 operations.
 
-    >>> print system(os.path.join('bin', 'buildout') + ' annotate'), # doctest: +ELLIPSIS
+    >>> print system(os.path.join('bin', 'buildout') + ' annotate'),
+    ... # doctest: +ELLIPSIS
     <BLANKLINE>
     Annotated sections
     ==================

Modified: zc.buildout/branches/use_distribute/src/zc/buildout/download.py
===================================================================
--- zc.buildout/branches/use_distribute/src/zc/buildout/download.py	2009-09-08 10:46:18 UTC (rev 103641)
+++ zc.buildout/branches/use_distribute/src/zc/buildout/download.py	2009-09-08 10:52:14 UTC (rev 103642)
@@ -21,6 +21,7 @@
 import logging
 import os
 import os.path
+import re
 import shutil
 import tempfile
 import urllib
@@ -56,6 +57,7 @@
 
     def __init__(self, options={}, cache=-1, namespace=None,
                  offline=-1, fallback=False, hash_name=False, logger=None):
+        self.directory = options.get('directory', '')
         self.cache = cache
         if cache == -1:
             self.cache = options.get('download-cache')
@@ -69,10 +71,15 @@
         self.logger = logger or logging.getLogger('zc.buildout')
 
     @property
-    def cache_dir(self):
+    def download_cache(self):
         if self.cache is not None:
-            return os.path.join(realpath(self.cache), self.namespace or '')
+            return realpath(os.path.join(self.directory, self.cache))
 
+    @property
+    def cache_dir(self):
+        if self.download_cache is not None:
+            return os.path.join(self.download_cache, self.namespace or '')
+
     def __call__(self, url, md5sum=None, path=None):
         """Download a file according to the utility's configuration.
 
@@ -84,11 +91,11 @@
 
         """
         if self.cache:
-            local_path = self.download_cached(url, md5sum)
+            local_path, is_temp = self.download_cached(url, md5sum)
         else:
-            local_path = self.download(url, md5sum, path)
+            local_path, is_temp = self.download(url, md5sum, path)
 
-        return locate_at(local_path, path)
+        return locate_at(local_path, path), is_temp
 
     def download_cached(self, url, md5sum=None):
         """Download a file from a URL using the cache.
@@ -98,17 +105,24 @@
         but will not remove the copy in that case.
 
         """
+        if not os.path.exists(self.download_cache):
+            raise zc.buildout.UserError(
+                'The directory:\n'
+                '%r\n'
+                "to be used as a download cache doesn't exist.\n"
+                % self.download_cache)
         cache_dir = self.cache_dir
         if not os.path.exists(cache_dir):
-            os.makedirs(cache_dir)
+            os.mkdir(cache_dir)
         cache_key = self.filename(url)
         cached_path = os.path.join(cache_dir, cache_key)
 
         self.logger.debug('Searching cache at %s' % cache_dir)
         if os.path.isfile(cached_path):
+            is_temp = False
             if self.fallback:
                 try:
-                    self.download(url, md5sum, cached_path)
+                    _, is_temp = self.download(url, md5sum, cached_path)
                 except ChecksumError:
                     raise
                 except Exception:
@@ -122,9 +136,9 @@
         else:
             self.logger.debug('Cache miss; will cache %s as %s' %
                               (url, cached_path))
-            self.download(url, md5sum, cached_path)
+            _, is_temp = self.download(url, md5sum, cached_path)
 
-        return cached_path
+        return cached_path, is_temp
 
     def download(self, url, md5sum=None, path=None):
         """Download a file from a URL to a given or temporary path.
@@ -135,6 +149,8 @@
         returned and the client code is responsible for cleaning it up.
 
         """
+        if re.match(r"^[A-Za-z]:\\", url):
+            url = 'file:' + url
         parsed_url = urlparse.urlparse(url, 'file')
         url_scheme, _, url_path = parsed_url[:3]
         if url_scheme == 'file':
@@ -143,7 +159,7 @@
                 raise ChecksumError(
                     'MD5 checksum mismatch for local resource at %r.' %
                     url_path)
-            return locate_at(url_path, path)
+            return locate_at(url_path, path), False
 
         if self.offline:
             raise zc.buildout.UserError(
@@ -152,18 +168,23 @@
         self.logger.info('Downloading %s' % url)
         urllib._urlopener = url_opener
         handle, tmp_path = tempfile.mkstemp(prefix='buildout-')
-        tmp_path, headers = urllib.urlretrieve(url, tmp_path)
-        os.close(handle)
-        if not check_md5sum(tmp_path, md5sum):
+        try:
+            try:
+                tmp_path, headers = urllib.urlretrieve(url, tmp_path)
+                if not check_md5sum(tmp_path, md5sum):
+                    raise ChecksumError(
+                        'MD5 checksum mismatch downloading %r' % url)
+            finally:
+                os.close(handle)
+        except:
             os.remove(tmp_path)
-            raise ChecksumError(
-                'MD5 checksum mismatch downloading %r' % url)
+            raise
 
         if path:
             shutil.move(tmp_path, path)
-            return path
+            return path, False
         else:
-            return tmp_path
+            return tmp_path, True
 
     def filename(self, url):
         """Determine a file name from a URL according to the configuration.
@@ -172,16 +193,27 @@
         if self.hash_name:
             return md5(url).hexdigest()
         else:
-            parsed = urlparse.urlparse(url)
+            if re.match(r"^[A-Za-z]:\\", url):
+                url = 'file:' + url
+            parsed = urlparse.urlparse(url, 'file')
             url_path = parsed[2]
-            for name in reversed(url_path.split('/')):
-                if name:
-                    return name
+
+            if parsed[0] == 'file':
+                while True:
+                    url_path, name = os.path.split(url_path)
+                    if name:
+                        return name
+                    if not url_path:
+                        break
             else:
-                url_host, url_port = parsed[-2:]
-                return '%s:%s' % (url_host, url_port)
+                for name in reversed(url_path.split('/')):
+                    if name:
+                        return name
 
+            url_host, url_port = parsed[-2:]
+            return '%s:%s' % (url_host, url_port)
 
+
 def check_md5sum(path, md5sum):
     """Tell whether the MD5 checksum of the file at path matches.
 

Modified: zc.buildout/branches/use_distribute/src/zc/buildout/download.txt
===================================================================
--- zc.buildout/branches/use_distribute/src/zc/buildout/download.txt	2009-09-08 10:46:18 UTC (rev 103641)
+++ zc.buildout/branches/use_distribute/src/zc/buildout/download.txt	2009-09-08 10:52:14 UTC (rev 103642)
@@ -12,7 +12,14 @@
 >>> write(server_data, 'foo.txt', 'This is a foo text.')
 >>> server_url = start_server(server_data)
 
+We also use a fresh directory for temporary files in order to make sure that
+all temporary files have been cleaned up in the end:
 
+>>> import tempfile
+>>> old_tempdir = tempfile.tempdir
+>>> tempfile.tempdir = tmpdir('tmp')
+
+
 Downloading without using the cache
 -----------------------------------
 
@@ -25,9 +32,11 @@
 None
 
 Downloading a file is achieved by calling the utility with the URL as an
-argument:
+argument. A tuple is returned that consists of the path to the downloaded copy
+of the file and a boolean value indicating whether this is a temporary file
+meant to be cleaned up during the same buildout run:
 
->>> path = download(server_url+'foo.txt')
+>>> path, is_temp = download(server_url+'foo.txt')
 >>> print path
 /.../buildout-...
 >>> cat(path)
@@ -36,50 +45,69 @@
 As we aren't using the download cache and haven't specified a target path
 either, the download has ended up in a temporary file:
 
+>>> is_temp
+True
+
 >>> import tempfile
 >>> path.startswith(tempfile.gettempdir())
 True
 
+We are responsible for cleaning up temporary files behind us:
+
+>>> remove(path)
+
 When trying to access a file that doesn't exist, we'll get an exception:
 
->>> download(server_url+'not-there')
-Traceback (most recent call last):
-IOError: ('http error', 404, 'Not Found',
-          <httplib.HTTPMessage instance at 0xa0ffd2c>)
+>>> try: download(server_url+'not-there') # doctest: +ELLIPSIS
+... except: print 'download error'
+... else: print 'woops'
+download error
 
+Downloading a local file doesn't produce a temporary file but simply returns
+the local file itself:
+
+>>> download(join(server_data, 'foo.txt'))
+('/sample_files/foo.txt', False)
+
 We can also have the downloaded file's MD5 sum checked:
 
 >>> try: from hashlib import md5
 ... except ImportError: from md5 import new as md5
 
->>> path = download(server_url+'foo.txt',
-...                 md5('This is a foo text.').hexdigest())
+>>> path, is_temp = download(server_url+'foo.txt',
+...                          md5('This is a foo text.').hexdigest())
+>>> is_temp
+True
+>>> remove(path)
 
->>> path = download(server_url+'foo.txt',
-...                 md5('The wrong text.').hexdigest())
+>>> download(server_url+'foo.txt',
+...          md5('The wrong text.').hexdigest())
 Traceback (most recent call last):
 ChecksumError: MD5 checksum mismatch downloading 'http://localhost/foo.txt'
 
 The error message in the event of an MD5 checksum mismatch for a local file
 reads somewhat differently:
 
->>> path = download(join(server_data, 'foo.txt'),
-...                 md5('This is a foo text.').hexdigest())
+>>> download(join(server_data, 'foo.txt'),
+...               md5('This is a foo text.').hexdigest())
+('/sample_files/foo.txt', False)
 
->>> path = download(join(server_data, 'foo.txt'),
-...                 md5('The wrong text.').hexdigest())
+>>> download(join(server_data, 'foo.txt'),
+...          md5('The wrong text.').hexdigest())
 Traceback (most recent call last):
 ChecksumError: MD5 checksum mismatch for local resource at '/sample_files/foo.txt'.
 
 Finally, we can download the file to a specified place in the file system:
 
 >>> target_dir = tmpdir('download-target')
->>> path = download(server_url+'foo.txt',
-...                 path=join(target_dir, 'downloaded.txt'))
+>>> path, is_temp = download(server_url+'foo.txt',
+...                          path=join(target_dir, 'downloaded.txt'))
 >>> print path
 /download-target/downloaded.txt
 >>> cat(path)
 This is a foo text.
+>>> is_temp
+False
 
 Trying to download a file in offline mode will result in an error:
 
@@ -91,13 +119,14 @@
 As an exception to this rule, file system paths and URLs in the ``file``
 scheme will still work:
 
->>> cat(download(join(server_data, 'foo.txt')))
+>>> cat(download(join(server_data, 'foo.txt'))[0])
 This is a foo text.
->>> cat(download('file://%s/foo.txt' % server_data))
+>>> cat(download('file:' + join(server_data, 'foo.txt'))[0])
 This is a foo text.
 
 >>> remove(path)
 
+
 Downloading using the download cache
 ------------------------------------
 
@@ -118,17 +147,19 @@
 to the cached copy:
 
 >>> ls(cache)
->>> path = download(server_url+'foo.txt')
+>>> path, is_temp = download(server_url+'foo.txt')
 >>> print path
 /download-cache/foo.txt
 >>> cat(path)
 This is a foo text.
+>>> is_temp
+False
 
 Whenever the file is downloaded again, the cached copy is used. Let's change
 the file on the server to see this:
 
 >>> write(server_data, 'foo.txt', 'The wrong text.')
->>> path = download(server_url+'foo.txt')
+>>> path, is_temp = download(server_url+'foo.txt')
 >>> print path
 /download-cache/foo.txt
 >>> cat(path)
@@ -137,7 +168,7 @@
 If we specify an MD5 checksum for a file that is already in the cache, the
 cached copy's checksum will be verified:
 
->>> path = download(server_url+'foo.txt', md5('The wrong text.').hexdigest())
+>>> download(server_url+'foo.txt', md5('The wrong text.').hexdigest())
 Traceback (most recent call last):
 ChecksumError: MD5 checksum mismatch for cached download
                from 'http://localhost/foo.txt' at '/download-cache/foo.txt'
@@ -147,7 +178,7 @@
 
 >>> mkdir(server_data, 'other')
 >>> write(server_data, 'other', 'foo.txt', 'The wrong text.')
->>> path = download(server_url+'other/foo.txt')
+>>> path, is_temp = download(server_url+'other/foo.txt')
 >>> print path
 /download-cache/foo.txt
 >>> cat(path)
@@ -161,30 +192,34 @@
 >>> ls(cache)
 >>> write(server_data, 'foo.txt', 'This is a foo text.')
 
->>> path = download(server_url+'foo.txt',
-...                 path=join(target_dir, 'downloaded.txt'))
+>>> path, is_temp = download(server_url+'foo.txt',
+...                          path=join(target_dir, 'downloaded.txt'))
 >>> print path
 /download-target/downloaded.txt
 >>> cat(path)
 This is a foo text.
+>>> is_temp
+False
 >>> ls(cache)
 - foo.txt
 
 >>> remove(path)
 >>> write(server_data, 'foo.txt', 'The wrong text.')
 
->>> path = download(server_url+'foo.txt',
-...                 path=join(target_dir, 'downloaded.txt'))
+>>> path, is_temp = download(server_url+'foo.txt',
+...                          path=join(target_dir, 'downloaded.txt'))
 >>> print path
 /download-target/downloaded.txt
 >>> cat(path)
 This is a foo text.
+>>> is_temp
+False
 
 In offline mode, downloads from any URL will be successful if the file is
 found in the cache:
 
 >>> download = Download(cache=cache, offline=True)
->>> cat(download(server_url+'foo.txt'))
+>>> cat(download(server_url+'foo.txt')[0])
 This is a foo text.
 
 Local resources will be cached just like any others since download caches are
@@ -196,14 +231,14 @@
 >>> write(server_data, 'foo.txt', 'This is a foo text.')
 >>> download = Download(cache=cache)
 
->>> cat(download('file://' + join(server_data, 'foo.txt'), path=path))
+>>> cat(download('file:' + join(server_data, 'foo.txt'), path=path)[0])
 This is a foo text.
 >>> ls(cache)
 - foo.txt
 
 >>> remove(cache, 'foo.txt')
 
->>> cat(download(join(server_data, 'foo.txt'), path=path))
+>>> cat(download(join(server_data, 'foo.txt'), path=path)[0])
 This is a foo text.
 >>> ls(cache)
 - foo.txt
@@ -219,6 +254,15 @@
 
 >>> remove(path)
 
+Finally, let's see what happens if the download cache to be used doesn't exist
+as a directory in the file system yet:
+
+>>> Download(cache=join(cache, 'non-existent'))(server_url+'foo.txt')
+Traceback (most recent call last):
+UserError: The directory:
+'/download-cache/non-existent'
+to be used as a download cache doesn't exist.
+
 Using namespace sub-directories of the download cache
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -240,7 +284,7 @@
 Downloading a file now creates the namespace sub-directory and places a copy
 of the file inside it:
 
->>> path = download(server_url+'foo.txt')
+>>> path, is_temp = download(server_url+'foo.txt')
 >>> print path
 /download-cache/test/foo.txt
 >>> ls(cache)
@@ -249,6 +293,8 @@
 - foo.txt
 >>> cat(path)
 This is a foo text.
+>>> is_temp
+False
 
 The next time we want to download that file, the copy from inside the cache
 namespace is used. To see this clearly, we put a file with the same name but
@@ -257,7 +303,7 @@
 >>> write(server_data, 'foo.txt', 'The wrong text.')
 >>> write(cache, 'foo.txt', 'The wrong text.')
 
->>> path = download(server_url+'foo.txt')
+>>> path, is_temp = download(server_url+'foo.txt')
 >>> print path
 /download-cache/test/foo.txt
 >>> cat(path)
@@ -278,7 +324,7 @@
 be used as the filename in the cache:
 
 >>> download = Download(cache=cache, hash_name=True)
->>> path = download(server_url+'foo.txt')
+>>> path, is_temp = download(server_url+'foo.txt')
 >>> print path
 /download-cache/09f5793fcdc1716727f72d49519c688d
 >>> cat(path)
@@ -291,13 +337,13 @@
 the test is run, so we don't actually know the full URL of the file. Let's
 check that the checksum actually belongs to the particular URL used:
 
->>> path == join(cache, md5(server_url+'foo.txt').hexdigest())
+>>> path.lower() == join(cache, md5(server_url+'foo.txt').hexdigest()).lower()
 True
 
 The cached copy is used when downloading the file again:
 
 >>> write(server_data, 'foo.txt', 'The wrong text.')
->>> path == download(server_url+'foo.txt')
+>>> (path, is_temp) == download(server_url+'foo.txt')
 True
 >>> cat(path)
 This is a foo text.
@@ -308,12 +354,12 @@
 file the same, the file will be downloaded again this time and put in the
 cache under a different name:
 
->>> path2 = download(server_url+'other/foo.txt')
+>>> path2, is_temp = download(server_url+'other/foo.txt')
 >>> print path2
 /download-cache/537b6d73267f8f4447586989af8c470e
 >>> path == path2
 False
->>> path2 == join(cache, md5(server_url+'other/foo.txt').hexdigest())
+>>> path2.lower() == join(cache, md5(server_url+'other/foo.txt').hexdigest()).lower()
 True
 >>> cat(path)
 This is a foo text.
@@ -343,40 +389,48 @@
 A downloaded file will be cached:
 
 >>> ls(cache)
->>> path = download(server_url+'foo.txt')
+>>> path, is_temp = download(server_url+'foo.txt')
 >>> ls(cache)
 - foo.txt
 >>> cat(cache, 'foo.txt')
 This is a foo text.
+>>> is_temp
+False
 
 If the file cannot be served, the cached copy will be used:
 
 >>> remove(server_data, 'foo.txt')
->>> Download()(server_url+'foo.txt')
-Traceback (most recent call last):
-IOError: ('http error', 404, 'Not Found',
-          <httplib.HTTPMessage instance at 0xa35d36c>)
->>> path = download(server_url+'foo.txt')
+>>> try: Download()(server_url+'foo.txt') # doctest: +ELLIPSIS
+... except: print 'download error'
+... else: print 'woops'
+download error
+>>> path, is_temp = download(server_url+'foo.txt')
 >>> cat(path)
 This is a foo text.
+>>> is_temp
+False
 
 Similarly, if the file is served but we're in offline mode, we'll fall back to
 using the cache:
 
 >>> write(server_data, 'foo.txt', 'The wrong text.')
->>> cat(Download()(server_url+'foo.txt'))
-The wrong text.
+>>> get(server_url+'foo.txt')
+'The wrong text.'
+
 >>> offline_download = Download(cache=cache, offline=True, fallback=True)
->>> path = offline_download(server_url+'foo.txt')
+>>> path, is_temp = offline_download(server_url+'foo.txt')
+>>> print path
+/download-cache/foo.txt
 >>> cat(path)
 This is a foo text.
+>>> is_temp
+False
 
 However, when downloading the file normally with the cache being used in
 fall-back mode, the file will be downloaded from the net and the cached copy
 will be replaced with the new content:
 
->>> path = download(server_url+'foo.txt')
->>> cat(path)
+>>> cat(download(server_url+'foo.txt')[0])
 The wrong text.
 >>> cat(cache, 'foo.txt')
 The wrong text.
@@ -408,6 +462,19 @@
 >>> print download.cache_dir
 /download-cache/cmmi
 
+If the ``download-cache`` option specifies a relative path, it is understood
+relative to the current working directory, or to the buildout directory if
+that is given:
+
+>>> download = Download({'download-cache': 'relative-cache'})
+>>> print download.cache_dir
+/sample-buildout/relative-cache/
+
+>>> download = Download({'directory': join(sample_buildout, 'root'),
+...                      'download-cache': 'relative-cache'})
+>>> print download.cache_dir
+/sample-buildout/root/relative-cache/
+
 Keyword parameters take precedence over the corresponding options:
 
 >>> download = Download({'download-cache': cache}, cache=None)
@@ -454,3 +521,15 @@
 >>> download = Download({'install-from-cache': 'false'}, offline=True)
 >>> download.offline
 True
+
+
+Clean up
+--------
+
+We should have cleaned up all temporary files created by downloading things:
+
+>>> ls(tempfile.tempdir)
+
+Reset the global temporary directory:
+
+>>> tempfile.tempdir = old_tempdir

Modified: zc.buildout/branches/use_distribute/src/zc/buildout/easy_install.py
===================================================================
--- zc.buildout/branches/use_distribute/src/zc/buildout/easy_install.py	2009-09-08 10:46:18 UTC (rev 103641)
+++ zc.buildout/branches/use_distribute/src/zc/buildout/easy_install.py	2009-09-08 10:52:14 UTC (rev 103642)
@@ -16,8 +16,6 @@
 This module provides a high-level Python API for installing packages.
 It doesn't install scripts.  It uses distribute and requires it to be
 installed.
-
-$Id$
 """
 
 import distutils.errors
@@ -34,7 +32,6 @@
 import subprocess
 import sys
 import tempfile
-import urlparse
 import zc.buildout
 import zipimport
 
@@ -55,7 +52,6 @@
 is_jython = sys.platform.startswith('java')
 
 if is_jython:
-    import subprocess
     import java.lang.System
     jython_os_name = (java.lang.System.getProperties()['os.name']).lower()
 
@@ -621,7 +617,9 @@
                 raise IncompatibleVersionError("Bad version", version)
 
             requirement = pkg_resources.Requirement.parse(
-                "%s ==%s" % (requirement.project_name, version))
+                "%s[%s] ==%s" % (requirement.project_name,
+                               ','.join(requirement.extras),
+                               version))
 
         return requirement
 
@@ -1023,7 +1021,7 @@
 import os
 
 join = os.path.join
-base = os.path.dirname(os.path.abspath(__file__))
+base = os.path.dirname(os.path.abspath(os.path.realpath(__file__)))
 """
 
 def _script(module_name, attrs, path, dest, executable, arguments,
@@ -1129,22 +1127,28 @@
 
 _interactive = True
 if len(sys.argv) > 1:
-    import getopt
-    _options, _args = getopt.getopt(sys.argv[1:], 'ic:')
+    _options, _args = __import__("getopt").getopt(sys.argv[1:], 'ic:m:')
     _interactive = False
     for (_opt, _val) in _options:
         if _opt == '-i':
             _interactive = True
         elif _opt == '-c':
             exec _val
+        elif _opt == '-m':
+            sys.argv[1:] = _args
+            _args = []
+            __import__("runpy").run_module(
+                 _val, {}, "__main__", alter_sys=True)
 
     if _args:
         sys.argv[:] = _args
-        execfile(sys.argv[0])
+        __file__ = _args[0]
+        del _options, _args
+        execfile(__file__)
 
 if _interactive:
-    import code
-    code.interact(banner="", local=globals())
+    del _interactive
+    __import__("code").interact(banner="", local=globals())
 '''
 
 runsetup_template = """

Modified: zc.buildout/branches/use_distribute/src/zc/buildout/easy_install.txt
===================================================================
--- zc.buildout/branches/use_distribute/src/zc/buildout/easy_install.txt	2009-09-08 10:46:18 UTC (rev 103641)
+++ zc.buildout/branches/use_distribute/src/zc/buildout/easy_install.txt	2009-09-08 10:52:14 UTC (rev 103642)
@@ -663,34 +663,59 @@
 
     >>> cat(bin, 'py') # doctest: +NORMALIZE_WHITESPACE
     #!/usr/local/bin/python2.4
+    <BLANKLINE>
     import sys
     <BLANKLINE>
     sys.path[0:0] = [
-      '/sample-install/demo-0.3-py2.4.egg',
-      '/sample-install/demoneeded-1.1-py2.4.egg',
+      '/sample-install/demo-0.3-pyN.N.egg',
+      '/sample-install/demoneeded-1.1-pyN.N.egg',
       ]
     <BLANKLINE>
     _interactive = True
     if len(sys.argv) > 1:
-        import getopt
-        _options, _args = getopt.getopt(sys.argv[1:], 'ic:')
+        _options, _args = __import__("getopt").getopt(sys.argv[1:], 'ic:m:')
         _interactive = False
         for (_opt, _val) in _options:
             if _opt == '-i':
                 _interactive = True
             elif _opt == '-c':
                 exec _val
+            elif _opt == '-m':
+                sys.argv[1:] = _args
+                _args = []
+                __import__("runpy").run_module(
+                     _val, {}, "__main__", alter_sys=True)
     <BLANKLINE>
         if _args:
             sys.argv[:] = _args
-            execfile(sys.argv[0])
+            __file__ = _args[0]
+            del _options, _args
+            execfile(__file__)
     <BLANKLINE>
     if _interactive:
-        import code
-        code.interact(banner="", local=globals())
+        del _interactive
+        __import__("code").interact(banner="", local=globals())
 
 If invoked with a script name and arguments, it will run that script, instead.
 
+    >>> write('ascript', '''
+    ... "demo doc"
+    ... print sys.argv
+    ... print (__name__, __file__, __doc__)
+    ... ''')
+    >>> print system(join(bin, 'py')+' ascript a b c'),
+    ['ascript', 'a', 'b', 'c']
+    ('__main__', 'ascript', 'demo doc')
+
+For Python 2.5 and higher, you can also use the -m option to run a
+module:
+
+    >>> print system(join(bin, 'py')+' -m pdb'),
+    usage: pdb.py scriptfile [arg] ...
+
+    >>> print system(join(bin, 'py')+' -m pdb what'),
+    Error: what does not exist
+
 An additional argument can be passed to define which scripts to install
 and to provide script names. The argument is a dictionary mapping
 original script names to new script names.
@@ -817,7 +842,7 @@
     import os
     <BLANKLINE>
     join = os.path.join
-    base = os.path.dirname(os.path.abspath(__file__))
+    base = os.path.dirname(os.path.abspath(os.path.realpath(__file__)))
     base = os.path.dirname(base)
     <BLANKLINE>
     import sys
@@ -849,7 +874,7 @@
     import os
     <BLANKLINE>
     join = os.path.join
-    base = os.path.dirname(os.path.abspath(__file__))
+    base = os.path.dirname(os.path.abspath(os.path.realpath(__file__)))
     base = os.path.dirname(base)
     <BLANKLINE>
     import sys
@@ -863,22 +888,28 @@
     <BLANKLINE>
     _interactive = True
     if len(sys.argv) > 1:
-        import getopt
-        _options, _args = getopt.getopt(sys.argv[1:], 'ic:')
+        _options, _args = __import__("getopt").getopt(sys.argv[1:], 'ic:m:')
         _interactive = False
         for (_opt, _val) in _options:
             if _opt == '-i':
                 _interactive = True
             elif _opt == '-c':
                 exec _val
+            elif _opt == '-m':
+                sys.argv[1:] = _args
+                _args = []
+                __import__("runpy").run_module(
+                     _val, {}, "__main__", alter_sys=True)
     <BLANKLINE>
         if _args:
             sys.argv[:] = _args
-            execfile(sys.argv[0])
+            __file__ = _args[0]
+            del _options, _args
+            execfile(__file__)
     <BLANKLINE>
     if _interactive:
-        import code
-        code.interact(banner="", local=globals())
+        del _interactive
+        __import__("code").interact(banner="", local=globals())
 
 
 Handling custom build options for extensions provided in source distributions

Modified: zc.buildout/branches/use_distribute/src/zc/buildout/extends-cache.txt
===================================================================
--- zc.buildout/branches/use_distribute/src/zc/buildout/extends-cache.txt	2009-09-08 10:46:18 UTC (rev 103641)
+++ zc.buildout/branches/use_distribute/src/zc/buildout/extends-cache.txt	2009-09-08 10:52:14 UTC (rev 103642)
@@ -13,7 +13,14 @@
 >>> server_url = start_server(server_data)
 >>> cd(sample_buildout)
 
+We also use a fresh directory for temporary files in order to make sure that
+all temporary files have been cleaned up in the end:
 
+>>> import tempfile
+>>> old_tempdir = tempfile.tempdir
+>>> tempfile.tempdir = tmpdir('tmp')
+
+
 Basic use of the extends cache
 ------------------------------
 
@@ -59,6 +66,7 @@
 running buildout and the base.cfg file will be put in it (with the file name
 being a hash of the complete URL):
 
+>>> mkdir('cache')
 >>> write('buildout.cfg', """\
 ... [buildout]
 ... extends = %sbase.cfg
@@ -159,6 +167,8 @@
 
 >>> mkdir('home')
 >>> mkdir('home', '.buildout')
+>>> mkdir('cache')
+>>> mkdir('user-cache')
 >>> os.environ['HOME'] = join(sample_buildout, 'home')
 >>> write('home', '.buildout', 'default.cfg', """\
 ... [buildout]
@@ -375,3 +385,15 @@
   Checking for upgrades.
 An internal error occured ...
 ValueError: install_from_cache set to true with no download cache
+
+
+Clean up
+--------
+
+We should have cleaned up all temporary files created by downloading things:
+
+>>> ls(tempfile.tempdir)
+
+Reset the global temporary directory:
+
+>>> tempfile.tempdir = old_tempdir

Modified: zc.buildout/branches/use_distribute/src/zc/buildout/testing.py
===================================================================
--- zc.buildout/branches/use_distribute/src/zc/buildout/testing.py	2009-09-08 10:46:18 UTC (rev 103641)
+++ zc.buildout/branches/use_distribute/src/zc/buildout/testing.py	2009-09-08 10:52:14 UTC (rev 103642)
@@ -121,7 +121,9 @@
     here = os.getcwd()
     try:
         os.chdir(d)
-        os.spawnle(os.P_WAIT, executable, zc.buildout.easy_install._safe_arg (executable), setup, *args)
+        os.spawnle(os.P_WAIT, executable,
+                   zc.buildout.easy_install._safe_arg(executable),
+                   setup, *args)
         if os.path.exists('build'):
             rmtree('build')
     finally:

Copied: zc.buildout/branches/use_distribute/src/zc/buildout/testrecipes.py (from rev 103641, zc.buildout/trunk/src/zc/buildout/testrecipes.py)
===================================================================
--- zc.buildout/branches/use_distribute/src/zc/buildout/testrecipes.py	                        (rev 0)
+++ zc.buildout/branches/use_distribute/src/zc/buildout/testrecipes.py	2009-09-08 10:52:14 UTC (rev 103642)
@@ -0,0 +1,16 @@
+
+class Debug:
+
+    def __init__(self, buildout, name, options):
+        self.buildout = buildout
+        self.name = name
+        self.options = options
+
+    def install(self):
+        items = self.options.items()
+        items.sort()
+        for option, value in items:
+            print "  %s=%r" % (option, value)
+        return ()
+
+    update = install

Modified: zc.buildout/branches/use_distribute/src/zc/buildout/tests.py
===================================================================
--- zc.buildout/branches/use_distribute/src/zc/buildout/tests.py	2009-09-08 10:46:18 UTC (rev 103641)
+++ zc.buildout/branches/use_distribute/src/zc/buildout/tests.py	2009-09-08 10:52:14 UTC (rev 103642)
@@ -11,17 +11,19 @@
 # FOR A PARTICULAR PURPOSE.
 #
 ##############################################################################
-"""XXX short summary goes here.
-
-$Id$
-"""
-
-import os, re, shutil, sys, tempfile, unittest, zipfile
-from zope.testing import doctest, renormalizing
+from zope.testing import doctest
+from zope.testing import renormalizing
+import os
 import pkg_resources
-import zc.buildout.testing, zc.buildout.easy_install
-
+import re
+import shutil
+import sys
+import tempfile
+import unittest
+import zc.buildout.easy_install
+import zc.buildout.testing
 import zc.buildout.testselectingpython
+import zipfile
 
 os_path_sep = os.path.sep
 if os_path_sep == '\\':
@@ -2527,7 +2529,130 @@
 
     """
 
+def make_sure_versions_dont_cancel_extras():
+    """
+    There was a bug that caused extras in requirements to be lost.
 
+    >>> open('setup.py', 'w').write('''
+    ... from setuptools import setup
+    ... setup(name='extraversiondemo', version='1.0',
+    ...       url='x', author='x', author_email='x',
+    ...       extras_require=dict(foo=['demo']), py_modules=['t'])
+    ... ''')
+    >>> open('README', 'w').close()
+    >>> open('t.py', 'w').close()
+
+    >>> sdist('.', sample_eggs)
+    >>> mkdir('dest')
+    >>> ws = zc.buildout.easy_install.install(
+    ...     ['extraversiondemo[foo]'], 'dest', links=[sample_eggs],
+    ...     versions = dict(extraversiondemo='1.0')
+    ... )
+    >>> sorted(dist.key for dist in ws)
+    ['demo', 'demoneeded', 'extraversiondemo']
+    """
+
+def increment_buildout_options():
+    r"""
+    >>> write('b1.cfg', '''
+    ... [buildout]
+    ... parts = p1
+    ... x = 1
+    ... y = a
+    ...     b
+    ...
+    ... [p1]
+    ... recipe = zc.buildout:debug
+    ... foo = ${buildout:x} ${buildout:y}
+    ... ''')
+
+    >>> write('buildout.cfg', '''
+    ... [buildout]
+    ... extends = b1.cfg
+    ... parts += p2
+    ... x += 2
+    ... y -= a
+    ...
+    ... [p2]
+    ... <= p1
+    ... ''')
+
+    >>> print system(buildout),
+    Installing p1.
+      foo='1\n2 b'
+      recipe='zc.buildout:debug'
+    Installing p2.
+      foo='1\n2 b'
+      recipe='zc.buildout:debug'
+    """
+
+def increment_buildout_with_multiple_extended_files_421022():
+    r"""
+    >>> write('foo.cfg', '''
+    ... [buildout]
+    ... foo-option = foo
+    ... [other]
+    ... foo-option = foo
+    ... ''')
+    >>> write('bar.cfg', '''
+    ... [buildout]
+    ... bar-option = bar
+    ... [other]
+    ... bar-option = bar
+    ... ''')
+    >>> write('buildout.cfg', '''
+    ... [buildout]
+    ... parts = p other
+    ... extends = bar.cfg foo.cfg
+    ... bar-option += baz
+    ... foo-option += ham
+    ...
+    ... [other]
+    ... recipe = zc.buildout:debug
+    ... bar-option += baz
+    ... foo-option += ham
+    ...
+    ... [p]
+    ... recipe = zc.buildout:debug
+    ... x = ${buildout:bar-option} ${buildout:foo-option}
+    ... ''')
+
+    >>> print system(buildout),
+    Installing p.
+      recipe='zc.buildout:debug'
+      x='bar\nbaz foo\nham'
+    Installing other.
+      bar-option='bar\nbaz'
+      foo-option='foo\nham'
+      recipe='zc.buildout:debug'
+    """
+
+def increment_on_command_line():
+    r"""
+    >>> write('buildout.cfg', '''
+    ... [buildout]
+    ... parts = p1
+    ... x = 1
+    ... y = a
+    ...     b
+    ...
+    ... [p1]
+    ... recipe = zc.buildout:debug
+    ... foo = ${buildout:x} ${buildout:y}
+    ...
+    ... [p2]
+    ... <= p1
+    ... ''')
+
+    >>> print system(buildout+' buildout:parts+=p2 p1:foo+=bar'),
+    Installing p1.
+      foo='1 a\nb\nbar'
+      recipe='zc.buildout:debug'
+    Installing p2.
+      foo='1 a\nb\nbar'
+      recipe='zc.buildout:debug'
+    """
+
 ######################################################################
 
 def create_sample_eggs(test, executable=sys.executable):
@@ -2809,7 +2934,11 @@
                (re.compile('extdemo[.]pyd'), 'extdemo.so'),
                (re.compile('[-d]  distribute-\S+[.]egg'), 'distribute.egg'),
                (re.compile(r'\\[\\]?'), '/'),
-               ]),
+               ]+(sys.version_info < (2, 5) and [
+                  (re.compile('.*No module named runpy.*', re.S), ''),
+                  (re.compile('.*usage: pdb.py scriptfile .*', re.S), ''),
+                  (re.compile('.*Error: what does not exist.*', re.S), ''),
+                  ] or [])),
             ),
 
         doctest.DocFileSuite(
@@ -2818,7 +2947,7 @@
             tearDown=zc.buildout.testing.buildoutTearDown,
             optionflags=doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS,
             checker=renormalizing.RENormalizing([
-               (re.compile('0x[0-9a-f]+'), '<MEM ADDRESS>'),
+               (re.compile(' at -?0x[^>]+'), '<MEM ADDRESS>'),
                (re.compile('http://localhost:[0-9]{4,5}/'),
                 'http://localhost/'),
                (re.compile('[0-9a-f]{32}'), '<MD5 CHECKSUM>'),

Modified: zc.buildout/branches/use_distribute/zc.recipe.egg_/src/zc/recipe/egg/README.txt
===================================================================
--- zc.buildout/branches/use_distribute/zc.recipe.egg_/src/zc/recipe/egg/README.txt	2009-09-08 10:46:18 UTC (rev 103641)
+++ zc.buildout/branches/use_distribute/zc.recipe.egg_/src/zc/recipe/egg/README.txt	2009-09-08 10:52:14 UTC (rev 103642)
@@ -425,7 +425,7 @@
     import os
     <BLANKLINE>
     join = os.path.join
-    base = os.path.dirname(os.path.abspath(__file__))
+    base = os.path.dirname(os.path.abspath(os.path.realpath(__file__)))
     base = os.path.dirname(base)
     <BLANKLINE>
     import sys
@@ -472,7 +472,7 @@
     import os
     <BLANKLINE>
     join = os.path.join
-    base = os.path.dirname(os.path.abspath(__file__))
+    base = os.path.dirname(os.path.abspath(os.path.realpath(__file__)))
     base = os.path.dirname(base)
     <BLANKLINE>
     import sys

Modified: zc.buildout/branches/use_distribute/zc.recipe.egg_/src/zc/recipe/egg/selecting-python.txt
===================================================================
--- zc.buildout/branches/use_distribute/zc.recipe.egg_/src/zc/recipe/egg/selecting-python.txt	2009-09-08 10:46:18 UTC (rev 103641)
+++ zc.buildout/branches/use_distribute/zc.recipe.egg_/src/zc/recipe/egg/selecting-python.txt	2009-09-08 10:52:14 UTC (rev 103642)
@@ -78,7 +78,10 @@
     ... else:
     ...    script_name = 'demo'
     >>> f = open(os.path.join(sample_buildout, 'bin', script_name))
-    >>> f.readline().strip() == '#!' + other_executable
+    >>> shebang = f.readline().strip()
+    >>> if shebang[:3] == '#!"' and shebang[-1] == '"':
+    ...     shebang = '#!'+shebang[3:-1]
+    >>> shebang == '#!' + other_executable
     True
     >>> print f.read(), # doctest: +NORMALIZE_WHITESPACE
     <BLANKLINE>
@@ -97,9 +100,14 @@
     ...     f = open(os.path.join(sample_buildout, 'bin', 'py-demo-script.py'))
     ... else:
     ...     f = open(os.path.join(sample_buildout, 'bin', 'py-demo'))
-    >>> f.readline().strip() == '#!' + other_executable
+
+    >>> shebang = f.readline().strip()
+    >>> if shebang[:3] == '#!"' and shebang[-1] == '"':
+    ...     shebang = '#!'+shebang[3:-1]
+    >>> shebang == '#!' + other_executable
     True
     >>> print f.read(), # doctest: +NORMALIZE_WHITESPACE
+    <BLANKLINE>
     import sys
     <BLANKLINE>
     sys.path[0:0] = [
@@ -109,21 +117,27 @@
     <BLANKLINE>
     _interactive = True
     if len(sys.argv) > 1:
-        import getopt
-        _options, _args = getopt.getopt(sys.argv[1:], 'ic:')
+        _options, _args = __import__("getopt").getopt(sys.argv[1:], 'ic:m:')
         _interactive = False
         for (_opt, _val) in _options:
             if _opt == '-i':
                 _interactive = True
             elif _opt == '-c':
                 exec _val
+            elif _opt == '-m':
+                sys.argv[1:] = _args
+                _args = []
+                __import__("runpy").run_module(
+                     _val, {}, "__main__", alter_sys=True)
     <BLANKLINE>
         if _args:
             sys.argv[:] = _args
-            execfile(sys.argv[0])
+            __file__ = _args[0]
+            del _options, _args
+            execfile(__file__)
     <BLANKLINE>
     if _interactive:
-        import code
-        code.interact(banner="", local=globals())
+        del _interactive
+        __import__("code").interact(banner="", local=globals())
 
     >>> f.close()



More information about the checkins mailing list