Merge "Install unzip explicitly for completeness"
diff --git a/playbooks/gerrit-plugin/build.yaml b/playbooks/gerrit-plugin/build.yaml
new file mode 100644
index 0000000..b29276a
--- /dev/null
+++ b/playbooks/gerrit-plugin/build.yaml
@@ -0,0 +1,5 @@
+- name: Build a gerrit plugin
+  hosts: all
+  roles:
+    - copy-plugin-deps
+    - bazelisk-build
diff --git a/playbooks/gerrit-base/pre.yaml b/playbooks/gerrit-setup/pre.yaml
similarity index 87%
rename from playbooks/gerrit-base/pre.yaml
rename to playbooks/gerrit-setup/pre.yaml
index 71553d0..2e83666 100644
--- a/playbooks/gerrit-base/pre.yaml
+++ b/playbooks/gerrit-setup/pre.yaml
@@ -3,7 +3,7 @@
   roles:
     - ensure-bazelisk
     - ensure-java
-    - role: install-nodejs
+    - role: ensure-nodejs
       node_version: 10
     - install-build-essential
     - prepare-gerrit-repos
diff --git a/playbooks/gerrit/build.yaml b/playbooks/gerrit/build.yaml
new file mode 100644
index 0000000..514def1
--- /dev/null
+++ b/playbooks/gerrit/build.yaml
@@ -0,0 +1,5 @@
+- name: Build Gerrit
+  hosts: all
+  roles:
+    - role: bazelisk-build
+      bazelisk_targets: "plugins:core release api"
diff --git a/playbooks/test-gerrit-base.yaml b/playbooks/test-gerrit-base.yaml
deleted file mode 100644
index eaa4fcf..0000000
--- a/playbooks/test-gerrit-base.yaml
+++ /dev/null
@@ -1,20 +0,0 @@
-- hosts: all
-  tasks:
-    - name: List submodule git dir
-      command: "ls src/gerrit.googlesource.com/gerrit/{{ item }}/.git"
-      args:
-        chdir: "{{ ansible_user_dir }}"
-      loop:
-        - modules/jgit
-        - plugins/codemirror-editor
-        - plugins/commit-message-length-validator
-        - plugins/delete-project
-        - plugins/download-commands
-        - plugins/gitiles
-        - plugins/hooks
-        - plugins/plugin-manager
-        - plugins/replication
-        - plugins/reviewnotes
-        - plugins/singleusergroup
-        - plugins/webhooks
-        - polymer-bridges
diff --git a/playbooks/test-gerrit-setup.yaml b/playbooks/test-gerrit-setup.yaml
new file mode 100644
index 0000000..01022e6
--- /dev/null
+++ b/playbooks/test-gerrit-setup.yaml
@@ -0,0 +1,8 @@
+- hosts: all
+  tasks:
+    - name: List submodule git dir
+      command: "ls src/gerrit.googlesource.com/gerrit/{{ item }}/.git"
+      args:
+        chdir: "{{ ansible_user_dir }}"
+      loop:
+        - modules/jgit
diff --git a/roles/bazelisk-build/README.rst b/roles/bazelisk-build/README.rst
new file mode 100644
index 0000000..95f8910
--- /dev/null
+++ b/roles/bazelisk-build/README.rst
@@ -0,0 +1,37 @@
+Run bazelisk build
+
+Runs bazelisk build with the specified targets.
+
+**Role Variables**
+
+.. zuul:rolevar:: bazelisk_targets
+   :default: ""
+
+   The bazelisk targets to build.
+
+.. zuul:rolevar:: bazelisk_test_targets
+   :default: ""
+
+   The bazelisk targets to test.  ``bazelisk test`` will only be run
+   if this value is not the empty string.
+
+.. zuul:rolevar:: bazelisk_executable
+   :default: bazelisk
+
+   The path to the bazelisk executable.  See
+   :zuul:role:`ensure-bazelisk`.
+
+.. zuul:rolevar:: bazelisk_artifacts
+   :default: []
+
+   Paths (relative to zuul_work_dir) of artifacts to collect.
+
+.. zuul:rolevar:: zuul_output_dir
+   :default: {{ ansible_user_dir }}/zuul-output
+
+   Base directory for collecting job output.
+
+.. zuul:rolevar:: zuul_work_dir
+   :default: {{ ansible_user_dir }}/{{ zuul.project.src_dir}}
+
+   The working directory in which to run bazelisk.
diff --git a/roles/bazelisk-build/defaults/main.yaml b/roles/bazelisk-build/defaults/main.yaml
new file mode 100644
index 0000000..6a5e01d
--- /dev/null
+++ b/roles/bazelisk-build/defaults/main.yaml
@@ -0,0 +1,6 @@
+bazelisk_targets: ""
+bazelisk_test_targets: ""
+bazelisk_executable: bazelisk
+bazelisk_artifacts: []
+zuul_output_dir: "{{ ansible_user_dir }}/zuul-output"
+zuul_work_dir: "{{ ansible_user_dir }}/{{ zuul.project.src_dir}}"
diff --git a/roles/bazelisk-build/tasks/main.yaml b/roles/bazelisk-build/tasks/main.yaml
new file mode 100644
index 0000000..71f0e31
--- /dev/null
+++ b/roles/bazelisk-build/tasks/main.yaml
@@ -0,0 +1,32 @@
+- name: Run bazelisk build
+  shell: |
+    java -fullversion
+    {{ bazelisk_executable }} version
+    {{ bazelisk_executable }} build --spawn_strategy=standalone --genrule_strategy=standalone {{ bazelisk_targets }}
+  args:
+    executable: /bin/bash
+    chdir: "{{ zuul_work_dir }}"
+- name: Run bazelisk test
+  when: "bazelisk_test_targets != ''"
+  shell: |
+    {{ bazelisk_executable }} test {{ bazelisk_test_targets }}
+  args:
+    executable: /bin/bash
+    chdir: "{{ zuul_work_dir }}"
+  register: test_result
+  failed_when: test_result.rc not in [0, 4]
+- name: Copy output to artifacts location
+  loop: "{{ bazelisk_artifacts }}"
+  command: "cp {{ item }} {{ zuul_output_dir }}/artifacts"
+  args:
+    chdir: "{{ zuul_work_dir }}"
+- name: Return built artifacts to Zuul
+  loop: "{{ bazelisk_artifacts }}"
+  zuul_return:
+    data:
+      zuul:
+        artifacts:
+          - name: "Build"
+            url: "artifacts/{{ item | basename }}"
+            metadata:
+              type: java_jar
diff --git a/roles/copy-plugin-deps/README.rst b/roles/copy-plugin-deps/README.rst
new file mode 100644
index 0000000..9a86596
--- /dev/null
+++ b/roles/copy-plugin-deps/README.rst
@@ -0,0 +1,11 @@
+Copy plugin bazel deps
+
+If the plugin being built has an ``external_plugin_deps.bzl`` file,
+copy it into the Gerrit plugins directory.
+
+**Role Variables**
+
+.. zuul:rolevar:: gerrit_plugin
+   :default: zuul.project.short_name
+
+   The name of the plugin to be built.
diff --git a/roles/copy-plugin-deps/defaults/main.yaml b/roles/copy-plugin-deps/defaults/main.yaml
new file mode 100644
index 0000000..80810bc
--- /dev/null
+++ b/roles/copy-plugin-deps/defaults/main.yaml
@@ -0,0 +1,3 @@
+gerrit_project_name: gerrit.googlesource.com/gerrit
+gerrit_plugin: "{{ zuul.project.short_name }}"
+gerrit_root: "{{ ansible_user_dir }}/{{ zuul.projects[gerrit_project_name].src_dir }}"
diff --git a/roles/copy-plugin-deps/tasks/main.yaml b/roles/copy-plugin-deps/tasks/main.yaml
new file mode 100644
index 0000000..d2fdbdb
--- /dev/null
+++ b/roles/copy-plugin-deps/tasks/main.yaml
@@ -0,0 +1,8 @@
+- name: Check if external plugin deps exists
+  stat:
+    path: "{{ ansible_user_dir }}/src/gerrit.googlesource.com/gerrit/plugins/{{ gerrit_plugin }}/external_plugin_deps.bzl"
+  register: deps_stat
+
+- name: Copy external plugin deps
+  command: "cp -f {{ ansible_user_dir }}/src/gerrit.googlesource.com/gerrit/plugins/{{ gerrit_plugin }}/external_plugin_deps.bzl {{ gerrit_root }}/plugins"
+  when: "deps_stat.stat.exists is true"
diff --git a/roles/prepare-gerrit-repos/README.rst b/roles/prepare-gerrit-repos/README.rst
index 81ec7bb..de7ca4b 100644
--- a/roles/prepare-gerrit-repos/README.rst
+++ b/roles/prepare-gerrit-repos/README.rst
@@ -65,3 +65,10 @@
       gerrit_project_mapping:
         gerrit: ''
         jgit: modules/jgit
