123 lines
2.8 KiB
Go
123 lines
2.8 KiB
Go
package gotool
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// Owner lazily discovers and reuses one Go tool while its inherited
|
|
// environment and persistent settings file remain unchanged.
|
|
type Owner struct {
|
|
directory string
|
|
tool *Tool
|
|
env map[string]string
|
|
file string
|
|
contents []byte
|
|
fileFound bool
|
|
snapshotAvailable bool
|
|
}
|
|
|
|
// NewOwner creates an operation-local Go tool owner without running Go.
|
|
func NewOwner(directory string) *Owner {
|
|
return &Owner{directory: directory}
|
|
}
|
|
|
|
// Acquire returns the operation's Go tool, rediscovering settings when their
|
|
// process or persistent inputs have changed.
|
|
func (owner *Owner) Acquire(ctx context.Context) (*Tool, error) {
|
|
if owner.tool != nil && owner.snapshotAvailable {
|
|
changed, err := owner.inputsChanged()
|
|
if err == nil && !changed {
|
|
return owner.tool, nil
|
|
}
|
|
}
|
|
if err := owner.discover(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
return owner.tool, nil
|
|
}
|
|
|
|
func (owner *Owner) discover(ctx context.Context) error {
|
|
environment := inheritedEnvironment()
|
|
tool, err := New(ctx, owner.directory)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
settingsFile, snapshotErr := resolveSettingsFile(owner.directory, tool.settingsFile)
|
|
var contents []byte
|
|
var found bool
|
|
if snapshotErr == nil {
|
|
contents, found, snapshotErr = readSettingsFile(settingsFile)
|
|
}
|
|
owner.tool = tool
|
|
owner.env = environment
|
|
owner.file = settingsFile
|
|
owner.contents = contents
|
|
owner.fileFound = found
|
|
owner.snapshotAvailable = snapshotErr == nil
|
|
return nil
|
|
}
|
|
|
|
func (owner *Owner) inputsChanged() (bool, error) {
|
|
if !sameEnvironment(owner.env, inheritedEnvironment()) {
|
|
return true, nil
|
|
}
|
|
contents, found, err := readSettingsFile(owner.file)
|
|
if err != nil {
|
|
return true, err
|
|
}
|
|
return found != owner.fileFound || !bytes.Equal(contents, owner.contents), nil
|
|
}
|
|
|
|
func inheritedEnvironment() map[string]string {
|
|
values := make(map[string]string)
|
|
for _, entry := range os.Environ() {
|
|
key, value, ok := strings.Cut(entry, "=")
|
|
if ok {
|
|
values[key] = value
|
|
}
|
|
}
|
|
return values
|
|
}
|
|
|
|
func sameEnvironment(left, right map[string]string) bool {
|
|
if len(left) != len(right) {
|
|
return false
|
|
}
|
|
for key, value := range left {
|
|
other, ok := right[key]
|
|
if !ok || other != value {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func resolveSettingsFile(directory, filename string) (string, error) {
|
|
if filename == "" || filename == "off" {
|
|
return "", nil
|
|
}
|
|
if !filepath.IsAbs(filename) {
|
|
filename = filepath.Join(directory, filename)
|
|
}
|
|
return filepath.Abs(filename)
|
|
}
|
|
|
|
func readSettingsFile(filename string) ([]byte, bool, error) {
|
|
if filename == "" {
|
|
return nil, false, nil
|
|
}
|
|
contents, err := os.ReadFile(filename)
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
return nil, false, nil
|
|
}
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
return contents, true, nil
|
|
}
|