Merge pull request #16104 from yashykt/finalinfo

Allow error strings in final_info to propagate to filters on call des…
diff --git a/BUILD b/BUILD
index 1c80e83..ee4b5df 100644
--- a/BUILD
+++ b/BUILD
@@ -64,11 +64,11 @@
 )
 
 # This should be updated along with build.yaml
-g_stands_for = "gladiolus"
+g_stands_for = "glider"
 
 core_version = "6.0.0-dev"
 
-version = "1.14.0-dev"
+version = "1.15.0-dev"
 
 GPR_PUBLIC_HDRS = [
     "include/grpc/support/alloc.h",
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 75f3a2f..84e9c08 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -24,7 +24,7 @@
 cmake_minimum_required(VERSION 2.8)
 
 set(PACKAGE_NAME      "grpc")
-set(PACKAGE_VERSION   "1.14.0-dev")
+set(PACKAGE_VERSION   "1.15.0-dev")
 set(PACKAGE_STRING    "${PACKAGE_NAME} ${PACKAGE_VERSION}")
 set(PACKAGE_TARNAME   "${PACKAGE_NAME}-${PACKAGE_VERSION}")
 set(PACKAGE_BUGREPORT "https://github.com/grpc/grpc/issues/")
@@ -5164,6 +5164,7 @@
   test/cpp/qps/client_sync.cc
   test/cpp/qps/driver.cc
   test/cpp/qps/parse_json.cc
+  test/cpp/qps/qps_server_builder.cc
   test/cpp/qps/qps_worker.cc
   test/cpp/qps/report.cc
   test/cpp/qps/server_async.cc
diff --git a/Makefile b/Makefile
index 3091879..2b4ed14 100644
--- a/Makefile
+++ b/Makefile
@@ -437,8 +437,8 @@
 endif
 
 CORE_VERSION = 6.0.0-dev
-CPP_VERSION = 1.14.0-dev
-CSHARP_VERSION = 1.14.0-dev
+CPP_VERSION = 1.15.0-dev
+CSHARP_VERSION = 1.15.0-dev
 
 CPPFLAGS_NO_ARCH += $(addprefix -I, $(INCLUDES)) $(addprefix -D, $(DEFINES))
 CPPFLAGS += $(CPPFLAGS_NO_ARCH) $(ARCH_FLAGS)
@@ -7438,6 +7438,7 @@
     test/cpp/qps/client_sync.cc \
     test/cpp/qps/driver.cc \
     test/cpp/qps/parse_json.cc \
+    test/cpp/qps/qps_server_builder.cc \
     test/cpp/qps/qps_worker.cc \
     test/cpp/qps/report.cc \
     test/cpp/qps/server_async.cc \
@@ -7493,6 +7494,7 @@
 $(OBJDIR)/$(CONFIG)/test/cpp/qps/client_sync.o: $(GENDIR)/src/proto/grpc/testing/messages.pb.cc $(GENDIR)/src/proto/grpc/testing/messages.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/payloads.pb.cc $(GENDIR)/src/proto/grpc/testing/payloads.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/stats.pb.cc $(GENDIR)/src/proto/grpc/testing/stats.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/control.pb.cc $(GENDIR)/src/proto/grpc/testing/control.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/benchmark_service.pb.cc $(GENDIR)/src/proto/grpc/testing/benchmark_service.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/report_qps_scenario_service.pb.cc $(GENDIR)/src/proto/grpc/testing/report_qps_scenario_service.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/worker_service.pb.cc $(GENDIR)/src/proto/grpc/testing/worker_service.grpc.pb.cc
 $(OBJDIR)/$(CONFIG)/test/cpp/qps/driver.o: $(GENDIR)/src/proto/grpc/testing/messages.pb.cc $(GENDIR)/src/proto/grpc/testing/messages.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/payloads.pb.cc $(GENDIR)/src/proto/grpc/testing/payloads.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/stats.pb.cc $(GENDIR)/src/proto/grpc/testing/stats.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/control.pb.cc $(GENDIR)/src/proto/grpc/testing/control.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/benchmark_service.pb.cc $(GENDIR)/src/proto/grpc/testing/benchmark_service.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/report_qps_scenario_service.pb.cc $(GENDIR)/src/proto/grpc/testing/report_qps_scenario_service.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/worker_service.pb.cc $(GENDIR)/src/proto/grpc/testing/worker_service.grpc.pb.cc
 $(OBJDIR)/$(CONFIG)/test/cpp/qps/parse_json.o: $(GENDIR)/src/proto/grpc/testing/messages.pb.cc $(GENDIR)/src/proto/grpc/testing/messages.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/payloads.pb.cc $(GENDIR)/src/proto/grpc/testing/payloads.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/stats.pb.cc $(GENDIR)/src/proto/grpc/testing/stats.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/control.pb.cc $(GENDIR)/src/proto/grpc/testing/control.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/benchmark_service.pb.cc $(GENDIR)/src/proto/grpc/testing/benchmark_service.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/report_qps_scenario_service.pb.cc $(GENDIR)/src/proto/grpc/testing/report_qps_scenario_service.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/worker_service.pb.cc $(GENDIR)/src/proto/grpc/testing/worker_service.grpc.pb.cc
+$(OBJDIR)/$(CONFIG)/test/cpp/qps/qps_server_builder.o: $(GENDIR)/src/proto/grpc/testing/messages.pb.cc $(GENDIR)/src/proto/grpc/testing/messages.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/payloads.pb.cc $(GENDIR)/src/proto/grpc/testing/payloads.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/stats.pb.cc $(GENDIR)/src/proto/grpc/testing/stats.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/control.pb.cc $(GENDIR)/src/proto/grpc/testing/control.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/benchmark_service.pb.cc $(GENDIR)/src/proto/grpc/testing/benchmark_service.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/report_qps_scenario_service.pb.cc $(GENDIR)/src/proto/grpc/testing/report_qps_scenario_service.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/worker_service.pb.cc $(GENDIR)/src/proto/grpc/testing/worker_service.grpc.pb.cc
 $(OBJDIR)/$(CONFIG)/test/cpp/qps/qps_worker.o: $(GENDIR)/src/proto/grpc/testing/messages.pb.cc $(GENDIR)/src/proto/grpc/testing/messages.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/payloads.pb.cc $(GENDIR)/src/proto/grpc/testing/payloads.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/stats.pb.cc $(GENDIR)/src/proto/grpc/testing/stats.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/control.pb.cc $(GENDIR)/src/proto/grpc/testing/control.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/benchmark_service.pb.cc $(GENDIR)/src/proto/grpc/testing/benchmark_service.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/report_qps_scenario_service.pb.cc $(GENDIR)/src/proto/grpc/testing/report_qps_scenario_service.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/worker_service.pb.cc $(GENDIR)/src/proto/grpc/testing/worker_service.grpc.pb.cc
 $(OBJDIR)/$(CONFIG)/test/cpp/qps/report.o: $(GENDIR)/src/proto/grpc/testing/messages.pb.cc $(GENDIR)/src/proto/grpc/testing/messages.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/payloads.pb.cc $(GENDIR)/src/proto/grpc/testing/payloads.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/stats.pb.cc $(GENDIR)/src/proto/grpc/testing/stats.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/control.pb.cc $(GENDIR)/src/proto/grpc/testing/control.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/benchmark_service.pb.cc $(GENDIR)/src/proto/grpc/testing/benchmark_service.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/report_qps_scenario_service.pb.cc $(GENDIR)/src/proto/grpc/testing/report_qps_scenario_service.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/worker_service.pb.cc $(GENDIR)/src/proto/grpc/testing/worker_service.grpc.pb.cc
 $(OBJDIR)/$(CONFIG)/test/cpp/qps/server_async.o: $(GENDIR)/src/proto/grpc/testing/messages.pb.cc $(GENDIR)/src/proto/grpc/testing/messages.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/payloads.pb.cc $(GENDIR)/src/proto/grpc/testing/payloads.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/stats.pb.cc $(GENDIR)/src/proto/grpc/testing/stats.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/control.pb.cc $(GENDIR)/src/proto/grpc/testing/control.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/benchmark_service.pb.cc $(GENDIR)/src/proto/grpc/testing/benchmark_service.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/report_qps_scenario_service.pb.cc $(GENDIR)/src/proto/grpc/testing/report_qps_scenario_service.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/worker_service.pb.cc $(GENDIR)/src/proto/grpc/testing/worker_service.grpc.pb.cc
@@ -24691,6 +24693,7 @@
 test/cpp/qps/client_sync.cc: $(OPENSSL_DEP)
 test/cpp/qps/driver.cc: $(OPENSSL_DEP)
 test/cpp/qps/parse_json.cc: $(OPENSSL_DEP)
+test/cpp/qps/qps_server_builder.cc: $(OPENSSL_DEP)
 test/cpp/qps/qps_worker.cc: $(OPENSSL_DEP)
 test/cpp/qps/report.cc: $(OPENSSL_DEP)
 test/cpp/qps/server_async.cc: $(OPENSSL_DEP)
diff --git a/README.md b/README.md
index 1af9ec3..1960f44 100644
--- a/README.md
+++ b/README.md
@@ -37,6 +37,8 @@
 
 You can find per-language quickstart guides and tutorials in [Documentation section on grpc.io website](https://grpc.io/docs/). The code examples are available in the [examples](examples) directory.
 
+Precompiled bleeding-edge package builds of gRPC `master` branch's `HEAD` are uploaded daily to [packages.grpc.io](https://packages.grpc.io).
+
 # To start developing gRPC
 
 Contributions are welcome!
diff --git a/build.yaml b/build.yaml
index 2ff98f1..30389ec 100644
--- a/build.yaml
+++ b/build.yaml
@@ -13,8 +13,8 @@
   '#09': Per-language overrides are possible with (eg) ruby_version tag here
   '#10': See the expand_version.py for all the quirks here
   core_version: 6.0.0-dev
-  g_stands_for: gladiolus
-  version: 1.14.0-dev
+  g_stands_for: glider
+  version: 1.15.0-dev
 filegroups:
 - name: alts_proto
   headers:
@@ -1963,6 +1963,7 @@
   - test/cpp/qps/histogram.h
   - test/cpp/qps/interarrival.h
   - test/cpp/qps/parse_json.h
+  - test/cpp/qps/qps_server_builder.h
   - test/cpp/qps/qps_worker.h
   - test/cpp/qps/report.h
   - test/cpp/qps/server.h
@@ -1981,6 +1982,7 @@
   - test/cpp/qps/client_sync.cc
   - test/cpp/qps/driver.cc
   - test/cpp/qps/parse_json.cc
+  - test/cpp/qps/qps_server_builder.cc
   - test/cpp/qps/qps_worker.cc
   - test/cpp/qps/report.cc
   - test/cpp/qps/server_async.cc
diff --git a/doc/g_stands_for.md b/doc/g_stands_for.md
index f86bd76..b3ed9bd 100644
--- a/doc/g_stands_for.md
+++ b/doc/g_stands_for.md
@@ -13,4 +13,5 @@
 - 1.11 'g' stands for ['gorgeous'](https://github.com/grpc/grpc/tree/v1.11.x)
 - 1.12 'g' stands for ['glorious'](https://github.com/grpc/grpc/tree/v1.12.x)
 - 1.13 'g' stands for ['gloriosa'](https://github.com/grpc/grpc/tree/v1.13.x)
-- 1.14 'g' stands for ['gladiolus'](https://github.com/grpc/grpc/tree/master)
+- 1.14 'g' stands for ['gladiolus'](https://github.com/grpc/grpc/tree/v1.14.x)
+- 1.15 'g' stands for ['glider'](https://github.com/grpc/grpc/tree/master)
diff --git a/gRPC-C++.podspec b/gRPC-C++.podspec
index 57d58cc..1d9237b 100644
--- a/gRPC-C++.podspec
+++ b/gRPC-C++.podspec
@@ -23,7 +23,7 @@
 Pod::Spec.new do |s|
   s.name     = 'gRPC-C++'
   # TODO (mxyan): use version that match gRPC version when pod is stabilized
-  # version = '1.14.0-dev'
+  # version = '1.15.0-dev'
   version = '0.0.3'
   s.version  = version
   s.summary  = 'gRPC C++ library'
@@ -31,7 +31,7 @@
   s.license  = 'Apache License, Version 2.0'
   s.authors  = { 'The gRPC contributors' => '[email protected]' }
 
-  grpc_version = '1.14.0-dev'
+  grpc_version = '1.15.0-dev'
 
   s.source = {
     :git => 'https://github.com/grpc/grpc.git',
diff --git a/gRPC-Core.podspec b/gRPC-Core.podspec
index 997617c..23edaec 100644
--- a/gRPC-Core.podspec
+++ b/gRPC-Core.podspec
@@ -22,7 +22,7 @@
 
 Pod::Spec.new do |s|
   s.name     = 'gRPC-Core'
-  version = '1.14.0-dev'
+  version = '1.15.0-dev'
   s.version  = version
   s.summary  = 'Core cross-platform gRPC library, written in C'
   s.homepage = 'https://grpc.io'
diff --git a/gRPC-ProtoRPC.podspec b/gRPC-ProtoRPC.podspec
index 6548f36..79ec679 100644
--- a/gRPC-ProtoRPC.podspec
+++ b/gRPC-ProtoRPC.podspec
@@ -21,7 +21,7 @@
 
 Pod::Spec.new do |s|
   s.name     = 'gRPC-ProtoRPC'
-  version = '1.14.0-dev'
+  version = '1.15.0-dev'
   s.version  = version
   s.summary  = 'RPC library for Protocol Buffers, based on gRPC'
   s.homepage = 'https://grpc.io'
diff --git a/gRPC-RxLibrary.podspec b/gRPC-RxLibrary.podspec
index ebd942c..7a461e2 100644
--- a/gRPC-RxLibrary.podspec
+++ b/gRPC-RxLibrary.podspec
@@ -21,7 +21,7 @@
 
 Pod::Spec.new do |s|
   s.name     = 'gRPC-RxLibrary'
-  version = '1.14.0-dev'
+  version = '1.15.0-dev'
   s.version  = version
   s.summary  = 'Reactive Extensions library for iOS/OSX.'
   s.homepage = 'https://grpc.io'
diff --git a/gRPC.podspec b/gRPC.podspec
index d6d59c2..bf3f56b 100644
--- a/gRPC.podspec
+++ b/gRPC.podspec
@@ -20,7 +20,7 @@
 
 Pod::Spec.new do |s|
   s.name     = 'gRPC'
-  version = '1.14.0-dev'
+  version = '1.15.0-dev'
   s.version  = version
   s.summary  = 'gRPC client library for iOS/OSX'
   s.homepage = 'https://grpc.io'
diff --git a/grpc.gyp b/grpc.gyp
index 7cbd9ed..e1485ef 100644
--- a/grpc.gyp
+++ b/grpc.gyp
@@ -1718,6 +1718,7 @@
         'test/cpp/qps/client_sync.cc',
         'test/cpp/qps/driver.cc',
         'test/cpp/qps/parse_json.cc',
+        'test/cpp/qps/qps_server_builder.cc',
         'test/cpp/qps/qps_worker.cc',
         'test/cpp/qps/report.cc',
         'test/cpp/qps/server_async.cc',
diff --git a/package.xml b/package.xml
index a9dc2dc..7f71536 100644
--- a/package.xml
+++ b/package.xml
@@ -13,8 +13,8 @@
  <date>2018-01-19</date>
  <time>16:06:07</time>
  <version>
-  <release>1.14.0dev</release>
-  <api>1.14.0dev</api>
+  <release>1.15.0dev</release>
+  <api>1.15.0dev</api>
  </version>
  <stability>
   <release>beta</release>
diff --git a/src/core/lib/gpr/arena.cc b/src/core/lib/gpr/arena.cc
index 141de69..e30b297 100644
--- a/src/core/lib/gpr/arena.cc
+++ b/src/core/lib/gpr/arena.cc
@@ -25,6 +25,7 @@
 #include <grpc/support/alloc.h>
 #include <grpc/support/atm.h>
 #include <grpc/support/log.h>
+#include <grpc/support/sync.h>
 
 #include "src/core/lib/gpr/alloc.h"
 
@@ -36,8 +37,6 @@
 
 #ifdef SIMPLE_ARENA_FOR_DEBUGGING
 
-#include <grpc/support/sync.h>
-
 struct gpr_arena {
   gpr_mu mu;
   void** ptrs;
@@ -78,14 +77,17 @@
 // would allow us to use the alignment actually needed by the caller.
 
 typedef struct zone {
-  size_t size_begin;
-  size_t size_end;
-  gpr_atm next_atm;
+  size_t size_begin;  // All the space we have set aside for allocations up
+                      // until this zone.
+  size_t size_end;  // size_end = size_begin plus all the space we set aside for
+                    // allocations in zone z itself.
+  zone* next;
 } zone;
 
 struct gpr_arena {
   gpr_atm size_so_far;
   zone initial_zone;
+  gpr_mu arena_growth_mutex;
 };
 
 static void* zalloc_aligned(size_t size) {
@@ -99,15 +101,17 @@
   gpr_arena* a = static_cast<gpr_arena*>(zalloc_aligned(
       GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(gpr_arena)) + initial_size));
   a->initial_zone.size_end = initial_size;
+  gpr_mu_init(&a->arena_growth_mutex);
   return a;
 }
 
 size_t gpr_arena_destroy(gpr_arena* arena) {
+  gpr_mu_destroy(&arena->arena_growth_mutex);
   gpr_atm size = gpr_atm_no_barrier_load(&arena->size_so_far);
-  zone* z = (zone*)gpr_atm_no_barrier_load(&arena->initial_zone.next_atm);
+  zone* z = arena->initial_zone.next;
   gpr_free_aligned(arena);
   while (z) {
-    zone* next_z = (zone*)gpr_atm_no_barrier_load(&z->next_atm);
+    zone* next_z = z->next;
     gpr_free_aligned(z);
     z = next_z;
   }
@@ -116,37 +120,55 @@
 
 void* gpr_arena_alloc(gpr_arena* arena, size_t size) {
   size = GPR_ROUND_UP_TO_ALIGNMENT_SIZE(size);
-  size_t start = static_cast<size_t>(
+  size_t previous_size_of_arena_allocations = static_cast<size_t>(
       gpr_atm_no_barrier_fetch_add(&arena->size_so_far, size));
+  size_t updated_size_of_arena_allocations =
+      previous_size_of_arena_allocations + size;
   zone* z = &arena->initial_zone;
-  while (start > z->size_end) {
-    zone* next_z = (zone*)gpr_atm_acq_load(&z->next_atm);
-    if (next_z == nullptr) {
-      size_t next_z_size =
-          static_cast<size_t>(gpr_atm_no_barrier_load(&arena->size_so_far));
-      next_z = static_cast<zone*>(zalloc_aligned(
-          GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(zone)) + next_z_size));
-      next_z->size_begin = z->size_end;
-      next_z->size_end = z->size_end + next_z_size;
-      if (!gpr_atm_rel_cas(&z->next_atm, static_cast<gpr_atm>(NULL),
-                           (gpr_atm)next_z)) {
-        gpr_free_aligned(next_z);
-        next_z = (zone*)gpr_atm_acq_load(&z->next_atm);
+  // Check to see if the allocation isn't able to end in the initial zone.
+  // This statement is true only in the uncommon case because of our arena
+  // sizing historesis (that is, most calls should have a large enough initial
+  // zone and will not need to grow the arena).
+  if (updated_size_of_arena_allocations > z->size_end) {
+    // Find a zone to fit this allocation
+    gpr_mu_lock(&arena->arena_growth_mutex);
+    while (updated_size_of_arena_allocations > z->size_end) {
+      if (z->next == nullptr) {
+        // Note that we do an extra increment of size_so_far to prevent multiple
+        // simultaneous callers from stepping on each other. However, this extra
+        // increment means some space in the arena is wasted.
+        // So whenever we need to allocate x bytes and there are x - n (where
+        // n > 0) remaining in the current zone, we will waste x bytes (x - n
+        // in the current zone and n in the new zone).
+        previous_size_of_arena_allocations = static_cast<size_t>(
+            gpr_atm_no_barrier_fetch_add(&arena->size_so_far, size));
+        updated_size_of_arena_allocations =
+            previous_size_of_arena_allocations + size;
+        size_t next_z_size = updated_size_of_arena_allocations;
+        z->next = static_cast<zone*>(zalloc_aligned(
+            GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(zone)) + next_z_size));
+        z->next->size_begin = z->size_end;
+        z->next->size_end = z->size_end + next_z_size;
       }
+      z = z->next;
     }
-    z = next_z;
+    gpr_mu_unlock(&arena->arena_growth_mutex);
   }
-  if (start + size > z->size_end) {
-    return gpr_arena_alloc(arena, size);
-  }
-  GPR_ASSERT(start >= z->size_begin);
-  GPR_ASSERT(start + size <= z->size_end);
-  char* ptr = (z == &arena->initial_zone)
-                  ? reinterpret_cast<char*>(arena) +
-                        GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(gpr_arena))
-                  : reinterpret_cast<char*>(z) +
-                        GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(zone));
-  return ptr + start - z->size_begin;
+  GPR_ASSERT(previous_size_of_arena_allocations >= z->size_begin);
+  GPR_ASSERT(updated_size_of_arena_allocations <= z->size_end);
+  // Skip the first part of the zone, which just contains tracking information.
+  // For the initial zone, this is the gpr_arena struct and for any other zone,
+  // it's the zone struct.
+  char* start_of_allocation_space =
+      (z == &arena->initial_zone)
+          ? reinterpret_cast<char*>(arena) +
+                GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(gpr_arena))
+          : reinterpret_cast<char*>(z) +
+                GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(zone));
+  // previous_size_of_arena_allocations - size_begin is how many bytes have been
+  // allocated into the current zone
+  return start_of_allocation_space + previous_size_of_arena_allocations -
+         z->size_begin;
 }
 
 #endif  // SIMPLE_ARENA_FOR_DEBUGGING
diff --git a/src/core/lib/security/credentials/alts/check_gcp_environment_linux.cc b/src/core/lib/security/credentials/alts/check_gcp_environment_linux.cc
index 7c4d7a7..8454fd7 100644
--- a/src/core/lib/security/credentials/alts/check_gcp_environment_linux.cc
+++ b/src/core/lib/security/credentials/alts/check_gcp_environment_linux.cc
@@ -41,8 +41,9 @@
 
 bool check_bios_data(const char* bios_data_file) {
   char* bios_data = read_bios_file(bios_data_file);
-  bool result = (!strcmp(bios_data, GRPC_ALTS_EXPECT_NAME_GOOGLE)) ||
-                (!strcmp(bios_data, GRPC_ALTS_EXPECT_NAME_GCE));
+  bool result =
+      bios_data && ((!strcmp(bios_data, GRPC_ALTS_EXPECT_NAME_GOOGLE)) ||
+                    (!strcmp(bios_data, GRPC_ALTS_EXPECT_NAME_GCE)));
   gpr_free(bios_data);
   return result;
 }
diff --git a/src/core/lib/surface/version.cc b/src/core/lib/surface/version.cc
index ac8cec2..e92fe2c 100644
--- a/src/core/lib/surface/version.cc
+++ b/src/core/lib/surface/version.cc
@@ -25,4 +25,4 @@
 
 const char* grpc_version_string(void) { return "6.0.0-dev"; }
 
-const char* grpc_g_stands_for(void) { return "gladiolus"; }
+const char* grpc_g_stands_for(void) { return "glider"; }
diff --git a/src/cpp/common/version_cc.cc b/src/cpp/common/version_cc.cc
index a7b093d..b8fca9a 100644
--- a/src/cpp/common/version_cc.cc
+++ b/src/cpp/common/version_cc.cc
@@ -22,5 +22,5 @@
 #include <grpcpp/grpcpp.h>
 
 namespace grpc {
-grpc::string Version() { return "1.14.0-dev"; }
+grpc::string Version() { return "1.15.0-dev"; }
 }  // namespace grpc
diff --git a/src/csharp/Grpc.Core/Grpc.Core.csproj b/src/csharp/Grpc.Core/Grpc.Core.csproj
index 6d44be7..0da95d2 100755
--- a/src/csharp/Grpc.Core/Grpc.Core.csproj
+++ b/src/csharp/Grpc.Core/Grpc.Core.csproj
@@ -46,10 +46,22 @@
       <PackagePath>runtimes/win/native/grpc_csharp_ext.x86.dll</PackagePath>
       <Pack>true</Pack>
     </Content>
-    <Content Include="Grpc.Core.targets">
+    <Content Include="..\nativelibs\csharp_ext_linux_android_armeabi-v7a\libgrpc_csharp_ext.so">
+      <PackagePath>runtimes/monoandroid/armeabi-v7a/libgrpc_csharp_ext.so</PackagePath>
+      <Pack>true</Pack>
+    </Content>
+    <Content Include="..\nativelibs\csharp_ext_linux_android_arm64-v8a\libgrpc_csharp_ext.so">
+      <PackagePath>runtimes/monoandroid/arm64-v8a/libgrpc_csharp_ext.so</PackagePath>
+      <Pack>true</Pack>
+    </Content>
+    <Content Include="build\net45\Grpc.Core.targets">
       <PackagePath>build/net45/</PackagePath>
       <Pack>true</Pack>
     </Content>
+    <Content Include="build\MonoAndroid\Grpc.Core.targets">
+      <PackagePath>build/MonoAndroid/</PackagePath>
+      <Pack>true</Pack>
+    </Content>
   </ItemGroup>
 
   <ItemGroup>
diff --git a/src/csharp/Grpc.Core/Internal/NativeExtension.cs b/src/csharp/Grpc.Core/Internal/NativeExtension.cs
index d5ec998..f526b91 100644
--- a/src/csharp/Grpc.Core/Internal/NativeExtension.cs
+++ b/src/csharp/Grpc.Core/Internal/NativeExtension.cs
@@ -106,7 +106,15 @@
         /// </summary>
         private static NativeMethods LoadNativeMethods()
         {
-            return PlatformApis.IsUnity ? LoadNativeMethodsUnity() : new NativeMethods(LoadUnmanagedLibrary());
+            if (PlatformApis.IsUnity)
+            {
+                return LoadNativeMethodsUnity();
+            }
+            if (PlatformApis.IsXamarin)
+            {
+                return LoadNativeMethodsXamarin();
+            }
+            return new NativeMethods(LoadUnmanagedLibrary());
         }
 
         /// <summary>
@@ -128,6 +136,20 @@
             }
         }
 
+        /// <summary>
+        /// Return native method delegates when running on the Xamarin platform.
+        /// WARNING: Xamarin support is experimental and work-in-progress. Don't expect it to work.
+        /// </summary>
+        private static NativeMethods LoadNativeMethodsXamarin()
+        {
+            if (PlatformApis.IsXamarinAndroid)
+            {
+                return new NativeMethods(new NativeMethods.DllImportsFromSharedLib());
+            }
+            // not tested yet
+            return new NativeMethods(new NativeMethods.DllImportsFromStaticLib());
+        }
+
         private static string GetAssemblyPath()
         {
             var assembly = typeof(NativeExtension).GetTypeInfo().Assembly;
diff --git a/src/csharp/Grpc.Core/Internal/NativeLogRedirector.cs b/src/csharp/Grpc.Core/Internal/NativeLogRedirector.cs
index bf64401..30264ac 100644
--- a/src/csharp/Grpc.Core/Internal/NativeLogRedirector.cs
+++ b/src/csharp/Grpc.Core/Internal/NativeLogRedirector.cs
@@ -51,6 +51,7 @@
             }
         }
 
+        [MonoPInvokeCallback(typeof(GprLogDelegate))]
         private static void HandleWrite(IntPtr fileStringPtr, int line, ulong threadId, IntPtr severityStringPtr, IntPtr msgPtr)
         {
             try
@@ -86,4 +87,22 @@
             }
         }
     }
+
+    /// <summary>
+    /// Use this attribute to mark methods that will be called back from P/Invoke calls.
+    /// iOS (and probably other AOT platforms) needs to have delegates registered.
+    /// Instead of depending on Xamarin.iOS for this, we can just create our own,
+    /// the iOS runtime just checks for the type name.
+    /// See: https://docs.microsoft.com/en-gb/xamarin/ios/internals/limitations#reverse-callbacks
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Method)]
+    internal sealed class MonoPInvokeCallbackAttribute : Attribute
+    {
+        public MonoPInvokeCallbackAttribute(Type type)
+        {
+            Type = type;
+        }
+
+        public Type Type { get; private set; }
+    }
 }
