From 301b230d2f294e04ab262aca09293fddbdc3fbad Mon Sep 17 00:00:00 2001 From: Kris Gesling Date: Fri, 13 Nov 2020 09:57:42 +0930 Subject: [PATCH 01/11] store allure results in unique directory --- Jenkinsfile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 611cdd0522..4628bb955b 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -55,7 +55,7 @@ pipeline { { sh 'docker run \ -v "$HOME/voight-kampff/identity:/root/.mycroft/identity" \ - -v "$HOME/voight-kampff/:/root/allure" \ + -v "$HOME/allure/core/$BRANCH_ALIAS:/root/allure" \ voight-kampff-mark-1:${BRANCH_ALIAS} \ -f allure_behave.formatter:AllureFormatter \ -o /root/allure/allure-result --tags ~@xfail' @@ -66,7 +66,7 @@ pipeline { echo 'Report Test Results' echo 'Changing ownership...' sh 'docker run \ - -v "$HOME/voight-kampff/:/root/allure" \ + -v "$HOME/allure/core/$BRANCH_ALIAS:/root/allure" \ --entrypoint=/bin/bash \ voight-kampff-mark-1:${BRANCH_ALIAS} \ -x -c "chown $(id -u $USER):$(id -g $USER) \ @@ -74,7 +74,9 @@ pipeline { echo 'Transferring...' sh 'rm -rf allure-result/*' - sh 'mv $HOME/voight-kampff/allure-result allure-result' + sh 'mv $HOME/allure/core/$BRANCH_ALIAS/allure-result allure-result' + // This directory should now be empty, rmdir will intentionally fail if not. + sh 'rmdir $HOME/allure/core/$BRANCH_ALIAS' script { allure([ includeProperties: false, From 8c6daf5385330cac7f232b55e35c69b754c08505 Mon Sep 17 00:00:00 2001 From: Kris Gesling Date: Fri, 13 Nov 2020 15:53:00 +0930 Subject: [PATCH 02/11] extract log files from VK test runs --- Jenkinsfile | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 4628bb955b..80e9b15f90 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -56,6 +56,7 @@ pipeline { sh 'docker run \ -v "$HOME/voight-kampff/identity:/root/.mycroft/identity" \ -v "$HOME/allure/core/$BRANCH_ALIAS:/root/allure" \ + -v "$HOME/mycroft-logs/core/$BRANCH_ALIAS:/var/log/mycroft" \ voight-kampff-mark-1:${BRANCH_ALIAS} \ -f allure_behave.formatter:AllureFormatter \ -o /root/allure/allure-result --tags ~@xfail' @@ -64,13 +65,20 @@ pipeline { post { always { echo 'Report Test Results' - echo 'Changing ownership...' + echo 'Changing ownership of Allure results...' sh 'docker run \ -v "$HOME/allure/core/$BRANCH_ALIAS:/root/allure" \ --entrypoint=/bin/bash \ voight-kampff-mark-1:${BRANCH_ALIAS} \ -x -c "chown $(id -u $USER):$(id -g $USER) \ -R /root/allure/"' + echo 'Changing ownership of Allure results...' + sh 'docker run \ + -v "$HOME/mycroft-logs/core/$BRANCH_ALIAS:/var/log/mycroft" \ + --entrypoint=/bin/bash \ + voight-kampff-mark-1:${BRANCH_ALIAS} \ + -x -c "chown $(id -u $USER):$(id -g $USER) \ + -R /var/log/mycroft"' echo 'Transferring...' sh 'rm -rf allure-result/*' @@ -87,12 +95,17 @@ pipeline { ]) } unarchive mapping:['allure-report.zip': 'allure-report.zip'] + sh 'zip mycroft-logs.zip -r $HOME/mycroft-logs/core/$BRANCH_ALIAS' + sh 'rm -r $HOME/mycroft-logs/core/$BRANCH_ALIAS' sh ( label: 'Publish Report to Web Server', script: '''scp allure-report.zip root@157.245.127.234:~; ssh root@157.245.127.234 "unzip -o ~/allure-report.zip"; ssh root@157.245.127.234 "rm -rf /var/www/voight-kampff/core/${BRANCH_ALIAS}"; ssh root@157.245.127.234 "mv allure-report /var/www/voight-kampff/core/${BRANCH_ALIAS}" + scp mycroft-logs.zip root@157.245.127.234:~; + ssh root@157.245.127.234 "mkdir -p /var/www/voight-kampff/core/${BRANCH_ALIAS}/logs" + ssh root@157.245.127.234 "unzip -oj ~/mycroft-logs.zip -d /var/www/voight-kampff/core/${BRANCH_ALIAS}/logs/"; ''' ) echo 'Report Published' @@ -102,7 +115,13 @@ pipeline { // Create comment for Pull Requests if (env.CHANGE_ID) { echo 'Sending PR comment' - pullRequest.comment('Voight Kampff Integration Test Failed ([Results](https://reports.mycroft.ai/core/' + env.BRANCH_ALIAS + '))') + pullRequest.comment('Voight Kampff Integration Test Failed ([Results](https://reports.mycroft.ai/core/' + env.BRANCH_ALIAS + ')). ' + + '\nMycroft logs are also available: ' + + '[skills.log](https://reports.mycroft.ai/core/' + env.BRANCH_ALIAS + '/logs/skills.log), ' + + '[audio.log](https://reports.mycroft.ai/core/' + env.BRANCH_ALIAS + '/logs/audio.log), ' + + '[voice.log](https://reports.mycroft.ai/core/' + env.BRANCH_ALIAS + '/logs/voice.log), ' + + '[bus.log](https://reports.mycroft.ai/core/' + env.BRANCH_ALIAS + '/logs/bus.log), ' + + '[enclosure.log](https://reports.mycroft.ai/core/' + env.BRANCH_ALIAS + '/logs/enclosure.log)') } } // Send failure email containing a link to the Jenkins build @@ -133,6 +152,17 @@ pipeline {


+

+ Mycroft logs are also available: +

+

+

Console log is attached.

""", replyTo: 'devops@mycroft.ai', to: 'dev@mycroft.ai', From 7564263d264941df623f15d2821079dcf6559b71 Mon Sep 17 00:00:00 2001 From: Kris Gesling Date: Fri, 13 Nov 2020 15:55:13 +0930 Subject: [PATCH 03/11] Lock CI jobs per PR As the Docker images now store Allure and log files in unique directories, multiple CI jobs from the same repository can now run simultaneously. --- Jenkinsfile | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 80e9b15f90..455616c040 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -5,7 +5,6 @@ pipeline { // building the Docker image. disableConcurrentBuilds() buildDiscarder(logRotator(numToKeepStr: '5')) - lock resource: 'VoightKampff' } stages { // Run the build in the against the dev branch to check for compile errors @@ -15,6 +14,9 @@ pipeline { changeRequest target: 'dev' } } + options { + lock(resource: "lock_${env.JOB_NAME}") + } environment { //spawns GITHUB_USR and GITHUB_PSW environment variables GITHUB=credentials('38b2e4a6-167a-40b2-be6f-d69be42c8190') @@ -34,6 +36,9 @@ pipeline { changeRequest target: 'dev' } } + options { + lock(resource: "lock_${env.JOB_NAME}") + } environment { // Some branches have a "/" in their name (e.g. feature/new-and-cool) // Some commands, such as those tha deal with directories, don't From e461f9e6920bb825666fa594af815c1da1b8a1e9 Mon Sep 17 00:00:00 2001 From: Kris Gesling Date: Fri, 13 Nov 2020 16:08:31 +0930 Subject: [PATCH 04/11] Remove created Docker images on CI success This does not include the Major release Docker image as it is intentionally built for re-use. --- Jenkinsfile | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Jenkinsfile b/Jenkinsfile index 455616c040..02ff7f41e1 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -54,6 +54,7 @@ pipeline { sh 'docker build -f test/Dockerfile \ --target voight_kampff_builder \ --build-arg platform=mycroft_mark_1 \ + --label build=${JOB_NAME} \ -t voight-kampff-mark-1:${BRANCH_ALIAS} .' echo 'Running Mark I Voight-Kampff Test Suite' timeout(time: 60, unit: 'MINUTES') @@ -62,6 +63,7 @@ pipeline { -v "$HOME/voight-kampff/identity:/root/.mycroft/identity" \ -v "$HOME/allure/core/$BRANCH_ALIAS:/root/allure" \ -v "$HOME/mycroft-logs/core/$BRANCH_ALIAS:/var/log/mycroft" \ + --label build=${JOB_NAME} \ voight-kampff-mark-1:${BRANCH_ALIAS} \ -f allure_behave.formatter:AllureFormatter \ -o /root/allure/allure-result --tags ~@xfail' @@ -74,6 +76,7 @@ pipeline { sh 'docker run \ -v "$HOME/allure/core/$BRANCH_ALIAS:/root/allure" \ --entrypoint=/bin/bash \ + --label build=${JOB_NAME} \ voight-kampff-mark-1:${BRANCH_ALIAS} \ -x -c "chown $(id -u $USER):$(id -g $USER) \ -R /root/allure/"' @@ -81,6 +84,7 @@ pipeline { sh 'docker run \ -v "$HOME/mycroft-logs/core/$BRANCH_ALIAS:/var/log/mycroft" \ --entrypoint=/bin/bash \ + --label build=${JOB_NAME} \ voight-kampff-mark-1:${BRANCH_ALIAS} \ -x -c "chown $(id -u $USER):$(id -g $USER) \ -R /var/log/mycroft"' @@ -274,6 +278,17 @@ pipeline { } } post { + success { + // Docker images should remain upon failure for troubleshooting purposes. However, + // if the stage is successful, there is no reason to look back at the Docker image. In theory + // broken builds will eventually be fixed so this step should run eventually for every PR + sh( + label: 'Delete Docker Image on Success', + script: ''' + docker image prune --all --force --filter label=build=${JOB_NAME}; + ''' + ) + } cleanup { sh( label: 'Docker Container and Image Cleanup', From 5c04fe309d15187e966226aebd01564cce7cb4e1 Mon Sep 17 00:00:00 2001 From: Kris Gesling Date: Sat, 14 Nov 2020 05:59:27 +0930 Subject: [PATCH 05/11] Restructure Jenkins directories Move all outputs of a CI job into the same directory --- Jenkinsfile | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 02ff7f41e1..20ee202935 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -59,10 +59,12 @@ pipeline { echo 'Running Mark I Voight-Kampff Test Suite' timeout(time: 60, unit: 'MINUTES') { + sh 'mkdir -p $HOME/core/$BRANCH_ALIAS/allure' + sh 'mkdir -p $HOME/core/$BRANCH_ALIAS/mycroft-logs' sh 'docker run \ -v "$HOME/voight-kampff/identity:/root/.mycroft/identity" \ - -v "$HOME/allure/core/$BRANCH_ALIAS:/root/allure" \ - -v "$HOME/mycroft-logs/core/$BRANCH_ALIAS:/var/log/mycroft" \ + -v "$HOME/core/$BRANCH_ALIAS/allure:/root/allure" \ + -v "$HOME/core/$BRANCH_ALIAS/mycroft-logs:/var/log/mycroft" \ --label build=${JOB_NAME} \ voight-kampff-mark-1:${BRANCH_ALIAS} \ -f allure_behave.formatter:AllureFormatter \ @@ -74,7 +76,7 @@ pipeline { echo 'Report Test Results' echo 'Changing ownership of Allure results...' sh 'docker run \ - -v "$HOME/allure/core/$BRANCH_ALIAS:/root/allure" \ + -v "$HOME/core/$BRANCH_ALIAS/allure:/root/allure" \ --entrypoint=/bin/bash \ --label build=${JOB_NAME} \ voight-kampff-mark-1:${BRANCH_ALIAS} \ @@ -82,7 +84,7 @@ pipeline { -R /root/allure/"' echo 'Changing ownership of Allure results...' sh 'docker run \ - -v "$HOME/mycroft-logs/core/$BRANCH_ALIAS:/var/log/mycroft" \ + -v "$HOME/core/$BRANCH_ALIAS/mycroft-logs:/var/log/mycroft" \ --entrypoint=/bin/bash \ --label build=${JOB_NAME} \ voight-kampff-mark-1:${BRANCH_ALIAS} \ @@ -91,9 +93,9 @@ pipeline { echo 'Transferring...' sh 'rm -rf allure-result/*' - sh 'mv $HOME/allure/core/$BRANCH_ALIAS/allure-result allure-result' + sh 'mv $HOME/core/$BRANCH_ALIAS/allure/allure-result allure-result' // This directory should now be empty, rmdir will intentionally fail if not. - sh 'rmdir $HOME/allure/core/$BRANCH_ALIAS' + sh 'rmdir $HOME/core/$BRANCH_ALIAS/allure' script { allure([ includeProperties: false, @@ -104,8 +106,10 @@ pipeline { ]) } unarchive mapping:['allure-report.zip': 'allure-report.zip'] - sh 'zip mycroft-logs.zip -r $HOME/mycroft-logs/core/$BRANCH_ALIAS' - sh 'rm -r $HOME/mycroft-logs/core/$BRANCH_ALIAS' + sh 'zip mycroft-logs.zip -r $HOME/core/$BRANCH_ALIAS/mycroft-logs' + sh 'rm -r $HOME/core/$BRANCH_ALIAS/mycroft-logs' + // This directory should now be empty, rmdir will intentionally fail if not. + sh 'rmdir $HOME/core/$BRANCH_ALIAS' sh ( label: 'Publish Report to Web Server', script: '''scp allure-report.zip root@157.245.127.234:~; From 19d4d0cdc381205d3553b6b2c559878f9454f33f Mon Sep 17 00:00:00 2001 From: jarbasal Date: Sun, 20 Sep 2020 21:38:33 +0100 Subject: [PATCH 06/11] extend timeout --- mycroft/skills/common_play_skill.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/mycroft/skills/common_play_skill.py b/mycroft/skills/common_play_skill.py index ed8e484fc4..162810a603 100644 --- a/mycroft/skills/common_play_skill.py +++ b/mycroft/skills/common_play_skill.py @@ -241,6 +241,19 @@ class CommonPlaySkill(MycroftSkill, ABC): # self.CPS_play("http://zoosh.com/stream_music") pass + def CPS_extend_timeout(self, timeout=5): + """Request Common Play Framework to wait another {timeout} seconds + for an answer from this skill. + + Arguments: + timeout (int): Number of seconds + """ + self.bus.emit(Message('play:query.response', + {"phrase": self.play_service_string, + "searching": True, + "timeout": timeout, + "skill_id": self.skill_id})) + def CPS_send_status(self, artist='', track='', album='', image='', uri='', track_length=None, elapsed_time=None, playlist_position=None, From a7702e09a368fa654cf394a3dbf96432eac6147f Mon Sep 17 00:00:00 2001 From: Kris Gesling Date: Fri, 13 Nov 2020 10:00:02 +0930 Subject: [PATCH 07/11] Run CLA check on all branches --- Jenkinsfile | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 20ee202935..cb532ca40f 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -9,11 +9,6 @@ pipeline { stages { // Run the build in the against the dev branch to check for compile errors stage('Add CLA label to PR') { - when { - anyOf { - changeRequest target: 'dev' - } - } options { lock(resource: "lock_${env.JOB_NAME}") } From 841998e2748a861046fb5977b5378ac13664ea6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85ke=20Forslund?= Date: Sat, 10 Oct 2020 08:53:25 +0200 Subject: [PATCH 08/11] Fix logs when Mimic is not found If Mimic was not found at all the LOG message would raise an exception, this fixes that issue and cleans up Exception chain --- mycroft/tts/mimic_tts.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/mycroft/tts/mimic_tts.py b/mycroft/tts/mimic_tts.py index 95a08ca3b6..f7d0c70846 100644 --- a/mycroft/tts/mimic_tts.py +++ b/mycroft/tts/mimic_tts.py @@ -185,10 +185,14 @@ class MimicValidator(TTSValidator): def validate_connection(self): try: subprocess.call([BIN, '--version']) - except Exception: - LOG.info("Failed to find mimic at: " + BIN) + except Exception as err: + if BIN: + LOG.error('Failed to find mimic at: {}'.format(BIN)) + else: + LOG.error('Mimic executable not found') raise Exception( - 'Mimic was not found. Run install-mimic.sh to install it.') + 'Mimic was not found. Run install-mimic.sh to install it.') \ + from err def get_tts_class(self): return Mimic From 26901f1d5447edb8b1108b56332905d244de7e00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85ke=20Forslund?= Date: Sat, 10 Oct 2020 09:20:21 +0200 Subject: [PATCH 09/11] Fix pylint issues --- mycroft/tts/mimic_tts.py | 54 +++++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/mycroft/tts/mimic_tts.py b/mycroft/tts/mimic_tts.py index f7d0c70846..6a1fceeb2c 100644 --- a/mycroft/tts/mimic_tts.py +++ b/mycroft/tts/mimic_tts.py @@ -12,15 +12,18 @@ # See the License for the specific language governing permissions and # limitations under the License. # +"""Mimic TTS, a local TTS backend. + +This Backend uses the mimic executable to render text into speech. +""" import os +import os.path +from os.path import exists, join, expanduser import stat import subprocess from threading import Thread from time import sleep -import os.path -from os.path import exists, join, expanduser - from mycroft import MYCROFT_ROOT_PATH from mycroft.api import DeviceApi from mycroft.configuration import Configuration @@ -54,8 +57,8 @@ def download_subscriber_voices(selected_voice): """Call back function to make the downloaded file executable.""" LOG.info('Make executable new voice binary executable') # make executable - st = os.stat(dest) - os.chmod(dest, st.st_mode | stat.S_IEXEC) + file_stat = os.stat(dest) + os.chmod(dest, file_stat.st_mode | stat.S_IEXEC) # First download the selected voice if needed voice_file = SUBSCRIBER_VOICES.get(selected_voice) @@ -64,9 +67,9 @@ def download_subscriber_voices(selected_voice): url = DeviceApi().get_subscriber_voice_url(selected_voice) # Check we got an url if url: - dl = download(url, voice_file, make_executable) + dl_status = download(url, voice_file, make_executable) # Wait for completion - while not dl.done: + while not dl_status.done: sleep(1) else: LOG.debug('{} is not available for this architecture' @@ -79,9 +82,9 @@ def download_subscriber_voices(selected_voice): url = DeviceApi().get_subscriber_voice_url(voice) # Check we got an url if url: - dl = download(url, voice_file, make_executable) + dl_status = download(url, voice_file, make_executable) # Wait for completion - while not dl.done: + while not dl_status.done: sleep(1) else: LOG.debug('{} is not available for this architecture' @@ -95,25 +98,26 @@ class Mimic(TTS): lang, config, MimicValidator(self), 'wav', ssml_tags=["speak", "ssml", "phoneme", "voice", "audio", "prosody"] ) - self.dl = None self.clear_cache() # Download subscriber voices if needed self.is_subscriber = DeviceApi().is_subscriber if self.is_subscriber: - t = Thread(target=download_subscriber_voices, args=[self.voice]) - t.daemon = True - t.start() + trd = Thread(target=download_subscriber_voices, args=[self.voice]) + trd.daemon = True + trd.start() def modify_tag(self, tag): - for key, value in [ - ('x-slow', '0.4'), - ('slow', '0.7'), - ('medium', '1.0'), - ('high', '1.3'), - ('x-high', '1.6'), - ('speed', 'rate') - ]: + """Modify the SSML to suite Mimic.""" + ssml_conversions = { + 'x-slow': '0.4', + 'slow': '0.7', + 'medium': '1.0', + 'high': '1.3', + 'x-high': '1.6', + 'speed': 'rate' + } + for key, value in ssml_conversions.items(): tag = tag.replace(key, value) return tag @@ -175,14 +179,13 @@ class Mimic(TTS): class MimicValidator(TTSValidator): - def __init__(self, tts): - super(MimicValidator, self).__init__(tts) - + """Validator class checking that Mimic can be used.""" def validate_lang(self): + """Verify that the language is supported.""" # TODO: Verify version of mimic can handle the requested language - pass def validate_connection(self): + """Check that Mimic executable is found and works.""" try: subprocess.call([BIN, '--version']) except Exception as err: @@ -195,6 +198,7 @@ class MimicValidator(TTSValidator): from err def get_tts_class(self): + """Return the TTS class associated with the validator.""" return Mimic From b663bdd4f1a03ad39cd4989fd80be54b8eb65c56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85ke=20Forslund?= Date: Sat, 14 Nov 2020 11:29:57 +0100 Subject: [PATCH 10/11] Validate fallback TTS object Verify that the fallback object can execute when creating it --- mycroft/audio/speech.py | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/mycroft/audio/speech.py b/mycroft/audio/speech.py index 9412d7df98..8472288318 100644 --- a/mycroft/audio/speech.py +++ b/mycroft/audio/speech.py @@ -134,17 +134,31 @@ def mute_and_speak(utterance, ident, listen=False): LOG.error('TTS execution failed ({})'.format(repr(e))) -def mimic_fallback_tts(utterance, ident, listen): +def _get_mimic_fallback(): + """Lazily initializes the fallback TTS if needed.""" global mimic_fallback_obj - # fallback if connection is lost - config = Configuration.get() - tts_config = config.get('tts', {}).get("mimic", {}) - lang = config.get("lang", "en-us") if not mimic_fallback_obj: - mimic_fallback_obj = Mimic(lang, tts_config) - tts = mimic_fallback_obj + config = Configuration.get() + tts_config = config.get('tts', {}).get("mimic", {}) + lang = config.get("lang", "en-us") + tts = Mimic(lang, tts_config) + tts.validator.validate() + tts.init(bus) + mimic_fallback_obj = tts + + return mimic_fallback_obj + + +def mimic_fallback_tts(utterance, ident, listen): + """Speak utterance using fallback TTS if connection is lost. + + Arguments: + utterance (str): sentence to speak + ident (str): interaction id for metrics + listen (bool): True if interaction should end with mycroft listening + """ + tts = _get_mimic_fallback() LOG.debug("Mimic fallback, utterance : " + str(utterance)) - tts.init(bus) tts.execute(utterance, ident, listen) From a6dc99c1f5a8c5c30cae10893205d501a5a33a22 Mon Sep 17 00:00:00 2001 From: Kris Gesling Date: Wed, 9 Dec 2020 14:35:02 +0930 Subject: [PATCH 11/11] add -f for file removal to ensure no prompt in CI --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index cb532ca40f..be7ea9d8fa 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -102,7 +102,7 @@ pipeline { } unarchive mapping:['allure-report.zip': 'allure-report.zip'] sh 'zip mycroft-logs.zip -r $HOME/core/$BRANCH_ALIAS/mycroft-logs' - sh 'rm -r $HOME/core/$BRANCH_ALIAS/mycroft-logs' + sh 'rm -rf $HOME/core/$BRANCH_ALIAS/mycroft-logs' // This directory should now be empty, rmdir will intentionally fail if not. sh 'rmdir $HOME/core/$BRANCH_ALIAS' sh (