Mercurial > bitten > bitten-test
changeset 147:395b67aa072e
Build recipes are now stored in the database with the build configuration. This means that it is no longer necessary to store the recipe in the repository. Closes #41.
At some point, there'll need to be a real user interface for creating/updating the recipe.
author | cmlenz |
---|---|
date | Sun, 21 Aug 2005 17:49:20 +0000 |
parents | affd91b4c6fb |
children | f3f5895e373c |
files | bitten/master.py bitten/model.py bitten/recipe.py bitten/slave.py bitten/trac_ext/htdocs/bitten.css bitten/trac_ext/templates/bitten_config.cs bitten/trac_ext/tests/web_ui.py bitten/trac_ext/web_ui.py bitten/upgrades.py recipe.xml |
diffstat | 10 files changed, 183 insertions(+), 147 deletions(-) [+] |
line wrap: on
line diff
--- a/bitten/master.py +++ b/bitten/master.py @@ -285,11 +285,11 @@ return self.send_snapshot(build, type, encoding) - xml = xmlio.Element('build', recipe='recipe.xml') - self.channel.send_msg(beep.Payload(xml), handle_reply=handle_reply) + config = BuildConfig.fetch(self.env, build.config) + self.channel.send_msg(beep.Payload(config.recipe), + handle_reply=handle_reply) def send_snapshot(self, build, type, encoding): - timestamp_delta = 0 if self.master.adjust_timestamps: d = datetime.now() - timedelta(seconds=self.master.check_interval) \
--- a/bitten/model.py +++ b/bitten/model.py @@ -26,19 +26,23 @@ _schema = [ Table('bitten_config', key='name')[ - Column('name'), Column('path'), Column('label'), - Column('active', type='int'), Column('description') + Column('name'), Column('path'), Column('active', type='int'), + Column('recipe'), Column('min_rev'), Column('max_rev'), + Column('label'), Column('description') ] ] - def __init__(self, env, name=None, path=None, label=None, active=False, - description=None): + def __init__(self, env, name=None, path=None, active=False, recipe=None, + min_rev=None, max_rev=None, label=None, description=None): self.env = env self._old_name = None self.name = name self.path = path or '' + self.active = bool(active) + self.recipe = recipe or '' + self.min_rev = min_rev or None + self.max_rev = max_rev or None self.label = label or '' - self.active = bool(active) self.description = description or '' exists = property(fget=lambda self: self._old_name is not None) @@ -53,14 +57,16 @@ handle_ta = False cursor = db.cursor() - cursor.execute("INSERT INTO bitten_config " - "(name,path,label,active,description) " - "VALUES (%s,%s,%s,%s,%s)", - (self.name, self.path, self.label or '', - int(self.active or 0), self.description or '')) + cursor.execute("INSERT INTO bitten_config (name,path,active," + "recipe,min_rev,max_rev,label,description) " + "VALUES (%s,%s,%s,%s,%s,%s,%s,%s)", + (self.name, self.path, int(self.active or 0), + self.recipe or '', self.min_rev, self.max_rev, + self.label or '', self.description or '')) if handle_ta: db.commit() + self._old_name = self.name def update(self, db=None): assert self.exists, 'Cannot update a non-existing configuration' @@ -72,21 +78,24 @@ handle_ta = False cursor = db.cursor() - cursor.execute("UPDATE bitten_config SET name=%s,path=%s,label=%s," - "active=%s,description=%s WHERE name=%s", - (self.name, self.path, self.label, int(self.active or 0), - self.description, self._old_name)) + cursor.execute("UPDATE bitten_config SET name=%s,path=%s,active=%s," + "recipe=%s,min_rev=%s,max_rev=%s,label=%s," + "description=%s WHERE name=%s", + (self.name, self.path, int(self.active or 0), + self.recipe, self.min_rev, self.max_rev, + self.label, self.description, self._old_name)) if handle_ta: db.commit() + self._old_name = self.name def fetch(cls, env, name, db=None): if not db: db = env.get_db_cnx() cursor = db.cursor() - cursor.execute("SELECT path,label,active,description " - "FROM bitten_config WHERE name=%s", (name,)) + cursor.execute("SELECT path,active,recipe,min_rev,max_rev,label," + "description FROM bitten_config WHERE name=%s", (name,)) row = cursor.fetchone() if not row: return None @@ -94,9 +103,12 @@ config = BuildConfig(env) config.name = config._old_name = name config.path = row[0] or '' - config.label = row[1] or '' - config.active = row[2] and True or False - config.description = row[3] or '' + config.active = row[1] and True or False + config.recipe = row[2] or '' + config.min_rev = row[3] or '' + config.max_rev = row[4] or '' + config.label = row[5] or '' + config.description = row[6] or '' return config fetch = classmethod(fetch) @@ -104,18 +116,21 @@ def select(cls, env, include_inactive=False, db=None): if not db: db = env.get_db_cnx() - where = '' + cursor = db.cursor() if include_inactive: - cursor.execute("SELECT name,path,label,active,description " - "FROM bitten_config ORDER BY name") + cursor.execute("SELECT name,path,active,recipe,min_rev,max_rev," + "label,description FROM bitten_config ORDER BY name") else: - cursor.execute("SELECT name,path,label,active,description " - "FROM bitten_config WHERE active=1 " - "ORDER BY name") - for name, path, label, active, description in cursor: + cursor.execute("SELECT name,path,active,recipe,min_rev,max_rev," + "label,description FROM bitten_config " + "WHERE active=1 ORDER BY name") + for name, path, active, recipe, min_rev, max_rev, label, description \ + in cursor: config = BuildConfig(env, name=name, path=path or '', - label=label or '', active=bool(active), + active=bool(active), recipe=recipe or '', + min_rev=min_rev or None, + max_rev=max_rev or None, label=label or '', description=description or '') config._old_name = name yield config @@ -643,4 +658,4 @@ schema = BuildConfig._schema + TargetPlatform._schema + Build._schema + \ BuildStep._schema + BuildLog._schema -schema_version = 2 +schema_version = 3
--- a/bitten/recipe.py +++ b/bitten/recipe.py @@ -113,7 +113,7 @@ func_name = self._translate_name(elem.name) try: module = __import__(elem.namespace[7:], globals(), locals(), - func_name) + [func_name]) func = getattr(module, func_name) return func except (ImportError, AttributeError), e: @@ -137,13 +137,17 @@ LOG = 'log' REPORT = 'report' - def __init__(self, filename='recipe.xml', basedir=os.getcwd()): + def __init__(self, filename='recipe.xml', basedir=os.getcwd(), + xml_elem=None): self.ctxt = Context(basedir) - fd = file(self.ctxt.resolve(filename), 'r') - try: - self._root = xmlio.parse(fd) - finally: - fd.close() + if filename: + fd = file(self.ctxt.resolve(filename), 'r') + try: + self._root = xmlio.parse(fd) + finally: + fd.close() + elif xml_elem: + self._root = xml_elem self.description = self._root.attr.get('description') def __iter__(self):
--- a/bitten/slave.py +++ b/bitten/slave.py @@ -58,7 +58,6 @@ def handle_connect(self): """Register with the build master.""" - self.recipe_path = None def handle_reply(cmd, msgno, ansno, payload): if cmd == 'ERR': @@ -105,12 +104,12 @@ self.channel.send_msg(beep.Payload(xml), handle_reply) def handle_msg(self, msgno, payload): + recipe_xml = None if payload.content_type == beep.BEEP_XML: elem = xmlio.parse(payload.body) if elem.name == 'build': + recipe_xml = elem # Received a build request - self.recipe_path = elem.attr['recipe'] - xml = xmlio.Element('proceed')[ xmlio.Element('accept', type='application/tar', encoding='bzip2'), @@ -157,15 +156,12 @@ for filename in files: os.chmod(os.path.join(root, filename), 0400) - self.execute_build(msgno, path, self.recipe_path) + self.execute_build(msgno, Recipe(basedir=path, xml_elem=recipe_xml)) - def execute_build(self, msgno, basedir, recipe_path): + def execute_build(self, msgno, recipe): global log - log.info('Building in directory %s using recipe %s', basedir, - recipe_path) + log.info('Building in directory %s', recipe.ctxt.basedir) try: - recipe = Recipe(recipe_path, basedir) - xml = xmlio.Element('started', time=datetime.utcnow().isoformat()) self.channel.send_ans(msgno, beep.Payload(xml))
--- a/bitten/trac_ext/htdocs/bitten.css +++ b/bitten/trac_ext/htdocs/bitten.css @@ -4,7 +4,10 @@ background-image: url(bitten_build.png) !important } -#content.build form.config td.active { vertical-align: bottom; } +#content.build #prefs { line-height: 1.4em; } + +#content.build form.config th { text-align: left; } +#content.build form.config fieldset { margin-bottom: 1em; } #content.build form.platforms ul { list-style: none; padding-left: 1em; } #content.build #builds { margin-top: 2em; }
--- a/bitten/trac_ext/templates/bitten_config.cs +++ b/bitten/trac_ext/templates/bitten_config.cs @@ -19,7 +19,7 @@ elif:page.mode == 'edit_config' ?> <form class="config" method="post" action=""> - <table><tr> + <table summary=""><tr> <td class="name"><label>Name:<br /> <input type="text" name="name" value="<?cs var:config.name ?>" /> </label></td> @@ -28,14 +28,6 @@ var:config.label ?>" /> </label></td> </tr><tr> - <td class="active"><label><input type="checkbox" name="active"<?cs - if:config.active ?> checked="checked"<?cs /if ?> /> Active - </label></td> - <td class="path"><label>Repository path:<br /> - <input type="text" name="path" size="48" value="<?cs - var:config.path ?>" /> - </label></td> - </tr><tr> <td colspan="2"><fieldset class="iefix"> <label for="description">Description (you may use <a tabindex="42" href="<?cs var:trac.href.wiki ?>/WikiFormatting">WikiFormatting</a> here):</label> @@ -45,6 +37,19 @@ var:htdocs_location ?>js/wikitoolbar.js"></script> </fieldset></td> </tr></table> + <fieldset id="recipe"> + <legend>Build Recipe</legend> + <textarea id="recipe" name="recipe" rows="8" cols="78"><?cs + var:config.recipe ?></textarea> + </fieldset> + <fieldset id="repos"> + <legend>Repository Mapping</legend> + <table summary=""><tr> + <th><label for="path">Path:</label></th> + <td><input type="text" name="path" size="48" value="<?cs + var:config.path ?>" /></td> + </tr></table> + </fieldset> <div class="buttons"> <input type="hidden" name="action" value="<?cs if:config.exists ?>edit<?cs else ?>new<?cs /if ?>" /> @@ -54,37 +59,53 @@ </div> </form><?cs if:config.exists ?><div class="platforms"> - <form class="platforms" method="post" action=""> - <h2>Target Platforms</h2><?cs - if:len(config.platforms) ?><ul><?cs - each:platform = config.platforms ?> - <li><input type="checkbox" name="delete_platform" value="<?cs - var:platform.id ?>"> <a href="<?cs - var:platform.href ?>"><?cs var:platform.name ?></a> - </li><?cs - /each ?></ul><?cs - /if ?> - <div class="buttons"> - <input type="submit" name="new" value="Add target platform" /> - <input type="submit" name="delete" value="Delete selected platforms" /> - </div> - </form> - </div><?cs + <form class="platforms" method="post" action=""> + <h2>Target Platforms</h2><?cs + if:len(config.platforms) ?><ul><?cs + each:platform = config.platforms ?> + <li><input type="checkbox" name="delete_platform" value="<?cs + var:platform.id ?>"> <a href="<?cs + var:platform.href ?>"><?cs var:platform.name ?></a> + </li><?cs + /each ?></ul><?cs + /if ?> + <div class="buttons"> + <input type="submit" name="new" value="Add target platform" /> + <input type="submit" name="delete" value="Delete selected platforms" /> + </div> + </form> + </div><?cs /if ?><?cs - elif:page.mode == 'view_config' ?><ul> - <li>Active: <?cs if:config.active ?>yes<?cs else ?>no<?cs /if ?></li> - <li>Path: <?cs if:config.path ?><a href="<?cs + elif:page.mode == 'view_config' ?><?cs + if:config.can_modify ?><form id="prefs" method="post" class="activation"><?cs + if:!config.active ?><div class="help">This build configuration is currently + inactive.<br /> No builds will be initiated for this configuration<br /> + until it is activated.</div><?cs + else ?><div class="help">This configuration is currently active.</div><?cs + /if ?> + <div class="buttons"> + <input type="hidden" name="action" value="edit" /><?cs + if:config.active ?> + <input type="submit" name="deactivate" value="Deactivate" /><?cs + else ?> + <input type="submit" name="activate" value="Activate" /><?cs + /if ?> + </div></form><?cs + /if ?> + <ul><li>Path: <?cs if:config.path ?><a href="<?cs var:config.browser_href ?>"><?cs var:config.path ?></a></li><?cs /if ?></ul><?cs if:config.description ?><div class="description"><?cs var:config.description ?></div><?cs /if ?><?cs - if:config.can_modify ?><div class="buttons"> - <form method="get" action=""><div> - <input type="hidden" name="action" value="edit" /> - <input type="submit" value="Edit configuration" /> - </div></form><?cs + if:config.can_modify ?> + <div class="buttons"> + <form method="get" action=""><div> + <input type="hidden" name="action" value="edit" /> + <input type="submit" value="Edit configuration" /> + </div></form> + </div><?cs /if ?><?cs if:len(config.platforms) ?> <table class="listing" id="builds"><thead><tr><th>Changeset</th><?cs
--- a/bitten/trac_ext/tests/web_ui.py +++ b/bitten/trac_ext/tests/web_ui.py @@ -109,9 +109,8 @@ req = Mock(Request, method='POST', path_info='/build', redirect=redirect, hdf=HDFWrapper(), perm=PermissionCache(self.env, 'joe'), - args={'action': 'new', 'name': 'test', 'active': 'on', - 'label': 'Test', 'path': 'test/trunk', - 'description': 'Bla bla'}) + args={'action': 'new', 'name': 'test', 'path': 'test/trunk', + 'label': 'Test', 'description': 'Bla bla'}) req.hdf['htdocs_location'] = '/htdocs' module = BuildConfigController(self.env) @@ -119,12 +118,12 @@ self.assertRaises(RequestDone, module.process_request, req) self.assertEqual('/trac.cgi/build/test', redirected_to[0]) - build = BuildConfig.fetch(self.env, 'test') - assert build.exists - assert build.active - self.assertEqual('Test', build.label) - self.assertEqual('test/trunk', build.path) - self.assertEqual('Bla bla', build.description) + config = BuildConfig.fetch(self.env, 'test') + assert config.exists + assert not config.active + self.assertEqual('Test', config.label) + self.assertEqual('test/trunk', config.path) + self.assertEqual('Bla bla', config.description) def test_new_config_cancel(self): PermissionSystem(self.env).grant_permission('joe', 'BUILD_ADMIN') @@ -174,9 +173,8 @@ req = Mock(Request, method='POST', path_info='/build/test', redirect=redirect, hdf=HDFWrapper(), perm=PermissionCache(self.env, 'joe'), - args={'action': 'edit', 'name': 'foo', 'active': 'on', - 'label': 'Test', 'path': 'test/trunk', - 'description': 'Bla bla'}) + args={'action': 'edit', 'name': 'foo', 'path': 'test/trunk', + 'label': 'Test', 'description': 'Bla bla'}) req.hdf['htdocs_location'] = '/htdocs' module = BuildConfigController(self.env) @@ -186,12 +184,11 @@ self.assertEqual(None, BuildConfig.fetch(self.env, 'test')) - build = BuildConfig.fetch(self.env, 'foo') - assert build.exists - assert build.active - self.assertEqual('Test', build.label) - self.assertEqual('test/trunk', build.path) - self.assertEqual('Bla bla', build.description) + config = BuildConfig.fetch(self.env, 'foo') + assert config.exists + self.assertEqual('Test', config.label) + self.assertEqual('test/trunk', config.path) + self.assertEqual('Bla bla', config.description) def test_edit_config_cancel(self): config = BuildConfig(self.env)
--- a/bitten/trac_ext/web_ui.py +++ b/bitten/trac_ext/web_ui.py @@ -160,10 +160,18 @@ if 'cancel' in req.args: req.redirect(self.env.href.build()) - config = BuildConfig(self.env, name=req.args.get('name'), + config_name = req.args.get('name') + + assert not BuildConfig.fetch(self.env, config_name), \ + 'A build configuration with the name "%s" already exists' \ + % config_name + + config = BuildConfig(self.env, name=config_name, path=req.args.get('path', ''), + recipe=req.args.get('recipe', ''), + min_rev=req.args.get('min_rev', ''), + max_rev=req.args.get('max_rev', ''), label=req.args.get('label', ''), - active=req.args.has_key('active'), description=req.args.get('description')) config.insert() @@ -178,13 +186,24 @@ config = BuildConfig.fetch(self.env, config_name) assert config, 'Build configuration "%s" does not exist' % config_name - config.name = req.args.get('name') - config.active = req.args.has_key('active') - config.label = req.args.get('label', '') - config.path = req.args.get('path', '') - config.description = req.args.get('description', '') + + if 'activate' in req.args: + config.active = True + + elif 'deactivate' in req.args: + config.active = False + + else: + # TODO: Validate recipe, repository path, etc + config.name = req.args.get('name') + config.path = req.args.get('path', '') + config.recipe = req.args.get('recipe', '') + config.min_rev = req.args.get('min_rev') + config.max_rev = req.args.get('max_rev') + config.label = req.args.get('label', '') + config.description = req.args.get('description', '') + config.update() - req.redirect(self.env.href.build(config.name)) def _do_create_platform(self, req, config_name): @@ -330,9 +349,11 @@ if config: req.perm.assert_permission('BUILD_MODIFY') req.hdf['config'] = { - 'name': config.name, 'label': config.label, 'path': config.path, - 'active': config.active, 'description': config.description, - 'exists': config.exists + 'name': config.name, 'exists': config.exists, + 'path': config.path, 'active': config.active, + 'recipe': config.recipe, 'min_rev': config.min_rev, + 'max_rev': config.max_rev, 'label': config.label, + 'description': config.description } req.hdf['title'] = 'Edit Build Configuration "%s"' \
--- a/bitten/upgrades.py +++ b/bitten/upgrades.py @@ -42,6 +42,21 @@ "started,stopped) SELECT build,name,description,status," "started,stopped FROM old_step") +def add_recipe_to_config(env, db): + from bitten.model import BuildConfig + cursor = db.cursor() + + cursor.execute("CREATE TEMP TABLE old_config AS " + "SELECT * FROM bitten_config") + cursor.execute("DROP TABLE bitten_config") + for table in BuildConfig._schema: + for stmt in db.to_sql(table): + cursor.execute(stmt) + cursor.execute("INSERT INTO bitten_config (name,path,active,recipe,min_rev," + "max_rev,label,description) SELECT name,path,0,'',NULL," + "NULL,label,description FROM old_config") + map = { - 2: [add_log_table] + 2: [add_log_table], + 3: [add_recipe_to_config] }
deleted file mode 100644 --- a/recipe.xml +++ /dev/null @@ -1,36 +0,0 @@ -<?xml version="1.0"?> -<build description="My project" - xmlns:c="bitten:bitten.build.ctools" - xmlns:python="bitten:bitten.build.pythontools"> - - <step id="build" title="Let Distutils build the python code"> - <python:distutils command="build"/> - </step> - - <step id="test" title="Unit tests" - description="Run unit tests and record code coverage"> - <python:distutils command="unittest"/> - <reports> - <python:unittest file="build/test-results.xml"/> - <python:trace summary="build/test-coverage.txt" - coverdir="build/coverage" include="trac*" exclude="*.tests.*"/> - </reports> - </step> - - <step id="lint" title="Run Pylint" onerror="ignore" - description="Run Pylint to check for bad style and potential errors"> - <python:exec module="logilab.pylint.lint" - output="build/pylint-results.txt" - args="--parseable=yes --include-ids=yes - --disable-msg=C0101,E0201,E0213,W0103,W0704,R0921,R0923 - --ignore=tests bitten"/> - <reports> - <python:pylint file="build/pylint-results.txt"/> - </reports> - </step> - - <step id="dist" title="Package up distributions"> - <python:distutils command="sdist"/> - </step> - -</build>