diff --git a/external-providers/generic-external-provider/Dockerfile b/external-providers/generic-external-provider/Dockerfile index bf1e3a7f..66308c5b 100644 --- a/external-providers/generic-external-provider/Dockerfile +++ b/external-providers/generic-external-provider/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.21 as go-builder +FROM golang:1.23 as go-builder COPY / /analyzer-lsp @@ -12,7 +12,7 @@ COPY external-providers/generic-external-provider/pkg/ pkg/ RUN go mod edit -replace=github.com/konveyor/analyzer-lsp=/analyzer-lsp && go mod tidy -RUN go build -o generic-external-provider main.go +RUN go build -o generic-external-provider main.go && go install golang.org/x/tools/gopls@latest FROM quay.io/konveyor/golang-dependency-provider as go-dep-provider @@ -26,8 +26,9 @@ RUN microdnf install gcc-c++ python-devel go-toolset python3-devel nodejs -y && RUN python3 -m ensurepip --upgrade RUN python3 -m pip install 'python-lsp-server>=1.8.2' RUN npm install -g typescript-language-server typescript -RUN go install golang.org/x/tools/gopls@latest + +COPY --from=go-builder /go/bin/gopls /usr/local/bin/gopls COPY --from=go-builder /generic-external-provider/generic-external-provider /usr/local/bin/generic-external-provider COPY --from=go-dep-provider /usr/local/bin/golang-dependency-provider /usr/local/bin/golang-dependency-provider diff --git a/external-providers/java-external-provider/go.mod b/external-providers/java-external-provider/go.mod index e2c39d82..1e20719a 100644 --- a/external-providers/java-external-provider/go.mod +++ b/external-providers/java-external-provider/go.mod @@ -4,7 +4,7 @@ go 1.21 require ( github.com/go-logr/logr v1.4.1 - github.com/konveyor/analyzer-lsp v0.5.3 + github.com/konveyor/analyzer-lsp v0.6.0-alpha.2.0.20241211222333-1480e154bf3a github.com/swaggest/openapi-go v0.2.50 go.lsp.dev/uri v0.3.0 go.opentelemetry.io/otel v1.11.2 diff --git a/external-providers/java-external-provider/go.sum b/external-providers/java-external-provider/go.sum index 222c8e4c..f41e7fea 100644 --- a/external-providers/java-external-provider/go.sum +++ b/external-providers/java-external-provider/go.sum @@ -32,6 +32,8 @@ github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mO github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= +github.com/konveyor/analyzer-lsp v0.6.0-alpha.2.0.20241211222333-1480e154bf3a h1:iGs1ucXLHe21ljIYujUT+JkiBbsgoG+mXCLBEUMSdTM= +github.com/konveyor/analyzer-lsp v0.6.0-alpha.2.0.20241211222333-1480e154bf3a/go.mod h1:l9XC3uazLba8yXoAFJWN7uBDju1s/g1Hc8TKBpE3B2U= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= diff --git a/external-providers/java-external-provider/pkg/java_external_provider/provider.go b/external-providers/java-external-provider/pkg/java_external_provider/provider.go index 9fd2879a..21b1d339 100644 --- a/external-providers/java-external-provider/pkg/java_external_provider/provider.go +++ b/external-providers/java-external-provider/pkg/java_external_provider/provider.go @@ -347,18 +347,54 @@ func (p *javaProvider) Init(ctx context.Context, log logr.Logger, config provide } } - args := []string{ + jdtlsBasePath, err := filepath.Abs(filepath.Dir(filepath.Dir(lspServerPath))) + if err != nil { + cancelFunc() + return nil, additionalBuiltinConfig, fmt.Errorf("failed finding jdtls base path - %w", err) + } + + sharedConfigPath, err := getSharedConfigPath(jdtlsBasePath) + if err != nil { + cancelFunc() + return nil, additionalBuiltinConfig, fmt.Errorf("failed to get shared config path - %w", err) + } + + jarPath, err := findEquinoxLauncher(jdtlsBasePath) + if err != nil { + cancelFunc() + return nil, additionalBuiltinConfig, fmt.Errorf("failed to find equinox launcher - %w", err) + } + + javaExec, err := getJavaExecutable(true) + if err != nil { + cancelFunc() + return nil, additionalBuiltinConfig, fmt.Errorf("failed getting java executable - %v", err) + } + + jdtlsArgs := []string{ + "-Declipse.application=org.eclipse.jdt.ls.core.id1", + "-Dosgi.bundles.defaultStartLevel=4", + "-Declipse.product=org.eclipse.jdt.ls.core.product", + "-Dosgi.checkConfiguration=true", + fmt.Sprintf("-Dosgi.sharedConfiguration.area=%s", sharedConfigPath), + "-Dosgi.sharedConfiguration.area.readOnly=true", + "-Dosgi.configuration.cascaded=true", + "-Xms1g", + "-XX:MaxRAMPercentage=70.0", + "--add-modules=ALL-SYSTEM", + "--add-opens", "java.base/java.util=ALL-UNNAMED", + "--add-opens", "java.base/java.lang=ALL-UNNAMED", + "-jar", jarPath, "-Djava.net.useSystemProxies=true", - "-configuration", - "./", - //"--jvm-arg=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:1044", - "-data", - workspace, + "-configuration", "./", + "-data", workspace, + //"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:1044", } + if val, ok := config.ProviderSpecificConfig[JVM_MAX_MEM_INIT_OPTION].(string); ok && val != "" { - args = append(args, fmt.Sprintf("-Xmx%s", val)) + jdtlsArgs = append(jdtlsArgs, fmt.Sprintf("-Xmx%s", val)) } - cmd := exec.CommandContext(ctx, lspServerPath, args...) + cmd := exec.CommandContext(ctx, javaExec, jdtlsArgs...) stdin, err := cmd.StdinPipe() if err != nil { cancelFunc() @@ -371,8 +407,11 @@ func (p *javaProvider) Init(ctx context.Context, log logr.Logger, config provide } waitErrorChannel := make(chan error) + wg := &sync.WaitGroup{} + wg.Add(1) go func() { err := cmd.Start() + wg.Done() if err != nil { cancelFunc() returnErr = err @@ -393,10 +432,14 @@ func (p *javaProvider) Init(ctx context.Context, log logr.Logger, config provide stdout.Close() } }() + // This will close the go routine above when wait has completed. go func() { waitErrorChannel <- cmd.Wait() }() + + wg.Wait() + rpc := jsonrpc2.NewConn(jsonrpc2.NewHeaderStream(stdout, stdin), log) rpc.AddHandler(jsonrpc2.NewBackoffHandler(log)) @@ -966,3 +1009,68 @@ func (p *javaProvider) BuildSettingsFile(m2CacheDir string) (settingsFile string return settingsFilePath, nil } + +func getJavaExecutable(validateJavaVersion bool) (string, error) { + javaExecutable := "java" + if javaHome, exists := os.LookupEnv("JAVA_HOME"); exists { + javaExecToTest := filepath.Join(javaHome, "bin", "java") + if runtime.GOOS == "windows" { + javaExecToTest += ".exe" + } + if _, err := os.Stat(javaExecToTest); err == nil { + javaExecutable = javaExecToTest + } + } + + if !validateJavaVersion { + return javaExecutable, nil + } + + out, err := exec.Command(javaExecutable, "-version").CombinedOutput() + if err != nil { + return "", fmt.Errorf("failed to run %s -version: %w", javaExecutable, err) + } + + re := regexp.MustCompile(`version\s"(\d+)[.\d]*"`) + matches := re.FindStringSubmatch(string(out)) + if len(matches) > 1 { + javaVersion := matches[1] + if majorVersion := javaVersion; majorVersion < "17" { + return "", errors.New("jdtls requires at least Java 17") + } + return javaExecutable, nil + } + + return "", errors.New("could not determine Java version") +} + +func findEquinoxLauncher(jdtlsBaseDir string) (string, error) { + pluginsDir := filepath.Join(jdtlsBaseDir, "plugins") + files, err := os.ReadDir(pluginsDir) + if err != nil { + return "", fmt.Errorf("failed to read plugins directory: %w", err) + } + + for _, file := range files { + if strings.HasPrefix(file.Name(), "org.eclipse.equinox.launcher_") && strings.HasSuffix(file.Name(), ".jar") { + return filepath.Join(pluginsDir, file.Name()), nil + } + } + + return "", errors.New("cannot find equinox launcher") +} + +func getSharedConfigPath(jdtlsBaseDir string) (string, error) { + var configDir string + switch runtime.GOOS { + case "linux", "freebsd": + configDir = "config_linux" + case "darwin": + configDir = "config_mac" + case "windows": + configDir = "config_win" + default: + return "", fmt.Errorf("unknown platform %s detected", runtime.GOOS) + } + return filepath.Join(jdtlsBaseDir, configDir), nil +} diff --git a/provider_container_settings.json b/provider_container_settings.json index f68b7bd9..be04563f 100644 --- a/provider_container_settings.json +++ b/provider_container_settings.json @@ -6,7 +6,7 @@ "analysisMode": "full", "providerSpecificConfig": { "lspServerName": "generic", - "lspServerPath": "/root/go/bin/gopls", + "lspServerPath": "/usr/local/bin/gopls", "lspServerArgs": [], "lspServerInitializationOptions": "", diff --git a/provider_pod_local_settings.json b/provider_pod_local_settings.json index c1251307..6542c68d 100644 --- a/provider_pod_local_settings.json +++ b/provider_pod_local_settings.json @@ -6,7 +6,7 @@ "analysisMode": "full", "providerSpecificConfig": { "lspServerName": "generic", - "lspServerPath": "/root/go/bin/gopls", + "lspServerPath": "/usr/local/bin/gopls", "lspServerArgs": [], "lspServerInitializationOptions": "", diff --git a/provider_settings.json.sample b/provider_settings.json.sample index 356e5ae5..198f1cfa 100644 --- a/provider_settings.json.sample +++ b/provider_settings.json.sample @@ -5,7 +5,7 @@ "initConfig": [{ "location": "/analyzer-lsp/examples/golang", "providerSpecificConfig": { - "lspServerPath": "/root/go/bin/gopls" + "lspServerPath": "/usr/local/bin/gopls" } }] },