Skip to content

Commit 9befcce

Browse files
authored
feat: auto env setup (#91)
1 parent bcc9fba commit 9befcce

File tree

3 files changed

+124
-30
lines changed

3 files changed

+124
-30
lines changed

lang/cxx/lib.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package cxx
1616

1717
import (
18+
"fmt"
1819
"time"
1920

2021
"github.com/cloudwego/abcoder/lang/uniast"
@@ -23,6 +24,10 @@ import (
2324

2425
const MaxWaitDuration = 5 * time.Minute
2526

27+
func InstallLanguageServer() (string, error) {
28+
return "", fmt.Errorf("please install clangd-18 manually. See https://releases.llvm.org/ (clangd is in clang-extra)")
29+
}
30+
2631
func GetDefaultLSP() (lang uniast.Language, name string) {
2732
return uniast.Cxx, "clangd-18"
2833
}

lang/parse.go

Lines changed: 52 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import (
2020
"context"
2121
"encoding/json"
2222
"fmt"
23-
"github.com/cloudwego/abcoder/lang/register"
2423
"os"
2524
"os/exec"
2625
"path/filepath"
@@ -33,6 +32,7 @@ import (
3332
"github.com/cloudwego/abcoder/lang/log"
3433
"github.com/cloudwego/abcoder/lang/lsp"
3534
"github.com/cloudwego/abcoder/lang/python"
35+
"github.com/cloudwego/abcoder/lang/register"
3636
"github.com/cloudwego/abcoder/lang/rust"
3737
"github.com/cloudwego/abcoder/lang/uniast"
3838
)
@@ -138,40 +138,63 @@ func checkRepoPath(repoPath string, language uniast.Language) (openfile string,
138138
}
139139

140140
func checkLSP(language uniast.Language, lspPath string, args ParseOptions) (l uniast.Language, s string, err error) {
141-
switch language {
142-
case uniast.Rust:
143-
l, s = rust.GetDefaultLSP()
144-
case uniast.Cxx:
145-
l, s = cxx.GetDefaultLSP()
146-
case uniast.Python:
147-
l, s = python.GetDefaultLSP()
148-
case uniast.Java:
149-
l, s = java.GetDefaultLSP(args.LspOptions)
150-
case uniast.Golang:
151-
l = uniast.Golang
152-
s = ""
153-
if _, err := exec.LookPath("go"); err != nil {
154-
if _, err := os.Stat(lspPath); os.IsNotExist(err) {
155-
log.Error("Go compiler not found, please make it excutable!\n", lspPath)
156-
return uniast.Unknown, "", err
141+
if lspPath != "" {
142+
// designated LSP
143+
l = language
144+
s = lspPath
145+
} else {
146+
// default LSP
147+
switch language {
148+
case uniast.Rust:
149+
l, s = rust.GetDefaultLSP()
150+
case uniast.Cxx:
151+
l, s = cxx.GetDefaultLSP()
152+
case uniast.Python:
153+
l, s = python.GetDefaultLSP()
154+
case uniast.Java:
155+
l, s = java.GetDefaultLSP(args.LspOptions)
156+
case uniast.Golang:
157+
if _, err := exec.LookPath("go"); err != nil {
158+
if _, err := os.Stat(lspPath); os.IsNotExist(err) {
159+
log.Error("Go compiler not found, please make it excutable!\n", lspPath)
160+
return uniast.Unknown, "", err
161+
}
157162
}
163+
return uniast.Golang, "", nil
164+
default:
165+
return uniast.Unknown, "", fmt.Errorf("unsupported language: %s", language)
158166
}
159-
return
160-
default:
161-
return uniast.Unknown, "", fmt.Errorf("unsupported language: %s", language)
162167
}
163-
// check if lsp excutable
164-
if lspPath != "" {
165-
if _, err := exec.LookPath(lspPath); err != nil {
166-
if _, err := os.Stat(lspPath); os.IsNotExist(err) {
167-
log.Error("Language server %s not found, please make it excutable!\n", lspPath)
168-
return uniast.Unknown, "", err
169-
}
168+
169+
// lsp already installed
170+
if absLspPath, err := exec.LookPath(s); err == nil {
171+
return l, absLspPath, nil
172+
}
173+
174+
// install the lsp.
175+
log.Error("Language server %s not found. Trying to auto install.\n", s)
176+
s, err = installLanguageServer(language)
177+
if err == nil {
178+
if absLspPath, err := exec.LookPath(s); err == nil {
179+
log.Error("Auto installation ok. lspPath=%s.", absLspPath)
180+
return l, absLspPath, nil
170181
}
171-
s = lspPath
172182
}
173183

174-
return
184+
// install failed or broken (lsp not in PATH)
185+
log.Info("Failed to install language server %s: %+w.\n", s, err)
186+
return uniast.Unknown, "", err
187+
}
188+
189+
func installLanguageServer(language uniast.Language) (string, error) {
190+
switch language {
191+
case uniast.Cxx:
192+
return cxx.InstallLanguageServer()
193+
case uniast.Python:
194+
return python.InstallLanguageServer()
195+
default:
196+
return "", fmt.Errorf("auto installation not supported for language: %s", language)
197+
}
175198
}
176199

177200
func collectSymbol(ctx context.Context, cli *lsp.LSPClient, repoPath string, opts collect.CollectOption) (repo *uniast.Repository, err error) {

lang/python/lib.go

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,82 @@
1515
package python
1616

1717
import (
18+
"fmt"
19+
"os"
20+
"os/exec"
21+
"regexp"
22+
"strconv"
23+
"strings"
1824
"time"
1925

26+
"github.com/cloudwego/abcoder/lang/log"
2027
"github.com/cloudwego/abcoder/lang/uniast"
2128
"github.com/cloudwego/abcoder/lang/utils"
2229
)
2330

2431
const MaxWaitDuration = 5 * time.Second
32+
const lspName = "pylsp"
33+
34+
func CheckPythonVersion() error {
35+
// Check python3 command availability and get version.
36+
output, err := exec.Command("python3", "--version").CombinedOutput()
37+
if err != nil {
38+
return fmt.Errorf("python3 not found: %w. Do you have it installed? Or is it `python` but not aliased?", err)
39+
}
40+
41+
// The regex is corrected to handle a capital 'P' and correctly capture the minor version.
42+
format := `^Python 3\.(\d+)\..*$`
43+
ptn := regexp.MustCompile(format)
44+
matches := ptn.FindStringSubmatch(strings.TrimSpace(string(output)))
45+
if len(matches) < 2 {
46+
return fmt.Errorf("unexpected `python3 --version` output format: %q", output)
47+
}
48+
subver, err := strconv.ParseInt(matches[1], 10, 64)
49+
if err != nil {
50+
return fmt.Errorf("failed to parse python version from `python3 --version` output %q: %w", output, err)
51+
}
52+
if subver < 9 {
53+
return fmt.Errorf("python version 3.%d is not supported; 3.9 or higher is required", subver)
54+
}
55+
return nil
56+
}
57+
58+
func InstallLanguageServer() (string, error) {
59+
if _, err := os.Stat("go.mod"); os.IsNotExist(err) {
60+
log.Error("Auto installation requires working directory to be /path/to/abcoder/")
61+
return "", fmt.Errorf("bad cwd")
62+
}
63+
if err := CheckPythonVersion(); err != nil {
64+
log.Error("python version check failed: %v", err)
65+
return "", err
66+
}
67+
// git submodule init
68+
log.Error("Installing pylsp...")
69+
if err := exec.Command("git", "submodule", "init").Run(); err != nil {
70+
log.Error("git submodule init failed: %v", err)
71+
return "", err
72+
}
73+
// git submodule update
74+
if err := exec.Command("git", "submodule", "update").Run(); err != nil {
75+
log.Error("git submodule update failed: %v", err)
76+
return "", err
77+
}
78+
// python -m pip install -e projectRoot/pylsp
79+
log.Error("Running `python3 -m pip install -e pylsp/` .\nThis might take some time, make sure the network connection is ok.")
80+
if err := exec.Command("python3", "-m", "pip", "install", "-e", "pylsp/").Run(); err != nil {
81+
log.Error("python -m pip install failed: %v", err)
82+
return "", err
83+
}
84+
if err := exec.Command("pylsp", "--version").Run(); err != nil {
85+
log.Error("`pylsp --version` failed: %v", err)
86+
return "", err
87+
}
88+
log.Error("pylsp installed.")
89+
return lspName, nil
90+
}
2591

2692
func GetDefaultLSP() (lang uniast.Language, name string) {
27-
return uniast.Python, "pylsp"
93+
return uniast.Python, lspName
2894
}
2995

3096
func CheckRepo(repo string) (string, time.Duration) {

0 commit comments

Comments
 (0)