diff --git a/src/bpm/main.go b/src/bpm/main.go index 7719e22..55a6d93 100644 --- a/src/bpm/main.go +++ b/src/bpm/main.go @@ -177,6 +177,18 @@ func main() { setupFlagsAndHelp(currentFlagSet, fmt.Sprintf("bpm %s ", subcommand), "Compare two version numbers", os.Args[2:]) compareVersions() + case "upgrade-persistent-data": + currentFlagSet = flag.NewFlagSet("upgrade-persistent-data", flag.ExitOnError) + currentFlagSet.StringP("root", "R", "/", "Operate on specified root directory") + setupFlagsAndHelp(currentFlagSet, fmt.Sprintf("bpm %s ", subcommand), "Upgrade BPM's persistent data directory contents", os.Args[2:]) + + rootDir, _ := currentFlagSet.GetString("root") + err = bpmlib.UpgradePersistentData(rootDir) + if err != nil { + log.Printf("Error: could not upgrade persistent data directory: %s", err) + exitCode = 1 + return + } default: printUsage() exitCode = 1 @@ -193,6 +205,14 @@ func showPackageInfo() { showDatabaseInfo, _ := currentFlagSet.GetBool("database") showHumanReadableSize, _ := currentFlagSet.GetBool("human-readable") + // Initialize installed packages map + err := bpmlib.InitializeLocalPackageInformation(rootDir) + if err != nil { + log.Printf("Error: %s", err) + exitCode = 1 + return + } + // Get packages packages := currentFlagSet.Args() if len(packages) == 0 { @@ -201,7 +221,7 @@ func showPackageInfo() { } // Read local databases - err := bpmlib.ReadLocalDatabaseFiles() + err = bpmlib.ReadLocalDatabaseFiles() if err != nil { log.Printf("Error: could not read local databases: %s", err) exitCode = 1 @@ -301,8 +321,16 @@ func showPackageList() { showMakeDepends = true } + // Initialize installed packages map + err := bpmlib.InitializeLocalPackageInformation(rootDir) + if err != nil { + log.Printf("Error: %s", err) + exitCode = 1 + return + } + // Read local databases - err := bpmlib.ReadLocalDatabaseFiles() + err = bpmlib.ReadLocalDatabaseFiles() if err != nil { log.Printf("Error: could not read local databases: %s", err) exitCode = 1 @@ -374,7 +402,7 @@ func showPackageList() { } } else { for _, pkg := range installedPackages { - installationReason := bpmlib.GetInstallationReason(pkg.pkgInfo.Name, rootDir) + installationReason := bpmlib.GetPackage(pkg.pkgInfo.Name, rootDir).LocalInfo.GetInstallationReason() if installationReason == bpmlib.InstallationReasonManual && !showManual { continue } else if installationReason == bpmlib.InstallationReasonDependency && !showDepends { @@ -406,7 +434,7 @@ func showPackageList() { return } for n, pkg := range installedPackages { - installationReason := bpmlib.GetInstallationReason(pkg.pkgInfo.Name, rootDir) + installationReason := bpmlib.GetPackage(pkg.pkgInfo.Name, rootDir).LocalInfo.GetInstallationReason() if installationReason == bpmlib.InstallationReasonManual && !showManual { continue } else if installationReason == bpmlib.InstallationReasonDependency && !showDepends { @@ -560,6 +588,14 @@ func installPackages() { } defer fileLock.Unlock() + // Initialize installed packages map + err = bpmlib.InitializeLocalPackageInformation(rootDir) + if err != nil { + log.Printf("Error: %s", err) + exitCode = 1 + return + } + // Read local databases err = bpmlib.ReadLocalDatabaseFiles() if err != nil { @@ -693,6 +729,14 @@ func removePackages() { } defer fileLock.Unlock() + // Initialize installed packages map + err = bpmlib.InitializeLocalPackageInformation(rootDir) + if err != nil { + log.Printf("Error: %s", err) + exitCode = 1 + return + } + // Read local databases err = bpmlib.ReadLocalDatabaseFiles() if err != nil { @@ -806,6 +850,14 @@ func doCleanup() { } defer fileLock.Unlock() + // Initialize installed packages map + err = bpmlib.InitializeLocalPackageInformation(rootDir) + if err != nil { + log.Printf("Error: %s", err) + exitCode = 1 + return + } + err = bpmlib.CleanupCache(rootDir, cleanupCompilationFiles, cleanupBinaryPackages, cleanupFetchedPackages, verbose) if err != nil { log.Printf("Error: could not complete cache cleanup: %s", err) @@ -946,6 +998,14 @@ func updatePackages() { } defer fileLock.Unlock() + // Initialize installed packages map + err = bpmlib.InitializeLocalPackageInformation(rootDir) + if err != nil { + log.Printf("Error: %s", err) + exitCode = 1 + return + } + // Read local databases if no sync if noSync { err := bpmlib.ReadLocalDatabaseFiles() @@ -1067,6 +1127,14 @@ func getFileOwner() { // Get flags rootDir, _ := currentFlagSet.GetString("root") + // Initialize installed packages map + err := bpmlib.InitializeLocalPackageInformation(rootDir) + if err != nil { + log.Printf("Error: %s", err) + exitCode = 1 + return + } + // Get files files := currentFlagSet.Args() if len(files) == 0 { @@ -1160,6 +1228,14 @@ func compilePackage() { outputFd, _ := currentFlagSet.GetInt("output-fd") compilationJobs, _ := currentFlagSet.GetInt("jobs") + // Initialize installed packages map + err := bpmlib.InitializeLocalPackageInformation(rootDir) + if err != nil { + log.Printf("Error: %s", err) + exitCode = 1 + return + } + // Get files sourcePackages := currentFlagSet.Args() if len(sourcePackages) == 0 { @@ -1168,7 +1244,7 @@ func compilePackage() { } // Read local databases - err := bpmlib.ReadLocalDatabaseFiles() + err = bpmlib.ReadLocalDatabaseFiles() if err != nil { log.Printf("Error: could not read local databases: %s", err) exitCode = 1 @@ -1409,6 +1485,7 @@ func printUsage() { fmt.Println(" o, owner Show what packages own the specified paths") fmt.Println(" c, compile Compile source packages and convert them to binary ones") fmt.Println(" p, vercmp Compare package version numbers") + } func setupFlagsAndHelp(flagset *flag.FlagSet, usage, desc string, args []string) { diff --git a/src/bpmlib/databases.go b/src/bpmlib/databases.go index a6b97eb..78b12d5 100644 --- a/src/bpmlib/databases.go +++ b/src/bpmlib/databases.go @@ -398,7 +398,7 @@ func (entry *BPMDatabaseEntry) CreateReadableInfo(rootDir string, humanReadableS // 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: diff --git a/src/bpmlib/general.go b/src/bpmlib/general.go index 178cc3f..d408df2 100644 --- a/src/bpmlib/general.go +++ b/src/bpmlib/general.go @@ -53,7 +53,7 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason, 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 } @@ -77,7 +77,7 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason, 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 } @@ -113,7 +113,7 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason, 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 } @@ -423,7 +423,7 @@ func UpdatePackages(rootDir string, syncDatabase bool, allowDowngrades bool, ins comparison := CompareVersions(entry.Info.GetFullVersion(), installedInfo.GetFullVersion()) if (!allowDowngrades && comparison > 0) || (allowDowngrades && comparison != 0) { operation.AppendAction(&FetchPackageAction{ - InstallationReason: GetInstallationReason(pkg, rootDir), + InstallationReason: GetPackage(pkg, rootDir).LocalInfo.GetInstallationReason(), DatabaseEntry: entry, }) } diff --git a/src/bpmlib/installed_packages.go b/src/bpmlib/installed_packages.go index b616508..c360e90 100644 --- a/src/bpmlib/installed_packages.go +++ b/src/bpmlib/installed_packages.go @@ -11,9 +11,11 @@ import ( "gopkg.in/yaml.v3" ) +var persistentDataVersion int = 1 + var localPackageInformation map[string]map[string]*PackageInfo = make(map[string]map[string]*PackageInfo) -func initializeLocalPackageInformation(rootDir string) (err error) { +func InitializeLocalPackageInformation(rootDir string) (err error) { // Return if information is already initialized if _, ok := localPackageInformation[rootDir]; ok { return nil @@ -21,8 +23,24 @@ func initializeLocalPackageInformation(rootDir string) (err error) { tempPackageInformation := 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) @@ -61,7 +79,7 @@ func initializeLocalPackageInformation(rootDir string) (err error) { func GetInstalledPackages(rootDir string) (ret []string, err error) { // Initialize local package information - err = initializeLocalPackageInformation(rootDir) + err = InitializeLocalPackageInformation(rootDir) if err != nil { return nil, err } @@ -79,7 +97,7 @@ func GetInstalledPackages(rootDir string) (ret []string, err error) { func IsPackageInstalled(pkg, rootDir string) bool { // Initialize local package information - err := initializeLocalPackageInformation(rootDir) + err := InitializeLocalPackageInformation(rootDir) if err != nil { return false } @@ -131,7 +149,7 @@ func IsPackageProvided(pkg, rootDir string) bool { } func GetPackageInfo(pkg string, rootDir string) *PackageInfo { - err := initializeLocalPackageInformation(rootDir) + err := InitializeLocalPackageInformation(rootDir) if err != nil { return nil } @@ -244,13 +262,13 @@ func getPackageLocalInfo(pkg, rootDir string) PackageLocalInfo { installedDir := path.Join(rootDir, "var/lib/bpm/installed/") pkgDir := path.Join(installedDir, pkg) - files := path.Join(pkgDir, "local") + localInfoFile := path.Join(path.Join(pkgDir, "local")) - if _, err := os.Stat(files); os.IsNotExist(err) { + if _, err := os.Stat(localInfoFile); os.IsNotExist(err) { return localInfo } - file, err := os.Open(files) + file, err := os.Open(localInfoFile) if err != nil { return localInfo } @@ -263,3 +281,102 @@ func getPackageLocalInfo(pkg, rootDir string) PackageLocalInfo { 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"), 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()) + + // Generate default local package information file + if _, err := os.Stat(path.Join(pkgDir, "local")); err != nil && !os.IsNotExist(err) { + return err + } else if 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"), out, 0644) + 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")) + 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"), 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 +} diff --git a/src/bpmlib/operations.go b/src/bpmlib/operations.go index a2d04a9..0e81e74 100644 --- a/src/bpmlib/operations.go +++ b/src/bpmlib/operations.go @@ -223,7 +223,7 @@ 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 { + if getPackageLocalInfo(pkg.Name, operation.RootDir).GetInstallationReason() != InstallationReasonManual { continue } @@ -701,19 +701,13 @@ 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, operation.RootDir, verbose, true) } else { - err = installPackage(fileToInstall, operation.RootDir, verbose, force) + err = installPackage(fileToInstall, value.InstallationReason, 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!") diff --git a/src/bpmlib/packages.go b/src/bpmlib/packages.go index ae81dd6..faed442 100644 --- a/src/bpmlib/packages.go +++ b/src/bpmlib/packages.go @@ -78,8 +78,9 @@ type PackageFileEntry struct { } type PackageLocalInfo struct { - InstalledOn int64 `yaml:"installed_on"` - LastUpdatedOn int64 `yaml:"last_updated_on"` + InstallationReason string `yaml:"installation_reason"` + InstalledOn int64 `yaml:"installed_on"` + LastUpdatedOn int64 `yaml:"last_updated_on"` } func (pkg *BPMPackage) GetInstalledSize() int64 { @@ -130,19 +131,8 @@ 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() { - return InstallationReasonManual - } - b, err := os.ReadFile(path.Join(pkgDir, "installation_reason")) - if err != nil { - return InstallationReasonUnknown - } - reason := strings.TrimSpace(string(b)) - - switch reason { +func (localInfo PackageLocalInfo) GetInstallationReason() InstallationReason { + switch localInfo.InstallationReason { case "manual": return InstallationReasonManual case "dependency": @@ -154,16 +144,6 @@ func GetInstallationReason(pkg, rootDir string) InstallationReason { } } -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) { if _, err := os.Stat(filename); os.IsNotExist(err) { return "", err @@ -623,7 +603,7 @@ func (pkgInfo *PackageInfo) CreateReadableInfo(rootDir string) string { // 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: @@ -802,7 +782,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, rootDir string, verbose, force bool) error { if _, err := os.Stat(filename); os.IsNotExist(err) { return err } @@ -992,17 +972,9 @@ func installPackage(filename, rootDir string, verbose, force bool) error { localInfo.InstalledOn = time.Now().Unix() } localInfo.LastUpdatedOn = time.Now().Unix() + localInfo.InstallationReason = string(installationReason) - localFile, err := os.OpenFile(path.Join(pkgDir, "local"), 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 - } + SetPackageLocalInfo(bpmpkg.PkgInfo.Name, localInfo, rootDir) // Save remove package scripts packageScripts, err := ReadPackageScripts(filename) @@ -1043,8 +1015,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 } @@ -1164,7 +1144,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 }