feat: default to system python if no version specified (#10)
* refactor: extract shared logic determining which python version is used * feat: default to unshimmed python if no version specified docs: mention of how system-python is evaluated * refactor: extract python version determining funcs * wip: ensure that output from system python check is cleaned for stray spaces * test: coverage for system python fallback * refactor: unused constant * chore: update version to 0.0.5
This commit is contained in:
parent
9d144d7ddc
commit
5bf3f36559
5 changed files with 145 additions and 7 deletions
31
commands.go
31
commands.go
|
@ -99,14 +99,35 @@ func ListVersions(args []string, flags Flags, currentState State) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Where prints out the system path to the executable being used by `python`.
|
||||
func Where(args []string, flags Flags, currentState State) error {
|
||||
version := currentState.GlobalVersion
|
||||
tag := VersionStringToStruct(version)
|
||||
fmt.Println(GetStatePath("runtimes", fmt.Sprintf("py-%s", currentState.GlobalVersion), "bin", fmt.Sprintf("python%s", tag.MajorMinor())))
|
||||
selectedVersion, _ := DetermineSelectedPythonVersion(currentState)
|
||||
|
||||
if selectedVersion == "SYSTEM" {
|
||||
_, sysPath := DetermineSystemPython()
|
||||
fmt.Println(sysPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
func Which(args []string, flags Flags, currentState State) error {
|
||||
fmt.Println(currentState.GlobalVersion)
|
||||
tag := VersionStringToStruct(selectedVersion)
|
||||
fmt.Println(GetStatePath("runtimes", fmt.Sprintf("py-%s", selectedVersion), "bin", fmt.Sprintf("python%s", tag.MajorMinor())))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Which prints out the Python version that will be used by shims. It can be invoked
|
||||
// directly by the `v which` command.
|
||||
//
|
||||
// If no version is set (i.e. none is installed, the specified version is not installed),
|
||||
// the system version is used and 'SYSTEM' is printed by Which.
|
||||
func Which(args []string, flags Flags, currentState State) error {
|
||||
selectedVersion, _ := DetermineSelectedPythonVersion(currentState)
|
||||
|
||||
if selectedVersion == "SYSTEM" {
|
||||
sysVersion, _ := DetermineSystemPython()
|
||||
fmt.Println(sysVersion)
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Println(selectedVersion)
|
||||
return nil
|
||||
}
|
||||
|
|
38
pythonversion.go
Normal file
38
pythonversion.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DetermineSelectedPythonVersion returns the Python runtime version that should be
|
||||
// used according to v.
|
||||
//
|
||||
// By default, 'SYSTEM' is returned, which signals that the non-v-managed Python
|
||||
// runtime is used.
|
||||
func DetermineSelectedPythonVersion(currentState State) (string, error) {
|
||||
if len(currentState.GlobalVersion) != 0 {
|
||||
return currentState.GlobalVersion, nil
|
||||
}
|
||||
|
||||
return "SYSTEM", nil
|
||||
}
|
||||
|
||||
// DetermineSystemPython returns the unshimmed Python version and path.
|
||||
// This is done by inspected the output of `which` and `python --version` if v's shims
|
||||
// are not in $PATH.
|
||||
func DetermineSystemPython() (string, string) {
|
||||
currentPathEnv := os.Getenv("PATH")
|
||||
pathWithoutShims := slices.DeleteFunc(strings.Split(currentPathEnv, ":"), func(element string) bool {
|
||||
return element == GetStatePath("shims")
|
||||
})
|
||||
// FIXME: This should be set through RunCommand instead.
|
||||
os.Setenv("PATH", strings.Join(pathWithoutShims, ":"))
|
||||
whichOut, _ := RunCommand([]string{"which", "python"}, GetStatePath(), true)
|
||||
versionOut, _ := RunCommand([]string{"python", "--version"}, GetStatePath(), true)
|
||||
|
||||
detectedVersion, _ := strings.CutPrefix(versionOut, "Python")
|
||||
|
||||
return strings.TrimSpace(detectedVersion), strings.TrimSpace(whichOut)
|
||||
}
|
70
pythonversion_test.go
Normal file
70
pythonversion_test.go
Normal file
|
@ -0,0 +1,70 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// SetupAndCleanupEnvironment sets up a test directory and
|
||||
// environment variables before the test and returns a cleanup
|
||||
// function that can be deferred to cleanup any changes to the
|
||||
// system.
|
||||
func SetupAndCleanupEnvironment(t *testing.T) func() {
|
||||
os.Setenv("V_ROOT", t.TempDir())
|
||||
|
||||
return func() {
|
||||
os.Unsetenv("V_ROOT")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDetermineSystemPythonGetsUnshimmedPythonRuntime(t *testing.T) {
|
||||
defer SetupAndCleanupEnvironment(t)()
|
||||
|
||||
ioutil.WriteFile(GetStatePath("shims", "python"), []byte("#!/bin/bash\necho \"Python 4.5.6\""), 0777)
|
||||
mockSystemPythonPath := t.TempDir()
|
||||
mockSystemPythonExecPath := path.Join(mockSystemPythonPath, "python")
|
||||
ioutil.WriteFile(mockSystemPythonExecPath, []byte("#!/bin/bash\necho \"Python 1.2.3\""), 0777)
|
||||
|
||||
oldPath := os.Getenv("PATH")
|
||||
os.Setenv("PATH", fmt.Sprintf("%s:%s:/usr/bin", GetStatePath("shims"), mockSystemPythonPath))
|
||||
defer os.Setenv("PATH", oldPath)
|
||||
sysVersion, sysPath := DetermineSystemPython()
|
||||
|
||||
if sysVersion != "1.2.3" {
|
||||
t.Errorf("Expected system Python to be 1.2.3, found %s instead.", sysVersion)
|
||||
}
|
||||
|
||||
if sysPath != mockSystemPythonExecPath {
|
||||
t.Errorf("Expected system Python path to be %s, found %s instead.", mockSystemPythonExecPath, sysPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDetermineSelectedPythonVersionGetsUserDefinedVersion(t *testing.T) {
|
||||
defer SetupAndCleanupEnvironment(t)()
|
||||
|
||||
// Writing a mock user-defined state.
|
||||
mockState := State{GlobalVersion: "1.0.0"}
|
||||
statePath := GetStatePath("state.json")
|
||||
stateData, _ := json.Marshal(mockState)
|
||||
ioutil.WriteFile(statePath, stateData, 0750)
|
||||
|
||||
version, err := DetermineSelectedPythonVersion(ReadState())
|
||||
|
||||
if err != nil || version != mockState.GlobalVersion {
|
||||
t.Errorf("Expected version to be %s, got %s instead.", mockState.GlobalVersion, version)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDetermineSelectedPythonVersionDefaultsToSystem(t *testing.T) {
|
||||
defer SetupAndCleanupEnvironment(t)
|
||||
|
||||
version, err := DetermineSelectedPythonVersion(ReadState())
|
||||
|
||||
if err != nil || version != "SYSTEM" {
|
||||
t.Errorf("Expected version to be 'SYSTEM', got %s instead.", version)
|
||||
}
|
||||
}
|
13
util.go
13
util.go
|
@ -30,19 +30,28 @@ func ValidateVersion(version string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// RunCommand is a thin wrapper around running command-line calls
|
||||
// programmatically. It abstracts common configuration like routing
|
||||
// output and handling the directory the calls are made from.
|
||||
func RunCommand(command []string, cwd string, quiet bool) (string, error) {
|
||||
cmd := exec.Command(command[0], command[1:]...)
|
||||
|
||||
cmd.Dir = cwd
|
||||
|
||||
var out strings.Builder
|
||||
var errOut strings.Builder
|
||||
|
||||
if !quiet {
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
} else {
|
||||
cmd.Stdout = &out
|
||||
cmd.Stderr = &errOut
|
||||
}
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return "", err
|
||||
return errOut.String(), err
|
||||
}
|
||||
|
||||
return "", nil
|
||||
return out.String(), nil
|
||||
}
|
||||
|
|
2
v.go
2
v.go
|
@ -5,7 +5,7 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
Version = "0.0.4"
|
||||
Version = "0.0.5"
|
||||
Author = "Marc Cataford <hello@karnov.club>"
|
||||
Homepage = "https://github.com/mcataford/v"
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue