46 Commits
Author SHA1 Message Date
EnumDev f23537ba7d Fix typo 2026-01-03 13:46:43 +02:00
EnumDev 6e9f16837c Fix split packages with the same name as the main package being removed from the database 2026-01-02 20:08:10 +02:00
EnumDev 271e87cb77 Exit with exit code 1 if path is not owned by any package 2026-01-02 18:54:09 +02:00
EnumDev 25ccc208d7 Change module names 2026-01-01 10:20:33 +02:00
EnumDev 9c7e20d27d Remove need for config module 2026-01-01 10:15:23 +02:00
EnumDev 6206fdbf43 Add 'BPM_PKG_URL' variable in package compilation 2025-12-31 09:10:36 +02:00
EnumDev 9e48d97dc8 Add 'allow-downgrades' flag to 'update' subcommand 2025-12-28 18:30:17 +02:00
EnumDev 08a4e24889 Add confirmation prompt for syncing in 'update' subcomamnd 2025-12-26 13:05:25 +02:00
EnumDev 4a32df6335 Fix possible panic when updating packages 2025-12-26 13:02:58 +02:00
EnumDev 548b2476bd Reduce memory usage for the 'list' subcommand 2025-12-26 12:34:08 +02:00
EnumDev a660039be1 Use package info instead of BPMPackage for the local package information map 2025-12-26 11:41:07 +02:00
EnumDev 44c2678ef9 Show optional dependencies after install or update operation 2025-12-17 15:36:08 +02:00
EnumDev 2a7c6ce5d9 Count size in signed 64-bit integers 2025-12-17 13:43:23 +02:00
EnumDev f98760d1dd Rearrange function order in utils.go 2025-12-17 12:18:07 +02:00
EnumDev 2ba78f16db Remove duplicate strings in packages slice from install and remove operations 2025-12-17 12:16:45 +02:00
EnumDev 04a71b573b Improve conflict checking 2025-12-17 12:07:44 +02:00
EnumDev aa229af8df Exit with exit code 1 if no valid subcommand is specified 2025-12-12 22:02:44 +02:00
EnumDev 9912b173ff Move pre-* package script execution into start of 'installPackage' function 2025-12-08 21:44:29 +02:00
EnumDev 879c832f60 Set BPM version using git 2025-12-08 17:04:15 +02:00
EnumDev 2c91a9332b Add 'vercmp' subcommand for package version comparisons in scripts 2025-12-08 16:08:55 +02:00
EnumDev 1895fabfb0 Do not strip files that use ELF as a container format 2025-12-05 20:16:11 +02:00
EnumDev 0dfb66c6c0 Bind mount etc/resolv.conf in package scripts for internet access 2025-12-05 18:02:54 +02:00
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
15 changed files with 1082 additions and 325 deletions
+2 -1
View File
@@ -7,12 +7,13 @@ SYSCONFDIR := $(PREFIX)/etc
GO ?= go GO ?= go
# Build-time variables # Build-time variables
VERSION ?= $(shell git describe --tags --dirty)
ROOT_COMPILATION_UID ?= 65534 ROOT_COMPILATION_UID ?= 65534
ROOT_COMPILATION_GID ?= 65534 ROOT_COMPILATION_GID ?= 65534
build: build:
mkdir -p build mkdir -p build
cd src/bpm; $(GO) build $(GOFLAGS) -ldflags "-w -X 'git.enumerated.dev/bubble-package-manager/bpm/src/bpmlib.rootCompilationUID=$(ROOT_COMPILATION_UID)' -X 'git.enumerated.dev/bubble-package-manager/bpm/src/bpmlib.rootCompilationGID=$(ROOT_COMPILATION_GID)'" -o ../../build/bpm git.enumerated.dev/bubble-package-manager/bpm/src/bpm cd src/bpm; $(GO) build $(GOFLAGS) -ldflags "-w -X 'main.BpmVersion=$(VERSION)' -X 'github.com/EnumeratedDev/bpm/src/bpmlib.rootCompilationUID=$(ROOT_COMPILATION_UID)' -X 'github.com/EnumeratedDev/bpm/src/bpmlib.rootCompilationGID=$(ROOT_COMPILATION_GID)'" -o ../../build/bpm bpm
install: build/bpm config/ install: build/bpm config/
# Create directory # Create directory
+11 -7
View File
@@ -1,19 +1,23 @@
module git.enumerated.dev/bubble-package-manager/bpm/src/bpm module bpm
go 1.23 go 1.24.0
toolchain go1.23.7
require ( require (
git.enumerated.dev/bubble-package-manager/bpm/src/bpmlib v0.5.0 github.com/EnumeratedDev/bpm/src/bpmlib v0.0.0
github.com/adrg/strutil v0.3.1 github.com/lithammer/fuzzysearch v1.1.8
github.com/spf13/pflag v1.0.10 github.com/spf13/pflag v1.0.10
) )
replace git.enumerated.dev/bubble-package-manager/bpm/src/bpmlib => ../bpmlib replace github.com/EnumeratedDev/bpm/src/bpmlib => ../bpmlib
require ( require (
github.com/drone/envsubst v1.0.3 // indirect github.com/drone/envsubst v1.0.3 // indirect
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f // 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 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/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM=
github.com/adrg/strutil v0.3.1/go.mod h1:8h90y18QLrs11IBffcGX3NW/GFBXCMcNg4M7H6MspPA= github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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/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 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/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 h1:xt29M2T6STgldg+WEP51gGePQCsQvklmP2eIhPIBK3g=
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f/go.mod h1:i4sF0l1fFnY1aiw08QQSwVAFxHEm311Me3WsU/X7nL0= 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 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/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
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.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 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=
+248 -56
View File
@@ -14,10 +14,9 @@ import (
"sort" "sort"
"strings" "strings"
"git.enumerated.dev/bubble-package-manager/bpm/src/bpmlib" "github.com/EnumeratedDev/bpm/src/bpmlib"
"github.com/adrg/strutil" "github.com/lithammer/fuzzysearch/fuzzy"
"github.com/adrg/strutil/metrics"
flag "github.com/spf13/pflag" flag "github.com/spf13/pflag"
) )
@@ -26,7 +25,7 @@ import (
/* A simple-to-use package manager */ /* A simple-to-use package manager */
/* ------------------------------------------------------- */ /* ------------------------------------------------------- */
var bpmVer = "0.6.0" var BpmVersion = "dev"
var currentFlagSet *flag.FlagSet var currentFlagSet *flag.FlagSet
@@ -38,21 +37,24 @@ func main() {
log.Fatalf("Error: could not read BPM config: %s", err) log.Fatalf("Error: could not read BPM config: %s", err)
} }
// Execute subcommand // Show usage if no arguments specified
subcommand := "help" if len(os.Args) == 1 {
if len(os.Args) >= 2 { printUsage()
subcommand = os.Args[1] return
} }
subcommand := os.Args[1]
switch subcommand { switch subcommand {
case "version": case "v", "version":
fmt.Println("Bubble Package Manager (BPM)") fmt.Println("Bubble Package Manager (BPM)")
fmt.Println("Version: " + bpmVer) fmt.Println("Version: " + BpmVersion)
case "q", "query": case "q", "query":
// Setup flags and help // Setup flags and help
currentFlagSet = flag.NewFlagSet("query", flag.ExitOnError) currentFlagSet = flag.NewFlagSet("query", flag.ExitOnError)
currentFlagSet.StringP("root", "R", "/", "Operate on specified root directory") currentFlagSet.StringP("root", "R", "/", "Operate on specified root directory")
currentFlagSet.BoolP("database", "d", false, "Show package information from remote databases") 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:]) setupFlagsAndHelp(currentFlagSet, fmt.Sprintf("bpm %s <options>", subcommand), "Show information on the specified packages", os.Args[2:])
showPackageInfo() showPackageInfo()
@@ -62,6 +64,13 @@ func main() {
currentFlagSet.StringP("root", "R", "/", "Operate on specified root directory") currentFlagSet.StringP("root", "R", "/", "Operate on specified root directory")
currentFlagSet.BoolP("count", "c", false, "Show total package count") currentFlagSet.BoolP("count", "c", false, "Show total package count")
currentFlagSet.BoolP("names", "n", false, "Show all package names") 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:]) setupFlagsAndHelp(currentFlagSet, fmt.Sprintf("bpm %s <options>", subcommand), "List packages", os.Args[2:])
showPackageList() showPackageList()
@@ -129,6 +138,7 @@ func main() {
currentFlagSet.BoolP("force", "f", false, "Bypass warnings during package update") currentFlagSet.BoolP("force", "f", false, "Bypass warnings during package update")
currentFlagSet.BoolP("yes", "y", false, "Enter 'yes' in all prompts") currentFlagSet.BoolP("yes", "y", false, "Enter 'yes' in all prompts")
currentFlagSet.BoolP("no-sync", "n", false, "Do not sync databases") currentFlagSet.BoolP("no-sync", "n", false, "Do not sync databases")
currentFlagSet.Bool("allow-downgrades", false, "Allow package downgrades")
currentFlagSet.BoolP("optional", "o", false, "Install all optional dependencies") currentFlagSet.BoolP("optional", "o", false, "Install all optional dependencies")
setupFlagsAndHelp(currentFlagSet, fmt.Sprintf("bpm %s <options>", subcommand), "Update installed packages", os.Args[2:]) setupFlagsAndHelp(currentFlagSet, fmt.Sprintf("bpm %s <options>", subcommand), "Update installed packages", os.Args[2:])
@@ -149,13 +159,20 @@ func main() {
currentFlagSet.BoolP("yes", "y", false, "Enter 'yes' in all prompts") currentFlagSet.BoolP("yes", "y", false, "Enter 'yes' in all prompts")
currentFlagSet.BoolP("depends", "d", false, "Install required dependencies for package compilation") 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("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.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") 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:]) setupFlagsAndHelp(currentFlagSet, fmt.Sprintf("bpm %s <options>", subcommand), "Compile source packages and convert them to binary ones", os.Args[2:])
compilePackage() compilePackage()
case "p", "vercmp":
currentFlagSet = flag.NewFlagSet("compile", flag.ExitOnError)
setupFlagsAndHelp(currentFlagSet, fmt.Sprintf("bpm %s <options>", subcommand), "Compare two version numbers", os.Args[2:])
compareVersions()
default: default:
listSubcommands() printUsage()
exitCode = 1
} }
if exitCode != 0 { if exitCode != 0 {
@@ -167,6 +184,7 @@ func showPackageInfo() {
// Get flags // Get flags
rootDir, _ := currentFlagSet.GetString("root") rootDir, _ := currentFlagSet.GetString("root")
showDatabaseInfo, _ := currentFlagSet.GetBool("database") showDatabaseInfo, _ := currentFlagSet.GetBool("database")
showHumanReadableSize, _ := currentFlagSet.GetBool("human-readable")
// Get packages // Get packages
packages := currentFlagSet.Args() packages := currentFlagSet.Args()
@@ -184,9 +202,6 @@ func showPackageInfo() {
} }
for n, pkg := range packages { for n, pkg := range packages {
var info *bpmlib.PackageInfo
isFile := false
showInstallationReason := false
if showDatabaseInfo { if showDatabaseInfo {
var err error var err error
var entry *bpmlib.BPMDatabaseEntry var entry *bpmlib.BPMDatabaseEntry
@@ -198,25 +213,33 @@ func showPackageInfo() {
return return
} }
} }
info = entry.Info
} else if stat, err := os.Stat(pkg); err == nil && !stat.IsDir() { if n != 0 {
bpmpkg, err := bpmlib.ReadPackage(pkg) 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 { if err != nil {
log.Printf("Error: could not read package: %s\n", err) log.Printf("Error: could not read package: %s\n", err)
exitCode = 1 exitCode = 1
return return
} }
info = bpmpkg.PkgInfo
isFile = true isFile = true
} else { } else {
if isVirtual, p := bpmlib.IsVirtualPackage(pkg, rootDir); isVirtual { if isVirtual, p := bpmlib.IsVirtualPackage(pkg, rootDir); isVirtual {
info = bpmlib.GetPackageInfo(p, rootDir) bpmpkg = bpmlib.GetPackage(p, rootDir)
} else { } 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) log.Printf("Error: package (%s) is not installed\n", pkg)
exitCode = 1 exitCode = 1
return return
@@ -233,7 +256,14 @@ func showPackageInfo() {
} }
fmt.Println("File: " + abs) fmt.Println("File: " + abs)
} }
fmt.Println(bpmlib.CreateReadableInfo(true, true, true, showInstallationReason, info, rootDir)) fmt.Println(bpmpkg.PkgInfo.CreateReadableInfo(rootDir))
if bpmpkg.PkgInfo.Type == "binary" {
if !showHumanReadableSize {
fmt.Printf("Installed size: %d\n", bpmpkg.GetInstalledSize())
} else {
fmt.Printf("Installed size: %s\n", bpmlib.BytesToHumanReadable(bpmpkg.GetInstalledSize()))
}
}
} }
} }
@@ -242,6 +272,19 @@ func showPackageList() {
rootDir, _ := currentFlagSet.GetString("root") rootDir, _ := currentFlagSet.GetString("root")
showPkgCount, _ := currentFlagSet.GetBool("count") showPkgCount, _ := currentFlagSet.GetBool("count")
showPkgNames, _ := currentFlagSet.GetBool("names") 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 // Read local databases
err := bpmlib.ReadLocalDatabaseFiles() err := bpmlib.ReadLocalDatabaseFiles()
@@ -251,33 +294,127 @@ func showPackageList() {
return return
} }
packages, err := bpmlib.GetInstalledPackages(rootDir) installedPackageNames, err := bpmlib.GetInstalledPackages(rootDir)
if err != nil { if err != nil {
log.Printf("Error: could not get installed packages: %s", err.Error()) log.Printf("Error: could not get installed packages: %s", err.Error())
exitCode = 1 exitCode = 1
return return
} }
installedPackages := make([]struct {
pkgInfo bpmlib.PackageInfo
installedSize int64
}, len(installedPackageNames))
for i, pkgName := range installedPackageNames {
pkgInfo := *bpmlib.GetPackageInfo(pkgName, rootDir)
installedSize := bpmlib.GetPackage(pkgName, rootDir).GetInstalledSize()
installedPackages[i] = struct {
pkgInfo bpmlib.PackageInfo
installedSize int64
}{
pkgInfo: pkgInfo,
installedSize: installedSize,
}
}
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 struct {
pkgInfo bpmlib.PackageInfo
installedSize int64
}) int {
return int(b.installedSize - a.installedSize)
})
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
}
if reversePackages {
slices.Reverse(installedPackages)
slices.Reverse(databaseEntries)
}
if showPkgCount { if showPkgCount {
fmt.Println(len(packages)) if showDatabase {
fmt.Println(len(databaseEntries))
} else {
fmt.Println(len(installedPackages))
}
} else if showPkgNames { } else if showPkgNames {
for _, pkg := range packages { if showDatabase {
fmt.Println(pkg) 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 { } else {
if len(packages) == 0 { if showDatabase {
fmt.Println("No packages have been installed") if len(databaseEntries) == 0 {
return fmt.Println("There are no database entries available")
} return
for n, pkg := range packages {
info := bpmlib.GetPackageInfo(pkg, rootDir)
if info == nil {
fmt.Printf("Package (%s) could not be found\n", pkg)
continue
} }
if n != 0 { for n, entry := range databaseEntries {
fmt.Println() if n != 0 {
fmt.Println()
}
fmt.Println(entry.CreateReadableInfo(rootDir, showHumanReadableSize))
}
} else {
if len(installedPackages) == 0 {
fmt.Println("No packages have been installed")
return
}
for n, 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
}
if n != 0 {
fmt.Println()
}
fmt.Println(pkg.pkgInfo.CreateReadableInfo(rootDir))
if pkg.pkgInfo.Type == "binary" {
if !showHumanReadableSize {
fmt.Printf("Installed size: %d\n", pkg.installedSize)
} else {
fmt.Printf("Installed size: %s\n", bpmlib.BytesToHumanReadable(pkg.installedSize))
}
}
} }
fmt.Println(bpmlib.CreateReadableInfo(true, true, true, true, info, rootDir))
} }
} }
} }
@@ -300,23 +437,19 @@ func searchForPackages() {
} }
for i, term := range searchTerms { for i, term := range searchTerms {
resultsMap := make(map[*bpmlib.PackageInfo]float64, 0) // Find matches
resultsMap := make(map[*bpmlib.BPMDatabaseEntry]int)
// Loop through all packages
for _, db := range bpmlib.BPMDatabases { for _, db := range bpmlib.BPMDatabases {
for _, entry := range db.Entries { for _, entry := range db.Entries {
// Calculate string similarity and add to map match := fuzzy.RankMatchNormalizedFold(term, entry.Info.Name)
similarity := strutil.Similarity(entry.Info.Name, term, metrics.NewSmithWatermanGotoh()) if match == -1 {
if similarity > 0.8 {
resultsMap[entry.Info] = similarity
continue continue
} }
similarity = strutil.Similarity(entry.Info.Description, term, metrics.NewSmithWatermanGotoh())
if similarity > 0.8 { resultsMap[entry] = match
resultsMap[entry.Info] = similarity
}
} }
} }
if len(resultsMap) == 0 { if len(resultsMap) == 0 {
log.Printf("Error: no results for term (%s) were found\n", term) log.Printf("Error: no results for term (%s) were found\n", term)
exitCode = 1 exitCode = 1
@@ -326,7 +459,7 @@ func searchForPackages() {
// Sort results // Sort results
results := slices.Collect(maps.Keys(resultsMap)) results := slices.Collect(maps.Keys(resultsMap))
sort.Slice(results, func(i, j int) bool { sort.Slice(results, func(i, j int) bool {
return resultsMap[results[i]] > resultsMap[results[j]] return resultsMap[results[i]] < resultsMap[results[j]]
}) })
// Print results // Print results
@@ -336,7 +469,7 @@ func searchForPackages() {
fmt.Printf("Results for term (%s)\n", term) fmt.Printf("Results for term (%s)\n", term)
for j := 0; j < 10 && j < len(results); j++ { for j := 0; j < 10 && j < len(results); j++ {
result := 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())
} }
} }
} }
@@ -471,6 +604,9 @@ func installPackages() {
} }
} }
// Get optional dependencies
optionalDepends := operation.GetOptionalDependencies()
// Execute operation // Execute operation
err = operation.Execute(verbose, force) err = operation.Execute(verbose, force)
if err != nil { if err != nil {
@@ -487,6 +623,18 @@ func installPackages() {
exitCode = 1 exitCode = 1
return return
} }
// Show optional dependencies
if !installOptional && len(optionalDepends) != 0 {
// List optional dependencies
fmt.Println("The following optional dependenices have been discovered:")
for dependant, depends := range optionalDepends {
fmt.Printf("%s: \n", dependant)
for _, depend := range depends {
fmt.Printf(" - %s\n", depend)
}
}
}
} }
func removePackages() { func removePackages() {
@@ -748,6 +896,7 @@ func updatePackages() {
force, _ := currentFlagSet.GetBool("force") force, _ := currentFlagSet.GetBool("force")
yesAll, _ := currentFlagSet.GetBool("yes") yesAll, _ := currentFlagSet.GetBool("yes")
noSync, _ := currentFlagSet.GetBool("no-sync") noSync, _ := currentFlagSet.GetBool("no-sync")
allowDowngrades, _ := currentFlagSet.GetBool("allow-downgrades")
installOptional, _ := currentFlagSet.GetBool("optional") installOptional, _ := currentFlagSet.GetBool("optional")
// Check for required permissions // Check for required permissions
@@ -776,14 +925,25 @@ func updatePackages() {
} }
} }
// Confirmation Prompt
if !noSync && !yesAll {
if !showConfirmationPrompt("Do you wish to sync all databases?", false) {
fmt.Println("Cancelling package update...")
exitCode = 1
return
}
}
// Create update operation // Create update operation
operation, err := bpmlib.UpdatePackages(rootDir, !noSync, installOptional, force, verbose) operation, err := bpmlib.UpdatePackages(rootDir, !noSync, allowDowngrades, installOptional, force, verbose)
if errors.As(err, &bpmlib.PackageNotFoundErr{}) || errors.As(err, &bpmlib.DependencyNotFoundErr{}) || errors.As(err, &bpmlib.PackageConflictErr{}) { if errors.As(err, &bpmlib.PackageNotFoundErr{}) || errors.As(err, &bpmlib.DependencyNotFoundErr{}) || errors.As(err, &bpmlib.PackageConflictErr{}) {
log.Printf("Error: %s", err) log.Printf("Error: %s", err)
exitCode = 1 exitCode = 1
return return
} else if err != nil { } else if err != nil {
log.Printf("Error: could not setup operation: %s\n", err) log.Printf("Error: could not setup operation: %s\n", err)
exitCode = 1
return
} }
// Exit if operation contains no actions // Exit if operation contains no actions
@@ -836,6 +996,9 @@ func updatePackages() {
} }
} }
// Get optional dependencies
optionalDepends := operation.GetOptionalDependencies()
// Execute operation // Execute operation
err = operation.Execute(verbose, force) err = operation.Execute(verbose, force)
if err != nil { if err != nil {
@@ -852,6 +1015,18 @@ func updatePackages() {
exitCode = 1 exitCode = 1
return return
} }
// Show optional dependencies
if !installOptional && len(optionalDepends) != 0 {
// List optional dependencies
fmt.Println("The following optional dependenices have been discovered:")
for dependant, depends := range optionalDepends {
fmt.Printf("%s: \n", dependant)
for _, depend := range depends {
fmt.Printf(" - %s\n", depend)
}
}
}
} }
func getFileOwner() { func getFileOwner() {
@@ -928,6 +1103,8 @@ func getFileOwner() {
// Print packages // Print packages
if len(pkgList) == 0 { if len(pkgList) == 0 {
fmt.Printf("%s (%s) is not owned by any packages!\n", absPath, pathType) fmt.Printf("%s (%s) is not owned by any packages!\n", absPath, pathType)
exitCode = 1
return
} else { } else {
fmt.Printf("%s (%s) is owned by the following packages:\n", absPath, pathType) fmt.Printf("%s (%s) is owned by the following packages:\n", absPath, pathType)
for _, pkg := range pkgList { for _, pkg := range pkgList {
@@ -942,6 +1119,7 @@ func compilePackage() {
rootDir, _ := currentFlagSet.GetString("root") rootDir, _ := currentFlagSet.GetString("root")
verbose, _ := currentFlagSet.GetBool("verbose") verbose, _ := currentFlagSet.GetBool("verbose")
yesAll, _ := currentFlagSet.GetBool("yes") yesAll, _ := currentFlagSet.GetBool("yes")
keepCompilationFiles, _ := currentFlagSet.GetBool("keep")
installSrcPkgDepends, _ := currentFlagSet.GetBool("depends") installSrcPkgDepends, _ := currentFlagSet.GetBool("depends")
skipChecks, _ := currentFlagSet.GetBool("skip-checks") skipChecks, _ := currentFlagSet.GetBool("skip-checks")
outputDirectory, _ := currentFlagSet.GetString("output-directory") outputDirectory, _ := currentFlagSet.GetString("output-directory")
@@ -1041,7 +1219,7 @@ func compilePackage() {
} else { } else {
// Ensure the required dependencies are installed // Ensure the required dependencies are installed
if len(unmetDepends) != 0 { 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 exitCode = 1
return return
} }
@@ -1139,7 +1317,7 @@ func compilePackage() {
return return
} }
outputBpmPackages, err := bpmlib.CompileSourcePackage(sourcePackage, outputDirectory, skipChecks) outputBpmPackages, err := bpmlib.CompileSourcePackage(sourcePackage, outputDirectory, skipChecks, keepCompilationFiles, verbose)
if err != nil { if err != nil {
// Remove unused packages // Remove unused packages
cleanupFunc() cleanupFunc()
@@ -1168,7 +1346,20 @@ func compilePackage() {
} }
} }
func listSubcommands() { func compareVersions() {
if currentFlagSet.NArg() < 2 {
log.Printf("Error: vercmp subcommand requires 2 arguments")
exitCode = 1
return
}
v1 := currentFlagSet.Arg(0)
v2 := currentFlagSet.Arg(1)
fmt.Println(bpmlib.CompareVersions(v1, v2))
}
func printUsage() {
fmt.Printf("Usage: %s <subcommand> [options]\n", os.Args[0]) fmt.Printf("Usage: %s <subcommand> [options]\n", os.Args[0])
fmt.Println("Description: Manage system packages") fmt.Println("Description: Manage system packages")
fmt.Println("Subcommands:") fmt.Println("Subcommands:")
@@ -1182,6 +1373,7 @@ func listSubcommands() {
fmt.Println(" u, update Update installed packages") fmt.Println(" u, update Update installed packages")
fmt.Println(" o, owner Show what packages own the specified paths") fmt.Println(" o, owner Show what packages own the specified paths")
fmt.Println(" c, compile Compile source packages and convert them to binary ones") fmt.Println(" c, compile Compile source packages and convert them to binary ones")
fmt.Println(" p, vercmp Compare package version numbers")
} }
func setupFlagsAndHelp(flagset *flag.FlagSet, usage, desc string, args []string) { func setupFlagsAndHelp(flagset *flag.FlagSet, usage, desc string, args []string) {
+124 -18
View File
@@ -6,9 +6,11 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/fs"
"os" "os"
"os/exec" "os/exec"
"path" "path"
"path/filepath"
"slices" "slices"
"strconv" "strconv"
"strings" "strings"
@@ -21,7 +23,7 @@ import (
var rootCompilationUID = "65534" var rootCompilationUID = "65534"
var rootCompilationGID = "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 // Initialize map
outputBpmPackages = make(map[string]string) outputBpmPackages = make(map[string]string)
@@ -122,7 +124,7 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
} }
// Download files // Download files
err = downloadPackageFiles(bpmpkg.PkgInfo, tempDirectory) err = downloadPackageFiles(bpmpkg.PkgInfo, tempDirectory, verbose)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -135,6 +137,7 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
env = append(env, "BPM_PKG_NAME="+bpmpkg.PkgInfo.Name) env = append(env, "BPM_PKG_NAME="+bpmpkg.PkgInfo.Name)
env = append(env, "BPM_PKG_VERSION="+bpmpkg.PkgInfo.Version) env = append(env, "BPM_PKG_VERSION="+bpmpkg.PkgInfo.Version)
env = append(env, "BPM_PKG_REVISION="+strconv.Itoa(bpmpkg.PkgInfo.Revision)) env = append(env, "BPM_PKG_REVISION="+strconv.Itoa(bpmpkg.PkgInfo.Revision))
env = append(env, "BPM_PKG_URL="+bpmpkg.PkgInfo.Url)
env = append(env, "BPM_PKG_ARCH="+bpmpkg.PkgInfo.OutputArch) env = append(env, "BPM_PKG_ARCH="+bpmpkg.PkgInfo.OutputArch)
env = append(env, CompilationBPMConfig.CompilationEnvironment...) env = append(env, CompilationBPMConfig.CompilationEnvironment...)
@@ -143,8 +146,8 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
"set -a\n"+ // Source and export functions and variables in source.sh script "set -a\n"+ // Source and export functions and variables in source.sh script
". \"${BPM_WORKDIR}\"/source.sh\n"+ ". \"${BPM_WORKDIR}\"/source.sh\n"+
"set +a\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 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 build) == \"function\" ]] && { echo \"Running build() function...\"; bash -e -c 'cd \"$BPM_SOURCE\" && build' || exit 1; }\n"+ // Run build() function if it exists
"exit 0") "exit 0")
cmd.Dir = tempDirectory cmd.Dir = tempDirectory
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
@@ -165,7 +168,7 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
"set -a\n"+ // Source and export functions and variables in source.sh script "set -a\n"+ // Source and export functions and variables in source.sh script
". \"${BPM_WORKDIR}\"/source.sh\n"+ ". \"${BPM_WORKDIR}\"/source.sh\n"+
"set +a\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") "exit 0")
cmd.Dir = tempDirectory cmd.Dir = tempDirectory
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
@@ -215,9 +218,8 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
"set -a\n"+ // Source and export functions and variables in source.sh script "set -a\n"+ // Source and export functions and variables in source.sh script
". \"${BPM_WORKDIR}\"/source.sh\n"+ ". \"${BPM_WORKDIR}\"/source.sh\n"+
"set +a\n"+ "set +a\n"+
"echo \"Running "+packageFunctionName+"() function\"\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 "( 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
cmd.Dir = tempDirectory cmd.Dir = tempDirectory
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
@@ -232,7 +234,92 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
return nil, err 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("file", "-b", path)
cmd.Stderr = os.Stderr
output, err = cmd.Output()
if err != nil {
return err
}
// Skip files that use ELF as a container format
if strings.Contains(string(output), "no machine") {
return nil
}
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 // 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 = 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.Dir = tempDirectory
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
@@ -286,6 +373,7 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
bpmArchiveFiles = append(bpmArchiveFiles, packageScripts...) // Package scripts bpmArchiveFiles = append(bpmArchiveFiles, packageScripts...) // Package scripts
// Create final BPM archive // 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 = exec.Command("bash", "-c", "tar -cf final-archive.bpm --owner=0 --group=0 -C \"$BPM_WORKDIR\" "+strings.Join(bpmArchiveFiles, " "))
cmd.Dir = tempDirectory cmd.Dir = tempDirectory
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
@@ -334,18 +422,25 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
outputBpmPackages[pkgInfo.Name] = outputFilename outputBpmPackages[pkgInfo.Name] = outputFilename
} }
// Delete temporary directory
if !keepCompilationFiles {
os.RemoveAll(tempDirectory)
}
return outputBpmPackages, nil return outputBpmPackages, nil
} }
func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string) error { func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string, verbose bool) error {
for _, download := range pkgInfo.Downloads { for _, download := range pkgInfo.Downloads {
// Replace variables // Replace variables
replaceVars := func(s string) string { replaceVars := func(s string) string {
switch s { switch s {
case "BPM_PKG_VERSION":
return pkgInfo.Version
case "BPM_PKG_NAME": case "BPM_PKG_NAME":
return pkgInfo.Name return pkgInfo.Name
case "BPM_PKG_VERSION":
return pkgInfo.Version
case "BPM_PKG_URL":
return pkgInfo.Url
case "BPM_SOURCE": case "BPM_SOURCE":
return path.Join(tempDirectory, "source/") return path.Join(tempDirectory, "source/")
default: default:
@@ -357,6 +452,10 @@ func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string) error {
if err != nil { if err != nil {
return err 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) extractTo, err := envsubst.Eval(strings.TrimSpace(download.ExtractTo), replaceVars)
if err != nil { if err != nil {
return err return err
@@ -377,12 +476,14 @@ func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string) error {
switch download.Type { switch download.Type {
case "", "file": case "", "file":
filepath := path.Join(tempDirectory, path.Base(downloadUrl)) if filepath == "" {
if download.Filepath != "" && download.Filepath[0] != '/' { filepath = path.Join(tempDirectory, path.Base(downloadUrl))
filepath = path.Join(tempDirectory, download.Filepath) }
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 { if err != nil {
return err return err
} }
@@ -409,7 +510,10 @@ func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string) error {
} }
if !download.NoExtract && (strings.Contains(filepath, ".tar") || strings.HasSuffix(filepath, ".tgz")) { 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 cmd.Dir = tempDirectory
if extractTo != "" { if extractTo != "" {
err := os.MkdirAll(extractTo, 0755) err := os.MkdirAll(extractTo, 0755)
@@ -456,10 +560,12 @@ func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string) error {
gitBranch := download.GitBranch gitBranch := download.GitBranch
gitBranch, err := envsubst.Eval(gitBranch, func(s string) string { gitBranch, err := envsubst.Eval(gitBranch, func(s string) string {
switch s { switch s {
case "BPM_PKG_VERSION":
return pkgInfo.Version
case "BPM_PKG_NAME": case "BPM_PKG_NAME":
return pkgInfo.Name return pkgInfo.Name
case "BPM_PKG_VERSION":
return pkgInfo.Version
case "BPM_PKG_URL":
return pkgInfo.Url
default: default:
return "" return ""
} }
+172 -15
View File
@@ -1,12 +1,17 @@
package bpmlib package bpmlib
import ( import (
"bytes"
"errors" "errors"
"fmt" "fmt"
"io"
"maps"
"net/http"
"net/url" "net/url"
"os" "os"
"path" "path"
"slices" "slices"
"strconv"
"strings" "strings"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
@@ -16,14 +21,15 @@ type BPMDatabase struct {
DatabaseVersion int `yaml:"database_version"` DatabaseVersion int `yaml:"database_version"`
Entries map[string]*BPMDatabaseEntry `yaml:"entries"` Entries map[string]*BPMDatabaseEntry `yaml:"entries"`
VirtualPackages map[string][]string VirtualPackages map[string][]string
Name string
Source string Source string
} }
type BPMDatabaseEntry struct { type BPMDatabaseEntry struct {
Info *PackageInfo `yaml:"info"` Info *PackageInfo `yaml:"info"`
Filepath string `yaml:"filepath"` Filepath string `yaml:"filepath"`
DownloadSize uint64 `yaml:"download_size"` DownloadSize int64 `yaml:"download_size"`
InstalledSize uint64 `yaml:"installed_size"` InstalledSize int64 `yaml:"installed_size"`
Database *BPMDatabase Database *BPMDatabase
} }
@@ -54,13 +60,15 @@ func (db *configDatabase) ReadLocalDatabase() error {
// Initialize struct values // Initialize struct values
database.VirtualPackages = make(map[string][]string) database.VirtualPackages = make(map[string][]string)
database.Name = db.Name
database.Source = db.Source database.Source = db.Source
entriesToRemove := make([]string, 0)
for entryName, entry := range database.Entries { for entryName, entry := range database.Entries {
entry.Database = database entry.Database = database
if entry.Info.IsSplitPackage() { if entry.Info.IsSplitPackage() {
delete(database.Entries, entryName)
// Handle split packages // Handle split packages
for _, splitPkg := range entry.Info.SplitPackages { for _, splitPkg := range entry.Info.SplitPackages {
// Turn split package into json data // Turn split package into json data
@@ -99,9 +107,6 @@ func (db *configDatabase) ReadLocalDatabase() error {
for _, p := range splitPkg.Provides { for _, p := range splitPkg.Provides {
database.VirtualPackages[p] = append(database.VirtualPackages[p], splitPkg.Name) database.VirtualPackages[p] = append(database.VirtualPackages[p], splitPkg.Name)
} }
// Add current entry to list for removal
entriesToRemove = append(entriesToRemove, entryName)
} }
} else { } else {
// Add virtual packages to database // Add virtual packages to database
@@ -111,11 +116,6 @@ func (db *configDatabase) ReadLocalDatabase() error {
} }
} }
// Remove entries
for _, entryName := range entriesToRemove {
delete(database.Entries, entryName)
}
BPMDatabases[db.Name] = database BPMDatabases[db.Name] = database
return nil return nil
@@ -131,13 +131,29 @@ func (db *configDatabase) SyncLocalDatabaseFile() error {
} }
// Retrieve data from URL // 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 { if err != nil {
return err return err
} }
// Unmarshal data to ensure it is a valid BPM database // Unmarshal data to ensure it is a valid BPM database
err = yaml.Unmarshal(buffer, &BPMDatabase{}) err = yaml.Unmarshal(buffer.Bytes(), &BPMDatabase{})
if err != nil { if err != nil {
return fmt.Errorf("could not decode database: %s", err) return fmt.Errorf("could not decode database: %s", err)
} }
@@ -155,7 +171,7 @@ func (db *configDatabase) SyncLocalDatabaseFile() error {
} }
defer out.Close() defer out.Close()
_, err = out.Write(buffer) _, err = out.Write(buffer.Bytes())
return nil return nil
} }
@@ -240,10 +256,151 @@ func (db *BPMDatabase) FetchPackage(pkg string) (string, error) {
} }
// Download package from url // 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 { if err != nil {
return "", err return "", err
} }
return path.Join("/var/cache/bpm/fetched/", path.Base(entry.Filepath)), nil 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 := 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")
}
+56 -28
View File
@@ -1,7 +1,6 @@
package bpmlib package bpmlib
import ( import (
"errors"
"fmt" "fmt"
"slices" "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 return allDepends
} }
@@ -170,53 +175,76 @@ func resolvePackageDependenciesFromDatabase(resolved *[]pkgInstallationReason, u
} }
} }
func GetPackageDependants(pkgName string, rootDir string) ([]string, error) { func (pkgInfo *PackageInfo) GetPackageDependants(rootDir string) (dependants []string) {
ret := make([]string, 0)
// Get BPM package
pkg := GetPackage(pkgName, rootDir)
if pkg == nil {
return nil, errors.New("package not found: " + pkgName)
}
// Get installed package names // Get installed package names
pkgs, err := GetInstalledPackages(rootDir) pkgs, ok := localPackageInformation[rootDir]
if err != nil { if !ok {
return nil, errors.New("could not get installed packages") return nil
} }
// Loop through all installed packages // Loop through all installed packages
for _, installedPkgName := range pkgs { for _, installedPkg := range pkgs {
// Get installed BPM package
installedPkg := GetPackage(installedPkgName, rootDir)
if installedPkg == nil {
return nil, errors.New("package not found: " + installedPkgName)
}
// Skip iteration if comparing the same packages // Skip iteration if comparing the same packages
if installedPkg.PkgInfo.Name == pkgName { if installedPkg.Name == pkgInfo.Name {
continue continue
} }
// Add installed package to list if its dependencies include pkgName // Add installed package to list if its dependencies include pkgName
if slices.ContainsFunc(installedPkg.PkgInfo.Depends, func(n string) bool { if slices.ContainsFunc(installedPkg.Depends, func(n string) bool {
return n == pkgName return n == pkgInfo.Name
}) { }) {
ret = append(ret, installedPkgName) dependants = append(dependants, installedPkg.Name)
continue continue
} }
// Loop through each virtual package // 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 // Add installed package to list if its dependencies contain a provided virtual package
if slices.ContainsFunc(installedPkg.PkgInfo.Depends, func(n string) bool { if slices.ContainsFunc(installedPkg.Depends, func(n string) bool {
return n == vpkg return n == vpkg
}) { }) {
ret = append(ret, installedPkgName) dependants = append(dependants, installedPkg.Name)
break break
} }
} }
} }
return ret, nil return dependants
}
func (pkgInfo *PackageInfo) GetPackageOptionalDependants(rootDir string) (dependants []string) {
// Get installed package names
pkgs, ok := localPackageInformation[rootDir]
if !ok {
return nil
}
// Loop through all installed packages
for _, installedPkg := range pkgs {
// Skip iteration if comparing the same packages
if installedPkg.Name == pkgInfo.Name {
continue
}
// Add installed package to list if its optional dependencies include pkgName
if slices.ContainsFunc(installedPkg.OptionalDepends, func(n string) bool {
return n == pkgInfo.Name
}) {
dependants = append(dependants, installedPkg.Name)
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.OptionalDepends, func(n string) bool {
return n == vpkg
}) {
dependants = append(dependants, installedPkg.Name)
break
}
}
}
return dependants
} }
+38 -12
View File
@@ -21,7 +21,6 @@ const (
// InstallPackages installs the specified packages into the given root directory by fetching them from databases or directly from local bpm archives // InstallPackages installs the specified packages into the given root directory by fetching them from databases or directly from local bpm archives
func InstallPackages(rootDir string, forceInstallationReason InstallationReason, reinstallMethod ReinstallMethod, installOptionalDependencies, forceInstallation, verbose bool, packages ...string) (operation *BPMOperation, err error) { func InstallPackages(rootDir string, forceInstallationReason InstallationReason, reinstallMethod ReinstallMethod, installOptionalDependencies, forceInstallation, verbose bool, packages ...string) (operation *BPMOperation, err error) {
// Setup operation struct // Setup operation struct
operation = &BPMOperation{ operation = &BPMOperation{
Actions: make([]OperationAction, 0), Actions: make([]OperationAction, 0),
@@ -31,6 +30,9 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
compiledPackages: make(map[string]string), compiledPackages: make(map[string]string),
} }
// Remove duplicates from packages
packages = removeDuplicates(packages)
// Resolve packages // Resolve packages
pkgsNotFound := make([]string, 0) pkgsNotFound := make([]string, 0)
for _, pkg := range packages { for _, pkg := range packages {
@@ -145,12 +147,9 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
operation.ReplaceObsoletePackages() operation.ReplaceObsoletePackages()
// Check for conflicts // Check for conflicts
conflicts, err := operation.CheckForConflicts() conflicts := operation.CheckForConflicts()
if err != nil {
return nil, fmt.Errorf("could not complete package conflict check: %s", err)
}
if len(conflicts) > 0 { if len(conflicts) > 0 {
err = nil err = fmt.Errorf("conflicts detected")
for pkg, conflict := range conflicts { for pkg, conflict := range conflicts {
err = errors.Join(err, PackageConflictErr{pkg, conflict}) err = errors.Join(err, PackageConflictErr{pkg, conflict})
} }
@@ -196,6 +195,9 @@ func RemovePackages(rootDir string, force, cleanupDependencies bool, packages ..
compiledPackages: make(map[string]string), compiledPackages: make(map[string]string),
} }
// Remove duplicates from packages
packages = removeDuplicates(packages)
// Search for packages // Search for packages
for _, pkg := range packages { for _, pkg := range packages {
bpmpkg := GetPackage(pkg, rootDir) bpmpkg := GetPackage(pkg, rootDir)
@@ -221,11 +223,12 @@ func RemovePackages(rootDir string, force, cleanupDependencies bool, packages ..
// Get packages and their dependants // Get packages and their dependants
packageDepndants := make(map[string][]string, 0) packageDepndants := make(map[string][]string, 0)
for _, action := range operation.Actions { for _, action := range operation.Actions {
dependants, err := GetPackageDependants(action.(*RemovePackageAction).BpmPackage.PkgInfo.Name, rootDir) // Skip package if ignored
if err != nil { if slices.Contains(MainBPMConfig.IgnorePackages, action.(*RemovePackageAction).BpmPackage.PkgInfo.Name) {
return nil, fmt.Errorf("could not get package dependants: %s", err) continue
} }
dependants := action.(*RemovePackageAction).BpmPackage.PkgInfo.GetPackageDependants(rootDir)
packageDepndants[action.(*RemovePackageAction).BpmPackage.PkgInfo.Name] = dependants packageDepndants[action.(*RemovePackageAction).BpmPackage.PkgInfo.Name] = dependants
} }
@@ -238,6 +241,14 @@ func RemovePackages(rootDir string, force, cleanupDependencies bool, packages ..
packageDepndants[pkg] = required 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 // Remove empty keys from map
maps.DeleteFunc(packageDepndants, func(pkg string, required []string) bool { maps.DeleteFunc(packageDepndants, func(pkg string, required []string) bool {
return len(required) == 0 return len(required) == 0
@@ -354,7 +365,7 @@ func CleanupCache(rootDir string, cleanupCompilationFiles, cleanupCompiledPackag
} }
// UpdatePackages fetches the newest versions of all installed packages from // UpdatePackages fetches the newest versions of all installed packages from
func UpdatePackages(rootDir string, syncDatabase bool, installOptionalDependencies, forceInstallation, verbose bool) (operation *BPMOperation, err error) { func UpdatePackages(rootDir string, syncDatabase bool, allowDowngrades bool, installOptionalDependencies, forceInstallation, verbose bool) (operation *BPMOperation, err error) {
// Sync databases // Sync databases
if syncDatabase { if syncDatabase {
err := SyncDatabase(verbose) err := SyncDatabase(verbose)
@@ -407,8 +418,8 @@ func UpdatePackages(rootDir string, syncDatabase bool, installOptionalDependenci
if installedInfo == nil { if installedInfo == nil {
return nil, fmt.Errorf("could not get package info for package (%s)", pkg) return nil, fmt.Errorf("could not get package info for package (%s)", pkg)
} else { } else {
comparison := ComparePackageVersions(*entry.Info, *installedInfo) comparison := CompareVersions(entry.Info.GetFullVersion(), installedInfo.GetFullVersion())
if comparison > 0 { if (!allowDowngrades && comparison > 0) || (allowDowngrades && comparison != 0) {
operation.AppendAction(&FetchPackageAction{ operation.AppendAction(&FetchPackageAction{
InstallationReason: GetInstallationReason(pkg, rootDir), InstallationReason: GetInstallationReason(pkg, rootDir),
DatabaseEntry: entry, DatabaseEntry: entry,
@@ -432,6 +443,21 @@ func UpdatePackages(rootDir string, syncDatabase bool, installOptionalDependenci
// Replace obsolete packages // Replace obsolete packages
operation.ReplaceObsoletePackages() operation.ReplaceObsoletePackages()
// Check for conflicts
conflicts := operation.CheckForConflicts()
if len(conflicts) > 0 {
err = fmt.Errorf("conflicts detected")
for pkg, conflict := range conflicts {
err = errors.Join(err, PackageConflictErr{pkg, conflict})
}
if !forceInstallation {
return nil, err
} else {
log.Printf("Warning: %s", err)
}
}
return operation, nil return operation, nil
} }
+10 -2
View File
@@ -1,9 +1,17 @@
module git.enumerated.dev/bubble-package-manager/bpm/src/bpmlib module github.com/EnumeratedDev/bpm/src/bpmlib
go 1.23 go 1.24.0
require ( require (
github.com/drone/envsubst v1.0.3 github.com/drone/envsubst v1.0.3
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f 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 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 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g=
github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g= 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 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 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 h1:xt29M2T6STgldg+WEP51gGePQCsQvklmP2eIhPIBK3g=
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f/go.mod h1:i4sF0l1fFnY1aiw08QQSwVAFxHEm311Me3WsU/X7nL0= 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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
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=
+25 -28
View File
@@ -9,7 +9,7 @@ import (
"strings" "strings"
) )
var localPackageInformation map[string]map[string]*BPMPackage = make(map[string]map[string]*BPMPackage) var localPackageInformation map[string]map[string]*PackageInfo = make(map[string]map[string]*PackageInfo)
func initializeLocalPackageInformation(rootDir string) (err error) { func initializeLocalPackageInformation(rootDir string) (err error) {
// Return if information is already initialized // Return if information is already initialized
@@ -17,7 +17,7 @@ func initializeLocalPackageInformation(rootDir string) (err error) {
return nil return nil
} }
tempPackageInformation := make(map[string]*BPMPackage) tempPackageInformation := make(map[string]*PackageInfo)
// Get path to installed package information directory // Get path to installed package information directory
installedDir := path.Join(rootDir, "var/lib/bpm/installed/") installedDir := path.Join(rootDir, "var/lib/bpm/installed/")
@@ -25,7 +25,7 @@ func initializeLocalPackageInformation(rootDir string) (err error) {
// Get directory content // Get directory content
items, err := os.ReadDir(installedDir) items, err := os.ReadDir(installedDir)
if os.IsNotExist(err) { if os.IsNotExist(err) {
localPackageInformation[rootDir] = make(map[string]*BPMPackage) localPackageInformation[rootDir] = make(map[string]*PackageInfo)
return nil return nil
} }
if err != nil { if err != nil {
@@ -49,14 +49,8 @@ func initializeLocalPackageInformation(rootDir string) (err error) {
return err return err
} }
// Read package files
files := getPackageFiles(info.Name, rootDir)
// Add package to slice // Add package to slice
tempPackageInformation[info.Name] = &BPMPackage{ tempPackageInformation[info.Name] = info
PkgInfo: info,
PkgFiles: files,
}
} }
localPackageInformation[rootDir] = tempPackageInformation localPackageInformation[rootDir] = tempPackageInformation
@@ -71,10 +65,13 @@ func GetInstalledPackages(rootDir string) (ret []string, err error) {
} }
// Loop through each package and add it to slice // Loop through each package and add it to slice
for _, bpmpkg := range localPackageInformation[rootDir] { for _, pkgInfo := range localPackageInformation[rootDir] {
ret = append(ret, bpmpkg.PkgInfo.Name) ret = append(ret, pkgInfo.Name)
} }
// Sort packages
slices.Sort(ret)
return ret, nil return ret, nil
} }
@@ -91,18 +88,6 @@ func IsPackageInstalled(pkg, rootDir string) bool {
return true return true
} }
func GetPackageInfo(pkg string, rootDir string) *PackageInfo {
// Get BPM package
bpmpkg := GetPackage(pkg, rootDir)
// Return nil if not found
if bpmpkg == nil {
return nil
}
return bpmpkg.PkgInfo
}
func IsVirtualPackage(pkg, rootDir string) (bool, string) { func IsVirtualPackage(pkg, rootDir string) (bool, string) {
pkgs, err := GetInstalledPackages(rootDir) pkgs, err := GetInstalledPackages(rootDir)
if err != nil { if err != nil {
@@ -143,15 +128,27 @@ func IsPackageProvided(pkg, rootDir string) bool {
return false return false
} }
func GetPackage(pkg, rootDir string) *BPMPackage { func GetPackageInfo(pkg string, rootDir string) *PackageInfo {
err := initializeLocalPackageInformation(rootDir) err := initializeLocalPackageInformation(rootDir)
if err != nil { if err != nil {
return nil return nil
} }
bpmpkg := localPackageInformation[rootDir][pkg] return localPackageInformation[rootDir][pkg]
}
return bpmpkg func GetPackage(pkg, rootDir string) *BPMPackage {
pkgInfo := GetPackageInfo(pkg, rootDir)
if pkgInfo == nil {
return nil
}
files := getPackageFiles(pkgInfo.Name, rootDir)
return &BPMPackage{
PkgInfo: pkgInfo,
PkgFiles: files,
}
} }
func GetAllPackageFiles(rootDir string, excludePackages ...string) (map[string][]*BPMPackage, error) { func GetAllPackageFiles(rootDir string, excludePackages ...string) (map[string][]*BPMPackage, error) {
@@ -222,7 +219,7 @@ func getPackageFiles(pkg, rootDir string) []*PackageFileEntry {
if err != nil { if err != nil {
return nil return nil
} }
size, err := strconv.ParseUint(stringEntry[len(stringEntry)-1], 0, 64) size, err := strconv.ParseInt(stringEntry[len(stringEntry)-1], 0, 64)
if err != nil { if err != nil {
return nil return nil
} }
+13 -16
View File
@@ -9,29 +9,22 @@ import (
"strings" "strings"
) )
func retrieveUrlData(u string) ([]byte, error) { func downloadFile(barText, u, filepath string, perm os.FileMode) 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 {
if strings.HasSuffix(filepath, "/") { if strings.HasSuffix(filepath, "/") {
return fmt.Errorf("Filepath must not end in '/'") return fmt.Errorf("Filepath must not end in '/'")
} }
data, err := retrieveUrlData(u) req, err := http.NewRequest("GET", u, nil)
if err != nil { if err != nil {
return err return err
} }
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
// Create parent directories // Create parent directories
err = os.MkdirAll(path.Dir(filepath), 0755) err = os.MkdirAll(path.Dir(filepath), 0755)
if err != nil { if err != nil {
@@ -45,8 +38,12 @@ func downloadFile(u, filepath string, perm os.FileMode) error {
} }
defer file.Close() defer file.Close()
// Create progress bar
bar := createProgressBar(resp.ContentLength, barText, false)
defer bar.Close()
// Copy data // Copy data
_, err = file.Write(data) _, err = io.Copy(io.MultiWriter(file, bar), resp.Body)
if err != nil { if err != nil {
return err return err
} }
+137 -61
View File
@@ -87,8 +87,8 @@ func (operation *BPMOperation) RemoveAction(pkg, actionType string) {
}) })
} }
func (operation *BPMOperation) GetTotalDownloadSize() uint64 { func (operation *BPMOperation) GetTotalDownloadSize() int64 {
var ret uint64 = 0 var ret int64 = 0
for _, action := range operation.Actions { for _, action := range operation.Actions {
if action.GetActionType() == "fetch" { if action.GetActionType() == "fetch" {
ret += action.(*FetchPackageAction).DatabaseEntry.DownloadSize ret += action.(*FetchPackageAction).DatabaseEntry.DownloadSize
@@ -97,8 +97,8 @@ func (operation *BPMOperation) GetTotalDownloadSize() uint64 {
return ret return ret
} }
func (operation *BPMOperation) GetTotalInstalledSize() uint64 { func (operation *BPMOperation) GetTotalInstalledSize() int64 {
var ret uint64 = 0 var ret int64 = 0
for _, action := range operation.Actions { for _, action := range operation.Actions {
if action.GetActionType() == "install" { if action.GetActionType() == "install" {
ret += action.(*InstallPackageAction).BpmPackage.GetInstalledSize() ret += action.(*InstallPackageAction).BpmPackage.GetInstalledSize()
@@ -113,14 +113,17 @@ func (operation *BPMOperation) GetFinalActionSize(rootDir string) int64 {
var ret int64 = 0 var ret int64 = 0
for _, action := range operation.Actions { for _, action := range operation.Actions {
if action.GetActionType() == "install" { if action.GetActionType() == "install" {
ret += int64(action.(*InstallPackageAction).BpmPackage.GetInstalledSize()) ret += action.(*InstallPackageAction).BpmPackage.GetInstalledSize()
if IsPackageInstalled(action.(*InstallPackageAction).BpmPackage.PkgInfo.Name, rootDir) { if IsPackageInstalled(action.(*InstallPackageAction).BpmPackage.PkgInfo.Name, rootDir) {
ret -= int64(GetPackage(action.(*InstallPackageAction).BpmPackage.PkgInfo.Name, rootDir).GetInstalledSize()) ret -= GetPackage(action.(*InstallPackageAction).BpmPackage.PkgInfo.Name, rootDir).GetInstalledSize()
} }
} else if action.GetActionType() == "fetch" { } else if action.GetActionType() == "fetch" {
ret += int64(action.(*FetchPackageAction).DatabaseEntry.InstalledSize) ret += action.(*FetchPackageAction).DatabaseEntry.InstalledSize
if IsPackageInstalled(action.(*FetchPackageAction).DatabaseEntry.Info.Name, rootDir) {
ret -= action.(*FetchPackageAction).DatabaseEntry.InstalledSize
}
} else if action.GetActionType() == "remove" { } else if action.GetActionType() == "remove" {
ret -= int64(action.(*RemovePackageAction).BpmPackage.GetInstalledSize()) ret -= action.(*RemovePackageAction).BpmPackage.GetInstalledSize()
} }
} }
return ret return ret
@@ -176,10 +179,7 @@ func (operation *BPMOperation) RemoveNeededPackages() error {
} }
for pkg, action := range removeActions { for pkg, action := range removeActions {
dependants, err := GetPackageDependants(action.BpmPackage.PkgInfo.Name, operation.RootDir) dependants := action.BpmPackage.PkgInfo.GetPackageDependants(operation.RootDir)
if err != nil {
return errors.New("could not get dependant packages for package (" + pkg + ")")
}
dependants = slices.DeleteFunc(dependants, func(d string) bool { dependants = slices.DeleteFunc(dependants, func(d string) bool {
if _, ok := removeActions[d]; ok { if _, ok := removeActions[d]; ok {
return true return true
@@ -232,7 +232,7 @@ func (operation *BPMOperation) Cleanup(cleanupMakeDepends bool) error {
keepPackages = append(keepPackages, pkg.Name) keepPackages = append(keepPackages, pkg.Name)
resolved := pkg.GetDependenciesRecursive(!cleanupMakeDepends, operation.RootDir) resolved := pkg.GetDependenciesRecursive(!cleanupMakeDepends, operation.RootDir)
for _, value := range resolved { for _, value := range resolved {
if !slices.Contains(keepPackages, value) { if !slices.Contains(keepPackages, value) && !slices.Contains(MainBPMConfig.IgnorePackages, value) {
keepPackages = append(keepPackages, value) keepPackages = append(keepPackages, value)
} }
} }
@@ -280,54 +280,92 @@ func (operation *BPMOperation) ReplaceObsoletePackages() {
} }
} }
func (operation *BPMOperation) CheckForConflicts() (map[string][]string, error) { func (operation *BPMOperation) CheckForConflicts() map[string][]string {
conflicts := make(map[string][]string) conflicts := make(map[string][]string)
installedPackages, err := GetInstalledPackages(operation.RootDir)
if err != nil { // Get installed packages
return nil, err installedPackages := localPackageInformation[operation.RootDir]
}
allPackages := make([]*PackageInfo, len(installedPackages)) // Get packages to be removed
for i, value := range installedPackages { removedPackages := make([]string, 0)
bpmpkg := GetPackage(value, operation.RootDir) for _, value := range slices.Clone(operation.Actions) {
if bpmpkg == nil { if value.GetActionType() != "remove" {
return nil, fmt.Errorf("could not find installed package (%s)", value) continue
} }
allPackages[i] = bpmpkg.PkgInfo
removedPackages = append(removedPackages, value.(*RemovePackageAction).BpmPackage.PkgInfo.Name)
} }
// Add all new packages to the allPackages slice // Check for conflicts
for _, value := range slices.Clone(operation.Actions) { for _, value := range slices.Clone(operation.Actions) {
var pkgInfo *PackageInfo
if value.GetActionType() == "install" { if value.GetActionType() == "install" {
action := value.(*InstallPackageAction) pkgInfo = value.(*InstallPackageAction).BpmPackage.PkgInfo
pkgInfo := action.BpmPackage.PkgInfo
allPackages = append(allPackages, pkgInfo)
} else if value.GetActionType() == "fetch" { } else if value.GetActionType() == "fetch" {
action := value.(*FetchPackageAction) pkgInfo = value.(*FetchPackageAction).DatabaseEntry.Info
pkgInfo := action.DatabaseEntry.Info } else {
allPackages = append(allPackages, pkgInfo) continue
} else if value.GetActionType() == "remove" { }
action := value.(*RemovePackageAction)
pkgInfo := action.BpmPackage.PkgInfo // Check for conflicts with installed packages
for i := len(allPackages) - 1; i >= 0; i-- { for _, installedPkg := range installedPackages {
info := allPackages[i] // Skip if package is to be removed
if info.Name == pkgInfo.Name { if slices.Contains(removedPackages, installedPkg.Name) {
allPackages = append(allPackages[:i], allPackages[i+1:]...) continue
}
// Skip if same package
if pkgInfo.Name == installedPkg.Name {
continue
}
// Check for new package conflicts
if slices.Contains(pkgInfo.Conflicts, installedPkg.Name) {
conflicts[pkgInfo.Name] = append(conflicts[pkgInfo.Name], installedPkg.Name)
}
for _, vpkg := range installedPkg.Provides {
if slices.Contains(pkgInfo.Conflicts, vpkg) {
conflicts[pkgInfo.Name] = append(conflicts[pkgInfo.Name], vpkg+" ("+installedPkg.Name+")")
}
}
// Check for installed package conflicts
for _, vpkg := range pkgInfo.Provides {
if slices.Contains(installedPkg.Conflicts, vpkg) {
conflicts[installedPkg.Name] = append(conflicts[installedPkg.Name], vpkg+" ("+pkgInfo.Name+")")
}
}
}
// Check for conflicts with other new packages
for _, value := range slices.Clone(operation.Actions) {
var pkgInfo2 *PackageInfo
if value.GetActionType() == "install" {
pkgInfo2 = value.(*InstallPackageAction).BpmPackage.PkgInfo
} else if value.GetActionType() == "fetch" {
pkgInfo2 = value.(*FetchPackageAction).DatabaseEntry.Info
} else {
continue
}
// Skip if same package
if pkgInfo.Name == pkgInfo2.Name {
continue
}
// Check for other package conflicts
if slices.Contains(pkgInfo.Conflicts, pkgInfo2.Name) {
conflicts[pkgInfo.Name] = append(conflicts[pkgInfo.Name], pkgInfo2.Name)
}
for _, vpkg := range pkgInfo2.Provides {
if slices.Contains(pkgInfo.Conflicts, vpkg) {
conflicts[pkgInfo.Name] = append(conflicts[pkgInfo.Name], vpkg+" ("+pkgInfo2.Name+")")
} }
} }
} }
} }
for _, value := range allPackages { return conflicts
for _, conflict := range value.Conflicts {
if slices.ContainsFunc(allPackages, func(info *PackageInfo) bool {
return info.Name == conflict
}) {
conflicts[value.Name] = append(conflicts[value.Name], conflict)
}
}
}
return conflicts, nil
} }
func (operation *BPMOperation) ShowOperationSummary() { func (operation *BPMOperation) ShowOperationSummary() {
@@ -373,7 +411,7 @@ func (operation *BPMOperation) ShowOperationSummary() {
if installedInfo == nil { if installedInfo == nil {
fmt.Fprintf(writer, "%s\t%s\t%s\t%s\t%t\n", pkgInfo.Name, pkgInfo.GetFullVersion(), "Install", installationReasonStr, pkgInfo.Type == "source") fmt.Fprintf(writer, "%s\t%s\t%s\t%s\t%t\n", pkgInfo.Name, pkgInfo.GetFullVersion(), "Install", installationReasonStr, pkgInfo.Type == "source")
} else { } else {
comparison := ComparePackageVersions(*pkgInfo, *installedInfo) comparison := CompareVersions(pkgInfo.GetFullVersion(), installedInfo.GetFullVersion())
if comparison < 0 { if comparison < 0 {
fmt.Fprintf(writer, "%s\t%s -> %s\t%s\t%s\t%t\n", pkgInfo.Name, installedInfo.GetFullVersion(), pkgInfo.GetFullVersion(), "Downgrade", installationReasonStr, pkgInfo.Type == "source") fmt.Fprintf(writer, "%s\t%s -> %s\t%s\t%s\t%t\n", pkgInfo.Name, installedInfo.GetFullVersion(), pkgInfo.GetFullVersion(), "Downgrade", installationReasonStr, pkgInfo.Type == "source")
} else if comparison > 0 { } else if comparison > 0 {
@@ -391,12 +429,12 @@ func (operation *BPMOperation) ShowOperationSummary() {
fmt.Println("Warning: Operating in " + operation.RootDir) fmt.Println("Warning: Operating in " + operation.RootDir)
} }
if operation.GetTotalDownloadSize() > 0 { if operation.GetTotalDownloadSize() > 0 {
fmt.Printf("%s will be downloaded to complete this operation\n", unsignedBytesToHumanReadable(operation.GetTotalDownloadSize())) fmt.Printf("%s will be downloaded to complete this operation\n", BytesToHumanReadable(operation.GetTotalDownloadSize()))
} }
if operation.GetFinalActionSize(operation.RootDir) > 0 { if operation.GetFinalActionSize(operation.RootDir) > 0 {
fmt.Printf("A total of %s will be installed after the operation finishes\n", bytesToHumanReadable(operation.GetFinalActionSize(operation.RootDir))) fmt.Printf("A total of %s will be installed after the operation finishes\n", BytesToHumanReadable(operation.GetFinalActionSize(operation.RootDir)))
} else if operation.GetFinalActionSize(operation.RootDir) < 0 { } else if operation.GetFinalActionSize(operation.RootDir) < 0 {
fmt.Printf("A total of %s will be freed after the operation finishes\n", strings.TrimPrefix(bytesToHumanReadable(operation.GetFinalActionSize(operation.RootDir)), "-")) fmt.Printf("A total of %s will be freed after the operation finishes\n", strings.TrimPrefix(BytesToHumanReadable(operation.GetFinalActionSize(operation.RootDir)), "-"))
} }
} }
@@ -428,6 +466,40 @@ func (operation *BPMOperation) ShowSourcePackageContent() (sourcePackagesShown i
return sourcePackagesShown, nil return sourcePackagesShown, nil
} }
func (operation *BPMOperation) GetOptionalDependencies() (optionalDepends map[string][]string) {
optionalDepends = make(map[string][]string)
// Find all optional dependencies
for _, value := range slices.Clone(operation.Actions) {
var pkgInfo *PackageInfo
if value.GetActionType() == "install" {
action := value.(*InstallPackageAction)
pkgInfo = action.BpmPackage.PkgInfo
} else if value.GetActionType() == "fetch" {
action := value.(*FetchPackageAction)
pkgInfo = action.DatabaseEntry.Info
} else {
continue
}
for _, depend := range pkgInfo.OptionalDepends {
// Skip if dependency is already installed
if IsPackageInstalled(depend, operation.RootDir) {
continue
}
// Skip if not a new dependency of the package
if installedPkg := GetPackage(pkgInfo.Name, operation.RootDir); installedPkg != nil && slices.Contains(installedPkg.PkgInfo.OptionalDepends, depend) {
continue
}
optionalDepends[pkgInfo.Name] = append(optionalDepends[pkgInfo.Name], depend)
}
}
return
}
func (operation *BPMOperation) RunHooks(verbose bool) error { func (operation *BPMOperation) RunHooks(verbose bool) error {
// Return if hooks directory does not exist // Return if hooks directory does not exist
if stat, err := os.Stat(path.Join(operation.RootDir, "var/lib/bpm/hooks")); err != nil || !stat.IsDir() { if stat, err := os.Stat(path.Join(operation.RootDir, "var/lib/bpm/hooks")); err != nil || !stat.IsDir() {
@@ -481,7 +553,7 @@ func (operation *BPMOperation) FetchPackages() (err error) {
var bpmpkg *BPMPackage var bpmpkg *BPMPackage
// Check if package has already been fetched from download link // 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 // Fetch package from database
fetchedPackage, err := entry.Database.FetchPackage(entry.Info.Name) fetchedPackage, err := entry.Database.FetchPackage(entry.Info.Name)
if err != nil { if err != nil {
@@ -496,16 +568,22 @@ func (operation *BPMOperation) FetchPackages() (err error) {
// Add fetched package to map // Add fetched package to map
fetchedPackages[entry.Filepath] = fetchedPackage fetchedPackages[entry.Filepath] = fetchedPackage
fmt.Printf("Package (%s) was successfully fetched!\n", entry.Info.Name)
} else { } else {
// Read fetched package // Read fetched package
bpmpkg, err = ReadPackage(fetchedPackages[entry.Filepath]) bpmpkg, err = ReadPackage(fetchedFilepath)
if err != nil { if err != nil {
return fmt.Errorf("could not read package (%s): %s\n", entry.Info.Name, err) 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() { if bpmpkg.PkgInfo.IsSplitPackage() {
@@ -592,7 +670,7 @@ func (operation *BPMOperation) Execute(verbose, force bool) (err error) {
// Compile source package if not compiled already // Compile source package if not compiled already
if _, ok := operation.compiledPackages[pkgNameToInstall]; !ok { 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 { if err != nil {
return fmt.Errorf("could not compile source package (%s): %s\n", value.File, err) return fmt.Errorf("could not compile source package (%s): %s\n", value.File, err)
} }
@@ -625,8 +703,6 @@ func (operation *BPMOperation) Execute(verbose, force bool) (err error) {
if err != nil { if err != nil {
return fmt.Errorf("could not set installation reason for package (%s): %s\n", value.BpmPackage.PkgInfo.Name, err) 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!") fmt.Println("Operation complete!")
+107 -48
View File
@@ -17,7 +17,6 @@ import (
"strings" "strings"
"syscall" "syscall"
version "github.com/knqyf263/go-rpm-version"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@@ -43,6 +42,7 @@ type PackageInfo struct {
Conflicts []string `yaml:"conflicts,omitempty"` Conflicts []string `yaml:"conflicts,omitempty"`
Replaces []string `yaml:"replaces,omitempty"` Replaces []string `yaml:"replaces,omitempty"`
Provides []string `yaml:"provides,omitempty"` Provides []string `yaml:"provides,omitempty"`
Options []string `yaml:"options,omitempty"`
Downloads []PackageDownload `yaml:"downloads,omitempty"` Downloads []PackageDownload `yaml:"downloads,omitempty"`
SplitPackages []*PackageInfo `yaml:"split_packages,omitempty"` SplitPackages []*PackageInfo `yaml:"split_packages,omitempty"`
} }
@@ -50,7 +50,7 @@ type PackageInfo struct {
type PackageDownload struct { type PackageDownload struct {
Url string `yaml:"url"` Url string `yaml:"url"`
Type string `yaml:"type,omitempty"` Type string `yaml:"type,omitempty"`
Filepath string `yaml:"filepath,omitempty,omitempty"` Filepath string `yaml:"filepath,omitempty"`
// Archive options // Archive options
NoExtract bool `yaml:"no_extract,omitempty"` NoExtract bool `yaml:"no_extract,omitempty"`
@@ -69,11 +69,11 @@ type PackageFileEntry struct {
OctalPerms uint32 OctalPerms uint32
UserID int UserID int
GroupID int GroupID int
SizeInBytes uint64 SizeInBytes int64
} }
func (pkg *BPMPackage) GetInstalledSize() uint64 { func (pkg *BPMPackage) GetInstalledSize() int64 {
var totalSize uint64 = 0 var totalSize int64 = 0
for _, entry := range pkg.PkgFiles { for _, entry := range pkg.PkgFiles {
totalSize += entry.SizeInBytes totalSize += entry.SizeInBytes
} }
@@ -120,13 +120,6 @@ const (
InstallationReasonUnknown InstallationReason = "unknown" InstallationReasonUnknown InstallationReason = "unknown"
) )
func ComparePackageVersions(info1, info2 PackageInfo) int {
v1 := version.NewVersion(info1.GetFullVersion())
v2 := version.NewVersion(info2.GetFullVersion())
return v1.Compare(v2)
}
func GetInstallationReason(pkg, rootDir string) InstallationReason { func GetInstallationReason(pkg, rootDir string) InstallationReason {
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)
@@ -239,7 +232,7 @@ func ReadPackage(filename string) (*BPMPackage, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
size, err := strconv.ParseUint(stringEntry[len(stringEntry)-1], 0, 64) size, err := strconv.ParseInt(stringEntry[len(stringEntry)-1], 0, 64)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -375,9 +368,44 @@ func executePackageScript(pkg, rootDir string, verbose bool, packageScript strin
cmd := exec.Command("/bin/bash", "-c", content) cmd := exec.Command("/bin/bash", "-c", content)
// Setup subprocess environment // Setup subprocess environment
cmd.Dir = "/" cmd.Dir = "/"
// Run hook in chroot if using the -R flag // Run package script in chroot if using the -R flag
if rootDir != "/" { if rootDir != "/" {
cmd.SysProcAttr = &syscall.SysProcAttr{Chroot: rootDir} cmd.SysProcAttr = &syscall.SysProcAttr{Chroot: rootDir}
// Bind mount /etc/resolv.conf
mounted, err := func() (bool, error) {
if _, err := os.Stat("/etc/resolv.conf"); err != nil {
return false, nil
}
if _, err := os.Stat(path.Join(rootDir, "etc/resolv.conf")); os.IsNotExist(err) {
err = os.WriteFile(path.Join(rootDir, "/etc/resolv.conf"), nil, 0644)
if err != nil {
return false, nil
}
} else if err != nil {
return false, err
}
mntCmd := exec.Command("mount", "-o", "ro,bind", "/etc/resolv.conf", path.Join(rootDir, "/etc/resolv.conf"))
if verbose {
mntCmd.Stdout = os.Stdout
mntCmd.Stderr = os.Stderr
}
err = mntCmd.Run()
if err != nil {
return false, err
}
return true, nil
}()
if err != nil {
return err
}
if mounted {
defer exec.Command("umount", "-f", path.Join(rootDir, "/etc/resolv.conf")).Run()
}
} }
// Show output if verbose // Show output if verbose
if verbose { if verbose {
@@ -434,6 +462,7 @@ func ReadPackageInfo(contents string) (*PackageInfo, error) {
Conflicts: make([]string, 0), Conflicts: make([]string, 0),
Replaces: make([]string, 0), Replaces: make([]string, 0),
Provides: make([]string, 0), Provides: make([]string, 0),
Options: make([]string, 0),
Downloads: make([]PackageDownload, 0), Downloads: make([]PackageDownload, 0),
SplitPackages: make([]*PackageInfo, 0), SplitPackages: make([]*PackageInfo, 0),
} }
@@ -507,39 +536,47 @@ func ReadPackageInfo(contents string) (*PackageInfo, error) {
return pkgInfo, nil return pkgInfo, nil
} }
func CreateReadableInfo(showArchitecture, showType, showPackageRelations, showInstallationReason bool, pkgInfo *PackageInfo, rootDir string) string { func (pkgInfo *PackageInfo) CreateReadableInfo(rootDir string) string {
ret := make([]string, 0) ret := make([]string, 0)
appendArray := func(label string, array []string) { appendArray := func(label string, array []string) {
if len(array) == 0 { if len(array) == 0 {
return return
} }
ret = append(ret, fmt.Sprintf("%s: %s", label, strings.Join(array, ", "))) ret = append(ret, fmt.Sprintf("%s: %s", label, strings.Join(array, ", ")))
} }
ret = append(ret, "Name: "+pkgInfo.Name) ret = append(ret, "Name: "+pkgInfo.Name)
ret = append(ret, "Description: "+pkgInfo.Description) ret = append(ret, "Description: "+pkgInfo.Description)
ret = append(ret, "Version: "+pkgInfo.GetFullVersion()) ret = append(ret, "Version: "+pkgInfo.GetFullVersion())
ret = append(ret, "URL: "+pkgInfo.Url) if pkgInfo.Url != "" {
ret = append(ret, "License: "+pkgInfo.License) ret = append(ret, "URL: "+pkgInfo.Url)
if showArchitecture {
ret = append(ret, "Architecture: "+pkgInfo.Arch)
} }
if showType { if pkgInfo.License != "" {
ret = append(ret, "Type: "+pkgInfo.Type) ret = append(ret, "License: "+pkgInfo.License)
} }
if showPackageRelations { ret = append(ret, "Architecture: "+pkgInfo.Arch)
appendArray("Dependencies", pkgInfo.Depends) if pkgInfo.Type == "source" && pkgInfo.OutputArch != "" && pkgInfo.OutputArch != GetArch() {
if pkgInfo.Type == "source" { ret = append(ret, "Output architecture: "+pkgInfo.Arch)
appendArray("Make Dependencies", pkgInfo.MakeDepends)
}
appendArray("Optional dependencies", pkgInfo.OptionalDepends)
dependants, err := GetPackageDependants(pkgInfo.Name, rootDir)
if err == nil {
appendArray("Dependant packages", dependants)
}
appendArray("Conflicting packages", pkgInfo.Conflicts)
appendArray("Provided packages", pkgInfo.Provides)
appendArray("Replaces packages", pkgInfo.Replaces)
} }
ret = append(ret, "Type: "+pkgInfo.Type)
appendArray("Dependencies", pkgInfo.Depends)
if pkgInfo.Type == "source" {
appendArray("Make Dependencies", pkgInfo.MakeDepends)
}
appendArray("Optional dependencies", pkgInfo.OptionalDepends)
dependants := pkgInfo.GetPackageDependants(rootDir)
if len(dependants) > 0 {
appendArray("Dependant packages", dependants)
}
optionalDependants := pkgInfo.GetPackageOptionalDependants(rootDir)
if len(optionalDependants) > 0 {
appendArray("Optionally dependant packages", optionalDependants)
}
appendArray("Conflicting packages", pkgInfo.Conflicts)
appendArray("Provided packages", pkgInfo.Provides)
appendArray("Replaces packages", pkgInfo.Replaces)
if pkgInfo.Type == "source" && len(pkgInfo.SplitPackages) != 0 { if pkgInfo.Type == "source" && len(pkgInfo.SplitPackages) != 0 {
splitPkgs := make([]string, len(pkgInfo.SplitPackages)) splitPkgs := make([]string, len(pkgInfo.SplitPackages))
for i, splitPkgInfo := range pkgInfo.SplitPackages { for i, splitPkgInfo := range pkgInfo.SplitPackages {
@@ -547,7 +584,8 @@ func CreateReadableInfo(showArchitecture, showType, showPackageRelations, showIn
} }
appendArray("Split Packages", splitPkgs) appendArray("Split Packages", splitPkgs)
} }
if IsPackageInstalled(pkgInfo.Name, rootDir) && showInstallationReason {
if rootDir != "" && IsPackageInstalled(pkgInfo.Name, rootDir) {
installationReason := GetInstallationReason(pkgInfo.Name, rootDir) installationReason := GetInstallationReason(pkgInfo.Name, rootDir)
var installationReasonString string var installationReasonString string
switch installationReason { switch installationReason {
@@ -562,27 +600,21 @@ func CreateReadableInfo(showArchitecture, showType, showPackageRelations, showIn
} }
ret = append(ret, "Installation Reason: "+installationReasonString) ret = append(ret, "Installation Reason: "+installationReasonString)
} }
return strings.Join(ret, "\n") return strings.Join(ret, "\n")
} }
func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string) error { func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string) error {
if !IsPackageInstalled(bpmpkg.PkgInfo.Name, rootDir) {
err := executePackageScript(filename, rootDir, verbose, "pre_install.sh")
if err != nil {
log.Printf("Warning: %s\n", err)
}
} else {
err := executePackageScript(filename, rootDir, verbose, "pre_update.sh")
if err != nil {
log.Printf("Warning: %s\n", err)
}
}
seenHardlinks := make(map[string]string) seenHardlinks := make(map[string]string)
file, err := os.Open(filename) file, err := os.Open(filename)
if err != nil { if err != nil {
return err return err
} }
// Initialize progress bar
bar := createProgressBar(bpmpkg.GetInstalledSize(), "Installing "+bpmpkg.PkgInfo.Name, verbose)
defer bar.Close()
tarballFile, err := readTarballFile(filename, "files.tar.gz") tarballFile, err := readTarballFile(filename, "files.tar.gz")
if err != nil { if err != nil {
return err return err
@@ -594,6 +626,7 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
return err return err
} }
packageFilesReader := tar.NewReader(archive) packageFilesReader := tar.NewReader(archive)
for { for {
header, err := packageFilesReader.Next() header, err := packageFilesReader.Next()
if err == io.EOF { if err == io.EOF {
@@ -630,6 +663,7 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
if verbose { if verbose {
fmt.Printf("Created directory %s (%o)\n", extractFilename, header.Mode) fmt.Printf("Created directory %s (%o)\n", extractFilename, header.Mode)
} }
bar.Add64(header.Size)
case tar.TypeReg: case tar.TypeReg:
skip := false skip := false
if _, err := os.Stat(extractFilename); err == nil { if _, err := os.Stat(extractFilename); err == nil {
@@ -686,6 +720,7 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
if verbose { if verbose {
fmt.Printf("Created File: %s (%o)\n", extractFilename, header.Mode) fmt.Printf("Created File: %s (%o)\n", extractFilename, header.Mode)
} }
bar.Add64(header.Size)
case tar.TypeSymlink: case tar.TypeSymlink:
err := os.Remove(extractFilename) err := os.Remove(extractFilename)
if err != nil && !os.IsNotExist(err) { if err != nil && !os.IsNotExist(err) {
@@ -700,6 +735,7 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
if verbose { if verbose {
fmt.Println("Created Symlink: " + extractFilename + " -> " + header.Linkname) fmt.Println("Created Symlink: " + extractFilename + " -> " + header.Linkname)
} }
bar.Add64(header.Size)
case tar.TypeLink: case tar.TypeLink:
if verbose { if verbose {
fmt.Println("Detected Hard Link: " + extractFilename + " -> " + path.Join(rootDir, strings.TrimPrefix(header.Linkname, "files/"))) fmt.Println("Detected Hard Link: " + extractFilename + " -> " + path.Join(rootDir, strings.TrimPrefix(header.Linkname, "files/")))
@@ -709,6 +745,7 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
if err != nil && !os.IsNotExist(err) { if err != nil && !os.IsNotExist(err) {
return err return err
} }
bar.Add64(header.Size)
default: default:
return errors.New("unknown type (" + strconv.Itoa(int(header.Typeflag)) + ") in " + extractFilename) return errors.New("unknown type (" + strconv.Itoa(int(header.Typeflag)) + ") in " + extractFilename)
} }
@@ -743,6 +780,20 @@ func installPackage(filename, rootDir string, verbose, force bool) error {
} }
packageInstalled := IsPackageInstalled(bpmpkg.PkgInfo.Name, rootDir) packageInstalled := IsPackageInstalled(bpmpkg.PkgInfo.Name, rootDir)
// Run pre-* package scripts
if !packageInstalled {
err := executePackageScript(filename, rootDir, verbose, "pre_install.sh")
if err != nil {
log.Printf("Warning: %s\n", err)
}
} else {
err := executePackageScript(filename, rootDir, verbose, "pre_update.sh")
if err != nil {
log.Printf("Warning: %s\n", err)
}
}
// Check if package is installed and remove current files // Check if package is installed and remove current files
if packageInstalled { if packageInstalled {
// Fetching and reversing package file entry list // Fetching and reversing package file entry list
@@ -924,6 +975,7 @@ func installPackage(filename, rootDir string, verbose, force bool) error {
f.Close() f.Close()
} }
// Run post-* package scripts
if !packageInstalled { if !packageInstalled {
err = executePackageScript(filename, rootDir, verbose, "post_install.sh") err = executePackageScript(filename, rootDir, verbose, "post_install.sh")
if err != nil { if err != nil {
@@ -943,7 +995,7 @@ func installPackage(filename, rootDir string, verbose, force bool) error {
} }
// Add or update package information for rootDir // Add or update package information for rootDir
localPackageInformation[rootDir][bpmpkg.PkgInfo.Name] = bpmpkg localPackageInformation[rootDir][bpmpkg.PkgInfo.Name] = bpmpkg.PkgInfo
return nil return nil
} }
@@ -961,8 +1013,11 @@ func removePackage(pkg string, verbose bool, rootDir string) error {
log.Printf("Warning: %s\n", err) log.Printf("Warning: %s\n", err)
} }
// Get BPM package
bpmpkg := GetPackage(pkg, rootDir)
// Fetching and reversing package file entry list // Fetching and reversing package file entry list
fileEntries := GetPackage(pkg, rootDir).PkgFiles fileEntries := bpmpkg.PkgFiles
sort.Slice(fileEntries, func(i, j int) bool { sort.Slice(fileEntries, func(i, j int) bool {
return fileEntries[i].Path < fileEntries[j].Path return fileEntries[i].Path < fileEntries[j].Path
}) })
@@ -972,8 +1027,12 @@ func removePackage(pkg string, verbose bool, rootDir string) error {
return err return err
} }
bar := createProgressBar(bpmpkg.GetInstalledSize(), "Removing "+bpmpkg.PkgInfo.Name, verbose)
defer bar.Close()
// Removing package files // Removing package files
for _, entry := range fileEntries { for _, entry := range fileEntries {
bar.Add64(entry.SizeInBytes)
file := path.Join(rootDir, entry.Path) file := path.Join(rootDir, entry.Path)
lstat, err := os.Lstat(file) lstat, err := os.Lstat(file)
if os.IsNotExist(err) { if os.IsNotExist(err) {
+69 -22
View File
@@ -2,10 +2,15 @@ package bpmlib
import ( import (
"fmt" "fmt"
"io"
"math" "math"
"os" "os"
"path" "path"
"syscall" "syscall"
"time"
version "github.com/knqyf263/go-rpm-version"
"github.com/schollz/progressbar/v3"
) )
type BPMLock struct { type BPMLock struct {
@@ -13,20 +18,6 @@ type BPMLock struct {
path string path string
} }
func (lock *BPMLock) Unlock() error {
err := lock.file.Close()
if err != nil {
return err
}
err = os.Remove(lock.path)
if err != nil {
return err
}
return nil
}
func LockBPM(rootDir string) (*BPMLock, error) { func LockBPM(rootDir string) (*BPMLock, error) {
// Create parent directories if they don't already exist // Create parent directories if they don't already exist
err := os.MkdirAll(path.Join(rootDir, "/var/lib/bpm"), 0755) err := os.MkdirAll(path.Join(rootDir, "/var/lib/bpm"), 0755)
@@ -49,6 +40,20 @@ func LockBPM(rootDir string) (*BPMLock, error) {
return &BPMLock{f, path.Join(rootDir, "var/lib/bpm/bpm.lock")}, nil return &BPMLock{f, path.Join(rootDir, "var/lib/bpm/bpm.lock")}, nil
} }
func (lock *BPMLock) Unlock() error {
err := lock.file.Close()
if err != nil {
return err
}
err = os.Remove(lock.path)
if err != nil {
return err
}
return nil
}
func GetArch() string { func GetArch() string {
uname := syscall.Utsname{} uname := syscall.Utsname{}
err := syscall.Uname(&uname) err := syscall.Uname(&uname)
@@ -64,6 +69,47 @@ func GetArch() string {
return string(byteString[:indexLength]) return string(byteString[:indexLength])
} }
func CompareVersions(version1, version2 string) int {
v1 := version.NewVersion(version1)
v2 := version.NewVersion(version2)
return v1.Compare(v2)
}
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 { func stringSliceRemove(s []string, r string) []string {
for i, v := range s { for i, v := range s {
if v == r { if v == r {
@@ -73,7 +119,7 @@ func stringSliceRemove(s []string, r string) []string {
return s return s
} }
func unsignedBytesToHumanReadable(b uint64) string { func BytesToHumanReadable(b int64) string {
bf := float64(b) bf := float64(b)
for _, unit := range []string{"", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"} { for _, unit := range []string{"", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"} {
if math.Abs(bf) < 1024.0 { if math.Abs(bf) < 1024.0 {
@@ -84,13 +130,14 @@ func unsignedBytesToHumanReadable(b uint64) string {
return fmt.Sprintf("%.1fYiB", bf) return fmt.Sprintf("%.1fYiB", bf)
} }
func bytesToHumanReadable(b int64) string { func removeDuplicates[T comparable](sliceList []T) []T {
bf := float64(b) allKeys := make(map[T]bool)
for _, unit := range []string{"", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"} { list := []T{}
if math.Abs(bf) < 1024.0 { for _, item := range sliceList {
return fmt.Sprintf("%3.1f%sB", bf, unit) if _, value := allKeys[item]; !value {
allKeys[item] = true
list = append(list, item)
} }
bf /= 1024.0
} }
return fmt.Sprintf("%.1fYiB", bf) return list
} }