Add SKQP bot to build universal APK to master

Cherry-picked to skqp/dev.

No-Try: true
Bug: skia:
Change-Id: Ib0e9ddb621056ddce2422b53f312ec42d4d7aa3c
Reviewed-on: https://skia-review.googlesource.com/107880
Commit-Queue: Stephan Altmueller <stephana@google.com>
Reviewed-by: Eric Boren <borenet@google.com>
(cherry picked from commit 2a552179cc20f4d198c9035106df226c36f5e8c9)
Reviewed-on: https://skia-review.googlesource.com/108641
Reviewed-by: Derek Sollenberger <djsollen@google.com>
diff --git a/infra/bots/assets/android_sdk/VERSION b/infra/bots/assets/android_sdk_linux/VERSION
similarity index 100%
rename from infra/bots/assets/android_sdk/VERSION
rename to infra/bots/assets/android_sdk_linux/VERSION
diff --git a/infra/bots/assets/android_sdk/common.py b/infra/bots/assets/android_sdk_linux/common.py
similarity index 94%
rename from infra/bots/assets/android_sdk/common.py
rename to infra/bots/assets/android_sdk_linux/common.py
index 4920c9b..caa0ad8 100755
--- a/infra/bots/assets/android_sdk/common.py
+++ b/infra/bots/assets/android_sdk_linux/common.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright 2016 Google Inc.
+# Copyright 2017 Google Inc.
 #
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
diff --git a/infra/bots/assets/android_sdk/create.py b/infra/bots/assets/android_sdk_linux/create.py
old mode 100644
new mode 100755
similarity index 96%
rename from infra/bots/assets/android_sdk/create.py
rename to infra/bots/assets/android_sdk_linux/create.py
index 256a41d..e84d335
--- a/infra/bots/assets/android_sdk/create.py
+++ b/infra/bots/assets/android_sdk_linux/create.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright 2016 Google Inc.
+# Copyright 2017 Google Inc.
 #
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
diff --git a/infra/bots/assets/android_sdk/create_and_upload.py b/infra/bots/assets/android_sdk_linux/create_and_upload.py
similarity index 97%
rename from infra/bots/assets/android_sdk/create_and_upload.py
rename to infra/bots/assets/android_sdk_linux/create_and_upload.py
index 01b1b2c..fa21a8a 100755
--- a/infra/bots/assets/android_sdk/create_and_upload.py
+++ b/infra/bots/assets/android_sdk_linux/create_and_upload.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright 2016 Google Inc.
+# Copyright 2017 Google Inc.
 #
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
diff --git a/infra/bots/assets/android_sdk/download.py b/infra/bots/assets/android_sdk_linux/download.py
similarity index 89%
rename from infra/bots/assets/android_sdk/download.py
rename to infra/bots/assets/android_sdk_linux/download.py
index 96cc87d..ca999e0 100755
--- a/infra/bots/assets/android_sdk/download.py
+++ b/infra/bots/assets/android_sdk_linux/download.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright 2016 Google Inc.
+# Copyright 2017 Google Inc.
 #
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
diff --git a/infra/bots/assets/android_sdk/upload.py b/infra/bots/assets/android_sdk_linux/upload.py
similarity index 88%
rename from infra/bots/assets/android_sdk/upload.py
rename to infra/bots/assets/android_sdk_linux/upload.py
index ba7fc8b..bdfbda7 100755
--- a/infra/bots/assets/android_sdk/upload.py
+++ b/infra/bots/assets/android_sdk_linux/upload.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright 2016 Google Inc.
+# Copyright 2017 Google Inc.
 #
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
diff --git a/infra/bots/gen_tasks.go b/infra/bots/gen_tasks.go
index ee4b231..e08e708 100644
--- a/infra/bots/gen_tasks.go
+++ b/infra/bots/gen_tasks.go
@@ -35,6 +35,7 @@
 	ISOLATE_SKP_NAME            = "Housekeeper-PerCommit-IsolateSKP"
 	ISOLATE_SVG_NAME            = "Housekeeper-PerCommit-IsolateSVG"
 	ISOLATE_NDK_LINUX_NAME      = "Housekeeper-PerCommit-IsolateAndroidNDKLinux"
