30 Commits
Author SHA1 Message Date
EnumDev c427499fb6 Merge branch 'remote-repositories-functionality' into 'master'
Finalize remote repositories functionality

See merge request bubble-package-manager/bpm!6
2024-09-14 15:58:43 +00:00
EnumDev 108e355e94 Standardized error message formatting 2024-09-14 16:09:20 +03:00
EnumDev 7b491874eb Small fixes 2024-09-14 12:27:14 +03:00
EnumDev 82d3c8bd51 Added package revision numbers 2024-09-11 13:11:35 +03:00
EnumDev 7a489af220 Added fetch status messages while installing or updating 2024-09-11 12:54:42 +03:00
EnumDev 6d9157e878 Merge branch 'remote-repositories-functionality' into 'master'
Simplified 'install' subcommand and fixed a few minor bugs related to installing local packages

See merge request bubble-package-manager/bpm!5
2024-09-10 08:54:40 +00:00
EnumDev fd6ddbfc41 Simplified 'install' subcommand and fixed a few minor bugs related to installing local packages 2024-09-10 11:53:16 +03:00
EnumDev 368b098888 Merge branch 'remote-repositories-functionality' into 'master'
Add repository functionality to BPM

See merge request bubble-package-manager/bpm!4
2024-09-09 08:45:45 +00:00
EnumDev 5e2fc138e9 Merge branch 'develop' into 'master'
Switch to yaml and preparation for repository functionality

See merge request bubble-package-manager/bpm!3
2024-09-09 08:40:27 +00:00
EnumDev bc489ebd23 Added 'update' subcommand and small fix to the 'install' subcommand 2024-09-09 11:33:48 +03:00
EnumDev 6247c6eff7 Added 'search' subcommand and removed repository functionality from 'info' subcommand 2024-09-08 12:51:47 +03:00
EnumDev c24b7c85e3 Improved dependency resolution and improved the 'install' subcommand 2024-09-08 11:50:16 +03:00
EnumDev 2fd01a3fc2 Fixed issue where ResolveAll would not resolve make dependencies and optional dependencies and removed conditional dependencies 2024-08-31 11:40:32 +03:00
EnumDev 747c770499 minor improvements to dependency resolution 2024-08-31 09:11:19 +03:00
EnumDev 26500d670d 'bpm info' will now exit with exit code 1 when package can't be found 2024-08-29 18:29:46 +03:00
EnumDev 59df2324e6 Added basic remote repository functionality to the install subcommand 2024-08-29 16:52:29 +03:00
EnumDev 12d5e7580e Disabled repositories will now be removed from the Repositories slice immediately 2024-08-28 10:58:49 +03:00
EnumDev 123697e1dc Added basic remote repository functionality 2024-08-28 10:34:27 +03:00
EnumDev 743918702a Improved dependency resolution to account for provided packages 2024-08-27 15:29:42 +03:00
EnumDev 7d2caa542c Renamed bpm_utils to utils to avoid confusion with the bpm package creation utilities 2024-08-27 15:22:51 +03:00
EnumDev ab75193022 Added installation reason and improved console output readability 2024-08-27 11:07:53 +03:00
EnumDev 7d577a8dc2 Improved 'keep' files/directory code and fixed said files from being removed as obsolete 2024-08-26 21:59:59 +03:00
EnumDev c85c9b5d1c Switched to using yaml for package metadata and added verbose flag 2024-08-26 20:51:21 +03:00
CapCreeperGR 721414679d Merge branch 'develop' into 'master'
Prevent direct split-pakcage installation and hide bpm file information when using -y

See merge request bubble-package-manager/bpm!2
2024-07-12 16:04:04 +00:00
CapCreeperGR c20e74203c BPM will throw an error when attempting to install a split source package 2024-07-12 18:20:51 +03:00
CapCreeperGR 23f9a13939 Package information will no longer show up if the -y flag is enabled 2024-07-12 16:22:27 +03:00
CapCreeperGR 1d41236740 Merge branch 'develop' into 'master'
Added Makefile and changed module name

