refactor(python): move python-specific logic to own module

This commit is contained in:
Marc 2024-01-25 23:09:52 -05:00
parent a5c7242045
commit c0d8d31ace
Signed by: marc
GPG key ID: 048E042F22B5DC79
8 changed files with 83 additions and 72 deletions

View file

@ -5,6 +5,7 @@ import (
"slices"
cli "v/cli"
logger "v/logger"
python "v/python"
state "v/state"
)
@ -58,12 +59,12 @@ func UninstallPython(args []string, flags cli.Flags, currentState state.State) e
func InstallPython(args []string, flags cli.Flags, currentState state.State) error {
version := args[1]
return InstallPythonDistribution(version, flags.NoCache, flags.Verbose)
return python.InstallPythonDistribution(version, flags.NoCache, flags.Verbose)
}
func Use(args []string, flags cli.Flags, currentState state.State) error {
version := args[1]
if err := ValidateVersion(version); err != nil {
if err := python.ValidateVersion(version); err != nil {
return err
}
@ -79,7 +80,7 @@ func Use(args []string, flags cli.Flags, currentState state.State) error {
if !found {
logger.InfoLogger.Println("Version not installed. Installing it first.")
InstallPythonDistribution(version, flags.NoCache, flags.Verbose)
python.InstallPythonDistribution(version, flags.NoCache, flags.Verbose)
}
state.WriteState(version)
@ -88,7 +89,7 @@ func Use(args []string, flags cli.Flags, currentState state.State) error {
return nil
}
func ListVersions(args []string, flags cli.Flags, currentState state.State) error {
installedVersions, err := ListInstalledVersions()
installedVersions, err := python.ListInstalledVersions()
if err != nil {
return err
@ -108,17 +109,17 @@ func ListVersions(args []string, flags cli.Flags, currentState state.State) erro
// Which prints out the system path to the executable being used by `python`.
func Which(args []string, flags cli.Flags, currentState state.State) error {
selectedVersion, _ := DetermineSelectedPythonVersion(currentState)
installedVersions, _ := ListInstalledVersions()
selectedVersion, _ := python.DetermineSelectedPythonVersion(currentState)
installedVersions, _ := python.ListInstalledVersions()
isInstalled := slices.Contains(installedVersions, selectedVersion.Version)
var printedPath string
if selectedVersion.Source == "system" {
_, sysPath := DetermineSystemPython()
_, sysPath := python.DetermineSystemPython()
printedPath = sysPath + " (system)"
} else if isInstalled {
tag := VersionStringToStruct(selectedVersion.Version)
tag := python.VersionStringToStruct(selectedVersion.Version)
printedPath = state.GetStatePath("runtimes", "py-"+selectedVersion.Version, "bin", "python"+tag.MajorMinor())
} else {
logger.InfoLogger.Printf("The desired version (%s) is not installed.\n", selectedVersion.Version)
@ -130,7 +131,7 @@ func Which(args []string, flags cli.Flags, currentState state.State) error {
if flags.RawOutput {
prefix = ""
} else {
printedPath = Bold(printedPath)
printedPath = logger.Bold(printedPath)
}
logger.InfoLogger.Printf("%s%s\n", prefix, printedPath)
@ -141,12 +142,12 @@ func Which(args []string, flags cli.Flags, currentState state.State) error {
// and what configures it. If the version is configured by a file, the file is returned
// under "source", if the system Python is used, "system" is returned as a source.
func CurrentVersion(args []string, flags cli.Flags, currentState state.State) error {
selectedVersion, _ := DetermineSelectedPythonVersion(currentState)
installedVersions, _ := ListInstalledVersions()
selectedVersion, _ := python.DetermineSelectedPythonVersion(currentState)
installedVersions, _ := python.ListInstalledVersions()
isInstalled := slices.Contains(installedVersions, selectedVersion.Version)
if !isInstalled {
logger.InfoLogger.Println(Bold(Yellow("WARNING: This version is not installed.")))
logger.InfoLogger.Println(logger.Bold(logger.Yellow("WARNING: This version is not installed.")))
}
if flags.RawOutput {
@ -154,6 +155,6 @@ func CurrentVersion(args []string, flags cli.Flags, currentState state.State) er
return nil
}
logger.InfoLogger.Printf("Python version: %s\nSource: %s\n", Bold(selectedVersion.Version), Bold(selectedVersion.Source))
logger.InfoLogger.Printf("Python version: %s\nSource: %s\n", logger.Bold(selectedVersion.Version), logger.Bold(selectedVersion.Source))
return nil
}

View file

@ -8,10 +8,11 @@ import (
cli "v/cli"
logger "v/logger"
state "v/state"
testutils "v/testutils"
)
func TestListVersionOutputsNoticeIfNoVersionsInstalled(t *testing.T) {
defer SetupAndCleanupEnvironment(t)()
defer testutils.SetupAndCleanupEnvironment(t)()
os.Mkdir(state.GetStatePath("runtimes"), 0750)
var out bytes.Buffer
@ -28,7 +29,7 @@ func TestListVersionOutputsNoticeIfNoVersionsInstalled(t *testing.T) {
}
func TestListVersionOutputsVersionsInstalled(t *testing.T) {
defer SetupAndCleanupEnvironment(t)()
defer testutils.SetupAndCleanupEnvironment(t)()
os.MkdirAll(state.GetStatePath("runtimes", "py-1.2.3"), 0750)
var out bytes.Buffer
@ -45,7 +46,7 @@ func TestListVersionOutputsVersionsInstalled(t *testing.T) {
}
func TestListVersionReturnsErrorOnFailure(t *testing.T) {
defer SetupAndCleanupEnvironment(t)()
defer testutils.SetupAndCleanupEnvironment(t)()
var out bytes.Buffer
@ -65,7 +66,7 @@ func TestListVersionReturnsErrorOnFailure(t *testing.T) {
}
func TestListVersionOutputsVersionSelectedAndWarnsNotInstalled(t *testing.T) {
defer SetupAndCleanupEnvironment(t)()
defer testutils.SetupAndCleanupEnvironment(t)()
var out bytes.Buffer
@ -81,7 +82,7 @@ func TestListVersionOutputsVersionSelectedAndWarnsNotInstalled(t *testing.T) {
}
func TestWhichOutputsVersionSelectedIfInstalled(t *testing.T) {
defer SetupAndCleanupEnvironment(t)()
defer testutils.SetupAndCleanupEnvironment(t)()
var out bytes.Buffer
@ -99,7 +100,7 @@ func TestWhichOutputsVersionSelectedIfInstalled(t *testing.T) {
}
func TestWhichOutputsSystemVersionIfNoneSelected(t *testing.T) {
defer SetupAndCleanupEnvironment(t)()
defer testutils.SetupAndCleanupEnvironment(t)()
var out bytes.Buffer
@ -116,7 +117,7 @@ func TestWhichOutputsSystemVersionIfNoneSelected(t *testing.T) {
}
func TestWhichOutputsVersionWithoutPrefixesIfRawOutput(t *testing.T) {
defer SetupAndCleanupEnvironment(t)()
defer testutils.SetupAndCleanupEnvironment(t)()
var out bytes.Buffer

View file

@ -1,26 +1,11 @@
package main
package exec
import (
"errors"
"os"
"os/exec"
"strings"
)
func VersionStringToStruct(version string) VersionTag {
splitVersion := strings.Split(version, ".")
return VersionTag{Major: splitVersion[0], Minor: splitVersion[1], Patch: splitVersion[2]}
}
func ValidateVersion(version string) error {
if splitVersion := strings.Split(version, "."); len(splitVersion) != 3 {
return errors.New("Invalid version string. Expected format 'a.b.c'.")
}
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.

View file

@ -1,4 +1,4 @@
package main
package logger
const (
RESET = "\033[0m"

View file

@ -1,4 +1,4 @@
package main
package python
import (
"errors"
@ -9,6 +9,7 @@ import (
"path"
"strings"
"time"
exec "v/exec"
logger "v/logger"
state "v/state"
)
@ -58,7 +59,7 @@ func downloadSource(version string, skipCache bool) (PackageMetadata, error) {
client := http.Client{}
logger.InfoLogger.Println(Bold("Downloading source for Python " + version))
logger.InfoLogger.Println(logger.Bold("Downloading source for Python " + version))
logger.InfoLogger.SetPrefix(" ")
defer logger.InfoLogger.SetPrefix("")
@ -88,7 +89,7 @@ func downloadSource(version string, skipCache bool) (PackageMetadata, error) {
}
func buildFromSource(pkgMeta PackageMetadata, verbose bool) (PackageMetadata, error) {
logger.InfoLogger.Println(Bold("Building from source"))
logger.InfoLogger.Println(logger.Bold("Building from source"))
logger.InfoLogger.SetPrefix(" ")
defer logger.InfoLogger.SetPrefix("")
@ -96,7 +97,7 @@ func buildFromSource(pkgMeta PackageMetadata, verbose bool) (PackageMetadata, er
logger.InfoLogger.Println("Unpacking source for " + pkgMeta.ArchivePath)
_, untarErr := RunCommand([]string{"tar", "zxvf", pkgMeta.ArchivePath}, state.GetStatePath("cache"), !verbose)
_, untarErr := exec.RunCommand([]string{"tar", "zxvf", pkgMeta.ArchivePath}, state.GetStatePath("cache"), !verbose)
if untarErr != nil {
return pkgMeta, untarErr
@ -108,14 +109,14 @@ func buildFromSource(pkgMeta PackageMetadata, verbose bool) (PackageMetadata, er
targetDirectory := state.GetStatePath("runtimes", "py-"+pkgMeta.Version)
_, configureErr := RunCommand([]string{"./configure", "--prefix=" + targetDirectory, "--enable-optimizations"}, unzippedRoot, !verbose)
_, configureErr := exec.RunCommand([]string{"./configure", "--prefix=" + targetDirectory, "--enable-optimizations"}, unzippedRoot, !verbose)
if configureErr != nil {
return pkgMeta, configureErr
}
logger.InfoLogger.Println("Building")
_, buildErr := RunCommand([]string{"make", "altinstall", "-j4"}, unzippedRoot, !verbose)
_, buildErr := exec.RunCommand([]string{"make", "altinstall", "-j4"}, unzippedRoot, !verbose)
if buildErr != nil {
return pkgMeta, buildErr

View file

@ -1,4 +1,4 @@
package main
package python
import (
"encoding/json"
@ -9,26 +9,11 @@ import (
"slices"
"testing"
state "v/state"
testutils "v/testutils"
)
// 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())
temporaryWd := t.TempDir()
os.Chdir(temporaryWd)
return func() {
os.Unsetenv("V_ROOT")
}
}
func TestDetermineSystemPythonGetsUnshimmedPythonRuntime(t *testing.T) {
defer SetupAndCleanupEnvironment(t)()
defer testutils.SetupAndCleanupEnvironment(t)()
ioutil.WriteFile(state.GetStatePath("shims", "python"), []byte("#!/bin/bash\necho \"Python 4.5.6\""), 0777)
mockSystemPythonPath := t.TempDir()
@ -49,7 +34,7 @@ func TestDetermineSystemPythonGetsUnshimmedPythonRuntime(t *testing.T) {
}
func TestDetermineSelectedPythonVersionUsesPythonVersionFileIfFound(t *testing.T) {
defer SetupAndCleanupEnvironment(t)()
defer testutils.SetupAndCleanupEnvironment(t)()
// Writing a mock user-defined state.
mockState := state.State{GlobalVersion: "1.0.0"}
@ -69,7 +54,7 @@ func TestDetermineSelectedPythonVersionUsesPythonVersionFileIfFound(t *testing.T
}
func TestDetermineSelectedPythonVersionGetsUserDefinedVersion(t *testing.T) {
defer SetupAndCleanupEnvironment(t)()
defer testutils.SetupAndCleanupEnvironment(t)()
// Writing a mock user-defined state.
mockState := state.State{GlobalVersion: "1.0.0"}
@ -85,7 +70,7 @@ func TestDetermineSelectedPythonVersionGetsUserDefinedVersion(t *testing.T) {
}
func TestDetermineSelectedPythonVersionDefaultsToSystem(t *testing.T) {
defer SetupAndCleanupEnvironment(t)()
defer testutils.SetupAndCleanupEnvironment(t)()
version, err := DetermineSelectedPythonVersion(state.ReadState())
@ -95,7 +80,7 @@ func TestDetermineSelectedPythonVersionDefaultsToSystem(t *testing.T) {
}
func TestSearchForPythonVersionFileFindsFileInCwd(t *testing.T) {
defer SetupAndCleanupEnvironment(t)()
defer testutils.SetupAndCleanupEnvironment(t)()
temporaryWd := t.TempDir()
os.Chdir(temporaryWd)
@ -109,7 +94,7 @@ func TestSearchForPythonVersionFileFindsFileInCwd(t *testing.T) {
}
func TestSearchForPythonVersionFileFindsFileInParents(t *testing.T) {
defer SetupAndCleanupEnvironment(t)()
defer testutils.SetupAndCleanupEnvironment(t)()
temporaryWd := t.TempDir()
@ -126,7 +111,7 @@ func TestSearchForPythonVersionFileFindsFileInParents(t *testing.T) {
}
func TestSearchForPythonVersionFileReturnsOnRootIfNoneFound(t *testing.T) {
defer SetupAndCleanupEnvironment(t)()
defer testutils.SetupAndCleanupEnvironment(t)()
versionFound, found := SearchForPythonVersionFile()
@ -136,7 +121,7 @@ func TestSearchForPythonVersionFileReturnsOnRootIfNoneFound(t *testing.T) {
}
func TestListInstalledVersion(t *testing.T) {
defer SetupAndCleanupEnvironment(t)()
defer testutils.SetupAndCleanupEnvironment(t)()
versions := []string{"1.2.3", "4.5.6", "7.8.9"}
@ -153,7 +138,7 @@ func TestListInstalledVersion(t *testing.T) {
}
func TestListInstalledVersionNoVersionsInstalled(t *testing.T) {
defer SetupAndCleanupEnvironment(t)()
defer testutils.SetupAndCleanupEnvironment(t)()
os.Mkdir(state.GetStatePath("runtimes"), 0750)
@ -165,7 +150,7 @@ func TestListInstalledVersionNoVersionsInstalled(t *testing.T) {
}
func TestListInstalledVersionNoRuntimesDir(t *testing.T) {
defer SetupAndCleanupEnvironment(t)()
defer testutils.SetupAndCleanupEnvironment(t)()
installedVersions, err := ListInstalledVersions()

View file

@ -1,13 +1,29 @@
package main
package python
import (
"errors"
"io/ioutil"
"os"
"path"
"strings"
exec "v/exec"
state "v/state"
)
func VersionStringToStruct(version string) VersionTag {
splitVersion := strings.Split(version, ".")
return VersionTag{Major: splitVersion[0], Minor: splitVersion[1], Patch: splitVersion[2]}
}
func ValidateVersion(version string) error {
if splitVersion := strings.Split(version, "."); len(splitVersion) != 3 {
return errors.New("Invalid version string. Expected format 'a.b.c'.")
}
return nil
}
type SelectedVersion struct {
Version string
Source string
@ -84,7 +100,7 @@ func DetermineSelectedPythonVersion(currentState state.State) (SelectedVersion,
// DetermineSystemPython returns the unshimmed Python version and path.
// It assumes that /bin/python is where system Python lives.
func DetermineSystemPython() (string, string) {
versionOut, _ := RunCommand([]string{"/bin/python", "--version"}, state.GetStatePath(), true)
versionOut, _ := exec.RunCommand([]string{"/bin/python", "--version"}, state.GetStatePath(), true)
detectedVersion, _ := strings.CutPrefix(versionOut, "Python")
return strings.TrimSpace(detectedVersion), "/bin/python"
}

22
testutils/setup.go Normal file
View file

@ -0,0 +1,22 @@
package testutils
import (
"os"
"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())
temporaryWd := t.TempDir()
os.Chdir(temporaryWd)
return func() {
os.Unsetenv("V_ROOT")
}
}