[Checkins] SVN: zc.buildout/trunk/ Added support controlling how
eggs with extensions are built.
Jim Fulton
jim at zope.com
Wed Aug 9 16:42:28 EDT 2006
Log message for revision 69383:
Added support controlling how eggs with extensions are built.
Changed:
U zc.buildout/trunk/src/zc/buildout/easy_install.py
U zc.buildout/trunk/src/zc/buildout/easy_install.txt
U zc.buildout/trunk/src/zc/buildout/testing.py
U zc.buildout/trunk/src/zc/buildout/tests.py
U zc.buildout/trunk/zc.recipe.egg_/README.txt
U zc.buildout/trunk/zc.recipe.egg_/setup.py
U zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/__init__.py
A zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/custom.py
A zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/custom.txt
U zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/tests.py
-=-
Modified: zc.buildout/trunk/src/zc/buildout/easy_install.py
===================================================================
--- zc.buildout/trunk/src/zc/buildout/easy_install.py 2006-08-09 13:26:53 UTC (rev 69382)
+++ zc.buildout/trunk/src/zc/buildout/easy_install.py 2006-08-09 20:42:27 UTC (rev 69383)
@@ -20,10 +20,15 @@
$Id$
"""
-import logging, os, re, sys
-import pkg_resources
+import logging, os, re, tempfile, sys
+import pkg_resources, setuptools.command.setopt
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. :(
+
logger = logging.getLogger('zc.buildout.easy_install')
# Include buildout and setuptools eggs in paths
@@ -131,15 +136,15 @@
logger.debug('Running easy_install:\n%s "%s"\npath=%s\n',
executable, '" "'.join(args), path)
- args += (dict(PYTHONPATH=path), )
+ args += (dict(os.environ, PYTHONPATH=path), )
sys.stdout.flush() # We want any pending output first
exit_code = os.spawnle(os.P_WAIT, executable, executable, *args)
+ assert exit_code == 0
# We may overwrite distributions, so clear importer
# cache.
sys.path_importer_cache.clear()
- assert exit_code == 0
def _get_dist(requirement, env, ws,
@@ -235,6 +240,77 @@
return ws
+
+def _editable(spec, dest, links=(), index = None, executable=sys.executable):
+ prefix = sys.exec_prefix + os.path.sep
+ path = os.pathsep.join([p for p in sys.path if not p.startswith(prefix)])
+ args = (
+ '-c', 'from setuptools.command.easy_install import main; main()',
+ '-eb', dest)
+ if links:
+ args += ('-f', ' '.join(links))
+ if index:
+ args += ('-i', index)
+ level = logger.getEffectiveLevel()
+ if level > logging.DEBUG:
+ args += ('-q', )
+ elif level < logging.DEBUG:
+ args += ('-v', )
+
+ args += (spec, )
+
+ if level <= logging.DEBUG:
+ logger.debug('Running easy_install:\n%s "%s"\npath=%s\n',
+ executable, '" "'.join(args), path)
+
+ args += (dict(os.environ, PYTHONPATH=path), )
+ sys.stdout.flush() # We want any pending output first
+ exit_code = os.spawnle(os.P_WAIT, executable, executable, *args)
+ assert exit_code == 0
+
+def build(spec, dest, build_ext,
+ links=(), index=None,
+ executable=sys.executable,
+ path=None):
+
+ # XXX we're going to download and build the egg every stinking time.
+ # We need to not do that.
+
+ logger.debug('Building %r', spec)
+
+ path = path and path[:] or []
+ if dest is not None:
+ path.insert(0, dest)
+
+ path += buildout_and_setuptools_path
+
+ links = list(links) # make copy, because we may need to mutate
+
+ # For each spec, see if it is already installed. We create a working
+ # set to keep track of what we've collected and to make sue than the
+ # distributions assembled are consistent.
+ env = pkg_resources.Environment(path, python=_get_version(executable))
+ requirement = pkg_resources.Requirement.parse(spec)
+
+ dist = _satisfied(requirement, env)
+ if dist is not None:
+ return dist
+
+ # Get an editable version of the package to a temporary directory:
+ tmp = tempfile.mkdtemp('editable')
+ _editable(spec, tmp, links, index, executable)
+
+ setup_cfg = os.path.join(tmp, requirement.key, 'setup.cfg')
+ if not os.path.exists(setup_cfg):
+ f = open(setup_cfg, 'w')
+ f.close()
+ setuptools.command.setopt.edit_config(setup_cfg, dict(build_ext=build_ext))
+
+ # Now run easy_install for real:
+ _call_easy_install(
+ os.path.join(tmp, requirement.key),
+ dest, links, index, executable, True)
+
def working_set(specs, executable, path):
return install(specs, None, executable=executable, path=path)
Modified: zc.buildout/trunk/src/zc/buildout/easy_install.txt
===================================================================
--- zc.buildout/trunk/src/zc/buildout/easy_install.txt 2006-08-09 13:26:53 UTC (rev 69382)
+++ zc.buildout/trunk/src/zc/buildout/easy_install.txt 2006-08-09 20:42:27 UTC (rev 69383)
@@ -18,7 +18,10 @@
look for additional distributions. We always give preference to
develop eggs.
-The easy_install module provides a single method, install. The
+- Distutils options for building extensions can be passed.
+
+The easy_install module provides a method, install, for installing one
+or more packages and their dependencies. The
install function takes 2 positional arguments:
- An iterable of setuptools requirement strings for the distributions
@@ -73,6 +76,7 @@
<a href="demoneeded-1.0-py2.4.egg">demoneeded-1.0-py2.4.egg</a><br>
<a href="demoneeded-1.1-py2.3.egg">demoneeded-1.1-py2.3.egg</a><br>
<a href="demoneeded-1.1-py2.4.egg">demoneeded-1.1-py2.4.egg</a><br>
+ <a href="extdemo-1.4.tar.gz">extdemo-1.4.tar.gz</a><br>
<a href="index/">index/</a><br>
<a href="other-1.0-py2.3.egg">other-1.0-py2.3.egg</a><br>
<a href="other-1.0-py2.4.egg">other-1.0-py2.4.egg</a><br>
@@ -267,3 +271,94 @@
>>> print system(os.path.join(bin, 'run')),
3 1
+
+Handling custom build options for extensions
+--------------------------------------------
+
+Sometimes, we need to control how extension modules are built. The
+build method provides this level of control. It takes a single
+package specification, downloads a source distribution, and builds it
+with specified custom build options.
+
+The build method takes 3 positional arguments:
+
+spec
+ A package specification
+
+dest
+ A destination directory
+
+build_ext
+ A dictionary of options to be passed to the distutils build_ext
+ command when building extensions.
+
+It supports a number of optional keyword arguments:
+
+links
+ a sequence of URLs, file names, or directories to look for
+ links to distributions,
+
+index
+ The URL of an index server, or almost any other valid URL. :)
+
+ If not specified, the Python Package Index,
+ http://cheeseshop.python.org/pypi, is used. You can specify an
+ alternate index with this option. If you use the links option and
+ if the links point to the needed distributions, then the index can
+ be anything and will be largely ignored. In the examples, here,
+ we'll just point to an empty directory on our link server. This
+ will make our examples run a little bit faster.
+
+executable
+ A path to a Python executable. Distributions will ne installed
+ using this executable and will be for the matching Python version.
+
+path
+ A list of additional directories to search for locally-installed
+ distributions.
+
+always_unzip
+ A flag indicating that newly-downloaded distributions should be
+ directories even if they could be installed as zip files.
+
+Our link server included a source distribution that includes a simple
+extension, extdemo.c::
+
+ #include <Python.h>
+ #include <extdemo.h>
+
+ static PyMethodDef methods[] = {};
+
+ PyMODINIT_FUNC
+ initextdemo(void)
+ {
+ PyObject *d;
+ d = Py_InitModule3("extdemo", methods, "");
+ PyDict_SetItemString(d, "val", PyInt_FromLong(EXTDEMO));
+ }
+
+The extension depends on a system-dependnt include file, extdemo.h,
+that defines a constant, EXTDEMO, that is exposed by the extension.
+
+We'll add an include directory to our sample buildout and add the
+needed include file to it:
+
+ >>> mkdir(sample_buildout, 'include')
+ >>> open(os.path.join(sample_buildout, 'include', 'extdemo.h'), 'w').write(
+ ... "#define EXTDEMO 42\n")
+
+Now, we can use the build function to create an egg from the source
+distribution:
+
+ >>> zc.buildout.easy_install.build(
+ ... 'extdemo', dest,
+ ... {'include-dirs': os.path.join(sample_buildout, 'include')},
+ ... links=[link_server], index=link_server+'index/')
+
+Now if we look in our destination directory, we see we have an extdemo egg:
+
+ >>> ls(dest)
+ d demo-0.3-py2.4.egg
+ d demoneeded-1.1-py2.4.egg
+ d extdemo-1.4-py2.3-unix-i686.egg
+
Modified: zc.buildout/trunk/src/zc/buildout/testing.py
===================================================================
--- zc.buildout/trunk/src/zc/buildout/testing.py 2006-08-09 13:26:53 UTC (rev 69382)
+++ zc.buildout/trunk/src/zc/buildout/testing.py 2006-08-09 20:42:27 UTC (rev 69383)
@@ -200,6 +200,49 @@
test.globs['python2_4_executable'] = p24
+
+extdemo_c = """
+#include <Python.h>
+#include <extdemo.h>
+
+static PyMethodDef methods[] = {};
+
+PyMODINIT_FUNC
+initextdemo(void)
+{
+ PyObject *d;
+ d = Py_InitModule3("extdemo", methods, "");
+ PyDict_SetItemString(d, "val", PyInt_FromLong(EXTDEMO));
+}
+"""
+
+extdemo_setup_py = """
+from distutils.core import setup, Extension
+
+setup(name = "extdemo", version = "1.4", url="http://www.zope.org",
+ author="Demo", author_email="demo at demo.com",
+ ext_modules = [Extension('extdemo', ['extdemo.c'])],
+ )
+"""
+
+def add_source_dist(test):
+ import tarfile
+ tmp = tempfile.mkdtemp('test-sdist')
+ open(os.path.join(tmp, 'extdemo.c'), 'w').write(extdemo_c);
+ open(os.path.join(tmp, 'setup.py'), 'w').write(extdemo_setup_py);
+ open(os.path.join(tmp, 'README'), 'w').write("");
+ open(os.path.join(tmp, 'MANIFEST.in'), 'w').write("include *.c\n");
+ here = os.getcwd()
+ os.chdir(tmp)
+ status = os.spawnl(os.P_WAIT, sys.executable, sys.executable,
+ os.path.join(tmp, 'setup.py'), '-q', 'sdist')
+ os.chdir(here)
+ assert status == 0
+ shutil.move(
+ os.path.join(tmp, 'dist', 'extdemo-1.4.tar.gz'),
+ os.path.join(test.globs['sample_eggs'], 'extdemo-1.4.tar.gz'),
+ )
+
def make_tree(test):
sample_eggs = test.globs['sample_eggs']
tree = dict(
@@ -266,6 +309,8 @@
self.send_header('Content-Length', len(out))
if name.endswith('.egg'):
self.send_header('Content-Type', 'application/zip')
+ elif name.endswith('.gz'):
+ self.send_header('Content-Type', 'application/x-gzip')
else:
self.send_header('Content-Type', 'text/html')
self.end_headers()
Modified: zc.buildout/trunk/src/zc/buildout/tests.py
===================================================================
--- zc.buildout/trunk/src/zc/buildout/tests.py 2006-08-09 13:26:53 UTC (rev 69382)
+++ zc.buildout/trunk/src/zc/buildout/tests.py 2006-08-09 20:42:27 UTC (rev 69383)
@@ -141,6 +141,12 @@
zc.buildout.testing.multi_python(test)
zc.buildout.testing.setUpServer(test, zc.buildout.testing.make_tree(test))
+def easy_install_SetUp(test):
+ zc.buildout.testing.buildoutSetUp(test, clear_home=False)
+ zc.buildout.testing.multi_python(test)
+ zc.buildout.testing.add_source_dist(test)
+ zc.buildout.testing.setUpServer(test, zc.buildout.testing.make_tree(test))
+
class PythonNormalizing(renormalizing.RENormalizing):
def _transform(self, want, got):
@@ -216,14 +222,15 @@
doctest.DocFileSuite(
'easy_install.txt',
- setUp=linkerSetUp, tearDown=zc.buildout.testing.buildoutTearDown,
+ setUp=easy_install_SetUp,
+ tearDown=zc.buildout.testing.buildoutTearDown,
checker=PythonNormalizing([
(re.compile("'%(sep)s\S+sample-install%(sep)s(dist%(sep)s)?"
% dict(sep=os.path.sep)),
'/sample-eggs/'),
- (re.compile("(- (demo(needed)?|other)"
- "-\d[.]\d-py)\d[.]\d[.]egg"),
+ (re.compile("([d-] ((ext)?demo(needed)?|other)"
+ "-\d[.]\d-py)\d[.]\d(-[^. \t\n]+)?[.]egg"),
'\\1V.V.egg'),
]),
),
Modified: zc.buildout/trunk/zc.recipe.egg_/README.txt
===================================================================
--- zc.buildout/trunk/zc.recipe.egg_/README.txt 2006-08-09 13:26:53 UTC (rev 69382)
+++ zc.buildout/trunk/zc.recipe.egg_/README.txt 2006-08-09 20:42:27 UTC (rev 69383)
@@ -35,6 +35,12 @@
disabled. If the option isn't given at all, then all scripts
defined by the named eggs will be generated.
+Custom eggs
+-----------
+
+The zc.recipe.egg:custom recipe supports building custom eggs,
+currently with specialized options for building extensions.
+
To do
-----
Modified: zc.buildout/trunk/zc.recipe.egg_/setup.py
===================================================================
--- zc.buildout/trunk/zc.recipe.egg_/setup.py 2006-08-09 13:26:53 UTC (rev 69382)
+++ zc.buildout/trunk/zc.recipe.egg_/setup.py 2006-08-09 20:42:27 UTC (rev 69383)
@@ -19,6 +19,9 @@
install_requires = ['zc.buildout', 'setuptools'],
tests_require = ['zope.testing'],
test_suite = name+'.tests.test_suite',
- entry_points = {'zc.buildout': ['default = %s:Egg' % name]},
+ entry_points = {'zc.buildout': ['default = %s:Egg' % name,
+ 'custom = %s:Custom' % name,
+ ]
+ },
dependency_links = ['http://download.zope.org/distribution/'],
)
Modified: zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/__init__.py
===================================================================
--- zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/__init__.py 2006-08-09 13:26:53 UTC (rev 69382)
+++ zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/__init__.py 2006-08-09 20:42:27 UTC (rev 69383)
@@ -1 +1,2 @@
-from zc.recipe.egg.egg import Egg
+from zc.recipe.egg.egg import Egg
+from zc.recipe.egg.custom import Custom
Copied: zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/custom.py (from rev 69373, zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/egg.py)
===================================================================
--- zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/egg.py 2006-08-08 11:46:36 UTC (rev 69373)
+++ zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/custom.py 2006-08-09 20:42:27 UTC (rev 69383)
@@ -0,0 +1,82 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Install packages as eggs
+
+$Id$
+"""
+
+import os, re, zipfile
+import zc.buildout.easy_install
+
+class Custom:
+
+ def __init__(self, buildout, name, options):
+ self.buildout = buildout
+ self.name = name
+ self.options = options
+ links = options.get('find-links',
+ buildout['buildout'].get('find-links'))
+ if links:
+ links = links.split()
+ options['find-links'] = '\n'.join(links)
+ else:
+ links = ()
+ self.links = links
+
+ index = options.get('index', buildout['buildout'].get('index'))
+ if index is not None:
+ options['index'] = index
+ self.index = index
+
+ options['_b'] = buildout['buildout']['bin-directory']
+ options['_e'] = buildout['buildout']['eggs-directory']
+ options['_d'] = buildout['buildout']['develop-eggs-directory']
+
+ assert options.get('unzip') in ('true', 'false', None)
+
+ python = options.get('python', buildout['buildout']['python'])
+ options['executable'] = buildout[python]['executable']
+
+ build_ext = {}
+ for be_option in ('include-dirs', 'library-dirs', 'rpath'):
+ value = options.get(be_option)
+ if value is None:
+ continue
+ value = [
+ os.path.join(
+ buildout['buildout']['directory'],
+ v.strip()
+ )
+ for v in value.strip().split('\n')
+ if v.strip()
+ ]
+ build_ext[be_option] = ':'.join(value)
+ options[be_option] = ':'.join(value)
+ self.build_ext = build_ext
+
+ def install(self):
+ if self.buildout['buildout'].get('offline') == 'true':
+ return
+ options = self.options
+ distribution = options.get('eggs', self.name).strip()
+ build_ext = dict([
+ (k, options[k])
+ for k in ('include-dirs', 'library-dirs', 'rpath')
+ if k in options
+ ])
+ zc.buildout.easy_install.build(
+ distribution, options['_d'], self.build_ext,
+ self.links, self.index, options['executable'], [options['_e']],
+ )
+
Added: zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/custom.txt
===================================================================
--- zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/custom.txt 2006-08-09 13:26:53 UTC (rev 69382)
+++ zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/custom.txt 2006-08-09 20:42:27 UTC (rev 69383)
@@ -0,0 +1,114 @@
+Custon eggs
+===========
+
+Sometimes, It's necessary to provide extra control over how an egg is
+created. This is commonly true for eggs with extension modules that
+need to access libraries or include files.
+
+The zc.recipe.egg:custom recipe can be used to define an egg with
+custom build parameters. The currently defined parameters are:
+
+include-dirs
+ A new-line separated list of directories to search for include
+ files.
+
+library-dirs
+ A new-line separated list of directories to search for libraries
+ to link with.
+
+rpath
+ A new-line separated list of directories to search for dynamic libraries
+ at run time.
+
+In addition, the following options can be used to specify the egg:
+
+egg
+ An eggs to install given as a setuptools requirement string.
+ This defaults to the part name.
+
+find-links
+ A list of URLs, files, or directories to search for distributions.
+
+index
+ The URL of an index server, or almost any other valid URL. :)
+
+ If not specified, the Python Package Index,
+ http://cheeseshop.python.org/pypi, is used. You can specify an
+ alternate index with this option. If you use the links option and
+ if the links point to the needed distributions, then the index can
+ be anything and will be largely ignored. In the examples, here,
+ we'll just point to an empty directory on our link server. This
+ will make our examples run a little bit faster.
+
+python
+ The name of a section to get the Python executable from.
+ If not specified, then the buildout python option is used. The
+ Python executable is found in the executable option of the named
+ section.
+
+To illustrate this, we'll define a buildout that builds an egg for a
+package that has a simple extension module::
+
+ #include <Python.h>
+ #include <extdemo.h>
+
+ static PyMethodDef methods[] = {};
+
+ PyMODINIT_FUNC
+ initextdemo(void)
+ {
+ PyObject *d;
+ d = Py_InitModule3("extdemo", methods, "");
+ PyDict_SetItemString(d, "val", PyInt_FromLong(EXTDEMO));
+ }
+
+The extension depends on a system-dependnt include file, extdemo.h,
+that defines a constant, EXTDEMO, that is exposed by the extension.
+
+The extension module is available as a source distribution,
+extdemo-1.4.tar.gz, on a distribution server.
+
+We have a sample buildout that we'll add an include directory to with
+the necessary include file:
+
+ >>> mkdir(sample_buildout, 'include')
+ >>> import os
+ >>> open(os.path.join(sample_buildout, 'include', 'extdemo.h'), 'w').write(
+ ... "#define EXTDEMO 42\n")
+
+We'll also update the buildout configuration file to define a part for
+the egg:
+
+ >>> write(sample_buildout, 'buildout.cfg',
+ ... """
+ ... [buildout]
+ ... parts = extdemo
+ ...
+ ... [extdemo]
+ ... recipe = zc.recipe.egg:custom
+ ... find-links = %(server)s
+ ... index = %(server)s/index
+ ... include-dirs = include
+ ... """ % dict(server=link_server))
+
+ >>> os.chdir(sample_buildout)
+ >>> buildout = os.path.join(sample_buildout, 'bin', 'buildout')
+
+ >>> print system(buildout),
+ zip_safe flag not set; analyzing archive contents...
+
+We got the zip_safe warning because the source distribution we used
+wasn't setuptools based and thus didn't set the option.
+
+The egg is created in the develop-eggs directory *not* the eggs
+directory because it depends on buildout-specific parameters and the
+eggs directory can be shared across multiple buildouts.
+
+ >>> ls(sample_buildout, 'develop-eggs')
+ d extdemo-1.4-py2.4-unix-i686.egg
+ - zc.recipe.egg.egg-link
+
+Note that no scripts or dependencies are installed. To install
+dependencies or scripts for a custom egg, define another part and use
+the zc.recipe.egg recipe, listing the custom egg as one of the eggs to
+be installed. The zc.recipe.egg recipe will use the installed egg.
Property changes on: zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/custom.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Modified: zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/tests.py
===================================================================
--- zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/tests.py 2006-08-09 13:26:53 UTC (rev 69382)
+++ zc.buildout/trunk/zc.recipe.egg_/src/zc/recipe/egg/tests.py 2006-08-09 20:42:27 UTC (rev 69383)
@@ -41,6 +41,16 @@
zc.buildout.testing.multi_python(test)
zc.buildout.testing.setUpServer(test, zc.buildout.testing.make_tree(test))
+
+def setUpCustom(test):
+ zc.buildout.testing.buildoutSetUp(test)
+ open(os.path.join(test.globs['sample_buildout'],
+ 'develop-eggs', 'zc.recipe.egg.egg-link'),
+ 'w').write(dirname(__file__, 4))
+ zc.buildout.testing.create_sample_eggs(test)
+ zc.buildout.testing.add_source_dist(test)
+ zc.buildout.testing.setUpServer(test, zc.buildout.testing.make_tree(test))
+
def test_suite():
return unittest.TestSuite((
@@ -87,7 +97,17 @@
r'/sample-\1/\2'),
(re.compile('\S+sample-(\w+)'), r'/sample-\1'),
]),
- ),
+ ),
+ doctest.DocFileSuite(
+ 'custom.txt',
+ setUp=setUpCustom, tearDown=zc.buildout.testing.buildoutTearDown,
+ checker=renormalizing.RENormalizing([
+ (re.compile("(d ((ext)?demo(needed)?|other)"
+ "-\d[.]\d-py)\d[.]\d(-[^. \t\n]+)?[.]egg"),
+ '\\1V.V.egg'),
+ ]),
+ ),
+
))
if __name__ == '__main__':
More information about the Checkins
mailing list