See merge request bubble-package-manager/bpm!1
2024-07-10 06:59:31 +00:00
CapCreeperGR e445863dca Added Makefile and changed module name 2024-07-10 09:48:42 +03:00
CapCreeperGR f9ef17cd66 Added 'Binary Output' and 'Compilation Directory' config options and parameters, removed test packages and added other small improvements 2024-07-07 22:02:57 +03:00
EnumDev 124802ecc1 Simplified CheckDependencies and CheckMakeDependencies functions 2024-07-01 13:06:26 +03:00
19 changed files with 1088 additions and 411 deletions
+38
View File
@@ -0,0 +1,38 @@
ifeq ($(PREFIX),)
PREFIX := /usr/local
endif
ifeq ($(BINDIR),)
BINDIR := $(PREFIX)/bin
endif
ifeq ($(SYSCONFDIR),)
SYSCONFDIR := $(PREFIX)/etc
endif
ifeq ($(GO),)
GO := $(shell type -a -P go | head -n 1)
endif
build:
mkdir -p build
$(GO) build -ldflags "-w" -o build/bpm gitlab.com/bubble-package-manager/bpm
install: build/bpm config/
mkdir -p $(DESTDIR)$(BINDIR)
mkdir -p $(DESTDIR)$(SYSCONFDIR)
cp build/bpm $(DESTDIR)$(BINDIR)/bpm
cp config/bpm.conf $(DESTDIR)$(SYSCONFDIR)/bpm.conf
compress: build/bpm config/
mkdir -p bpm/$(BINDIR)
mkdir -p bpm/$(SYSCONFDIR)
cp build/bpm bpm/$(BINDIR)/bpm
cp config/bpm.conf bpm/$(SYSCONFDIR)/bpm.conf
tar --owner=root --group=root -czf bpm.tar.gz bpm
rm -r bpm
run: build/bpm
build/bpm
clean:
rm -r build/
.PHONY: build
+9 -8
View File
@@ -15,15 +15,16 @@ BPM is still in very early development. It should not be installed on any system
## Build from source ## Build from source
BPM requires go 1.22 or above to be built properly - Download `go` from your package manager or from the go website
- Download `make` from your package manager
```sh - Run the following command to compile the project
git clone https://gitlab.com/bubble-package-manager/bpm.git ```
cd bpm make
mkdir build ```
go build -o ./build/bpm capcreepergr.me/bpm - Run the following command to install stormfetch into your system. You may also append a DESTDIR variable at the end of this line if you wish to install in a different location
```
make install PREFIX=/usr SYSCONFDIR=/etc
``` ```
You are now able to copy the executable in the ./build directory in a VM or container's /usr/bin/ directory
## How to use ## How to use
-29
View File
@@ -1,29 +0,0 @@
package bpm_utils
import (
"gopkg.in/yaml.v3"
"os"
)
type BPMConfigStruct struct {
CompilationEnv []string `yaml:"compilation_env"`
SilentCompilation bool `yaml:"silent_compilation"`
}
var BPMConfig BPMConfigStruct = BPMConfigStruct{
CompilationEnv: make([]string, 0),
SilentCompilation: false}
func ReadConfig() {
if _, err := os.Stat("/etc/bpm.conf"); os.IsNotExist(err) {
return
}
bytes, err := os.ReadFile("/etc/bpm.conf")
if err != nil {
return
}
err = yaml.Unmarshal(bytes, &BPMConfig)
if err != nil {
return
}
}
+8
View File
@@ -0,0 +1,8 @@
compilation_env: []
silent_compilation: false
compilation_dir: "/var/tmp/"
binary_output_dir: "/var/lib/bpm/compiled/"
repositories:
- name: example-repository
source: https://my-repo.xyz/
disabled: true
+5 -2
View File
@@ -1,5 +1,8 @@
module capcreepergr.me/bpm module gitlab.com/bubble-package-manager/bpm
go 1.22 go 1.22
require gopkg.in/yaml.v3 v3.0.1 // indirect require (
github.com/elliotchance/orderedmap/v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+2
View File
@@ -1,3 +1,5 @@
github.com/elliotchance/orderedmap/v2 v2.4.0 h1:6tUmMwD9F998FNpwFxA5E6NQvSpk2PVw7RKsVq3+2Cw=
github.com/elliotchance/orderedmap/v2 v2.4.0/go.mod h1:85lZyVbpGaGvHvnKa7Qhx7zncAdBIBq6u56Hb1PRU5Q=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+476 -111
View File
@@ -2,9 +2,10 @@ package main
import ( import (
"bufio" "bufio"
"capcreepergr.me/bpm/bpm_utils"
"flag" "flag"
"fmt" "fmt"
"github.com/elliotchance/orderedmap/v2"
"gitlab.com/bubble-package-manager/bpm/utils"
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
@@ -17,23 +18,28 @@ import (
/* A simple-to-use package manager */ /* A simple-to-use package manager */
/* ---------------------------------- */ /* ---------------------------------- */
var bpmVer = "0.3.0" var bpmVer = "0.4.1"
var subcommand = "help" var subcommand = "help"
var subcommandArgs []string var subcommandArgs []string
// Flags // Flags
var rootDir = "/" var rootDir = "/"
var verbose = false
var yesAll = false var yesAll = false
var buildSource = false var buildSource = false
var skipCheck = false var skipCheck = false
var keepTempDir = false var keepTempDir = false
var forceInstall = false var force = false
var pkgListNumbers = false var pkgListNumbers = false
var pkgListNames = false var pkgListNames = false
var reinstall = false
var reinstallAll = false
var noOptional = false
var nosync = true
func main() { func main() {
bpm_utils.ReadConfig() utils.ReadConfig()
resolveFlags() resolveFlags()
resolveCommand() resolveCommand()
} }
@@ -45,7 +51,10 @@ const (
version version
info info
list list
search
install install
update
sync
remove remove
file file
) )
@@ -58,8 +67,14 @@ func getCommandType() commandType {
return info return info
case "list": case "list":
return list return list
case "search":
return search
case "install": case "install":
return install return install
case "update":
return update
case "sync":
return sync
case "remove": case "remove":
return remove return remove
case "file": case "file":
@@ -81,20 +96,21 @@ func resolveCommand() {
return return
} }
for n, pkg := range packages { for n, pkg := range packages {
info := bpm_utils.GetPackageInfo(pkg, rootDir, false) var info *utils.PackageInfo
info = utils.GetPackageInfo(pkg, rootDir, false)
if info == nil { if info == nil {
fmt.Printf("Package (%s) could not be found\n", pkg) log.Fatalf("Error: package (%s) is not installed\n", pkg)
continue
} }
fmt.Print("----------------\n" + bpm_utils.CreateInfoFile(*info, true)) fmt.Println("----------------")
fmt.Println(utils.CreateReadableInfo(true, true, true, info, rootDir))
if n == len(packages)-1 { if n == len(packages)-1 {
fmt.Println("----------------") fmt.Println("----------------")
} }
} }
case list: case list:
packages, err := bpm_utils.GetInstalledPackages(rootDir) packages, err := utils.GetInstalledPackages(rootDir)
if err != nil { if err != nil {
log.Fatalf("Could not get installed packages\nError: %s", err.Error()) log.Fatalf("Error: could not get installed packages: %s", err.Error())
return return
} }
if pkgListNumbers { if pkgListNumbers {
@@ -109,118 +125,411 @@ func resolveCommand() {
return return
} }
for n, pkg := range packages { for n, pkg := range packages {
info := bpm_utils.GetPackageInfo(pkg, rootDir, false) info := utils.GetPackageInfo(pkg, rootDir, false)
if info == nil { if info == nil {
fmt.Printf("Package (%s) could not be found\n", pkg) fmt.Printf("Package (%s) could not be found\n", pkg)
continue continue
} }
fmt.Print("----------------\n" + bpm_utils.CreateInfoFile(*info, true)) fmt.Println("----------------\n" + utils.CreateReadableInfo(true, true, true, info, rootDir))
if n == len(packages)-1 { if n == len(packages)-1 {
fmt.Println("----------------") fmt.Println("----------------")
} }
} }
} }
case search:
searchTerms := subcommandArgs
if len(searchTerms) == 0 {
log.Fatalf("Error: no search terms given")
}
for _, term := range searchTerms {
nameResults := make([]*utils.PackageInfo, 0)
descResults := make([]*utils.PackageInfo, 0)
for _, repo := range utils.BPMConfig.Repositories {
for _, entry := range repo.Entries {
if strings.Contains(entry.Info.Name, term) {
nameResults = append(nameResults, entry.Info)
} else if strings.Contains(entry.Info.Description, term) {
descResults = append(descResults, entry.Info)
}
}
}
results := append(nameResults, descResults...)
if len(results) == 0 {
log.Fatalf("Error: no results for term (%s) were found\n", term)
}
fmt.Printf("Results for term (%s)\n", term)
for i, result := range results {
fmt.Println("----------------")
fmt.Printf("%d) %s: %s (%s)\n", i+1, result.Name, result.Description, result.GetFullVersion())
}
}
case install: case install:
if os.Getuid() != 0 { if os.Getuid() != 0 {
fmt.Println("This subcommand needs to be run with superuser permissions") log.Fatalf("Error: this subcommand needs to be run with superuser permissions")
os.Exit(0)
} }
files := subcommandArgs pkgs := subcommandArgs
if len(files) == 0 { if len(pkgs) == 0 {
fmt.Println("No files were given to install") fmt.Println("No packages or files were given to install")
return return
} }
for _, file := range files {
pkgInfo, err := bpm_utils.ReadPackage(file) pkgsToInstall := orderedmap.NewOrderedMap[string, *struct {
if err != nil { bpmFile string
log.Fatalf("Could not read package\nError: %s\n", err) isDependency bool
shouldFetch bool
pkgInfo *utils.PackageInfo
}]()
unresolvedDepends := make([]string, 0)
// Search for packages
for _, pkg := range pkgs {
if stat, err := os.Stat(pkg); err == nil && !stat.IsDir() {
pkgInfo, err := utils.ReadPackage(pkg)
if err != nil {
log.Fatalf("Error: could not read package: %s\n", err)
}
if !reinstall && utils.IsPackageInstalled(pkgInfo.Name, rootDir) && utils.GetPackageInfo(pkgInfo.Name, rootDir, true).GetFullVersion() == pkgInfo.GetFullVersion() {
continue
}
pkgsToInstall.Set(pkgInfo.Name, &struct {
bpmFile string
isDependency bool
shouldFetch bool
pkgInfo *utils.PackageInfo
}{bpmFile: pkg, isDependency: false, shouldFetch: false, pkgInfo: pkgInfo})
} else {
entry, _, err := utils.GetRepositoryEntry(pkg)
if err != nil {
log.Fatalf("Error: could not find package (%s) in any repository\n", pkg)
}
if !reinstall && utils.IsPackageInstalled(entry.Info.Name, rootDir) && utils.GetPackageInfo(entry.Info.Name, rootDir, true).GetFullVersion() == entry.Info.GetFullVersion() {
continue
}
pkgsToInstall.Set(entry.Info.Name, &struct {
bpmFile string
isDependency bool
shouldFetch bool
pkgInfo *utils.PackageInfo
}{bpmFile: "", isDependency: false, shouldFetch: true, pkgInfo: entry.Info})
} }
fmt.Print("----------------\n" + bpm_utils.CreateInfoFile(*pkgInfo, true)) }
fmt.Println("----------------")
verb := "install" clone := pkgsToInstall.Copy()
pkgsToInstall = orderedmap.NewOrderedMap[string, *struct {
bpmFile string
isDependency bool
shouldFetch bool
pkgInfo *utils.PackageInfo
}]()
for _, pkg := range clone.Keys() {
value, _ := clone.Get(pkg)
resolved, unresolved := value.pkgInfo.ResolveAll(&[]string{}, &[]string{}, value.pkgInfo.Type == "source", !noOptional, !reinstall, rootDir)
unresolvedDepends = append(unresolvedDepends, unresolved...)
for _, depend := range resolved {
if _, ok := pkgsToInstall.Get(depend); !ok && depend != value.pkgInfo.Name {
if !reinstallAll && utils.IsPackageInstalled(depend, rootDir) {
continue
}
entry, _, err := utils.GetRepositoryEntry(depend)
if err != nil {
log.Fatalf("Error: could not find package (%s) in any repository\n", pkg)
}
pkgsToInstall.Set(depend, &struct {
bpmFile string
isDependency bool
shouldFetch bool
pkgInfo *utils.PackageInfo
}{bpmFile: "", isDependency: true, shouldFetch: true, pkgInfo: entry.Info})
}
}
pkgsToInstall.Set(pkg, value)
}
// Show summary
if len(unresolvedDepends) != 0 {
if !force {
log.Fatalf("Error: the following dependencies could not be found in any repositories: %s\n", strings.Join(unresolvedDepends, ", "))
} else {
log.Println("Warning: The following dependencies could not be found in any repositories: " + strings.Join(unresolvedDepends, ", "))
}
}
if pkgsToInstall.Len() == 0 {
fmt.Println("All packages are up to date!")
os.Exit(0)
}
for _, pkg := range pkgsToInstall.Keys() {
value, _ := pkgsToInstall.Get(pkg)
pkgInfo := value.pkgInfo
installedInfo := utils.GetPackageInfo(pkgInfo.Name, rootDir, false)
sourceInfo := ""
if pkgInfo.Type == "source" { if pkgInfo.Type == "source" {
verb = "build" if rootDir != "/" && !force {
log.Fatalf("Error: cannot compile and install source packages to a different root directory")
}
sourceInfo = "(From Source)"
} }
if !forceInstall { if installedInfo == nil {
if pkgInfo.Arch != "any" && pkgInfo.Arch != bpm_utils.GetArch() { fmt.Printf("%s: %s (Install) %s\n", pkgInfo.Name, pkgInfo.GetFullVersion(), sourceInfo)
fmt.Printf("skipping... cannot %s a package with a different architecture\n", verb) } else if strings.Compare(pkgInfo.GetFullVersion(), installedInfo.GetFullVersion()) < 0 {
continue fmt.Printf("%s: %s -> %s (Downgrade) %s\n", pkgInfo.Name, installedInfo.GetFullVersion(), pkgInfo.GetFullVersion(), sourceInfo)
} } else if strings.Compare(pkgInfo.GetFullVersion(), installedInfo.GetFullVersion()) > 0 {
if unresolved := bpm_utils.CheckDependencies(pkgInfo, rootDir); len(unresolved) != 0 { fmt.Printf("%s: %s -> %s (Upgrade) %s\n", pkgInfo.Name, installedInfo.GetFullVersion(), pkgInfo.GetFullVersion(), sourceInfo)
fmt.Printf("skipping... cannot %s package (%s) due to missing dependencies: %s\n", verb, pkgInfo.Name, strings.Join(unresolved, ", ")) } else {
continue fmt.Printf("%s: %s (Reinstall) %s\n", pkgInfo.Name, pkgInfo.GetFullVersion(), sourceInfo)
}
if pkgInfo.Type == "source" {
if unresolved := bpm_utils.CheckMakeDependencies(pkgInfo, "/"); len(unresolved) != 0 {
fmt.Printf("skipping... cannot %s package (%s) due to missing make dependencies: %s\n", verb, pkgInfo.Name, strings.Join(unresolved, ", "))
continue
}
}
} }
if rootDir != "/" { }
fmt.Println("Warning: Operating in " + rootDir) if rootDir != "/" {
fmt.Println("Warning: Operating in " + rootDir)
}
if !yesAll {
reader := bufio.NewReader(os.Stdin)
if pkgsToInstall.Len() == 1 {
fmt.Printf("Do you wish to install this package? [y\\N] ")
} else {
fmt.Printf("Do you wish to install these %d packages? [y\\N] ", pkgsToInstall.Len())
} }
if !yesAll {
reader := bufio.NewReader(os.Stdin) text, _ := reader.ReadString('\n')
if pkgInfo.Type == "source" { if strings.TrimSpace(strings.ToLower(text)) != "y" && strings.TrimSpace(strings.ToLower(text)) != "yes" {
fmt.Print("Would you like to view the source.sh file of this package? [Y\\n] ") fmt.Println("Cancelling...")
text, _ := reader.ReadString('\n') os.Exit(1)
if strings.TrimSpace(strings.ToLower(text)) != "n" && strings.TrimSpace(strings.ToLower(text)) != "no" {
script, err := bpm_utils.GetSourceScript(file)
if err != nil {
log.Fatalf("Could not read source script\nError: %s\n", err)
}
fmt.Println(script)
fmt.Println("-------EOF-------")
}
}
} }
if bpm_utils.IsPackageInstalled(pkgInfo.Name, rootDir) { }
if !yesAll {
installedInfo := bpm_utils.GetPackageInfo(pkgInfo.Name, rootDir, false) // Fetch packages from repositories
if strings.Compare(pkgInfo.Version, installedInfo.Version) > 0 { fmt.Println("Fetching packages from available repositories...")
fmt.Println("This file contains a newer version of this package (" + installedInfo.Version + " -> " + pkgInfo.Version + ")") for _, pkg := range pkgsToInstall.Keys() {
fmt.Print("Do you wish to update this package? [y\\N] ") value, _ := pkgsToInstall.Get(pkg)
} else if strings.Compare(pkgInfo.Version, installedInfo.Version) < 0 { if !value.shouldFetch {
fmt.Println("This file contains an older version of this package (" + installedInfo.Version + " -> " + pkgInfo.Version + ")") continue
fmt.Print("Do you wish to downgrade this package? (Not recommended) [y\\N] ") }
} else if strings.Compare(pkgInfo.Version, installedInfo.Version) == 0 { entry, repo, err := utils.GetRepositoryEntry(pkg)
fmt.Println("This package is already installed on the system and is up to date") if err != nil {
fmt.Printf("Do you wish to re%s this package? [y\\N] ", verb) log.Fatalf("Error: could not find package (%s) in any repository\n", pkg)
} }
reader := bufio.NewReader(os.Stdin) fetchedPackage, err := repo.FetchPackage(entry.Info.Name)
text, _ := reader.ReadString('\n') if err != nil {
if strings.TrimSpace(strings.ToLower(text)) != "y" && strings.TrimSpace(strings.ToLower(text)) != "yes" { log.Fatalf("Error: could not fetch package (%s): %s\n", pkg, err)
fmt.Printf("Skipping package (%s)...\n", pkgInfo.Name) }
continue fmt.Printf("Package (%s) was successfully fetched!\n", value.pkgInfo.Name)
} value.bpmFile = fetchedPackage
} pkgsToInstall.Set(pkg, value)
} else if !yesAll { }
reader := bufio.NewReader(os.Stdin)
fmt.Printf("Do you wish to %s this package? [y\\N] ", verb) // Install fetched packages
text, _ := reader.ReadString('\n') for _, pkg := range pkgsToInstall.Keys() {
if strings.TrimSpace(strings.ToLower(text)) != "y" && strings.TrimSpace(strings.ToLower(text)) != "yes" { value, _ := pkgsToInstall.Get(pkg)
fmt.Printf("Skipping package (%s)...\n", pkgInfo.Name) pkgInfo := value.pkgInfo
continue var err error
} if value.isDependency {
err = utils.InstallPackage(value.bpmFile, rootDir, verbose, true, buildSource, skipCheck, keepTempDir)
} else {
err = utils.InstallPackage(value.bpmFile, rootDir, verbose, force, buildSource, skipCheck, keepTempDir)
} }
err = bpm_utils.InstallPackage(file, rootDir, forceInstall, buildSource, skipCheck, keepTempDir)
if err != nil { if err != nil {
if pkgInfo.Type == "source" && keepTempDir { if pkgInfo.Type == "source" && keepTempDir {
fmt.Println("BPM temp directory was created at /var/tmp/bpm_source-" + pkgInfo.Name) fmt.Println("BPM temp directory was created at /var/tmp/bpm_source-" + pkgInfo.Name)
} }
log.Fatalf("Could not install package\nError: %s\n", err) log.Fatalf("Error: could not install package (%s): %s\n", pkg, err)
}
fmt.Printf("Package (%s) was successfully installed\n", pkgInfo.Name)
if value.isDependency {
err := utils.SetInstallationReason(pkgInfo.Name, utils.Dependency, rootDir)
if err != nil {
log.Fatalf("Error: could not set installation reason for package: %s\n", err)
}
} }
fmt.Printf("Package (%s) was successfully installed!\n", pkgInfo.Name)
if pkgInfo.Type == "source" && keepTempDir { if pkgInfo.Type == "source" && keepTempDir {
fmt.Println("** It is recommended you delete the temporary bpm folder in /var/tmp **") fmt.Println("** It is recommended you delete the temporary bpm folder in /var/tmp **")
} }
} }
case update:
if os.Getuid() != 0 {
log.Fatalf("Error: this subcommand needs to be run with superuser permissions")
}
// Sync repositories
if !nosync {
for _, repo := range utils.BPMConfig.Repositories {
fmt.Printf("Fetching package database for repository (%s)...\n", repo.Name)
err := repo.SyncLocalDatabase()
if err != nil {
log.Fatalf("Error: could not sync local database for repository (%s): %s\n", repo.Name, err)
}
}
fmt.Println("All package databases synced successfully!")
}
utils.ReadConfig()
// Get installed packages and check for updates
pkgs, err := utils.GetInstalledPackages(rootDir)
if err != nil {
log.Fatalf("Error: could not get installed packages: %s\n", err)
}
toUpdate := orderedmap.NewOrderedMap[string, *struct {
isDependency bool
entry *utils.RepositoryEntry
}]()
for _, pkg := range pkgs {
entry, _, err := utils.GetRepositoryEntry(pkg)
if err != nil {
continue
}
installedInfo := utils.GetPackageInfo(pkg, rootDir, true)
if installedInfo == nil {
log.Fatalf("Error: could not get package info for (%s)\n", pkg)
}
if strings.Compare(entry.Info.GetFullVersion(), installedInfo.GetFullVersion()) > 0 {
toUpdate.Set(entry.Info.Name, &struct {
isDependency bool
entry *utils.RepositoryEntry
}{isDependency: false, entry: entry})
} else if reinstall {
toUpdate.Set(entry.Info.Name, &struct {
isDependency bool
entry *utils.RepositoryEntry
}{isDependency: false, entry: entry})
}
}
if toUpdate.Len() == 0 {
fmt.Println("All packages are up to date!")
os.Exit(0)
}
// Check for new dependencies in updated packages
unresolved := make([]string, 0)
clone := toUpdate.Copy()
for _, key := range clone.Keys() {
pkg, _ := clone.Get(key)
r, u := pkg.entry.Info.ResolveAll(&[]string{}, &[]string{}, pkg.entry.Info.Type == "source", !noOptional, true, rootDir)
unresolved = append(unresolved, u...)
for _, depend := range r {
if _, ok := toUpdate.Get(depend); !ok {
entry, _, err := utils.GetRepositoryEntry(depend)
if err != nil {
log.Fatalf("Error: could not find package (%s) in any repository\n", depend)
}
toUpdate.Set(depend, &struct {
isDependency bool
entry *utils.RepositoryEntry
}{isDependency: true, entry: entry})
}
}
}
if len(unresolved) != 0 {
if !force {
log.Fatalf("Error: the following dependencies could not be found in any repositories: %s\n", strings.Join(unresolved, ", "))
} else {
log.Printf("Warning: the following dependencies could not be found in any repositories: %s\n", strings.Join(unresolved, ", "))
}
}
for _, key := range toUpdate.Keys() {
value, _ := toUpdate.Get(key)
installedInfo := utils.GetPackageInfo(value.entry.Info.Name, rootDir, true)
sourceInfo := ""
if value.entry.Info.Type == "source" {
sourceInfo = "(From Source)"
}
if installedInfo == nil {
fmt.Printf("%s: %s (Install) %s\n", value.entry.Info.Name, value.entry.Info.GetFullVersion(), sourceInfo)
continue
}
if strings.Compare(value.entry.Info.GetFullVersion(), installedInfo.GetFullVersion()) > 0 {
fmt.Printf("%s: %s -> %s (Upgrade) %s\n", value.entry.Info.Name, installedInfo.GetFullVersion(), value.entry.Info.GetFullVersion(), sourceInfo)
} else if reinstall {
fmt.Printf("%s: %s -> %s (Reinstall) %s\n", value.entry.Info.Name, installedInfo.GetFullVersion(), value.entry.Info.GetFullVersion(), sourceInfo)
}
}
// Update confirmation prompt
if !yesAll {
fmt.Printf("Are you sure you wish to update all %d packages? [y\\N] ", toUpdate.Len())
reader := bufio.NewReader(os.Stdin)
text, _ := reader.ReadString('\n')
if strings.TrimSpace(strings.ToLower(text)) != "y" && strings.TrimSpace(strings.ToLower(text)) != "yes" {
fmt.Println("Cancelling update...")
os.Exit(1)
}
}
// Fetch packages
pkgsToInstall := orderedmap.NewOrderedMap[string, *struct {
isDependency bool
entry *utils.RepositoryEntry
}]()
fmt.Println("Fetching packages from available repositories...")
for _, pkg := range toUpdate.Keys() {
value, _ := toUpdate.Get(pkg)
entry, repo, err := utils.GetRepositoryEntry(pkg)
if err != nil {
log.Fatalf("Error: could not find package (%s) in any repository\n", pkg)
}
fetchedPackage, err := repo.FetchPackage(entry.Info.Name)
if err != nil {
log.Fatalf("Error: could not fetch package (%s): %s\n", pkg, err)
}
fmt.Printf("Package (%s) was successfully fetched!\n", value.entry.Info.Name)
pkgsToInstall.Set(fetchedPackage, value)
}
// Install fetched packages
for _, pkg := range pkgsToInstall.Keys() {
value, _ := pkgsToInstall.Get(pkg)
pkgInfo := value.entry.Info
var err error
if value.isDependency {
err = utils.InstallPackage(pkg, rootDir, verbose, true, buildSource, skipCheck, keepTempDir)
} else {
err = utils.InstallPackage(pkg, rootDir, verbose, force, buildSource, skipCheck, keepTempDir)
}
if err != nil {
if pkgInfo.Type == "source" && keepTempDir {
fmt.Println("BPM temp directory was created at /var/tmp/bpm_source-" + pkgInfo.Name)
}
log.Fatalf("Error: could not install package (%s): %s\n", pkg, err)
}
fmt.Printf("Package (%s) was successfully installed!\n", pkgInfo.Name)
if value.isDependency {
err := utils.SetInstallationReason(pkgInfo.Name, utils.Dependency, rootDir)
if err != nil {
log.Fatalf("Error: could not set installation reason for package: %s\n", err)
}
}
if pkgInfo.Type == "source" && keepTempDir {
fmt.Println("** It is recommended you delete the temporary bpm folder in /var/tmp **")
}
}
case sync:
if os.Getuid() != 0 {
log.Fatalf("Error: this subcommand needs to be run with superuser permissions")
}
if !yesAll {
fmt.Printf("Are you sure you wish to sync all databases? [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.Println("Cancelling sync...")
os.Exit(1)
}
}
for _, repo := range utils.BPMConfig.Repositories {
fmt.Printf("Fetching package database for repository (%s)...\n", repo.Name)
err := repo.SyncLocalDatabase()
if err != nil {
log.Fatalf("Error: could not sync local database for repository (%s): %s\n", repo.Name, err)
}
}
fmt.Println("All package databases synced successfully!")
case remove: case remove:
if os.Getuid() != 0 { if os.Getuid() != 0 {
fmt.Println("This subcommand needs to be run with superuser permissions") log.Fatalf("Error: this subcommand needs to be run with superuser permissions")
os.Exit(0)
} }
packages := subcommandArgs packages := subcommandArgs
if len(packages) == 0 { if len(packages) == 0 {
@@ -228,12 +537,12 @@ func resolveCommand() {
return return
} }
for _, pkg := range packages { for _, pkg := range packages {
pkgInfo := bpm_utils.GetPackageInfo(pkg, rootDir, false) pkgInfo := utils.GetPackageInfo(pkg, rootDir, false)
if pkgInfo == nil { if pkgInfo == nil {
fmt.Printf("Package (%s) could not be found\n", pkg) fmt.Printf("Package (%s) could not be found\n", pkg)
continue continue
} }
fmt.Print("----------------\n" + bpm_utils.CreateInfoFile(*pkgInfo, true)) fmt.Println("----------------\n" + utils.CreateReadableInfo(false, false, false, pkgInfo, rootDir))
fmt.Println("----------------") fmt.Println("----------------")
if rootDir != "/" { if rootDir != "/" {
fmt.Println("Warning: Operating in " + rootDir) fmt.Println("Warning: Operating in " + rootDir)
@@ -247,10 +556,10 @@ func resolveCommand() {
continue continue
} }
} }
err := bpm_utils.RemovePackage(pkg, rootDir) err := utils.RemovePackage(pkg, verbose, rootDir)
if err != nil { if err != nil {
log.Fatalf("Could not remove package\nError: %s\n", err) log.Fatalf("Error: could not remove package: %s\n", err)
} }
fmt.Printf("Package (%s) was successfully removed!\n", pkgInfo.Name) fmt.Printf("Package (%s) was successfully removed!\n", pkgInfo.Name)
} }
@@ -263,23 +572,23 @@ func resolveCommand() {
for _, file := range files { for _, file := range files {
absFile, err := filepath.Abs(file) absFile, err := filepath.Abs(file)
if err != nil { if err != nil {
log.Fatalf("Could not get absolute path of %s", file) log.Fatalf("Error: could not get absolute path of file (%s)\n", file)
} }
stat, err := os.Stat(absFile) stat, err := os.Stat(absFile)
if os.IsNotExist(err) { if os.IsNotExist(err) {
log.Fatalf(absFile + " does not exist!") log.Fatalf("Error: file (%s) does not exist!\n", absFile)
} }
pkgs, err := bpm_utils.GetInstalledPackages(rootDir) pkgs, err := utils.GetInstalledPackages(rootDir)
if err != nil { if err != nil {
log.Fatalf("Could not get installed packages. Error %s", err.Error()) log.Fatalf("Error: could not get installed packages: %s\n", err.Error())
} }
if !strings.HasPrefix(absFile, rootDir) { if !strings.HasPrefix(absFile, rootDir) {
log.Fatalf("Could not get relative path of %s to root path", absFile) log.Fatalf("Error: could not get path of file (%s) relative to root path", absFile)
} }
absFile, err = filepath.Rel(rootDir, absFile) absFile, err = filepath.Rel(rootDir, absFile)
if err != nil { if err != nil {
log.Fatalf("Could not get relative path of %s to root path", absFile) log.Fatalf("Error: could not get path of file (%s) relative to root path", absFile)
} }
absFile = strings.TrimPrefix(absFile, "/") absFile = strings.TrimPrefix(absFile, "/")
if stat.IsDir() { if stat.IsDir() {
@@ -288,7 +597,7 @@ func resolveCommand() {
var pkgList []string var pkgList []string
for _, pkg := range pkgs { for _, pkg := range pkgs {
if slices.Contains(bpm_utils.GetPackageFiles(pkg, rootDir), absFile) { if slices.Contains(utils.GetPackageFiles(pkg, rootDir), absFile) {
pkgList = append(pkgList, pkg) pkgList = append(pkgList, pkg)
} }
} }
@@ -312,20 +621,39 @@ func printHelp() {
fmt.Println("-> flags will be read if passed right after the subcommand otherwise they will be read as subcommand arguments") fmt.Println("-> flags will be read if passed right after the subcommand otherwise they will be read as subcommand arguments")
fmt.Println("\033[1m---- Command List ----\033[0m") fmt.Println("\033[1m---- Command List ----\033[0m")
fmt.Println("-> bpm version | shows information on the installed version of bpm") fmt.Println("-> bpm version | shows information on the installed version of bpm")
fmt.Println("-> bpm info [-R] | shows information on an installed package") fmt.Println("-> bpm info [-R] <packages...> | shows information on an installed package")
fmt.Println(" -R=<root_path> lets you define the root path which will be used") fmt.Println(" -R=<path> lets you define the root path which will be used")
fmt.Println("-> bpm list [-R, -c, -n] | lists all installed packages") fmt.Println("-> bpm list [-R, -c, -n] | lists all installed packages")
fmt.Println(" -R=<root_path> lets you define the root path which will be used") fmt.Println(" -R=<path> lets you define the root path which will be used")
fmt.Println(" -c lists the amount of installed packages") fmt.Println(" -c lists the amount of installed packages")
fmt.Println(" -n lists only the names of installed packages") fmt.Println(" -n lists only the names of installed packages")
fmt.Println("-> bpm install [-R, -y, -f, -b] <files...> | installs the following files") fmt.Println("-> bpm search <search terms...> | Searches for packages through declared repositories")
fmt.Println(" -R=<root_path> lets you define the root path which will be used") fmt.Println("-> bpm install [-R, -v, -y, -f, -o, -c, -b, -k, --reinstall, --reinstall-all, --no-optional] <packages...> | installs the following files")
fmt.Println(" -R=<path> lets you define the root path which will be used")
fmt.Println(" -v Show additional information about what BPM is doing")
fmt.Println(" -y skips the confirmation prompt") fmt.Println(" -y skips the confirmation prompt")
fmt.Println(" -f skips dependency and architecture checking") fmt.Println(" -f skips dependency, conflict and architecture checking")
fmt.Println(" -b creates a binary package for a source package after compilation and saves it in /var/lib/bpm/compiled") fmt.Println(" -o=<path> set the binary package output directory (defaults to /var/lib/bpm/compiled)")
fmt.Println(" -k keeps the temp directory created by BPM after source package installation") fmt.Println(" -c=<path> set the compilation directory (defaults to /var/tmp)")
fmt.Println("-> bpm remove [-R, -y] <packages...> | removes the following packages") fmt.Println(" -b creates a binary package from a source package after compilation and saves it in the binary package output directory")
fmt.Println(" -R=<root_path> lets you define the root path which will be used") fmt.Println(" -k keeps the compilation directory created by BPM after source package installation")
fmt.Println(" --reinstall Reinstalls packages even if they do not have a newer version available")
fmt.Println(" --reinstall-all Same as --reinstall but also reinstalls dependencies")
fmt.Println(" --no-optional Prevents installation of optional dependencies")
fmt.Println("-> bpm update [-R, -v, -y, -f, --reinstall, --no-sync] | updates all packages that are available in the repositories")
fmt.Println(" -R=<path> lets you define the root path which will be used")
fmt.Println(" -v Show additional information about what BPM is doing")
fmt.Println(" -y skips the confirmation prompt")
fmt.Println(" -f skips dependency, conflict and architecture checking")
fmt.Println(" --reinstall Fetches and reinstalls all packages even if they do not have a newer version available")
fmt.Println(" --no-sync Skips package database syncing")
fmt.Println("-> bpm sync [-R, -v, -y] | Syncs package databases without updating packages")
fmt.Println(" -R=<path> lets you define the root path which will be used")
fmt.Println(" -v Show additional information about what BPM is doing")
fmt.Println(" -y skips the confirmation prompt")
fmt.Println("-> bpm remove [-R, -v, -y] <packages...> | removes the following packages")
fmt.Println(" -v Show additional information about what BPM is doing")
fmt.Println(" -R=<path> lets you define the root path which will be used")
fmt.Println(" -y skips the confirmation prompt") fmt.Println(" -y skips the confirmation prompt")
fmt.Println("-> bpm file [-R] <files...> | shows what packages the following packages are managed by") fmt.Println("-> bpm file [-R] <files...> | shows what packages the following packages are managed by")
fmt.Println(" -R=<root_path> lets you define the root path which will be used") fmt.Println(" -R=<root_path> lets you define the root path which will be used")
@@ -346,15 +674,37 @@ func resolveFlags() {
// Install flags // Install flags
installFlagSet := flag.NewFlagSet("Install flags", flag.ExitOnError) installFlagSet := flag.NewFlagSet("Install flags", flag.ExitOnError)
installFlagSet.StringVar(&rootDir, "R", "/", "Set the destination root") installFlagSet.StringVar(&rootDir, "R", "/", "Set the destination root")
installFlagSet.BoolVar(&verbose, "v", false, "Show additional information about what BPM is doing")
installFlagSet.BoolVar(&yesAll, "y", false, "Skip confirmation prompts") installFlagSet.BoolVar(&yesAll, "y", false, "Skip confirmation prompts")
installFlagSet.StringVar(&utils.BPMConfig.BinaryOutputDir, "o", utils.BPMConfig.BinaryOutputDir, "Set the binary output directory")
installFlagSet.StringVar(&utils.BPMConfig.CompilationDir, "c", utils.BPMConfig.CompilationDir, "Set the compilation directory")
installFlagSet.BoolVar(&buildSource, "b", false, "Build binary package from source package") installFlagSet.BoolVar(&buildSource, "b", false, "Build binary package from source package")
installFlagSet.BoolVar(&skipCheck, "s", false, "Skip check function during source compilation") installFlagSet.BoolVar(&skipCheck, "s", false, "Skip check function during source compilation")
installFlagSet.BoolVar(&keepTempDir, "k", false, "Keep temporary directory after source compilation") installFlagSet.BoolVar(&keepTempDir, "k", false, "Keep temporary directory after source compilation")
installFlagSet.BoolVar(&forceInstall, "f", false, "Force installation by skipping architecture and dependency resolution") installFlagSet.BoolVar(&force, "f", false, "Force installation by skipping architecture and dependency resolution")
installFlagSet.BoolVar(&reinstall, "reinstall", false, "Reinstalls packages even if they do not have a newer version available")
installFlagSet.BoolVar(&reinstallAll, "reinstall-all", false, "Same as --reinstall but also reinstalls dependencies")
installFlagSet.BoolVar(&noOptional, "no-optional", false, "Prevents installation of optional dependencies")
installFlagSet.Usage = printHelp installFlagSet.Usage = printHelp
// Update flags
updateFlagSet := flag.NewFlagSet("Update flags", flag.ExitOnError)
updateFlagSet.StringVar(&rootDir, "R", "/", "Set the destination root")
updateFlagSet.BoolVar(&verbose, "v", false, "Show additional information about what BPM is doing")
updateFlagSet.BoolVar(&yesAll, "y", false, "Skip confirmation prompts")
updateFlagSet.BoolVar(&force, "f", false, "Force update by skipping architecture and dependency resolution")
updateFlagSet.BoolVar(&reinstall, "reinstall", false, "Fetches and reinstalls all packages even if they do not have a newer version available")
updateFlagSet.BoolVar(&nosync, "no-sync", false, "Skips package database syncing")
updateFlagSet.Usage = printHelp
// Sync flags
syncFlagSet := flag.NewFlagSet("Sync flags", flag.ExitOnError)
syncFlagSet.StringVar(&rootDir, "R", "/", "Set the destination root")
syncFlagSet.BoolVar(&verbose, "v", false, "Show additional information about what BPM is doing")
syncFlagSet.BoolVar(&yesAll, "y", false, "Skip confirmation prompts")
syncFlagSet.Usage = printHelp
// Remove flags // Remove flags
removeFlagSet := flag.NewFlagSet("Remove flags", flag.ExitOnError) removeFlagSet := flag.NewFlagSet("Remove flags", flag.ExitOnError)
removeFlagSet.StringVar(&rootDir, "R", "/", "Set the destination root") removeFlagSet.StringVar(&rootDir, "R", "/", "Set the destination root")
removeFlagSet.BoolVar(&verbose, "v", false, "Show additional information about what BPM is doing")
removeFlagSet.BoolVar(&yesAll, "y", false, "Skip confirmation prompts") removeFlagSet.BoolVar(&yesAll, "y", false, "Skip confirmation prompts")
removeFlagSet.Usage = printHelp removeFlagSet.Usage = printHelp
// File flags // File flags
@@ -384,6 +734,18 @@ func resolveFlags() {
return return
} }
subcommandArgs = installFlagSet.Args() subcommandArgs = installFlagSet.Args()
} else if getCommandType() == update {
err := updateFlagSet.Parse(subcommandArgs)
if err != nil {
return
}
subcommandArgs = updateFlagSet.Args()
} else if getCommandType() == sync {
err := syncFlagSet.Parse(subcommandArgs)
if err != nil {
return
}
subcommandArgs = syncFlagSet.Args()
} else if getCommandType() == remove { } else if getCommandType() == remove {
err := removeFlagSet.Parse(subcommandArgs) err := removeFlagSet.Parse(subcommandArgs)
if err != nil { if err != nil {
@@ -397,5 +759,8 @@ func resolveFlags() {
} }
subcommandArgs = fileFlagSet.Args() subcommandArgs = fileFlagSet.Args()
} }
if reinstallAll {
reinstall = true
}
} }
} }
Binary file not shown.
@@ -1,3 +0,0 @@
---
compilation_env: []
silent_compilation: false
Binary file not shown.
@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2024 CapCreeperGR
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-8
View File
@@ -1,8 +0,0 @@
name: bpm
description: The Bubble Package Manager
version: 0.3.0
url: https://gitlab.com/bubble-package-manager/bpm/
license: GPL3
architecture: x86_64
type: binary
keep: etc/bpm.conf
Binary file not shown.
Binary file not shown.
-5
View File
@@ -1,5 +0,0 @@
name: hello
description: A simple hello world program
version: 1.0
architecture: x86_64
type: binary
+49
View File
@@ -0,0 +1,49 @@
package utils
import (
"gopkg.in/yaml.v3"
"log"
"os"
)
type BPMConfigStruct struct {
CompilationEnv []string `yaml:"compilation_env"`
SilentCompilation bool `yaml:"silent_compilation"`
BinaryOutputDir string `yaml:"binary_output_dir"`
CompilationDir string `yaml:"compilation_dir"`
Repositories []*Repository `yaml:"repositories"`
}
var BPMConfig BPMConfigStruct
func ReadConfig() {
if _, err := os.Stat("/etc/bpm.conf"); os.IsNotExist(err) {
log.Fatal(err)
}
bytes, err := os.ReadFile("/etc/bpm.conf")
if err != nil {
log.Fatal(err)
}
BPMConfig = BPMConfigStruct{
CompilationEnv: make([]string, 0),
SilentCompilation: false,
BinaryOutputDir: "/var/lib/bpm/compiled/",
CompilationDir: "/var/tmp/",
}
err = yaml.Unmarshal(bytes, &BPMConfig)
if err != nil {
log.Fatal(err)
}
for i := len(BPMConfig.Repositories) - 1; i >= 0; i-- {
if BPMConfig.Repositories[i].Disabled != nil && *BPMConfig.Repositories[i].Disabled {
BPMConfig.Repositories = append(BPMConfig.Repositories[:i], BPMConfig.Repositories[i+1:]...)
}
}
for _, repo := range BPMConfig.Repositories {
repo.Entries = make(map[string]*RepositoryEntry)
err := repo.ReadLocalDatabase()
if err != nil {
log.Fatal(err)
}
}
}
@@ -1,4 +1,4 @@
package bpm_utils package utils
import ( import (
"io" "io"
@@ -1,4 +1,4 @@
package bpm_utils package utils
import ( import (
"archive/tar" "archive/tar"
@@ -6,6 +6,7 @@ import (
"compress/gzip" "compress/gzip"
"errors" "errors"
"fmt" "fmt"
"gopkg.in/yaml.v3"
"io" "io"
"io/fs" "io/fs"
"os" "os"
@@ -19,17 +20,61 @@ import (
) )
type PackageInfo struct { type PackageInfo struct {
Name string Name string `yaml:"name,omitempty"`
Description string Description string `yaml:"description,omitempty"`
Version string Version string `yaml:"version,omitempty"`
Url string Revision int `yaml:"revision,omitempty"`
License string Url string `yaml:"url,omitempty"`
Arch string License string `yaml:"license,omitempty"`
Type string Arch string `yaml:"architecture,omitempty"`
Keep []string Type string `yaml:"type,omitempty"`
Depends []string Keep []string `yaml:"keep,omitempty"`
MakeDepends []string Depends []string `yaml:"depends,omitempty"`
Provides []string MakeDepends []string `yaml:"make_depends,omitempty"`
OptionalDepends []string `yaml:"optional_depends,omitempty"`
Conflicts []string `yaml:"conflicts,omitempty"`
Provides []string `yaml:"provides,omitempty"`
}
func (pkgInfo *PackageInfo) GetFullVersion() string {
return pkgInfo.Version + "-" + strconv.Itoa(pkgInfo.Revision)
}
type InstallationReason string
const (
Manual InstallationReason = "manual"
Dependency InstallationReason = "dependency"
Unknown InstallationReason = "unknown"
)
func GetInstallationReason(pkg, rootDir string) InstallationReason {
installedDir := path.Join(rootDir, "var/lib/bpm/installed/")
pkgDir := path.Join(installedDir, pkg)
if stat, err := os.Stat(path.Join(pkgDir, "installation_reason")); err != nil || stat.IsDir() {
return Manual
}
bytes, err := os.ReadFile(path.Join(pkgDir, "installation_reason"))
if err != nil {
return Unknown
}
reason := string(bytes)
if reason == "manual" {
return Manual
} else if reason == "dependency" {
return Dependency
}
return Unknown
}
func SetInstallationReason(pkg string, reason InstallationReason, rootDir string) error {
installedDir := path.Join(rootDir, "var/lib/bpm/installed/")
pkgDir := path.Join(installedDir, pkg)
err := os.WriteFile(path.Join(pkgDir, "installation_reason"), []byte(reason), 0644)
if err != nil {
return err
}
return nil
} }
func GetPackageInfoRaw(filename string) (string, error) { func GetPackageInfoRaw(filename string) (string, error) {
@@ -92,7 +137,7 @@ func ReadPackage(filename string) (*PackageInfo, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
pkgInfo, err := ReadPackageInfo(string(bs), false) pkgInfo, err := ReadPackageInfo(string(bs))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -266,114 +311,86 @@ func ExecutePackageScripts(filename, rootDir string, operation Operation, postOp
return nil return nil
} }
func ReadPackageInfo(contents string, defaultValues bool) (*PackageInfo, error) { func ReadPackageInfo(contents string) (*PackageInfo, error) {
pkgInfo := PackageInfo{ pkgInfo := PackageInfo{
Name: "", Name: "",
Description: "", Description: "",
Version: "", Version: "",
Url: "", Revision: 1,
License: "", Url: "",
Arch: "", License: "",
Type: "", Arch: "",
Keep: nil, Type: "",
Depends: nil, Keep: make([]string, 0),
MakeDepends: nil, Depends: make([]string, 0),
Provides: nil, MakeDepends: make([]string, 0),
OptionalDepends: make([]string, 0),
Conflicts: make([]string, 0),
Provides: make([]string, 0),
} }
lines := strings.Split(contents, "\n") err := yaml.Unmarshal([]byte(contents), &pkgInfo)
for num, line := range lines { if err != nil {
if len(strings.TrimSpace(line)) == 0 { return nil, err
continue
}
if line[0] == '#' {
continue
}
split := strings.SplitN(line, ":", 2)
if len(split) != 2 {
return nil, errors.New("invalid pkg.info format at line " + strconv.Itoa(num))
}
split[0] = strings.Trim(split[0], " ")
split[1] = strings.Trim(split[1], " ")
switch split[0] {
case "name":
if strings.Contains(split[1], " ") {
return nil, errors.New("the " + split[0] + " field cannot contain spaces")
}
pkgInfo.Name = split[1]
case "description":
pkgInfo.Description = split[1]
case "version":
if strings.Contains(split[1], " ") {
return nil, errors.New("the " + split[0] + " field cannot contain spaces")
}
pkgInfo.Version = split[1]
case "url":
pkgInfo.Url = split[1]
case "license":
pkgInfo.License = split[1]
case "architecture":
pkgInfo.Arch = split[1]
case "type":
pkgInfo.Type = split[1]
case "keep":
pkgInfo.Keep = strings.Split(strings.Replace(split[1], " ", "", -1), ",")
pkgInfo.Keep = stringSliceRemoveEmpty(pkgInfo.Keep)
case "depends":
pkgInfo.Depends = strings.Split(strings.Replace(split[1], " ", "", -1), ",")
pkgInfo.Depends = stringSliceRemoveEmpty(pkgInfo.Depends)
case "make_depends":
pkgInfo.MakeDepends = strings.Split(strings.Replace(split[1], " ", "", -1), ",")
pkgInfo.MakeDepends = stringSliceRemoveEmpty(pkgInfo.MakeDepends)
case "provides":
pkgInfo.Provides = strings.Split(strings.Replace(split[1], " ", "", -1), ",")
pkgInfo.Provides = stringSliceRemoveEmpty(pkgInfo.Provides)
}
} }
if !defaultValues { if pkgInfo.Name == "" {
if pkgInfo.Name == "" { return nil, errors.New("this package contains no name")
return nil, errors.New("this package contains no name") } else if pkgInfo.Description == "" {
} else if pkgInfo.Description == "" { return nil, errors.New("this package contains no description")
return nil, errors.New("this package contains no description") } else if pkgInfo.Version == "" {
} else if pkgInfo.Version == "" { return nil, errors.New("this package contains no version")
return nil, errors.New("this package contains no version") } else if pkgInfo.Revision <= 0 {
} else if pkgInfo.Arch == "" { return nil, errors.New("this package contains a revision number less or equal to 0")
return nil, errors.New("this package contains no architecture") } else if pkgInfo.Arch == "" {
} else if pkgInfo.Type == "" { return nil, errors.New("this package contains no architecture")
return nil, errors.New("this package contains no type") } else if pkgInfo.Type == "" {
} return nil, errors.New("this package contains no type")
}
for i := 0; i < len(pkgInfo.Keep); i++ {
pkgInfo.Keep[i] = strings.TrimPrefix(pkgInfo.Keep[i], "/")
} }
return &pkgInfo, nil return &pkgInfo, nil
} }
func CreateInfoFile(pkgInfo PackageInfo, keepSourceFields bool) string { func CreateInfoFile(pkgInfo *PackageInfo) string {
ret := "" bytes, err := yaml.Marshal(&pkgInfo)
ret = ret + "name: " + pkgInfo.Name + "\n" if err != nil {
ret = ret + "description: " + pkgInfo.Description + "\n" return ""
ret = ret + "version: " + pkgInfo.Version + "\n"
if pkgInfo.Url != "" {
ret = ret + "url: " + pkgInfo.Url + "\n"
} }
if pkgInfo.License != "" { return string(bytes)
ret = ret + "license: " + pkgInfo.License + "\n"
}
ret = ret + "architecture: " + pkgInfo.Arch + "\n"
ret = ret + "type: " + pkgInfo.Type + "\n"
if len(pkgInfo.Keep) > 0 {
ret = ret + "keep (" + strconv.Itoa(len(pkgInfo.Keep)) + "): " + strings.Join(pkgInfo.Keep, ",") + "\n"
}
if len(pkgInfo.Depends) > 0 {
ret = ret + "depends (" + strconv.Itoa(len(pkgInfo.Depends)) + "): " + strings.Join(pkgInfo.Depends, ",") + "\n"
}
if len(pkgInfo.MakeDepends) > 0 && keepSourceFields {
ret = ret + "make_depends (" + strconv.Itoa(len(pkgInfo.MakeDepends)) + "): " + strings.Join(pkgInfo.MakeDepends, ",") + "\n"
}
if len(pkgInfo.Provides) > 0 {
ret = ret + "provides (" + strconv.Itoa(len(pkgInfo.Provides)) + "): " + strings.Join(pkgInfo.Provides, ",") + "\n"
}
return ret
} }
func extractPackage(pkgInfo *PackageInfo, filename, rootDir string) (error, []string) { func CreateReadableInfo(showArchitecture, showType, showPackageRelations bool, pkgInfo *PackageInfo, rootDir string) string {
ret := make([]string, 0)
appendArray := func(label string, array []string) {
if len(array) == 0 {
return
}
ret = append(ret, fmt.Sprintf("%s: %s", label, strings.Join(array, ", ")))
}
ret = append(ret, "Name: "+pkgInfo.Name)
ret = append(ret, "Description: "+pkgInfo.Description)
ret = append(ret, "Version: "+pkgInfo.GetFullVersion())
ret = append(ret, "URL: "+pkgInfo.Url)
ret = append(ret, "License: "+pkgInfo.License)
if showArchitecture {
ret = append(ret, "Architecture: "+pkgInfo.Arch)
}
if showType {
ret = append(ret, "Type: "+pkgInfo.Type)
}
if showPackageRelations {
appendArray("Dependencies", pkgInfo.Depends)
appendArray("Make Dependencies", pkgInfo.MakeDepends)
appendArray("Optional dependencies", pkgInfo.OptionalDepends)
appendArray("Conflicting packages", pkgInfo.Conflicts)
appendArray("Provided packages", pkgInfo.Provides)
}
ret = append(ret, "Installation Reason: "+string(GetInstallationReason(pkgInfo.Name, rootDir)))
return strings.Join(ret, "\n")
}
func extractPackage(pkgInfo *PackageInfo, verbose bool, filename, rootDir string) (error, []string) {
var files []string var files []string
if !IsPackageInstalled(pkgInfo.Name, rootDir) { if !IsPackageInstalled(pkgInfo.Name, rootDir) {
err := ExecutePackageScripts(filename, rootDir, Install, false) err := ExecutePackageScripts(filename, rootDir, Install, false)
@@ -415,22 +432,46 @@ func extractPackage(pkgInfo *PackageInfo, filename, rootDir string) (error, []st
return err, nil return err, nil
} }
} else { } else {
fmt.Println("Creating Directory: " + extractFilename) if verbose {
fmt.Println("Creating Directory: " + extractFilename)
}
} }
case tar.TypeReg: case tar.TypeReg:
skip := false
if _, err := os.Stat(extractFilename); err == nil { if _, err := os.Stat(extractFilename); err == nil {
if slices.Contains(pkgInfo.Keep, trimmedName) { for _, k := range pkgInfo.Keep {
fmt.Println("Skipping File: " + extractFilename + "(File is configured to be kept during installs/updates)") if strings.HasSuffix(k, "/") {
files = append(files, trimmedName) if strings.HasPrefix(trimmedName, k) {
continue if verbose {
fmt.Println("Skipping File: " + extractFilename + " (Containing directory is set to be kept during installs/updates)")
}
files = append(files, strings.TrimPrefix(header.Name, "files/"))
skip = true
continue
}
} else {
if trimmedName == k {
if verbose {
fmt.Println("Skipping File: " + extractFilename + " (File is configured to be kept during installs/updates)")
}
files = append(files, strings.TrimPrefix(header.Name, "files/"))
skip = true
continue
}
}
} }
} }
if skip {
continue
}
err := os.Remove(extractFilename) err := os.Remove(extractFilename)
if err != nil && !os.IsNotExist(err) { if err != nil && !os.IsNotExist(err) {
return err, nil return err, nil
} }
outFile, err := os.Create(extractFilename) outFile, err := os.Create(extractFilename)
fmt.Println("Creating File: " + extractFilename) if verbose {
fmt.Println("Creating File: " + extractFilename)
}
files = append(files, strings.TrimPrefix(header.Name, "files/")) files = append(files, strings.TrimPrefix(header.Name, "files/"))
if err != nil { if err != nil {
return err, nil return err, nil
@@ -446,7 +487,9 @@ func extractPackage(pkgInfo *PackageInfo, filename, rootDir string) (error, []st
return err, nil return err, nil
} }
case tar.TypeSymlink: case tar.TypeSymlink:
fmt.Println("Creating Symlink: " + extractFilename + " -> " + header.Linkname) if verbose {
fmt.Println("Creating Symlink: " + extractFilename + " -> " + header.Linkname)
}
files = append(files, strings.TrimPrefix(header.Name, "files/")) files = append(files, strings.TrimPrefix(header.Name, "files/"))
err := os.Remove(extractFilename) err := os.Remove(extractFilename)
if err != nil && !os.IsNotExist(err) { if err != nil && !os.IsNotExist(err) {
@@ -457,7 +500,9 @@ func extractPackage(pkgInfo *PackageInfo, filename, rootDir string) (error, []st
return err, nil return err, nil
} }
case tar.TypeLink: case tar.TypeLink:
fmt.Println("Detected Hard Link: " + extractFilename + " -> " + path.Join(rootDir, strings.TrimPrefix(header.Linkname, "files/"))) if verbose {
fmt.Println("Detected Hard Link: " + extractFilename + " -> " + path.Join(rootDir, strings.TrimPrefix(header.Linkname, "files/")))
}
files = append(files, strings.TrimPrefix(header.Name, "files/")) files = append(files, strings.TrimPrefix(header.Name, "files/"))
seenHardlinks[extractFilename] = path.Join(strings.TrimPrefix(header.Linkname, "files/")) seenHardlinks[extractFilename] = path.Join(strings.TrimPrefix(header.Linkname, "files/"))
err := os.Remove(extractFilename) err := os.Remove(extractFilename)
@@ -465,12 +510,14 @@ func extractPackage(pkgInfo *PackageInfo, filename, rootDir string) (error, []st
return err, nil return err, nil
} }
default: default:
return errors.New("ExtractTarGz: unknown type: " + strconv.Itoa(int(header.Typeflag)) + " in " + extractFilename), nil return errors.New("unknown type (" + strconv.Itoa(int(header.Typeflag)) + ") in " + extractFilename), nil
} }
} }
} }
for extractFilename, destination := range seenHardlinks { for extractFilename, destination := range seenHardlinks {
fmt.Println("Creating Hard Link: " + extractFilename + " -> " + path.Join(rootDir, destination)) if verbose {
fmt.Println("Creating Hard Link: " + extractFilename + " -> " + path.Join(rootDir, destination))
}
err := os.Link(path.Join(rootDir, destination), extractFilename) err := os.Link(path.Join(rootDir, destination), extractFilename)
if err != nil { if err != nil {
return err, nil return err, nil
@@ -481,7 +528,22 @@ func extractPackage(pkgInfo *PackageInfo, filename, rootDir string) (error, []st
return nil, files return nil, files
} }
func compilePackage(pkgInfo *PackageInfo, filename, rootDir string, binaryPkgFromSrc, skipCheck, keepTempDir bool) (error, []string) { func isSplitPackage(filename string) bool {
pkgInfo, err := ReadPackage(filename)
if err != nil {
return false
}
if pkgInfo.Type != "source" {
return false
}
cmd := exec.Command("/bin/bash", "-c", fmt.Sprintf("test $(tar -tf %s | grep '^pkg.info' | wc -l) -eq 1", filename))
if err := cmd.Run(); err == nil {
return false
}
return true
}
func compilePackage(pkgInfo *PackageInfo, filename, rootDir string, verbose, binaryPkgFromSrc, skipCheck, keepTempDir bool) (error, []string) {
var files []string var files []string
if !IsPackageInstalled(pkgInfo.Name, rootDir) { if !IsPackageInstalled(pkgInfo.Name, rootDir) {
err := ExecutePackageScripts(filename, rootDir, Install, false) err := ExecutePackageScripts(filename, rootDir, Install, false)
@@ -505,12 +567,14 @@ func compilePackage(pkgInfo *PackageInfo, filename, rootDir string, binaryPkgFro
} }
tr := tar.NewReader(archive) tr := tar.NewReader(archive)
temp := "/var/tmp/bpm_source-" + pkgInfo.Name temp := path.Join(BPMConfig.CompilationDir, "bpm_source-"+pkgInfo.Name)
err = os.RemoveAll(temp) err = os.RemoveAll(temp)
if err != nil { if err != nil {
return err, nil return err, nil
} }
fmt.Println("Creating temp directory at: " + temp) if verbose {
fmt.Println("Creating temp directory at: " + temp)
}
err = os.Mkdir(temp, 0755) err = os.Mkdir(temp, 0755)
if err != nil { if err != nil {
return err, nil return err, nil
@@ -537,7 +601,9 @@ func compilePackage(pkgInfo *PackageInfo, filename, rootDir string, binaryPkgFro
return err, nil return err, nil
} }
} else { } else {
fmt.Println("Creating Directory: " + extractFilename) if verbose {
fmt.Println("Creating Directory: " + extractFilename)
}
err = os.Chown(extractFilename, 65534, 65534) err = os.Chown(extractFilename, 65534, 65534)
if err != nil { if err != nil {
return err, nil return err, nil
@@ -549,7 +615,9 @@ func compilePackage(pkgInfo *PackageInfo, filename, rootDir string, binaryPkgFro
return err, nil return err, nil
} }
outFile, err := os.Create(extractFilename) outFile, err := os.Create(extractFilename)
fmt.Println("Creating File: " + extractFilename) if verbose {
fmt.Println("Creating File: " + extractFilename)
}
if err != nil { if err != nil {
return err, nil return err, nil
} }
@@ -568,11 +636,15 @@ func compilePackage(pkgInfo *PackageInfo, filename, rootDir string, binaryPkgFro
return err, nil return err, nil
} }
case tar.TypeSymlink: case tar.TypeSymlink:
fmt.Println("Skipping symlink (Bundling symlinks in source packages is not supported)") if verbose {
fmt.Println("Skipping symlink (Bundling symlinks in source packages is not supported)")
}
case tar.TypeLink: case tar.TypeLink:
fmt.Println("Skipping hard link (Bundling hard links in source packages is not supported)") if verbose {
fmt.Println("Skipping hard link (Bundling hard links in source packages is not supported)")
}
default: default:
return errors.New("ExtractTarGz: unknown type: " + strconv.Itoa(int(header.Typeflag)) + " in " + extractFilename), nil return errors.New("unknown type (" + strconv.Itoa(int(header.Typeflag)) + ") in " + extractFilename), nil
} }
} }
if header.Name == "source.sh" { if header.Name == "source.sh" {
@@ -668,7 +740,6 @@ if [ $? -ne 0 ]; then
echo "Failed to run package() function in source.sh" echo "Failed to run package() function in source.sh"
fi fi
` `
err = os.WriteFile(path.Join(temp, "run.sh"), []byte(runScript), 0644) err = os.WriteFile(path.Join(temp, "run.sh"), []byte(runScript), 0644)
if err != nil { if err != nil {
return err, nil return err, nil
@@ -683,6 +754,8 @@ fi
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: 65534, Gid: 65534} cmd.SysProcAttr.Credential = &syscall.Credential{Uid: 65534, Gid: 65534}
cmd.Dir = temp cmd.Dir = temp
cmd.Env = os.Environ() cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, "USER=nobody")
cmd.Env = append(cmd.Env, "HOME="+temp)
err = os.Mkdir(path.Join(temp, "source"), 0755) err = os.Mkdir(path.Join(temp, "source"), 0755)
if err != nil { if err != nil {
@@ -709,6 +782,7 @@ fi
cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_NAME=%s", pkgInfo.Name)) cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_NAME=%s", pkgInfo.Name))
cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_DESC=%s", pkgInfo.Description)) cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_DESC=%s", pkgInfo.Description))
cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_VERSION=%s", pkgInfo.Version)) cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_VERSION=%s", pkgInfo.Version))
cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_REVISION=%d", pkgInfo.Revision))
cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_URL=%s", pkgInfo.Url)) cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_URL=%s", pkgInfo.Url))
cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_ARCH=%s", pkgInfo.Arch)) cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_ARCH=%s", pkgInfo.Arch))
depends := make([]string, len(pkgInfo.Depends)) depends := make([]string, len(pkgInfo.Depends))
@@ -763,12 +837,16 @@ fi
return err return err
} }
} else { } else {
fmt.Println("Creating Directory: " + extractFilename) if verbose {
fmt.Println("Creating Directory: " + extractFilename)
}
} }
} else if d.Type().IsRegular() { } else if d.Type().IsRegular() {
if _, err := os.Stat(extractFilename); err == nil { if _, err := os.Stat(extractFilename); err == nil {
if slices.Contains(pkgInfo.Keep, relFilename) { if slices.Contains(pkgInfo.Keep, relFilename) {
fmt.Println("Skipping File: " + extractFilename + "(File is configured to be kept during installs/updates)") if verbose {
fmt.Println("Skipping File: " + extractFilename + "(File is configured to be kept during installs/updates)")
}
files = append(files, relFilename) files = append(files, relFilename)
return nil return nil
} }
@@ -778,7 +856,9 @@ fi
return err return err
} }
outFile, err := os.Create(extractFilename) outFile, err := os.Create(extractFilename)
fmt.Println("Creating File: " + extractFilename) if verbose {
fmt.Println("Creating File: " + extractFilename)
}
files = append(files, relFilename) files = append(files, relFilename)
if err != nil { if err != nil {
return err return err
@@ -814,7 +894,9 @@ fi
if err != nil && !os.IsNotExist(err) { if err != nil && !os.IsNotExist(err) {
return err return err
} }
fmt.Println("Creating Symlink: "+extractFilename, " -> "+link) if verbose {
fmt.Println("Creating Symlink: "+extractFilename, " -> "+link)
}
files = append(files, relFilename) files = append(files, relFilename)
err = os.Symlink(link, extractFilename) err = os.Symlink(link, extractFilename)
if err != nil { if err != nil {
@@ -827,13 +909,13 @@ fi
return err, nil return err, nil
} }
if binaryPkgFromSrc { if binaryPkgFromSrc {
compiledDir := path.Join(rootDir, "var/lib/bpm/compiled/") compiledDir := path.Join(BPMConfig.BinaryOutputDir)
err = os.MkdirAll(compiledDir, 0755) err = os.MkdirAll(compiledDir, 0755)
compiledInfo := PackageInfo{} compiledInfo := PackageInfo{}
compiledInfo = *pkgInfo compiledInfo = *pkgInfo
compiledInfo.Type = "binary" compiledInfo.Type = "binary"
compiledInfo.Arch = GetArch() compiledInfo.Arch = GetArch()
err = os.WriteFile(path.Join(temp, "pkg.info"), []byte(CreateInfoFile(compiledInfo, false)), 0644) err = os.WriteFile(path.Join(temp, "pkg.info"), []byte(CreateInfoFile(&compiledInfo)), 0644)
if err != nil { if err != nil {
return err, nil return err, nil
} }
@@ -853,7 +935,7 @@ fi
} }
} }
sed := fmt.Sprintf("s/output/files/") sed := fmt.Sprintf("s/output/files/")
fileName := compiledInfo.Name + "-" + compiledInfo.Version + "-" + compiledInfo.Arch + ".bpm" fileName := compiledInfo.Name + "-" + compiledInfo.GetFullVersion() + "-" + compiledInfo.Arch + ".bpm"
cmd := exec.Command("/usr/bin/fakeroot", "-i fakeroot_file", "tar", "-czvpf", fileName, "pkg.info", "output/", "--transform", sed) cmd := exec.Command("/usr/bin/fakeroot", "-i fakeroot_file", "tar", "-czvpf", fileName, "pkg.info", "output/", "--transform", sed)
if !BPMConfig.SilentCompilation { if !BPMConfig.SilentCompilation {
cmd.Stdin = os.Stdin cmd.Stdin = os.Stdin
@@ -893,7 +975,7 @@ fi
return nil, files return nil, files
} }
func InstallPackage(filename, rootDir string, force, binaryPkgFromSrc, skipCheck, keepTempDir bool) error { func InstallPackage(filename, rootDir string, verbose, force, binaryPkgFromSrc, skipCheck, keepTempDir bool) error {
if _, err := os.Stat(filename); os.IsNotExist(err) { if _, err := os.Stat(filename); os.IsNotExist(err) {
return err return err
} }
@@ -911,24 +993,27 @@ func InstallPackage(filename, rootDir string, force, binaryPkgFromSrc, skipCheck
if pkgInfo.Arch != "any" && pkgInfo.Arch != GetArch() { if pkgInfo.Arch != "any" && pkgInfo.Arch != GetArch() {
return errors.New("cannot install a package with a different architecture") return errors.New("cannot install a package with a different architecture")
} }
if unresolved := CheckDependencies(pkgInfo, rootDir); len(unresolved) != 0 { if unresolved := pkgInfo.CheckDependencies(pkgInfo.Type == "source", true, rootDir); len(unresolved) != 0 {
return errors.New("Could not resolve all dependencies. Missing " + strings.Join(unresolved, ", ")) return errors.New("the following dependencies are not installed: " + strings.Join(unresolved, ", "))
} }
} }
if pkgInfo.Type == "binary" { if pkgInfo.Type == "binary" {
err, i := extractPackage(pkgInfo, filename, rootDir) err, i := extractPackage(pkgInfo, verbose, filename, rootDir)
if err != nil { if err != nil {
return err return err
} }
files = i files = i
} else if pkgInfo.Type == "source" { } else if pkgInfo.Type == "source" {
err, i := compilePackage(pkgInfo, filename, rootDir, binaryPkgFromSrc, skipCheck, keepTempDir) if isSplitPackage(filename) {
return errors.New("BPM is unable to install split source packages")
}
err, i := compilePackage(pkgInfo, filename, rootDir, verbose, binaryPkgFromSrc, skipCheck, keepTempDir)
if err != nil { if err != nil {
return err return err
} }
files = i files = i
} else { } else {
return errors.New("Unknown package type: " + pkgInfo.Type) return errors.New("unknown package type: " + pkgInfo.Type)
} }
slices.Sort(files) slices.Sort(files)
slices.Reverse(files) slices.Reverse(files)
@@ -1004,7 +1089,7 @@ func InstallPackage(filename, rootDir string, force, binaryPkgFromSrc, skipCheck
} }
if len(filesDiff) != 0 { if len(filesDiff) != 0 {
fmt.Println("Removing obsolete files") fmt.Println("Removing obsolete files...")
var symlinks []string var symlinks []string
for _, f := range filesDiff { for _, f := range filesDiff {
f = path.Join(rootDir, f) f = path.Join(rootDir, f)
@@ -1030,14 +1115,18 @@ func InstallPackage(filename, rootDir string, force, binaryPkgFromSrc, skipCheck
return err return err
} }
if len(dir) == 0 { if len(dir) == 0 {
fmt.Println("Removing: " + f) if verbose {
fmt.Println("Removing: " + f)
}
err := os.Remove(f) err := os.Remove(f)
if err != nil { if err != nil {
return err return err
} }
} }
} else { } else {
fmt.Println("Removing: " + f) if verbose {
fmt.Println("Removing: " + f)
}
err := os.Remove(f) err := os.Remove(f)
if err != nil { if err != nil {
return err return err
@@ -1063,7 +1152,9 @@ func InstallPackage(filename, rootDir string, force, binaryPkgFromSrc, skipCheck
return err return err
} }
removals++ removals++
fmt.Println("Removing: " + f) if verbose {
fmt.Println("Removing: " + f)
}
} else if err != nil { } else if err != nil {
return err return err
} }
@@ -1125,84 +1216,75 @@ func GetSourceScript(filename string) (string, error) {
return "", errors.New("package does not contain a source.sh file") return "", errors.New("package does not contain a source.sh file")
} }
func CheckDependencies(pkgInfo *PackageInfo, rootDir string) []string { func (pkgInfo *PackageInfo) GetAllDependencies(checkMake, checkOptional bool) []string {
unresolved := make([]string, len(pkgInfo.Depends)) allDepends := make([]string, 0)
copy(unresolved, pkgInfo.Depends) allDepends = append(allDepends, pkgInfo.Depends...)
installedDir := path.Join(rootDir, "var/lib/bpm/installed/") if checkMake {
if _, err := os.Stat(installedDir); err != nil { allDepends = append(allDepends, pkgInfo.MakeDepends...)
return nil
} }
items, err := os.ReadDir(installedDir) if checkOptional {
if err != nil { allDepends = append(allDepends, pkgInfo.OptionalDepends...)
return nil
} }
return allDepends
for _, item := range items {
if !item.IsDir() {
continue
}
_, err := os.Stat(path.Join(installedDir, item.Name(), "/info"))
if err != nil {
return nil
}
bs, err := os.ReadFile(path.Join(installedDir, item.Name(), "/info"))
if err != nil {
return nil
}
info, err := ReadPackageInfo(string(bs), false)
if err != nil {
return nil
}
if slices.Contains(unresolved, info.Name) {
unresolved = stringSliceRemove(unresolved, info.Name)
}
for _, prov := range info.Provides {
if slices.Contains(unresolved, prov) {
unresolved = stringSliceRemove(unresolved, prov)
}
}
}
return unresolved
} }
func CheckMakeDependencies(pkgInfo *PackageInfo, rootDir string) []string { func (pkgInfo *PackageInfo) CheckDependencies(checkMake, checkOptional bool, rootDir string) []string {
unresolved := make([]string, len(pkgInfo.MakeDepends)) var ret []string
copy(unresolved, pkgInfo.MakeDepends) for _, dependency := range pkgInfo.Depends {
installedDir := path.Join(rootDir, "var/lib/bpm/installed/") if !IsPackageProvided(dependency, rootDir) {
if _, err := os.Stat(installedDir); err != nil { ret = append(ret, dependency)
return nil }
} }
items, err := os.ReadDir(installedDir) if checkMake {
if err != nil { for _, dependency := range pkgInfo.MakeDepends {
return nil if !IsPackageProvided(dependency, rootDir) {
} ret = append(ret, dependency)
for _, item := range items {
if !item.IsDir() {
continue
}
_, err := os.Stat(path.Join(installedDir, item.Name(), "/info"))
if err != nil {
return nil
}
bs, err := os.ReadFile(path.Join(installedDir, item.Name(), "/info"))
if err != nil {
return nil
}
info, err := ReadPackageInfo(string(bs), false)
if err != nil {
return nil
}
if slices.Contains(unresolved, info.Name) {
unresolved = stringSliceRemove(unresolved, info.Name)
}
for _, prov := range info.Provides {
if slices.Contains(unresolved, prov) {
unresolved = stringSliceRemove(unresolved, prov)
} }
} }
} }
return unresolved if checkOptional {
for _, dependency := range pkgInfo.OptionalDepends {
if !IsPackageProvided(dependency, rootDir) {
ret = append(ret, dependency)
}
}
}
return ret
}
func (pkgInfo *PackageInfo) CheckConflicts(rootDir string) []string {
var ret []string
for _, conflict := range pkgInfo.Conflicts {
if IsPackageInstalled(conflict, rootDir) {
ret = append(ret, conflict)
}
}
return ret
}
func (pkgInfo *PackageInfo) ResolveAll(resolved, unresolved *[]string, checkMake, checkOptional, ignoreInstalled bool, rootDir string) ([]string, []string) {
*unresolved = append(*unresolved, pkgInfo.Name)
for _, depend := range pkgInfo.GetAllDependencies(checkMake, checkOptional) {
if !slices.Contains(*resolved, depend) {
if slices.Contains(*unresolved, depend) || (ignoreInstalled && IsPackageInstalled(depend, rootDir)) {
continue
}
entry, _, err := GetRepositoryEntry(depend)
if err != nil {
if !slices.Contains(*unresolved, depend) {
*unresolved = append(*unresolved, depend)
}
continue
}
entry.Info.ResolveAll(resolved, unresolved, checkMake, checkOptional, ignoreInstalled, rootDir)
}
}
if !slices.Contains(*resolved, pkgInfo.Name) {
*resolved = append(*resolved, pkgInfo.Name)
}
*unresolved = stringSliceRemove(*unresolved, pkgInfo.Name)
return *resolved, *unresolved
} }
func IsPackageInstalled(pkg, rootDir string) bool { func IsPackageInstalled(pkg, rootDir string) bool {
@@ -1214,6 +1296,26 @@ func IsPackageInstalled(pkg, rootDir string) bool {
return true return true
} }
func IsPackageProvided(pkg, rootDir string) bool {
pkgs, err := GetInstalledPackages(rootDir)
if err != nil {
return false
}
for _, p := range pkgs {
if p == pkg {
return true
}
i := GetPackageInfo(p, rootDir, true)
if i == nil {
continue
}
if slices.Contains(i.Provides, pkg) {
return true
}
}
return false
}
func GetInstalledPackages(rootDir string) ([]string, error) { func GetInstalledPackages(rootDir string) ([]string, error) {
installedDir := path.Join(rootDir, "var/lib/bpm/installed/") installedDir := path.Join(rootDir, "var/lib/bpm/installed/")
items, err := os.ReadDir(installedDir) items, err := os.ReadDir(installedDir)
@@ -1270,14 +1372,14 @@ func GetPackageInfo(pkg, rootDir string, defaultValues bool) *PackageInfo {
if err != nil { if err != nil {
return nil return nil
} }
info, err := ReadPackageInfo(string(bs), defaultValues) info, err := ReadPackageInfo(string(bs))
if err != nil { if err != nil {
return nil return nil
} }
return info return info
} }
func RemovePackage(pkg, rootDir string) error { func RemovePackage(pkg string, verbose bool, rootDir string) error {
installedDir := path.Join(rootDir, "var/lib/bpm/installed/") installedDir := path.Join(rootDir, "var/lib/bpm/installed/")
pkgDir := path.Join(installedDir, pkg) pkgDir := path.Join(installedDir, pkg)
pkgInfo := GetPackageInfo(pkg, rootDir, false) pkgInfo := GetPackageInfo(pkg, rootDir, false)
@@ -1312,14 +1414,18 @@ func RemovePackage(pkg, rootDir string) error {
return err return err
} }
if len(dir) == 0 { if len(dir) == 0 {
fmt.Println("Removing: " + file) if verbose {
fmt.Println("Removing: " + file)
}
err := os.Remove(file) err := os.Remove(file)
if err != nil { if err != nil {
return err return err
} }
} }
} else { } else {
fmt.Println("Removing: " + file) if verbose {
fmt.Println("Removing: " + file)
}
err := os.Remove(file) err := os.Remove(file)
if err != nil { if err != nil {
return err return err
@@ -1345,7 +1451,9 @@ func RemovePackage(pkg, rootDir string) error {
return err return err
} }
removals++ removals++
fmt.Println("Removing: " + file) if verbose {
fmt.Println("Removing: " + file)
}
} else if err != nil { } else if err != nil {
return err return err
} }
@@ -1364,6 +1472,7 @@ func RemovePackage(pkg, rootDir string) error {
cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_NAME=%s", pkgInfo.Name)) cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_NAME=%s", pkgInfo.Name))
cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_DESC=%s", pkgInfo.Description)) cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_DESC=%s", pkgInfo.Description))
cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_VERSION=%s", pkgInfo.Version)) cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_VERSION=%s", pkgInfo.Version))
cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_REVISION=%d", pkgInfo.Revision))
cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_URL=%s", pkgInfo.Url)) cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_URL=%s", pkgInfo.Url))
cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_ARCH=%s", pkgInfo.Arch)) cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_ARCH=%s", pkgInfo.Arch))
depends := make([]string, len(pkgInfo.Depends)) depends := make([]string, len(pkgInfo.Depends))
@@ -1388,6 +1497,8 @@ func RemovePackage(pkg, rootDir string) error {
if err != nil { if err != nil {
return err return err
} }
fmt.Println("Removing: " + pkgDir) if verbose {
fmt.Println("Removing: " + pkgDir)
}
return nil return nil
} }
+166
View File
@@ -0,0 +1,166 @@
package utils
import (
"errors"
"gopkg.in/yaml.v3"
"io"
"net/http"
"net/url"
"os"
"path"
"strings"
)
type Repository struct {
Name string `yaml:"name"`
Source string `yaml:"source"`
Disabled *bool `yaml:"disabled"`
Entries map[string]*RepositoryEntry
}
type RepositoryEntry struct {
Info *PackageInfo `yaml:"info"`
Download string `yaml:"download"`
}
func (repo *Repository) ContainsPackage(pkg string) bool {
_, ok := repo.Entries[pkg]
return ok
}
func (repo *Repository) ReadLocalDatabase() error {
repoFile := "/var/lib/bpm/repositories/" + repo.Name + ".bpmdb"
if _, err := os.Stat(repoFile); err != nil {
return nil
}
bytes, err := os.ReadFile(repoFile)
if err != nil {
return err
}
data := string(bytes)
for _, b := range strings.Split(data, "---") {
entry := RepositoryEntry{
Info: &PackageInfo{
Name: "",
Description: "",
Version: "",
Revision: 1,
Url: "",
License: "",
Arch: "",
Type: "",
Keep: make([]string, 0),
Depends: make([]string, 0),
MakeDepends: make([]string, 0),
OptionalDepends: make([]string, 0),
Conflicts: make([]string, 0),
Provides: make([]string, 0),
},
Download: "",
}
err := yaml.Unmarshal([]byte(b), &entry)
if err != nil {
return err
}
repo.Entries[entry.Info.Name] = &entry
}
return nil
}
func (repo *Repository) SyncLocalDatabase() error {
repoFile := "/var/lib/bpm/repositories/" + repo.Name + ".bpmdb"
err := os.MkdirAll(path.Dir(repoFile), 0755)
if err != nil {
return err
}
u, err := url.JoinPath(repo.Source, "database.bpmdb")
if err != nil {
return err
}
resp, err := http.Get(u)
if err != nil {
return err
}
defer resp.Body.Close()
out, err := os.Create(repoFile)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, resp.Body)
return nil
}
func GetRepository(name string) *Repository {
for _, repo := range BPMConfig.Repositories {
if repo.Name == name {
return repo
}
}
return nil
}
func GetRepositoryEntry(str string) (*RepositoryEntry, *Repository, error) {
split := strings.Split(str, "/")
if len(split) == 1 {
pkgName := strings.TrimSpace(split[0])
if pkgName == "" {
return nil, nil, errors.New("could not find repository entry for this package")
}
for _, repo := range BPMConfig.Repositories {
if repo.ContainsPackage(pkgName) {
return repo.Entries[pkgName], repo, nil
}
}
return nil, nil, errors.New("could not find repository entry for this package")
} else if len(split) == 2 {
repoName := strings.TrimSpace(split[0])
pkgName := strings.TrimSpace(split[1])
if repoName == "" || pkgName == "" {
return nil, nil, errors.New("could not find repository entry for this package")
}
repo := GetRepository(repoName)
if repo == nil || !repo.ContainsPackage(pkgName) {
return nil, nil, errors.New("could not find repository entry for this package")
}
return repo.Entries[pkgName], repo, nil
} else {
return nil, nil, errors.New("could not find repository entry for this package")
}
}
func (repo *Repository) FetchPackage(pkg string) (string, error) {
if !repo.ContainsPackage(pkg) {
return "", errors.New("could not fetch package '" + pkg + "'")
}
entry := repo.Entries[pkg]
URL, err := url.JoinPath(repo.Source, entry.Download)
if err != nil {
return "", err
}
resp, err := http.Get(URL)
if err != nil {
return "", err
}
defer resp.Body.Close()
err = os.MkdirAll("/var/cache/bpm/packages/", 0755)
if err != nil {
return "", err
}
out, err := os.Create("/var/cache/bpm/packages/" + path.Base(entry.Download))
if err != nil {
return "", err
}
defer out.Close()
_, err = io.Copy(out, resp.Body)
return "/var/cache/bpm/packages/" + path.Base(entry.Download), nil
}