diff --git a/bpm_package/bpm.bpm b/bpm_package/bpm.bpm index 2f32b1f..b20728a 100644 Binary files a/bpm_package/bpm.bpm and b/bpm_package/bpm.bpm differ diff --git a/bpm_package/files/usr/bin/bpm b/bpm_package/files/usr/bin/bpm index 750f537..4b0ecef 100755 Binary files a/bpm_package/files/usr/bin/bpm and b/bpm_package/files/usr/bin/bpm differ diff --git a/bpm_package/pkg.info b/bpm_package/pkg.info index d52d8b3..9617a31 100644 --- a/bpm_package/pkg.info +++ b/bpm_package/pkg.info @@ -1,4 +1,4 @@ name: bpm description: The Bubble Package Manager -version: 0.0.1 +version: 0.0.3 type: binary diff --git a/main.go b/main.go index 95d4a4c..dcce01c 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os" + "slices" "strings" ) @@ -13,18 +14,12 @@ import ( /* A simple-to-use package manager */ /* ---------------------------------- */ -var bpmVer = "0.0.1" +var bpmVer = "0.0.3" var rootDir string = "/" func main() { - fmt.Printf("Running %s %s\n", getKernel(), getArch()) + //fmt.Printf("Running %s %s\n", getKernel(), getArch()) resolveCommand() - /*_, err := readPackage("test_hello_package/hello.bpm") - err := installPackage("test_hello_package/hello.bpm", rootDir) - if err != nil { - log.Fatalf("Could not read package\nError: %s\n", err) - } - removePackage("hello")*/ } func getArgs() []string { @@ -64,15 +59,18 @@ func getCommandType() commandType { default: return help } + } func resolveCommand() { switch getCommandType() { case version: + resolveFlags() fmt.Println("Bubble Package Manager (BPM)") fmt.Println("Version: " + bpmVer) case info: - packages := getArgs()[1:] + _, i := resolveFlags() + packages := getArgs()[1+i:] if len(packages) == 0 { fmt.Println("No packages were given") return @@ -83,13 +81,36 @@ func resolveCommand() { fmt.Printf("Package (%s) could not be found\n", pkg) continue } - fmt.Print("---------------\n" + createInfoFile(*info)) + fmt.Print("----------------\n" + createInfoFile(*info)) if n == len(packages)-1 { - fmt.Println("---------------") + fmt.Println("----------------") + } + } + case list: + resolveFlags() + packages, err := getInstalledPackages() + if err != nil { + log.Fatalf("Could not get installed packages\nError: %s", err.Error()) + return + } + if len(packages) == 0 { + fmt.Println("No packages have been installed") + return + } + for n, pkg := range packages { + info := getPackageInfo(pkg) + if info == nil { + fmt.Printf("Package (%s) could not be found\n", pkg) + continue + } + fmt.Print("----------------\n" + createInfoFile(*info)) + if n == len(packages)-1 { + fmt.Println("----------------") } } case install: - files := getArgs()[1:] + flags, i := resolveFlags() + files := getArgs()[1+i:] if len(files) == 0 { fmt.Println("No files were given to install") return @@ -99,15 +120,42 @@ func resolveCommand() { if err != nil { log.Fatalf("Could not read package\nError: %s\n", err) } - fmt.Print("---------------\n" + createInfoFile(*pkgInfo)) - fmt.Println("---------------") - reader := bufio.NewReader(os.Stdin) - fmt.Print("Do you wish to install this package? [y\\N] ") - text, _ := reader.ReadString('\n') - if strings.TrimSpace(strings.ToLower(text)) != "y" && strings.TrimSpace(strings.ToLower(text)) != "yes" { - fmt.Printf("Skipping package (%s)...\n", pkgInfo.name) - continue + fmt.Print("----------------\n" + createInfoFile(*pkgInfo)) + fmt.Println("----------------") + if isPackageInstalled(pkgInfo.name) { + if !slices.Contains(flags, "y") { + installedInfo := getPackageInfo(pkgInfo.name) + if strings.Compare(pkgInfo.version, installedInfo.version) > 0 { + fmt.Println("This file contains a newer version of this package (" + installedInfo.version + " -> " + pkgInfo.version + ")") + fmt.Print("Do you wish to update this package? [y\\N] ") + } else if strings.Compare(pkgInfo.version, installedInfo.version) < 0 { + fmt.Println("This file contains an older version of this package (" + installedInfo.version + " -> " + pkgInfo.version + ")") + fmt.Print("Do you wish to downgrade this package? (Not recommended) [y\\N] ") + } else if strings.Compare(pkgInfo.version, installedInfo.version) == 0 { + fmt.Println("This package is already installed on the system and is up to date") + fmt.Print("Do you wish to reinstall this package? [y\\N] ") + } + reader := bufio.NewReader(os.Stdin) + text, _ := reader.ReadString('\n') + if strings.TrimSpace(strings.ToLower(text)) != "y" && strings.TrimSpace(strings.ToLower(text)) != "yes" { + fmt.Printf("Skipping package (%s)...\n", pkgInfo.name) + continue + } + } + err := removePackage(pkgInfo.name) + if err != nil { + log.Fatalf("Could not remove current version of the package\nError: %s\n", err) + } + } else if !slices.Contains(flags, "y") { + fmt.Print("Do you wish to install this package? [y\\N] ") + reader := bufio.NewReader(os.Stdin) + text, _ := reader.ReadString('\n') + if strings.TrimSpace(strings.ToLower(text)) != "y" && strings.TrimSpace(strings.ToLower(text)) != "yes" { + fmt.Printf("Skipping package (%s)...\n", pkgInfo.name) + continue + } } + err = installPackage(file, rootDir) if err != nil { log.Fatalf("Could not install package\nError: %s\n", err) @@ -115,7 +163,8 @@ func resolveCommand() { fmt.Printf("Package (%s) was successfully installed!\n", pkgInfo.name) } case remove: - packages := getArgs()[1:] + flags, i := resolveFlags() + packages := getArgs()[1+i:] if len(packages) == 0 { fmt.Println("No packages were given") return @@ -126,14 +175,16 @@ func resolveCommand() { fmt.Printf("Package (%s) could not be found\n", pkg) continue } - fmt.Print("---------------\n" + createInfoFile(*pkgInfo)) - fmt.Println("---------------") - reader := bufio.NewReader(os.Stdin) - fmt.Print("Do you wish to remove this package? [y\\N] ") - text, _ := reader.ReadString('\n') - if strings.TrimSpace(strings.ToLower(text)) != "y" && strings.TrimSpace(strings.ToLower(text)) != "yes" { - fmt.Printf("Skipping package (%s)...\n", pkgInfo.name) - continue + fmt.Print("----------------\n" + createInfoFile(*pkgInfo)) + fmt.Println("----------------") + if !slices.Contains(flags, "y") { + reader := bufio.NewReader(os.Stdin) + fmt.Print("Do you wish to remove this package? [y\\N] ") + text, _ := reader.ReadString('\n') + if strings.TrimSpace(strings.ToLower(text)) != "y" && strings.TrimSpace(strings.ToLower(text)) != "yes" { + fmt.Printf("Skipping package (%s)...\n", pkgInfo.name) + continue + } } err := removePackage(pkg) if err != nil { @@ -142,6 +193,48 @@ func resolveCommand() { fmt.Printf("Package (%s) was successfully removed!\n", pkgInfo.name) } default: - fmt.Println("help...") + fmt.Println("------Help------") + fmt.Println("bpm version | shows information on the installed version of bpm") + fmt.Println("bpm info | shows information on an installed package") + fmt.Println("bpm list [-e] | lists all installed packages. -e lists only explicitly installed packages") + fmt.Println("bpm install [-y] | installs the following files. -y skips the confirmation prompt") + fmt.Println("bpm remove [-y] | removes the following packages. -y skips the confirmation prompt") + fmt.Println("bpm cleanup | removes all unneeded dependencies") + fmt.Println("----------------") } } + +func resolveFlags() ([]string, int) { + flags := getArgs()[1:] + var ret []string + for _, flag := range flags { + if strings.HasPrefix(flag, "-") { + f := strings.TrimPrefix(flag, "-") + switch getCommandType() { + default: + log.Fatalf("Invalid flag " + flag) + case list: + v := [...]string{"e"} + if !slices.Contains(v[:], f) { + log.Fatalf("Invalid flag " + flag) + } + ret = append(ret, f) + case install: + v := [...]string{"y"} + if !slices.Contains(v[:], f) { + log.Fatalf("Invalid flag " + flag) + } + ret = append(ret, f) + case remove: + v := [...]string{"y"} + if !slices.Contains(v[:], f) { + log.Fatalf("Invalid flag " + flag) + } + ret = append(ret, f) + } + } else { + break + } + } + return ret, len(ret) +} diff --git a/package_utils.go b/package_utils.go index 2f00654..08f3119 100644 --- a/package_utils.go +++ b/package_utils.go @@ -206,6 +206,15 @@ func installPackage(filename, installDir string) error { return nil } +func isPackageInstalled(pkg string) bool { + dataDir := path.Join(rootDir, "var/lib/bpm/installed/") + pkgDir := path.Join(dataDir, pkg) + if _, err := os.Stat(pkgDir); err != nil { + return false + } + return true +} + func getInstalledPackages() ([]string, error) { dataDir := path.Join(rootDir, "var/lib/bpm/installed/") items, err := os.ReadDir(dataDir) @@ -276,6 +285,9 @@ func removePackage(pkg string) error { for _, file := range files { file = path.Join(rootDir, file) stat, err := os.Stat(file) + if os.IsNotExist(err) { + continue + } if err != nil { return err } diff --git a/test_hello_package/hello.bpm b/test_hello_package/hello.bpm index a779dd4..94bd298 100644 Binary files a/test_hello_package/hello.bpm and b/test_hello_package/hello.bpm differ diff --git a/test_hello_package/pkg.info b/test_hello_package/pkg.info index 34646ad..de1bc65 100644 --- a/test_hello_package/pkg.info +++ b/test_hello_package/pkg.info @@ -1,4 +1,4 @@ name: hello description: A simple hello world program -version: 1.0 +version: 1.0-1 type: binary \ No newline at end of file diff --git a/version.go b/version.go new file mode 100644 index 0000000..6c5832f --- /dev/null +++ b/version.go @@ -0,0 +1,118 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +// Package cmpver implements a variant of debian version number +// comparison. +// +// A version is a string consisting of alternating non-numeric and +// numeric fields. When comparing two versions, each one is broken +// down into its respective fields, and the fields are compared +// pairwise. The comparison is lexicographic for non-numeric fields, +// numeric for numeric fields. The first non-equal field pair +// determines the ordering of the two versions. +// +// This comparison scheme is a simplified version of Debian's version +// number comparisons. Debian differs in a few details of +// lexicographical field comparison, where certain characters have +// special meaning and ordering. We don't need that, because Tailscale +// version numbers don't need it. + +package main + +import ( + "fmt" + "strconv" + "strings" +) + +// Less reports whether v1 is less than v2. +// +// Note that "12" is less than "12.0". +func Less(v1, v2 string) bool { + return Compare(v1, v2) < 0 +} + +// LessEq reports whether v1 is less than or equal to v2. +// +// Note that "12" is less than "12.0". +func LessEq(v1, v2 string) bool { + return Compare(v1, v2) <= 0 +} + +func isnum(r rune) bool { + return r >= '0' && r <= '9' +} + +func notnum(r rune) bool { + return !isnum(r) +} + +// Compare returns an integer comparing two strings as version numbers. +// The result will be -1, 0, or 1 representing the sign of v1 - v2: +// +// Compare(v1, v2) < 0 if v1 < v2 +// == 0 if v1 == v2 +// > 0 if v1 > v2 +func Compare(v1, v2 string) int { + var ( + f1, f2 string + n1, n2 uint64 + err error + ) + for v1 != "" || v2 != "" { + // Compare the non-numeric character run lexicographically. + f1, v1 = splitPrefixFunc(v1, notnum) + f2, v2 = splitPrefixFunc(v2, notnum) + + if res := strings.Compare(f1, f2); res != 0 { + return res + } + + // Compare the numeric character run numerically. + f1, v1 = splitPrefixFunc(v1, isnum) + f2, v2 = splitPrefixFunc(v2, isnum) + + // ParseUint refuses to parse empty strings, which would only + // happen if we reached end-of-string. We follow the Debian + // convention that empty strings mean zero, because + // empirically that produces reasonable-feeling comparison + // behavior. + n1 = 0 + if f1 != "" { + n1, err = strconv.ParseUint(f1, 10, 64) + if err != nil { + panic(fmt.Sprintf("all-number string %q didn't parse as string: %s", f1, err)) + } + } + + n2 = 0 + if f2 != "" { + n2, err = strconv.ParseUint(f2, 10, 64) + if err != nil { + panic(fmt.Sprintf("all-number string %q didn't parse as string: %s", f2, err)) + } + } + + switch { + case n1 == n2: + case n1 < n2: + return -1 + case n1 > n2: + return 1 + } + } + + // Only way to reach here is if v1 and v2 run out of fields + // simultaneously - i.e. exactly equal versions. + return 0 +} + +// splitPrefixFunc splits s at the first rune where f(rune) is false. +func splitPrefixFunc(s string, f func(rune) bool) (string, string) { + for i, r := range s { + if !f(r) { + return s[:i], s[i:] + } + } + return s, s[:0] +}