+	ISOLATE_SDK_LINUX_NAME      = "Housekeeper-PerCommit-IsolateAndroidSDKLinux"
 	ISOLATE_WIN_TOOLCHAIN_NAME  = "Housekeeper-PerCommit-IsolateWinToolchain"
 	ISOLATE_WIN_VULKAN_SDK_NAME = "Housekeeper-PerCommit-IsolateWinVulkanSDK"
 
@@ -445,6 +446,10 @@
 		isolateFile: "isolate_ndk_linux.isolate",
 		cipdPkg:     "android_ndk_linux",
 	},
+	ISOLATE_SDK_LINUX_NAME: {
+		isolateFile: "isolate_android_sdk_linux.isolate",
+		cipdPkg:     "android_sdk_linux",
+	},
 	ISOLATE_WIN_TOOLCHAIN_NAME: {
 		isolateFile: "isolate_win_toolchain.isolate",
 		cipdPkg:     "win_toolchain",
@@ -512,6 +517,9 @@
 			pkgs = append(pkgs, pkg)
 		} else {
 			deps = append(deps, isolateCIPDAsset(b, ISOLATE_NDK_LINUX_NAME))
+			if strings.Contains(name, "SKQP") {
+				deps = append(deps, isolateCIPDAsset(b, ISOLATE_SDK_LINUX_NAME))
+			}
 		}
 	} else if strings.Contains(name, "Chromecast") {
 		pkgs = append(pkgs, b.MustGetCipdPackageFromAsset("cast_toolchain"))
diff --git a/infra/bots/isolate_android_sdk_linux.isolate b/infra/bots/isolate_android_sdk_linux.isolate
new file mode 100644
index 0000000..3de92cf
--- /dev/null
+++ b/infra/bots/isolate_android_sdk_linux.isolate
@@ -0,0 +1,7 @@
+{
+  'variables': {
+    'command': [
+      '/bin/cp', '-rL', 'android_sdk_linux', '${ISOLATED_OUTDIR}',
+    ],
+  },
+}
diff --git a/infra/bots/jobs.json b/infra/bots/jobs.json
index b78a026..c6c028c 100644
--- a/infra/bots/jobs.json
+++ b/infra/bots/jobs.json
@@ -15,6 +15,7 @@
   "Build-Debian9-Clang-arm64-Release-Android_ASAN_Vulkan",
   "Build-Debian9-Clang-arm64-Release-Android_Vulkan",
   "Build-Debian9-Clang-gce_x86_phone-eng-Android_Framework",
+  "Build-Debian9-Clang-universal-devrel-Android_SKQP",
   "Build-Debian9-Clang-x64-Debug-Android",
   "Build-Debian9-Clang-x64-Release-Android",
   "Build-Debian9-Clang-x86-Debug",
diff --git a/infra/bots/recipe_modules/flavor/default_flavor.py b/infra/bots/recipe_modules/flavor/default_flavor.py
index 0d565e6..c7343c7 100644
--- a/infra/bots/recipe_modules/flavor/default_flavor.py
+++ b/infra/bots/recipe_modules/flavor/default_flavor.py
@@ -157,3 +157,62 @@
   def cleanup_steps(self):
     """Run any device-specific cleanup steps."""
     pass
+
+  def get_branch(self):
+    """Returns the branch of the current tryjob run by querying Gerrit."""
+    # get the issue and patchset ids.
+    issue = str(self.m.properties.get('patch_issue', ''))
+    patchset = str(self.m.properties.get('patch_set', ''))
+
+    # if none are provided we assume master.
+    if not issue or not patchset:
+      return "master"
+
+    # inline python script called below.
+    script="""
+import contextlib
+import json
+import math
+import socket
+import sys
+import time
+import urllib2
+
+GERRIT_URL_TMPL = 'https://skia-review.googlesource.com/changes/%s/detail'
+RETRIES = 3
+TIMEOUT = 10
+WAIT_BASE = 10
+
+socket.setdefaulttimeout(TIMEOUT)
+issue_id = int(sys.argv[1])
+for retry in range(RETRIES):
+  try:
+    url = GERRIT_URL_TMPL % issue_id
+    with contextlib.closing(urllib2.urlopen(url, timeout=TIMEOUT)) as w:
+      # strip out the XSS prefix.
+      body = w.read().lstrip().lstrip(")]}'")
+      print json.loads(body)['branch']
+      break
+  except Exception as e:
+    print 'Failed to get branch for issue %s:' % issue_id
+    print e
+    if retry == RETRIES:
+      raise
+    waittime = WAIT_BASE * math.pow(2, retry)
+    print 'Retry in %d seconds.' % waittime
+    time.sleep(waittime)
+"""
+
+    # Make a call to Gerrit to retrieve the branch.
+    branch = self.m.run(
+      self.m.python.inline,
+      'get branch for issue',
+      program=script,
+      args=[issue],
+      abort_on_failure=True,
+      fail_build_on_failure=True,
+      infra_step=True,
+      stdout=self.m.raw_io.output()).stdout
+    # Use the last line of the output since it will contain the branch name.
+    branch = [x.strip() for x in branch.splitlines()][-1]
+    return branch or 'master'
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/Build-Debian9-Clang-universal-devrel-Android_SKQP_dev_branch.json b/infra/bots/recipe_modules/flavor/examples/full.expected/Build-Debian9-Clang-universal-devrel-Android_SKQP_dev_branch.json
new file mode 100644
index 0000000..833f038
--- /dev/null
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/Build-Debian9-Clang-universal-devrel-Android_SKQP_dev_branch.json
@@ -0,0 +1,90 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[CUSTOM_/_B_WORK]/skia/bin/fetch-gn"
+    ],
+    "cwd": "[CUSTOM_/_B_WORK]/skia",
+    "env": {
+      "BUILDTYPE": "devrel",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]",
+      "SKIA_OUT": "[CUSTOM_/_B_WORK]/skia/out/Build-Debian9-Clang-universal-devrel-Android_SKQP_dev_branch"
+    },
+    "infra_step": true,
+    "name": "fetch-gn"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport contextlib\nimport json\nimport math\nimport socket\nimport sys\nimport time\nimport urllib2\n\nGERRIT_URL_TMPL = 'https://skia-review.googlesource.com/changes/%s/detail'\nRETRIES = 3\nTIMEOUT = 10\nWAIT_BASE = 10\n\nsocket.setdefaulttimeout(TIMEOUT)\nissue_id = int(sys.argv[1])\nfor retry in range(RETRIES):\n  try:\n    url = GERRIT_URL_TMPL % issue_id\n    with contextlib.closing(urllib2.urlopen(url, timeout=TIMEOUT)) as w:\n      # strip out the XSS prefix.\n      body = w.read().lstrip().lstrip(\")]}'\")\n      print json.loads(body)['branch']\n      break\n  except Exception as e:\n    print 'Failed to get branch for issue %s:' % issue_id\n    print e\n    if retry == RETRIES:\n      raise\n    waittime = WAIT_BASE * math.pow(2, retry)\n    print 'Retry in %d seconds.' % waittime\n    time.sleep(waittime)\n",
+      "12345"
+    ],
+    "env": {
+      "BUILDTYPE": "devrel",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]",
+      "SKIA_OUT": "[CUSTOM_/_B_WORK]/skia/out/Build-Debian9-Clang-universal-devrel-Android_SKQP_dev_branch"
+    },
+    "infra_step": true,
+    "name": "get branch for issue",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import contextlib@@@",
+      "@@@STEP_LOG_LINE@python.inline@import json@@@",
+      "@@@STEP_LOG_LINE@python.inline@import math@@@",
+      "@@@STEP_LOG_LINE@python.inline@import socket@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@import time@@@",
+      "@@@STEP_LOG_LINE@python.inline@import urllib2@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@GERRIT_URL_TMPL = 'https://skia-review.googlesource.com/changes/%s/detail'@@@",
+      "@@@STEP_LOG_LINE@python.inline@RETRIES = 3@@@",
+      "@@@STEP_LOG_LINE@python.inline@TIMEOUT = 10@@@",
+      "@@@STEP_LOG_LINE@python.inline@WAIT_BASE = 10@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@socket.setdefaulttimeout(TIMEOUT)@@@",
+      "@@@STEP_LOG_LINE@python.inline@issue_id = int(sys.argv[1])@@@",
+      "@@@STEP_LOG_LINE@python.inline@for retry in range(RETRIES):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  try:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    url = GERRIT_URL_TMPL % issue_id@@@",
+      "@@@STEP_LOG_LINE@python.inline@    with contextlib.closing(urllib2.urlopen(url, timeout=TIMEOUT)) as w:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      # strip out the XSS prefix.@@@",
+      "@@@STEP_LOG_LINE@python.inline@      body = w.read().lstrip().lstrip(\")]}'\")@@@",
+      "@@@STEP_LOG_LINE@python.inline@      print json.loads(body)['branch']@@@",
+      "@@@STEP_LOG_LINE@python.inline@      break@@@",
+      "@@@STEP_LOG_LINE@python.inline@  except Exception as e:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print 'Failed to get branch for issue %s:' % issue_id@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print e@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if retry == RETRIES:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      raise@@@",
+      "@@@STEP_LOG_LINE@python.inline@    waittime = WAIT_BASE * math.pow(2, retry)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print 'Retry in %d seconds.' % waittime@@@",
+      "@@@STEP_LOG_LINE@python.inline@    time.sleep(waittime)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CUSTOM_/_B_WORK]/skia/tools/skqp/make_universal_apk"
+    ],
+    "cwd": "[CUSTOM_/_B_WORK]/skia",
+    "env": {
+      "ANDROID_HOME": "[START_DIR]/android_sdk_linux/android-sdk",
+      "ANDROID_NDK": "[START_DIR]/android_ndk_linux",
+      "BUILDTYPE": "devrel",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]",
+      "SKIA_OUT": "[CUSTOM_/_B_WORK]/skia/out/Build-Debian9-Clang-universal-devrel-Android_SKQP_dev_branch"
+    },
+    "name": "make_universal"
+  },
+  {
+    "name": "$result",
+    "recipe_result": null,
+    "status_code": 0
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/Build-Debian9-Clang-universal-devrel-Android_SKQP_master_branch.json b/infra/bots/recipe_modules/flavor/examples/full.expected/Build-Debian9-Clang-universal-devrel-Android_SKQP_master_branch.json
new file mode 100644
index 0000000..af3e20a
--- /dev/null
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/Build-Debian9-Clang-universal-devrel-Android_SKQP_master_branch.json
@@ -0,0 +1,75 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[CUSTOM_/_B_WORK]/skia/bin/fetch-gn"
+    ],
+    "cwd": "[CUSTOM_/_B_WORK]/skia",
+    "env": {
+      "BUILDTYPE": "devrel",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]",
+      "SKIA_OUT": "[CUSTOM_/_B_WORK]/skia/out/Build-Debian9-Clang-universal-devrel-Android_SKQP_master_branch"
+    },
+    "infra_step": true,
+    "name": "fetch-gn"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport contextlib\nimport json\nimport math\nimport socket\nimport sys\nimport time\nimport urllib2\n\nGERRIT_URL_TMPL = 'https://skia-review.googlesource.com/changes/%s/detail'\nRETRIES = 3\nTIMEOUT = 10\nWAIT_BASE = 10\n\nsocket.setdefaulttimeout(TIMEOUT)\nissue_id = int(sys.argv[1])\nfor retry in range(RETRIES):\n  try:\n    url = GERRIT_URL_TMPL % issue_id\n    with contextlib.closing(urllib2.urlopen(url, timeout=TIMEOUT)) as w:\n      # strip out the XSS prefix.\n      body = w.read().lstrip().lstrip(\")]}'\")\n      print json.loads(body)['branch']\n      break\n  except Exception as e:\n    print 'Failed to get branch for issue %s:' % issue_id\n    print e\n    if retry == RETRIES:\n      raise\n    waittime = WAIT_BASE * math.pow(2, retry)\n    print 'Retry in %d seconds.' % waittime\n    time.sleep(waittime)\n",
+      "12345"
+    ],
+    "env": {
+      "BUILDTYPE": "devrel",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]",
+      "SKIA_OUT": "[CUSTOM_/_B_WORK]/skia/out/Build-Debian9-Clang-universal-devrel-Android_SKQP_master_branch"
+    },
+    "infra_step": true,
+    "name": "get branch for issue",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import contextlib@@@",
+      "@@@STEP_LOG_LINE@python.inline@import json@@@",
+      "@@@STEP_LOG_LINE@python.inline@import math@@@",
+      "@@@STEP_LOG_LINE@python.inline@import socket@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@import time@@@",
+      "@@@STEP_LOG_LINE@python.inline@import urllib2@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@GERRIT_URL_TMPL = 'https://skia-review.googlesource.com/changes/%s/detail'@@@",
+      "@@@STEP_LOG_LINE@python.inline@RETRIES = 3@@@",
+      "@@@STEP_LOG_LINE@python.inline@TIMEOUT = 10@@@",
+      "@@@STEP_LOG_LINE@python.inline@WAIT_BASE = 10@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@socket.setdefaulttimeout(TIMEOUT)@@@",
+      "@@@STEP_LOG_LINE@python.inline@issue_id = int(sys.argv[1])@@@",
+      "@@@STEP_LOG_LINE@python.inline@for retry in range(RETRIES):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  try:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    url = GERRIT_URL_TMPL % issue_id@@@",
+      "@@@STEP_LOG_LINE@python.inline@    with contextlib.closing(urllib2.urlopen(url, timeout=TIMEOUT)) as w:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      # strip out the XSS prefix.@@@",
+      "@@@STEP_LOG_LINE@python.inline@      body = w.read().lstrip().lstrip(\")]}'\")@@@",
+      "@@@STEP_LOG_LINE@python.inline@      print json.loads(body)['branch']@@@",
+      "@@@STEP_LOG_LINE@python.inline@      break@@@",
+      "@@@STEP_LOG_LINE@python.inline@  except Exception as e:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print 'Failed to get branch for issue %s:' % issue_id@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print e@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if retry == RETRIES:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      raise@@@",
+      "@@@STEP_LOG_LINE@python.inline@    waittime = WAIT_BASE * math.pow(2, retry)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print 'Retry in %d seconds.' % waittime@@@",
+      "@@@STEP_LOG_LINE@python.inline@    time.sleep(waittime)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "name": "$result",
+    "recipe_result": null,
+    "status_code": 0
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/Build-Debian9-Clang-universal-devrel-Android_SKQP_no_issue.json b/infra/bots/recipe_modules/flavor/examples/full.expected/Build-Debian9-Clang-universal-devrel-Android_SKQP_no_issue.json
new file mode 100644
index 0000000..d7b1578
--- /dev/null
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/Build-Debian9-Clang-universal-devrel-Android_SKQP_no_issue.json
@@ -0,0 +1,23 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[CUSTOM_/_B_WORK]/skia/bin/fetch-gn"
+    ],
+    "cwd": "[CUSTOM_/_B_WORK]/skia",
+    "env": {
+      "BUILDTYPE": "devrel",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]",
+      "SKIA_OUT": "[CUSTOM_/_B_WORK]/skia/out/Build-Debian9-Clang-universal-devrel-Android_SKQP_no_issue"
+    },
+    "infra_step": true,
+    "name": "fetch-gn"
+  },
+  {
+    "name": "$result",
+    "recipe_result": null,
+    "status_code": 0
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/Build-Debian9-Clang-universal-devrel-Android_SKQP_release_branch.json b/infra/bots/recipe_modules/flavor/examples/full.expected/Build-Debian9-Clang-universal-devrel-Android_SKQP_release_branch.json
new file mode 100644
index 0000000..b5375f1
--- /dev/null
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/Build-Debian9-Clang-universal-devrel-Android_SKQP_release_branch.json
@@ -0,0 +1,90 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[CUSTOM_/_B_WORK]/skia/bin/fetch-gn"
+    ],
+    "cwd": "[CUSTOM_/_B_WORK]/skia",
+    "env": {
+      "BUILDTYPE": "devrel",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]",
+      "SKIA_OUT": "[CUSTOM_/_B_WORK]/skia/out/Build-Debian9-Clang-universal-devrel-Android_SKQP_release_branch"
+    },
+    "infra_step": true,
+    "name": "fetch-gn"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport contextlib\nimport json\nimport math\nimport socket\nimport sys\nimport time\nimport urllib2\n\nGERRIT_URL_TMPL = 'https://skia-review.googlesource.com/changes/%s/detail'\nRETRIES = 3\nTIMEOUT = 10\nWAIT_BASE = 10\n\nsocket.setdefaulttimeout(TIMEOUT)\nissue_id = int(sys.argv[1])\nfor retry in range(RETRIES):\n  try:\n    url = GERRIT_URL_TMPL % issue_id\n    with contextlib.closing(urllib2.urlopen(url, timeout=TIMEOUT)) as w:\n      # strip out the XSS prefix.\n      body = w.read().lstrip().lstrip(\")]}'\")\n      print json.loads(body)['branch']\n      break\n  except Exception as e:\n    print 'Failed to get branch for issue %s:' % issue_id\n    print e\n    if retry == RETRIES:\n      raise\n    waittime = WAIT_BASE * math.pow(2, retry)\n    print 'Retry in %d seconds.' % waittime\n    time.sleep(waittime)\n",
+      "12345"
+    ],
+    "env": {
+      "BUILDTYPE": "devrel",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]",
+      "SKIA_OUT": "[CUSTOM_/_B_WORK]/skia/out/Build-Debian9-Clang-universal-devrel-Android_SKQP_release_branch"
+    },
+    "infra_step": true,
+    "name": "get branch for issue",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import contextlib@@@",
+      "@@@STEP_LOG_LINE@python.inline@import json@@@",
+      "@@@STEP_LOG_LINE@python.inline@import math@@@",
+      "@@@STEP_LOG_LINE@python.inline@import socket@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@import time@@@",
+      "@@@STEP_LOG_LINE@python.inline@import urllib2@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@GERRIT_URL_TMPL = 'https://skia-review.googlesource.com/changes/%s/detail'@@@",
+      "@@@STEP_LOG_LINE@python.inline@RETRIES = 3@@@",
+      "@@@STEP_LOG_LINE@python.inline@TIMEOUT = 10@@@",
+      "@@@STEP_LOG_LINE@python.inline@WAIT_BASE = 10@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@socket.setdefaulttimeout(TIMEOUT)@@@",
+      "@@@STEP_LOG_LINE@python.inline@issue_id = int(sys.argv[1])@@@",
+      "@@@STEP_LOG_LINE@python.inline@for retry in range(RETRIES):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  try:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    url = GERRIT_URL_TMPL % issue_id@@@",
+      "@@@STEP_LOG_LINE@python.inline@    with contextlib.closing(urllib2.urlopen(url, timeout=TIMEOUT)) as w:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      # strip out the XSS prefix.@@@",
+      "@@@STEP_LOG_LINE@python.inline@      body = w.read().lstrip().lstrip(\")]}'\")@@@",
+      "@@@STEP_LOG_LINE@python.inline@      print json.loads(body)['branch']@@@",
+      "@@@STEP_LOG_LINE@python.inline@      break@@@",
+      "@@@STEP_LOG_LINE@python.inline@  except Exception as e:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print 'Failed to get branch for issue %s:' % issue_id@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print e@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if retry == RETRIES:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      raise@@@",
+      "@@@STEP_LOG_LINE@python.inline@    waittime = WAIT_BASE * math.pow(2, retry)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print 'Retry in %d seconds.' % waittime@@@",
+      "@@@STEP_LOG_LINE@python.inline@    time.sleep(waittime)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CUSTOM_/_B_WORK]/skia/tools/skqp/make_universal_apk"
+    ],
+    "cwd": "[CUSTOM_/_B_WORK]/skia",
+    "env": {
+      "ANDROID_HOME": "[START_DIR]/android_sdk_linux/android-sdk",
+      "ANDROID_NDK": "[START_DIR]/android_ndk_linux",
+      "BUILDTYPE": "devrel",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]",
+      "SKIA_OUT": "[CUSTOM_/_B_WORK]/skia/out/Build-Debian9-Clang-universal-devrel-Android_SKQP_release_branch"
+    },
+    "name": "make_universal"
+  },
+  {
+    "name": "$result",
+    "recipe_result": null,
+    "status_code": 0
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipe_modules/flavor/examples/full.py b/infra/bots/recipe_modules/flavor/examples/full.py
index e42ed13..e9c4832 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.py
+++ b/infra/bots/recipe_modules/flavor/examples/full.py
@@ -114,16 +114,21 @@
   'Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-SafeStack',
 ]
 
