First implementation of protoc-gen-upbdev plugin

PiperOrigin-RevId: 454243195
diff --git a/BUILD b/BUILD
index 4616a49..29a9962 100644
--- a/BUILD
+++ b/BUILD
@@ -129,6 +129,26 @@
 )
 
 cc_library(
+    name = "mini_descriptor",
+    srcs = [
+        "upb/mini_descriptor.c",
+    ],
+    hdrs = [
+        "upb/mini_descriptor.h",
+    ],
+    copts = UPB_DEFAULT_COPTS,
+    visibility = ["//visibility:public"],
+    deps = [
+        ":descriptor_upb_proto",
+        ":mini_table",
+        ":port",
+        ":reflection",
+        ":table",
+        ":upb",
+    ],
+)
+
+cc_library(
     name = "mini_table_internal",
     hdrs = ["upb/msg_internal.h"],
     deps = [
diff --git a/upb/mini_descriptor.c b/upb/mini_descriptor.c
new file mode 100644
index 0000000..8004964
--- /dev/null
+++ b/upb/mini_descriptor.c
@@ -0,0 +1,355 @@
+/*
+ * Copyright (c) 2009-2022, Google LLC
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of Google LLC nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL Google LLC BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "upb/mini_descriptor.h"
+
+#include <assert.h>
+#include <inttypes.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "upb/def.h"
+#include "upb/mini_table.h"
+
+// Must be last.
+#include "upb/port_def.inc"
+
+/* DescState ******************************************************************/
+
+// Manages the storage for mini descriptor strings as they are being encoded.
+// TODO(b/234740652): Move some of this state directly into the encoder, maybe.
+
+typedef struct {
+  upb_MtDataEncoder e;
+  size_t bufsize;
+  char* buf;
+  char* ptr;
+} DescState;
+
+static void upb_DescState_Init(DescState* d) {
+  d->bufsize = kUpb_MtDataEncoder_MinSize * 2;
+  d->buf = NULL;
+  d->ptr = NULL;
+}
+
+static bool upb_DescState_Grow(DescState* d, upb_Arena* a) {
+  const size_t oldbufsize = d->bufsize;
+  const int used = d->ptr - d->buf;
+
+  if (!d->buf) {
+    d->buf = upb_Arena_Malloc(a, d->bufsize);
+    if (!d->buf) return false;
+    d->ptr = d->buf;
+    d->e.end = d->buf + d->bufsize;
+  }
+
+  if (oldbufsize - used < kUpb_MtDataEncoder_MinSize) {
+    d->bufsize *= 2;
+    d->buf = upb_Arena_Realloc(a, d->buf, oldbufsize, d->bufsize);
+    if (!d->buf) return false;
+    d->ptr = d->buf + used;
+    d->e.end = d->buf + d->bufsize;
+  }
+
+  return true;
+}
+
+static void upb_DescState_Emit(const DescState* d, upb_StringView* str) {
+  *str = upb_StringView_FromDataAndSize(d->buf, d->ptr - d->buf);
+}
+
+/******************************************************************************/
+
+// Type and Field accessors.
+
+static inline bool upb_Type_IsPackable(upb_FieldType type) {
+  return (type != kUpb_FieldType_String && type != kUpb_FieldType_Group &&
+          type != kUpb_FieldType_Message && type != kUpb_FieldType_Bytes);
+}
+
+static inline bool upb_Field_IsOneof(const google_protobuf_FieldDescriptorProto* f) {
+  return google_protobuf_FieldDescriptorProto_has_oneof_index(f);
+}
+
+static inline bool upb_Field_IsOptional(const google_protobuf_FieldDescriptorProto* f) {
+  const upb_Label label = google_protobuf_FieldDescriptorProto_label(f);
+  return label == kUpb_Label_Optional;
+}
+
+static inline bool upb_Field_IsRepeated(const google_protobuf_FieldDescriptorProto* f) {
+  const upb_Label label = google_protobuf_FieldDescriptorProto_label(f);
+  return label == kUpb_Label_Repeated;
+}
+
+static inline bool upb_Field_IsRequired(const google_protobuf_FieldDescriptorProto* f) {
+  const upb_Label label = google_protobuf_FieldDescriptorProto_label(f);
+  return label == kUpb_Label_Required;
+}
+
+static inline bool upb_Field_IsPackable(const google_protobuf_FieldDescriptorProto* f) {
+  if (!upb_Field_IsRepeated(f)) return false;
+
+  const upb_FieldType type = google_protobuf_FieldDescriptorProto_type(f);
+  return upb_Type_IsPackable(type);
+}
+
+static bool upb_Field_IsPacked(const google_protobuf_FieldDescriptorProto* f,
+                               upb_Syntax syntax) {
+  if (!upb_Field_IsPackable(f)) return false;
+
+  const bool has_options = google_protobuf_FieldDescriptorProto_has_options(f);
+  const google_protobuf_FieldOptions* options = google_protobuf_FieldDescriptorProto_options(f);
+
+  switch (syntax) {
+    case kUpb_Syntax_Proto2:
+      if (!has_options) return false;
+      break;
+
+    default:
+      if (!has_options) return true;
+      if (!google_protobuf_FieldOptions_has_packed(options)) return true;
+      break;
+  }
+
+  return google_protobuf_FieldOptions_packed(options);
+}
+
+static inline int Field_OneofIndex(const google_protobuf_FieldDescriptorProto* f) {
+  return google_protobuf_FieldDescriptorProto_oneof_index(f);
+}
+
+static bool upb_Field_HasPresence(const google_protobuf_FieldDescriptorProto* f,
+                                  upb_Syntax syntax) {
+  if (upb_Field_IsRepeated(f)) return false;
+
+  const upb_FieldType type = google_protobuf_FieldDescriptorProto_type(f);
+  return type == kUpb_FieldType_Message || type == kUpb_FieldType_Group ||
+         upb_Field_IsOneof(f) || syntax == kUpb_Syntax_Proto2;
+}
+
+uint64_t upb_Field_Modifier(const google_protobuf_FieldDescriptorProto* f,
+                            upb_Syntax syntax) {
+  uint64_t out = 0;
+  if (upb_Field_IsRepeated(f)) {
+    out |= kUpb_FieldModifier_IsRepeated;
+  }
+  if (upb_Field_IsPacked(f, syntax)) {
+    out |= kUpb_FieldModifier_IsPacked;
+  }
+  if (google_protobuf_FieldDescriptorProto_type(f) == kUpb_FieldType_Enum &&
+      syntax == kUpb_Syntax_Proto2) {
+    out |= kUpb_FieldModifier_IsClosedEnum;
+  }
+  if (upb_Field_IsOptional(f) && !upb_Field_HasPresence(f, syntax)) {
+    out |= kUpb_FieldModifier_IsProto3Singular;
+  }
+  if (upb_Field_IsRequired(f)) {
+    out |= kUpb_FieldModifier_IsRequired;
+  }
+  return out;
+}
+
+/******************************************************************************/
+
+// Sort by enum value.
+static int upb_MiniDescriptor_CompareEnums(const void* a, const void* b) {
+  const google_protobuf_EnumValueDescriptorProto* A = *(void**)a;
+  const google_protobuf_EnumValueDescriptorProto* B = *(void**)b;
+  if ((uint32_t)google_protobuf_EnumValueDescriptorProto_number(A) <
+      (uint32_t)google_protobuf_EnumValueDescriptorProto_number(B))
+    return -1;
+  if ((uint32_t)google_protobuf_EnumValueDescriptorProto_number(A) >
+      (uint32_t)google_protobuf_EnumValueDescriptorProto_number(B))
+    return 1;
+  return 0;
+}
+
+// Sort by field number.
+static int upb_MiniDescriptor_CompareFields(const void* a, const void* b) {
+  const google_protobuf_FieldDescriptorProto* A = *(void**)a;
+  const google_protobuf_FieldDescriptorProto* B = *(void**)b;
+  if (google_protobuf_FieldDescriptorProto_number(A) <
+      google_protobuf_FieldDescriptorProto_number(B))
+    return -1;
+  if (google_protobuf_FieldDescriptorProto_number(A) >
+      google_protobuf_FieldDescriptorProto_number(B))
+    return 1;
+  return 0;
+}
+
+// Sort first by oneof index then by field number.
+static int upb_MiniDescriptor_CompareOneofs(const void* a, const void* b) {
+  const google_protobuf_FieldDescriptorProto* A = *(void**)a;
+  const google_protobuf_FieldDescriptorProto* B = *(void**)b;
+  const int indexA = upb_Field_IsOneof(A) ? Field_OneofIndex(A) : -1;
+  const int indexB = upb_Field_IsOneof(B) ? Field_OneofIndex(B) : -1;
+  if (indexA < indexB) return -1;
+  if (indexA > indexB) return 1;
+  if (google_protobuf_FieldDescriptorProto_number(A) <
+      google_protobuf_FieldDescriptorProto_number(B))
+    return -1;
+  if (google_protobuf_FieldDescriptorProto_number(A) >
+      google_protobuf_FieldDescriptorProto_number(B))
+    return 1;
+  return 0;
+}
+
+upb_StringView upb_MiniDescriptor_EncodeEnum(
+    const google_protobuf_EnumDescriptorProto* enum_type, upb_Arena* a) {
+  upb_StringView out;
+  out.data = NULL;
+  out.size = 0;
+
+  size_t len = 0;
+  const google_protobuf_EnumValueDescriptorProto* const* value_types =
+      google_protobuf_EnumDescriptorProto_value(enum_type, &len);
+
+  // Copy and sort.
+  google_protobuf_EnumValueDescriptorProto** sorted = upb_gmalloc(len * sizeof(void*));
+  if (!sorted) goto err;
+  memcpy(sorted, value_types, len * sizeof(void*));
+  qsort(sorted, len, sizeof(void*), upb_MiniDescriptor_CompareEnums);
+
+  DescState s;
+  upb_DescState_Init(&s);
+
+  upb_MtDataEncoder_StartEnum(&s.e);
+
+  for (size_t i = 0; i < len; i++) {
+    if (!upb_DescState_Grow(&s, a)) goto err;
+    const uint32_t number = google_protobuf_EnumValueDescriptorProto_number(sorted[i]);
+    s.ptr = upb_MtDataEncoder_PutEnumValue(&s.e, s.ptr, number);
+    UPB_ASSERT(s.ptr);
+  }
+
+  if (!upb_DescState_Grow(&s, a)) goto err;
+  s.ptr = upb_MtDataEncoder_EndEnum(&s.e, s.ptr);
+  UPB_ASSERT(s.ptr);
+
+  upb_DescState_Emit(&s, &out);
+
+err:
+  if (sorted) upb_gfree(sorted);
+  return out;
+}
+
+upb_StringView upb_MiniDescriptor_EncodeExtension(
+    const google_protobuf_FieldDescriptorProto* extension_type, upb_Syntax syntax,
+    upb_Arena* a) {
+  upb_StringView out;
+  out.data = NULL;
+  out.size = 0;
+
+  DescState s;
+  upb_DescState_Init(&s);
+
+  if (!upb_DescState_Grow(&s, a)) goto err;
+  upb_MtDataEncoder_StartMessage(&s.e, s.ptr, 0);
+
+  const upb_FieldType type = google_protobuf_FieldDescriptorProto_type(extension_type);
+  const int number = google_protobuf_FieldDescriptorProto_number(extension_type);
+  const uint64_t modifier = upb_Field_Modifier(extension_type, syntax);
+  upb_MtDataEncoder_PutField(&s.e, s.ptr, type, number, modifier);
+
+  upb_DescState_Emit(&s, &out);
+
+err:
+  return out;
+}
+
+upb_StringView upb_MiniDescriptor_EncodeMessage(
+    const google_protobuf_DescriptorProto* message_type, upb_Syntax syntax,
+    upb_Arena* a) {
+  upb_StringView out;
+  out.data = NULL;
+  out.size = 0;
+
+  size_t len = 0;
+  const google_protobuf_FieldDescriptorProto* const* field_types =
+      google_protobuf_DescriptorProto_field(message_type, &len);
+
+  // Copy and sort.
+  google_protobuf_FieldDescriptorProto** sorted = upb_gmalloc(len * sizeof(void*));
+  if (!sorted) goto err;
+  memcpy(sorted, field_types, len * sizeof(void*));
+  qsort(sorted, len, sizeof(void*), upb_MiniDescriptor_CompareFields);
+
+  DescState s;
+  upb_DescState_Init(&s);
+
+  if (!upb_DescState_Grow(&s, a)) goto err;
+  upb_MtDataEncoder_StartMessage(&s.e, s.ptr, 0);
+
+  // Encode the fields.
+  size_t oneof_fields = 0;
+  for (size_t i = 0; i < len; i++) {
+    google_protobuf_FieldDescriptorProto* field_type = sorted[i];
+    if (upb_Field_IsOneof(field_type)) {
+      // Put all oneof fields at the beginning of the list for the next pass.
+      sorted[oneof_fields++] = field_type;
+    }
+
+    const upb_FieldType type = google_protobuf_FieldDescriptorProto_type(field_type);
+    const int number = google_protobuf_FieldDescriptorProto_number(field_type);
+    const uint64_t modifier = upb_Field_Modifier(field_type, syntax);
+
+    if (!upb_DescState_Grow(&s, a)) goto err;
+    s.ptr = upb_MtDataEncoder_PutField(&s.e, s.ptr, type, number, modifier);
+    UPB_ASSERT(s.ptr);
+  }
+
+  qsort(sorted, oneof_fields, sizeof(void*), upb_MiniDescriptor_CompareOneofs);
+
+  // Encode the oneofs.
+  int previous_index = -1;
+  for (size_t i = 0; i < oneof_fields; i++) {
+    google_protobuf_FieldDescriptorProto* field_type = sorted[i];
+    if (!upb_Field_IsOneof(field_type)) continue;
+
+    const int index = Field_OneofIndex(field_type);
+    if (previous_index != index) {
+      if (!upb_DescState_Grow(&s, a)) goto err;
+      s.ptr = upb_MtDataEncoder_StartOneof(&s.e, s.ptr);
+      UPB_ASSERT(s.ptr);
+
+      previous_index = index;
+    }
+
+    if (!upb_DescState_Grow(&s, a)) goto err;
+    s.ptr = upb_MtDataEncoder_PutOneofField(
+        &s.e, s.ptr, google_protobuf_FieldDescriptorProto_number(field_type));
+    UPB_ASSERT(s.ptr);
+  }
+
+  upb_DescState_Emit(&s, &out);
+
+err:
+  if (sorted) upb_gfree(sorted);
+  return out;
+}
diff --git a/upb/mini_descriptor.h b/upb/mini_descriptor.h
new file mode 100644
index 0000000..0a53a72
--- /dev/null
+++ b/upb/mini_descriptor.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2009-2022, Google LLC
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of Google LLC nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL Google LLC BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UPB_MINI_DESCRIPTOR_H_
+#define UPB_MINI_DESCRIPTOR_H_
+
+#include "upb/def.h"
+#include "upb/upb.h"
+
+// Must be last.
+#include "upb/port_def.inc"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** upb_MiniDescriptor ********************************************************/
+
+upb_StringView upb_MiniDescriptor_EncodeEnum(
+    const google_protobuf_EnumDescriptorProto* enum_type, upb_Arena* a);
+
+upb_StringView upb_MiniDescriptor_EncodeExtension(
+    const google_protobuf_FieldDescriptorProto* extension_type, upb_Syntax syntax,
+    upb_Arena* a);
+
+upb_StringView upb_MiniDescriptor_EncodeMessage(
+    const google_protobuf_DescriptorProto* message_type, upb_Syntax syntax,
+    upb_Arena* a);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#include "upb/port_undef.inc"
+
+#endif /* UPB_MINI_DESCRIPTOR_H_ */
diff --git a/upbc/BUILD b/upbc/BUILD
index 0b98f29..d32ffa4 100644
--- a/upbc/BUILD
+++ b/upbc/BUILD
@@ -27,9 +27,45 @@
     "//bazel:build_defs.bzl",
     "UPB_DEFAULT_CPPOPTS",
 )
+load(
+    "//bazel:upb_proto_library.bzl",
+    "upb_proto_library",
+    "upb_proto_reflection_library",
+)
 
 licenses(["notice"])
 
+proto_library(
+    name = "code_generator_request",
+    srcs = ["code_generator_request.proto"],
+    visibility = ["//:friends"],
+    deps = ["@com_google_protobuf//:compiler_plugin_proto"],
+)
+
+upb_proto_library(
+    name = "code_generator_request_upb_proto",
+    visibility = ["//:friends"],
+    deps = [":code_generator_request"],
+)
+
+upb_proto_reflection_library(
+    name = "code_generator_request_upb_proto_reflection",
+    visibility = ["//:friends"],
+    deps = [":code_generator_request"],
+)
+
+upb_proto_library(
+    name = "plugin_upb_proto",
+    visibility = ["//:friends"],
+    deps = ["@com_google_protobuf//:compiler_plugin_proto"],
+)
+
+upb_proto_reflection_library(
+    name = "plugin_upb_proto_reflection",
+    visibility = ["//:friends"],
+    deps = ["@com_google_protobuf//:compiler_plugin_proto"],
+)
+
 cc_library(
     name = "common",
     srcs = ["common.cc"],
@@ -76,3 +112,29 @@
         "@com_google_protobuf//:protoc_lib",
     ],
 )
