github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/cmd/state-exec/meta.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"strings"
     8  
     9  	"github.com/ActiveState/cli/pkg/platform/runtime/executors/execmeta"
    10  )
    11  
    12  const (
    13  	envVarSeparator   = "="
    14  	pathEnvVarPrefix  = "PATH" + envVarSeparator
    15  	pathListSeparator = string(os.PathListSeparator)
    16  )
    17  
    18  type executorMeta struct {
    19  	*execmeta.ExecMeta
    20  	MatchingBin    string
    21  	TransformedEnv []string
    22  }
    23  
    24  func newExecutorMeta(execPath string) (*executorMeta, error) {
    25  	execDir := filepath.Dir(execPath)
    26  	metaPath := filepath.Join(execDir, execmeta.MetaFileName)
    27  	meta, err := execmeta.NewFromFile(metaPath)
    28  	if err != nil {
    29  		return nil, fmt.Errorf("cannot get execmeta from file: %w", err)
    30  	}
    31  
    32  	matchingBin, err := matchingBinByPath(meta.Bins, execPath)
    33  	if err != nil {
    34  		return nil, fmt.Errorf("cannot get matching bin by path: %w", err)
    35  	}
    36  
    37  	em := executorMeta{
    38  		ExecMeta:       meta,
    39  		MatchingBin:    matchingBin,
    40  		TransformedEnv: transformedEnv(os.Environ(), meta.Env),
    41  	}
    42  
    43  	return &em, nil
    44  }
    45  
    46  // matchingBinByPath receives a list of binaries (from the meta file), as well
    47  // as the path to this program. The base name of the path is used to match one
    48  // of the binaries that will then be forwarded to as a child process.
    49  func matchingBinByPath(bins map[string]string, path string) (string, error) {
    50  	alias := filepath.Base(path)
    51  	if dest, ok := bins[alias]; ok {
    52  		return dest, nil
    53  	}
    54  	return "", fmt.Errorf("no matching binary by path %q", path)
    55  }
    56  
    57  // transformedEnv will update the current environment. Update entries are
    58  // appended (which supersede existing entries) except for: PATH is updated with
    59  // the update value prepended to the existing PATH.
    60  func transformedEnv(current []string, updates []string) []string {
    61  	for _, update := range updates {
    62  		if strings.HasPrefix(strings.ToLower(update), strings.ToLower(pathEnvVarPrefix)) {
    63  			pathCurrentV, pathCurrentK, ok := getEnvVar(current, pathEnvVarPrefix)
    64  			if ok {
    65  				current[pathCurrentK] = update + pathListSeparator + pathCurrentV
    66  				continue
    67  			}
    68  		}
    69  		current = append(current, update)
    70  	}
    71  	return current
    72  }
    73  
    74  // getEnvVar returns the value and index of the environment variable from the
    75  // provided environment slice. The prefix should end with the environment
    76  // variable separator.
    77  func getEnvVar(env []string, prefix string) (string, int, bool) {
    78  	for k, v := range env {
    79  		if strings.HasPrefix(strings.ToLower(v), strings.ToLower(prefix)) {
    80  			return v[len(prefix):], k, true
    81  		}
    82  	}
    83  	return "", 0, false
    84  }