Backout "Bug 1144808 part 2: Harden phone-builder image."
authorWander Lairson Costa <wcosta@mozilla.com>
Wed, 28 Oct 2015 11:17:58 -0200
changeset 305084 0244d3de8b62548f6197b663aabf7be23d7afe03
parent 305083 fdbd1a793ca9d998d5a258ba44d99b42e8809c67
child 305085 e8bcd464373d8edde8f66d490e25408112c11af6
push id1001
push userraliiev@mozilla.com
push dateMon, 18 Jan 2016 19:06:03 +0000
treeherdermozilla-release@8b89261f3ac4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1144808
milestone44.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backout "Bug 1144808 part 2: Harden phone-builder image." Things just got too hard for B2G people to change build scripts, so we are backing out this.
testing/docker/phone-builder/Dockerfile
testing/docker/phone-builder/VERSION
testing/docker/phone-builder/bin/bootstrap.py
testing/docker/phone-builder/bin/build-dolphin.sh
testing/docker/phone-builder/bin/build-phone-ota.sh
testing/docker/phone-builder/bin/build-phone.sh
testing/docker/phone-builder/bin/post-build.sh
testing/docker/phone-builder/bin/pre-build.sh
testing/docker/phone-builder/bin/validate_task.py
testing/docker/phone-builder/tests/invalid_base_repo.yml
testing/docker/phone-builder/tests/invalid_command.yml
testing/docker/phone-builder/tests/invalid_head_repo.yml
testing/docker/phone-builder/tests/invalid_public.yml
testing/docker/phone-builder/tests/public.yml
testing/docker/phone-builder/tests/test_validation.py
testing/docker/phone-builder/tests/valid.yml
testing/docker/phone-builder/tests/valid_external_repo.yml
testing/taskcluster/scripts/phone-builder/build-dolphin.sh
testing/taskcluster/scripts/phone-builder/build-phone-ota.sh
testing/taskcluster/scripts/phone-builder/build-phone.sh
testing/taskcluster/scripts/phone-builder/post-build.sh
testing/taskcluster/scripts/phone-builder/pre-build.sh
testing/taskcluster/tasks/builds/b2g_aries_spark_debug.yml
testing/taskcluster/tasks/builds/b2g_aries_spark_opt.yml
testing/taskcluster/tasks/builds/b2g_aries_spark_ota_base.yml
testing/taskcluster/tasks/builds/b2g_dolphin_base.yml
testing/taskcluster/tasks/builds/b2g_flame_kk_debug.yml
testing/taskcluster/tasks/builds/b2g_flame_kk_opt.yml
testing/taskcluster/tasks/builds/b2g_flame_kk_ota_base.yml
testing/taskcluster/tasks/builds/b2g_flame_kk_spark_eng.yml
testing/taskcluster/tasks/builds/b2g_nexus_4_eng.yml
testing/taskcluster/tasks/builds/b2g_nexus_4_kk_eng.yml
testing/taskcluster/tasks/builds/b2g_nexus_4_kk_user.yml
testing/taskcluster/tasks/builds/b2g_nexus_4_user.yml
testing/taskcluster/tasks/builds/b2g_nexus_5l_eng.yml
testing/taskcluster/tasks/builds/b2g_nexus_5l_user.yml
testing/taskcluster/tasks/builds/b2g_phone_eng_base.yml
--- a/testing/docker/phone-builder/Dockerfile
+++ b/testing/docker/phone-builder/Dockerfile
@@ -8,10 +8,10 @@ ADD           bin                   /hom
 ADD           config                /home/worker/.aws/config
 ADD           socorro.token         /home/worker/socorro.token
 
 RUN           yum install -y bc lzop java-1.7.0-openjdk
 RUN           pip install awscli
 RUN           npm install -g bower gulp apm grunt-cli
 
 # Set a default command useful for debugging
-ENTRYPOINT ["bootstrap.py"]
+ENTRYPOINT ["validate_task.py"]
 
