Commit c1598ef6 authored by Jim Fulton's avatar Jim Fulton

Improved intro text and description of setup.

Added documentation of buildout:directory option.

Added support for extending configurations through multiple
configuration files.

Added command-line options to:

- Specify a configuration file

- Override configuration options

Renamed several options to use -s rather than _s.
parent 299c235c
...@@ -51,46 +51,40 @@ class Options(dict): ...@@ -51,46 +51,40 @@ class Options(dict):
class Buildout(dict): class Buildout(dict):
def __init__(self): def __init__(self, config_file, cloptions):
self._buildout_dir = os.path.abspath(os.getcwd()) config_file = os.path.abspath(config_file)
self._config_file = self.buildout_path('buildout.cfg') self._config_file = config_file
super(Buildout, self).__init__(self._open( super(Buildout, self).__init__()
directory = self._buildout_dir,
eggs_directory = 'eggs', # default options
bin_directory = 'bin', data = dict(buildout={'directory': os.path.dirname(config_file),
parts_directory = 'parts', 'eggs-directory': 'eggs',
installed = '.installed.cfg', 'bin-directory': 'bin',
)) 'parts-directory': 'parts',
'installed': '.installed.cfg',
options = self['buildout'] },
)
links = options.get('find_links', '') # load user defaults, which override defaults
self._links = links and links.split() or () if 'HOME' in os.environ:
user_config = os.path.join(os.environ['HOME'],
'.buildout', 'default.cfg')
if os.path.exists(user_config):
_update(data, _open(os.path.dirname(user_config), user_config,
[]))
for name in ('bin', 'parts', 'eggs'): # load configuration files
d = self.buildout_path(options[name+'_directory']) _update(data, _open(os.path.dirname(config_file), config_file, []))
setattr(self, name, d)
if not os.path.exists(d):
os.mkdir(d)
_template_split = re.compile('([$]{\w+:\w+})').split # apply command-line options
def _open(self, **predefined): for (section, option, value) in cloptions:
# Open configuration files options = data.get(section)
parser = ConfigParser.SafeConfigParser() if options is None:
parser.add_section('buildout') options = self[section] = {}
for k, v in predefined.iteritems(): options[option] = value
parser.set('buildout', k, v)
parser.read(self._config_file)
data = dict([
(section,
Options(self, section,
[(k, v.strip()) for (k, v) in parser.items(section)])
)
for section in parser.sections()
])
# do substitutions
converted = {} converted = {}
for section, options in data.iteritems(): for section, options in data.iteritems():
for option, value in options.iteritems(): for option, value in options.iteritems():
...@@ -100,7 +94,22 @@ class Buildout(dict): ...@@ -100,7 +94,22 @@ class Buildout(dict):
options[option] = value options[option] = value
converted[(section, option)] = value converted[(section, option)] = value
return data # copy data into self:
for section, options in data.iteritems():
self[section] = Options(self, section, options)
# initialize some attrs and buildout directories.
options = self['buildout']
links = options.get('find-links', '')
self._links = links and links.split() or ()
self._buildout_dir = options['directory']
for name in ('bin', 'parts', 'eggs'):
d = self.buildout_path(options[name+'-directory'])
setattr(self, name, d)
if not os.path.exists(d):
os.mkdir(d)
def _dosubs(self, section, option, value, data, converted, seen): def _dosubs(self, section, option, value, data, converted, seen):
key = section, option key = section, option
...@@ -116,6 +125,7 @@ class Buildout(dict): ...@@ -116,6 +125,7 @@ class Buildout(dict):
seen.pop() seen.pop()
return value return value
_template_split = re.compile('([$]{\w+:\w+})').split
def _dosubs_esc(self, value, data, converted, seen): def _dosubs_esc(self, value, data, converted, seen):
value = self._template_split(value) value = self._template_split(value)
subs = [] subs = []
...@@ -141,7 +151,7 @@ class Buildout(dict): ...@@ -141,7 +151,7 @@ class Buildout(dict):
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)
def install(self): def install(self, install_parts):
self._develop() self._develop()
new_part_options = self._gather_part_info() new_part_options = self._gather_part_info()
installed_part_options = self._read_installed_part_options() installed_part_options = self._read_installed_part_options()
...@@ -150,6 +160,12 @@ class Buildout(dict): ...@@ -150,6 +160,12 @@ class Buildout(dict):
new_old_parts = [] new_old_parts = []
for part in old_parts: for part in old_parts:
if install_parts and (part not in install_parts):
# We were asked to install specific parts and this
# wasn't one of them. Leave it alone.
new_old_parts.append(part)
continue
installed_options = installed_part_options[part].copy() installed_options = installed_part_options[part].copy()
installed = installed_options.pop('__buildout_installed__') installed = installed_options.pop('__buildout_installed__')
if installed_options != new_part_options.get(part): if installed_options != new_part_options.get(part):
...@@ -162,10 +178,11 @@ class Buildout(dict): ...@@ -162,10 +178,11 @@ class Buildout(dict):
new_parts = [] new_parts = []
try: try:
for part in new_part_options['buildout']['parts'].split(): for part in new_part_options['buildout']['parts'].split():
if (not install_parts) or (part in install_parts):
installed = self._install(part) installed = self._install(part)
new_part_options[part]['__buildout_installed__'] = installed new_part_options[part]['__buildout_installed__'] = installed
new_parts.append(part)
installed_part_options[part] = new_part_options[part] installed_part_options[part] = new_part_options[part]
new_parts.append(part)
new_old_parts = [p for p in new_old_parts if p != part] new_old_parts = [p for p in new_old_parts if p != part]
finally: finally:
new_parts.extend(new_old_parts) new_parts.extend(new_old_parts)
...@@ -274,13 +291,61 @@ class Buildout(dict): ...@@ -274,13 +291,61 @@ class Buildout(dict):
for d in installed] for d in installed]
return ' '.join(installed) return ' '.join(installed)
def _save_installed_options(self, installed_options): def _save_installed_options(self, installed_options):
f = open(self._installed_path(), 'w')
_save_options('buildout', installed_options['buildout'], f)
for part in installed_options['buildout']['parts'].split():
print >>f
_save_options(part, installed_options[part], f)
f.close()
def _save_options(section, options, f):
print >>f, '[%s]' % section
items = options.items()
items.sort()
for option, value in items:
print >>f, option, '=', str(value).replace('\n', '\n\t')
def _open(base, filename, seen):
"""Open a configuration file and return the result as a dictionary,
Recursively open other files based on buildout options found.
"""
filename = os.path.join(base, filename)
if filename in seen:
raise ValueError("Recursive file include", seen, filename)
base = os.path.dirname(filename)
seen.append(filename)
result = {}
parser = ConfigParser.SafeConfigParser() parser = ConfigParser.SafeConfigParser()
for section in installed_options: parser.readfp(open(filename))
parser.add_section(section) extends = extended_by = None
for option, value in installed_options[section].iteritems(): for section in parser.sections():
parser.set(section, option, value) options = dict(parser.items(section))
parser.write(open(self._installed_path(), 'w')) if section == 'buildout':
extends = options.pop('extends', extends)
extended_by = options.pop('extended-by', extended_by)
result[section] = options
if extends:
extends = extends.split()
extends.reverse()
for fname in extends:
result = _update(_open(base, fname, seen), result)
if extended_by:
for fname in extended_by.split():
result = _update(result, _open(base, fname, seen))
seen.pop()
return result
def _dir_hash(dir): def _dir_hash(dir):
hash = md5.new() hash = md5.new()
...@@ -306,5 +371,44 @@ def _dists_sig(dists, base): ...@@ -306,5 +371,44 @@ def _dists_sig(dists, base):
result.append(location) result.append(location)
return result return result
def main(): def _update(d1, d2):
Buildout().install() for section in d2:
if section in d1:
d1[section].update(d2[section])
else:
d1[section] = d2[section]
return d1
def _error(*message):
sys.syderr.write(' '.join(message) +'\n')
sys.exit(1)
def main(args=None):
if args is None:
args = sys.argv[1:]
if args and args[0] == '-c':
args.pop(0)
if not args:
_error("No configuration file specified,")
config_file = args.pop(0)
else:
config_file = 'buildout.cfg'
options = []
while args and '=' in args[0]:
option, value = args.pop(0).split('=', 1)
if len(option.split(':')) != 2:
_error('Invalid option:', option)
section, option = option.split(':')
options.append((section.strip(), option.strip(), value.strip()))
buildout = Buildout(config_file, options)
if args:
command = args.pop(0)
if command != 'install':
_error('invalid command:', command)
else:
command = 'install'
getattr(buildout, command)(args)
This diff is collapsed.
...@@ -29,7 +29,7 @@ Asking for a section that doesn't exist, yields a key error: ...@@ -29,7 +29,7 @@ Asking for a section that doesn't exist, yields a key error:
>>> import os >>> import os
>>> os.chdir(sample_buildout) >>> os.chdir(sample_buildout)
>>> import zc.buildout.buildout >>> import zc.buildout.buildout
>>> buildout = zc.buildout.buildout.Buildout() >>> buildout = zc.buildout.buildout.Buildout('buildout.cfg', [])
>>> buildout['eek'] >>> buildout['eek']
Traceback (most recent call last): Traceback (most recent call last):
... ...
...@@ -64,8 +64,6 @@ It is an error to create a variable-reference cycle: ...@@ -64,8 +64,6 @@ It is an error to create a variable-reference cycle:
''' '''
def runsetup(d): def runsetup(d):
here = os.getcwd() here = os.getcwd()
try: try:
...@@ -120,6 +118,18 @@ def linkerTearDown(test): ...@@ -120,6 +118,18 @@ def linkerTearDown(test):
shutil.rmtree(test.globs['_sample_eggs_container']) shutil.rmtree(test.globs['_sample_eggs_container'])
zc.buildout.testing.buildoutTearDown(test) zc.buildout.testing.buildoutTearDown(test)
def buildoutSetUp(test):
zc.buildout.testing.buildoutSetUp(test)
test.globs['_oldhome'] = os.environ.get('HOME')
def buildoutTearDoen(test):
if test.globs['_oldhome'] is not None:
os.environ['HOME'] = test.globs['_oldhome']
shutil.rmtree(test.globs['extensions'])
shutil.rmtree(test.globs['home'])
zc.buildout.testing.buildoutTearDown(test)
def test_suite(): def test_suite():
return unittest.TestSuite(( return unittest.TestSuite((
......
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