130 lines
3.3 KiB
Go
130 lines
3.3 KiB
Go
package python
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"path"
|
|
"strings"
|
|
"time"
|
|
exec "v/exec"
|
|
logger "v/logger"
|
|
state "v/state"
|
|
)
|
|
|
|
var pythonReleasesBaseURL = "https://www.python.org/ftp/python"
|
|
|
|
type PackageMetadata struct {
|
|
ArchivePath string
|
|
InstallPath string
|
|
Version string
|
|
}
|
|
|
|
type VersionTag struct {
|
|
Major string
|
|
Minor string
|
|
Patch string
|
|
}
|
|
|
|
func (t VersionTag) MajorMinor() string {
|
|
return t.Major + "." + t.Minor
|
|
}
|
|
|
|
func InstallPythonDistribution(version string, noCache bool) error {
|
|
if err := ValidateVersion(version); err != nil {
|
|
return err
|
|
}
|
|
|
|
packageMetadata, dlerr := downloadSource(version, noCache)
|
|
|
|
if dlerr != nil {
|
|
return dlerr
|
|
}
|
|
|
|
if _, err := buildFromSource(packageMetadata); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Fetches the Python tarball for version <version> from python.org.
|
|
func downloadSource(version string, skipCache bool) (PackageMetadata, error) {
|
|
archiveName := "Python-" + version + ".tgz"
|
|
archivePath := state.GetStatePath("cache", archiveName)
|
|
sourceUrl, _ := url.JoinPath(pythonReleasesBaseURL, version, archiveName)
|
|
|
|
client := http.Client{}
|
|
|
|
logger.InfoLogger.Println(logger.Bold("Downloading source for Python " + version))
|
|
logger.InfoLogger.SetPrefix(" ")
|
|
defer logger.InfoLogger.SetPrefix("")
|
|
|
|
start := time.Now()
|
|
|
|
if _, err := os.Stat(archivePath); errors.Is(err, os.ErrNotExist) || skipCache {
|
|
logger.InfoLogger.Println("Fetching from " + sourceUrl)
|
|
|
|
resp, err := client.Get(sourceUrl)
|
|
|
|
if err != nil {
|
|
return PackageMetadata{}, err
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
file, _ := os.Create(archivePath)
|
|
io.Copy(file, resp.Body)
|
|
|
|
defer file.Close()
|
|
} else {
|
|
logger.InfoLogger.Println("Found in cache: " + archivePath)
|
|
}
|
|
|
|
logger.InfoLogger.Printf("✅ Done (%s)\n", time.Since(start))
|
|
return PackageMetadata{ArchivePath: archivePath, Version: version}, nil
|
|
}
|
|
|
|
func buildFromSource(pkgMeta PackageMetadata) (PackageMetadata, error) {
|
|
logger.InfoLogger.Println(logger.Bold("Building from source"))
|
|
logger.InfoLogger.SetPrefix(" ")
|
|
defer logger.InfoLogger.SetPrefix("")
|
|
|
|
start := time.Now()
|
|
|
|
logger.InfoLogger.Println("Unpacking source for " + pkgMeta.ArchivePath)
|
|
|
|
if _, untarErr := exec.RunCommand([]string{"tar", "zxvf", pkgMeta.ArchivePath}, state.GetStatePath("cache")); untarErr != nil {
|
|
return pkgMeta, untarErr
|
|
}
|
|
|
|
unzippedRoot := strings.TrimSuffix(pkgMeta.ArchivePath, path.Ext(pkgMeta.ArchivePath))
|
|
|
|
logger.InfoLogger.Println("Configuring installer")
|
|
|
|
if _, err := os.Stat(state.GetStatePath("runtimes", "python")); os.IsNotExist(err) {
|
|
os.Mkdir(state.GetStatePath("runtimes", "python"), 0775)
|
|
}
|
|
|
|
targetDirectory := state.GetStatePath("runtimes", "python", pkgMeta.Version)
|
|
|
|
if _, configureErr := exec.RunCommand([]string{"./configure", "--prefix=" + targetDirectory, "--enable-optimizations"}, unzippedRoot); configureErr != nil {
|
|
return pkgMeta, configureErr
|
|
}
|
|
|
|
logger.InfoLogger.Println("Building")
|
|
|
|
if _, buildErr := exec.RunCommand([]string{"make", "altinstall", "-j4"}, unzippedRoot); buildErr != nil {
|
|
return pkgMeta, buildErr
|
|
}
|
|
|
|
if cleanupErr := os.RemoveAll(unzippedRoot); cleanupErr != nil {
|
|
return pkgMeta, cleanupErr
|
|
}
|
|
|
|
pkgMeta.InstallPath = targetDirectory
|
|
|
|
logger.InfoLogger.Printf("✅ Installed Python %s at %s (%s)\n", pkgMeta.Version, pkgMeta.InstallPath, time.Since(start))
|
|
return pkgMeta, nil
|
|
}
|