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)); } } }
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)}"); } }