24 Commits
Author SHA1 Message Date
EnumDev dfdf38bad8 Replace variables in download filepath during compilation 2025-11-08 18:49:03 +02:00
EnumDev 52f25c8f3f Skip stripping if 'strip' binary is not in path 2025-11-01 12:08:26 +02:00
EnumDev 7e372eef91 Fix progress bar still overflowing 2025-10-30 13:57:40 +02:00
EnumDev b07a7f8778 Change printf message 2025-10-29 18:40:07 +02:00
EnumDev b2fc963389 Make progress bars look nicer 2025-10-29 18:38:52 +02:00
EnumDev b27e9fb0df Show optionally dependant packages in readable info 2025-10-29 18:02:42 +02:00
EnumDev 089bc61d58 Add more functionality to ignored packages 2025-10-28 19:50:46 +02:00
EnumDev fb821b3190 Remove bufio writer in databases.go 2025-10-28 19:20:36 +02:00
EnumDev 444b2032e2 Add automatic ELF binary stripping 2025-10-27 22:11:02 +02:00
EnumDev 70ba64ca6b Change file download message 2025-10-27 17:53:10 +02:00
EnumDev ed172f2805 Add progress bars 2025-10-26 20:06:55 +02:00
EnumDev f0ea57d500 Switch to fuzzy matching for searching through databases 2025-10-25 16:06:50 +03:00
EnumDev e923aefff9 Add 'sort' and 'reverse' flags to the 'list' subcommand 2025-10-25 15:20:23 +03:00
EnumDev c2d420d854 Show installed size for binary packages 2025-10-25 13:14:33 +03:00
EnumDev 2db01fb0d4 Disable tar verbose mode by default 2025-10-23 19:42:09 +03:00
EnumDev b451c5c320 Add shorthand for 'version' subcommand 2025-10-19 16:28:52 +03:00
EnumDev 27427d5654 Update version number to 0.6.1 2025-10-19 16:28:04 +03:00
EnumDev 04e966fef5 Hide URL and license from readable info if empty 2025-10-19 16:26:57 +03:00
EnumDev 8a7519628f Show output architecture in readable info 2025-10-19 16:24:05 +03:00
EnumDev 8986e24334 Add 'database' flag to the 'list' subcommand 2025-10-19 14:53:37 +03:00
EnumDev f066e26dc2 Add new flags to the 'list' subcommand 2025-10-19 14:28:14 +03:00
EnumDev bee23e2f56 Improve local package querying 2025-10-18 09:51:36 +03:00
EnumDev d56005e63e Improve database entry querying 2025-10-18 09:16:15 +03:00
EnumDev c9e16d6df1 Add 'keep' flag 2025-10-17 21:58:26 +03:00
14 changed files with 714 additions and 161 deletions
+8 -4
View File
@@ -1,12 +1,10 @@
module git.enumerated.dev/bubble-package-manager/bpm/src/bpm
go 1.23
toolchain go1.23.7
go 1.24.0
require (
git.enumerated.dev/bubble-package-manager/bpm/src/bpmlib v0.5.0
github.com/adrg/strutil v0.3.1
github.com/lithammer/fuzzysearch v1.1.8
github.com/spf13/pflag v1.0.10
)
@@ -15,5 +13,11 @@ replace git.enumerated.dev/bubble-package-manager/bpm/src/bpmlib => ../bpmlib
require (
github.com/drone/envsubst v1.0.3 // indirect
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/schollz/progressbar/v3 v3.18.0 // indirect
golang.org/x/sys v0.37.0 // indirect
golang.org/x/term v0.36.0 // indirect
golang.org/x/text v0.9.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+50 -11
View File
@@ -1,6 +1,5 @@
github.com/adrg/strutil v0.3.1 h1:OLvSS7CSJO8lBii4YmBt8jiK9QOtB9CzCzwl4Ic/Fz4=
github.com/adrg/strutil v0.3.1/go.mod h1:8h90y18QLrs11IBffcGX3NW/GFBXCMcNg4M7H6MspPA=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM=
github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g=
@@ -9,19 +8,59 @@ github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f h1:xt29M2T6STgldg+WEP51gGePQCsQvklmP2eIhPIBK3g=
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f/go.mod h1:i4sF0l1fFnY1aiw08QQSwVAFxHEm311Me3WsU/X7nL0=
github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/schollz/progressbar/v3 v3.18.0 h1:uXdoHABRFmNIjUfte/Ex7WtuyVslrw2wVPQmCN62HpA=
github.com/schollz/progressbar/v3 v3.18.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+145 -45
View File
@@ -16,8 +16,7 @@ import (
"git.enumerated.dev/bubble-package-manager/bpm/src/bpmlib"
"github.com/adrg/strutil"
"github.com/adrg/strutil/metrics"
"github.com/lithammer/fuzzysearch/fuzzy"
flag "github.com/spf13/pflag"
)
@@ -26,7 +25,7 @@ import (
/* A simple-to-use package manager */
/* ------------------------------------------------------- */
var bpmVer = "0.6.0"
var bpmVer = "0.6.1"
var currentFlagSet *flag.FlagSet
@@ -45,7 +44,7 @@ func main() {
}
switch subcommand {
case "version":
case "v", "version":
fmt.Println("Bubble Package Manager (BPM)")
fmt.Println("Version: " + bpmVer)
case "q", "query":
@@ -53,6 +52,7 @@ func main() {
currentFlagSet = flag.NewFlagSet("query", flag.ExitOnError)
currentFlagSet.StringP("root", "R", "/", "Operate on specified root directory")
currentFlagSet.BoolP("database", "d", false, "Show package information from remote databases")
currentFlagSet.BoolP("human-readable", "h", false, "Show package installed size in a human readable format")
setupFlagsAndHelp(currentFlagSet, fmt.Sprintf("bpm %s <options>", subcommand), "Show information on the specified packages", os.Args[2:])
showPackageInfo()
@@ -62,6 +62,13 @@ func main() {
currentFlagSet.StringP("root", "R", "/", "Operate on specified root directory")
currentFlagSet.BoolP("count", "c", false, "Show total package count")
currentFlagSet.BoolP("names", "n", false, "Show all package names")
currentFlagSet.BoolP("database", "d", false, "Show packages from remote databases")
currentFlagSet.Bool("manual", false, "Show packages installed as dependencies")
currentFlagSet.Bool("depends", false, "Show packages installed as dependencies")
currentFlagSet.Bool("make-depends", false, "Show packages installed as make dependencies")
currentFlagSet.String("sort", "", "Sort listed packages by 'name' or 'size")
currentFlagSet.Bool("reverse", false, "Reverse the order in which packages are listed")
currentFlagSet.BoolP("human-readable", "h", false, "Show package installed size in a human readable format")
setupFlagsAndHelp(currentFlagSet, fmt.Sprintf("bpm %s <options>", subcommand), "List packages", os.Args[2:])
showPackageList()
@@ -149,6 +156,7 @@ func main() {
currentFlagSet.BoolP("yes", "y", false, "Enter 'yes' in all prompts")
currentFlagSet.BoolP("depends", "d", false, "Install required dependencies for package compilation")
currentFlagSet.BoolP("skip-checks", "s", false, "Skip the check function in source.sh scripts")
currentFlagSet.BoolP("keep", "k", false, "Keep compilation files after successful package compilation")
currentFlagSet.BoolP("output-directory", "o", false, "Set the output directory for the binary packages")
currentFlagSet.Int("output-fd", -1, "Set the file descriptor output package names will be written to")
setupFlagsAndHelp(currentFlagSet, fmt.Sprintf("bpm %s <options>", subcommand), "Compile source packages and convert them to binary ones", os.Args[2:])
@@ -167,6 +175,7 @@ func showPackageInfo() {
// Get flags
rootDir, _ := currentFlagSet.GetString("root")
showDatabaseInfo, _ := currentFlagSet.GetBool("database")
showHumanReadableSize, _ := currentFlagSet.GetBool("human-readable")
// Get packages
packages := currentFlagSet.Args()
@@ -184,9 +193,6 @@ func showPackageInfo() {
}
for n, pkg := range packages {
var info *bpmlib.PackageInfo
isFile := false
showInstallationReason := false
if showDatabaseInfo {
var err error
var entry *bpmlib.BPMDatabaseEntry
@@ -198,25 +204,33 @@ func showPackageInfo() {
return
}
}
info = entry.Info
} else if stat, err := os.Stat(pkg); err == nil && !stat.IsDir() {
bpmpkg, err := bpmlib.ReadPackage(pkg)
if n != 0 {
fmt.Println()
}
fmt.Println(entry.CreateReadableInfo(rootDir, showHumanReadableSize))
return
}
var bpmpkg *bpmlib.BPMPackage
isFile := false
if stat, err := os.Stat(pkg); err == nil && !stat.IsDir() {
bpmpkg, err = bpmlib.ReadPackage(pkg)
if err != nil {
log.Printf("Error: could not read package: %s\n", err)
exitCode = 1
return
}
info = bpmpkg.PkgInfo
isFile = true
} else {
if isVirtual, p := bpmlib.IsVirtualPackage(pkg, rootDir); isVirtual {
info = bpmlib.GetPackageInfo(p, rootDir)
bpmpkg = bpmlib.GetPackage(p, rootDir)
} else {
info = bpmlib.GetPackageInfo(pkg, rootDir)
bpmpkg = bpmlib.GetPackage(pkg, rootDir)
}
showInstallationReason = true
}
if info == nil {
if bpmpkg == nil {
log.Printf("Error: package (%s) is not installed\n", pkg)
exitCode = 1
return
@@ -233,7 +247,7 @@ func showPackageInfo() {
}
fmt.Println("File: " + abs)
}
fmt.Println(bpmlib.CreateReadableInfo(true, true, true, showInstallationReason, info, rootDir))
fmt.Println(bpmpkg.CreateReadableInfo(rootDir, showHumanReadableSize))
}
}
@@ -242,6 +256,19 @@ func showPackageList() {
rootDir, _ := currentFlagSet.GetString("root")
showPkgCount, _ := currentFlagSet.GetBool("count")
showPkgNames, _ := currentFlagSet.GetBool("names")
showDatabase, _ := currentFlagSet.GetBool("database")
showManual, _ := currentFlagSet.GetBool("manual")
showDepends, _ := currentFlagSet.GetBool("depends")
showMakeDepends, _ := currentFlagSet.GetBool("make-depends")
sortPackages, _ := currentFlagSet.GetString("sort")
reversePackages, _ := currentFlagSet.GetBool("reverse")
showHumanReadableSize, _ := currentFlagSet.GetBool("human-readable")
if !isFlagSet(currentFlagSet, "manual") && !isFlagSet(currentFlagSet, "depends") && !isFlagSet(currentFlagSet, "make-depends") {
showManual = true
showDepends = true
showMakeDepends = true
}
// Read local databases
err := bpmlib.ReadLocalDatabaseFiles()
@@ -251,33 +278,109 @@ func showPackageList() {
return
}
packages, err := bpmlib.GetInstalledPackages(rootDir)
installedPackageNames, err := bpmlib.GetInstalledPackages(rootDir)
if err != nil {
log.Printf("Error: could not get installed packages: %s", err.Error())
exitCode = 1
return
}
if showPkgCount {
fmt.Println(len(packages))
} else if showPkgNames {
for _, pkg := range packages {
fmt.Println(pkg)
installedPackages := make([]*bpmlib.BPMPackage, len(installedPackageNames))
for i, pkgName := range installedPackageNames {
installedPackages[i] = bpmlib.GetPackage(pkgName, rootDir)
}
} else {
if len(packages) == 0 {
fmt.Println("No packages have been installed")
databaseEntries := make([]*bpmlib.BPMDatabaseEntry, 0)
for _, db := range bpmlib.BPMDatabases {
databaseEntries = append(databaseEntries, slices.Collect(maps.Values(db.Entries))...)
}
switch sortPackages {
case "", "name":
case "size":
slices.SortFunc(installedPackages, func(a, b *bpmlib.BPMPackage) int {
return int(b.GetInstalledSize() - a.GetInstalledSize())
})
slices.SortFunc(databaseEntries, func(a, b *bpmlib.BPMDatabaseEntry) int {
return int(b.InstalledSize - a.InstalledSize)
})
default:
log.Printf("Error: cannot sort by '%s'", sortPackages)
exitCode = 1
return
}
for n, pkg := range packages {
info := bpmlib.GetPackageInfo(pkg, rootDir)
if info == nil {
fmt.Printf("Package (%s) could not be found\n", pkg)
if reversePackages {
slices.Reverse(installedPackages)
slices.Reverse(databaseEntries)
}
if showPkgCount {
if showDatabase {
fmt.Println(len(databaseEntries))
} else {
fmt.Println(len(installedPackages))
}
} else if showPkgNames {
if showDatabase {
for _, entry := range databaseEntries {
fmt.Println(entry.Database.Name + "/" + entry.Info.Name)
}
} else {
for _, pkg := range installedPackages {
installationReason := bpmlib.GetInstallationReason(pkg.PkgInfo.Name, rootDir)
if installationReason == bpmlib.InstallationReasonManual && !showManual {
continue
} else if installationReason == bpmlib.InstallationReasonDependency && !showDepends {
continue
} else if installationReason == bpmlib.InstallationReasonMakeDependency && !showMakeDepends {
continue
} else if installationReason == bpmlib.InstallationReasonUnknown && (!showManual || !showDepends || !showMakeDepends) {
continue
}
fmt.Println(pkg.PkgInfo.Name)
}
}
} else {
if showDatabase {
if len(databaseEntries) == 0 {
fmt.Println("There are no database entries available")
return
}
for n, entry := range databaseEntries {
if n != 0 {
fmt.Println()
}
fmt.Println(bpmlib.CreateReadableInfo(true, true, true, true, info, rootDir))
fmt.Println(entry.CreateReadableInfo(rootDir, showHumanReadableSize))
}
} else {
if len(installedPackages) == 0 {
fmt.Println("No packages have been installed")
return
}
for n, bpmpkg := range installedPackages {
if bpmpkg == nil {
fmt.Printf("Package (%s) could not be found\n", installedPackageNames[n])
continue
}
installationReason := bpmlib.GetInstallationReason(bpmpkg.PkgInfo.Name, rootDir)
if installationReason == bpmlib.InstallationReasonManual && !showManual {
continue
} else if installationReason == bpmlib.InstallationReasonDependency && !showDepends {
continue
} else if installationReason == bpmlib.InstallationReasonMakeDependency && !showMakeDepends {
continue
} else if installationReason == bpmlib.InstallationReasonUnknown && (!showManual || !showDepends || !showMakeDepends) {
continue
}
if n != 0 {
fmt.Println()
}
fmt.Println(bpmpkg.CreateReadableInfo(rootDir, showHumanReadableSize))
}
}
}
}
@@ -300,23 +403,19 @@ func searchForPackages() {
}
for i, term := range searchTerms {
resultsMap := make(map[*bpmlib.PackageInfo]float64, 0)
// Loop through all packages
// Find matches
resultsMap := make(map[*bpmlib.BPMDatabaseEntry]int)
for _, db := range bpmlib.BPMDatabases {
for _, entry := range db.Entries {
// Calculate string similarity and add to map
similarity := strutil.Similarity(entry.Info.Name, term, metrics.NewSmithWatermanGotoh())
if similarity > 0.8 {
resultsMap[entry.Info] = similarity
match := fuzzy.RankMatchNormalizedFold(term, entry.Info.Name)
if match == -1 {
continue
}
similarity = strutil.Similarity(entry.Info.Description, term, metrics.NewSmithWatermanGotoh())
if similarity > 0.8 {
resultsMap[entry.Info] = similarity
}
resultsMap[entry] = match
}
}
if len(resultsMap) == 0 {
log.Printf("Error: no results for term (%s) were found\n", term)
exitCode = 1
@@ -326,7 +425,7 @@ func searchForPackages() {
// Sort results
results := slices.Collect(maps.Keys(resultsMap))
sort.Slice(results, func(i, j int) bool {
return resultsMap[results[i]] > resultsMap[results[j]]
return resultsMap[results[i]] < resultsMap[results[j]]
})
// Print results
@@ -336,7 +435,7 @@ func searchForPackages() {
fmt.Printf("Results for term (%s)\n", term)
for j := 0; j < 10 && j < len(results); j++ {
result := results[j]
fmt.Printf("%d) %s: %s (%s)\n", j+1, result.Name, result.Description, result.GetFullVersion())
fmt.Printf("%d) %s/%s: %s (%s)\n", j+1, result.Database.Name, result.Info.Name, result.Info.Description, result.Info.GetFullVersion())
}
}
}
@@ -942,6 +1041,7 @@ func compilePackage() {
rootDir, _ := currentFlagSet.GetString("root")
verbose, _ := currentFlagSet.GetBool("verbose")
yesAll, _ := currentFlagSet.GetBool("yes")
keepCompilationFiles, _ := currentFlagSet.GetBool("keep")
installSrcPkgDepends, _ := currentFlagSet.GetBool("depends")
skipChecks, _ := currentFlagSet.GetBool("skip-checks")
outputDirectory, _ := currentFlagSet.GetString("output-directory")
@@ -1041,7 +1141,7 @@ func compilePackage() {
} else {
// Ensure the required dependencies are installed
if len(unmetDepends) != 0 {
log.Printf("Error: could not resolve dependencies: the following dependencies were not found in any databases: " + strings.Join(unmetDepends, ", "))
log.Printf("Error: the following dependencies were not found in any databases: %s", strings.Join(unmetDepends, ", "))
exitCode = 1
return
}
@@ -1139,7 +1239,7 @@ func compilePackage() {
return
}
outputBpmPackages, err := bpmlib.CompileSourcePackage(sourcePackage, outputDirectory, skipChecks)
outputBpmPackages, err := bpmlib.CompileSourcePackage(sourcePackage, outputDirectory, skipChecks, keepCompilationFiles, verbose)
if err != nil {
// Remove unused packages
cleanupFunc()
+104 -14
View File
@@ -6,9 +6,11 @@ import (
"errors"
"fmt"
"io"
"io/fs"
"os"
"os/exec"
"path"
"path/filepath"
"slices"
"strconv"
"strings"
@@ -21,7 +23,7 @@ import (
var rootCompilationUID = "65534"
var rootCompilationGID = "65534"
func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bool) (outputBpmPackages map[string]string, err error) {
func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks, keepCompilationFiles, verbose bool) (outputBpmPackages map[string]string, err error) {
// Initialize map
outputBpmPackages = make(map[string]string)
@@ -122,7 +124,7 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
}
// Download files
err = downloadPackageFiles(bpmpkg.PkgInfo, tempDirectory)
err = downloadPackageFiles(bpmpkg.PkgInfo, tempDirectory, verbose)
if err != nil {
return nil, err
}
@@ -143,8 +145,8 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
"set -a\n"+ // Source and export functions and variables in source.sh script
". \"${BPM_WORKDIR}\"/source.sh\n"+
"set +a\n"+
"[[ $(type -t prepare) == \"function\" ]] && { echo \"Running prepare() function\"; bash -e -c 'cd \"$BPM_WORKDIR\" && prepare' || exit 1; }\n"+ // Run prepare() function if it exists
"[[ $(type -t build) == \"function\" ]] && { echo \"Running build() function\"; bash -e -c 'cd \"$BPM_SOURCE\" && build' || exit 1; }\n"+ // Run build() function if it exists
"[[ $(type -t prepare) == \"function\" ]] && { echo \"Running prepare() function...\"; bash -e -c 'cd \"$BPM_WORKDIR\" && prepare' || exit 1; }\n"+ // Run prepare() function if it exists
"[[ $(type -t build) == \"function\" ]] && { echo \"Running build() function...\"; bash -e -c 'cd \"$BPM_SOURCE\" && build' || exit 1; }\n"+ // Run build() function if it exists
"exit 0")
cmd.Dir = tempDirectory
cmd.Stdout = os.Stdout
@@ -165,7 +167,7 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
"set -a\n"+ // Source and export functions and variables in source.sh script
". \"${BPM_WORKDIR}\"/source.sh\n"+
"set +a\n"+
"[[ $(type -t check) == \"function\" ]] && { echo \"Running check() function\"; bash -e -c 'cd \"$BPM_SOURCE\" && check' || exit 1; }\n"+ // Run check() function if it exists
"[[ $(type -t check) == \"function\" ]] && { echo \"Running check() function...\"; bash -e -c 'cd \"$BPM_SOURCE\" && check' || exit 1; }\n"+ // Run check() function if it exists
"exit 0")
cmd.Dir = tempDirectory
cmd.Stdout = os.Stdout
@@ -215,9 +217,8 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
"set -a\n"+ // Source and export functions and variables in source.sh script
". \"${BPM_WORKDIR}\"/source.sh\n"+
"set +a\n"+
"echo \"Running "+packageFunctionName+"() function\"\n"+
"( cd \"$BPM_SOURCE\" && fakeroot -s \"$BPM_WORKDIR\"/fakeroot_file bash -e -c '"+packageFunctionName+"' ) || exit 1\n"+ // Run package() function
"fakeroot -i \"$BPM_WORKDIR\"/fakeroot_file find \"$BPM_OUTPUT\" -mindepth 1 -printf \"%P %#m %U %G %s\\n\" > \"$BPM_WORKDIR\"/pkg.files") // Create package file list
"echo \"Running "+packageFunctionName+"() function...\"\n"+
"( cd \"$BPM_SOURCE\" && fakeroot -s \"$BPM_WORKDIR\"/fakeroot_file bash -e -c '"+packageFunctionName+"' ) || exit 1\n") // Run package() function
cmd.Dir = tempDirectory
cmd.Stdout = os.Stdout
@@ -232,7 +233,81 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
return nil, err
}
// Strip all ELF binaries
if !slices.Contains(pkg.Options, "nostrip") {
if _, err := exec.LookPath("strip"); err != nil {
fmt.Println("Skip stripping ELF binaries. 'strip' not in path")
} else {
fmt.Println("Stripping ELF binaries...")
err = filepath.WalkDir(path.Join(tempDirectory, "output_"+pkg.Name), func(path string, d fs.DirEntry, err error) error {
cmd = exec.Command("file", "-bi", path)
cmd.Stderr = os.Stderr
output, err := cmd.Output()
if err != nil {
return err
}
mimetype := strings.Split(string(output), ";")[0]
if strings.HasPrefix(mimetype, "application/x-executable") {
cmd := exec.Command("strip", path)
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
return err
}
if verbose {
fmt.Printf("Stripped %s (%s)\n", path, mimetype)
}
} else if strings.HasPrefix(mimetype, "application/x-pie-executable") || strings.HasPrefix(mimetype, "application/x-sharedlib") {
cmd := exec.Command("strip", "--strip-unneeded", path)
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
return err
}
if verbose {
fmt.Printf("Stripped %s (%s)\n", path, mimetype)
}
} else if strings.HasPrefix(mimetype, "application/x-archive") {
cmd := exec.Command("strip", "--strip-debug", path)
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
return err
}
if verbose {
fmt.Printf("Stripped %s (%s)\n", path, mimetype)
}
}
return nil
})
if err != nil {
return nil, err
}
}
}
// Generate package file list
fmt.Println("Generating package file list...")
cmd = exec.Command("bash", "-c", "fakeroot -i \"$BPM_WORKDIR\"/fakeroot_file find \"$BPM_OUTPUT\" -mindepth 1 -printf \"%P %#m %U %G %s\\n\" > \"$BPM_WORKDIR\"/pkg.files")
cmd.Dir = tempDirectory
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Env = append(env, "BPM_OUTPUT="+path.Join(tempDirectory, "output_"+pkg.Name))
if os.Getuid() == 0 {
cmd.SysProcAttr = &syscall.SysProcAttr{}
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
}
err = cmd.Run()
if err != nil {
return nil, err
}
// Create gzip-compressed archive for the package files
fmt.Println("Generating compressed file archive...")
cmd = exec.Command("bash", "-c", fmt.Sprintf("find %s -printf \"%%P\\n\" | fakeroot -i %s/fakeroot_file tar -czf files.tar.gz --no-recursion -C %s -T -", "output_"+pkg.Name, tempDirectory, "output_"+pkg.Name))
cmd.Dir = tempDirectory
cmd.Stdout = os.Stdout
@@ -286,6 +361,7 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
bpmArchiveFiles = append(bpmArchiveFiles, packageScripts...) // Package scripts
// Create final BPM archive
fmt.Println("Generating final BPM archive...")
cmd = exec.Command("bash", "-c", "tar -cf final-archive.bpm --owner=0 --group=0 -C \"$BPM_WORKDIR\" "+strings.Join(bpmArchiveFiles, " "))
cmd.Dir = tempDirectory
cmd.Stdout = os.Stdout
@@ -334,10 +410,15 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
outputBpmPackages[pkgInfo.Name] = outputFilename
}
// Delete temporary directory
if !keepCompilationFiles {
os.RemoveAll(tempDirectory)
}
return outputBpmPackages, nil
}
func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string) error {
func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string, verbose bool) error {
for _, download := range pkgInfo.Downloads {
// Replace variables
replaceVars := func(s string) string {
@@ -357,6 +438,10 @@ func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string) error {
if err != nil {
return err
}
filepath, err := envsubst.Eval(strings.TrimSpace(download.Filepath), replaceVars)
if err != nil {
return err
}
extractTo, err := envsubst.Eval(strings.TrimSpace(download.ExtractTo), replaceVars)
if err != nil {
return err
@@ -377,12 +462,14 @@ func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string) error {
switch download.Type {
case "", "file":
filepath := path.Join(tempDirectory, path.Base(downloadUrl))
if download.Filepath != "" && download.Filepath[0] != '/' {
filepath = path.Join(tempDirectory, download.Filepath)
if filepath == "" {
filepath = path.Join(tempDirectory, path.Base(downloadUrl))
}
if filepath[0] != '/' {
filepath = path.Join(tempDirectory, filepath)
}
err := downloadFile(downloadUrl, filepath, 0644)
err := downloadFile("Downloading file "+path.Base(filepath), downloadUrl, filepath, 0644)
if err != nil {
return err
}
@@ -409,7 +496,10 @@ func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string) error {
}
if !download.NoExtract && (strings.Contains(filepath, ".tar") || strings.HasSuffix(filepath, ".tgz")) {
cmd := exec.Command("tar", "xvf", filepath, "--strip-components="+strconv.Itoa(download.ExtractStripComponents))
cmd := exec.Command("tar", "xf", filepath, "--strip-components="+strconv.Itoa(download.ExtractStripComponents))
if verbose {
cmd.Args[1] = "xvf"
}
cmd.Dir = tempDirectory
if extractTo != "" {
err := os.MkdirAll(extractTo, 0755)
+168 -4
View File
@@ -1,12 +1,17 @@
package bpmlib
import (
"bytes"
"errors"
"fmt"
"io"
"maps"
"net/http"
"net/url"
"os"
"path"
"slices"
"strconv"
"strings"
"gopkg.in/yaml.v3"
@@ -16,6 +21,7 @@ type BPMDatabase struct {
DatabaseVersion int `yaml:"database_version"`
Entries map[string]*BPMDatabaseEntry `yaml:"entries"`
VirtualPackages map[string][]string
Name string
Source string
}
@@ -54,6 +60,7 @@ func (db *configDatabase) ReadLocalDatabase() error {
// Initialize struct values
database.VirtualPackages = make(map[string][]string)
database.Name = db.Name
database.Source = db.Source
entriesToRemove := make([]string, 0)
@@ -131,13 +138,29 @@ func (db *configDatabase) SyncLocalDatabaseFile() error {
}
// Retrieve data from URL
buffer, err := retrieveUrlData(u)
req, err := http.NewRequest("GET", u, nil)
if err != nil {
return err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
// Create progress bar
bar := createProgressBar(resp.ContentLength, "Syncing "+db.Name, false)
// Copy data
var buffer bytes.Buffer
_, err = io.Copy(io.MultiWriter(&buffer, bar), resp.Body)
if err != nil {
return err
}
// Unmarshal data to ensure it is a valid BPM database
err = yaml.Unmarshal(buffer, &BPMDatabase{})
err = yaml.Unmarshal(buffer.Bytes(), &BPMDatabase{})
if err != nil {
return fmt.Errorf("could not decode database: %s", err)
}
@@ -155,7 +178,7 @@ func (db *configDatabase) SyncLocalDatabaseFile() error {
}
defer out.Close()
_, err = out.Write(buffer)
_, err = out.Write(buffer.Bytes())
return nil
}
@@ -240,10 +263,151 @@ func (db *BPMDatabase) FetchPackage(pkg string) (string, error) {
}
// Download package from url
err = downloadFile(u, path.Join("/var/cache/bpm/fetched/", path.Base(entry.Filepath)), 0644)
err = downloadFile("Downloading "+entry.Info.Name, u, path.Join("/var/cache/bpm/fetched/", path.Base(entry.Filepath)), 0644)
if err != nil {
return "", err
}
return path.Join("/var/cache/bpm/fetched/", path.Base(entry.Filepath)), nil
}
func (entry *BPMDatabaseEntry) GetEntryDependants() (dependants []string) {
dependantsMap := make(map[string][]string)
for _, db := range BPMDatabases {
for _, e := range db.Entries {
if slices.Contains(e.Info.Depends, entry.Info.Name) {
dependantsMap[e.Info.Name] = append(dependantsMap[e.Info.Name], e.Database.Name)
}
}
}
// Get keys
keySlice := slices.Collect(maps.Keys(dependantsMap))
slices.Sort(keySlice)
// Add all dependant entries to slice in alphabetical order
for _, entryName := range keySlice {
dbs := dependantsMap[entryName]
if len(dbs) > 1 {
for _, db := range dbs {
dependants = append(dependants, db+"/"+entryName)
}
} else {
dependants = append(dependants, entryName)
}
}
return dependants
}
func (entry *BPMDatabaseEntry) GetEntryOptionalDependants() (dependants []string) {
dependantsMap := make(map[string][]string)
for _, db := range BPMDatabases {
for _, e := range db.Entries {
if slices.Contains(e.Info.OptionalDepends, entry.Info.Name) {
dependantsMap[e.Info.Name] = append(dependantsMap[e.Info.Name], e.Database.Name)
}
}
}
// Get keys
keySlice := slices.Collect(maps.Keys(dependantsMap))
slices.Sort(keySlice)
// Add all dependant entries to slice in alphabetical order
for _, entryName := range keySlice {
dbs := dependantsMap[entryName]
if len(dbs) > 1 {
for _, db := range dbs {
dependants = append(dependants, db+"/"+entryName)
}
} else {
dependants = append(dependants, entryName)
}
}
return dependants
}
func (entry *BPMDatabaseEntry) CreateReadableInfo(rootDir string, humanReadableSize bool) string {
ret := make([]string, 0)
appendArray := func(label string, array []string, sort bool) {
if len(array) == 0 {
return
}
if sort {
// Sort array
slices.Sort(array)
}
ret = append(ret, fmt.Sprintf("%s: %s", label, strings.Join(array, ", ")))
}
ret = append(ret, "Name: "+entry.Info.Name)
ret = append(ret, "Database: "+entry.Database.Name)
ret = append(ret, "Description: "+entry.Info.Description)
ret = append(ret, "Version: "+entry.Info.GetFullVersion())
if entry.Info.Url != "" {
ret = append(ret, "URL: "+entry.Info.Url)
}
if entry.Info.License != "" {
ret = append(ret, "License: "+entry.Info.License)
}
ret = append(ret, "Architecture: "+entry.Info.Arch)
if entry.Info.Type == "source" && entry.Info.OutputArch != "" && entry.Info.OutputArch != GetArch() {
ret = append(ret, "Output architecture: "+entry.Info.OutputArch)
}
ret = append(ret, "Type: "+entry.Info.Type)
appendArray("Dependencies", entry.Info.Depends, true)
if entry.Info.Type == "source" {
appendArray("Make Dependencies", entry.Info.MakeDepends, true)
}
appendArray("Optional dependencies", entry.Info.OptionalDepends, true)
dependants := entry.GetEntryDependants()
if len(dependants) > 0 {
appendArray("Dependant packages", dependants, false)
}
optionalDependants := entry.GetEntryOptionalDependants()
if len(optionalDependants) > 0 {
appendArray("Optionally dependant packages", optionalDependants, false)
}
appendArray("Conflicting packages", entry.Info.Conflicts, true)
appendArray("Provided packages", entry.Info.Provides, true)
appendArray("Replaces packages", entry.Info.Replaces, true)
if entry.Info.Type == "source" && len(entry.Info.SplitPackages) != 0 {
splitPkgs := make([]string, len(entry.Info.SplitPackages))
for i, splitPkgInfo := range entry.Info.SplitPackages {
splitPkgs[i] = splitPkgInfo.Name
}
appendArray("Split Packages", splitPkgs, true)
}
if rootDir != "" && IsPackageInstalled(entry.Info.Name, rootDir) {
installationReason := GetInstallationReason(entry.Info.Name, rootDir)
var installationReasonString string
switch installationReason {
case InstallationReasonManual:
installationReasonString = "Manual"
case InstallationReasonDependency:
installationReasonString = "Dependency"
case InstallationReasonMakeDependency:
installationReasonString = "Make dependency"
default:
installationReasonString = "Unknown"
}
ret = append(ret, "Installation Reason: "+installationReasonString)
}
if entry.Info.Type == "binary" {
installedSize := int64(entry.InstalledSize)
var installedSizeStr string
if humanReadableSize {
installedSizeStr = bytesToHumanReadable(installedSize)
} else {
installedSizeStr = strconv.FormatInt(installedSize, 10)
}
ret = append(ret, "Installed size: "+installedSizeStr)
}
return strings.Join(ret, "\n")
}
+58 -18
View File
@@ -1,7 +1,6 @@
package bpmlib
import (
"errors"
"fmt"
"slices"
)
@@ -48,6 +47,12 @@ func (pkgInfo *PackageInfo) GetDependencies(includeMakeDepends, includeOptionalD
}
}
}
// Skip ignored packages
allDepends = slices.DeleteFunc(allDepends, func(depend pkgInstallationReason) bool {
return slices.Contains(MainBPMConfig.IgnorePackages, depend.PkgName)
})
return allDepends
}
@@ -170,19 +175,11 @@ func resolvePackageDependenciesFromDatabase(resolved *[]pkgInstallationReason, u
}
}
func GetPackageDependants(pkgName string, rootDir string) ([]string, error) {
ret := make([]string, 0)
// Get BPM package
pkg := GetPackage(pkgName, rootDir)
if pkg == nil {
return nil, errors.New("package not found: " + pkgName)
}
func (pkgInfo *PackageInfo) GetPackageDependants(rootDir string) (dependants []string) {
// Get installed package names
pkgs, err := GetInstalledPackages(rootDir)
if err != nil {
return nil, errors.New("could not get installed packages")
return nil
}
// Loop through all installed packages
@@ -190,33 +187,76 @@ func GetPackageDependants(pkgName string, rootDir string) ([]string, error) {
// Get installed BPM package
installedPkg := GetPackage(installedPkgName, rootDir)
if installedPkg == nil {
return nil, errors.New("package not found: " + installedPkgName)
return nil
}
// Skip iteration if comparing the same packages
if installedPkg.PkgInfo.Name == pkgName {
if installedPkg.PkgInfo.Name == pkgInfo.Name {
continue
}
// Add installed package to list if its dependencies include pkgName
if slices.ContainsFunc(installedPkg.PkgInfo.Depends, func(n string) bool {
return n == pkgName
return n == pkgInfo.Name
}) {
ret = append(ret, installedPkgName)
dependants = append(dependants, installedPkgName)
continue
}
// Loop through each virtual package
for _, vpkg := range pkg.PkgInfo.Provides {
for _, vpkg := range pkgInfo.Provides {
// Add installed package to list if its dependencies contain a provided virtual package
if slices.ContainsFunc(installedPkg.PkgInfo.Depends, func(n string) bool {
return n == vpkg
}) {
ret = append(ret, installedPkgName)
dependants = append(dependants, installedPkgName)
break
}
}
}
return ret, nil
return dependants
}
func (pkgInfo *PackageInfo) GetPackageOptionalDependants(rootDir string) (dependants []string) {
// Get installed package names
pkgs, err := GetInstalledPackages(rootDir)
if err != nil {
return nil
}
// Loop through all installed packages
for _, installedPkgName := range pkgs {
// Get installed BPM package
installedPkg := GetPackage(installedPkgName, rootDir)
if installedPkg == nil {
return nil
}
// Skip iteration if comparing the same packages
if installedPkg.PkgInfo.Name == pkgInfo.Name {
continue
}
// Add installed package to list if its optional dependencies include pkgName
if slices.ContainsFunc(installedPkg.PkgInfo.OptionalDepends, func(n string) bool {
return n == pkgInfo.Name
}) {
dependants = append(dependants, installedPkgName)
continue
}
// Loop through each virtual package
for _, vpkg := range pkgInfo.Provides {
// Add installed package to list if its optional dependencies contain a provided virtual package
if slices.ContainsFunc(installedPkg.PkgInfo.OptionalDepends, func(n string) bool {
return n == vpkg
}) {
dependants = append(dependants, installedPkgName)
break
}
}
}
return dependants
}
+12 -3
View File
@@ -221,11 +221,12 @@ func RemovePackages(rootDir string, force, cleanupDependencies bool, packages ..
// Get packages and their dependants
packageDepndants := make(map[string][]string, 0)
for _, action := range operation.Actions {
dependants, err := GetPackageDependants(action.(*RemovePackageAction).BpmPackage.PkgInfo.Name, rootDir)
if err != nil {
return nil, fmt.Errorf("could not get package dependants: %s", err)
// Skip package if ignored
if slices.Contains(MainBPMConfig.IgnorePackages, action.(*RemovePackageAction).BpmPackage.PkgInfo.Name) {
continue
}
dependants := action.(*RemovePackageAction).BpmPackage.PkgInfo.GetPackageDependants(rootDir)
packageDepndants[action.(*RemovePackageAction).BpmPackage.PkgInfo.Name] = dependants
}
@@ -238,6 +239,14 @@ func RemovePackages(rootDir string, force, cleanupDependencies bool, packages ..
packageDepndants[pkg] = required
}
// Remove dependant packages if ignored
for pkg, required := range packageDepndants {
required = slices.DeleteFunc(required, func(pkgName string) bool {
return slices.Contains(MainBPMConfig.IgnorePackages, pkgName)
})
packageDepndants[pkg] = required
}
// Remove empty keys from map
maps.DeleteFunc(packageDepndants, func(pkg string, required []string) bool {
return len(required) == 0
+9 -1
View File
@@ -1,9 +1,17 @@
module git.enumerated.dev/bubble-package-manager/bpm/src/bpmlib
go 1.23
go 1.24.0
require (
github.com/drone/envsubst v1.0.3
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f
github.com/schollz/progressbar/v3 v3.18.0
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/rivo/uniseg v0.4.7 // indirect
golang.org/x/sys v0.37.0 // indirect
golang.org/x/term v0.36.0 // indirect
)
+20
View File
@@ -1,9 +1,29 @@
github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM=
github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g=
github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f h1:xt29M2T6STgldg+WEP51gGePQCsQvklmP2eIhPIBK3g=
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f/go.mod h1:i4sF0l1fFnY1aiw08QQSwVAFxHEm311Me3WsU/X7nL0=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/schollz/progressbar/v3 v3.18.0 h1:uXdoHABRFmNIjUfte/Ex7WtuyVslrw2wVPQmCN62HpA=
github.com/schollz/progressbar/v3 v3.18.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+3
View File
@@ -75,6 +75,9 @@ func GetInstalledPackages(rootDir string) (ret []string, err error) {
ret = append(ret, bpmpkg.PkgInfo.Name)
}
// Sort packages
slices.Sort(ret)
return ret, nil
}
+13 -16
View File
@@ -9,29 +9,22 @@ import (
"strings"
)
func retrieveUrlData(u string) ([]byte, error) {
resp, err := http.Get(u)
if err != nil {
return nil, err
}
defer resp.Body.Close()
// Load data into byte buffer
buffer, err := io.ReadAll(resp.Body)
return buffer, nil
}
func downloadFile(u, filepath string, perm os.FileMode) error {
func downloadFile(barText, u, filepath string, perm os.FileMode) error {
if strings.HasSuffix(filepath, "/") {
return fmt.Errorf("Filepath must not end in '/'")
}
data, err := retrieveUrlData(u)
req, err := http.NewRequest("GET", u, nil)
if err != nil {
return err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
// Create parent directories
err = os.MkdirAll(path.Dir(filepath), 0755)
if err != nil {
@@ -45,8 +38,12 @@ func downloadFile(u, filepath string, perm os.FileMode) error {
}
defer file.Close()
// Create progress bar
bar := createProgressBar(resp.ContentLength, barText, false)
defer bar.Close()
// Copy data
_, err = file.Write(data)
_, err = io.Copy(io.MultiWriter(file, bar), resp.Body)
if err != nil {
return err
}
+14 -13
View File
@@ -176,10 +176,7 @@ func (operation *BPMOperation) RemoveNeededPackages() error {
}
for pkg, action := range removeActions {
dependants, err := GetPackageDependants(action.BpmPackage.PkgInfo.Name, operation.RootDir)
if err != nil {
return errors.New("could not get dependant packages for package (" + pkg + ")")
}
dependants := action.BpmPackage.PkgInfo.GetPackageDependants(operation.RootDir)
dependants = slices.DeleteFunc(dependants, func(d string) bool {
if _, ok := removeActions[d]; ok {
return true
@@ -232,7 +229,7 @@ func (operation *BPMOperation) Cleanup(cleanupMakeDepends bool) error {
keepPackages = append(keepPackages, pkg.Name)
resolved := pkg.GetDependenciesRecursive(!cleanupMakeDepends, operation.RootDir)
for _, value := range resolved {
if !slices.Contains(keepPackages, value) {
if !slices.Contains(keepPackages, value) && !slices.Contains(MainBPMConfig.IgnorePackages, value) {
keepPackages = append(keepPackages, value)
}
}
@@ -481,7 +478,7 @@ func (operation *BPMOperation) FetchPackages() (err error) {
var bpmpkg *BPMPackage
// Check if package has already been fetched from download link
if _, ok := fetchedPackages[entry.Filepath]; !ok {
if fetchedFilepath, ok := fetchedPackages[entry.Filepath]; !ok {
// Fetch package from database
fetchedPackage, err := entry.Database.FetchPackage(entry.Info.Name)
if err != nil {
@@ -496,16 +493,22 @@ func (operation *BPMOperation) FetchPackages() (err error) {
// Add fetched package to map
fetchedPackages[entry.Filepath] = fetchedPackage
fmt.Printf("Package (%s) was successfully fetched!\n", entry.Info.Name)
} else {
// Read fetched package
bpmpkg, err = ReadPackage(fetchedPackages[entry.Filepath])
bpmpkg, err = ReadPackage(fetchedFilepath)
if err != nil {
return fmt.Errorf("could not read package (%s): %s\n", entry.Info.Name, err)
}
fmt.Printf("Package (%s) was successfully fetched!\n", entry.Info.Name)
// Get size of fetched archive
stat, err := os.Stat(fetchedFilepath)
if err != nil {
return err
}
bar := createProgressBar(stat.Size(), "Downloading "+entry.Info.Name, false)
bar.Set64(stat.Size())
bar.Close()
}
if bpmpkg.PkgInfo.IsSplitPackage() {
@@ -592,7 +595,7 @@ func (operation *BPMOperation) Execute(verbose, force bool) (err error) {
// Compile source package if not compiled already
if _, ok := operation.compiledPackages[pkgNameToInstall]; !ok {
outputBpmPackages, err := CompileSourcePackage(value.File, compiledDir, false)
outputBpmPackages, err := CompileSourcePackage(value.File, compiledDir, false, false, verbose)
if err != nil {
return fmt.Errorf("could not compile source package (%s): %s\n", value.File, err)
}
@@ -625,8 +628,6 @@ func (operation *BPMOperation) Execute(verbose, force bool) (err error) {
if err != nil {
return fmt.Errorf("could not set installation reason for package (%s): %s\n", value.BpmPackage.PkgInfo.Name, err)
}
fmt.Printf("Package (%s) was successfully installed\n", bpmpkg.PkgInfo.Name)
}
}
fmt.Println("Operation complete!")
+67 -27
View File
@@ -43,6 +43,7 @@ type PackageInfo struct {
Conflicts []string `yaml:"conflicts,omitempty"`
Replaces []string `yaml:"replaces,omitempty"`
Provides []string `yaml:"provides,omitempty"`
Options []string `yaml:"options,omitempty"`
Downloads []PackageDownload `yaml:"downloads,omitempty"`
SplitPackages []*PackageInfo `yaml:"split_packages,omitempty"`
}
@@ -50,7 +51,7 @@ type PackageInfo struct {
type PackageDownload struct {
Url string `yaml:"url"`
Type string `yaml:"type,omitempty"`
Filepath string `yaml:"filepath,omitempty,omitempty"`
Filepath string `yaml:"filepath,omitempty"`
// Archive options
NoExtract bool `yaml:"no_extract,omitempty"`
@@ -434,6 +435,7 @@ func ReadPackageInfo(contents string) (*PackageInfo, error) {
Conflicts: make([]string, 0),
Replaces: make([]string, 0),
Provides: make([]string, 0),
Options: make([]string, 0),
Downloads: make([]PackageDownload, 0),
SplitPackages: make([]*PackageInfo, 0),
}
@@ -507,48 +509,60 @@ func ReadPackageInfo(contents string) (*PackageInfo, error) {
return pkgInfo, nil
}
func CreateReadableInfo(showArchitecture, showType, showPackageRelations, showInstallationReason bool, pkgInfo *PackageInfo, rootDir string) string {
func (bpmpkg *BPMPackage) CreateReadableInfo(rootDir string, humanReadableSize bool) string {
ret := make([]string, 0)
appendArray := func(label string, array []string) {
if len(array) == 0 {
return
}
// Sort array
slices.Sort(array)
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)
ret = append(ret, "Name: "+bpmpkg.PkgInfo.Name)
ret = append(ret, "Description: "+bpmpkg.PkgInfo.Description)
ret = append(ret, "Version: "+bpmpkg.PkgInfo.GetFullVersion())
if bpmpkg.PkgInfo.Url != "" {
ret = append(ret, "URL: "+bpmpkg.PkgInfo.Url)
}
if showType {
ret = append(ret, "Type: "+pkgInfo.Type)
if bpmpkg.PkgInfo.License != "" {
ret = append(ret, "License: "+bpmpkg.PkgInfo.License)
}
if showPackageRelations {
appendArray("Dependencies", pkgInfo.Depends)
if pkgInfo.Type == "source" {
appendArray("Make Dependencies", pkgInfo.MakeDepends)
ret = append(ret, "Architecture: "+bpmpkg.PkgInfo.Arch)
if bpmpkg.PkgInfo.Type == "source" && bpmpkg.PkgInfo.OutputArch != "" && bpmpkg.PkgInfo.OutputArch != GetArch() {
ret = append(ret, "Output architecture: "+bpmpkg.PkgInfo.Arch)
}
appendArray("Optional dependencies", pkgInfo.OptionalDepends)
dependants, err := GetPackageDependants(pkgInfo.Name, rootDir)
if err == nil {
ret = append(ret, "Type: "+bpmpkg.PkgInfo.Type)
appendArray("Dependencies", bpmpkg.PkgInfo.Depends)
if bpmpkg.PkgInfo.Type == "source" {
appendArray("Make Dependencies", bpmpkg.PkgInfo.MakeDepends)
}
appendArray("Optional dependencies", bpmpkg.PkgInfo.OptionalDepends)
dependants := bpmpkg.PkgInfo.GetPackageDependants(rootDir)
if len(dependants) > 0 {
appendArray("Dependant packages", dependants)
}
appendArray("Conflicting packages", pkgInfo.Conflicts)
appendArray("Provided packages", pkgInfo.Provides)
appendArray("Replaces packages", pkgInfo.Replaces)
optionalDependants := bpmpkg.PkgInfo.GetPackageOptionalDependants(rootDir)
if len(optionalDependants) > 0 {
appendArray("Optionally dependant packages", optionalDependants)
}
if pkgInfo.Type == "source" && len(pkgInfo.SplitPackages) != 0 {
splitPkgs := make([]string, len(pkgInfo.SplitPackages))
for i, splitPkgInfo := range pkgInfo.SplitPackages {
appendArray("Conflicting packages", bpmpkg.PkgInfo.Conflicts)
appendArray("Provided packages", bpmpkg.PkgInfo.Provides)
appendArray("Replaces packages", bpmpkg.PkgInfo.Replaces)
if bpmpkg.PkgInfo.Type == "source" && len(bpmpkg.PkgInfo.SplitPackages) != 0 {
splitPkgs := make([]string, len(bpmpkg.PkgInfo.SplitPackages))
for i, splitPkgInfo := range bpmpkg.PkgInfo.SplitPackages {
splitPkgs[i] = splitPkgInfo.Name
}
appendArray("Split Packages", splitPkgs)
}
if IsPackageInstalled(pkgInfo.Name, rootDir) && showInstallationReason {
installationReason := GetInstallationReason(pkgInfo.Name, rootDir)
if rootDir != "" && IsPackageInstalled(bpmpkg.PkgInfo.Name, rootDir) {
installationReason := GetInstallationReason(bpmpkg.PkgInfo.Name, rootDir)
var installationReasonString string
switch installationReason {
case InstallationReasonManual:
@@ -562,6 +576,16 @@ func CreateReadableInfo(showArchitecture, showType, showPackageRelations, showIn
}
ret = append(ret, "Installation Reason: "+installationReasonString)
}
if bpmpkg.PkgInfo.Type == "binary" {
installedSize := int64(bpmpkg.GetInstalledSize())
var installedSizeStr string
if humanReadableSize {
installedSizeStr = bytesToHumanReadable(installedSize)
} else {
installedSizeStr = strconv.FormatInt(installedSize, 10)
}
ret = append(ret, "Installed size: "+installedSizeStr)
}
return strings.Join(ret, "\n")
}
@@ -583,6 +607,10 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
return err
}
// Initialize progress bar
bar := createProgressBar(int64(bpmpkg.GetInstalledSize()), "Installing "+bpmpkg.PkgInfo.Name, verbose)
defer bar.Close()
tarballFile, err := readTarballFile(filename, "files.tar.gz")
if err != nil {
return err
@@ -594,6 +622,7 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
return err
}
packageFilesReader := tar.NewReader(archive)
for {
header, err := packageFilesReader.Next()
if err == io.EOF {
@@ -630,6 +659,7 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
if verbose {
fmt.Printf("Created directory %s (%o)\n", extractFilename, header.Mode)
}
bar.Add64(header.Size)
case tar.TypeReg:
skip := false
if _, err := os.Stat(extractFilename); err == nil {
@@ -686,6 +716,7 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
if verbose {
fmt.Printf("Created File: %s (%o)\n", extractFilename, header.Mode)
}
bar.Add64(header.Size)
case tar.TypeSymlink:
err := os.Remove(extractFilename)
if err != nil && !os.IsNotExist(err) {
@@ -700,6 +731,7 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
if verbose {
fmt.Println("Created Symlink: " + extractFilename + " -> " + header.Linkname)
}
bar.Add64(header.Size)
case tar.TypeLink:
if verbose {
fmt.Println("Detected Hard Link: " + extractFilename + " -> " + path.Join(rootDir, strings.TrimPrefix(header.Linkname, "files/")))
@@ -709,6 +741,7 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
if err != nil && !os.IsNotExist(err) {
return err
}
bar.Add64(header.Size)
default:
return errors.New("unknown type (" + strconv.Itoa(int(header.Typeflag)) + ") in " + extractFilename)
}
@@ -961,8 +994,11 @@ func removePackage(pkg string, verbose bool, rootDir string) error {
log.Printf("Warning: %s\n", err)
}
// Get BPM package
bpmpkg := GetPackage(pkg, rootDir)
// Fetching and reversing package file entry list
fileEntries := GetPackage(pkg, rootDir).PkgFiles
fileEntries := bpmpkg.PkgFiles
sort.Slice(fileEntries, func(i, j int) bool {
return fileEntries[i].Path < fileEntries[j].Path
})
@@ -972,8 +1008,12 @@ func removePackage(pkg string, verbose bool, rootDir string) error {
return err
}
bar := createProgressBar(int64(bpmpkg.GetInstalledSize()), "Removing "+bpmpkg.PkgInfo.Name, verbose)
defer bar.Close()
// Removing package files
for _, entry := range fileEntries {
bar.Add64(int64(entry.SizeInBytes))
file := path.Join(rootDir, entry.Path)
lstat, err := os.Lstat(file)
if os.IsNotExist(err) {
+38
View File
@@ -2,10 +2,14 @@ package bpmlib
import (
"fmt"
"io"
"math"
"os"
"path"
"syscall"
"time"
"github.com/schollz/progressbar/v3"
)
type BPMLock struct {
@@ -64,6 +68,40 @@ func GetArch() string {
return string(byteString[:indexLength])
}
func createProgressBar(max int64, description string, hideBar bool) *progressbar.ProgressBar {
var output io.Writer
if hideBar {
output = io.Discard
} else {
output = os.Stderr
}
if len(description) < 40 {
for i := len(description); i < 40; i++ {
description += " "
}
}
if len(description) > 40 {
overflow := len(description) - 37
description = description[:len(description)-overflow] + "..."
}
return progressbar.NewOptions64(max,
progressbar.OptionSetDescription(description),
progressbar.OptionSetWriter(output),
progressbar.OptionShowBytes(true),
progressbar.OptionShowTotalBytes(true),
progressbar.OptionSetWidth(20),
progressbar.OptionThrottle(65*time.Millisecond),
progressbar.OptionShowCount(),
progressbar.OptionOnCompletion(func() {
fmt.Fprint(output, "\n")
}),
progressbar.OptionSpinnerType(14),
progressbar.OptionSetRenderBlankState(true),
progressbar.OptionSetTheme(progressbar.ThemeASCII))
}
func stringSliceRemove(s []string, r string) []string {
for i, v := range s {
if v == r {