Repository: google/protobuf.dart
Pull request #991: read from and generate grpc service metadata
Author: login: devoncarew
Created: 2025-05-14T02:43:50Z | Merged: 2025-05-15T19:25:10Z

- read `default_host` and `oauth_scopes` options from gRPC service definitions and write that information to the generated gRPC clients

In generated service clients this looks like:

```dart
  /// The hostname for this service.
  static const $core.String defaultHost = 'www.example.com';

  /// OAuth scopes needed for the client.
  static const $core.List<$core.String> oauthScopes = [
    'https://www.googleapis.com/auth/cloud-platform',
    'https://www.googleapis.com/auth/datastore',
  ];
```

W/o this, people will have to look up the correct info for the service through some other means.



Diff:
diff --git a/protoc_plugin/CHANGELOG.md b/protoc_plugin/CHANGELOG.md
index 5c5fe9597..012572d14 100644
--- a/protoc_plugin/CHANGELOG.md
+++ b/protoc_plugin/CHANGELOG.md
@@ -1,7 +1,38 @@
-## 22.1.1-wip
+## 22.2.0
 
+* Read `default_host` and `oauth_scopes` options from gRPC service definitions
+  and write that information to the generated gRPC clients.
 * Generate dartdocs for grpc services ([#973]).
 
+We now parse and generate code cooresponding to the proto options
+`google.api.default_host` and `google.api.oauth_scopes`:
+
+```proto
+service Firestore {
+  option (google.api.default_host) = "firestore.googleapis.com";
+  option (google.api.oauth_scopes) =
+      "https://www.googleapis.com/auth/cloud-platform,"
+      "https://www.googleapis.com/auth/datastore";
+
+  ...
+```
+
+Will generate as:
+
+```dart
+class FirestoreClient extends $grpc.Client {
+  /// The hostname for this service.
+  static const $core.String defaultHost = 'firestore.googleapis.com';
+
+  /// OAuth scopes needed for the client.
+  static const $core.List<$core.String> oauthScopes = [
+    'https://www.googleapis.com/auth/cloud-platform',
+    'https://www.googleapis.com/auth/datastore',
+  ];
+
+  ...
+```
+
 [#973]: https://github.com/google/protobuf.dart/issues/973
 
 ## 22.1.0
diff --git a/protoc_plugin/Makefile b/protoc_plugin/Makefile
index 6c04fa5cc..9b68b43a9 100644
--- a/protoc_plugin/Makefile
+++ b/protoc_plugin/Makefile
@@ -77,7 +77,7 @@ TEST_PROTO_SRC_DIR=test/protos
 TEST_PROTO_SRCS=$(foreach proto, $(TEST_PROTO_LIST), \
   $(TEST_PROTO_SRC_DIR)/$(proto).proto)
 
-PREGENERATED_SRCS=protos/descriptor.proto protos/plugin.proto protos/dart_options.proto
+PREGENERATED_SRCS=protos/descriptor.proto protos/client.proto protos/plugin.proto protos/dart_options.proto
 
 $(TEST_PROTO_LIBS): $(PLUGIN_SRC) $(TEST_PROTO_SRCS)
 	mkdir -p $(TEST_PROTO_DIR)
@@ -104,6 +104,7 @@ $(TEST_PROTO_LIBS): $(PLUGIN_SRC) $(TEST_PROTO_SRCS)
 
 update-pregenerated: $(PLUGIN_PATH) $(PREGENERATED_SRCS)
 	protoc --dart_out=lib/src/generated -Iprotos --plugin=protoc-gen-dart=$(realpath $(PLUGIN_PATH)) $(PREGENERATED_SRCS)
+	rm lib/src/generated/client.pb{json,server}.dart
 	rm lib/src/generated/descriptor.pb{json,server}.dart
 	rm lib/src/generated/dart_options.pb{enum,json,server}.dart
 	rm lib/src/generated/plugin.pb{json,server}.dart
diff --git a/protoc_plugin/lib/protoc.dart b/protoc_plugin/lib/protoc.dart
index e6d175bd7..802040515 100644
--- a/protoc_plugin/lib/protoc.dart
+++ b/protoc_plugin/lib/protoc.dart
@@ -7,6 +7,7 @@ import 'indenting_writer.dart';
 import 'mixins.dart';
 import 'names.dart';
 import 'src/code_generator.dart';
+import 'src/generated/client.pb.dart';
 import 'src/generated/dart_options.pb.dart';
 import 'src/generated/descriptor.pb.dart';
 import 'src/generated/plugin.pb.dart';
diff --git a/protoc_plugin/lib/src/code_generator.dart b/protoc_plugin/lib/src/code_generator.dart
index dd4c19f3b..45cfe5506 100644
--- a/protoc_plugin/lib/src/code_generator.dart
+++ b/protoc_plugin/lib/src/code_generator.dart
@@ -11,6 +11,7 @@ import 'package:protobuf/protobuf.dart';
 
 import '../names.dart' show lowerCaseFirstLetter;
 import '../protoc.dart' show FileGenerator;
+import 'generated/client.pb.dart';
 import 'generated/dart_options.pb.dart';
 import 'generated/plugin.pb.dart';
 import 'linker.dart';
@@ -87,57 +88,57 @@ class CodeGenerator {
   /// for details), and [config] can be used to override where
   /// generated files are created and how imports between generated files are
   /// constructed (see [OutputConfiguration] for details).
-  void generate(
-      {Map<String, SingleOptionParser>? optionParsers,
-      OutputConfiguration config = const DefaultOutputConfiguration()}) {
+  Future<void> generate({
+    Map<String, SingleOptionParser>? optionParsers,
+    OutputConfiguration config = const DefaultOutputConfiguration(),
+  }) async {
     final extensions = ExtensionRegistry();
-    Dart_options.registerAllExtensions(extensions);
 
-    _streamIn
-        .fold(
-            BytesBuilder(), (BytesBuilder builder, data) => builder..add(data))
-        .then((builder) => builder.takeBytes())
-        .then((List<int> bytes) {
-      // Suppress CodedBufferReader builtin size limitation when reading
-      // the request, as protobuf definitions can be larger than default
-      // limit of 64Mb.
-      final reader = CodedBufferReader(bytes, sizeLimit: bytes.length);
-      final request = CodeGeneratorRequest();
-      request.mergeFromCodedBufferReader(reader, extensions);
-      reader.checkLastTagWas(0);
-
-      request.protoFile.sortBy((desc) => desc.name);
-
-      final response = CodeGeneratorResponse();
-
-      // Parse the options in the request. Return the errors if any.
-      final options = parseGenerationOptions(request, response, optionParsers);
-      if (options == null) {
-        _streamOut.add(response.writeToBuffer());
-        return;
-      }
+    Dart_options.registerAllExtensions(extensions);
+    Client.registerAllExtensions(extensions);
+
+    final builder = await _streamIn.fold(
+      BytesBuilder(),
+      (builder, data) => builder..add(data),
+    );
+    final bytes = builder.takeBytes();
+    // Suppress CodedBufferReader builtin size limitation when reading the
+    // request; protobuf definitions can be larger than default limit of 64Mb.
+    final reader = CodedBufferReader(bytes, sizeLimit: bytes.length);
+    final request = CodeGeneratorRequest();
+    request.mergeFromCodedBufferReader(reader, extensions);
+    reader.checkLastTagWas(0);
+
+    request.protoFile.sortBy((desc) => desc.name);
+
+    final response = CodeGeneratorResponse();
+
+    // Parse the options in the request. Return the errors if any.
+    final options = parseGenerationOptions(request, response, optionParsers);
+    if (options == null) {
+      _streamOut.add(response.writeToBuffer());
+      return;
+    }
 
-      // Create a syntax tree for each .proto file given to us.
-      // (We may import it even if we don't generate the .pb.dart file.)
-      final generators = <FileGenerator>[];
-      for (final file in request.protoFile) {
-        generators.add(FileGenerator(file, options));
-      }
+    // Create a syntax tree for each .proto file given to us.
+    // (We may import it even if we don't generate the .pb.dart file.)
+    final generators = <FileGenerator>[];
+    for (final file in request.protoFile) {
+      generators.add(FileGenerator(file, options));
+    }
 
-      // Collect field types and importable files.
-      link(options, generators);
+    // Collect field types and importable files.
+    link(options, generators);
 
-      // Generate the .pb.dart file if requested.
-      for (final gen in generators) {
-        final name = gen.descriptor.name;
-        if (request.fileToGenerate.contains(name)) {
-          response.file.addAll(gen.generateFiles(config));
-        }
+    // Generate the .pb.dart file if requested.
+    for (final gen in generators) {
+      final name = gen.descriptor.name;
+      if (request.fileToGenerate.contains(name)) {
+        response.file.addAll(gen.generateFiles(config));
       }
-      response.supportedFeatures =
-          Int64(CodeGeneratorResponse_Feature.FEATURE_PROTO3_OPTIONAL.value);
-
-      _streamOut.add(response.writeToBuffer());
-    });
+    }
+    response.supportedFeatures =
+        Int64(CodeGeneratorResponse_Feature.FEATURE_PROTO3_OPTIONAL.value);
+    _streamOut.add(response.writeToBuffer());
   }
 }
diff --git a/protoc_plugin/lib/src/generated/client.pb.dart b/protoc_plugin/lib/src/generated/client.pb.dart
new file mode 100644
index 000000000..ccb157d05
--- /dev/null
+++ b/protoc_plugin/lib/src/generated/client.pb.dart
@@ -0,0 +1,1263 @@
+//
+//  Generated code. Do not modify.
+//  source: client.proto
+//
+// @dart = 3.3
+
+// ignore_for_file: annotate_overrides, camel_case_types, comment_references
+// ignore_for_file: constant_identifier_names, library_prefixes
+// ignore_for_file: non_constant_identifier_names, prefer_final_fields
+// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
+
+import 'dart:core' as $core;
+
+import 'package:protobuf/protobuf.dart' as $pb;
+
+import 'client.pbenum.dart';
+
+export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions;
+
+export 'client.pbenum.dart';
+
+/// Required information for every language.
+class CommonLanguageSettings extends $pb.GeneratedMessage {
+  factory CommonLanguageSettings({
+    @$core.Deprecated('This field is deprecated.')
+    $core.String? referenceDocsUri,
+    $core.Iterable<ClientLibraryDestination>? destinations,
+    SelectiveGapicGeneration? selectiveGapicGeneration,
+  }) {
+    final $result = create();
+    if (referenceDocsUri != null) {
+      // ignore: deprecated_member_use_from_same_package
+      $result.referenceDocsUri = referenceDocsUri;
+    }
+    if (destinations != null) {
+      $result.destinations.addAll(destinations);
+    }
+    if (selectiveGapicGeneration != null) {
+      $result.selectiveGapicGeneration = selectiveGapicGeneration;
+    }
+    return $result;
+  }
+  CommonLanguageSettings._() : super();
+  factory CommonLanguageSettings.fromBuffer($core.List<$core.int> i,
+          [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+      create()..mergeFromBuffer(i, r);
+  factory CommonLanguageSettings.fromJson($core.String i,
+          [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+      create()..mergeFromJson(i, r);
+
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(
+      _omitMessageNames ? '' : 'CommonLanguageSettings',
+      package: const $pb.PackageName(_omitMessageNames ? '' : 'google.api'),
+      createEmptyInstance: create)
+    ..aOS(1, _omitFieldNames ? '' : 'referenceDocsUri')
+    ..pc<ClientLibraryDestination>(
+        2, _omitFieldNames ? '' : 'destinations', $pb.PbFieldType.KE,
+        valueOf: ClientLibraryDestination.valueOf,
+        enumValues: ClientLibraryDestination.values,
+        defaultEnumValue:
+            ClientLibraryDestination.CLIENT_LIBRARY_DESTINATION_UNSPECIFIED)
+    ..aOM<SelectiveGapicGeneration>(
+        3, _omitFieldNames ? '' : 'selectiveGapicGeneration',
+        subBuilder: SelectiveGapicGeneration.create)
+    ..hasRequiredFields = false;
+
+  @$core.Deprecated('Using this can add significant overhead to your binary. '
+      'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+      'Will be removed in next major version')
+  CommonLanguageSettings clone() =>
+      CommonLanguageSettings()..mergeFromMessage(this);
+  @$core.Deprecated('Using this can add significant overhead to your binary. '
+      'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+      'Will be removed in next major version')
+  CommonLanguageSettings copyWith(
+          void Function(CommonLanguageSettings) updates) =>
+      super.copyWith((message) => updates(message as CommonLanguageSettings))
+          as CommonLanguageSettings;
+
+  $pb.BuilderInfo get info_ => _i;
+
+  @$core.pragma('dart2js:noInline')
+  static CommonLanguageSettings create() => CommonLanguageSettings._();
+  CommonLanguageSettings createEmptyInstance() => create();
+  static $pb.PbList<CommonLanguageSettings> createRepeated() =>
+      $pb.PbList<CommonLanguageSettings>();
+  @$core.pragma('dart2js:noInline')
+  static CommonLanguageSettings getDefault() => _defaultInstance ??=
+      $pb.GeneratedMessage.$_defaultFor<CommonLanguageSettings>(create);
+  static CommonLanguageSettings? _defaultInstance;
+
+  /// Link to automatically generated reference documentation.  Example:
+  /// https://cloud.google.com/nodejs/docs/reference/asset/latest
+  @$core.Deprecated('This field is deprecated.')
+  @$pb.TagNumber(1)
+  $core.String get referenceDocsUri => $_getSZ(0);
+  @$core.Deprecated('This field is deprecated.')
+  @$pb.TagNumber(1)
+  set referenceDocsUri($core.String v) {
+    $_setString(0, v);
+  }
+
+  @$core.Deprecated('This field is deprecated.')
+  @$pb.TagNumber(1)
+  $core.bool hasReferenceDocsUri() => $_has(0);
+  @$core.Deprecated('This field is deprecated.')
+  @$pb.TagNumber(1)
+  void clearReferenceDocsUri() => $_clearField(1);
+
+  /// The destination where API teams want this client library to be published.
+  @$pb.TagNumber(2)
+  $pb.PbList<ClientLibraryDestination> get destinations => $_getList(1);
+
+  /// Configuration for which RPCs should be generated in the GAPIC client.
+  @$pb.TagNumber(3)
+  SelectiveGapicGeneration get selectiveGapicGeneration => $_getN(2);
+  @$pb.TagNumber(3)
+  set selectiveGapicGeneration(SelectiveGapicGeneration v) {
+    $_setField(3, v);
+  }
+
+  @$pb.TagNumber(3)
+  $core.bool hasSelectiveGapicGeneration() => $_has(2);
+  @$pb.TagNumber(3)
+  void clearSelectiveGapicGeneration() => $_clearField(3);
+  @$pb.TagNumber(3)
+  SelectiveGapicGeneration ensureSelectiveGapicGeneration() => $_ensure(2);
+}
+
+/// This message configures the settings for publishing [Google Cloud Client
+/// libraries](https://cloud.google.com/apis/docs/cloud-client-libraries)
+/// generated from the service config.
+class Publishing extends $pb.GeneratedMessage {
+  factory Publishing({
+    $core.String? newIssueUri,
+    $core.String? documentationUri,
+    $core.String? apiShortName,
+    $core.String? githubLabel,
+    $core.Iterable<$core.String>? codeownerGithubTeams,
+    $core.String? docTagPrefix,
+    ClientLibraryOrganization? organization,
+    $core.String? protoReferenceDocumentationUri,
+    $core.String? restReferenceDocumentationUri,
+  }) {
+    final $result = create();
+    if (newIssueUri != null) {
+      $result.newIssueUri = newIssueUri;
+    }
+    if (documentationUri != null) {
+      $result.documentationUri = documentationUri;
+    }
+    if (apiShortName != null) {
+      $result.apiShortName = apiShortName;
+    }
+    if (githubLabel != null) {
+      $result.githubLabel = githubLabel;
+    }
+    if (codeownerGithubTeams != null) {
+      $result.codeownerGithubTeams.addAll(codeownerGithubTeams);
+    }
+    if (docTagPrefix != null) {
+      $result.docTagPrefix = docTagPrefix;
+    }
+    if (organization != null) {
+      $result.organization = organization;
+    }
+    if (protoReferenceDocumentationUri != null) {
+      $result.protoReferenceDocumentationUri = protoReferenceDocumentationUri;
+    }
+    if (restReferenceDocumentationUri != null) {
+      $result.restReferenceDocumentationUri = restReferenceDocumentationUri;
+    }
+    return $result;
+  }
+  Publishing._() : super();
+  factory Publishing.fromBuffer($core.List<$core.int> i,
+          [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+      create()..mergeFromBuffer(i, r);
+  factory Publishing.fromJson($core.String i,
+          [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+      create()..mergeFromJson(i, r);
+
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(
+      _omitMessageNames ? '' : 'Publishing',
+      package: const $pb.PackageName(_omitMessageNames ? '' : 'google.api'),
+      createEmptyInstance: create)
+    ..aOS(101, _omitFieldNames ? '' : 'newIssueUri')
+    ..aOS(102, _omitFieldNames ? '' : 'documentationUri')
+    ..aOS(103, _omitFieldNames ? '' : 'apiShortName')
+    ..aOS(104, _omitFieldNames ? '' : 'githubLabel')
+    ..pPS(105, _omitFieldNames ? '' : 'codeownerGithubTeams')
+    ..aOS(106, _omitFieldNames ? '' : 'docTagPrefix')
+    ..e<ClientLibraryOrganization>(
+        107, _omitFieldNames ? '' : 'organization', $pb.PbFieldType.OE,
+        defaultOrMaker:
+            ClientLibraryOrganization.CLIENT_LIBRARY_ORGANIZATION_UNSPECIFIED,
+        valueOf: ClientLibraryOrganization.valueOf,
+        enumValues: ClientLibraryOrganization.values)
+    ..aOS(110, _omitFieldNames ? '' : 'protoReferenceDocumentationUri')
+    ..aOS(111, _omitFieldNames ? '' : 'restReferenceDocumentationUri')
+    ..hasRequiredFields = false;
+
+  @$core.Deprecated('Using this can add significant overhead to your binary. '
+      'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+      'Will be removed in next major version')
+  Publishing clone() => Publishing()..mergeFromMessage(this);
+  @$core.Deprecated('Using this can add significant overhead to your binary. '
+      'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+      'Will be removed in next major version')
+  Publishing copyWith(void Function(Publishing) updates) =>
+      super.copyWith((message) => updates(message as Publishing)) as Publishing;
+
+  $pb.BuilderInfo get info_ => _i;
+
+  @$core.pragma('dart2js:noInline')
+  static Publishing create() => Publishing._();
+  Publishing createEmptyInstance() => create();
+  static $pb.PbList<Publishing> createRepeated() => $pb.PbList<Publishing>();
+  @$core.pragma('dart2js:noInline')
+  static Publishing getDefault() => _defaultInstance ??=
+      $pb.GeneratedMessage.$_defaultFor<Publishing>(create);
+  static Publishing? _defaultInstance;
+
+  /// Link to a *public* URI where users can report issues.  Example:
+  /// https://issuetracker.google.com/issues/new?component=190865&template=1161103
+  @$pb.TagNumber(101)
+  $core.String get newIssueUri => $_getSZ(0);
+  @$pb.TagNumber(101)
+  set newIssueUri($core.String v) {
+    $_setString(0, v);
+  }
+
+  @$pb.TagNumber(101)
+  $core.bool hasNewIssueUri() => $_has(0);
+  @$pb.TagNumber(101)
+  void clearNewIssueUri() => $_clearField(101);
+
+  /// Link to product home page.  Example:
+  /// https://cloud.google.com/asset-inventory/docs/overview
+  @$pb.TagNumber(102)
+  $core.String get documentationUri => $_getSZ(1);
+  @$pb.TagNumber(102)
+  set documentationUri($core.String v) {
+    $_setString(1, v);
+  }
+
+  @$pb.TagNumber(102)
+  $core.bool hasDocumentationUri() => $_has(1);
+  @$pb.TagNumber(102)
+  void clearDocumentationUri() => $_clearField(102);
+
+  /// Used as a tracking tag when collecting data about the APIs developer
+  /// relations artifacts like docs, packages delivered to package managers,
+  /// etc.  Example: "speech".
+  @$pb.TagNumber(103)
+  $core.String get apiShortName => $_getSZ(2);
+  @$pb.TagNumber(103)
+  set apiShortName($core.String v) {
+    $_setString(2, v);
+  }
+
+  @$pb.TagNumber(103)
+  $core.bool hasApiShortName() => $_has(2);
+  @$pb.TagNumber(103)
+  void clearApiShortName() => $_clearField(103);
+
+  /// GitHub label to apply to issues and pull requests opened for this API.
+  @$pb.TagNumber(104)
+  $core.String get githubLabel => $_getSZ(3);
+  @$pb.TagNumber(104)
+  set githubLabel($core.String v) {
+    $_setString(3, v);
+  }
+
+  @$pb.TagNumber(104)
+  $core.bool hasGithubLabel() => $_has(3);
+  @$pb.TagNumber(104)
+  void clearGithubLabel() => $_clearField(104);
+
+  /// GitHub teams to be added to CODEOWNERS in the directory in GitHub
+  /// containing source code for the client libraries for this API.
+  @$pb.TagNumber(105)
+  $pb.PbList<$core.String> get codeownerGithubTeams => $_getList(4);
+
+  /// A prefix used in sample code when demarking regions to be included in
+  /// documentation.
+  @$pb.TagNumber(106)
+  $core.String get docTagPrefix => $_getSZ(5);
+  @$pb.TagNumber(106)
+  set docTagPrefix($core.String v) {
+    $_setString(5, v);
+  }
+
+  @$pb.TagNumber(106)
+  $core.bool hasDocTagPrefix() => $_has(5);
+  @$pb.TagNumber(106)
+  void clearDocTagPrefix() => $_clearField(106);
+
+  /// For whom the client library is being published.
+  @$pb.TagNumber(107)
+  ClientLibraryOrganization get organization => $_getN(6);
+  @$pb.TagNumber(107)
+  set organization(ClientLibraryOrganization v) {
+    $_setField(107, v);
+  }
+
+  @$pb.TagNumber(107)
+  $core.bool hasOrganization() => $_has(6);
+  @$pb.TagNumber(107)
+  void clearOrganization() => $_clearField(107);
+
+  /// Optional link to proto reference documentation.  Example:
+  /// https://cloud.google.com/pubsub/lite/docs/reference/rpc
+  @$pb.TagNumber(110)
+  $core.String get protoReferenceDocumentationUri => $_getSZ(7);
+  @$pb.TagNumber(110)
+  set protoReferenceDocumentationUri($core.String v) {
+    $_setString(7, v);
+  }
+
+  @$pb.TagNumber(110)
+  $core.bool hasProtoReferenceDocumentationUri() => $_has(7);
+  @$pb.TagNumber(110)
+  void clearProtoReferenceDocumentationUri() => $_clearField(110);
+
+  /// Optional link to REST reference documentation.  Example:
+  /// https://cloud.google.com/pubsub/lite/docs/reference/rest
+  @$pb.TagNumber(111)
+  $core.String get restReferenceDocumentationUri => $_getSZ(8);
+  @$pb.TagNumber(111)
+  set restReferenceDocumentationUri($core.String v) {
+    $_setString(8, v);
+  }
+
+  @$pb.TagNumber(111)
+  $core.bool hasRestReferenceDocumentationUri() => $_has(8);
+  @$pb.TagNumber(111)
+  void clearRestReferenceDocumentationUri() => $_clearField(111);
+}
+
+/// Settings for Java client libraries.
+class JavaSettings extends $pb.GeneratedMessage {
+  factory JavaSettings({
+    $core.String? libraryPackage,
+    $core.Iterable<$core.MapEntry<$core.String, $core.String>>?
+        serviceClassNames,
+    CommonLanguageSettings? common,
+  }) {
+    final $result = create();
+    if (libraryPackage != null) {
+      $result.libraryPackage = libraryPackage;
+    }
+    if (serviceClassNames != null) {
+      $result.serviceClassNames.addEntries(serviceClassNames);
+    }
+    if (common != null) {
+      $result.common = common;
+    }
+    return $result;
+  }
+  JavaSettings._() : super();
+  factory JavaSettings.fromBuffer($core.List<$core.int> i,
+          [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+      create()..mergeFromBuffer(i, r);
+  factory JavaSettings.fromJson($core.String i,
+          [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+      create()..mergeFromJson(i, r);
+
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(
+      _omitMessageNames ? '' : 'JavaSettings',
+      package: const $pb.PackageName(_omitMessageNames ? '' : 'google.api'),
+      createEmptyInstance: create)
+    ..aOS(1, _omitFieldNames ? '' : 'libraryPackage')
+    ..m<$core.String, $core.String>(
+        2, _omitFieldNames ? '' : 'serviceClassNames',
+        entryClassName: 'JavaSettings.ServiceClassNamesEntry',
+        keyFieldType: $pb.PbFieldType.OS,
+        valueFieldType: $pb.PbFieldType.OS,
+        packageName: const $pb.PackageName('google.api'))
+    ..aOM<CommonLanguageSettings>(3, _omitFieldNames ? '' : 'common',
+        subBuilder: CommonLanguageSettings.create)
+    ..hasRequiredFields = false;
+
+  @$core.Deprecated('Using this can add significant overhead to your binary. '
+      'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+      'Will be removed in next major version')
+  JavaSettings clone() => JavaSettings()..mergeFromMessage(this);
+  @$core.Deprecated('Using this can add significant overhead to your binary. '
+      'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+      'Will be removed in next major version')
+  JavaSettings copyWith(void Function(JavaSettings) updates) =>
+      super.copyWith((message) => updates(message as JavaSettings))
+          as JavaSettings;
+
+  $pb.BuilderInfo get info_ => _i;
+
+  @$core.pragma('dart2js:noInline')
+  static JavaSettings create() => JavaSettings._();
+  JavaSettings createEmptyInstance() => create();
+  static $pb.PbList<JavaSettings> createRepeated() =>
+      $pb.PbList<JavaSettings>();
+  @$core.pragma('dart2js:noInline')
+  static JavaSettings getDefault() => _defaultInstance ??=
+      $pb.GeneratedMessage.$_defaultFor<JavaSettings>(create);
+  static JavaSettings? _defaultInstance;
+
+  /// The package name to use in Java. Clobbers the java_package option
+  /// set in the protobuf. This should be used **only** by APIs
+  /// who have already set the language_settings.java.package_name" field
+  /// in gapic.yaml. API teams should use the protobuf java_package option
+  /// where possible.
+  ///
+  /// Example of a YAML configuration::
+  ///
+  ///  publishing:
+  ///    java_settings:
+  ///      library_package: com.google.cloud.pubsub.v1
+  @$pb.TagNumber(1)
+  $core.String get libraryPackage => $_getSZ(0);
+  @$pb.TagNumber(1)
+  set libraryPackage($core.String v) {
+    $_setString(0, v);
+  }
+
+  @$pb.TagNumber(1)
+  $core.bool hasLibraryPackage() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearLibraryPackage() => $_clearField(1);
+
+  /// Configure the Java class name to use instead of the service's for its
+  /// corresponding generated GAPIC client. Keys are fully-qualified
+  /// service names as they appear in the protobuf (including the full
+  /// the language_settings.java.interface_names" field in gapic.yaml. API
+  /// teams should otherwise use the service name as it appears in the
+  /// protobuf.
+  ///
+  /// Example of a YAML configuration::
+  ///
+  ///  publishing:
+  ///    java_settings:
+  ///      service_class_names:
+  ///        - google.pubsub.v1.Publisher: TopicAdmin
+  ///        - google.pubsub.v1.Subscriber: SubscriptionAdmin
+  @$pb.TagNumber(2)
+  $pb.PbMap<$core.String, $core.String> get serviceClassNames => $_getMap(1);
+
+  /// Some settings.
+  @$pb.TagNumber(3)
+  CommonLanguageSettings get common => $_getN(2);
+  @$pb.TagNumber(3)
+  set common(CommonLanguageSettings v) {
+    $_setField(3, v);
+  }
+
+  @$pb.TagNumber(3)
+  $core.bool hasCommon() => $_has(2);
+  @$pb.TagNumber(3)
+  void clearCommon() => $_clearField(3);
+  @$pb.TagNumber(3)
+  CommonLanguageSettings ensureCommon() => $_ensure(2);
+}
+
+/// Settings for C++ client libraries.
+class CppSettings extends $pb.GeneratedMessage {
+  factory CppSettings({
+    CommonLanguageSettings? common,
+  }) {
+    final $result = create();
+    if (common != null) {
+      $result.common = common;
+    }
+    return $result;
+  }
+  CppSettings._() : super();
+  factory CppSettings.fromBuffer($core.List<$core.int> i,
+          [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+      create()..mergeFromBuffer(i, r);
+  factory CppSettings.fromJson($core.String i,
+          [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+      create()..mergeFromJson(i, r);
+
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(
+      _omitMessageNames ? '' : 'CppSettings',
+      package: const $pb.PackageName(_omitMessageNames ? '' : 'google.api'),
+      createEmptyInstance: create)
+    ..aOM<CommonLanguageSettings>(1, _omitFieldNames ? '' : 'common',
+        subBuilder: CommonLanguageSettings.create)
+    ..hasRequiredFields = false;
+
+  @$core.Deprecated('Using this can add significant overhead to your binary. '
+      'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+      'Will be removed in next major version')
+  CppSettings clone() => CppSettings()..mergeFromMessage(this);
+  @$core.Deprecated('Using this can add significant overhead to your binary. '
+      'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+      'Will be removed in next major version')
+  CppSettings copyWith(void Function(CppSettings) updates) =>
+      super.copyWith((message) => updates(message as CppSettings))
+          as CppSettings;
+
+  $pb.BuilderInfo get info_ => _i;
+
+  @$core.pragma('dart2js:noInline')
+  static CppSettings create() => CppSettings._();
+  CppSettings createEmptyInstance() => create();
+  static $pb.PbList<CppSettings> createRepeated() => $pb.PbList<CppSettings>();
+  @$core.pragma('dart2js:noInline')
+  static CppSettings getDefault() => _defaultInstance ??=
+      $pb.GeneratedMessage.$_defaultFor<CppSettings>(create);
+  static CppSettings? _defaultInstance;
+
+  /// Some settings.
+  @$pb.TagNumber(1)
+  CommonLanguageSettings get common => $_getN(0);
+  @$pb.TagNumber(1)
+  set common(CommonLanguageSettings v) {
+    $_setField(1, v);
+  }
+
+  @$pb.TagNumber(1)
+  $core.bool hasCommon() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearCommon() => $_clearField(1);
+  @$pb.TagNumber(1)
+  CommonLanguageSettings ensureCommon() => $_ensure(0);
+}
+
+/// Settings for Php client libraries.
+class PhpSettings extends $pb.GeneratedMessage {
+  factory PhpSettings({
+    CommonLanguageSettings? common,
+  }) {
+    final $result = create();
+    if (common != null) {
+      $result.common = common;
+    }
+    return $result;
+  }
+  PhpSettings._() : super();
+  factory PhpSettings.fromBuffer($core.List<$core.int> i,
+          [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+      create()..mergeFromBuffer(i, r);
+  factory PhpSettings.fromJson($core.String i,
+          [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+      create()..mergeFromJson(i, r);
+
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(
+      _omitMessageNames ? '' : 'PhpSettings',
+      package: const $pb.PackageName(_omitMessageNames ? '' : 'google.api'),
+      createEmptyInstance: create)
+    ..aOM<CommonLanguageSettings>(1, _omitFieldNames ? '' : 'common',
+        subBuilder: CommonLanguageSettings.create)
+    ..hasRequiredFields = false;
+
+  @$core.Deprecated('Using this can add significant overhead to your binary. '
+      'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+      'Will be removed in next major version')
+  PhpSettings clone() => PhpSettings()..mergeFromMessage(this);
+  @$core.Deprecated('Using this can add significant overhead to your binary. '
+      'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+      'Will be removed in next major version')
+  PhpSettings copyWith(void Function(PhpSettings) updates) =>
+      super.copyWith((message) => updates(message as PhpSettings))
+          as PhpSettings;
+
+  $pb.BuilderInfo get info_ => _i;
+
+  @$core.pragma('dart2js:noInline')
+  static PhpSettings create() => PhpSettings._();
+  PhpSettings createEmptyInstance() => create();
+  static $pb.PbList<PhpSettings> createRepeated() => $pb.PbList<PhpSettings>();
+  @$core.pragma('dart2js:noInline')
+  static PhpSettings getDefault() => _defaultInstance ??=
+      $pb.GeneratedMessage.$_defaultFor<PhpSettings>(create);
+  static PhpSettings? _defaultInstance;
+
+  /// Some settings.
+  @$pb.TagNumber(1)
+  CommonLanguageSettings get common => $_getN(0);
+  @$pb.TagNumber(1)
+  set common(CommonLanguageSettings v) {
+    $_setField(1, v);
+  }
+
+  @$pb.TagNumber(1)
+  $core.bool hasCommon() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearCommon() => $_clearField(1);
+  @$pb.TagNumber(1)
+  CommonLanguageSettings ensureCommon() => $_ensure(0);
+}
+
+/// Experimental features to be included during client library generation.
+/// These fields will be deprecated once the feature graduates and is enabled
+/// by default.
+class PythonSettings_ExperimentalFeatures extends $pb.GeneratedMessage {
+  factory PythonSettings_ExperimentalFeatures({
+    $core.bool? restAsyncIoEnabled,
+    $core.bool? protobufPythonicTypesEnabled,
+    $core.bool? unversionedPackageDisabled,
+  }) {
+    final $result = create();
+    if (restAsyncIoEnabled != null) {
+      $result.restAsyncIoEnabled = restAsyncIoEnabled;
+    }
+    if (protobufPythonicTypesEnabled != null) {
+      $result.protobufPythonicTypesEnabled = protobufPythonicTypesEnabled;
+    }
+    if (unversionedPackageDisabled != null) {
+      $result.unversionedPackageDisabled = unversionedPackageDisabled;
+    }
+    return $result;
+  }
+  PythonSettings_ExperimentalFeatures._() : super();
+  factory PythonSettings_ExperimentalFeatures.fromBuffer(
+          $core.List<$core.int> i,
+          [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+      create()..mergeFromBuffer(i, r);
+  factory PythonSettings_ExperimentalFeatures.fromJson($core.String i,
+          [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+      create()..mergeFromJson(i, r);
+
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(
+      _omitMessageNames ? '' : 'PythonSettings.ExperimentalFeatures',
+      package: const $pb.PackageName(_omitMessageNames ? '' : 'google.api'),
+      createEmptyInstance: create)
+    ..aOB(1, _omitFieldNames ? '' : 'restAsyncIoEnabled')
+    ..aOB(2, _omitFieldNames ? '' : 'protobufPythonicTypesEnabled')
+    ..aOB(3, _omitFieldNames ? '' : 'unversionedPackageDisabled')
+    ..hasRequiredFields = false;
+
+  @$core.Deprecated('Using this can add significant overhead to your binary. '
+      'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+      'Will be removed in next major version')
+  PythonSettings_ExperimentalFeatures clone() =>
+      PythonSettings_ExperimentalFeatures()..mergeFromMessage(this);
+  @$core.Deprecated('Using this can add significant overhead to your binary. '
+      'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+      'Will be removed in next major version')
+  PythonSettings_ExperimentalFeatures copyWith(
+          void Function(PythonSettings_ExperimentalFeatures) updates) =>
+      super.copyWith((message) =>
+              updates(message as PythonSettings_ExperimentalFeatures))
+          as PythonSettings_ExperimentalFeatures;
+
+  $pb.BuilderInfo get info_ => _i;
+
+  @$core.pragma('dart2js:noInline')
+  static PythonSettings_ExperimentalFeatures create() =>
+      PythonSettings_ExperimentalFeatures._();
+  PythonSettings_ExperimentalFeatures createEmptyInstance() => create();
+  static $pb.PbList<PythonSettings_ExperimentalFeatures> createRepeated() =>
+      $pb.PbList<PythonSettings_ExperimentalFeatures>();
+  @$core.pragma('dart2js:noInline')
+  static PythonSettings_ExperimentalFeatures getDefault() =>
+      _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<
+          PythonSettings_ExperimentalFeatures>(create);
+  static PythonSettings_ExperimentalFeatures? _defaultInstance;
+
+  /// Enables generation of asynchronous REST clients if `rest` transport is
+  /// enabled. By default, asynchronous REST clients will not be generated.
+  /// This feature will be enabled by default 1 month after launching the
+  /// feature in preview packages.
+  @$pb.TagNumber(1)
+  $core.bool get restAsyncIoEnabled => $_getBF(0);
+  @$pb.TagNumber(1)
+  set restAsyncIoEnabled($core.bool v) {
+    $_setBool(0, v);
+  }
+
+  @$pb.TagNumber(1)
+  $core.bool hasRestAsyncIoEnabled() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearRestAsyncIoEnabled() => $_clearField(1);
+
+  /// Enables generation of protobuf code using new types that are more
+  /// Pythonic which are included in `protobuf>=5.29.x`. This feature will be
+  /// enabled by default 1 month after launching the feature in preview
+  /// packages.
+  @$pb.TagNumber(2)
+  $core.bool get protobufPythonicTypesEnabled => $_getBF(1);
+  @$pb.TagNumber(2)
+  set protobufPythonicTypesEnabled($core.bool v) {
+    $_setBool(1, v);
+  }
+
+  @$pb.TagNumber(2)
+  $core.bool hasProtobufPythonicTypesEnabled() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearProtobufPythonicTypesEnabled() => $_clearField(2);
+
+  /// Disables generation of an unversioned Python package for this client
+  /// library. This means that the module names will need to be versioned in
+  /// import statements. For example `import google.cloud.library_v2` instead
+  /// of `import google.cloud.library`.
+  @$pb.TagNumber(3)
+  $core.bool get unversionedPackageDisabled => $_getBF(2);
+  @$pb.TagNumber(3)
+  set unversionedPackageDisabled($core.bool v) {
+    $_setBool(2, v);
+  }
+
+  @$pb.TagNumber(3)
+  $core.bool hasUnversionedPackageDisabled() => $_has(2);
+  @$pb.TagNumber(3)
+  void clearUnversionedPackageDisabled() => $_clearField(3);
+}
+
+/// Settings for Python client libraries.
+class PythonSettings extends $pb.GeneratedMessage {
+  factory PythonSettings({
+    CommonLanguageSettings? common,
+    PythonSettings_ExperimentalFeatures? experimentalFeatures,
+  }) {
+    final $result = create();
+    if (common != null) {
+      $result.common = common;
+    }
+    if (experimentalFeatures != null) {
+      $result.experimentalFeatures = experimentalFeatures;
+    }
+    return $result;
+  }
+  PythonSettings._() : super();
+  factory PythonSettings.fromBuffer($core.List<$core.int> i,
+          [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+      create()..mergeFromBuffer(i, r);
+  factory PythonSettings.fromJson($core.String i,
+          [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+      create()..mergeFromJson(i, r);
+
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(
+      _omitMessageNames ? '' : 'PythonSettings',
+      package: const $pb.PackageName(_omitMessageNames ? '' : 'google.api'),
+      createEmptyInstance: create)
+    ..aOM<CommonLanguageSettings>(1, _omitFieldNames ? '' : 'common',
+        subBuilder: CommonLanguageSettings.create)
+    ..aOM<PythonSettings_ExperimentalFeatures>(
+        2, _omitFieldNames ? '' : 'experimentalFeatures',
+        subBuilder: PythonSettings_ExperimentalFeatures.create)
+    ..hasRequiredFields = false;
+
+  @$core.Deprecated('Using this can add significant overhead to your binary. '
+      'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+      'Will be removed in next major version')
+  PythonSettings clone() => PythonSettings()..mergeFromMessage(this);
+  @$core.Deprecated('Using this can add significant overhead to your binary. '
+      'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+      'Will be removed in next major version')
+  PythonSettings copyWith(void Function(PythonSettings) updates) =>
+      super.copyWith((message) => updates(message as PythonSettings))
+          as PythonSettings;
+
+  $pb.BuilderInfo get info_ => _i;
+
+  @$core.pragma('dart2js:noInline')
+  static PythonSettings create() => PythonSettings._();
+  PythonSettings createEmptyInstance() => create();
+  static $pb.PbList<PythonSettings> createRepeated() =>
+      $pb.PbList<PythonSettings>();
+  @$core.pragma('dart2js:noInline')
+  static PythonSettings getDefault() => _defaultInstance ??=
+      $pb.GeneratedMessage.$_defaultFor<PythonSettings>(create);
+  static PythonSettings? _defaultInstance;
+
+  /// Some settings.
+  @$pb.TagNumber(1)
+  CommonLanguageSettings get common => $_getN(0);
+  @$pb.TagNumber(1)
+  set common(CommonLanguageSettings v) {
+    $_setField(1, v);
+  }
+
+  @$pb.TagNumber(1)
+  $core.bool hasCommon() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearCommon() => $_clearField(1);
+  @$pb.TagNumber(1)
+  CommonLanguageSettings ensureCommon() => $_ensure(0);
+
+  /// Experimental features to be included during client library generation.
+  @$pb.TagNumber(2)
+  PythonSettings_ExperimentalFeatures get experimentalFeatures => $_getN(1);
+  @$pb.TagNumber(2)
+  set experimentalFeatures(PythonSettings_ExperimentalFeatures v) {
+    $_setField(2, v);
+  }
+
+  @$pb.TagNumber(2)
+  $core.bool hasExperimentalFeatures() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearExperimentalFeatures() => $_clearField(2);
+  @$pb.TagNumber(2)
+  PythonSettings_ExperimentalFeatures ensureExperimentalFeatures() =>
+      $_ensure(1);
+}
+
+/// Settings for Node client libraries.
+class NodeSettings extends $pb.GeneratedMessage {
+  factory NodeSettings({
+    CommonLanguageSettings? common,
+  }) {
+    final $result = create();
+    if (common != null) {
+      $result.common = common;
+    }
+    return $result;
+  }
+  NodeSettings._() : super();
+  factory NodeSettings.fromBuffer($core.List<$core.int> i,
+          [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+      create()..mergeFromBuffer(i, r);
+  factory NodeSettings.fromJson($core.String i,
+          [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+      create()..mergeFromJson(i, r);
+
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(
+      _omitMessageNames ? '' : 'NodeSettings',
+      package: const $pb.PackageName(_omitMessageNames ? '' : 'google.api'),
+      createEmptyInstance: create)
+    ..aOM<CommonLanguageSettings>(1, _omitFieldNames ? '' : 'common',
+        subBuilder: CommonLanguageSettings.create)
+    ..hasRequiredFields = false;
+
+  @$core.Deprecated('Using this can add significant overhead to your binary. '
+      'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+      'Will be removed in next major version')
+  NodeSettings clone() => NodeSettings()..mergeFromMessage(this);
+  @$core.Deprecated('Using this can add significant overhead to your binary. '
+      'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+      'Will be removed in next major version')
+  NodeSettings copyWith(void Function(NodeSettings) updates) =>
+      super.copyWith((message) => updates(message as NodeSettings))
+          as NodeSettings;
+
+  $pb.BuilderInfo get info_ => _i;
+
+  @$core.pragma('dart2js:noInline')
+  static NodeSettings create() => NodeSettings._();
+  NodeSettings createEmptyInstance() => create();
+  static $pb.PbList<NodeSettings> createRepeated() =>
+      $pb.PbList<NodeSettings>();
+  @$core.pragma('dart2js:noInline')
+  static NodeSettings getDefault() => _defaultInstance ??=
+      $pb.GeneratedMessage.$_defaultFor<NodeSettings>(create);
+  static NodeSettings? _defaultInstance;
+
+  /// Some settings.
+  @$pb.TagNumber(1)
+  CommonLanguageSettings get common => $_getN(0);
+  @$pb.TagNumber(1)
+  set common(CommonLanguageSettings v) {
+    $_setField(1, v);
+  }
+
+  @$pb.TagNumber(1)
+  $core.bool hasCommon() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearCommon() => $_clearField(1);
+  @$pb.TagNumber(1)
+  CommonLanguageSettings ensureCommon() => $_ensure(0);
+}
+
+/// Settings for Dotnet client libraries.
+class DotnetSettings extends $pb.GeneratedMessage {
+  factory DotnetSettings({
+    CommonLanguageSettings? common,
+    $core.Iterable<$core.MapEntry<$core.String, $core.String>>? renamedServices,
+    $core.Iterable<$core.MapEntry<$core.String, $core.String>>?
+        renamedResources,
+    $core.Iterable<$core.String>? ignoredResources,
+    $core.Iterable<$core.String>? forcedNamespaceAliases,
+    $core.Iterable<$core.String>? handwrittenSignatures,
+  }) {
+    final $result = create();
+    if (common != null) {
+      $result.common = common;
+    }
+    if (renamedServices != null) {
+      $result.renamedServices.addEntries(renamedServices);
+    }
+    if (renamedResources != null) {
+      $result.renamedResources.addEntries(renamedResources);
+    }
+    if (ignoredResources != null) {
+      $result.ignoredResources.addAll(ignoredResources);
+    }
+    if (forcedNamespaceAliases != null) {
+      $result.forcedNamespaceAliases.addAll(forcedNamespaceAliases);
+    }
+    if (handwrittenSignatures != null) {
+      $result.handwrittenSignatures.addAll(handwrittenSignatures);
+    }
+    return $result;
+  }
+  DotnetSettings._() : super();
+  factory DotnetSettings.fromBuffer($core.List<$core.int> i,
+          [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+      create()..mergeFromBuffer(i, r);
+  factory DotnetSettings.fromJson($core.String i,
+          [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+      create()..mergeFromJson(i, r);
+
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(
+      _omitMessageNames ? '' : 'DotnetSettings',
+      package: const $pb.PackageName(_omitMessageNames ? '' : 'google.api'),
+      createEmptyInstance: create)
+    ..aOM<CommonLanguageSettings>(1, _omitFieldNames ? '' : 'common',
+        subBuilder: CommonLanguageSettings.create)
+    ..m<$core.String, $core.String>(2, _omitFieldNames ? '' : 'renamedServices',
+        entryClassName: 'DotnetSettings.RenamedServicesEntry',
+        keyFieldType: $pb.PbFieldType.OS,
+        valueFieldType: $pb.PbFieldType.OS,
+        packageName: const $pb.PackageName('google.api'))
+    ..m<$core.String, $core.String>(
+        3, _omitFieldNames ? '' : 'renamedResources',
+        entryClassName: 'DotnetSettings.RenamedResourcesEntry',
+        keyFieldType: $pb.PbFieldType.OS,
+        valueFieldType: $pb.PbFieldType.OS,
+        packageName: const $pb.PackageName('google.api'))
+    ..pPS(4, _omitFieldNames ? '' : 'ignoredResources')
+    ..pPS(5, _omitFieldNames ? '' : 'forcedNamespaceAliases')
+    ..pPS(6, _omitFieldNames ? '' : 'handwrittenSignatures')
+    ..hasRequiredFields = false;
+
+  @$core.Deprecated('Using this can add significant overhead to your binary. '
+      'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+      'Will be removed in next major version')
+  DotnetSettings clone() => DotnetSettings()..mergeFromMessage(this);
+  @$core.Deprecated('Using this can add significant overhead to your binary. '
+      'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+      'Will be removed in next major version')
+  DotnetSettings copyWith(void Function(DotnetSettings) updates) =>
+      super.copyWith((message) => updates(message as DotnetSettings))
+          as DotnetSettings;
+
+  $pb.BuilderInfo get info_ => _i;
+
+  @$core.pragma('dart2js:noInline')
+  static DotnetSettings create() => DotnetSettings._();
+  DotnetSettings createEmptyInstance() => create();
+  static $pb.PbList<DotnetSettings> createRepeated() =>
+      $pb.PbList<DotnetSettings>();
+  @$core.pragma('dart2js:noInline')
+  static DotnetSettings getDefault() => _defaultInstance ??=
+      $pb.GeneratedMessage.$_defaultFor<DotnetSettings>(create);
+  static DotnetSettings? _defaultInstance;
+
+  /// Some settings.
+  @$pb.TagNumber(1)
+  CommonLanguageSettings get common => $_getN(0);
+  @$pb.TagNumber(1)
+  set common(CommonLanguageSettings v) {
+    $_setField(1, v);
+  }
+
+  @$pb.TagNumber(1)
+  $core.bool hasCommon() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearCommon() => $_clearField(1);
+  @$pb.TagNumber(1)
+  CommonLanguageSettings ensureCommon() => $_ensure(0);
+
+  /// Map from original service names to renamed versions.
+  /// This is used when the default generated types
+  /// would cause a naming conflict. (Neither name is
+  /// fully-qualified.)
+  /// Example: Subscriber to SubscriberServiceApi.
+  @$pb.TagNumber(2)
+  $pb.PbMap<$core.String, $core.String> get renamedServices => $_getMap(1);
+
+  /// Map from full resource types to the effective short name
+  /// for the resource. This is used when otherwise resource
+  /// named from different services would cause naming collisions.
+  /// Example entry:
+  /// "datalabeling.googleapis.com/Dataset": "DataLabelingDataset"
+  @$pb.TagNumber(3)
+  $pb.PbMap<$core.String, $core.String> get renamedResources => $_getMap(2);
+
+  /// List of full resource types to ignore during generation.
+  /// This is typically used for API-specific Location resources,
+  /// which should be handled by the generator as if they were actually
+  /// the common Location resources.
+  /// Example entry: "documentai.googleapis.com/Location"
+  @$pb.TagNumber(4)
+  $pb.PbList<$core.String> get ignoredResources => $_getList(3);
+
+  /// Namespaces which must be aliased in snippets due to
+  /// a known (but non-generator-predictable) naming collision
+  @$pb.TagNumber(5)
+  $pb.PbList<$core.String> get forcedNamespaceAliases => $_getList(4);
+
+  /// Method signatures (in the form "service.method(signature)")
+  /// which are provided separately, so shouldn't be generated.
+  /// Snippets *calling* these methods are still generated, however.
+  @$pb.TagNumber(6)
+  $pb.PbList<$core.String> get handwrittenSignatures => $_getList(5);
+}
+
+/// Settings for Ruby client libraries.
+class RubySettings extends $pb.GeneratedMessage {
+  factory RubySettings({
+    CommonLanguageSettings? common,
+  }) {
+    final $result = create();
+    if (common != null) {
+      $result.common = common;
+    }
+    return $result;
+  }
+  RubySettings._() : super();
+  factory RubySettings.fromBuffer($core.List<$core.int> i,
+          [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+      create()..mergeFromBuffer(i, r);
+  factory RubySettings.fromJson($core.String i,
+          [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+      create()..mergeFromJson(i, r);
+
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(
+      _omitMessageNames ? '' : 'RubySettings',
+      package: const $pb.PackageName(_omitMessageNames ? '' : 'google.api'),
+      createEmptyInstance: create)
+    ..aOM<CommonLanguageSettings>(1, _omitFieldNames ? '' : 'common',
+        subBuilder: CommonLanguageSettings.create)
+    ..hasRequiredFields = false;
+
+  @$core.Deprecated('Using this can add significant overhead to your binary. '
+      'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+      'Will be removed in next major version')
+  RubySettings clone() => RubySettings()..mergeFromMessage(this);
+  @$core.Deprecated('Using this can add significant overhead to your binary. '
+      'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+      'Will be removed in next major version')
+  RubySettings copyWith(void Function(RubySettings) updates) =>
+      super.copyWith((message) => updates(message as RubySettings))
+          as RubySettings;
+
+  $pb.BuilderInfo get info_ => _i;
+
+  @$core.pragma('dart2js:noInline')
+  static RubySettings create() => RubySettings._();
+  RubySettings createEmptyInstance() => create();
+  static $pb.PbList<RubySettings> createRepeated() =>
+      $pb.PbList<RubySettings>();
+  @$core.pragma('dart2js:noInline')
+  static RubySettings getDefault() => _defaultInstance ??=
+      $pb.GeneratedMessage.$_defaultFor<RubySettings>(create);
+  static RubySettings? _defaultInstance;
+
+  /// Some settings.
+  @$pb.TagNumber(1)
+  CommonLanguageSettings get common => $_getN(0);
+  @$pb.TagNumber(1)
+  set common(CommonLanguageSettings v) {
+    $_setField(1, v);
+  }
+
+  @$pb.TagNumber(1)
+  $core.bool hasCommon() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearCommon() => $_clearField(1);
+  @$pb.TagNumber(1)
+  CommonLanguageSettings ensureCommon() => $_ensure(0);
+}
+
+/// Settings for Go client libraries.
+class GoSettings extends $pb.GeneratedMessage {
+  factory GoSettings({
+    CommonLanguageSettings? common,
+    $core.Iterable<$core.MapEntry<$core.String, $core.String>>? renamedServices,
+  }) {
+    final $result = create();
+    if (common != null) {
+      $result.common = common;
+    }
+    if (renamedServices != null) {
+      $result.renamedServices.addEntries(renamedServices);
+    }
+    return $result;
+  }
+  GoSettings._() : super();
+  factory GoSettings.fromBuffer($core.List<$core.int> i,
+          [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+      create()..mergeFromBuffer(i, r);
+  factory GoSettings.fromJson($core.String i,
+          [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+      create()..mergeFromJson(i, r);
+
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(
+      _omitMessageNames ? '' : 'GoSettings',
+      package: const $pb.PackageName(_omitMessageNames ? '' : 'google.api'),
+      createEmptyInstance: create)
+    ..aOM<CommonLanguageSettings>(1, _omitFieldNames ? '' : 'common',
+        subBuilder: CommonLanguageSettings.create)
+    ..m<$core.String, $core.String>(2, _omitFieldNames ? '' : 'renamedServices',
+        entryClassName: 'GoSettings.RenamedServicesEntry',
+        keyFieldType: $pb.PbFieldType.OS,
+        valueFieldType: $pb.PbFieldType.OS,
+        packageName: const $pb.PackageName('google.api'))
+    ..hasRequiredFields = false;
+
+  @$core.Deprecated('Using this can add significant overhead to your binary. '
+      'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+      'Will be removed in next major version')
+  GoSettings clone() => GoSettings()..mergeFromMessage(this);
+  @$core.Deprecated('Using this can add significant overhead to your binary. '
+      'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+      'Will be removed in next major version')
+  GoSettings copyWith(void Function(GoSettings) updates) =>
+      super.copyWith((message) => updates(message as GoSettings)) as GoSettings;
+
+  $pb.BuilderInfo get info_ => _i;
+
+  @$core.pragma('dart2js:noInline')
+  static GoSettings create() => GoSettings._();
+  GoSettings createEmptyInstance() => create();
+  static $pb.PbList<GoSettings> createRepeated() => $pb.PbList<GoSettings>();
+  @$core.pragma('dart2js:noInline')
+  static GoSettings getDefault() => _defaultInstance ??=
+      $pb.GeneratedMessage.$_defaultFor<GoSettings>(create);
+  static GoSettings? _defaultInstance;
+
+  /// Some settings.
+  @$pb.TagNumber(1)
+  CommonLanguageSettings get common => $_getN(0);
+  @$pb.TagNumber(1)
+  set common(CommonLanguageSettings v) {
+    $_setField(1, v);
+  }
+
+  @$pb.TagNumber(1)
+  $core.bool hasCommon() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearCommon() => $_clearField(1);
+  @$pb.TagNumber(1)
+  CommonLanguageSettings ensureCommon() => $_ensure(0);
+
+  /// Map of service names to renamed services. Keys are the package relative
+  /// service names and values are the name to be used for the service client
+  /// and call options.
+  ///
+  /// publishing:
+  ///   go_settings:
+  ///     renamed_services:
+  ///       Publisher: TopicAdmin
+  @$pb.TagNumber(2)
+  $pb.PbMap<$core.String, $core.String> get renamedServices => $_getMap(1);
+}
+
+/// This message is used to configure the generation of a subset of the RPCs in
+/// a service for client libraries.
+class SelectiveGapicGeneration extends $pb.GeneratedMessage {
+  factory SelectiveGapicGeneration({
+    $core.Iterable<$core.String>? methods,
+    $core.bool? generateOmittedAsInternal,
+  }) {
+    final $result = create();
+    if (methods != null) {
+      $result.methods.addAll(methods);
+    }
+    if (generateOmittedAsInternal != null) {
+      $result.generateOmittedAsInternal = generateOmittedAsInternal;
+    }
+    return $result;
+  }
+  SelectiveGapicGeneration._() : super();
+  factory SelectiveGapicGeneration.fromBuffer($core.List<$core.int> i,
+          [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+      create()..mergeFromBuffer(i, r);
+  factory SelectiveGapicGeneration.fromJson($core.String i,
+          [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+      create()..mergeFromJson(i, r);
+
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(
+      _omitMessageNames ? '' : 'SelectiveGapicGeneration',
+      package: const $pb.PackageName(_omitMessageNames ? '' : 'google.api'),
+      createEmptyInstance: create)
+    ..pPS(1, _omitFieldNames ? '' : 'methods')
+    ..aOB(2, _omitFieldNames ? '' : 'generateOmittedAsInternal')
+    ..hasRequiredFields = false;
+
+  @$core.Deprecated('Using this can add significant overhead to your binary. '
+      'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+      'Will be removed in next major version')
+  SelectiveGapicGeneration clone() =>
+      SelectiveGapicGeneration()..mergeFromMessage(this);
+  @$core.Deprecated('Using this can add significant overhead to your binary. '
+      'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+      'Will be removed in next major version')
+  SelectiveGapicGeneration copyWith(
+          void Function(SelectiveGapicGeneration) updates) =>
+      super.copyWith((message) => updates(message as SelectiveGapicGeneration))
+          as SelectiveGapicGeneration;
+
+  $pb.BuilderInfo get info_ => _i;
+
+  @$core.pragma('dart2js:noInline')
+  static SelectiveGapicGeneration create() => SelectiveGapicGeneration._();
+  SelectiveGapicGeneration createEmptyInstance() => create();
+  static $pb.PbList<SelectiveGapicGeneration> createRepeated() =>
+      $pb.PbList<SelectiveGapicGeneration>();
+  @$core.pragma('dart2js:noInline')
+  static SelectiveGapicGeneration getDefault() => _defaultInstance ??=
+      $pb.GeneratedMessage.$_defaultFor<SelectiveGapicGeneration>(create);
+  static SelectiveGapicGeneration? _defaultInstance;
+
+  /// An allowlist of the fully qualified names of RPCs that should be included
+  /// on public client surfaces.
+  @$pb.TagNumber(1)
+  $pb.PbList<$core.String> get methods => $_getList(0);
+
+  /// Setting this to true indicates to the client generators that methods
+  /// that would be excluded from the generation should instead be generated
+  /// in a way that indicates these methods should not be consumed by
+  /// end users. How this is expressed is up to individual language
+  /// implementations to decide. Some examples may be: added annotations,
+  /// obfuscated identifiers, or other language idiomatic patterns.
+  @$pb.TagNumber(2)
+  $core.bool get generateOmittedAsInternal => $_getBF(1);
+  @$pb.TagNumber(2)
+  set generateOmittedAsInternal($core.bool v) {
+    $_setBool(1, v);
+  }
+
+  @$pb.TagNumber(2)
+  $core.bool hasGenerateOmittedAsInternal() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearGenerateOmittedAsInternal() => $_clearField(2);
+}
+
+class Client {
+  static final methodSignature = $pb.Extension<$core.String>.repeated(
+      _omitMessageNames ? '' : 'google.protobuf.MethodOptions',
+      _omitFieldNames ? '' : 'methodSignature',
+      1051,
+      $pb.PbFieldType.PS,
+      check: $pb.getCheckFunction($pb.PbFieldType.PS));
+  static final defaultHost = $pb.Extension<$core.String>(
+      _omitMessageNames ? '' : 'google.protobuf.ServiceOptions',
+      _omitFieldNames ? '' : 'defaultHost',
+      1049,
+      $pb.PbFieldType.OS);
+  static final oauthScopes = $pb.Extension<$core.String>(
+      _omitMessageNames ? '' : 'google.protobuf.ServiceOptions',
+      _omitFieldNames ? '' : 'oauthScopes',
+      1050,
+      $pb.PbFieldType.OS);
+  static final apiVersion = $pb.Extension<$core.String>(
+      _omitMessageNames ? '' : 'google.protobuf.ServiceOptions',
+      _omitFieldNames ? '' : 'apiVersion',
+      525000001,
+      $pb.PbFieldType.OS);
+  static void registerAllExtensions($pb.ExtensionRegistry registry) {
+    registry.add(methodSignature);
+    registry.add(defaultHost);
+    registry.add(oauthScopes);
+    registry.add(apiVersion);
+  }
+}
+
+const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names');
+const _omitMessageNames =
+    $core.bool.fromEnvironment('protobuf.omit_message_names');
diff --git a/protoc_plugin/lib/src/generated/client.pbenum.dart b/protoc_plugin/lib/src/generated/client.pbenum.dart
new file mode 100644
index 000000000..bc568da9e
--- /dev/null
+++ b/protoc_plugin/lib/src/generated/client.pbenum.dart
@@ -0,0 +1,103 @@
+//
+//  Generated code. Do not modify.
+//  source: client.proto
+//
+// @dart = 3.3
+
+// ignore_for_file: annotate_overrides, camel_case_types, comment_references
+// ignore_for_file: constant_identifier_names, library_prefixes
+// ignore_for_file: non_constant_identifier_names, prefer_final_fields
+// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
+
+import 'dart:core' as $core;
+
+import 'package:protobuf/protobuf.dart' as $pb;
+
+/// The organization for which the client libraries are being published.
+/// Affects the url where generated docs are published, etc.
+class ClientLibraryOrganization extends $pb.ProtobufEnum {
+  /// Not useful.
+  static const ClientLibraryOrganization
+      CLIENT_LIBRARY_ORGANIZATION_UNSPECIFIED = ClientLibraryOrganization._(
+          0, _omitEnumNames ? '' : 'CLIENT_LIBRARY_ORGANIZATION_UNSPECIFIED');
+
+  /// Google Cloud Platform Org.
+  static const ClientLibraryOrganization CLOUD =
+      ClientLibraryOrganization._(1, _omitEnumNames ? '' : 'CLOUD');
+
+  /// Ads (Advertising) Org.
+  static const ClientLibraryOrganization ADS =
+      ClientLibraryOrganization._(2, _omitEnumNames ? '' : 'ADS');
+
+  /// Photos Org.
+  static const ClientLibraryOrganization PHOTOS =
+      ClientLibraryOrganization._(3, _omitEnumNames ? '' : 'PHOTOS');
+
+  /// Street View Org.
+  static const ClientLibraryOrganization STREET_VIEW =
+      ClientLibraryOrganization._(4, _omitEnumNames ? '' : 'STREET_VIEW');
+
+  /// Shopping Org.
+  static const ClientLibraryOrganization SHOPPING =
+      ClientLibraryOrganization._(5, _omitEnumNames ? '' : 'SHOPPING');
+
+  /// Geo Org.
+  static const ClientLibraryOrganization GEO =
+      ClientLibraryOrganization._(6, _omitEnumNames ? '' : 'GEO');
+
+  /// Generative AI - https://developers.generativeai.google
+  static const ClientLibraryOrganization GENERATIVE_AI =
+      ClientLibraryOrganization._(7, _omitEnumNames ? '' : 'GENERATIVE_AI');
+
+  static const $core.List<ClientLibraryOrganization> values =
+      <ClientLibraryOrganization>[
+    CLIENT_LIBRARY_ORGANIZATION_UNSPECIFIED,
+    CLOUD,
+    ADS,
+    PHOTOS,
+    STREET_VIEW,
+    SHOPPING,
+    GEO,
+    GENERATIVE_AI,
+  ];
+
+  static final $core.List<ClientLibraryOrganization?> _byValue =
+      $pb.ProtobufEnum.$_initByValueList(values, 7);
+  static ClientLibraryOrganization? valueOf($core.int value) =>
+      value < 0 || value >= _byValue.length ? null : _byValue[value];
+
+  const ClientLibraryOrganization._(super.v, super.n);
+}
+
+/// To where should client libraries be published?
+class ClientLibraryDestination extends $pb.ProtobufEnum {
+  /// Client libraries will neither be generated nor published to package
+  /// managers.
+  static const ClientLibraryDestination CLIENT_LIBRARY_DESTINATION_UNSPECIFIED =
+      ClientLibraryDestination._(
+          0, _omitEnumNames ? '' : 'CLIENT_LIBRARY_DESTINATION_UNSPECIFIED');
+
+  /// Generate the client library in a repo under github.com/googleapis,
+  /// but don't publish it to package managers.
+  static const ClientLibraryDestination GITHUB =
+      ClientLibraryDestination._(10, _omitEnumNames ? '' : 'GITHUB');
+
+  /// Publish the library to package managers like nuget.org and npmjs.com.
+  static const ClientLibraryDestination PACKAGE_MANAGER =
+      ClientLibraryDestination._(20, _omitEnumNames ? '' : 'PACKAGE_MANAGER');
+
+  static const $core.List<ClientLibraryDestination> values =
+      <ClientLibraryDestination>[
+    CLIENT_LIBRARY_DESTINATION_UNSPECIFIED,
+    GITHUB,
+    PACKAGE_MANAGER,
+  ];
+
+  static final $core.Map<$core.int, ClientLibraryDestination> _byValue =
+      $pb.ProtobufEnum.initByValue(values);
+  static ClientLibraryDestination? valueOf($core.int value) => _byValue[value];
+
+  const ClientLibraryDestination._(super.v, super.n);
+}
+
+const _omitEnumNames = $core.bool.fromEnvironment('protobuf.omit_enum_names');
diff --git a/protoc_plugin/lib/src/grpc_generator.dart b/protoc_plugin/lib/src/grpc_generator.dart
index 85ad5e961..b7b378454 100644
--- a/protoc_plugin/lib/src/grpc_generator.dart
+++ b/protoc_plugin/lib/src/grpc_generator.dart
@@ -124,6 +124,26 @@ class GrpcServiceGenerator {
     }
     out.println("@$protobufImportPrefix.GrpcServiceName('$_fullServiceName')");
     out.addBlock('class $_clientClassname extends $_client {', '}', () {
+      // Look for and generate default_host info.
+      final defaultHost = _descriptor.options.defaultHost;
+      if (defaultHost != null) {
+        out.println('/// The hostname for this service.');
+        out.println("static const $_string defaultHost = '$defaultHost';");
+        out.println();
+      }
+
+      // Look for and generate oauth_scopes info.
+      final oauthScopes = _descriptor.options.oauthScopes;
+      if (oauthScopes != null) {
+        out.println('/// OAuth scopes needed for the client.');
+        out.println('static const $_list<$_string> oauthScopes = [');
+        for (final scope in oauthScopes.split(',')) {
+          out.println("  '$scope',");
+        }
+        out.println('];');
+        out.println();
+      }
+
       for (final method in _methods) {
         method.generateClientMethodDescriptor(out);
       }
@@ -161,6 +181,8 @@ class GrpcServiceGenerator {
   static final String _callOptions = '$grpcImportPrefix.CallOptions';
   static final String _client = '$grpcImportPrefix.Client';
   static final String _service = '$grpcImportPrefix.Service';
+  static final String _list = '$coreImportPrefix.List';
+  static final String _string = '$coreImportPrefix.String';
 }
 
 class _GrpcMethod {
@@ -318,3 +340,9 @@ class _GrpcMethod {
   static final String _responseFuture = '$grpcImportPrefix.ResponseFuture';
   static final String _responseStream = '$grpcImportPrefix.ResponseStream';
 }
+
+extension on ServiceOptions {
+  String? get defaultHost => getExtension(Client.defaultHost) as String?;
+
+  String? get oauthScopes => getExtension(Client.oauthScopes) as String?;
+}
diff --git a/protoc_plugin/protos/client.proto b/protoc_plugin/protos/client.proto
new file mode 100644
index 000000000..0991b2da8
--- /dev/null
+++ b/protoc_plugin/protos/client.proto
@@ -0,0 +1,369 @@
+// Copyright 2025 Google LLC
+//
+// 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.
+
+syntax = "proto3";
+
+package google.api;
+
+import "descriptor.proto";
+
+option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations";
+option java_multiple_files = true;
+option java_outer_classname = "ClientProto";
+option java_package = "com.google.api";
+option objc_class_prefix = "GAPI";
+
+extend google.protobuf.MethodOptions {
+  // A definition of a client library method signature.
+  //
+  // In client libraries, each proto RPC corresponds to one or more methods
+  // which the end user is able to call, and calls the underlying RPC.
+  // Normally, this method receives a single argument (a struct or instance
+  // corresponding to the RPC request object). Defining this field will
+  // add one or more overloads providing flattened or simpler method signatures
+  // in some languages.
+  //
+  // The fields on the method signature are provided as a comma-separated
+  // string.
+  //
+  // For example, the proto RPC and annotation:
+  //
+  //   rpc CreateSubscription(CreateSubscriptionRequest)
+  //       returns (Subscription) {
+  //     option (google.api.method_signature) = "name,topic";
+  //   }
+  //
+  // Would add the following Java overload (in addition to the method accepting
+  // the request object):
+  //
+  //   public final Subscription createSubscription(String name, String topic)
+  //
+  // The following backwards-compatibility guidelines apply:
+  //
+  //   * Adding this annotation to an unannotated method is backwards
+  //     compatible.
+  //   * Adding this annotation to a method which already has existing
+  //     method signature annotations is backwards compatible if and only if
+  //     the new method signature annotation is last in the sequence.
+  //   * Modifying or removing an existing method signature annotation is
+  //     a breaking change.
+  //   * Re-ordering existing method signature annotations is a breaking
+  //     change.
+  repeated string method_signature = 1051;
+}
+
+extend google.protobuf.ServiceOptions {
+  // The hostname for this service.
+  // This should be specified with no prefix or protocol.
+  //
+  // Example:
+  //
+  //   service Foo {
+  //     option (google.api.default_host) = "foo.googleapi.com";
+  //     ...
+  //   }
+  string default_host = 1049;
+
+  // OAuth scopes needed for the client.
+  //
+  // Example:
+  //
+  //   service Foo {
+  //     option (google.api.oauth_scopes) = \
+  //       "https://www.googleapis.com/auth/cloud-platform";
+  //     ...
+  //   }
+  //
+  // If there is more than one scope, use a comma-separated string:
+  //
+  // Example:
+  //
+  //   service Foo {
+  //     option (google.api.oauth_scopes) = \
+  //       "https://www.googleapis.com/auth/cloud-platform,"
+  //       "https://www.googleapis.com/auth/monitoring";
+  //     ...
+  //   }
+  string oauth_scopes = 1050;
+
+  // The API version of this service, which should be sent by version-aware
+  // clients to the service. This allows services to abide by the schema and
+  // behavior of the service at the time this API version was deployed.
+  // The format of the API version must be treated as opaque by clients.
+  // Services may use a format with an apparent structure, but clients must
+  // not rely on this to determine components within an API version, or attempt
+  // to construct other valid API versions. Note that this is for upcoming
+  // functionality and may not be implemented for all services.
+  //
+  // Example:
+  //
+  //   service Foo {
+  //     option (google.api.api_version) = "v1_20230821_preview";
+  //   }
+  string api_version = 525000001;
+}
+
+// Required information for every language.
+message CommonLanguageSettings {
+  // Link to automatically generated reference documentation.  Example:
+  // https://cloud.google.com/nodejs/docs/reference/asset/latest
+  string reference_docs_uri = 1 [deprecated = true];
+
+  // The destination where API teams want this client library to be published.
+  repeated ClientLibraryDestination destinations = 2;
+
+  // Configuration for which RPCs should be generated in the GAPIC client.
+  SelectiveGapicGeneration selective_gapic_generation = 3;
+}
+
+// This message configures the settings for publishing [Google Cloud Client
+// libraries](https://cloud.google.com/apis/docs/cloud-client-libraries)
+// generated from the service config.
+message Publishing {
+  // Link to a *public* URI where users can report issues.  Example:
+  // https://issuetracker.google.com/issues/new?component=190865&template=1161103
+  string new_issue_uri = 101;
+
+  // Link to product home page.  Example:
+  // https://cloud.google.com/asset-inventory/docs/overview
+  string documentation_uri = 102;
+
+  // Used as a tracking tag when collecting data about the APIs developer
+  // relations artifacts like docs, packages delivered to package managers,
+  // etc.  Example: "speech".
+  string api_short_name = 103;
+
+  // GitHub label to apply to issues and pull requests opened for this API.
+  string github_label = 104;
+
+  // GitHub teams to be added to CODEOWNERS in the directory in GitHub
+  // containing source code for the client libraries for this API.
+  repeated string codeowner_github_teams = 105;
+
+  // A prefix used in sample code when demarking regions to be included in
+  // documentation.
+  string doc_tag_prefix = 106;
+
+  // For whom the client library is being published.
+  ClientLibraryOrganization organization = 107;
+
+  // Optional link to proto reference documentation.  Example:
+  // https://cloud.google.com/pubsub/lite/docs/reference/rpc
+  string proto_reference_documentation_uri = 110;
+
+  // Optional link to REST reference documentation.  Example:
+  // https://cloud.google.com/pubsub/lite/docs/reference/rest
+  string rest_reference_documentation_uri = 111;
+}
+
+// Settings for Java client libraries.
+message JavaSettings {
+  // The package name to use in Java. Clobbers the java_package option
+  // set in the protobuf. This should be used **only** by APIs
+  // who have already set the language_settings.java.package_name" field
+  // in gapic.yaml. API teams should use the protobuf java_package option
+  // where possible.
+  //
+  // Example of a YAML configuration::
+  //
+  //  publishing:
+  //    java_settings:
+  //      library_package: com.google.cloud.pubsub.v1
+  string library_package = 1;
+
+  // Configure the Java class name to use instead of the service's for its
+  // corresponding generated GAPIC client. Keys are fully-qualified
+  // service names as they appear in the protobuf (including the full
+  // the language_settings.java.interface_names" field in gapic.yaml. API
+  // teams should otherwise use the service name as it appears in the
+  // protobuf.
+  //
+  // Example of a YAML configuration::
+  //
+  //  publishing:
+  //    java_settings:
+  //      service_class_names:
+  //        - google.pubsub.v1.Publisher: TopicAdmin
+  //        - google.pubsub.v1.Subscriber: SubscriptionAdmin
+  map<string, string> service_class_names = 2;
+
+  // Some settings.
+  CommonLanguageSettings common = 3;
+}
+
+// Settings for C++ client libraries.
+message CppSettings {
+  // Some settings.
+  CommonLanguageSettings common = 1;
+}
+
+// Settings for Php client libraries.
+message PhpSettings {
+  // Some settings.
+  CommonLanguageSettings common = 1;
+}
+
+// Settings for Python client libraries.
+message PythonSettings {
+  // Experimental features to be included during client library generation.
+  // These fields will be deprecated once the feature graduates and is enabled
+  // by default.
+  message ExperimentalFeatures {
+    // Enables generation of asynchronous REST clients if `rest` transport is
+    // enabled. By default, asynchronous REST clients will not be generated.
+    // This feature will be enabled by default 1 month after launching the
+    // feature in preview packages.
+    bool rest_async_io_enabled = 1;
+
+    // Enables generation of protobuf code using new types that are more
+    // Pythonic which are included in `protobuf>=5.29.x`. This feature will be
+    // enabled by default 1 month after launching the feature in preview
+    // packages.
+    bool protobuf_pythonic_types_enabled = 2;
+
+    // Disables generation of an unversioned Python package for this client
+    // library. This means that the module names will need to be versioned in
+    // import statements. For example `import google.cloud.library_v2` instead
+    // of `import google.cloud.library`.
+    bool unversioned_package_disabled = 3;
+  }
+
+  // Some settings.
+  CommonLanguageSettings common = 1;
+
+  // Experimental features to be included during client library generation.
+  ExperimentalFeatures experimental_features = 2;
+}
+
+// Settings for Node client libraries.
+message NodeSettings {
+  // Some settings.
+  CommonLanguageSettings common = 1;
+}
+
+// Settings for Dotnet client libraries.
+message DotnetSettings {
+  // Some settings.
+  CommonLanguageSettings common = 1;
+
+  // Map from original service names to renamed versions.
+  // This is used when the default generated types
+  // would cause a naming conflict. (Neither name is
+  // fully-qualified.)
+  // Example: Subscriber to SubscriberServiceApi.
+  map<string, string> renamed_services = 2;
+
+  // Map from full resource types to the effective short name
+  // for the resource. This is used when otherwise resource
+  // named from different services would cause naming collisions.
+  // Example entry:
+  // "datalabeling.googleapis.com/Dataset": "DataLabelingDataset"
+  map<string, string> renamed_resources = 3;
+
+  // List of full resource types to ignore during generation.
+  // This is typically used for API-specific Location resources,
+  // which should be handled by the generator as if they were actually
+  // the common Location resources.
+  // Example entry: "documentai.googleapis.com/Location"
+  repeated string ignored_resources = 4;
+
+  // Namespaces which must be aliased in snippets due to
+  // a known (but non-generator-predictable) naming collision
+  repeated string forced_namespace_aliases = 5;
+
+  // Method signatures (in the form "service.method(signature)")
+  // which are provided separately, so shouldn't be generated.
+  // Snippets *calling* these methods are still generated, however.
+  repeated string handwritten_signatures = 6;
+}
+
+// Settings for Ruby client libraries.
+message RubySettings {
+  // Some settings.
+  CommonLanguageSettings common = 1;
+}
+
+// Settings for Go client libraries.
+message GoSettings {
+  // Some settings.
+  CommonLanguageSettings common = 1;
+
+  // Map of service names to renamed services. Keys are the package relative
+  // service names and values are the name to be used for the service client
+  // and call options.
+  //
+  // publishing:
+  //   go_settings:
+  //     renamed_services:
+  //       Publisher: TopicAdmin
+  map<string, string> renamed_services = 2;
+}
+
+// The organization for which the client libraries are being published.
+// Affects the url where generated docs are published, etc.
+enum ClientLibraryOrganization {
+  // Not useful.
+  CLIENT_LIBRARY_ORGANIZATION_UNSPECIFIED = 0;
+
+  // Google Cloud Platform Org.
+  CLOUD = 1;
+
+  // Ads (Advertising) Org.
+  ADS = 2;
+
+  // Photos Org.
+  PHOTOS = 3;
+
+  // Street View Org.
+  STREET_VIEW = 4;
+
+  // Shopping Org.
+  SHOPPING = 5;
+
+  // Geo Org.
+  GEO = 6;
+
+  // Generative AI - https://developers.generativeai.google
+  GENERATIVE_AI = 7;
+}
+
+// To where should client libraries be published?
+enum ClientLibraryDestination {
+  // Client libraries will neither be generated nor published to package
+  // managers.
+  CLIENT_LIBRARY_DESTINATION_UNSPECIFIED = 0;
+
+  // Generate the client library in a repo under github.com/googleapis,
+  // but don't publish it to package managers.
+  GITHUB = 10;
+
+  // Publish the library to package managers like nuget.org and npmjs.com.
+  PACKAGE_MANAGER = 20;
+}
+
+// This message is used to configure the generation of a subset of the RPCs in
+// a service for client libraries.
+message SelectiveGapicGeneration {
+  // An allowlist of the fully qualified names of RPCs that should be included
+  // on public client surfaces.
+  repeated string methods = 1;
+
+  // Setting this to true indicates to the client generators that methods
+  // that would be excluded from the generation should instead be generated
+  // in a way that indicates these methods should not be consumed by
+  // end users. How this is expressed is up to individual language
+  // implementations to decide. Some examples may be: added annotations,
+  // obfuscated identifiers, or other language idiomatic patterns.
+  bool generate_omitted_as_internal = 2;
+}
diff --git a/protoc_plugin/pubspec.yaml b/protoc_plugin/pubspec.yaml
index c1c12a248..a1070de99 100644
--- a/protoc_plugin/pubspec.yaml
+++ b/protoc_plugin/pubspec.yaml
@@ -1,5 +1,5 @@
 name: protoc_plugin
-version: 22.1.1-wip
+version: 22.2.0
 description: A protobuf protoc compiler plugin used to generate Dart code.
 repository: https://github.com/google/protobuf.dart/tree/master/protoc_plugin
 
diff --git a/protoc_plugin/test/file_generator_test.dart b/protoc_plugin/test/file_generator_test.dart
index f248e8ace..26a297d04 100644
--- a/protoc_plugin/test/file_generator_test.dart
+++ b/protoc_plugin/test/file_generator_test.dart
@@ -4,6 +4,7 @@
 
 import 'package:protoc_plugin/indenting_writer.dart';
 import 'package:protoc_plugin/protoc.dart';
+import 'package:protoc_plugin/src/generated/client.pb.dart';
 import 'package:protoc_plugin/src/generated/descriptor.pb.dart';
 import 'package:protoc_plugin/src/generated/plugin.pb.dart';
 import 'package:protoc_plugin/src/linker.dart';
@@ -328,8 +329,17 @@ void main() {
       ..clientStreaming = false
       ..serverStreaming = false;
 
+    final serviceOptions = ServiceOptions();
+    serviceOptions.setExtension(Client.defaultHost, 'www.example.com');
+    serviceOptions.setExtension(
+      Client.oauthScopes,
+      'https://www.googleapis.com/auth/cloud-platform,'
+      'https://www.googleapis.com/auth/datastore',
+    );
+
     final sd = ServiceDescriptorProto()
       ..name = 'Test'
+      ..options = serviceOptions
       ..method.addAll([
         unary,
         clientStreaming,
diff --git a/protoc_plugin/test/goldens/grpc_service.pbgrpc b/protoc_plugin/test/goldens/grpc_service.pbgrpc
index 2d6d5a266..eb99c2005 100644
--- a/protoc_plugin/test/goldens/grpc_service.pbgrpc
+++ b/protoc_plugin/test/goldens/grpc_service.pbgrpc
@@ -21,6 +21,15 @@ export 'test.pb.dart';
 
 @$pb.GrpcServiceName('Test')
 class TestClient extends $grpc.Client {
+  /// The hostname for this service.
+  static const $core.String defaultHost = 'www.example.com';
+
+  /// OAuth scopes needed for the client.
+  static const $core.List<$core.String> oauthScopes = [
+    'https://www.googleapis.com/auth/cloud-platform',
+    'https://www.googleapis.com/auth/datastore',
+  ];
+
   static final _$unary = $grpc.ClientMethod<$0.Input, $0.Output>(
       '/Test/Unary',
       ($0.Input value) => value.writeToBuffer(),


Reviews:
user: login: osa1
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T09:54:05Z
user: login: devoncarew
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T17:18:45Z

Review comments:
diff_hunk: @@ -1,3 +1,8 @@
+## 22.2.0-wip
+
+* Read `default_host` and `oauth_scopes` options from gRPC service definitions
+  and write that information to the generated gRPC clients.
path: protoc_plugin/CHANGELOG.md
user: login: osa1
body: Do we have any documentation showing generated classes and how they should be used?

If we do, it would be good to update that with the new members.

If not, maybe we should show the new members here?
created_at: 2025-05-15T09:53:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,3 +1,8 @@
+## 22.2.0-wip
+
+* Read `default_host` and `oauth_scopes` options from gRPC service definitions
+  and write that information to the generated gRPC clients.
path: protoc_plugin/CHANGELOG.md
user: login: devoncarew
body: Here's what they look like in proto files: https://github.com/googleapis/googleapis/blob/master/google/firestore/v1/firestore.proto#L53

and what they look like in generated code:

```dart
class FirestoreClient extends $grpc.Client {
  /// The hostname for this service.
  static const $core.String defaultHost = 'firestore.googleapis.com';

  /// OAuth scopes needed for the client.
  static const $core.List<$core.String> oauthScopes = [
    'https://www.googleapis.com/auth/cloud-platform',
    'https://www.googleapis.com/auth/datastore',
  ];

  ...
```

We'd need some additional protos (`google/api/annotations.proto`, ...) to be able to use these annotations in this repo. I think having an example here would be useful, but we'll need to re-work a bit how we bring the google/* protos in, perhaps what our goldens look like, ... That's all useful, but likely work for future PRs. I'll add some docs to the readme or to the changelog for now.

created_at: 2025-05-15T17:18:45Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-05-15T17:30:12Z
body: ## Package publishing

| Package | Version | Status | Publish tag (post-merge) |
| :--- | ---: | :--- | ---: |
| package:protobuf | 4.0.1-wip | WIP (no publish necessary) |  |
| package:protoc_plugin | 22.2.0 | **ready to publish** | [`protoc_plugin-v22.2.0`](https://github.com/google/protobuf.dart/releases/new?tag=protoc_plugin-v22.2.0&title=package%3Aprotoc_plugin+v22.2.0&body=%2A+Read+%60default_host%60+and+%60oauth_scopes%60+options+from+gRPC+service+definitions%0A++and+write+that+information+to+the+generated+gRPC+clients.%0A%2A+Generate+dartdocs+for+grpc+services+%28%5B%23973%5D%29.%0AWe+now+parse+and+generate+code+cooresponding+to+the+proto+options%0A%60google.api.default_host%60+and+%60google.api.oauth_scopes%60%3A%0A%60%60%60proto%0Aservice+Firestore+%7B%0A++option+%28google.api.default_host%29+%3D+%22firestore.googleapis.com%22%3B%0A++option+%28google.api.oauth_scopes%29+%3D%0A++++++%22https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcloud-platform%2C%22%0A++++++%22https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdatastore%22%3B%0A++...%0A%60%60%60%0AWill+generate+as%3A%0A%60%60%60dart%0Aclass+FirestoreClient+extends+%24grpc.Client+%7B%0A++%2F%2F%2F+The+hostname+for+this+service.%0A++static+const+%24core.String+defaultHost+%3D+%27firestore.googleapis.com%27%3B%0A++%2F%2F%2F+OAuth+scopes+needed+for+the+client.%0A++static+const+%24core.List%3C%24core.String%3E+oauthScopes+%3D+%5B%0A++++%27https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcloud-platform%27%2C%0A++++%27https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdatastore%27%2C%0A++%5D%3B%0A++...%0A%60%60%60%0A%5B%23973%5D%3A+https%3A%2F%2Fgithub.com%2Fgoogle%2Fprotobuf.dart%2Fissues%2F973) |

Documentation at https://github.com/dart-lang/ecosystem/wiki/Publishing-automation.

author_association: NONE
user: login: devoncarew
created_at: 2025-05-15T17:55:38Z
body: @osa1, fyi, I rev'd to a new stable version for protoc_plugin in prep for publishing.

Separately, https://github.com/dart-lang/labs/tree/main/pkgs/appengine has many generated google protos and grpc services (appengine, datastore, logging, iam, ...). With a little work it could serve as more effective canary for new releases than the sdk repo.

author_association: CONTRIBUTOR