diff --git a/src/csharp/Grpc.Core/Internal/PlatformApis.cs b/src/csharp/Grpc.Core/Internal/PlatformApis.cs
index b90fbcc..c501aa8 100644
--- a/src/csharp/Grpc.Core/Internal/PlatformApis.cs
+++ b/src/csharp/Grpc.Core/Internal/PlatformApis.cs
@@ -33,12 +33,18 @@
     internal static class PlatformApis
     {
         const string UnityEngineApplicationClassName = "UnityEngine.Application, UnityEngine";
+        const string XamarinAndroidObjectClassName = "Java.Lang.Object, Mono.Android";
+        const string XamarinIOSObjectClassName = "Foundation.NSObject, Xamarin.iOS";
+
         static readonly bool isLinux;
         static readonly bool isMacOSX;
         static readonly bool isWindows;
         static readonly bool isMono;
         static readonly bool isNetCore;
         static readonly bool isUnity;
+        static readonly bool isXamarin;
+        static readonly bool isXamarinIOS;
+        static readonly bool isXamarinAndroid;
 
         static PlatformApis()
         {
@@ -58,6 +64,9 @@
 #endif
             isMono = Type.GetType("Mono.Runtime") != null;
             isUnity = Type.GetType(UnityEngineApplicationClassName) != null;
+            isXamarinIOS = Type.GetType(XamarinIOSObjectClassName) != null;
+            isXamarinAndroid = Type.GetType(XamarinAndroidObjectClassName) != null;
+            isXamarin = isXamarinIOS || isXamarinAndroid;
         }
 
         public static bool IsLinux
@@ -89,6 +98,31 @@
         }
 
         /// <summary>
