Skip to content

Commit 3e66f72

Browse files
authored
Merge pull request #3709 from ActiveState/mitchell/cp-1028
Initial ecosystem implementation for Go.
2 parents 8ec8759 + 3be639d commit 3e66f72

File tree

2 files changed

+170
-0
lines changed

2 files changed

+170
-0
lines changed

pkg/runtime/ecosystem.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ func init() {
2121
func() ecosystem { return &ecosys.JavaScript{} },
2222
func() ecosystem { return &ecosys.Rust{} },
2323
func() ecosystem { return &ecosys.DotNet{} },
24+
func() ecosystem { return &ecosys.Golang{} },
2425
}
2526
}
2627

pkg/runtime/ecosystem/golang.go

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
package ecosystem
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"sort"
7+
"strings"
8+
9+
"golang.org/x/mod/semver"
10+
11+
"github.com/ActiveState/cli/internal/errs"
12+
"github.com/ActiveState/cli/internal/fileutils"
13+
"github.com/ActiveState/cli/internal/smartlink"
14+
"github.com/ActiveState/cli/internal/unarchiver"
15+
"github.com/ActiveState/cli/pkg/buildplan"
16+
)
17+
18+
type Golang struct {
19+
runtimeDir string
20+
proxyDir string
21+
addedModuleVersions map[string][]string
22+
}
23+
24+
func (e *Golang) Init(runtimePath string, buildplan *buildplan.BuildPlan) error {
25+
e.runtimeDir = runtimePath
26+
e.proxyDir = filepath.Join("usr", "goproxy")
27+
err := fileutils.MkdirUnlessExists(filepath.Join(e.runtimeDir, e.proxyDir))
28+
if err != nil {
29+
return errs.Wrap(err, "Unable to create Go proxy directory")
30+
}
31+
e.addedModuleVersions = make(map[string][]string)
32+
return nil
33+
}
34+
35+
func (e *Golang) Namespaces() []string {
36+
return []string{"language/golang"}
37+
}
38+
39+
// Unpack the module into the proxy directory.
40+
// We also inject the GOPROXY environment variable into runtime.json to force offline use.
41+
// We also inject GOMODCACHE to avoid polluting the default user cache.
42+
func (e *Golang) Add(artifact *buildplan.Artifact, artifactSrcPath string) (_ []string, rerr error) {
43+
installedFiles := []string{}
44+
45+
files, err := fileutils.ListDir(artifactSrcPath, false)
46+
if err != nil {
47+
return nil, errs.Wrap(err, "Unable to read artifact source directory")
48+
}
49+
50+
for _, file := range files {
51+
if file.Name() == "runtime.json" {
52+
err = injectEnvVar(file.AbsolutePath(), "GOPROXY", "file://${INSTALLDIR}/usr/goproxy")
53+
if err != nil {
54+
return nil, errs.Wrap(err, "Unable to add GOPROXY to runtime.json")
55+
}
56+
err = injectEnvVar(file.AbsolutePath(), "GOMODCACHE", "${INSTALLDIR}/usr/goproxy/cache")
57+
if err != nil {
58+
return nil, errs.Wrap(err, "Unable to add GOMODCACHE to runtime.json")
59+
}
60+
continue
61+
}
62+
if !strings.HasSuffix(file.Name(), ".zip") {
63+
continue
64+
}
65+
66+
// The structure of a Go proxy is:
67+
// proxydir/
68+
// - example.com/
69+
// - mymodule/
70+
// - @v/
71+
// - list
72+
// - v1.0.0.mod
73+
// - v1.0.0.zip
74+
75+
relativeProxied := filepath.Join(e.proxyDir, artifact.Name())
76+
absProxied := filepath.Join(e.runtimeDir, relativeProxied)
77+
78+
// Create the @v directory if it doesn't already exist.
79+
vDir := filepath.Join(absProxied, "@v")
80+
err = fileutils.MkdirUnlessExists(vDir)
81+
if err != nil {
82+
return nil, errs.Wrap(err, "Could not create proxy module @v directory")
83+
}
84+
85+
// Link/copy the zip file into the @v directory.
86+
err = smartlink.Link(file.AbsolutePath(), filepath.Join(vDir, file.Name()))
87+
88+
// Extract the go.mod from the zip and copy it into the @v directory with a versioned name.
89+
ua := unarchiver.NewZip()
90+
unpackDir := fileutils.TempFilePath("", "")
91+
f, size, err := ua.PrepareUnpacking(file.AbsolutePath(), unpackDir)
92+
if err != nil {
93+
return nil, errs.Wrap(err, "Unable to prepare for unpacking downloaded module")
94+
}
95+
err = ua.Unarchive(f, size, unpackDir)
96+
if err != nil {
97+
return nil, errs.Wrap(err, "Unable to unpack downloaded module")
98+
}
99+
defer func() {
100+
err := os.RemoveAll(unpackDir)
101+
if err != nil {
102+
rerr = errs.Pack(rerr, errs.Wrap(err, "Unable to remove unpacked module in %s", unpackDir))
103+
}
104+
}()
105+
zipFiles, err := fileutils.ListDirSimple(unpackDir, false)
106+
if err != nil {
107+
return nil, errs.Wrap(err, "Unable to list unpacked module")
108+
}
109+
goModFound := false
110+
for _, file := range zipFiles {
111+
if filepath.Base(file) != "go.mod" {
112+
continue
113+
}
114+
err = fileutils.CopyFile(file, filepath.Join(vDir, artifact.Version()+".mod"))
115+
if err != nil {
116+
return nil, errs.Wrap(err, "Unable to copy go.mod from unpacked module")
117+
}
118+
goModFound = true
119+
break
120+
}
121+
122+
// If the archive did not have a go.mod to copy, create a versioned one in the @v directory.
123+
if !goModFound {
124+
err = fileutils.WriteFile(filepath.Join(vDir, artifact.Version()+".mod"), []byte("module "+artifact.Name()))
125+
if err != nil {
126+
return nil, errs.Wrap(err, "Unable to write go.mod file")
127+
}
128+
}
129+
130+
installedFiles = append(installedFiles, relativeProxied)
131+
}
132+
133+
e.addedModuleVersions[artifact.Name()] = append(e.addedModuleVersions[artifact.Name()], artifact.Version())
134+
135+
return installedFiles, nil
136+
}
137+
138+
func (e *Golang) Remove(artifact *buildplan.Artifact) error {
139+
return nil // TODO: CP-956
140+
}
141+
142+
// Create/update each added module's version list file.
143+
func (e *Golang) Apply() error {
144+
for name, versions := range e.addedModuleVersions {
145+
listFile := filepath.Join(e.runtimeDir, e.proxyDir, name, "@v", "list")
146+
if fileutils.FileExists(listFile) {
147+
// Add known versions to the new versions so we can write a comprehensive list.
148+
contents, err := fileutils.ReadFile(listFile)
149+
if err != nil {
150+
return errs.Wrap(err, "Unable to read %s", listFile)
151+
}
152+
for _, version := range strings.Split(string(contents), "\n") {
153+
versions = append(versions, version)
154+
}
155+
}
156+
157+
// Sort versions in descending order by semver.
158+
sort.SliceStable(versions, func(i, j int) bool {
159+
return semver.Compare(versions[i], versions[j]) < 0
160+
})
161+
162+
// Write all known versions.
163+
err := fileutils.WriteFile(listFile, []byte(strings.Join(versions, "\n")))
164+
if err != nil {
165+
return errs.Wrap(err, "Unable to write %s", listFile)
166+
}
167+
}
168+
return nil
169+
}

0 commit comments

Comments
 (0)