+
+.. zuul:rolevar:: gerrit_project_name
+   :default: gerrit.googlesource.com/gerrit
+
+   The canonical name of the Gerrit repository.  This role uses this
+   value to look up the branch of Gerrit which is checked out in order
+   to detect whether or not sub-projects contain the same branch.
diff --git a/roles/prepare-gerrit-repos/defaults/main.yaml b/roles/prepare-gerrit-repos/defaults/main.yaml
index ce0cbe7..0ba7b8a 100644
--- a/roles/prepare-gerrit-repos/defaults/main.yaml
+++ b/roles/prepare-gerrit-repos/defaults/main.yaml
@@ -1,4 +1,6 @@
+gerrit_project_name: gerrit.googlesource.com/gerrit
 gerrit_project_mapping:
   gerrit: ''
   jgit: modules/jgit
   'zuul/jobs': ''
+gerrit_root: "{{ ansible_user_dir }}/{{ zuul.projects[gerrit_project_name].src_dir }}"
diff --git a/roles/prepare-gerrit-repos/tasks/repo.yaml b/roles/prepare-gerrit-repos/tasks/repo.yaml
index f4da53f..a4fbdb2 100644
--- a/roles/prepare-gerrit-repos/tasks/repo.yaml
+++ b/roles/prepare-gerrit-repos/tasks/repo.yaml
@@ -4,41 +4,61 @@
 
 - name: Check if zuul.branch exists in repo
   set_fact:
