Skip to content

Commit e31fda1

Browse files
authored
Merge pull request #52 from toodofun/feature/java
Feature/java
2 parents 348378b + 62b1e99 commit e31fda1

File tree

6 files changed

+358
-6
lines changed

6 files changed

+358
-6
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ Use "gvm [command] --help" for more information about a command.
5959
## 🐍  Supported Programming Languages
6060
- [x] Golang
6161
- [x] Node
62-
- [ ] Java
63-
- [x] Python (test)
62+
- [x] Java (zulu)
63+
- [x] Python
6464
- [ ] C#
6565
- [ ] C++/C
6666
- [ ] Ruby

README_zh.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ Use "gvm [command] --help" for more information about a command.
5959
## 🐍  支持的编程语言
6060
- [x] Golang
6161
- [x] Node
62-
- [ ] Java
63-
- [x] Python (test)
62+
- [x] Java (zulu)
63+
- [x] Python
6464
- [ ] C#
6565
- [ ] C++/C
6666
- [ ] Ruby

internal/view/header.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func (a *Application) createHeader(ctx context.Context) tview.Primitive {
3333
logger := log.GetLogger(ctx)
3434
u, err := user.Current()
3535
if err != nil {
36-
u = &user.User{Name: "unknow"}
36+
u = &user.User{Username: "unknow"}
3737
}
3838

3939
hn, err := os.Hostname()
@@ -49,7 +49,7 @@ func (a *Application) createHeader(ctx context.Context) tview.Primitive {
4949
{Key: "[yellow] Hostname[-:-:-]", Value: hn},
5050
{Key: "[yellow] System[-:-:-]", Value: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)},
5151
{Key: "[yellow] GVM Rev.[-:-:-]", Value: core.Version},
52-
{Key: "[yellow] Username[-:-:-]", Value: u.Name},
52+
{Key: "[yellow] Username[-:-:-]", Value: u.Username},
5353
{Key: "[yellow] Loglevel[-:-:-]", Value: log.GetLevel()},
5454
}
5555
desc := tview.NewTable().