+
+cc_binary(
+    name = "protoc-gen-upbdev",
+    srcs = [
+        "code_generator_request.c",
+        "code_generator_request.h",
+        "protoc-gen-upbdev.cc",
+        "subprocess.cc",
+        "subprocess.h",
+    ],
+    copts = UPB_DEFAULT_CPPOPTS,
+    visibility = ["//visibility:public"],
+    deps = [
+        ":code_generator_request_upb_proto",
+        ":code_generator_request_upb_proto_reflection",
+        ":plugin_upb_proto",
+        ":plugin_upb_proto_reflection",
+        "//:json",
+        "//:mini_descriptor",
+        "//:mini_table",
+        "//:port",
+        "//:reflection",
+        "//:upb",
+        "@com_google_absl//absl/strings",
+    ],
+)
diff --git a/upbc/code_generator_request.c b/upbc/code_generator_request.c
new file mode 100644
index 0000000..95b8347
--- /dev/null
+++ b/upbc/code_generator_request.c
@@ -0,0 +1,287 @@
+/*
+ * Copyright (c) 2009-2022, Google LLC
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of Google LLC nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL Google LLC BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "upbc/code_generator_request.h"
+
+#include <assert.h>
+#include <inttypes.h>
+#include <setjmp.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "google/protobuf/compiler/plugin.upb.h"
+#include "upb/def.h"
+#include "upb/mini_descriptor.h"
+#include "upb/mini_table.h"
+
+// Must be last.
+#include "upb/port_def.inc"
+
+enum {
+  kErrArenaMalloc = 1,
+  kErrEnumName,
+  kErrExtensionName,
+  kErrFieldName,
+  kErrFilePackage,
+  kErrMapCollision,
+  kErrMiniDescriptorsSet,
+  kErrStateGrow,
+};
+
+/* upbc_PathState *************************************************************/
+
+// Manages the current fully qualified path name as we dig down into a proto.
+// Basically just a string that grows and shrinks like a stack.
+
+typedef struct {
+  size_t len;
+  char path[4000];  // TODO(salo): make this dynamic
+} upbc_PathState;
+
+static void upbc_PathState_Init(upbc_PathState* p) { p->len = 0; }
+
+static void upbc_PathState_Push(upbc_PathState* p, upb_StringView name) {
+  if (p->len) {
+    p->path[p->len++] = '.';
+  }
+  memcpy(&p->path[p->len], name.data, name.size);
+  p->len += name.size;
+}
+
+static void upbc_PathState_Pop(upbc_PathState* p, upb_StringView name) {
+  p->len -= name.size;
+  if (p->len) {
+    p->len--;
+  }
+}
+
+static upb_StringView upbc_PathState_String(const upbc_PathState* p) {
+  return upb_StringView_FromDataAndSize(p->path, p->len);
+}
+
+/******************************************************************************/
+
+// Kitchen sink storage for the mini descriptor state.
+
+typedef struct {
+  upb_Arena* a;
+  upb_Syntax syntax;
+
+  upbc_CodeGeneratorRequest* out;
+
+  jmp_buf err;
+
+  upbc_PathState path;
+} upbc_ScrapeState;
+
+static void upbc_ScrapeState_Init(upbc_ScrapeState* s, upb_Arena* a) {
+  s->a = a;
+
+  upbc_PathState_Init(&s->path);
+
+  s->out = upbc_CodeGeneratorRequest_new(a);
+  if (!s->out) UPB_LONGJMP(s->err, kErrArenaMalloc);
+}
+
+static void upbc_ScrapeState_Push(upbc_ScrapeState* s, upb_StringView name) {
+  upbc_PathState_Push(&s->path, name);
+
+  const upb_StringView key = upbc_PathState_String(&s->path);
+  if (upbc_CodeGeneratorRequest_mini_descriptors_get(s->out, key, NULL)) {
+    UPB_LONGJMP(s->err, kErrMapCollision);
+  }
+}
+
+static void upbc_ScrapeState_Pop(upbc_ScrapeState* s, upb_StringView name) {
+  upbc_PathState_Pop(&s->path, name);
+}
+
+static void upbc_ScrapeState_String(upbc_ScrapeState* s,
+                                    upb_StringView encoding) {
+  const upb_StringView path = upbc_PathState_String(&s->path);
+  bool ok = upbc_CodeGeneratorRequest_mini_descriptors_set(s->out, path,
+                                                           encoding, s->a);
+  if (!ok) UPB_LONGJMP(s->err, kErrMiniDescriptorsSet);
+}
+
+/******************************************************************************/
+
+// File accessors.
+
+static upb_Syntax upbc_File_Syntax(const google_protobuf_FileDescriptorProto* file) {
+  if (google_protobuf_FileDescriptorProto_has_syntax(file)) {
+    const upb_StringView syntax = google_protobuf_FileDescriptorProto_syntax(file);
+    const upb_StringView proto3 = upb_StringView_FromString("proto3");
+    if (upb_StringView_IsEqual(syntax, proto3)) return kUpb_Syntax_Proto3;
+  }
+  return kUpb_Syntax_Proto2;
+}
+
+/******************************************************************************/
+
+// Forward declaration.
+static void upbc_Scrape_Messages(upbc_ScrapeState*,
+                                 const google_protobuf_DescriptorProto* const*, size_t);
+
+static void upbc_Scrape_Enum(upbc_ScrapeState* s,
+                             const google_protobuf_EnumDescriptorProto* enum_type) {
+  if (!google_protobuf_EnumDescriptorProto_has_name(enum_type)) {
+    UPB_LONGJMP(s->err, kErrEnumName);
+  }
+  const upb_StringView name = google_protobuf_EnumDescriptorProto_name(enum_type);
+
+  upbc_ScrapeState_Push(s, name);
+
+  const upb_StringView encoding =
+      upb_MiniDescriptor_EncodeEnum(enum_type, s->a);
+
+  upbc_ScrapeState_String(s, encoding);
+  upbc_ScrapeState_Pop(s, name);
+}
+
+static void upbc_Scrape_Enums(
+    upbc_ScrapeState* s, const google_protobuf_EnumDescriptorProto* const* enum_types,
+    size_t len) {
+  for (size_t i = 0; i < len; i++) {
+    upbc_Scrape_Enum(s, enum_types[i]);
+  }
+}
+
+static void upbc_Scrape_Extension(
+    upbc_ScrapeState* s, const google_protobuf_FieldDescriptorProto* extension_type) {
+  if (!google_protobuf_FieldDescriptorProto_has_name(extension_type)) {
+    UPB_LONGJMP(s->err, kErrExtensionName);
+  }
+  const upb_StringView name = google_protobuf_FieldDescriptorProto_name(extension_type);
+
+  upbc_ScrapeState_Push(s, name);
+
+  const upb_StringView encoding =
+      upb_MiniDescriptor_EncodeExtension(extension_type, s->syntax, s->a);
+
+  upbc_ScrapeState_String(s, encoding);
+  upbc_ScrapeState_Pop(s, name);
+}
+
+static void upbc_Scrape_Extensions(
+    const google_protobuf_FieldDescriptorProto* const* extension_types, size_t len,
+    upbc_ScrapeState* s) {
+  for (size_t i = 0; i < len; i++) {
+    upbc_Scrape_Extension(s, extension_types[i]);
+  }
+}
+
+static void upbc_Scrape_File(upbc_ScrapeState* s,
+                             const google_protobuf_FileDescriptorProto* file_type) {
+  if (!google_protobuf_FileDescriptorProto_has_package(file_type)) {
+    UPB_LONGJMP(s->err, kErrFilePackage);
+  }
+  const upb_StringView package = google_protobuf_FileDescriptorProto_package(file_type);
+  upbc_ScrapeState_Push(s, package);
+
+  s->syntax = upbc_File_Syntax(file_type);
+
+  size_t len = 0;
+  const google_protobuf_EnumDescriptorProto* const* enum_types =
+      google_protobuf_FileDescriptorProto_enum_type(file_type, &len);
+  upbc_Scrape_Enums(s, enum_types, len);
+
+  const google_protobuf_FieldDescriptorProto* const* extension_types =
+      google_protobuf_FileDescriptorProto_extension(file_type, &len);
+  upbc_Scrape_Extensions(extension_types, len, s);
+
+  const google_protobuf_DescriptorProto* const* message_types =
+      google_protobuf_FileDescriptorProto_message_type(file_type, &len);
+  upbc_Scrape_Messages(s, message_types, len);
+
+  upbc_ScrapeState_Pop(s, package);
+}
+
+static void upbc_Scrape_Files(
+    upbc_ScrapeState* s, const google_protobuf_FileDescriptorProto* const* file_types,
+    size_t len) {
+  for (size_t i = 0; i < len; i++) {
+    upbc_Scrape_File(s, file_types[i]);
+  }
+}
+
+static void upbc_Scrape_Message(upbc_ScrapeState* s,
+                                const google_protobuf_DescriptorProto* message_type) {
+  if (!google_protobuf_DescriptorProto_has_name(message_type)) return;
+
+  const upb_StringView name = google_protobuf_DescriptorProto_name(message_type);
+  upbc_ScrapeState_Push(s, name);
+
+  const upb_StringView encoding =
+      upb_MiniDescriptor_EncodeMessage(message_type, s->syntax, s->a);
+  upbc_ScrapeState_String(s, encoding);
+
+  size_t len = 0;
+  const google_protobuf_EnumDescriptorProto* const* enum_types =
+      google_protobuf_DescriptorProto_enum_type(message_type, &len);
+  upbc_Scrape_Enums(s, enum_types, len);
+
+  const google_protobuf_FieldDescriptorProto* const* extension_types =
+      google_protobuf_DescriptorProto_extension(message_type, &len);
+  upbc_Scrape_Extensions(extension_types, len, s);
+
+  const google_protobuf_DescriptorProto* const* nested_types =
+      google_protobuf_DescriptorProto_nested_type(message_type, &len);
+  upbc_Scrape_Messages(s, nested_types, len);
+
+  upbc_ScrapeState_Pop(s, name);
+}
+
+static void upbc_Scrape_Messages(
+    upbc_ScrapeState* s, const google_protobuf_DescriptorProto* const* message_types,
+    size_t len) {
+  for (size_t i = 0; i < len; i++) {
+    upbc_Scrape_Message(s, message_types[i]);
+  }
+}
+
+upbc_CodeGeneratorRequest* upbc_MakeCodeGeneratorRequest(
+    google_protobuf_compiler_CodeGeneratorRequest* request, upb_Arena* a,
+    upb_Status* status) {
+  upbc_ScrapeState s;
+  int err = UPB_SETJMP(s.err);
+  if (err) {
+    upb_Status_SetErrorFormat(status, "%s(): error %d", __func__, err);
+    return NULL;
+  }
+  upbc_ScrapeState_Init(&s, a);
+
+  size_t len = 0;
+  const google_protobuf_FileDescriptorProto* const* file_types =
+      google_protobuf_compiler_CodeGeneratorRequest_proto_file(request, &len);
+  upbc_Scrape_Files(&s, file_types, len);
+
+  upbc_CodeGeneratorRequest_set_request(s.out, request);
+  return s.out;
+}
diff --git a/upbc/code_generator_request.h b/upbc/code_generator_request.h
new file mode 100644
index 0000000..747d98d
--- /dev/null
+++ b/upbc/code_generator_request.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2009-2022, Google LLC
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of Google LLC nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL Google LLC BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UPBC_CODE_GENERATOR_REQUEST_H_
+#define UPBC_CODE_GENERATOR_REQUEST_H_
+
+#include "upb/def.h"
+#include "upb/upb.h"
+#include "upbc/code_generator_request.upb.h"
+
+// Must be last.
+#include "upb/port_def.inc"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+upbc_CodeGeneratorRequest* upbc_MakeCodeGeneratorRequest(
+    struct google_protobuf_compiler_CodeGeneratorRequest* request, upb_Arena* a,
+    upb_Status* s);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#include "upb/port_undef.inc"
+
+#endif /* UPBC_CODE_GENERATOR_REQUEST_H_ */
diff --git a/upbc/code_generator_request.proto b/upbc/code_generator_request.proto
new file mode 100644
index 0000000..5491b13
--- /dev/null
+++ b/upbc/code_generator_request.proto
@@ -0,0 +1,13 @@
+syntax = "proto2";
+
+package upbc;
+
+import "google/protobuf/compiler/plugin.proto";
+
+message CodeGeneratorRequest {
+  // The pb sent by protoc to its plugins.
+  optional google.protobuf.compiler.CodeGeneratorRequest request = 1;
+
+  // Mini descriptors for the above pb, keyed by the fully qualified names.
+  map<string, string> mini_descriptors = 2;
+}
diff --git a/upbc/protoc-gen-upbdev.cc b/upbc/protoc-gen-upbdev.cc
new file mode 100644
index 0000000..e711ec7
--- /dev/null
+++ b/upbc/protoc-gen-upbdev.cc
@@ -0,0 +1,144 @@
+// Copyright (c) 2009-2022, Google LLC
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//     * Redistributions of source code must retain the above copyright
+//       notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above copyright
+//       notice, this list of conditions and the following disclaimer in the
+//       documentation and/or other materials provided with the distribution.
+//     * Neither the name of Google LLC nor the
+//       names of its contributors may be used to endorse or promote products
+//       derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL Google LLC BE LIABLE FOR ANY DIRECT,
+// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <assert.h>
+
+#include <iostream>
+#include <string>
+
+#include "google/protobuf/compiler/plugin.upb.h"
+#include "google/protobuf/compiler/plugin.upbdefs.h"
+#include "upb/json_decode.h"
+#include "upb/json_encode.h"
+#include "upb/mini_descriptor.h"
+#include "upb/upb.h"
+#include "upbc/code_generator_request.h"
+#include "upbc/code_generator_request.upb.h"
+#include "upbc/code_generator_request.upbdefs.h"
+#include "upbc/subprocess.h"
+
+static constexpr char kDefaultPlugin[] = "protoc_dart_plugin";
+
+static std::string JsonEncode(const upbc_CodeGeneratorRequest* request,
+                              upb_Arena* a) {
+  upb_DefPool* s = upb_DefPool_New();
+  const upb_MessageDef* m = upbc_CodeGeneratorRequest_getmsgdef(s);
+
+  upb_Status status;
+  upb_Status_Clear(&status);
+
+  const size_t json_size = upb_JsonEncode(request, m, s, 0, NULL, 0, &status);
+  assert(upb_Status_IsOk(&status));
+
+  char* json_buf = (char*)upb_Arena_Malloc(a, json_size + 1);
+
+  (void)upb_JsonEncode(request, m, s, 0, json_buf, json_size + 1, &status);
+  assert(upb_Status_IsOk(&status));
+
+  upb_DefPool_Free(s);
+
+  return std::string(json_buf, json_size);
+}
+
+static google_protobuf_compiler_CodeGeneratorResponse* JsonDecode(
+    const std::string& json, upb_Arena* a) {
+  google_protobuf_compiler_CodeGeneratorResponse* response =
+      google_protobuf_compiler_CodeGeneratorResponse_new(a);
+
+  upb_DefPool* s = upb_DefPool_New();
+  const upb_MessageDef* m = google_protobuf_compiler_CodeGeneratorResponse_getmsgdef(s);
+
+  upb_Status status;
+  upb_Status_Clear(&status);
+
+  (void)upb_JsonDecode(json.c_str(), json.size(), response, m, s, 0, a,
+                       &status);
+  assert(upb_Status_IsOk(&status));
+
+  upb_DefPool_Free(s);
+
+  return response;
+}
+
+static std::string Serialize(
+    const google_protobuf_compiler_CodeGeneratorResponse* response, upb_Arena* a) {
+  size_t len = 0;
+  const char* buf =
+      google_protobuf_compiler_CodeGeneratorResponse_serialize(response, a, &len);
+  return std::string(buf, len);
+}
+
+int main() {
+  upb_Arena* a = upb_Arena_New();
+
+  // Read (binary) stdin into a string.
+  const std::string input = {std::istreambuf_iterator<char>(std::cin),
+                             std::istreambuf_iterator<char>()};
+
+  // Parse the request.
+  auto inner_request = google_protobuf_compiler_CodeGeneratorRequest_parse(
+      input.c_str(), input.size(), a);
+
+  // Check the request for a plugin name.
+  std::string plugin = kDefaultPlugin;
+  if (google_protobuf_compiler_CodeGeneratorRequest_has_parameter(inner_request)) {
+    auto param = google_protobuf_compiler_CodeGeneratorRequest_parameter(inner_request);
+    plugin = std::string(param.data, param.size);
+  }
+
+  // Wrap the request inside a upbc_CodeGeneratorRequest.
+  upb_Status status;
+  upb_Status_Clear(&status);
+  auto outer_request = upbc_MakeCodeGeneratorRequest(inner_request, a, &status);
+  if (!upb_Status_IsOk(&status)) {
+    std::cerr << status.msg << std::endl;
+    return -1;
+  }
+
+  const std::string json_request = JsonEncode(outer_request, a);
+
+  // Launch the subprocess.
+  upbc::Subprocess subprocess;
+  subprocess.Start(plugin, upbc::Subprocess::SEARCH_PATH);
+
+  // Exchange JSON strings with the subprocess.
+  std::string json_response, error;
+  const bool ok = subprocess.Communicate(json_request, &json_response, &error);
+  if (!ok) {
+    // Dump the JSON request to stderr if we can't launch the next plugin.
+    std::cerr << json_request << std::endl;
+    return -1;
+  }
+
+  // Decode and serialize the JSON response.
+  const auto response = JsonDecode(json_response, a);
+  const std::string output = Serialize(response, a);
+
+  // Question: Is this sufficient for sending reliably to stdout?
+  std::cout << output;
+
+  upb_Arena_Free(a);
+  return 0;
+}
diff --git a/upbc/subprocess.cc b/upbc/subprocess.cc
new file mode 100644
index 0000000..e46abc8
--- /dev/null
+++ b/upbc/subprocess.cc
@@ -0,0 +1,462 @@
+/*
+ * Copyright (c) 2009-2022, Google LLC
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of Google LLC nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL Google LLC BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Shamelessly copied from the protobuf compiler's subprocess.cc
+// except this version passes strings instead of Messages.
+
+#include "upbc/subprocess.h"
+
+#include <algorithm>
+#include <cstring>
+#include <iostream>
+
+#ifndef _WIN32
+#include <errno.h>
+#include <signal.h>
+#include <sys/select.h>
+#include <sys/wait.h>
+#endif
+
+#include "absl/strings/substitute.h"
+#include "upb/upb.h"
+
+/* Must be last. */
+#include "upb/port_def.inc"
+
+namespace upbc {
+
+namespace {
+char* portable_strdup(const char* s) {
+  char* ns = (char*)malloc(strlen(s) + 1);
+  if (ns != nullptr) {
+    strcpy(ns, s);
+  }
+  return ns;
+}
+}  // namespace
+
+#ifdef _WIN32
+
+static void CloseHandleOrDie(HANDLE handle) {
+  if (!CloseHandle(handle)) {
+    GOOGLE_LOG(FATAL) << "CloseHandle: "
+                      << Subprocess::Win32ErrorMessage(GetLastError());
+  }
+}
+
+Subprocess::Subprocess()
+    : process_start_error_(ERROR_SUCCESS),
+      child_handle_(nullptr),
+      child_stdin_(nullptr),
+      child_stdout_(nullptr) {}
+
+Subprocess::~Subprocess() {
+  if (child_stdin_ != nullptr) {
+    CloseHandleOrDie(child_stdin_);
+  }
+  if (child_stdout_ != nullptr) {
+    CloseHandleOrDie(child_stdout_);
+  }
+}
+
+void Subprocess::Start(const std::string& program, SearchMode search_mode) {
+  // Create the pipes.
+  HANDLE stdin_pipe_read;
+  HANDLE stdin_pipe_write;
+  HANDLE stdout_pipe_read;
+  HANDLE stdout_pipe_write;
+
+  if (!CreatePipe(&stdin_pipe_read, &stdin_pipe_write, nullptr, 0)) {
+    GOOGLE_LOG(FATAL) << "CreatePipe: " << Win32ErrorMessage(GetLastError());
+  }
+  if (!CreatePipe(&stdout_pipe_read, &stdout_pipe_write, nullptr, 0)) {
+    GOOGLE_LOG(FATAL) << "CreatePipe: " << Win32ErrorMessage(GetLastError());
+  }
+
+  // Make child side of the pipes inheritable.
+  if (!SetHandleInformation(stdin_pipe_read, HANDLE_FLAG_INHERIT,
+                            HANDLE_FLAG_INHERIT)) {
+    GOOGLE_LOG(FATAL) << "SetHandleInformation: "
+                      << Win32ErrorMessage(GetLastError());
+  }
+  if (!SetHandleInformation(stdout_pipe_write, HANDLE_FLAG_INHERIT,
+                            HANDLE_FLAG_INHERIT)) {
+    GOOGLE_LOG(FATAL) << "SetHandleInformation: "
+                      << Win32ErrorMessage(GetLastError());
+  }
+
+  // Setup STARTUPINFO to redirect handles.
+  STARTUPINFOA startup_info;
+  ZeroMemory(&startup_info, sizeof(startup_info));
+  startup_info.cb = sizeof(startup_info);
+  startup_info.dwFlags = STARTF_USESTDHANDLES;
+  startup_info.hStdInput = stdin_pipe_read;
+  startup_info.hStdOutput = stdout_pipe_write;
+  startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE);
+
+  if (startup_info.hStdError == INVALID_HANDLE_VALUE) {
+    GOOGLE_LOG(FATAL) << "GetStdHandle: " << Win32ErrorMessage(GetLastError());
+  }
+
+  // Invoking cmd.exe allows for '.bat' files from the path as well as '.exe'.
+  // Using a malloc'ed string because CreateProcess() can mutate its second
+  // parameter.
+  char* command_line =
+      portable_strdup(("cmd.exe /c \"" + program + "\"").c_str());
+
+  // Create the process.
+  PROCESS_INFORMATION process_info;
+
+  if (CreateProcessA((search_mode == SEARCH_PATH) ? nullptr : program.c_str(),
+                     (search_mode == SEARCH_PATH) ? command_line : nullptr,
+                     nullptr,  // process security attributes
+                     nullptr,  // thread security attributes
+                     TRUE,     // inherit handles?
+                     0,        // obscure creation flags
+                     nullptr,  // environment (inherit from parent)
+                     nullptr,  // current directory (inherit from parent)
+                     &startup_info, &process_info)) {
+    child_handle_ = process_info.hProcess;
+    CloseHandleOrDie(process_info.hThread);
+    child_stdin_ = stdin_pipe_write;
+    child_stdout_ = stdout_pipe_read;
+  } else {
+    process_start_error_ = GetLastError();
+    CloseHandleOrDie(stdin_pipe_write);
+    CloseHandleOrDie(stdout_pipe_read);
+  }
+
+  CloseHandleOrDie(stdin_pipe_read);
+  CloseHandleOrDie(stdout_pipe_write);
+  free(command_line);
+}
+
+bool Subprocess::Communicate(const std::string& input_data,
+                             std::string* output_data, std::string* error) {
+  if (process_start_error_ != ERROR_SUCCESS) {
+    *error = Win32ErrorMessage(process_start_error_);
+    return false;
+  }
+
+  GOOGLE_CHECK(child_handle_ != nullptr) << "Must call Start() first.";
+
+  int input_pos = 0;
+
+  while (child_stdout_ != nullptr) {
+    HANDLE handles[2];
+    int handle_count = 0;
+
+    if (child_stdin_ != nullptr) {
+      handles[handle_count++] = child_stdin_;
+    }
+    if (child_stdout_ != nullptr) {
+      handles[handle_count++] = child_stdout_;
+    }
+
+    DWORD wait_result =
+        WaitForMultipleObjects(handle_count, handles, FALSE, INFINITE);
+
+    HANDLE signaled_handle = nullptr;
+    if (wait_result >= WAIT_OBJECT_0 &&
+        wait_result < WAIT_OBJECT_0 + handle_count) {
+      signaled_handle = handles[wait_result - WAIT_OBJECT_0];
+    } else if (wait_result == WAIT_FAILED) {
+      GOOGLE_LOG(FATAL) << "WaitForMultipleObjects: "
+                        << Win32ErrorMessage(GetLastError());
+    } else {
+      GOOGLE_LOG(FATAL) << "WaitForMultipleObjects: Unexpected return code: "
+                        << wait_result;
+    }
+
+    if (signaled_handle == child_stdin_) {
+      DWORD n;
+      if (!WriteFile(child_stdin_, input_data.data() + input_pos,
+                     input_data.size() - input_pos, &n, nullptr)) {
+        // Child closed pipe.  Presumably it will report an error later.
+        // Pretend we're done for now.
+        input_pos = input_data.size();
+      } else {
+        input_pos += n;
+      }
+
+      if (input_pos == input_data.size()) {
+        // We're done writing.  Close.
+        CloseHandleOrDie(child_stdin_);
+        child_stdin_ = nullptr;
+      }
+    } else if (signaled_handle == child_stdout_) {
+      char buffer[4096];
+      DWORD n;
+
+      if (!ReadFile(child_stdout_, buffer, sizeof(buffer), &n, nullptr)) {
+        // We're done reading.  Close.
+        CloseHandleOrDie(child_stdout_);
+        child_stdout_ = nullptr;
+      } else {
+        output_data->append(buffer, n);
+      }
+    }
+  }
+
+  if (child_stdin_ != nullptr) {
+    // Child did not finish reading input before it closed the output.
+    // Presumably it exited with an error.
+    CloseHandleOrDie(child_stdin_);
+    child_stdin_ = nullptr;
+  }
+
+  DWORD wait_result = WaitForSingleObject(child_handle_, INFINITE);
+
+  if (wait_result == WAIT_FAILED) {
+    GOOGLE_LOG(FATAL) << "WaitForSingleObject: "
+                      << Win32ErrorMessage(GetLastError());
+  } else if (wait_result != WAIT_OBJECT_0) {
+    GOOGLE_LOG(FATAL) << "WaitForSingleObject: Unexpected return code: "
+                      << wait_result;
+  }
+
+  DWORD exit_code;
+  if (!GetExitCodeProcess(child_handle_, &exit_code)) {
+    GOOGLE_LOG(FATAL) << "GetExitCodeProcess: "
+                      << Win32ErrorMessage(GetLastError());
+  }
+
+  CloseHandleOrDie(child_handle_);
+  child_handle_ = nullptr;
+
+  if (exit_code != 0) {
+    *error = absl::Substitute("Plugin failed with status code $0.", exit_code);
+    return false;
+  }
+
+  return true;
+}
+
+std::string Subprocess::Win32ErrorMessage(DWORD error_code) {
+  char* message;
+
+  // WTF?
+  FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
+                     FORMAT_MESSAGE_IGNORE_INSERTS,
+                 nullptr, error_code,
+                 MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
+                 (LPSTR)&message,  // NOT A BUG!
+                 0, nullptr);
+
+  std::string result = message;
+  LocalFree(message);
+  return result;
+}
+
+// ===================================================================
+
+#else  // _WIN32
+
+Subprocess::Subprocess()
+    : child_pid_(-1), child_stdin_(-1), child_stdout_(-1) {}
+
+Subprocess::~Subprocess() {
+  if (child_stdin_ != -1) {
+    close(child_stdin_);
+  }
+  if (child_stdout_ != -1) {
+    close(child_stdout_);
+  }
+}
+
+void Subprocess::Start(const std::string& program, SearchMode search_mode) {
+  // Note that we assume that there are no other threads, thus we don't have to
+  // do crazy stuff like using socket pairs or avoiding libc locks.
+
+  // [0] is read end, [1] is write end.
+  int stdin_pipe[2];
+  int stdout_pipe[2];
+
+  int p0 = pipe(stdin_pipe);
+  int p1 = pipe(stdout_pipe);
+  UPB_ASSERT(p0 != -1);
+  UPB_ASSERT(p1 != -1);
+
+  char* argv[2] = {portable_strdup(program.c_str()), nullptr};
+
+  child_pid_ = fork();
+  if (child_pid_ == -1) {
+    std::cerr << "fork: " << strerror(errno);
+  } else if (child_pid_ == 0) {
+    // We are the child.
+    dup2(stdin_pipe[0], STDIN_FILENO);
+    dup2(stdout_pipe[1], STDOUT_FILENO);
+
+    close(stdin_pipe[0]);
+    close(stdin_pipe[1]);
+    close(stdout_pipe[0]);
+    close(stdout_pipe[1]);
+
+    switch (search_mode) {
+      case SEARCH_PATH:
+        execvp(argv[0], argv);
+        break;
+      case EXACT_NAME:
+        execv(argv[0], argv);
+        break;
+    }
+
+    // Write directly to STDERR_FILENO to avoid stdio code paths that may do
+    // stuff that is unsafe here.
+    int ignored;
+    ignored = write(STDERR_FILENO, argv[0], strlen(argv[0]));
+    const char* message =
+        ": program not found or is not executable\n"
+        "Please specify a program using absolute path or make sure "
+        "the program is available in your PATH system variable\n";
+    ignored = write(STDERR_FILENO, message, strlen(message));
+    (void)ignored;
+
+    // Must use _exit() rather than exit() to avoid flushing output buffers
+    // that will also be flushed by the parent.
+    _exit(1);
+  } else {
+    free(argv[0]);
+
+    close(stdin_pipe[0]);
+    close(stdout_pipe[1]);
+
+    child_stdin_ = stdin_pipe[1];
+    child_stdout_ = stdout_pipe[0];
+  }
+}
+
+bool Subprocess::Communicate(const std::string& input_data,
+                             std::string* output_data, std::string* error) {
+  if (child_stdin_ == -1) {
+    std::cerr << "Must call Start() first." << std::endl;
+    UPB_ASSERT(child_stdin_ != -1);
+  }
+
+  // The "sighandler_t" typedef is GNU-specific, so define our own.
+  typedef void SignalHandler(int);
+
+  // Make sure SIGPIPE is disabled so that if the child dies it doesn't kill us.
+  SignalHandler* old_pipe_handler = signal(SIGPIPE, SIG_IGN);
+
+  int input_pos = 0;
+  int max_fd = std::max(child_stdin_, child_stdout_);
+
+  while (child_stdout_ != -1) {
+    fd_set read_fds;
+    fd_set write_fds;
+    FD_ZERO(&read_fds);
+    FD_ZERO(&write_fds);
+    if (child_stdout_ != -1) {
+      FD_SET(child_stdout_, &read_fds);
+    }
+    if (child_stdin_ != -1) {
+      FD_SET(child_stdin_, &write_fds);
+    }
+
+    if (select(max_fd + 1, &read_fds, &write_fds, nullptr, nullptr) < 0) {
+      if (errno == EINTR) {
+        // Interrupted by signal.  Try again.
+        continue;
+      } else {
+        std::cerr << "select: " << strerror(errno) << std::endl;
+        UPB_ASSERT(0);
+      }
+    }
+
+    if (child_stdin_ != -1 && FD_ISSET(child_stdin_, &write_fds)) {
+      int n = write(child_stdin_, input_data.data() + input_pos,
+                    input_data.size() - input_pos);
+      if (n < 0) {
+        // Child closed pipe.  Presumably it will report an error later.
+        // Pretend we're done for now.
+        input_pos = input_data.size();
+      } else {
+        input_pos += n;
+      }
+
+      if (input_pos == (int)input_data.size()) {
+        // We're done writing.  Close.
+        close(child_stdin_);
+        child_stdin_ = -1;
+      }
+    }
+
+    if (child_stdout_ != -1 && FD_ISSET(child_stdout_, &read_fds)) {
+      char buffer[4096];
+      int n = read(child_stdout_, buffer, sizeof(buffer));
+
+      if (n > 0) {
+        output_data->append(buffer, (size_t)n);
+      } else {
+        // We're done reading.  Close.
+        close(child_stdout_);
+        child_stdout_ = -1;
+      }
+    }
+  }
+
+  if (child_stdin_ != -1) {
+    // Child did not finish reading input before it closed the output.
+    // Presumably it exited with an error.
+    close(child_stdin_);
+    child_stdin_ = -1;
+  }
+
+  int status;
+  while (waitpid(child_pid_, &status, 0) == -1) {
+    if (errno != EINTR) {
+      std::cerr << "waitpid: " << strerror(errno) << std::endl;
+      UPB_ASSERT(0);
+    }
+  }
+
+  // Restore SIGPIPE handling.
+  signal(SIGPIPE, old_pipe_handler);
+
+  if (WIFEXITED(status)) {
+    if (WEXITSTATUS(status) != 0) {
+      int error_code = WEXITSTATUS(status);
+      *error =
+          absl::Substitute("Plugin failed with status code $0.", error_code);
+      return false;
+    }
+  } else if (WIFSIGNALED(status)) {
+    int signal = WTERMSIG(status);
+    *error = absl::Substitute("Plugin killed by signal $0.", signal);
+    return false;
+  } else {
+    *error = "Neither WEXITSTATUS nor WTERMSIG is true?";
+    return false;
+  }
+
+  return true;
+}
+
+#endif  // !_WIN32
+
+}  // namespace upbc
diff --git a/upbc/subprocess.h b/upbc/subprocess.h
new file mode 100644
index 0000000..14276d1
--- /dev/null
+++ b/upbc/subprocess.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2009-2022, Google LLC
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of Google LLC nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL Google LLC BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Shamelessly copied from the protobuf compiler's subprocess.h
+// except this version passes strings instead of Messages.
+
+#ifndef THIRD_PARTY_UPB_UPBC_H_
+#define THIRD_PARTY_UPB_UPBC_H_
+
+#ifdef _WIN32
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN  // right...
+#endif
+#include <windows.h>
+#else  // _WIN32
+#include <sys/types.h>
+#include <unistd.h>
+#endif  // !_WIN32
+#include <string>
+
+namespace upbc {
+
+// Utility class for launching sub-processes.
+class Subprocess {
+ public:
+  Subprocess();
+  ~Subprocess();
+
+  enum SearchMode {
+    SEARCH_PATH,  // Use PATH environment variable.
+    EXACT_NAME    // Program is an exact file name; don't use the PATH.
+  };
+
+  // Start the subprocess.  Currently we don't provide a way to specify
+  // arguments as protoc plugins don't have any.
+  void Start(const std::string& program, SearchMode search_mode);
+
+  // Pipe the input message to the subprocess's stdin, then close the pipe.
+  // Meanwhile, read from the subprocess's stdout and copy into *output.
+  // All this is done carefully to avoid deadlocks.
+  // Returns true if successful.  On any sort of error, returns false and sets
+  // *error to a description of the problem.
+  bool Communicate(const std::string& input_data, std::string* output_data,
+                   std::string* error);
+
+#ifdef _WIN32
+  // Given an error code, returns a human-readable error message.  This is
+  // defined here so that CommandLineInterface can share it.
+  static std::string Win32ErrorMessage(DWORD error_code);
+#endif
+
+ private:
+#ifdef _WIN32
+  DWORD process_start_error_;
+  HANDLE child_handle_;
+
+  // The file handles for our end of the child's pipes.  We close each and
+  // set it to NULL when no longer needed.
+  HANDLE child_stdin_;
+  HANDLE child_stdout_;
+
+#else  // _WIN32
+  pid_t child_pid_;
+
+  // The file descriptors for our end of the child's pipes.  We close each and
+  // set it to -1 when no longer needed.
+  int child_stdin_;
+  int child_stdout_;
+
+#endif  // !_WIN32
+};
+
+}  // namespace upbc
+
+#endif  // THIRD_PARTY_UPB_UPBC_H_