-    # If zuul checked out the branch we're testing, then it exists.
-    project_branch_exists: "{{ zuul.branch == project.checkout }}"
+    # If zuul checked out the branch we're testing (ie, gerrit's
+    # branch), then it exists.
+    project_branch_exists: "{{ zuul.projects[gerrit_project_name].checkout == project.checkout }}"
 
 - name: Check if repo has a dependent change
   set_fact:
-    repo_has_dependent_change: "{{ zuul['items'] | selectattr('project.canonical_name', 'eq', zuul.project.canonical_name) | list | length | bool }}"
+    repo_has_dependent_change: "{{ zuul['items'] | selectattr('project.canonical_name', 'eq', project.canonical_name) | list | length | bool }}"
+
+- name: Check if repo is in submodules
+  ignore_errors: true
+  set_fact:
+    project_in_gitmodules: "{{ lookup ('ini', 'path section=submodule \"' + project_dest + '\" file=' + zuul.executor.work_root + '/' + zuul.projects[gerrit_project_name].src_dir + '/.gitmodules', errors='ignore') }}"
+- name: Coerce submodule check to boolean
+  set_fact:
+    project_in_gitmodules: "{{ project_in_gitmodules | default('') | length > 0 | bool }}"
+
+- name: Check if repo is a tracking branch
+  ignore_errors: true
+  set_fact:
+    tracking_branch: "{{ lookup ('ini', 'branch section=submodule \"' + project_dest + '\" file=' + zuul.executor.work_root + '/' + zuul.projects[gerrit_project_name].src_dir + '/.gitmodules', errors='ignore') }}"
+- name: Coerce tracking branch to boolean
+  set_fact:
+    tracking_branch: "{{ ((tracking_branch | default('')) == '.') | bool }}"
 
 - name: Check for unsatisfiable source repo condition
   when:
     - "project.canonical_name != zuul.project.canonical_name"
-    - "not project_branch_exists"
+    - "not project_branch_exists or not tracking_branch"
     - "repo_has_dependent_change"
   fail:
     msg: >-
       The repository {{ project.name }} does not contain the branch
-      under test ({{ zuul.branch }}), but this change depends on a
-      change to that project and branch.  While Zuul is able to check
-      out the repos in the requested state, the branch mismatch means
-      that Gerrit's submodule subscription would not automatically
-      update the submodule pointer, and the merged state would not
-      reflect the tested state.
+      under test ({{ zuul.projects[gerrit_project_name].checkout }}),
+      but this change depends on a change to that project and branch.
+      While Zuul is able to check out the repos in the requested
+      state, the branch mismatch means that Gerrit's submodule
+      subscription would not automatically update the submodule
+      pointer, and the merged state would not reflect the tested
+      state.
 
       This configuration would be testable by creating a {{
-      zuul.branch }} branch in the {{ project.name }} repo.
-      Alternatively, you can merge the dependent change, manually
-      update the submodule pointer, then test this change again.
+      zuul.projects[gerrit_project_name].checkout }} branch in the {{
+      project.name }} repo.  Alternatively, you can merge the
+      dependent change, manually update the submodule pointer, then
+      test this change again.
 
 # If there is no matching branch we need to check out the actual sha
 # defined in the parent repo.
 - name: Update submodule