+# Default properties used for TEST_BUILDERS and skqp_builders.
+defaultProps = lambda buildername: dict(
+  buildername=buildername,
+  repository='https://skia.googlesource.com/skia.git',
+  revision='abc123',
+  path_config='kitchen',
+  patch_set=2,
+  swarm_out_dir='[SWARM_OUT_DIR]'
+)
 
 def GenTests(api):
   for buildername in TEST_BUILDERS:
     test = (
       api.test(buildername) +
-      api.properties(buildername=buildername,
-                     repository='https://skia.googlesource.com/skia.git',
-                     revision='abc123',
-                     path_config='kitchen',
-                     swarm_out_dir='[SWARM_OUT_DIR]')
+      api.properties(**defaultProps(buildername))
     )
     if 'Chromebook' in buildername and not 'Build' in buildername:
       test += api.step_data(
@@ -135,6 +140,33 @@
           stdout=api.raw_io.output('192.168.1.2:5555'))
     yield test
 
+  # Test SKQP builder with different branches and issue info.
+  skqp_builders = [
+    'Build-Debian9-Clang-universal-devrel-Android_SKQP_master_branch',
+    'Build-Debian9-Clang-universal-devrel-Android_SKQP_no_issue',
+    'Build-Debian9-Clang-universal-devrel-Android_SKQP_dev_branch',
+    'Build-Debian9-Clang-universal-devrel-Android_SKQP_release_branch',
+  ]
+  for builder in skqp_builders:
+    # should we add issue information to this tests.
+    with_issue = 'no_issue' not in builder
+    props = defaultProps(builder)
+    if with_issue:
+      props.update(patch_issue=12345, patch_set=2)
+
+    test = (
+      api.test(builder) +
+      api.properties(**props)
+    )
+
+    if ('SKQP' in builder) and with_issue:
+      branch = 'skqp/dev' if 'dev_branch' in builder else (
+               'skqp/release' if 'release_branch' in builder else
+               'master')
+      test += api.step_data('get branch for issue',
+                            stdout=api.raw_io.output(branch))
+    yield test
+
   builder = 'Test-Debian9-GCC-GCE-CPU-AVX2-x86_64-Release-All'
   yield (
       api.test('exceptions') +
diff --git a/infra/bots/recipe_modules/flavor/gn_android_flavor.py b/infra/bots/recipe_modules/flavor/gn_android_flavor.py
index c5c3bbd..d00f771 100644
--- a/infra/bots/recipe_modules/flavor/gn_android_flavor.py
+++ b/infra/bots/recipe_modules/flavor/gn_android_flavor.py
@@ -389,14 +389,35 @@
       args['extra_cflags'] = repr(extra_cflags).replace("'", '"')
 
     gn_args = ' '.join('%s=%s' % (k,v) for (k,v) in sorted(args.iteritems()))
-
-    gn    = 'gn.exe'    if 'Win' in os else 'gn'
-    ninja = 'ninja.exe' if 'Win' in os else 'ninja'
-    gn = self.m.vars.skia_dir.join('bin', gn)
+    gn      = 'gn.exe'    if 'Win' in os else 'gn'
+    ninja   = 'ninja.exe' if 'Win' in os else 'ninja'
+    gn      = self.m.vars.skia_dir.join('bin', gn)
 
     self._py('fetch-gn', self.m.vars.skia_dir.join('bin', 'fetch-gn'))
-    self._run('gn gen', gn, 'gen', self.out_dir, '--args=' + gn_args)
-    self._run('ninja', ninja, '-k', '0', '-C', self.out_dir)
+
+    # If this is the SkQP built, set up the environment and run the script
+    # build the universal APK.
+    if 'SKQP' in extra_tokens:
+      # If this is not an SkQP branch make this a no-op or the build will fail.
+      if 'skqp' not in self.get_branch():
+        return
+
+      ndk_asset = 'android_ndk_linux'
+      sdk_asset = 'android_sdk_linux'
+      android_ndk = self.m.vars.slave_dir.join(ndk_asset)
+      android_home = self.m.vars.slave_dir.join(sdk_asset, 'android-sdk')
+      env = {
+        'ANDROID_NDK': android_ndk,
+        'ANDROID_HOME': android_home,
+      }
+
+      mk_universal = self.m.vars.skia_dir.join('tools', 'skqp',
+                                               'make_universal_apk')
+      with self.m.context(env=env):
+        self._run('make_universal', mk_universal)
+    else:
+      self._run('gn gen', gn, 'gen', self.out_dir, '--args=' + gn_args)
+      self._run('ninja', ninja, '-k', '0', '-C', self.out_dir)
 
   def install(self):
     self._adb('mkdir ' + self.device_dirs.resource_dir,
diff --git a/infra/bots/tasks.json b/infra/bots/tasks.json
index bce9c6c..743a877 100644
--- a/infra/bots/tasks.json
+++ b/infra/bots/tasks.json
@@ -96,6 +96,12 @@
         "Build-Debian9-Clang-gce_x86_phone-eng-Android_Framework"
       ]
     },
