94 lines
2.7 KiB
Go
Raw Permalink Normal View History

2026-03-12 20:51:38 -07:00
package deploy
import (
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
)
// PipPackage holds the name and version of an installed pip package.
type PipPackage struct {
Name string `json:"name"`
Version string `json:"version"`
}
// CreateVenv creates a Python virtual environment at <dir>/venv.
func CreateVenv(dir string) error {
python, err := exec.LookPath("python3")
if err != nil {
return fmt.Errorf("python3 not found: %w", err)
}
venvPath := filepath.Join(dir, "venv")
out, err := exec.Command(python, "-m", "venv", venvPath).CombinedOutput()
if err != nil {
return fmt.Errorf("create venv: %s: %w", strings.TrimSpace(string(out)), err)
}
return nil
}
// UpgradePip upgrades pip, setuptools, and wheel inside the virtual environment
// rooted at venvDir.
func UpgradePip(venvDir string) error {
pip := filepath.Join(venvDir, "bin", "pip")
if _, err := os.Stat(pip); err != nil {
return fmt.Errorf("pip not found at %s: %w", pip, err)
}
out, err := exec.Command(pip, "install", "--upgrade", "pip", "setuptools", "wheel").CombinedOutput()
if err != nil {
return fmt.Errorf("upgrade pip: %s: %w", strings.TrimSpace(string(out)), err)
}
return nil
}
// InstallRequirements installs packages from a requirements file into the
// virtual environment rooted at venvDir.
func InstallRequirements(venvDir, reqFile string) (string, error) {
pip := filepath.Join(venvDir, "bin", "pip")
if _, err := os.Stat(pip); err != nil {
return "", fmt.Errorf("pip not found at %s: %w", pip, err)
}
if _, err := os.Stat(reqFile); err != nil {
return "", fmt.Errorf("requirements file not found: %w", err)
}
out, err := exec.Command(pip, "install", "-r", reqFile).CombinedOutput()
if err != nil {
return string(out), fmt.Errorf("pip install: %s: %w", strings.TrimSpace(string(out)), err)
}
return string(out), nil
}
// ListPackages returns all installed packages in the virtual environment
// rooted at venvDir.
func ListPackages(venvDir string) ([]PipPackage, error) {
pip := filepath.Join(venvDir, "bin", "pip")
if _, err := os.Stat(pip); err != nil {
return nil, fmt.Errorf("pip not found at %s: %w", pip, err)
}
out, err := exec.Command(pip, "list", "--format=json").Output()
if err != nil {
return nil, fmt.Errorf("pip list: %w", err)
}
var packages []PipPackage
if err := json.Unmarshal(out, &packages); err != nil {
return nil, fmt.Errorf("parse pip list output: %w", err)
}
return packages, nil
}
// VenvExists returns true if a virtual environment with a working python3
// binary exists at <dir>/venv.
func VenvExists(dir string) bool {
python := filepath.Join(dir, "venv", "bin", "python3")
info, err := os.Stat(python)
return err == nil && !info.IsDir()
}