diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 54b16bbb049..2dc0e7daff6 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -4,4 +4,6 @@ - Implement `GenerateProgram` and `GenerateProject` RPC endpoints for Java +- Implement the `GeneratePackage` RPC endpoint for Java + ### Bug Fixes \ No newline at end of file diff --git a/pkg/cmd/pulumi-language-java/main.go b/pkg/cmd/pulumi-language-java/main.go index e55505fbcd0..7596c1a41c1 100644 --- a/pkg/cmd/pulumi-language-java/main.go +++ b/pkg/cmd/pulumi-language-java/main.go @@ -13,6 +13,7 @@ import ( "os" "os/exec" "os/signal" + "path/filepath" "strings" "time" @@ -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" @@ -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 +} diff --git a/pkg/codegen/java/build.gradle.template b/pkg/codegen/java/build.gradle.template index 1f746c5208c..f2d05d27c3b 100644 --- a/pkg/codegen/java/build.gradle.template +++ b/pkg/codegen/java/build.gradle.template @@ -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/") @@ -195,4 +200,4 @@ if (signingKey) { useInMemoryPgpKeys(signingKey, signingPassword) sign publishing.publications.mainPublication } -} +} \ No newline at end of file diff --git a/pkg/codegen/java/package_info.go b/pkg/codegen/java/package_info.go index 32ef596f349..faf39ed68bb 100644 --- a/pkg/codegen/java/package_info.go +++ b/pkg/codegen/java/package_info.go @@ -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 diff --git a/pkg/codegen/java/templates_gradle.go b/pkg/codegen/java/templates_gradle.go index 24b42bc3704..f5774ff04ad 100644 --- a/pkg/codegen/java/templates_gradle.go +++ b/pkg/codegen/java/templates_gradle.go @@ -7,6 +7,7 @@ import ( _ "embed" "fmt" "net/url" + "slices" "strings" "github.com/pulumi/pulumi/pkg/v3/codegen/schema" @@ -74,6 +75,7 @@ type gradleTemplateContext struct { ProjectGitURL string ProjectDescription string ProjectInceptionYear string + Repositories []string Dependencies map[string]string DeveloperID string DeveloperName string @@ -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 {