+    "Build-Debian9-Clang-universal-devrel-Android_SKQP": {
+      "priority": 0.8,
+      "tasks": [
+        "Build-Debian9-Clang-universal-devrel-Android_SKQP"
+      ]
+    },
     "Build-Debian9-Clang-x64-Debug-Android": {
       "priority": 0.8,
       "tasks": [
@@ -3635,6 +3641,33 @@
       "isolate": "compile_skia.isolate",
       "priority": 0.8
     },
+    "Build-Debian9-Clang-universal-devrel-Android_SKQP": {
+      "dependencies": [
+        "Housekeeper-PerCommit-IsolateAndroidNDKLinux",
+        "Housekeeper-PerCommit-IsolateAndroidSDKLinux"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "os:Debian-9.2",
+        "pool:Skia"
+      ],
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "compile",
+        "repository=<(REPO)",
+        "buildername=Build-Debian9-Clang-universal-devrel-Android_SKQP",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_repo=<(PATCH_REPO)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "isolate": "compile_skia.isolate",
+      "priority": 0.8
+    },
     "Build-Debian9-Clang-x64-Debug-Android": {
       "dependencies": [
         "Housekeeper-PerCommit-IsolateAndroidNDKLinux"
@@ -6254,6 +6287,23 @@
       "isolate": "isolate_ndk_linux.isolate",
       "priority": 0.7
     },
+    "Housekeeper-PerCommit-IsolateAndroidSDKLinux": {
+      "cipd_packages": [
+        {
+          "name": "skia/bots/android_sdk_linux",
+          "path": "android_sdk_linux",
+          "version": "version:0"
+        }
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "os:Debian-9.2",
+        "pool:Skia"
+      ],
+      "isolate": "isolate_android_sdk_linux.isolate",
+      "priority": 0.7
+    },
     "Housekeeper-PerCommit-IsolateSKP": {
       "cipd_packages": [
         {