Skip to content

Commit a7a158c

Browse files
Feature: Add java runtime (for PMD) [PLUTO-1420] (#114)
- Add Java runtime plugin configuration for PMD analysis - Implement Java-specific version handling - Add Java binary paths and download templates - Configure PMD tool dependencies and execution paths
1 parent 796ade5 commit a7a158c

File tree

13 files changed

+402
-75
lines changed

13 files changed

+402
-75
lines changed

.codacy/codacy.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ runtimes:
22
33
44
5+
56
tools:
67
78

.cursor/rules/cursor.mdc

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ alwaysApply: true
77
# Your rule content
88

99
## Key Rules
10-
- use full names like e.g. feature, instead of feat
10+
- avoid code copy pasting and duplication, refactor to function eagerly when possible
11+
- use fulnames like e.g. feature, instaed of feat
1112
- run go build after each code modification to see if app compiles
12-
- remove dead unused code
13+
- remove dead unused code
14+
- look for constants like file permissons in `constants` folder
1315

1416
## Code Style Guidelines
1517
- **Imports**: Standard lib first, external packages second, internal last
@@ -24,4 +26,4 @@ alwaysApply: true
2426
- `cmd/`: CLI command implementations
2527
- `config/`: Configuration handling
2628
- `tools/`: Tool-specific implementations
27-
- `utils/`: Utility functions and static - look for static like default file permisson here
29+
- `utils/`: Utility functions

cmd/init.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ func configFileTemplate(tools []tools.Tool) string {
140140
needsNode := false
141141
needsPython := false
142142
needsDart := false
143+
needsJava := false
143144

144145
// Default versions
145146
defaultVersions := map[string]string{
@@ -168,6 +169,8 @@ func configFileTemplate(tools []tools.Tool) string {
168169
needsPython = true
169170
} else if tool.Uuid == DartAnalyzer {
170171
needsDart = true
172+
} else if tool.Uuid == PMD {
173+
needsJava = true
171174
}
172175
}
173176

@@ -186,11 +189,15 @@ func configFileTemplate(tools []tools.Tool) string {
186189
if needsDart {
187190
sb.WriteString(" - [email protected]\n")
188191
}
192+
if needsJava {
193+
sb.WriteString(" - [email protected]\n")
194+
}
189195
} else {
190196
// In local mode with no tools specified, include all runtimes
191197
sb.WriteString(" - [email protected]\n")
192198
sb.WriteString(" - [email protected]\n")
193199
sb.WriteString(" - [email protected]\n")
200+
sb.WriteString(" - [email protected]\n")
194201
}
195202

196203
sb.WriteString("tools:\n")

config/runtimes-installer.go

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ func InstallRuntimes(config *ConfigType) error {
4747

4848
// InstallRuntime installs a specific runtime
4949
func InstallRuntime(name string, runtimeInfo *plugins.RuntimeInfo) error {
50+
5051
// Check if the runtime is already installed
5152
if isRuntimeInstalled(runtimeInfo) {
5253
logger.Info("Runtime already installed", logrus.Fields{
@@ -63,6 +64,15 @@ func InstallRuntime(name string, runtimeInfo *plugins.RuntimeInfo) error {
6364
return fmt.Errorf("failed to download and extract runtime %s: %w", name, err)
6465
}
6566

67+
// Verify that the runtime binaries are available
68+
if !isRuntimeInstalled(runtimeInfo) {
69+
logger.Error("Runtime binaries not found after extraction", logrus.Fields{
70+
"runtime": name,
71+
"version": runtimeInfo.Version,
72+
})
73+
return fmt.Errorf("runtime %s was extracted but binaries are not available", name)
74+
}
75+
6676
return nil
6777
}
6878

@@ -71,18 +81,30 @@ func isRuntimeInstalled(runtimeInfo *plugins.RuntimeInfo) bool {
7181
// If there are no binaries, check the install directory
7282
if len(runtimeInfo.Binaries) == 0 {
7383
_, err := os.Stat(runtimeInfo.InstallDir)
74-
return err == nil
84+
if err != nil {
85+
logger.Debug("Runtime install directory not found", logrus.Fields{
86+
"directory": runtimeInfo.InstallDir,
87+
"error": err,
88+
})
89+
return false
90+
}
91+
return true
7592
}
7693

7794
// Check if at least one binary exists
78-
for _, binaryPath := range runtimeInfo.Binaries {
95+
for binaryName, binaryPath := range runtimeInfo.Binaries {
7996
_, err := os.Stat(binaryPath)
80-
if err == nil {
81-
return true
97+
if err != nil {
98+
logger.Debug("Runtime binary not found", logrus.Fields{
99+
"binary": binaryName,
100+
"path": binaryPath,
101+
"error": err,
102+
})
103+
return false
82104
}
83105
}
84106

85-
return false
107+
return true
86108
}
87109

88110
// downloadAndExtractRuntime downloads and extracts a runtime
@@ -140,6 +162,19 @@ func downloadAndExtractRuntime(runtimeInfo *plugins.RuntimeInfo) error {
140162
return fmt.Errorf("failed to extract runtime: %w", err)
141163
}
142164

165+
// Ensure binaries have executable permissions
166+
for _, binaryPath := range runtimeInfo.Binaries {
167+
fullPath := filepath.Join(Config.RuntimesDirectory(), filepath.Base(runtimeInfo.InstallDir), binaryPath)
168+
if err := os.Chmod(fullPath, utils.DefaultDirPerms); err != nil {
169+
logger.Debug("Failed to set binary permissions", logrus.Fields{
170+
"binary": binaryPath,
171+
"path": fullPath,
172+
"error": err,
173+
})
174+
return fmt.Errorf("failed to set binary permissions for %s: %w", binaryPath, err)
175+
}
176+
}
177+
143178
logger.Debug("Runtime extraction completed", logrus.Fields{
144179
"runtime": runtimeInfo.Name,
145180
"version": runtimeInfo.Version,

constants/permissions.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package constants
2+
3+
const (
4+
// FilePermission represents the default file permission (rw-r--r--)
5+
// This permission gives:
6+
// - read/write (rw-) permissions to the owner
7+
// - read-only (r--) permissions to the group
8+
// - read-only (r--) permissions to others
9+
DefaultFilePerms = 0644
10+
11+
// DefaultDirPerms represents the default directory permission (rwxr-xr-x)
12+
// This permission gives:
13+
// - read/write/execute (rwx) permissions to the owner
14+
// - read/execute (r-x) permissions to the group
15+
// - read/execute (r-x) permissions to others
16+
//
17+
// Execute permission on directories is required to:
18+
// - List directory contents (ls)
19+
// - Access files within the directory (cd)
20+
// - Create/delete files in the directory
21+
// Without execute permission, users cannot traverse into or use the directory,
22+
// even if they have read/write permissions on files inside it
23+
DefaultDirPerms = 0755 // For directories
24+
)

plugins/runtime-utils.go

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,14 @@ var pluginsFS embed.FS
1717

1818
// binary represents a binary executable provided by the runtime
1919
type binary struct {
20-
Name string `yaml:"name"`
21-
Path string `yaml:"path"`
20+
Name string `yaml:"name"`
21+
Path interface{} `yaml:"path"` // Can be either string or map[string]string
22+
}
23+
24+
// binaryPath represents OS-specific paths for a binary
25+
type binaryPath struct {
26+
Darwin string `yaml:"darwin"`
27+
Linux string `yaml:"linux"`
2228
}
2329

2430
// pluginConfig holds the structure of the plugin.yaml file
@@ -48,6 +54,7 @@ type extensionConfig struct {
4854
// templateData holds the data to be used in template substitution
4955
type templateData struct {
5056
Version string
57+
MajorVersion string
5158
FileName string
5259
OS string
5360
Arch string
@@ -118,14 +125,31 @@ func processRuntime(config RuntimeConfig, runtimesDir string) (*RuntimeInfo, err
118125

119126
// Process binary paths
120127
for _, binary := range plugin.Config.Binaries {
121-
binaryPath := path.Join(installDir, binary.Path)
128+
var binaryPath string
129+
130+
switch path := binary.Path.(type) {
131+
case string:
132+
// If path is a simple string, use it directly
133+
binaryPath = path
134+
case map[string]interface{}:
135+
// If path is a map, get the OS-specific path
136+
if osPath, ok := path[runtime.GOOS]; ok {
137+
binaryPath = osPath.(string)
138+
} else {
139+
return nil, fmt.Errorf("no binary path specified for OS %s", runtime.GOOS)
140+
}
141+
default:
142+
return nil, fmt.Errorf("invalid path format for binary %s", binary.Name)
143+
}
144+
145+
fullPath := path.Join(installDir, binaryPath)
122146

123147
// Add file extension for Windows executables
124-
if runtime.GOOS == "windows" && !strings.HasSuffix(binaryPath, ".exe") {
125-
binaryPath += ".exe"
148+
if runtime.GOOS == "windows" && !strings.HasSuffix(fullPath, ".exe") {
149+
fullPath += ".exe"
126150
}
127151

128-
info.Binaries[binary.Name] = binaryPath
152+
info.Binaries[binary.Name] = fullPath
129153
}
130154

131155
return info, nil
@@ -172,6 +196,14 @@ func (p *runtimePlugin) getExtension(goos string) string {
172196
return p.Config.Download.Extension.Default
173197
}
174198

199+
// getMajorVersion extracts the major version from a version string (e.g. "17.0.10" -> "17")
200+
func (p *runtimePlugin) getMajorVersion(version string) string {
201+
if idx := strings.Index(version, "."); idx != -1 {
202+
return version[:idx]
203+
}
204+
return version
205+
}
206+
175207
// GetFileName generates the filename based on the template in plugin.yaml
176208
func (p *runtimePlugin) getFileName(version string) string {
177209
goos := runtime.GOOS
@@ -185,6 +217,7 @@ func (p *runtimePlugin) getFileName(version string) string {
185217
// Prepare template data
186218
data := templateData{
187219
Version: version,
220+
MajorVersion: p.getMajorVersion(version),
188221
OS: mappedOS,
189222
Arch: mappedArch,
190223
ReleaseVersion: releaseVersion,
@@ -230,6 +263,7 @@ func (p *runtimePlugin) getDownloadURL(version string) string {
230263
// Prepare template data
231264
data := templateData{
232265
Version: version,
266+
MajorVersion: p.getMajorVersion(version),
233267
FileName: fileName,
234268
OS: mappedOS,
235269
Arch: mappedArch,

plugins/runtimes/java/plugin.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
name: java
2+
description: Java Runtime Environment
3+
download:
4+
url_template: "https://github.com/adoptium/temurin{{.MajorVersion}}-binaries/releases/download/jdk-{{.Version}}%2B7/OpenJDK{{.MajorVersion}}U-jdk_{{.Arch}}_{{.OS}}_hotspot_{{.Version}}_7.{{.Extension}}"
5+
file_name_template: "jdk-{{.Version}}+7"
6+
extension:
7+
default: "tar.gz"
8+
arch_mapping:
9+
"386": "x86-32"
10+
"amd64": "x64"
11+
"arm": "arm"
12+
"arm64": "aarch64"
13+
os_mapping:
14+
"darwin": "mac"
15+
"linux": "linux"
16+
binaries:
17+
- name: java
18+
path:
19+
darwin: "Contents/Home/bin/java"
20+
linux: "bin/java"

0 commit comments

Comments
 (0)