Skip to content

Commit d2a85f0

Browse files
authored
Centralize SDK generation logic (#1515)
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 the Java side of #1508. Alongside new unit tests and existing conformance tests, this changeset has been manually tested using `pulumi-azure-native` and changes akin to those in pulumi/pulumi-azure-native#3776 (using a locally modified `pulumi package gen-sdk` that can be mainstreamed when these changes have been merged and released). Closes #1404 Part of #1508
1 parent d52870d commit d2a85f0

File tree

7 files changed

+289
-67
lines changed

7 files changed

+289
-67
lines changed

pkg/cmd/pulumi-java-gen/generate.go

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,16 @@ package main
1616

1717
import (
1818
"fmt"
19+
"os"
1920
"path/filepath"
2021

2122
"github.com/blang/semver"
23+
"github.com/hashicorp/hcl/v2"
2224

2325
javagen "github.com/pulumi/pulumi-java/pkg/codegen/java"
2426
pschema "github.com/pulumi/pulumi/pkg/v3/codegen/schema"
27+
"github.com/pulumi/pulumi/sdk/v3/go/common/diag"
28+
"github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil"
2529
)
2630

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

56-
pkgSpec, err := dedupTypes(rawPkgSpec)
60+
pkgSpec, diags, err := javagen.DeduplicateTypes(rawPkgSpec)
5761
if err != nil {
5862
return fmt.Errorf("failed to dedup types in schema from %s: %w", cfg.Schema, err)
5963
}
64+
printDiagnostics(diags)
6065

6166
pkg, err := pschema.ImportSpec(*pkgSpec, nil)
6267
if err != nil {
@@ -80,7 +85,13 @@ func generateJava(cfg generateJavaOptions) error {
8085
if err != nil {
8186
return err
8287
}
83-
files, err := javagen.GeneratePackage("pulumi-java-gen", pkg, extraFiles, cfg.Local)
88+
files, err := javagen.GeneratePackage(
89+
"pulumi-java-gen",
90+
pkg,
91+
extraFiles,
92+
nil, /*localDependencies*/
93+
cfg.Local,
94+
)
8495
if err != nil {
8596
return err
8697
}
@@ -99,3 +110,15 @@ func generateJava(cfg generateJavaOptions) error {
99110

100111
return nil
101112
}
113+
114+
// printDiagnostics prints the given diagnostics to stdout and stderr.
115+
func printDiagnostics(diagnostics hcl.Diagnostics) {
116+
sink := diag.DefaultSink(os.Stdout, os.Stderr, diag.FormatOptions{Color: cmdutil.GetGlobalColorization()})
117+
for _, diagnostic := range diagnostics {
118+
if diagnostic.Severity == hcl.DiagError {
119+
sink.Errorf(diag.Message("", "%s"), diagnostic)
120+
} else {
121+
sink.Warningf(diag.Message("", "%s"), diagnostic)
122+
}
123+
}
124+
}

pkg/cmd/pulumi-language-java/main.go

Lines changed: 26 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"time"
2020

2121
pbempty "github.com/golang/protobuf/ptypes/empty"
22+
"github.com/hashicorp/hcl/v2"
2223
"github.com/pkg/errors"
2324
hclsyntax "github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/syntax"
2425
"github.com/pulumi/pulumi/pkg/v3/codegen/pcl"
@@ -32,7 +33,6 @@ import (
3233
"github.com/pulumi/pulumi/sdk/v3/go/common/util/rpcutil"
3334
"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
3435
pulumirpc "github.com/pulumi/pulumi/sdk/v3/proto/go"
35-
"golang.org/x/exp/maps"
3636
"google.golang.org/grpc"
3737
"google.golang.org/grpc/credentials/insecure"
3838
"google.golang.org/protobuf/types/known/structpb"
@@ -703,57 +703,40 @@ func (host *javaLanguageHost) GeneratePackage(
703703
return nil, err
704704
}
705705

706-
pkg, diags, err := schema.BindSpec(spec, loader)
706+
diags := hcl.Diagnostics{}
707+
708+
// Historically, Java has "deduplicated" PackageSpecs to reduce sets of multiple types whose names differ only in
709+
// case down to just one type that is then shared (assuming that, apart from name, the types are otherwise
710+
// identical). We thus perform that deduplication here before we bind the schema and resolve any references.
711+
dedupedSpec, dedupeDiags, err := codegen.DeduplicateTypes(&spec)
707712
if err != nil {
708713
return nil, err
709714
}
710-
rpcDiagnostics := plugin.HclDiagnosticsToRPCDiagnostics(diags)
711-
if diags.HasErrors() {
715+
diags = diags.Extend(dedupeDiags)
716+
if dedupeDiags.HasErrors() {
712717
return &pulumirpc.GeneratePackageResponse{
713-
Diagnostics: rpcDiagnostics,
718+
Diagnostics: plugin.HclDiagnosticsToRPCDiagnostics(diags),
714719
}, nil
715720
}
716721

717-
if pkg.Description == "" {
718-
pkg.Description = " "
719-
}
720-
if pkg.Repository == "" {
721-
pkg.Repository = "https://example.com"
722-
}
723-
724-
// Presently, we only support generating Java SDKs which use Gradle as a build system. Specify that here, as well as
725-
// the set of dependencies that all generated SDKs rely on.
726-
pkgInfo := codegen.PackageInfo{
727-
BuildFiles: "gradle",
728-
Dependencies: map[string]string{
729-
"com.google.code.gson:gson": "2.8.9",
730-
"com.google.code.findbugs:jsr305": "3.0.2",
731-
},
722+
pkg, bindDiags, err := schema.BindSpec(*dedupedSpec, loader)
723+
if err != nil {
724+
return nil, err
732725
}
733-
734-
repositories := map[string]bool{}
735-
736-
for name, dep := range req.LocalDependencies {
737-
parts := strings.Split(dep, ":")
738-
if len(parts) < 3 {
739-
return nil, fmt.Errorf(
740-
"invalid dependency for %s %s; must be of the form groupId:artifactId:version[:repositoryPath]",
741-
name, dep,
742-
)
743-
}
744-
745-
k := parts[0] + ":" + parts[1]
746-
pkgInfo.Dependencies[k] = parts[2]
747-
748-
if len(parts) == 4 {
749-
repositories[parts[3]] = true
750-
}
726+
diags = diags.Extend(bindDiags)
727+
if bindDiags.HasErrors() {
728+
return &pulumirpc.GeneratePackageResponse{
729+
Diagnostics: plugin.HclDiagnosticsToRPCDiagnostics(diags),
730+
}, nil
751731
}
752732

753-
pkgInfo.Repositories = maps.Keys(repositories)
754-
pkg.Language["java"] = pkgInfo
755-
756-
files, err := codegen.GeneratePackage("pulumi-language-java", pkg, req.ExtraFiles, req.Local)
733+
files, err := codegen.GeneratePackage(
734+
"pulumi-language-java",
735+
pkg,
736+
req.ExtraFiles,
737+
req.LocalDependencies,
738+
req.Local,
739+
)
757740
if err != nil {
758741
return nil, err
759742
}
@@ -772,7 +755,7 @@ func (host *javaLanguageHost) GeneratePackage(
772755
}
773756

774757
return &pulumirpc.GeneratePackageResponse{
775-
Diagnostics: rpcDiagnostics,
758+
Diagnostics: plugin.HclDiagnosticsToRPCDiagnostics(diags),
776759
}, nil
777760
}
778761

pkg/cmd/pulumi-java-gen/dedup.go renamed to pkg/codegen/java/dedup.go

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
// Copyright 2022, Pulumi Corporation. All rights reserved.
1+
// Copyright 2024, Pulumi Corporation. All rights reserved.
22

3-
package main
3+
package java
44

55
import (
66
"bytes"
@@ -9,15 +9,16 @@ import (
99
"reflect"
1010
"strings"
1111

12-
pschema "github.com/pulumi/pulumi/pkg/v3/codegen/schema"
12+
"github.com/hashicorp/hcl/v2"
13+
"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
1314
)
1415

15-
// Detects cases when identical types have similar names modulo case
16-
// such as `azure-native:network:IpAllocationMethod` vs
17-
// `azure-native:network:IPAllocationMethod`, deterministically picks
18-
// one of these names, and rewrites the schema as if there was only
19-
// one such type.
20-
func dedupTypes(spec *pschema.PackageSpec) (*pschema.PackageSpec, error) {
16+
// DeduplicateTypes detects multiple types in a PackageSpec whose names are the same modulo case, such as
17+
// `azure-native:network:IpAllocationMethod` and `azure-native:network:IPAllocationMethod`, deterministically picks one
18+
// of these names, and rewrites the schema as if there was only one such type.
19+
func DeduplicateTypes(spec *schema.PackageSpec) (*schema.PackageSpec, hcl.Diagnostics, error) {
20+
diags := hcl.Diagnostics{}
21+
2122
normalizedTokens := map[string]string{}
2223
for typeToken := range spec.Types {
2324
key := strings.ToUpper(typeToken)
@@ -41,12 +42,12 @@ func dedupTypes(spec *pschema.PackageSpec) (*pschema.PackageSpec, error) {
4142

4243
var buf bytes.Buffer
4344
if err := json.NewEncoder(&buf).Encode(spec); err != nil {
44-
return nil, err
45+
return nil, nil, err
4546
}
4647

4748
var rawSchema interface{}
4849
if err := json.NewDecoder(bytes.NewReader(buf.Bytes())).Decode(&rawSchema); err != nil {
49-
return nil, err
50+
return nil, nil, err
5051
}
5152

5253
types := map[string]interface{}{}
@@ -64,13 +65,19 @@ func dedupTypes(spec *pschema.PackageSpec) (*pschema.PackageSpec, error) {
6465
transformJSONTree(stripDescription, types[newToken]),
6566
)
6667
if eq {
67-
fmt.Printf("WARN renaming %s to %s in the schema\n",
68-
oldToken, newToken)
68+
diags = append(diags, &hcl.Diagnostic{
69+
Severity: hcl.DiagWarning,
70+
Summary: fmt.Sprintf("Renaming '%s' to '%s' in the schema", oldToken, newToken),
71+
})
6972
delete(types, oldToken)
7073
} else {
71-
fmt.Printf("WARN not renaming %s to %s in the schema "+
72-
"because they differ structurally\n",
73-
oldToken, newToken)
74+
diags = append(diags, &hcl.Diagnostic{
75+
Severity: hcl.DiagWarning,
76+
Summary: fmt.Sprintf(
77+
"Not renaming '%s' to '%s' in the schema because they differ structurally",
78+
oldToken, newToken,
79+
),
80+
})
7481
}
7582
}
7683

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

102112
if err := json.NewEncoder(&buf).Encode(&rawSchema); err != nil {
103-
return nil, err
113+
return nil, nil, err
104114
}
105115

106-
var fixedSpec pschema.PackageSpec
116+
var fixedSpec schema.PackageSpec
107117
if err := json.NewDecoder(bytes.NewReader(buf.Bytes())).Decode(&fixedSpec); err != nil {
108-
return nil, err
118+
return nil, nil, err
109119
}
110120

111-
return &fixedSpec, nil
121+
return &fixedSpec, diags, nil
112122
}
113123

114124
func transformJSONTree(t func(interface{}) interface{}, tree interface{}) interface{} {

0 commit comments

Comments
 (0)