+        /// true if running on a Xamarin platform (either Xamarin.Android or Xamarin.iOS),
+        /// false otherwise.
+        /// </summary>
+        public static bool IsXamarin
+        {
+            get { return isXamarin; }
+        }
+
+        /// <summary>
+        /// true if running on Xamarin.iOS, false otherwise.
+        /// </summary>
+        public static bool IsXamarinIOS
+        {
+            get { return isXamarinIOS; }
+        }
+
+        /// <summary>
+        /// true if running on Xamarin.Android, false otherwise.
+        /// </summary>
+        public static bool IsXamarinAndroid
+        {
+            get { return isXamarinAndroid; }
+        }
+
+        /// <summary>
         /// true if running on .NET Core (CoreCLR), false otherwise.
         /// </summary>
         public static bool IsNetCore
diff --git a/src/csharp/Grpc.Core/Version.csproj.include b/src/csharp/Grpc.Core/Version.csproj.include
index 2b45e7a..1a746ac 100755
--- a/src/csharp/Grpc.Core/Version.csproj.include
+++ b/src/csharp/Grpc.Core/Version.csproj.include
@@ -1,7 +1,7 @@
 <!-- This file is generated -->
 <Project>
   <PropertyGroup>
-    <GrpcCsharpVersion>1.14.0-dev</GrpcCsharpVersion>
+    <GrpcCsharpVersion>1.15.0-dev</GrpcCsharpVersion>
     <GoogleProtobufVersion>3.5.1</GoogleProtobufVersion>
   </PropertyGroup>
 </Project>
diff --git a/src/csharp/Grpc.Core/VersionInfo.cs b/src/csharp/Grpc.Core/VersionInfo.cs
index c4531e5..295e0f2 100644
--- a/src/csharp/Grpc.Core/VersionInfo.cs
+++ b/src/csharp/Grpc.Core/VersionInfo.cs
@@ -33,11 +33,11 @@
         /// <summary>
         /// Current <c>AssemblyFileVersion</c> of gRPC C# assemblies
         /// </summary>
-        public const string CurrentAssemblyFileVersion = "1.14.0.0";
+        public const string CurrentAssemblyFileVersion = "1.15.0.0";
 
         /// <summary>
         /// Current version of gRPC C#
         /// </summary>
-        public const string CurrentVersion = "1.14.0-dev";
+        public const string CurrentVersion = "1.15.0-dev";
     }
 }
diff --git a/src/csharp/Grpc.Core/build/MonoAndroid/Grpc.Core.targets b/src/csharp/Grpc.Core/build/MonoAndroid/Grpc.Core.targets
new file mode 100644
index 0000000..f764f4c
--- /dev/null
+++ b/src/csharp/Grpc.Core/build/MonoAndroid/Grpc.Core.targets
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <_GrpcCoreNugetNativePath Condition="'$(_GrpcCoreNugetNativePath)' == ''">$(MSBuildThisFileDirectory)..\..\</_GrpcCoreNugetNativePath>
+  </PropertyGroup>
+
+  <ItemGroup Condition="'$(TargetFrameworkIdentifier)' == 'MonoAndroid'">
+    <AndroidNativeLibrary Include="$(_GrpcCoreNugetNativePath)runtimes\monoandroid\arm64-v8a\libgrpc_csharp_ext.so">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+      <Abi>arm64-v8a</Abi>
+    </AndroidNativeLibrary>
+  </ItemGroup>
+
+  <ItemGroup Condition="'$(TargetFrameworkIdentifier)' == 'MonoAndroid'">
+    <AndroidNativeLibrary Include="$(_GrpcCoreNugetNativePath)runtimes\monoandroid\armeabi-v7a\libgrpc_csharp_ext.so">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+      <Abi>armeabi-v7a</Abi>
+    </AndroidNativeLibrary>
+  </ItemGroup>
+
+</Project>
diff --git a/src/csharp/Grpc.Core/Grpc.Core.targets b/src/csharp/Grpc.Core/build/net45/Grpc.Core.targets
similarity index 100%
rename from src/csharp/Grpc.Core/Grpc.Core.targets
rename to src/csharp/Grpc.Core/build/net45/Grpc.Core.targets
diff --git a/src/csharp/build_packages_dotnetcli.bat b/src/csharp/build_packages_dotnetcli.bat
index 394b859..67af258 100755
--- a/src/csharp/build_packages_dotnetcli.bat
+++ b/src/csharp/build_packages_dotnetcli.bat
@@ -13,7 +13,7 @@
 @rem limitations under the License.
 
 @rem Current package versions
-set VERSION=1.14.0-dev
+set VERSION=1.15.0-dev
 
 @rem Adjust the location of nuget.exe
 set NUGET=C:\nuget\nuget.exe
diff --git a/src/csharp/build_packages_dotnetcli.sh b/src/csharp/build_packages_dotnetcli.sh
index 273d745..88b4d63 100755
--- a/src/csharp/build_packages_dotnetcli.sh
+++ b/src/csharp/build_packages_dotnetcli.sh
@@ -45,8 +45,8 @@
 dotnet pack --configuration Release Grpc.HealthCheck --output ../../../artifacts
 dotnet pack --configuration Release Grpc.Reflection --output ../../../artifacts
 
-nuget pack Grpc.nuspec -Version "1.14.0-dev" -OutputDirectory ../../artifacts
-nuget pack Grpc.Core.NativeDebug.nuspec -Version "1.14.0-dev" -OutputDirectory ../../artifacts
-nuget pack Grpc.Tools.nuspec -Version "1.14.0-dev" -OutputDirectory ../../artifacts
+nuget pack Grpc.nuspec -Version "1.15.0-dev" -OutputDirectory ../../artifacts
+nuget pack Grpc.Core.NativeDebug.nuspec -Version "1.15.0-dev" -OutputDirectory ../../artifacts
+nuget pack Grpc.Tools.nuspec -Version "1.15.0-dev" -OutputDirectory ../../artifacts
 
 (cd ../../artifacts && zip csharp_nugets_dotnetcli.zip *.nupkg)
