private static async Task <int> Main(string[] args) { FixClientBuilder(); // TODO: Figure out why `dotnet run` from generateapis.sh is sending 6 args instead of 3 as in: arg1 arg2 arg3 arg1 arg2 arg3 if (args.Length < 3) { Console.WriteLine("Arguments: <project file> <api client name> <user client name>"); return(1); } var projectFile = args[0]; var apiClientName = args[1]; var userClientName = args[2]; MSBuildLocator.RegisterDefaults(); var workspace = MSBuildWorkspace.Create(new Dictionary <string, string> { ["TargetFramework"] = "net45" }); Project project; try { project = await workspace.OpenProjectAsync(projectFile); } catch (FileNotFoundException) { Console.WriteLine($"Could not find project file {projectFile}"); return(2); } var compilation = await project.GetCompilationAsync(); var symbols = compilation.GetSymbolsWithName(name => name == apiClientName, SymbolFilter.Type).ToList(); if (symbols.Count != 1) { Console.WriteLine($"Could not find type {apiClientName}"); var errors = compilation.GetDiagnostics().Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); if (errors.Count != 0) { Console.WriteLine($"Errors: {errors.Count}, first error: {errors[0]}"); } return(2); } var apiClientSymbol = (INamedTypeSymbol)symbols[0]; // Find the API client from a generated file (there should be only one). var generatedApiClientSyntax = apiClientSymbol.DeclaringSyntaxReferences .Select(syntaxReference => syntaxReference.GetSyntax()) .Cast <ClassDeclarationSyntax>() .SingleOrDefault( syntax => syntax.SyntaxTree.GetRoot().GetLeadingTrivia().ToFullString().Contains("// Generated code. DO NOT EDIT!")); if (generatedApiClientSyntax == null) { Console.WriteLine($"Could not find an auto-generated file containing the {apiClientName} definition"); return(3); } var syntaxTree = generatedApiClientSyntax.SyntaxTree; var semanticModel = compilation.GetSemanticModel(syntaxTree); var generator = SyntaxGenerator.GetGenerator(project.GetDocument(syntaxTree)); var requestMethodRewriter = new RequestMethodRewriter(semanticModel); var nonRequestMethodRewriter = new ClientMethodRewriter(semanticModel); var requestMethodToImplRewriter = new RequestMethodToImplRewriter(); var convertToAsyncCancellationTokenOverload = new ConvertToAsyncCancellationTokenOverloadRewriter(); var userClientSyntax = (ClassDeclarationSyntax)generator.ClassDeclaration( userClientName, accessibility: Accessibility.Public, modifiers: DeclarationModifiers.Partial) .WithLeadingTrivia(generatedApiClientSyntax.GetLeadingTrivia()); var userClientImplSyntax = (ClassDeclarationSyntax)generator.ClassDeclaration( userClientName + "Impl", accessibility: Accessibility.Public, modifiers: DeclarationModifiers.Sealed | DeclarationModifiers.Partial, baseType: ParseTypeName(userClientName)) .WithLeadingTrivia(generatedApiClientSyntax.GetLeadingTrivia()); // Copy the request methods from the API client with the user client. We only require these to be copied since // we already have handwritten flattening overloads to change ByteString to BigtableByteString. foreach (var methodSyntax in generatedApiClientSyntax.Members.OfType <MethodDeclarationSyntax>()) { var method = semanticModel.GetDeclaredSymbol(methodSyntax); if (method.DeclaredAccessibility != Accessibility.Public || method.Parameters.IsEmpty) { continue; } if (method.Parameters.Length == 2 && method.Parameters[0].Type.Name.EndsWith("Request")) { var clientMethod = (MethodDeclarationSyntax)requestMethodRewriter.Visit(methodSyntax); userClientSyntax = userClientSyntax.AddMembers(clientMethod); if (method.Parameters[1].Type.Name.EndsWith(nameof(CallSettings))) { var clientImplMethod = (MethodDeclarationSyntax)requestMethodToImplRewriter.Visit(clientMethod); if (s_customStreamMethods.TryGetValue(method.Name, out var customStreamMethodInfo) && customStreamMethodInfo.SplitSyncAndAsync) { var asyncMethod = clientMethod.ToAsync(); userClientSyntax = userClientSyntax.AddMembers(asyncMethod); var asyncMethodWithCancellationToken = (MethodDeclarationSyntax)convertToAsyncCancellationTokenOverload.Visit(asyncMethod); userClientSyntax = userClientSyntax.AddMembers(asyncMethodWithCancellationToken); var clientImplSyncMethod = clientImplMethod.WithBodySafe( Task().Member(nameof(System.Threading.Tasks.Task.Run)) .Invoke(Lambda(asyncMethod.Invoke(clientImplMethod.ParameterList.AsArguments()))) .Member(nameof(gax::TaskExtensions.ResultWithUnwrappedExceptions)).Invoke()); userClientImplSyntax = userClientImplSyntax.AddMembers(clientImplSyncMethod); var clientImplAsyncMethod = clientImplMethod.ToAsync(); userClientImplSyntax = userClientImplSyntax.AddMembers(clientImplAsyncMethod); } else { userClientImplSyntax = userClientImplSyntax.AddMembers(clientImplMethod); } } } else if ( method.Name != "Create" && method.Name != "CreateAsync" && !s_customStreamMethods.ContainsKey(method.Name) && !method.Parameters.Any(p => p.Type.Name == "ByteString")) { // TODO: We could also have this remap to BigtableByteString automatically, but we currently // have some validations for most methods with ByteStrings which I think we's lose by // autogenerating at the moment. // For any other methods which aren't custom streaming methods and which don't use ByteString, // which we remap to BigtableByteString, copy them (with some small fix ups) into the generated // client. userClientSyntax = userClientSyntax.AddMembers( (MethodDeclarationSyntax)nonRequestMethodRewriter.Visit(methodSyntax)); } } // Create a CompilationUnitSyntax from the Usings node of the original file, which will also contain the // copyright notice and generated code warnings in its leading trivia. // We also need a using directive for GAX, so that we can use ResultWithUnwrappedExceptions. var usings = syntaxTree.GetCompilationUnitRoot().Usings .Add(UsingDirective(ParseName(typeof(gax::TaskExtensions).Namespace))); var compilationUnit = CompilationUnit().WithUsings(usings); // Add in the namespace with the ...Client and ...ClientImpl classes. compilationUnit = compilationUnit.AddMembers( (NamespaceDeclarationSyntax)generator.NamespaceDeclaration( apiClientSymbol.ContainingNamespace.ToDisplayString(), userClientSyntax, userClientImplSyntax)); // Use idiomatic formatting for the project. compilationUnit = (CompilationUnitSyntax)Formatter.Format( compilationUnit, workspace, workspace.Options.WithChangedOption(FormattingOptions.NewLine, LanguageNames.CSharp, "\n")); var resultText = compilationUnit.ToFullString(); try { var resultPath = Path.Combine(Path.GetDirectoryName(projectFile), $"{userClientName}.cs"); File.WriteAllText(resultPath, resultText); } catch (Exception e) { Console.WriteLine($"Could not write the auto-generated {userClientName}:\n{e}"); return(4); } return(0); }
private static async Task <int> Main(string[] args) { // TODO: Figure out why `dotnet run` from generateapis.sh is sending 6 args instead of 3 as in: arg1 arg2 arg3 arg1 arg2 arg3 if (args.Length < 3) { Console.WriteLine("Arguments: <project file> <api client name> <user client name>"); return(1); } var projectFile = args[0]; var apiClientName = args[1]; var userClientName = args[2]; var workspace = MSBuildWorkspace.Create(new Dictionary <string, string> { ["TargetFramework"] = "net45" }); Project project; try { project = await workspace.OpenProjectAsync(projectFile); } catch (FileNotFoundException) { Console.WriteLine($"Could not find project file {projectFile}"); return(2); } var compilation = await project.GetCompilationAsync(); var symbols = compilation.GetSymbolsWithName(name => name == apiClientName, SymbolFilter.Type).ToList(); if (symbols.Count != 1) { Console.WriteLine($"Could not find type {apiClientName}"); return(2); } var apiClientSymbol = (INamedTypeSymbol)symbols[0]; // Find the API client from a generated file (there should be only one). var generatedApiClientSyntax = apiClientSymbol.DeclaringSyntaxReferences .Select(syntaxReference => syntaxReference.GetSyntax()) .Cast <ClassDeclarationSyntax>() .SingleOrDefault( syntax => syntax.SyntaxTree.GetRoot().GetLeadingTrivia().ToFullString().Contains("// Generated code. DO NOT EDIT!")); if (generatedApiClientSyntax == null) { Console.WriteLine($"Could not find an auto-generated file containing the {apiClientName} definition"); return(3); } var syntaxTree = generatedApiClientSyntax.SyntaxTree; var semanticModel = compilation.GetSemanticModel(syntaxTree); var generator = SyntaxGenerator.GetGenerator(project.GetDocument(syntaxTree)); var requestMethodRewriter = new RequestMethodRewriter(semanticModel); var requestMethodToImplRewriter = new RequestMethodToImplRewriter(); var userClientSyntax = (ClassDeclarationSyntax)generator.ClassDeclaration( userClientName, accessibility: Accessibility.Public, modifiers: DeclarationModifiers.Partial) .WithLeadingTrivia(generatedApiClientSyntax.GetLeadingTrivia()); var userClientImplSyntax = (ClassDeclarationSyntax)generator.ClassDeclaration( userClientName + "Impl", accessibility: Accessibility.Public, modifiers: DeclarationModifiers.Sealed | DeclarationModifiers.Partial, baseType: ParseTypeName(userClientName)) .WithLeadingTrivia(generatedApiClientSyntax.GetLeadingTrivia()); // Copy the request methods from the API client with the user client. We only require these to be copied since // we already have handwritten flattening overloads to change ByteString to BigtableByteString. foreach (var methodSyntax in generatedApiClientSyntax.Members.OfType <MethodDeclarationSyntax>()) { var method = semanticModel.GetDeclaredSymbol(methodSyntax); if (method.DeclaredAccessibility != Accessibility.Public || method.Parameters.IsEmpty) { continue; } if (method.Parameters[0].Type.Name.EndsWith("Request")) { var clientMethod = (MethodDeclarationSyntax)requestMethodRewriter.Visit(methodSyntax); userClientSyntax = userClientSyntax.AddMembers(clientMethod); var clientImplMethod = (MethodDeclarationSyntax)requestMethodToImplRewriter.Visit(clientMethod); if (s_customStreamMethods.TryGetValue(method.Name, out var customStreamMethodInfo) && customStreamMethodInfo.SplitSyncAndAsync) { var asyncMethod = clientMethod.ToAsync(); userClientSyntax = userClientSyntax.AddMembers(asyncMethod); var clientImplSyncMethod = clientImplMethod.WithBody( Task().Member("Run") .Invoke(Lambda(asyncMethod.Invoke(clientImplMethod.ParameterList.AsArguments()))) .Member("ResultWithUnwrappedExceptions").Invoke()); userClientImplSyntax = userClientImplSyntax.AddMembers(clientImplSyncMethod); var clientImplAsyncMethod = clientImplMethod.ToAsync(); userClientImplSyntax = userClientImplSyntax.AddMembers(clientImplAsyncMethod); } else { userClientImplSyntax = userClientImplSyntax.AddMembers(clientImplMethod); } } } // Create a CompilationUnitSyntax from the Usings node of the original file, which will also contain the // copyright notice and generated code warnings in its leading trivia. var compilationUnit = CompilationUnit().WithUsings(syntaxTree.GetCompilationUnitRoot().Usings); // Add in the namespace with the ...Client and ...ClientImpl classes. compilationUnit = compilationUnit.AddMembers( (NamespaceDeclarationSyntax)generator.NamespaceDeclaration( apiClientSymbol.ContainingNamespace.ToDisplayString(), userClientSyntax, userClientImplSyntax)); // Use idiomatic formatting for the project. compilationUnit = (CompilationUnitSyntax)Formatter.Format( compilationUnit, workspace, workspace.Options.WithChangedOption(FormattingOptions.NewLine, LanguageNames.CSharp, "\n")); var resultText = compilationUnit.ToFullString(); try { var resultPath = Path.Combine(Path.GetDirectoryName(projectFile), $"{userClientName}.cs"); File.WriteAllText(resultPath, resultText); } catch (Exception e) { Console.WriteLine($"Could not write the auto-generated {userClientName}:\n{e}"); return(4); } return(0); }