mirror of
https://github.com/EnumeratedDev/bpm.git
synced 2026-09-16 02:26:12 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80382c849b
|
||
|
|
6f524ab6fa
|
||
|
|
cb01c96ad6
|
||
|
|
e6456105e1
|
||
|
|
a496777b9e
|
||
|
|
cb191dc16a
|
||
|
|
140cd3c64b
|
||
|
|
677da23c9d
|
||
|
|
7f279ef8e9
|
||
|
|
3950c5f0d6
|
||
|
|
f3c2b9f0f6
|
||
|
|
dc8959c2f8
|
||
|
|
9122ffb9ca
|
||
|
|
a4e20b7d88
|
||
|
|
a03c9ad9ce
|
||
|
|
3f205ec412
|
||
|
|
2f9487affe
|
||
|
|
596d8753a7
|
||
|
|
2de4eea7a4
|
||
|
|
5d0312856d
|
||
|
|
248359c02f
|
||
|
|
1d81690bff
|
||
|
|
618916cd22
|
||
|
|
8e4177c899
|
||
|
|
3f23580d16
|
||
|
|
95dee0560a
|
||
|
|
45721cfc43
|
||
|
|
2aceb9efe8
|
||
|
|
bc3df83936
|
||
|
|
f18c837bc2
|
||
|
|
76ad8e77c5
|
||
|
|
9ae2d3f893
|
||
|
|
89c0c0892f
|
||
|
|
ad89650dc4
|
||
|
|
3a7adb27ab
|
||
|
|
f6aa01339e
|
||
|
|
a29de2e52d
|
||
|
|
619335bbe6
|
||
|
|
8579c688d7
|
||
|
|
48cec35f0c
|
||
|
|
e19d64bf04
|
||
|
|
ee80640153
|
||
|
|
e4d94e378f
|
||
|
|
b37360ed82
|
||
|
|
ba0d288e45
|
||
|
|
c1fa9d9474
|
||
|
|
0742c81f2f
|
||
|
|
dd20574470
|
||
|
|
b3dc6e3e34
|
||
|
|
0a06808303
|
||
|
|
9f8a20c671
|
||
|
|
2072e3856f
|
||
|
|
72f574209f
|
||
|
|
9c2d4595c7
|
||
|
|
72f17f8954
|
||
|
|
fd646a820d
|
||
|
|
f23537ba7d
|
||
|
|
6e9f16837c
|
||
|
|
271e87cb77
|
||
|
|
25ccc208d7
|
||
|
|
9c7e20d27d
|
||
|
|
6206fdbf43
|
||
|
|
9e48d97dc8
|
||
|
|
08a4e24889
|
||
|
|
4a32df6335
|
||
|
|
548b2476bd
|
||
|
|
a660039be1
|
||
|
|
44c2678ef9
|
||
|
|
2a7c6ce5d9
|
||
|
|
f98760d1dd
|
||
|
|
2ba78f16db
|
||
|
|
04a71b573b
|
||
|
|
aa229af8df
|
||
|
|
9912b173ff
|
||
|
|
879c832f60
|
||
|
|
2c91a9332b
|
||
|
|
1895fabfb0
|
||
|
|
0dfb66c6c0
|
||
|
|
dfdf38bad8
|
||
|
|
52f25c8f3f
|
||
|
|
7e372eef91
|
||
|
|
b07a7f8778
|
||
|
|
b2fc963389
|
||
|
|
b27e9fb0df
|
||
|
|
089bc61d58
|
||
|
|
fb821b3190
|
||
|
|
444b2032e2
|
||
|
|
70ba64ca6b
|
||
|
|
ed172f2805
|
||
|
|
f0ea57d500
|
||
|
|
e923aefff9
|
||
|
|
c2d420d854
|
||
|
|
2db01fb0d4
|
||
|
|
b451c5c320
|
||
|
|
27427d5654
|
||
|
|
04e966fef5
|
||
|
|
8a7519628f
|
||
|
|
8986e24334
|
||
|
|
f066e26dc2
|
||
|
|
bee23e2f56
|
||
|
|
d56005e63e
|
||
|
|
c9e16d6df1
|
@@ -7,12 +7,13 @@ SYSCONFDIR := $(PREFIX)/etc
|
||||
GO ?= go
|
||||
|
||||
# Build-time variables
|
||||
VERSION ?= $(shell git describe --tags --dirty)
|
||||
ROOT_COMPILATION_UID ?= 65534
|
||||
ROOT_COMPILATION_GID ?= 65534
|
||||
|
||||
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/
|
||||
# Create directory
|
||||
|
||||
+11
-7
@@ -1,19 +1,23 @@
|
||||
module git.enumerated.dev/bubble-package-manager/bpm/src/bpm
|
||||
module bpm
|
||||
|
||||
go 1.23
|
||||
|
||||
toolchain go1.23.7
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
git.enumerated.dev/bubble-package-manager/bpm/src/bpmlib v0.5.0
|
||||
github.com/adrg/strutil v0.3.1
|
||||
github.com/EnumeratedDev/bpm/src/bpmlib v0.0.0
|
||||
github.com/lithammer/fuzzysearch v1.1.8
|
||||
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 (
|
||||
github.com/drone/envsubst v1.0.3 // indirect
|
||||
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f // indirect
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/schollz/progressbar/v3 v3.18.0 // indirect
|
||||
golang.org/x/sys v0.37.0 // indirect
|
||||
golang.org/x/term v0.36.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
+50
-11
@@ -1,6 +1,5 @@
|
||||
github.com/adrg/strutil v0.3.1 h1:OLvSS7CSJO8lBii4YmBt8jiK9QOtB9CzCzwl4Ic/Fz4=
|
||||
github.com/adrg/strutil v0.3.1/go.mod h1:8h90y18QLrs11IBffcGX3NW/GFBXCMcNg4M7H6MspPA=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM=
|
||||
github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g=
|
||||
@@ -9,19 +8,59 @@ github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f h1:xt29M2T6STgldg+WEP51gGePQCsQvklmP2eIhPIBK3g=
|
||||
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f/go.mod h1:i4sF0l1fFnY1aiw08QQSwVAFxHEm311Me3WsU/X7nL0=
|
||||
github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
|
||||
github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/schollz/progressbar/v3 v3.18.0 h1:uXdoHABRFmNIjUfte/Ex7WtuyVslrw2wVPQmCN62HpA=
|
||||
github.com/schollz/progressbar/v3 v3.18.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
|
||||
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
+661
-164
File diff suppressed because it is too large
Load Diff
+257
-42
@@ -6,9 +6,12 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -21,7 +24,12 @@ import (
|
||||
var rootCompilationUID = "65534"
|
||||
var rootCompilationGID = "65534"
|
||||
|
||||
func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bool) (outputBpmPackages map[string]string, err error) {
|
||||
func CompileSourcePackage(archiveFilename, outputDirectory string, flags map[string]string, compilationJobs int, skipChecks, keepCompilationFiles, verbose bool) (outputBpmPackages map[string]string, err error) {
|
||||
// Set compilation jobs
|
||||
if compilationJobs <= 0 || compilationJobs > runtime.NumCPU() {
|
||||
compilationJobs = runtime.NumCPU()
|
||||
}
|
||||
|
||||
// Initialize map
|
||||
outputBpmPackages = make(map[string]string)
|
||||
|
||||
@@ -88,8 +96,8 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Extract source.sh file
|
||||
err = extractTarballFile(archiveFilename, "source.sh", tempDirectory, uid, gid)
|
||||
// Extract recipe.sh file
|
||||
err = extractTarballFile(archiveFilename, "recipe.sh", tempDirectory, uid, gid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -122,7 +130,7 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
|
||||
}
|
||||
|
||||
// Download files
|
||||
err = downloadPackageFiles(bpmpkg.PkgInfo, tempDirectory)
|
||||
err = downloadPackageFiles(bpmpkg.PkgInfo, tempDirectory, verbose)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -135,16 +143,45 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
|
||||
env = append(env, "BPM_PKG_NAME="+bpmpkg.PkgInfo.Name)
|
||||
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_URL="+bpmpkg.PkgInfo.Url)
|
||||
env = append(env, "BPM_PKG_ARCH="+bpmpkg.PkgInfo.OutputArch)
|
||||
env = append(env, "BPM_JOBS="+strconv.Itoa(compilationJobs))
|
||||
for _, flag := range bpmpkg.PkgInfo.Flags {
|
||||
if value, ok := flags[flag.Name]; ok {
|
||||
env = append(env, "BPM_PKG_FLAG_"+strings.ToUpper(flag.Name)+"="+value)
|
||||
fmt.Printf("Package flag: %s=%s\n", flag.Name, value)
|
||||
} else {
|
||||
env = append(env, "BPM_PKG_FLAG_"+strings.ToUpper(flag.Name)+"="+flag.DefaultValue)
|
||||
fmt.Printf("Package flag: %s=%s\n", flag.Name, flag.DefaultValue)
|
||||
}
|
||||
}
|
||||
env = append(env, CompilationBPMConfig.CompilationEnvironment...)
|
||||
|
||||
// Execute prepare and build functions in source.sh script
|
||||
// Set common flags used for limiting job count
|
||||
makeflags := ""
|
||||
env = slices.DeleteFunc(env, func(s string) bool {
|
||||
if strings.HasPrefix(s, "MAKEFLAGS=") {
|
||||
makeflags = s + " "
|
||||
return true
|
||||
} else if strings.HasPrefix(s, "CMAKE_BUILD_PARALLEL_LEVEL=") {
|
||||
return true
|
||||
} else if strings.HasPrefix(s, "CARGO_BUILD_JOBS=") {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
env = append(env, makeflags+"-j"+strconv.Itoa(compilationJobs))
|
||||
env = append(env, "CMAKE_BUILD_PARALLEL_LEVEL="+strconv.Itoa(compilationJobs))
|
||||
env = append(env, "CARGO_BUILD_JOBS="+strconv.Itoa(compilationJobs))
|
||||
|
||||
// Execute prepare and build functions in recipe.sh script
|
||||
cmd := exec.Command("bash", "-c",
|
||||
"set -a\n"+ // Source and export functions and variables in source.sh script
|
||||
". \"${BPM_WORKDIR}\"/source.sh\n"+
|
||||
"set -a\n"+ // Source and export functions and variables in recipe.sh script
|
||||
". \"${BPM_WORKDIR}\"/recipe.sh\n"+
|
||||
"set +a\n"+
|
||||
"[[ $(type -t prepare) == \"function\" ]] && { echo \"Running prepare() function\"; bash -e -c 'cd \"$BPM_WORKDIR\" && prepare' || exit 1; }\n"+ // Run prepare() function if it exists
|
||||
"[[ $(type -t build) == \"function\" ]] && { echo \"Running build() function\"; bash -e -c 'cd \"$BPM_SOURCE\" && build' || exit 1; }\n"+ // Run build() function if it exists
|
||||
"[[ $(type -t prepare) == \"function\" ]] && { echo \"Running prepare() function...\"; bash -e -c 'cd \"$BPM_WORKDIR\" && prepare' || exit 1; }\n"+ // Run prepare() function if it exists
|
||||
"[[ $(type -t build) == \"function\" ]] && { echo \"Running build() function...\"; bash -e -c 'cd \"$BPM_SOURCE\" && build' || exit 1; }\n"+ // Run build() function if it exists
|
||||
"exit 0")
|
||||
cmd.Dir = tempDirectory
|
||||
cmd.Stdout = os.Stdout
|
||||
@@ -159,13 +196,13 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Execute check function in source.sh script if not skipping checks
|
||||
// Execute check function in recipe.sh script if not skipping checks
|
||||
if !skipChecks {
|
||||
cmd = exec.Command("bash", "-c",
|
||||
"set -a\n"+ // Source and export functions and variables in source.sh script
|
||||
". \"${BPM_WORKDIR}\"/source.sh\n"+
|
||||
"set -a\n"+ // Source and export functions and variables in recipe.sh script
|
||||
". \"${BPM_WORKDIR}\"/recipe.sh\n"+
|
||||
"set +a\n"+
|
||||
"[[ $(type -t check) == \"function\" ]] && { echo \"Running check() function\"; bash -e -c 'cd \"$BPM_SOURCE\" && check' || exit 1; }\n"+ // Run check() function if it exists
|
||||
"[[ $(type -t check) == \"function\" ]] && { echo \"Running check() function...\"; bash -e -c 'cd \"$BPM_SOURCE\" && check' || exit 1; }\n"+ // Run check() function if it exists
|
||||
"exit 0")
|
||||
cmd.Dir = tempDirectory
|
||||
cmd.Stdout = os.Stdout
|
||||
@@ -210,14 +247,13 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
|
||||
packageFunctionName = "package_" + pkg.Name
|
||||
}
|
||||
|
||||
// Execute package function in source.sh script and generate package file list
|
||||
// Execute package function in recipe.sh script and generate package file list
|
||||
cmd = exec.Command("bash", "-c",
|
||||
"set -a\n"+ // Source and export functions and variables in source.sh script
|
||||
". \"${BPM_WORKDIR}\"/source.sh\n"+
|
||||
"set -a\n"+ // Source and export functions and variables in recipe.sh script
|
||||
". \"${BPM_WORKDIR}\"/recipe.sh\n"+
|
||||
"set +a\n"+
|
||||
"echo \"Running "+packageFunctionName+"() function\"\n"+
|
||||
"( cd \"$BPM_SOURCE\" && fakeroot -s \"$BPM_WORKDIR\"/fakeroot_file bash -e -c '"+packageFunctionName+"' ) || exit 1\n"+ // Run package() function
|
||||
"fakeroot -i \"$BPM_WORKDIR\"/fakeroot_file find \"$BPM_OUTPUT\" -mindepth 1 -printf \"%P %#m %U %G %s\\n\" > \"$BPM_WORKDIR\"/pkg.files") // Create package file list
|
||||
"echo \"Running "+packageFunctionName+"() function...\"\n"+
|
||||
"( cd \"$BPM_SOURCE\" && fakeroot -s \"$BPM_WORKDIR\"/fakeroot_file bash -e -c '"+packageFunctionName+"' ) || exit 1\n") // Run package() function
|
||||
|
||||
cmd.Dir = tempDirectory
|
||||
cmd.Stdout = os.Stdout
|
||||
@@ -232,8 +268,101 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Strip all ELF binaries
|
||||
if !slices.Contains(pkg.Options, "nostrip") {
|
||||
if _, err := exec.LookPath("strip"); err != nil {
|
||||
fmt.Println("Skip stripping ELF binaries. 'strip' not in path")
|
||||
} else {
|
||||
fmt.Println("Stripping ELF binaries...")
|
||||
|
||||
err = filepath.WalkDir(path.Join(tempDirectory, "output_"+pkg.Name), func(path string, d fs.DirEntry, err error) error {
|
||||
cmd = exec.Command("file", "-bi", path)
|
||||
cmd.Stderr = os.Stderr
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mimetype := strings.Split(string(output), ";")[0]
|
||||
|
||||
if strings.HasPrefix(mimetype, "application/x-executable") {
|
||||
cmd := exec.Command("strip", path)
|
||||
cmd.Stderr = os.Stderr
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if verbose {
|
||||
fmt.Printf("Stripped %s (%s)\n", path, mimetype)
|
||||
}
|
||||
} else if strings.HasPrefix(mimetype, "application/x-pie-executable") || strings.HasPrefix(mimetype, "application/x-sharedlib") {
|
||||
cmd = exec.Command("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\"/files.txt")
|
||||
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
|
||||
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))
|
||||
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 \
|
||||
--sort=name \
|
||||
--pax-option=exthdr.name=%%d/PaxHeaders/%%f,delete=atime,delete=ctime \
|
||||
--mtime="UTC 1970-01-01" \
|
||||
--numeric-owner \
|
||||
--no-recursion \
|
||||
-C %s \
|
||||
-T -`,
|
||||
"output_"+pkg.Name, tempDirectory, "output_"+pkg.Name))
|
||||
cmd.Dir = tempDirectory
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
@@ -261,6 +390,31 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
|
||||
pkgInfo.SplitPackages = nil
|
||||
pkgInfo.Downloads = nil
|
||||
|
||||
// Set built flag values and add their dependencies
|
||||
for i, flag := range pkgInfo.Flags {
|
||||
if value, ok := flags[flag.Name]; ok {
|
||||
flag.BuiltValue = value
|
||||
} else {
|
||||
flag.BuiltValue = flag.DefaultValue
|
||||
}
|
||||
|
||||
acceptedValueIndex := slices.IndexFunc(flag.AcceptedValues, func(acceptedValue PackageAcceptedValue) bool {
|
||||
return acceptedValue.Value == flag.BuiltValue
|
||||
})
|
||||
if acceptedValueIndex < 0 {
|
||||
return nil, fmt.Errorf("flag value not accepted: %s=%s", flag.Name, flag.BuiltValue)
|
||||
}
|
||||
acceptedValue := flag.AcceptedValues[acceptedValueIndex]
|
||||
|
||||
pkgInfo.Flags[i] = flag
|
||||
|
||||
pkgInfo.Depends = append(pkgInfo.Depends, acceptedValue.Depends...)
|
||||
pkgInfo.RuntimeDepends = append(pkgInfo.RuntimeDepends, acceptedValue.RuntimeDepends...)
|
||||
pkgInfo.OptionalDepends = append(pkgInfo.OptionalDepends, acceptedValue.OptionalDepends...)
|
||||
pkgInfo.MakeDepends = append(pkgInfo.MakeDepends, acceptedValue.MakeDepends...)
|
||||
pkgInfo.CheckDepends = append(pkgInfo.CheckDepends, acceptedValue.CheckDepends...)
|
||||
}
|
||||
|
||||
// Marshal package info
|
||||
pkgInfoBytes, err := yaml.Marshal(pkgInfo)
|
||||
if err != nil {
|
||||
@@ -268,25 +422,31 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
|
||||
}
|
||||
pkgInfoBytes = append(pkgInfoBytes, '\n')
|
||||
|
||||
// Create pkg.info file
|
||||
err = os.WriteFile(path.Join(tempDirectory, "pkg.info"), pkgInfoBytes, 0644)
|
||||
// Create info.yml file
|
||||
err = os.WriteFile(path.Join(tempDirectory, "info.yml"), pkgInfoBytes, 0644)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Change pkg.info file owner
|
||||
err = os.Chown(path.Join(tempDirectory, "pkg.info"), uid, gid)
|
||||
// Change info.yml file owner
|
||||
err = os.Chown(path.Join(tempDirectory, "info.yml"), uid, gid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get files to include in BPM archive
|
||||
bpmArchiveFiles := make([]string, 0)
|
||||
bpmArchiveFiles = append(bpmArchiveFiles, "pkg.info", "pkg.files", "files.tar.gz") // Base files
|
||||
bpmArchiveFiles = append(bpmArchiveFiles, "info.yml", "files.txt", "files.tar.gz") // Base files
|
||||
bpmArchiveFiles = append(bpmArchiveFiles, packageScripts...) // Package scripts
|
||||
|
||||
// Create final BPM archive
|
||||
cmd = exec.Command("bash", "-c", "tar -cf final-archive.bpm --owner=0 --group=0 -C \"$BPM_WORKDIR\" "+strings.Join(bpmArchiveFiles, " "))
|
||||
fmt.Println("Generating final BPM archive...")
|
||||
cmd = exec.Command("tar", "cf", "final-archive.bpm",
|
||||
"--sort=name",
|
||||
"--pax-option=exthdr.name=%d/PaxHeaders/%f,delete=atime,delete=ctime",
|
||||
"--mtime=UTC 1970-01-01",
|
||||
"--owner=0", "--group=0", "--numeric-owner")
|
||||
cmd.Args = append(cmd.Args, bpmArchiveFiles...)
|
||||
cmd.Dir = tempDirectory
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
@@ -304,8 +464,8 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
|
||||
return nil, fmt.Errorf("BPM archive could not be created: %s", err)
|
||||
}
|
||||
|
||||
// Remove pkg.info file
|
||||
err = os.Remove(path.Join(tempDirectory, "pkg.info"))
|
||||
// Remove info.yml file
|
||||
err = os.Remove(path.Join(tempDirectory, "info.yml"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -334,18 +494,43 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
|
||||
outputBpmPackages[pkgInfo.Name] = outputFilename
|
||||
}
|
||||
|
||||
// Delete temporary directory
|
||||
if !keepCompilationFiles {
|
||||
os.RemoveAll(tempDirectory)
|
||||
}
|
||||
|
||||
return outputBpmPackages, nil
|
||||
}
|
||||
|
||||
func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string) error {
|
||||
func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string, verbose bool) error {
|
||||
// Get UID and GID to use for compilation
|
||||
var uid, gid int
|
||||
if os.Getuid() == 0 {
|
||||
_uid, err := strconv.ParseInt(rootCompilationUID, 10, 32)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not convert UID '%s' to int", rootCompilationUID)
|
||||
}
|
||||
_gid, err := strconv.ParseInt(rootCompilationGID, 10, 32)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not convert GID '%s' to int", rootCompilationGID)
|
||||
}
|
||||
uid = int(_uid)
|
||||
gid = int(_gid)
|
||||
} else {
|
||||
uid = os.Getuid()
|
||||
gid = os.Getgid()
|
||||
}
|
||||
|
||||
for _, download := range pkgInfo.Downloads {
|
||||
// Replace variables
|
||||
replaceVars := func(s string) string {
|
||||
switch s {
|
||||
case "BPM_PKG_VERSION":
|
||||
return pkgInfo.Version
|
||||
case "BPM_PKG_NAME":
|
||||
return pkgInfo.Name
|
||||
case "BPM_PKG_VERSION":
|
||||
return pkgInfo.Version
|
||||
case "BPM_PKG_URL":
|
||||
return pkgInfo.Url
|
||||
case "BPM_SOURCE":
|
||||
return path.Join(tempDirectory, "source/")
|
||||
default:
|
||||
@@ -357,6 +542,10 @@ func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filepath, err := envsubst.Eval(strings.TrimSpace(download.Filepath), replaceVars)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
extractTo, err := envsubst.Eval(strings.TrimSpace(download.ExtractTo), replaceVars)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -377,12 +566,14 @@ func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string) error {
|
||||
|
||||
switch download.Type {
|
||||
case "", "file":
|
||||
filepath := path.Join(tempDirectory, path.Base(downloadUrl))
|
||||
if download.Filepath != "" && download.Filepath[0] != '/' {
|
||||
filepath = path.Join(tempDirectory, download.Filepath)
|
||||
if filepath == "" {
|
||||
filepath = path.Join(tempDirectory, path.Base(downloadUrl))
|
||||
}
|
||||
if filepath[0] != '/' {
|
||||
filepath = path.Join(tempDirectory, filepath)
|
||||
}
|
||||
|
||||
err := downloadFile(downloadUrl, filepath, 0644)
|
||||
err := downloadFile("Downloading file "+path.Base(filepath), downloadUrl, filepath, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -408,9 +599,22 @@ func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string) error {
|
||||
fmt.Println("Skipping checksum checking...")
|
||||
}
|
||||
|
||||
// Change downloaded file ownership
|
||||
err = os.Chown(filepath, uid, gid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !download.NoExtract && (strings.Contains(filepath, ".tar") || strings.HasSuffix(filepath, ".tgz")) {
|
||||
cmd := exec.Command("tar", "xvf", filepath, "--strip-components="+strconv.Itoa(download.ExtractStripComponents))
|
||||
cmd := exec.Command("tar", "xf", filepath, "--strip-components="+strconv.Itoa(download.ExtractStripComponents))
|
||||
if verbose {
|
||||
cmd.Args[1] = "xvf"
|
||||
}
|
||||
cmd.Dir = tempDirectory
|
||||
if os.Getuid() == 0 {
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
||||
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
|
||||
}
|
||||
if extractTo != "" {
|
||||
err := os.MkdirAll(extractTo, 0755)
|
||||
if err != nil {
|
||||
@@ -428,6 +632,11 @@ func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string) error {
|
||||
}
|
||||
} else if !download.NoExtract && strings.HasSuffix(filepath, ".zip") {
|
||||
cmd := exec.Command("unzip", filepath)
|
||||
cmd.Dir = tempDirectory
|
||||
if os.Getuid() == 0 {
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
||||
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
|
||||
}
|
||||
if extractTo != "" {
|
||||
err := os.MkdirAll(extractTo, 0755)
|
||||
if err != nil {
|
||||
@@ -456,10 +665,12 @@ func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string) error {
|
||||
gitBranch := download.GitBranch
|
||||
gitBranch, err := envsubst.Eval(gitBranch, func(s string) string {
|
||||
switch s {
|
||||
case "BPM_PKG_VERSION":
|
||||
return pkgInfo.Version
|
||||
case "BPM_PKG_NAME":
|
||||
return pkgInfo.Name
|
||||
case "BPM_PKG_VERSION":
|
||||
return pkgInfo.Version
|
||||
case "BPM_PKG_URL":
|
||||
return pkgInfo.Url
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
@@ -470,6 +681,10 @@ func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string) error {
|
||||
|
||||
cmd := exec.Command("git", "clone", "--depth=1", downloadUrl)
|
||||
cmd.Dir = tempDirectory
|
||||
if os.Getuid() == 0 {
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
||||
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
|
||||
}
|
||||
if gitBranch != "" {
|
||||
cmd.Args = slices.Insert(cmd.Args, len(cmd.Args)-1, "--branch="+gitBranch)
|
||||
}
|
||||
@@ -557,8 +772,8 @@ func showPackageFiles(archiveFilename string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Print pkg.info content
|
||||
err = printTarballContent("pkg.info")
|
||||
// Print info.yml content
|
||||
err = printTarballContent("info.yml")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -580,8 +795,8 @@ func showPackageFiles(archiveFilename string) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Print source.sh content
|
||||
err = printTarballContent("source.sh")
|
||||
// Print recipe.sh content
|
||||
err = printTarballContent("recipe.sh")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -8,19 +8,22 @@ import (
|
||||
|
||||
type MainBPMConfigStruct struct {
|
||||
IgnorePackages []string `yaml:"ignore_packages"`
|
||||
IgnorePaths []string `yaml:"ignore_paths"`
|
||||
ShowSourcePackageContents string `yaml:"show_source_package_contents"`
|
||||
CleanupMakeDependencies bool `yaml:"cleanup_make_dependencies"`
|
||||
Databases []configDatabase `yaml:"databases"`
|
||||
}
|
||||
|
||||
type configDatabase struct {
|
||||
Name string `yaml:"name"`
|
||||
Source string `yaml:"source"`
|
||||
Disabled *bool `yaml:"disabled"`
|
||||
Name string `yaml:"name"`
|
||||
Source string `yaml:"source"`
|
||||
VerificationLevel string `yaml:"verification_level"`
|
||||
Disabled *bool `yaml:"disabled"`
|
||||
}
|
||||
|
||||
type CompilationBPMConfigStruct struct {
|
||||
PrivilegeEscalatorCmd string `yaml:"privilege_escalator_cmd"`
|
||||
CompilationJobs int `yaml:"compilation_jobs"`
|
||||
CompilationEnvironment []string `yaml:"compilation_env"`
|
||||
}
|
||||
|
||||
|
||||
+515
-48
@@ -1,33 +1,48 @@
|
||||
package bpmlib
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"maps"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type VerificationLevel int
|
||||
|
||||
const (
|
||||
VerificationLevelNone VerificationLevel = iota
|
||||
VerificationLevelAll
|
||||
VerificationLevelTrusted
|
||||
)
|
||||
|
||||
type BPMDatabase struct {
|
||||
DatabaseVersion int `yaml:"database_version"`
|
||||
Entries map[string]*BPMDatabaseEntry `yaml:"entries"`
|
||||
VirtualPackages map[string][]string
|
||||
Source string
|
||||
DatabaseVersion int `yaml:"database_version"`
|
||||
Entries map[string]*BPMDatabaseEntry `yaml:"entries"`
|
||||
VirtualPackages map[string][]*BPMDatabaseEntry
|
||||
Name string
|
||||
VerificationLevel VerificationLevel
|
||||
Source string
|
||||
}
|
||||
|
||||
type BPMDatabaseEntry struct {
|
||||
Info *PackageInfo `yaml:"info"`
|
||||
Filepath string `yaml:"filepath"`
|
||||
DownloadSize uint64 `yaml:"download_size"`
|
||||
InstalledSize uint64 `yaml:"installed_size"`
|
||||
DownloadSize int64 `yaml:"download_size"`
|
||||
InstalledSize int64 `yaml:"installed_size"`
|
||||
Database *BPMDatabase
|
||||
}
|
||||
|
||||
var BPMDatabases = make(map[string]*BPMDatabase)
|
||||
var BPMDatabases = make([]*BPMDatabase, 0)
|
||||
|
||||
func (db *BPMDatabase) ContainsPackage(pkg string) bool {
|
||||
_, ok := db.Entries[pkg]
|
||||
@@ -53,14 +68,26 @@ func (db *configDatabase) ReadLocalDatabase() error {
|
||||
}
|
||||
|
||||
// Initialize struct values
|
||||
database.VirtualPackages = make(map[string][]string)
|
||||
database.VirtualPackages = make(map[string][]*BPMDatabaseEntry)
|
||||
database.Name = db.Name
|
||||
switch db.VerificationLevel {
|
||||
case "0", "none":
|
||||
database.VerificationLevel = VerificationLevelNone
|
||||
case "1", "all":
|
||||
database.VerificationLevel = VerificationLevelAll
|
||||
case "2", "trusted":
|
||||
database.VerificationLevel = VerificationLevelTrusted
|
||||
default:
|
||||
database.VerificationLevel = VerificationLevelAll
|
||||
}
|
||||
database.Source = db.Source
|
||||
|
||||
entriesToRemove := make([]string, 0)
|
||||
for entryName, entry := range database.Entries {
|
||||
entry.Database = database
|
||||
|
||||
if entry.Info.IsSplitPackage() {
|
||||
delete(database.Entries, entryName)
|
||||
|
||||
// Handle split packages
|
||||
for _, splitPkg := range entry.Info.SplitPackages {
|
||||
// Turn split package into json data
|
||||
@@ -97,26 +124,18 @@ func (db *configDatabase) ReadLocalDatabase() error {
|
||||
|
||||
// Add virtual packages to database
|
||||
for _, p := range splitPkg.Provides {
|
||||
database.VirtualPackages[p] = append(database.VirtualPackages[p], splitPkg.Name)
|
||||
database.VirtualPackages[p] = append(database.VirtualPackages[p], database.Entries[splitPkg.Name])
|
||||
}
|
||||
|
||||
// Add current entry to list for removal
|
||||
entriesToRemove = append(entriesToRemove, entryName)
|
||||
}
|
||||
} else {
|
||||
// Add virtual packages to database
|
||||
for _, p := range entry.Info.Provides {
|
||||
database.VirtualPackages[p] = append(database.VirtualPackages[p], entry.Info.Name)
|
||||
database.VirtualPackages[p] = append(database.VirtualPackages[p], entry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove entries
|
||||
for _, entryName := range entriesToRemove {
|
||||
delete(database.Entries, entryName)
|
||||
}
|
||||
|
||||
BPMDatabases[db.Name] = database
|
||||
BPMDatabases = append(BPMDatabases, database)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -131,13 +150,29 @@ func (db *configDatabase) SyncLocalDatabaseFile() error {
|
||||
}
|
||||
|
||||
// Retrieve data from URL
|
||||
buffer, err := retrieveUrlData(u)
|
||||
req, err := http.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Create progress bar
|
||||
bar := createProgressBar(resp.ContentLength, "Syncing "+db.Name, false)
|
||||
|
||||
// Copy data
|
||||
var buffer bytes.Buffer
|
||||
_, err = io.Copy(io.MultiWriter(&buffer, bar), resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Unmarshal data to ensure it is a valid BPM database
|
||||
err = yaml.Unmarshal(buffer, &BPMDatabase{})
|
||||
err = yaml.Unmarshal(buffer.Bytes(), &BPMDatabase{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not decode database: %s", err)
|
||||
}
|
||||
@@ -155,7 +190,7 @@ func (db *configDatabase) SyncLocalDatabaseFile() error {
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
_, err = out.Write(buffer)
|
||||
_, err = out.Write(buffer.Bytes())
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -172,33 +207,85 @@ func ReadLocalDatabaseFiles() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetDatabaseEntry(str string) (*BPMDatabaseEntry, *BPMDatabase, error) {
|
||||
split := strings.Split(str, "/")
|
||||
func ResolveDatabaseEntry(d DeconstructedPackageString, rootDir string) *BPMDatabaseEntry {
|
||||
results := SearchDatabaseEntries(d.PkgName)
|
||||
for _, result := range results {
|
||||
|
||||
if EvaluatePackageString(result.Info, d) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
installedProviders := GetVirtualPackageInfo(d.PkgName, rootDir)
|
||||
for _, provider := range installedProviders {
|
||||
results := SearchDatabaseEntries(provider.Name)
|
||||
for _, result := range results {
|
||||
if EvaluatePackageString(result.Info, d) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
databaseProviders := SearchDatabaseVirtualPackageProviders(d.PkgName)
|
||||
for _, provider := range databaseProviders {
|
||||
results := SearchDatabaseEntries(provider.Info.Name)
|
||||
for _, result := range results {
|
||||
if EvaluatePackageString(result.Info, d) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func SearchDatabaseEntries(pkg string) (results []*BPMDatabaseEntry) {
|
||||
split := strings.Split(pkg, "/")
|
||||
if len(split) == 1 {
|
||||
pkgName := strings.TrimSpace(split[0])
|
||||
if pkgName == "" {
|
||||
return nil, nil, errors.New("could not find database entry for this package")
|
||||
return results
|
||||
}
|
||||
for _, db := range BPMDatabases {
|
||||
if db.ContainsPackage(pkgName) {
|
||||
return db.Entries[pkgName], db, nil
|
||||
results = append(results, db.Entries[pkgName])
|
||||
}
|
||||
}
|
||||
return nil, nil, errors.New("could not find database entry for this package")
|
||||
return results
|
||||
} else if len(split) == 2 {
|
||||
dbName := strings.TrimSpace(split[0])
|
||||
pkgName := strings.TrimSpace(split[1])
|
||||
if dbName == "" || pkgName == "" {
|
||||
return nil, nil, errors.New("could not find database entry for this package")
|
||||
return results
|
||||
}
|
||||
db := BPMDatabases[dbName]
|
||||
if db == nil || !db.ContainsPackage(pkgName) {
|
||||
return nil, nil, errors.New("could not find database entry for this package")
|
||||
dbIndex := slices.IndexFunc(BPMDatabases, func(db *BPMDatabase) bool {
|
||||
return db.Name == dbName
|
||||
})
|
||||
if dbIndex < 0 {
|
||||
return results
|
||||
}
|
||||
return db.Entries[pkgName], db, nil
|
||||
} else {
|
||||
return nil, nil, errors.New("could not find database entry for this package")
|
||||
|
||||
db := BPMDatabases[dbIndex]
|
||||
if !db.ContainsPackage(pkgName) {
|
||||
return results
|
||||
}
|
||||
|
||||
results = append(results, db.Entries[pkgName])
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func SearchDatabaseVirtualPackageProviders(vpkg string) (providers []*BPMDatabaseEntry) {
|
||||
for _, db := range BPMDatabases {
|
||||
providers = append(providers, db.VirtualPackages[vpkg]...)
|
||||
}
|
||||
|
||||
slices.SortFunc(providers, func(a, b *BPMDatabaseEntry) int {
|
||||
return strings.Compare(a.Info.Name, b.Info.Name)
|
||||
})
|
||||
|
||||
return providers
|
||||
}
|
||||
|
||||
func FindReplacement(pkg string) *BPMDatabaseEntry {
|
||||
@@ -215,17 +302,6 @@ func FindReplacement(pkg string) *BPMDatabaseEntry {
|
||||
return nil
|
||||
}
|
||||
|
||||
func ResolveVirtualPackage(vpkg string) *BPMDatabaseEntry {
|
||||
for _, db := range BPMDatabases {
|
||||
if v, ok := db.VirtualPackages[vpkg]; ok {
|
||||
slices.Sort(v)
|
||||
return db.Entries[v[0]]
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *BPMDatabase) FetchPackage(pkg string) (string, error) {
|
||||
// Check if package exists in database
|
||||
if !db.ContainsPackage(pkg) {
|
||||
@@ -240,10 +316,401 @@ func (db *BPMDatabase) FetchPackage(pkg string) (string, error) {
|
||||
}
|
||||
|
||||
// Download package from url
|
||||
err = downloadFile(u, path.Join("/var/cache/bpm/fetched/", path.Base(entry.Filepath)), 0644)
|
||||
filepath := path.Join("/var/cache/bpm/fetched/", path.Base(entry.Filepath))
|
||||
err = downloadFile("Downloading "+entry.Info.Name, u, filepath, 0644)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return path.Join("/var/cache/bpm/fetched/", path.Base(entry.Filepath)), nil
|
||||
// Download and verify signature if required
|
||||
if db.VerificationLevel != VerificationLevelNone {
|
||||
err = downloadFile("", u+".sig", filepath+".sig", 0644)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err := VerifySignature(filepath, filepath+".sig", db.VerificationLevel == VerificationLevelTrusted, "/")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Could not verify signature for %s: %s", filepath, err)
|
||||
}
|
||||
}
|
||||
|
||||
return filepath, nil
|
||||
}
|
||||
|
||||
func (entry *BPMDatabaseEntry) GetEntryDependants() (dependants []string) {
|
||||
dependantsMap := make(map[string][]string)
|
||||
|
||||
// Loop through all entries
|
||||
for _, db := range BPMDatabases {
|
||||
for _, e := range db.Entries {
|
||||
// Skip iteration if comparing the same packages
|
||||
if e.Info.Name == entry.Info.Name {
|
||||
continue
|
||||
}
|
||||
|
||||
// Add installed package to list if its dependencies include pkgName
|
||||
if slices.ContainsFunc(e.Info.Depends, func(n string) bool {
|
||||
d, err := DeconstructPackageString(n)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return d.PkgName == entry.Info.Name
|
||||
}) {
|
||||
dependantsMap[e.Info.Name] = append(dependantsMap[e.Info.Name], db.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
// Add installed package to list if its runtime dependencies include pkgName
|
||||
if slices.ContainsFunc(e.Info.RuntimeDepends, func(n string) bool {
|
||||
d, err := DeconstructPackageString(n)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return d.PkgName == entry.Info.Name
|
||||
}) {
|
||||
dependantsMap[e.Info.Name] = append(dependantsMap[e.Info.Name], db.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
// Loop through each virtual package
|
||||
for _, vpkg := range entry.Info.Provides {
|
||||
// Add installed package to list if its dependencies contain a provided virtual package
|
||||
if slices.ContainsFunc(e.Info.Depends, func(n string) bool {
|
||||
d, err := DeconstructPackageString(n)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return d.PkgName == vpkg
|
||||
}) {
|
||||
dependantsMap[e.Info.Name] = append(dependantsMap[e.Info.Name], db.Name)
|
||||
break
|
||||
}
|
||||
|
||||
// Add installed package to list if its runtime dependencies contain a provided virtual package
|
||||
if slices.ContainsFunc(e.Info.RuntimeDepends, func(n string) bool {
|
||||
d, err := DeconstructPackageString(n)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return d.PkgName == vpkg
|
||||
}) {
|
||||
dependantsMap[e.Info.Name] = append(dependantsMap[e.Info.Name], db.Name)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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.ContainsFunc(e.Info.OptionalDepends, func(n string) bool {
|
||||
// Deconstruct package string
|
||||
d, err := DeconstructPackageString(n)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return d.PkgName == 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) GetEntryMakeDependants() (dependants []string) {
|
||||
dependantsMap := make(map[string][]string)
|
||||
for _, db := range BPMDatabases {
|
||||
for _, e := range db.Entries {
|
||||
if slices.ContainsFunc(e.Info.MakeDepends, func(n string) bool {
|
||||
// Deconstruct package string
|
||||
d, err := DeconstructPackageString(n)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return d.PkgName == 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, showBytes bool) string {
|
||||
builder := strings.Builder{}
|
||||
builderWriteStringNotEmpty := func(label string, value string) {
|
||||
if value != "" {
|
||||
builder.WriteString(label + ": " + value + "\n")
|
||||
}
|
||||
}
|
||||
builderWriteArray := func(label string, array []string, sort bool) {
|
||||
if len(array) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Sort array
|
||||
if sort {
|
||||
slices.Sort(array)
|
||||
}
|
||||
|
||||
builder.WriteString(label + " (" + strconv.Itoa(len(array)) + "):\n")
|
||||
for _, val := range array {
|
||||
builder.WriteString(" - " + val + "\n")
|
||||
}
|
||||
}
|
||||
builderWriteDependencyArray := func(label string, depends []string) {
|
||||
if len(depends) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Sort array
|
||||
slices.Sort(depends)
|
||||
|
||||
builder.WriteString(label + " (" + strconv.Itoa(len(depends)) + "):\n")
|
||||
for _, val := range depends {
|
||||
builder.WriteString(" - " + val)
|
||||
|
||||
// Show virtual package providers
|
||||
if providers := SearchDatabaseVirtualPackageProviders(val); len(providers) > 0 {
|
||||
builder.WriteString(" (")
|
||||
for i, vpkg := range providers {
|
||||
if i == len(providers)-1 {
|
||||
builder.WriteString(vpkg.Info.Name)
|
||||
} else {
|
||||
builder.WriteString(vpkg.Info.Name + ", ")
|
||||
}
|
||||
}
|
||||
builder.WriteString(")")
|
||||
}
|
||||
|
||||
builder.WriteString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
// Main information
|
||||
builder.WriteString("Name: " + entry.Info.Name + "\n")
|
||||
builder.WriteString("Database: " + entry.Database.Name + "\n")
|
||||
builder.WriteString("Description: " + entry.Info.Description + "\n")
|
||||
builder.WriteString("Version: " + entry.Info.GetFullVersion() + "\n")
|
||||
builderWriteStringNotEmpty("URL", entry.Info.Url)
|
||||
builderWriteStringNotEmpty("License", entry.Info.License)
|
||||
builderWriteArray("Maintainers", entry.Info.Maintainers, false)
|
||||
builder.WriteString("Architecture: " + entry.Info.Arch + "\n")
|
||||
if entry.Info.Type == "source" && entry.Info.OutputArch != "" && entry.Info.OutputArch != GetArch() {
|
||||
builder.WriteString("Output architecture: " + entry.Info.OutputArch + "\n")
|
||||
}
|
||||
builder.WriteString("Type: " + entry.Info.Type + "\n")
|
||||
|
||||
// Flags
|
||||
if entry.Info.Type == "binary" {
|
||||
var flags []string
|
||||
for _, flag := range entry.Info.Flags {
|
||||
flags = append(flags, fmt.Sprintf("%s=%s", flag.Name, flag.BuiltValue))
|
||||
}
|
||||
builderWriteArray("Built with flags:", flags, false)
|
||||
} else {
|
||||
if len(entry.Info.Flags) > 0 {
|
||||
builder.WriteString("Available flags (")
|
||||
builder.WriteString(strconv.Itoa(len(entry.Info.Flags)))
|
||||
builder.WriteString("):\n")
|
||||
for _, flag := range entry.Info.Flags {
|
||||
builder.WriteString(" - Flag: ")
|
||||
builder.WriteString(flag.Name)
|
||||
builder.WriteRune('\n')
|
||||
if flag.DefaultValue != "" {
|
||||
builder.WriteString(" Default value: ")
|
||||
builder.WriteString(flag.DefaultValue)
|
||||
builder.WriteRune('\n')
|
||||
}
|
||||
if len(flag.AcceptedValues) > 0 {
|
||||
builder.WriteString(" Accepted values (")
|
||||
builder.WriteString(strconv.Itoa(len(flag.AcceptedValues)))
|
||||
builder.WriteString("):\n")
|
||||
|
||||
for i, acceptedValue := range flag.AcceptedValues {
|
||||
writeValueDepends := func(text string, depends []string) {
|
||||
if len(depends) > 0 {
|
||||
builder.WriteString(" ")
|
||||
builder.WriteString(text)
|
||||
builder.WriteString(" (")
|
||||
builder.WriteString(strconv.Itoa(len(depends)))
|
||||
builder.WriteString("): ")
|
||||
for _, depend := range depends {
|
||||
if i != 0 {
|
||||
builder.WriteString(", ")
|
||||
}
|
||||
builder.WriteString(depend)
|
||||
}
|
||||
builder.WriteRune('\n')
|
||||
}
|
||||
}
|
||||
|
||||
builder.WriteString(" - Value: ")
|
||||
builder.WriteString(acceptedValue.Value)
|
||||
builder.WriteRune('\n')
|
||||
writeValueDepends("Dependencies", acceptedValue.Depends)
|
||||
writeValueDepends("Make Dependencies", acceptedValue.MakeDepends)
|
||||
writeValueDepends("Check Dependencies", acceptedValue.CheckDepends)
|
||||
writeValueDepends("Runtime Dependencies", acceptedValue.RuntimeDepends)
|
||||
writeValueDepends("Optional Dependencies", acceptedValue.OptionalDepends)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dependencies
|
||||
builderWriteDependencyArray("Dependencies", entry.Info.Depends)
|
||||
if entry.Info.Type == "source" {
|
||||
builderWriteDependencyArray("Make dependencies", entry.Info.MakeDepends)
|
||||
builderWriteDependencyArray("Check dependencies", entry.Info.CheckDepends)
|
||||
}
|
||||
builderWriteDependencyArray("Runtime dependencies", entry.Info.RuntimeDepends)
|
||||
if len(entry.Info.OptionalDepends) > 0 {
|
||||
builder.WriteString("Optional dependencies: (" + strconv.Itoa(len(entry.Info.OptionalDepends)) + ")\n")
|
||||
for _, depend := range entry.Info.OptionalDepends {
|
||||
dependSplit := strings.SplitN(depend, ":", 2)
|
||||
if len(dependSplit) == 2 {
|
||||
builder.WriteString(fmt.Sprintf(" - %s (%s)", dependSplit[0], dependSplit[1]))
|
||||
} else {
|
||||
builder.WriteString(" - " + dependSplit[0])
|
||||
}
|
||||
|
||||
// Show virtual package providers
|
||||
if providers := SearchDatabaseVirtualPackageProviders(dependSplit[0]); len(providers) > 0 {
|
||||
builder.WriteString(" (")
|
||||
for i, vpkg := range providers {
|
||||
if i == len(providers)-1 {
|
||||
builder.WriteString(vpkg.Info.Name)
|
||||
} else {
|
||||
builder.WriteString(vpkg.Info.Name + ", ")
|
||||
}
|
||||
}
|
||||
builder.WriteString(")")
|
||||
}
|
||||
|
||||
builder.WriteString("\n")
|
||||
}
|
||||
}
|
||||
builderWriteArray("Dependant packages", entry.GetEntryDependants(), true)
|
||||
builderWriteArray("Optionally dependant packages", entry.GetEntryOptionalDependants(), true)
|
||||
builderWriteArray("Make dependant packages", entry.GetEntryMakeDependants(), true)
|
||||
|
||||
// Other package relations
|
||||
builderWriteArray("Conflicting packages", entry.Info.Conflicts, true)
|
||||
builderWriteArray("Provided packages", entry.Info.Provides, true)
|
||||
builderWriteArray("Replaces packages", entry.Info.Replaces, true)
|
||||
|
||||
// Split packages
|
||||
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
|
||||
}
|
||||
builderWriteArray("Split packages", splitPkgs, true)
|
||||
}
|
||||
|
||||
// Installation reason
|
||||
if rootDir != "" && IsPackageInstalled(entry.Info.Name, rootDir) {
|
||||
installationReason := GetPackage(entry.Info.Name, rootDir).LocalInfo.GetInstallationReason()
|
||||
var installationReasonString string
|
||||
switch installationReason {
|
||||
case InstallationReasonManual:
|
||||
installationReasonString = "Manual"
|
||||
case InstallationReasonDependency:
|
||||
installationReasonString = "Dependency"
|
||||
case InstallationReasonMakeDependency:
|
||||
installationReasonString = "Make dependency"
|
||||
default:
|
||||
installationReasonString = "Unknown"
|
||||
}
|
||||
builder.WriteString("Installation reason: " + installationReasonString + "\n")
|
||||
}
|
||||
|
||||
// Download size
|
||||
downloadSize := entry.DownloadSize
|
||||
var downloadSizeStr string
|
||||
if showBytes {
|
||||
downloadSizeStr = strconv.FormatInt(downloadSize, 10)
|
||||
} else {
|
||||
downloadSizeStr = BytesToHumanReadable(downloadSize)
|
||||
}
|
||||
builder.WriteString("Download size: " + downloadSizeStr + "\n")
|
||||
|
||||
// Installed size
|
||||
if entry.Info.Type == "binary" {
|
||||
installedSize := entry.InstalledSize
|
||||
var installedSizeStr string
|
||||
if showBytes {
|
||||
installedSizeStr = strconv.FormatInt(installedSize, 10)
|
||||
} else {
|
||||
installedSizeStr = BytesToHumanReadable(installedSize)
|
||||
}
|
||||
builder.WriteString("Installed size: " + installedSizeStr + "\n")
|
||||
}
|
||||
|
||||
return strings.TrimSpace(builder.String())
|
||||
}
|
||||
|
||||
+396
-192
@@ -1,222 +1,426 @@
|
||||
package bpmlib
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type pkgInstallationReason struct {
|
||||
PkgName string
|
||||
InstallationReason InstallationReason
|
||||
}
|
||||
|
||||
func (pkgInfo *PackageInfo) GetDependencies(includeMakeDepends, includeOptionalDepends bool) []pkgInstallationReason {
|
||||
allDepends := make([]pkgInstallationReason, 0)
|
||||
|
||||
for _, depend := range pkgInfo.Depends {
|
||||
if !slices.ContainsFunc(allDepends, func(p pkgInstallationReason) bool {
|
||||
return p.PkgName == depend
|
||||
}) {
|
||||
allDepends = append(allDepends, pkgInstallationReason{
|
||||
PkgName: depend,
|
||||
InstallationReason: InstallationReasonDependency,
|
||||
})
|
||||
}
|
||||
}
|
||||
if includeOptionalDepends {
|
||||
for _, depend := range pkgInfo.OptionalDepends {
|
||||
if !slices.ContainsFunc(allDepends, func(p pkgInstallationReason) bool {
|
||||
return p.PkgName == depend
|
||||
}) {
|
||||
allDepends = append(allDepends, pkgInstallationReason{
|
||||
PkgName: depend,
|
||||
InstallationReason: InstallationReasonManual,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
if includeMakeDepends {
|
||||
for _, depend := range pkgInfo.MakeDepends {
|
||||
if !slices.ContainsFunc(allDepends, func(p pkgInstallationReason) bool {
|
||||
return p.PkgName == depend
|
||||
}) {
|
||||
allDepends = append(allDepends, pkgInstallationReason{
|
||||
PkgName: depend,
|
||||
InstallationReason: InstallationReasonMakeDependency,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return allDepends
|
||||
}
|
||||
|
||||
func (pkgInfo *PackageInfo) GetDependenciesRecursive(includeMakeDepends bool, rootDir string) (resolved []string) {
|
||||
// Initialize slices
|
||||
resolved = make([]string, 0)
|
||||
unresolved := make([]string, 0)
|
||||
|
||||
// Call unexported function
|
||||
pkgInfo.getDependenciesRecursive(&resolved, &unresolved, includeMakeDepends, rootDir)
|
||||
|
||||
return resolved
|
||||
}
|
||||
|
||||
func (pkgInfo *PackageInfo) getDependenciesRecursive(resolved *[]string, unresolved *[]string, includeMakeDepends bool, rootDir string) {
|
||||
// Add current package name to unresolved slice
|
||||
*unresolved = append(*unresolved, pkgInfo.Name)
|
||||
|
||||
// Loop through all dependencies
|
||||
for _, pkgIR := range pkgInfo.GetDependencies(includeMakeDepends, false) {
|
||||
depend := pkgIR.PkgName
|
||||
|
||||
if isVirtual, p := IsVirtualPackage(depend, rootDir); isVirtual {
|
||||
depend = p
|
||||
}
|
||||
|
||||
if !slices.Contains(*resolved, depend) {
|
||||
// Add current dependency to resolved slice when circular dependency is detected
|
||||
if slices.Contains(*unresolved, depend) {
|
||||
if !slices.Contains(*resolved, depend) {
|
||||
*resolved = append(*resolved, depend)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
dependInfo := GetPackageInfo(depend, rootDir)
|
||||
|
||||
if dependInfo != nil {
|
||||
dependInfo.getDependenciesRecursive(resolved, unresolved, includeMakeDepends, rootDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !slices.Contains(*resolved, pkgInfo.Name) {
|
||||
*resolved = append(*resolved, pkgInfo.Name)
|
||||
}
|
||||
*unresolved = stringSliceRemove(*unresolved, pkgInfo.Name)
|
||||
}
|
||||
|
||||
func ResolveAllPackageDependenciesFromDatabases(pkgInfo *PackageInfo, checkMake, checkOptional, ignoreInstalled, verbose bool, rootDir string) (resolved []pkgInstallationReason, unresolved []string) {
|
||||
// Initialize slices
|
||||
resolved = make([]pkgInstallationReason, 0)
|
||||
unresolved = make([]string, 0)
|
||||
|
||||
// Call unexported function
|
||||
resolvePackageDependenciesFromDatabase(&resolved, &unresolved, pkgInfo, checkMake, checkOptional, ignoreInstalled, verbose, rootDir)
|
||||
|
||||
// Remove main package from unresolved slice
|
||||
unresolved = stringSliceRemove(unresolved, pkgInfo.Name)
|
||||
|
||||
return resolved, unresolved
|
||||
}
|
||||
|
||||
func resolvePackageDependenciesFromDatabase(resolved *[]pkgInstallationReason, unresolved *[]string, pkgInfo *PackageInfo, checkMake, checkOptional, ignoreInstalled, verbose bool, rootDir string) {
|
||||
// Add current package name to unresolved slice
|
||||
*unresolved = append(*unresolved, pkgInfo.Name)
|
||||
|
||||
// Loop through all dependencies
|
||||
for _, pkgIR := range pkgInfo.GetDependencies(pkgInfo.Type == "source", checkOptional) {
|
||||
// Skip dependency if it has already been resolved
|
||||
if slices.ContainsFunc(*resolved, func(p pkgInstallationReason) bool {
|
||||
return p.PkgName == pkgIR.PkgName
|
||||
}) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Add current dependency to resolved slice when circular dependency is detected
|
||||
if slices.Contains(*unresolved, pkgIR.PkgName) {
|
||||
if verbose {
|
||||
fmt.Printf("Circular dependency was detected (%s -> %s). Installing %s first\n", pkgInfo.Name, pkgIR.PkgName, pkgIR.PkgName)
|
||||
}
|
||||
|
||||
*resolved = append(*resolved, pkgInstallationReason{
|
||||
PkgName: pkgIR.PkgName,
|
||||
InstallationReason: pkgIR.InstallationReason,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip dependency if it is already installed or provided
|
||||
if ignoreInstalled && IsPackageProvided(pkgIR.PkgName, rootDir) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Get database entry for dependency
|
||||
var err error
|
||||
var entry *BPMDatabaseEntry
|
||||
entry, _, err = GetDatabaseEntry(pkgIR.PkgName)
|
||||
if err != nil {
|
||||
if entry = ResolveVirtualPackage(pkgIR.PkgName); entry == nil {
|
||||
if !slices.Contains(*unresolved, pkgIR.PkgName) {
|
||||
*unresolved = append(*unresolved, pkgIR.PkgName)
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve the dependencies of this dependency
|
||||
resolvePackageDependenciesFromDatabase(resolved, unresolved, entry.Info, checkMake, false, ignoreInstalled, verbose, rootDir)
|
||||
|
||||
// Move dependency from the unresolved slice to the resolved slice
|
||||
if !slices.ContainsFunc(*resolved, func(p pkgInstallationReason) bool {
|
||||
return p.PkgName == entry.Info.Name
|
||||
}) {
|
||||
*resolved = append(*resolved, pkgInstallationReason{
|
||||
PkgName: entry.Info.Name,
|
||||
InstallationReason: pkgIR.InstallationReason,
|
||||
})
|
||||
}
|
||||
*unresolved = stringSliceRemove(*unresolved, entry.Info.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func GetPackageDependants(pkgName string, rootDir string) ([]string, error) {
|
||||
ret := make([]string, 0)
|
||||
|
||||
// Get BPM package
|
||||
pkg := GetPackage(pkgName, rootDir)
|
||||
if pkg == nil {
|
||||
return nil, errors.New("package not found: " + pkgName)
|
||||
}
|
||||
|
||||
func (pkgInfo *PackageInfo) GetPackageDependants(rootDir string, skipMultipleProviders bool) (dependants []string) {
|
||||
// Get installed package names
|
||||
pkgs, err := GetInstalledPackages(rootDir)
|
||||
if err != nil {
|
||||
return nil, errors.New("could not get installed packages")
|
||||
pkgs, ok := localPackageInformation[rootDir]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Loop through all installed packages
|
||||
for _, installedPkgName := range pkgs {
|
||||
// Get installed BPM package
|
||||
installedPkg := GetPackage(installedPkgName, rootDir)
|
||||
if installedPkg == nil {
|
||||
return nil, errors.New("package not found: " + installedPkgName)
|
||||
}
|
||||
|
||||
for _, installedPkg := range pkgs {
|
||||
// Skip iteration if comparing the same packages
|
||||
if installedPkg.PkgInfo.Name == pkgName {
|
||||
if installedPkg.Name == pkgInfo.Name {
|
||||
continue
|
||||
}
|
||||
|
||||
// Add installed package to list if its dependencies include pkgName
|
||||
if slices.ContainsFunc(installedPkg.PkgInfo.Depends, func(n string) bool {
|
||||
return n == pkgName
|
||||
if slices.ContainsFunc(installedPkg.Depends, func(n string) bool {
|
||||
// Deconstruct package string
|
||||
d, err := DeconstructPackageString(n)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return d.PkgName == pkgInfo.Name
|
||||
}) {
|
||||
ret = append(ret, installedPkgName)
|
||||
dependants = append(dependants, installedPkg.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
// Add installed package to list if its runtime dependencies include pkgName
|
||||
if slices.ContainsFunc(installedPkg.RuntimeDepends, func(n string) bool {
|
||||
// Deconstruct package string
|
||||
d, err := DeconstructPackageString(n)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return d.PkgName == pkgInfo.Name
|
||||
}) {
|
||||
dependants = append(dependants, installedPkg.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
// Loop through each virtual package
|
||||
for _, vpkg := range pkg.PkgInfo.Provides {
|
||||
for _, vpkg := range pkgInfo.Provides {
|
||||
if skipMultipleProviders && len(GetVirtualPackageInfo(vpkg, rootDir)) > 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Add installed package to list if its dependencies contain a provided virtual package
|
||||
if slices.ContainsFunc(installedPkg.PkgInfo.Depends, func(n string) bool {
|
||||
return n == vpkg
|
||||
if slices.ContainsFunc(installedPkg.Depends, func(n string) bool {
|
||||
// Deconstruct package string
|
||||
d, err := DeconstructPackageString(n)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return d.PkgName == vpkg
|
||||
}) {
|
||||
ret = append(ret, installedPkgName)
|
||||
dependants = append(dependants, installedPkg.Name)
|
||||
break
|
||||
}
|
||||
|
||||
// Add installed package to list if its runtime dependencies contain a provided virtual package
|
||||
if slices.ContainsFunc(installedPkg.RuntimeDepends, func(n string) bool {
|
||||
// Deconstruct package string
|
||||
d, err := DeconstructPackageString(n)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return d.PkgName == vpkg
|
||||
}) {
|
||||
dependants = append(dependants, installedPkg.Name)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
// Deconstruct package string
|
||||
d, err := DeconstructPackageString(n)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return d.PkgName == 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 {
|
||||
// Deconstruct package string
|
||||
d, err := DeconstructPackageString(n)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return d.PkgName == vpkg
|
||||
}) {
|
||||
dependants = append(dependants, installedPkg.Name)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dependants
|
||||
}
|
||||
|
||||
type ResolvedPackage struct {
|
||||
DatabaseEntry *BPMDatabaseEntry
|
||||
Flags map[string]string
|
||||
InstallationReason InstallationReason
|
||||
}
|
||||
|
||||
func ResolveDependencies(pkgInfo *PackageInfo, flags, resolvedVirtualPackages map[string]string, includeRuntimeDepends bool, rootDir string) (resolved []ResolvedPackage, unresolved map[string]string) {
|
||||
unresolved = make(map[string]string)
|
||||
visited := make([]string, 0)
|
||||
|
||||
var dfs func(resolvedPkg *PackageInfo, flags map[string]string)
|
||||
dfs = func(pkgInfo *PackageInfo, flags map[string]string) {
|
||||
checkDependencies := func(dependencies []string, installationReason InstallationReason) {
|
||||
for _, depend := range dependencies {
|
||||
// Deconstruct package string
|
||||
d, err := DeconstructPackageString(depend)
|
||||
if err != nil {
|
||||
unresolved[depend] = "could not deconstruct package string: " + err.Error()
|
||||
continue
|
||||
}
|
||||
|
||||
// Check resolved virtual packages
|
||||
if resolvedPkg, ok := resolvedVirtualPackages[d.PkgName]; ok {
|
||||
d.PkgName = resolvedPkg
|
||||
}
|
||||
|
||||
// Find database entry for dependency
|
||||
dependEntry := ResolveDatabaseEntry(d, rootDir)
|
||||
if dependEntry == nil {
|
||||
unresolved[depend] = "could not find in any database"
|
||||
continue
|
||||
}
|
||||
d.PkgName = dependEntry.Info.Name
|
||||
|
||||
// Skip ignored packages in config
|
||||
if rootDir == "/" && slices.Contains(MainBPMConfig.IgnorePackages, dependEntry.Info.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Add user defined package flags and resolve entry again if package is already installed
|
||||
if installedInfo := GetPackageInfo(dependEntry.Info.Name, rootDir); installedInfo != nil {
|
||||
d.Flags, err = CombineFlags(getPackageLocalInfo(dependEntry.Info.Name, rootDir).Flags, d.Flags)
|
||||
if err != nil {
|
||||
unresolved[depend] = "could not combine flags: " + err.Error()
|
||||
continue
|
||||
}
|
||||
|
||||
dependEntry = ResolveDatabaseEntry(d, rootDir)
|
||||
if dependEntry == nil {
|
||||
unresolved[depend] = "could not find in any database"
|
||||
continue
|
||||
}
|
||||
|
||||
shouldIgnore := true
|
||||
|
||||
// Skip if no update/downgrade is available
|
||||
if installedInfo.GetFullVersion() != dependEntry.Info.GetFullVersion() {
|
||||
shouldIgnore = false
|
||||
}
|
||||
|
||||
// Skip if no new package flags
|
||||
if !maps.Equal(getPackageLocalInfo(dependEntry.Info.Name, rootDir).Flags, d.Flags) {
|
||||
shouldIgnore = false
|
||||
}
|
||||
|
||||
if shouldIgnore {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve virtual packages
|
||||
for _, vpkg := range dependEntry.Info.Provides {
|
||||
if _, ok := resolvedVirtualPackages[vpkg]; !ok {
|
||||
resolvedVirtualPackages[vpkg] = dependEntry.Info.Name
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve entry dependencies
|
||||
if !slices.Contains(visited, dependEntry.Info.Name) {
|
||||
dfs(dependEntry.Info, d.Flags)
|
||||
resolved = append(resolved, ResolvedPackage{DatabaseEntry: dependEntry, Flags: d.Flags, InstallationReason: installationReason})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
visited = append(visited, pkgInfo.Name)
|
||||
|
||||
checkDependencies(pkgInfo.Depends, InstallationReasonDependency)
|
||||
if includeRuntimeDepends {
|
||||
checkDependencies(pkgInfo.RuntimeDepends, InstallationReasonDependency)
|
||||
}
|
||||
if pkgInfo.Type == "source" {
|
||||
checkDependencies(pkgInfo.MakeDepends, InstallationReasonMakeDependency)
|
||||
checkDependencies(pkgInfo.CheckDepends, InstallationReasonMakeDependency)
|
||||
|
||||
// Resolve flag-specific dependencies
|
||||
for _, flag := range pkgInfo.Flags {
|
||||
acceptedValueIndex := slices.IndexFunc(flag.AcceptedValues, func(acceptedValue PackageAcceptedValue) bool {
|
||||
if value, ok := flags[flag.Name]; ok {
|
||||
return acceptedValue.Value == value
|
||||
} else {
|
||||
return acceptedValue.Value == flag.DefaultValue
|
||||
}
|
||||
})
|
||||
if acceptedValueIndex < 0 {
|
||||
continue
|
||||
}
|
||||
acceptedValue := flag.AcceptedValues[acceptedValueIndex]
|
||||
|
||||
checkDependencies(acceptedValue.Depends, InstallationReasonDependency)
|
||||
checkDependencies(acceptedValue.RuntimeDepends, InstallationReasonDependency)
|
||||
checkDependencies(acceptedValue.MakeDepends, InstallationReasonMakeDependency)
|
||||
checkDependencies(acceptedValue.CheckDepends, InstallationReasonMakeDependency)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dfs(pkgInfo, flags)
|
||||
|
||||
return resolved, unresolved
|
||||
}
|
||||
|
||||
type DeconstructedPackageString struct {
|
||||
PkgName string
|
||||
Description string
|
||||
Flags map[string]string
|
||||
RequiredVersionOperator string
|
||||
RequiredVersion string
|
||||
}
|
||||
|
||||
func DeconstructPackageString(pkg string) (ret DeconstructedPackageString, err error) {
|
||||
// Initialize values
|
||||
ret.Flags = make(map[string]string)
|
||||
|
||||
// Get dependency description
|
||||
if i := strings.IndexRune(pkg, ':'); i > 0 {
|
||||
ret.Description = pkg[i+1:]
|
||||
pkg = pkg[0:i]
|
||||
}
|
||||
|
||||
if left := strings.IndexRune(pkg, '['); left > 0 {
|
||||
if right := strings.IndexRune(pkg, ']'); right > left {
|
||||
flagsStr := pkg[left+1 : right]
|
||||
pkg = pkg[0:left] + pkg[right+1:]
|
||||
|
||||
if flagsStr != "" {
|
||||
for flag := range strings.SplitSeq(flagsStr, ",") {
|
||||
flagSplit := strings.SplitN(flag, "=", 2)
|
||||
if len(flagSplit) != 2 {
|
||||
return ret, fmt.Errorf("could not parse flag: %s", flag)
|
||||
}
|
||||
|
||||
ret.Flags[flagSplit[0]] = flagSplit[1]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return ret, fmt.Errorf("could not find closing square bracket for set flags")
|
||||
}
|
||||
}
|
||||
|
||||
if strings.Contains(pkg, ">=") {
|
||||
ret.RequiredVersionOperator = ">="
|
||||
} else if strings.Contains(pkg, ">") {
|
||||
ret.RequiredVersionOperator = ">"
|
||||
} else if strings.Contains(pkg, "<=") {
|
||||
ret.RequiredVersionOperator = "<="
|
||||
} else if strings.Contains(pkg, "<") {
|
||||
ret.RequiredVersionOperator = "<"
|
||||
} else if strings.Contains(pkg, "=") {
|
||||
ret.RequiredVersionOperator = "="
|
||||
}
|
||||
if ret.RequiredVersionOperator != "" {
|
||||
pkgSplit := strings.SplitN(pkg, ret.RequiredVersionOperator, 2)
|
||||
if len(pkgSplit) != 2 {
|
||||
return ret, fmt.Errorf("could not parse required version: %s", pkg)
|
||||
}
|
||||
pkg = pkgSplit[0]
|
||||
ret.RequiredVersion = pkgSplit[1]
|
||||
}
|
||||
|
||||
ret.PkgName = pkg
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func EvaluatePackageString(pkgInfo *PackageInfo, match DeconstructedPackageString) bool {
|
||||
// Validate flags
|
||||
for flag, value := range match.Flags {
|
||||
pkgFlagIndex := slices.IndexFunc(pkgInfo.Flags, func(f PackageFlag) bool {
|
||||
return f.Name == flag
|
||||
})
|
||||
if pkgFlagIndex < 0 {
|
||||
return false
|
||||
}
|
||||
pkgFlag := pkgInfo.Flags[pkgFlagIndex]
|
||||
|
||||
if pkgInfo.Type == "binary" {
|
||||
if pkgFlag.BuiltValue != value {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if len(pkgFlag.AcceptedValues) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if !slices.ContainsFunc(pkgFlag.AcceptedValues, func(acceptedFlag PackageAcceptedValue) bool {
|
||||
return acceptedFlag.Value == value
|
||||
}) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Validate version
|
||||
switch match.RequiredVersionOperator {
|
||||
case ">=":
|
||||
return CompareVersions(match.RequiredVersion, pkgInfo.Version) >= 0
|
||||
case ">":
|
||||
return CompareVersions(match.RequiredVersion, pkgInfo.Version) > 0
|
||||
case "<=":
|
||||
return CompareVersions(match.RequiredVersion, pkgInfo.Version) <= 0
|
||||
case "<":
|
||||
return CompareVersions(match.RequiredVersion, pkgInfo.Version) < 0
|
||||
case "=":
|
||||
if cutPkgVersion, ok := strings.CutSuffix(match.RequiredVersion, "*"); ok {
|
||||
return strings.HasPrefix(pkgInfo.Version, cutPkgVersion)
|
||||
} else {
|
||||
return CompareVersions(pkgInfo.Version, match.RequiredVersion) == 0
|
||||
}
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func GetBuiltFlags(pkg, rootDir string) map[string]string {
|
||||
builtFlags := make(map[string]string)
|
||||
|
||||
pkgInfo := GetPackageInfo(pkg, rootDir)
|
||||
if pkgInfo == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, flag := range pkgInfo.Flags {
|
||||
builtFlags[flag.Name] = flag.BuiltValue
|
||||
}
|
||||
|
||||
return builtFlags
|
||||
}
|
||||
|
||||
func RemoveInvalidFlags(pkgInfo *PackageInfo, userFlags map[string]string) (ret map[string]string) {
|
||||
ret = make(map[string]string)
|
||||
|
||||
for userFlag, userValue := range userFlags {
|
||||
flagIndex := slices.IndexFunc(pkgInfo.Flags, func(flag PackageFlag) bool {
|
||||
return flag.Name == userFlag
|
||||
})
|
||||
if flagIndex < 0 {
|
||||
continue
|
||||
}
|
||||
flag := pkgInfo.Flags[flagIndex]
|
||||
|
||||
if len(flag.AcceptedValues) > 0 && !slices.ContainsFunc(flag.AcceptedValues, func(acceptedValue PackageAcceptedValue) bool {
|
||||
return acceptedValue.Value == userValue
|
||||
}) {
|
||||
continue
|
||||
}
|
||||
|
||||
ret[userFlag] = userValue
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func CombineFlags(flags, newFlags map[string]string) (map[string]string, error) {
|
||||
ret := make(map[string]string)
|
||||
maps.Copy(ret, flags)
|
||||
|
||||
for flag, value := range newFlags {
|
||||
if oldValue, ok := flags[flag]; ok && value != oldValue {
|
||||
return ret, fmt.Errorf("flag already defined: %s=%s/%s", flag, oldValue, value)
|
||||
}
|
||||
|
||||
ret[flag] = value
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
+13
-10
@@ -2,23 +2,25 @@ package bpmlib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type PackageNotFoundErr struct {
|
||||
packages []string
|
||||
type PackageNotResolvedErr struct {
|
||||
packages map[string]string
|
||||
}
|
||||
|
||||
func (e PackageNotFoundErr) Error() string {
|
||||
return "The following packages were not found in any databases: " + strings.Join(e.packages, ", ")
|
||||
}
|
||||
func (e PackageNotResolvedErr) Error() (ret string) {
|
||||
ret = "The following packages could not be resolved:"
|
||||
|
||||
type DependencyNotFoundErr struct {
|
||||
dependencies []string
|
||||
}
|
||||
keys := slices.Collect(maps.Keys(e.packages))
|
||||
slices.Sort(keys)
|
||||
for _, key := range keys {
|
||||
ret += "\n " + key + ": " + e.packages[key]
|
||||
}
|
||||
|
||||
func (e DependencyNotFoundErr) Error() string {
|
||||
return "The following dependencies were not found in any databases: " + strings.Join(e.dependencies, ", ")
|
||||
return ret
|
||||
}
|
||||
|
||||
type PackageConflictErr struct {
|
||||
@@ -27,6 +29,7 @@ type PackageConflictErr struct {
|
||||
}
|
||||
|
||||
func (e PackageConflictErr) Error() string {
|
||||
slices.Sort(e.conflicts)
|
||||
return fmt.Sprintf("Package (%s) is in conflict with the following packages: %s", e.pkg, strings.Join(e.conflicts, ", "))
|
||||
|
||||
}
|
||||
|
||||
+215
-81
@@ -11,54 +11,95 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ReinstallMethod uint8
|
||||
|
||||
const (
|
||||
ReinstallMethodNone ReinstallMethod = iota
|
||||
ReinstallMethodSpecified ReinstallMethod = iota
|
||||
ReinstallMethodAll ReinstallMethod = iota
|
||||
)
|
||||
|
||||
// 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, reinstallPackages bool, installRuntimeDependencies, forceInstallation, runChecks bool, verbose bool, packages ...string) (operation *BPMOperation, err error) {
|
||||
// Setup operation struct
|
||||
operation = &BPMOperation{
|
||||
Actions: make([]OperationAction, 0),
|
||||
UnresolvedDepends: make([]string, 0),
|
||||
Changes: make(map[string]string),
|
||||
UnresolvedDepends: make(map[string]string, 0),
|
||||
ModifiedFiles: make(map[string]string),
|
||||
RunChecks: runChecks,
|
||||
RootDir: rootDir,
|
||||
compiledPackages: make(map[string]string),
|
||||
}
|
||||
|
||||
// Remove duplicates from packages
|
||||
packages = removeDuplicates(packages)
|
||||
|
||||
// Resolve packages
|
||||
pkgsNotFound := make([]string, 0)
|
||||
unresolvedPackages := make(map[string]string)
|
||||
resolvedVirtualPackages := make(map[string]string)
|
||||
for _, pkg := range packages {
|
||||
if stat, err := os.Stat(pkg); err == nil && !stat.IsDir() {
|
||||
bpmpkg, err := ReadPackage(pkg)
|
||||
d, err := DeconstructPackageString(pkg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not deconstruct package string: %s", err)
|
||||
}
|
||||
|
||||
if stat, err := os.Stat(d.PkgName); err == nil && !stat.IsDir() {
|
||||
bpmpkg, err := ReadPackage(d.PkgName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read package: %s", err)
|
||||
}
|
||||
|
||||
if bpmpkg.PkgInfo.Type == "source" && bpmpkg.PkgInfo.IsSplitPackage() {
|
||||
// Add user defined package flags
|
||||
if !reinstallPackages && IsPackageInstalled(bpmpkg.PkgInfo.Name, rootDir) {
|
||||
d.Flags, err = CombineFlags(getPackageLocalInfo(bpmpkg.PkgInfo.Name, rootDir).Flags, d.Flags)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not combine flags for package (%s): %s", bpmpkg.PkgInfo.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
if !EvaluatePackageString(bpmpkg.PkgInfo, d) {
|
||||
unresolvedPackages[pkg] = "could not evaluate package"
|
||||
continue
|
||||
}
|
||||
|
||||
if bpmpkg.PkgInfo.IsSplitPackage() {
|
||||
for _, splitPkg := range bpmpkg.PkgInfo.SplitPackages {
|
||||
if reinstallMethod == ReinstallMethodNone && IsPackageInstalled(splitPkg.Name, rootDir) && GetPackageInfo(splitPkg.Name, rootDir).GetFullVersion() == splitPkg.GetFullVersion() {
|
||||
if !reinstallPackages && IsPackageInstalled(splitPkg.Name, rootDir) && GetPackageInfo(splitPkg.Name, rootDir).GetFullVersion() == splitPkg.GetFullVersion() {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if installation can be ignored
|
||||
if !reinstallPackages && IsPackageInstalled(splitPkg.Name, rootDir) {
|
||||
shouldIgnore := true
|
||||
|
||||
// Skip if no update/downgrade is available
|
||||
if GetPackageInfo(splitPkg.Name, rootDir).GetFullVersion() != splitPkg.GetFullVersion() {
|
||||
shouldIgnore = false
|
||||
}
|
||||
|
||||
// Skip if no new package flags
|
||||
if !maps.Equal(getPackageLocalInfo(splitPkg.Name, rootDir).Flags, d.Flags) {
|
||||
shouldIgnore = false
|
||||
}
|
||||
|
||||
if shouldIgnore {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Set package installation reason
|
||||
installationReason := forceInstallationReason
|
||||
if installationReason == InstallationReasonUnknown {
|
||||
if IsPackageInstalled(splitPkg.Name, rootDir) {
|
||||
installationReason = GetInstallationReason(splitPkg.Name, rootDir)
|
||||
installationReason = GetPackage(splitPkg.Name, rootDir).LocalInfo.GetInstallationReason()
|
||||
} else {
|
||||
installationReason = InstallationReasonManual
|
||||
}
|
||||
}
|
||||
|
||||
operation.AppendAction(&InstallPackageAction{
|
||||
// Resolve virtual packages
|
||||
for _, vpkg := range splitPkg.Provides {
|
||||
if _, ok := resolvedVirtualPackages[vpkg]; !ok {
|
||||
resolvedVirtualPackages[vpkg] = splitPkg.Name
|
||||
}
|
||||
}
|
||||
|
||||
operation.Actions = append(operation.Actions, &InstallPackageAction{
|
||||
File: pkg,
|
||||
InstallationReason: installationReason,
|
||||
Flags: d.Flags,
|
||||
BpmPackage: bpmpkg,
|
||||
SplitPackageToInstall: splitPkg.Name,
|
||||
})
|
||||
@@ -66,78 +107,132 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
|
||||
continue
|
||||
}
|
||||
|
||||
if reinstallMethod == ReinstallMethodNone && IsPackageInstalled(bpmpkg.PkgInfo.Name, rootDir) && GetPackageInfo(bpmpkg.PkgInfo.Name, rootDir).GetFullVersion() == bpmpkg.PkgInfo.GetFullVersion() {
|
||||
continue
|
||||
// Check if installation can be ignored
|
||||
if !reinstallPackages && IsPackageInstalled(bpmpkg.PkgInfo.Name, rootDir) {
|
||||
shouldIgnore := true
|
||||
|
||||
// Skip if no update/downgrade is available
|
||||
if GetPackageInfo(bpmpkg.PkgInfo.Name, rootDir).GetFullVersion() != bpmpkg.PkgInfo.GetFullVersion() {
|
||||
shouldIgnore = false
|
||||
}
|
||||
|
||||
// Skip if no new package flags
|
||||
if !maps.Equal(getPackageLocalInfo(bpmpkg.PkgInfo.Name, rootDir).Flags, d.Flags) {
|
||||
shouldIgnore = false
|
||||
}
|
||||
|
||||
if shouldIgnore {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Set package installation reason
|
||||
installationReason := forceInstallationReason
|
||||
if installationReason == InstallationReasonUnknown {
|
||||
if IsPackageInstalled(bpmpkg.PkgInfo.Name, rootDir) {
|
||||
installationReason = GetInstallationReason(bpmpkg.PkgInfo.Name, rootDir)
|
||||
installationReason = GetPackage(bpmpkg.PkgInfo.Name, rootDir).LocalInfo.GetInstallationReason()
|
||||
} else {
|
||||
installationReason = InstallationReasonManual
|
||||
}
|
||||
}
|
||||
|
||||
operation.AppendAction(&InstallPackageAction{
|
||||
// Resolve virtual packages
|
||||
for _, vpkg := range bpmpkg.PkgInfo.Provides {
|
||||
if _, ok := resolvedVirtualPackages[vpkg]; !ok {
|
||||
resolvedVirtualPackages[vpkg] = bpmpkg.PkgInfo.Name
|
||||
}
|
||||
}
|
||||
|
||||
operation.Actions = append(operation.Actions, &InstallPackageAction{
|
||||
File: pkg,
|
||||
InstallationReason: installationReason,
|
||||
Flags: d.Flags,
|
||||
BpmPackage: bpmpkg,
|
||||
})
|
||||
} else {
|
||||
var entry *BPMDatabaseEntry
|
||||
// Check resolved virtual packages
|
||||
if resolvedPkg, ok := resolvedVirtualPackages[d.PkgName]; ok {
|
||||
d.PkgName = resolvedPkg
|
||||
}
|
||||
|
||||
if e, _, err := GetDatabaseEntry(pkg); err == nil {
|
||||
entry = e
|
||||
} else if isVirtual, p := IsVirtualPackage(pkg, rootDir); isVirtual {
|
||||
entry, _, err = GetDatabaseEntry(p)
|
||||
if err != nil {
|
||||
pkgsNotFound = append(pkgsNotFound, pkg)
|
||||
continue
|
||||
}
|
||||
} else if e := ResolveVirtualPackage(pkg); e != nil {
|
||||
entry = e
|
||||
} else {
|
||||
pkgsNotFound = append(pkgsNotFound, pkg)
|
||||
entry := ResolveDatabaseEntry(d, rootDir)
|
||||
if entry == nil {
|
||||
unresolvedPackages[pkg] = "could not find in any database"
|
||||
continue
|
||||
}
|
||||
if reinstallMethod == ReinstallMethodNone && IsPackageInstalled(entry.Info.Name, rootDir) && GetPackageInfo(entry.Info.Name, rootDir).GetFullVersion() == entry.Info.GetFullVersion() {
|
||||
continue
|
||||
|
||||
pkgInfo := GetPackageInfo(entry.Info.Name, rootDir)
|
||||
|
||||
// Add user defined package flags and resolve entry again if package is already installed
|
||||
if !reinstallPackages && pkgInfo != nil {
|
||||
d.Flags, err = CombineFlags(getPackageLocalInfo(entry.Info.Name, rootDir).Flags, d.Flags)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not combine flags for package (%s): %s", entry.Info.Name, err)
|
||||
}
|
||||
|
||||
entry = ResolveDatabaseEntry(d, rootDir)
|
||||
if entry == nil {
|
||||
unresolvedPackages[pkg] = "could not find in any database"
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Check if installation can be ignored
|
||||
if !reinstallPackages && pkgInfo != nil {
|
||||
shouldIgnore := true
|
||||
|
||||
// Skip if no update/downgrade is available
|
||||
if GetPackageInfo(entry.Info.Name, rootDir).GetFullVersion() != entry.Info.GetFullVersion() {
|
||||
shouldIgnore = false
|
||||
}
|
||||
|
||||
// Skip if no new package flags
|
||||
if !maps.Equal(getPackageLocalInfo(entry.Info.Name, rootDir).Flags, d.Flags) {
|
||||
shouldIgnore = false
|
||||
}
|
||||
|
||||
if shouldIgnore {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Set package installation reason
|
||||
installationReason := forceInstallationReason
|
||||
if installationReason == InstallationReasonUnknown {
|
||||
if IsPackageInstalled(entry.Info.Name, rootDir) {
|
||||
installationReason = GetInstallationReason(entry.Info.Name, rootDir)
|
||||
installationReason = GetPackage(entry.Info.Name, rootDir).LocalInfo.GetInstallationReason()
|
||||
} else {
|
||||
installationReason = InstallationReasonManual
|
||||
}
|
||||
}
|
||||
|
||||
operation.AppendAction(&FetchPackageAction{
|
||||
// Resolve virtual packages
|
||||
for _, vpkg := range entry.Info.Provides {
|
||||
if _, ok := resolvedVirtualPackages[vpkg]; !ok {
|
||||
resolvedVirtualPackages[vpkg] = entry.Info.Name
|
||||
}
|
||||
}
|
||||
|
||||
operation.Actions = append(operation.Actions, &FetchPackageAction{
|
||||
InstallationReason: installationReason,
|
||||
Flags: d.Flags,
|
||||
DatabaseEntry: entry,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Return error if not all packages are found
|
||||
if len(pkgsNotFound) != 0 {
|
||||
return nil, PackageNotFoundErr{pkgsNotFound}
|
||||
if len(unresolvedPackages) != 0 {
|
||||
return nil, PackageNotResolvedErr{unresolvedPackages}
|
||||
}
|
||||
|
||||
// Resolve dependencies
|
||||
err = operation.ResolveDependencies(reinstallMethod == ReinstallMethodAll, installOptionalDependencies, verbose)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not resolve dependencies: %s", err)
|
||||
}
|
||||
operation.ResolveDependencies(installRuntimeDependencies)
|
||||
if len(operation.UnresolvedDepends) != 0 {
|
||||
if !forceInstallation {
|
||||
return nil, DependencyNotFoundErr{operation.UnresolvedDepends}
|
||||
return nil, PackageNotResolvedErr{operation.UnresolvedDepends}
|
||||
} else if verbose {
|
||||
log.Printf("Warning: %s", DependencyNotFoundErr{operation.UnresolvedDepends})
|
||||
log.Printf("Warning: %s", PackageNotResolvedErr{operation.UnresolvedDepends})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,12 +240,9 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
|
||||
operation.ReplaceObsoletePackages()
|
||||
|
||||
// Check for conflicts
|
||||
conflicts, err := operation.CheckForConflicts()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not complete package conflict check: %s", err)
|
||||
}
|
||||
conflicts := operation.CheckForConflicts()
|
||||
if len(conflicts) > 0 {
|
||||
err = nil
|
||||
err = fmt.Errorf("conflicts detected")
|
||||
for pkg, conflict := range conflicts {
|
||||
err = errors.Join(err, PackageConflictErr{pkg, conflict})
|
||||
}
|
||||
@@ -190,22 +282,25 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
|
||||
func RemovePackages(rootDir string, force, cleanupDependencies bool, packages ...string) (operation *BPMOperation, err error) {
|
||||
operation = &BPMOperation{
|
||||
Actions: make([]OperationAction, 0),
|
||||
UnresolvedDepends: make([]string, 0),
|
||||
Changes: make(map[string]string),
|
||||
UnresolvedDepends: make(map[string]string),
|
||||
ModifiedFiles: make(map[string]string),
|
||||
RootDir: rootDir,
|
||||
compiledPackages: make(map[string]string),
|
||||
}
|
||||
|
||||
// Remove duplicates from packages
|
||||
packages = removeDuplicates(packages)
|
||||
|
||||
// Search for packages
|
||||
for _, pkg := range packages {
|
||||
bpmpkg := GetPackage(pkg, rootDir)
|
||||
if isVirutal, vpkg := IsVirtualPackage(pkg, rootDir); isVirutal {
|
||||
bpmpkg = GetPackage(vpkg, rootDir)
|
||||
if providers := GetVirtualPackageInfo(pkg, rootDir); len(providers) > 0 {
|
||||
bpmpkg = GetPackage(providers[0].Name, rootDir)
|
||||
}
|
||||
if bpmpkg == nil {
|
||||
continue
|
||||
}
|
||||
operation.AppendAction(&RemovePackageAction{BpmPackage: bpmpkg})
|
||||
operation.Actions = append(operation.Actions, &RemovePackageAction{BpmPackage: bpmpkg})
|
||||
}
|
||||
|
||||
// Do package cleanup
|
||||
@@ -221,11 +316,12 @@ func RemovePackages(rootDir string, force, cleanupDependencies bool, packages ..
|
||||
// Get packages and their dependants
|
||||
packageDepndants := make(map[string][]string, 0)
|
||||
for _, action := range operation.Actions {
|
||||
dependants, err := GetPackageDependants(action.(*RemovePackageAction).BpmPackage.PkgInfo.Name, rootDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get package dependants: %s", err)
|
||||
// Skip package if ignored in config
|
||||
if rootDir == "/" && slices.Contains(MainBPMConfig.IgnorePackages, action.(*RemovePackageAction).BpmPackage.PkgInfo.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
dependants := action.(*RemovePackageAction).BpmPackage.PkgInfo.GetPackageDependants(rootDir, true)
|
||||
packageDepndants[action.(*RemovePackageAction).BpmPackage.PkgInfo.Name] = dependants
|
||||
}
|
||||
|
||||
@@ -238,6 +334,14 @@ func RemovePackages(rootDir string, force, cleanupDependencies bool, packages ..
|
||||
packageDepndants[pkg] = required
|
||||
}
|
||||
|
||||
// Remove dependant packages if ignored
|
||||
for pkg, required := range packageDepndants {
|
||||
required = slices.DeleteFunc(required, func(pkgName string) bool {
|
||||
return rootDir == "/" && slices.Contains(MainBPMConfig.IgnorePackages, pkgName)
|
||||
})
|
||||
packageDepndants[pkg] = required
|
||||
}
|
||||
|
||||
// Remove empty keys from map
|
||||
maps.DeleteFunc(packageDepndants, func(pkg string, required []string) bool {
|
||||
return len(required) == 0
|
||||
@@ -256,8 +360,8 @@ func RemovePackages(rootDir string, force, cleanupDependencies bool, packages ..
|
||||
func CleanupPackages(cleanupMakeDepends bool, rootDir string) (operation *BPMOperation, err error) {
|
||||
operation = &BPMOperation{
|
||||
Actions: make([]OperationAction, 0),
|
||||
UnresolvedDepends: make([]string, 0),
|
||||
Changes: make(map[string]string),
|
||||
UnresolvedDepends: make(map[string]string),
|
||||
ModifiedFiles: make(map[string]string),
|
||||
RootDir: rootDir,
|
||||
compiledPackages: make(map[string]string),
|
||||
}
|
||||
@@ -354,7 +458,7 @@ func CleanupCache(rootDir string, cleanupCompilationFiles, cleanupCompiledPackag
|
||||
}
|
||||
|
||||
// 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, allowDowngrades, forceInstallation, runChecks, verbose bool) (operation *BPMOperation, err error) {
|
||||
// Sync databases
|
||||
if syncDatabase {
|
||||
err := SyncDatabase(verbose)
|
||||
@@ -384,54 +488,84 @@ func UpdatePackages(rootDir string, syncDatabase bool, installOptionalDependenci
|
||||
|
||||
operation = &BPMOperation{
|
||||
Actions: make([]OperationAction, 0),
|
||||
UnresolvedDepends: make([]string, 0),
|
||||
Changes: make(map[string]string),
|
||||
UnresolvedDepends: make(map[string]string),
|
||||
ModifiedFiles: make(map[string]string),
|
||||
RunChecks: runChecks,
|
||||
RootDir: rootDir,
|
||||
compiledPackages: make(map[string]string),
|
||||
}
|
||||
|
||||
// Search for packages
|
||||
resolvedVirtualPackages := make(map[string]string)
|
||||
for _, pkg := range pkgs {
|
||||
if slices.Contains(MainBPMConfig.IgnorePackages, pkg) {
|
||||
if rootDir == "/" && slices.Contains(MainBPMConfig.IgnorePackages, pkg) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Get installed package built flags
|
||||
flags := getPackageLocalInfo(pkg, rootDir).Flags
|
||||
|
||||
var entry *BPMDatabaseEntry
|
||||
// Check if installed package can be replaced and install that instead
|
||||
if e := FindReplacement(pkg); e != nil {
|
||||
entry = e
|
||||
} else if entry, _, err = GetDatabaseEntry(pkg); err != nil {
|
||||
} else if entry = ResolveDatabaseEntry(DeconstructedPackageString{PkgName: pkg, Flags: flags}, rootDir); entry == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
installedInfo := GetPackageInfo(pkg, rootDir)
|
||||
if installedInfo == nil {
|
||||
// Remove invalid flags
|
||||
flags = RemoveInvalidFlags(entry.Info, flags)
|
||||
|
||||
if installedInfo := GetPackageInfo(pkg, rootDir); installedInfo == nil {
|
||||
return nil, fmt.Errorf("could not get package info for package (%s)", pkg)
|
||||
} else {
|
||||
comparison := ComparePackageVersions(*entry.Info, *installedInfo)
|
||||
if comparison > 0 {
|
||||
operation.AppendAction(&FetchPackageAction{
|
||||
InstallationReason: GetInstallationReason(pkg, rootDir),
|
||||
comparison := CompareVersions(entry.Info.GetFullVersion(), installedInfo.GetFullVersion())
|
||||
if (!allowDowngrades && comparison > 0) || (allowDowngrades && comparison != 0) {
|
||||
// Resolve virtual packages
|
||||
for _, vpkg := range entry.Info.Provides {
|
||||
if _, ok := resolvedVirtualPackages[vpkg]; !ok {
|
||||
resolvedVirtualPackages[vpkg] = entry.Info.Name
|
||||
}
|
||||
}
|
||||
|
||||
bpmpkg := GetPackage(pkg, rootDir)
|
||||
|
||||
operation.Actions = append(operation.Actions, &FetchPackageAction{
|
||||
InstallationReason: bpmpkg.LocalInfo.GetInstallationReason(),
|
||||
Flags: flags,
|
||||
DatabaseEntry: entry,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for new dependencies in updated packages
|
||||
err = operation.ResolveDependencies(false, installOptionalDependencies, verbose)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not resolve dependencies: %s", err)
|
||||
}
|
||||
// Resolve dependencies
|
||||
operation.ResolveDependencies(true)
|
||||
if len(operation.UnresolvedDepends) != 0 {
|
||||
if !forceInstallation {
|
||||
return nil, DependencyNotFoundErr{operation.UnresolvedDepends}
|
||||
return nil, PackageNotResolvedErr{operation.UnresolvedDepends}
|
||||
} else if verbose {
|
||||
log.Printf("Warning: %s", DependencyNotFoundErr{operation.UnresolvedDepends})
|
||||
log.Printf("Warning: %s", PackageNotResolvedErr{operation.UnresolvedDepends})
|
||||
}
|
||||
}
|
||||
|
||||
// Replace obsolete packages
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
+10
-2
@@ -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 (
|
||||
github.com/drone/envsubst v1.0.3
|
||||
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f
|
||||
github.com/schollz/progressbar/v3 v3.18.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
golang.org/x/sys v0.37.0 // indirect
|
||||
golang.org/x/term v0.36.0 // indirect
|
||||
)
|
||||
|
||||
@@ -1,9 +1,29 @@
|
||||
github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM=
|
||||
github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g=
|
||||
github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g=
|
||||
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f h1:xt29M2T6STgldg+WEP51gGePQCsQvklmP2eIhPIBK3g=
|
||||
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f/go.mod h1:i4sF0l1fFnY1aiw08QQSwVAFxHEm311Me3WsU/X7nL0=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/schollz/progressbar/v3 v3.18.0 h1:uXdoHABRFmNIjUfte/Ex7WtuyVslrw2wVPQmCN62HpA=
|
||||
github.com/schollz/progressbar/v3 v3.18.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
|
||||
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
package bpmlib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func InitializeKeyring(rootDir string) error {
|
||||
gpgHomedir := path.Join(rootDir, "/var/lib/bpm/gpg")
|
||||
|
||||
// Create GPG directory
|
||||
err := os.Mkdir(gpgHomedir, 0700)
|
||||
if err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get number of secret keys
|
||||
cmd := exec.Command("gpg", "--homedir="+gpgHomedir, "--list-secret-keys", "--with-colons")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
secretKeysLineCount := len(strings.Split(strings.TrimSpace(string(output)), "\n"))
|
||||
|
||||
// Create signing key
|
||||
if secretKeysLineCount <= 1 {
|
||||
cmd := exec.Command("gpg", "--homedir="+gpgHomedir, "--batch", "--gen-key")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Stdin = strings.NewReader(`%echo Creating signing key...
|
||||
Key-Type: RSA
|
||||
Key-Length: 4096
|
||||
Key-Usage: sign
|
||||
Name-Real: BPM signing key
|
||||
Name-Email: bpm@localhost
|
||||
Expire-Date: 0
|
||||
%no-protection
|
||||
%commit
|
||||
%echo Done`)
|
||||
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func IsKeyringInitialized(rootDir string) bool {
|
||||
gpgHomedir := path.Join(rootDir, "/var/lib/bpm/gpg")
|
||||
|
||||
// Check if gpg directory exists
|
||||
if stat, err := os.Stat(gpgHomedir); err != nil || !stat.IsDir() {
|
||||
return false
|
||||
}
|
||||
|
||||
// Get number of secret keys
|
||||
cmd := exec.Command("gpg", "--homedir="+gpgHomedir, "--list-secret-keys", "--with-colons")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
secretKeysLineCount := len(strings.Split(strings.TrimSpace(string(output)), "\n"))
|
||||
|
||||
// Return false if no signing key has been created
|
||||
if secretKeysLineCount <= 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func PopulateKeyring(rootDir string) error {
|
||||
gpgHomedir := path.Join(rootDir, "/var/lib/bpm/gpg")
|
||||
keyringsDir := path.Join(rootDir, "/var/lib/bpm/keyrings")
|
||||
|
||||
dirEntries, err := os.ReadDir(keyringsDir)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove removed keys
|
||||
for _, entry := range dirEntries {
|
||||
if entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
if !strings.HasSuffix(entry.Name(), ".revoked") {
|
||||
continue
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(path.Join(keyringsDir, entry.Name()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Loop over all key IDs
|
||||
for entry := range strings.SplitSeq(strings.TrimSpace(string(data)), "\n") {
|
||||
// Ensure key ID exists
|
||||
err := exec.Command("gpg", "--homedir="+gpgHomedir, "--list-keys", entry).Run()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
cmd := exec.Command("gpg", "--homedir="+gpgHomedir, "--batch", "--yes", "--delete-secret-and-public-keys", entry)
|
||||
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Import all keyrings
|
||||
for _, entry := range dirEntries {
|
||||
if entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
if !strings.HasSuffix(entry.Name(), ".pgp") && !strings.HasSuffix(entry.Name(), ".asc") {
|
||||
continue
|
||||
}
|
||||
|
||||
cmd := exec.Command("gpg", "--homedir="+gpgHomedir, "--import", path.Join(keyringsDir, entry.Name()))
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Trust keys
|
||||
for _, entry := range dirEntries {
|
||||
if entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
if !strings.HasSuffix(entry.Name(), ".trustdb") {
|
||||
continue
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(path.Join(keyringsDir, entry.Name()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Loop over all key IDs
|
||||
for entry := range strings.SplitSeq(strings.TrimSpace(string(data)), "\n") {
|
||||
keyID := strings.Split(entry, ":")[0]
|
||||
|
||||
// Ensure key ID exists
|
||||
err := exec.Command("gpg", "--homedir="+gpgHomedir, "--list-keys", keyID).Run()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Sign key
|
||||
cmd := exec.Command("gpg", "--homedir="+gpgHomedir, "--command-fd=0", "--batch", "--lsign-key", keyID)
|
||||
cmd.Stdin = strings.NewReader("y\ny\n")
|
||||
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Import owner trust database
|
||||
cmd := exec.Command("gpg", "--homedir="+gpgHomedir, "--import-ownertrust", path.Join(keyringsDir, entry.Name()))
|
||||
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func VerifySignature(filename, signature string, requireTrusted bool, rootDir string) error {
|
||||
gpgHomedir := path.Join(rootDir, "/var/lib/bpm/gpg")
|
||||
|
||||
if _, err := os.Stat(gpgHomedir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := exec.Command("gpg", "--homedir="+gpgHomedir, "--status-fd=3", "--verify", signature, filename)
|
||||
pipeReader, pipeWriter, err := os.Pipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer pipeReader.Close()
|
||||
defer pipeWriter.Close()
|
||||
cmd.ExtraFiles = append(cmd.ExtraFiles, pipeWriter)
|
||||
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pipeWriter.Close()
|
||||
|
||||
if requireTrusted {
|
||||
data, err := io.ReadAll(pipeReader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dataStr := string(data)
|
||||
if !strings.Contains(dataStr, "[GNUPG:] TRUST_FULLY") && !strings.Contains(dataStr, "[GNUPG:] TRUST_ULTIMATE") {
|
||||
return fmt.Errorf("signature verified but not trusted")
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
+46
-67
@@ -1,26 +1,27 @@
|
||||
package bpmlib
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"gopkg.in/yaml.v3"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type BPMHook struct {
|
||||
SourcePath string
|
||||
SourceContent string
|
||||
TriggerOperations []string `yaml:"trigger_operations"`
|
||||
TargetType string `yaml:"target_type"`
|
||||
Targets []string `yaml:"targets"`
|
||||
Depends []string `yaml:"depends"`
|
||||
Run string `yaml:"run"`
|
||||
SourcePath string
|
||||
SourceContent string
|
||||
TriggerActions []string `yaml:"trigger_actions"`
|
||||
TriggerPreOperation bool `yaml:"trigger_pre_operation"`
|
||||
Targets []string `yaml:"targets"`
|
||||
Run string `yaml:"run"`
|
||||
PassTargets bool `yaml:"pass_targets"`
|
||||
}
|
||||
|
||||
// createHook returns a BPMHook instance based on the content of the given string
|
||||
@@ -33,13 +34,11 @@ func createHook(sourcePath string) (*BPMHook, error) {
|
||||
|
||||
// Create base hook structure
|
||||
hook := &BPMHook{
|
||||
SourcePath: sourcePath,
|
||||
SourceContent: string(bytes),
|
||||
TriggerOperations: nil,
|
||||
TargetType: "",
|
||||
Targets: nil,
|
||||
Depends: nil,
|
||||
Run: "",
|
||||
SourcePath: sourcePath,
|
||||
SourceContent: string(bytes),
|
||||
TriggerActions: nil,
|
||||
Targets: nil,
|
||||
Run: "",
|
||||
}
|
||||
|
||||
// Unmarshal yaml string
|
||||
@@ -61,19 +60,15 @@ func (hook *BPMHook) IsValid() error {
|
||||
ValidOperations := []string{"install", "upgrade", "remove"}
|
||||
|
||||
// Return error if any trigger operation is not valid or none are given
|
||||
if len(hook.TriggerOperations) == 0 {
|
||||
if len(hook.TriggerActions) == 0 {
|
||||
return errors.New("no trigger operations specified")
|
||||
}
|
||||
for _, operation := range hook.TriggerOperations {
|
||||
for _, operation := range hook.TriggerActions {
|
||||
if !slices.Contains(ValidOperations, operation) {
|
||||
return errors.New("trigger operation '" + operation + "' is not valid")
|
||||
}
|
||||
}
|
||||
|
||||
if hook.TargetType != "package" && hook.TargetType != "path" {
|
||||
return errors.New("target type '" + hook.TargetType + "' is not valid")
|
||||
}
|
||||
|
||||
if len(hook.Run) == 0 {
|
||||
return errors.New("command to run is empty")
|
||||
}
|
||||
@@ -83,55 +78,30 @@ func (hook *BPMHook) IsValid() error {
|
||||
}
|
||||
|
||||
// Execute hook if all conditions are met
|
||||
func (hook *BPMHook) Execute(packageChanges map[string]string, verbose bool, rootDir string) error {
|
||||
// Check if package dependencies are met
|
||||
installedPackages, err := GetInstalledPackages(rootDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, depend := range hook.Depends {
|
||||
if !slices.Contains(installedPackages, depend) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Get modified files slice
|
||||
modifiedFiles := make([]*PackageFileEntry, 0)
|
||||
for pkg := range packageChanges {
|
||||
if GetPackage(pkg, rootDir) != nil {
|
||||
modifiedFiles = append(modifiedFiles, GetPackage(pkg, rootDir).PkgFiles...)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (hook *BPMHook) Execute(modifiedFiles map[string]string, preOperation bool, verbose bool, rootDir string) error {
|
||||
// Check if any targets are met
|
||||
targetMet := false
|
||||
targetsMet := make([]string, 0)
|
||||
for _, target := range hook.Targets {
|
||||
if targetMet {
|
||||
break
|
||||
}
|
||||
if hook.TargetType == "package" {
|
||||
for change, operation := range packageChanges {
|
||||
if target == change && slices.Contains(hook.TriggerOperations, operation) {
|
||||
targetMet = true
|
||||
break
|
||||
}
|
||||
for modifiedFile, action := range modifiedFiles {
|
||||
// Check if this hook is triggered by this file's action
|
||||
if !slices.Contains(hook.TriggerActions, action) {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
glob, err := filepath.Glob(path.Join(rootDir, target))
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
// Check if file has already been checked
|
||||
if slices.Contains(targetsMet, modifiedFile) {
|
||||
continue
|
||||
}
|
||||
for _, change := range modifiedFiles {
|
||||
if slices.Contains(glob, path.Join(rootDir, change.Path)) {
|
||||
targetMet = true
|
||||
break
|
||||
}
|
||||
|
||||
if matched, _ := filepath.Match(target, modifiedFile); !matched {
|
||||
continue
|
||||
}
|
||||
|
||||
targetsMet = append(targetsMet, modifiedFile)
|
||||
}
|
||||
}
|
||||
if !targetMet {
|
||||
|
||||
if len(targetsMet) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -140,16 +110,25 @@ func (hook *BPMHook) Execute(packageChanges map[string]string, verbose bool, roo
|
||||
cmd := exec.Command(splitCommand[0], splitCommand[1:]...)
|
||||
// Setup subprocess environment
|
||||
cmd.Dir = "/"
|
||||
// Pass targets
|
||||
if hook.PassTargets {
|
||||
buffer := bytes.Buffer{}
|
||||
buffer.WriteString(strings.Join(targetsMet, "\n") + "\n")
|
||||
|
||||
cmd.Stdin = &buffer
|
||||
}
|
||||
// Run hook in chroot if using the -R flag
|
||||
if rootDir != "/" {
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{Chroot: rootDir}
|
||||
}
|
||||
|
||||
if verbose {
|
||||
fmt.Printf("Running hook (%s) with run command: %s\n", hook.SourcePath, strings.Join(splitCommand, " "))
|
||||
if !verbose {
|
||||
fmt.Printf("Running hook (%s)\n", filepath.Base(hook.SourcePath))
|
||||
} else {
|
||||
fmt.Printf("Running hook (%s) with run command: %s\n", filepath.Base(hook.SourcePath), strings.Join(splitCommand, " "))
|
||||
}
|
||||
|
||||
err = cmd.Run()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -4,28 +4,51 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var localPackageInformation map[string]map[string]*BPMPackage = make(map[string]map[string]*BPMPackage)
|
||||
var persistentDataVersion int = 1
|
||||
|
||||
func initializeLocalPackageInformation(rootDir string) (err error) {
|
||||
var localPackageInformation map[string]map[string]*PackageInfo = make(map[string]map[string]*PackageInfo)
|
||||
var installedVirtualPackages map[string]map[string][]*PackageInfo = make(map[string]map[string][]*PackageInfo)
|
||||
|
||||
func InitializeLocalPackageInformation(rootDir string) (err error) {
|
||||
// Return if information is already initialized
|
||||
if _, ok := localPackageInformation[rootDir]; ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
tempPackageInformation := make(map[string]*BPMPackage)
|
||||
tempPackageInformation := make(map[string]*PackageInfo)
|
||||
tempInstalledVirtualPackages := make(map[string][]*PackageInfo)
|
||||
|
||||
// Get path to installed package information directory
|
||||
installedDir := path.Join(rootDir, "var/lib/bpm/installed/")
|
||||
// Get paths
|
||||
persistentDataDir := path.Join(rootDir, "var/lib/bpm")
|
||||
installedDir := path.Join(persistentDataDir, "installed")
|
||||
|
||||
// Ensure persistent data directory is up-to-date
|
||||
if _, err := os.Stat(persistentDataDir); err == nil {
|
||||
data, err := os.ReadFile(path.Join(persistentDataDir, ".version"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("persistent data is not up-to-date! Please run 'bpm upgrade-persistent-data' first")
|
||||
}
|
||||
currentPersistentDataVersion, err := strconv.Atoi(strings.TrimSpace(string(data)))
|
||||
if err != nil {
|
||||
return fmt.Errorf("persistent data is not up-to-date! Please run 'bpm upgrade-persistent-data' first")
|
||||
}
|
||||
if currentPersistentDataVersion != persistentDataVersion {
|
||||
return fmt.Errorf("persistent data is not up-to-date! Please run 'bpm upgrade-persistent-data' first")
|
||||
}
|
||||
}
|
||||
|
||||
// Get directory content
|
||||
items, err := os.ReadDir(installedDir)
|
||||
if os.IsNotExist(err) {
|
||||
localPackageInformation[rootDir] = make(map[string]*BPMPackage)
|
||||
localPackageInformation[rootDir] = make(map[string]*PackageInfo)
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
@@ -40,7 +63,7 @@ func initializeLocalPackageInformation(rootDir string) (err error) {
|
||||
}
|
||||
|
||||
// Read package info
|
||||
infoData, err := os.ReadFile(path.Join(installedDir, item.Name(), "info"))
|
||||
infoData, err := os.ReadFile(path.Join(installedDir, item.Name(), "info.yml"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -49,30 +72,68 @@ func initializeLocalPackageInformation(rootDir string) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
// Read package files
|
||||
files := getPackageFiles(info.Name, rootDir)
|
||||
|
||||
// Add package to slice
|
||||
tempPackageInformation[info.Name] = &BPMPackage{
|
||||
PkgInfo: info,
|
||||
PkgFiles: files,
|
||||
tempPackageInformation[info.Name] = info
|
||||
|
||||
// Add virtual packages
|
||||
for _, vpkg := range info.Provides {
|
||||
tempInstalledVirtualPackages[vpkg] = append(tempInstalledVirtualPackages[vpkg], info)
|
||||
}
|
||||
}
|
||||
|
||||
localPackageInformation[rootDir] = tempPackageInformation
|
||||
installedVirtualPackages[rootDir] = tempInstalledVirtualPackages
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetInstalledPackages(rootDir string) (ret []string, err error) {
|
||||
// Initialize local package information
|
||||
err = initializeLocalPackageInformation(rootDir)
|
||||
err = InitializeLocalPackageInformation(rootDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Loop through each package and add it to slice
|
||||
for _, bpmpkg := range localPackageInformation[rootDir] {
|
||||
ret = append(ret, bpmpkg.PkgInfo.Name)
|
||||
for _, pkgInfo := range localPackageInformation[rootDir] {
|
||||
ret = append(ret, pkgInfo.Name)
|
||||
}
|
||||
|
||||
// Sort packages
|
||||
slices.Sort(ret)
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func GetPathOwners(path, rootDir string) (ret []string, err error) {
|
||||
// Get absolte path to path
|
||||
path, err = filepath.Abs(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
path, err = filepath.Rel(rootDir, path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Trim leading and trailing slashes
|
||||
path = strings.TrimLeft(path, "/")
|
||||
path = strings.TrimRight(path, "/")
|
||||
|
||||
// Get installed packages
|
||||
pkgs, err := GetInstalledPackages(rootDir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Add packages that own path to list
|
||||
for _, pkg := range pkgs {
|
||||
pkgFiles := getPackageFiles(pkg, rootDir)
|
||||
if slices.ContainsFunc(pkgFiles, func(entry *PackageFileEntry) bool {
|
||||
return entry.Path == path
|
||||
}) {
|
||||
ret = append(ret, pkg)
|
||||
}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
@@ -80,7 +141,7 @@ func GetInstalledPackages(rootDir string) (ret []string, err error) {
|
||||
|
||||
func IsPackageInstalled(pkg, rootDir string) bool {
|
||||
// Initialize local package information
|
||||
err := initializeLocalPackageInformation(rootDir)
|
||||
err := InitializeLocalPackageInformation(rootDir)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
@@ -91,67 +152,43 @@ func IsPackageInstalled(pkg, rootDir string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func GetPackageInfo(pkg string, rootDir string) *PackageInfo {
|
||||
// Get BPM package
|
||||
bpmpkg := GetPackage(pkg, rootDir)
|
||||
|
||||
// Return nil if not found
|
||||
if bpmpkg == nil {
|
||||
func GetVirtualPackageInfo(vpkg, rootDir string) []*PackageInfo {
|
||||
err := InitializeLocalPackageInformation(rootDir)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return bpmpkg.PkgInfo
|
||||
providers := installedVirtualPackages[rootDir][vpkg]
|
||||
slices.SortFunc(providers, func(a, b *PackageInfo) int {
|
||||
return strings.Compare(a.Name, b.Name)
|
||||
})
|
||||
|
||||
return providers
|
||||
}
|
||||
|
||||
func IsVirtualPackage(pkg, rootDir string) (bool, string) {
|
||||
pkgs, err := GetInstalledPackages(rootDir)
|
||||
func GetPackageInfo(pkg string, rootDir string) *PackageInfo {
|
||||
err := InitializeLocalPackageInformation(rootDir)
|
||||
if err != nil {
|
||||
return false, ""
|
||||
return nil
|
||||
}
|
||||
for _, p := range pkgs {
|
||||
if p == pkg {
|
||||
return false, ""
|
||||
}
|
||||
i := GetPackageInfo(p, rootDir)
|
||||
if i == nil {
|
||||
continue
|
||||
}
|
||||
if slices.Contains(i.Provides, pkg) {
|
||||
return true, p
|
||||
}
|
||||
}
|
||||
return false, ""
|
||||
}
|
||||
|
||||
func IsPackageProvided(pkg, rootDir string) bool {
|
||||
pkgs, err := GetInstalledPackages(rootDir)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
for _, p := range pkgs {
|
||||
if p == pkg {
|
||||
return true
|
||||
}
|
||||
i := GetPackageInfo(p, rootDir)
|
||||
if i == nil {
|
||||
continue
|
||||
}
|
||||
if slices.Contains(i.Provides, pkg) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return localPackageInformation[rootDir][pkg]
|
||||
}
|
||||
|
||||
func GetPackage(pkg, rootDir string) *BPMPackage {
|
||||
err := initializeLocalPackageInformation(rootDir)
|
||||
if err != nil {
|
||||
pkgInfo := GetPackageInfo(pkg, rootDir)
|
||||
if pkgInfo == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
bpmpkg := localPackageInformation[rootDir][pkg]
|
||||
files := getPackageFiles(pkgInfo.Name, rootDir)
|
||||
localInfo := getPackageLocalInfo(pkgInfo.Name, rootDir)
|
||||
|
||||
return bpmpkg
|
||||
return &BPMPackage{
|
||||
PkgInfo: pkgInfo,
|
||||
PkgFiles: files,
|
||||
LocalInfo: localInfo,
|
||||
}
|
||||
}
|
||||
|
||||
func GetAllPackageFiles(rootDir string, excludePackages ...string) (map[string][]*BPMPackage, error) {
|
||||
@@ -183,7 +220,7 @@ func getPackageFiles(pkg, rootDir string) []*PackageFileEntry {
|
||||
var pkgFiles []*PackageFileEntry
|
||||
installedDir := path.Join(rootDir, "var/lib/bpm/installed/")
|
||||
pkgDir := path.Join(installedDir, pkg)
|
||||
files := path.Join(pkgDir, "files")
|
||||
files := path.Join(pkgDir, "files.txt")
|
||||
if _, err := os.Stat(installedDir); os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
@@ -222,7 +259,7 @@ func getPackageFiles(pkg, rootDir string) []*PackageFileEntry {
|
||||
if err != 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 {
|
||||
return nil
|
||||
}
|
||||
@@ -237,3 +274,154 @@ func getPackageFiles(pkg, rootDir string) []*PackageFileEntry {
|
||||
|
||||
return pkgFiles
|
||||
}
|
||||
|
||||
func getPackageLocalInfo(pkg, rootDir string) PackageLocalInfo {
|
||||
localInfo := PackageLocalInfo{}
|
||||
|
||||
installedDir := path.Join(rootDir, "var/lib/bpm/installed/")
|
||||
pkgDir := path.Join(installedDir, pkg)
|
||||
localInfoFile := path.Join(path.Join(pkgDir, "local.yml"))
|
||||
|
||||
if _, err := os.Stat(localInfoFile); os.IsNotExist(err) {
|
||||
return localInfo
|
||||
}
|
||||
|
||||
file, err := os.Open(localInfoFile)
|
||||
if err != nil {
|
||||
return localInfo
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
err = yaml.NewDecoder(file).Decode(&localInfo)
|
||||
if err != nil {
|
||||
return localInfo
|
||||
}
|
||||
|
||||
return localInfo
|
||||
}
|
||||
|
||||
func SetPackageLocalInfo(pkg string, localInfo PackageLocalInfo, rootDir string) error {
|
||||
installedDir := path.Join(rootDir, "var/lib/bpm/installed/")
|
||||
pkgDir := path.Join(installedDir, pkg)
|
||||
|
||||
localFile, err := os.OpenFile(path.Join(pkgDir, "local.yml"), os.O_WRONLY|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer localFile.Close()
|
||||
|
||||
err = yaml.NewEncoder(localFile).Encode(localInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func UpgradePersistentData(rootDir string) error {
|
||||
persistentDataDir := path.Join(rootDir, "var/lib/bpm")
|
||||
|
||||
// Create persistent data directory
|
||||
os.MkdirAll(persistentDataDir, 0755)
|
||||
|
||||
// Upgrade installed package directories
|
||||
dirEntries, err := os.ReadDir(path.Join(persistentDataDir, "installed"))
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
} else if err == nil {
|
||||
for _, entry := range dirEntries {
|
||||
pkgDir := path.Join(persistentDataDir, "installed", entry.Name())
|
||||
|
||||
// Rename 'info' file to 'info.yml'
|
||||
if _, err := os.Stat(path.Join(pkgDir, "info")); err == nil {
|
||||
fmt.Printf("Moving 'info' to 'info.yml' for package (%s)\n", entry.Name())
|
||||
err := os.Rename(path.Join(pkgDir, "info"), path.Join(pkgDir, "info.yml"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Rename 'files' file to 'files.txt'
|
||||
if _, err := os.Stat(path.Join(pkgDir, "files")); err == nil {
|
||||
fmt.Printf("Moving 'files' to 'files.txt' for package (%s)\n", entry.Name())
|
||||
err := os.Rename(path.Join(pkgDir, "files"), path.Join(pkgDir, "files.txt"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Rename 'local' file to 'local.yml'
|
||||
if _, err := os.Stat(path.Join(pkgDir, "local")); err == nil {
|
||||
fmt.Printf("Moving 'local' to 'local.yml' for package (%s)\n", entry.Name())
|
||||
err := os.Rename(path.Join(pkgDir, "local"), path.Join(pkgDir, "local.yml"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Generate default local package information file
|
||||
if _, err := os.Stat(path.Join(pkgDir, "local.yml")); os.IsNotExist(err) {
|
||||
fmt.Printf("Generating local package information for package (%s)\n", entry.Name())
|
||||
|
||||
out, err := yaml.Marshal(PackageLocalInfo{
|
||||
InstallationReason: "unknown",
|
||||
InstalledOn: 0,
|
||||
LastUpdatedOn: 0,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.WriteFile(path.Join(pkgDir, "local.yml"), out, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Move installation reason to local package information file
|
||||
if installationReason, err := os.ReadFile(path.Join(pkgDir, "installation_reason")); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
} else if err == nil {
|
||||
fmt.Printf("Moving installation reason to local package information for package (%s)\n", entry.Name())
|
||||
|
||||
data, err := os.ReadFile(path.Join(pkgDir, "local.yml"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
localInfo := &PackageLocalInfo{}
|
||||
err = yaml.Unmarshal(data, localInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
localInfo.InstallationReason = strings.TrimSpace(string(installationReason))
|
||||
|
||||
out, err := yaml.Marshal(localInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.WriteFile(path.Join(pkgDir, "local.yml"), out, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.Remove(path.Join(pkgDir, "installation_reason"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set persistent data version number
|
||||
err = os.WriteFile(path.Join(persistentDataDir, ".version"), []byte(strconv.Itoa(persistentDataVersion)), 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
+13
-16
@@ -9,29 +9,22 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func retrieveUrlData(u string) ([]byte, error) {
|
||||
resp, err := http.Get(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Load data into byte buffer
|
||||
buffer, err := io.ReadAll(resp.Body)
|
||||
|
||||
return buffer, nil
|
||||
}
|
||||
|
||||
func downloadFile(u, filepath string, perm os.FileMode) error {
|
||||
func downloadFile(barText, u, filepath string, perm os.FileMode) error {
|
||||
if strings.HasSuffix(filepath, "/") {
|
||||
return fmt.Errorf("Filepath must not end in '/'")
|
||||
}
|
||||
|
||||
data, err := retrieveUrlData(u)
|
||||
req, err := http.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Create parent directories
|
||||
err = os.MkdirAll(path.Dir(filepath), 0755)
|
||||
if err != nil {
|
||||
@@ -45,8 +38,12 @@ func downloadFile(u, filepath string, perm os.FileMode) error {
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Create progress bar
|
||||
bar := createProgressBar(resp.ContentLength, barText, barText == "")
|
||||
defer bar.Close()
|
||||
|
||||
// Copy data
|
||||
_, err = file.Write(data)
|
||||
_, err = io.Copy(io.MultiWriter(file, bar), resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+430
-205
@@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"maps"
|
||||
"os"
|
||||
"path"
|
||||
"slices"
|
||||
@@ -13,82 +14,18 @@ import (
|
||||
|
||||
type BPMOperation struct {
|
||||
Actions []OperationAction
|
||||
UnresolvedDepends []string
|
||||
Changes map[string]string
|
||||
UnresolvedDepends map[string]string
|
||||
ModifiedFiles map[string]string
|
||||
CompilationJobs int
|
||||
RunChecks bool
|
||||
RootDir string
|
||||
|
||||
compiledPackages map[string]string
|
||||
hasFetchedPackages bool
|
||||
}
|
||||
|
||||
func (operation *BPMOperation) ActionsContainPackage(pkg string) bool {
|
||||
for _, action := range operation.Actions {
|
||||
if action.GetActionType() == "install" {
|
||||
if action.(*InstallPackageAction).BpmPackage.PkgInfo.Name == pkg {
|
||||
return true
|
||||
}
|
||||
} else if action.GetActionType() == "fetch" {
|
||||
if action.(*FetchPackageAction).DatabaseEntry.Info.Name == pkg {
|
||||
return true
|
||||
}
|
||||
} else if action.GetActionType() == "remove" {
|
||||
if action.(*RemovePackageAction).BpmPackage.PkgInfo.Name == pkg {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (operation *BPMOperation) AppendAction(action OperationAction) {
|
||||
operation.InsertActionAt(len(operation.Actions), action)
|
||||
}
|
||||
|
||||
func (operation *BPMOperation) InsertActionAt(index int, action OperationAction) {
|
||||
if len(operation.Actions) == index { // nil or empty slice or after last element
|
||||
operation.Actions = append(operation.Actions, action)
|
||||
} else {
|
||||
operation.Actions = append(operation.Actions[:index+1], operation.Actions[index:]...) // index < len(a)
|
||||
operation.Actions[index] = action
|
||||
}
|
||||
|
||||
if action.GetActionType() == "install" {
|
||||
pkgInfo := action.(*InstallPackageAction).BpmPackage.PkgInfo
|
||||
if !IsPackageInstalled(pkgInfo.Name, operation.RootDir) {
|
||||
operation.Changes[pkgInfo.Name] = "install"
|
||||
} else {
|
||||
operation.Changes[pkgInfo.Name] = "upgrade"
|
||||
}
|
||||
} else if action.GetActionType() == "fetch" {
|
||||
pkgInfo := action.(*FetchPackageAction).DatabaseEntry.Info
|
||||
if !IsPackageInstalled(pkgInfo.Name, operation.RootDir) {
|
||||
operation.Changes[pkgInfo.Name] = "install"
|
||||
} else {
|
||||
operation.Changes[pkgInfo.Name] = "upgrade"
|
||||
}
|
||||
} else if action.GetActionType() == "remove" {
|
||||
operation.Changes[action.(*RemovePackageAction).BpmPackage.PkgInfo.Name] = "remove"
|
||||
}
|
||||
}
|
||||
|
||||
func (operation *BPMOperation) RemoveAction(pkg, actionType string) {
|
||||
operation.Actions = slices.DeleteFunc(operation.Actions, func(a OperationAction) bool {
|
||||
if a.GetActionType() != actionType {
|
||||
return false
|
||||
}
|
||||
if a.GetActionType() == "install" {
|
||||
return a.(*InstallPackageAction).BpmPackage.PkgInfo.Name == pkg
|
||||
} else if a.GetActionType() == "fetch" {
|
||||
return a.(*FetchPackageAction).DatabaseEntry.Info.Name == pkg
|
||||
} else if a.GetActionType() == "remove" {
|
||||
return a.(*RemovePackageAction).BpmPackage.PkgInfo.Name == pkg
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
func (operation *BPMOperation) GetTotalDownloadSize() uint64 {
|
||||
var ret uint64 = 0
|
||||
func (operation *BPMOperation) GetTotalDownloadSize() int64 {
|
||||
var ret int64 = 0
|
||||
for _, action := range operation.Actions {
|
||||
if action.GetActionType() == "fetch" {
|
||||
ret += action.(*FetchPackageAction).DatabaseEntry.DownloadSize
|
||||
@@ -97,8 +34,8 @@ func (operation *BPMOperation) GetTotalDownloadSize() uint64 {
|
||||
return ret
|
||||
}
|
||||
|
||||
func (operation *BPMOperation) GetTotalInstalledSize() uint64 {
|
||||
var ret uint64 = 0
|
||||
func (operation *BPMOperation) GetTotalInstalledSize() int64 {
|
||||
var ret int64 = 0
|
||||
for _, action := range operation.Actions {
|
||||
if action.GetActionType() == "install" {
|
||||
ret += action.(*InstallPackageAction).BpmPackage.GetInstalledSize()
|
||||
@@ -113,21 +50,25 @@ func (operation *BPMOperation) GetFinalActionSize(rootDir string) int64 {
|
||||
var ret int64 = 0
|
||||
for _, action := range operation.Actions {
|
||||
if action.GetActionType() == "install" {
|
||||
ret += int64(action.(*InstallPackageAction).BpmPackage.GetInstalledSize())
|
||||
ret += action.(*InstallPackageAction).BpmPackage.GetInstalledSize()
|
||||
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" {
|
||||
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" {
|
||||
ret -= int64(action.(*RemovePackageAction).BpmPackage.GetInstalledSize())
|
||||
ret -= action.(*RemovePackageAction).BpmPackage.GetInstalledSize()
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (operation *BPMOperation) ResolveDependencies(reinstallDependencies, installOptionalDependencies, verbose bool) error {
|
||||
pos := 0
|
||||
func (operation *BPMOperation) ResolveDependencies(installRuntimeDepends bool) {
|
||||
// Discover resolved virtual packages
|
||||
resolvedVirtualPackages := make(map[string]string)
|
||||
for _, value := range slices.Clone(operation.Actions) {
|
||||
var pkgInfo *PackageInfo
|
||||
if value.GetActionType() == "install" {
|
||||
@@ -137,61 +78,66 @@ func (operation *BPMOperation) ResolveDependencies(reinstallDependencies, instal
|
||||
action := value.(*FetchPackageAction)
|
||||
pkgInfo = action.DatabaseEntry.Info
|
||||
} else {
|
||||
pos++
|
||||
continue
|
||||
}
|
||||
|
||||
resolved, unresolved := ResolveAllPackageDependenciesFromDatabases(pkgInfo, pkgInfo.Type == "source", installOptionalDependencies, !reinstallDependencies, verbose, operation.RootDir)
|
||||
for _, vpkg := range pkgInfo.Provides {
|
||||
if _, ok := resolvedVirtualPackages[vpkg]; !ok {
|
||||
resolvedVirtualPackages[vpkg] = pkgInfo.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
operation.UnresolvedDepends = append(operation.UnresolvedDepends, unresolved...)
|
||||
// Discover all dependencies
|
||||
newActions := make([]OperationAction, 0)
|
||||
for _, value := range operation.Actions {
|
||||
var pkgInfo *PackageInfo
|
||||
var flags map[string]string
|
||||
if value.GetActionType() == "install" {
|
||||
action := value.(*InstallPackageAction)
|
||||
pkgInfo = action.BpmPackage.PkgInfo
|
||||
flags = action.Flags
|
||||
} else if value.GetActionType() == "fetch" {
|
||||
action := value.(*FetchPackageAction)
|
||||
pkgInfo = action.DatabaseEntry.Info
|
||||
flags = action.Flags
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
|
||||
resolved, unresolved := ResolveDependencies(pkgInfo, flags, resolvedVirtualPackages, installRuntimeDepends, operation.RootDir)
|
||||
|
||||
// Copy unresolved dependencies
|
||||
maps.Copy(operation.UnresolvedDepends, unresolved)
|
||||
|
||||
for _, resolvedPkg := range resolved {
|
||||
if !operation.ActionsContainPackage(resolvedPkg.PkgName) && resolvedPkg.PkgName != pkgInfo.Name {
|
||||
if !reinstallDependencies && IsPackageInstalled(resolvedPkg.PkgName, operation.RootDir) {
|
||||
continue
|
||||
}
|
||||
entry, _, err := GetDatabaseEntry(resolvedPkg.PkgName)
|
||||
if err != nil {
|
||||
return errors.New("could not get database entry for package (" + resolvedPkg.PkgName + ")")
|
||||
}
|
||||
operation.InsertActionAt(pos, &FetchPackageAction{
|
||||
if ActionSliceIndex(newActions, resolvedPkg.DatabaseEntry.Info.Name) == -1 { // Dependency not in actions slice
|
||||
var action OperationAction = &FetchPackageAction{
|
||||
InstallationReason: resolvedPkg.InstallationReason,
|
||||
DatabaseEntry: entry,
|
||||
})
|
||||
pos++
|
||||
Flags: resolvedPkg.Flags,
|
||||
DatabaseEntry: resolvedPkg.DatabaseEntry,
|
||||
}
|
||||
newActions = append(newActions, action)
|
||||
|
||||
for _, vpkg := range resolvedPkg.DatabaseEntry.Info.Provides {
|
||||
if _, ok := resolvedVirtualPackages[vpkg]; !ok {
|
||||
resolvedVirtualPackages[vpkg] = resolvedPkg.DatabaseEntry.Info.Name
|
||||
}
|
||||
}
|
||||
|
||||
// Check if can move original action
|
||||
if i := ActionSliceIndex(operation.Actions, resolvedPkg.DatabaseEntry.Info.Name); i != -1 {
|
||||
newActions[len(newActions)-1] = operation.Actions[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
pos++
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (operation *BPMOperation) RemoveNeededPackages() error {
|
||||
removeActions := make(map[string]*RemovePackageAction)
|
||||
for _, action := range slices.Clone(operation.Actions) {
|
||||
if action.GetActionType() == "remove" {
|
||||
removeActions[action.(*RemovePackageAction).BpmPackage.PkgInfo.Name] = action.(*RemovePackageAction)
|
||||
if ActionSliceIndex(newActions, pkgInfo.Name) == -1 {
|
||||
newActions = append(newActions, value)
|
||||
}
|
||||
}
|
||||
|
||||
for pkg, action := range removeActions {
|
||||
dependants, err := GetPackageDependants(action.BpmPackage.PkgInfo.Name, operation.RootDir)
|
||||
if err != nil {
|
||||
return errors.New("could not get dependant packages for package (" + pkg + ")")
|
||||
}
|
||||
dependants = slices.DeleteFunc(dependants, func(d string) bool {
|
||||
if _, ok := removeActions[d]; ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
if len(dependants) != 0 {
|
||||
operation.RemoveAction(pkg, action.GetActionType())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
operation.Actions = newActions
|
||||
}
|
||||
|
||||
func (operation *BPMOperation) Cleanup(cleanupMakeDepends bool) error {
|
||||
@@ -202,11 +148,11 @@ func (operation *BPMOperation) Cleanup(cleanupMakeDepends bool) error {
|
||||
}
|
||||
installedPackages := make([]*PackageInfo, len(installedPackageNames))
|
||||
for i, value := range installedPackageNames {
|
||||
bpmpkg := GetPackage(value, operation.RootDir)
|
||||
if bpmpkg == nil {
|
||||
pkgInfo := GetPackageInfo(value, operation.RootDir)
|
||||
if pkgInfo == nil {
|
||||
return errors.New("could not find installed package (" + value + ")")
|
||||
}
|
||||
installedPackages[i] = bpmpkg.PkgInfo
|
||||
installedPackages[i] = pkgInfo
|
||||
}
|
||||
|
||||
// Get packages to remove
|
||||
@@ -217,37 +163,76 @@ func (operation *BPMOperation) Cleanup(cleanupMakeDepends bool) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Get manually installed packages, resolve all their dependencies and add them to the keepPackages slice
|
||||
keepPackages := make([]string, 0)
|
||||
for _, pkg := range slices.Clone(installedPackages) {
|
||||
if GetInstallationReason(pkg.Name, operation.RootDir) != InstallationReasonManual {
|
||||
// Run BFS on all manually installed packages
|
||||
visited := make([]string, 0)
|
||||
for _, pkg := range installedPackages {
|
||||
if getPackageLocalInfo(pkg.Name, operation.RootDir).GetInstallationReason() != InstallationReasonManual {
|
||||
continue
|
||||
}
|
||||
|
||||
// Do not resolve dependencies or add package to keepPackages slice if package removal action exists for it
|
||||
if _, ok := removeActions[pkg.Name]; ok {
|
||||
continue
|
||||
}
|
||||
queue := make([]*PackageInfo, 0)
|
||||
|
||||
keepPackages = append(keepPackages, pkg.Name)
|
||||
resolved := pkg.GetDependenciesRecursive(!cleanupMakeDepends, operation.RootDir)
|
||||
for _, value := range resolved {
|
||||
if !slices.Contains(keepPackages, value) {
|
||||
keepPackages = append(keepPackages, value)
|
||||
queue = append(queue, pkg)
|
||||
|
||||
for len(queue) > 0 {
|
||||
v := queue[len(queue)-1]
|
||||
queue = queue[:len(queue)-1]
|
||||
|
||||
// Skip package if it's to be removed
|
||||
if _, ok := removeActions[v.Name]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// Mark package as visited
|
||||
visited = append(visited, v.Name)
|
||||
|
||||
// Get all package dependencies
|
||||
depends := v.Depends
|
||||
depends = append(depends, v.RuntimeDepends...)
|
||||
if cleanupMakeDepends && v.Type == "source" {
|
||||
depends = append(depends, v.MakeDepends...)
|
||||
depends = append(depends, v.CheckDepends...)
|
||||
}
|
||||
|
||||
// Loop through all dependencies
|
||||
for _, depend := range depends {
|
||||
// Deconstruct package string
|
||||
d, err := DeconstructPackageString(depend)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not deconstruct package string: %s", err)
|
||||
}
|
||||
|
||||
// Resolve dependency
|
||||
var dependPkgInfo *PackageInfo
|
||||
if providers := GetVirtualPackageInfo(d.PkgName, operation.RootDir); len(providers) > 0 {
|
||||
dependPkgInfo = providers[0]
|
||||
} else {
|
||||
dependPkgInfo = GetPackageInfo(d.PkgName, operation.RootDir)
|
||||
}
|
||||
if dependPkgInfo == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Mark dependency as visited and add it to the queue
|
||||
if !slices.Contains(visited, dependPkgInfo.Name) {
|
||||
visited = append(visited, dependPkgInfo.Name)
|
||||
queue = append(queue, dependPkgInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get all installed packages that are not in the keepPackages slice and add them to the BPM operation
|
||||
// Remove all packages that were not discovered after running BFS
|
||||
for _, pkg := range installedPackageNames {
|
||||
// Do not add package removal action if there already is one
|
||||
if _, ok := removeActions[pkg]; ok {
|
||||
continue
|
||||
}
|
||||
if !slices.Contains(keepPackages, pkg) {
|
||||
|
||||
if !slices.Contains(visited, pkg) {
|
||||
bpmpkg := GetPackage(pkg, operation.RootDir)
|
||||
if bpmpkg == nil {
|
||||
return errors.New("Error: could not find installed package (" + pkg + ")")
|
||||
return fmt.Errorf("could not find installed package (%s)", pkg)
|
||||
}
|
||||
operation.Actions = append(operation.Actions, &RemovePackageAction{BpmPackage: bpmpkg})
|
||||
}
|
||||
@@ -271,63 +256,102 @@ func (operation *BPMOperation) ReplaceObsoletePackages() {
|
||||
}
|
||||
|
||||
for _, r := range pkgInfo.Replaces {
|
||||
if bpmpkg := GetPackage(r, operation.RootDir); bpmpkg != nil && !operation.ActionsContainPackage(bpmpkg.PkgInfo.Name) {
|
||||
operation.InsertActionAt(0, &RemovePackageAction{
|
||||
if bpmpkg := GetPackage(r, operation.RootDir); bpmpkg != nil && ActionSliceIndex(operation.Actions, bpmpkg.PkgInfo.Name) == -1 {
|
||||
var action OperationAction = &RemovePackageAction{
|
||||
BpmPackage: bpmpkg,
|
||||
})
|
||||
}
|
||||
operation.Actions = slices.Insert(operation.Actions, 0, action)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (operation *BPMOperation) CheckForConflicts() (map[string][]string, error) {
|
||||
func (operation *BPMOperation) CheckForConflicts() map[string][]string {
|
||||
conflicts := make(map[string][]string)
|
||||
installedPackages, err := GetInstalledPackages(operation.RootDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
allPackages := make([]*PackageInfo, len(installedPackages))
|
||||
for i, value := range installedPackages {
|
||||
bpmpkg := GetPackage(value, operation.RootDir)
|
||||
if bpmpkg == nil {
|
||||
return nil, fmt.Errorf("could not find installed package (%s)", value)
|
||||
|
||||
// Get installed packages
|
||||
installedPackages := localPackageInformation[operation.RootDir]
|
||||
|
||||
// Get packages to be removed
|
||||
removedPackages := make([]string, 0)
|
||||
for _, value := range slices.Clone(operation.Actions) {
|
||||
if value.GetActionType() != "remove" {
|
||||
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) {
|
||||
var pkgInfo *PackageInfo
|
||||
if value.GetActionType() == "install" {
|
||||
action := value.(*InstallPackageAction)
|
||||
pkgInfo := action.BpmPackage.PkgInfo
|
||||
allPackages = append(allPackages, pkgInfo)
|
||||
pkgInfo = value.(*InstallPackageAction).BpmPackage.PkgInfo
|
||||
} else if value.GetActionType() == "fetch" {
|
||||
action := value.(*FetchPackageAction)
|
||||
pkgInfo := action.DatabaseEntry.Info
|
||||
allPackages = append(allPackages, pkgInfo)
|
||||
} else if value.GetActionType() == "remove" {
|
||||
action := value.(*RemovePackageAction)
|
||||
pkgInfo := action.BpmPackage.PkgInfo
|
||||
for i := len(allPackages) - 1; i >= 0; i-- {
|
||||
info := allPackages[i]
|
||||
if info.Name == pkgInfo.Name {
|
||||
allPackages = append(allPackages[:i], allPackages[i+1:]...)
|
||||
pkgInfo = value.(*FetchPackageAction).DatabaseEntry.Info
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check for conflicts with installed packages
|
||||
for _, installedPkg := range installedPackages {
|
||||
// Skip if package is to be removed
|
||||
if slices.Contains(removedPackages, installedPkg.Name) {
|
||||
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 {
|
||||
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
|
||||
return conflicts
|
||||
}
|
||||
|
||||
func (operation *BPMOperation) ShowOperationSummary() {
|
||||
@@ -341,15 +365,30 @@ func (operation *BPMOperation) ShowOperationSummary() {
|
||||
|
||||
for _, value := range operation.Actions {
|
||||
var pkgInfo *PackageInfo
|
||||
var flagsStr string
|
||||
var installationReason = InstallationReasonUnknown
|
||||
if value.GetActionType() == "install" {
|
||||
installationReason = value.(*InstallPackageAction).InstallationReason
|
||||
pkgInfo = value.(*InstallPackageAction).BpmPackage.PkgInfo
|
||||
if len(value.(*InstallPackageAction).Flags) > 0 {
|
||||
flagsSlice := make([]string, 0)
|
||||
for flag, value := range value.(*InstallPackageAction).Flags {
|
||||
flagsSlice = append(flagsSlice, flag+"="+value)
|
||||
}
|
||||
flagsStr = "[" + strings.Join(flagsSlice, ",") + "]"
|
||||
}
|
||||
if value.(*InstallPackageAction).SplitPackageToInstall != "" {
|
||||
pkgInfo = pkgInfo.GetSplitPackageInfo(value.(*InstallPackageAction).SplitPackageToInstall)
|
||||
}
|
||||
} else if value.GetActionType() == "fetch" {
|
||||
installationReason = value.(*FetchPackageAction).InstallationReason
|
||||
if len(value.(*FetchPackageAction).Flags) > 0 {
|
||||
flagsSlice := make([]string, 0)
|
||||
for flag, value := range value.(*FetchPackageAction).Flags {
|
||||
flagsSlice = append(flagsSlice, flag+"="+value)
|
||||
}
|
||||
flagsStr = "[" + strings.Join(flagsSlice, ",") + "]"
|
||||
}
|
||||
pkgInfo = value.(*FetchPackageAction).DatabaseEntry.Info
|
||||
} else {
|
||||
pkgInfo = value.(*RemovePackageAction).BpmPackage.PkgInfo
|
||||
@@ -371,15 +410,15 @@ func (operation *BPMOperation) ShowOperationSummary() {
|
||||
|
||||
installedInfo := GetPackageInfo(pkgInfo.Name, operation.RootDir)
|
||||
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+flagsStr, pkgInfo.GetFullVersion(), "Install", installationReasonStr, pkgInfo.Type == "source")
|
||||
} else {
|
||||
comparison := ComparePackageVersions(*pkgInfo, *installedInfo)
|
||||
comparison := CompareVersions(pkgInfo.GetFullVersion(), installedInfo.GetFullVersion())
|
||||
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+flagsStr, installedInfo.GetFullVersion(), pkgInfo.GetFullVersion(), "Downgrade", installationReasonStr, pkgInfo.Type == "source")
|
||||
} else if comparison > 0 {
|
||||
fmt.Fprintf(writer, "%s\t%s -> %s\t%s\t%s\t%t\n", pkgInfo.Name, installedInfo.GetFullVersion(), pkgInfo.GetFullVersion(), "Upgrade", installationReasonStr, pkgInfo.Type == "source")
|
||||
fmt.Fprintf(writer, "%s\t%s -> %s\t%s\t%s\t%t\n", pkgInfo.Name+flagsStr, installedInfo.GetFullVersion(), pkgInfo.GetFullVersion(), "Upgrade", installationReasonStr, pkgInfo.Type == "source")
|
||||
} else {
|
||||
fmt.Fprintf(writer, "%s\t%s\t%s\t%s\t%t\n", pkgInfo.Name, pkgInfo.GetFullVersion(), "Reinstall", installationReasonStr, pkgInfo.Type == "source")
|
||||
fmt.Fprintf(writer, "%s\t%s\t%s\t%s\t%t\n", pkgInfo.Name+flagsStr, pkgInfo.GetFullVersion(), "Reinstall", installationReasonStr, pkgInfo.Type == "source")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -391,12 +430,12 @@ func (operation *BPMOperation) ShowOperationSummary() {
|
||||
fmt.Println("Warning: Operating in " + operation.RootDir)
|
||||
}
|
||||
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 {
|
||||
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 {
|
||||
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,7 +467,83 @@ func (operation *BPMOperation) ShowSourcePackageContent() (sourcePackagesShown i
|
||||
return sourcePackagesShown, nil
|
||||
}
|
||||
|
||||
func (operation *BPMOperation) RunHooks(verbose bool) error {
|
||||
func (operation *BPMOperation) GetOptionalDependencies() (optionalDepends map[string][]string) {
|
||||
optionalDepends = make(map[string][]string)
|
||||
|
||||
// Find all optional dependencies
|
||||
for _, value := range 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 {
|
||||
// Deconstruct package string
|
||||
d, err := DeconstructPackageString(depend)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip if dependency is already installed
|
||||
if IsPackageInstalled(d.PkgName, operation.RootDir) || len(GetVirtualPackageInfo(d.PkgName, operation.RootDir)) > 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip if dependency is going to be installed
|
||||
if slices.IndexFunc(operation.Actions, func(action OperationAction) bool {
|
||||
var pkgInfo *PackageInfo
|
||||
if action.GetActionType() == "install" {
|
||||
action := action.(*InstallPackageAction)
|
||||
pkgInfo = action.BpmPackage.PkgInfo
|
||||
} else if action.GetActionType() == "fetch" {
|
||||
action := action.(*FetchPackageAction)
|
||||
pkgInfo = action.DatabaseEntry.Info
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
||||
if pkgInfo.Name == d.PkgName {
|
||||
return true
|
||||
} else if slices.Contains(pkgInfo.Provides, d.PkgName) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}) != -1 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip if not a new dependency of the package
|
||||
if installedPkg := GetPackage(pkgInfo.Name, operation.RootDir); installedPkg != nil && slices.ContainsFunc(installedPkg.PkgInfo.OptionalDepends, func(n string) bool {
|
||||
// Deconstruct package string
|
||||
d2, err := DeconstructPackageString(depend)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return d2.PkgName == d.PkgName
|
||||
}) {
|
||||
continue
|
||||
}
|
||||
|
||||
if d.Description != "" {
|
||||
optionalDepends[pkgInfo.Name] = append(optionalDepends[pkgInfo.Name], fmt.Sprintf("%s (%s)", d.PkgName, d.Description))
|
||||
} else {
|
||||
optionalDepends[pkgInfo.Name] = append(optionalDepends[pkgInfo.Name], d.PkgName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (operation *BPMOperation) RunPreHooks(verbose bool) error {
|
||||
// Return if hooks directory does not exist
|
||||
if stat, err := os.Stat(path.Join(operation.RootDir, "var/lib/bpm/hooks")); err != nil || !stat.IsDir() {
|
||||
return nil
|
||||
@@ -448,7 +563,46 @@ func (operation *BPMOperation) RunHooks(verbose bool) error {
|
||||
log.Printf("Error while reading hook (%s): %s", entry.Name(), err)
|
||||
}
|
||||
|
||||
err = hook.Execute(operation.Changes, verbose, operation.RootDir)
|
||||
if !hook.TriggerPreOperation {
|
||||
continue
|
||||
}
|
||||
|
||||
err = hook.Execute(operation.ModifiedFiles, false, verbose, operation.RootDir)
|
||||
if err != nil {
|
||||
log.Printf("Warning: could not execute hook (%s): %s\n", entry.Name(), err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (operation *BPMOperation) RunPostHooks(verbose bool) error {
|
||||
// Return if hooks directory does not exist
|
||||
if stat, err := os.Stat(path.Join(operation.RootDir, "var/lib/bpm/hooks")); err != nil || !stat.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get directory entries in hooks directory
|
||||
dirEntries, err := os.ReadDir(path.Join(operation.RootDir, "var/lib/bpm/hooks"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Find all hooks, validate and execute them
|
||||
for _, entry := range dirEntries {
|
||||
if entry.Type().IsRegular() && strings.HasSuffix(entry.Name(), ".bpmhook") {
|
||||
hook, err := createHook(path.Join(operation.RootDir, "var/lib/bpm/hooks", entry.Name()))
|
||||
if err != nil {
|
||||
log.Printf("Error while reading hook (%s): %s", entry.Name(), err)
|
||||
}
|
||||
|
||||
if hook.TriggerPreOperation {
|
||||
continue
|
||||
}
|
||||
|
||||
err = hook.Execute(operation.ModifiedFiles, false, verbose, operation.RootDir)
|
||||
if err != nil {
|
||||
log.Printf("Warning: could not execute hook (%s): %s\n", entry.Name(), err)
|
||||
continue
|
||||
@@ -481,7 +635,7 @@ func (operation *BPMOperation) FetchPackages() (err error) {
|
||||
var bpmpkg *BPMPackage
|
||||
|
||||
// Check if package has already been fetched from download link
|
||||
if _, ok := fetchedPackages[entry.Filepath]; !ok {
|
||||
if fetchedFilepath, ok := fetchedPackages[entry.Filepath]; !ok {
|
||||
// Fetch package from database
|
||||
fetchedPackage, err := entry.Database.FetchPackage(entry.Info.Name)
|
||||
if err != nil {
|
||||
@@ -496,22 +650,29 @@ func (operation *BPMOperation) FetchPackages() (err error) {
|
||||
|
||||
// Add fetched package to map
|
||||
fetchedPackages[entry.Filepath] = fetchedPackage
|
||||
|
||||
fmt.Printf("Package (%s) was successfully fetched!\n", entry.Info.Name)
|
||||
} else {
|
||||
// Read fetched package
|
||||
bpmpkg, err = ReadPackage(fetchedPackages[entry.Filepath])
|
||||
bpmpkg, err = ReadPackage(fetchedFilepath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read package (%s): %s\n", entry.Info.Name, err)
|
||||
}
|
||||
|
||||
fmt.Printf("Package (%s) was successfully fetched!\n", entry.Info.Name)
|
||||
// Get size of fetched archive
|
||||
stat, err := os.Stat(fetchedFilepath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bar := createProgressBar(stat.Size(), "Downloading "+entry.Info.Name, false)
|
||||
bar.Set64(stat.Size())
|
||||
bar.Close()
|
||||
}
|
||||
|
||||
if bpmpkg.PkgInfo.IsSplitPackage() {
|
||||
operation.Actions[i] = &InstallPackageAction{
|
||||
File: fetchedPackages[entry.Filepath],
|
||||
InstallationReason: action.(*FetchPackageAction).InstallationReason,
|
||||
Flags: action.(*FetchPackageAction).Flags,
|
||||
BpmPackage: bpmpkg,
|
||||
SplitPackageToInstall: entry.Info.Name,
|
||||
}
|
||||
@@ -519,6 +680,7 @@ func (operation *BPMOperation) FetchPackages() (err error) {
|
||||
operation.Actions[i] = &InstallPackageAction{
|
||||
File: fetchedPackages[entry.Filepath],
|
||||
InstallationReason: action.(*FetchPackageAction).InstallationReason,
|
||||
Flags: action.(*FetchPackageAction).Flags,
|
||||
BpmPackage: bpmpkg,
|
||||
}
|
||||
}
|
||||
@@ -526,9 +688,40 @@ func (operation *BPMOperation) FetchPackages() (err error) {
|
||||
}
|
||||
|
||||
operation.hasFetchedPackages = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (operation *BPMOperation) GetModifiedFiles() {
|
||||
// Get modified files
|
||||
for _, action := range operation.Actions {
|
||||
if action.GetActionType() == "install" {
|
||||
installAction := action.(*InstallPackageAction)
|
||||
isUpgrade := IsPackageInstalled(installAction.BpmPackage.PkgInfo.Name, operation.RootDir)
|
||||
|
||||
if isUpgrade {
|
||||
for _, pkgFile := range installAction.BpmPackage.PkgFiles {
|
||||
operation.ModifiedFiles[pkgFile.Path] = "upgrade"
|
||||
}
|
||||
for _, pkgFile := range GetPackage(installAction.BpmPackage.PkgInfo.Name, operation.RootDir).PkgFiles {
|
||||
operation.ModifiedFiles[pkgFile.Path] = "upgrade"
|
||||
}
|
||||
} else {
|
||||
for _, pkgFile := range installAction.BpmPackage.PkgFiles {
|
||||
operation.ModifiedFiles[pkgFile.Path] = "install"
|
||||
}
|
||||
}
|
||||
}
|
||||
if action.GetActionType() == "remove" {
|
||||
removeAction := action.(*RemovePackageAction)
|
||||
|
||||
for _, pkgFile := range removeAction.BpmPackage.PkgFiles {
|
||||
operation.ModifiedFiles[pkgFile.Path] = "remove"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (operation *BPMOperation) Execute(verbose, force bool) (err error) {
|
||||
// Fetch packages
|
||||
if !operation.hasFetchedPackages {
|
||||
@@ -568,6 +761,7 @@ func (operation *BPMOperation) Execute(verbose, force bool) (err error) {
|
||||
} else if action.GetActionType() == "install" {
|
||||
value := action.(*InstallPackageAction)
|
||||
fileToInstall := value.File
|
||||
compilationFlags := value.Flags
|
||||
bpmpkg := value.BpmPackage
|
||||
var err error
|
||||
|
||||
@@ -592,7 +786,7 @@ func (operation *BPMOperation) Execute(verbose, force bool) (err error) {
|
||||
|
||||
// Compile source package if not compiled already
|
||||
if _, ok := operation.compiledPackages[pkgNameToInstall]; !ok {
|
||||
outputBpmPackages, err := CompileSourcePackage(value.File, compiledDir, false)
|
||||
outputBpmPackages, err := CompileSourcePackage(value.File, compiledDir, compilationFlags, operation.CompilationJobs, !operation.RunChecks, false, verbose)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not compile source package (%s): %s\n", value.File, err)
|
||||
}
|
||||
@@ -612,24 +806,15 @@ func (operation *BPMOperation) Execute(verbose, force bool) (err error) {
|
||||
}
|
||||
|
||||
if value.InstallationReason != InstallationReasonManual {
|
||||
err = installPackage(fileToInstall, operation.RootDir, verbose, true)
|
||||
err = installPackage(fileToInstall, value.InstallationReason, value.Flags, operation.RootDir, verbose, true)
|
||||
} else {
|
||||
err = installPackage(fileToInstall, operation.RootDir, verbose, force)
|
||||
err = installPackage(fileToInstall, value.InstallationReason, value.Flags, operation.RootDir, verbose, force)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not install package (%s): %s\n", bpmpkg.PkgInfo.Name, err)
|
||||
}
|
||||
|
||||
// Set installed package's installation reason
|
||||
err = SetInstallationReason(bpmpkg.PkgInfo.Name, value.InstallationReason, operation.RootDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not set installation reason for package (%s): %s\n", value.BpmPackage.PkgInfo.Name, err)
|
||||
}
|
||||
|
||||
fmt.Printf("Package (%s) was successfully installed\n", bpmpkg.PkgInfo.Name)
|
||||
}
|
||||
}
|
||||
fmt.Println("Operation complete!")
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -641,6 +826,7 @@ type OperationAction interface {
|
||||
type InstallPackageAction struct {
|
||||
File string
|
||||
InstallationReason InstallationReason
|
||||
Flags map[string]string
|
||||
SplitPackageToInstall string
|
||||
BpmPackage *BPMPackage
|
||||
}
|
||||
@@ -651,6 +837,7 @@ func (action *InstallPackageAction) GetActionType() string {
|
||||
|
||||
type FetchPackageAction struct {
|
||||
InstallationReason InstallationReason
|
||||
Flags map[string]string
|
||||
DatabaseEntry *BPMDatabaseEntry
|
||||
}
|
||||
|
||||
@@ -665,3 +852,41 @@ type RemovePackageAction struct {
|
||||
func (action *RemovePackageAction) GetActionType() string {
|
||||
return "remove"
|
||||
}
|
||||
|
||||
func ActionSliceIndex(actions []OperationAction, pkg string) int {
|
||||
for i, action := range actions {
|
||||
if action.GetActionType() == "install" {
|
||||
if action.(*InstallPackageAction).BpmPackage.PkgInfo.Name == pkg {
|
||||
return i
|
||||
}
|
||||
} else if action.GetActionType() == "fetch" {
|
||||
if action.(*FetchPackageAction).DatabaseEntry.Info.Name == pkg {
|
||||
return i
|
||||
}
|
||||
} else if action.GetActionType() == "remove" {
|
||||
if action.(*RemovePackageAction).BpmPackage.PkgInfo.Name == pkg {
|
||||
return i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
func ActionSliceRemove(actions []OperationAction, pkg, actionType string) []OperationAction {
|
||||
actions = slices.DeleteFunc(actions, func(a OperationAction) bool {
|
||||
if a.GetActionType() != actionType {
|
||||
return false
|
||||
}
|
||||
if a.GetActionType() == "install" {
|
||||
return a.(*InstallPackageAction).BpmPackage.PkgInfo.Name == pkg
|
||||
} else if a.GetActionType() == "fetch" {
|
||||
return a.(*FetchPackageAction).DatabaseEntry.Info.Name == pkg
|
||||
} else if a.GetActionType() == "remove" {
|
||||
return a.(*RemovePackageAction).BpmPackage.PkgInfo.Name == pkg
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
return actions
|
||||
}
|
||||
|
||||
+420
-129
@@ -10,20 +10,22 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
version "github.com/knqyf263/go-rpm-version"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type BPMPackage struct {
|
||||
PkgInfo *PackageInfo
|
||||
PkgFiles []*PackageFileEntry
|
||||
PkgInfo *PackageInfo
|
||||
PkgFiles []*PackageFileEntry
|
||||
LocalInfo PackageLocalInfo
|
||||
}
|
||||
|
||||
type PackageInfo struct {
|
||||
@@ -33,24 +35,45 @@ type PackageInfo struct {
|
||||
Revision int `yaml:"revision,omitempty"`
|
||||
Url string `yaml:"url,omitempty"`
|
||||
License string `yaml:"license,omitempty"`
|
||||
Maintainers []string `yaml:"maintainers,omitempty"`
|
||||
Arch string `yaml:"architecture,omitempty"`
|
||||
OutputArch string `yaml:"output_architecture,omitempty"`
|
||||
Type string `yaml:"type,omitempty"`
|
||||
Keep []string `yaml:"keep,omitempty"`
|
||||
Depends []string `yaml:"depends,omitempty"`
|
||||
RuntimeDepends []string `yaml:"runtime_depends,omitempty"`
|
||||
OptionalDepends []string `yaml:"optional_depends,omitempty"`
|
||||
MakeDepends []string `yaml:"make_depends,omitempty"`
|
||||
CheckDepends []string `yaml:"check_depends,omitempty"`
|
||||
Conflicts []string `yaml:"conflicts,omitempty"`
|
||||
Replaces []string `yaml:"replaces,omitempty"`
|
||||
Provides []string `yaml:"provides,omitempty"`
|
||||
Options []string `yaml:"options,omitempty"`
|
||||
Flags []PackageFlag `yaml:"flags,omitempty"`
|
||||
Downloads []PackageDownload `yaml:"downloads,omitempty"`
|
||||
SplitPackages []*PackageInfo `yaml:"split_packages,omitempty"`
|
||||
}
|
||||
|
||||
type PackageFlag struct {
|
||||
Name string `yaml:"name"`
|
||||
DefaultValue string `yaml:"default_value"`
|
||||
BuiltValue string `yaml:"built_value"`
|
||||
AcceptedValues []PackageAcceptedValue `yaml:"accepted_values,omitempty"`
|
||||
}
|
||||
|
||||
type PackageAcceptedValue struct {
|
||||
Value string `yaml:"value"`
|
||||
Depends []string `yaml:"depends,omitempty"`
|
||||
RuntimeDepends []string `yaml:"runtime_depends,omitempty"`
|
||||
OptionalDepends []string `yaml:"optional_depends,omitempty"`
|
||||
MakeDepends []string `yaml:"make_depends,omitempty"`
|
||||
CheckDepends []string `yaml:"check_depends,omitempty"`
|
||||
}
|
||||
|
||||
type PackageDownload struct {
|
||||
Url string `yaml:"url"`
|
||||
Type string `yaml:"type,omitempty"`
|
||||
Filepath string `yaml:"filepath,omitempty,omitempty"`
|
||||
Filepath string `yaml:"filepath,omitempty"`
|
||||
|
||||
// Archive options
|
||||
NoExtract bool `yaml:"no_extract,omitempty"`
|
||||
@@ -69,11 +92,18 @@ type PackageFileEntry struct {
|
||||
OctalPerms uint32
|
||||
UserID int
|
||||
GroupID int
|
||||
SizeInBytes uint64
|
||||
SizeInBytes int64
|
||||
}
|
||||
|
||||
func (pkg *BPMPackage) GetInstalledSize() uint64 {
|
||||
var totalSize uint64 = 0
|
||||
type PackageLocalInfo struct {
|
||||
InstallationReason string `yaml:"installation_reason"`
|
||||
Flags map[string]string `yaml:"flags"`
|
||||
InstalledOn int64 `yaml:"installed_on"`
|
||||
LastUpdatedOn int64 `yaml:"last_updated_on"`
|
||||
}
|
||||
|
||||
func (pkg *BPMPackage) GetInstalledSize() int64 {
|
||||
var totalSize int64 = 0
|
||||
for _, entry := range pkg.PkgFiles {
|
||||
totalSize += entry.SizeInBytes
|
||||
}
|
||||
@@ -120,42 +150,17 @@ const (
|
||||
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 {
|
||||
installedDir := path.Join(rootDir, "var/lib/bpm/installed/")
|
||||
pkgDir := path.Join(installedDir, pkg)
|
||||
if stat, err := os.Stat(path.Join(pkgDir, "installation_reason")); err != nil || stat.IsDir() {
|
||||
func (localInfo PackageLocalInfo) GetInstallationReason() InstallationReason {
|
||||
switch localInfo.InstallationReason {
|
||||
case "manual":
|
||||
return InstallationReasonManual
|
||||
}
|
||||
b, err := os.ReadFile(path.Join(pkgDir, "installation_reason"))
|
||||
if err != nil {
|
||||
case "dependency":
|
||||
return InstallationReasonDependency
|
||||
case "make_dependency":
|
||||
return InstallationReasonMakeDependency
|
||||
default:
|
||||
return InstallationReasonUnknown
|
||||
}
|
||||
reason := strings.TrimSpace(string(b))
|
||||
if reason == "manual" {
|
||||
return InstallationReasonManual
|
||||
} else if reason == "dependency" {
|
||||
return InstallationReasonDependency
|
||||
} else if reason == "make_dependency" {
|
||||
return InstallationReasonMakeDependency
|
||||
}
|
||||
return InstallationReasonUnknown
|
||||
}
|
||||
|
||||
func SetInstallationReason(pkg string, reason InstallationReason, rootDir string) error {
|
||||
installedDir := path.Join(rootDir, "var/lib/bpm/installed/")
|
||||
pkgDir := path.Join(installedDir, pkg)
|
||||
err := os.WriteFile(path.Join(pkgDir, "installation_reason"), []byte(reason), 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetPackageInfoRaw(filename string) (string, error) {
|
||||
@@ -176,7 +181,7 @@ func GetPackageInfoRaw(filename string) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if header.Name == "pkg.info" {
|
||||
if header.Name == "info.yml" {
|
||||
bs, _ := io.ReadAll(tr)
|
||||
err := file.Close()
|
||||
if err != nil {
|
||||
@@ -185,7 +190,7 @@ func GetPackageInfoRaw(filename string) (string, error) {
|
||||
return string(bs), nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("pkg.info not found in archive")
|
||||
return "", errors.New("info.yml not found in archive")
|
||||
}
|
||||
|
||||
func ReadPackage(filename string) (*BPMPackage, error) {
|
||||
@@ -211,13 +216,13 @@ func ReadPackage(filename string) (*BPMPackage, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if header.Name == "pkg.info" {
|
||||
if header.Name == "info.yml" {
|
||||
bs, _ := io.ReadAll(tr)
|
||||
pkgInfo, err = ReadPackageInfo(string(bs))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if header.Name == "pkg.files" {
|
||||
} else if header.Name == "files.txt" {
|
||||
bs, _ := io.ReadAll(tr)
|
||||
for _, line := range strings.Split(string(bs), "\n") {
|
||||
if strings.TrimSpace(line) == "" {
|
||||
@@ -225,7 +230,7 @@ func ReadPackage(filename string) (*BPMPackage, error) {
|
||||
}
|
||||
stringEntry := strings.Split(strings.TrimSpace(line), " ")
|
||||
if len(stringEntry) < 5 {
|
||||
return nil, errors.New("pkg.files is not formatted correctly")
|
||||
return nil, errors.New("files.txt is not formatted correctly")
|
||||
}
|
||||
octalPerms, err := strconv.ParseUint(stringEntry[len(stringEntry)-4], 8, 32)
|
||||
if err != nil {
|
||||
@@ -239,7 +244,7 @@ func ReadPackage(filename string) (*BPMPackage, error) {
|
||||
if err != nil {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
@@ -255,7 +260,7 @@ func ReadPackage(filename string) (*BPMPackage, error) {
|
||||
}
|
||||
|
||||
if pkgInfo == nil {
|
||||
return nil, errors.New("pkg.info not found in archive")
|
||||
return nil, errors.New("info.yml not found in archive")
|
||||
}
|
||||
return &BPMPackage{
|
||||
PkgInfo: pkgInfo,
|
||||
@@ -375,9 +380,44 @@ func executePackageScript(pkg, rootDir string, verbose bool, packageScript strin
|
||||
cmd := exec.Command("/bin/bash", "-c", content)
|
||||
// Setup subprocess environment
|
||||
cmd.Dir = "/"
|
||||
// Run hook in chroot if using the -R flag
|
||||
// Run package script in chroot if using the -R flag
|
||||
if 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
|
||||
if verbose {
|
||||
@@ -429,11 +469,13 @@ func ReadPackageInfo(contents string) (*PackageInfo, error) {
|
||||
OutputArch: GetArch(),
|
||||
Keep: make([]string, 0),
|
||||
Depends: make([]string, 0),
|
||||
RuntimeDepends: make([]string, 0),
|
||||
MakeDepends: make([]string, 0),
|
||||
OptionalDepends: make([]string, 0),
|
||||
Conflicts: make([]string, 0),
|
||||
Replaces: make([]string, 0),
|
||||
Provides: make([]string, 0),
|
||||
Options: make([]string, 0),
|
||||
Downloads: make([]PackageDownload, 0),
|
||||
SplitPackages: make([]*PackageInfo, 0),
|
||||
}
|
||||
@@ -507,48 +549,187 @@ func ReadPackageInfo(contents string) (*PackageInfo, error) {
|
||||
return pkgInfo, nil
|
||||
}
|
||||
|
||||
func CreateReadableInfo(showArchitecture, showType, showPackageRelations, showInstallationReason bool, pkgInfo *PackageInfo, rootDir string) string {
|
||||
ret := make([]string, 0)
|
||||
appendArray := func(label string, array []string) {
|
||||
func (pkgInfo *PackageInfo) CreateReadableInfo(rootDir string) string {
|
||||
builder := strings.Builder{}
|
||||
builderWriteStringNotEmpty := func(label string, value string) {
|
||||
if value != "" {
|
||||
builder.WriteString(label + ": " + value + "\n")
|
||||
}
|
||||
}
|
||||
builderWriteArray := func(label string, array []string, sort bool) {
|
||||
if len(array) == 0 {
|
||||
return
|
||||
}
|
||||
ret = append(ret, fmt.Sprintf("%s: %s", label, strings.Join(array, ", ")))
|
||||
}
|
||||
ret = append(ret, "Name: "+pkgInfo.Name)
|
||||
ret = append(ret, "Description: "+pkgInfo.Description)
|
||||
ret = append(ret, "Version: "+pkgInfo.GetFullVersion())
|
||||
ret = append(ret, "URL: "+pkgInfo.Url)
|
||||
ret = append(ret, "License: "+pkgInfo.License)
|
||||
if showArchitecture {
|
||||
ret = append(ret, "Architecture: "+pkgInfo.Arch)
|
||||
}
|
||||
if showType {
|
||||
ret = append(ret, "Type: "+pkgInfo.Type)
|
||||
}
|
||||
if showPackageRelations {
|
||||
appendArray("Dependencies", pkgInfo.Depends)
|
||||
if pkgInfo.Type == "source" {
|
||||
appendArray("Make Dependencies", pkgInfo.MakeDepends)
|
||||
|
||||
// Sort array
|
||||
if sort {
|
||||
slices.Sort(array)
|
||||
}
|
||||
appendArray("Optional dependencies", pkgInfo.OptionalDepends)
|
||||
dependants, err := GetPackageDependants(pkgInfo.Name, rootDir)
|
||||
if err == nil {
|
||||
appendArray("Dependant packages", dependants)
|
||||
|
||||
builder.WriteString(label + " (" + strconv.Itoa(len(array)) + "):\n")
|
||||
for _, val := range array {
|
||||
builder.WriteString(" - " + val + "\n")
|
||||
}
|
||||
appendArray("Conflicting packages", pkgInfo.Conflicts)
|
||||
appendArray("Provided packages", pkgInfo.Provides)
|
||||
appendArray("Replaces packages", pkgInfo.Replaces)
|
||||
}
|
||||
builderWriteDependencyArray := func(label string, depends []string) {
|
||||
if len(depends) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Sort array
|
||||
slices.Sort(depends)
|
||||
|
||||
builder.WriteString(label + " (" + strconv.Itoa(len(depends)) + "):\n")
|
||||
for _, val := range depends {
|
||||
builder.WriteString(" - " + val)
|
||||
|
||||
// Show virtual package providers
|
||||
if providers := GetVirtualPackageInfo(val, rootDir); len(providers) > 0 {
|
||||
builder.WriteString(" (")
|
||||
for i, vpkg := range providers {
|
||||
if i == len(providers)-1 {
|
||||
builder.WriteString(vpkg.Name)
|
||||
} else {
|
||||
builder.WriteString(vpkg.Name + ", ")
|
||||
}
|
||||
}
|
||||
builder.WriteString(")")
|
||||
}
|
||||
|
||||
builder.WriteString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
// Main information
|
||||
builder.WriteString("Name: " + pkgInfo.Name + "\n")
|
||||
builder.WriteString("Description: " + pkgInfo.Description + "\n")
|
||||
builder.WriteString("Version: " + pkgInfo.GetFullVersion() + "\n")
|
||||
builderWriteStringNotEmpty("URL", pkgInfo.Url)
|
||||
builderWriteStringNotEmpty("License", pkgInfo.License)
|
||||
builderWriteArray("Maintainers", pkgInfo.Maintainers, false)
|
||||
builder.WriteString("Architecture: " + pkgInfo.Arch + "\n")
|
||||
if pkgInfo.Type == "source" && pkgInfo.OutputArch != "" && pkgInfo.OutputArch != GetArch() {
|
||||
builder.WriteString("Output architecture: " + pkgInfo.OutputArch + "\n")
|
||||
}
|
||||
builder.WriteString("Type: " + pkgInfo.Type + "\n")
|
||||
|
||||
// Flags
|
||||
if pkgInfo.Type == "binary" {
|
||||
var flags []string
|
||||
for _, flag := range pkgInfo.Flags {
|
||||
if pkgInfo != GetPackageInfo(pkgInfo.Name, rootDir) {
|
||||
flags = append(flags, fmt.Sprintf("%s=%s", flag.Name, flag.BuiltValue))
|
||||
} else if _, ok := getPackageLocalInfo(pkgInfo.Name, rootDir).Flags[flag.Name]; ok {
|
||||
flags = append(flags, fmt.Sprintf("%s=%s (User defined)", flag.Name, flag.BuiltValue))
|
||||
} else {
|
||||
flags = append(flags, fmt.Sprintf("%s=%s (Default value)", flag.Name, flag.BuiltValue))
|
||||
}
|
||||
}
|
||||
builderWriteArray("Built with flags:", flags, false)
|
||||
} else {
|
||||
if len(pkgInfo.Flags) > 0 {
|
||||
builder.WriteString("Available flags (")
|
||||
builder.WriteString(strconv.Itoa(len(pkgInfo.Flags)))
|
||||
builder.WriteString("):\n")
|
||||
for _, flag := range pkgInfo.Flags {
|
||||
builder.WriteString(" - Flag: ")
|
||||
builder.WriteString(flag.Name)
|
||||
builder.WriteRune('\n')
|
||||
if flag.DefaultValue != "" {
|
||||
builder.WriteString(" Default value: ")
|
||||
builder.WriteString(flag.DefaultValue)
|
||||
builder.WriteRune('\n')
|
||||
}
|
||||
if len(flag.AcceptedValues) > 0 {
|
||||
builder.WriteString(" Accepted values (")
|
||||
builder.WriteString(strconv.Itoa(len(flag.AcceptedValues)))
|
||||
builder.WriteString("):\n")
|
||||
|
||||
for i, acceptedValue := range flag.AcceptedValues {
|
||||
writeValueDepends := func(text string, depends []string) {
|
||||
if len(depends) > 0 {
|
||||
builder.WriteString(" ")
|
||||
builder.WriteString(text)
|
||||
builder.WriteString(" (")
|
||||
builder.WriteString(strconv.Itoa(len(depends)))
|
||||
builder.WriteString("): ")
|
||||
for _, depend := range depends {
|
||||
if i != 0 {
|
||||
builder.WriteString(", ")
|
||||
}
|
||||
builder.WriteString(depend)
|
||||
}
|
||||
builder.WriteRune('\n')
|
||||
}
|
||||
}
|
||||
|
||||
builder.WriteString(" - Value: ")
|
||||
builder.WriteString(acceptedValue.Value)
|
||||
builder.WriteRune('\n')
|
||||
writeValueDepends("Dependencies", acceptedValue.Depends)
|
||||
writeValueDepends("Make Dependencies", acceptedValue.MakeDepends)
|
||||
writeValueDepends("Check Dependencies", acceptedValue.CheckDepends)
|
||||
writeValueDepends("Runtime Dependencies", acceptedValue.RuntimeDepends)
|
||||
writeValueDepends("Optional Dependencies", acceptedValue.OptionalDepends)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dependencies
|
||||
builderWriteDependencyArray("Dependencies", pkgInfo.Depends)
|
||||
if pkgInfo.Type == "source" {
|
||||
builderWriteDependencyArray("Make dependencies", pkgInfo.MakeDepends)
|
||||
builderWriteDependencyArray("Check dependencies", pkgInfo.CheckDepends)
|
||||
}
|
||||
builderWriteDependencyArray("Runtime dependencies", pkgInfo.RuntimeDepends)
|
||||
if len(pkgInfo.OptionalDepends) > 0 {
|
||||
builder.WriteString("Optional dependencies (" + strconv.Itoa(len(pkgInfo.OptionalDepends)) + "):\n")
|
||||
for _, depend := range pkgInfo.OptionalDepends {
|
||||
dependSplit := strings.SplitN(depend, ":", 2)
|
||||
if len(dependSplit) == 2 {
|
||||
builder.WriteString(fmt.Sprintf(" - %s (%s)", dependSplit[0], dependSplit[1]))
|
||||
} else {
|
||||
builder.WriteString(" - " + dependSplit[0])
|
||||
}
|
||||
|
||||
// Show virtual package providers
|
||||
if providers := GetVirtualPackageInfo(dependSplit[0], rootDir); len(providers) > 0 {
|
||||
builder.WriteString(" (")
|
||||
for i, vpkg := range providers {
|
||||
if i == len(providers)-1 {
|
||||
builder.WriteString(vpkg.Name)
|
||||
} else {
|
||||
builder.WriteString(vpkg.Name + ", ")
|
||||
}
|
||||
}
|
||||
builder.WriteString(")")
|
||||
}
|
||||
|
||||
builder.WriteString("\n")
|
||||
}
|
||||
}
|
||||
builderWriteArray("Dependant packages", pkgInfo.GetPackageDependants(rootDir, false), true)
|
||||
builderWriteArray("Optionally dependant packages", pkgInfo.GetPackageOptionalDependants(rootDir), true)
|
||||
|
||||
// Other package relations
|
||||
builderWriteArray("Conflicting packages", pkgInfo.Conflicts, true)
|
||||
builderWriteArray("Provided packages", pkgInfo.Provides, true)
|
||||
builderWriteArray("Replaces packages", pkgInfo.Replaces, true)
|
||||
|
||||
// Split packages
|
||||
if pkgInfo.Type == "source" && len(pkgInfo.SplitPackages) != 0 {
|
||||
splitPkgs := make([]string, len(pkgInfo.SplitPackages))
|
||||
for i, splitPkgInfo := range pkgInfo.SplitPackages {
|
||||
splitPkgs[i] = splitPkgInfo.Name
|
||||
}
|
||||
appendArray("Split Packages", splitPkgs)
|
||||
builderWriteArray("Split packages", splitPkgs, true)
|
||||
}
|
||||
if IsPackageInstalled(pkgInfo.Name, rootDir) && showInstallationReason {
|
||||
installationReason := GetInstallationReason(pkgInfo.Name, rootDir)
|
||||
|
||||
// Installation reason
|
||||
if rootDir != "" && IsPackageInstalled(pkgInfo.Name, rootDir) {
|
||||
installationReason := GetPackage(pkgInfo.Name, rootDir).LocalInfo.GetInstallationReason()
|
||||
var installationReasonString string
|
||||
switch installationReason {
|
||||
case InstallationReasonManual:
|
||||
@@ -560,29 +741,23 @@ func CreateReadableInfo(showArchitecture, showType, showPackageRelations, showIn
|
||||
default:
|
||||
installationReasonString = "Unknown"
|
||||
}
|
||||
ret = append(ret, "Installation Reason: "+installationReasonString)
|
||||
builder.WriteString("Installation reason: " + installationReasonString + "\n")
|
||||
}
|
||||
return strings.Join(ret, "\n")
|
||||
|
||||
return strings.TrimSpace(builder.String())
|
||||
}
|
||||
|
||||
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)
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Initialize progress bar
|
||||
bar := createProgressBar(bpmpkg.GetInstalledSize(), "Installing "+bpmpkg.PkgInfo.Name, verbose)
|
||||
defer bar.Close()
|
||||
|
||||
tarballFile, err := readTarballFile(filename, "files.tar.gz")
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -594,6 +769,7 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
|
||||
return err
|
||||
}
|
||||
packageFilesReader := tar.NewReader(archive)
|
||||
|
||||
for {
|
||||
header, err := packageFilesReader.Next()
|
||||
if err == io.EOF {
|
||||
@@ -605,6 +781,17 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
|
||||
extractFilename := path.Join(rootDir, header.Name)
|
||||
switch header.Typeflag {
|
||||
case tar.TypeDir:
|
||||
// Check if path is set to be ignored
|
||||
if ok := slices.ContainsFunc(MainBPMConfig.IgnorePaths, func(s string) bool {
|
||||
matched, _ := filepath.Match(s, header.Name)
|
||||
return matched
|
||||
}); rootDir == "/" && ok {
|
||||
if verbose {
|
||||
fmt.Printf("Skipping Directory: %s (Path was ignored)\n", extractFilename)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if _, err := os.Stat(extractFilename); err == nil {
|
||||
if verbose {
|
||||
fmt.Printf("Skipping Directory: %s (Directory already exists)\n", extractFilename)
|
||||
@@ -630,7 +817,19 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
|
||||
if verbose {
|
||||
fmt.Printf("Created directory %s (%o)\n", extractFilename, header.Mode)
|
||||
}
|
||||
bar.Add64(header.Size)
|
||||
case tar.TypeReg:
|
||||
// Check if path is set to be ignored
|
||||
if ok := slices.ContainsFunc(MainBPMConfig.IgnorePaths, func(s string) bool {
|
||||
matched, _ := filepath.Match(s, header.Name)
|
||||
return matched
|
||||
}); rootDir == "/" && ok {
|
||||
if verbose {
|
||||
fmt.Printf("Skipping File: %s (Path was ignored)\n", extractFilename)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
skip := false
|
||||
if _, err := os.Stat(extractFilename); err == nil {
|
||||
for _, k := range bpmpkg.PkgInfo.Keep {
|
||||
@@ -686,7 +885,19 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
|
||||
if verbose {
|
||||
fmt.Printf("Created File: %s (%o)\n", extractFilename, header.Mode)
|
||||
}
|
||||
bar.Add64(header.Size)
|
||||
case tar.TypeSymlink:
|
||||
// Check if path is set to be ignored
|
||||
if ok := slices.ContainsFunc(MainBPMConfig.IgnorePaths, func(s string) bool {
|
||||
matched, _ := filepath.Match(s, header.Name)
|
||||
return matched
|
||||
}); rootDir == "/" && ok {
|
||||
if verbose {
|
||||
fmt.Printf("Skipping Symlink: %s (Path was ignored)\n", extractFilename)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
err := os.Remove(extractFilename)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
@@ -700,7 +911,19 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
|
||||
if verbose {
|
||||
fmt.Println("Created Symlink: " + extractFilename + " -> " + header.Linkname)
|
||||
}
|
||||
bar.Add64(header.Size)
|
||||
case tar.TypeLink:
|
||||
// Check if path is set to be ignored
|
||||
if ok := slices.ContainsFunc(MainBPMConfig.IgnorePaths, func(s string) bool {
|
||||
matched, _ := filepath.Match(s, header.Name)
|
||||
return matched
|
||||
}); rootDir == "/" && ok {
|
||||
if verbose {
|
||||
fmt.Printf("Skipping Hard Link: %s (Path was ignored)\n", extractFilename)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if verbose {
|
||||
fmt.Println("Detected Hard Link: " + extractFilename + " -> " + path.Join(rootDir, strings.TrimPrefix(header.Linkname, "files/")))
|
||||
}
|
||||
@@ -709,6 +932,7 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
bar.Add64(header.Size)
|
||||
default:
|
||||
return errors.New("unknown type (" + strconv.Itoa(int(header.Typeflag)) + ") in " + extractFilename)
|
||||
}
|
||||
@@ -728,7 +952,7 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
|
||||
return nil
|
||||
}
|
||||
|
||||
func installPackage(filename, rootDir string, verbose, force bool) error {
|
||||
func installPackage(filename string, installationReason InstallationReason, flags map[string]string, rootDir string, verbose, force bool) error {
|
||||
if _, err := os.Stat(filename); os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
@@ -743,6 +967,20 @@ func installPackage(filename, rootDir string, verbose, force bool) error {
|
||||
}
|
||||
|
||||
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
|
||||
if packageInstalled {
|
||||
// Fetching and reversing package file entry list
|
||||
@@ -761,26 +999,39 @@ func installPackage(filename, rootDir string, verbose, force bool) error {
|
||||
fmt.Printf("Removing old files for package (%s)...\n", bpmpkg.PkgInfo.Name)
|
||||
}
|
||||
for _, entry := range fileEntries {
|
||||
file := path.Join(rootDir, entry.Path)
|
||||
stat, err := os.Lstat(file)
|
||||
finalPath := path.Join(rootDir, entry.Path)
|
||||
|
||||
stat, err := os.Lstat(finalPath)
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(files[entry.Path]) != 0 {
|
||||
|
||||
// Check if path is set to be ignored
|
||||
if ok := slices.ContainsFunc(MainBPMConfig.IgnorePaths, func(s string) bool {
|
||||
matched, _ := filepath.Match(s, entry.Path)
|
||||
return matched
|
||||
}); rootDir == "/" && ok {
|
||||
if verbose {
|
||||
fmt.Println("Skipping path: " + file + " (Path is managed by multiple packages)")
|
||||
fmt.Printf("Skipping path: %s (Path was ignored)\n", finalPath)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if len(files[entry.Path]) != 0 {
|
||||
if verbose {
|
||||
fmt.Println("Skipping path: " + finalPath + " (Path is managed by multiple packages)")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
shouldContinue := false
|
||||
for _, value := range bpmpkg.PkgInfo.Keep {
|
||||
if strings.HasSuffix(value, "/") {
|
||||
if strings.HasPrefix(entry.Path, value) || entry.Path == strings.TrimSuffix(value, "/") {
|
||||
if verbose {
|
||||
fmt.Println("Skipping path: " + file + " (Path is set to be kept during reinstalls/updates)")
|
||||
fmt.Println("Skipping path: " + finalPath + " (Path is set to be kept during reinstalls/updates)")
|
||||
}
|
||||
shouldContinue = true
|
||||
continue
|
||||
@@ -788,7 +1039,7 @@ func installPackage(filename, rootDir string, verbose, force bool) error {
|
||||
} else {
|
||||
if entry.Path == value {
|
||||
if verbose {
|
||||
fmt.Println("Skipping path: " + file + " (Path is set to be kept during reinstalls/updates)")
|
||||
fmt.Println("Skipping path: " + finalPath + " (Path is set to be kept during reinstalls/updates)")
|
||||
}
|
||||
shouldContinue = true
|
||||
continue
|
||||
@@ -800,37 +1051,37 @@ func installPackage(filename, rootDir string, verbose, force bool) error {
|
||||
}
|
||||
if stat.Mode()&os.ModeSymlink != 0 {
|
||||
if verbose {
|
||||
fmt.Println("Removing: " + file)
|
||||
fmt.Println("Removing: " + finalPath)
|
||||
}
|
||||
err := os.Remove(file)
|
||||
err := os.Remove(finalPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if stat.IsDir() {
|
||||
dir, err := os.ReadDir(file)
|
||||
dir, err := os.ReadDir(finalPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(dir) != 0 {
|
||||
if verbose {
|
||||
fmt.Println("Skipping non-empty directory: " + file)
|
||||
fmt.Println("Skipping non-empty directory: " + finalPath)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if verbose {
|
||||
fmt.Println("Removing: " + file)
|
||||
fmt.Println("Removing: " + finalPath)
|
||||
}
|
||||
err = os.Remove(file)
|
||||
err = os.Remove(finalPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if verbose {
|
||||
fmt.Println("Removing: " + file)
|
||||
fmt.Println("Removing: " + finalPath)
|
||||
}
|
||||
err := os.Remove(file)
|
||||
err := os.Remove(finalPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -865,12 +1116,12 @@ func installPackage(filename, rootDir string, verbose, force bool) error {
|
||||
return err
|
||||
}
|
||||
|
||||
f, err := os.Create(path.Join(pkgDir, "files"))
|
||||
f, err := os.Create(path.Join(pkgDir, "files.txt"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tarballFile, err := readTarballFile(filename, "pkg.files")
|
||||
tarballFile, err := readTarballFile(filename, "files.txt")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -881,7 +1132,7 @@ func installPackage(filename, rootDir string, verbose, force bool) error {
|
||||
return err
|
||||
}
|
||||
|
||||
f, err = os.Create(path.Join(pkgDir, "info"))
|
||||
f, err = os.Create(path.Join(pkgDir, "info.yml"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -898,6 +1149,17 @@ func installPackage(filename, rootDir string, verbose, force bool) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write local package information
|
||||
localInfo := getPackageLocalInfo(bpmpkg.PkgInfo.Name, rootDir)
|
||||
if !packageInstalled {
|
||||
localInfo.InstalledOn = time.Now().Unix()
|
||||
}
|
||||
localInfo.LastUpdatedOn = time.Now().Unix()
|
||||
localInfo.InstallationReason = string(installationReason)
|
||||
localInfo.Flags = flags
|
||||
|
||||
SetPackageLocalInfo(bpmpkg.PkgInfo.Name, localInfo, rootDir)
|
||||
|
||||
// Save remove package scripts
|
||||
packageScripts, err := ReadPackageScripts(filename)
|
||||
if err != nil {
|
||||
@@ -924,6 +1186,7 @@ func installPackage(filename, rootDir string, verbose, force bool) error {
|
||||
f.Close()
|
||||
}
|
||||
|
||||
// Run post-* package scripts
|
||||
if !packageInstalled {
|
||||
err = executePackageScript(filename, rootDir, verbose, "post_install.sh")
|
||||
if err != nil {
|
||||
@@ -936,14 +1199,22 @@ func installPackage(filename, rootDir string, verbose, force bool) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Write persistent data version number
|
||||
if _, err := os.Stat(path.Join(rootDir, "var/lib/bpm/.version")); err != nil {
|
||||
err = os.WriteFile(path.Join(rootDir, "var/lib/bpm/.version"), []byte(strconv.Itoa(persistentDataVersion)), 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure local package information has been initialized for rootDir
|
||||
err = initializeLocalPackageInformation(rootDir)
|
||||
err = InitializeLocalPackageInformation(rootDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add or update package information for rootDir
|
||||
localPackageInformation[rootDir][bpmpkg.PkgInfo.Name] = bpmpkg
|
||||
localPackageInformation[rootDir][bpmpkg.PkgInfo.Name] = bpmpkg.PkgInfo
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -961,8 +1232,11 @@ func removePackage(pkg string, verbose bool, rootDir string) error {
|
||||
log.Printf("Warning: %s\n", err)
|
||||
}
|
||||
|
||||
// Get BPM package
|
||||
bpmpkg := GetPackage(pkg, rootDir)
|
||||
|
||||
// Fetching and reversing package file entry list
|
||||
fileEntries := GetPackage(pkg, rootDir).PkgFiles
|
||||
fileEntries := bpmpkg.PkgFiles
|
||||
sort.Slice(fileEntries, func(i, j int) bool {
|
||||
return fileEntries[i].Path < fileEntries[j].Path
|
||||
})
|
||||
@@ -972,33 +1246,50 @@ func removePackage(pkg string, verbose bool, rootDir string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
bar := createProgressBar(bpmpkg.GetInstalledSize(), "Removing "+bpmpkg.PkgInfo.Name, verbose)
|
||||
defer bar.Close()
|
||||
|
||||
// Removing package files
|
||||
for _, entry := range fileEntries {
|
||||
file := path.Join(rootDir, entry.Path)
|
||||
lstat, err := os.Lstat(file)
|
||||
bar.Add64(entry.SizeInBytes)
|
||||
|
||||
finalPath := path.Join(rootDir, entry.Path)
|
||||
|
||||
lstat, err := os.Lstat(finalPath)
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if path is set to be ignored
|
||||
if ok := slices.ContainsFunc(MainBPMConfig.IgnorePaths, func(s string) bool {
|
||||
matched, _ := filepath.Match(s, entry.Path)
|
||||
return matched
|
||||
}); rootDir == "/" && ok {
|
||||
if verbose {
|
||||
fmt.Printf("Skipping path: %s (Path was ignored)\n", finalPath)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if len(files[entry.Path]) != 0 {
|
||||
if verbose {
|
||||
fmt.Println("Skipping path: " + file + "(Path is managed by multiple packages)")
|
||||
fmt.Println("Skipping path: " + finalPath + "(Path is managed by multiple packages)")
|
||||
}
|
||||
continue
|
||||
}
|
||||
if lstat.Mode()&os.ModeSymlink != 0 {
|
||||
if verbose {
|
||||
fmt.Println("Removing: " + file)
|
||||
fmt.Println("Removing: " + finalPath)
|
||||
}
|
||||
err := os.Remove(file)
|
||||
err := os.Remove(finalPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
stat, err := os.Stat(file)
|
||||
stat, err := os.Stat(finalPath)
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
@@ -1006,28 +1297,28 @@ func removePackage(pkg string, verbose bool, rootDir string) error {
|
||||
return err
|
||||
}
|
||||
if stat.IsDir() {
|
||||
dir, err := os.ReadDir(file)
|
||||
dir, err := os.ReadDir(finalPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(dir) != 0 {
|
||||
if verbose {
|
||||
fmt.Println("Skipping non-empty directory: " + file)
|
||||
fmt.Println("Skipping non-empty directory: " + finalPath)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if verbose {
|
||||
fmt.Println("Removing: " + file)
|
||||
fmt.Println("Removing: " + finalPath)
|
||||
}
|
||||
err = os.Remove(file)
|
||||
err = os.Remove(finalPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if verbose {
|
||||
fmt.Println("Removing: " + file)
|
||||
fmt.Println("Removing: " + finalPath)
|
||||
}
|
||||
err := os.Remove(file)
|
||||
err := os.Remove(finalPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1050,7 +1341,7 @@ func removePackage(pkg string, verbose bool, rootDir string) error {
|
||||
}
|
||||
|
||||
// Ensure local package information has been initialized for rootDir
|
||||
err = initializeLocalPackageInformation(rootDir)
|
||||
err = InitializeLocalPackageInformation(rootDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+77
-30
@@ -2,10 +2,15 @@ package bpmlib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"path"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
version "github.com/knqyf263/go-rpm-version"
|
||||
"github.com/schollz/progressbar/v3"
|
||||
)
|
||||
|
||||
type BPMLock struct {
|
||||
@@ -13,6 +18,28 @@ type BPMLock struct {
|
||||
path string
|
||||
}
|
||||
|
||||
func LockBPM(rootDir string) (*BPMLock, error) {
|
||||
// Create cache directory if it doesn't already exist
|
||||
err := os.MkdirAll(path.Join(rootDir, "/var/cache/bpm"), 0755)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create file
|
||||
f, err := os.Create(path.Join(rootDir, "var/cache/bpm/bpm.lock"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get exclusive file lock on file
|
||||
err = syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &BPMLock{f, path.Join(rootDir, "var/cache/bpm/bpm.lock")}, nil
|
||||
}
|
||||
|
||||
func (lock *BPMLock) Unlock() error {
|
||||
err := lock.file.Close()
|
||||
if err != nil {
|
||||
@@ -27,28 +54,6 @@ func (lock *BPMLock) Unlock() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func LockBPM(rootDir string) (*BPMLock, error) {
|
||||
// Create parent directories if they don't already exist
|
||||
err := os.MkdirAll(path.Join(rootDir, "/var/lib/bpm"), 0755)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create file
|
||||
f, err := os.Create(path.Join(rootDir, "var/lib/bpm/bpm.lock"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get exclusive file lock on file
|
||||
err = syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &BPMLock{f, path.Join(rootDir, "var/lib/bpm/bpm.lock")}, nil
|
||||
}
|
||||
|
||||
func GetArch() string {
|
||||
uname := syscall.Utsname{}
|
||||
err := syscall.Uname(&uname)
|
||||
@@ -64,6 +69,47 @@ func GetArch() string {
|
||||
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 {
|
||||
for i, v := range s {
|
||||
if v == r {
|
||||
@@ -73,7 +119,7 @@ func stringSliceRemove(s []string, r string) []string {
|
||||
return s
|
||||
}
|
||||
|
||||
func unsignedBytesToHumanReadable(b uint64) string {
|
||||
func BytesToHumanReadable(b int64) string {
|
||||
bf := float64(b)
|
||||
for _, unit := range []string{"", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"} {
|
||||
if math.Abs(bf) < 1024.0 {
|
||||
@@ -84,13 +130,14 @@ func unsignedBytesToHumanReadable(b uint64) string {
|
||||
return fmt.Sprintf("%.1fYiB", bf)
|
||||
}
|
||||
|
||||
func bytesToHumanReadable(b int64) string {
|
||||
bf := float64(b)
|
||||
for _, unit := range []string{"", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"} {
|
||||
if math.Abs(bf) < 1024.0 {
|
||||
return fmt.Sprintf("%3.1f%sB", bf, unit)
|
||||
func removeDuplicates[T comparable](sliceList []T) []T {
|
||||
allKeys := make(map[T]bool)
|
||||
list := []T{}
|
||||
for _, item := range sliceList {
|
||||
if _, value := allKeys[item]; !value {
|
||||
allKeys[item] = true
|
||||
list = append(list, item)
|
||||
}
|
||||
bf /= 1024.0
|
||||
}
|
||||
return fmt.Sprintf("%.1fYiB", bf)
|
||||
return list
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user