summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <lars.wirzenius@codethink.co.uk>2013-03-11 17:40:41 +0000
committerLars Wirzenius <lars.wirzenius@codethink.co.uk>2013-03-11 17:40:41 +0000
commit63855bc7f8314b216c8842a03b0dfb3a84f1c770 (patch)
treeba72a3b0ed76752d250658a8fcbbeb0b7bcdd1ef
parentc11cdb16e2d10e29c96873db5796e911979012d8 (diff)
parent3e28bc3553c24434f5e89c10e475713ca69632f2 (diff)
downloadmorph-63855bc7f8314b216c8842a03b0dfb3a84f1c770.tar.gz
Merge branch 'staging'
Merging all the things from the staging branch. They have all already been reviewed.
-rw-r--r--README32
-rwxr-xr-xbaserock-bootstrap43
-rwxr-xr-xmorphlib/app.py5
-rw-r--r--morphlib/buildcommand.py10
-rw-r--r--morphlib/builder2.py18
-rw-r--r--morphlib/buildsystem.py14
-rwxr-xr-xmorphlib/exts/virtualbox-ssh.write8
-rw-r--r--morphlib/git.py65
-rw-r--r--morphlib/morph2.py8
-rw-r--r--morphlib/morph2_tests.py16
-rw-r--r--morphlib/plugins/branch_and_merge_plugin.py17
-rw-r--r--morphlib/util.py17
-rwxr-xr-xscripts/edit-morph22
-rwxr-xr-xscripts/find-artifacts96
-rwxr-xr-xtests.branching/add-then-edit.script61
-rwxr-xr-xtests.branching/add-then-edit.setup40
-rw-r--r--tests.branching/add-then-edit.stdout1
17 files changed, 342 insertions, 131 deletions
diff --git a/README b/README
index 9ed92845..36d33a43 100644
--- a/README
+++ b/README
@@ -80,15 +80,44 @@ For chunks, use the following fields:
`*-commands` fields; only `autotools` is currently known; the commands
that the build system specifies can be overridden;
optional
+
+* `pre-configure-commands`: a list of shell commands to run at
+ the configuration phase of a build, before the list in `configure-commands`;
+ optional
* `configure-commands`: a list of shell commands to run at the configuraiton
phase of a build; optional
+* `post-configure-commands`: a list of shell commands to run at
+ the configuration phase of a build, after the list in `configure-commands`;
+ optional
+
+* `pre-build-commands`: a list of shell commands to run at
+ the build phase of a build, before the list in `build-commands`;
+ optional
* `build-commands`: a list of shell commands to run to build (compile) the
project; optional
+* `post-build-commands`: a list of shell commands to run at
+ the build phase of a build, after the list in `build-commands`;
+ optional
+
+* `pre-test-commands`: a list of shell commands to run at
+ the test phase of a build, before the list in `test-commands`;
+ optional
* `test-commands`: a list of shell commands to run unit tests and other
non-interactive tests on the built but un-installed project; optional
+* `post-test-commands`: a list of shell commands to run at
+ the test phase of a build, after the list in `test-commands`;
+ optional
+
+* `pre-install-commands`: a list of shell commands to run at
+ the install phase of a build, before the list in `install-commands`;
+ optional
* `install-commands`: a list of shell commands to install the built project;
the install should go into the directory named in the `DESTDIR` environment
variable, not the actual system; optional
+* `post-install-commands`: a list of shell commands to run at
+ the install phase of a build, after the list in `install-commands`;
+ optional
+
* `max-jobs`: a string to be given to `make` as the argument to the `-j`
option to specify the maximum number of parallel jobs; the only sensible
value is `"1"` (including the quotes), to prevent parallel jobs to run
@@ -96,6 +125,7 @@ For chunks, use the following fields:
since the other phases are often not safe when run in parallel; `morph`
picks a default value based on the number of CPUs on the host system;
optional
+
* `chunks`: a key/value map of lists of regular expressions;
the key is the name
of a binary chunk, the regexps match the pathnames that will be
@@ -332,7 +362,7 @@ uses the strata that were build in stage 2.
Legalese
--------
-Copyright (C) 2011, 2012 Codethink Limited
+Copyright (C) 2011-2013 Codethink Limited
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/baserock-bootstrap b/baserock-bootstrap
index 315ed0fa..619baeb4 100755
--- a/baserock-bootstrap
+++ b/baserock-bootstrap
@@ -365,6 +365,49 @@ pass3_build_with_morph_in_chroot()
{
echo "Building Baserock with morph"
+ cat <<'EOF' >"$LFS/usr/bin/linux-user-chroot"
+#!/bin/sh
+
+CHDIR=.
+
+while true; do
+ case "$1" in
+ --help)
+ echo 'See "man linux-user-chroot"'
+ exit 0
+ ;;
+ --version)
+ echo 'Fake'
+ exit 0
+ ;;
+ --mount-bind)
+ # swallow option and arguments
+ shift 3
+ ;;
+ --mount-readonly)
+ shift 2
+ ;;
+ --mount-proc|--unshare-ipc|--unshare-pid)
+ # swallow configure flag
+ shift
+ ;;
+ --chdir)
+ CHDIR="$2"
+ shift 2
+ ;;
+ *)
+ # terminate arg processing
+ ROOTDIR="$1"
+ shift
+ break
+ ;;
+ esac
+done
+
+exec chroot "$ROOTDIR" sh -c 'cd "$1" && shift && exec "$@"' -- "$CHDIR" "$@"
+EOF
+ chmod +x "$LFS/usr/bin/linux-user-chroot"
+
cat <<EOF > "$LFS/baserock/build_pass3.sh"
#!/bin/bash
set -e
diff --git a/morphlib/app.py b/morphlib/app.py
index 21e18e32..87710ee5 100755
--- a/morphlib/app.py
+++ b/morphlib/app.py
@@ -395,8 +395,9 @@ class Morph(cliapp.Application):
chatty=True)
# Log the environment.
- for name in kwargs['env']:
- logging.debug('environment: %s=%s' % (name, kwargs['env'][name]))
+ prev = getattr(self, 'prev_env', {})
+ morphlib.util.log_dict_diff(kwargs['env'], prev)
+ self.prev_env = kwargs['env']
# run the command line
return cliapp.Application.runcmd(self, argv, *args, **kwargs)
diff --git a/morphlib/buildcommand.py b/morphlib/buildcommand.py
index f857171e..dfacd760 100644
--- a/morphlib/buildcommand.py
+++ b/morphlib/buildcommand.py
@@ -223,7 +223,7 @@ class BuildCommand(object):
if artifact.source.morphology.needs_staging_area:
self.install_fillers(staging_area)
self.install_chunk_artifacts(staging_area,
- deps)
+ deps, artifact)
morphlib.builder2.ldconfig(self.app.runcmd,
staging_area.tempdir)
@@ -359,7 +359,7 @@ class BuildCommand(object):
filename=filename)
staging_area.install_artifact(f)
- def install_chunk_artifacts(self, staging_area, artifacts):
+ def install_chunk_artifacts(self, staging_area, artifacts, parent_art):
'''Install chunk artifacts into staging area.
We only ever care about chunk artifacts as build dependencies,
@@ -373,7 +373,8 @@ class BuildCommand(object):
for artifact in artifacts:
if artifact.source.morphology['kind'] != 'chunk':
continue
- self.app.status(msg='Installing chunk %(chunk_name)s',
+ self.app.status(msg='[%(name)s] Installing chunk %(chunk_name)s',
+ name=parent_art.name,
chunk_name=artifact.name)
handle = self.lac.get(artifact)
staging_area.install_artifact(handle)
@@ -381,7 +382,8 @@ class BuildCommand(object):
def build_and_cache(self, staging_area, artifact):
'''Build an artifact and put it into the local artifact cache.'''
- self.app.status(msg='Starting actual build')
+ self.app.status(msg='Starting actual build: %(name)s',
+ name=artifact.name)
setup_mounts = self.app.settings['staging-chroot']
builder = morphlib.builder2.Builder(
self.app, staging_area, self.lac, self.rac, self.lrc,
diff --git a/morphlib/builder2.py b/morphlib/builder2.py
index 82a95820..73745d66 100644
--- a/morphlib/builder2.py
+++ b/morphlib/builder2.py
@@ -377,10 +377,20 @@ class ChunkBuilder(BuilderBase):
relative_destdir = self.staging_area.relative(destdir)
self.build_env.env['DESTDIR'] = relative_destdir
- steps = [('configure', False),
- ('build', True),
- ('test', False),
- ('install', False)]
+ steps = [
+ ('pre-configure', False),
+ ('configure', False),
+ ('post-configure', False),
+ ('pre-build', True),
+ ('build', True),
+ ('post-build', True),
+ ('pre-test', False),
+ ('test', False),
+ ('post-test', False),
+ ('pre-install', False),
+ ('install', False),
+ ('post-install', False),
+ ]
for step, in_parallel in steps:
with self.build_watch(step):
key = '%s-commands' % step
diff --git a/morphlib/buildsystem.py b/morphlib/buildsystem.py
index 6287063a..a6645eb2 100644
--- a/morphlib/buildsystem.py
+++ b/morphlib/buildsystem.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012 Codethink Limited
+# Copyright (C) 2012-2013 Codethink Limited
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -32,10 +32,18 @@ class BuildSystem(object):
'''
def __init__(self):
+ self.pre_configure_commands = []
self.configure_commands = []
+ self.post_configure_commands = []
+ self.pre_build_commands = []
self.build_commands = []
+ self.post_build_commands = []
+ self.pre_test_commands = []
self.test_commands = []
+ self.post_test_commands = []
+ self.pre_install_commands = []
self.install_commands = []
+ self.post_install_commands = []
def __getitem__(self, key):
key = '_'.join(key.split('-'))
@@ -82,6 +90,7 @@ class DummyBuildSystem(BuildSystem):
name = 'dummy'
def __init__(self):
+ BuildSystem.__init__(self)
self.configure_commands = ['echo dummy configure']
self.build_commands = ['echo dummy build']
self.test_commands = ['echo dummy test']
@@ -98,6 +107,7 @@ class AutotoolsBuildSystem(BuildSystem):
name = 'autotools'
def __init__(self):
+ BuildSystem.__init__(self)
self.configure_commands = [
'export NOCONFIGURE=1; ' +
'if [ -e autogen ]; then ./autogen; ' +
@@ -134,6 +144,7 @@ class PythonDistutilsBuildSystem(BuildSystem):
name = 'python-distutils'
def __init__(self):
+ BuildSystem.__init__(self)
self.configure_commands = [
]
self.build_commands = [
@@ -160,6 +171,7 @@ class CPANBuildSystem(BuildSystem):
name = 'cpan'
def __init__(self):
+ BuildSystem.__init__(self)
self.configure_commands = [
'perl Makefile.PL INSTALLDIRS=perl '
'INSTALLARCHLIB="$PREFIX/lib/perl" '
diff --git a/morphlib/exts/virtualbox-ssh.write b/morphlib/exts/virtualbox-ssh.write
index c21dcc57..9b99c7a1 100755
--- a/morphlib/exts/virtualbox-ssh.write
+++ b/morphlib/exts/virtualbox-ssh.write
@@ -26,6 +26,7 @@ accessed over ssh. The machine gets created, but not started.
import cliapp
import os
import re
+import sys
import time
import tempfile
import urlparse
@@ -68,7 +69,7 @@ class VirtualBoxPlusSshWriteExtension(morphlib.writeexts.WriteExtension):
try:
self.transfer_and_convert_to_vdi(
- raw_disk, size, ssh_host, vdi_path)
+ raw_disk, ssh_host, vdi_path)
self.create_virtualbox_guest(ssh_host, vm_name, vdi_path)
except BaseException:
sys.stderr.write('Error deploying to VirtualBox')
@@ -93,14 +94,15 @@ class VirtualBoxPlusSshWriteExtension(morphlib.writeexts.WriteExtension):
raise cliapp.AppException('Cannot parse location %s' % location)
return x.netloc, m.group('guest'), m.group('path')
- def transfer_and_convert_to_vdi(self, raw_disk, size, ssh_host, vdi_path):
+ def transfer_and_convert_to_vdi(self, raw_disk, ssh_host, vdi_path):
'''Transfer raw disk image to VirtualBox host, and convert to VDI.'''
self.status(msg='Transfer disk and convert to VDI')
with open(raw_disk, 'rb') as f:
cliapp.runcmd(
['ssh', ssh_host,
- 'VBoxManage', 'convertfromraw', 'stdin', vdi_path, str(size)],
+ 'VBoxManage', 'convertfromraw', 'stdin', vdi_path,
+ str(os.path.getsize(raw_disk))],
stdin=f)
def create_virtualbox_guest(self, ssh_host, vm_name, vdi_path):
diff --git a/morphlib/git.py b/morphlib/git.py
index fffc05d0..c491991c 100644
--- a/morphlib/git.py
+++ b/morphlib/git.py
@@ -157,17 +157,45 @@ def update_submodules(app, repo_dir): # pragma: no cover
app.runcmd(['git', 'submodule', 'update'], cwd=repo_dir)
+class ConfigNotSetException(cliapp.AppException):
+
+ def __init__(self, missing, defaults):
+ self.missing = missing
+ self.defaults = defaults
+ if len(missing) == 1:
+ self.preamble = ('Git configuration for %s has not been set. '
+ 'Please set it with:' % missing[0])
+ else:
+ self.preamble = ('Git configuration for keys %s and %s '
+ 'have not been set. Please set them with:'
+ % (', '.join(missing[:-1]), missing[-1]))
+
+ def __str__(self):
+ lines = [self.preamble]
+ lines.extend('git config --global %s \'%s\'' % (k, self.defaults[k])
+ for k in self.missing)
+ return '\n '.join(lines)
+
+
+class IdentityNotSetException(ConfigNotSetException):
+
+ preamble = 'Git user info incomplete. Please set your identity, using:'
+
+ def __init__(self, missing):
+ self.defaults = {"user.name": "My Name",
+ "user.email": "me@example.com"}
+ self.missing = missing
+
+
def get_user_name(runcmd):
'''Get user.name configuration setting. Complain if none was found.'''
if 'GIT_AUTHOR_NAME' in os.environ:
return os.environ['GIT_AUTHOR_NAME'].strip()
try:
- return runcmd(['git', 'config', 'user.name']).strip()
- except cliapp.AppException:
- raise cliapp.AppException(
- 'No git user info found. Please set your identity, using: \n'
- ' git config --global user.name "My Name"\n'
- ' git config --global user.email "me@example.com"\n')
+ config = check_config_set(runcmd, keys={"user.name": "My Name"})
+ return config['user.name']
+ except ConfigNotSetException, e:
+ raise IdentityNotSetException(e.missing)
def get_user_email(runcmd):
@@ -175,29 +203,24 @@ def get_user_email(runcmd):
if 'GIT_AUTHOR_EMAIL' in os.environ:
return os.environ['GIT_AUTHOR_EMAIL'].strip()
try:
- return runcmd(['git', 'config', 'user.email']).strip()
- except cliapp.AppException:
- raise cliapp.AppException(
- 'No git user info found. Please set your identity, using: \n'
- ' git config --global user.email "My Name"\n'
- ' git config --global user.email "me@example.com"\n')
+ cfg = check_config_set(runcmd, keys={"user.email": "me@example.com"})
+ return cfg['user.email']
+ except ConfigNotSetException, e:
+ raise IdentityNotSetException(e.missing)
-
-def check_config_set(runcmd, keys=("user.name", "user.email"), cwd='.'):
+def check_config_set(runcmd, keys, cwd='.'):
''' Check whether the given keys have values in git config. '''
missing = []
+ found = {}
for key in keys:
try:
- runcmd(['git', 'config', key], cwd=cwd)
+ value = runcmd(['git', 'config', key], cwd=cwd).strip()
+ found[key] = value
except cliapp.AppException:
missing.append(key)
if missing:
- if len(missing) == 1:
- emesg = 'Git configuration for %s has not been set' % missing[0]
- else:
- emesg = ('Git configuration for keys %s and %s have not been set'
- % (', '.join(missing[:-1]), missing[-1]))
- raise cliapp.AppException(emesg)
+ raise ConfigNotSetException(missing, keys)
+ return found
def set_remote(runcmd, gitdir, name, url):
diff --git a/morphlib/morph2.py b/morphlib/morph2.py
index ee58ecdc..3cdf49a9 100644
--- a/morphlib/morph2.py
+++ b/morphlib/morph2.py
@@ -32,10 +32,18 @@ class Morphology(object):
static_defaults = {
'chunk': [
('description', ''),
+ ('pre-configure-commands', None),
('configure-commands', None),
+ ('post-configure-commands', None),
+ ('pre-build-commands', None),
('build-commands', None),
+ ('post-build-commands', None),
+ ('pre-test-commands', None),
('test-commands', None),
+ ('post-test-commands', None),
+ ('pre-install-commands', None),
('install-commands', None),
+ ('post-install-commands', None),
('devices', None),
('chunks', []),
('max-jobs', None),
diff --git a/morphlib/morph2_tests.py b/morphlib/morph2_tests.py
index d2973d5c..49be9c8c 100644
--- a/morphlib/morph2_tests.py
+++ b/morphlib/morph2_tests.py
@@ -37,10 +37,18 @@ class MorphologyTests(unittest.TestCase):
self.assertEqual(m['name'], 'foo')
self.assertEqual(m['kind'], 'chunk')
self.assertEqual(m['build-system'], 'manual')
+ self.assertEqual(m['pre-configure-commands'], None)
self.assertEqual(m['configure-commands'], None)
+ self.assertEqual(m['post-configure-commands'], None)
+ self.assertEqual(m['pre-build-commands'], None)
self.assertEqual(m['build-commands'], None)
+ self.assertEqual(m['post-build-commands'], None)
+ self.assertEqual(m['pre-test-commands'], None)
self.assertEqual(m['test-commands'], None)
+ self.assertEqual(m['post-test-commands'], None)
+ self.assertEqual(m['pre-install-commands'], None)
self.assertEqual(m['install-commands'], None)
+ self.assertEqual(m['post-install-commands'], None)
self.assertEqual(m['max-jobs'], None)
self.assertEqual(m['chunks'], [])
@@ -55,10 +63,18 @@ class MorphologyTests(unittest.TestCase):
self.assertEqual(m['name'], 'foo')
self.assertEqual(m['kind'], 'chunk')
self.assertEqual(m['build-system'], 'manual')
+ self.assertEqual(m['pre-configure-commands'], None)
self.assertEqual(m['configure-commands'], None)
+ self.assertEqual(m['post-configure-commands'], None)
+ self.assertEqual(m['pre-build-commands'], None)
self.assertEqual(m['build-commands'], None)
+ self.assertEqual(m['post-build-commands'], None)
+ self.assertEqual(m['pre-test-commands'], None)
self.assertEqual(m['test-commands'], None)
+ self.assertEqual(m['post-test-commands'], None)
+ self.assertEqual(m['pre-install-commands'], None)
self.assertEqual(m['install-commands'], None)
+ self.assertEqual(m['post-install-commands'], None)
self.assertEqual(m['max-jobs'], None)
self.assertEqual(m['chunks'], [])
diff --git a/morphlib/plugins/branch_and_merge_plugin.py b/morphlib/plugins/branch_and_merge_plugin.py
index cc221e85..141c3187 100644
--- a/morphlib/plugins/branch_and_merge_plugin.py
+++ b/morphlib/plugins/branch_and_merge_plugin.py
@@ -30,7 +30,7 @@ import uuid
import morphlib
-def requires_git_config(*keys):
+def warns_git_config(keys):
def decorator(func):
@functools.wraps(func)
def check_config(self, *args, **kwargs):
@@ -38,13 +38,17 @@ def requires_git_config(*keys):
morphlib.git.check_config_set(self.app.runcmd, keys)
except cliapp.AppException, e:
self.app.status(msg="WARNING: %(message)s",
- message=e.msg, error=True)
+ message=str(e), error=True)
return func(self, *args, **kwargs)
return check_config
return decorator
+warns_git_identity = warns_git_config({'user.name': 'My Name',
+ 'user.email': 'me@example.com'})
+
+
class BranchAndMergePlugin(cliapp.Plugin):
def __init__(self):
@@ -588,7 +592,7 @@ class BranchAndMergePlugin(cliapp.Plugin):
self.remove_branch_dir_safe(workspace, branch_name)
raise
- @requires_git_config('user.name', 'user.email')
+ @warns_git_identity
def branch(self, args):
'''Create a new system branch.'''
@@ -609,7 +613,7 @@ class BranchAndMergePlugin(cliapp.Plugin):
workspace = self.deduce_workspace()
self._create_branch(workspace, new_branch, repo, commit)
- @requires_git_config('user.name', 'user.email')
+ @warns_git_identity
def checkout(self, args):
'''Check out an existing system branch.'''
@@ -681,7 +685,7 @@ class BranchAndMergePlugin(cliapp.Plugin):
branch_dir, spec['repo'], branch, parent_ref=spec['ref'])
return repo_dir
- @requires_git_config('user.name', 'user.email')
+ @warns_git_identity
def edit(self, args):
'''Edit a component in a system branch.'''
@@ -1002,7 +1006,7 @@ class BranchAndMergePlugin(cliapp.Plugin):
self.print_changelog('The following changes were made but have not '
'been committed')
- @requires_git_config('user.name', 'user.email')
+ @warns_git_identity
def tag(self, args):
if len(args) < 1:
raise cliapp.AppException('morph tag expects a tag name')
@@ -1502,7 +1506,6 @@ class BranchAndMergePlugin(cliapp.Plugin):
self.reset_work_tree_safe(repo_dir)
raise
- @requires_git_config('user.name', 'user.email')
def build(self, args):
'''Build a system from the current system branch'''
diff --git a/morphlib/util.py b/morphlib/util.py
index c832a141..b4e06092 100644
--- a/morphlib/util.py
+++ b/morphlib/util.py
@@ -16,6 +16,7 @@
import re
import morphlib
+import logging
'''Utility functions for morph.'''
@@ -170,6 +171,22 @@ def new_repo_caches(app): # pragma: no cover
return lrc, rrc
+def log_dict_diff(cur, pre): # pragma: no cover
+ '''Log the differences between two dicts to debug log'''
+ dictA = cur
+ dictB = pre
+ for key in dictA.keys():
+ if key not in dictB:
+ logging.debug("New environment: %s = %s" % (key, dictA[key]))
+ elif dictA[key] != dictB[key]:
+ logging.debug(
+ "Environment changed: %(key)s = %(valA)s to %(key)s = %(valB)s"
+ % {"key": key, "valA": dictA[key], "valB": dictB[key]})
+ for key in dictB.keys():
+ if key not in dictA:
+ logging.debug("Environment removed: %s = %s" % (key, dictB[key]))
+
+
# This acquired from rdiff-backup which is GPLv2+ and a patch from 2011
# which has not yet been merged, combined with a tad of tidying from us.
def copyfileobj(inputfp, outputfp, blocksize=1024*1024): # pragma: no cover
diff --git a/scripts/edit-morph b/scripts/edit-morph
index a4932e75..967bbc13 100755
--- a/scripts/edit-morph
+++ b/scripts/edit-morph
@@ -41,8 +41,7 @@ class EditMorph(cliapp.Application):
if expected_kind is not None and morphology['kind'] != expected_kind:
raise morphlib.Error("Expected: a %s morphology" % expected_kind)
- return morphology
-
+ return morphology, text
def cmd_remove_chunk(self, args):
'''Removes from STRATUM all reference of CHUNK'''
@@ -53,7 +52,8 @@ class EditMorph(cliapp.Application):
file_name = args[0]
chunk_name = args[1]
- morphology = self.load_morphology(file_name, expected_kind = 'stratum')
+ morphology, text = self.load_morphology(file_name,
+ expected_kind='stratum')
component_count = 0
build_depends_count = 0
@@ -68,7 +68,7 @@ class EditMorph(cliapp.Application):
morphology._dict['chunks'] = new_chunks
with morphlib.savefile.SaveFile(file_name, 'w') as f:
- morphology.write_to_file(f)
+ morphology.update_text(text, f)
self.output.write("Removed: %i chunk(s) and %i build depend(s).\n" %
(component_count, build_depends_count))
@@ -80,7 +80,8 @@ class EditMorph(cliapp.Application):
raise cliapp.AppException("sort expects a morphology file name")
file_name = args[0]
- morphology = self.load_morphology(file_name, expected_kind='stratum')
+ morphology, text = self.load_morphology(file_name,
+ expected_kind='stratum')
for chunk in morphology['chunks']:
chunk['build-depends'].sort()
@@ -88,7 +89,7 @@ class EditMorph(cliapp.Application):
morphology._dict['chunks'] = self.sort_chunks(morphology['chunks'])
with morphlib.savefile.SaveFile(file_name, 'w') as f:
- morphology.write_to_file(f)
+ morphology.update_text(text, f)
def cmd_to_json(self, args):
"""Convert one or more FILES to JSON.
@@ -101,9 +102,7 @@ class EditMorph(cliapp.Application):
for file_name in args:
try:
- with open(file_name, 'r') as f:
- text = f.read()
- morphology = morphlib.morph2.Morphology(text)
+ morphology, text = self.load_morphology(file_name)
if not file_name.endswith('.yaml'):
raise morphlib.Error('file name does not end with .yaml')
@@ -124,9 +123,8 @@ class EditMorph(cliapp.Application):
for file_name in args:
try:
- with open(file_name, 'r') as f:
- text = f.read()
- morphology = morphlib.morph2.Morphology(text)
+ morphology, text = self.load_morphology(file_name)
+
with morphlib.savefile.SaveFile(file_name + '.yaml', 'w') as f:
morphology.update_text(text, f, convert_to='yaml')
except Exception as e:
diff --git a/scripts/find-artifacts b/scripts/find-artifacts
index bd2c02c8..462126fc 100755
--- a/scripts/find-artifacts
+++ b/scripts/find-artifacts
@@ -1,6 +1,6 @@
#!/usr/bin/env python
#
-# Copyright (C) 2011-2012 Codethink Limited
+# Copyright (C) 2011-2013 Codethink Limited
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -31,86 +31,27 @@ path = os.path.dirname(os.path.dirname(__file__))
sys.path.append(path)
import morphlib
-# MountableImage yanked from the TrebuchetPlugin and modified slightly
-class MountableImage(object):
- '''Mountable image (deals with decompression).
-
- Note, this is a read-only mount in the sense that the decompressed
- image is not then recompressed after, instead any changes are discarded.
-
- '''
- def __init__(self, app, artifact_path):
- self.app = app
- self.artifact_path = artifact_path
-
- def setup(self, path):
- (tempfd, self.temp_path) = \
- tempfile.mkstemp(dir=self.app.settings['tempdir'])
-
- try:
- with os.fdopen(tempfd, "wb") as outfh:
- infh = gzip.open(path, "rb")
- morphlib.util.copyfileobj(infh, outfh)
- infh.close()
- except:
- os.unlink(self.temp_path)
- raise
- part = morphlib.fsutils.setup_device_mapping(self.app.runcmd,
- self.temp_path)
- mount_point = tempfile.mkdtemp(dir=self.app.settings['tempdir'])
- morphlib.fsutils.mount(self.app.runcmd, part, mount_point)
- self.mount_point = mount_point
- return mount_point
-
- def cleanup(self, path, mount_point):
- try:
- morphlib.fsutils.unmount(self.app.runcmd, mount_point)
- except:
- pass
- try:
- morphlib.fsutils.undo_device_mapping(self.app.runcmd, path)
- except:
- pass
- try:
- os.rmdir(mount_point)
- os.unlink(path)
- except:
- pass
-
- def __enter__(self):
- return self.setup(self.artifact_path)
-
- def __exit__(self, exctype, excvalue, exctraceback):
- self.cleanup(self.temp_path, self.mount_point)
-
-
-class FindArtifacts(cliapp.Application):
-
- def add_settings(self):
- self.settings.string(['cachedir'], 'The directory morph writes its '
- 'cache to')
- self.settings.string(['tempdir'], 'A temporary directory to mount the '
- 'system image in')
+class FindArtifacts(morphlib.app.Morph):
def process_args(self, args):
artifacts_dir = os.path.join(self.settings['cachedir'], 'artifacts')
# args[0] is the path to the built image.
- # Mount the image
- mount_point = None
- with MountableImage(self, args[0]) as mount_point:
- # For each meta file:
- metadir = os.path.join(mount_point, 'factory-run', 'baserock')
- metaglob = os.path.join(metadir, '*.meta')
- for metafile in glob.iglob(metaglob):
- metafilepath = os.path.join(metadir, metafile)
- # Read the file as JSON and extract the kind and cache-key
- with open(metafilepath) as openedmetafile:
- metajson = json.load(openedmetafile)
- cache_key = metajson['cache-key']
-
- # Grab every file in the artifact cache which matches the
- # cache-key
+ artifact = args[0]
+
+ def find_artifacts(dirname):
+ metadirs = [
+ os.path.join(dirname, 'factory', 'baserock'),
+ os.path.join(dirname, 'baserock')
+ ]
+ existing_metadirs = [x for x in metadirs if os.path.isdir(x)]
+ if not existing_metadirs:
+ raise NotASystemArtifactError(artifact)
+ metadir = existing_metadirs[0]
+ for basename in glob.glob(os.path.join(metadir, '*.meta')):
+ metafile = os.path.join(metadir, basename)
+ metadata = json.load(open(metafile))
+ cache_key = metadata['cache-key']
artifact_glob = os.path.join(artifacts_dir, cache_key) + '*'
found_artifacts = glob.glob(artifact_glob)
if not found_artifacts:
@@ -120,4 +61,7 @@ class FindArtifacts(cliapp.Application):
for cached_file in found_artifacts:
self.output.write(cached_file + "\n")
+ morphlib.bins.call_in_artifact_directory(
+ self, artifact, find_artifacts)
+
FindArtifacts().run()
diff --git a/tests.branching/add-then-edit.script b/tests.branching/add-then-edit.script
new file mode 100755
index 00000000..40ee7161
--- /dev/null
+++ b/tests.branching/add-then-edit.script
@@ -0,0 +1,61 @@
+#!/bin/sh
+#
+# Copyright (C) 2013 Codethink Limited
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+## Test the workflow of adding a new chunk to a stratum then editing it
+
+set -eu
+
+cd "$DATADIR/workspace"
+"$SRCDIR/scripts/test-morph" init
+"$SRCDIR/scripts/test-morph" branch test:morphs "me/add-then-edit"
+
+cd "me/add-then-edit"
+
+# add a chunk
+cd test:morphs
+
+## Sub-optimally, to alter the stratum, you have to `morph edit` it first
+"$SRCDIR/scripts/test-morph" edit hello-system hello-stratum
+
+git apply <<'EOF'
+diff --git a/hello-stratum.morph b/hello-stratum.morph
+index 3b7be17..c79a9af 100644
+--- a/hello-stratum.morph
++++ b/hello-stratum.morph
+@@ -7,6 +7,12 @@
+ "repo": "test:hello",
+ "ref": "master",
+ "build-depends": []
++ },
++ {
++ "name": "goodbye",
++ "repo": "test:goodbye",
++ "ref": "master",
++ "build-depends": []
+ }
+ ]
+ }
+EOF
+
+"$SRCDIR/scripts/test-morph" edit hello-system hello-stratum goodbye
+
+# check whether the stratum still contains the goodbye chunk
+grep -qFe goodbye hello-stratum.morph
+
+# check whether edit has cloned the repository to the right branch
+git --git-dir="../test:goodbye/.git" rev-parse --abbrev-ref HEAD
diff --git a/tests.branching/add-then-edit.setup b/tests.branching/add-then-edit.setup
new file mode 100755
index 00000000..7dbe28c0
--- /dev/null
+++ b/tests.branching/add-then-edit.setup
@@ -0,0 +1,40 @@
+#!/bin/sh
+# Copyright (C) 2013 Codethink Limited
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+set -eu
+
+# Create goodbye chunk
+mkdir "$DATADIR/goodbye"
+cd "$DATADIR/goodbye"
+
+cat >goodbye <<'EOF'
+#!/bin/sh
+echo goodbye
+EOF
+chmod +x goodbye
+
+cat >goodbye.morph <<'EOF'
+{
+ "name": "goodbye",
+ "kind": "chunk",
+ "install-commands": [
+ "install goodbye \"$DESTDIR$PREFIX/bin/goodbye\""
+ ]
+}
+EOF
+git init .
+git add goodbye.morph goodbye
+git commit -m "Initial commit"
diff --git a/tests.branching/add-then-edit.stdout b/tests.branching/add-then-edit.stdout
new file mode 100644
index 00000000..e0950ab5
--- /dev/null
+++ b/tests.branching/add-then-edit.stdout
@@ -0,0 +1 @@
+me/add-then-edit