-  when: "not project_branch_exists"
-  command: "git submodule update --init {{ project.name }}"
+  when: "project_in_gitmodules and (not project_branch_exists or not tracking_branch)"
+  command: "git submodule update --init {{ project_dest }}"
   args:
     chdir: "{{ gerrit_root }}"
 
+# Else:
 - name: Move repo into place
-  when: "project_branch_exists"
+  when: "not (project_in_gitmodules and (not project_branch_exists or not tracking_branch))"
   command: "mv -T -f {{ ansible_user_dir }}/{{ project.src_dir }} {{ gerrit_root }}/{{ project_dest }}"
diff --git a/zuul.d/jobs.yaml b/zuul.d/jobs.yaml
index 8107e4f..fdb3343 100644
--- a/zuul.d/jobs.yaml
+++ b/zuul.d/jobs.yaml
@@ -1,22 +1,74 @@
 - job:
-    name: gerrit-base
+    name: gerrit-setup
     description: |
-      Base job for building gerrit
+      Base job for building Gerrit
 
-      This job sets up all the repos which are required for a gerrit build.
-    pre-run: playbooks/gerrit-base/pre.yaml
+      This job sets up any supplied repos which are required for any
+      kind of gerrit-related build (plugins or Gerrit itself).
+
+      However, it does not include all of the required repos; for
+      that, see :zuul:job:`gerrit-base` which inherits from this and
+      adds branch-appropriate required-projects.
+    pre-run: playbooks/gerrit-setup/pre.yaml
     required-projects:
       - gerrit
-      - jgit
-      - plugins/codemirror-editor
-      - plugins/commit-message-length-validator
-      - plugins/delete-project
-      - plugins/download-commands
-      - plugins/gitiles
-      - plugins/hooks
-      - plugins/plugin-manager
-      - plugins/replication
-      - plugins/reviewnotes
-      - plugins/singleusergroup
-      - plugins/webhooks
-      - polymer-bridges
+
+- job:
+    name: gerrit-build-base
+    parent: gerrit-base
+    description: |
+      Build Gerrit
+
+      This job sets up submodule repos in a Gerrit tree and runs
+      bazelisk.  However, it does not include all of the required
+      repos; for that, see :zuul:job:`gerrit-build` which inherits
+      from this and adds required-projects.
+
+      Responds to these variables:
+
+      .. zuul:jobvar:: baselisk_targets
+
+         The bazelisk targets to build.  Defaults to ":release".
+
+      .. zuul:jobvar:: baselisk_test_targets
+
+         The bazelisk targets to test.
+    run: playbooks/gerrit/build.yaml
+    timeout: 3600
+    vars:
+      bazelisk_targets: ":release"
+      bazelisk_artifacts:
+        - bazel-bin/release.war
+      zuul_work_dir: "{{ ansible_user_dir }}/src/gerrit.googlesource.com/gerrit"
+
+- job:
+    name: gerrit-plugin-build
+    parent: gerrit-base
+    description: |
+      Builds a Gerrit plugin in-tree
+
+      Responds to these variables:
+
+      .. zuul:jobvar:: gerrit_plugin
+
+         The name of the plugin to build.  Defaults to the project
+         under test, but can be specified explicitly to build
+         cross-repo.
+
+      .. zuul:jobvar:: baselisk_targets
+
+         The bazelisk targets to build.  Defaults to the gerrit_plugin
+         specified above.
+
+      .. zuul:jobvar:: baselisk_test_targets
+
+         The bazelisk targets to test.  Defaults to
+         "plugins/{{gerrit_plugin }}/..."
+    run: playbooks/gerrit-plugin/build.yaml
+    vars:
+      gerrit_plugin: "{{ zuul.project.short_name }}"
+      bazelisk_targets: "plugins/{{ gerrit_plugin }}:{{ gerrit_plugin }}"
+      bazelisk_test_targets: "plugins/{{ gerrit_plugin }}/..."
+      bazelisk_artifacts:
+        - "bazel-bin/plugins/{{ gerrit_plugin }}/{{ gerrit_plugin }}.jar"
+      zuul_work_dir: "{{ ansible_user_dir }}/src/gerrit.googlesource.com/gerrit"
diff --git a/zuul.d/project.yaml b/zuul.d/project.yaml
index 3645f44..91877b2 100644
--- a/zuul.d/project.yaml
+++ b/zuul.d/project.yaml
@@ -3,5 +3,10 @@
       jobs:
         - test-ensure-bazelisk
         - test-ensure-java
