Example #1
0
        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);
        }
Example #2
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);
        }