diff --git a/src/csharp/experimental/build_native_ext_for_android.sh b/src/csharp/experimental/build_native_ext_for_android.sh
index 8197df7..6518e03 100755
--- a/src/csharp/experimental/build_native_ext_for_android.sh
+++ b/src/csharp/experimental/build_native_ext_for_android.sh
@@ -23,17 +23,30 @@
 cd build
 
 # set to the location where Android SDK is installed
-# e.g. ANDROID_NDK_PATH="$HOME/android-ndk-r16b"
+# e.g. ANDROID_SDK_PATH="$HOME/Android/Sdk"
 
-cmake ../.. \
-  -DCMAKE_SYSTEM_NAME=Android \
-  -DCMAKE_SYSTEM_VERSION=15 \
-  -DCMAKE_ANDROID_ARCH_ABI=armeabi-v7a \
+# set to location where Android NDK is installed, usually a subfolder of Android SDK
+# to install the Android NKD, use the "sdkmanager" tool
+# e.g. ANDROID_NDK_PATH=${ANDROID_SDK_PATH}/ndk-bundle
+
+# set to location of the cmake executable from the Android SDK
+# to install cmake, use the "sdkmanager" tool
+# e.g. ANDROID_SDK_CMAKE=${ANDROID_SDK_PATH}/cmake/3.6.4111459/bin/cmake
+
+# ANDROID_ABI in ('arm64-v8a', 'armeabi-v7a')
+# e.g. ANDROID_ABI=armeabi-v7a
+
+# android-19 corresponds to Kitkat 4.4
+${ANDROID_SDK_CMAKE} ../.. \
+  -DCMAKE_TOOLCHAIN_FILE="${ANDROID_NDK_PATH}/build/cmake/android.toolchain.cmake" \
   -DCMAKE_ANDROID_NDK="${ANDROID_NDK_PATH}" \
   -DCMAKE_ANDROID_STL_TYPE=c++_static \
   -DRUN_HAVE_POSIX_REGEX=0 \
   -DRUN_HAVE_STD_REGEX=0 \
   -DRUN_HAVE_STEADY_CLOCK=0 \
-  -DCMAKE_BUILD_TYPE=Release
+  -DCMAKE_BUILD_TYPE=Release \
+  -DANDROID_PLATFORM=android-19 \
+  -DANDROID_ABI="${ANDROID_ABI}" \
+  -DANDROID_NDK="${ANDROID_NDK_PATH}"
 
 make -j4 grpc_csharp_ext
diff --git "a/src/objective-c/\041ProtoCompiler-gRPCPlugin.podspec" "b/src/objective-c/\041ProtoCompiler-gRPCPlugin.podspec"
index 28dfffd..6ad9166 100644
--- "a/src/objective-c/\041ProtoCompiler-gRPCPlugin.podspec"
+++ "b/src/objective-c/\041ProtoCompiler-gRPCPlugin.podspec"
@@ -42,7 +42,7 @@
   # exclamation mark ensures that other "regular" pods will be able to find it as it'll be installed
   # before them.
   s.name     = '!ProtoCompiler-gRPCPlugin'
-  v = '1.14.0-dev'
+  v = '1.15.0-dev'
   s.version  = v
   s.summary  = 'The gRPC ProtoC plugin generates Objective-C files from .proto services.'
   s.description = <<-DESC
diff --git a/src/objective-c/GRPCClient/private/version.h b/src/objective-c/GRPCClient/private/version.h
index 60571b1..52fe0dd 100644
--- a/src/objective-c/GRPCClient/private/version.h
+++ b/src/objective-c/GRPCClient/private/version.h
@@ -22,4 +22,4 @@
 // instead. This file can be regenerated from the template by running
 // `tools/buildgen/generate_projects.sh`.
 
-#define GRPC_OBJC_VERSION_STRING @"1.14.0-dev"
+#define GRPC_OBJC_VERSION_STRING @"1.15.0-dev"
diff --git a/src/objective-c/tests/version.h b/src/objective-c/tests/version.h
index fafcac6..d532eed 100644
--- a/src/objective-c/tests/version.h
+++ b/src/objective-c/tests/version.h
@@ -22,5 +22,5 @@
 // instead. This file can be regenerated from the template by running
 // `tools/buildgen/generate_projects.sh`.
 
-#define GRPC_OBJC_VERSION_STRING @"1.14.0-dev"
+#define GRPC_OBJC_VERSION_STRING @"1.15.0-dev"
 #define GRPC_C_VERSION_STRING @"6.0.0-dev"
diff --git a/src/php/composer.json b/src/php/composer.json
index 9491841..b6716b8 100644
--- a/src/php/composer.json
+++ b/src/php/composer.json
@@ -2,7 +2,7 @@
   "name": "grpc/grpc-dev",
   "description": "gRPC library for PHP - for Developement use only",
   "license": "Apache-2.0",
-  "version": "1.14.0",
+  "version": "1.15.0",
   "require": {
     "php": ">=5.5.0",
     "google/protobuf": "^v3.3.0"
diff --git a/src/php/ext/grpc/version.h b/src/php/ext/grpc/version.h
index c985863..99dd9d6 100644
--- a/src/php/ext/grpc/version.h
+++ b/src/php/ext/grpc/version.h
@@ -20,6 +20,6 @@
 #ifndef VERSION_H
 #define VERSION_H
 
-#define PHP_GRPC_VERSION "1.14.0dev"
+#define PHP_GRPC_VERSION "1.15.0dev"
 
 #endif /* VERSION_H */
diff --git a/src/python/grpcio/grpc/_grpcio_metadata.py b/src/python/grpcio/grpc/_grpcio_metadata.py
index b336e6a..c33911e 100644
--- a/src/python/grpcio/grpc/_grpcio_metadata.py
+++ b/src/python/grpcio/grpc/_grpcio_metadata.py
@@ -14,4 +14,4 @@
 
 # AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio/grpc/_grpcio_metadata.py.template`!!!
 
-__version__ = """1.14.0.dev0"""
+__version__ = """1.15.0.dev0"""
diff --git a/src/python/grpcio/grpc_version.py b/src/python/grpcio/grpc_version.py
index 2eeaabc..9337800 100644
--- a/src/python/grpcio/grpc_version.py
+++ b/src/python/grpcio/grpc_version.py
@@ -14,4 +14,4 @@
 
 # AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio/grpc_version.py.template`!!!
 
-VERSION = '1.14.0.dev0'
+VERSION = '1.15.0.dev0'
diff --git a/src/python/grpcio_health_checking/grpc_version.py b/src/python/grpcio_health_checking/grpc_version.py
index f36de8d..3b84f7a 100644
--- a/src/python/grpcio_health_checking/grpc_version.py
+++ b/src/python/grpcio_health_checking/grpc_version.py
@@ -14,4 +14,4 @@
 
 # AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio_health_checking/grpc_version.py.template`!!!
 
-VERSION = '1.14.0.dev0'
+VERSION = '1.15.0.dev0'
diff --git a/src/python/grpcio_reflection/grpc_version.py b/src/python/grpcio_reflection/grpc_version.py
index 2249b07..7b0e48e 100644
--- a/src/python/grpcio_reflection/grpc_version.py
+++ b/src/python/grpcio_reflection/grpc_version.py
@@ -14,4 +14,4 @@
 
 # AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio_reflection/grpc_version.py.template`!!!
 
-VERSION = '1.14.0.dev0'
+VERSION = '1.15.0.dev0'
diff --git a/src/python/grpcio_testing/grpc_version.py b/src/python/grpcio_testing/grpc_version.py
index a5b275a..df9953f 100644
--- a/src/python/grpcio_testing/grpc_version.py
+++ b/src/python/grpcio_testing/grpc_version.py
@@ -14,4 +14,4 @@
 
 # AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio_testing/grpc_version.py.template`!!!
 
-VERSION = '1.14.0.dev0'
+VERSION = '1.15.0.dev0'
diff --git a/src/python/grpcio_tests/grpc_version.py b/src/python/grpcio_tests/grpc_version.py
index 816dfb5..b2cf129 100644
--- a/src/python/grpcio_tests/grpc_version.py
+++ b/src/python/grpcio_tests/grpc_version.py
@@ -14,4 +14,4 @@
 
 # AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio_tests/grpc_version.py.template`!!!
 
-VERSION = '1.14.0.dev0'
+VERSION = '1.15.0.dev0'
diff --git a/src/ruby/lib/grpc/version.rb b/src/ruby/lib/grpc/version.rb
index 902d59b..5c21a5b 100644
--- a/src/ruby/lib/grpc/version.rb
+++ b/src/ruby/lib/grpc/version.rb
@@ -14,5 +14,5 @@
 
 # GRPC contains the General RPC module.
 module GRPC
-  VERSION = '1.14.0.dev'
+  VERSION = '1.15.0.dev'
 end
diff --git a/src/ruby/tools/version.rb b/src/ruby/tools/version.rb
index dad09bb..1b5e2c3 100644
--- a/src/ruby/tools/version.rb
+++ b/src/ruby/tools/version.rb
@@ -14,6 +14,6 @@
 
 module GRPC
   module Tools
-    VERSION = '1.14.0.dev'
+    VERSION = '1.15.0.dev'
   end
 end
diff --git a/test/core/security/check_gcp_environment_linux_test.cc b/test/core/security/check_gcp_environment_linux_test.cc
index 3acd5b6..b01471a 100644
--- a/test/core/security/check_gcp_environment_linux_test.cc
+++ b/test/core/security/check_gcp_environment_linux_test.cc
@@ -69,6 +69,7 @@
   GPR_ASSERT(!check_bios_data_linux_test("Amazon"));
   GPR_ASSERT(!check_bios_data_linux_test("Google-Chrome\t\t"));
   GPR_ASSERT(!check_bios_data_linux_test("Amazon"));
+  GPR_ASSERT(!check_bios_data_linux_test("\n"));
 }
 
 int main(int argc, char** argv) {
diff --git a/test/cpp/qps/BUILD b/test/cpp/qps/BUILD
index e7d093c..b958c75 100644
--- a/test/cpp/qps/BUILD
+++ b/test/cpp/qps/BUILD
@@ -34,11 +34,13 @@
         "qps_worker.cc",
         "server_async.cc",
         "server_sync.cc",
+        "qps_server_builder.cc",
     ],
     hdrs = [
         "client.h",
         "qps_worker.h",
         "server.h",
+        "qps_server_builder.h",
     ],
     deps = [
         ":histogram",
@@ -55,6 +57,10 @@
         "//test/core/util:gpr_test_util",
         "//test/core/util:grpc_test_util",
         "//test/cpp/util:test_util",
+        "//test/cpp/util:test_config",
+    ],
+    external_deps = [
+        "gflags",
     ],
 )
 
