diff --git a/main.go b/main.go index ae69150..f16b05f 100644 --- a/main.go +++ b/main.go @@ -4,7 +4,7 @@ import ( "bufio" "flag" "fmt" - "gitlab.com/bubble-package-manager/bpm/bpm_utils" + "gitlab.com/bubble-package-manager/bpm/utils" "log" "os" "path/filepath" @@ -17,23 +17,25 @@ import ( /* A simple-to-use package manager */ /* ---------------------------------- */ -var bpmVer = "0.3.2" +var bpmVer = "0.4" var subcommand = "help" var subcommandArgs []string // Flags var rootDir = "/" +var verbose = false var yesAll = false var buildSource = false var skipCheck = false var keepTempDir = false var forceInstall = false +var showInstalled = false var pkgListNumbers = false var pkgListNames = false func main() { - bpm_utils.ReadConfig() + utils.ReadConfig() resolveFlags() resolveCommand() } @@ -81,18 +83,33 @@ func resolveCommand() { return } for n, pkg := range packages { - info := bpm_utils.GetPackageInfo(pkg, rootDir, false) - if info == nil { - fmt.Printf("Package (%s) could not be found\n", pkg) - continue + var info *utils.PackageInfo + if _, err := os.Stat(pkg); err == nil && !showInstalled { + info, err = utils.ReadPackage(pkg) + if err != nil { + fmt.Printf("File (%s) could not be read\n", pkg) + continue + } + + } else { + info = utils.GetPackageInfo(pkg, rootDir, false) + if info == nil { + fmt.Printf("Package (%s) could not be found\n", pkg) + continue + } + } + fmt.Println("----------------") + if showInstalled { + fmt.Println(utils.CreateReadableInfo(false, false, true, false, true, info, rootDir)) + } else { + fmt.Println(utils.CreateReadableInfo(true, true, true, true, true, info, rootDir)) } - fmt.Print("----------------\n" + bpm_utils.CreateInfoFile(*info, true)) if n == len(packages)-1 { fmt.Println("----------------") } } case list: - packages, err := bpm_utils.GetInstalledPackages(rootDir) + packages, err := utils.GetInstalledPackages(rootDir) if err != nil { log.Fatalf("Could not get installed packages\nError: %s", err.Error()) return @@ -109,12 +126,12 @@ func resolveCommand() { return } for n, pkg := range packages { - info := bpm_utils.GetPackageInfo(pkg, rootDir, false) + info := utils.GetPackageInfo(pkg, rootDir, false) if info == nil { fmt.Printf("Package (%s) could not be found\n", pkg) continue } - fmt.Print("----------------\n" + bpm_utils.CreateInfoFile(*info, true)) + fmt.Println("----------------\n" + utils.CreateReadableInfo(true, true, true, true, true, info, rootDir)) if n == len(packages)-1 { fmt.Println("----------------") } @@ -131,36 +148,34 @@ func resolveCommand() { return } for _, file := range files { - pkgInfo, err := bpm_utils.ReadPackage(file) + pkgInfo, err := utils.ReadPackage(file) if err != nil { log.Fatalf("Could not read package\nError: %s\n", err) } if !yesAll { - fmt.Print("----------------\n" + bpm_utils.CreateInfoFile(*pkgInfo, true)) + fmt.Println("----------------\n" + utils.CreateReadableInfo(true, true, true, true, false, pkgInfo, rootDir)) fmt.Println("----------------") } verb := "install" if pkgInfo.Type == "source" { if _, err := os.Stat("/bin/fakeroot"); os.IsNotExist(err) { - fmt.Printf("Skipping... cannot %s package (%s) due to fakeroot not being installed") + fmt.Printf("Skipping... cannot %s package (%s) due to fakeroot not being installed", verb, pkgInfo.Name) continue } verb = "build" } if !forceInstall { - if pkgInfo.Arch != "any" && pkgInfo.Arch != bpm_utils.GetArch() { + if pkgInfo.Arch != "any" && pkgInfo.Arch != utils.GetArch() { fmt.Printf("skipping... cannot %s a package with a different architecture\n", verb) continue } - if unresolved := bpm_utils.CheckDependencies(pkgInfo, rootDir); len(unresolved) != 0 { + if unresolved := utils.CheckDependencies(pkgInfo, true, true, rootDir); len(unresolved) != 0 { fmt.Printf("skipping... cannot %s package (%s) due to missing dependencies: %s\n", verb, pkgInfo.Name, strings.Join(unresolved, ", ")) continue } - if pkgInfo.Type == "source" { - if unresolved := bpm_utils.CheckMakeDependencies(pkgInfo, "/"); len(unresolved) != 0 { - fmt.Printf("skipping... cannot %s package (%s) due to missing make dependencies: %s\n", verb, pkgInfo.Name, strings.Join(unresolved, ", ")) - continue - } + if conflicts := utils.CheckConflicts(pkgInfo, true, rootDir); len(conflicts) != 0 { + fmt.Printf("skipping... cannot %s package (%s) due to conflicting packages: %s\n", verb, pkgInfo.Name, strings.Join(conflicts, ", ")) + continue } } if rootDir != "/" { @@ -172,7 +187,7 @@ func resolveCommand() { fmt.Print("Would you like to view the source.sh file of this package? [Y\\n] ") text, _ := reader.ReadString('\n') if strings.TrimSpace(strings.ToLower(text)) != "n" && strings.TrimSpace(strings.ToLower(text)) != "no" { - script, err := bpm_utils.GetSourceScript(file) + script, err := utils.GetSourceScript(file) if err != nil { log.Fatalf("Could not read source script\nError: %s\n", err) } @@ -181,9 +196,9 @@ func resolveCommand() { } } } - if bpm_utils.IsPackageInstalled(pkgInfo.Name, rootDir) { + if utils.IsPackageInstalled(pkgInfo.Name, rootDir) { if !yesAll { - installedInfo := bpm_utils.GetPackageInfo(pkgInfo.Name, rootDir, false) + installedInfo := utils.GetPackageInfo(pkgInfo.Name, rootDir, false) if strings.Compare(pkgInfo.Version, installedInfo.Version) > 0 { fmt.Println("This file contains a newer version of this package (" + installedInfo.Version + " -> " + pkgInfo.Version + ")") fmt.Print("Do you wish to update this package? [y\\N] ") @@ -211,7 +226,7 @@ func resolveCommand() { } } - err = bpm_utils.InstallPackage(file, rootDir, forceInstall, buildSource, skipCheck, keepTempDir) + err = utils.InstallPackage(file, rootDir, verbose, forceInstall, buildSource, skipCheck, keepTempDir) if err != nil { if pkgInfo.Type == "source" && keepTempDir { fmt.Println("BPM temp directory was created at /var/tmp/bpm_source-" + pkgInfo.Name) @@ -234,12 +249,12 @@ func resolveCommand() { return } for _, pkg := range packages { - pkgInfo := bpm_utils.GetPackageInfo(pkg, rootDir, false) + pkgInfo := utils.GetPackageInfo(pkg, rootDir, false) if pkgInfo == nil { fmt.Printf("Package (%s) could not be found\n", pkg) continue } - fmt.Print("----------------\n" + bpm_utils.CreateInfoFile(*pkgInfo, true)) + fmt.Println("----------------\n" + utils.CreateReadableInfo(true, true, true, true, true, pkgInfo, rootDir)) fmt.Println("----------------") if rootDir != "/" { fmt.Println("Warning: Operating in " + rootDir) @@ -253,7 +268,7 @@ func resolveCommand() { continue } } - err := bpm_utils.RemovePackage(pkg, rootDir) + err := utils.RemovePackage(pkg, verbose, rootDir) if err != nil { log.Fatalf("Could not remove package\nError: %s\n", err) @@ -275,7 +290,7 @@ func resolveCommand() { if os.IsNotExist(err) { log.Fatalf(absFile + " does not exist!") } - pkgs, err := bpm_utils.GetInstalledPackages(rootDir) + pkgs, err := utils.GetInstalledPackages(rootDir) if err != nil { log.Fatalf("Could not get installed packages. Error %s", err.Error()) } @@ -294,7 +309,7 @@ func resolveCommand() { var pkgList []string for _, pkg := range pkgs { - if slices.Contains(bpm_utils.GetPackageFiles(pkg, rootDir), absFile) { + if slices.Contains(utils.GetPackageFiles(pkg, rootDir), absFile) { pkgList = append(pkgList, pkg) } } @@ -320,19 +335,22 @@ func printHelp() { fmt.Println("-> bpm version | shows information on the installed version of bpm") fmt.Println("-> bpm info [-R] | shows information on an installed package") fmt.Println(" -R= lets you define the root path which will be used") + fmt.Println(" -i shows information about the currently installed package") fmt.Println("-> bpm list [-R, -c, -n] | lists all installed packages") fmt.Println(" -R= lets you define the root path which will be used") fmt.Println(" -c lists the amount of installed packages") fmt.Println(" -n lists only the names of installed packages") - fmt.Println("-> bpm install [-R, -y, -f, -o, -c, -b, -k] | installs the following files") + fmt.Println("-> bpm install [-R, -v, -y, -f, -o, -c, -b, -k] | installs the following files") fmt.Println(" -R= lets you define the root path which will be used") + fmt.Println(" -v Show additional information about what BPM is doing") fmt.Println(" -y skips the confirmation prompt") fmt.Println(" -f skips dependency and architecture checking") fmt.Println(" -o= set the binary package output directory (defaults to /var/lib/bpm/compiled)") fmt.Println(" -c= set the compilation directory (defaults to /var/tmp)") fmt.Println(" -b creates a binary package from a source package after compilation and saves it in the binary package output directory") fmt.Println(" -k keeps the compilation directory created by BPM after source package installation") - fmt.Println("-> bpm remove [-R, -y] | removes the following packages") + fmt.Println("-> bpm remove [-R, -v, -y] | removes the following packages") + fmt.Println(" -v Show additional information about what BPM is doing") fmt.Println(" -R= lets you define the root path which will be used") fmt.Println(" -y skips the confirmation prompt") fmt.Println("-> bpm file [-R] | shows what packages the following packages are managed by") @@ -350,13 +368,15 @@ func resolveFlags() { // Info flags infoFlagSet := flag.NewFlagSet("Info flags", flag.ExitOnError) infoFlagSet.StringVar(&rootDir, "R", "/", "Set the destination root") + infoFlagSet.BoolVar(&showInstalled, "i", false, "Shows information about the currently installed package") infoFlagSet.Usage = printHelp // Install flags installFlagSet := flag.NewFlagSet("Install flags", flag.ExitOnError) installFlagSet.StringVar(&rootDir, "R", "/", "Set the destination root") + installFlagSet.BoolVar(&verbose, "v", false, "Show additional information about what BPM is doing") installFlagSet.BoolVar(&yesAll, "y", false, "Skip confirmation prompts") - installFlagSet.StringVar(&bpm_utils.BPMConfig.BinaryOutputDir, "o", bpm_utils.BPMConfig.BinaryOutputDir, "Set the binary output directory") - installFlagSet.StringVar(&bpm_utils.BPMConfig.CompilationDir, "c", bpm_utils.BPMConfig.CompilationDir, "Set the compilation directory") + installFlagSet.StringVar(&utils.BPMConfig.BinaryOutputDir, "o", utils.BPMConfig.BinaryOutputDir, "Set the binary output directory") + installFlagSet.StringVar(&utils.BPMConfig.CompilationDir, "c", utils.BPMConfig.CompilationDir, "Set the compilation directory") installFlagSet.BoolVar(&buildSource, "b", false, "Build binary package from source package") installFlagSet.BoolVar(&skipCheck, "s", false, "Skip check function during source compilation") installFlagSet.BoolVar(&keepTempDir, "k", false, "Keep temporary directory after source compilation") @@ -365,6 +385,7 @@ func resolveFlags() { // Remove flags removeFlagSet := flag.NewFlagSet("Remove flags", flag.ExitOnError) removeFlagSet.StringVar(&rootDir, "R", "/", "Set the destination root") + removeFlagSet.BoolVar(&verbose, "v", false, "Show additional information about what BPM is doing") removeFlagSet.BoolVar(&yesAll, "y", false, "Skip confirmation prompts") removeFlagSet.Usage = printHelp // File flags diff --git a/bpm_utils/config.go b/utils/config.go similarity index 97% rename from bpm_utils/config.go rename to utils/config.go index 1a6482d..9fd0a81 100644 --- a/bpm_utils/config.go +++ b/utils/config.go @@ -1,4 +1,4 @@ -package bpm_utils +package utils import ( "gopkg.in/yaml.v3" diff --git a/bpm_utils/general_utils.go b/utils/general_utils.go similarity index 97% rename from bpm_utils/general_utils.go rename to utils/general_utils.go index e825209..ca568c1 100644 --- a/bpm_utils/general_utils.go +++ b/utils/general_utils.go @@ -1,4 +1,4 @@ -package bpm_utils +package utils import ( "io" diff --git a/bpm_utils/package_utils.go b/utils/package_utils.go similarity index 74% rename from bpm_utils/package_utils.go rename to utils/package_utils.go index 28bbb3d..ddec923 100644 --- a/bpm_utils/package_utils.go +++ b/utils/package_utils.go @@ -1,4 +1,4 @@ -package bpm_utils +package utils import ( "archive/tar" @@ -6,6 +6,7 @@ import ( "compress/gzip" "errors" "fmt" + "gopkg.in/yaml.v3" "io" "io/fs" "os" @@ -19,17 +20,60 @@ import ( ) type PackageInfo struct { - Name string - Description string - Version string - Url string - License string - Arch string - Type string - Keep []string - Depends []string - MakeDepends []string - Provides []string + Name string `yaml:"name,omitempty"` + Description string `yaml:"description,omitempty"` + Version string `yaml:"version,omitempty"` + Url string `yaml:"url,omitempty"` + License string `yaml:"license,omitempty"` + Arch string `yaml:"architecture,omitempty"` + Type string `yaml:"type,omitempty"` + Keep []string `yaml:"keep,omitempty"` + Depends []string `yaml:"depends,omitempty"` + ConditionalDepends map[string][]string `yaml:"conditional_depends,omitempty"` + MakeDepends []string `yaml:"make_depends,omitempty"` + ConditionalMakeDepends map[string][]string `yaml:"conditional_make_depends,omitempty"` + Conflicts []string `yaml:"conflicts,omitempty"` + ConditionalConflicts map[string][]string `yaml:"conditional_conflicts,omitempty"` + Optional []string `yaml:"optional,omitempty"` + ConditionalOptional map[string][]string `yaml:"conditional_optional,omitempty"` + Provides []string `yaml:"provides,omitempty"` +} + +type InstallationReason string + +const ( + Manual InstallationReason = "manual" + Dependency InstallationReason = "dependency" + Unknown 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() { + return Manual + } + bytes, err := os.ReadFile(path.Join(pkgDir, "installation_reason")) + if err != nil { + return Unknown + } + reason := string(bytes) + if reason == "manual" { + return Manual + } else if reason == "dependency" { + return Dependency + } + return Unknown +} + +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) { @@ -268,66 +312,27 @@ func ExecutePackageScripts(filename, rootDir string, operation Operation, postOp func ReadPackageInfo(contents string, defaultValues bool) (*PackageInfo, error) { pkgInfo := PackageInfo{ - Name: "", - Description: "", - Version: "", - Url: "", - License: "", - Arch: "", - Type: "", - Keep: nil, - Depends: nil, - MakeDepends: nil, - Provides: nil, + Name: "", + Description: "", + Version: "", + Url: "", + License: "", + Arch: "", + Type: "", + Keep: make([]string, 0), + Depends: make([]string, 0), + ConditionalDepends: make(map[string][]string), + MakeDepends: make([]string, 0), + ConditionalMakeDepends: make(map[string][]string), + Conflicts: make([]string, 0), + ConditionalConflicts: make(map[string][]string), + Optional: make([]string, 0), + ConditionalOptional: make(map[string][]string), + Provides: make([]string, 0), } - lines := strings.Split(contents, "\n") - for num, line := range lines { - if len(strings.TrimSpace(line)) == 0 { - continue - } - if line[0] == '#' { - continue - } - split := strings.SplitN(line, ":", 2) - if len(split) != 2 { - return nil, errors.New("invalid pkg.info format at line " + strconv.Itoa(num)) - } - split[0] = strings.Trim(split[0], " ") - split[1] = strings.Trim(split[1], " ") - switch split[0] { - case "name": - if strings.Contains(split[1], " ") { - return nil, errors.New("the " + split[0] + " field cannot contain spaces") - } - pkgInfo.Name = split[1] - case "description": - pkgInfo.Description = split[1] - case "version": - if strings.Contains(split[1], " ") { - return nil, errors.New("the " + split[0] + " field cannot contain spaces") - } - pkgInfo.Version = split[1] - case "url": - pkgInfo.Url = split[1] - case "license": - pkgInfo.License = split[1] - case "architecture": - pkgInfo.Arch = split[1] - case "type": - pkgInfo.Type = split[1] - case "keep": - pkgInfo.Keep = strings.Split(strings.Replace(split[1], " ", "", -1), ",") - pkgInfo.Keep = stringSliceRemoveEmpty(pkgInfo.Keep) - case "depends": - pkgInfo.Depends = strings.Split(strings.Replace(split[1], " ", "", -1), ",") - pkgInfo.Depends = stringSliceRemoveEmpty(pkgInfo.Depends) - case "make_depends": - pkgInfo.MakeDepends = strings.Split(strings.Replace(split[1], " ", "", -1), ",") - pkgInfo.MakeDepends = stringSliceRemoveEmpty(pkgInfo.MakeDepends) - case "provides": - pkgInfo.Provides = strings.Split(strings.Replace(split[1], " ", "", -1), ",") - pkgInfo.Provides = stringSliceRemoveEmpty(pkgInfo.Provides) - } + err := yaml.Unmarshal([]byte(contents), &pkgInfo) + if err != nil { + return nil, err } if !defaultValues { if pkgInfo.Name == "" { @@ -342,38 +347,74 @@ func ReadPackageInfo(contents string, defaultValues bool) (*PackageInfo, error) return nil, errors.New("this package contains no type") } } + for i := 0; i < len(pkgInfo.Keep); i++ { + pkgInfo.Keep[i] = strings.TrimPrefix(pkgInfo.Keep[i], "/") + } return &pkgInfo, nil } -func CreateInfoFile(pkgInfo PackageInfo, keepSourceFields bool) string { - ret := "" - ret = ret + "name: " + pkgInfo.Name + "\n" - ret = ret + "description: " + pkgInfo.Description + "\n" - ret = ret + "version: " + pkgInfo.Version + "\n" - if pkgInfo.Url != "" { - ret = ret + "url: " + pkgInfo.Url + "\n" +func CreateInfoFile(pkgInfo *PackageInfo) string { + bytes, err := yaml.Marshal(&pkgInfo) + if err != nil { + return "" } - if pkgInfo.License != "" { - ret = ret + "license: " + pkgInfo.License + "\n" - } - ret = ret + "architecture: " + pkgInfo.Arch + "\n" - ret = ret + "type: " + pkgInfo.Type + "\n" - if len(pkgInfo.Keep) > 0 { - ret = ret + "keep (" + strconv.Itoa(len(pkgInfo.Keep)) + "): " + strings.Join(pkgInfo.Keep, ",") + "\n" - } - if len(pkgInfo.Depends) > 0 { - ret = ret + "depends (" + strconv.Itoa(len(pkgInfo.Depends)) + "): " + strings.Join(pkgInfo.Depends, ",") + "\n" - } - if len(pkgInfo.MakeDepends) > 0 && keepSourceFields { - ret = ret + "make_depends (" + strconv.Itoa(len(pkgInfo.MakeDepends)) + "): " + strings.Join(pkgInfo.MakeDepends, ",") + "\n" - } - if len(pkgInfo.Provides) > 0 { - ret = ret + "provides (" + strconv.Itoa(len(pkgInfo.Provides)) + "): " + strings.Join(pkgInfo.Provides, ",") + "\n" - } - return ret + return string(bytes) } -func extractPackage(pkgInfo *PackageInfo, filename, rootDir string) (error, []string) { +func CreateReadableInfo(showArchitecture, showType, showPackageRelations, showRemoteInfo, showInstallationReason bool, pkgInfo *PackageInfo, rootDir string) string { + ret := make([]string, 0) + appendArray := func(label string, array []string) { + if len(array) == 0 { + return + } + ret = append(ret, fmt.Sprintf("%s: %s", label, strings.Join(array, ", "))) + } + appendMap := func(label string, m map[string][]string) { + if len(m) == 0 { + return + } + ret = append(ret, label+":") + for k, v := range m { + if len(v) == 0 { + continue + } + ret = append(ret, fmt.Sprintf(" %s: %s", k, strings.Join(v, ", "))) + } + } + ret = append(ret, "Name: "+pkgInfo.Name) + ret = append(ret, "Description: "+pkgInfo.Description) + ret = append(ret, "Version: "+pkgInfo.Version) + ret = append(ret, "URL: "+pkgInfo.Url) + ret = append(ret, "License: "+pkgInfo.License) + if showArchitecture { + ret = append(ret, "Architecture: "+pkgInfo.Arch) + } + if showType { + ret = append(ret, "Type: "+pkgInfo.Type) + } + if showPackageRelations { + appendArray("Dependencies", pkgInfo.Depends) + appendArray("Make Dependencies", pkgInfo.MakeDepends) + appendArray("Provided packages", pkgInfo.Provides) + appendArray("Conflicting packages", pkgInfo.Conflicts) + appendArray("Optional dependencies", pkgInfo.Optional) + appendMap("Conditional dependencies", pkgInfo.ConditionalDepends) + appendMap("Conditional make dependencies", pkgInfo.ConditionalMakeDepends) + appendMap("Conditional conflicting packages", pkgInfo.ConditionalConflicts) + appendMap("Conditional optional dependencies", pkgInfo.ConditionalOptional) + } + if showInstallationReason { + if IsPackageInstalled(pkgInfo.Name, rootDir) { + ret = append(ret, "Installed: yes") + ret = append(ret, "Installation Reason: "+string(GetInstallationReason(pkgInfo.Name, rootDir))) + } else { + ret = append(ret, "Installed: no") + } + } + return strings.Join(ret, "\n") +} + +func extractPackage(pkgInfo *PackageInfo, verbose bool, filename, rootDir string) (error, []string) { var files []string if !IsPackageInstalled(pkgInfo.Name, rootDir) { err := ExecutePackageScripts(filename, rootDir, Install, false) @@ -415,22 +456,46 @@ func extractPackage(pkgInfo *PackageInfo, filename, rootDir string) (error, []st return err, nil } } else { - fmt.Println("Creating Directory: " + extractFilename) + if verbose { + fmt.Println("Creating Directory: " + extractFilename) + } } case tar.TypeReg: + skip := false if _, err := os.Stat(extractFilename); err == nil { - if slices.Contains(pkgInfo.Keep, trimmedName) { - fmt.Println("Skipping File: " + extractFilename + "(File is configured to be kept during installs/updates)") - files = append(files, trimmedName) - continue + for _, k := range pkgInfo.Keep { + if strings.HasSuffix(k, "/") { + if strings.HasPrefix(trimmedName, k) { + if verbose { + fmt.Println("Skipping File: " + extractFilename + " (Containing directory is set to be kept during installs/updates)") + } + files = append(files, strings.TrimPrefix(header.Name, "files/")) + skip = true + continue + } + } else { + if trimmedName == k { + if verbose { + fmt.Println("Skipping File: " + extractFilename + " (File is configured to be kept during installs/updates)") + } + files = append(files, strings.TrimPrefix(header.Name, "files/")) + skip = true + continue + } + } } } + if skip { + continue + } err := os.Remove(extractFilename) if err != nil && !os.IsNotExist(err) { return err, nil } outFile, err := os.Create(extractFilename) - fmt.Println("Creating File: " + extractFilename) + if verbose { + fmt.Println("Creating File: " + extractFilename) + } files = append(files, strings.TrimPrefix(header.Name, "files/")) if err != nil { return err, nil @@ -446,7 +511,9 @@ func extractPackage(pkgInfo *PackageInfo, filename, rootDir string) (error, []st return err, nil } case tar.TypeSymlink: - fmt.Println("Creating Symlink: " + extractFilename + " -> " + header.Linkname) + if verbose { + fmt.Println("Creating Symlink: " + extractFilename + " -> " + header.Linkname) + } files = append(files, strings.TrimPrefix(header.Name, "files/")) err := os.Remove(extractFilename) if err != nil && !os.IsNotExist(err) { @@ -457,7 +524,9 @@ func extractPackage(pkgInfo *PackageInfo, filename, rootDir string) (error, []st return err, nil } case tar.TypeLink: - fmt.Println("Detected Hard Link: " + extractFilename + " -> " + path.Join(rootDir, strings.TrimPrefix(header.Linkname, "files/"))) + if verbose { + fmt.Println("Detected Hard Link: " + extractFilename + " -> " + path.Join(rootDir, strings.TrimPrefix(header.Linkname, "files/"))) + } files = append(files, strings.TrimPrefix(header.Name, "files/")) seenHardlinks[extractFilename] = path.Join(strings.TrimPrefix(header.Linkname, "files/")) err := os.Remove(extractFilename) @@ -470,7 +539,9 @@ func extractPackage(pkgInfo *PackageInfo, filename, rootDir string) (error, []st } } for extractFilename, destination := range seenHardlinks { - fmt.Println("Creating Hard Link: " + extractFilename + " -> " + path.Join(rootDir, destination)) + if verbose { + fmt.Println("Creating Hard Link: " + extractFilename + " -> " + path.Join(rootDir, destination)) + } err := os.Link(path.Join(rootDir, destination), extractFilename) if err != nil { return err, nil @@ -496,7 +567,7 @@ func isSplitPackage(filename string) bool { return true } -func compilePackage(pkgInfo *PackageInfo, filename, rootDir string, binaryPkgFromSrc, skipCheck, keepTempDir bool) (error, []string) { +func compilePackage(pkgInfo *PackageInfo, filename, rootDir string, verbose, binaryPkgFromSrc, skipCheck, keepTempDir bool) (error, []string) { var files []string if !IsPackageInstalled(pkgInfo.Name, rootDir) { err := ExecutePackageScripts(filename, rootDir, Install, false) @@ -525,7 +596,9 @@ func compilePackage(pkgInfo *PackageInfo, filename, rootDir string, binaryPkgFro if err != nil { return err, nil } - fmt.Println("Creating temp directory at: " + temp) + if verbose { + fmt.Println("Creating temp directory at: " + temp) + } err = os.Mkdir(temp, 0755) if err != nil { return err, nil @@ -552,7 +625,9 @@ func compilePackage(pkgInfo *PackageInfo, filename, rootDir string, binaryPkgFro return err, nil } } else { - fmt.Println("Creating Directory: " + extractFilename) + if verbose { + fmt.Println("Creating Directory: " + extractFilename) + } err = os.Chown(extractFilename, 65534, 65534) if err != nil { return err, nil @@ -564,7 +639,9 @@ func compilePackage(pkgInfo *PackageInfo, filename, rootDir string, binaryPkgFro return err, nil } outFile, err := os.Create(extractFilename) - fmt.Println("Creating File: " + extractFilename) + if verbose { + fmt.Println("Creating File: " + extractFilename) + } if err != nil { return err, nil } @@ -583,9 +660,13 @@ func compilePackage(pkgInfo *PackageInfo, filename, rootDir string, binaryPkgFro return err, nil } case tar.TypeSymlink: - fmt.Println("Skipping symlink (Bundling symlinks in source packages is not supported)") + if verbose { + fmt.Println("Skipping symlink (Bundling symlinks in source packages is not supported)") + } case tar.TypeLink: - fmt.Println("Skipping hard link (Bundling hard links in source packages is not supported)") + if verbose { + fmt.Println("Skipping hard link (Bundling hard links in source packages is not supported)") + } default: return errors.New("ExtractTarGz: unknown type: " + strconv.Itoa(int(header.Typeflag)) + " in " + extractFilename), nil } @@ -779,12 +860,16 @@ fi return err } } else { - fmt.Println("Creating Directory: " + extractFilename) + if verbose { + fmt.Println("Creating Directory: " + extractFilename) + } } } else if d.Type().IsRegular() { if _, err := os.Stat(extractFilename); err == nil { if slices.Contains(pkgInfo.Keep, relFilename) { - fmt.Println("Skipping File: " + extractFilename + "(File is configured to be kept during installs/updates)") + if verbose { + fmt.Println("Skipping File: " + extractFilename + "(File is configured to be kept during installs/updates)") + } files = append(files, relFilename) return nil } @@ -794,7 +879,9 @@ fi return err } outFile, err := os.Create(extractFilename) - fmt.Println("Creating File: " + extractFilename) + if verbose { + fmt.Println("Creating File: " + extractFilename) + } files = append(files, relFilename) if err != nil { return err @@ -830,7 +917,9 @@ fi if err != nil && !os.IsNotExist(err) { return err } - fmt.Println("Creating Symlink: "+extractFilename, " -> "+link) + if verbose { + fmt.Println("Creating Symlink: "+extractFilename, " -> "+link) + } files = append(files, relFilename) err = os.Symlink(link, extractFilename) if err != nil { @@ -849,7 +938,7 @@ fi compiledInfo = *pkgInfo compiledInfo.Type = "binary" compiledInfo.Arch = GetArch() - err = os.WriteFile(path.Join(temp, "pkg.info"), []byte(CreateInfoFile(compiledInfo, false)), 0644) + err = os.WriteFile(path.Join(temp, "pkg.info"), []byte(CreateInfoFile(&compiledInfo)), 0644) if err != nil { return err, nil } @@ -909,7 +998,7 @@ fi return nil, files } -func InstallPackage(filename, rootDir string, force, binaryPkgFromSrc, skipCheck, keepTempDir bool) error { +func InstallPackage(filename, rootDir string, verbose, force, binaryPkgFromSrc, skipCheck, keepTempDir bool) error { if _, err := os.Stat(filename); os.IsNotExist(err) { return err } @@ -927,12 +1016,12 @@ func InstallPackage(filename, rootDir string, force, binaryPkgFromSrc, skipCheck if pkgInfo.Arch != "any" && pkgInfo.Arch != GetArch() { return errors.New("cannot install a package with a different architecture") } - if unresolved := CheckDependencies(pkgInfo, rootDir); len(unresolved) != 0 { + if unresolved := CheckDependencies(pkgInfo, true, true, rootDir); len(unresolved) != 0 { return errors.New("Could not resolve all dependencies. Missing " + strings.Join(unresolved, ", ")) } } if pkgInfo.Type == "binary" { - err, i := extractPackage(pkgInfo, filename, rootDir) + err, i := extractPackage(pkgInfo, verbose, filename, rootDir) if err != nil { return err } @@ -941,7 +1030,7 @@ func InstallPackage(filename, rootDir string, force, binaryPkgFromSrc, skipCheck if isSplitPackage(filename) { return errors.New("BPM is unable to install split source packages") } - err, i := compilePackage(pkgInfo, filename, rootDir, binaryPkgFromSrc, skipCheck, keepTempDir) + err, i := compilePackage(pkgInfo, filename, rootDir, verbose, binaryPkgFromSrc, skipCheck, keepTempDir) if err != nil { return err } @@ -1023,7 +1112,7 @@ func InstallPackage(filename, rootDir string, force, binaryPkgFromSrc, skipCheck } if len(filesDiff) != 0 { - fmt.Println("Removing obsolete files") + fmt.Println("Removing obsolete files...") var symlinks []string for _, f := range filesDiff { f = path.Join(rootDir, f) @@ -1049,14 +1138,18 @@ func InstallPackage(filename, rootDir string, force, binaryPkgFromSrc, skipCheck return err } if len(dir) == 0 { - fmt.Println("Removing: " + f) + if verbose { + fmt.Println("Removing: " + f) + } err := os.Remove(f) if err != nil { return err } } } else { - fmt.Println("Removing: " + f) + if verbose { + fmt.Println("Removing: " + f) + } err := os.Remove(f) if err != nil { return err @@ -1082,7 +1175,9 @@ func InstallPackage(filename, rootDir string, force, binaryPkgFromSrc, skipCheck return err } removals++ - fmt.Println("Removing: " + f) + if verbose { + fmt.Println("Removing: " + f) + } } else if err != nil { return err } @@ -1144,24 +1239,67 @@ func GetSourceScript(filename string) (string, error) { return "", errors.New("package does not contain a source.sh file") } -func CheckDependencies(pkgInfo *PackageInfo, rootDir string) []string { - var unresolved []string +func CheckDependencies(pkgInfo *PackageInfo, checkMake, checkConditional bool, rootDir string) []string { + var ret []string for _, dependency := range pkgInfo.Depends { - if !IsPackageInstalled(dependency, rootDir) { - unresolved = append(unresolved, dependency) + if !IsPackageProvided(dependency, rootDir) { + ret = append(ret, dependency) } } - return unresolved + if checkMake { + for _, dependency := range pkgInfo.MakeDepends { + if !IsPackageProvided(dependency, rootDir) { + ret = append(ret, dependency) + } + } + } + if checkConditional { + for condition, dependencies := range pkgInfo.ConditionalDepends { + if !IsPackageInstalled(condition, rootDir) { + continue + } + for _, dependency := range dependencies { + if !IsPackageProvided(dependency, rootDir) { + ret = append(ret, dependency) + } + } + } + if checkMake { + for condition, dependencies := range pkgInfo.ConditionalMakeDepends { + if !IsPackageInstalled(condition, rootDir) { + continue + } + for _, dependency := range dependencies { + if !IsPackageInstalled(dependency, rootDir) { + ret = append(ret, dependency) + } + } + } + } + } + return ret } -func CheckMakeDependencies(pkgInfo *PackageInfo, rootDir string) []string { - var unresolved []string - for _, dependency := range pkgInfo.MakeDepends { - if !IsPackageInstalled(dependency, "/") { - unresolved = append(unresolved, dependency) +func CheckConflicts(pkgInfo *PackageInfo, checkConditional bool, rootDir string) []string { + var ret []string + for _, conflict := range pkgInfo.Conflicts { + if IsPackageInstalled(conflict, rootDir) { + ret = append(ret, conflict) } } - return unresolved + if checkConditional { + for condition, conflicts := range pkgInfo.ConditionalConflicts { + if !IsPackageInstalled(condition, rootDir) { + continue + } + for _, conflict := range conflicts { + if IsPackageInstalled(conflict, rootDir) { + ret = append(ret, conflict) + } + } + } + } + return ret } func IsPackageInstalled(pkg, rootDir string) bool { @@ -1173,6 +1311,22 @@ func IsPackageInstalled(pkg, rootDir string) bool { return true } +func IsPackageProvided(pkg, rootDir string) bool { + pkgs, err := GetInstalledPackages(rootDir) + if err != nil { + return false + } + for _, p := range pkgs { + if p == pkg { + return true + } + if slices.Contains(GetPackageInfo(p, rootDir, true).Provides, pkg) { + return true + } + } + return false +} + func GetInstalledPackages(rootDir string) ([]string, error) { installedDir := path.Join(rootDir, "var/lib/bpm/installed/") items, err := os.ReadDir(installedDir) @@ -1236,7 +1390,7 @@ func GetPackageInfo(pkg, rootDir string, defaultValues bool) *PackageInfo { return info } -func RemovePackage(pkg, rootDir string) error { +func RemovePackage(pkg string, verbose bool, rootDir string) error { installedDir := path.Join(rootDir, "var/lib/bpm/installed/") pkgDir := path.Join(installedDir, pkg) pkgInfo := GetPackageInfo(pkg, rootDir, false) @@ -1271,14 +1425,18 @@ func RemovePackage(pkg, rootDir string) error { return err } if len(dir) == 0 { - fmt.Println("Removing: " + file) + if verbose { + fmt.Println("Removing: " + file) + } err := os.Remove(file) if err != nil { return err } } } else { - fmt.Println("Removing: " + file) + if verbose { + fmt.Println("Removing: " + file) + } err := os.Remove(file) if err != nil { return err @@ -1304,7 +1462,9 @@ func RemovePackage(pkg, rootDir string) error { return err } removals++ - fmt.Println("Removing: " + file) + if verbose { + fmt.Println("Removing: " + file) + } } else if err != nil { return err } @@ -1347,6 +1507,8 @@ func RemovePackage(pkg, rootDir string) error { if err != nil { return err } - fmt.Println("Removing: " + pkgDir) + if verbose { + fmt.Println("Removing: " + pkgDir) + } return nil }