Skip to content

Commit

Permalink
Centralize SDK generation logic
Browse files Browse the repository at this point in the history
Presently, the majority of Java code generation is driven by the
`pulumi-java-gen` binary, since Java usage began before we had time to implement
the `Generate*` family of language host gRPC methods. Recently, these gRPC
methods were implemented, to support (among other things) conformance testing.
Unfortunately, while both routes end in `pkg/codegen/java`'s `Generate*`
functions, each had accumulated its own special "setup logic" ahead of the call
into `pkg/codegen`. This commit attempts to sort this out, pushing all that
logic into `pkg/codegen` so that both routes behave identically. As a result of
this, we should be able to deprecate `pulumi-java-gen` more safely when the time
comes, remove direct build-time dependencies on `pulumi-java` from
`pulumi/pulumi` and fix some issues that have arisen as a result of the historic
differences, such as #1404 (which looks like it may have already been fixed, but
this should cement it), and #1508

Closes #1404
Fixes #1508
  • Loading branch information
lunaris committed Dec 20, 2024
1 parent d52870d commit d1aed25
Show file tree
Hide file tree
Showing 7 changed files with 286 additions and 67 deletions.
27 changes: 25 additions & 2 deletions pkg/cmd/pulumi-java-gen/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,16 @@ package main

import (
"fmt"
"os"
"path/filepath"

"github.com/blang/semver"
"github.com/hashicorp/hcl/v2"

javagen "github.com/pulumi/pulumi-java/pkg/codegen/java"
pschema "github.com/pulumi/pulumi/pkg/v3/codegen/schema"
"github.com/pulumi/pulumi/sdk/v3/go/common/diag"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil"
)