languages/java/java.go

Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
// Copyright 2025 The Toodofun Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http:www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package java
16+
17+
import (
18+
"context"
19+
"encoding/json"
20+
"fmt"
21+
"net/url"
22+
"os"
23+
"path/filepath"
24+
"runtime"
25+
"strconv"
26+
"strings"
27+
28+
goversion "github.com/hashicorp/go-version"
29+
30+
"github.com/toodofun/gvm/internal/core"
31+
"github.com/toodofun/gvm/internal/http"
32+
"github.com/toodofun/gvm/internal/log"
33+
"github.com/toodofun/gvm/internal/util/compress"
34+
"github.com/toodofun/gvm/internal/util/env"
35+
"github.com/toodofun/gvm/internal/util/path"
36+
"github.com/toodofun/gvm/languages"
37+
)
38+
39+
const (
40+
lang = "java"
41+
zuluUrl = "https://api.azul.com/metadata/v1/zulu/packages"
42+
)
43+
44+
type Java struct{}
45+
46+
func (j *Java) Name() string {
47+
return lang
48+
}
49+
50+
func (j *Java) getCurrentSystemInfo() (os, arch, hwBitness string) {
51+
switch strings.ToLower(runtime.GOOS) {
52+
case "linux":
53+
os = "linux"
54+
case "windows":
55+
os = "windows"
56+
case "darwin":
57+
os = "macos"
58+
}
59+
60+
switch strings.ToLower(runtime.GOARCH) {
61+
case "amd64":
62+
arch = "x86"
63+
hwBitness = "64"
64+
case "arm64":
65+
arch = "arm"
66+
hwBitness = "64"
67+
if runtime.GOOS == "darwin" {
68+
arch = ""
69+
hwBitness = ""
70+
}
71+
case "386":
72+
arch = "x86"
73+
hwBitness = "32"
74+
case "arm":
75+
arch = "arm"
76+
hwBitness = "32"
77+
}
78+
return
79+
}
80+
81+
func (j *Java) fetchRemote(
82+
ctx context.Context,
83+
page, size int,
84+
callback func(version *core.RemoteVersion),
85+
) (more bool, err error) {
86+
logger := log.GetLogger(ctx)
87+
params := url.Values{}
88+
params.Set("page", strconv.Itoa(page))
89+
params.Set("page_size", strconv.Itoa(size))
90+
params.Set("availability_types", "ca")
91+
params.Set("release_status", "both")
92+
params.Set(
93+
"include_fields",
94+
"java_package_features,release_status,support_term,os,arch,hw_bitness,abi,java_package_type,javafx_bundled,sha256_hash,cpu_gen,size,archive_type,certifications,lib_c_type,crac_supported",
95+
)
96+
params.Set("azul_com", "true")
97+
params.Set("archive_type", "tar.gz")
98+
params.Set("lib_c_type", "glibc")
99+
100+
osStr, arch, hwBitness := j.getCurrentSystemInfo()
101+
params.Set("os", osStr)
102+
if len(arch) > 0 {
103+
params.Set("arch", arch)
104+
}
105+
if len(hwBitness) > 0 {
106+
params.Set("hw_bitness", hwBitness)
107+
}
108+
109+
targetUrl := zuluUrl + "?" + params.Encode()
110+
111+
logger.Infof("Fetching %s", targetUrl)
112+
body, err := http.Default().Get(ctx, targetUrl)
113+
if err != nil {
114+
logger.Errorf("Failed to fetch %s: %s", targetUrl, err)
115+
return false, err
116+
}
117+
118+
versions := make([]Version, 0)
119+
if err := json.Unmarshal(body, &versions); err != nil {
120+
logger.Errorf("Failed to unmarshal %s: %s", targetUrl, err)
121+
return false, err
122+
}
123+
124+
for _, v := range versions {
125+
if v.Os == "linux" && v.LibCType != "glibc" {
126+
continue
127+
}
128+
if v.JavaPackageType != "jdk" {
129+
continue
130+
}
131+
vs := make([]string, len(v.JavaVersion))
132+
for i, num := range v.JavaVersion {
133+
vs[i] = strconv.Itoa(num)
134+
}
135+
136+
ver, err := goversion.NewVersion(strings.Join(vs, ".") + "-zulu-" + v.Sha256Hash[:4])
137+
if err != nil {
138+
logger.Errorf("Failed to parse version %s: %s", v.Name, err)
139+
return false, err
140+
}
141+
142+
comment := strings.ReplaceAll(v.Name, ".tar.gz", "")
143+
144+
callback(&core.RemoteVersion{
145+
Version: ver,
146+
Origin: v.DownloadUrl,
147+
Comment: comment,
148+
})
149+
}
150+
return len(versions) == 1000, nil
151+
}
152+
153+
func (j *Java) ListRemoteVersions(ctx context.Context) ([]*core.RemoteVersion, error) {
154+
logger := log.GetLogger(ctx)
155+
res := make([]*core.RemoteVersion, 0)
156+
157+
page := 1
158+
pageSize := 1000
159+
for {
160+
more, err := j.fetchRemote(ctx, page, pageSize, func(version *core.RemoteVersion) {
161+
logger.Debugf("Fetching remote version %+v", version)
162+
res = append(res, version)
163+
})
164+
if err != nil {
165+
return nil, err
166+
}
167+
if !more {
168+
break
169+
}
170+
page++
171+
}
172+
return res, nil
173+
}
174+
175+
func (j *Java) ListInstalledVersions(ctx context.Context) ([]*core.InstalledVersion, error) {
176+
return languages.NewLanguage(j).ListInstalledVersions(ctx, "bin")
177+
}
178+
179+
func (j *Java) SetDefaultVersion(ctx context.Context, version string) error {
180+
envs := []env.KV{
181+
{
182+
Key: "PATH",
183+
Value: filepath.Join(path.GetLangRoot(lang), path.Current, "bin"),
184+
Append: true,
185+
},
186+
}
187+
return languages.NewLanguage(j).SetDefaultVersion(ctx, version, envs)
188+
}
189+
190+
func (j *Java) GetDefaultVersion(ctx context.Context) *core.InstalledVersion {
191+
return languages.NewLanguage(j).GetDefaultVersion()
192+
}
193+
194+
func (j *Java) Uninstall(ctx context.Context, version string) error {
195+
return languages.NewLanguage(j).Uninstall(version)
196+
}
197+
198+
func (j *Java) Install(ctx context.Context, version *core.RemoteVersion) error {
199+
logger := log.GetLogger(ctx)
200+
logger.Debugf("Install version: %+v", version)
201+
logger.Infof("Installing %s(%s)", version.Version.String(), version.Comment)
202+
203+
file, err := http.Default().
204+
Download(ctx, version.Origin, filepath.Join(path.GetLangRoot(lang), version.Version.String()), fmt.Sprintf("%s.%s-%s.tar.gz", version.Version.String(), runtime.GOOS, "amd64"))
205+
logger.Infof("")
206+
if err != nil {
207+
return fmt.Errorf("failed to download version: %s(%s): %w", version.Version.String(), version.Comment, err)
208+
}
209+
210+
installDir := filepath.Join(path.GetLangRoot(lang), version.Version.String())
211+
212+
if err := compress.UnTarGz(ctx, file, installDir); err != nil {
213+
return fmt.Errorf("failed to unTarGz: %s(%s): %w", version.Version.String(), version.Comment, err)
214+
}
215+
216+
if err = os.RemoveAll(file); err != nil {
217+
logger.Warnf("failed to clean %s: %v", file, err)
218+
}
219+
220+
dirs, err := filepath.Glob(filepath.Join(installDir, "/*"))
221+
if err != nil {
222+
logger.Errorf("failed to glob %s: %v", installDir, err)
223+
return err
224+
}
225+
for _, dir := range dirs {
226+
sourceDir := dir
227+
files, err := os.ReadDir(sourceDir)
228+
if err != nil {
229+
return err
230+
}
231+
232+
for _, f := range files {
233+
sourcePath := filepath.Join(sourceDir, f.Name())
234+
destPath := filepath.Join(installDir, f.Name())
235+
236+
if _, err := os.Stat(destPath); err == nil {
237+
logger.Warnf("%s already exists", destPath)
238+
continue
239+
}
240+
241+
err := os.Rename(sourcePath, destPath)
242+
if err != nil {
243+
return err
244+
}
245+
}
246+
}
247+
248+
logger.Infof(
249+
"Version %s(%s) was successfully installed in %s",
250+
version.Version.String(),
251+
version.Comment,
252+
installDir,
253+
)
254+
255+
return nil
256+
}
257+
258+
type Version struct {
259+
Abi string `json:"abi"`
260+
Arch string `json:"arch"`
261+
ArchiveType string `json:"archive_type"`
262+
AvailabilityType string `json:"availability_type"`
263+
Certifications []string `json:"certifications"`
264+
CpuGen []interface{} `json:"cpu_gen"`
265+
CracSupported bool `json:"crac_supported"`
266+
DistroVersion []int `json:"distro_version"`
267+
DownloadUrl string `json:"download_url"`
268+
HwBitness int `json:"hw_bitness"`
269+
JavaPackageFeatures []string `json:"java_package_features"`
270+
JavaPackageType string `json:"java_package_type"`
271+
JavaVersion []int `json:"java_version"`
272+
JavafxBundled bool `json:"javafx_bundled"`
273+
Latest bool `json:"latest"`
274+
LibCType string `json:"lib_c_type"`
275+
Name string `json:"name"`
276+
OpenjdkBuildNumber int `json:"openjdk_build_number"`
277+
Os string `json:"os"`
278+
PackageUuid string `json:"package_uuid"`
279+
Product string `json:"product"`
280+
ReleaseStatus string `json:"release_status"`
281+
Sha256Hash string `json:"sha256_hash"`
282+
Size int `json:"size"`
283+
SupportTerm string `json:"support_term"`
284+
}
285+
286+
func init() {
287+
core.RegisterLanguage(&Java{})
288+
}