diff --git a/test/cpp/qps/qps_server_builder.cc b/test/cpp/qps/qps_server_builder.cc
new file mode 100644
index 0000000..5fbc682
--- /dev/null
+++ b/test/cpp/qps/qps_server_builder.cc
@@ -0,0 +1,45 @@
+/*
+ *
+ * Copyright 2016 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qps_server_builder.h"
+
+using grpc::ServerBuilder;
+
+namespace grpc {
+namespace testing {
+
+namespace {
+std::unique_ptr<ServerBuilder> DefaultCreateQpsServerBuilder() {
+  return std::unique_ptr<ServerBuilder>(new ServerBuilder());
+}
+
+std::function<std::unique_ptr<ServerBuilder>()> g_create_qps_server_builder =
+    DefaultCreateQpsServerBuilder;
+}  // namespace
+
+std::unique_ptr<ServerBuilder> CreateQpsServerBuilder() {
+  return g_create_qps_server_builder();
+}
+
+void SetCreateQpsServerBuilderFunc(
+    std::function<std::unique_ptr<ServerBuilder>()> create_qps_server_builder) {
+  g_create_qps_server_builder = std::move(create_qps_server_builder);
+}
+
+}  // namespace testing
+}  // namespace grpc
diff --git a/test/cpp/qps/qps_server_builder.h b/test/cpp/qps/qps_server_builder.h
new file mode 100644
index 0000000..98f9fa7
--- /dev/null
+++ b/test/cpp/qps/qps_server_builder.h
@@ -0,0 +1,46 @@
+/*
+ *
+ * Copyright 2016 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#ifndef GRPC_QPS_SERVER_BUILDER_H
+#define GRPC_QPS_SERVER_BUILDER_H
+
+#include <functional>
+#include <memory>
+
+#include <grpcpp/server_builder.h>
+
+namespace grpc {
+namespace testing {
+
+// CreateQpsServerBuilder creates a new ServerBuilder.
+// This uses the "create ServerBuilder" func that was set
+// in SetCreateQpsServerBuilderFunc if one has been set,
+// otherwise, this defaults to creating a new ServerBuilder
+// with only its default constructor.
+std::unique_ptr<ServerBuilder> CreateQpsServerBuilder();
+
+// SetCreateQpsServerBuilderFunc sets a function to use to create new
+// ServerBuilders in "CreateQpsServerBuilder". It can be used to modify options
+// that the server is built with.
+void SetCreateQpsServerBuilderFunc(
+    std::function<std::unique_ptr<ServerBuilder>()>);
+
+}  // namespace testing
+}  // namespace grpc
+
+#endif  // GRPC_QPS_SERVER_BUILDER_H
diff --git a/test/cpp/qps/qps_worker.cc b/test/cpp/qps/qps_worker.cc
index d3f0380..7ddf3c1 100644
--- a/test/cpp/qps/qps_worker.cc
+++ b/test/cpp/qps/qps_worker.cc
@@ -39,6 +39,7 @@
 #include "test/core/util/grpc_profiler.h"
 #include "test/core/util/histogram.h"
 #include "test/cpp/qps/client.h"
+#include "test/cpp/qps/qps_server_builder.h"
 #include "test/cpp/qps/server.h"
 #include "test/cpp/util/create_test_channel.h"
 #include "test/cpp/util/test_credentials_provider.h"
@@ -272,18 +273,18 @@
   impl_.reset(new WorkerServiceImpl(server_port, this));
   gpr_atm_rel_store(&done_, static_cast<gpr_atm>(0));
 
-  ServerBuilder builder;
+  std::unique_ptr<ServerBuilder> builder = CreateQpsServerBuilder();
   if (driver_port >= 0) {
     char* server_address = nullptr;
     gpr_join_host_port(&server_address, "::", driver_port);
-    builder.AddListeningPort(
+    builder->AddListeningPort(
         server_address,
         GetCredentialsProvider()->GetServerCredentials(credential_type));
     gpr_free(server_address);
   }
-  builder.RegisterService(impl_.get());
+  builder->RegisterService(impl_.get());
 
-  server_ = builder.BuildAndStart();
+  server_ = builder->BuildAndStart();
 }
 
 QpsWorker::~QpsWorker() {}
diff --git a/test/cpp/qps/server_async.cc b/test/cpp/qps/server_async.cc
index 1dfef6c..5cd975c 100644
--- a/test/cpp/qps/server_async.cc
+++ b/test/cpp/qps/server_async.cc
@@ -38,6 +38,7 @@
 #include "src/core/lib/surface/completion_queue.h"
 #include "src/proto/grpc/testing/benchmark_service.grpc.pb.h"
 #include "test/core/util/test_config.h"
+#include "test/cpp/qps/qps_server_builder.h"
 #include "test/cpp/qps/server.h"
 
 namespace grpc {
@@ -74,19 +75,19 @@
                                  ResponseType*)>
           process_rpc)
       : Server(config) {
-    ServerBuilder builder;
+    std::unique_ptr<ServerBuilder> builder = CreateQpsServerBuilder();
 
     auto port_num = port();
     // Negative port number means inproc server, so no listen port needed
     if (port_num >= 0) {
       char* server_address = nullptr;
       gpr_join_host_port(&server_address, "::", port_num);
-      builder.AddListeningPort(server_address,
-                               Server::CreateServerCredentials(config));
+      builder->AddListeningPort(server_address,
+                                Server::CreateServerCredentials(config));
       gpr_free(server_address);
     }
 
-    register_service(&builder, &async_service_);
+    register_service(builder.get(), &async_service_);
 
     int num_threads = config.async_server_threads();
     if (num_threads <= 0) {  // dynamic sizing
@@ -97,15 +98,15 @@
     int tpc = std::max(1, config.threads_per_cq());  // 1 if unspecified
     int num_cqs = (num_threads + tpc - 1) / tpc;     // ceiling operator
     for (int i = 0; i < num_cqs; i++) {
-      srv_cqs_.emplace_back(builder.AddCompletionQueue());
+      srv_cqs_.emplace_back(builder->AddCompletionQueue());
     }
     for (int i = 0; i < num_threads; i++) {
       cq_.emplace_back(i % srv_cqs_.size());
     }
 
-    ApplyConfigToBuilder(config, &builder);
+    ApplyConfigToBuilder(config, builder.get());
 
-    server_ = builder.BuildAndStart();
+    server_ = builder->BuildAndStart();
 
     auto process_rpc_bound =
         std::bind(process_rpc, config.payload_config(), std::placeholders::_1,
diff --git a/test/cpp/qps/server_sync.cc b/test/cpp/qps/server_sync.cc
index b8facf9..2e63f5e 100644
--- a/test/cpp/qps/server_sync.cc
+++ b/test/cpp/qps/server_sync.cc
@@ -27,6 +27,7 @@
 
 #include "src/core/lib/gpr/host_port.h"
 #include "src/proto/grpc/testing/benchmark_service.grpc.pb.h"
+#include "test/cpp/qps/qps_server_builder.h"
 #include "test/cpp/qps/server.h"
 #include "test/cpp/qps/usage_timer.h"
 
@@ -154,23 +155,23 @@
 class SynchronousServer final : public grpc::testing::Server {
  public:
   explicit SynchronousServer(const ServerConfig& config) : Server(config) {
-    ServerBuilder builder;
+    std::unique_ptr<ServerBuilder> builder = CreateQpsServerBuilder();
 
     auto port_num = port();
     // Negative port number means inproc server, so no listen port needed
     if (port_num >= 0) {
       char* server_address = nullptr;
       gpr_join_host_port(&server_address, "::", port_num);
-      builder.AddListeningPort(server_address,
-                               Server::CreateServerCredentials(config));
+      builder->AddListeningPort(server_address,
+                                Server::CreateServerCredentials(config));
       gpr_free(server_address);
     }
 
-    ApplyConfigToBuilder(config, &builder);
+    ApplyConfigToBuilder(config, builder.get());
 
-    builder.RegisterService(&service_);
+    builder->RegisterService(&service_);
 
-    impl_ = builder.BuildAndStart();
+    impl_ = builder->BuildAndStart();
   }
 
   std::shared_ptr<Channel> InProcessChannel(
diff --git a/test/cpp/server/load_reporter/load_reporter_test.cc b/test/cpp/server/load_reporter/load_reporter_test.cc
index 719c3a6..0d56cdf 100644
--- a/test/cpp/server/load_reporter/load_reporter_test.cc
+++ b/test/cpp/server/load_reporter/load_reporter_test.cc
@@ -172,9 +172,9 @@
     // TODO(juanlishen): The error is big because we use sleep(). It should be
     // much smaller when we use fake clock.
     ASSERT_THAT(static_cast<double>(lb_feedback.calls_per_second()),
-                DoubleNear(expected_qps, expected_qps / 50));
+                DoubleNear(expected_qps, expected_qps * 0.05));
     ASSERT_THAT(static_cast<double>(lb_feedback.errors_per_second()),
-                DoubleNear(expected_eps, expected_eps / 50));
+                DoubleNear(expected_eps, expected_eps * 0.05));
     gpr_log(GPR_INFO,
             "Verified LB feedback matches the samples of index [%lu, %lu).",
             start, start + count);
diff --git a/tools/distrib/python/grpcio_tools/grpc_version.py b/tools/distrib/python/grpcio_tools/grpc_version.py
index ae249e7..ccb69a8 100644
--- a/tools/distrib/python/grpcio_tools/grpc_version.py
+++ b/tools/distrib/python/grpcio_tools/grpc_version.py
@@ -14,4 +14,4 @@
 
 # AUTO-GENERATED FROM `$REPO_ROOT/templates/tools/distrib/python/grpcio_tools/grpc_version.py.template`!!!
 
-VERSION = '1.14.0.dev0'
+VERSION = '1.15.0.dev0'
diff --git a/tools/dockerfile/grpc_artifact_android_ndk/Dockerfile b/tools/dockerfile/grpc_artifact_android_ndk/Dockerfile
index 77b6acf..be96f13 100644
--- a/tools/dockerfile/grpc_artifact_android_ndk/Dockerfile
+++ b/tools/dockerfile/grpc_artifact_android_ndk/Dockerfile
@@ -12,9 +12,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# Docker file for building gRPC artifacts.
+# Docker file for building gRPC artifacts for Android.
 
-# Recent enough cmake (>=3.9) needed by Android SDK
 FROM debian:sid
 
 RUN apt-get update && apt-get install -y debian-keyring && apt-key update
@@ -47,20 +46,26 @@
   wget \
   zip && apt-get clean
 
-# Cmake for cross-compilation
-RUN apt-get update && apt-get install -y cmake golang && apt-get clean
+# golang needed to build BoringSSL with cmake
+RUN apt-get update && apt-get install -y golang && apt-get clean
 
-##################
-# Android NDK
+# Java required by Android SDK
+RUN apt-get update && apt-get -y install openjdk-8-jdk && apt-get clean
 
-# Download and install Android NDK
-RUN wget -q https://dl.google.com/android/repository/android-ndk-r16b-linux-x86_64.zip -O android_ndk.zip \
-    && unzip -q android_ndk.zip \
-    && rm android_ndk.zip \
-    && mv ./android-ndk-r16b /opt
-ENV ANDROID_NDK_PATH /opt/android-ndk-r16b
+# Install Android SDK
+ENV ANDROID_SDK_VERSION 4333796
+RUN mkdir -p /opt/android-sdk && cd /opt/android-sdk && \
+    wget -q https://dl.google.com/android/repository/sdk-tools-linux-${ANDROID_SDK_VERSION}.zip && \
+    unzip -q sdk-tools-linux-${ANDROID_SDK_VERSION}.zip && \
+    rm sdk-tools-linux-${ANDROID_SDK_VERSION}.zip
+ENV ANDROID_SDK_PATH /opt/android-sdk
 
-RUN apt-get update && apt-get install -y libpthread-stubs0-dev && apt-get clean
+# Install Android NDK and cmake using sdkmanager
+RUN mkdir -p ~/.android && touch ~/.android/repositories.cfg
+RUN yes | ${ANDROID_SDK_PATH}/tools/bin/sdkmanager --licenses  # accept all licenses
+RUN ${ANDROID_SDK_PATH}/tools/bin/sdkmanager ndk-bundle 'cmake;3.6.4111459'
+ENV ANDROID_NDK_PATH ${ANDROID_SDK_PATH}/ndk-bundle
+ENV ANDROID_SDK_CMAKE ${ANDROID_SDK_PATH}/cmake/3.6.4111459/bin/cmake
 
 RUN mkdir /var/local/jenkins
 
diff --git a/tools/doxygen/Doxyfile.c++ b/tools/doxygen/Doxyfile.c++
index 322ab5e..2f06bda 100644
--- a/tools/doxygen/Doxyfile.c++
+++ b/tools/doxygen/Doxyfile.c++
@@ -40,7 +40,7 @@
 # could be handy for archiving the generated documentation or if some version
 # control system is used.
 
-PROJECT_NUMBER         = 1.14.0-dev
+PROJECT_NUMBER         = 1.15.0-dev
 
 # Using the PROJECT_BRIEF tag one can provide an optional one line description
 # for a project that appears at the top of each page and should give viewer a
diff --git a/tools/doxygen/Doxyfile.c++.internal b/tools/doxygen/Doxyfile.c++.internal
index ba322a9..a46ebe6 100644
--- a/tools/doxygen/Doxyfile.c++.internal
+++ b/tools/doxygen/Doxyfile.c++.internal
@@ -40,7 +40,7 @@
 # could be handy for archiving the generated documentation or if some version
 # control system is used.
 
-PROJECT_NUMBER         = 1.14.0-dev
+PROJECT_NUMBER         = 1.15.0-dev
 
 # Using the PROJECT_BRIEF tag one can provide an optional one line description
 # for a project that appears at the top of each page and should give viewer a
diff --git a/tools/internal_ci/linux/grpc_build_artifacts_extra_release.cfg b/tools/internal_ci/linux/grpc_build_artifacts_extra_release.cfg
new file mode 100644
index 0000000..619e3ea
--- /dev/null
+++ b/tools/internal_ci/linux/grpc_build_artifacts_extra_release.cfg
@@ -0,0 +1,26 @@
+# Copyright 2017 gRPC authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Config file for the internal CI (in protobuf text format)
+
+# Location of the continuous shell script in repository.
+build_file: "grpc/tools/internal_ci/linux/grpc_build_artifacts_extra.sh"
+timeout_mins: 240
+action {
+  define_artifacts {
+    regex: "**/*sponge_log.xml"
+    regex: "github/grpc/reports/**"
+    regex: "github/grpc/artifacts/**"
+  }
+}
diff --git a/tools/internal_ci/linux/grpc_publish_packages.sh b/tools/internal_ci/linux/grpc_publish_packages.sh
index 89da369..fecb9a5 100644
--- a/tools/internal_ci/linux/grpc_publish_packages.sh
+++ b/tools/internal_ci/linux/grpc_publish_packages.sh
@@ -17,94 +17,214 @@
 
 shopt -s nullglob
 
