Skip to content

Commit

Permalink
Implement GeneratePackage
Browse files Browse the repository at this point in the history
The `GeneratePackage` method of the `LanguageRuntime` gRPC service allows
callers to generate SDKs for a specified schema. This commits implements this
endpoint for `pulumi-language-java`, using the existing `pkg/codegen`
functionality that is currently only exposed through the `pulumi-java-gen`
binary. Doing so means we can begin to get conformance tests working for Java.

As part of this work, we extend the work we started when implementing
`GenerateProject` to support local repositories in generated SDKs. As with other
languages, we add a field to `PackageInfo` to this end. Since SDK generation
uses Gradle (and not Maven, as program generation does), there is a bit of new
plumbing to do here, but in principle the outcome is the same.

> [!NOTE]
> It is not certain yet that `GeneratePackage` behaves identically to the
> currently-used binary endpoint in `pulumi-java-gen` -- we should make sure of
> this before switching over. This work is primarily a means to get conformance
> tests going.
  • Loading branch information
lunaris committed Nov 18, 2024
1 parent 32c6b70 commit dcc5fce
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 1 deletion.
2 changes: 2 additions & 0 deletions CHANGELOG_PENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@

- Implement `GenerateProgram` and `GenerateProject` RPC endpoints for Java

- Implement the `GeneratePackage` RPC endpoint for Java

### Bug Fixes
92 changes: 92 additions & 0 deletions pkg/cmd/pulumi-language-java/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"os"
"os/exec"
"os/signal"
"path/filepath"
"strings"
"time"

Expand All @@ -29,6 +30,7 @@ import (
"github.com/pulumi/pulumi/sdk/v3/go/common/version"
"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 @@ -675,3 +677,93 @@ func (host *javaLanguageHost) GenerateProgram(
Diagnostics: rpcDiagnostics,
}, nil
}

// Implements the `LanguageRuntime.GeneratePackage` RPC method, which generates a Java SDK package from a supplied
// schema.
func (host *javaLanguageHost) GeneratePackage(
_ context.Context,
req *pulumirpc.GeneratePackageRequest,
) (*pulumirpc.GeneratePackageResponse, error) {
loader, err := schema.NewLoaderClient(req.LoaderTarget)
if err != nil {
return nil, err
}

var spec schema.PackageSpec
err = json.Unmarshal([]byte(req.Schema), &spec)
if err != nil {
return nil, err
}

pkg, diags, err := schema.BindSpec(spec, loader)
if err != nil {
return nil, err
}
rpcDiagnostics := plugin.HclDiagnosticsToRPCDiagnostics(diags)
if diags.HasErrors() {
return &pulumirpc.GeneratePackageResponse{
Diagnostics: rpcDiagnostics,
}, 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",
},
}

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
}
}

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

files, err := codegen.GeneratePackage("pulumi-language-java", pkg, req.ExtraFiles, req.Local)
if err != nil {
return nil, err
}

for filename, data := range files {
outPath := filepath.Join(req.Directory, filename)
err := os.MkdirAll(filepath.Dir(outPath), 0o700)
if err != nil {
return nil, fmt.Errorf("could not create output directory %s: %w", filepath.Dir(filename), err)
}

err = os.WriteFile(outPath, data, 0o600)
if err != nil {
return nil, fmt.Errorf("could not write output file %s: %w", filename, err)
}
}

return &pulumirpc.GeneratePackageResponse{
Diagnostics: rpcDiagnostics,
}, nil
}
7 changes: 6 additions & 1 deletion pkg/codegen/java/build.gradle.template
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ compileJava {
}

repositories {
{{- range $url := .Repositories }}
maven {
url("{{ $url }}")
}
{{- end }}
mavenLocal()
maven { // The google mirror is less flaky than mavenCentral()
url("https://maven-central.storage-download.googleapis.com/maven2/")
Expand Down Expand Up @@ -195,4 +200,4 @@ if (signingKey) {
useInMemoryPgpKeys(signingKey, signingPassword)
sign publishing.publications.mainPublication
}
}
}
5 changes: 5 additions & 0 deletions pkg/codegen/java/package_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ type PackageInfo struct {
// Gradle build files.
BuildFiles string `json:"buildFiles"`

// If non-empty, specifies a list of Maven repositories that should be
// referenced by the generated package. This can be useful for specifying
// the locations of e.g. local or private dependencies.
Repositories []string `json:"repositories,omitempty"`

// Specifies Maven-style dependencies for the generated code.
//
// The dependency on Java SDK (com.pulumi:pulumi) is special
Expand Down
9 changes: 9 additions & 0 deletions pkg/codegen/java/templates_gradle.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
_ "embed"
"fmt"
"net/url"
"slices"
"strings"

"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
Expand Down Expand Up @@ -74,6 +75,7 @@ type gradleTemplateContext struct {
ProjectGitURL string
ProjectDescription string
ProjectInceptionYear string
Repositories []string
Dependencies map[string]string
DeveloperID string
DeveloperName string
Expand Down Expand Up @@ -110,6 +112,13 @@ func newGradleTemplateContext(
ctx.Version = "0.0.1"
}

if packageInfo.Repositories != nil {
ctx.Repositories = packageInfo.Repositories
slices.Sort(ctx.Repositories)
} else {
ctx.Repositories = []string{}
}

if packageInfo.Dependencies != nil {
ctx.Dependencies = packageInfo.Dependencies
} else {
Expand Down

0 comments on commit dcc5fce

Please sign in to comment.