type generateJavaOptions struct {
Expand Down Expand Up @@ -53,10 +57,11 @@ func generateJava(cfg generateJavaOptions) error {
return fmt.Errorf("failed to read schema from %s: %w", cfg.Schema, err)
}

pkgSpec, err := dedupTypes(rawPkgSpec)
pkgSpec, diags, err := javagen.DeduplicateTypes(rawPkgSpec)
if err != nil {
return fmt.Errorf("failed to dedup types in schema from %s: %w", cfg.Schema, err)
}
printDiagnostics(diags)

pkg, err := pschema.ImportSpec(*pkgSpec, nil)
if err != nil {
Expand All @@ -80,7 +85,13 @@ func generateJava(cfg generateJavaOptions) error {
if err != nil {
return err
}
files, err := javagen.GeneratePackage("pulumi-java-gen", pkg, extraFiles, cfg.Local)
files, err := javagen.GeneratePackage(
"pulumi-java-gen",
pkg,
extraFiles,
nil, /*localDependencies*/
cfg.Local,
)
if err != nil {
return err
}
Expand All @@ -99,3 +110,15 @@ func generateJava(cfg generateJavaOptions) error {

return nil
}

// printDiagnostics prints the given diagnostics to stdout and stderr.
func printDiagnostics(diagnostics hcl.Diagnostics) {
sink := diag.DefaultSink(os.Stdout, os.Stderr, diag.FormatOptions{Color: cmdutil.GetGlobalColorization()})
for _, diagnostic := range diagnostics {
if diagnostic.Severity == hcl.DiagError {
sink.Errorf(diag.Message("", "%s"), diagnostic)
} else {
sink.Warningf(diag.Message("", "%s"), diagnostic)
}
}
}
69 changes: 26 additions & 43 deletions pkg/cmd/pulumi-language-java/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"time"

pbempty "github.com/golang/protobuf/ptypes/empty"
"github.com/hashicorp/hcl/v2"
"github.com/pkg/errors"
hclsyntax "github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/syntax"
"github.com/pulumi/pulumi/pkg/v3/codegen/pcl"
Expand All @@ -32,7 +33,6 @@ import (
"github.com/pulumi/pulumi/sdk/v3/go/common/util/rpcutil"
"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
pulumirpc "github.com/pulumi/pulumi/sdk/v3/proto/go"
"golang.org/x/exp/maps"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/protobuf/types/known/structpb"
Expand Down Expand Up @@ -703,57 +703,40 @@ func (host *javaLanguageHost) GeneratePackage(
return nil, err
}

pkg, diags, err := schema.BindSpec(spec, loader)
diags := hcl.Diagnostics{}

// Historically, Java has "deduplicated" PackageSpecs to reduce sets of multiple types whose names differ only in
// case down to just one type that is then shared (assuming that, apart from name, the types are otherwise
// identical). We thus perform that deduplication here before we bind the schema and resolve any references.
dedupedSpec, dedupeDiags, err := codegen.DeduplicateTypes(&spec)
if err != nil {
return nil, err
}
rpcDiagnostics := plugin.HclDiagnosticsToRPCDiagnostics(diags)
if diags.HasErrors() {
diags = diags.Extend(dedupeDiags)
if dedupeDiags.HasErrors() {
return &pulumirpc.GeneratePackageResponse{
Diagnostics: rpcDiagnostics,
Diagnostics: plugin.HclDiagnosticsToRPCDiagnostics(diags),
}, nil
}

if pkg.Description == "" {
pkg.Description = " "
}
if pkg.Repository == "" {
pkg.Repository = "https://example.com"
}

// Presently, we only support generating Java SDKs which use Gradle as a build system. Specify that here, as well as
// the set of dependencies that all generated SDKs rely on.
pkgInfo := codegen.PackageInfo{
BuildFiles: "gradle",
Dependencies: map[string]string{
"com.google.code.gson:gson": "2.8.9",
"com.google.code.findbugs:jsr305": "3.0.2",
},
pkg, bindDiags, err := schema.BindSpec(*dedupedSpec, loader)
if err != nil {
return nil, err
}

repositories := map[string]bool{}

for name, dep := range req.LocalDependencies {
parts := strings.Split(dep, ":")
if len(parts) < 3 {
return nil, fmt.Errorf(
"invalid dependency for %s %s; must be of the form groupId:artifactId:version[:repositoryPath]",
name, dep,
)
}

k := parts[0] + ":" + parts[1]
pkgInfo.Dependencies[k] = parts[2]

if len(parts) == 4 {
repositories[parts[3]] = true
}
diags = diags.Extend(bindDiags)
if bindDiags.HasErrors() {
return &pulumirpc.GeneratePackageResponse{
Diagnostics: plugin.HclDiagnosticsToRPCDiagnostics(diags),
}, nil
}

pkgInfo.Repositories = maps.Keys(repositories)
pkg.Language["java"] = pkgInfo

files, err := codegen.GeneratePackage("pulumi-language-java", pkg, req.ExtraFiles, req.Local)
files, err := codegen.GeneratePackage(
"pulumi-language-java",
pkg,
req.ExtraFiles,
req.LocalDependencies,
req.Local,
)
if err != nil {
return nil, err
}
Expand All @@ -772,7 +755,7 @@ func (host *javaLanguageHost) GeneratePackage(
}

return &pulumirpc.GeneratePackageResponse{
Diagnostics: rpcDiagnostics,
Diagnostics: plugin.HclDiagnosticsToRPCDiagnostics(diags),
}, nil
}

Expand Down
52 changes: 31 additions & 21 deletions pkg/cmd/pulumi-java-gen/dedup.go → pkg/codegen/java/dedup.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright 2022, Pulumi Corporation. All rights reserved.
// Copyright 2024, Pulumi Corporation. All rights reserved.

package main
package java

import (
"bytes"
Expand All @@ -9,15 +9,16 @@ import (
"reflect"
"strings"

pschema "github.com/pulumi/pulumi/pkg/v3/codegen/schema"
"github.com/hashicorp/hcl/v2"
"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
)

// Detects cases when identical types have similar names modulo case
// such as `azure-native:network:IpAllocationMethod` vs
// `azure-native:network:IPAllocationMethod`, deterministically picks
// one of these names, and rewrites the schema as if there was only
// one such type.
func dedupTypes(spec *pschema.PackageSpec) (*pschema.PackageSpec, error) {
// DeduplicateTypes detects multiple types in a PackageSpec whose names are the same modulo case, such as
// `azure-native:network:IpAllocationMethod` and `azure-native:network:IPAllocationMethod`, deterministically picks one
// of these names, and rewrites the schema as if there was only one such type.
func DeduplicateTypes(spec *schema.PackageSpec) (*schema.PackageSpec, hcl.Diagnostics, error) {
diags := hcl.Diagnostics{}

normalizedTokens := map[string]string{}
for typeToken := range spec.Types {
key := strings.ToUpper(typeToken)
Expand All @@ -41,12 +42,12 @@ func dedupTypes(spec *pschema.PackageSpec) (*pschema.PackageSpec, error) {

var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(spec); err != nil {
return nil, err
return nil, nil, err
}

var rawSchema interface{}
if err := json.NewDecoder(bytes.NewReader(buf.Bytes())).Decode(&rawSchema); err != nil {
return nil, err
return nil, nil, err
}

types := map[string]interface{}{}
Expand All @@ -64,13 +65,19 @@ func dedupTypes(spec *pschema.PackageSpec) (*pschema.PackageSpec, error) {
transformJSONTree(stripDescription, types[newToken]),
)
if eq {
fmt.Printf("WARN renaming %s to %s in the schema\n",
oldToken, newToken)
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: fmt.Sprintf("Renaming '%s' to '%s' in the schema", oldToken, newToken),
})
delete(types, oldToken)
} else {
fmt.Printf("WARN not renaming %s to %s in the schema "+
"because they differ structurally\n",
oldToken, newToken)
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: fmt.Sprintf(
"Not renaming '%s' to '%s' in the schema because they differ structurally",
oldToken, newToken,
),
})
}
}

Expand All @@ -89,7 +96,10 @@ func dedupTypes(spec *pschema.PackageSpec) (*pschema.PackageSpec, error) {
return node
}
if r, isRenamed := renamedRefs[s]; isRenamed {
fmt.Printf("Rewritten %s to %s\n", s, r)
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: fmt.Sprintf("Rewrote reference '%s' to '%s'", s, r),
})
return r
}
return node
Expand All @@ -100,15 +110,15 @@ func dedupTypes(spec *pschema.PackageSpec) (*pschema.PackageSpec, error) {
buf.Reset()

if err := json.NewEncoder(&buf).Encode(&rawSchema); err != nil {
return nil, err
return nil, nil, err
}

var fixedSpec pschema.PackageSpec
var fixedSpec schema.PackageSpec
if err := json.NewDecoder(bytes.NewReader(buf.Bytes())).Decode(&fixedSpec); err != nil {
return nil, err
return nil, nil, err
}

return &fixedSpec, nil
return &fixedSpec, diags, nil
}

func transformJSONTree(t func(interface{}) interface{}, tree interface{}) interface{} {
Expand Down
Loading

0 comments on commit d1aed25

Please sign in to comment.