-export GOOGLE_APPLICATION_CREDENTIALS=${KOKORO_GFILE_DIR}/GrpcTesting-d0eeee2db331.json
+INPUT_ARTIFACTS=$KOKORO_GFILE_DIR/github/grpc/artifacts
+INDEX_FILENAME=index.xml
 
-GCS_ROOT=gs://packages.grpc.io
-MANIFEST_FILE=index.xml
-ARCHIVE_UUID=${KOKORO_BUILD_ID:-$(uuidgen)}
-GIT_BRANCH_NAME=master #${KOKORO_GITHUB_COMMIT:-master}
-GIT_COMMIT=${KOKORO_GIT_COMMIT:-unknown}
-ARCHIVE_TIMESTAMP=$(date -Iseconds)
-TARGET_DIR=$(mktemp -d grpc_publish_packages.sh.XXXX)
-YEAR_MONTH_PREFIX=$(date "+%Y/%m")
-YEAR_PREFIX=${YEAR_MONTH_PREFIX%%/*}
-UPLOAD_ROOT=$TARGET_DIR/$YEAR_PREFIX
-RELATIVE_PATH=$YEAR_MONTH_PREFIX/$ARCHIVE_UUID
-BUILD_ROOT=$TARGET_DIR/$RELATIVE_PATH
+BUILD_ID=${KOKORO_BUILD_ID:-$(uuidgen)}
+BUILD_BRANCH_NAME=master
+BUILD_GIT_COMMIT=${KOKORO_GIT_COMMIT:-unknown}
+BUILD_TIMESTAMP=$(date -Iseconds)
+BUILD_RELPATH=$(date "+%Y/%m")/$BUILD_ID/
 
-LINUX_PACKAGES=$KOKORO_GFILE_DIR/github/grpc/artifacts
-WINDOWS_PACKAGES=$KOKORO_GFILE_DIR/github/grpc/artifacts
-# TODO(mmx): enable linux_extra
-# LINUX_EXTRA_PACKAGES=$KOKORO_GFILE_DIR/github/grpc/artifacts
+GCS_ROOT=gs://packages.grpc.io/
+GCS_ARCHIVE_PREFIX=archive/
+GCS_ARCHIVE_ROOT=$GCS_ROOT$GCS_ARCHIVE_PREFIX
+GCS_INDEX=$GCS_ROOT$INDEX_FILENAME
 
-PYTHON_PACKAGES=(
-  "$LINUX_PACKAGES"/grpcio-[0-9]*.whl
-  "$LINUX_PACKAGES"/grpcio-[0-9]*.tar.gz
-  "$LINUX_PACKAGES"/grpcio_tools-[0-9]*.whl
-  "$LINUX_PACKAGES"/grpcio-tools-[0-9]*.tar.gz
-  "$LINUX_PACKAGES"/grpcio-health-checking-[0-9]*.tar.gz
-  "$LINUX_PACKAGES"/grpcio-reflection-[0-9]*.tar.gz
-  "$LINUX_PACKAGES"/grpcio-testing-[0-9]*.tar.gz
-  #"$LINUX_EXTRA_PACKAGES"/grpcio-[0-9]*.whl
-  #"$LINUX_EXTRA_PACKAGES"/grpcio_tools-[0-9]*.whl
+LOCAL_STAGING_TEMPDIR=$(mktemp -d)
+LOCAL_BUILD_ROOT=$LOCAL_STAGING_TEMPDIR/$BUILD_RELPATH
+LOCAL_BUILD_INDEX=$LOCAL_BUILD_ROOT$INDEX_FILENAME
+
+mkdir -p "$LOCAL_BUILD_ROOT"
+
+find "$INPUT_ARTIFACTS" -type f
+
+# protoc Plugins
+PROTOC_PLUGINS_ZIPPED_PACKAGES=$(mktemp -d)
+for zip_dir in protoc_windows_{x86,x64}
+do
+  zip -jr "$PROTOC_PLUGINS_ZIPPED_PACKAGES/$zip_dir.zip" "$INPUT_ARTIFACTS/$zip_dir/"*
+done
+for tar_dir in protoc_{linux,macos}_{x86,x64}
+do
+  chmod +x "$INPUT_ARTIFACTS/$tar_dir"/*
+  tar -cvzf "$PROTOC_PLUGINS_ZIPPED_PACKAGES/$tar_dir.tar.gz" -C "$INPUT_ARTIFACTS/$tar_dir" .
+done
+
+PROTOC_PACKAGES=(
+  "$PROTOC_PLUGINS_ZIPPED_PACKAGES"/protoc_windows_{x86,x64}.zip
+  "$PROTOC_PLUGINS_ZIPPED_PACKAGES"/protoc_{linux,macos}_{x86,x64}.tar.gz
 )
 
-PHP_PACKAGES=(
-  "$LINUX_PACKAGES"/grpc-[0-9]*.tgz
-)
-
-RUBY_PACKAGES=(
-  "$LINUX_PACKAGES"/grpc-[0-9]*.gem
-  "$LINUX_PACKAGES"/grpc-tools-[0-9]*.gem
-)
-
+# C#
+UNZIPPED_CSHARP_PACKAGES=$(mktemp -d)
+unzip "$INPUT_ARTIFACTS/csharp_nugets_windows_dotnetcli.zip" -d "$UNZIPPED_CSHARP_PACKAGES"
 CSHARP_PACKAGES=(
-  "$WINDOWS_PACKAGES"/csharp_nugets_windows_dotnetcli.zip
+  "$UNZIPPED_CSHARP_PACKAGES"/*
+)
+
+# Python
+PYTHON_GRPCIO_PACKAGES=(
+  "$INPUT_ARTIFACTS"/grpcio-[0-9]*.tar.gz
+  "$INPUT_ARTIFACTS"/grpcio-[0-9]*.whl
+  "$INPUT_ARTIFACTS"/python_linux_extra_arm*/grpcio-[0-9]*.whl
+)
+PYTHON_GRPCIO_TOOLS_PACKAGES=(
+  "$INPUT_ARTIFACTS"/grpcio-tools-[0-9]*.tar.gz
+  "$INPUT_ARTIFACTS"/grpcio_tools-[0-9]*.whl
+  "$INPUT_ARTIFACTS"/python_linux_extra_arm*/grpcio_tools-[0-9]*.whl
+)
+PYTHON_GRPCIO_HEALTH_CHECKING_PACKAGES=(
+  "$INPUT_ARTIFACTS"/grpcio-health-checking-[0-9]*.tar.gz
+)
+PYTHON_GRPCIO_REFLECTION_PACKAGES=(
+  "$INPUT_ARTIFACTS"/grpcio-reflection-[0-9]*.tar.gz
+)
+PYTHON_GRPCIO_TESTING_PACKAGES=(
+  "$INPUT_ARTIFACTS"/grpcio-testing-[0-9]*.tar.gz
+)
+
+# PHP
+PHP_PACKAGES=(
+  "$INPUT_ARTIFACTS"/grpc-[0-9]*.tgz
+)
+
+# Ruby
+RUBY_PACKAGES=(
+  "$INPUT_ARTIFACTS"/grpc-[0-9]*.gem
+  "$INPUT_ARTIFACTS"/grpc-tools-[0-9]*.gem
 )
 
 function add_to_manifest() {
-  local xml_type=$1
-  local xml_name
-  xml_name=$(basename "$2")
-  local xml_sha256
-  xml_sha256=$(openssl sha256 -r "$2" | cut -d " " -f 1)
-  cp "$2" "$BUILD_ROOT"
-  echo "<artifact type='$xml_type' name='$xml_name' sha256='$xml_sha256' />"
+  local artifact_type=$1
+  local artifact_file=$2
+  local artifact_prefix=$3
+  local artifact_name
+  artifact_name=$(basename "$artifact_file")
+  local artifact_size
+  artifact_size=$(stat -c%s "$artifact_file")
+  local artifact_sha256
+  artifact_sha256=$(openssl sha256 -r "$artifact_file" | cut -d " " -f 1)
+  local artifact_target=$LOCAL_BUILD_ROOT/$artifact_type/$artifact_prefix
+  mkdir -p "$artifact_target"
+  cp "$artifact_file" "$artifact_target"
+  cat <<EOF
+    <artifact name='$artifact_name'
+              type='$artifact_type'
+              path='$artifact_type/$artifact_prefix$artifact_name'
+              size='$artifact_size'
+              sha256='$artifact_sha256' />
+EOF
 }
 
-mkdir -p "$BUILD_ROOT"
-
 {
   cat <<EOF
 <?xml version="1.0"?>
-<?xml-stylesheet href="/web-assets/build.xsl" type="text/xsl"?>
+<?xml-stylesheet href="/web-assets/build-201807.xsl" type="text/xsl"?>
+<build id='$BUILD_ID' timestamp='$BUILD_TIMESTAMP' version="201807">
+  <metadata>
+    <project>gRPC</project>
+    <repository>https://github.com/grpc/grpc</repository>
+    <branch>$BUILD_BRANCH_NAME</branch>
+    <commit>$BUILD_GIT_COMMIT</commit>
+  </metadata>
+  <artifacts>
 EOF
-  echo "<build id='$ARCHIVE_UUID' timestamp='$ARCHIVE_TIMESTAMP'>"
-  echo "<metadata>"
-  echo "<branch>$GIT_BRANCH_NAME</branch>"
-  echo "<commit>$GIT_COMMIT</commit>"
-  echo "</metadata><artifacts>"
 
-  for pkg in "${PYTHON_PACKAGES[@]}"; do add_to_manifest python "$pkg"; done
+  for pkg in "${PROTOC_PACKAGES[@]}"; do add_to_manifest protoc "$pkg"; done
   for pkg in "${CSHARP_PACKAGES[@]}"; do add_to_manifest csharp "$pkg"; done
   for pkg in "${PHP_PACKAGES[@]}"; do add_to_manifest php "$pkg"; done
+  for pkg in "${PYTHON_GRPCIO_PACKAGES[@]}"; do add_to_manifest python "$pkg" grpcio/; done
+  for pkg in "${PYTHON_GRPCIO_TOOLS_PACKAGES[@]}"; do add_to_manifest python "$pkg" grpcio-tools/; done
+  for pkg in "${PYTHON_GRPCIO_HEALTH_CHECKING_PACKAGES[@]}"; do add_to_manifest python "$pkg" grpcio-health-checking/; done
+  for pkg in "${PYTHON_GRPCIO_REFLECTION_PACKAGES[@]}"; do add_to_manifest python "$pkg" grpcio-reflection/; done
+  for pkg in "${PYTHON_GRPCIO_TESTING_PACKAGES[@]}"; do add_to_manifest python "$pkg" grpcio-testing/; done
   for pkg in "${RUBY_PACKAGES[@]}"; do add_to_manifest ruby "$pkg"; done
 
-  echo "</artifacts></build>"
-}> "$BUILD_ROOT/$MANIFEST_FILE"
+  cat <<EOF
+  </artifacts>
+</build>
+EOF
+}> "$LOCAL_BUILD_INDEX"
 
