public ServiceDetails(ProtoCatalog catalog, string ns, ServiceDescriptor desc, ServiceConfig grpcServiceConfig) { Catalog = catalog; Namespace = ns; ProtoPackage = desc.File.Package; DocLines = desc.Declaration.DocLines().ToList(); SnippetsNamespace = $"{ns}.Snippets"; UnitTestsNamespace = $"{ns}.Tests"; // Must come early; used by `MethodDetails.Create()` MethodGrpcConfigsByName = grpcServiceConfig?.MethodConfig .SelectMany(conf => conf.Name.Select(name => (name, conf))) .Where(x => x.name.Service == desc.FullName) .ToImmutableDictionary(x => $"{x.name.Service}/{x.name.Method}", x => x.conf) ?? ImmutableDictionary <string, MethodConfig> .Empty; ServiceFullName = desc.FullName; ServiceName = desc.Name; DocumentationName = desc.Name; // TODO: There may be a more suitable name than this. ProtoTyp = Typ.Manual(ns, desc.Name); GrpcClientTyp = Typ.Nested(ProtoTyp, $"{desc.Name}Client"); SettingsTyp = Typ.Manual(ns, $"{desc.Name}Settings"); BuilderTyp = Typ.Manual(ns, $"{desc.Name}ClientBuilder"); ClientAbstractTyp = Typ.Manual(ns, $"{desc.Name}Client"); ClientImplTyp = Typ.Manual(ns, $"{desc.Name}ClientImpl"); DefaultHost = desc.GetExtension(ClientExtensions.DefaultHost) ?? ""; DefaultPort = 443; // Hardcoded; this is not specifiable by proto annotation. var oauthScopes = desc.GetExtension(ClientExtensions.OauthScopes); DefaultScopes = string.IsNullOrEmpty(oauthScopes) ? Enumerable.Empty <string>() : oauthScopes.Split(',', ' '); Methods = desc.Methods.Select(x => MethodDetails.Create(this, x)).ToList(); SnippetsTyp = Typ.Manual(SnippetsNamespace, $"Generated{desc.Name}ClientSnippets"); StandaloneSnippetsTyp = Typ.Manual(SnippetsNamespace, $"Generated{desc.Name}ClientStandaloneSnippets"); SnippetsClientName = $"{desc.Name.ToLowerCamelCase()}Client"; UnitTestsTyp = Typ.Manual(UnitTestsNamespace, $"Generated{desc.Name}ClientTest"); NonStandardLro = NonStandardLroDetails.ForService(desc); }
public static IEnumerable <ResultFile> Generate(IReadOnlyList <FileDescriptorProto> descriptorProtos, IEnumerable <string> filesToGenerate, IClock clock, string grpcServiceConfigPath, IEnumerable <string> commonResourcesConfigPaths, bool generateMetadata) { var descriptors = FileDescriptor.BuildFromByteStrings(descriptorProtos.Select(proto => proto.ToByteString()), s_registry); // Load side-loaded configurations; both optional. var grpcServiceConfig = grpcServiceConfigPath != null?ServiceConfig.Parser.ParseJson(File.ReadAllText(grpcServiceConfigPath)) : null; var commonResourcesConfigs = commonResourcesConfigPaths != null? commonResourcesConfigPaths.Select(path => CommonResources.Parser.ParseJson(File.ReadAllText(path))) : null; // TODO: Multi-package support not tested. var filesToGenerateSet = filesToGenerate.ToHashSet(); var byPackage = descriptors.Where(x => filesToGenerateSet.Contains(x.Name)).GroupBy(x => x.Package).ToList(); if (byPackage.Count == 0) { throw new InvalidOperationException("No packages specified to build."); } foreach (var singlePackageFileDescs in byPackage) { var namespaces = singlePackageFileDescs.Select(x => x.CSharpNamespace()).Distinct().ToList(); if (namespaces.Count > 1) { throw new InvalidOperationException( "All files in the same package must have the same C# namespace. " + $"Found namespaces '{string.Join(", ", namespaces)}' in package '{singlePackageFileDescs.Key}'."); } var catalog = new ProtoCatalog(singlePackageFileDescs.Key, descriptors, commonResourcesConfigs); foreach (var resultFile in GeneratePackage(namespaces[0], singlePackageFileDescs, catalog, clock, grpcServiceConfig, generateMetadata)) { yield return(resultFile); } } }
public ServiceDetails(ProtoCatalog catalog, string ns, ServiceDescriptor desc, ServiceConfig grpcServiceConfig, Service serviceConfig, ApiTransports transports) { Catalog = catalog; Namespace = ns; ProtoPackage = desc.File.Package; PackageVersion = ProtoPackage.Split('.').FirstOrDefault(part => ApiVersionPattern.IsMatch(part)); DocLines = desc.Declaration.DocLines().ToList(); SnippetsNamespace = $"{ns}.Snippets"; UnitTestsNamespace = $"{ns}.Tests"; // Must come early; used by `MethodDetails.Create()` MethodGrpcConfigsByName = grpcServiceConfig?.MethodConfig .SelectMany(conf => conf.Name.Select(name => (name, conf))) .Where(x => x.name.Service == desc.FullName) .ToImmutableDictionary(x => $"{x.name.Service}/{x.name.Method}", x => x.conf) ?? ImmutableDictionary <string, MethodConfig> .Empty; ServiceFullName = desc.FullName; ServiceName = desc.Name; DocumentationName = desc.Name; // TODO: There may be a more suitable name than this. ProtoTyp = Typ.Manual(ns, desc.Name); GrpcClientTyp = Typ.Nested(ProtoTyp, $"{desc.Name}Client"); SettingsTyp = Typ.Manual(ns, $"{desc.Name}Settings"); BuilderTyp = Typ.Manual(ns, $"{desc.Name}ClientBuilder"); ClientAbstractTyp = Typ.Manual(ns, $"{desc.Name}Client"); ClientImplTyp = Typ.Manual(ns, $"{desc.Name}ClientImpl"); DefaultHost = desc.GetExtension(ClientExtensions.DefaultHost) ?? ""; // We need to account for regional default endpoints like "us-east1-pubsub.googleapis.com" DefaultHostServiceName = DefaultHost .Split('.', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault() ?.Split('-', StringSplitOptions.RemoveEmptyEntries).LastOrDefault(); DefaultPort = 443; // Hardcoded; this is not specifiable by proto annotation. var oauthScopes = desc.GetExtension(ClientExtensions.OauthScopes); DefaultScopes = string.IsNullOrEmpty(oauthScopes) ? Enumerable.Empty <string>() : oauthScopes.Split(',', ' '); Methods = desc.Methods.Select(x => MethodDetails.Create(this, x)).ToList(); ServiceSnippetsTyp = Typ.Manual(SnippetsNamespace, $"AllGenerated{desc.Name}ClientSnippets"); SnippetsTyp = Typ.Manual(SnippetsNamespace, $"Generated{desc.Name}ClientSnippets"); SnippetsClientName = $"{desc.Name.ToLowerCamelCase()}Client"; UnitTestsTyp = Typ.Manual(UnitTestsNamespace, $"Generated{desc.Name}ClientTest"); NonStandardLro = NonStandardLroDetails.ForService(desc); Mixins = serviceConfig?.Apis .Select(api => AvailableMixins.GetValueOrDefault(api.Name)) .Where(mixin => mixin is object) // Don't use mixins within the package that contains that mixin. .Where(mixin => mixin.GapicClientType.Namespace != ns) .ToList() ?? Enumerable.Empty <MixinDetails>(); Transports = transports; }
private static IEnumerable <ResultFile> GeneratePackage(string ns, IEnumerable <FileDescriptor> packageFileDescriptors, ProtoCatalog catalog, IClock clock, ServiceConfig grpcServiceConfig, bool generateMetadata) { var clientPathPrefix = $"{ns}{Path.DirectorySeparatorChar}"; var snippetsPathPrefix = $"{ns}.Snippets{Path.DirectorySeparatorChar}"; var standaloneSnippetsPathPrefix = $"{ns}.StandaloneSnippets{Path.DirectorySeparatorChar}"; var unitTestsPathPrefix = $"{ns}.Tests{Path.DirectorySeparatorChar}"; bool hasLro = false; bool hasContent = false; HashSet <string> allResourceNameClasses = new HashSet <string>(); HashSet <string> duplicateResourceNameClasses = new HashSet <string>(); var allServiceDetails = new List <ServiceDetails>(); foreach (var fileDesc in packageFileDescriptors) { foreach (var service in fileDesc.Services) { // Generate settings and client code for requested package. var serviceDetails = new ServiceDetails(catalog, ns, service, grpcServiceConfig); allServiceDetails.Add(serviceDetails); var ctx = SourceFileContext.CreateFullyAliased(clock, s_wellknownNamespaceAliases); var code = ServiceCodeGenerator.Generate(ctx, serviceDetails); var filename = $"{clientPathPrefix}{serviceDetails.ClientAbstractTyp.Name}.g.cs"; yield return(new ResultFile(filename, code)); // Generate snippets for the service // TODO: Consider removing this once we have integrated the standalone snippets // with docs generation. var snippetCtx = SourceFileContext.CreateUnaliased( clock, s_wellknownNamespaceAliases, s_avoidAliasingNamespaceRegex, maySkipOwnNamespaceImport: true); var snippetCode = SnippetCodeGenerator.Generate(snippetCtx, serviceDetails); var snippetFilename = $"{snippetsPathPrefix}{serviceDetails.ClientAbstractTyp.Name}Snippets.g.cs"; yield return(new ResultFile(snippetFilename, snippetCode)); // Generate standalone snippets for the service // TODO: Integrate standalone snippets with docs generation. // TODO: Once (and if we) generate just one set of snippets, stop using "standalone" as a differentiatior. foreach (var snippetGenerator in SnippetCodeGenerator.StandaloneGenerators(serviceDetails)) { var standaloneSnippetCtx = SourceFileContext.CreateUnaliased( clock, s_wellknownNamespaceAliases, s_avoidAliasingNamespaceRegex, maySkipOwnNamespaceImport: false); var standaloneSnippetCode = snippetGenerator.Generate(standaloneSnippetCtx); var standaloneSnippetFilename = $"{standaloneSnippetsPathPrefix}{serviceDetails.ClientAbstractTyp.Name}.{snippetGenerator.SnippetMethodName}Snippet.g.cs"; yield return(new ResultFile(standaloneSnippetFilename, standaloneSnippetCode)); } // Generate unit tests for the the service. var unitTestCtx = SourceFileContext.CreateFullyAliased(clock, s_wellknownNamespaceAliases); var unitTestCode = UnitTestCodeGeneration.Generate(unitTestCtx, serviceDetails); var unitTestFilename = $"{unitTestsPathPrefix}{serviceDetails.ClientAbstractTyp.Name}Test.g.cs"; yield return(new ResultFile(unitTestFilename, unitTestCode)); // Record whether LRO is used. hasLro |= serviceDetails.Methods.Any(x => x is MethodDetails.Lro); hasContent = true; } var resCtx = SourceFileContext.CreateFullyAliased(clock, s_wellknownNamespaceAliases); var(resCode, resCodeClassCount) = ResourceNamesGenerator.Generate(catalog, resCtx, fileDesc); // Only produce an output file if it contains >0 [partial] classes. if (resCodeClassCount > 0) { // Keep track of the resource names, to spot duplicates var resourceNameClasses = catalog.GetResourceDefsByFile(fileDesc) .Where(def => def.HasNotWildcard && !def.IsCommon) .Select(def => def.ResourceNameTyp.Name); foreach (var resourceNameClass in resourceNameClasses) { if (!allResourceNameClasses.Add(resourceNameClass)) { duplicateResourceNameClasses.Add(resourceNameClass); } } // Yield the file to be generated. var filenamePrefix = Path.GetFileNameWithoutExtension(fileDesc.Name).ToUpperCamelCase(); var resFilename = $"{clientPathPrefix}{filenamePrefix}ResourceNames.g.cs"; yield return(new ResultFile(resFilename, resCode)); hasContent = true; } } // Now we've processed all the files, check for duplicate resource names. if (duplicateResourceNameClasses.Count > 0) { throw new InvalidOperationException($"The following resource name classes were created multiple times: {string.Join(", ", duplicateResourceNameClasses)}"); } // Only output csproj's if there is any other generated content. // When processing a (proto) package without any services there will be no generated content. if (hasContent) { // Generate client csproj. var csprojContent = CsProjGenerator.GenerateClient(hasLro); var csprojFilename = $"{clientPathPrefix}{ns}.csproj"; yield return(new ResultFile(csprojFilename, csprojContent)); // Generate snippets csproj. var snippetsCsprojContent = CsProjGenerator.GenerateSnippets(ns); var snippetsCsProjFilename = $"{snippetsPathPrefix}{ns}.Snippets.csproj"; yield return(new ResultFile(snippetsCsProjFilename, snippetsCsprojContent)); // Generate standalone snippets csproj. var standaloneSnippetsCsprojContent = CsProjGenerator.GenerateSnippets(ns); var standaloneSnippetsCsProjFilename = $"{standaloneSnippetsPathPrefix}{ns}.StandaloneSnippets.csproj"; yield return(new ResultFile(standaloneSnippetsCsProjFilename, standaloneSnippetsCsprojContent)); // Generate unit-tests csproj. var unitTestsCsprojContent = CsProjGenerator.GenerateUnitTests(ns); var unitTestsCsprojFilename = $"{unitTestsPathPrefix}{ns}.Tests.csproj"; yield return(new ResultFile(unitTestsCsprojFilename, unitTestsCsprojContent)); if (generateMetadata && allServiceDetails.Any()) { // Generate gapic_metadata.json, if there are any services. var gapicMetadataJsonContent = MetadataGenerator.GenerateGapicMetadataJson(allServiceDetails); yield return(new ResultFile("gapic_metadata.json", gapicMetadataJsonContent)); } } }
private ResourceNamesGenerator(ProtoCatalog catalog, SourceFileContext ctx, FileDescriptor fileDesc) => (_catalog, _ctx, _fileDesc) = (catalog, ctx, fileDesc);
public static (CompilationUnitSyntax, int) Generate(ProtoCatalog catalog, SourceFileContext ctx, FileDescriptor fileDesc) =>
private static IEnumerable <ResultFile> GeneratePackage(string ns, IEnumerable <FileDescriptor> packageFileDescriptors, ProtoCatalog catalog, IClock clock, ServiceConfig grpcServiceConfig, Service serviceConfig, List <ServiceDetails> allServiceDetails, ApiTransports transports) { var clientPathPrefix = $"{ns}{Path.DirectorySeparatorChar}"; var serviceSnippetsPathPrefix = $"{ns}.Snippets{Path.DirectorySeparatorChar}"; var snippetsPathPrefix = $"{ns}.GeneratedSnippets{Path.DirectorySeparatorChar}"; var unitTestsPathPrefix = $"{ns}.Tests{Path.DirectorySeparatorChar}"; bool hasLro = false; HashSet <string> mixins = new HashSet <string>(); bool hasContent = false; var packageServiceDetails = new List <ServiceDetails>(); HashSet <string> allResourceNameClasses = new HashSet <string>(); HashSet <string> duplicateResourceNameClasses = new HashSet <string>(); IList <Snippet> snippets = new List <Snippet>(); IEnumerable <Typ> packageTyps = packageFileDescriptors.SelectMany( fileDescriptor => fileDescriptor.Services.Select(serv => Typ.Manual(ns, serv.Name)) .Union(fileDescriptor.MessageTypes.Select(msg => Typ.Manual(ns, msg.Name))) .Union(fileDescriptor.EnumTypes.Select(e => Typ.Manual(ns, e.Name, isEnum: true)))); var seenPaginatedResponseTyps = new HashSet <Typ>(); foreach (var fileDesc in packageFileDescriptors) { foreach (var service in fileDesc.Services) { // Generate settings and client code for requested package. var serviceDetails = new ServiceDetails(catalog, ns, service, grpcServiceConfig, serviceConfig, transports); packageServiceDetails.Add(serviceDetails); var ctx = SourceFileContext.CreateFullyAliased(clock, s_wellknownNamespaceAliases); var code = ServiceCodeGenerator.Generate(ctx, serviceDetails, seenPaginatedResponseTyps); var filename = $"{clientPathPrefix}{serviceDetails.ClientAbstractTyp.Name}.g.cs"; yield return(new ResultFile(filename, code)); // Generate service-per-file snippets for the service // TODO: Consider removing this once we have integrated the snippet-per-file snippets // with docs generation. var serviceSnippetsCtx = SourceFileContext.CreateUnaliased( clock, s_wellknownNamespaceAliases, s_avoidAliasingNamespaceRegex, packageTyps, maySkipOwnNamespaceImport: true); var serviceSnippetsCode = SnippetCodeGenerator.Generate(serviceSnippetsCtx, serviceDetails); var serviceSnippetsFilename = $"{serviceSnippetsPathPrefix}{serviceDetails.ClientAbstractTyp.Name}Snippets.g.cs"; yield return(new ResultFile(serviceSnippetsFilename, serviceSnippetsCode)); // Generate snippet-per-file snippets for the service // TODO: Integrate snippet-per-file snippets with docs generation. foreach (var snippetGenerator in SnippetCodeGenerator.SnippetsGenerators(serviceDetails)) { var snippetCtx = SourceFileContext.CreateUnaliased( clock, s_wellknownNamespaceAliases, s_avoidAliasingNamespaceRegex, packageTyps, maySkipOwnNamespaceImport: false); var(snippetCode, snippetMetadata) = snippetGenerator.Generate(snippetCtx); snippetMetadata.File = $"{serviceDetails.ClientAbstractTyp.Name}.{snippetGenerator.SnippetMethodName}Snippet.g.cs"; var snippetFile = $"{snippetsPathPrefix}{snippetMetadata.File}"; snippets.Add(snippetMetadata); yield return(new ResultFile(snippetFile, snippetCode)); } // Generate unit tests for the the service. var unitTestCtx = SourceFileContext.CreateFullyAliased(clock, s_wellknownNamespaceAliases); var unitTestCode = UnitTestCodeGeneration.Generate(unitTestCtx, serviceDetails); var unitTestFilename = $"{unitTestsPathPrefix}{serviceDetails.ClientAbstractTyp.Name}Test.g.cs"; yield return(new ResultFile(unitTestFilename, unitTestCode)); // Record whether LRO/mixins are used. hasLro |= serviceDetails.Methods.Any(x => x is MethodDetails.Lro); foreach (var mixin in serviceDetails.Mixins) { mixins.Add(mixin.GrpcServiceName); } hasContent = true; } var resCtx = SourceFileContext.CreateFullyAliased(clock, s_wellknownNamespaceAliases); var(resCode, resCodeClassCount) = ResourceNamesGenerator.Generate(catalog, resCtx, fileDesc); // Only produce an output file if it contains >0 [partial] classes. if (resCodeClassCount > 0) { // Keep track of the resource names, to spot duplicates var resourceNameClasses = catalog.GetResourceDefsByFile(fileDesc) .Where(def => def.HasNotWildcard && !def.IsCommon) .Select(def => def.ResourceNameTyp.Name); foreach (var resourceNameClass in resourceNameClasses) { if (!allResourceNameClasses.Add(resourceNameClass)) { duplicateResourceNameClasses.Add(resourceNameClass); } } // Yield the file to be generated. var filenamePrefix = Path.GetFileNameWithoutExtension(fileDesc.Name).ToUpperCamelCase(); var resFilename = $"{clientPathPrefix}{filenamePrefix}ResourceNames.g.cs"; yield return(new ResultFile(resFilename, resCode)); hasContent = true; } var lroAdaptationCtx = SourceFileContext.CreateFullyAliased(clock, s_wellknownNamespaceAliases); var(lroCode, lroClassCount) = LroAdaptationGenerator.Generate(catalog, lroAdaptationCtx, fileDesc); if (lroClassCount > 0) { var filenamePrefix = Path.GetFileNameWithoutExtension(fileDesc.Name).ToUpperCamelCase(); var lroAdaptationFilename = $"{clientPathPrefix}{filenamePrefix}LroAdaptation.g.cs"; yield return(new ResultFile(lroAdaptationFilename, lroCode)); hasContent = true; } } // Now we've processed all the files, check for duplicate resource names. if (duplicateResourceNameClasses.Count > 0) { throw new InvalidOperationException($"The following resource name classes were created multiple times: {string.Join(", ", duplicateResourceNameClasses)}"); } // Only output csproj's and snippet metadata if there is any other generated content. // When processing a (proto) package without any services there will be no generated content. if (hasContent) { // Generate client csproj. var csprojContent = CsProjGenerator.GenerateClient(hasLro, mixins); var csprojFilename = $"{clientPathPrefix}{ns}.csproj"; yield return(new ResultFile(csprojFilename, csprojContent)); // If we only generated resources, we don't need to generate all of these. if (packageServiceDetails.Count > 0) { allServiceDetails.AddRange(packageServiceDetails); // Generate the package-wide API metadata var ctx = SourceFileContext.CreateFullyAliased(clock, s_wellknownNamespaceAliases); var packageApiMetadataContent = PackageApiMetadataGenerator.GeneratePackageApiMetadata(ns, ctx, packageFileDescriptors); var packageApiMetadataFilename = $"{clientPathPrefix}{PackageApiMetadataGenerator.FileName}"; yield return(new ResultFile(packageApiMetadataFilename, packageApiMetadataContent)); // Generate snippets csproj. var serviceSnippetsCsprojContent = CsProjGenerator.GenerateSnippets(ns); var serviceSnippetsCsProjFilename = $"{serviceSnippetsPathPrefix}{ns}.Snippets.csproj"; yield return(new ResultFile(serviceSnippetsCsProjFilename, serviceSnippetsCsprojContent)); // Generate dependency injection extension methods var dependencyInjectionExtensionsContent = ServiceCollectionExtensionsGenerator.GenerateExtensions(ctx, packageServiceDetails); var dependencyInjectionExtensionsFilename = $"{clientPathPrefix}{ServiceCollectionExtensionsGenerator.FileName}"; yield return(new ResultFile(dependencyInjectionExtensionsFilename, dependencyInjectionExtensionsContent)); // Generate snippet metadata (only for snippet-per-file snippets). // All services in this package have the same package information, namespace etc. so, we // just pick the first one var serviceDetails = packageServiceDetails.First(); var snippetIndexJsonContent = SnippetCodeGenerator.GenerateSnippetIndexJson(snippets, serviceDetails); yield return(new ResultFile($"{snippetsPathPrefix}snippet_metadata_{serviceDetails.ProtoPackage}.json", snippetIndexJsonContent)); // Generate snippet-per-file snippets csproj. var snippetsCsprojContent = CsProjGenerator.GenerateSnippets(ns); var snippetsCsProjFilename = $"{snippetsPathPrefix}{ns}.GeneratedSnippets.csproj"; yield return(new ResultFile(snippetsCsProjFilename, snippetsCsprojContent)); // Generate unit-tests csproj. var unitTestsCsprojContent = CsProjGenerator.GenerateUnitTests(ns); var unitTestsCsprojFilename = $"{unitTestsPathPrefix}{ns}.Tests.csproj"; yield return(new ResultFile(unitTestsCsprojFilename, unitTestsCsprojContent)); } } }
public static IEnumerable <ResultFile> Generate(IReadOnlyList <FileDescriptorProto> descriptorProtos, IEnumerable <string> filesToGenerate, IClock clock, string grpcServiceConfigPath, string serviceConfigPath, IEnumerable <string> commonResourcesConfigPaths, ApiTransports transports) { var descriptors = FileDescriptor.BuildFromByteStrings(descriptorProtos.Select(proto => proto.ToByteString()), s_registry); // Load side-loaded configurations; both optional. var grpcServiceConfig = grpcServiceConfigPath is object?ServiceConfig.Parser.ParseJson(File.ReadAllText(grpcServiceConfigPath)) : null; var serviceConfig = ParseServiceConfigYaml(serviceConfigPath); var commonResourcesConfigs = commonResourcesConfigPaths != null? commonResourcesConfigPaths.Select(path => CommonResources.Parser.ParseJson(File.ReadAllText(path))) : null; // TODO: Multi-package support not tested. var filesToGenerateSet = filesToGenerate.ToHashSet(); var byPackage = descriptors.Where(x => filesToGenerateSet.Contains(x.Name)).GroupBy(x => x.Package).ToList(); if (byPackage.Count == 0) { throw new InvalidOperationException("No packages specified to build."); } // Collect all service details here to emit one `gapic_metadata.json` file for multi-package situations (e.g. Kms with Iam) var allServiceDetails = new List <ServiceDetails>(); foreach (var singlePackageFileDescs in byPackage) { var namespaces = singlePackageFileDescs.Select(x => x.CSharpNamespace()).Distinct().ToList(); if (namespaces.Count > 1) { throw new InvalidOperationException( "All files in the same package must have the same C# namespace. " + $"Found namespaces '{string.Join(", ", namespaces)}' in package '{singlePackageFileDescs.Key}'."); } var catalog = new ProtoCatalog(singlePackageFileDescs.Key, descriptors, singlePackageFileDescs, commonResourcesConfigs); foreach (var resultFile in GeneratePackage(namespaces[0], singlePackageFileDescs, catalog, clock, grpcServiceConfig, serviceConfig, allServiceDetails, transports)) { yield return(resultFile); } } // We assume that the first service we've generated corresponds to the service config (if we have one), // and is a service from the primary library we're generating. This is used for API validation and // gapic_metadata.json generation. This means it doesn't matter (for gapic_metadata.json) // if we're actually asked to generate more services than we really want. This currently // happens for services with IAM/location mix-ins, for example. var primaryLibraryProtoPackage = allServiceDetails.FirstOrDefault()?.ProtoPackage; var primaryLibraryServices = allServiceDetails.Where(s => s.ProtoPackage == primaryLibraryProtoPackage).ToList(); if (primaryLibraryServices.Any()) { // Generate gapic_metadata.json, if there are any services. var gapicMetadataJsonContent = MetadataGenerator.GenerateGapicMetadataJson(primaryLibraryServices); yield return(new ResultFile("gapic_metadata.json", gapicMetadataJsonContent)); } var unhandledApis = (serviceConfig?.Apis.Select(api => api.Name) ?? Enumerable.Empty <string>()) .Except(primaryLibraryServices.Select(s => s.ServiceFullName)) .Except(AllowedAdditionalServices) .ToList(); if (unhandledApis.Any()) { throw new InvalidOperationException($"Unhandled APIs in service config: {string.Join(", ", unhandledApis)}"); } }
private static IEnumerable <ResultFile> GeneratePackage(string ns, IEnumerable <FileDescriptor> packageFileDescriptors, ProtoCatalog catalog, IClock clock, ServiceConfig grpcServiceConfig) { var clientPathPrefix = $"{ns}{Path.DirectorySeparatorChar}"; var snippetsPathPrefix = $"{ns}.Snippets{Path.DirectorySeparatorChar}"; var unitTestsPathPrefix = $"{ns}.Tests{Path.DirectorySeparatorChar}"; bool hasLro = false; bool hasContent = false; foreach (var fileDesc in packageFileDescriptors) { foreach (var service in fileDesc.Services) { // Generate settings and client code for requested package. var serviceDetails = new ServiceDetails(catalog, ns, service, grpcServiceConfig); var ctx = SourceFileContext.CreateFullyAliased(clock, s_wellknownNamespaceAliases); var code = ServiceCodeGenerator.Generate(ctx, serviceDetails); var filename = $"{clientPathPrefix}{serviceDetails.ClientAbstractTyp.Name}.g.cs"; yield return(new ResultFile(filename, code)); // Generate snippets for the service var snippetCtx = SourceFileContext.CreateUnaliased(clock); var snippetCode = SnippetCodeGenerator.Generate(snippetCtx, serviceDetails); var snippetFilename = $"{snippetsPathPrefix}{serviceDetails.ClientAbstractTyp.Name}Snippets.g.cs"; yield return(new ResultFile(snippetFilename, snippetCode)); // Generate unit tests for the the service. var unitTestCtx = SourceFileContext.CreateFullyAliased(clock, s_wellknownNamespaceAliases); var unitTestCode = UnitTestCodeGeneration.Generate(unitTestCtx, serviceDetails); var unitTestFilename = $"{unitTestsPathPrefix}{serviceDetails.ClientAbstractTyp.Name}Test.g.cs"; yield return(new ResultFile(unitTestFilename, unitTestCode)); // Record whether LRO is used. hasLro |= serviceDetails.Methods.Any(x => x is MethodDetails.Lro); hasContent = true; } var resCtx = SourceFileContext.CreateFullyAliased(clock, s_wellknownNamespaceAliases); var(resCode, resCodeClassCount) = ResourceNamesGenerator.Generate(catalog, resCtx, fileDesc); // Only produce an output file if it contains >0 [partial] classes. if (resCodeClassCount > 0) { var filenamePrefix = Path.GetFileNameWithoutExtension(fileDesc.Name).ToUpperCamelCase(); var resFilename = $"{clientPathPrefix}{filenamePrefix}ResourceNames.g.cs"; yield return(new ResultFile(resFilename, resCode)); hasContent = true; } } // Only output csproj's if there is any other generated content. // When processing a (proto) package without any services there will be no generated content. if (hasContent) { // Generate client csproj. var csprojContent = CsProjGenerator.GenerateClient(hasLro); var csprojFilename = $"{clientPathPrefix}{ns}.csproj"; yield return(new ResultFile(csprojFilename, csprojContent)); // Generate snippets csproj. var snippetsCsprojContent = CsProjGenerator.GenerateSnippets(ns); var snippetsCsProjFilename = $"{snippetsPathPrefix}{ns}.Snippets.csproj"; yield return(new ResultFile(snippetsCsProjFilename, snippetsCsprojContent)); // Generate unit-tests csproj. var unitTestsCsprojContent = CsProjGenerator.GenerateUnitTests(ns); var unitTestsCsprojFilename = $"{unitTestsPathPrefix}{ns}.Tests.csproj"; yield return(new ResultFile(unitTestsCsprojFilename, unitTestsCsprojContent)); } }
private LroAdaptationGenerator(ProtoCatalog catalog, SourceFileContext ctx, FileDescriptor fileDesc) => (_catalog, _ctx, _fileDesc) = (catalog, ctx, fileDesc);