26 Commits
Author SHA1 Message Date
EnumDev 45c536b326 Allow additional GOFLAGS in makefile 2025-10-09 16:42:03 +03:00
EnumDev 705aec8e6e Resolve virtual packages in remove subcommand 2025-10-09 14:14:14 +03:00
EnumDev 34fb7ce1d6 Ensure virtual packages are sorted alphabetically 2025-10-09 14:08:22 +03:00
EnumDev fee0e6a96f Make cleanup flag in the remove subcommand use the config option for
make dependencies
2025-10-09 12:37:51 +03:00
EnumDev c5f48becf8 Change remove subcommand behaviour to show error when breaking installed package dependencies 2025-10-09 12:35:58 +03:00
EnumDev ac1ba2ada3 Improve confirmation prompts 2025-10-08 20:46:44 +03:00
EnumDev 5fb8211933 Validate package names using regex 2025-10-07 20:50:08 +03:00
EnumDev 4110ad9c5c Create different output directory for each split package during
compilation
2025-10-07 19:00:17 +03:00
EnumDev dcff59d560 Add 'all' flag to cleanup subcommand 2025-10-07 18:24:14 +03:00
EnumDev 1594eec5ce Remove subcommand will now run without specifying any packages 2025-10-07 18:20:48 +03:00
EnumDev 0c30d85589 The 'cleanup' flag for the remove subcommand will now use 'n' for its
shorthand form similar to the 'cleanup' subcommand
2025-10-07 18:19:30 +03:00
EnumDev 768c75c9bd Cleanup subcommand will now remove optional dependencies. Optional dependencies will now be installed as manual when passing the 'optional' flag to the install subcommand 2025-10-07 18:17:42 +03:00
EnumDev abc6674b97 Run 'cleanup' subcommand when 'compile' subcommand encounters an error 2025-10-06 20:10:20 +03:00
EnumDev 1d858076fd Add 'yes' flag to cleanup subcommand 2025-10-06 20:09:36 +03:00
EnumDev 5909370407 Improve operation summary 2025-10-06 16:38:03 +03:00
EnumDev 5a78fd3002 Show source package contents during package installation 2025-10-06 13:33:18 +03:00
EnumDev a7c03c272e Add back file descriptor flag for 'compile' subcommand 2025-10-05 19:59:27 +03:00
EnumDev 8ba209cf35 Improve 'search' subcommand 2025-10-05 19:46:57 +03:00
EnumDev 6f27f39065 Rename 'file' subcommand to 'owner' and improve it 2025-10-05 18:00:44 +03:00
EnumDev 91479e58f1 Rename 'info' subcommand to 'query' and add shorthand for all
subcommands
2025-10-05 16:56:04 +03:00
EnumDev b6d055a38d Redo bpm flag and subcommand system 2025-10-05 16:38:59 +03:00
EnumDev 0c754a7e0f Change 'extract_to_bpm_source' field to 'extract_to' and 'clone_to' 2025-10-05 12:14:05 +03:00
EnumDev eeb9a01e31 Add file locks to prevent multiple processes at the same time 2025-10-04 17:24:20 +03:00
EnumDev 53e08ce7f9 Ensure package downloads stay in the temporary directory 2025-10-04 09:43:44 +03:00
EnumDev 6f124e0586 Improve package info parsing 2025-09-29 10:34:47 +03:00
EnumDev 93921d4093 Use drone/envsubst module for bash variable expansion 2025-09-28 12:38:47 +03:00
16 changed files with 1467 additions and 916 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ ROOT_COMPILATION_GID ?= 65534
build:
mkdir -p build
cd src/bpm; $(GO) build -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 '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
install: build/bpm config/
# Create directory
+1
View File
@@ -1,4 +1,5 @@
ignore_packages: []
show_source_package_contents: always
cleanup_make_dependencies: true
databases:
- name: example-database
+6 -1
View File
@@ -4,11 +4,16 @@ go 1.23
toolchain go1.23.7
require git.enumerated.dev/bubble-package-manager/bpm/src/bpmlib v0.5.0
require (
git.enumerated.dev/bubble-package-manager/bpm/src/bpmlib v0.5.0
github.com/adrg/strutil v0.3.1
github.com/spf13/pflag v1.0.10
)
replace git.enumerated.dev/bubble-package-manager/bpm/src/bpmlib => ../bpmlib
require (
github.com/drone/envsubst v1.0.3 // indirect
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+21
View File
@@ -1,6 +1,27 @@
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/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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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=
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=
+1067 -799
View File
File diff suppressed because it is too large Load Diff
+153 -39
View File
@@ -14,6 +14,7 @@ import (
"strings"
"syscall"
"github.com/drone/envsubst"
"gopkg.in/yaml.v3"
)
@@ -131,7 +132,6 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
env = append(env, "HOME="+tempDirectory)
env = append(env, "BPM_WORKDIR="+tempDirectory)
env = append(env, "BPM_SOURCE="+path.Join(tempDirectory, "source"))
env = append(env, "BPM_OUTPUT="+path.Join(tempDirectory, "output"))
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))
@@ -187,6 +187,21 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
packagesToCompile = append(packagesToCompile, bpmpkg.PkgInfo)
}
// Create output directories for each package
for _, pkg := range packagesToCompile {
// Create new output directory
err = os.Mkdir(path.Join(tempDirectory, "output_"+pkg.Name), 0755)
if err != nil {
return nil, err
}
// Change output directory owner
err = os.Chown(path.Join(tempDirectory, "output_"+pkg.Name), uid, gid)
if err != nil {
return nil, err
}
}
// Compile each package
for _, pkg := range packagesToCompile {
// Get package function name
@@ -195,26 +210,6 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
packageFunctionName = "package_" + pkg.Name
}
// Remove output directory if it already exists
if _, err := os.Stat(path.Join(tempDirectory, "output")); err == nil {
err := os.RemoveAll(path.Join(tempDirectory, "output"))
if err != nil {
return nil, err
}
}
// Create new output directory
err = os.Mkdir(path.Join(tempDirectory, "output"), 0755)
if err != nil {
return nil, err
}
// Change output directory owner
err = os.Chown(path.Join(tempDirectory, "output"), uid, gid)
if err != nil {
return nil, err
}
// Execute package function in source.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
@@ -223,10 +218,11 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
"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
cmd.Dir = tempDirectory
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Env = env
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)}
@@ -237,7 +233,7 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
}
// Create gzip-compressed archive for the package files
cmd = exec.Command("bash", "-c", "find output -printf \"%P\\n\" | fakeroot -i \"$BPM_WORKDIR\"/fakeroot_file tar -czf files.tar.gz --no-recursion -C output -T -")
cmd = exec.Command("bash", "-c", fmt.Sprintf("find %s -printf \"%%P\\n\" | fakeroot -i %s/fakeroot_file tar -czf files.tar.gz --no-recursion -C %s -T -", "output_"+pkg.Name, tempDirectory, "output_"+pkg.Name))
cmd.Dir = tempDirectory
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
@@ -329,6 +325,12 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
return nil, err
}
// Remove output directory
err = os.RemoveAll(path.Join(tempDirectory, "output_"+pkg.Name))
if err != nil {
return nil, err
}
outputBpmPackages[pkgInfo.Name] = outputFilename
}
@@ -337,24 +339,47 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string) error {
for _, download := range pkgInfo.Downloads {
// Replace variables in download url
downloadUrl := download.Url
downloadUrl = os.Expand(downloadUrl, func(s string) string {
// 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_SOURCE":
return path.Join(tempDirectory, "source/")
default:
return ""
}
})
}
downloadUrl, err := envsubst.Eval(strings.TrimSpace(download.Url), replaceVars)
if err != nil {
return err
}
extractTo, err := envsubst.Eval(strings.TrimSpace(download.ExtractTo), replaceVars)
if err != nil {
return err
}
cloneTo, err := envsubst.Eval(strings.TrimSpace(download.CloneTo), replaceVars)
if err != nil {
return err
}
// Make relative paths absolute
if extractTo != "" && extractTo[0] != '/' {
extractTo = path.Join(tempDirectory, extractTo)
}
// Make relative paths absolute
if cloneTo != "" && cloneTo[0] != '/' {
cloneTo = path.Join(tempDirectory, cloneTo)
}
switch download.Type {
case "", "file":
filepath := path.Join(tempDirectory, path.Base(downloadUrl))
if download.Filepath != "" {
filepath = download.Filepath
if download.Filepath != "" && download.Filepath[0] != '/' {
filepath = path.Join(tempDirectory, download.Filepath)
}
err := downloadFile(downloadUrl, filepath, 0644)
@@ -385,8 +410,13 @@ func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string) error {
if !download.NoExtract && (strings.Contains(filepath, ".tar") || strings.HasSuffix(filepath, ".tgz")) {
cmd := exec.Command("tar", "xvf", filepath, "--strip-components="+strconv.Itoa(download.ExtractStripComponents))
if download.ExtractToBPMSource {
cmd.Args = append(cmd.Args, "-C", path.Join(tempDirectory, "source"))
cmd.Dir = tempDirectory
if extractTo != "" {
err := os.MkdirAll(extractTo, 0755)
if err != nil {
return err
}
cmd.Args = append(cmd.Args, "-C", extractTo)
}
cmd.Stdout = os.Stdout
@@ -398,8 +428,12 @@ func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string) error {
}
} else if !download.NoExtract && strings.HasSuffix(filepath, ".zip") {
cmd := exec.Command("unzip", filepath)
if download.ExtractToBPMSource {
cmd.Args = append(cmd.Args, "-d", path.Join(tempDirectory, "source"))
if extractTo != "" {
err := os.MkdirAll(extractTo, 0755)
if err != nil {
return err
}
cmd.Args = append(cmd.Args, "-d", extractTo)
} else {
err := os.Mkdir(path.Join(tempDirectory, strings.TrimSuffix(path.Base(filepath), ".zip")), 0755)
if err != nil {
@@ -420,7 +454,7 @@ func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string) error {
case "git":
// Replace variables in git branch
gitBranch := download.GitBranch
gitBranch = os.Expand(gitBranch, func(s string) string {
gitBranch, err := envsubst.Eval(gitBranch, func(s string) string {
switch s {
case "BPM_PKG_VERSION":
return pkgInfo.Version
@@ -430,27 +464,35 @@ func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string) error {
return ""
}
})
if err != nil {
return err
}
cmd := exec.Command("git", "clone", "--depth=1", downloadUrl)
cmd.Dir = tempDirectory
if gitBranch != "" {
cmd.Args = slices.Insert(cmd.Args, len(cmd.Args)-1, "--branch="+gitBranch)
}
if download.ExtractToBPMSource {
cmd.Args = append(cmd.Args, path.Join(tempDirectory, "source"))
if cloneTo != "" {
err := os.MkdirAll(cloneTo, 0755)
if err != nil {
return err
}
cmd.Args = append(cmd.Args, cloneTo)
}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
err = cmd.Run()
if err != nil {
return err
}
if download.Checksum != "skip" {
cmd := exec.Command("git", "rev-parse", "HEAD")
if download.ExtractToBPMSource {
cmd.Dir = path.Join(tempDirectory, "source")
if cloneTo != "" {
cmd.Dir = cloneTo
} else {
cmd.Dir = path.Join(tempDirectory, strings.TrimSuffix(path.Base(downloadUrl), ".git"))
}
@@ -474,3 +516,75 @@ func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string) error {
return nil
}
func showPackageFiles(archiveFilename string) error {
// Read BPM archive
bpmpkg, err := ReadPackage(archiveFilename)
if err != nil {
return err
}
// Ensure package type is 'source'
if bpmpkg.PkgInfo.Type != "source" {
return fmt.Errorf("cannot compile a non-source package")
}
printTarballContent := func(filename string) error {
// Get tarball reader for file
reader, err := readTarballFile(archiveFilename, filename)
if err != nil {
return err
}
// Read file content
data, err := io.ReadAll(reader.tarReader)
if err != nil {
return err
}
// Print file name and content
fmt.Println("==============================")
fmt.Printf("%s: %s\n", bpmpkg.PkgInfo.Name, filename)
fmt.Println("==============================")
fmt.Printf("%s\n", data)
// Close reader
err = reader.file.Close()
if err != nil {
return err
}
return nil
}
// Print pkg.info content
err = printTarballContent("pkg.info")
if err != nil {
return err
}
// Print source files contents
files, err := listTarballContent(archiveFilename)
if err != nil {
return err
}
for _, file := range files {
if !strings.HasPrefix(file, "source-files/") {
continue
}
// Print source file content
err = printTarballContent(file)
if err != nil {
return err
}
}
// Print source.sh content
err = printTarballContent("source.sh")
if err != nil {
return err
}
return nil
}
+6 -4
View File
@@ -7,9 +7,10 @@ import (
)
type MainBPMConfigStruct struct {
IgnorePackages []string `yaml:"ignore_packages"`
CleanupMakeDependencies bool `yaml:"cleanup_make_dependencies"`
Databases []configDatabase `yaml:"databases"`
IgnorePackages []string `yaml:"ignore_packages"`
ShowSourcePackageContents string `yaml:"show_source_package_contents"`
CleanupMakeDependencies bool `yaml:"cleanup_make_dependencies"`
Databases []configDatabase `yaml:"databases"`
}
type configDatabase struct {
@@ -31,7 +32,8 @@ func ReadConfig() (err error) {
// Set default config options
MainBPMConfig = MainBPMConfigStruct{
CleanupMakeDependencies: true,
ShowSourcePackageContents: "always",
CleanupMakeDependencies: true,
}
// Read main BPM config
+3 -3
View File
@@ -6,6 +6,7 @@ import (
"net/url"
"os"
"path"
"slices"
"strings"
"gopkg.in/yaml.v3"
@@ -217,9 +218,8 @@ func FindReplacement(pkg string) *BPMDatabaseEntry {
func ResolveVirtualPackage(vpkg string) *BPMDatabaseEntry {
for _, db := range BPMDatabases {
if v, ok := db.VirtualPackages[vpkg]; ok {
for _, pkg := range v {
return db.Entries[pkg]
}
slices.Sort(v)
return db.Entries[v[0]]
}
}
+11 -14
View File
@@ -31,7 +31,7 @@ func (pkgInfo *PackageInfo) GetDependencies(includeMakeDepends, includeOptionalD
}) {
allDepends = append(allDepends, pkgInstallationReason{
PkgName: depend,
InstallationReason: InstallationReasonDependency,
InstallationReason: InstallationReasonManual,
})
}
}
@@ -51,23 +51,23 @@ func (pkgInfo *PackageInfo) GetDependencies(includeMakeDepends, includeOptionalD
return allDepends
}
func (pkgInfo *PackageInfo) GetAllDependencies(includeMakeDepends, includeOptionalDepends bool, rootDir string) (resolved []string) {
func (pkgInfo *PackageInfo) GetDependenciesRecursive(includeMakeDepends bool, rootDir string) (resolved []string) {
// Initialize slices
resolved = make([]string, 0)
unresolved := make([]string, 0)
// Call unexported function
pkgInfo.getAllDependencies(&resolved, &unresolved, includeMakeDepends, includeOptionalDepends, rootDir)
pkgInfo.getDependenciesRecursive(&resolved, &unresolved, includeMakeDepends, rootDir)
return resolved
}
func (pkgInfo *PackageInfo) getAllDependencies(resolved *[]string, unresolved *[]string, includeMakeDepends, includeOptionalDepends bool, rootDir string) {
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, includeOptionalDepends) {
for _, pkgIR := range pkgInfo.GetDependencies(includeMakeDepends, false) {
depend := pkgIR.PkgName
if isVirtual, p := IsVirtualPackage(depend, rootDir); isVirtual {
@@ -86,7 +86,7 @@ func (pkgInfo *PackageInfo) getAllDependencies(resolved *[]string, unresolved *[
dependInfo := GetPackageInfo(depend, rootDir)
if dependInfo != nil {
dependInfo.getAllDependencies(resolved, unresolved, includeMakeDepends, includeOptionalDepends, rootDir)
dependInfo.getDependenciesRecursive(resolved, unresolved, includeMakeDepends, rootDir)
}
}
}
@@ -155,7 +155,7 @@ func resolvePackageDependenciesFromDatabase(resolved *[]pkgInstallationReason, u
}
// Resolve the dependencies of this dependency
resolvePackageDependenciesFromDatabase(resolved, unresolved, entry.Info, checkMake, checkOptional, ignoreInstalled, verbose, rootDir)
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 {
@@ -198,12 +198,9 @@ func GetPackageDependants(pkgName string, rootDir string) ([]string, error) {
continue
}
// Get installed package dependencies
dependencies := installedPkg.PkgInfo.GetDependencies(false, true)
// Add installed package to list if its dependencies include pkgName
if slices.ContainsFunc(dependencies, func(p pkgInstallationReason) bool {
return p.PkgName == pkgName
if slices.ContainsFunc(installedPkg.PkgInfo.Depends, func(n string) bool {
return n == pkgName
}) {
ret = append(ret, installedPkgName)
continue
@@ -212,8 +209,8 @@ func GetPackageDependants(pkgName string, rootDir string) ([]string, error) {
// Loop through each virtual package
for _, vpkg := range pkg.PkgInfo.Provides {
// Add installed package to list if its dependencies contain a provided virtual package
if slices.ContainsFunc(dependencies, func(p pkgInstallationReason) bool {
return p.PkgName == vpkg
if slices.ContainsFunc(installedPkg.PkgInfo.Depends, func(n string) bool {
return n == vpkg
}) {
ret = append(ret, installedPkgName)
break
+8
View File
@@ -40,3 +40,11 @@ type PackageScriptErr struct {
func (e PackageScriptErr) Error() string {
return fmt.Sprintf("could not execute package script (%s) for package (%s): %s", e.packageScript, e.packageName, e.err)
}
type PackageRemovalDependencyErr struct {
RequiredPackages map[string][]string
}
func (e PackageRemovalDependencyErr) Error() string {
return "removing these package would break other installed packages"
}
+40 -10
View File
@@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"log"
"maps"
"os"
"path"
"slices"
@@ -186,7 +187,7 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
}
// RemovePackages removes the specified packages from the given root directory
func RemovePackages(rootDir string, removeUnusedPackagesOnly, cleanupDependencies bool, packages ...string) (operation *BPMOperation, err error) {
func RemovePackages(rootDir string, force, cleanupDependencies bool, packages ...string) (operation *BPMOperation, err error) {
operation = &BPMOperation{
Actions: make([]OperationAction, 0),
UnresolvedDepends: make([]string, 0),
@@ -198,27 +199,56 @@ func RemovePackages(rootDir string, removeUnusedPackagesOnly, cleanupDependencie
// Search for packages
for _, pkg := range packages {
bpmpkg := GetPackage(pkg, rootDir)
if isVirutal, vpkg := IsVirtualPackage(pkg, rootDir); isVirutal {
bpmpkg = GetPackage(vpkg, rootDir)
}
if bpmpkg == nil {
continue
}
operation.AppendAction(&RemovePackageAction{BpmPackage: bpmpkg})
}
// Do not remove packages which other packages depend on
if removeUnusedPackagesOnly {
err := operation.RemoveNeededPackages()
if err != nil {
return nil, fmt.Errorf("could not skip needed packages: %s", err)
}
}
// Do package cleanup
if cleanupDependencies {
err := operation.Cleanup(true)
err := operation.Cleanup(MainBPMConfig.CleanupMakeDependencies)
if err != nil {
return nil, fmt.Errorf("could not perform cleanup for operation: %s", err)
}
}
// Return error if other packages depend on removed ones
if !force {
// 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)
}
packageDepndants[action.(*RemovePackageAction).BpmPackage.PkgInfo.Name] = dependants
}
// Remove dependant packages from map if they are to be removed by this operation
for pkg, required := range packageDepndants {
required = slices.DeleteFunc(required, func(pkgName string) bool {
_, ok := packageDepndants[pkgName]
return ok
})
packageDepndants[pkg] = required
}
// Remove empty keys from map
maps.DeleteFunc(packageDepndants, func(pkg string, required []string) bool {
return len(required) == 0
})
// Return error
if len(packageDepndants) != 0 {
return nil, PackageRemovalDependencyErr{RequiredPackages: packageDepndants}
}
}
return operation, nil
}
+1
View File
@@ -3,6 +3,7 @@ module git.enumerated.dev/bubble-package-manager/bpm/src/bpmlib
go 1.23
require (
github.com/drone/envsubst v1.0.3
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f
gopkg.in/yaml.v3 v3.0.1
)
+4
View File
@@ -1,3 +1,7 @@
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=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+64 -18
View File
@@ -8,6 +8,7 @@ import (
"path"
"slices"
"strings"
"text/tabwriter"
)
type BPMOperation struct {
@@ -15,7 +16,9 @@ type BPMOperation struct {
UnresolvedDepends []string
Changes map[string]string
RootDir string
compiledPackages map[string]string
compiledPackages map[string]string
hasFetchedPackages bool
}
func (operation *BPMOperation) ActionsContainPackage(pkg string) bool {
@@ -227,7 +230,7 @@ func (operation *BPMOperation) Cleanup(cleanupMakeDepends bool) error {
}
keepPackages = append(keepPackages, pkg.Name)
resolved := pkg.GetAllDependencies(!cleanupMakeDepends, true, operation.RootDir)
resolved := pkg.GetDependenciesRecursive(!cleanupMakeDepends, operation.RootDir)
for _, value := range resolved {
if !slices.Contains(keepPackages, value) {
keepPackages = append(keepPackages, value)
@@ -333,6 +336,9 @@ func (operation *BPMOperation) ShowOperationSummary() {
return
}
writer := tabwriter.NewWriter(os.Stdout, 6, 4, 6, ' ', 0)
fmt.Fprintln(writer, "Name\tVersion\tAction\tInstallation Reason\tFrom Source")
for _, value := range operation.Actions {
var pkgInfo *PackageInfo
var installationReason = InstallationReasonUnknown
@@ -347,41 +353,40 @@ func (operation *BPMOperation) ShowOperationSummary() {
pkgInfo = value.(*FetchPackageAction).DatabaseEntry.Info
} else {
pkgInfo = value.(*RemovePackageAction).BpmPackage.PkgInfo
fmt.Printf("%s: %s (Remove)\n", pkgInfo.Name, pkgInfo.GetFullVersion())
fmt.Fprintf(writer, "%s\t%s\t%s\t%s\t%s\n", pkgInfo.Name, pkgInfo.GetFullVersion(), "Remove", "-", "-")
continue
}
installedInfo := GetPackageInfo(pkgInfo.Name, operation.RootDir)
additionalInfo := ""
installationReasonStr := ""
switch installationReason {
case InstallationReasonManual:
additionalInfo = "(Manual)"
installationReasonStr = "Manual"
case InstallationReasonDependency:
additionalInfo = "(Dependency)"
installationReasonStr = "Dependency"
case InstallationReasonMakeDependency:
additionalInfo = "(Make dependency)"
installationReasonStr = "Make dependency"
default:
additionalInfo = "(Unknown)"
}
if pkgInfo.Type == "source" {
additionalInfo += " (From Source)"
installationReasonStr = "Unknown"
}
installedInfo := GetPackageInfo(pkgInfo.Name, operation.RootDir)
if installedInfo == nil {
fmt.Printf("%s: %s (Install) %s\n", pkgInfo.Name, pkgInfo.GetFullVersion(), additionalInfo)
fmt.Fprintf(writer, "%s\t%s\t%s\t%s\t%t\n", pkgInfo.Name, pkgInfo.GetFullVersion(), "Install", installationReasonStr, pkgInfo.Type == "source")
} else {
comparison := ComparePackageVersions(*pkgInfo, *installedInfo)
if comparison < 0 {
fmt.Printf("%s: %s -> %s (Downgrade) %s\n", pkgInfo.Name, installedInfo.GetFullVersion(), pkgInfo.GetFullVersion(), additionalInfo)
fmt.Fprintf(writer, "%s\t%s -> %s\t%s\t%s\t%t\n", pkgInfo.Name, installedInfo.GetFullVersion(), pkgInfo.GetFullVersion(), "Downgrade", installationReasonStr, pkgInfo.Type == "source")
} else if comparison > 0 {
fmt.Printf("%s: %s -> %s (Upgrade) %s\n", pkgInfo.Name, installedInfo.GetFullVersion(), pkgInfo.GetFullVersion(), additionalInfo)
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")
} else {
fmt.Printf("%s: %s (Reinstall) %s\n", pkgInfo.Name, pkgInfo.GetFullVersion(), additionalInfo)
fmt.Fprintf(writer, "%s\t%s\t%s\t%s\t%t\n", pkgInfo.Name, pkgInfo.GetFullVersion(), "Reinstall", installationReasonStr, pkgInfo.Type == "source")
}
}
}
writer.Flush()
fmt.Println()
if operation.RootDir != "/" {
fmt.Println("Warning: Operating in " + operation.RootDir)
}
@@ -395,6 +400,34 @@ func (operation *BPMOperation) ShowOperationSummary() {
}
}
func (operation *BPMOperation) ShowSourcePackageContent() (sourcePackagesShown int, err error) {
// Fetch packages
if !operation.hasFetchedPackages {
err = operation.FetchPackages()
if err != nil {
return 0, err
}
}
for _, action := range operation.Actions {
if action.GetActionType() != "install" {
continue
}
if action.(*InstallPackageAction).BpmPackage.PkgInfo.Type != "source" {
continue
}
err = showPackageFiles(action.(*InstallPackageAction).File)
if err != nil {
return 0, err
}
sourcePackagesShown++
}
return sourcePackagesShown, nil
}
func (operation *BPMOperation) RunHooks(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() {
@@ -426,7 +459,7 @@ func (operation *BPMOperation) RunHooks(verbose bool) error {
return nil
}
func (operation *BPMOperation) Execute(verbose, force bool) (err error) {
func (operation *BPMOperation) FetchPackages() (err error) {
// Fetch packages from databases
if slices.ContainsFunc(operation.Actions, func(action OperationAction) bool {
return action.GetActionType() == "fetch"
@@ -492,6 +525,19 @@ func (operation *BPMOperation) Execute(verbose, force bool) (err error) {
}
}
operation.hasFetchedPackages = true
return nil
}
func (operation *BPMOperation) Execute(verbose, force bool) (err error) {
// Fetch packages
if !operation.hasFetchedPackages {
err = operation.FetchPackages()
if err != nil {
return err
}
}
// Determine words to be used for the following message
words := make([]string, 0)
if slices.ContainsFunc(operation.Actions, func(action OperationAction) bool {
+38 -27
View File
@@ -10,6 +10,7 @@ import (
"os"
"os/exec"
"path"
"regexp"
"slices"
"sort"
"strconv"
@@ -27,8 +28,8 @@ type BPMPackage struct {
type PackageInfo struct {
Name string `yaml:"name"`
Description string `yaml:"description"`
Version string `yaml:"version"`
Description string `yaml:"description,omitempty"`
Version string `yaml:"version,omitempty"`
Revision int `yaml:"revision,omitempty"`
Url string `yaml:"url,omitempty"`
License string `yaml:"license,omitempty"`
@@ -37,8 +38,8 @@ type PackageInfo struct {
Type string `yaml:"type,omitempty"`
Keep []string `yaml:"keep,omitempty"`
Depends []string `yaml:"depends,omitempty"`
MakeDepends []string `yaml:"make_depends,omitempty"`
OptionalDepends []string `yaml:"optional_depends,omitempty"`
MakeDepends []string `yaml:"make_depends,omitempty"`
Conflicts []string `yaml:"conflicts,omitempty"`
Replaces []string `yaml:"replaces,omitempty"`
Provides []string `yaml:"provides,omitempty"`
@@ -47,14 +48,20 @@ type PackageInfo struct {
}
type PackageDownload struct {
Url string `yaml:"url"`
Type string `yaml:"type"`
NoExtract bool `yaml:"no_extract"`
ExtractToBPMSource bool `yaml:"extract_to_bpm_source"`
ExtractStripComponents int `yaml:"extract_strip_components"`
GitBranch string `yaml:"git_branch"`
Filepath string `yaml:"filepath,omitempty"`
Checksum string `yaml:"checksum"`
Url string `yaml:"url"`
Type string `yaml:"type,omitempty"`
Filepath string `yaml:"filepath,omitempty,omitempty"`
// Archive options
NoExtract bool `yaml:"no_extract,omitempty"`
ExtractTo string `yaml:"extract_to,omitempty"`
ExtractStripComponents int `yaml:"extract_strip_components,omitempty"`
// Git options
CloneTo string `yaml:"clone_to,omitempty"`
GitBranch string `yaml:"git_branch,omitempty"`
Checksum string `yaml:"checksum,omitempty"`
}
type PackageFileEntry struct {
@@ -418,15 +425,8 @@ func executePackageScript(pkg, rootDir string, verbose bool, packageScript strin
func ReadPackageInfo(contents string) (*PackageInfo, error) {
pkgInfo := &PackageInfo{
Name: "",
Description: "",
Version: "",
Revision: 1,
Url: "",
License: "",
Arch: "",
OutputArch: GetArch(),
Type: "",
Keep: make([]string, 0),
Depends: make([]string, 0),
MakeDepends: make([]string, 0),
@@ -462,35 +462,46 @@ func ReadPackageInfo(contents string) (*PackageInfo, error) {
}
}
// Ensure package name is valid
if match, _ := regexp.MatchString("^[a-zA-Z0-9._-]+$", pkgInfo.Name); !match {
return nil, fmt.Errorf("package name (%s) is invalid", pkgInfo.Name)
}
// Setup split package information
for i, splitPkg := range pkgInfo.SplitPackages {
// Ensure split package contains a name
if splitPkg.Name == "" {
return nil, fmt.Errorf("invalid split package name: %s", splitPkg.Name)
return nil, fmt.Errorf("package name (%s) is invalid", splitPkg.Name)
}
// Turn split package into json data
splitPkgJson, err := yaml.Marshal(splitPkg)
// Ensure split package name is valid
if match, _ := regexp.MatchString("^[a-zA-Z0-9._-]+$", splitPkg.Name); !match {
return nil, fmt.Errorf("package name (%s) is invalid", splitPkg.Name)
}
// Turn split package into yaml data
splitPkgYaml, err := yaml.Marshal(splitPkg)
if err != nil {
return nil, err
}
// Clone all main package fields onto split package
pkgInfoClone := *pkgInfo
pkgInfo.SplitPackages[i] = &pkgInfoClone
*splitPkg = *pkgInfo
// Set split package field of split package to nil
pkgInfo.SplitPackages[i].SplitPackages = nil
splitPkg.SplitPackages = nil
// Unmarshal json data back to struct
err = yaml.Unmarshal(splitPkgJson, &pkgInfo.SplitPackages[i])
err = yaml.Unmarshal(splitPkgYaml, splitPkg)
if err != nil {
return nil, err
}
// Force set split package version, revision
pkgInfo.SplitPackages[i].Version = pkgInfo.Version
pkgInfo.SplitPackages[i].Revision = pkgInfo.Revision
splitPkg.Version = pkgInfo.Version
splitPkg.Revision = pkgInfo.Revision
pkgInfo.SplitPackages[i] = splitPkg
}
return pkgInfo, nil
+43
View File
@@ -3,9 +3,52 @@ package bpmlib
import (
"fmt"
"math"
"os"
"path"
"syscall"
)
type BPMLock struct {
file *os.File
path string
}
func (lock *BPMLock) Unlock() error {
err := lock.file.Close()
if err != nil {
return err
}
err = os.Remove(lock.path)
if err != nil {
return err
}
return nil
}
func LockBPM(rootDir string) (*BPMLock, error) {
// 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)