-BUILD_XML_SHA=$(openssl sha256 -r "$BUILD_ROOT/$MANIFEST_FILE" | cut -d " " -f 1)
+LOCAL_BUILD_INDEX_SIZE=$(stat -c%s "$LOCAL_BUILD_INDEX")
+LOCAL_BUILD_INDEX_SHA256=$(openssl sha256 -r "$LOCAL_BUILD_INDEX" | cut -d " " -f 1)
 
-PREV_HOME=$(mktemp old-XXXXX-$MANIFEST_FILE)
-NEW_HOME=$(mktemp new-XXXXX-$MANIFEST_FILE)
-gsutil cp "$GCS_ROOT/$MANIFEST_FILE" "$PREV_HOME"
+OLD_INDEX=$(mktemp)
+NEW_INDEX=$(mktemp)
+
+# Download the current /index.xml into $OLD_INDEX
+gsutil cp "$GCS_INDEX" "$OLD_INDEX"
 
 {
-  head --lines=4 "$PREV_HOME"
-  echo "<build id='$ARCHIVE_UUID' timestamp='$ARCHIVE_TIMESTAMP' branch='$GIT_BRANCH_NAME' commit='$GIT_COMMIT' manifest='archive/$RELATIVE_PATH/$MANIFEST_FILE' manifest-sha256='$BUILD_XML_SHA' />"
-  tail --lines=+5 "$PREV_HOME"
-}> "$NEW_HOME"
+  # we want to add an entry as the first child under <builds> tag
+  # we can get by without a real XML parser by rewriting the header,
+  # injecting our new tag, and then dumping the rest of the file as is.
+  cat <<EOF
+<?xml version="1.0"?>
+<?xml-stylesheet href="/web-assets/home.xsl" type="text/xsl"?>
+<packages>
+  <builds>
+    <build id='$BUILD_ID'
+           timestamp='$BUILD_TIMESTAMP'
+           branch='$BUILD_BRANCH_NAME'
+           commit='$BUILD_GIT_COMMIT'
+           path='$GCS_ARCHIVE_PREFIX$BUILD_RELPATH$INDEX_FILENAME'
+           size='$LOCAL_BUILD_INDEX_SIZE'
+           sha256='$LOCAL_BUILD_INDEX_SHA256' />
+EOF
+  tail --lines=+5 "$OLD_INDEX"
+}> "$NEW_INDEX"
 
-gsutil -m cp -r "$UPLOAD_ROOT" "$GCS_ROOT/archive"
-gsutil -h "Content-Type:application/xml" cp "$NEW_HOME" "$GCS_ROOT/$MANIFEST_FILE"
 