-        - test-gerrit-base
+        - test-gerrit-setup
         - test-install-build-essential
+        - test-gerrit-plugin-build
+        - test-gerrit-build-master
+        - test-gerrit-build-stable-3.1
+        - test-gerrit-build-stable-3.0
+        - test-gerrit-build-stable-2.16
diff --git a/zuul.d/test-jobs.yaml b/zuul.d/test-jobs.yaml
index 62211bc..b31c86c 100644
--- a/zuul.d/test-jobs.yaml
+++ b/zuul.d/test-jobs.yaml
@@ -1,16 +1,8 @@
 # Jobs used for testing roles in this repository
 
-# A temporary nodeset until we add this to zuul/config
-- nodeset:
-    name: temp-debian
-    nodes:
-      - name: testnode
-        label: debian-stretch-8G
-
 - job:
     name: test-ensure-bazelisk
     run: playbooks/test-ensure-bazelisk.yaml
-    nodeset: temp-debian
     files:
       - roles/ensure-bazelisk/.*
       - playbooks/test-ensure-bazelisk.yaml
@@ -18,7 +10,6 @@
 - job:
     name: test-ensure-java
     run: playbooks/test-ensure-java.yaml
-    nodeset: temp-debian
     files:
       - roles/ensure-java/.*
       - playbooks/test-ensure-java.yaml
@@ -26,19 +17,68 @@
 - job:
     name: test-install-build-essential
     run: playbooks/test-install-build-essential.yaml
-    nodeset: temp-debian
     files:
       - roles/install-build-essential/.*
       - playbooks/test-install-build-essential.yaml
 
 - job:
-    name: test-gerrit-base
-    parent: gerrit-base
-    run: playbooks/test-gerrit-base.yaml
-    nodeset: temp-debian
+    name: test-gerrit-setup
+    parent: gerrit-setup
+    run: playbooks/test-gerrit-setup.yaml
+    required-projects:
+      - jgit
+      - plugins/codemirror-editor
+      - plugins/commit-message-length-validator
+      - plugins/delete-project
+      - plugins/download-commands
+      - plugins/gitiles
+      - plugins/hooks
+      - plugins/plugin-manager
+      - plugins/replication
+      - plugins/reviewnotes
+      - plugins/singleusergroup
+      - plugins/webhooks
+      - polymer-bridges
     files:
       - roles/ensure-bazelisk/.*
       - roles/ensure-java/.*
       - roles/prepare-gerrit-repos/.*
       - roles/install-build-essential/.*
-      - playbooks/test-gerrit-base.yaml
+      - playbooks/test-gerrit-setup.yaml
+
+- job:
+    name: test-gerrit-plugin-build
+    parent: gerrit-plugin-build
+    required-projects:
+      - plugins/gitiles
+    vars:
+      gerrit_plugin: gitiles
+    files:
+      - roles/ensure-bazelisk/.*
+      - roles/prepare-gerrit-repos/.*
+      - roles/copy-plugin-deps/.*
+      - playbooks/gerrit-plugin/build.yaml
+
+- job:
+    name: test-gerrit-build-master
+    parent: gerrit-build
+    files:
+      - roles/ensure-bazelisk/.*
+      - roles/prepare-gerrit-repos/.*
+      - roles/copy-plugin-deps/.*
+      - playbooks/gerrit/.*
+
+- job:
+    name: test-gerrit-build-stable-3.1
+    parent: test-gerrit-build-master
+    override-checkout: stable-3.1
+
+- job:
+    name: test-gerrit-build-stable-3.0
+    parent: test-gerrit-build-master
+    override-checkout: stable-3.0
+
+- job:
+    name: test-gerrit-build-stable-2.16
+    parent: test-gerrit-build-master
+    override-checkout: stable-2.16