Commit a4291845 authored by jim's avatar jim

Merged revisions 71277 to 71397 from dev branch:

Feature Changes
---------------

- Variable substitutions now reflect option data written by recipes.

- A part referenced by a part in a parts list is now added to the parts
  list before the referencing part.  This means that you can omit
  parts from the parts list if they are referenced by other parts.

- Added a develop function to the easy_install module to aid in
  creating develop eggs with custom build_ext options.

- The build and develop functions in the easy_install module now
  return the path of the egg or egg link created.

- Removed the limitation that parts named in the install command can
  only name configured parts.

- Removed support ConfigParser-style variable substitutions
  (e.g. %(foo)s). Only the string-template style of variable
  (e.g. ${section:option}) substitutions will be supported.
  Supporting both violates "there's only one way to do it".

- Deprecated the buildout-section extendedBy option.


git-svn-id: http://svn.zope.org/repos/main/zc.buildout/trunk@71398 62d5b8a3-27da-0310-9561-8e5933582275
parent b296bd0d
...@@ -20,9 +20,34 @@ priorities include: ...@@ -20,9 +20,34 @@ priorities include:
Change History Change History
************** **************
1.0.0b12 (2006-10-?) 1.0.0b13 (2006-12-04)
===================== =====================
Feature Changes
---------------
- Variable substitutions now reflect option data written by recipes.
- A part referenced by a part in a parts list is now added to the parts
list before the referencing part. This means that you can omit
parts from the parts list if they are referenced by other parts.
- Added a develop function to the easy_install module to aid in
creating develop eggs with custom build_ext options.
- The build and develop functions in the easy_install module now
return the path of the egg or egg link created.
- Removed the limitation that parts named in the install command can
only name configured parts.
- Removed support ConfigParser-style variable substitutions
(e.g. %(foo)s). Only the string-template style of variable
(e.g. ${section:option}) substitutions will be supported.
Supporting both violates "there's only one way to do it".
- Deprecated the buildout-section extendedBy option.
Bugs Fixed Bugs Fixed
---------- ----------
......
...@@ -25,6 +25,8 @@ setup( ...@@ -25,6 +25,8 @@ setup(
+ '\n' + + '\n' +
read('src', 'zc', 'buildout', 'testing.txt') read('src', 'zc', 'buildout', 'testing.txt')
+ '\n' + + '\n' +
read('src', 'zc', 'buildout', 'easy_install.txt')
+ '\n' +
'Download\n' 'Download\n'
'**********************\n' '**********************\n'
), ),
......
...@@ -22,14 +22,22 @@ import os ...@@ -22,14 +22,22 @@ import os
import pprint import pprint
import re import re
import shutil import shutil
import cStringIO
import sys import sys
import tempfile import tempfile
import ConfigParser import ConfigParser
import UserDict
import pkg_resources import pkg_resources
import zc.buildout import zc.buildout
import zc.buildout.easy_install import zc.buildout.easy_install
try:
realpath = os.path.realpath
except AttributeError:
def realpath(path):
return path
pkg_resources_loc = pkg_resources.working_set.find( pkg_resources_loc = pkg_resources.working_set.find(
pkg_resources.Requirement.parse('setuptools')).location pkg_resources.Requirement.parse('setuptools')).location
...@@ -42,30 +50,11 @@ class MissingSection(zc.buildout.UserError, KeyError): ...@@ -42,30 +50,11 @@ class MissingSection(zc.buildout.UserError, KeyError):
"""A required section is missinh """A required section is missinh
""" """
class Options(dict): def __str__(self):
return "The referenced section, %r, was not defined." % self[0]
def __init__(self, buildout, section, data):
self.buildout = buildout
self.section = section
super(Options, self).__init__(data)
def __getitem__(self, option):
try:
return super(Options, self).__getitem__(option)
except KeyError:
raise MissingOption("Missing option: %s:%s"
% (self.section, option))
# XXX need test
def __setitem__(self, option, value):
if not isinstance(value, str):
raise TypeError('Option values must be strings', value)
super(Options, self).__setitem__(option, value)
def copy(self): class Buildout(UserDict.DictMixin):
return Options(self.buildout, self.section, self)
class Buildout(dict):
def __init__(self, config_file, cloptions, windows_restart=False): def __init__(self, config_file, cloptions, windows_restart=False):
config_file = os.path.abspath(config_file) config_file = os.path.abspath(config_file)
...@@ -75,8 +64,6 @@ class Buildout(dict): ...@@ -75,8 +64,6 @@ class Buildout(dict):
print 'Warning: creating', config_file print 'Warning: creating', config_file
open(config_file, 'w').write('[buildout]\nparts = \n') open(config_file, 'w').write('[buildout]\nparts = \n')
super(Buildout, self).__init__()
# default options # default options
data = dict(buildout={ data = dict(buildout={
'directory': os.path.dirname(config_file), 'directory': os.path.dirname(config_file),
...@@ -110,19 +97,9 @@ class Buildout(dict): ...@@ -110,19 +97,9 @@ class Buildout(dict):
options[option] = value options[option] = value
# The egg dire # The egg dire
# do substitutions self._raw = data
converted = {} self._data = {}
for section, options in data.iteritems(): self._parts = []
for option, value in options.iteritems():
if '$' in value:
value = self._dosubs(section, option, value,
data, converted, [])
options[option] = value
converted[(section, option)] = value
# copy data into self:
for section, options in data.iteritems():
self[section] = Options(self, section, options)
# initialize some attrs and buildout directories. # initialize some attrs and buildout directories.
options = self['buildout'] options = self['buildout']
...@@ -140,72 +117,11 @@ class Buildout(dict): ...@@ -140,72 +117,11 @@ class Buildout(dict):
self._setup_logging() self._setup_logging()
def _dosubs(self, section, option, value, data, converted, seen): offline = options.get('offline', 'false')
key = section, option if offline not in ('true', 'false'):
r = converted.get(key) self._error('Invalid value for offline option: %s', offline)
if r is not None: options['offline'] = offline
return r
if key in seen:
raise zc.buildout.UserError(
"Circular reference in substitutions.\n"
"We're evaluating %s\nand are referencing: %s.\n"
% (", ".join([":".join(k) for k in seen]),
":".join(key)
)
)
seen.append(key)
value = '$$'.join([self._dosubs_esc(s, data, converted, seen)
for s in value.split('$$')
])
seen.pop()
return value
_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
def _dosubs_esc(self, value, data, converted, seen):
value = self._template_split(value)
subs = []
for ref in value[1::2]:
s = tuple(ref[2:-1].split(':'))
if not self._valid(ref):
if len(s) < 2:
raise zc.buildout.UserError("The substitution, %s,\n"
"doesn't contain a colon."
% ref)
if len(s) > 2:
raise zc.buildout.UserError("The substitution, %s,\n"
"has too many colons."
% ref)
if not self._simple(s[0]):
raise zc.buildout.UserError(
"The section name in substitution, %s,\n"
"has invalid characters."
% ref)
if not self._simple(s[1]):
raise zc.buildout.UserError(
"The option name in substitution, %s,\n"
"has invalid characters."
% ref)
v = converted.get(s)
if v is None:
options = data.get(s[0])
if options is None:
raise MissingSection(
"Referenced section does not exist", s[0])
v = options.get(s[1])
if v is None:
raise MissingOption("Referenced option does not exist:",
*s)
if '$' in v:
v = self._dosubs(s[0], s[1], v, data, converted, seen)
options[s[1]] = v
converted[s] = v
subs.append(v)
subs.append('')
return ''.join([''.join(v) for v in zip(value[::2], subs)])
def _buildout_path(self, *names): def _buildout_path(self, *names):
return os.path.join(self._buildout_dir, *names) return os.path.join(self._buildout_dir, *names)
...@@ -268,25 +184,26 @@ class Buildout(dict): ...@@ -268,25 +184,26 @@ class Buildout(dict):
conf_parts = conf_parts and conf_parts.split() or [] conf_parts = conf_parts and conf_parts.split() or []
installed_parts = installed_part_options['buildout']['parts'] installed_parts = installed_part_options['buildout']['parts']
installed_parts = installed_parts and installed_parts.split() or [] installed_parts = installed_parts and installed_parts.split() or []
# If install_parts is given, then they must be listed in parts
# and we don't uninstall anything. Otherwise, we install
# the configured parts and uninstall anything else.
if install_parts: if install_parts:
extra = [p for p in install_parts if p not in conf_parts]
if extra:
self._error(
'Invalid install parts: %s.\n'
'Install parts must be listed in the configuration.',
' '.join(extra))
uninstall_missing = False uninstall_missing = False
else: else:
install_parts = conf_parts install_parts = conf_parts
uninstall_missing = True uninstall_missing = True
# load recipes # load and initialize recipes
recipes = self._load_recipes(install_parts) [self[part]['recipe'] for part in install_parts]
install_parts = self._parts
if self._log_level <= logging.DEBUG:
sections = list(self)
sections.sort()
print
print 'Configuration data:'
for section in self._data:
_save_options(section, self[section], sys.stdout)
print
# compute new part recipe signatures # compute new part recipe signatures
self._compute_part_signatures(install_parts) self._compute_part_signatures(install_parts)
...@@ -339,19 +256,20 @@ class Buildout(dict): ...@@ -339,19 +256,20 @@ class Buildout(dict):
for part in install_parts: for part in install_parts:
signature = self[part].pop('__buildout_signature__') signature = self[part].pop('__buildout_signature__')
saved_options = self[part].copy() saved_options = self[part].copy()
recipe = self[part].recipe
if part in installed_parts: if part in installed_parts:
self._logger.info('Updating %s', part) self._logger.info('Updating %s', part)
old_options = installed_part_options[part] old_options = installed_part_options[part]
old_installed_files = old_options['__buildout_installed__'] old_installed_files = old_options['__buildout_installed__']
try: try:
update = recipes[part].update update = recipe.update
except AttributeError: except AttributeError:
update = recipes[part].install update = recipe.install
self._logger.warning( self._logger.warning(
"The recipe for %s doesn't define an update " "The recipe for %s doesn't define an update "
"method. Using its install method", "method. Using its install method",
part) part)
try: try:
installed_files = update() installed_files = update()
except: except:
...@@ -364,7 +282,7 @@ class Buildout(dict): ...@@ -364,7 +282,7 @@ class Buildout(dict):
else: else:
self._logger.info('Installing %s', part) self._logger.info('Installing %s', part)
installed_files = recipes[part].install() installed_files = recipe.install()
if installed_files is None: if installed_files is None:
self._logger.warning( self._logger.warning(
"The %s install returned None. A path or " "The %s install returned None. A path or "
...@@ -380,15 +298,12 @@ class Buildout(dict): ...@@ -380,15 +298,12 @@ class Buildout(dict):
] = '\n'.join(installed_files) ] = '\n'.join(installed_files)
saved_options['__buildout_signature__'] = signature saved_options['__buildout_signature__'] = signature
if part not in installed_parts: installed_parts = [p for p in installed_parts if p != part]
installed_parts.append(part) installed_parts.append(part)
finally: finally:
installed_part_options['buildout']['parts'] = ' '.join( installed_part_options['buildout']['parts'] = (
[p for p in conf_parts if p in installed_parts] ' '.join(installed_parts))
+
[p for p in installed_parts if p not in conf_parts]
)
installed_part_options['buildout']['installed_develop_eggs' installed_part_options['buildout']['installed_develop_eggs'
] = installed_develop_eggs ] = installed_develop_eggs
...@@ -419,46 +334,8 @@ class Buildout(dict): ...@@ -419,46 +334,8 @@ class Buildout(dict):
try: try:
for setup in develop.split(): for setup in develop.split():
setup = self._buildout_path(setup) setup = self._buildout_path(setup)
if os.path.isdir(setup):
setup = os.path.join(setup, 'setup.py')
self._logger.info("Develop: %s", setup) self._logger.info("Develop: %s", setup)
zc.buildout.easy_install.develop(setup, dest)
fd, tsetup = tempfile.mkstemp()
try:
os.write(fd, runsetup_template % dict(
setuptools=pkg_resources_loc,
setupdir=os.path.dirname(setup),
setup=setup,
__file__ = setup,
))
args = [
zc.buildout.easy_install._safe_arg(tsetup),
'-q', 'develop', '-mxN',
'-f', zc.buildout.easy_install._safe_arg(
' '.join(self._links)
),
'-d', zc.buildout.easy_install._safe_arg(dest),
]
if self._log_level <= logging.DEBUG:
if self._log_level == logging.DEBUG:
del args[1]
else:
args[1] == '-v'
self._logger.debug("in: %s\n%r",
os.path.dirname(setup), args)
assert os.spawnl(
os.P_WAIT, sys.executable, sys.executable,
*args) == 0
finally:
os.close(fd)
os.remove(tsetup)
except: except:
# if we had an error, we need to roll back changes, by # if we had an error, we need to roll back changes, by
# removing any files we created. # removing any files we created.
...@@ -490,90 +367,34 @@ class Buildout(dict): ...@@ -490,90 +367,34 @@ class Buildout(dict):
self._logger.warning( self._logger.warning(
"Unexpected entry, %s, in develop-eggs directory", f) "Unexpected entry, %s, in develop-eggs directory", f)
def _load_recipes(self, parts):
recipes = {}
if not parts:
return recipes
recipes_requirements = []
pkg_resources.working_set.add_entry(
self['buildout']['develop-eggs-directory'])
pkg_resources.working_set.add_entry(self['buildout']['eggs-directory'])
# Gather requirements
for part in parts:
options = self.get(part)
if options is None:
raise MissingSection("No section was specified for part", part)
recipe, entry = self._recipe(part, options)
if recipe not in recipes_requirements:
recipes_requirements.append(recipe)
# Install the recipe distros
offline = self['buildout'].get('offline', 'false')
if offline not in ('true', 'false'):
self._error('Invalid value for offline option: %s', offline)
if offline == 'false':
dest = self['buildout']['eggs-directory']
else:
dest = None
ws = zc.buildout.easy_install.install(
recipes_requirements, dest,
links=self._links,
index=self['buildout'].get('index'),
path=[self['buildout']['develop-eggs-directory'],
self['buildout']['eggs-directory'],
],
working_set=pkg_resources.working_set,
)
# instantiate the recipes
for part in parts:
options = self[part]
recipe, entry = self._recipe(part, options)
recipe_class = pkg_resources.load_entry_point(
recipe, 'zc.buildout', entry)
recipes[part] = recipe_class(self, part, options)
return recipes
def _compute_part_signatures(self, parts): def _compute_part_signatures(self, parts):
# Compute recipe signature and add to options # Compute recipe signature and add to options
for part in parts: for part in parts:
options = self.get(part) options = self.get(part)
if options is None: if options is None:
options = self[part] = {} options = self[part] = {}
recipe, entry = self._recipe(part, options) recipe, entry = _recipe(options)
req = pkg_resources.Requirement.parse(recipe) req = pkg_resources.Requirement.parse(recipe)
sig = _dists_sig(pkg_resources.working_set.resolve([req])) sig = _dists_sig(pkg_resources.working_set.resolve([req]))
options['__buildout_signature__'] = ' '.join(sig) options['__buildout_signature__'] = ' '.join(sig)
def _recipe(self, part, options):
recipe = options['recipe']
if ':' in recipe:
recipe, entry = recipe.split(':')
else:
entry = 'default'
return recipe, entry
def _read_installed_part_options(self): def _read_installed_part_options(self):
old = self._installed_path() old = self._installed_path()
if os.path.isfile(old): if os.path.isfile(old):
parser = ConfigParser.SafeConfigParser(_spacey_defaults) parser = ConfigParser.RawConfigParser()
parser.optionxform = lambda s: s parser.optionxform = lambda s: s
parser.read(old) parser.read(old)
return dict([ result = {}
(section, for section in parser.sections():
Options(self, section, options = {}
[item for item in parser.items(section) for option, value in parser.items(section):
if item[0] not in _spacey_defaults] if '%(' in value:
) for k, v in _spacey_defaults:
) value = value.replace(k, v)
for section in parser.sections()]) options[option] = value
result[section] = Options(self, section, options)
return result
else: else:
return {'buildout': Options(self, 'buildout', {'parts': ''})} return {'buildout': Options(self, 'buildout', {'parts': ''})}
...@@ -592,7 +413,7 @@ class Buildout(dict): ...@@ -592,7 +413,7 @@ class Buildout(dict):
def _install(self, part): def _install(self, part):
options = self[part] options = self[part]
recipe, entry = self._recipe(part, options) recipe, entry = _recipe(options)
recipe_class = pkg_resources.load_entry_point( recipe_class = pkg_resources.load_entry_point(
recipe, 'zc.buildout', entry) recipe, 'zc.buildout', entry)
installed = recipe_class(self, part, options).install() installed = recipe_class(self, part, options).install()
...@@ -642,14 +463,6 @@ class Buildout(dict): ...@@ -642,14 +463,6 @@ class Buildout(dict):
root_logger.setLevel(level) root_logger.setLevel(level)
self._log_level = level self._log_level = level
if level <= logging.DEBUG:
sections = list(self)
sections.sort()
print 'Configuration data:'
for section in sections:
_save_options(section, self[section], sys.stdout)
print
def _maybe_upgrade(self): def _maybe_upgrade(self):
# See if buildout or setuptools need to be upgraded. # See if buildout or setuptools need to be upgraded.
# If they do, do the upgrade and restart the buildout process. # If they do, do the upgrade and restart the buildout process.
...@@ -677,10 +490,25 @@ class Buildout(dict): ...@@ -677,10 +490,25 @@ class Buildout(dict):
if not upgraded: if not upgraded:
return return
if (os.path.abspath(sys.argv[0]) if (realpath(os.path.abspath(sys.argv[0]))
!= os.path.join(os.path.abspath(self['buildout']['bin-directory']), !=
'buildout') realpath(
os.path.join(os.path.abspath(
self['buildout']['bin-directory']
),
'buildout',
)
)
): ):
self._logger.debug("Running %r", realpath(sys.argv[0]))
self._logger.debug(
"Local buildout is %r",
realpath(
os.path.join(
os.path.abspath(self['buildout']['bin-directory']),
'buildout')
)
)
self._logger.warn("Not upgrading because not running a local " self._logger.warn("Not upgrading because not running a local "
"buildout command") "buildout command")
return return
...@@ -745,7 +573,7 @@ class Buildout(dict): ...@@ -745,7 +573,7 @@ class Buildout(dict):
fd, tsetup = tempfile.mkstemp() fd, tsetup = tempfile.mkstemp()
try: try:
os.write(fd, runsetup_template % dict( os.write(fd, zc.buildout.easy_install.runsetup_template % dict(
setuptools=pkg_resources_loc, setuptools=pkg_resources_loc,
setupdir=os.path.dirname(setup), setupdir=os.path.dirname(setup),
setup=setup, setup=setup,
...@@ -758,20 +586,180 @@ class Buildout(dict): ...@@ -758,20 +586,180 @@ class Buildout(dict):
os.close(fd) os.close(fd)
os.remove(tsetup) os.remove(tsetup)
runsetup = setup # backward compat runsetup = setup # backward compat.
def __getitem__(self, section):
try:
return self._data[section]
except KeyError:
pass
try:
data = self._raw[section]
except KeyError:
raise MissingSection(section)
options = Options(self, section, data)
self._data[section] = options
options._initialize()
return options
def __setitem__(self, key, value):
raise NotImplementedError('__setitem__')
def __delitem__(self, key):
raise NotImplementedError('__delitem__')
def keys(self):
return self._raw.keys()
def __iter__(self):
return iter(self._raw)
class Options(UserDict.DictMixin):
def __init__(self, buildout, section, data):
self.buildout = buildout
self.name = section
self._raw = data
self._data = {}
def _initialize(self):
# force substitutions
for k in self._raw:
self.get(k)
recipe = self.get('recipe')
if not recipe:
return
runsetup_template = """ reqs, entry = _recipe(self._data)
import sys req = pkg_resources.Requirement.parse(reqs)
sys.path.insert(0, %(setuptools)r) buildout = self.buildout
import os, setuptools
if pkg_resources.working_set.find(req) is None:
offline = buildout['buildout']['offline'] == 'true'
if offline:
dest = None
path = [buildout['buildout']['develop-eggs-directory'],
buildout['buildout']['eggs-directory'],
]
else:
dest = buildout['buildout']['eggs-directory']
path = [buildout['buildout']['develop-eggs-directory']]
zc.buildout.easy_install.install(
[reqs], dest,
links=buildout._links,
index=buildout['buildout'].get('index'),
path=path,
working_set=pkg_resources.working_set,
)
recipe_class = pkg_resources.load_entry_point(
req.project_name, 'zc.buildout', entry)
__file__ = %(__file__)r self.recipe = recipe_class(buildout, self.name, self)
buildout._parts.append(self.name)
os.chdir(%(setupdir)r) def get(self, option, default=None, seen=None):
sys.argv[0] = %(setup)r try:
execfile(%(setup)r) return self._data[option]
""" except KeyError:
pass
v = self._raw.get(option)
if v is None:
return default
if '${' in v:
key = self.name, option
if seen is None:
seen = [key]
elif key in seen:
raise zc.buildout.UserError(
"Circular reference in substitutions.\n"
"We're evaluating %s\nand are referencing: %s.\n"
% (", ".join([":".join(k) for k in seen]),
":".join(key)
)
)
else:
seen.append(key)
v = '$$'.join([self._sub(s, seen) for s in v.split('$$')])
seen.pop()
self._data[option] = v
return v
_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
def _sub(self, template, seen):
value = self._template_split(template)
subs = []
for ref in value[1::2]:
s = tuple(ref[2:-1].split(':'))
if not self._valid(ref):
if len(s) < 2:
raise zc.buildout.UserError("The substitution, %s,\n"
"doesn't contain a colon."
% ref)
if len(s) > 2:
raise zc.buildout.UserError("The substitution, %s,\n"
"has too many colons."
% ref)
if not self._simple(s[0]):
raise zc.buildout.UserError(
"The section name in substitution, %s,\n"
"has invalid characters."
% ref)
if not self._simple(s[1]):
raise zc.buildout.UserError(
"The option name in substitution, %s,\n"
"has invalid characters."
% ref)
v = self.buildout[s[0]].get(s[1], None, seen)
if v is None:
raise MissingOption("Referenced option does not exist:", *s)
subs.append(v)
subs.append('')
return ''.join([''.join(v) for v in zip(value[::2], subs)])
def __getitem__(self, key):
try:
return self._data[key]
except KeyError:
pass
v = self.get(key)
if v is None:
raise MissingOption("Missing option: %s:%s"
% (self.name, key))
return v
def __setitem__(self, option, value):
if not isinstance(value, str):
raise TypeError('Option values must be strings', value)
self._data[option] = value
def __delitem__(self, key):
if key in self._raw:
del self._raw[key]
if key in self._data:
del self._data[key]
elif key in self._data:
del self._data[key]
else:
raise KeyError, key
def keys(self):
raw = self._raw
return list(self._raw) + [k for k in self._data if k not in raw]
def copy(self):
return dict([(k, self[k]) for k in self.keys()])
_spacey_nl = re.compile('[ \t\r\f\v]*\n[ \t\r\f\v\n]*' _spacey_nl = re.compile('[ \t\r\f\v]*\n[ \t\r\f\v\n]*'
'|' '|'
...@@ -780,6 +768,14 @@ _spacey_nl = re.compile('[ \t\r\f\v]*\n[ \t\r\f\v\n]*' ...@@ -780,6 +768,14 @@ _spacey_nl = re.compile('[ \t\r\f\v]*\n[ \t\r\f\v\n]*'
'[ \t\r\f\v]+$' '[ \t\r\f\v]+$'
) )
_spacey_defaults = [
('%(__buildout_space__)s', ' '),
('%(__buildout_space_n__)s', '\n'),
('%(__buildout_space_r__)s', '\r'),
('%(__buildout_space_f__)s', '\f'),
('%(__buildout_space_v__)s', '\v'),
]
def _quote_spacey_nl(match): def _quote_spacey_nl(match):
match = match.group(0).split('\n', 1) match = match.group(0).split('\n', 1)
result = '\n\t'.join( result = '\n\t'.join(
...@@ -794,20 +790,11 @@ def _quote_spacey_nl(match): ...@@ -794,20 +790,11 @@ def _quote_spacey_nl(match):
) )
return result return result
_spacey_defaults = dict(
__buildout_space__ = ' ',
__buildout_space_r__ = '\r',
__buildout_space_f__ = '\f',
__buildout_space_v__ = '\v',
__buildout_space_n__ = '\n',
)
def _save_options(section, options, f): def _save_options(section, options, f):
print >>f, '[%s]' % section print >>f, '[%s]' % section
items = options.items() items = options.items()
items.sort() items.sort()
for option, value in items: for option, value in items:
value = value.replace('%', '%%')
value = _spacey_nl.sub(_quote_spacey_nl, value) value = _spacey_nl.sub(_quote_spacey_nl, value)
if value.startswith('\n\t'): if value.startswith('\n\t'):
value = '%(__buildout_space_n__)s' + value[2:] value = '%(__buildout_space_n__)s' + value[2:]
...@@ -832,7 +819,7 @@ def _open(base, filename, seen): ...@@ -832,7 +819,7 @@ def _open(base, filename, seen):
result = {} result = {}
parser = ConfigParser.SafeConfigParser() parser = ConfigParser.RawConfigParser()
parser.optionxform = lambda s: s parser.optionxform = lambda s: s
parser.readfp(open(filename)) parser.readfp(open(filename))
extends = extended_by = None extends = extended_by = None
...@@ -850,6 +837,9 @@ def _open(base, filename, seen): ...@@ -850,6 +837,9 @@ def _open(base, filename, seen):
result = _update(_open(base, fname, seen), result) result = _update(_open(base, fname, seen), result)
if extended_by: if extended_by:
self._logger.warn(
"The extendedBy option is deprecated. Stop using it."
)
for fname in extended_by.split(): for fname in extended_by.split():
result = _update(result, _open(base, fname, seen)) result = _update(result, _open(base, fname, seen))
...@@ -887,6 +877,15 @@ def _update(d1, d2): ...@@ -887,6 +877,15 @@ def _update(d1, d2):
d1[section] = d2[section] d1[section] = d2[section]
return d1 return d1
def _recipe(options):
recipe = options['recipe']
if ':' in recipe:
recipe, entry = recipe.split(':')
else:
entry = 'default'
return recipe, entry
def _error(*message): def _error(*message):
sys.stderr.write('Error: ' + ' '.join(message) +'\n') sys.stderr.write('Error: ' + ' '.join(message) +'\n')
sys.exit(1) sys.exit(1)
......
...@@ -286,7 +286,7 @@ buildout: ...@@ -286,7 +286,7 @@ buildout:
>>> os.chdir(sample_buildout) >>> os.chdir(sample_buildout)
>>> buildout = os.path.join(sample_buildout, 'bin', 'buildout') >>> buildout = os.path.join(sample_buildout, 'bin', 'buildout')
>>> print system(buildout), >>> print system(buildout),
buildout: Develop: /sample-buildout/recipes/setup.py buildout: Develop: /sample-buildout/recipes
buildout: Installing data-dir buildout: Installing data-dir
data-dir: Creating directory mystuff data-dir: Creating directory mystuff
...@@ -335,7 +335,7 @@ we'll see that the directory gets removed and recreated: ...@@ -335,7 +335,7 @@ we'll see that the directory gets removed and recreated:
... """) ... """)
>>> print system(buildout), >>> print system(buildout),
buildout: Develop: /sample-buildout/recipes/setup.py buildout: Develop: /sample-buildout/recipes
buildout: Uninstalling data-dir buildout: Uninstalling data-dir
buildout: Installing data-dir buildout: Installing data-dir
data-dir: Creating directory mydata data-dir: Creating directory mydata
...@@ -355,7 +355,7 @@ the part will be reinstalled: ...@@ -355,7 +355,7 @@ the part will be reinstalled:
>>> rmdir(sample_buildout, 'mydata') >>> rmdir(sample_buildout, 'mydata')
>>> print system(buildout), >>> print system(buildout),
buildout: Develop: /sample-buildout/recipes/setup.py buildout: Develop: /sample-buildout/recipes
buildout: Uninstalling data-dir buildout: Uninstalling data-dir
buildout: Installing data-dir buildout: Installing data-dir
data-dir: Creating directory mydata data-dir: Creating directory mydata
...@@ -386,7 +386,7 @@ the directory in: ...@@ -386,7 +386,7 @@ the directory in:
We'll get a user error, not a traceback. We'll get a user error, not a traceback.
>>> print system(buildout), >>> print system(buildout),
buildout: Develop: /sample-buildout/recipes/setup.py buildout: Develop: /sample-buildout/recipes
data-dir: Cannot create /xxx/mydata. /xxx is not a directory. data-dir: Cannot create /xxx/mydata. /xxx is not a directory.
Error: Invalid Path Error: Invalid Path
...@@ -418,9 +418,8 @@ characters, hyphens, and periods. ...@@ -418,9 +418,8 @@ characters, hyphens, and periods.
Variable substitutions Variable substitutions
---------------------- ----------------------
Buildout configuration files support two kinds of substitutions, Buildout configuration files support variable substitution.
standard ConfigParser substitutions, and string-template To illustrate this, we'll create an debug recipe to
substitutions. To illustrate this, we'll create an debug recipe to
allow us to see interactions with the buildout: allow us to see interactions with the buildout:
>>> write(sample_buildout, 'recipes', 'debug.py', >>> write(sample_buildout, 'recipes', 'debug.py',
...@@ -442,10 +441,9 @@ allow us to see interactions with the buildout: ...@@ -442,10 +441,9 @@ allow us to see interactions with the buildout:
... update = install ... update = install
... """) ... """)
In this example, we've used a simple base class that provides a This recipe doesn't actually create anything. The install method
boilerplate constructor. This recipe doesn't actually create doesn't return anything, because it didn't create any files or
anything. The install method doesn't return anything, because it directories.
didn't create any files or directories.
We also have to update our setup script: We also have to update our setup script:
...@@ -478,16 +476,11 @@ examples: ...@@ -478,16 +476,11 @@ examples:
... [debug] ... [debug]
... recipe = recipes:debug ... recipe = recipes:debug
... File 1 = ${data-dir:path}/file ... File 1 = ${data-dir:path}/file
... File 2 = %(File 1)s.out ... File 2 = ${debug:File 1}/log
... File 3 = %(base)s/file3
... File 4 = ${debug:File 3}/log
... ...
... [data-dir] ... [data-dir]
... recipe = recipes:mkdir ... recipe = recipes:mkdir
... path = mydata ... path = mydata
...
... [DEFAULT]
... base = var
... """) ... """)
In this example, we've used ConfigParser substitutions for file2 and In this example, we've used ConfigParser substitutions for file2 and
...@@ -504,18 +497,18 @@ Now, if we run the buildout, we'll see the options with the values ...@@ -504,18 +497,18 @@ Now, if we run the buildout, we'll see the options with the values
substituted. substituted.
>>> print system(buildout), >>> print system(buildout),
buildout: Develop: /sample-buildout/recipes/setup.py buildout: Develop: /sample-buildout/recipes
buildout: Uninstalling data-dir buildout: Uninstalling data-dir
buildout: Installing data-dir buildout: Installing data-dir
data-dir: Creating directory mydata data-dir: Creating directory mydata
buildout: Installing debug buildout: Installing debug
File 1 mydata/file File 1 /sample-buildout/mydata/file
File 2 mydata/file.out File 2 /sample-buildout/mydata/file/log
File 3 var/file3
File 4 var/file3/log
base var
recipe recipes:debug recipe recipes:debug
Note that the substitution of the data-dir path option reflects the
update to the option performed by the mkdir recipe.
It might seem surprising that mydata was created again. This is It might seem surprising that mydata was created again. This is
because we changed our recipes package by adding the debug module. because we changed our recipes package by adding the debug module.
The buildout system didn't know if this module could effect the mkdir The buildout system didn't know if this module could effect the mkdir
...@@ -523,14 +516,11 @@ recipe, so it assumed it could and reinstalled mydata. If we rerun ...@@ -523,14 +516,11 @@ recipe, so it assumed it could and reinstalled mydata. If we rerun
the buildout: the buildout:
>>> print system(buildout), >>> print system(buildout),
buildout: Develop: /sample-buildout/recipes/setup.py buildout: Develop: /sample-buildout/recipes
buildout: Updating data-dir buildout: Updating data-dir
buildout: Updating debug buildout: Updating debug
File 1 mydata/file File 1 /sample-buildout/mydata/file
File 2 mydata/file.out File 2 /sample-buildout/mydata/file/log
File 3 var/file3
File 4 var/file3/log
base var
recipe recipes:debug recipe recipes:debug
We can see that mydata was not recreated. We can see that mydata was not recreated.
...@@ -542,23 +532,96 @@ Section and option names in variable substitutions are only allowed to ...@@ -542,23 +532,96 @@ Section and option names in variable substitutions are only allowed to
contain alphanumeric characters, hyphens, periods and spaces. This contain alphanumeric characters, hyphens, periods and spaces. This
restriction might be relaxed in future releases. restriction might be relaxed in future releases.
Multiple configuration files
----------------------------
You can use multiple configuration files. From your main Automatic part selection and ordering
configuration file, you can include other configuration files in 2 -------------------------------------
ways:
When a section with a recipe is refered to, either through variable
substitution or by an initializing recipe, the section is treated as a
part and added to the part list before the referencing part. For
example, we can leave data-dir out of the parts list:
>>> write(sample_buildout, 'buildout.cfg',
... """
... [buildout]
... develop = recipes
... parts = debug
... log-level = INFO
...
... [debug]
... recipe = recipes:debug
... File 1 = ${data-dir:path}/file
... File 2 = ${debug:File 1}/log
...
... [data-dir]
... recipe = recipes:mkdir
... path = mydata
... """)
It will still be treated as a part:
>>> print system(buildout),
buildout: Develop: /sample-buildout/recipes
buildout: Updating data-dir
buildout: Updating debug
File 1 /sample-buildout/mydata/file
File 2 /sample-buildout/mydata/file/log
recipe recipes:debug
>>> cat('.installed.cfg') # doctest: +ELLIPSIS
[buildout]
installed_develop_eggs = /sample-buildout/develop-eggs/recipes.egg-link
parts = data-dir debug
...
Note that the data-dir part is included *before* the debug part,
because the debug part refers to the data-dir part. Even if we list
the data-dir part after the debug part, it will be included before:
>>> write(sample_buildout, 'buildout.cfg',
... """
... [buildout]
... develop = recipes
... parts = debug data-dir
... log-level = INFO
...
... [debug]
... recipe = recipes:debug
... File 1 = ${data-dir:path}/file
... File 2 = ${debug:File 1}/log
...
... [data-dir]
... recipe = recipes:mkdir
... path = mydata
... """)
- Your configuration file can "extend" another configuration file. It will still be treated as a part:
Option are read from the other configuration file if they aren't
already defined by your configuration file.
- Your configuration file can be "extended-by" another configuration >>> print system(buildout),
file, In this case, the options in the other configuration file buildout: Develop: /sample-buildout/recipes
override options in your configuration file. buildout: Updating data-dir
buildout: Updating debug
File 1 /sample-buildout/mydata/file
File 2 /sample-buildout/mydata/file/log
recipe recipes:debug
The configuration files your file extends or is extended by can extend >>> cat('.installed.cfg') # doctest: +ELLIPSIS
or be extended by other configuration files. The same file may be [buildout]
installed_develop_eggs = /sample-buildout/develop-eggs/recipes.egg-link
parts = data-dir debug
...
Multiple configuration files
----------------------------
A configuration file can "extend" another configuration file.
Options are read from the other configuration file if they aren't
already defined by your configuration file.
The configuration files your file extends can extend
other configuration files. The same file may be
used more than once although, of course, cycles aren't allowed. used more than once although, of course, cycles aren't allowed.
To see how this works, we use an example: To see how this works, we use an example:
...@@ -584,7 +647,7 @@ To see how this works, we use an example: ...@@ -584,7 +647,7 @@ To see how this works, we use an example:
... """) ... """)
>>> print system(buildout), >>> print system(buildout),
buildout: Develop: /sample-buildout/recipes/setup.py buildout: Develop: /sample-buildout/recipes
buildout: Uninstalling debug buildout: Uninstalling debug
buildout: Uninstalling data-dir buildout: Uninstalling data-dir
buildout: Installing debug buildout: Installing debug
...@@ -598,20 +661,16 @@ customization. ...@@ -598,20 +661,16 @@ customization.
Here is a more elaborate example. Here is a more elaborate example.
>>> extensions = tmpdir('extensions') >>> other = tmpdir('other')
>>> write(sample_buildout, 'buildout.cfg', >>> write(sample_buildout, 'buildout.cfg',
... """ ... """
... [buildout] ... [buildout]
... extends = b1.cfg b2.cfg ... extends = b1.cfg b2.cfg %(b3)s
... extended-by = e1.cfg %(e2)s
... ...
... [debug] ... [debug]
... op = %%(name)s ... op = buildout
... ... """ % dict(b3=os.path.join(other, 'b3.cfg')))
... [DEFAULT]
... name = buildout
... """ % dict(e2=os.path.join(extensions, 'e2.cfg')))
>>> write(sample_buildout, 'b1.cfg', >>> write(sample_buildout, 'b1.cfg',
... """ ... """
...@@ -619,12 +678,8 @@ Here is a more elaborate example. ...@@ -619,12 +678,8 @@ Here is a more elaborate example.
... extends = base.cfg ... extends = base.cfg
... ...
... [debug] ... [debug]
... op1 = %(name)s 1 ... op1 = b1 1
... op2 = %(name)s 2 ... op2 = b1 2
... op3 = %(name)s 3
...
... [DEFAULT]
... name = b1
... """) ... """)
>>> write(sample_buildout, 'b2.cfg', >>> write(sample_buildout, 'b2.cfg',
...@@ -633,90 +688,57 @@ Here is a more elaborate example. ...@@ -633,90 +688,57 @@ Here is a more elaborate example.
... extends = base.cfg ... extends = base.cfg
... ...
... [debug] ... [debug]
... op3 = %(name)s 3 ... op2 = b2 2
... op4 = %(name)s 4 ... op3 = b2 3
... op5 = %(name)s 5
...
... [DEFAULT]
... name = b2
... """) ... """)
>>> write(sample_buildout, 'base.cfg', >>> write(other, 'b3.cfg',
... """ ... """
... [buildout] ... [buildout]
... develop = recipes ... extends = b3base.cfg
... parts = debug
... ...
... [debug] ... [debug]
... recipe = recipes:debug ... op4 = b3 4
... name = base
... """) ... """)
>>> write(sample_buildout, 'e1.cfg', >>> write(other, 'b3base.cfg',
... """ ... """
... [debug] ... [debug]
... op1 = %(name)s 1 ... op5 = b3base 5
...
... [DEFAULT]
... name = e1
... """) ... """)
>>> write(extensions, 'e2.cfg', >>> write(sample_buildout, 'base.cfg',
... """ ... """
... [buildout] ... [buildout]
... extends = eb.cfg ... develop = recipes
... extended-by = ee.cfg ... parts = debug
... """)
>>> write(extensions, 'eb.cfg',
... """
... [debug]
... op5 = %(name)s 5
... ...
... [DEFAULT]
... name = eb
... """)
>>> write(extensions, 'ee.cfg',
... """
... [debug] ... [debug]
... op6 = %(name)s 6 ... recipe = recipes:debug
... ... name = base
... [DEFAULT]
... name = ee
... """) ... """)
>>> print system(buildout), >>> print system(buildout),
buildout: Develop: /sample-buildout/recipes/setup.py buildout: Develop: /sample-buildout/recipes
buildout: Uninstalling debug buildout: Uninstalling debug
buildout: Installing debug buildout: Installing debug
name ee name base
op buildout op buildout
op1 e1 1 op1 b1 1
op2 b1 2 op2 b2 2
op3 b2 3 op3 b2 3
op4 b2 4 op4 b3 4
op5 eb 5 op5 b3base 5
op6 ee 6
recipe recipes:debug recipe recipes:debug
There are several things to note about this example: There are several things to note about this example:
- We can name multiple files in an extends or extended-by option. - We can name multiple files in an extends option.
- We can reference files recursively. - We can reference files recursively.
- DEFAULT sections only directly affect the configuration file they're - Relative file names in extended options are interpreted relative to
used in, but they can have secondary effects. For example, the name the directory containing the referencing configuration file.
option showed up in the debug section because it was defined in the
debug sections in several of the input files by virtue of being in
their DEFAULT sections.
- Relative file names in extended and extended-by options are
interpreted relative to the directory containing the referencing
configuration file. The files eb.cfg and ee.cfg were found in the
extensions directory because they were referenced from a file in
that directory.
User defaults User defaults
------------- -------------
...@@ -737,17 +759,16 @@ delimiter.) ...@@ -737,17 +759,16 @@ delimiter.)
>>> os.environ['HOME'] = home >>> os.environ['HOME'] = home
>>> print system(buildout), >>> print system(buildout),
buildout: Develop: /sample-buildout/recipes/setup.py buildout: Develop: /sample-buildout/recipes
buildout: Uninstalling debug buildout: Uninstalling debug
buildout: Installing debug buildout: Installing debug
name ee name base
op buildout op buildout
op1 e1 1 op1 b1 1
op2 b1 2 op2 b2 2
op3 b2 3 op3 b2 3
op4 b2 4 op4 b3 4
op5 eb 5 op5 b3base 5
op6 ee 6
op7 7 op7 7
recipe recipes:debug recipe recipes:debug
...@@ -765,16 +786,13 @@ set the logging level to WARNING ...@@ -765,16 +786,13 @@ set the logging level to WARNING
... [buildout] ... [buildout]
... log-level = WARNING ... log-level = WARNING
... extends = b1.cfg b2.cfg ... extends = b1.cfg b2.cfg
... extended-by = e1.cfg
... """) ... """)
>>> print system(buildout), >>> print system(buildout),
name e1 name base
op1 e1 1 op1 b1 1
op2 b1 2 op2 b2 2
op3 b2 3 op3 b2 3
op4 b2 4
op5 b2 5
recipe recipes:debug recipe recipes:debug
Command-line usage Command-line usage
...@@ -821,7 +839,7 @@ Note that we used the installed buildout option to specify an ...@@ -821,7 +839,7 @@ Note that we used the installed buildout option to specify an
alternate file to store information about installed parts. alternate file to store information about installed parts.
>>> print system(buildout+' -c other.cfg debug:op1=foo -v'), >>> print system(buildout+' -c other.cfg debug:op1=foo -v'),
buildout: Develop: /sample-buildout/recipes/setup.py buildout: Develop: /sample-buildout/recipes
buildout: Installing debug buildout: Installing debug
name other name other
op1 foo op1 foo
...@@ -834,7 +852,7 @@ WARNING. ...@@ -834,7 +852,7 @@ WARNING.
Options can also be combined in the usual Unix way, as in: Options can also be combined in the usual Unix way, as in:
>>> print system(buildout+' -vcother.cfg debug:op1=foo'), >>> print system(buildout+' -vcother.cfg debug:op1=foo'),
buildout: Develop: /sample-buildout/recipes/setup.py buildout: Develop: /sample-buildout/recipes
buildout: Updating debug buildout: Updating debug
name other name other
op1 foo op1 foo
...@@ -876,7 +894,7 @@ the buildout in the usual way: ...@@ -876,7 +894,7 @@ the buildout in the usual way:
... """) ... """)
>>> print system(buildout), >>> print system(buildout),
buildout: Develop: /sample-buildout/recipes/setup.py buildout: Develop: /sample-buildout/recipes
buildout: Uninstalling debug buildout: Uninstalling debug
buildout: Installing debug buildout: Installing debug
recipe recipes:debug recipe recipes:debug
...@@ -898,7 +916,6 @@ the buildout in the usual way: ...@@ -898,7 +916,6 @@ the buildout in the usual way:
d d2 d d2
d d3 d d3
d develop-eggs d develop-eggs
- e1.cfg
d eggs d eggs
d parts d parts
d recipes d recipes
...@@ -959,7 +976,7 @@ Now we'll update our configuration file: ...@@ -959,7 +976,7 @@ Now we'll update our configuration file:
and run the buildout specifying just d3 and d4: and run the buildout specifying just d3 and d4:
>>> print system(buildout+' install d3 d4'), >>> print system(buildout+' install d3 d4'),
buildout: Develop: /sample-buildout/recipes/setup.py buildout: Develop: /sample-buildout/recipes
buildout: Uninstalling d3 buildout: Uninstalling d3
buildout: Installing d3 buildout: Installing d3
d3: Creating directory data3 d3: Creating directory data3
...@@ -978,7 +995,6 @@ and run the buildout specifying just d3 and d4: ...@@ -978,7 +995,6 @@ and run the buildout specifying just d3 and d4:
d data3 d data3
d data4 d data4
d develop-eggs d develop-eggs
- e1.cfg
d eggs d eggs
d parts d parts
d recipes d recipes
...@@ -991,13 +1007,19 @@ The .installed.cfg is only updated for the recipes that ran: ...@@ -991,13 +1007,19 @@ 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 installed_develop_eggs = /sample-buildout/develop-eggs/recipes.egg-link
parts = debug d2 d3 d4 d1 parts = debug d1 d2 d3 d4
<BLANKLINE> <BLANKLINE>
[debug] [debug]
__buildout_installed__ = __buildout_installed__ =
__buildout_signature__ = recipes-PiIFiO8ny5yNZ1S3JfT0xg== __buildout_signature__ = recipes-PiIFiO8ny5yNZ1S3JfT0xg==
recipe = recipes:debug recipe = recipes:debug
<BLANKLINE> <BLANKLINE>
[d1]
__buildout_installed__ = /sample-buildout/d1
__buildout_signature__ = recipes-PiIFiO8ny5yNZ1S3JfT0xg==
path = /sample-buildout/d1
recipe = recipes:mkdir
<BLANKLINE>
[d2] [d2]
__buildout_installed__ = /sample-buildout/d2 __buildout_installed__ = /sample-buildout/d2
__buildout_signature__ = recipes-PiIFiO8ny5yNZ1S3JfT0xg== __buildout_signature__ = recipes-PiIFiO8ny5yNZ1S3JfT0xg==
...@@ -1015,12 +1037,6 @@ The .installed.cfg is only updated for the recipes that ran: ...@@ -1015,12 +1037,6 @@ The .installed.cfg is only updated for the recipes that ran:
__buildout_signature__ = recipes-PiIFiO8ny5yNZ1S3JfT0xg== __buildout_signature__ = recipes-PiIFiO8ny5yNZ1S3JfT0xg==
path = /sample-buildout/data4 path = /sample-buildout/data4
recipe = recipes:mkdir recipe = recipes:mkdir
<BLANKLINE>
[d1]
__buildout_installed__ = /sample-buildout/d1
__buildout_signature__ = recipes-PiIFiO8ny5yNZ1S3JfT0xg==
path = /sample-buildout/d1
recipe = recipes:mkdir
Note that the installed data for debug, d1, and d2 haven't changed, Note that the installed data for debug, d1, and d2 haven't changed,
because we didn't install those parts and that the d1 and d2 because we didn't install those parts and that the d1 and d2
...@@ -1029,9 +1045,9 @@ directories are still there. ...@@ -1029,9 +1045,9 @@ directories are still there.
Now, if we run the buildout without the install command: Now, if we run the buildout without the install command:
>>> print system(buildout), >>> print system(buildout),
buildout: Develop: /sample-buildout/recipes/setup.py buildout: Develop: /sample-buildout/recipes
buildout: Uninstalling d1
buildout: Uninstalling d2 buildout: Uninstalling d2
buildout: Uninstalling d1
buildout: Uninstalling debug buildout: Uninstalling debug
buildout: Installing debug buildout: Installing debug
recipe recipes:debug recipe recipes:debug
...@@ -1055,7 +1071,6 @@ also see that d1 and d2 have gone away: ...@@ -1055,7 +1071,6 @@ also see that d1 and d2 have gone away:
d data3 d data3
d data4 d data4
d develop-eggs d develop-eggs
- e1.cfg
d eggs d eggs
d parts d parts
d recipes d recipes
...@@ -1090,7 +1105,7 @@ provide alternate locations, and even names for these directories. ...@@ -1090,7 +1105,7 @@ provide alternate locations, and even names for these directories.
buildout: Creating directory /sample-alt/work buildout: Creating directory /sample-alt/work
buildout: Creating directory /sample-alt/basket buildout: Creating directory /sample-alt/basket
buildout: Creating directory /sample-alt/developbasket buildout: Creating directory /sample-alt/developbasket
buildout: Develop: /sample-buildout/recipes/setup.py buildout: Develop: /sample-buildout/recipes
buildout: Uninstalling d4 buildout: Uninstalling d4
buildout: Uninstalling d3 buildout: Uninstalling d3
buildout: Uninstalling d2 buildout: Uninstalling d2
...@@ -1126,7 +1141,7 @@ You can also specify an alternate buildout directory: ...@@ -1126,7 +1141,7 @@ You can also specify an alternate buildout directory:
buildout: Creating directory /sample-alt/parts buildout: Creating directory /sample-alt/parts
buildout: Creating directory /sample-alt/eggs buildout: Creating directory /sample-alt/eggs
buildout: Creating directory /sample-alt/develop-eggs buildout: Creating directory /sample-alt/develop-eggs
buildout: Develop: /sample-buildout/recipes/setup.py buildout: Develop: /sample-buildout/recipes
>>> ls(alt) >>> ls(alt)
- .installed.cfg - .installed.cfg
...@@ -1162,12 +1177,11 @@ of changing the format: ...@@ -1162,12 +1177,11 @@ of changing the format:
... parts = ... parts =
... log-level = 25 ... log-level = 25
... verbosity = 5 ... verbosity = 5
... log-format = %%(levelname)s %%(message)s ... log-format = %(levelname)s %(message)s
... """) ... """)
Here, we've changed the format to include the log-level name, rather Here, we've changed the format to include the log-level name, rather
than the logger name. Note that we had to double percent signs, than the logger name.
because configuration options allow ConfigParser variable substitution.
We've also illustrated, with a contrived example, that the log level We've also illustrated, with a contrived example, that the log level
can be a numeric value and that the verbosity can be specified in the can be a numeric value and that the verbosity can be specified in the
...@@ -1175,7 +1189,7 @@ configuration file. Because the verbosity is subtracted from the log ...@@ -1175,7 +1189,7 @@ configuration file. Because the verbosity is subtracted from the log
level, we get a final log level of 20, which is the INFO level. level, we get a final log level of 20, which is the INFO level.
>>> print system(buildout), >>> print system(buildout),
INFO Develop: /sample-buildout/recipes/setup.py INFO Develop: /sample-buildout/recipes
Predefined buildout options Predefined buildout options
--------------------------- ---------------------------
...@@ -1193,6 +1207,11 @@ database is shown. ...@@ -1193,6 +1207,11 @@ database is shown.
... """) ... """)
>>> print system(buildout+' -v'), >>> print system(buildout+' -v'),
zc.buildout.easy_install: Installing ['zc.buildout', 'setuptools']
zc.buildout.easy_install: We have a develop egg for zc.buildout
zc.buildout.easy_install: We have the best distribution that satisfies
setuptools
<BLANKLINE>
Configuration data: Configuration data:
[buildout] [buildout]
bin-directory = /sample-buildout/bin bin-directory = /sample-buildout/bin
...@@ -1201,18 +1220,15 @@ database is shown. ...@@ -1201,18 +1220,15 @@ database is shown.
eggs-directory = /sample-buildout/eggs eggs-directory = /sample-buildout/eggs
executable = /usr/local/bin/python2.3 executable = /usr/local/bin/python2.3
installed = /sample-buildout/.installed.cfg installed = /sample-buildout/.installed.cfg
log-format = %%(name)s: %%(message)s log-format = %(name)s: %(message)s
log-level = INFO log-level = INFO
offline = false
parts = parts =
parts-directory = /sample-buildout/parts parts-directory = /sample-buildout/parts
python = buildout python = buildout
verbosity = 10 verbosity = 10
<BLANKLINE> <BLANKLINE>
zc.buildout.easy_install: Installing ['zc.buildout', 'setuptools']
zc.buildout.easy_install: We have a develop egg for zc.buildout
zc.buildout.easy_install: We have the best distribution that satisfies
setuptools
All of these options can be overridden by configuration files or by All of these options can be overridden by configuration files or by
command-line assignments. We've discussed most of these options command-line assignments. We've discussed most of these options
already, but let's review them and touch on some we haven't discussed: already, but let's review them and touch on some we haven't discussed:
...@@ -1377,7 +1393,7 @@ egg to be built: ...@@ -1377,7 +1393,7 @@ egg to be built:
>>> os.chdir(sample_bootstrapped) >>> os.chdir(sample_bootstrapped)
>>> print system(os.path.join(sample_bootstrapped, 'bin', 'buildout')), >>> print system(os.path.join(sample_bootstrapped, 'bin', 'buildout')),
buildout: Develop: /sample-bootstrapped/demo/setup.py buildout: Develop: /sample-bootstrapped/demo
Now we can add the extensions option. We were a bit tricly and ran Now we can add the extensions option. We were a bit tricly and ran
the buildout once with the demo develop egg defined but without the the buildout once with the demo develop egg defined but without the
...@@ -1398,7 +1414,7 @@ We see that out extension is loaded and executed: ...@@ -1398,7 +1414,7 @@ We see that out extension is loaded and executed:
>>> print system(os.path.join(sample_bootstrapped, 'bin', 'buildout')), >>> print system(os.path.join(sample_bootstrapped, 'bin', 'buildout')),
ext ['buildout'] ext ['buildout']
buildout: Develop: /sample-bootstrapped/demo/setup.py buildout: Develop: /sample-bootstrapped/demo
......
...@@ -34,10 +34,13 @@ logger = logging.getLogger('zc.buildout.easy_install') ...@@ -34,10 +34,13 @@ logger = logging.getLogger('zc.buildout.easy_install')
url_match = re.compile('[a-z0-9+.-]+://').match url_match = re.compile('[a-z0-9+.-]+://').match
setuptools_loc = pkg_resources.working_set.find(
pkg_resources.Requirement.parse('setuptools')
).location
# Include buildout and setuptools eggs in paths # Include buildout and setuptools eggs in paths
buildout_and_setuptools_path = [ buildout_and_setuptools_path = [
pkg_resources.working_set.find( setuptools_loc,
pkg_resources.Requirement.parse('setuptools')).location,
pkg_resources.working_set.find( pkg_resources.working_set.find(
pkg_resources.Requirement.parse('zc.buildout')).location, pkg_resources.Requirement.parse('zc.buildout')).location,
] ]
...@@ -404,12 +407,15 @@ def build(spec, dest, build_ext, ...@@ -404,12 +407,15 @@ def build(spec, dest, build_ext,
dist = _satisfied(requirement, env, dest, executable, index_url, links) dist = _satisfied(requirement, env, dest, executable, index_url, links)
if dist is not None: if dist is not None:
return dist return [dist.location]
# Get an editable version of the package to a temporary directory: undo = []
tmp = tempfile.mkdtemp('editable')
tmp2 = tempfile.mkdtemp('editable')
try: try:
tmp = tempfile.mkdtemp('build')
undo.append(lambda : shutil.rmtree(tmp))
tmp2 = tempfile.mkdtemp('build')
undo.append(lambda : shutil.rmtree(tmp2))
index = _get_index(executable, index_url, links) index = _get_index(executable, index_url, links)
dist = index.fetch_distribution(requirement, tmp2, False, True) dist = index.fetch_distribution(requirement, tmp2, False, True)
if dist is None: if dist is None:
...@@ -442,13 +448,105 @@ def build(spec, dest, build_ext, ...@@ -442,13 +448,105 @@ def build(spec, dest, build_ext,
setuptools.command.setopt.edit_config( setuptools.command.setopt.edit_config(
setup_cfg, dict(build_ext=build_ext)) setup_cfg, dict(build_ext=build_ext))
# Now run easy_install for real: tmp3 = tempfile.mkdtemp('build', dir=dest)
undo.append(lambda : shutil.rmtree(tmp3))
_call_easy_install(base, env, pkg_resources.WorkingSet(), _call_easy_install(base, env, pkg_resources.WorkingSet(),
dest, links, index_url, executable, True) tmp3, links, index_url, executable, True)
return _copyeggs(tmp3, dest, '.egg', undo)
finally: finally:
shutil.rmtree(tmp) undo.reverse()
shutil.rmtree(tmp2) [f() for f in undo]
def _rm(*paths):
for path in paths:
if os.path.isdir(path):
shutil.rmtree(path)
elif os.path.exists(path):
os.remove(path)
def _copyeggs(src, dest, suffix, undo):
result = []
undo.append(lambda : _rm(*result))
for name in os.listdir(src):
if name.endswith(suffix):
new = os.path.join(dest, name)
_rm(new)
os.rename(os.path.join(src, name), new)
result.append(new)
assert len(result) == 1
undo.pop()
return result[0]
def develop(setup, dest,
build_ext=None,
executable=sys.executable):
if os.path.isdir(setup):
directory = setup
setup = os.path.join(directory, 'setup.py')
else:
directory = os.path.dirname(setup)
undo = []
try:
if build_ext:
setup_cfg = os.path.join(directory, 'setup.cfg')
if os.path.exists(setup_cfg):
os.rename(setup_cfg, setup_cfg+'-develop-aside')
def restore_old_setup():
if os.path.exists(setup_cfg):
os.remove(setup_cfg)
os.rename(setup_cfg+'-develop-aside', setup_cfg)
undo.append(restore_old_setup)
else:
open(setup_cfg, 'w')
undo.append(lambda: os.remove(setup_cfg))
setuptools.command.setopt.edit_config(
setup_cfg, dict(build_ext=build_ext))
fd, tsetup = tempfile.mkstemp()
undo.append(lambda: os.remove(tsetup))
undo.append(lambda: os.close(fd))
os.write(fd, runsetup_template % dict(
setuptools=setuptools_loc,
setupdir=directory,
setup=setup,
__file__ = setup,
))
tmp3 = tempfile.mkdtemp('build', dir=dest)
undo.append(lambda : shutil.rmtree(tmp3))
args = [
zc.buildout.easy_install._safe_arg(tsetup),
'-q', 'develop', '-mxN',
'-d', _safe_arg(tmp3),
]
log_level = logger.getEffectiveLevel()
if log_level <= logging.DEBUG:
if log_level == logging.DEBUG:
del args[1]
else:
args[1] == '-v'
logger.debug("in: %s\n%r", directory, args)
assert os.spawnl(os.P_WAIT, executable, executable, *args) == 0
return _copyeggs(tmp3, dest, '.egg-link', undo)
finally:
undo.reverse()
[f() for f in undo]
def working_set(specs, executable, path): def working_set(specs, executable, path):
return install(specs, None, executable=executable, path=path) return install(specs, None, executable=executable, path=path)
...@@ -595,6 +693,15 @@ if _interactive: ...@@ -595,6 +693,15 @@ if _interactive:
import code import code
code.interact(banner="", local=globals()) code.interact(banner="", local=globals())
''' '''
runsetup_template = """
import sys
sys.path.insert(0, %(setuptools)r)
import os, setuptools
__file__ = %(__file__)r
os.chdir(%(setupdir)r)
sys.argv[0] = %(setup)r
execfile(%(setup)r)
"""
Minimal Python interface to easy_install Python API for egg and script installation
======================================== ==========================================
The easy_install module provides a minimal interface to the setuptools The easy_install module provides some functions to provide support for
easy_install command that provides some additional semantics: egg and script installation. It provides functionality at the python
level that is similar to easy_install, with a few exceptions:
- By default, we look for new packages *and* the packages that - By default, we look for new packages *and* the packages that
they depend on. This is somewhat like (and uses) the --upgrade they depend on. This is somewhat like (and uses) the --upgrade
...@@ -21,8 +22,8 @@ easy_install command that provides some additional semantics: ...@@ -21,8 +22,8 @@ easy_install command that provides some additional semantics:
- Distutils options for building extensions can be passed. - Distutils options for building extensions can be passed.
The easy_install module provides a method, install, for installing one The easy_install module provides a method, install, for installing one
or more packages and their dependencies. The or more packages and their dependencies. The install function takes 2
install function takes 2 positional arguments: positional arguments:
- An iterable of setuptools requirement strings for the distributions - An iterable of setuptools requirement strings for the distributions
to be installed, and to be installed, and
...@@ -426,18 +427,18 @@ You can also pass script initialization code: ...@@ -426,18 +427,18 @@ You can also pass script initialization code:
eggrecipedemo.main(1, 2) eggrecipedemo.main(1, 2)
Handling custom build options for extensions Handling custom build options for extensions provided in source distributions
-------------------------------------------- -----------------------------------------------------------------------------
Sometimes, we need to control how extension modules are built. The Sometimes, we need to control how extension modules are built. The
build method provides this level of control. It takes a single build function provides this level of control. It takes a single
package specification, downloads a source distribution, and builds it package specification, downloads a source distribution, and builds it
with specified custom build options. with specified custom build options.
The build method takes 3 positional arguments: The build function takes 3 positional arguments:
spec spec
A package specification A package specification for a source distribution
dest dest
A destination directory A destination directory
...@@ -486,9 +487,13 @@ extension, extdemo.c:: ...@@ -486,9 +487,13 @@ extension, extdemo.c::
PyMODINIT_FUNC PyMODINIT_FUNC
initextdemo(void) initextdemo(void)
{ {
PyObject *d; PyObject *m;
d = Py_InitModule3("extdemo", methods, ""); m = Py_InitModule3("extdemo", methods, "");
PyDict_SetItemString(d, "val", PyInt_FromLong(EXTDEMO)); #ifdef TWO
PyModule_AddObject(m, "val", PyInt_FromLong(2));
#else
PyModule_AddObject(m, "val", PyInt_FromLong(EXTDEMO));
#endif
} }
The extension depends on a system-dependnt include file, extdemo.h, The extension depends on a system-dependnt include file, extdemo.h,
...@@ -497,9 +502,11 @@ that defines a constant, EXTDEMO, that is exposed by the extension. ...@@ -497,9 +502,11 @@ that defines a constant, EXTDEMO, that is exposed by the extension.
We'll add an include directory to our sample buildout and add the We'll add an include directory to our sample buildout and add the
needed include file to it: needed include file to it:
>>> mkdir(sample_buildout, 'include') >>> mkdir('include')
>>> open(os.path.join(sample_buildout, 'include', 'extdemo.h'), 'w').write( >>> write('include', 'extdemo.h',
... "#define EXTDEMO 42\n") ... """
... #define EXTDEMO 42
... """)
Now, we can use the build function to create an egg from the source Now, we can use the build function to create an egg from the source
distribution: distribution:
...@@ -508,6 +515,9 @@ distribution: ...@@ -508,6 +515,9 @@ distribution:
... 'extdemo', dest, ... 'extdemo', dest,
... {'include-dirs': os.path.join(sample_buildout, 'include')}, ... {'include-dirs': os.path.join(sample_buildout, 'include')},
... links=[link_server], index=link_server+'index/') ... links=[link_server], index=link_server+'index/')
'/sample-install/extdemo-1.4-py2.4-unix-i686.egg'
The function returns the list of eggs
Now if we look in our destination directory, we see we have an extdemo egg: Now if we look in our destination directory, we see we have an extdemo egg:
...@@ -516,3 +526,68 @@ Now if we look in our destination directory, we see we have an extdemo egg: ...@@ -516,3 +526,68 @@ Now if we look in our destination directory, we see we have an extdemo egg:
d demoneeded-1.1-py2.4.egg d demoneeded-1.1-py2.4.egg
d extdemo-1.4-py2.4-unix-i686.egg d extdemo-1.4-py2.4-unix-i686.egg
Handling custom build options for extensions in develop eggs
------------------------------------------------------------
The develop function is similar to the build function, except that,
rather than building an egg from a source directory containing a
setup.py script.
The develop function takes 2 positional arguments:
setup
The path to a setup script, typically named "setup.py", or a
directory containing a setup.py script.
dest
The directory to install the egg link to
It supports some optional keyword argument:
build_ext
A dictionary of options to be passed to the distutils build_ext
command when building extensions.
executable
A path to a Python executable. Distributions will ne installed
using this executable and will be for the matching Python version.
We have a local directory containing the extdemo source:
>>> ls(extdemo)
- MANIFEST
- MANIFEST.in
- README
- extdemo.c
- setup.py
Now, we can use the develop function to create a develop egg from the source
distribution:
>>> zc.buildout.easy_install.develop(
... extdemo, dest,
... {'include-dirs': os.path.join(sample_buildout, 'include')})
'/sample-install/extdemo.egg-link'
The name of the egg link created is returned.
Now if we look in our destination directory, we see we have an extdemo
egg link:
>>> ls(dest)
d demo-0.3-py2.4.egg
d demoneeded-1.1-py2.4.egg
d extdemo-1.4-py2.4-linux-i686.egg
- extdemo.egg-link
And that the source directory contains the compiled extension:
>>> ls(extdemo)
- MANIFEST
- MANIFEST.in
- README
d build
- extdemo.c
d extdemo.egg-info
- extdemo.so
- setup.py
...@@ -45,7 +45,7 @@ We should be able to deal with setup scripts that aren't setuptools based. ...@@ -45,7 +45,7 @@ We should be able to deal with setup scripts that aren't setuptools based.
... ''') ... ''')
>>> print system(join('bin', 'buildout')), >>> print system(join('bin', 'buildout')),
buildout: Develop: /sample-buildout/foo/setup.py buildout: Develop: /sample-buildout/foo
>>> ls('develop-eggs') >>> ls('develop-eggs')
- foo.egg-link - foo.egg-link
...@@ -71,9 +71,8 @@ We should be able to deal with setup scripts that aren't setuptools based. ...@@ -71,9 +71,8 @@ We should be able to deal with setup scripts that aren't setuptools based.
... ''') ... ''')
>>> print system(join('bin', 'buildout')+' -v'), # doctest: +ELLIPSIS >>> print system(join('bin', 'buildout')+' -v'), # doctest: +ELLIPSIS
Configuration data: zc.buildout...
... buildout: Develop: /sample-buildout/foo
buildout: Develop: /sample-buildout/foo/setup.py
... ...
Installed /sample-buildout/foo Installed /sample-buildout/foo
... ...
...@@ -86,7 +85,7 @@ We should be able to deal with setup scripts that aren't setuptools based. ...@@ -86,7 +85,7 @@ We should be able to deal with setup scripts that aren't setuptools based.
def buildout_error_handling(): def buildout_error_handling():
r"""Buildout error handling r"""Buildout error handling
Asking for a section that doesn't exist, yields a key error: Asking for a section that doesn't exist, yields a missing section error:
>>> import os >>> import os
>>> os.chdir(sample_buildout) >>> os.chdir(sample_buildout)
...@@ -95,7 +94,7 @@ Asking for a section that doesn't exist, yields a key error: ...@@ -95,7 +94,7 @@ Asking for a section that doesn't exist, yields a key error:
>>> buildout['eek'] >>> buildout['eek']
Traceback (most recent call last): Traceback (most recent call last):
... ...
KeyError: 'eek' MissingSection: The referenced section, 'eek', was not defined.
Asking for an option that doesn't exist, a MissingOption error is raised: Asking for an option that doesn't exist, a MissingOption error is raised:
...@@ -109,8 +108,7 @@ It is an error to create a variable-reference cycle: ...@@ -109,8 +108,7 @@ It is an error to create a variable-reference cycle:
>>> write(sample_buildout, 'buildout.cfg', >>> write(sample_buildout, 'buildout.cfg',
... ''' ... '''
... [buildout] ... [buildout]
... develop = recipes ... parts =
... parts = data_dir debug
... x = ${buildout:y} ... x = ${buildout:y}
... y = ${buildout:z} ... y = ${buildout:z}
... z = ${buildout:x} ... z = ${buildout:x}
...@@ -183,7 +181,7 @@ Al parts have to have a section: ...@@ -183,7 +181,7 @@ Al parts have to have a section:
... ''') ... ''')
>>> print system(os.path.join(sample_buildout, 'bin', 'buildout')), >>> print system(os.path.join(sample_buildout, 'bin', 'buildout')),
Error: No section was specified for part x Error: The referenced section, 'x', was not defined.
and all parts have to have a specified recipe: and all parts have to have a specified recipe:
...@@ -265,15 +263,15 @@ def test_comparing_saved_options_with_funny_characters(): ...@@ -265,15 +263,15 @@ def test_comparing_saved_options_with_funny_characters():
>>> os.chdir(sample_buildout) >>> os.chdir(sample_buildout)
>>> buildout = os.path.join(sample_buildout, 'bin', 'buildout') >>> buildout = os.path.join(sample_buildout, 'bin', 'buildout')
>>> print system(buildout), # doctest: +ELLIPSIS >>> print system(buildout),
buildout: Develop: ...setup.py buildout: Develop: /sample-buildout/recipes
buildout: Installing debug buildout: Installing debug
If we run the buildout again, we shoudn't get a message about If we run the buildout again, we shoudn't get a message about
uninstalling anything because the configuration hasn't changed. uninstalling anything because the configuration hasn't changed.
>>> print system(buildout), # doctest: +ELLIPSIS >>> print system(buildout),
buildout: Develop: ...setup.py buildout: Develop: /sample-buildout/recipes
buildout: Updating debug buildout: Updating debug
""" """
...@@ -317,20 +315,21 @@ Then try to install it again: ...@@ -317,20 +315,21 @@ Then try to install it again:
""" """
def error_for_indefined_install_parts(): # Why?
""" ## def error_for_undefined_install_parts():
Any parts we pass to install on the command line must be ## """
listed in the configuration. ## Any parts we pass to install on the command line must be
## listed in the configuration.
>>> print system(join('bin', 'buildout') + ' install foo'), ## >>> print system(join('bin', 'buildout') + ' install foo'),
buildout: Invalid install parts: foo. ## buildout: Invalid install parts: foo.
Install parts must be listed in the configuration. ## Install parts must be listed in the configuration.
>>> print system(join('bin', 'buildout') + ' install foo bar'), ## >>> print system(join('bin', 'buildout') + ' install foo bar'),
buildout: Invalid install parts: foo bar. ## buildout: Invalid install parts: foo bar.
Install parts must be listed in the configuration. ## Install parts must be listed in the configuration.
""" ## """
bootstrap_py = os.path.join( bootstrap_py = os.path.join(
...@@ -515,7 +514,7 @@ Create a develop egg: ...@@ -515,7 +514,7 @@ Create a develop egg:
... """) ... """)
>>> print system(join('bin', 'buildout')), >>> print system(join('bin', 'buildout')),
buildout: Develop: /sample-buildout/foo/setup.py buildout: Develop: /sample-buildout/foo
>>> ls('develop-eggs') >>> ls('develop-eggs')
- foox.egg-link - foox.egg-link
...@@ -536,8 +535,8 @@ Create another: ...@@ -536,8 +535,8 @@ Create another:
... """) ... """)
>>> print system(join('bin', 'buildout')), >>> print system(join('bin', 'buildout')),
buildout: Develop: /sample-buildout/foo/setup.py buildout: Develop: /sample-buildout/foo
buildout: Develop: /sample-buildout/bar/setup.py buildout: Develop: /sample-buildout/bar
>>> ls('develop-eggs') >>> ls('develop-eggs')
- foox.egg-link - foox.egg-link
...@@ -552,7 +551,7 @@ Remove one: ...@@ -552,7 +551,7 @@ Remove one:
... parts = ... parts =
... """) ... """)
>>> print system(join('bin', 'buildout')), >>> print system(join('bin', 'buildout')),
buildout: Develop: /sample-buildout/bar/setup.py buildout: Develop: /sample-buildout/bar
It is gone It is gone
...@@ -611,7 +610,7 @@ a devlop egg, we will also generate a warning. ...@@ -611,7 +610,7 @@ a devlop egg, we will also generate a warning.
... """) ... """)
>>> print system(join('bin', 'buildout')), >>> print system(join('bin', 'buildout')),
buildout: Develop: /sample-buildout/foo/setup.py buildout: Develop: /sample-buildout/foo
Now, if we generate a working set using the egg link, we will get a warning 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. and we will get setuptools included in the working set.
...@@ -659,7 +658,6 @@ We do not get a warning, but we do get setuptools included in the working set: ...@@ -659,7 +658,6 @@ We do not get a warning, but we do get setuptools included in the working set:
... ])] ... ])]
['foox', 'setuptools'] ['foox', 'setuptools']
>>> print handler, >>> print handler,
We get the same behavior if the it is a depedency that uses a We get the same behavior if the it is a depedency that uses a
...@@ -682,8 +680,8 @@ namespace package. ...@@ -682,8 +680,8 @@ namespace package.
... """) ... """)
>>> print system(join('bin', 'buildout')), >>> print system(join('bin', 'buildout')),
buildout: Develop: /sample-buildout/foo/setup.py buildout: Develop: /sample-buildout/foo
buildout: Develop: /sample-buildout/bar/setup.py buildout: Develop: /sample-buildout/bar
>>> [dist.project_name >>> [dist.project_name
... for dist in zc.buildout.easy_install.working_set( ... for dist in zc.buildout.easy_install.working_set(
...@@ -703,6 +701,52 @@ namespace package. ...@@ -703,6 +701,52 @@ namespace package.
>>> handler.uninstall() >>> handler.uninstall()
''' '''
def develop_preserves_existing_setup_cfg():
"""
See "Handling custom build options for extensions in develop eggs" in
easy_install.txt. This will be very similar except that we'll have an
existing setup.cfg:
>>> write(extdemo, "setup.cfg",
... '''
... # sampe cfg file
...
... [foo]
... bar = 1
...
... [build_ext]
... define = X,Y
... ''')
>>> mkdir('include')
>>> write('include', 'extdemo.h',
... '''
... #define EXTDEMO 42
... ''')
>>> dest = tmpdir('dest')
>>> zc.buildout.easy_install.develop(
... extdemo, dest,
... {'include-dirs': os.path.join(sample_buildout, 'include')})
'/tmp/tmp7AFYXv/_TEST_/dest/extdemo.egg-link'
>>> ls(dest)
- extdemo.egg-link
>>> cat(extdemo, "setup.cfg")
<BLANKLINE>
# sampe cfg file
<BLANKLINE>
[foo]
bar = 1
<BLANKLINE>
[build_ext]
define = X,Y
"""
def create_sample_eggs(test, executable=sys.executable): def create_sample_eggs(test, executable=sys.executable):
write = test.globs['write'] write = test.globs['write']
...@@ -762,9 +806,13 @@ static PyMethodDef methods[] = {{NULL}}; ...@@ -762,9 +806,13 @@ static PyMethodDef methods[] = {{NULL}};
PyMODINIT_FUNC PyMODINIT_FUNC
initextdemo(void) initextdemo(void)
{ {
PyObject *d; PyObject *m;
d = Py_InitModule3("extdemo", methods, ""); m = Py_InitModule3("extdemo", methods, "");
PyDict_SetItemString(d, "val", PyInt_FromLong(EXTDEMO)); #ifdef TWO
PyModule_AddObject(m, "val", PyInt_FromLong(2));
#else
PyModule_AddObject(m, "val", PyInt_FromLong(EXTDEMO));
#endif
} }
""" """
...@@ -778,8 +826,8 @@ setup(name = "extdemo", version = "1.4", url="http://www.zope.org", ...@@ -778,8 +826,8 @@ setup(name = "extdemo", version = "1.4", url="http://www.zope.org",
""" """
def add_source_dist(test): def add_source_dist(test):
import tarfile
tmp = tempfile.mkdtemp('test-sdist') tmp = test.globs['extdemo'] = test.globs['tmpdir']('extdemo')
write = test.globs['write'] write = test.globs['write']
try: try:
write(tmp, 'extdemo.c', extdemo_c); write(tmp, 'extdemo.c', extdemo_c);
...@@ -930,7 +978,7 @@ def test_suite(): ...@@ -930,7 +978,7 @@ def test_suite():
]), ]),
), ),
doctest.DocTestSuite( doctest.DocTestSuite(
setUp=zc.buildout.testing.buildoutSetUp, setUp=easy_install_SetUp,
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,
......
...@@ -74,7 +74,7 @@ new versions found in new releases: ...@@ -74,7 +74,7 @@ new versions found in new releases:
zc.buildout version 99.99, zc.buildout version 99.99,
setuptools version 99.99; setuptools version 99.99;
restarting. restarting.
buildout: Develop: /sample-buildout/showversions/setup.py buildout: Develop: /sample-buildout/showversions
buildout: Installing show-versions buildout: Installing show-versions
zc.buildout 99.99 zc.buildout 99.99
setuptools 99.99 setuptools 99.99
...@@ -122,7 +122,7 @@ We'll actually "upgrade" to an earlier version. ...@@ -122,7 +122,7 @@ We'll actually "upgrade" to an earlier version.
zc.buildout version 1.0.0, zc.buildout version 1.0.0,
setuptools version 0.6; setuptools version 0.6;
restarting. restarting.
buildout: Develop: /sample-buildout/showversions/setup.py buildout: Develop: /sample-buildout/showversions
buildout: Updating show-versions buildout: Updating show-versions
zc.buildout 1.0.0 zc.buildout 1.0.0
setuptools 0.6 setuptools 0.6
...@@ -143,7 +143,7 @@ We won't upgrade in offline mode: ...@@ -143,7 +143,7 @@ We won't upgrade in offline mode:
... """ % dict(new_releases=new_releases)) ... """ % dict(new_releases=new_releases))
>>> print system(buildout), >>> print system(buildout),
buildout: Develop: /sample-buildout/showversions/setup.py buildout: Develop: /sample-buildout/showversions
buildout: Updating show-versions buildout: Updating show-versions
zc.buildout 1.0.0 zc.buildout 1.0.0
setuptools 0.6 setuptools 0.6
...@@ -174,4 +174,3 @@ directory: ...@@ -174,4 +174,3 @@ directory:
buildout: Not upgrading because not running a local buildout command buildout: Not upgrading because not running a local buildout command
>>> ls('bin') >>> ls('bin')
...@@ -8,6 +8,27 @@ To do ...@@ -8,6 +8,27 @@ To do
Change History Change History
************** **************
1.0.0b3 (2006-12-04)
====================
Feature Changes
---------------
- Added a develop recipe for creating develop eggs.
This is useful to:
- Specify custom extension building options,
- Specify a version of Python to use, and to
- Cause develop eggs to be created after other parts.
- The develop and build recipes now return the paths created, so that
created eggs or egg links are removed when a part is removed (or
changed).
1.0.0b2 (2006-10-16) 1.0.0b2 (2006-10-16)
==================== ====================
......
...@@ -42,7 +42,9 @@ setup( ...@@ -42,7 +42,9 @@ setup(
tests_require = ['zope.testing'], tests_require = ['zope.testing'],
test_suite = name+'.tests.test_suite', test_suite = name+'.tests.test_suite',
entry_points = {'zc.buildout': ['default = %s:Egg' % name, entry_points = {'zc.buildout': ['default = %s:Egg' % name,
'script = %s:Egg' % name,
'custom = %s:Custom' % name, 'custom = %s:Custom' % name,
'develop = %s:Develop' % name,
] ]
}, },
zip_safe=False, zip_safe=False,
......
from zc.recipe.egg.egg import Egg from zc.recipe.egg.egg import Egg
from zc.recipe.egg.custom import Custom from zc.recipe.egg.custom import Custom, Develop
...@@ -19,12 +19,27 @@ $Id$ ...@@ -19,12 +19,27 @@ $Id$
import os, re, zipfile import os, re, zipfile
import zc.buildout.easy_install import zc.buildout.easy_install
class Custom: class Base:
def __init__(self, buildout, name, options): def __init__(self, buildout, name, options):
self.buildout = buildout self.name, self.options = name, options
self.name = name
self.options = options options['_d'] = buildout['buildout']['develop-eggs-directory']
python = options.get('python', buildout['buildout']['python'])
options['executable'] = buildout[python]['executable']
self.build_ext = build_ext(buildout, options)
def update(self):
return self.install()
class Custom(Base):
def __init__(self, buildout, name, options):
Base.__init__(self, buildout, name, options)
links = options.get('find-links', links = options.get('find-links',
buildout['buildout'].get('find-links')) buildout['buildout'].get('find-links'))
if links: if links:
...@@ -39,47 +54,66 @@ class Custom: ...@@ -39,47 +54,66 @@ class Custom:
options['index'] = index options['index'] = index
self.index = index self.index = index
options['_b'] = buildout['buildout']['bin-directory']
options['_e'] = buildout['buildout']['eggs-directory'] options['_e'] = buildout['buildout']['eggs-directory']
options['_d'] = buildout['buildout']['develop-eggs-directory']
assert options.get('unzip') in ('true', 'false', None) assert options.get('unzip') in ('true', 'false', None)
python = options.get('python', buildout['buildout']['python']) if buildout['buildout'].get('offline') == 'true':
options['executable'] = buildout[python]['executable'] self.install = lambda: ()
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): def install(self):
if self.buildout['buildout'].get('offline') == 'true':
return ()
options = self.options options = self.options
distribution = options.get('eggs', self.name).strip() distribution = options.get('eggs', self.name).strip()
build_ext = dict([ return zc.buildout.easy_install.build(
(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, distribution, options['_d'], self.build_ext,
self.links, self.index, options['executable'], [options['_e']], self.links, self.index, options['executable'], [options['_e']],
) )
return () class Develop(Base):
def __init__(self, buildout, name, options):
Base.__init__(self, buildout, name, options)
options['setup'] = os.path.join(buildout['buildout']['directory'],
options['setup'])
def install(self):
options = self.options
return zc.buildout.easy_install.develop(
options['setup'], options['_d'], self.build_ext,
options['executable'],
)
def build_ext(buildout, options):
result = {}
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()
]
result[be_option] = os.pathsep.join(value)
options[be_option] = os.pathsep.join(value)
swig = options.get('swig')
if swig:
options['swig'] = result['swig'] = os.path.join(
buildout['buildout']['directory'],
swig,
)
for be_option in ('define', 'undef', 'libraries', 'link-objects',
'debug', 'force', 'compiler', 'swig-cpp', 'swig-opts',
):
value = options.get(be_option)
if value is None:
continue
result[be_option] = value
update = install return result
Custon eggs Creating eggs with extensions neededing custom build settings
=========== =============================================================
Sometimes, It's necessary to provide extra control over how an egg is Sometimes, It's necessary to provide extra control over how an egg is
created. This is commonly true for eggs with extension modules that created. This is commonly true for eggs with extension modules that
...@@ -20,6 +20,42 @@ rpath ...@@ -20,6 +20,42 @@ rpath
A new-line separated list of directories to search for dynamic libraries A new-line separated list of directories to search for dynamic libraries
at run time. at run time.
define
A comma-separated list of names of C preprocessor variables to
define.
undef
A comman separated list of names of C preprocessor variables to
undefine.
libraries
The name of an additional library to link with. Due to limitations
in distutils and desprite the option name, only a single library
can be specified.
link-objects
The name of an link object to link afainst. Due to limitations
in distutils and desprite the option name, only a single link object
can be specified.
debug
Compile/link with debugging information
force
Forcibly build everything (ignore file timestamps)
compiler
Specify the compiler type
swig
The path to the swig executable
swig-cpp
Make SWIG create C++ files (default is C)
swig-opts
List of SWIG command line options
In addition, the following options can be used to specify the egg: In addition, the following options can be used to specify the egg:
egg egg
...@@ -57,9 +93,13 @@ package that has a simple extension module:: ...@@ -57,9 +93,13 @@ package that has a simple extension module::
PyMODINIT_FUNC PyMODINIT_FUNC
initextdemo(void) initextdemo(void)
{ {
PyObject *d; PyObject *m;
d = Py_InitModule3("extdemo", methods, ""); m = Py_InitModule3("extdemo", methods, "");
PyDict_SetItemString(d, "val", PyInt_FromLong(EXTDEMO)); #ifdef TWO
PyModule_AddObject(m, "val", PyInt_FromLong(2));
#else
PyModule_AddObject(m, "val", PyInt_FromLong(EXTDEMO));
#endif
} }
The extension depends on a system-dependnt include file, extdemo.h, The extension depends on a system-dependnt include file, extdemo.h,
...@@ -71,10 +111,11 @@ extdemo-1.4.tar.gz, on a distribution server. ...@@ -71,10 +111,11 @@ extdemo-1.4.tar.gz, on a distribution server.
We have a sample buildout that we'll add an include directory to with We have a sample buildout that we'll add an include directory to with
the necessary include file: the necessary include file:
>>> mkdir(sample_buildout, 'include') >>> mkdir('include')
>>> import os >>> write('include', 'extdemo.h',
>>> open(os.path.join(sample_buildout, 'include', 'extdemo.h'), 'w').write( ... """
... "#define EXTDEMO 42\n") ... #define EXTDEMO 42
... """)
We'll also update the buildout configuration file to define a part for We'll also update the buildout configuration file to define a part for
the egg: the egg:
...@@ -91,8 +132,7 @@ the egg: ...@@ -91,8 +132,7 @@ the egg:
... include-dirs = include ... include-dirs = include
... """ % dict(server=link_server)) ... """ % dict(server=link_server))
>>> os.chdir(sample_buildout) >>> buildout = join('bin', 'buildout')
>>> buildout = os.path.join(sample_buildout, 'bin', 'buildout')
>>> print system(buildout), >>> print system(buildout),
buildout: Installing extdemo buildout: Installing extdemo
...@@ -113,3 +153,177 @@ Note that no scripts or dependencies are installed. To install ...@@ -113,3 +153,177 @@ Note that no scripts or dependencies are installed. To install
dependencies or scripts for a custom egg, define another part and use 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 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. be installed. The zc.recipe.egg recipe will use the installed egg.
Let's define a script that uses out ext demo:
>>> mkdir('demo')
>>> write('demo', 'demo.py',
... """
... import extdemo
... def main():
... print extdemo.val
... """)
>>> write('demo', 'setup.py',
... """
... from setuptools import setup
... setup(name='demo')
... """)
>>> write('buildout.cfg',
... """
... [buildout]
... develop = demo
... parts = extdemo demo
...
... [extdemo]
... recipe = zc.recipe.egg:custom
... find-links = %(server)s
... index = %(server)s/index
... include-dirs = include
...
... [demo]
... recipe = zc.recipe.egg
... eggs = demo
... extdemo
... entry-points = demo=demo:main
... """ % dict(server=link_server))
>>> print system(buildout),
buildout: Develop: /sample-buildout/demo
buildout: Updating extdemo
buildout: Installing demo
When we run the script, we'll 42 printed:
>>> print system(join('bin', 'demo')),
42
Controlling develop-egg generation
==================================
If you want to provide custom build options for a develop egg, you can
use the develop recipe. The recipe has the following options:
path
The path to a setup script or directory containing a startup
script. This is required.
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.
define
A comma-separated list of names of C preprocessor variables to
define.
undef
A comman separated list of names of C preprocessor variables to
undefine.
libraries
The name of an additional library to link with. Due to limitations
in distutils and desprite the option name, only a single library
can be specified.
link-objects
The name of an link object to link afainst. Due to limitations
in distutils and desprite the option name, only a single link object
can be specified.
debug
Compile/link with debugging information
force
Forcibly build everything (ignore file timestamps)
compiler
Specify the compiler type
swig
The path to the swig executable
swig-cpp
Make SWIG create C++ files (default is C)
swig-opts
List of SWIG command line options
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 use a directory containing the extdemo
example from the earlier section:
>>> ls(extdemo)
- MANIFEST
- MANIFEST.in
- README
- extdemo.c
- setup.py
>>> write('buildout.cfg',
... """
... [buildout]
... develop = demo
... parts = extdemo demo
...
... [extdemo]
... setup = %(extdemo)s
... recipe = zc.recipe.egg:develop
... include-dirs = include
... define = TWO
...
... [demo]
... recipe = zc.recipe.egg
... eggs = demo
... extdemo
... entry-points = demo=demo:main
... """ % dict(extdemo=extdemo))
Note that we added a define option to cause the preprocessor variable
TWO to be defined. This will cause the module-variable, 'val', to be
set with a value of 2.
>>> print system(buildout),
buildout: Develop: /tmp/tmpCXjRps/_TEST_/sample-buildout/demo
buildout: Uninstalling extdemo
buildout: Installing extdemo
buildout: Updating demo
Our develop-eggs now includes an egg link for extdemo:
>>> ls('develop-eggs')
- demo.egg-link
- extdemo.egg-link
- zc.recipe.egg.egg-link
and the extdemo now has a built extension:
>>> ls(extdemo)
- MANIFEST
- MANIFEST.in
- README
d build
- extdemo.c
d extdemo.egg-info
- extdemo.so
- setup.py
Because develop eggs take precedence over non-develop eggs, the demo
script will use the new develop egg:
>>> print system(join('bin', 'demo')),
2
...@@ -78,6 +78,7 @@ def test_suite(): ...@@ -78,6 +78,7 @@ def test_suite():
'custom.txt', 'custom.txt',
setUp=setUp, tearDown=zc.buildout.testing.buildoutTearDown, setUp=setUp, tearDown=zc.buildout.testing.buildoutTearDown,
checker=renormalizing.RENormalizing([ checker=renormalizing.RENormalizing([
zc.buildout.testing.normalize_path,
(re.compile("(d ((ext)?demo(needed)?|other)" (re.compile("(d ((ext)?demo(needed)?|other)"
"-\d[.]\d-py)\d[.]\d(-\S+)?[.]egg"), "-\d[.]\d-py)\d[.]\d(-\S+)?[.]egg"),
'\\1V.V.egg'), '\\1V.V.egg'),
......
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