Moved
This commit is contained in:
parent
5f4ad63266
commit
fb9f151979
4 changed files with 661 additions and 2 deletions
|
|
@ -1 +1 @@
|
||||||
crystal build src/mol.cr -o mol.exe --release
|
go build -o mol.exe mol.go
|
||||||
2
build.sh
2
build.sh
|
|
@ -1,2 +1,2 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
crystal build src/mol.cr -o mol --release
|
go build -o mol.exe mol.go
|
||||||
BIN
mol.exe
BIN
mol.exe
Binary file not shown.
659
mol.go
Normal file
659
mol.go
Normal file
|
|
@ -0,0 +1,659 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const version = "0.4.0-boredos"
|
||||||
|
|
||||||
|
var (
|
||||||
|
home = os.Getenv("HOME")
|
||||||
|
bin = filepath.Join(home, ".local", "bin")
|
||||||
|
src = ".mol/src"
|
||||||
|
db = ".mol/repo"
|
||||||
|
stage = ".mol/stage"
|
||||||
|
manifestDir = ".mol/manifests"
|
||||||
|
reposFile = ".mol/repos"
|
||||||
|
depCache = ".mol/depcache.json"
|
||||||
|
defaultRepo = envOr("MOL_REPO", "https://git.sr.ht/~chersbobers/mol-pkgrepo/blob/master/PACKAGES?raw=true")
|
||||||
|
goos = runtime.GOOS
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if goos == "windows" {
|
||||||
|
bin = filepath.Join(os.Getenv("USERPROFILE"), "AppData", "Local", "mol", "bin")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func envOr(key, def string) string {
|
||||||
|
if v := os.Getenv(key); v != "" {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
reset = "\033[0m"
|
||||||
|
bold = "\033[1m"
|
||||||
|
dim = "\033[2m"
|
||||||
|
green = "\033[32m"
|
||||||
|
yellow = "\033[33m"
|
||||||
|
cyan = "\033[36m"
|
||||||
|
red = "\033[31m"
|
||||||
|
)
|
||||||
|
|
||||||
|
func uiInfo(msg string) { fmt.Printf("%s →%s %s\n", cyan, reset, msg) }
|
||||||
|
func uiOk(msg string) { fmt.Printf("%s ✓%s %s%s%s\n", green, reset, bold, msg, reset) }
|
||||||
|
func uiWarn(msg string) { fmt.Printf("%s ⚠%s %s\n", yellow, reset, msg) }
|
||||||
|
func uiErr(msg string) { fmt.Fprintf(os.Stderr, "%s ✗%s %s%s%s\n", red, reset, bold, msg, reset) }
|
||||||
|
func uiDim(msg string) { fmt.Printf("%s %s%s\n", dim, msg, reset) }
|
||||||
|
func uiSection(msg string) { fmt.Printf("\n%s%s▸ %s%s\n", bold, cyan, msg, reset) }
|
||||||
|
|
||||||
|
func uiHeader() {
|
||||||
|
fmt.Printf("%s%smol%s %sv%s · GPLv3%s\n", bold, cyan, reset, dim, version, reset)
|
||||||
|
fmt.Printf("%s%s%s\n", dim, strings.Repeat("─", 42), reset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func uiProgress(label string, total int, fn func(int)) {
|
||||||
|
for i := 0; i < total; i++ {
|
||||||
|
pct := ((i + 1) * 100) / total
|
||||||
|
done := (30 * pct) / 100
|
||||||
|
bar := strings.Repeat("█", done) + strings.Repeat("░", 30-done)
|
||||||
|
fmt.Printf("\r %s%s%s %d%% %s%s%s ", cyan, bar, reset, pct, dim, label, reset)
|
||||||
|
fn(i)
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(cmd string, args []string, dir string) bool {
|
||||||
|
c := exec.Command(cmd, args...)
|
||||||
|
c.Dir = dir
|
||||||
|
c.Stdout = os.Stdout
|
||||||
|
c.Stderr = os.Stderr
|
||||||
|
return c.Run() == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func stageBegin(name string) string {
|
||||||
|
path := filepath.Join(stage, name)
|
||||||
|
os.RemoveAll(path)
|
||||||
|
os.MkdirAll(path, 0755)
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
func stageCommit(name, stagedPath string) {
|
||||||
|
manifestPath := filepath.Join(manifestDir, name+".list")
|
||||||
|
os.MkdirAll(manifestDir, 0755)
|
||||||
|
var written []string
|
||||||
|
|
||||||
|
filepath.Walk(stagedPath, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil || info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
rel, _ := filepath.Rel(stagedPath, path)
|
||||||
|
dest := filepath.Join("/", rel)
|
||||||
|
os.MkdirAll(filepath.Dir(dest), 0755)
|
||||||
|
copyFile(path, dest)
|
||||||
|
written = append(written, dest)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
os.WriteFile(manifestPath, []byte(strings.Join(written, "\n")), 0644)
|
||||||
|
os.RemoveAll(stagedPath)
|
||||||
|
uiOk(fmt.Sprintf("committed %d file(s) for %s", len(written), name))
|
||||||
|
}
|
||||||
|
|
||||||
|
func stageRollback(name string) {
|
||||||
|
manifestPath := filepath.Join(manifestDir, name+".list")
|
||||||
|
if _, err := os.Stat(manifestPath); os.IsNotExist(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uiWarn("rolling back " + name + "…")
|
||||||
|
f, _ := os.Open(manifestPath)
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
for scanner.Scan() {
|
||||||
|
path := strings.TrimSpace(scanner.Text())
|
||||||
|
if path == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
os.Remove(path)
|
||||||
|
uiDim("removed " + path)
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
os.Remove(manifestPath)
|
||||||
|
uiOk("rollback complete for " + name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasManifest(name string) bool {
|
||||||
|
_, err := os.Stat(filepath.Join(manifestDir, name+".list"))
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyFile(src, dst string) error {
|
||||||
|
in, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer in.Close()
|
||||||
|
out, err := os.Create(dst)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
_, err = io.Copy(out, in)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type graph map[string][]string
|
||||||
|
|
||||||
|
func loadGraph() graph {
|
||||||
|
g := make(graph)
|
||||||
|
data, err := os.ReadFile(depCache)
|
||||||
|
if err != nil {
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
json.Unmarshal(data, &g)
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveGraph(g graph) {
|
||||||
|
os.MkdirAll(filepath.Dir(depCache), 0755)
|
||||||
|
data, _ := json.Marshal(g)
|
||||||
|
os.WriteFile(depCache, data, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveGraph(root string, g graph) []string {
|
||||||
|
var order []string
|
||||||
|
visited := make(map[string]bool)
|
||||||
|
var visit func(string)
|
||||||
|
visit = func(name string) {
|
||||||
|
if visited[name] {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
visited[name] = true
|
||||||
|
for _, dep := range g[name] {
|
||||||
|
visit(dep)
|
||||||
|
}
|
||||||
|
order = append(order, name)
|
||||||
|
}
|
||||||
|
visit(root)
|
||||||
|
return order
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadRepos() []string {
|
||||||
|
repos := []string{defaultRepo}
|
||||||
|
f, err := os.Open(reposFile)
|
||||||
|
if err != nil {
|
||||||
|
return repos
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
for scanner.Scan() {
|
||||||
|
l := strings.TrimSpace(scanner.Text())
|
||||||
|
if l == "" || strings.HasPrefix(l, "#") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
found := false
|
||||||
|
for _, r := range repos {
|
||||||
|
if r == l {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
repos = append(repos, l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return repos
|
||||||
|
}
|
||||||
|
|
||||||
|
func addRepo(url string) {
|
||||||
|
os.MkdirAll(filepath.Dir(reposFile), 0755)
|
||||||
|
f, _ := os.OpenFile(reposFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||||
|
defer f.Close()
|
||||||
|
fmt.Fprintln(f, url)
|
||||||
|
uiOk("added repo: " + url)
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookup(t string) (string, string) {
|
||||||
|
if strings.Contains(t, "://") {
|
||||||
|
return t, ""
|
||||||
|
}
|
||||||
|
f, err := os.Open(db)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "%s not found — run: mol sync\n", t)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
for scanner.Scan() {
|
||||||
|
l := strings.TrimSpace(scanner.Text())
|
||||||
|
if l == "" || strings.HasPrefix(l, "#") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parts := strings.Fields(l)
|
||||||
|
if len(parts) < 2 || parts[0] != t {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
binURL := ""
|
||||||
|
for _, p := range parts {
|
||||||
|
if strings.HasPrefix(p, "B=") {
|
||||||
|
binURL = p[2:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parts[1], binURL
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "%s not found — run: mol sync\n", t)
|
||||||
|
os.Exit(1)
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func installed(name string) bool {
|
||||||
|
if hasManifest(name) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(filepath.Join(src, name)); err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(filepath.Join(bin, name)); err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func linkBins(path string) {
|
||||||
|
os.MkdirAll(bin, 0755)
|
||||||
|
candidates := []string{
|
||||||
|
filepath.Join(path, "bin"),
|
||||||
|
filepath.Join(path, "target", "release"),
|
||||||
|
path,
|
||||||
|
}
|
||||||
|
for _, d := range candidates {
|
||||||
|
if _, err := os.Stat(d); os.IsNotExist(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
entries, _ := os.ReadDir(d)
|
||||||
|
for _, e := range entries {
|
||||||
|
if e.IsDir() || strings.Contains(e.Name(), ".") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
full := filepath.Join(d, e.Name())
|
||||||
|
info, _ := e.Info()
|
||||||
|
if info.Mode()&0111 == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dest := filepath.Join(bin, e.Name())
|
||||||
|
copyFile(full, dest)
|
||||||
|
os.Chmod(dest, 0755)
|
||||||
|
uiInfo("linked → " + dest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseMolpkg(path string) ([]string, []string) {
|
||||||
|
var deps, build []string
|
||||||
|
file := filepath.Join(path, "mol.pkg")
|
||||||
|
f, err := os.Open(file)
|
||||||
|
if err != nil {
|
||||||
|
return deps, build
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
inBuild, inOs, inInstall := false, false, false
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
for scanner.Scan() {
|
||||||
|
l := strings.TrimSpace(scanner.Text())
|
||||||
|
switch {
|
||||||
|
case strings.Contains(l, "(deps"):
|
||||||
|
for _, m := range extractQuoted(l) {
|
||||||
|
deps = append(deps, m)
|
||||||
|
}
|
||||||
|
case strings.Contains(l, "(build"):
|
||||||
|
inBuild = true
|
||||||
|
case strings.Contains(l, "(install"):
|
||||||
|
inBuild, inInstall = false, true
|
||||||
|
case inBuild && strings.Contains(l, "("+goos):
|
||||||
|
inOs = true
|
||||||
|
case inBuild && inOs && strings.HasPrefix(l, "(run"):
|
||||||
|
parts := extractQuoted(l)
|
||||||
|
if len(parts) > 0 {
|
||||||
|
build = append(build, strings.Join(parts, " "))
|
||||||
|
}
|
||||||
|
case l == ")" && inOs:
|
||||||
|
inOs = false
|
||||||
|
case l == ")" && inBuild:
|
||||||
|
inBuild = false
|
||||||
|
case inInstall && strings.Contains(l, "(bin"):
|
||||||
|
quoted := extractQuoted(l)
|
||||||
|
if len(quoted) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name := quoted[0]
|
||||||
|
srcPath := filepath.Join(path, name)
|
||||||
|
if goos == "windows" {
|
||||||
|
if _, err := os.Stat(srcPath); os.IsNotExist(err) {
|
||||||
|
srcPath = srcPath + ".exe"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(srcPath); err == nil {
|
||||||
|
dest := filepath.Join(bin, filepath.Base(srcPath))
|
||||||
|
copyFile(srcPath, dest)
|
||||||
|
os.Chmod(dest, 0755)
|
||||||
|
uiInfo("linked → " + dest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return deps, build
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractQuoted(s string) []string {
|
||||||
|
var results []string
|
||||||
|
for {
|
||||||
|
start := strings.Index(s, `"`)
|
||||||
|
if start == -1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
end := strings.Index(s[start+1:], `"`)
|
||||||
|
if end == -1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
results = append(results, s[start+1:start+1+end])
|
||||||
|
s = s[start+1+end+1:]
|
||||||
|
}
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
func install(name string, fromSrc, fast bool) {
|
||||||
|
if installed(name) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uiSection("installing " + name)
|
||||||
|
|
||||||
|
url, binURL := lookup(name)
|
||||||
|
|
||||||
|
if (fast || !fromSrc) && binURL != "" {
|
||||||
|
uiInfo("fetching prebuilt binary…")
|
||||||
|
resp, err := http.Get(binURL)
|
||||||
|
if err != nil || resp.StatusCode != 200 {
|
||||||
|
fmt.Fprintln(os.Stderr, "download failed")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
os.MkdirAll(bin, 0755)
|
||||||
|
dest := filepath.Join(bin, filepath.Base(binURL))
|
||||||
|
data, _ := io.ReadAll(resp.Body)
|
||||||
|
os.WriteFile(dest, data, 0755)
|
||||||
|
uiOk(name + " installed (binary)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
n := strings.TrimSuffix(strings.TrimSuffix(filepath.Base(url), ".git"), ".hg")
|
||||||
|
clonePath := filepath.Join(src, n)
|
||||||
|
vcs := "git"
|
||||||
|
if strings.Contains(url, "hg") {
|
||||||
|
vcs = "hg"
|
||||||
|
}
|
||||||
|
uiInfo("cloning " + url + "…")
|
||||||
|
if !run(vcs, []string{"clone", url, clonePath}, "") {
|
||||||
|
fmt.Fprintln(os.Stderr, "clone failed")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
deps, buildCmds := parseMolpkg(clonePath)
|
||||||
|
|
||||||
|
g := loadGraph()
|
||||||
|
g[name] = deps
|
||||||
|
saveGraph(g)
|
||||||
|
|
||||||
|
order := resolveGraph(name, g)
|
||||||
|
for _, dep := range order {
|
||||||
|
if dep != name && !installed(dep) {
|
||||||
|
uiInfo("dependency: " + dep)
|
||||||
|
install(dep, fromSrc, fast)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(buildCmds) == 0 {
|
||||||
|
fmt.Fprintf(os.Stderr, "no build steps for %s in %s\n", goos, n)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
staged := stageBegin(name)
|
||||||
|
uiSection("building " + name)
|
||||||
|
failed := false
|
||||||
|
uiProgress("compiling", len(buildCmds), func(i int) {
|
||||||
|
parts := strings.Fields(buildCmds[i])
|
||||||
|
if !run(parts[0], parts[1:], clonePath) {
|
||||||
|
failed = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if failed {
|
||||||
|
stageRollback(name)
|
||||||
|
fmt.Fprintln(os.Stderr, "build failed")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
linkBins(clonePath)
|
||||||
|
stageCommit(name, staged)
|
||||||
|
uiOk(name + " installed from source")
|
||||||
|
}
|
||||||
|
|
||||||
|
func removePkg(name string) {
|
||||||
|
uiSection("removing " + name)
|
||||||
|
if hasManifest(name) {
|
||||||
|
stageRollback(name)
|
||||||
|
}
|
||||||
|
os.RemoveAll(filepath.Join(src, name))
|
||||||
|
os.Remove(filepath.Join(bin, name))
|
||||||
|
os.Remove(filepath.Join(bin, name+".exe"))
|
||||||
|
uiOk(name + " removed")
|
||||||
|
}
|
||||||
|
|
||||||
|
func updatePkg(name string, fast bool) {
|
||||||
|
clonePath := filepath.Join(src, name)
|
||||||
|
if fast {
|
||||||
|
uiInfo("fast update: reinstalling binary for " + name + "…")
|
||||||
|
removePkg(name)
|
||||||
|
install(name, false, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uiSection("updating " + name)
|
||||||
|
if _, err := os.Stat(clonePath); os.IsNotExist(err) {
|
||||||
|
fmt.Fprintln(os.Stderr, name+" not found, install first")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
vcs := "git"
|
||||||
|
if _, err := os.Stat(filepath.Join(clonePath, ".hg")); err == nil {
|
||||||
|
vcs = "hg"
|
||||||
|
}
|
||||||
|
args := []string{"pull"}
|
||||||
|
if vcs == "hg" {
|
||||||
|
args = []string{"pull", "-u"}
|
||||||
|
}
|
||||||
|
if !run(vcs, args, clonePath) {
|
||||||
|
fmt.Fprintln(os.Stderr, "pull failed")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
_, buildCmds := parseMolpkg(clonePath)
|
||||||
|
staged := stageBegin(name)
|
||||||
|
for _, cmd := range buildCmds {
|
||||||
|
parts := strings.Fields(cmd)
|
||||||
|
if !run(parts[0], parts[1:], clonePath) {
|
||||||
|
stageRollback(name)
|
||||||
|
fmt.Fprintln(os.Stderr, "build failed: "+cmd)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
linkBins(clonePath)
|
||||||
|
stageCommit(name, staged)
|
||||||
|
uiOk(name + " updated")
|
||||||
|
}
|
||||||
|
|
||||||
|
func syncAll() {
|
||||||
|
uiSection("syncing repos")
|
||||||
|
repos := loadRepos()
|
||||||
|
os.MkdirAll(filepath.Dir(db), 0755)
|
||||||
|
var combined strings.Builder
|
||||||
|
for i, repo := range repos {
|
||||||
|
uiInfo(fmt.Sprintf("(%d/%d) %s", i+1, len(repos), repo))
|
||||||
|
resp, err := http.Get(repo)
|
||||||
|
if err != nil || resp.StatusCode != 200 {
|
||||||
|
uiWarn(fmt.Sprintf("skipped: %s", repo))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
data, _ := io.ReadAll(resp.Body)
|
||||||
|
resp.Body.Close()
|
||||||
|
combined.Write(data)
|
||||||
|
combined.WriteString("\n")
|
||||||
|
}
|
||||||
|
os.WriteFile(db, []byte(combined.String()), 0644)
|
||||||
|
uiOk(fmt.Sprintf("synced %d repo(s)", len(repos)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func bootstrap(recipePath string) {
|
||||||
|
uiSection("bootstrapping BoredOS from " + recipePath)
|
||||||
|
if _, err := os.Stat(recipePath); os.IsNotExist(err) {
|
||||||
|
fmt.Fprintln(os.Stderr, "recipe not found: "+recipePath)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
deps, buildCmds := parseMolpkg(filepath.Dir(recipePath))
|
||||||
|
uiInfo("installing bootstrap deps…")
|
||||||
|
for _, d := range deps {
|
||||||
|
install(d, false, false)
|
||||||
|
}
|
||||||
|
uiInfo("running bootstrap build steps…")
|
||||||
|
for _, cmd := range buildCmds {
|
||||||
|
parts := strings.Fields(cmd)
|
||||||
|
uiDim("$ " + cmd)
|
||||||
|
if !run(parts[0], parts[1:], "") {
|
||||||
|
fmt.Fprintln(os.Stderr, "bootstrap step failed: "+cmd)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uiOk("bootstrap complete")
|
||||||
|
}
|
||||||
|
|
||||||
|
func contains(args []string, flag string) bool {
|
||||||
|
for _, a := range args {
|
||||||
|
if a == flag {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
os.MkdirAll(src, 0755)
|
||||||
|
os.MkdirAll(stage, 0755)
|
||||||
|
os.MkdirAll(manifestDir, 0755)
|
||||||
|
os.MkdirAll(filepath.Dir(db), 0755)
|
||||||
|
|
||||||
|
args := os.Args[1:]
|
||||||
|
cmd := ""
|
||||||
|
if len(args) > 0 {
|
||||||
|
cmd = args[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
switch cmd {
|
||||||
|
case "install":
|
||||||
|
if len(args) < 2 {
|
||||||
|
fmt.Fprintln(os.Stderr, "usage: mol install <name> [--source] [-f]")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
install(args[1], contains(args, "--source"), contains(args, "-f"))
|
||||||
|
|
||||||
|
case "remove":
|
||||||
|
if len(args) < 2 {
|
||||||
|
fmt.Fprintln(os.Stderr, "usage: mol remove <name>")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
removePkg(args[1])
|
||||||
|
|
||||||
|
case "update":
|
||||||
|
if len(args) < 2 {
|
||||||
|
fmt.Fprintln(os.Stderr, "usage: mol update <name> [-f]")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
updatePkg(args[1], contains(args, "-f"))
|
||||||
|
|
||||||
|
case "sync":
|
||||||
|
syncAll()
|
||||||
|
|
||||||
|
case "repo":
|
||||||
|
if len(args) < 2 {
|
||||||
|
fmt.Fprintln(os.Stderr, "usage: mol repo <add|list>")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
switch args[1] {
|
||||||
|
case "add":
|
||||||
|
if len(args) < 3 {
|
||||||
|
fmt.Fprintln(os.Stderr, "usage: mol repo add <url>")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
addRepo(args[2])
|
||||||
|
case "list":
|
||||||
|
for i, r := range loadRepos() {
|
||||||
|
fmt.Printf(" %d. %s\n", i+1, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case "bootstrap":
|
||||||
|
if len(args) < 2 {
|
||||||
|
fmt.Fprintln(os.Stderr, "usage: mol bootstrap <mol.pkg path>")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
bootstrap(args[1])
|
||||||
|
|
||||||
|
case "-l":
|
||||||
|
if _, err := os.Stat(db); os.IsNotExist(err) {
|
||||||
|
fmt.Fprintln(os.Stderr, "no repo — run: mol sync")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
f, _ := os.Open(db)
|
||||||
|
defer f.Close()
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
for scanner.Scan() {
|
||||||
|
l := strings.TrimSpace(scanner.Text())
|
||||||
|
if l == "" || strings.HasPrefix(l, "#") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Println(strings.Fields(l)[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
case "--version", "-v":
|
||||||
|
uiHeader()
|
||||||
|
fmt.Printf(" OS : %s\n", goos)
|
||||||
|
fmt.Printf(" arch : %s\n", runtime.GOARCH)
|
||||||
|
|
||||||
|
default:
|
||||||
|
uiHeader()
|
||||||
|
fmt.Printf(`
|
||||||
|
%sUsage:%s mol <command> [options]
|
||||||
|
|
||||||
|
%sPackage commands:%s
|
||||||
|
install <name> [--source] [-f] install a package
|
||||||
|
remove <name> remove a package
|
||||||
|
update <name> [-f] pull, rebuild, re-link
|
||||||
|
sync fetch all configured repos
|
||||||
|
|
||||||
|
%sRepo commands:%s
|
||||||
|
repo add <url> register an external repo
|
||||||
|
repo list show configured repos
|
||||||
|
|
||||||
|
%sBoredOS tools:%s
|
||||||
|
bootstrap <mol.pkg> build BoredOS from a host recipe
|
||||||
|
|
||||||
|
%sInfo:%s
|
||||||
|
-l list available packages
|
||||||
|
--version show version and platform info
|
||||||
|
|
||||||
|
`, bold, reset, cyan, reset, cyan, reset, cyan, reset, cyan, reset)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in a new issue