# HG changeset patch
# User cmlenz
# Date 1129383563 0
# Node ID 6abd43d0cd8a48d8773f8ca5328e93d021e23e28
# Parent 4fabcaf757878d5b4fa1ff8e6ddc95cedc5bbd23
The build slave now stores snapshot archives and the corresponding work directories in project folders of the main work folder, to keep build configurations from different projects that share the same name separate. This also requires transmitting the project name (simply the name of the environment directory) with the build initiation.
diff --git a/bitten/master.py b/bitten/master.py
--- a/bitten/master.py
+++ b/bitten/master.py
@@ -198,8 +198,9 @@
else:
self.send_snapshot(queue, build, snapshot)
- self.channel.send_msg(beep.Payload(config.recipe),
- handle_reply=handle_reply)
+ xml = xmlio.parse(config.recipe)
+ xml.attr['project'] = os.path.basename(queue.env.path)
+ self.channel.send_msg(beep.Payload(xml), handle_reply=handle_reply)
def send_snapshot(self, queue, build, snapshot):
timestamp_delta = 0
@@ -418,10 +419,16 @@
host = ip
envs = []
+ names = set()
for arg in args:
if not os.path.isdir(arg):
log.warning('Ignoring %s: not a directory', arg)
continue
+ name = os.path.basename(arg)
+ if name in names:
+ log.warning('Ignoring %s: duplicate project name "%s"', arg, name)
+ continue
+ names.add(name)
env = Environment(arg)
if BuildSystem(env):
if env.needs_upgrade():
diff --git a/bitten/slave.py b/bitten/slave.py
--- a/bitten/slave.py
+++ b/bitten/slave.py
@@ -60,7 +60,7 @@
def handle_connect(self):
"""Register with the build master."""
- self.recipe_xml = None
+ self.build_xml = None
def handle_reply(cmd, msgno, ansno, payload):
if cmd == 'ERR':
@@ -99,34 +99,40 @@
elem = xmlio.parse(payload.body)
if elem.name == 'build':
# Received a build request
- self.recipe_xml = elem
+ self.build_xml = elem
xml = xmlio.Element('proceed')
self.channel.send_rpy(msgno, beep.Payload(xml))
elif payload.content_type == 'application/zip':
# Received snapshot archive for build
+ project_name = self.build_xml.attr.get('project', 'default')
+ project_dir = os.path.join(self.session.work_dir, project_name)
+ if not os.path.exists(project_dir):
+ os.mkdir(project_dir)
+
archive_name = payload.content_disposition
if not archive_name:
archive_name = 'snapshot.zip'
- archive_path = os.path.join(self.session.work_dir, archive_name)
+ archive_path = os.path.join(project_dir, archive_name)
archive_file = file(archive_path, 'wb')
try:
shutil.copyfileobj(payload.body, archive_file)
finally:
archive_file.close()
- basedir = self.unpack_snapshot(msgno, archive_path)
+ basedir = self.unpack_snapshot(msgno, project_dir, archive_name)
try:
- recipe = Recipe(self.recipe_xml, basedir, self.config)
+ recipe = Recipe(self.build_xml, basedir, self.config)
self.execute_build(msgno, recipe)
finally:
if not self.session.keep_files:
shutil.rmtree(basedir)
os.remove(archive_path)
- def unpack_snapshot(self, msgno, path):
+ def unpack_snapshot(self, msgno, project_dir, archive_name):
"""Unpack a snapshot archive."""
+ path = os.path.join(project_dir, archive_name)
log.debug('Received snapshot archive: %s', path)
try:
zip_file = zipfile.ZipFile(path, 'r')
@@ -142,7 +148,7 @@
if name.startswith('/') or '..' in name:
continue
names.append(os.path.normpath(name))
- fullpath = os.path.join(self.session.work_dir, name)
+ fullpath = os.path.join(project_dir, name)
if name.endswith('/'):
os.makedirs(fullpath)
else:
@@ -157,8 +163,7 @@
finally:
zip_file.close()
- basedir = os.path.join(self.session.work_dir,
- os.path.commonprefix(names))
+ basedir = os.path.join(project_dir, os.path.commonprefix(names))
log.debug('Unpacked snapshot to %s' % basedir)
return basedir
diff --git a/bitten/util/beep.py b/bitten/util/beep.py
--- a/bitten/util/beep.py
+++ b/bitten/util/beep.py
@@ -729,7 +729,7 @@
if data is None:
data = ''
- if isinstance(data, xmlio.Element):
+ if isinstance(data, (xmlio.Element, xmlio.ParsedElement)):
self.body = StringIO(str(data))
elif isinstance(data, basestring):
self.body = StringIO(data)
diff --git a/bitten/util/xmlio.py b/bitten/util/xmlio.py
--- a/bitten/util/xmlio.py
+++ b/bitten/util/xmlio.py
@@ -17,6 +17,7 @@
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
+from UserDict import DictMixin
__all__ = ['Element', 'parse']
@@ -185,13 +186,71 @@
class ParsedElement(object):
"""Representation of an XML element that was parsed from a string or
file.
+
+ This class should not be used directly. Rather, XML text parsed using
+ `xmlio.parse()` will return an instance of this class.
+
+ >>> xml = parse('')
+ >>> print xml.name
+ root
+
+ Parsed elements can be serialized to a string using the `write()` method:
+
+ >>> import sys
+ >>> parse('').write(sys.stdout)
+
+
+ For convenience, this is also done when coercing the object to a string
+ using the builtin `str()` function, which is used when `print`ing an object:
+
+ >>> print parse('')
+
+
+ (Note that serializing the element will produce a normalized representation
+ that may not excatly match the input string.)
+
+ Attributes are accessed via the `attr` member:
+
+ >>> print parse('').attr['foo']
+ bar
+
+ Attributes can also be updated, added or removed:
+
+ >>> xml = parse('')
+ >>> xml.attr['foo'] = 'baz'
+ >>> print xml
+
+
+ >>> del xml.attr['foo']
+ >>> print xml
+
+
+ >>> xml.attr['foo'] = 'bar'
+ >>> print xml
+
"""
__slots__ = ['_node', 'attr']
+ class _Attrs(DictMixin):
+ """Simple wrapper around the element attributes to provide a dictionary
+ interface."""
+ def __init__(self, node):
+ self._node = node
+ def __getitem__(self, name):
+ attr = self._node.getAttributeNode(name)
+ if not attr:
+ raise KeyError, name
+ return attr.value.encode()
+ def __setitem__(self, name, value):
+ self._node.setAttribute(name, value)
+ def __delitem__(self, name):
+ self._node.removeAttribute(name)
+ def keys(self):
+ return [name.encode() for name in self._node.attributes.keys()]
+
def __init__(self, node):
self._node = node
- self.attr = dict([(name.encode(), value.encode()) for name, value
- in node.attributes.items()])
+ self.attr = ParsedElement._Attrs(node)
name = property(fget=lambda self: self._node.localName)
namespace = property(fget=lambda self: self._node.namespaceURI)