diff options
author | Lars Wirzenius <lars.wirzenius@codethink.co.uk> | 2013-03-11 17:40:41 +0000 |
---|---|---|
committer | Lars Wirzenius <lars.wirzenius@codethink.co.uk> | 2013-03-11 17:40:41 +0000 |
commit | 63855bc7f8314b216c8842a03b0dfb3a84f1c770 (patch) | |
tree | ba72a3b0ed76752d250658a8fcbbeb0b7bcdd1ef | |
parent | c11cdb16e2d10e29c96873db5796e911979012d8 (diff) | |
parent | 3e28bc3553c24434f5e89c10e475713ca69632f2 (diff) | |
download | morph-63855bc7f8314b216c8842a03b0dfb3a84f1c770.tar.gz |
Merge branch 'staging'
Merging all the things from the staging branch. They have all
already been reviewed.
-rw-r--r-- | README | 32 | ||||
-rwxr-xr-x | baserock-bootstrap | 43 | ||||
-rwxr-xr-x | morphlib/app.py | 5 | ||||
-rw-r--r-- | morphlib/buildcommand.py | 10 | ||||
-rw-r--r-- | morphlib/builder2.py | 18 | ||||
-rw-r--r-- | morphlib/buildsystem.py | 14 | ||||
-rwxr-xr-x | morphlib/exts/virtualbox-ssh.write | 8 | ||||
-rw-r--r-- | morphlib/git.py | 65 | ||||
-rw-r--r-- | morphlib/morph2.py | 8 | ||||
-rw-r--r-- | morphlib/morph2_tests.py | 16 | ||||
-rw-r--r-- | morphlib/plugins/branch_and_merge_plugin.py | 17 | ||||
-rw-r--r-- | morphlib/util.py | 17 | ||||
-rwxr-xr-x | scripts/edit-morph | 22 | ||||
-rwxr-xr-x | scripts/find-artifacts | 96 | ||||
-rwxr-xr-x | tests.branching/add-then-edit.script | 61 | ||||
-rwxr-xr-x | tests.branching/add-then-edit.setup | 40 | ||||
-rw-r--r-- | tests.branching/add-then-edit.stdout | 1 |
17 files changed, 342 insertions, 131 deletions
@@ -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 |