Commit 13cf059d authored by jim's avatar jim

- If a distribution defines namespace packages but fails to declare

  setuptools as one of its dependencies, we now treat setuptools as an 
  implicit dependency.  We generate a warning if the distribution
  is a develop egg.

- Remove old develop egg links. This requires storing the old link
  paths in .installed.cfg.


git-svn-id: http://svn.zope.org/repos/main/zc.buildout/trunk@70578 62d5b8a3-27da-0310-9561-8e5933582275
parent baff5c19
...@@ -33,6 +33,17 @@ Feature Changes ...@@ -33,6 +33,17 @@ Feature Changes
update is called. For backward compatibility, recipes that don't update is called. For backward compatibility, recipes that don't
define update methiods are still supported. define update methiods are still supported.
- If a distribution defines namespace packages but fails to declare
setuptools as one of its dependencies, we now treat setuptools as an
implicit dependency. We generate a warning if the distribution
is a develop egg.
Bugs Fixed
----------
- Egg links weren't removed when corresponding entries were removed
from develop sections.
1.0.0b9 (2006-10-02) 1.0.0b9 (2006-10-02)
==================== ====================
......
...@@ -251,12 +251,18 @@ class Buildout(dict): ...@@ -251,12 +251,18 @@ class Buildout(dict):
# Check for updates. This could cause the process to be rstarted # Check for updates. This could cause the process to be rstarted
self._maybe_upgrade() self._maybe_upgrade()
# Build develop eggs
self._develop()
# load installed data # load installed data
installed_part_options = self._read_installed_part_options() installed_part_options = self._read_installed_part_options()
# Remove old develop eggs
self._uninstall(
installed_part_options['buildout'].get(
'installed_develop_eggs', '')
)
# Build develop eggs
installed_develop_eggs = self._develop()
# get configured and installed part lists # get configured and installed part lists
conf_parts = self['buildout']['parts'] conf_parts = self['buildout']['parts']
conf_parts = conf_parts and conf_parts.split() or [] conf_parts = conf_parts and conf_parts.split() or []
...@@ -380,6 +386,9 @@ class Buildout(dict): ...@@ -380,6 +386,9 @@ class Buildout(dict):
+ +
[p for p in installed_parts if p not in conf_parts] [p for p in installed_parts if p not in conf_parts]
) )
installed_part_options['buildout']['installed_develop_eggs'
] = installed_develop_eggs
self._save_installed_options(installed_part_options) self._save_installed_options(installed_part_options)
def _setup_directories(self): def _setup_directories(self):
...@@ -395,9 +404,15 @@ class Buildout(dict): ...@@ -395,9 +404,15 @@ class Buildout(dict):
"""Install sources by running setup.py develop on them """Install sources by running setup.py develop on them
""" """
develop = self['buildout'].get('develop') develop = self['buildout'].get('develop')
if develop: if not develop:
return ''
dest = self['buildout']['develop-eggs-directory']
old_files = os.listdir(dest)
env = dict(os.environ, PYTHONPATH=pkg_resources_loc) env = dict(os.environ, PYTHONPATH=pkg_resources_loc)
here = os.getcwd() here = os.getcwd()
try:
try: try:
for setup in develop.split(): for setup in develop.split():
setup = self._buildout_path(setup) setup = self._buildout_path(setup)
...@@ -413,9 +428,7 @@ class Buildout(dict): ...@@ -413,9 +428,7 @@ class Buildout(dict):
'-f', zc.buildout.easy_install._safe_arg( '-f', zc.buildout.easy_install._safe_arg(
' '.join(self._links) ' '.join(self._links)
), ),
'-d', zc.buildout.easy_install._safe_arg( '-d', zc.buildout.easy_install._safe_arg(dest),
self['buildout']['develop-eggs-directory']
),
] ]
if self._log_level <= logging.DEBUG: if self._log_level <= logging.DEBUG:
...@@ -427,10 +440,38 @@ class Buildout(dict): ...@@ -427,10 +440,38 @@ class Buildout(dict):
os.path.dirname(setup), args) os.path.dirname(setup), args)
args.append(env) args.append(env)
os.spawnle(os.P_WAIT, sys.executable, sys.executable, *args) assert os.spawnle(
os.P_WAIT, sys.executable, sys.executable, *args
) == 0
except:
# if we had an error, we need to roll back changes, by
# removing any files we created.
self._sanity_check_develop_eggs_files(dest, old_files)
self._uninstall('\n'.join(
[os.path.join(dest, f)
for f in os.listdir(dest)
if f not in old_files
]))
else:
self._sanity_check_develop_eggs_files(dest, old_files)
return '\n'.join([os.path.join(dest, f)
for f in os.listdir(dest)
if f not in old_files
])
finally: finally:
os.chdir(here) os.chdir(here)
def _sanity_check_develop_eggs_files(self, dest, old_files):
for f in os.listdir(dest):
if f in old_files:
continue
if not (os.path.isfile(os.path.join(dest, f))
and f.endswith('.egg-link')):
self._logger.warning(
"Unexpected entry, %s, in develop-eggs directory", f)
def _load_recipes(self, parts): def _load_recipes(self, parts):
recipes = {} recipes = {}
if not parts: if not parts:
......
...@@ -307,6 +307,7 @@ about the part we installed: ...@@ -307,6 +307,7 @@ about the part we installed:
>>> cat(sample_buildout, '.installed.cfg') >>> cat(sample_buildout, '.installed.cfg')
[buildout] [buildout]
installed_develop_eggs = /sample-buildout/develop-eggs/recipes.egg-link
parts = data-dir parts = data-dir
<BLANKLINE> <BLANKLINE>
[data-dir] [data-dir]
...@@ -904,6 +905,7 @@ the buildout in the usual way: ...@@ -904,6 +905,7 @@ the buildout in the usual way:
>>> cat(sample_buildout, '.installed.cfg') >>> cat(sample_buildout, '.installed.cfg')
[buildout] [buildout]
installed_develop_eggs = /sample-buildout/develop-eggs/recipes.egg-link
parts = debug d1 d2 d3 parts = debug d1 d2 d3
<BLANKLINE> <BLANKLINE>
[debug] [debug]
...@@ -988,6 +990,7 @@ The .installed.cfg is only updated for the recipes that ran: ...@@ -988,6 +990,7 @@ The .installed.cfg is only updated for the recipes that ran:
>>> cat(sample_buildout, '.installed.cfg') >>> cat(sample_buildout, '.installed.cfg')
[buildout] [buildout]
installed_develop_eggs = /sample-buildout/develop-eggs/recipes.egg-link
parts = debug d2 d3 d4 d1 parts = debug d2 d3 d4 d1
<BLANKLINE> <BLANKLINE>
[debug] [debug]
......
...@@ -325,9 +325,28 @@ def install(specs, dest, ...@@ -325,9 +325,28 @@ def install(specs, dest,
ws = working_set ws = working_set
for requirement in requirements: for requirement in requirements:
ws.add(_get_dist(requirement, env, ws, dist = _get_dist(requirement, env, ws,
dest, links, index, executable, always_unzip) dest, links, index, executable, always_unzip)
) ws.add(dist)
if dist.has_metadata('namespace_packages.txt'):
for r in dist.requires():
if r.project_name == 'setuptools':
break
else:
# We have a namespace package but no requirement for setuptools
if dist.precedence == pkg_resources.DEVELOP_DIST:
logger.warn(
"Develop distribution for %s\n"
"uses namespace packages but the distribution "
"does not require setuptools.",
dist)
requirement = pkg_resources.Requirement.parse('setuptools')
if ws.find(requirement) is None:
dist = _get_dist(requirement, env, ws,
dest, links, index, executable,
False)
ws.add(dist)
# OK, we have the requested distributions and they're in the working # OK, we have the requested distributions and they're in the working
# set, but they may have unmet requirements. We'll simply keep # set, but they may have unmet requirements. We'll simply keep
......
...@@ -382,6 +382,178 @@ bootstrapping. ...@@ -382,6 +382,178 @@ bootstrapping.
buildout: Creating directory /sample-bootstrap/develop-eggs buildout: Creating directory /sample-bootstrap/develop-eggs
""" """
def removing_eggs_from_develop_section_causes_egg_link_to_be_removed():
'''
>>> cd(sample_buildout)
Create a develop egg:
>>> mkdir('foo')
>>> write('foo', 'setup.py',
... """
... from setuptools import setup
... setup(name='foox')
... """)
>>> write('buildout.cfg',
... """
... [buildout]
... develop = foo
... parts =
... """)
>>> print system(join('bin', 'buildout')),
buildout: Develop: /sample-buildout/foo/setup.py
>>> ls('develop-eggs')
- foox.egg-link
Create another:
>>> mkdir('bar')
>>> write('bar', 'setup.py',
... """
... from setuptools import setup
... setup(name='fooy')
... """)
>>> write('buildout.cfg',
... """
... [buildout]
... develop = foo bar
... parts =
... """)
>>> print system(join('bin', 'buildout')),
buildout: Develop: /sample-buildout/foo/setup.py
buildout: Develop: /sample-buildout/bar/setup.py
>>> ls('develop-eggs')
- foox.egg-link
- fooy.egg-link
Remove one:
>>> write('buildout.cfg',
... """
... [buildout]
... develop = bar
... parts =
... """)
>>> print system(join('bin', 'buildout')),
buildout: Develop: /sample-buildout/bar/setup.py
It is gone
>>> ls('develop-eggs')
- fooy.egg-link
Remove the other:
>>> write('buildout.cfg',
... """
... [buildout]
... parts =
... """)
>>> print system(join('bin', 'buildout')),
All gone
>>> ls('develop-eggs')
'''
def add_setuptools_to_dependencies_when_namespace_packages():
'''
Often, a package depends on setuptools soley by virtue of using
namespace packages. In this situation, package authors often forget to
declare setuptools as a dependency. This is a mistake, but,
unfortunately, a common one that we need to work around. If an egg
uses namespace packages and does not include setuptools as a depenency,
we willll still include setuptools in the working set. If we see this for
a devlop egg, we will also generate a warning.
>>> cd(sample_buildout)
>>> mkdir('foo')
>>> mkdir('foo', 'src')
>>> mkdir('foo', 'src', 'stuff')
>>> write('foo', 'src', 'stuff', '__init__.py',
... """__import__('pkg_resources').declare_namespace(__name__)
... """)
>>> mkdir('foo', 'src', 'stuff', 'foox')
>>> write('foo', 'src', 'stuff', 'foox', '__init__.py', '')
>>> write('foo', 'setup.py',
... """
... from setuptools import setup
... setup(name='foox',
... namespace_packages = ['stuff'],
... package_dir = {'': 'src'},
... packages = ['stuff', 'stuff.foox'],
... )
... """)
>>> write('foo', 'README.txt', '')
>>> write('buildout.cfg',
... """
... [buildout]
... develop = foo
... parts =
... """)
>>> print system(join('bin', 'buildout')),
buildout: Develop: /sample-buildout/foo/setup.py
Now, if we generate a working set using the egg link, we will get a warning
and we will get setuptools included in the working set.
>>> import logging, zope.testing.loggingsupport
>>> handler = zope.testing.loggingsupport.InstalledHandler(
... 'zc.buildout', level=logging.WARNING)
>>> logging.getLogger('zc').propagate = False
>>> [dist.project_name
... for dist in zc.buildout.easy_install.working_set(
... ['foox'], sys.executable,
... [join(sample_buildout, 'eggs'),
... join(sample_buildout, 'develop-eggs'),
... ])]
['foox', 'setuptools']
>>> print handler
zc.buildout.easy_install WARNING
Develop distribution for foox 0.0.0
uses namespace packages but the distribution does not require setuptools.
>>> handler.clear()
On the other hand, if we have a regular egg, rather than a develop egg:
>>> os.remove(join('develop-eggs', 'foox.egg-link'))
>>> _ = system(join('bin', 'buildout') + ' setup foo bdist_egg -d'
... + join(sample_buildout, 'eggs'))
>>> ls('develop-eggs')
>>> ls('eggs') # doctest: +ELLIPSIS
- foox-0.0.0-py2.4.egg
...
We do not get a warning, but we do get setuptools included in the working set:
>>> [dist.project_name
... for dist in zc.buildout.easy_install.working_set(
... ['foox'], sys.executable,
... [join(sample_buildout, 'eggs'),
... join(sample_buildout, 'develop-eggs'),
... ])]
['foox', 'setuptools']
>>> print handler,
>>> logging.getLogger('zc').propagate = True
>>> handler.uninstall()
'''
def create_sample_eggs(test, executable=sys.executable): def create_sample_eggs(test, executable=sys.executable):
write = test.globs['write'] write = test.globs['write']
...@@ -603,7 +775,6 @@ def test_suite(): ...@@ -603,7 +775,6 @@ def test_suite():
doctest.DocTestSuite( doctest.DocTestSuite(
setUp=zc.buildout.testing.buildoutSetUp, setUp=zc.buildout.testing.buildoutSetUp,
tearDown=zc.buildout.testing.buildoutTearDown, tearDown=zc.buildout.testing.buildoutTearDown,
checker=renormalizing.RENormalizing([ checker=renormalizing.RENormalizing([
zc.buildout.testing.normalize_path, zc.buildout.testing.normalize_path,
zc.buildout.testing.normalize_script, zc.buildout.testing.normalize_script,
......
...@@ -96,6 +96,7 @@ computed by the egg recipe by looking at .installed.cfg: ...@@ -96,6 +96,7 @@ computed by the egg recipe by looking at .installed.cfg:
>>> cat(sample_buildout, '.installed.cfg') >>> cat(sample_buildout, '.installed.cfg')
[buildout] [buildout]
installed_develop_eggs = /sample-buildout/develop-eggs/sample.egg-link
parts = sample-part parts = sample-part
<BLANKLINE> <BLANKLINE>
[sample-part] [sample-part]
...@@ -104,9 +105,9 @@ computed by the egg recipe by looking at .installed.cfg: ...@@ -104,9 +105,9 @@ computed by the egg recipe by looking at .installed.cfg:
zc.recipe.egg-cAsnudgkduAa/Fd+WJIM6Q== zc.recipe.egg-cAsnudgkduAa/Fd+WJIM6Q==
setuptools-0.6-py2.4.egg setuptools-0.6-py2.4.egg
zc.buildout-+rYeCcmFuD1K/aB77XTj5A== zc.buildout-+rYeCcmFuD1K/aB77XTj5A==
_b = /tmp/tmpb7kP9bsample-buildout/bin _b = /sample-buildout/bin
_d = /tmp/tmpb7kP9bsample-buildout/develop-eggs _d = /sample-buildout/develop-eggs
_e = /tmp/tmpb7kP9bsample-buildout/eggs _e = /sample-buildout/eggs
eggs = demo<0.3 eggs = demo<0.3
executable = /usr/local/bin/python2.3 executable = /usr/local/bin/python2.3
extras = other extras = other
......
...@@ -56,8 +56,7 @@ def test_suite(): ...@@ -56,8 +56,7 @@ def test_suite():
'api.txt', 'api.txt',
setUp=setUp, tearDown=zc.buildout.testing.buildoutTearDown, setUp=setUp, tearDown=zc.buildout.testing.buildoutTearDown,
checker=renormalizing.RENormalizing([ checker=renormalizing.RENormalizing([
(re.compile('_b = \S+sample-buildout.bin'), zc.buildout.testing.normalize_path,
'_b = sample-buildout/bin'),
(re.compile('__buildout_signature__ = ' (re.compile('__buildout_signature__ = '
'sample-\S+\s+' 'sample-\S+\s+'
'zc.recipe.egg-\S+\s+' 'zc.recipe.egg-\S+\s+'
...@@ -65,10 +64,6 @@ def test_suite(): ...@@ -65,10 +64,6 @@ def test_suite():
'zc.buildout-\S+\s*' 'zc.buildout-\S+\s*'
), ),
'__buildout_signature__ = sample- zc.recipe.egg-'), '__buildout_signature__ = sample- zc.recipe.egg-'),
(re.compile('_d = \S+sample-buildout.develop-eggs'),
'_d = sample-buildout/develop-eggs'),
(re.compile('_e = \S+sample-buildout.eggs'),
'_e = sample-buildout/eggs'),
(re.compile('executable = \S+python\S*'), (re.compile('executable = \S+python\S*'),
'executable = python'), 'executable = python'),
(re.compile('index = \S+python\S+'), (re.compile('index = \S+python\S+'),
......
...@@ -205,6 +205,7 @@ extra-paths option to specify them: ...@@ -205,6 +205,7 @@ extra-paths option to specify them:
sys.path[0:0] = [ sys.path[0:0] = [
'/sample-buildout/demo', '/sample-buildout/demo',
'/sample-buildout/eggs/zope.testing-3.0-py2.3.egg', '/sample-buildout/eggs/zope.testing-3.0-py2.3.egg',
'/sample-buildout/eggs/setuptools-0.6-py1.3.egg',
'/usr/local/zope/lib/python', '/usr/local/zope/lib/python',
] ]
<BLANKLINE> <BLANKLINE>
...@@ -248,6 +249,7 @@ using the -v option. ...@@ -248,6 +249,7 @@ using the -v option.
sys.path[0:0] = [ sys.path[0:0] = [
'/sample-buildout/demo', '/sample-buildout/demo',
'/sample-buildout/eggs/zope.testing-3.0-py2.4.egg', '/sample-buildout/eggs/zope.testing-3.0-py2.4.egg',
'/sample-buildout/eggs/setuptools-0.6-py1.3.egg',
'/usr/local/zope/lib/python', '/usr/local/zope/lib/python',
] ]
<BLANKLINE> <BLANKLINE>
......
...@@ -45,6 +45,7 @@ def test_suite(): ...@@ -45,6 +45,7 @@ def test_suite():
(re.compile('#!\S+python\S*'), '#!python'), (re.compile('#!\S+python\S*'), '#!python'),
(re.compile('\d[.]\d+ seconds'), '0.001 seconds'), (re.compile('\d[.]\d+ seconds'), '0.001 seconds'),
(re.compile('zope.testing-[^-]+-'), 'zope.testing-X-'), (re.compile('zope.testing-[^-]+-'), 'zope.testing-X-'),
(re.compile('setuptools-[^-]+-'), 'setuptools-X-'),
]) ])
), ),
......
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