Skip to content

Commit 240cb5d

Browse files
chore: Update GitHub Actions workflow to support multiple OS environments for testing
1 parent 36db748 commit 240cb5d

File tree

2 files changed

+297
-1
lines changed

2 files changed

+297
-1
lines changed

.github/workflows/go.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ permissions:
55

66
jobs:
77
test:
8-
runs-on: ubuntu-latest
8+
runs-on: ${{ matrix.os }}
9+
strategy:
10+
matrix:
11+
os: [ubuntu-latest, windows-latest, macos-latest]
912
steps:
1013
- name: Checkout
1114
uses: actions/checkout@v4
@@ -19,6 +22,7 @@ jobs:
1922
run: |
2023
go test -coverprofile=unit.coverage.out ./...
2124
- name: "Upload coverage to Codacy"
25+
if: matrix.os == 'ubuntu-latest' # Only upload coverage from Ubuntu
2226
env:
2327
CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
2428
run: |

config/runtime_manager.go

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
package config
2+
3+
import (
4+
"fmt"
5+
"log"
6+
"os"
7+
"path"
8+
"path/filepath"
9+
"runtime"
10+
"strconv"
11+
"strings"
12+
13+
"codacy/cli-v2/utils"
14+
)
15+
16+
// RuntimeManager handles the installation and management of different runtimes
17+
type RuntimeManager struct {
18+
runtimesDir string
19+
}
20+
21+
// NewRuntimeManager creates a new runtime manager
22+
func NewRuntimeManager(runtimesDir string) *RuntimeManager {
23+
return &RuntimeManager{
24+
runtimesDir: runtimesDir,
25+
}
26+
}
27+
28+
// matchesOSAndArch checks if a download configuration matches the current OS and architecture
29+
func matchesOSAndArch(download Download, goos, goarch string) bool {
30+
// Handle OS matching
31+
switch osValue := download.OS.(type) {
32+
case string:
33+
// Direct string match
34+
if osValue == goos || (goos == "darwin" && osValue == "macos") {
35+
return true
36+
}
37+
case map[string]interface{}:
38+
// Map of OS mappings
39+
if mappedOS, ok := osValue["macos"].(string); ok && goos == "darwin" {
40+
return mappedOS == "apple-darwin"
41+
}
42+
if mappedOS, ok := osValue[goos].(string); ok {
43+
return mappedOS == getOSString(goos)
44+
}
45+
}
46+
47+
// Handle CPU architecture matching if specified
48+
if download.CPU != nil {
49+
switch cpuValue := download.CPU.(type) {
50+
case string:
51+
// Direct string match
52+
if cpuValue != getArchString(goarch) {
53+
return false
54+
}
55+
case map[string]interface{}:
56+
// Map of CPU mappings
57+
if mappedArch, ok := cpuValue[goarch].(string); ok {
58+
return mappedArch == getArchString(goarch)
59+
}
60+
return false
61+
}
62+
}
63+
64+
return false
65+
}
66+
67+
// getOSString converts Go OS names to runtime-specific OS names
68+
func getOSString(goos string) string {
69+
switch goos {
70+
case "darwin":
71+
return "darwin"
72+
case "windows":
73+
return "win"
74+
default:
75+
return goos
76+
}
77+
}
78+
79+
// getArchString converts Go architecture names to runtime-specific architecture names
80+
func getArchString(goarch string) string {
81+
switch goarch {
82+
case "amd64":
83+
return "x64"
84+
case "386":
85+
return "x86"
86+
case "arm64":
87+
return "arm64"
88+
default:
89+
return goarch
90+
}
91+
}
92+
93+
// InstallRuntime installs a runtime based on its plugin.yaml definition
94+
func (rm *RuntimeManager) InstallRuntime(runtimeName, version string) error {
95+
// Load the plugin configuration
96+
pluginPath := filepath.Join("definitions/runtimes", runtimeName, "plugin.yaml")
97+
plugin, err := LoadPluginConfig(pluginPath)
98+
if err != nil {
99+
return fmt.Errorf("failed to load plugin config for %s: %w", runtimeName, err)
100+
}
101+
102+
// Get current OS and architecture
103+
goos := runtime.GOOS
104+
goarch := runtime.GOARCH
105+
106+
// Find the runtime download configuration
107+
var runtimeDownload *Download
108+
for _, rt := range plugin.Downloads {
109+
if rt.Name == runtimeName {
110+
// Find the download configuration that matches our OS, architecture, and version
111+
for _, download := range rt.Downloads {
112+
// Check if this download matches our OS and architecture
113+
if matchesOSAndArch(download, goos, goarch) {
114+
// Check if the version constraint matches
115+
if download.Version != "" {
116+
// For now, we'll just check if the version is less than or equal to the constraint
117+
// This is a simple check and should be improved with proper semver comparison
118+
if strings.HasPrefix(download.Version, "<=") {
119+
constraint := strings.TrimPrefix(download.Version, "<=")
120+
// Split versions into major.minor.patch
121+
versionParts := strings.Split(version, ".")
122+
constraintParts := strings.Split(constraint, ".")
123+
124+
// Convert parts to integers for comparison
125+
versionMajor, _ := strconv.Atoi(versionParts[0])
126+
versionMinor, _ := strconv.Atoi(versionParts[1])
127+
versionPatch, _ := strconv.Atoi(versionParts[2])
128+
constraintMajor, _ := strconv.Atoi(constraintParts[0])
129+
constraintMinor, _ := strconv.Atoi(constraintParts[1])
130+
constraintPatch, _ := strconv.Atoi(constraintParts[2])
131+
132+
// Compare major version
133+
if versionMajor < constraintMajor {
134+
runtimeDownload = &download
135+
break
136+
} else if versionMajor == constraintMajor {
137+
// Compare minor version
138+
if versionMinor < constraintMinor {
139+
runtimeDownload = &download
140+
break
141+
} else if versionMinor == constraintMinor {
142+
// Compare patch version
143+
if versionPatch <= constraintPatch {
144+
runtimeDownload = &download
145+
break
146+
}
147+
}
148+
}
149+
}
150+
} else {
151+
runtimeDownload = &download
152+
break
153+
}
154+
}
155+
}
156+
if runtimeDownload != nil {
157+
break
158+
}
159+
}
160+
}
161+
162+
if runtimeDownload == nil {
163+
return fmt.Errorf("no matching download found for %s runtime version %s on OS %s and architecture %s", runtimeName, version, goos, goarch)
164+
}
165+
166+
// Get the mapped OS and CPU values from the download configuration
167+
var mappedOS, mappedCPU string
168+
if osMap, ok := runtimeDownload.OS.(map[string]interface{}); ok {
169+
if goos == "darwin" {
170+
if mapped, ok := osMap["macos"].(string); ok {
171+
mappedOS = mapped
172+
}
173+
} else if mapped, ok := osMap[goos].(string); ok {
174+
mappedOS = mapped
175+
}
176+
}
177+
178+
if cpuMap, ok := runtimeDownload.CPU.(map[string]interface{}); ok {
179+
if mapped, ok := cpuMap[goarch].(string); ok {
180+
mappedCPU = mapped
181+
}
182+
}
183+
184+
// Extract the date from the URL (e.g., "20221106" from the example URL)
185+
urlParts := strings.Split(runtimeDownload.URL, "/")
186+
var date string
187+
for _, part := range urlParts {
188+
if len(part) == 8 && strings.HasPrefix(part, "20") {
189+
date = part
190+
break
191+
}
192+
}
193+
194+
// Construct the URL in the correct format
195+
url := fmt.Sprintf("https://github.com/indygreg/python-build-standalone/releases/download/%s/cpython-%s+%s-%s-%s-install_only.tar.gz",
196+
date,
197+
version,
198+
date,
199+
mappedCPU,
200+
mappedOS)
201+
202+
// Log the constructed URL for debugging
203+
log.Printf("Download URL: %s", url)
204+
205+
// Download the runtime
206+
log.Printf("Fetching %s %s...\n", runtimeName, version)
207+
archivePath, err := utils.DownloadFile(url, rm.runtimesDir)
208+
if err != nil {
209+
return fmt.Errorf("failed to download %s: %w", runtimeName, err)
210+
}
211+
212+
// Extract the archive
213+
archive, err := os.Open(archivePath)
214+
defer archive.Close()
215+
if err != nil {
216+
return fmt.Errorf("failed to open downloaded archive: %w", err)
217+
}
218+
219+
err = utils.ExtractTarGz(archive, rm.runtimesDir)
220+
if err != nil {
221+
return fmt.Errorf("failed to extract %s archive: %w", runtimeName, err)
222+
}
223+
224+
return nil
225+
}
226+
227+
// GetRuntimeInfo returns information about the installed runtime
228+
func (rm *RuntimeManager) GetRuntimeInfo(runtimeName, version string) map[string]string {
229+
info := make(map[string]string)
230+
231+
switch runtimeName {
232+
case "node":
233+
// Node.js installation directory structure
234+
nodeDir := path.Join(rm.runtimesDir, fmt.Sprintf("node-v%s-%s-%s", version, runtime.GOOS, getArchString(runtime.GOARCH)))
235+
info["installDir"] = nodeDir
236+
info["node"] = path.Join(nodeDir, "bin", "node")
237+
info["npm"] = path.Join(nodeDir, "bin", "npm")
238+
info["npx"] = path.Join(nodeDir, "bin", "npx")
239+
info["corepack"] = path.Join(nodeDir, "bin", "corepack")
240+
case "python":
241+
// Python installation directory structure
242+
pythonDir := path.Join(rm.runtimesDir, fmt.Sprintf("python-v%s", version))
243+
info["installDir"] = pythonDir
244+
info["python"] = path.Join(pythonDir, "bin", "python")
245+
info["pip"] = path.Join(pythonDir, "bin", "pip")
246+
default:
247+
log.Printf("Warning: Unknown runtime type %s, using default directory structure", runtimeName)
248+
runtimeDir := path.Join(rm.runtimesDir, fmt.Sprintf("%s-v%s", runtimeName, version))
249+
info["installDir"] = runtimeDir
250+
}
251+
252+
return info
253+
}
254+
255+
// IsRuntimeInstalled checks if a specific version of a runtime is installed
256+
func (rm *RuntimeManager) IsRuntimeInstalled(runtimeName, version string) bool {
257+
var runtimeDir string
258+
switch runtimeName {
259+
case "node":
260+
runtimeDir = path.Join(rm.runtimesDir, fmt.Sprintf("node-v%s-%s-%s", version, runtime.GOOS, getArchString(runtime.GOARCH)))
261+
case "python":
262+
runtimeDir = path.Join(rm.runtimesDir, fmt.Sprintf("python-v%s", version))
263+
default:
264+
runtimeDir = path.Join(rm.runtimesDir, fmt.Sprintf("%s-v%s", runtimeName, version))
265+
}
266+
267+
// Check if the directory exists and contains the expected binaries
268+
_, err := os.Stat(runtimeDir)
269+
if err != nil {
270+
return false
271+
}
272+
273+
// For Node.js, check for the presence of node and npm
274+
if runtimeName == "node" {
275+
nodePath := path.Join(runtimeDir, "bin", "node")
276+
npmPath := path.Join(runtimeDir, "bin", "npm")
277+
_, nodeErr := os.Stat(nodePath)
278+
_, npmErr := os.Stat(npmPath)
279+
return nodeErr == nil && npmErr == nil
280+
}
281+
282+
// For Python, check for the presence of python and pip
283+
if runtimeName == "python" {
284+
pythonPath := path.Join(runtimeDir, "bin", "python")
285+
pipPath := path.Join(runtimeDir, "bin", "pip")
286+
_, pythonErr := os.Stat(pythonPath)
287+
_, pipErr := os.Stat(pipPath)
288+
return pythonErr == nil && pipErr == nil
289+
}
290+
291+
return true
292+
}

0 commit comments

Comments
 (0)