--- a/testing/docker/phone-builder/VERSION
+++ b/testing/docker/phone-builder/VERSION
@@ -1,1 +1,1 @@
-0.1.5
+0.0.22
deleted file mode 100755
--- a/testing/docker/phone-builder/bin/bootstrap.py
+++ /dev/null
@@ -1,131 +0,0 @@
-#!/usr/bin/env python
-
-from __future__ import print_function
-import os
-import json
-import urllib2
-import sys
-import re
-import subprocess
-import shutil
-
-HOME = os.getenv('HOME')
-
-WORKSPACE = os.path.join(HOME, 'workspace')
-ARTIFACTS_PUBLIC = os.path.normpath(os.path.join(HOME, 'artifacts-public'))
-SCRIPTS_PATH = os.path.join(HOME, 'bin')
-COMMANDS = {
-    'phone': 'build-phone.sh',
-    'phone-ota': 'build-phone-ota.sh',
-    'dolphin': 'build-dolphin.sh'
-}
-
-# Be careful when adding username containing '.', as it expands to any character
-# in regular expressions. It must be escaped properly:
-#  right: r'foo\.bar',
-#  wrong: 'foo.bar' # matches fooabar, foo1bar, foo_bar, etc
-
-github_allowed_accounts = [
-    'walac',
-    'nhirata',
-    'selenamarie',
-    'ShakoHo',
-]
-
-bitbucket_allowed_accounts = [
-    'walac',
-    'selenamarie',
-]
-
-def build_repo_matcher():
-    github_expr = r'(github\.com/(' + '|'.join(github_allowed_accounts) + ')/)'
-    bitbucket_expr = r'(bitbucket\.org/(' + '|'.join(bitbucket_allowed_accounts) + ')/)'
-    mozilla_expr = r'((hg|git)\.mozilla\.org)'
-    expr = r'^https?://(' + '|'.join((github_expr, bitbucket_expr, mozilla_expr)) + ')'
-    return re.compile(expr)
-
-repo_matcher = build_repo_matcher()
-
-def get_task(taskid):
-    return json.load(urllib2.urlopen('https://queue.taskcluster.net/v1/task/' + taskid))
-
-def check_repo(repo):
-    if not repo_matcher.match(repo):
-        print('Invalid repository "{}"'.format(repo), file=sys.stderr)
-        return -1
-    return 0
-
-# Cleanup artifacts and known credentials. This is to avoid a malicious
-# task to map a directory containing sensible files expose secret files.
-def cleanup(task):
-    payload = task['payload']
-    for key, value in payload.get('artifacts', {}).items():
-        shutil.rmtree(value['path'], ignore_errors=True)
-
-    shutil.rmtree(os.path.join(HOME, '.aws'), ignore_errors=True)
-
-    try:
-        os.remove(os.path.join(HOME, 'socorro.token'))
-    except (IOError, OSError):
-        pass
-
-def check_task(task):
-    repositories_to_check = [
-        'GECKO_HEAD_REPOSITORY',
-        'GECKO_BASE_REPOSITORY',
-    ]
-
-    payload = task['payload']
-
-    for repo in repositories_to_check:
-        if repo not in payload['env']:
-            print('Repository {} is not in payload.env.'.format(repo), file=sys.stderr)
-            return -1
-        ret = check_repo(payload['env'][repo])
-        if ret != 0:
-            return ret
-
-    for key, value in payload.get('artifacts', {}).items():
-        if key.startswith('public') and \
-                os.path.normpath(value['path']) != ARTIFACTS_PUBLIC:
-            print('{} cannot be a public artifact.'.format(value['path']),
-                  file=sys.stderr)
-            return -1
-
-    if sys.argv[1] not in COMMANDS:
-        print("Invalid build command '{}', valid commands are '{}'".format(sys.argv[1], ", ".join(COMMANDS.keys())))
-        return -1
-
-    return 0
-
-def run():
-    command = COMMANDS[sys.argv[1]]
-
-    checkout_gecko = ['checkout-gecko', WORKSPACE]
-    cd_scripts = ['cd', SCRIPTS_PATH]
-    build = ['buildbot_step', '"Build"', os.path.join(SCRIPTS_PATH, command), WORKSPACE]
-    and_ = ['&&']
-    command = ' '.join(checkout_gecko + and_ + cd_scripts + and_ + build)
-
-    try:
-        return subprocess.call(command, shell=True)
-    except subprocess.CalledProcessError as e:
-        return e.returncode
-
-def main():
-    taskid = os.getenv('TASK_ID')
-
-    # If the task id is None, we assume we are running docker locally
-    if taskid is not None:
-        task = get_task(taskid)
-        ret = check_task(task)
-        if ret != 0:
-            cleanup(task)
-            return ret
-
-    if len(sys.argv) > 1:
-        return run()
-
-
-if __name__ == '__main__':
-    sys.exit(main())
deleted file mode 100755
--- a/testing/docker/phone-builder/bin/build-dolphin.sh
+++ /dev/null
@@ -1,36 +0,0 @@
-#! /bin/bash -vex
-
-. pre-build.sh
-
-debug_flag=""
-if [ 0$B2G_DEBUG -ne 0 ]; then
-  debug_flag='--debug'
-fi
-
-$WORKSPACE/gecko/testing/mozharness/scripts/b2g_build.py \
-  --config b2g/taskcluster-phone.py \
-  "$debug_flag" \
-  --disable-mock \
-  --variant=$VARIANT \
-  --work-dir=$WORKSPACE/B2G \
-  --gaia-languages-file $WORKSPACE/B2G/device/sprd/scx15/languages.json \
-  --log-level=debug \
-  --target=$TARGET \
-  --b2g-config-dir=$TARGET \
-  --checkout-revision=$GECKO_HEAD_REV \
-  --base-repo=$GECKO_BASE_REPOSITORY \
-  --repo=$GECKO_HEAD_REPOSITORY
-
-# Don't cache backups
-rm -rf $WORKSPACE/B2G/backup-*
-
-# Move files into artifact locations!
-mkdir -p $HOME/artifacts
-
-mv $WORKSPACE/B2G/upload/sources.xml $HOME/artifacts/sources.xml
-mv $WORKSPACE/B2G/upload/b2g-*.crashreporter-symbols.zip $HOME/artifacts/b2g-crashreporter-symbols.zip
-mv $WORKSPACE/B2G/upload/b2g-*.android-arm.tar.gz $HOME/artifacts/b2g-android-arm.tar.gz
-mv $WORKSPACE/B2G/upload/${TARGET}.zip $HOME/artifacts/${TARGET}.zip
-mv $WORKSPACE/B2G/upload/gaia.zip $HOME/artifacts/gaia.zip
-
-ccache -s
deleted file mode 100755
--- a/testing/docker/phone-builder/bin/build-phone-ota.sh
+++ /dev/null
@@ -1,46 +0,0 @@
-#! /bin/bash -vex
-
-. pre-build.sh
-
-if [ $TARGET == "aries" -o $TARGET == "shinano" ]; then
-  # caching objects might be dangerous for some devices (aka aries)
-  rm -rf $gecko_objdir
-  rm -rf $WORKSPACE/B2G/out
-fi
-
-PLATFORM=${TARGET%%-*}
-
-aws s3 cp s3://b2g-nightly-credentials/balrog_credentials .
-mar_file=b2g-$PLATFORM-gecko-update.mar
-
-# We need different platform names for each variant (user, userdebug and
-# eng). We do not append variant suffix for "user" to keep compability with
-# verions already installed in the phones.
-if [ 0$DOGFOOD -ne 1 -a $VARIANT != "user" ]; then
-  PLATFORM=$PLATFORM-$VARIANT
-fi
-
-MOZHARNESS_CONFIG=${MOZHARNESS_CONFIG:=b2g/taskcluster-phone-ota.py}
-BALROG_SERVER_CONFIG=${BALROG_SERVER_CONFIG:=balrog/docker-worker.py}
-
-rm -rf $WORKSPACE/B2G/upload-public/
-rm -rf $WORKSPACE/B2G/upload/
-
-$WORKSPACE/gecko/testing/mozharness/scripts/b2g_build.py \
-  --config $MOZHARNESS_CONFIG \
-  --config $BALROG_SERVER_CONFIG \
-  "$debug_flag" \
-  --disable-mock \
-  --variant=$VARIANT \
-  --work-dir=$WORKSPACE/B2G \
-  --gaia-languages-file locales/languages_all.json \
-  --log-level=debug \
-  --target=$TARGET \
-  --b2g-config-dir=$TARGET \
-  --checkout-revision=$GECKO_HEAD_REV \
-  --repo=$WORKSPACE/gecko \
-  --platform $PLATFORM \
-  --gecko-objdir=$gecko_objdir \
-  --complete-mar-url https://queue.taskcluster.net/v1/task/$TASK_ID/runs/$RUN_ID/artifacts/public/build/$mar_file
-
-. post-build.sh
deleted file mode 100755
--- a/testing/docker/phone-builder/bin/build-phone.sh
+++ /dev/null
@@ -1,29 +0,0 @@
-#! /bin/bash -vex
-
-. pre-build.sh
-
-if [ $TARGET == "aries" -o $TARGET == "shinano" ]; then
-  # caching objects might be dangerous for some devices (aka aries)
-  rm -rf $gecko_objdir
-  rm -rf $WORKSPACE/B2G/out
-fi
-
-MOZHARNESS_CONFIG=${MOZHARNESS_CONFIG:=b2g/taskcluster-phone.py}
-
-rm -rf $WORKSPACE/B2G/upload/
-
-$WORKSPACE/gecko/testing/mozharness/scripts/b2g_build.py \
-  --config $MOZHARNESS_CONFIG \
-  "$debug_flag" \
-  --disable-mock \
-  --variant=$VARIANT \
-  --work-dir=$WORKSPACE/B2G \
-  --gaia-languages-file locales/languages_all.json \
-  --log-level=debug \
-  --target=$TARGET \
-  --b2g-config-dir=$TARGET \
-  --checkout-revision=$GECKO_HEAD_REV \
-  --repo=$WORKSPACE/gecko \
-  --gecko-objdir=$gecko_objdir
-
-. post-build.sh
deleted file mode 100755
--- a/testing/docker/phone-builder/bin/post-build.sh
+++ /dev/null
@@ -1,33 +0,0 @@
-#! /bin/bash -vex
-
-# Don't cache backups
-rm -rf $WORKSPACE/B2G/backup-*
-
-if [ -f balrog_credentials ]; then
-  rm -f balrog_credentials
-fi
-
-mkdir -p $HOME/artifacts
-mkdir -p $HOME/artifacts-public
-
-DEVICE=${TARGET%%-*}
-
-mv $WORKSPACE/B2G/upload/sources.xml $HOME/artifacts/sources.xml
-mv $WORKSPACE/B2G/upload/b2g-*.android-arm.tar.gz $HOME/artifacts/b2g-android-arm.tar.gz
-mv $WORKSPACE/B2G/upload/${TARGET}.zip $HOME/artifacts/${TARGET}.zip
-mv $WORKSPACE/B2G/upload/gaia.zip $HOME/artifacts/gaia.zip
-
-if [ -f $WORKSPACE/B2G/upload/b2g-*.crashreporter-symbols.zip ]; then
-  mv $WORKSPACE/B2G/upload/b2g-*.crashreporter-symbols.zip $HOME/artifacts/b2g-crashreporter-symbols.zip
-fi
-
-if [ -f $WORKSPACE/B2G/upload-public/*.blobfree-dist.zip ]; then
-  mv $WORKSPACE/B2G/upload-public/*.blobfree-dist.zip $HOME/artifacts-public/
-fi
-
-if [ -f $WORKSPACE/B2G/upload-public/$mar_file ]; then
-  mv $WORKSPACE/B2G/upload-public/$mar_file $HOME/artifacts-public/
-fi
-
-ccache -s
-
deleted file mode 100755
--- a/testing/docker/phone-builder/bin/pre-build.sh
+++ /dev/null
@@ -1,49 +0,0 @@
-#!/bin/bash -vex
-
-# Ensure all the scripts in this dir are on the path....
-DIRNAME=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
-PATH=$DIRNAME:$PATH
-
-export WORKSPACE=$HOME/workspace
-
-gecko_objdir=/home/worker/objdir-gecko/objdir
-
-### Check that require variables are defined
-test -d $WORKSPACE
-test $GECKO_HEAD_REPOSITORY # Should be an hg repository url to pull from
-test $GECKO_BASE_REPOSITORY # Should be an hg repository url to clone from
-test $GECKO_HEAD_REV # Should be an hg revision to pull down
-test $TARGET
-test $VARIANT
-
-export CCACHE_DIR=$WORKSPACE/ccache
-
-ccache -M 12G
-ccache -s
-
-# Figure out where the remote manifest is so we can use caches for it.
-MANIFEST=$(repository-url.py $GECKO_HEAD_REPOSITORY $GECKO_HEAD_REV b2g/config/$TARGET/sources.xml)
-tc-vcs repo-checkout $WORKSPACE/B2G https://git.mozilla.org/b2g/B2G.git $MANIFEST
-
-# Ensure symlink has been created to gecko...
-rm -f $WORKSPACE/B2G/gecko
-ln -s $WORKSPACE/gecko $WORKSPACE/B2G/gecko
-
-debug_flag=""
-if [ 0$B2G_DEBUG -ne 0 ]; then
-  debug_flag='--debug'
-fi
-
-if ! aws --output=text s3 ls s3://b2g-phone-backups/; then
-    echo "[aws:error] Failed to connect to AWS! Are the AWS credentials ok?"
-    exit 1
-fi
-
-backup_file=$(aws --output=text s3 ls s3://b2g-phone-backups/$TARGET/ | tail -1 | awk '{print $NF}')
-
-if echo $backup_file | grep '\.tar\.bz2'; then
-    aws s3 cp s3://b2g-phone-backups/$TARGET/$backup_file .
-    tar -xjf $backup_file -C $WORKSPACE/B2G
-    rm -f $backup_file
-fi
-
new file mode 100755
--- /dev/null
+++ b/testing/docker/phone-builder/bin/validate_task.py
@@ -0,0 +1,67 @@
+#!/usr/bin/env python
+
+from __future__ import print_function
+import os
+import os.path
+import json
+import urllib2
+import sys
+import re
+import subprocess
+
+repo_matcher = re.compile(r'[a-z]+://(hg|git)\.mozilla\.org')
+
+def get_task(taskid):
+    return json.load(urllib2.urlopen('https://queue.taskcluster.net/v1/task/' + taskid))
+
+def check_task(task):
+    payload = task['payload']
+
+    if 'GECKO_HEAD_REPOSITORY' not in payload['env']:
+        print('Task has no head gecko repository', file=sys.stderr)
+        return -1
+
+    repo = payload['env']['GECKO_HEAD_REPOSITORY']
+    # if it is not a mozilla repository, fail
+    if not repo_matcher.match(repo):
+        print('Invalid head repository', repo, file=sys.stderr)
+        return -1
+
+    if 'GECKO_BASE_REPOSITORY' not in payload['env']:
+        print('Task has no base gecko repository', file=sys.stderr)
+        return -1
+
+    repo = payload['env']['GECKO_BASE_REPOSITORY']
+    if not repo_matcher.match(repo):
+        print('Invalid base repository', repo, file=sys.stderr)
+        return -1
+
+    locations = task["extra"]["locations"]
+    if "img" in locations:
+        img = locations["img"]
+        if img.startswith("public"):
+            print('Cannot upload images to public', file=sys.stderr)
+            return -1
+
+    return 0
+
+def main():
+    taskid = os.getenv('TASK_ID')
+
+    # If the task id is None, we assume we are running docker locally
+    if taskid is not None:
+        task = get_task(taskid)
+        ret = check_task(task)
+        if ret != 0:
+            return ret
+
+    if len(sys.argv) > 1:
+        try:
+            return subprocess.call(sys.argv[1:], shell=True)
+        except subprocess.CalledProcessError as e:
+            return e.returncode
+
+    return 0
+
+if __name__ == '__main__':
+    sys.exit(main())
--- a/testing/docker/phone-builder/tests/invalid_base_repo.yml
+++ b/testing/docker/phone-builder/tests/invalid_base_repo.yml
@@ -18,23 +18,23 @@ task:
       build-emulator-objects: '/home/worker/object-folder-flame-kk-1'
 
     env:
       TARGET: 'flame-kk'
       B2G_DEBUG: '1'
       # revision/project params defined originally here https://github.com/taskcluster/taskcluster-try/blob/master/try/instantiate.js
       REVISION: 'tip'
       GECKO_HEAD_REPOSITORY: 'http://hg.mozilla.org/mozilla-central'
-      GECKO_BASE_REPOSITORY: 'http://github.com/mozilla/gecko-dev'
+      GECKO_BASE_REPOSITORY: 'git@github.com:mozilla/gecko-dev.git'
 
     image: 'quay.io/mozilla/phone-builder:0.0.1'
     maxRunTime: 14400
 
     command:
-      - phone
+      - build-phone.sh
 
     artifacts:
       'private/build':
         type: directory
         path: '/home/worker/artifacts/'
 
   extra:
     # Rather then enforcing particular conventions we require that all build
deleted file mode 100644
--- a/testing/docker/phone-builder/tests/invalid_command.yml
+++ /dev/null
@@ -1,52 +0,0 @@
-taskId: 1
-task:
-  metadata:
-    source: http://todo.com/soon
-    owner: user@domain.com
-    name: B2G flame-kk opt
-    description: B2G flame-kk opt
-
-  workerType: b2gbuild
-  provisionerId: aws-provisioner
-
-  scopes:
-    - 'docker-worker:cache:build-phone-objects'
-    - 'docker-worker:image:{{#docker_image}}phone-builder{{/docker_image}}'
-
-  payload:
-    cache:
-      build-phone-objects: '/home/worker/object-folder-flame-kk-1'
-
-    env:
-      TARGET: 'flame-kk'
-      B2G_DEBUG: '1'
-      # revision/project params defined originally here https://github.com/taskcluster/taskcluster-try/blob/master/try/instantiate.js
-      REVISION: 'tip'
-      GECKO_HEAD_REPOSITORY: 'http://hg.mozilla.org/mozilla-central'
-      GECKO_BASE_REPOSITORY: 'https://hg.mozilla.org/mozilla-central'
-
-    image: '{{#docker_image}}phone-builder{{/docker_image}}'
-    maxRunTime: 14400
-
-    command:
-      - build-phone.sh
-
-    artifacts:
-      'private/build':
-        type: directory
-        path: '/home/worker/artifacts/'
-        expires: '{{#from_now}}1 year{{/from_now}}'
-
-  extra:
-    # Rather then enforcing particular conventions we require that all build
-    # tasks provide the "build" extra field to specify where the build and tests
-    # files are located.
-    locations:
-      build: 'private/build/b2g-android-arm.tar.gz'
-      img: 'private/build/flame-kk.zip'
-      tests: 'private/build/gaia.zip'
-      symbols: 'private/build/b2g-crashreporter-symbols.zip'
-      sources: 'private/build/sources.xml'
-
-    treeherder:
-      symbol: B
--- a/testing/docker/phone-builder/tests/invalid_head_repo.yml
+++ b/testing/docker/phone-builder/tests/invalid_head_repo.yml
@@ -17,24 +17,24 @@ task:
     cache:
       build-emulator-objects: '/home/worker/object-folder-flame-kk-1'
 
     env:
       TARGET: 'flame-kk'
       B2G_DEBUG: '1'
       # revision/project params defined originally here https://github.com/taskcluster/taskcluster-try/blob/master/try/instantiate.js
       REVISION: 'tip'
-      GECKO_HEAD_REPOSITORY: 'http://bitbucket.org/mozilla/gecko-dev'
+      GECKO_HEAD_REPOSITORY: 'git@github.com:mozilla/gecko-dev.git'
       GECKO_BASE_REPOSITORY: 'http://hg.mozilla.org/mozilla-central'
 
     image: 'quay.io/mozilla/phone-builder:0.0.1'
     maxRunTime: 14400
 
     command:
-      - phone
+      - build-phone.sh
 
     artifacts:
       'private/build':
         type: directory
         path: '/home/worker/artifacts/'
 
   extra:
     # Rather then enforcing particular conventions we require that all build
deleted file mode 100644
--- a/testing/docker/phone-builder/tests/invalid_public.yml
+++ /dev/null
@@ -1,51 +0,0 @@
-taskId: 1
-task:
-  metadata:
-    source: http://todo.com/soon
-    owner: user@domain.com
-    name: B2G Emulator
-    description: B2G Emulator
-
-  workerType: b2gbuild
-  provisionerId: aws-provisioner
-
-  scopes:
-    - 'docker-worker:cache:build-emulator-objects'
-    - 'docker-worker:image:quay.io/mozilla/phone-builder:0.0.1'
-
-  payload:
-    cache:
-      build-emulator-objects: '/home/worker/object-folder-flame-kk-1'
-
-    env:
-      TARGET: 'flame-kk'
-      B2G_DEBUG: '1'
-      # revision/project params defined originally here https://github.com/taskcluster/taskcluster-try/blob/master/try/instantiate.js
-      REVISION: 'tip'
-      GECKO_HEAD_REPOSITORY: 'http://hg.mozilla.org/mozilla-central'
-      GECKO_BASE_REPOSITORY: 'http://hg.mozilla.org/mozilla-central'
-
-    image: 'quay.io/mozilla/phone-builder:0.0.1'
-    maxRunTime: 14400
-
-    command:
-      - phone
-
-    artifacts:
-      'public/build':
-        type: directory
-        path: '/home/worker/artifacts/'
-
-  extra:
-    # Rather then enforcing particular conventions we require that all build
-    # tasks provide the "build" extra field to specify where the build and tests
-    # files are located.
-    locations:
-      build: 'private/build/emulator.tar.gz'
-      tests: 'private/build/b2g-tests.zip'
-      symbols: 'private/build/b2g-crashreporter-symbols.zip'
-      sources: 'private/build/sources.xml'
-      img: 'private/build/image.zip'
-
-    treeherder:
-      symbol: B
new file mode 100644
--- /dev/null
+++ b/testing/docker/phone-builder/tests/public.yml
@@ -0,0 +1,50 @@
+taskId: 1
+task:
+  metadata:
+    source: http://todo.com/soon
+    owner: user@domain.com
+    name: B2G Emulator
+    description: B2G Emulator
+
+  workerType: b2gbuild
+  provisionerId: aws-provisioner
+
+  scopes:
+    - 'docker-worker:cache:build-emulator-objects'
+    - 'docker-worker:image:quay.io/mozilla/phone-builder:0.0.1'
+
+  payload:
+    cache:
+      build-emulator-objects: '/home/worker/object-folder-flame-kk-1'
+
+    env:
+      TARGET: 'flame-kk'
+      B2G_DEBUG: '1'
+      # revision/project params defined originally here https://github.com/taskcluster/taskcluster-try/blob/master/try/instantiate.js
+      REVISION: 'tip'
+      GECKO_HEAD_REPOSITORY: 'http://hg.mozilla.org/mozilla-central'
+      GECKO_BASE_REPOSITORY: 'http://hg.mozilla.org/mozilla-central'
+
+    image: 'quay.io/mozilla/phone-builder:0.0.1'
+    maxRunTime: 14400
+
+    command:
+      - build-phone.sh
+
+    artifacts:
+      'public/build':
+        type: directory
+        path: '/home/worker/artifacts/'
+
+  extra:
+    # Rather then enforcing particular conventions we require that all build
+    # tasks provide the "build" extra field to specify where the build and tests
+    # files are located.
+    locations:
+      build: 'public/build/emulator.tar.gz'
+      tests: 'public/build/b2g-tests.zip'
+      symbols: 'public/build/b2g-crashreporter-symbols.zip'
+      sources: 'public/build/sources.xml'
+
+    treeherder:
+      symbol: B
--- a/testing/docker/phone-builder/tests/test_validation.py
+++ b/testing/docker/phone-builder/tests/test_validation.py
@@ -1,34 +1,31 @@
 #!/usr/bin/env python
 
 import unittest
 import sys
 import yaml
 sys.path.append('../bin')
-from bootstrap import check_task
-import glob
+from validate_task import check_task
 
 def load_task(task_file):
     content = open(task_file, 'r')
-    task = yaml.load(content)['task']
-    sys.argv[1:] = task['payload']['command']
-    return task
+    return yaml.load(content)['task']
 
 class TaskValidationTest(unittest.TestCase):
-    def __init__(self, methodName='runTest'):
-        super(TaskValidationTest, self).__init__(methodName)
-        sys.argv.append('')
+    def test_valid_task(self):
+        task = load_task('valid.yml')
+        self.assertEquals(check_task(task), 0)
+
+    def test_invalid_base_repo(self):
+        task = load_task('invalid_base_repo.yml')
+        self.assertEquals(check_task(task), -1)
 
-    def test_valid_tasks(self):
-        valid_tasks = glob.glob('valid*.yml')
-        for t in valid_tasks:
-            task = load_task(t)
-            self.assertEqual(check_task(task), 0)
+    def test_invalid_head_repo(self):
+        task = load_task('invalid_head_repo.yml')
+        self.assertEquals(check_task(task), -1)
 
-    def test_invalid_tasks(self):
-        invalid_tasks = glob.glob('invalid*.yml')
-        for t in invalid_tasks:
-            task = load_task(t)
-            self.assertNotEquals(check_task(task), 0)
+    def test_public_artifact(self):
+        task = load_task('public.yml')
+        self.assertEquals(check_task(task), -1)
 
 if __name__ == '__main__':
     unittest.main()
--- a/testing/docker/phone-builder/tests/valid.yml
+++ b/testing/docker/phone-builder/tests/valid.yml
@@ -18,23 +18,23 @@ task:
       build-phone-objects: '/home/worker/object-folder-flame-kk-1'
 
     env:
       TARGET: 'flame-kk'
       B2G_DEBUG: '1'
       # revision/project params defined originally here https://github.com/taskcluster/taskcluster-try/blob/master/try/instantiate.js
       REVISION: 'tip'
       GECKO_HEAD_REPOSITORY: 'http://hg.mozilla.org/mozilla-central'
-      GECKO_BASE_REPOSITORY: 'https://hg.mozilla.org/mozilla-central'
+      GECKO_BASE_REPOSITORY: 'http://hg.mozilla.org/mozilla-central'
 
     image: '{{#docker_image}}phone-builder{{/docker_image}}'
     maxRunTime: 14400
 
     command:
-      - phone
+      - build-phone.sh
 
     artifacts:
       'private/build':
         type: directory
         path: '/home/worker/artifacts/'
         expires: '{{#from_now}}1 year{{/from_now}}'
 
   extra:
deleted file mode 100644
--- a/testing/docker/phone-builder/tests/valid_external_repo.yml
+++ /dev/null
@@ -1,52 +0,0 @@
-taskId: 1
-task:
-  metadata:
-    source: http://todo.com/soon
-    owner: user@domain.com
-    name: B2G flame-kk opt
-    description: B2G flame-kk opt
-
-  workerType: b2gbuild
-  provisionerId: aws-provisioner
-
-  scopes:
-    - 'docker-worker:cache:build-phone-objects'
-    - 'docker-worker:image:{{#docker_image}}phone-builder{{/docker_image}}'
-
-  payload:
-    cache:
-      build-phone-objects: '/home/worker/object-folder-flame-kk-1'
-
-    env:
-      TARGET: 'flame-kk'
-      B2G_DEBUG: '1'
-      # revision/project params defined originally here https://github.com/taskcluster/taskcluster-try/blob/master/try/instantiate.js
-      REVISION: 'tip'
-      GECKO_HEAD_REPOSITORY: 'http://github.com/walac/gecko-dev'
-      GECKO_BASE_REPOSITORY: 'https://bitbucket.org/walac/gecko-dev'
-
-    image: '{{#docker_image}}phone-builder{{/docker_image}}'
-    maxRunTime: 14400
-
-    command:
-      - phone
-
-    artifacts:
-      'private/build':
-        type: directory
-        path: '/home/worker/artifacts/'
-        expires: '{{#from_now}}1 year{{/from_now}}'
-
-  extra:
-    # Rather then enforcing particular conventions we require that all build
-    # tasks provide the "build" extra field to specify where the build and tests
-    # files are located.
-    locations:
-      build: 'private/build/b2g-android-arm.tar.gz'
-      img: 'private/build/flame-kk.zip'
-      tests: 'private/build/gaia.zip'
-      symbols: 'private/build/b2g-crashreporter-symbols.zip'
-      sources: 'private/build/sources.xml'
-
-    treeherder:
-      symbol: B
new file mode 100755
--- /dev/null
+++ b/testing/taskcluster/scripts/phone-builder/build-dolphin.sh
@@ -0,0 +1,36 @@
+#! /bin/bash -vex
+
+. pre-build.sh
+
+debug_flag=""
+if [ 0$B2G_DEBUG -ne 0 ]; then
+  debug_flag='--debug'
+fi
+
+$WORKSPACE/gecko/testing/mozharness/scripts/b2g_build.py \
+  --config b2g/taskcluster-phone.py \
+  "$debug_flag" \
+  --disable-mock \
+  --variant=$VARIANT \
+  --work-dir=$WORKSPACE/B2G \
+  --gaia-languages-file $WORKSPACE/B2G/device/sprd/scx15/languages.json \
+  --log-level=debug \
+  --target=$TARGET \
+  --b2g-config-dir=$TARGET \
+  --checkout-revision=$GECKO_HEAD_REV \
+  --base-repo=$GECKO_BASE_REPOSITORY \
+  --repo=$GECKO_HEAD_REPOSITORY
+
+# Don't cache backups
+rm -rf $WORKSPACE/B2G/backup-*
+
+# Move files into artifact locations!
+mkdir -p $HOME/artifacts
+
+mv $WORKSPACE/B2G/upload/sources.xml $HOME/artifacts/sources.xml
+mv $WORKSPACE/B2G/upload/b2g-*.crashreporter-symbols.zip $HOME/artifacts/b2g-crashreporter-symbols.zip
+mv $WORKSPACE/B2G/upload/b2g-*.android-arm.tar.gz $HOME/artifacts/b2g-android-arm.tar.gz
+mv $WORKSPACE/B2G/upload/${TARGET}.zip $HOME/artifacts/${TARGET}.zip
+mv $WORKSPACE/B2G/upload/gaia.zip $HOME/artifacts/gaia.zip
+
+ccache -s
new file mode 100755
--- /dev/null
+++ b/testing/taskcluster/scripts/phone-builder/build-phone-ota.sh
@@ -0,0 +1,46 @@
+#! /bin/bash -vex
+
+. pre-build.sh
+
+if [ $TARGET == "aries" -o $TARGET == "shinano" ]; then
+  # caching objects might be dangerous for some devices (aka aries)
+  rm -rf $gecko_objdir
+  rm -rf $WORKSPACE/B2G/out
+fi
+
+PLATFORM=${TARGET%%-*}
+
+aws s3 cp s3://b2g-nightly-credentials/balrog_credentials .
+mar_file=b2g-$PLATFORM-gecko-update.mar
+
+# We need different platform names for each variant (user, userdebug and
+# eng). We do not append variant suffix for "user" to keep compability with
+# verions already installed in the phones.
+if [ 0$DOGFOOD -ne 1 -a $VARIANT != "user" ]; then
+  PLATFORM=$PLATFORM-$VARIANT
+fi
+
+MOZHARNESS_CONFIG=${MOZHARNESS_CONFIG:=b2g/taskcluster-phone-ota.py}
+BALROG_SERVER_CONFIG=${BALROG_SERVER_CONFIG:=balrog/docker-worker.py}
+
+rm -rf $WORKSPACE/B2G/upload-public/
+rm -rf $WORKSPACE/B2G/upload/
+
+$WORKSPACE/gecko/testing/mozharness/scripts/b2g_build.py \
+  --config $MOZHARNESS_CONFIG \
+  --config $BALROG_SERVER_CONFIG \
+  "$debug_flag" \
+  --disable-mock \
+  --variant=$VARIANT \
+  --work-dir=$WORKSPACE/B2G \
+  --gaia-languages-file locales/languages_all.json \
+  --log-level=debug \
+  --target=$TARGET \
+  --b2g-config-dir=$TARGET \
+  --checkout-revision=$GECKO_HEAD_REV \
+  --repo=$WORKSPACE/gecko \
+  --platform $PLATFORM \
+  --gecko-objdir=$gecko_objdir \
+  --complete-mar-url https://queue.taskcluster.net/v1/task/$TASK_ID/runs/$RUN_ID/artifacts/public/build/$mar_file
+
+. post-build.sh
new file mode 100755
--- /dev/null
+++ b/testing/taskcluster/scripts/phone-builder/build-phone.sh
@@ -0,0 +1,29 @@
+#! /bin/bash -vex
+
+. pre-build.sh
+
+if [ $TARGET == "aries" -o $TARGET == "shinano" ]; then
+  # caching objects might be dangerous for some devices (aka aries)
+  rm -rf $gecko_objdir
+  rm -rf $WORKSPACE/B2G/out
+fi
+
+MOZHARNESS_CONFIG=${MOZHARNESS_CONFIG:=b2g/taskcluster-phone.py}
+
+rm -rf $WORKSPACE/B2G/upload/
+
+$WORKSPACE/gecko/testing/mozharness/scripts/b2g_build.py \
+  --config $MOZHARNESS_CONFIG \
+  "$debug_flag" \
+  --disable-mock \
+  --variant=$VARIANT \
+  --work-dir=$WORKSPACE/B2G \
+  --gaia-languages-file locales/languages_all.json \
+  --log-level=debug \
+  --target=$TARGET \
+  --b2g-config-dir=$TARGET \
+  --checkout-revision=$GECKO_HEAD_REV \
+  --repo=$WORKSPACE/gecko \
+  --gecko-objdir=$gecko_objdir
+
+. post-build.sh
new file mode 100755
--- /dev/null
+++ b/testing/taskcluster/scripts/phone-builder/post-build.sh
@@ -0,0 +1,33 @@
+#! /bin/bash -vex
+
+# Don't cache backups
+rm -rf $WORKSPACE/B2G/backup-*
+
+if [ -f balrog_credentials ]; then
+  rm -f balrog_credentials
+fi
+
+mkdir -p $HOME/artifacts
+mkdir -p $HOME/artifacts-public
+
+DEVICE=${TARGET%%-*}
+
+mv $WORKSPACE/B2G/upload/sources.xml $HOME/artifacts/sources.xml
+mv $WORKSPACE/B2G/upload/b2g-*.android-arm.tar.gz $HOME/artifacts/b2g-android-arm.tar.gz
+mv $WORKSPACE/B2G/upload/${TARGET}.zip $HOME/artifacts/${TARGET}.zip
+mv $WORKSPACE/B2G/upload/gaia.zip $HOME/artifacts/gaia.zip
+
+if [ -f $WORKSPACE/B2G/upload/b2g-*.crashreporter-symbols.zip ]; then
+  mv $WORKSPACE/B2G/upload/b2g-*.crashreporter-symbols.zip $HOME/artifacts/b2g-crashreporter-symbols.zip
+fi
+
+if [ -f $WORKSPACE/B2G/upload-public/*.blobfree-dist.zip ]; then
+  mv $WORKSPACE/B2G/upload-public/*.blobfree-dist.zip $HOME/artifacts-public/
+fi
+
+if [ -f $WORKSPACE/B2G/upload-public/$mar_file ]; then
+  mv $WORKSPACE/B2G/upload-public/$mar_file $HOME/artifacts-public/
+fi
+
+ccache -s
+
new file mode 100755
--- /dev/null
+++ b/testing/taskcluster/scripts/phone-builder/pre-build.sh
@@ -0,0 +1,46 @@
+#!/bin/bash -vex
+
+# Ensure all the scripts in this dir are on the path....
+DIRNAME=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
+PATH=$DIRNAME:$PATH
+
+WORKSPACE=$1
+
+gecko_objdir=/home/worker/objdir-gecko/objdir
+
+### Check that require variables are defined
+test -d $WORKSPACE
+test $GECKO_HEAD_REPOSITORY # Should be an hg repository url to pull from
+test $GECKO_BASE_REPOSITORY # Should be an hg repository url to clone from
+test $GECKO_HEAD_REV # Should be an hg revision to pull down
+test $TARGET
+test $VARIANT
+
+. ../builder/setup-ccache.sh
+
+# Figure out where the remote manifest is so we can use caches for it.
+MANIFEST=$(repository-url.py $GECKO_HEAD_REPOSITORY $GECKO_HEAD_REV b2g/config/$TARGET/sources.xml)
+tc-vcs repo-checkout $WORKSPACE/B2G https://git.mozilla.org/b2g/B2G.git $MANIFEST
+
+# Ensure symlink has been created to gecko...
+rm -f $WORKSPACE/B2G/gecko
+ln -s $WORKSPACE/gecko $WORKSPACE/B2G/gecko
+
+debug_flag=""
+if [ 0$B2G_DEBUG -ne 0 ]; then
+  debug_flag='--debug'
+fi
+
+if ! aws --output=text s3 ls s3://b2g-phone-backups/; then
+    echo "[aws:error] Failed to connect to AWS! Are the AWS credentials ok?"
+    exit 1
+fi
+
+backup_file=$(aws --output=text s3 ls s3://b2g-phone-backups/$TARGET/ | tail -1 | awk '{print $NF}')
+
+if echo $backup_file | grep '\.tar\.bz2'; then
+    aws s3 cp s3://b2g-phone-backups/$TARGET/$backup_file .
+    tar -xjf $backup_file -C $WORKSPACE/B2G
+    rm -f $backup_file
+fi
+
--- a/testing/taskcluster/tasks/builds/b2g_aries_spark_debug.yml
+++ b/testing/taskcluster/tasks/builds/b2g_aries_spark_debug.yml
@@ -18,17 +18,20 @@ task:
     env:
       TARGET: 'aries'
       DEBUG: 0
       VARIANT: userdebug
       GAIA_OPTIMIZE: '1'
       B2G_SYSTEM_APPS: '1'
       MOZHARNESS_CONFIG: b2g/taskcluster-spark.py
     command:
-      - phone
+      - >
+        checkout-gecko workspace &&
+        cd ./workspace/gecko/testing/taskcluster/scripts/phone-builder &&
+        buildbot_step 'Build' ./build-phone.sh $HOME/workspace
   extra:
     treeherderEnv:
       - production
       - staging
     treeherder:
       symbol: B
       groupSymbol: Aries
       groupName: Aries Device Image
--- a/testing/taskcluster/tasks/builds/b2g_aries_spark_opt.yml
+++ b/testing/taskcluster/tasks/builds/b2g_aries_spark_opt.yml
@@ -18,17 +18,20 @@ task:
     env:
       TARGET: 'aries'
       DEBUG: 0
       VARIANT: user
       GAIA_OPTIMIZE: '1'
       B2G_SYSTEM_APPS: '1'
       MOZHARNESS_CONFIG: b2g/taskcluster-spark.py
     command:
-      - phone
+      - >
+        checkout-gecko workspace &&
+        cd ./workspace/gecko/testing/taskcluster/scripts/phone-builder &&
+        buildbot_step 'Build' ./build-phone.sh $HOME/workspace
   extra:
     treeherderEnv:
       - production
       - staging
     treeherder:
       symbol: B
       groupSymbol: Aries
       groupName: Aries Device Image
--- a/testing/taskcluster/tasks/builds/b2g_aries_spark_ota_base.yml
+++ b/testing/taskcluster/tasks/builds/b2g_aries_spark_ota_base.yml
@@ -8,17 +8,20 @@ task:
   payload:
     features:
       balrogVPNProxy: true
     env:
       TARGET: 'aries'
       DEBUG: 0
       MOZHARNESS_CONFIG: b2g/taskcluster-spark-ota.py
     command:
-      - phone-ota
+      - >
+        checkout-gecko workspace &&
+        cd ./workspace/gecko/testing/taskcluster/scripts/phone-builder &&
+        buildbot_step 'Build' ./build-phone-ota.sh $HOME/workspace
 
   extra:
     treeherder:
       machine:
         platform: b2g-device-image
     locations:
       img: 'private/build/aries.zip'
       mar: 'public/build/b2g-aries-gecko-update.mar'
--- a/testing/taskcluster/tasks/builds/b2g_dolphin_base.yml
+++ b/testing/taskcluster/tasks/builds/b2g_dolphin_base.yml
@@ -10,17 +10,20 @@ task:
       REPO_TRACE: 1
       VARIANT: user
       DEBUG: 0
 
     # Dolphin could take more than one hours to build!
     maxRunTime: 7200
 
     command:
-      - dolphin
+      - >
+        checkout-gecko workspace &&
+        cd ./workspace/gecko/testing/taskcluster/scripts/phone-builder &&
+        buildbot_step 'Build' ./build-dolphin.sh $HOME/workspace
 
   extra:
     # Rather then enforcing particular conventions we require that all build
     # tasks provide the "build" extra field to specify where the build and tests
     # files are located.
     locations:
       build: 'private/build/b2g-android-arm.tar.gz'
       tests: 'private/build/gaia.zip'
--- a/testing/taskcluster/tasks/builds/b2g_flame_kk_debug.yml
+++ b/testing/taskcluster/tasks/builds/b2g_flame_kk_debug.yml
@@ -15,17 +15,20 @@ task:
     cache:
       build-flame-kk-debug: /home/worker/workspace
       build-flame-kk-debug-objdir-gecko-{{project}}: /home/worker/objdir-gecko
     env:
       TARGET: 'flame-kk'
       DEBUG: 0
       VARIANT: userdebug
     command:
-      - phone
+      - >
+        checkout-gecko workspace &&
+        cd ./workspace/gecko/testing/taskcluster/scripts/phone-builder &&
+        buildbot_step 'Build' ./build-phone.sh $HOME/workspace
   extra:
     treeherderEnv:
       - production
       - staging
     treeherder:
       symbol: B
       groupSymbol: Flame-KK
       groupName: Flame KitKat Device Image
--- a/testing/taskcluster/tasks/builds/b2g_flame_kk_opt.yml
+++ b/testing/taskcluster/tasks/builds/b2g_flame_kk_opt.yml
@@ -14,17 +14,20 @@ task:
   payload:
     cache:
       build-flame-kk-opt: /home/worker/workspace
       build-flame-kk-opt-objdir-gecko-{{project}}: /home/worker/objdir-gecko
     env:
       TARGET: 'flame-kk'
       DEBUG: 0
     command:
-      - phone
+      - >
+        checkout-gecko workspace &&
+        cd ./workspace/gecko/testing/taskcluster/scripts/phone-builder &&
+        buildbot_step 'Build' ./build-phone.sh $HOME/workspace
   extra:
     treeherderEnv:
       - production
       - staging
     treeherder:
       symbol: B
       groupSymbol: Flame-KK
       groupName: Flame KitKat Device Image
--- a/testing/taskcluster/tasks/builds/b2g_flame_kk_ota_base.yml
+++ b/testing/taskcluster/tasks/builds/b2g_flame_kk_ota_base.yml
@@ -7,17 +7,20 @@ task:
 
   payload:
     features:
       balrogVPNProxy: true
     env:
       TARGET: 'flame-kk'
       DEBUG: 0
     command:
-      - phone-ota
+      - >
+        checkout-gecko workspace &&
+        cd ./workspace/gecko/testing/taskcluster/scripts/phone-builder &&
+        buildbot_step 'Build' ./build-phone-ota.sh $HOME/workspace
 
   extra:
     treeherder:
       machine:
         platform: b2g-device-image
     locations:
       img: 'private/build/flame-kk.zip'
       mar: 'public/build/b2g-flame-gecko-update.mar'
--- a/testing/taskcluster/tasks/builds/b2g_flame_kk_spark_eng.yml
+++ b/testing/taskcluster/tasks/builds/b2g_flame_kk_spark_eng.yml
@@ -11,16 +11,17 @@ task:
     name: '[TC] B2G Flame KK Eng (spark)'
 
   payload:
     cache:
       build-flame-kk-spark-eng: /home/worker/workspace
       build-flame-kk-spark-eng-objdir-gecko-{{project}}: /home/worker/objdir-gecko
     env:
       TARGET: 'flame-kk'
+      MOZHARNESS_CONFIG: b2g/taskcluster-spark.py
   extra:
     treeherderEnv:
       - staging
     treeherder:
       symbol: Be
       groupSymbol: Flame-KK-spark
       groupName: Flame KitKat Device Image
       machine:
--- a/testing/taskcluster/tasks/builds/b2g_nexus_4_eng.yml
+++ b/testing/taskcluster/tasks/builds/b2g_nexus_4_eng.yml
@@ -10,16 +10,21 @@ task:
   metadata:
     name: '[TC] B2G Nexus 4 Eng'
   payload:
     cache:
       build-nexus-4-eng: /home/worker/object-folder
     env:
       TARGET: 'nexus-4'
       DEBUG: 0
+    command:
+      - >
+        checkout-gecko workspace &&
+        cd ./workspace/gecko/testing/taskcluster/scripts/phone-builder &&
+        buildbot_step 'Build' ./build-phone.sh $HOME/workspace
   extra:
     treeherderEnv:
       - production
       - staging
     treeherder:
       symbol: Be
       groupSymbol: Nexus 4
       groupName: Nexus 4 Device Image
--- a/testing/taskcluster/tasks/builds/b2g_nexus_4_kk_eng.yml
+++ b/testing/taskcluster/tasks/builds/b2g_nexus_4_kk_eng.yml
@@ -12,16 +12,21 @@ task:
     name: '[TC] B2G Nexus 4 KK Eng'
   payload:
     cache:
       build-nexus-4-kk-eng: /home/worker/workspace
       build-nexus-4-kk-eng-objdir-gecko-{{project}}: /home/worker/objdir-gecko
     env:
       TARGET: 'nexus-4-kk'
       DEBUG: 0
+    command:
+      - >
+        checkout-gecko workspace &&
+        cd ./workspace/gecko/testing/taskcluster/scripts/phone-builder &&
+        buildbot_step 'Build' ./build-phone.sh $HOME/workspace
   extra:
     treeherderEnv:
       - production
       - staging
     treeherder:
       symbol: Be
       groupSymbol: Nexus 4 KK
       groupName: Nexus 4 Kitkat Device Image
--- a/testing/taskcluster/tasks/builds/b2g_nexus_4_kk_user.yml
+++ b/testing/taskcluster/tasks/builds/b2g_nexus_4_kk_user.yml
@@ -13,17 +13,20 @@ task:
   payload:
     cache:
       build-nexus-4-kk-user: /home/worker/workspace
       build-nexus-4-kk-user-objdir-gecko-{{project}}: /home/worker/objdir-gecko
     env:
       TARGET: 'nexus-4-kk'
       DEBUG: 0
     command:
-      - phone
+      - >
+        checkout-gecko workspace &&
+        cd ./workspace/gecko/testing/taskcluster/scripts/phone-builder &&
+        buildbot_step 'Build' ./build-phone.sh $HOME/workspace
   extra:
     treeherderEnv:
       - production
       - staging
     treeherder:
       symbol: B
       groupSymbol: Nexus 4 KK
       groupName: Nexus 4 Kitkat Device Image
--- a/testing/taskcluster/tasks/builds/b2g_nexus_4_user.yml
+++ b/testing/taskcluster/tasks/builds/b2g_nexus_4_user.yml
@@ -12,17 +12,20 @@ task:
 
   payload:
     cache:
       build-nexus-4-user: /home/worker/object-folder
     env:
       TARGET: 'nexus-4'
       DEBUG: 0
     command:
-      - phone
+      - >
+        checkout-gecko workspace &&
+        cd ./workspace/gecko/testing/taskcluster/scripts/phone-builder &&
+        buildbot_step 'Build' ./build-phone.sh $HOME/workspace
   extra:
     treeherderEnv:
       - production
       - staging
     treeherder:
       symbol: B
       groupSymbol: Nexus 4
       groupName: Nexus 4 Device Image
--- a/testing/taskcluster/tasks/builds/b2g_nexus_5l_eng.yml
+++ b/testing/taskcluster/tasks/builds/b2g_nexus_5l_eng.yml
@@ -10,16 +10,21 @@ task:
   metadata:
     name: '[TC] B2G Nexus 5-L Eng'
   payload:
     cache:
       build-nexus-5-l-eng: /home/worker/object-folder
     env:
       TARGET: 'nexus-5-l'
       DEBUG: 0
+    command:
+      - >
+        checkout-gecko workspace &&
+        cd ./workspace/gecko/testing/taskcluster/scripts/phone-builder &&
+        buildbot_step 'Build' ./build-phone.sh $HOME/workspace
   extra:
     treeherderEnv:
       - production
       - staging
     treeherder:
       symbol: Be
       groupSymbol: Nexus 5-L
       groupName: Nexus 5-L Device Image
--- a/testing/taskcluster/tasks/builds/b2g_nexus_5l_user.yml
+++ b/testing/taskcluster/tasks/builds/b2g_nexus_5l_user.yml
@@ -12,17 +12,20 @@ task:
 
   payload:
     cache:
       build-nexus-5-l-user: /home/worker/object-folder
     env:
       TARGET: 'nexus-5-l'
       DEBUG: 0
     command:
-      - phone
+      - >
+        checkout-gecko workspace &&
+        cd ./workspace/gecko/testing/taskcluster/scripts/phone-builder &&
+        buildbot_step 'Build' ./build-phone.sh $HOME/workspace
   extra:
     treeherderEnv:
       - production
       - staging
     treeherder:
       symbol: B
       groupSymbol: Nexus 5-L
       groupName: Nexus 5-L Device Image
--- a/testing/taskcluster/tasks/builds/b2g_phone_eng_base.yml
+++ b/testing/taskcluster/tasks/builds/b2g_phone_eng_base.yml
@@ -9,9 +9,12 @@ task:
       VARIANT: eng
       GAIA_OPTIMIZE: '1'
       B2G_SYSTEM_APPS: '1'
       MOZ_TELEMETRY_REPORTING: '1'
       MOZ_CRASHREPORTER_NO_REPORT: '1'
       GAIA_KEYBOARD_LAYOUTS: 'en,pt-BR,es,de,fr,pl,zh-Hans-Pinyin,zh-Hant-Zhuyin,en-Dvorak'
       B2G_UPDATE_CHANNEL: 'default'
     command:
-      - phone
+      - >
+        checkout-gecko workspace &&
+        cd ./workspace/gecko/testing/taskcluster/scripts/phone-builder &&
+        buildbot_step 'Build' ./build-phone.sh $HOME/workspace