languages/java/java_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright 2025 The Toodofun Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http:www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package java
16+
17+
import (
18+
"context"
19+
"testing"
20+
)
21+
22+
func TestJava_Name(t *testing.T) {
23+
j := &Java{}
24+
if j.Name() != "java" {
25+
t.Errorf("expected java, got %s", j.Name())
26+
}
27+
}
28+
29+
//func TestJava_ListRemoteVersions(t *testing.T) {
30+
// j := &Java{}
31+
// versions, err := j.ListRemoteVersions(context.Background())
32+
// if err != nil {
33+
// t.Errorf("ListRemoteVersions error: %v", err)
34+
// }
35+
//
36+
// if len(versions) == 0 {
37+
// t.Errorf("expected remote versions, got 0")
38+
// }
39+
//}
40+
41+
func TestJava_ListInstalledVersions(t *testing.T) {
42+
j := &Java{}
43+
_, err := j.ListInstalledVersions(context.Background())
44+
if err != nil {
45+
t.Errorf("ListInstalledVersions error: %v", err)
46+
}
47+
}
48+
49+
func TestJava_GetDefaultVersion(t *testing.T) {
50+
j := &Java{}
51+
version := j.GetDefaultVersion(context.Background())
52+
if version == nil {
53+
t.Errorf("GetDefaultVersion should not return nil")
54+
}
55+
}
56+
57+
func TestJava_Uninstall(t *testing.T) {
58+
j := &Java{}
59+
err := j.Uninstall(context.Background(), "non-existent-version")
60+
if err != nil {
61+
t.Errorf("Uninstall should not return error for non-existent version, got: %v", err)
62+
}
63+
}

0 commit comments

Comments
 (0)