-
Notifications
You must be signed in to change notification settings - Fork 14
Initial ecosystem implementation for Go. #3709
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| package ecosystem | ||
|
|
||
| import ( | ||
| "os" | ||
| "path/filepath" | ||
| "sort" | ||
| "strings" | ||
|
|
||
| "golang.org/x/mod/semver" | ||
|
|
||
| "github.com/ActiveState/cli/internal/errs" | ||
| "github.com/ActiveState/cli/internal/fileutils" | ||
| "github.com/ActiveState/cli/internal/smartlink" | ||
| "github.com/ActiveState/cli/internal/unarchiver" | ||
| "github.com/ActiveState/cli/pkg/buildplan" | ||
| ) | ||
|
|
||
| type Golang struct { | ||
| runtimeDir string | ||
| proxyDir string | ||
| addedModuleVersions map[string][]string | ||
| } | ||
|
|
||
| func (e *Golang) Init(runtimePath string, buildplan *buildplan.BuildPlan) error { | ||
| e.runtimeDir = runtimePath | ||
| e.proxyDir = filepath.Join("usr", "goproxy") | ||
| err := fileutils.MkdirUnlessExists(filepath.Join(e.runtimeDir, e.proxyDir)) | ||
| if err != nil { | ||
| return errs.Wrap(err, "Unable to create Go proxy directory") | ||
| } | ||
| e.addedModuleVersions = make(map[string][]string) | ||
| return nil | ||
| } | ||
|
|
||
| func (e *Golang) Namespaces() []string { | ||
| return []string{"language/golang"} | ||
| } | ||
|
|
||
| // Unpack the module into the proxy directory. | ||
| // We also inject the GOPROXY environment variable into runtime.json to force offline use. | ||
| // We also inject GOMODCACHE to avoid polluting the default user cache. | ||
| func (e *Golang) Add(artifact *buildplan.Artifact, artifactSrcPath string) ([]string, error) { | ||
| installedFiles := []string{} | ||
|
|
||
| files, err := fileutils.ListDir(artifactSrcPath, false) | ||
| if err != nil { | ||
| return nil, errs.Wrap(err, "Unable to read artifact source directory") | ||
| } | ||
|
|
||
| for _, file := range files { | ||
| if file.Name() == "runtime.json" { | ||
| err = injectEnvVar(file.AbsolutePath(), "GOPROXY", "file://${INSTALLDIR}/usr/goproxy") | ||
| if err != nil { | ||
| return nil, errs.Wrap(err, "Unable to add GOPROXY to runtime.json") | ||
| } | ||
| err = injectEnvVar(file.AbsolutePath(), "GOMODCACHE", "${INSTALLDIR}/usr/goproxy/cache") | ||
| if err != nil { | ||
| return nil, errs.Wrap(err, "Unable to add GOMODCACHE to runtime.json") | ||
| } | ||
| continue | ||
| } | ||
| if !strings.HasSuffix(file.Name(), ".zip") { | ||
| continue | ||
| } | ||
|
|
||
| // The structure of a Go proxy is: | ||
| // proxydir/ | ||
| // - example.com/ | ||
| // - mymodule/ | ||
| // - @v/ | ||
| // - list | ||
| // - v1.0.0.mod | ||
| // - v1.0.0.zip | ||
|
|
||
| relativeProxied := filepath.Join(e.proxyDir, artifact.Name()) | ||
| absProxied := filepath.Join(e.runtimeDir, relativeProxied) | ||
|
|
||
| // Create the @v directory if it doesn't already exist. | ||
| vDir := filepath.Join(absProxied, "@v") | ||
| err = fileutils.MkdirUnlessExists(vDir) | ||
| if err != nil { | ||
| return nil, errs.Wrap(err, "Could not create proxy module @v directory") | ||
| } | ||
|
|
||
| // Link/copy the zip file into the @v directory. | ||
| err = smartlink.Link(file.AbsolutePath(), filepath.Join(vDir, file.Name())) | ||
|
|
||
| // Extract the go.mod from the zip and copy it into the @v directory with a versioned name. | ||
| ua := unarchiver.NewZip() | ||
| unpackDir := fileutils.TempFilePath("", "") | ||
| f, size, err := ua.PrepareUnpacking(file.AbsolutePath(), unpackDir) | ||
| if err != nil { | ||
| return nil, errs.Wrap(err, "Unable to prepare for unpacking downloaded module") | ||
| } | ||
| err = ua.Unarchive(f, size, unpackDir) | ||
| if err != nil { | ||
| return nil, errs.Wrap(err, "Unable to unpack downloaded module") | ||
| } | ||
| err = fileutils.CopyFile(filepath.Join(unpackDir, artifact.NameAndVersion(), "go.mod"), filepath.Join(vDir, artifact.Version()+".mod")) | ||
| if err != nil { | ||
| return nil, errs.Wrap(err, "Unable to copy go.mod from unpacked module") | ||
| } | ||
| err = os.RemoveAll(unpackDir) | ||
| if err != nil { | ||
| return nil, errs.Wrap(err, "Unable to remove unpacked module") | ||
| } | ||
|
||
|
|
||
| installedFiles = append(installedFiles, relativeProxied) | ||
| } | ||
|
|
||
| e.addedModuleVersions[artifact.Name()] = append(e.addedModuleVersions[artifact.Name()], artifact.Version()) | ||
|
|
||
| return installedFiles, nil | ||
| } | ||
|
|
||
| func (e *Golang) Remove(artifact *buildplan.Artifact) error { | ||
| return nil // TODO: CP-956 | ||
| } | ||
|
|
||
| // Create/update each added module's version list file. | ||
| func (e *Golang) Apply() error { | ||
| for name, versions := range e.addedModuleVersions { | ||
| listFile := filepath.Join(e.runtimeDir, e.proxyDir, name, "@v", "list") | ||
| if fileutils.FileExists(listFile) { | ||
| // Add known versions to the new versions so we can write a comprehensive list. | ||
| contents, err := fileutils.ReadFile(listFile) | ||
| if err != nil { | ||
| return errs.Wrap(err, "Unable to read %s", listFile) | ||
| } | ||
| for _, version := range strings.Split(string(contents), "\n") { | ||
| versions = append(versions, version) | ||
| } | ||
| } | ||
|
|
||
| // Sort versions in descending order by semver. | ||
| sort.SliceStable(versions, func(i, j int) bool { | ||
| return semver.Compare(versions[i], versions[j]) < 0 | ||
| }) | ||
|
|
||
| // Write all known versions. | ||
| err := fileutils.WriteFile(listFile, []byte(strings.Join(versions, "\n"))) | ||
| if err != nil { | ||
| return errs.Wrap(err, "Unable to write %s", listFile) | ||
| } | ||
| } | ||
| return nil | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Jeremy pointed out not every package will have a go.mod, is that a concern here? I vaguely recall something about the go proxy injecting one, which DI is using so maybe it's fine..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also is
artifact.NameAndVersion()robust enough? Feels a bit risky to assume a 1:1 mapping there. Perhaps we could locate the go.mod file instead? Assuming the answer to my previous question is that it always exists.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair points. I've changed this to a list-dir-and-search approach. If go.mod wasn't found, we create one, according to the goproxy protocol.