IMPLEMENTS implementations ========================== Implementation sections for definitions repo --------------------------------------------- IMPLEMENTS WHEN the user clones definitions git clone "$(cat $DATADIR/definitions-repo-url)" "$DATADIR/definitions" IMPLEMENTS WHEN the user pulls definitions from (\S+) (\S+) run_in "$DATADIR/definitions" git pull "$MATCH_1" "$MATCH_2" IMPLEMENTS WHEN the user creates a new definitions branch (\S+), based on (\S+) run_in "$DATADIR/definitions" git branch "$MATCH_1" "$MATCH_2" IMPLEMENTS WHEN the user checks out definitions branch (\S+) run_in "$DATADIR/definitions" git checkout "$MATCH_1" Implementation sections for checking morph exit status ------------------------------------------------------ IMPLEMENTS THEN morph failed case $(cat "$DATADIR/morph-exit") in 0) die "Morph should have failed, but didn't. Unexpected success!" ;; esac IMPLEMENTS THEN morph succeeded case $(cat "$DATADIR/morph-exit") in 0) echo "Morph succeeded!" ;; *) cat "$DATADIR/result-latest" >&2 die "Morph should have succeeded, but didn't. Unexpected failure!" ;; esac IMPLEMENTS THEN morph output (.*) grep -q "$MATCH_1" "$DATADIR/out-latest" Implementation sections related to a simulated Trove ---------------------------------------------------- Morph needs access to a Trove, i.e., a git server, in order to do certain kinds of stuff. We simulate this by creating a set of git repositories locally, which we'll tell Morph to access using `file:` URLs. Specifically, we'll create a repository to hold system and stratum morphologies, and another to hold a chunk. IMPLEMENTS GIVEN a chunk with recursive submodules mkdir "$DATADIR/gits/grandchild-chunk" cd "$DATADIR/gits/grandchild-chunk" git init . touch grandchild-file git add . git commit -m "Initial commit" mkdir "$DATADIR/gits/child-chunk" cd "$DATADIR/gits/child-chunk" git init . touch child-file git add . git commit -m "Initial commit" git submodule add -b master file://$DATADIR/gits/grandchild-chunk sed -i -e 's#file://$DATADIR/gits/granchild-chunk#fake/location#' .gitmodules git add . git commit -m "Initial submodule" mkdir "$DATADIR/gits/chunk-with-submodules" cd "$DATADIR/gits/chunk-with-submodules" git init . git add . git commit --allow-empty -m "Initial commit" git submodule add -b master file://$DATADIR/gits/child-chunk sed -i -e 's#file://$DATADIR/gits/child-chunk#fake/location#' .gitmodules git add . git commit -m "Add submodule" cd "$DATADIR/gits/definitions" echo "version: 8" > VERSION cat << EOF >> strata/core.morph - name: chunk-with-submodules morph: chunk-with-submodules.morph repo: test:chunk-with-submodules ref: master submodules: grandchild-chunk: url: file://$DATADIR/gits/grandchild-chunk child-chunk: url: file://$DATADIR/gits/child-chunk EOF cat << EOF >> chunk-with-submodules.morph name: chunk-with-submodules kind: chunk build-system: manual build-commands: - file exists child-chunk/child-file - file exists child-chunk/grandchild-chunk/grandchild-file EOF git add . git commit -m "Add moar stuff" IMPLEMENTS GIVEN a git server # Create a directory for all the git repositories. mkdir "$DATADIR/gits" # Create the bootstrap chunk repositories mkdir "$DATADIR/gits/bootstrap-chunk" cd "$DATADIR/gits/bootstrap-chunk" git init . git checkout -b bootstrap cp "$SRCDIR/scripts/test-shell.c" sh.c install /dev/stdin <<'EOF' configure #!/bin/true EOF printf >Makefile ' CFLAGS = -D_GNU_SOURCE -static all: sh install: sh \tinstall -D -m755 sh $(DESTDIR)/bin/sh' git add . git commit -m "Add bootstrap shell" git checkout --orphan master HEAD # Commit a pre-built test-shell, as a compiler is too heavy to bootstrap make sh mkdir bin mv sh bin/sh git rm -f Makefile sh.c configure git add bin/sh git commit -m "Build bootstrap shell with bootstrap shell" # Create the test chunk repository. mkdir "$DATADIR/gits/test-chunk" cd "$DATADIR/gits/test-chunk" git init . # To verify that chunk splitting works, we have a chunk that installs # dummy files in all the places that different kinds of files are # usually installed. e.g. executables in `/bin` and `/usr/bin` PREFIX=/usr DESTDIR=. # It's important that we can test whether executables get # installed, so we install an empty script into `/usr/bin/test` and # `/usr/sbin/test`. # `install -D` will create the leading components for us, and install # defaults to creating the file with its executable bit set. # `install` needs a source file to install, but since we only care # that the file exists, rather than its contents, we can use /dev/null # as the source. for bindir in bin sbin; do install -D /dev/null "$DESTDIR/$PREFIX/$bindir/test" done # We need shared libraries too, sometimes they're libraries to support # the executables that a chunk provides, sometimes for other chunks. # Libraries can be found in a variety of places, hence why we install # them into lib, lib32 and lib64. # Shared libraries' file names start with lib and end with `.so` # for shared-object, with version numbers optionally suffixed. for libdir in lib lib32 lib64; do dirpath="$DESTDIR/$PREFIX/$libdir" install -D /dev/null "$dirpath/libtest.so" ln -s libtest.so "$dirpath/libtest.so.0" ln -s libtest.so.0 "$dirpath/libtest.so.0.0" ln -s libtest.so.0.0 "$dirpath/libtest.so.0.0.0" done # Shared objects aren't the only kind of library, some executable # binaries count as libraries, such as git's plumbing commands. # In some distributions they go into /lib, in others, and the default # autotools configuration, they go into /libexec. install -D /dev/stdin "$DESTDIR/$PREFIX/libexec/test-bin" <<'EOF' #!/bin/sh echo Hello World EOF # As well as run-time libraries, there's development files. For C # this is headers, which describe the API of the libraries, which # then use the shared objects, and other files which are needed # to build the executables, but aren't needed to run them, such as # static libraries. # Header files go into `include` and end with `.h`. They are not # executable, so the install command changes the permissions with the # `-m` option. install -D -m 644 /dev/stdin <<'EOF' "$DESTDIR/$PREFIX/include/test.h" int foo(void); EOF # `pkg-config` is a standard way to locate libraries and get the # compiler flags needed to build with the library. It's also used # for other configuration for packages that don't install binaries, # so as well as being found in `lib/pkgconfig`, it can be found in # `share/pkgconfig`, so we install dummy files to both. for pkgdir in lib lib32 lib64 share; do install -D -m 644 /dev/stdin < VERSION install -m644 -D /dev/stdin << EOF "DEFAULTS" # This is a simplified version of the DEFAULTS file supplied with the # Baserock reference system definitions. build-systems: autotools: configure-commands: - ./configure build-commands: - make install-commands: - make install manual: configure-commands: [] build-commands: [] install-commands: [] split-rules: chunk: - artifact: -devel include: - (usr/)?include/.* - (usr/)?lib/.*\.a - (usr/)?share/man/.* - artifact: -runtime include: - .* stratum: - artifact: -devel include: - .*-devel - artifact: -runtime include: - .*-runtime EOF arch=$(run_morph print-architecture) install -m644 -D /dev/stdin << EOF "systems/test-system.morph" name: test-system kind: system arch: $arch strata: - name: build-essential morph: strata/build-essential.morph - name: core morph: strata/core.morph EOF mkdir "$DATADIR/gits/definitions/clusters" cd "$DATADIR/gits/definitions" install -m644 -D /dev/stdin << EOF "clusters/test-cluster.morph" name: test-cluster kind: cluster systems: - morph: systems/test-system.morph deploy: test-system: type: tar location: test.tar EOF install -m644 -D /dev/stdin << EOF "strata/build-essential.morph" name: build-essential kind: stratum chunks: - name: stage1-chunk repo: test:bootstrap-chunk ref: $(run_in "$DATADIR/gits/bootstrap-chunk" git rev-parse bootstrap) unpetrify-ref: nootstrap build-mode: bootstrap build-system: autotools build-depends: [] - name: stage2-chunk morph: stage2-chunk.morph repo: test:bootstrap-chunk ref: $(run_in "$DATADIR/gits/bootstrap-chunk" git rev-parse master) unpetrify-ref: master build-depends: - stage1-chunk EOF install -m644 -D /dev/stdin << EOF "strata/core.morph" name: core kind: stratum build-depends: - morph: strata/build-essential.morph chunks: - name: test-chunk morph: test-chunk.morph repo: test:test-chunk unpetrify-ref: master ref: $(run_in "$DATADIR/gits/test-chunk" git rev-parse master) build-depends: [] EOF install -m644 -D /dev/stdin << 'EOF' "test-chunk.morph" name: test-chunk kind: chunk build-system: manual # `build-commands` is a list of shell commands to run. Commands # may be on multiple lines, and indeed anything programmatic will # benefit from doing so. Arguably we could have just one command, # but it's split into multiple so that morph can inform us which # command failed without us having to include a lot of status # information in the command and look at the error message. # `build-commands` are passed MAKEFLAGS to specify build parallelism, # and are expected to generate files to be installed in `install-files` build-commands: # All of morph's building needs to handle binary output, so we can echo # that out on the command line. # Trust me, this gets decoded to "echo " then a bunch of binary output. - !!binary | ZWNobyBQ1+k661ol3khrsF4VO4HNcuYzwN0LYxEWS8mPmhQiQ7Vu8CME2+gsnKQaoIRIFuUEiLCI vfIj1GTdXG6cVTJfNQ== install-commands: - copy files system-integration: test-chunk-runtime: 00-passwd: - | create file /etc/passwd root:x:0:0:Super user:/root:/bin/sh daemon:x:1:1:daemon:/usr/sbin:/bin/sh nobody:x:65534:65534:nobody:/nonexistent:/bin/false EOF install -m644 -D /dev/stdin << 'EOF' "stage2-chunk.morph" name: stage2-chunk kind: chunk build-system: manual install-commands: - copy files EOF install -m644 -D /dev/stdin << EOF "clusters/partial-test-cluster.morph" name: partial-test-cluster kind: cluster systems: - morph: systems/test-system.morph deploy: tar: type: tar location: test.tar partial-deploy-components: - strata/build-essential.morph sysroot: type: sysroot location: test.sysroot partial-deploy-components: - strata/build-essential.morph rawdisk: type: rawdisk location: test.img partial-deploy-components: - strata/build-essential.morph EOF git add . git commit -m Initial. git tag -a "test-tag" -m "Tagging test-tag" # A new branch is created here as the presence of an empty stratum will # break any morph commands which load all definitions in the repository. git checkout -b empty-stratum install -m644 -D /dev/stdin << EOF "systems/empty-stratum-system.morph" name: empty-stratum-system kind: system arch: $arch strata: - name: build-essential morph: strata/build-essential.morph - name: core morph: strata/core.morph - name: empty morph: strata/empty.morph EOF install -m644 -D /dev/stdin << EOF "strata/empty.morph" name: empty kind: stratum EOF git add . git commit -m 'Add an empty stratum' git checkout master # Start a git daemon to serve our git repositories port_file="$DATADIR/git-daemon-port" pid_file="$DATADIR/git-daemon-pid" mkfifo "$port_file" # git-daemon needs --foo=bar style arguments so we do that for consistency start-stop-daemon --start --pidfile="$pid_file" --background \ --make-pidfile --verbose \ --startas="$SRCDIR/scripts/git-daemon-wrap" -- \ --port-file="$port_file" \ --export-all --verbose --base-path="$DATADIR/gits" \ --enable=receive-pack #allow push GIT_DAEMON_PORT="$(cat "$port_file")" echo "git://127.0.0.1:$GIT_DAEMON_PORT/definitions" > "$DATADIR/definitions-repo-url" # Create the Morph configuration file so we can access the repos # using test:foo URL aliases. cat << EOF > "$DATADIR/morph.conf" [config] repo-alias = test=git://127.0.0.1:$GIT_DAEMON_PORT/%s#git://127.0.0.1:$GIT_DAEMON_PORT/%s cachedir = $DATADIR/cache tempdir = $TMPDIR trove-host = 127.0.0.1 trove-id = testtrove build-ref-prefix = testtrove/builds EOF mkdir "$DATADIR/cache" mkdir -p "$TMPDIR" Some resources are cleaned up by yarn, forked processes aren't one of these, so need to shut down the git daemon after we finish. IMPLEMENTS FINALLY the git server is shut down pid_file="$DATADIR/git-daemon-pid" if [ -e "$pid_file" ]; then start-stop-daemon --stop --pidfile "$pid_file" --oknodo fi We need a consistent value for the architecture in some tests, so we have a morphology using the test architecture. IMPLEMENTS GIVEN a system called (\S+) for the test architecture in the git server name="$(basename "${MATCH_1%.*}")" cat << EOF > "$DATADIR/gits/definitions/$MATCH_1" arch: testarch configuration-extensions: [] description: A system called $name for test architecture kind: system name: $name strata: - name: build-essential morph: strata/build-essential.morph - name: core morph: strata/core.morph EOF run_in "$DATADIR/gits/definitions" git add "strata/build-essential.morph" run_in "$DATADIR/gits/definitions" git add "strata/core.morph" run_in "$DATADIR/gits/definitions" git add "$MATCH_1" run_in "$DATADIR/gits/definitions" git commit -m "Added $MATCH_1 and strata morphologies." You need an architecture to build a system, we don't default to the host architecture. IMPLEMENTS GIVEN a system called (\S+) with no architecture in the git server name="$(basename "${MATCH_1%.*}")" cat << EOF > "$DATADIR/gits/definitions/$MATCH_1" configuration-extensions: [] description: A system called $name for test architecture kind: system name: $name strata: - name: build-essential morph: strata/build-essential.morph - name: core morph: strata/core.morph EOF run_in "$DATADIR/gits/definitions" git add "$MATCH_1" run_in "$DATADIR/gits/definitions" git commit -m "Added $MATCH_1." IMPLEMENTS THEN the repo for chunk (\S+) was cloned into the directory (\S+) if [[ -d "$DATADIR/$MATCH_2" ]]; then exit 0 fi exit 1 IMPLEMENTS THEN the repo in directory (\S+) has HEAD (\S+) cd "$DATADIR/$MATCH_1" if [ "$MATCH_2" == `git rev-parse --abbrev-ref HEAD` ]; then exit 0 fi exit 1 To produce buildable morphologies, we need them to be of the same architecture as the machine doing the testing. This uses `morph print-architecture` to get a value appropriate for morph. IMPLEMENTS WHEN the user creates an uncommitted system morphology called (\S+) for our architecture in branch (\S+) arch=$(run_morph print-architecture) name="$(basename "${MATCH_1%.*}")" cd "$DATADIR/definitions" git checkout "$MATCH_2" install -m644 -D /dev/stdin << EOF "$MATCH_1" arch: $arch configuration-extensions: [] description: A system called $name for architectures $arch kind: system name: $name strata: - name: build-essential morph: strata/build-essential.morph - name: core morph: strata/core.morph EOF git checkout - Generating a manifest. IMPLEMENTS GIVEN a system artifact mkdir "$DATADIR/hello_world" git init "$DATADIR/hello_world" touch "$DATADIR/hello_world/configure.ac" run_in "$DATADIR/hello_world" git add configure.ac run_in "$DATADIR/hello_world" git commit -m 'Add configure.ac' mkdir "$DATADIR/baserock" run_in "$DATADIR/hello_world" cat << EOF \ > "$DATADIR/baserock/hello_world.meta" { "artifact-name": "hello_world", "cache-key": "ab8d00a80298a842446ce23507cea6b4d0e34c7ddfa05c67f460318b04d21308", "kind": "chunk", "morphology": "hello_world.morph", "original_ref": "$(run_in "$DATADIR/hello_world" git rev-parse HEAD)", "repo": "file://$DATADIR/hello_world", "repo-alias": "upstream:hello_world", "sha1": "$(run_in "$DATADIR/hello_world" git rev-parse HEAD)", "source-name": "hello_world" } EOF run_in "$DATADIR" tar -c baserock > "$DATADIR/artifact.tar" IMPLEMENTS WHEN morph generates a manifest run_morph generate-manifest-genivi "$DATADIR/artifact.tar" > "$DATADIR/manifest" IMPLEMENTS WHEN morph generates a manifest for system (\S+) at ref (\S+) in repository (\S+) cd "$DATADIR" attempt_morph generate-manifest-csv --check-license=single-file "$MATCH_3" "$MATCH_2" "$MATCH_1" IMPLEMENTS THEN the manifest is generated # Generated manifest should contain the name of the repository if ! grep -q hello_world "$DATADIR/manifest"; then die "Output isn't what we expect" fi IMPLEMENTS THEN the csv manifest is generated if [ -e "$DATADIR"/*-manifest.csv ]; then exit 0 fi die "Expected CSV manifest not found" IMPLEMENTS WHEN the user commits all changes in branch (\S+) cd "$DATADIR/definitions" git checkout "$MATCH_1" git commit -a --allow-empty -m 'Commit all changes' git checkout - Implementations for `morph certify` ----------------------------------- IMPLEMENTS WHEN the user certifies the system (\S+) at ref (\S+) in repository (\S+) attempt_morph certify "$MATCH_3" "$MATCH_2" "$MATCH_1" Implementations for `morph list-artifacts` ------------------------------------------ IMPLEMENTS WHEN the user lists the artifacts which make up the system (\S+) at ref (\S+) cd "$DATADIR/definitions" git checkout "$MATCH_2" attempt_morph list-artifacts "$MATCH_1" Implementation sections for building ==================================== IMPLEMENTS WHEN the user (attempts to )?((dist)?build)s? the system (\S+) in branch (\S+) cd "$DATADIR/definitions" git checkout "$MATCH_5" set "$MATCH_2" "$MATCH_4" if [ "$MATCH_1" != "attempts to " ]; then run_morph "$@" else attempt_morph "$@"; fi IMPLEMENTS WHEN the user (attempts to )?((dist)?build)s? (\S+) from the system (\S+) in branch (\S+) cd "$DATADIR/definitions" git checkout "$MATCH_5" set "$MATCH_2" "$MATCH_5" "$MATCH_4" if [ "$MATCH_1" != "attempts to " ]; then run_morph "$@" else attempt_morph "$@"; fi Implementation sections for cross-bootstrapping =============================================== IMPLEMENTS WHEN the user (attempts to )?cross-bootstraps? the system (\S+) in branch (\S+) to the arch (\S+) cd "$DATADIR/definitions" git checkout "$MATCH_3" set -- cross-bootstrap "$MATCH_4" "$MATCH_2" if [ "$MATCH_1" != "attempts to " ]; then run_morph "$@" else attempt_morph "$@"; fi echo $@ >&2 Implementation sections for deployment ====================================== Defaults are set in the cluster morphology, so we can deploy without setting any extra parameters, but we also need to be able to override them, so they can be added to the end of the implements section. IMPLEMENTS WHEN (from directory (\S+) )?the user (attempts to deploy|deploys) the (system|cluster) (using the absolute path to )?(\S+) in branch (\S+)( with options (.*))? if [ "$MATCH_1" != "" ] then cd "$DATADIR/$MATCH_2" else cd "$DATADIR/definitions" fi git checkout "$MATCH_7" if [ "$MATCH_5" != "" ] then set -- deploy "$(pwd)/$MATCH_6" else set -- deploy "$MATCH_6" fi if [ "$MATCH_8" != '' ]; then # eval used so word splitting in the text is preserved eval set -- '"$@"' $MATCH_9 fi if [ "$MATCH_3" = "deploys" ]; then run_morph "$@" else attempt_morph "$@"; fi git checkout - IMPLEMENTS WHEN the user (attempts to deploy|deploys) (.*) from cluster (\S+) in branch (\S+) cd "$DATADIR/definitions" git checkout "$MATCH_4" set -- deploy "$MATCH_3" systems=$(echo "$MATCH_2" | sed -e 's/, /\n/g' -e 's/ and /\n/g') set -- "$@" $systems if [ "$MATCH_1" = "deploys" ]; then run_morph "$@" else attempt_morph "$@"; fi git checkout - IMPLEMENTS WHEN the user (attempts to partially deploy|partially deploys) (.*) from cluster (\S+) in branch (\S+) cd "$DATADIR/definitions" git checkout "$MATCH_4" set -- deploy "$MATCH_3" systems=$(echo "$MATCH_2" | sed -e 's/, /\n/g' -e 's/ and /\n/g') echo "partial=True" >> "$DATADIR/morph.conf" set -- "$@" $systems if [ "$MATCH_1" = "partially deploys" ]; then run_morph "$@" else attempt_morph "$@"; fi git checkout - IMPLEMENTS WHEN the user (attempts to upgrade|upgrades) the (system|cluster) (\S+) in branch (\S+)( with options (.*))? cd "$DATADIR/definitions" git checkout "$MATCH_4" set -- upgrade "$MATCH_3" if [ "$MATCH_5" != '' ]; then # eval used so word splitting in the text is preserved eval set -- '"$@"' $MATCH_6 fi if [ "$MATCH_1" = "upgrades" ]; then run_morph "$@" else attempt_morph "$@"; fi git checkout - Implementations sections for reading error messages =================================================== IMPLEMENTS THEN the (branch|build|cross-bootstrap|checkout|deploy|edit|init|help) error message includes the string "(.*)" grep "$MATCH_2" "$DATADIR/result-$MATCH_1" IMPLEMENTS for test file and directory handling =============================================== The IMPLEMENTS sections in this chapter create files and directories for use as test data, and set and test their contents and permissions and ownerships. Create a directory ------------------ IMPLEMENTS GIVEN a directory called (\S+) mkdir -p "$DATADIR/$MATCH_1" Create a file ------------- The file contents is used as a `printf`(1) format string. IMPLEMENTS GIVEN a file called (\S+) containing "(.*)" printf "$MATCH_2" > "$DATADIR/$MATCH_1" Remove a file ------------- IMPLEMENTS GIVEN the file(s)? (.*) (is|are) removed cd "$DATADIR" files=$(echo "$MATCH_2" | sed -e 's/, /\n/g' -e 's/ and /\n/g') rm $files Set attributes on a file or directory ------------------------------------- IMPLEMENTS GIVEN (\S+) is owned by uid (\S+) chown "$MATCH_2" "$DATADIR/$MATCH_1" IMPLEMENTS GIVEN (\S+) is owned by gid (\S+) chgrp "$MATCH_2" "$DATADIR/$MATCH_1" IMPLEMENTS GIVEN (\S+) has permissions (\S+) chmod "$MATCH_2" "$DATADIR/$MATCH_1" Check attributes of a file on the filesystem -------------------------------------------- IMPLEMENTS THEN file (\S+) exists test -e "$DATADIR/$MATCH_1" IMPLEMENTS THEN file (\S+) does not exist test ! -e "$DATADIR/$MATCH_1" IMPLEMENTS THEN file (\S+) has permissions (\S+) stat -c %A "$DATADIR/$MATCH_1" | grep -Fx -e "$MATCH_2" IMPLEMENTS THEN file (\S+) is owned by uid (\d+) stat -c %u "$DATADIR/$MATCH_1" | grep -Fx -e "$MATCH_2" IMPLEMENTS THEN file (\S+) is owned by gid (\d+) stat -c %g "$DATADIR/$MATCH_1" | grep -Fx -e "$MATCH_2" IMPLEMENTS THEN file (\S+) is empty stat -c %s "$DATADIR/$MATCH_1" | grep -Fx 0 IMPLEMENTS THEN file (\S+) matches (.*) grep -q "$MATCH_2" "$DATADIR/$MATCH_1" Disk image manipulation ----------------------- We need to test disk images we create. In the absence of tools for inspecting disks without mounting them, we need commands to handle this. IMPLEMENTS WHEN disk image (\S+) is mounted at (.*) mkdir -p "$DATADIR/$MATCH_2" mount -o loop "$DATADIR/$MATCH_1" "$DATADIR/$MATCH_2" IMPLEMENTS FINALLY (\S+) is unmounted umount -d "$DATADIR/$MATCH_1" We may not have enough space to run some tests that have disk images. IMPLEMENTS ASSUMING there is space for (\d+) (\d+)(\S*) disk images? # Count is included as an argument, so that if we change the disk # image sizes then it's more obvious when we need to change the # assumption, since it's the same value. count="$MATCH_1" case "$MATCH_3" in '') size="$MATCH_2" ;; M) size=$(expr "$MATCH_2" '*' 1024 '*' 1024 ) ;; G) size=$(expr "$MATCH_2" '*' 1024 '*' 1024 '*' 1024 ) ;; *) echo Unrecognized size suffix: "$MATCH_3" >&2 exit 1 esac total_image_size="$(expr "$size" '*' "$count" )" blocks="$(stat -f -c %a "$DATADIR")" block_size="$(stat -f -c %S "$DATADIR")" disk_free=$(expr "$blocks" '*' "$block_size" ) test "$disk_free" -gt "$total_image_size" Check contents of a file ------------------------ We treat the contents of the file in the step as a `printf`(1) format string, to allow newlines and other such stuff to be expressed. IMPLEMENTS THEN file (\S+) contains "(.*)" printf "$MATCH_2" | diff - "$DATADIR/$MATCH_1" IMPLEMENTS for running programs =============================== This chapter contains IMPLEMENTS sections for running programs. It is currently a bit of a placeholder. Remember environment variables to set when running -------------------------------------------------- We need to manage the environment. We store the extra environment variables in `$DATADIR/env`. We treat the value as a format string for `printf`(1) so that newlines etc can be used. IMPLEMENTS GIVEN an environment variable (\S+) containing "(.*)" printf "export $MATCH_1=$MATCH_2" >> "$DATADIR/env" Implementations for building systems ------------------------------------ IMPLEMENTS THEN morph ((dist)?build) the system (\S+) of the (branch|tag) (\S+) cd "$DATADIR/definitions" git checkout "$MATCH_5" run_morph "$MATCH_1" "$MATCH_3" IMPLEMENTS WHEN from the directory (\S+) the user attempts to morph build the system (\S+) cd "$DATADIR/$MATCH_1" attempt_morph build "$MATCH_2" IMPLEMENTS WHEN from the directory (\S+) the user attempts to morph build the system using the absolute path to (\S+) cd "$DATADIR/$MATCH_1" attempt_morph build "$(pwd)/$MATCH_2" IMPLEMENTS THEN the system artifact for (\S+) is in the cache system_name="$MATCH_1" system_artifact="$(ls "$DATADIR/cache/artifacts/"*"$system_name"-rootfs)" tar tf "$system_artifact" | LC_ALL=C sort | sed '/^\.\/./s:^\./::' | grep -v '^baserock/' IMPLEMENTS THEN the build logs for the chunks were saved to the artifact cache sources="$DATADIR"/sources find "$DATADIR"/cache/artifacts -name '*.chunk.*' | \ sed 's@\.chunk\..*@@' | sort -u > "$sources" while read source; do test -e "$source".build-log done < "$sources" IMPLEMENTS THEN the build log for chunk (\S+) contains (\S+) chunk_name="$MATCH_1" string="$MATCH_2" artifact=$(ls "$DATADIR/cache/artifacts/"*chunk\."$chunk_name"-devel) cachekey="$(echo "$(basename "$artifact")" | cut -d. -f1)" logfile="$DATADIR/cache/artifacts/$cachekey.build-log" grep -q "$string" "$logfile" IMPLEMENTS WHEN the user defines build commands for chunk (\S+) that will fail, in branch (\S+) chunk_name="$MATCH_1" branch="$MATCH_2" cd "$DATADIR"/definitions git checkout "$branch" cat << EOF > "${chunk_name}".morph name: "${chunk_name}" kind: chunk configure-commands: - echo dummy configure build-commands: - echo The next command will fail - 'false' EOF git add "${chunk_name}".morph git commit -m "Update ${chunk_name} build commands" git checkout - IMPLEMENTS GIVEN a chunk with submodules mkdir "$DATADIR/gits/child-chunk" cd "$DATADIR/gits/child-chunk" git init . touch submodule-file git add . git commit -m "Initial commit" mkdir "$DATADIR/gits/parent-chunk" cd "$DATADIR/gits/parent-chunk" git init . git commit --allow-empty -m "Initial commit" git submodule add -b master file://$DATADIR/gits/child-chunk git commit -m "Add child-chunk submodule" IMPLEMENTS WHEN the user adds the chunk with submodules to an existing stratum in branch (\S+) branch="$MATCH_1" cd "$DATADIR/definitions" git checkout "$branch" cat << EOF >> strata/core.morph - name: parent-chunk morph: parent-chunk.morph repo: test:parent-chunk ref: master EOF cat << EOF > parent-chunk.morph name: parent-chunk kind: chunk build-system: manual build-commands: - file exists child-chunk/submodule-file EOF git add strata/core.morph parent-chunk.morph git commit -m "Add parent-chunk to core" git checkout - IMPLEMENTS WHEN the user adds the chunk with custom prefixes to an existing stratum in branch (\S+) branch="$MATCH_1" cd "$DATADIR"/definitions git checkout "$branch" cat << EOF >> strata/core.morph - name: xyzzy morph: xyzzy.morph repo: test:test-chunk ref: master build-depends: [] prefix: /plover - name: plugh morph: plugh.morph repo: test:test-chunk ref: master build-depends: - xyzzy EOF cat << EOF > xyzzy.morph name: xyzzy kind: chunk build-mode: manual install-commands: - printvar PREFIX EOF cat << EOF > plugh.morph name: plugh kind: chunk build-mode: manual install-commands: - printvar PREFIX - printvar PATH EOF git add strata/core.morph xyzzy.morph plugh.morph git commit -m "Add moar stuff" git checkout - IMPLEMENTS WHEN the user changes the build commands for chunk (\S+) in branch (\S+) chunk_name="$MATCH_1" branch="$MATCH_2" cd "$DATADIR"/definitions git checkout "$branch" cat << EOF >> "${chunk_name}".morph post-install-commands: - create file foo EOF git checkout - IMPLEMENTS THEN there are (\S+) artifacts named (\S+) in the cache expected_num="$MATCH_1" artifact_name="$MATCH_2" num_artifacts="$(ls -l "$DATADIR"/cache/artifacts/*"$artifact_name" | wc -l)" test "$expected_num" -eq "$num_artifacts" Implementations for tarball inspection -------------------------------------- IMPLEMENTS THEN tarball (\S+) contains (.*) tar -tf "$DATADIR/$MATCH_1" | grep -Fe "$MATCH_2" IMPLEMENTS THEN tarball (\S+) doesn't contain (.*) ! tar -tf "$DATADIR/$MATCH_1" | grep -Fe "$MATCH_2" Implementations for morphology manipulation ========================================== Altering morphologies in their source repositories -------------------------------------------------- IMPLEMENTS GIVEN system (\S+) uses (.+) from (\S+) "$SRCDIR/scripts/edit-morph" set-system-artifact-depends \ "$DATADIR/gits/definitions/$MATCH_1" "$MATCH_3" "$MATCH_2" run_in "$DATADIR/gits/definitions" git add "$MATCH_1" run_in "$DATADIR/gits/definitions" git commit -m "Make $MATCH_1 only use $MATCH_2" IMPLEMENTS GIVEN stratum (\S+) has match rules: (.*) cd "$DATADIR/gits/definitions" "$SRCDIR/scripts/edit-morph" set-stratum-match-rules \ "$MATCH_1" "$MATCH_2" git add "$MATCH_1" git commit -m "Make $MATCH_1 match $MATCH_2" ### Altering clusters ### IMPLEMENTS GIVEN a cluster called (\S+) in branch (\S+) cluster="$MATCH_1" branch="$MATCH_2" run_in "$DATADIR/gits/definitions" git checkout "$branch" "$SRCDIR/scripts/edit-morph" cluster-init \ "$DATADIR/gits/definitions/$cluster" run_in "$DATADIR/gits/definitions" git add "$cluster" run_in "$DATADIR/gits/definitions" git commit -m "Add cluster $cluster" run_in "$DATADIR/gits/definitions" git checkout - IMPLEMENTS GIVEN a (sub)?system in cluster (\S+) in branch (\S+) called (\S+) cluster="$MATCH_2" branch="$MATCH_3" name="$MATCH_4" run_in "$DATADIR/gits/definitions" git checkout "$branch" "$SRCDIR/scripts/edit-morph" cluster-system-init \ "$DATADIR/gits/definitions/$cluster" "$name" run_in "$DATADIR/gits/definitions" git add "$cluster" run_in "$DATADIR/gits/definitions" git commit -m "Add system/subsystem $name to cluster $cluster" run_in "$DATADIR/gits/definitions" git checkout - IMPLEMENTS GIVEN (sub)?system (\S+) in cluster (\S+) in branch (\S+) builds (\S+) name="$MATCH_2" cluster="$MATCH_3" branch="$MATCH_4" morphology="$MATCH_5" run_in "$DATADIR/gits/definitions" git checkout "$branch" "$SRCDIR/scripts/edit-morph" cluster-system-set-morphology \ "$DATADIR/gits/definitions/$cluster" "$name" \ "$morphology" run_in "$DATADIR/gits/definitions" git add "$cluster" run_in "$DATADIR/gits/definitions" git commit -m "Make system $name in cluster $cluster build $morphology" run_in "$DATADIR/gits/definitions" git checkout - IMPLEMENTS GIVEN (sub)?system (\S+) in cluster (\S+) in branch (\S+) has deployment type: (\S+) name="$MATCH_2" cluster="$MATCH_3" branch="$MATCH_4" type="$MATCH_5" run_in "$DATADIR/gits/definitions" git checkout "$branch" "$SRCDIR/scripts/edit-morph" cluster-system-set-deploy-type \ "$DATADIR/gits/definitions/$cluster" "$name" \ "$type" run_in "$DATADIR/gits/definitions" git add "$cluster" run_in "$DATADIR/gits/definitions" git commit -m "Add system $name to cluster $cluster" run_in "$DATADIR/gits/definitions" git checkout - IMPLEMENTS GIVEN (sub)?system (\S+) in cluster (\S+) in branch (\S+) has deployment location: (\S+) name="$MATCH_2" cluster="$MATCH_3" branch="$MATCH_4" location="$MATCH_5" run_in "$DATADIR/gits/definitions" git checkout "$branch" "$SRCDIR/scripts/edit-morph" cluster-system-set-deploy-location \ "$DATADIR/gits/definitions/$cluster" "$name" \ "$location" run_in "$DATADIR/gits/definitions" git add "$cluster" run_in "$DATADIR/gits/definitions" git commit -m "Set deploy location of system $name in cluster $cluster" run_in "$DATADIR/gits/definitions" git checkout - IMPLEMENTS GIVEN (sub)?system (\S+) in cluster (\S+) in branch (\S+) has deployment variable: ([^=]+)=(.*) name="$MATCH_2" cluster="$MATCH_3" branch="$MATCH_4" key="$MATCH_5" val="$MATCH_6" run_in "$DATADIR/gits/definitions" git checkout "$branch" "$SRCDIR/scripts/edit-morph" cluster-system-set-deploy-variable \ "$DATADIR/gits/definitions/$cluster" "$name" \ "$key" "$val" run_in "$DATADIR/gits/definitions" git add "$cluster" run_in "$DATADIR/gits/definitions" git commit -m "Set deploy variable for system $name in cluster $cluster" run_in "$DATADIR/gits/definitions" git checkout - IMPLEMENTS GIVEN stratum (\S+) in branch (\S+) has match rules: (.*) run_in "$DATADIR/gits/definitions" git checkout "$MATCH_2" "$SRCDIR/scripts/edit-morph" set-stratum-match-rules \ "$DATADIR/gits/definitions/$MATCH_1" "$MATCH_3" run_in "$DATADIR/gits/definitions" git add "$MATCH_1" run_in "$DATADIR/gits/definitions" git commit -m "Make $MATCH_1 match $MATCH_2" run_in "$DATADIR/gits/definitions" git checkout - Altering morphologies in the definitions repo --------------------------------------------- IMPLEMENTS WHEN chunk (\S+) in stratum (\S+) in branch (\S+) is updated to use (\S+) from chunk repository (\S+) cd "$DATADIR/definitions" git checkout "$MATCH_3" sha1=$(cd "$DATADIR/gits/$MATCH_5" && git rev-parse "$MATCH_4") "$SRCDIR/scripts/edit-morph" update-stratum-chunk-ref \ "$MATCH_2" "$MATCH_1" "$sha1" git checkout - IMPLEMENTS WHEN chunk repository (\S+) is re-tagged as (\S+) cd "$DATADIR/gits/$MATCH_1" git commit --allow-empty -m "Prepare for $MATCH_2" git tag -af -m "Release $MATCH_2" "$MATCH_2" Distbuild ========= IMPLEMENTS ASSUMING the morph-cache-server can be run "$SRCDIR/morph-cache-server" --version IMPLEMENTS GIVEN a communal cache server # The communal cache server has direct access to the git repositories # and can have artifacts placed on it artifact_dir="$DATADIR/shared-artifacts" mkdir -p "$artifact_dir" read_cache_server_port_file="$DATADIR/read-cache-server-port" read_cache_server_pid_file="$DATADIR/read-cache-server-pid" start_cache_server "$read_cache_server_port_file" \ "$read_cache_server_pid_file" \ "$artifact_dir" write_cache_server_port_file="$DATADIR/write-cache-server-port" write_cache_server_pid_file="$DATADIR/write-cache-server-pid" start_cache_server "$write_cache_server_port_file" \ "$write_cache_server_pid_file" \ "$artifact_dir" --enable-writes IMPLEMENTS FINALLY the communal cache server is terminated stop_daemon "$DATADIR/read-cache-server-pid" stop_daemon "$DATADIR/write-cache-server-pid" IMPLEMENTS GIVEN a distbuild worker # start worker cache server, so other workers can download results worker_cachedir="$DATADIR/distbuild-worker-cache" worker_artifacts="$worker_cachedir/artifacts" mkdir -p "$worker_artifacts" worker_cache_port_file="$DATADIR/worker-cache-server-port" worker_cache_pid_file="$DATADIR/worker-cache-server-pid" start_cache_server "$worker_cache_port_file" \ "$worker_cache_pid_file" \ "$worker_artifacts" # start worker daemon worker_daemon_port_file="$DATADIR/worker-daemon-port" worker_daemon_pid_file="$DATADIR/worker-daemon-pid" mkfifo "$worker_daemon_port_file" communal_cache_port="$(cat "$DATADIR/read-cache-server-port")" start-stop-daemon --start --pidfile="$worker_daemon_pid_file" \ --background --make-pidfile --verbose \ --startas="$SRCDIR/morph" -- worker-daemon \ --no-default-configs --config "$DATADIR/morph.conf" \ --worker-daemon-port=0 \ --worker-daemon-port-file="$worker_daemon_port_file" \ --cachedir="$worker_cachedir" \ --artifact-cache-server="http://localhost:$communal_cache_port/" \ --git-resolve-cache-server="http://localhost:$communal_cache_port/" \ --log="$DATADIR/worker-daemon-log" \ >"$DATADIR/worker-daemon-out" 2>"$DATADIR/worker-daemon-err" worker_daemon_port="$(cat "$worker_daemon_port_file")" rm "$worker_daemon_port_file" echo "$worker_daemon_port" >"$worker_daemon_port_file" # start worker helper helper_pid_file="$DATADIR/worker-daemon-helper-pid" start-stop-daemon --start --pidfile="$helper_pid_file" \ --background --make-pidfile --verbose \ --startas="$SRCDIR/distbuild-helper" -- \ --no-default-configs \ --parent-port="$worker_daemon_port" # set up builder config install /dev/stdin <<'EOF' "$DATADIR/morph" #!/bin/sh exec "$SRCDIR/morph" --quiet \ --cachedir-min-space=0 --tempdir-min-space=0 \ --no-default-config --config "$DATADIR/morph.conf" \ --cachedir "$DATADIR/distbuild-worker-cache" "$@" EOF IMPLEMENTS FINALLY the distbuild worker is terminated stop_daemon "$DATADIR/worker-cache-server-pid" stop_daemon "$DATADIR/worker-daemon-pid" stop_daemon "$DATADIR/worker-daemon-helper-pid" IMPLEMENTS GIVEN a distbuild controller worker_cache_port_file="$DATADIR/worker-cache-server-port" worker_cache_server_port="$(cat "$worker_cache_port_file")" worker_daemon_port_file="$DATADIR/worker-daemon-port" worker_daemon_port="$(cat "$worker_daemon_port_file")" communal_cache_read_port="$(cat "$DATADIR/read-cache-server-port")" communal_cache_write_port="$(cat "$DATADIR/write-cache-server-port")" initiator_port_file="$DATADIR/controller-initiator-port" mkfifo "$initiator_port_file" helper_port_file="$DATADIR/controller-helper-port" mkfifo "$helper_port_file" controller_pid_file="$DATADIR/controller-pid" start-stop-daemon --start --pidfile="$controller_pid_file" \ --background --make-pidfile --verbose \ --startas="$SRCDIR/morph" -- controller-daemon \ --no-default-configs --config "$DATADIR/morph.conf" \ --worker="localhost:$worker_daemon_port" \ --worker-cache-server-port="$worker_cache_server_port" \ --artifact-cache-server="http://localhost:$communal_cache_read_port/" \ --git-resolve-cache-server="http://localhost:$communal_cache_read_port/" \ --writeable-cache-server="http://localhost:$communal_cache_write_port/" \ --controller-helper-port=0 \ --controller-helper-port-file="$helper_port_file" \ --controller-initiator-port=0 \ --controller-initiator-port-file="$initiator_port_file" \ --morph-instance="$DATADIR/morph" \ --log="$DATADIR/controller-daemon-log" \ >"$DATADIR/controller-daemon-out" 2>"$DATADIR/controller-daemon-err" helper_port="$(cat "$helper_port_file")" rm "$helper_port_file" echo "$helper_port" >"$helper_port_file" initiator_port="$(cat "$initiator_port_file")" rm "$initiator_port_file" echo "$initiator_port" >"$initiator_port_file" # start worker helper helper_pid_file="$DATADIR/controller-helper-pid" start-stop-daemon --start --pidfile="$helper_pid_file" \ --background --make-pidfile --verbose \ --startas="$SRCDIR/distbuild-helper" -- \ --no-default-configs \ --parent-port="$helper_port" # make builds use distbuild echo "controller-initiator-port = $initiator_port" \ >>"$DATADIR/morph.conf" echo "controller-initiator-address = localhost" \ >>"$DATADIR/morph.conf" echo "initiator-step-output-dir = $DATADIR" \ >>"$DATADIR/morph.conf" IMPLEMENTS FINALLY the distbuild controller is terminated stop_daemon "$DATADIR/controller-helper-pid" stop_daemon "$DATADIR/controller-pid"