56 Commits
Author SHA1 Message Date
EnumDev 80382c849b Add package compilation flags 2026-06-27 17:22:30 +03:00
EnumDev 6f524ab6fa Add '--force' flag to compilation subcommand 2026-06-23 08:23:32 +03:00
EnumDev cb01c96ad6 Improve update operation new dependency resolution 2026-06-22 13:56:03 +03:00
EnumDev e6456105e1 Improve optional dependency detection during package installation 2026-06-21 09:47:28 +03:00
EnumDev a496777b9e Fix virtual package resolution 2026-06-14 18:57:41 +03:00
EnumDev cb191dc16a Unify normal and virtual package entry resolution 2026-06-14 17:59:44 +03:00
EnumDev 140cd3c64b Changes in package format 2026-06-10 11:41:35 +03:00
EnumDev 677da23c9d Include installed package files in upgrade actions 2026-05-28 19:35:07 +03:00
EnumDev 7f279ef8e9 Allow querying packages with version matching 2026-05-14 21:13:46 +03:00
EnumDev 3950c5f0d6 Dependency resolution improvements 2026-05-12 20:03:40 +03:00
EnumDev f3c2b9f0f6 Make 'ignore_packages' and 'ignore_paths' config options only work without rootdir 2026-05-10 19:45:07 +03:00
EnumDev dc8959c2f8 Add 'ignore_paths' config option 2026-05-10 17:26:38 +03:00
EnumDev 9122ffb9ca Improve hook system and add pre-transaction hooks 2026-05-10 16:43:58 +03:00
EnumDev a4e20b7d88 Add dependency version requirement resolution 2026-05-09 19:06:20 +03:00
EnumDev a03c9ad9ce Do not add fetch action for missing dependency again if it already exists 2026-04-26 15:13:25 +03:00
EnumDev 3f205ec412 Update GetEntryDependants function 2026-04-26 15:07:23 +03:00
EnumDev 2f9487affe Sort packages by name in error messages 2026-04-24 20:41:40 +03:00
EnumDev 596d8753a7 Allow removal of packages that depend on virtual packages with more than 1 provider 2026-04-24 20:41:16 +03:00
EnumDev 2de4eea7a4 Improve ignore_package functionality 2026-04-24 18:40:08 +03:00
EnumDev 5d0312856d Install missing dependencies on update 2026-04-20 17:17:54 +03:00
EnumDev 248359c02f Add 'GetPathOwners' function 2026-03-24 18:09:54 +02:00
EnumDev 1d81690bff Readd download size to database entry readable info 2026-03-14 11:08:42 +02:00
EnumDev 618916cd22 Remove flag to install all optional dependencies 2026-03-14 09:24:07 +02:00
EnumDev 8e4177c899 Show make dependant packages in database entry readable info 2026-03-14 09:18:47 +02:00
EnumDev 3f23580d16 Add sorting by installation date and last update date in 'list' subcommand 2026-02-24 20:51:04 +02:00
EnumDev 95dee0560a Add GPG signature verification 2026-02-24 18:50:50 +02:00
EnumDev 45721cfc43 Changes in printUsage function output 2026-02-07 18:05:15 +02:00
EnumDev 2aceb9efe8 Remove 'reinstall-all' flag 2026-02-07 15:07:10 +02:00
EnumDev bc3df83936 Reimplement runtime dependency resolution toggle 2026-02-07 15:03:38 +02:00
EnumDev f18c837bc2 Remove obsolete functions 2026-02-07 14:59:25 +02:00
EnumDev 76ad8e77c5 Greatly simplify dependency resolution 2026-02-07 14:52:14 +02:00
EnumDev 9ae2d3f893 Return virtual package providers in a sorted array 2026-02-07 11:51:05 +02:00
EnumDev 89c0c0892f Cleanup make and check dependencies only if package type is 'source' 2026-02-06 13:50:01 +02:00
EnumDev ad89650dc4 Sort packages by name correctly in 'list' subcommand 2026-02-05 18:08:34 +02:00
EnumDev 3a7adb27ab Replace 'human-readable' flag with 'show-bytes' 2026-02-05 18:02:02 +02:00
EnumDev f6aa01339e Show array length in readable info 2026-02-05 17:57:11 +02:00
EnumDev a29de2e52d Show virtual package providers in readable info 2026-02-05 17:52:40 +02:00
EnumDev 619335bbe6 Replace ResolveVirtualPackage function with GetDatabaseVirtualPackageEntry 2026-02-05 14:54:08 +02:00
EnumDev 8579c688d7 Use BFS for finding unused dependencies 2026-02-05 14:22:16 +02:00
EnumDev 48cec35f0c Cache installed virtual packages 2026-02-05 13:21:50 +02:00
EnumDev e19d64bf04 Replace an instance of GetPackage with GetPackageInfo 2026-02-04 14:50:25 +02:00
EnumDev ee80640153 Move installation reason to package local info 2026-02-04 14:48:52 +02:00
EnumDev e4d94e378f Move bpm locks to var/cache/bpm 2026-02-04 13:03:52 +02:00
EnumDev b37360ed82 Add package installation and last update dates 2026-02-03 21:25:33 +02:00
EnumDev ba0d288e45 Add check dependencies 2026-01-28 18:50:25 +02:00
EnumDev c1fa9d9474 Allow skipping tests during compilation in 'install' and 'update' subcommands 2026-01-28 13:02:37 +02:00
EnumDev 0742c81f2f Fix file ownership issues with package compilation as the root user 2026-01-28 12:33:26 +02:00
EnumDev dd20574470 More improvements for the readable info functions 2026-01-27 12:47:25 +02:00
EnumDev b3dc6e3e34 Add optional dependency descriptions 2026-01-26 13:25:25 +02:00
EnumDev 0a06808303 Improve readable info functions 2026-01-25 19:55:09 +02:00
EnumDev 9f8a20c671 Show arrays in multiple lines in readable info 2026-01-25 19:23:39 +02:00
EnumDev 2072e3856f Add 'maintainers' field to pkg.info 2026-01-25 17:23:53 +02:00
EnumDev 72f574209f Keep track of resolved virtual packages during dependency resolution 2026-01-22 12:35:08 +02:00
EnumDev 9c2d4595c7 Add 'jobs' flag to the 'compile' and 'install' subcommands 2026-01-19 17:42:47 +02:00
EnumDev 72f17f8954 Rework runtime dependency system 2026-01-06 19:35:42 +02:00
EnumDev fd646a820d Add 'runtime_depends' field to package info 2026-01-04 14:40:55 +02:00
14 changed files with 2661 additions and 880 deletions
+440 -135
View File
File diff suppressed because it is too large Load Diff
+135 -26
View File
@@ -11,6 +11,7 @@ import (
"os/exec"
"path"
"path/filepath"
"runtime"
"slices"
"strconv"
"strings"
@@ -23,7 +24,12 @@ import (
var rootCompilationUID = "65534"
var rootCompilationGID = "65534"
func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks, keepCompilationFiles, verbose 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)
@@ -90,8 +96,8 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks, k
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
}
@@ -139,12 +145,40 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks, k
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
@@ -162,11 +196,11 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks, k
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
"exit 0")
@@ -213,10 +247,10 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks, k
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
@@ -304,7 +338,7 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks, k
// Generate package file list
fmt.Println("Generating package file list...")
cmd = exec.Command("bash", "-c", "fakeroot -i \"$BPM_WORKDIR\"/fakeroot_file find \"$BPM_OUTPUT\" -mindepth 1 -printf \"%P %#m %U %G %s\\n\" > \"$BPM_WORKDIR\"/pkg.files")
cmd = 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
@@ -320,7 +354,15 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks, k
// Create gzip-compressed archive for the package files
fmt.Println("Generating compressed file archive...")
cmd = exec.Command("bash", "-c", fmt.Sprintf("find %s -printf \"%%P\\n\" | fakeroot -i %s/fakeroot_file tar -czf files.tar.gz --no-recursion -C %s -T -", "output_"+pkg.Name, tempDirectory, "output_"+pkg.Name))
cmd = exec.Command("bash", "-c", fmt.Sprintf(`find %s -printf "%%P\n" | fakeroot -i %s/fakeroot_file tar czf files.tar.gz \
--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
@@ -348,6 +390,31 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks, k
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 {
@@ -355,26 +422,31 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks, k
}
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
fmt.Println("Generating final BPM archive...")
cmd = exec.Command("bash", "-c", "tar -cf final-archive.bpm --owner=0 --group=0 -C \"$BPM_WORKDIR\" "+strings.Join(bpmArchiveFiles, " "))
cmd = exec.Command("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
@@ -392,8 +464,8 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks, k
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
}
@@ -431,6 +503,24 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks, k
}
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 {
@@ -509,12 +599,22 @@ func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string, verbose bo
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", "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 {
@@ -532,6 +632,11 @@ func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string, verbose bo
}
} 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 {
@@ -576,6 +681,10 @@ func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string, verbose bo
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)
}
@@ -663,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
}
@@ -686,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
}
+3
View File
@@ -8,6 +8,7 @@ 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"`
@@ -16,11 +17,13 @@ type MainBPMConfigStruct struct {
type configDatabase struct {
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"`
}
+384 -74
View File
@@ -17,11 +17,20 @@ import (
"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
VirtualPackages map[string][]*BPMDatabaseEntry
Name string
VerificationLevel VerificationLevel
Source string
}
@@ -33,7 +42,7 @@ type BPMDatabaseEntry struct {
Database *BPMDatabase
}
var BPMDatabases = make(map[string]*BPMDatabase)
var BPMDatabases = make([]*BPMDatabase, 0)
func (db *BPMDatabase) ContainsPackage(pkg string) bool {
_, ok := db.Entries[pkg]
@@ -59,8 +68,18 @@ 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
for entryName, entry := range database.Entries {
@@ -105,18 +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])
}
}
} 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)
}
}
}
BPMDatabases[db.Name] = database
BPMDatabases = append(BPMDatabases, database)
return nil
}
@@ -188,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 {
@@ -231,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) {
@@ -256,20 +316,92 @@ func (db *BPMDatabase) FetchPackage(pkg string) (string, error) {
}
// Download package from url
err = downloadFile("Downloading "+entry.Info.Name, 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 {
if slices.Contains(e.Info.Depends, entry.Info.Name) {
dependantsMap[e.Info.Name] = append(dependantsMap[e.Info.Name], e.Database.Name)
// 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
}
}
}
}
@@ -297,7 +429,15 @@ func (entry *BPMDatabaseEntry) GetEntryOptionalDependants() (dependants []string
dependantsMap := make(map[string][]string)
for _, db := range BPMDatabases {
for _, e := range db.Entries {
if slices.Contains(e.Info.OptionalDepends, entry.Info.Name) {
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)
}
}
@@ -322,63 +462,220 @@ func (entry *BPMDatabaseEntry) GetEntryOptionalDependants() (dependants []string
return dependants
}
func (entry *BPMDatabaseEntry) CreateReadableInfo(rootDir string, humanReadableSize bool) string {
ret := make([]string, 0)
appendArray := func(label string, array []string, sort bool) {
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
}
if sort {
// Sort array
if sort {
slices.Sort(array)
}
ret = append(ret, fmt.Sprintf("%s: %s", label, strings.Join(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
}
ret = append(ret, "Name: "+entry.Info.Name)
ret = append(ret, "Database: "+entry.Database.Name)
ret = append(ret, "Description: "+entry.Info.Description)
ret = append(ret, "Version: "+entry.Info.GetFullVersion())
if entry.Info.Url != "" {
ret = append(ret, "URL: "+entry.Info.Url)
// 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 + ", ")
}
if entry.Info.License != "" {
ret = append(ret, "License: "+entry.Info.License)
}
ret = append(ret, "Architecture: "+entry.Info.Arch)
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() {
ret = append(ret, "Output architecture: "+entry.Info.OutputArch)
builder.WriteString("Output architecture: " + entry.Info.OutputArch + "\n")
}
ret = append(ret, "Type: "+entry.Info.Type)
appendArray("Dependencies", entry.Info.Depends, true)
if entry.Info.Type == "source" {
appendArray("Make Dependencies", entry.Info.MakeDepends, true)
}
appendArray("Optional dependencies", entry.Info.OptionalDepends, true)
dependants := entry.GetEntryDependants()
if len(dependants) > 0 {
appendArray("Dependant packages", dependants, false)
}
optionalDependants := entry.GetEntryOptionalDependants()
if len(optionalDependants) > 0 {
appendArray("Optionally dependant packages", optionalDependants, false)
}
appendArray("Conflicting packages", entry.Info.Conflicts, true)
appendArray("Provided packages", entry.Info.Provides, true)
appendArray("Replaces packages", entry.Info.Replaces, true)
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
}
appendArray("Split Packages", splitPkgs, true)
builderWriteArray("Split packages", splitPkgs, true)
}
// Installation reason
if rootDir != "" && IsPackageInstalled(entry.Info.Name, rootDir) {
installationReason := GetInstallationReason(entry.Info.Name, rootDir)
installationReason := GetPackage(entry.Info.Name, rootDir).LocalInfo.GetInstallationReason()
var installationReasonString string
switch installationReason {
case InstallationReasonManual:
@@ -390,17 +687,30 @@ func (entry *BPMDatabaseEntry) CreateReadableInfo(rootDir string, humanReadableS
default:
installationReasonString = "Unknown"
}
ret = append(ret, "Installation Reason: "+installationReasonString)
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 humanReadableSize {
installedSizeStr = BytesToHumanReadable(installedSize)
} else {
if showBytes {
installedSizeStr = strconv.FormatInt(installedSize, 10)
} else {
installedSizeStr = BytesToHumanReadable(installedSize)
}
ret = append(ret, "Installed size: "+installedSizeStr)
builder.WriteString("Installed size: " + installedSizeStr + "\n")
}
return strings.Join(ret, "\n")
return strings.TrimSpace(builder.String())
}
+351 -175
View File
@@ -2,180 +2,12 @@ package bpmlib
import (
"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,
})
}
}
}
// Skip ignored packages
allDepends = slices.DeleteFunc(allDepends, func(depend pkgInstallationReason) bool {
return slices.Contains(MainBPMConfig.IgnorePackages, depend.PkgName)
})
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 (pkgInfo *PackageInfo) GetPackageDependants(rootDir string) (dependants []string) {
func (pkgInfo *PackageInfo) GetPackageDependants(rootDir string, skipMultipleProviders bool) (dependants []string) {
// Get installed package names
pkgs, ok := localPackageInformation[rootDir]
if !ok {
@@ -191,7 +23,27 @@ func (pkgInfo *PackageInfo) GetPackageDependants(rootDir string) (dependants []s
// Add installed package to list if its dependencies include pkgName
if slices.ContainsFunc(installedPkg.Depends, func(n string) bool {
return n == pkgInfo.Name
// Deconstruct package string
d, err := DeconstructPackageString(n)
if err != nil {
return false
}
return d.PkgName == pkgInfo.Name
}) {
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
@@ -199,9 +51,33 @@ func (pkgInfo *PackageInfo) GetPackageDependants(rootDir string) (dependants []s
// Loop through each virtual package
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.Depends, func(n string) bool {
return n == vpkg
// Deconstruct package string
d, err := DeconstructPackageString(n)
if err != nil {
return false
}
return d.PkgName == vpkg
}) {
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
@@ -228,7 +104,13 @@ func (pkgInfo *PackageInfo) GetPackageOptionalDependants(rootDir string) (depend
// Add installed package to list if its optional dependencies include pkgName
if slices.ContainsFunc(installedPkg.OptionalDepends, func(n string) bool {
return n == pkgInfo.Name
// Deconstruct package string
d, err := DeconstructPackageString(n)
if err != nil {
return false
}
return d.PkgName == pkgInfo.Name
}) {
dependants = append(dependants, installedPkg.Name)
continue
@@ -238,7 +120,13 @@ func (pkgInfo *PackageInfo) GetPackageOptionalDependants(rootDir string) (depend
for _, vpkg := range pkgInfo.Provides {
// Add installed package to list if its optional dependencies contain a provided virtual package
if slices.ContainsFunc(installedPkg.OptionalDepends, func(n string) bool {
return n == vpkg
// Deconstruct package string
d, err := DeconstructPackageString(n)
if err != nil {
return false
}
return d.PkgName == vpkg
}) {
dependants = append(dependants, installedPkg.Name)
break
@@ -248,3 +136,291 @@ func (pkgInfo *PackageInfo) GetPackageOptionalDependants(rootDir string) (depend
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
View File
@@ -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:"
keys := slices.Collect(maps.Keys(e.packages))
slices.Sort(keys)
for _, key := range keys {
ret += "\n " + key + ": " + e.packages[key]
}
type DependencyNotFoundErr struct {
dependencies []string
}
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, ", "))
}
+179 -71
View File
@@ -11,21 +11,14 @@ 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),
}
@@ -34,33 +27,79 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
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() {
for _, splitPkg := range bpmpkg.PkgInfo.SplitPackages {
if reinstallMethod == ReinstallMethodNone && IsPackageInstalled(splitPkg.Name, rootDir) && GetPackageInfo(splitPkg.Name, rootDir).GetFullVersion() == splitPkg.GetFullVersion() {
// 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 !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,
})
@@ -68,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() {
// 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)
entry := ResolveDatabaseEntry(d, rootDir)
if entry == nil {
unresolvedPackages[pkg] = "could not find in any database"
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 {
pkgsNotFound = append(pkgsNotFound, pkg)
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
}
} else if e := ResolveVirtualPackage(pkg); e != nil {
entry = e
} else {
pkgsNotFound = append(pkgsNotFound, pkg)
}
// 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
}
if reinstallMethod == ReinstallMethodNone && IsPackageInstalled(entry.Info.Name, rootDir) && GetPackageInfo(entry.Info.Name, rootDir).GetFullVersion() == entry.Info.GetFullVersion() {
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})
}
}
@@ -189,8 +282,8 @@ 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),
}
@@ -201,13 +294,13 @@ func RemovePackages(rootDir string, force, cleanupDependencies bool, 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
@@ -223,12 +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 {
// Skip package if ignored
if slices.Contains(MainBPMConfig.IgnorePackages, action.(*RemovePackageAction).BpmPackage.PkgInfo.Name) {
// 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)
dependants := action.(*RemovePackageAction).BpmPackage.PkgInfo.GetPackageDependants(rootDir, true)
packageDepndants[action.(*RemovePackageAction).BpmPackage.PkgInfo.Name] = dependants
}
@@ -244,7 +337,7 @@ func RemovePackages(rootDir string, force, cleanupDependencies bool, packages ..
// Remove dependant packages if ignored
for pkg, required := range packageDepndants {
required = slices.DeleteFunc(required, func(pkgName string) bool {
return slices.Contains(MainBPMConfig.IgnorePackages, pkgName)
return rootDir == "/" && slices.Contains(MainBPMConfig.IgnorePackages, pkgName)
})
packageDepndants[pkg] = required
}
@@ -267,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),
}
@@ -365,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, allowDowngrades 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)
@@ -395,49 +488,64 @@ func UpdatePackages(rootDir string, syncDatabase bool, allowDowngrades bool, ins
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 := CompareVersions(entry.Info.GetFullVersion(), installedInfo.GetFullVersion())
if (!allowDowngrades && comparison > 0) || (allowDowngrades && comparison != 0) {
operation.AppendAction(&FetchPackageAction{
InstallationReason: GetInstallationReason(pkg, rootDir),
// 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})
}
}
+216
View File
@@ -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
}
+40 -61
View File
@@ -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"`
TriggerActions []string `yaml:"trigger_actions"`
TriggerPreOperation bool `yaml:"trigger_pre_operation"`
Targets []string `yaml:"targets"`
Depends []string `yaml:"depends"`
Run string `yaml:"run"`
PassTargets bool `yaml:"pass_targets"`
}
// createHook returns a BPMHook instance based on the content of the given string
@@ -35,10 +36,8 @@ func createHook(sourcePath string) (*BPMHook, error) {
hook := &BPMHook{
SourcePath: sourcePath,
SourceContent: string(bytes),
TriggerOperations: nil,
TargetType: "",
TriggerActions: nil,
Targets: nil,
Depends: nil,
Run: "",
}
@@ -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
for modifiedFile, action := range modifiedFiles {
// Check if this hook is triggered by this file's action
if !slices.Contains(hook.TriggerActions, action) {
continue
}
if hook.TargetType == "package" {
for change, operation := range packageChanges {
if target == change && slices.Contains(hook.TriggerOperations, operation) {
targetMet = true
break
// Check if file has already been checked
if slices.Contains(targetsMet, modifiedFile) {
continue
}
if matched, _ := filepath.Match(target, modifiedFile); !matched {
continue
}
targetsMet = append(targetsMet, modifiedFile)
}
}
} else {
glob, err := filepath.Glob(path.Join(rootDir, target))
if err != nil {
return err
}
for _, change := range modifiedFiles {
if slices.Contains(glob, path.Join(rootDir, change.Path)) {
targetMet = true
break
}
}
}
}
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
}
+235 -44
View File
@@ -4,23 +4,46 @@ import (
"fmt"
"os"
"path"
"path/filepath"
"slices"
"strconv"
"strings"
"gopkg.in/yaml.v3"
)
var localPackageInformation map[string]map[string]*PackageInfo = make(map[string]map[string]*PackageInfo)
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]*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)
@@ -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
}
@@ -51,15 +74,21 @@ func initializeLocalPackageInformation(rootDir string) (err error) {
// Add package to slice
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
}
@@ -75,9 +104,44 @@ func GetInstalledPackages(rootDir string) (ret []string, err error) {
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
}
func IsPackageInstalled(pkg, rootDir string) bool {
// Initialize local package information
err := initializeLocalPackageInformation(rootDir)
err := InitializeLocalPackageInformation(rootDir)
if err != nil {
return false
}
@@ -88,48 +152,22 @@ func IsPackageInstalled(pkg, rootDir string) bool {
return true
}
func IsVirtualPackage(pkg, rootDir string) (bool, string) {
pkgs, err := GetInstalledPackages(rootDir)
func GetVirtualPackageInfo(vpkg, rootDir string) []*PackageInfo {
err := InitializeLocalPackageInformation(rootDir)
if err != nil {
return false, ""
}
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, ""
return nil
}
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
providers := installedVirtualPackages[rootDir][vpkg]
slices.SortFunc(providers, func(a, b *PackageInfo) int {
return strings.Compare(a.Name, b.Name)
})
return providers
}
func GetPackageInfo(pkg string, rootDir string) *PackageInfo {
err := initializeLocalPackageInformation(rootDir)
err := InitializeLocalPackageInformation(rootDir)
if err != nil {
return nil
}
@@ -144,10 +182,12 @@ func GetPackage(pkg, rootDir string) *BPMPackage {
}
files := getPackageFiles(pkgInfo.Name, rootDir)
localInfo := getPackageLocalInfo(pkgInfo.Name, rootDir)
return &BPMPackage{
PkgInfo: pkgInfo,
PkgFiles: files,
LocalInfo: localInfo,
}
}
@@ -180,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
}
@@ -234,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
}
+1 -1
View File
@@ -39,7 +39,7 @@ func downloadFile(barText, u, filepath string, perm os.FileMode) error {
defer file.Close()
// Create progress bar
bar := createProgressBar(resp.ContentLength, barText, false)
bar := createProgressBar(resp.ContentLength, barText, barText == "")
defer bar.Close()
// Copy data
+295 -146
View File
@@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"log"
"maps"
"os"
"path"
"slices"
@@ -13,80 +14,16 @@ 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() int64 {
var ret int64 = 0
for _, action := range operation.Actions {
@@ -129,8 +66,9 @@ func (operation *BPMOperation) GetFinalActionSize(rootDir string) int64 {
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" {
@@ -140,58 +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++
}
}
pos++
Flags: resolvedPkg.Flags,
DatabaseEntry: resolvedPkg.DatabaseEntry,
}
newActions = append(newActions, action)
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)
for _, vpkg := range resolvedPkg.DatabaseEntry.Info.Provides {
if _, ok := resolvedVirtualPackages[vpkg]; !ok {
resolvedVirtualPackages[vpkg] = resolvedPkg.DatabaseEntry.Info.Name
}
}
for pkg, action := range removeActions {
dependants := action.BpmPackage.PkgInfo.GetPackageDependants(operation.RootDir)
dependants = slices.DeleteFunc(dependants, func(d string) bool {
if _, ok := removeActions[d]; ok {
return true
// Check if can move original action
if i := ActionSliceIndex(operation.Actions, resolvedPkg.DatabaseEntry.Info.Name); i != -1 {
newActions[len(newActions)-1] = operation.Actions[i]
}
return false
})
if len(dependants) != 0 {
operation.RemoveAction(pkg, action.GetActionType())
}
}
return nil
if ActionSliceIndex(newActions, pkgInfo.Name) == -1 {
newActions = append(newActions, value)
}
}
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 {
queue := make([]*PackageInfo, 0)
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
}
keepPackages = append(keepPackages, pkg.Name)
resolved := pkg.GetDependenciesRecursive(!cleanupMakeDepends, operation.RootDir)
for _, value := range resolved {
if !slices.Contains(keepPackages, value) && !slices.Contains(MainBPMConfig.IgnorePackages, value) {
keepPackages = append(keepPackages, value)
// 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,10 +256,11 @@ 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)
}
}
}
@@ -379,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
@@ -409,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 := 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")
}
}
}
@@ -470,7 +471,7 @@ func (operation *BPMOperation) GetOptionalDependencies() (optionalDepends map[st
optionalDepends = make(map[string][]string)
// Find all optional dependencies
for _, value := range slices.Clone(operation.Actions) {
for _, value := range operation.Actions {
var pkgInfo *PackageInfo
if value.GetActionType() == "install" {
action := value.(*InstallPackageAction)
@@ -483,24 +484,66 @@ func (operation *BPMOperation) GetOptionalDependencies() (optionalDepends map[st
}
for _, depend := range pkgInfo.OptionalDepends {
// Deconstruct package string
d, err := DeconstructPackageString(depend)
if err != nil {
continue
}
// Skip if dependency is already installed
if IsPackageInstalled(depend, operation.RootDir) {
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.Contains(installedPkg.PkgInfo.OptionalDepends, depend) {
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
}
optionalDepends[pkgInfo.Name] = append(optionalDepends[pkgInfo.Name], depend)
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) RunHooks(verbose bool) error {
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
@@ -520,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
@@ -590,6 +672,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,
SplitPackageToInstall: entry.Info.Name,
}
@@ -597,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,
}
}
@@ -604,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 {
@@ -646,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
@@ -670,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, false, verbose)
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)
}
@@ -690,22 +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.Println("Operation complete!")
return nil
}
@@ -717,6 +826,7 @@ type OperationAction interface {
type InstallPackageAction struct {
File string
InstallationReason InstallationReason
Flags map[string]string
SplitPackageToInstall string
BpmPackage *BPMPackage
}
@@ -727,6 +837,7 @@ func (action *InstallPackageAction) GetActionType() string {
type FetchPackageAction struct {
InstallationReason InstallationReason
Flags map[string]string
DatabaseEntry *BPMDatabaseEntry
}
@@ -741,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
}
+331 -99
View File
@@ -10,12 +10,14 @@ import (
"os"
"os/exec"
"path"
"path/filepath"
"regexp"
"slices"
"sort"
"strconv"
"strings"
"syscall"
"time"
"gopkg.in/yaml.v3"
)
@@ -23,6 +25,7 @@ import (
type BPMPackage struct {
PkgInfo *PackageInfo
PkgFiles []*PackageFileEntry
LocalInfo PackageLocalInfo
}
type PackageInfo struct {
@@ -32,21 +35,41 @@ 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"`
@@ -72,6 +95,13 @@ type PackageFileEntry struct {
SizeInBytes int64
}
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 {
@@ -120,35 +150,17 @@ const (
InstallationReasonUnknown InstallationReason = "unknown"
)
func GetInstallationReason(pkg, rootDir string) InstallationReason {
installedDir := path.Join(rootDir, "var/lib/bpm/installed/")
pkgDir := path.Join(installedDir, pkg)
if stat, err := os.Stat(path.Join(pkgDir, "installation_reason")); err != nil || stat.IsDir() {
func (localInfo PackageLocalInfo) GetInstallationReason() InstallationReason {
switch localInfo.InstallationReason {
case "manual":
return InstallationReasonManual
}
b, err := os.ReadFile(path.Join(pkgDir, "installation_reason"))
if err != nil {
return InstallationReasonUnknown
}
reason := strings.TrimSpace(string(b))
if reason == "manual" {
return InstallationReasonManual
} else if reason == "dependency" {
case "dependency":
return InstallationReasonDependency
} else if reason == "make_dependency" {
case "make_dependency":
return InstallationReasonMakeDependency
}
default:
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) {
@@ -169,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 {
@@ -178,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) {
@@ -204,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) == "" {
@@ -218,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 {
@@ -248,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,
@@ -457,6 +469,7 @@ 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),
@@ -537,56 +550,186 @@ func ReadPackageInfo(contents string) (*PackageInfo, error) {
}
func (pkgInfo *PackageInfo) CreateReadableInfo(rootDir string) string {
ret := make([]string, 0)
appendArray := func(label string, array []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, ", ")))
// Sort array
if sort {
slices.Sort(array)
}
ret = append(ret, "Name: "+pkgInfo.Name)
ret = append(ret, "Description: "+pkgInfo.Description)
ret = append(ret, "Version: "+pkgInfo.GetFullVersion())
if pkgInfo.Url != "" {
ret = append(ret, "URL: "+pkgInfo.Url)
builder.WriteString(label + " (" + strconv.Itoa(len(array)) + "):\n")
for _, val := range array {
builder.WriteString(" - " + val + "\n")
}
if pkgInfo.License != "" {
ret = append(ret, "License: "+pkgInfo.License)
}
ret = append(ret, "Architecture: "+pkgInfo.Arch)
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() {
ret = append(ret, "Output architecture: "+pkgInfo.Arch)
builder.WriteString("Output architecture: " + pkgInfo.OutputArch + "\n")
}
ret = append(ret, "Type: "+pkgInfo.Type)
appendArray("Dependencies", pkgInfo.Depends)
if pkgInfo.Type == "source" {
appendArray("Make Dependencies", pkgInfo.MakeDepends)
}
appendArray("Optional dependencies", pkgInfo.OptionalDepends)
dependants := pkgInfo.GetPackageDependants(rootDir)
if len(dependants) > 0 {
appendArray("Dependant packages", dependants)
}
optionalDependants := pkgInfo.GetPackageOptionalDependants(rootDir)
if len(optionalDependants) > 0 {
appendArray("Optionally dependant packages", optionalDependants)
}
appendArray("Conflicting packages", pkgInfo.Conflicts)
appendArray("Provided packages", pkgInfo.Provides)
appendArray("Replaces packages", pkgInfo.Replaces)
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)
}
// Installation reason
if rootDir != "" && IsPackageInstalled(pkgInfo.Name, rootDir) {
installationReason := GetInstallationReason(pkgInfo.Name, rootDir)
installationReason := GetPackage(pkgInfo.Name, rootDir).LocalInfo.GetInstallationReason()
var installationReasonString string
switch installationReason {
case InstallationReasonManual:
@@ -598,10 +741,10 @@ func (pkgInfo *PackageInfo) CreateReadableInfo(rootDir string) string {
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 {
@@ -638,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)
@@ -665,6 +819,17 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
}
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 {
@@ -722,6 +887,17 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
}
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
@@ -737,6 +913,17 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
}
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/")))
}
@@ -765,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
}
@@ -812,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
@@ -839,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
@@ -851,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
}
@@ -916,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
}
@@ -932,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
}
@@ -949,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 {
@@ -988,8 +1199,16 @@ 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
}
@@ -1033,31 +1252,44 @@ func removePackage(pkg string, verbose bool, rootDir string) error {
// Removing package files
for _, entry := range fileEntries {
bar.Add64(entry.SizeInBytes)
file := path.Join(rootDir, entry.Path)
lstat, err := os.Lstat(file)
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
}
@@ -1065,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
}
@@ -1109,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
}
+4 -4
View File
@@ -19,14 +19,14 @@ type BPMLock struct {
}
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)
// 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/lib/bpm/bpm.lock"))
f, err := os.Create(path.Join(rootDir, "var/cache/bpm/bpm.lock"))
if err != nil {
return nil, err
}
@@ -37,7 +37,7 @@ func LockBPM(rootDir string) (*BPMLock, error) {
return nil, err
}
return &BPMLock{f, path.Join(rootDir, "var/lib/bpm/bpm.lock")}, nil
return &BPMLock{f, path.Join(rootDir, "var/cache/bpm/bpm.lock")}, nil
}
func (lock *BPMLock) Unlock() error {