+function generate_directory_index()
+{
+  local target_dir=$1
+  local current_directory_name
+  current_directory_name=$(basename "$target_dir")
+  cat <<EOF
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <title>Index of $current_directory_name - packages.grpc.io</title>
+    <link rel="stylesheet" type="text/css" href="/web-assets/dirindex.css" />
+  </head>
+  <body>
+    <h1>Index of <a href="#"><code>$current_directory_name</code></a></h1>
+    <ul>
+      <li><a href="#">.</a></li>
+      <li><a href="..">..</a></li>
+EOF
+
+(
+  cd "$target_dir"
+  find * -maxdepth 0 -type d -print | sort | while read -r line
+  do
+    echo "      <li><a href='$line/'>$line/</a></li>"
+  done
+  find * -maxdepth 0 -type f -print | sort | while read -r line
+  do
+    echo "      <li><a href='$line'>$line</a></li>"
+  done
+)
+
+cat <<EOF
+    </ul>
+  </body>
+</html>
+EOF
+}
+
+# Upload the current build artifacts
+gsutil -m cp -r "$LOCAL_STAGING_TEMPDIR/${BUILD_RELPATH%%/*}" "$GCS_ARCHIVE_ROOT"
+# Upload directory indicies for subdirectories
+(
+  cd "$LOCAL_BUILD_ROOT"
+  find * -type d | while read -r directory
+  do
+    generate_directory_index "$directory" | gsutil -h 'Content-Type:text/html' cp - "$GCS_ARCHIVE_ROOT$BUILD_RELPATH$directory/$INDEX_FILENAME"
+  done
+)
+# Upload the new /index.xml
+gsutil -h "Content-Type:application/xml" cp "$NEW_INDEX" "$GCS_INDEX"
diff --git a/tools/package_hosting/404.html b/tools/package_hosting/404.html
new file mode 100644
index 0000000..44d986c
--- /dev/null
+++ b/tools/package_hosting/404.html
@@ -0,0 +1 @@
+404 Not Found
diff --git a/tools/package_hosting/build-201807.xsl b/tools/package_hosting/build-201807.xsl
new file mode 100644
index 0000000..69a1904
--- /dev/null
+++ b/tools/package_hosting/build-201807.xsl
@@ -0,0 +1,114 @@
+<?xml version="1.0"?>                
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+
+<xsl:template match="//build">
+    <html>
+      <head> 
+        <title>Artifacts for gRPC Build <xsl:value-of select="@id"/> </title>
+        <link rel="stylesheet" type="text/css" href="/web-assets/style.css" />
+        <link rel="apple-touch-icon" href="/web-assets/favicons/apple-touch-icon.png" sizes="180x180" />
+        <link rel="icon" type="image/png" href="/web-assets/favicons/android-chrome-192x192.png" sizes="192x192" />
+        <link rel="icon" type="image/png" href="/web-assets/favicons/favicon-32x32.png" sizes="32x32" />
+        <link rel="icon" type="image/png" href="/web-assets/favicons/favicon-16x16.png" sizes="16x16" />
+        <link rel="manifest" href="/web-assets/favicons/manifest.json" />
+        <link rel="mask-icon" href="/web-assets/favicons/safari-pinned-tab.svg" color="#2DA6B0" />
+        <meta name="msapplication-TileColor" content="#ffffff" />
+        <meta name="msapplication-TileImage" content="/web-assets/favicons/mstile-150x150.png" />
+
+        <meta name="og:title" content="gRPC Package Build"/>
+        <meta name="og:image" content="https://grpc.io/img/grpc_square_reverse_4x.png"/>
+        <meta name="og:description" content="gRPC Package Build"/>
+     </head>
+     <body bgcolor="#ffffff">
+     <div id="topbar">
+      <span class="title">Artifacts for gRPC Build <xsl:value-of select="@id"/></span>
+     </div>
+     <div id="main">
+      <div id="metadata">
+       <span class="fieldname">Build: </span> <a href='#'><xsl:value-of select="@id"/></a>
+       [<a href="https://source.cloud.google.com/results/invocations/{@id}">invocation</a>]<br />
+      <span class="fieldname">Timestamp: </span>
+        <xsl:value-of select="@timestamp"/> <br />
+       <span class="fieldname">Branch: </span>
+       <a href="https://github.com/grpc/grpc/tree/{./metadata/branch[text()]}">
+        <xsl:value-of select="./metadata/branch[text()]" />
+       </a><br />
+       <span class="fieldname">Commit: </span>
+       <a href="https://github.com/grpc/grpc/tree/{./metadata/commit[text()]}">
+        <xsl:value-of select="./metadata/commit[text()]" /><br /></a>
+      </div>
+      <xsl:apply-templates select="artifacts" />
+      <br />
+      <br />
+
+      <p class="description"><a href="https://grpc.io">gRPC</a> is a <a href="https://www.cncf.io" class="external">Cloud Native Computing Foundation</a> project. <a href="https://policies.google.com/privacy" class="external">Privacy Policy</a>.</p>
+      <p class="description">
+      Copyright &#169;&#160;<xsl:value-of select="substring(@timestamp, 1, 4)" />&#160;<a href="https://github.com/grpc/grpc/blob/{./metadata/commit[text()]}/AUTHORS">The gRPC Authors</a></p>
+      <br />
+      <br />
+      </div>
+     </body>
+    </html>
+</xsl:template>
+
+<xsl:template match="artifacts">
+<h2> gRPC <code>protoc</code> Plugins </h2>
+<table>
+<xsl:apply-templates select="artifact[@type='protoc']">
+    <xsl:sort select="artifact/@name" />
+  </xsl:apply-templates>
+</table>
+
+<h2> C# </h2>
+<table>
+<xsl:apply-templates select="artifact[@type='csharp']">
+    <xsl:sort select="artifact/@name" />
+  </xsl:apply-templates>
+</table>
+
+<h2> PHP </h2>
+<table>
+<xsl:apply-templates select="artifact[@type='php']">
+    <xsl:sort select="artifact/@name" />
+  </xsl:apply-templates>
+</table>
+
+<h2> Python </h2>
+<script type="text/javascript">
+// <![CDATA[
+var pythonRepoLink = document.createElement("a");
+pythonRepoLink.href = './python';
+var pythonRepo = pythonRepoLink.href;
+document.write("<p><code>" +
+"$ pip install --pre --upgrade --force-reinstall --extra-index-url \\<br />" +
+"&nbsp;&nbsp;&nbsp;&nbsp;<a href='" +  pythonRepo + "'>" + pythonRepo + "</a> \\<br />" +
+"&nbsp;&nbsp;&nbsp;&nbsp;grpcio grpcio-{tools,health-checking,reflection,testing}</code></p>");
+// ]]>
+</script>
+<table>
+  <xsl:apply-templates select="artifact[@type='python']">
+    <xsl:sort select="artifact/@name" />
+  </xsl:apply-templates>
+</table>
+
+<h2> Ruby </h2>
+<table>
+<xsl:apply-templates select="artifact[@type='ruby']">
+    <xsl:sort select="artifact/@name" />
+  </xsl:apply-templates>
+</table>
+
+</xsl:template>
+
+
+<xsl:template match="artifact">
+<tr>
+<td class="name"> <a href="{@path}"><xsl:value-of select="@name" /></a> </td>
+<td class="hash"> <xsl:value-of select="@sha256"/> </td>
+</tr>
+</xsl:template>
+
+<xsl:template match="metadata">
+</xsl:template>
+
+</xsl:stylesheet>
diff --git a/tools/package_hosting/dirindex.css b/tools/package_hosting/dirindex.css
new file mode 100644
index 0000000..6c4b04e
--- /dev/null
+++ b/tools/package_hosting/dirindex.css
@@ -0,0 +1,16 @@
+ul {
+  list-style-type: none;
+}
+a{
+  text-decoration: none;
+}
+a:hover {
+  text-decoration: underline;
+}
+ul li a {
+  font-family: 'SF Mono', 'Menlo', 'Monaco', 'Consolas', 'Courier New', Courier, monospace
+}
+h1 {
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif
+}
+
diff --git a/tools/package_hosting/home.xsl b/tools/package_hosting/home.xsl
new file mode 100644
index 0000000..3f79303
--- /dev/null
+++ b/tools/package_hosting/home.xsl
@@ -0,0 +1,86 @@
+<?xml version="1.0"?>
+<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+
+<xsl:template match="//packages">
+  <html>
+    <head>
+      <title>gRPC Packages</title>
+      <link rel="stylesheet" type="text/css" href="/web-assets/style.css" />
+      <link rel="apple-touch-icon" href="/web-assets/favicons/apple-touch-icon.png" sizes="180x180" />
+      <link rel="icon" type="image/png" href="/web-assets/favicons/android-chrome-192x192.png" sizes="192x192" />
+      <link rel="icon" type="image/png" href="/web-assets/favicons/favicon-32x32.png" sizes="32x32" />
+      <link rel="icon" type="image/png" href="/web-assets/favicons/favicon-16x16.png" sizes="16x16" />
+      <link rel="manifest" href="/web-assets/favicons/manifest.json" />
+      <link rel="mask-icon" href="/web-assets/favicons/safari-pinned-tab.svg" color="#2DA6B0" />
+      <meta name="msapplication-TileColor" content="#ffffff" />
+      <meta name="msapplication-TileImage" content="/web-assets/favicons/mstile-150x150.png" />
+      <meta name="og:title" content="gRPC Packages"/>
+      <meta name="og:image" content="https://grpc.io/img/grpc_square_reverse_4x.png"/>
+      <meta name="og:description" content="gRPC Packages"/>
+    </head>
+    <body bgcolor="#ffffff">
+      <div id="topbar">
+        <span class="title">gRPC Packages</span>
+      </div>
+      <div id="main">
+        <xsl:apply-templates select="releases" />
+        <xsl:apply-templates select="builds" />
+        <br />
+        <br />
+        <p class="description"><a href="https://grpc.io">gRPC</a> is a <a href="https://www.cncf.io" class="external">Cloud Native Computing Foundation</a> project. <a href="https://policies.google.com/privacy" class="external">Privacy Policy</a>.</p>
+        <p class="description">Copyright &#169; 2018 <a href="https://github.com/grpc/grpc/blob/master/AUTHORS">The gRPC Authors</a></p>
+      </div>
+    </body>
+  </html>
+</xsl:template>
+
+<xsl:template match="releases">
+  <h2>Official gRPC Releases</h2>
+  <p>Commits corresponding to  <a href="https://github.com/grpc/grpc/releases">official gRPC release points and release candidates</a> are tagged on GitHub.</p>
+  <p>To maximize usability, gRPC supports the standard way of adding dependencies in your language of choice (if there is one).
+  In most languages, the gRPC runtime comes in form of a package available in your language's package manager.</p>
+  <p>For instructions on how to use the language-specific gRPC runtime in your project, please refer to the following:</p>
+  <ul>
+    <li><a href="https://github.com/grpc/grpc/blob/master/src/cpp">C++</a>: follow the instructions under the <a href="https://github.com/grpc/grpc/tree/master/src/cpp"><code>src/cpp</code> directory</a></li>
+    <li><a href="https://github.com/grpc/grpc/blob/master/src/csharp">C#</a>: NuGet package <code>Grpc</code></li>
+    <li><a href="https://github.com/grpc/grpc-dart">Dart</a>: pub package <code>grpc</code></li>
+    <li><a href="https://github.com/grpc/grpc-go">Go</a>: <code>go get google.golang.org/grpc</code></li>
+    <li><a href="https://github.com/grpc/grpc-java">Java</a>: Use JARs from <a href="https://mvnrepository.com/artifact/io.grpc">gRPC Maven Central Repository</a></li>
+    <li><a href="https://github.com/grpc/grpc-node">Node</a>: <code>npm install grpc</code></li>
+    <li><a href="https://github.com/grpc/grpc/blob/master/src/objective-c">Objective-C</a>: Add <code>gRPC-ProtoRPC</code> dependency to podspec</li>
+    <li><a href="https://github.com/grpc/grpc/blob/master/src/php">PHP</a>: <code>pecl install grpc</code></li>
+    <li><a href="https://github.com/grpc/grpc/blob/master/src/python/grpcio">Python</a>: <code>pip install grpcio</code></li>
+    <li><a href="https://github.com/grpc/grpc/blob/master/src/ruby">Ruby</a>: <code>gem install grpc</code></li>
+    <li><a href="https://github.com/grpc/grpc-web">WebJS</a>: follow the <a href="https://github.com/grpc/grpc-web">instructions in <code>grpc-web</code> repository</a></li>
+  </ul>
+</xsl:template>
+
+<xsl:template match="builds">
+  <h2> Daily Builds of <a href="https://github.com/grpc/grpc/tree/master"><code>master</code></a> Branch</h2>
+  <p>gRPC packages are built on a daily basis at the <code>HEAD</code> of <a href="https://github.com/grpc/grpc/tree/master">the <code>master</code> branch</a> and are archived here.</p>
+  <p>
+    <a href="#">The current document</a> (view source) is an XML feed pointing to the packages as they get built and uploaded.
+    You can subscribe to this feed and fetch, deploy, and test the precompiled packages with your continuous integration infrastructure.
+  </p>
+  <p>For stable release packages, please consult the above section and the common package manager for your language.</p>
+  <table style="border:solid black 1px">
+    <tr style="background-color:lightgray">
+      <td>Timestamp</td>
+      <td>Commit</td>
+      <td>Build ID</td>
+    </tr>
+    <xsl:apply-templates select="build[@branch='master']">
+      <xsl:sort select="@timestamp" data-type="text" order="descending" />
+    </xsl:apply-templates>
+  </table>
+</xsl:template>
+
+<xsl:template match="build">
+  <tr>
+    <td class="name"><xsl:value-of select="@timestamp" /></td>
+    <td class="name"> <a href="https://github.com/grpc/grpc/tree/{@commit}"><xsl:value-of select="@commit" /></a></td>
+    <td class="name"> <a href="{@path}"><xsl:value-of select="@id" /></a></td>
+  </tr>
+</xsl:template>
+
+</xsl:stylesheet>
diff --git a/tools/package_hosting/style.css b/tools/package_hosting/style.css
new file mode 100644
index 0000000..dbd26d5
--- /dev/null
+++ b/tools/package_hosting/style.css
@@ -0,0 +1,76 @@
+html, body
+{
+    margin: 0;
+    font-family: sans-serif;
+}
+
+a, a:visited, a:link, a:active {
+    color: #2da6b0;
+    text-decoration: none;
+}
+
+a:hover {
+    color: #2da6b0;
+    text-decoration: underline;
+}
+
+#topbar {
+    background-color: #2da6b0;
+    height: 60px;
+    margin:auto;
+}
+
+#topbar .title {
+    position: relative;
+    top: 24px;
+    left: 24px;
+    color: white;
+    font-family: sans-serif;
+    font-weight: bold;
+}
+
+#main {
+    max-width:1100px;
+    margin:auto;
+}
+
+#main h2 {
+    text-align: left;
+}
+
+#main table {
+    width:100%;
+    border-collapse: collapse;
+    font-size: small;
+    font-family: 'SF Mono', 'Menlo', 'Monaco', 'Courier New', Courier, monospace;
+}
+#main table tr td {
+    border: solid black 1px;
+    padding: 5px;
+}
+
+#main table tr td.hash {
+    text-align: right;
+    border-left: none;
+    font-size: x-small;
+}
+
+#main table tr td.name {
+    text-align: left;
+    border-right: none;
+}
+
+p.description
+{
+    font-size: smaller;
+}
+
+#metadata {
+    margin-top: 15px;
+    padding: 15px;
+    font-family: 'SF Mono', 'Menlo', 'Monaco', 'Courier New', Courier, monospace;
+}
+
+#metadata span.fieldname {
+    font-family: sans-serif;
+}
\ No newline at end of file
diff --git a/tools/package_hosting/upload_web_assets.sh b/tools/package_hosting/upload_web_assets.sh
new file mode 100755
index 0000000..dcf258e
--- /dev/null
+++ b/tools/package_hosting/upload_web_assets.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+# Copyright 2018 The gRPC Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set -ex
+
+cd "$(dirname "$0")"
+
+GCS_WEB_ASSETS=gs://packages.grpc.io/web-assets/
+
+WEB_ASSETS=(
+  404.html
+  build-201807.xsl
+  dirindex.css
+  home.xsl
+  style.css
+)
+
+gsutil -m cp "${WEB_ASSETS[@]}" "$GCS_WEB_ASSETS"
diff --git a/tools/run_tests/artifacts/artifact_targets.py b/tools/run_tests/artifacts/artifact_targets.py
index edde3ea..4500b22 100644
--- a/tools/run_tests/artifacts/artifact_targets.py
+++ b/tools/run_tests/artifacts/artifact_targets.py
@@ -212,11 +212,15 @@
 class CSharpExtArtifact:
     """Builds C# native extension library"""
 
-    def __init__(self, platform, arch):
+    def __init__(self, platform, arch, arch_abi=None):
         self.name = 'csharp_ext_%s_%s' % (platform, arch)
         self.platform = platform
         self.arch = arch
+        self.arch_abi = arch_abi
         self.labels = ['artifact', 'csharp', platform, arch]
+        if arch_abi:
+            self.name += '_%s' % arch_abi
+            self.labels.append(arch_abi)
 
     def pre_build_jobspecs(self):
         return []
@@ -227,7 +231,9 @@
                 self.name,
                 'tools/dockerfile/grpc_artifact_android_ndk',
                 'tools/run_tests/artifacts/build_artifact_csharp_android.sh',
-                environ={})
+                environ={
+                    'ANDROID_ABI': self.arch_abi
+                })
         elif self.platform == 'windows':
             cmake_arch_option = 'Win32' if self.arch == 'x86' else self.arch
             return create_jobspec(
@@ -348,7 +354,8 @@
         for Cls in (CSharpExtArtifact, ProtocArtifact)
         for platform in ('linux', 'macos', 'windows') for arch in ('x86', 'x64')
     ] + [
-        CSharpExtArtifact('linux', 'android'),
+        CSharpExtArtifact('linux', 'android', arch_abi='arm64-v8a'),
+        CSharpExtArtifact('linux', 'android', arch_abi='armeabi-v7a'),
         PythonArtifact('linux', 'x86', 'cp27-cp27m'),
         PythonArtifact('linux', 'x86', 'cp27-cp27mu'),
         PythonArtifact('linux', 'x86', 'cp34-cp34m'),
diff --git a/tools/run_tests/artifacts/build_artifact_csharp_android.sh b/tools/run_tests/artifacts/build_artifact_csharp_android.sh
index ba598e7..067eb30 100755
--- a/tools/run_tests/artifacts/build_artifact_csharp_android.sh
+++ b/tools/run_tests/artifacts/build_artifact_csharp_android.sh
@@ -17,6 +17,7 @@
 
 cd "$(dirname "$0")/../../.."
 
+# ANDROID_ABI is set by the job definition in artifact_targets.py
 src/csharp/experimental/build_native_ext_for_android.sh
 
 mkdir -p "${ARTIFACTS_OUT}"
diff --git a/tools/run_tests/generated/sources_and_headers.json b/tools/run_tests/generated/sources_and_headers.json
index bf3ddb4..072402b 100644
--- a/tools/run_tests/generated/sources_and_headers.json
+++ b/tools/run_tests/generated/sources_and_headers.json
@@ -7761,6 +7761,7 @@
       "test/cpp/qps/histogram.h", 
       "test/cpp/qps/interarrival.h", 
       "test/cpp/qps/parse_json.h", 
+      "test/cpp/qps/qps_server_builder.h", 
       "test/cpp/qps/qps_worker.h", 
       "test/cpp/qps/report.h", 
       "test/cpp/qps/server.h", 
@@ -7782,6 +7783,8 @@
       "test/cpp/qps/interarrival.h", 
       "test/cpp/qps/parse_json.cc", 
       "test/cpp/qps/parse_json.h", 
+      "test/cpp/qps/qps_server_builder.cc", 
+      "test/cpp/qps/qps_server_builder.h", 
       "test/cpp/qps/qps_worker.cc", 
       "test/cpp/qps/qps_worker.h", 
       "test/cpp/qps/report.cc",