From 5085981f5225c7bcc915d5a34f2908b9d85ead3b Mon Sep 17 00:00:00 2001 From: EnumDev Date: Sat, 26 Apr 2025 15:29:45 +0300 Subject: [PATCH] Allow split source package compilation --- src/bpm/main.go | 6 +- src/bpmlib/compilation.go | 330 +++++++++++++++++++++++--------------- src/bpmlib/general.go | 10 ++ src/bpmlib/operations.go | 4 +- src/bpmlib/packages.go | 39 +++-- 5 files changed, 238 insertions(+), 151 deletions(-) diff --git a/src/bpm/main.go b/src/bpm/main.go index b3dcf57..7560c24 100644 --- a/src/bpm/main.go +++ b/src/bpm/main.go @@ -671,12 +671,14 @@ func resolveCommand() { outputFilename = path.Join(outputFilename, fmt.Sprintf("%s-%s-%d.bpm", bpmpkg.PkgInfo.Name, bpmpkg.PkgInfo.Version, bpmpkg.PkgInfo.Revision)) } - err = bpmlib.CompileSourcePackage(sourcePackage, outputFilename, skipChecks) + outputBpmPackages, err := bpmlib.CompileSourcePackage(sourcePackage, outputFilename, skipChecks) if err != nil { log.Fatalf("Error: could not compile source package (%s): %s", sourcePackage, err) } - fmt.Printf("Package (%s) was successfully compiled! Binary package generated at: %s\n", sourcePackage, outputFilename) + for k, v := range outputBpmPackages { + fmt.Printf("Package (%s) was successfully compiled! Binary package generated at: %s\n", k, v) + } // Remove unused packages if installSrcPkgDepends && len(unmetDepends) > 0 { diff --git a/src/bpmlib/compilation.go b/src/bpmlib/compilation.go index e0e0f99..370feb9 100644 --- a/src/bpmlib/compilation.go +++ b/src/bpmlib/compilation.go @@ -15,28 +15,31 @@ import ( var rootCompilationUID = "65534" var rootCompilationGID = "65534" -func CompileSourcePackage(archiveFilename, outputFilename string, skipChecks bool) (err error) { +func CompileSourcePackage(archiveFilename, outputFilename string, skipChecks bool) (outputBpmPackages map[string]string, err error) { + // Initialize map + outputBpmPackages = make(map[string]string) + // Read BPM archive bpmpkg, err := ReadPackage(archiveFilename) if err != nil { - return err + return nil, err } // Ensure package type is 'source' if bpmpkg.PkgInfo.Type != "source" { - return errors.New("cannot compile a non-source package") + return nil, errors.New("cannot compile a non-source package") } // Read compilation options file in current directory compilationOptions, err := readCompilationOptionsFile() if err != nil { - return err + return nil, err } // Get HOME directory homeDir, err := os.UserHomeDir() if err != nil { - return err + return nil, err } // Get UID and GID to use for compilation @@ -44,11 +47,11 @@ func CompileSourcePackage(archiveFilename, outputFilename string, skipChecks boo 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) + return nil, 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) + return nil, fmt.Errorf("could not convert GID '%s' to int", rootCompilationGID) } uid = int(_uid) gid = int(_gid) @@ -63,26 +66,26 @@ func CompileSourcePackage(archiveFilename, outputFilename string, skipChecks boo if _, err := os.Stat(tempDirectory); err == nil { err := os.RemoveAll(tempDirectory) if err != nil { - return err + return nil, err } } // Create temporary directory err = os.MkdirAll(tempDirectory, 0755) if err != nil { - return err + return nil, err } // Change temporary directory owner err = os.Chown(tempDirectory, uid, gid) if err != nil { - return err + return nil, err } // Extract source.sh file err = extractTarballFile(archiveFilename, "source.sh", tempDirectory, uid, gid) if err != nil { - return err + return nil, err } // Get package scripts and extract them @@ -90,26 +93,26 @@ func CompileSourcePackage(archiveFilename, outputFilename string, skipChecks boo for _, script := range packageScripts { err = extractTarballFile(archiveFilename, script, tempDirectory, uid, gid) if err != nil { - return err + return nil, err } } // Extract source files err = extractTarballDirectory(archiveFilename, "source-files", tempDirectory, uid, gid) if err != nil { - return err + return nil, err } // Create source directory err = os.Mkdir(path.Join(tempDirectory, "source"), 0755) if err != nil { - return err + return nil, err } // Change source directory owner err = os.Chown(path.Join(tempDirectory, "source"), uid, gid) if err != nil { - return err + return nil, err } // Setup environment for commands @@ -145,7 +148,7 @@ func CompileSourcePackage(archiveFilename, outputFilename string, skipChecks boo cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)} err = cmd.Run() if err != nil { - return err + return nil, err } // Execute check function in source.sh script if not skipping checks @@ -164,129 +167,192 @@ func CompileSourcePackage(archiveFilename, outputFilename string, skipChecks boo cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)} err = cmd.Run() if err != nil { - return err + return nil, err } } - // Remove output directory if it already exists - if _, err := os.Stat(path.Join(tempDirectory, "output")); err == nil { - err := os.RemoveAll(path.Join(tempDirectory, "output")) + // Variable that will be used later + isSplitPkg := true + + // Get all packages to compile + packagesToCompile := bpmpkg.PkgInfo.SplitPackages + if len(packagesToCompile) == 0 { + packagesToCompile = append(packagesToCompile, bpmpkg.PkgInfo) + isSplitPkg = false + } + + // Compile each package + for _, pkg := range packagesToCompile { + // Get package function name + packageFunctionName := "package" + if isSplitPkg { + packageFunctionName = "package_" + pkg.Name + } + + // Remove output directory if it already exists + if _, err := os.Stat(path.Join(tempDirectory, "output")); err == nil { + err := os.RemoveAll(path.Join(tempDirectory, "output")) + if err != nil { + return nil, err + } + } + + // Create new output directory + err = os.Mkdir(path.Join(tempDirectory, "output"), 0755) if err != nil { - return err + return nil, err } + + // Change output directory owner + err = os.Chown(path.Join(tempDirectory, "output"), uid, gid) + if err != nil { + return nil, err + } + + // Execute package function in source.sh script and generate package file list + cmd = exec.Command("bash", "-c", + "set -a\n"+ // Source and export functions and variables in source.sh script + ". \"${BPM_WORKDIR}\"/source.sh\n"+ + "set +a\n"+ + "echo \"Running "+packageFunctionName+"() function\"\n"+ + "( cd \"$BPM_SOURCE\" && fakeroot -s \"$BPM_WORKDIR\"/fakeroot_file bash -e -c '"+packageFunctionName+"' ) || exit 1\n"+ // Run package() function + "fakeroot -i \"$BPM_WORKDIR\"/fakeroot_file find \"$BPM_OUTPUT\" -mindepth 1 -printf \"%P %#m %U %G %s\\n\" > \"$BPM_WORKDIR\"/pkg.files") // Create package file list + cmd.Dir = tempDirectory + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Env = env + cmd.SysProcAttr = &syscall.SysProcAttr{} + cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)} + err = cmd.Run() + if err != nil { + return nil, err + } + + // Create gzip-compressed archive for the package files + cmd = exec.Command("bash", "-c", "find output -printf \"%P\\n\" | fakeroot -i \"$BPM_WORKDIR\"/fakeroot_file tar -czf files.tar.gz --no-recursion -C output -T -") + cmd.Dir = tempDirectory + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Env = env + cmd.SysProcAttr = &syscall.SysProcAttr{} + cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)} + err = cmd.Run() + if err != nil { + return nil, fmt.Errorf("files.tar.gz archive could not be created: %s", err) + } + + // Clone source package info + var pkgInfo PackageInfo + if !isSplitPkg { + pkgInfo = *bpmpkg.PkgInfo + } else { + pkgInfo = *pkg + + // Ensure required fields are set + if strings.TrimSpace(pkgInfo.Name) == "" { + return nil, fmt.Errorf("split package name is empty") + } + + // Copy data from main source package + if pkgInfo.Description == "" { + pkgInfo.Description = bpmpkg.PkgInfo.Description + } + pkgInfo.Version = bpmpkg.PkgInfo.Version + pkgInfo.Revision = bpmpkg.PkgInfo.Revision + pkgInfo.Url = bpmpkg.PkgInfo.Url + if pkgInfo.License == "" { + pkgInfo.License = bpmpkg.PkgInfo.License + } + } + + // Set package type to binary + pkgInfo.Type = "binary" + + // Set package architecture + if val, ok := compilationOptions["ARCH"]; ok { + pkgInfo.Arch = val + } else { + pkgInfo.Arch = GetArch() + } + + // Remove source package specific fields + pkgInfo.MakeDepends = nil + pkgInfo.SplitPackages = nil + + // Marshal package info + pkgInfoBytes, err := yaml.Marshal(pkgInfo) + if err != nil { + return nil, err + } + pkgInfoBytes = append(pkgInfoBytes, '\n') + + // Create pkg.info file + err = os.WriteFile(path.Join(tempDirectory, "pkg.info"), pkgInfoBytes, 0644) + if err != nil { + return nil, err + } + + // Change pkg.info file owner + err = os.Chown(path.Join(tempDirectory, "pkg.info"), 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, packageScripts...) // Package scripts + + // Create final BPM archive + cmd = exec.Command("bash", "-c", "tar -cf final-archive.bpm --owner=0 --group=0 -C \"$BPM_WORKDIR\" "+strings.Join(bpmArchiveFiles, " ")) + cmd.Dir = tempDirectory + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + currentDir, err := os.Getwd() + if err != nil { + return nil, err + } + cmd.Env = append(env, "CURRENT_DIR="+currentDir) + cmd.SysProcAttr = &syscall.SysProcAttr{} + cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)} + err = cmd.Run() + if err != nil { + return nil, fmt.Errorf("BPM archive could not be created: %s", err) + } + + // Remove pkg.info file + err = os.Remove(path.Join(tempDirectory, "pkg.info")) + if err != nil { + return nil, err + } + + // Set output filename if split package + if len(bpmpkg.PkgInfo.SplitPackages) != 1 { + // Get current working directory + workdir, err := os.Getwd() + if err != nil { + return nil, fmt.Errorf("could not get working directory: %s", err) + } + + outputFilename = path.Join(workdir, fmt.Sprintf("%s-%s-%d.bpm", pkgInfo.Name, pkgInfo.Version, pkgInfo.Revision)) + } + + // Move final BPM archive + err = os.Rename(path.Join(tempDirectory, "final-archive.bpm"), outputFilename) + if err != nil { + return nil, err + } + + // Set final BPM archive owner + err = os.Chown(outputFilename, os.Getuid(), os.Getgid()) + if err != nil { + return nil, err + } + + outputBpmPackages[pkgInfo.Name] = outputFilename } - // Create new output directory - err = os.Mkdir(path.Join(tempDirectory, "output"), 0755) - if err != nil { - return err - } - - // Change output directory owner - err = os.Chown(path.Join(tempDirectory, "output"), uid, gid) - if err != nil { - return err - } - - // Execute package function in source.sh script and generate package file list - cmd = exec.Command("bash", "-c", - "set -a\n"+ // Source and export functions and variables in source.sh script - ". \"${BPM_WORKDIR}\"/source.sh\n"+ - "set +a\n"+ - "echo \"Running package() function\"\n"+ - "( cd \"$BPM_SOURCE\" && fakeroot -s \"$BPM_WORKDIR\"/fakeroot_file bash -e -c 'package' ) || exit 1\n"+ // Run package() function - "fakeroot -i \"$BPM_WORKDIR\"/fakeroot_file find \"$BPM_OUTPUT\" -mindepth 1 -printf \"%P %#m %U %G %s\\n\" > \"$BPM_WORKDIR\"/pkg.files") // Create package file list - cmd.Dir = tempDirectory - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - cmd.Env = env - cmd.SysProcAttr = &syscall.SysProcAttr{} - cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)} - err = cmd.Run() - if err != nil { - return err - } - - // Create gzip-compressed archive for the package files - cmd = exec.Command("bash", "-c", "find output -printf \"%P\\n\" | fakeroot -i \"$BPM_WORKDIR\"/fakeroot_file tar -czf files.tar.gz --no-recursion -C output -T -") - cmd.Dir = tempDirectory - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - cmd.Env = env - cmd.SysProcAttr = &syscall.SysProcAttr{} - cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)} - err = cmd.Run() - if err != nil { - return fmt.Errorf("files.tar.gz archive could not be created: %s", err) - } - - // Copy pkgInfo struct - pkgInfo := bpmpkg.PkgInfo - - // Set package type to binary - pkgInfo.Type = "binary" - - // Set package architecture - if val, ok := compilationOptions["ARCH"]; ok { - pkgInfo.Arch = val - } else { - pkgInfo.Arch = GetArch() - } - - // Marshal package info - pkgInfoBytes, err := yaml.Marshal(pkgInfo) - if err != nil { - return err - } - pkgInfoBytes = append(pkgInfoBytes, '\n') - - // Create pkg.info file - err = os.WriteFile(path.Join(tempDirectory, "pkg.info"), pkgInfoBytes, 0644) - if err != nil { - return err - } - - // Change pkg.info file owner - err = os.Chown(path.Join(tempDirectory, "pkg.info"), uid, gid) - if err != nil { - return 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, packageScripts...) // Package scripts - - // Create final BPM archive - cmd = exec.Command("bash", "-c", "tar -cf final-archive.bpm --owner=0 --group=0 -C \"$BPM_WORKDIR\" "+strings.Join(bpmArchiveFiles, " ")) - cmd.Dir = tempDirectory - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - currentDir, err := os.Getwd() - if err != nil { - return err - } - cmd.Env = append(env, "CURRENT_DIR="+currentDir) - cmd.SysProcAttr = &syscall.SysProcAttr{} - cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)} - err = cmd.Run() - if err != nil { - return fmt.Errorf("BPM archive could not be created: %s", err) - } - - // Move final BPM archive - err = os.Rename(path.Join(tempDirectory, "final-archive.bpm"), outputFilename) - if err != nil { - return err - } - - // Set final BPM archive owner - err = os.Chown(outputFilename, os.Getuid(), os.Getgid()) - if err != nil { - return err - } - - return nil + return outputBpmPackages, nil } func readCompilationOptionsFile() (options map[string]string, err error) { diff --git a/src/bpmlib/general.go b/src/bpmlib/general.go index c88ac32..4c7657a 100644 --- a/src/bpmlib/general.go +++ b/src/bpmlib/general.go @@ -38,6 +38,11 @@ func InstallPackages(rootDir string, installationReason InstallationReason, rein if reinstallMethod == ReinstallMethodNone && IsPackageInstalled(bpmpkg.PkgInfo.Name, rootDir) && GetPackageInfo(bpmpkg.PkgInfo.Name, rootDir).GetFullVersion() == bpmpkg.PkgInfo.GetFullVersion() { continue } + + if bpmpkg.PkgInfo.Type == "source" && len(bpmpkg.PkgInfo.SplitPackages) != 0 { + return nil, fmt.Errorf("direct source package installation has not been implemented") + } + operation.AppendAction(&InstallPackageAction{ File: pkg, IsDependency: false, @@ -63,6 +68,11 @@ func InstallPackages(rootDir string, installationReason InstallationReason, rein if reinstallMethod == ReinstallMethodNone && IsPackageInstalled(entry.Info.Name, rootDir) && GetPackageInfo(entry.Info.Name, rootDir).GetFullVersion() == entry.Info.GetFullVersion() { continue } + + if entry.Info.Type == "source" && len(entry.Info.SplitPackages) != 0 { + return nil, fmt.Errorf("direct source package installation has not been implemented") + } + operation.AppendAction(&FetchPackageAction{ IsDependency: false, RepositoryEntry: entry, diff --git a/src/bpmlib/operations.go b/src/bpmlib/operations.go index 1078be9..b3e011b 100644 --- a/src/bpmlib/operations.go +++ b/src/bpmlib/operations.go @@ -486,13 +486,13 @@ func (operation *BPMOperation) Execute(verbose, force bool) error { } // Compile source package - err = CompileSourcePackage(value.File, outputFilename, false) + outputBpmPackages, err := CompileSourcePackage(value.File, outputFilename, false) if err != nil { return fmt.Errorf("could not compile source package (%s): %s\n", value.File, err) } // Set values - fileToInstall = outputFilename + fileToInstall = outputBpmPackages[bpmpkg.PkgInfo.Name] bpmpkg, err = ReadPackage(outputFilename) if err != nil { return fmt.Errorf("could not read package (%s): %s\n", fileToInstall, err) diff --git a/src/bpmlib/packages.go b/src/bpmlib/packages.go index 7f29197..fd48021 100644 --- a/src/bpmlib/packages.go +++ b/src/bpmlib/packages.go @@ -24,21 +24,22 @@ type BPMPackage struct { } type PackageInfo struct { - Name string `yaml:"name,omitempty"` - Description string `yaml:"description,omitempty"` - Version string `yaml:"version,omitempty"` - Revision int `yaml:"revision,omitempty"` - Url string `yaml:"url,omitempty"` - License string `yaml:"license,omitempty"` - Arch string `yaml:"architecture,omitempty"` - Type string `yaml:"type,omitempty"` - Keep []string `yaml:"keep,omitempty"` - Depends []string `yaml:"depends,omitempty"` - MakeDepends []string `yaml:"make_depends,omitempty"` - OptionalDepends []string `yaml:"optional_depends,omitempty"` - Conflicts []string `yaml:"conflicts,omitempty"` - Replaces []string `yaml:"replaces,omitempty"` - Provides []string `yaml:"provides,omitempty"` + Name string `yaml:"name,omitempty"` + Description string `yaml:"description,omitempty"` + Version string `yaml:"version,omitempty"` + Revision int `yaml:"revision,omitempty"` + Url string `yaml:"url,omitempty"` + License string `yaml:"license,omitempty"` + Arch string `yaml:"architecture,omitempty"` + Type string `yaml:"type,omitempty"` + Keep []string `yaml:"keep,omitempty"` + Depends []string `yaml:"depends,omitempty"` + MakeDepends []string `yaml:"make_depends,omitempty"` + OptionalDepends []string `yaml:"optional_depends,omitempty"` + Conflicts []string `yaml:"conflicts,omitempty"` + Replaces []string `yaml:"replaces,omitempty"` + Provides []string `yaml:"provides,omitempty"` + SplitPackages []*PackageInfo `yaml:"split_packages,omitempty"` } type PackageFileEntry struct { @@ -403,6 +404,7 @@ func ReadPackageInfo(contents string) (*PackageInfo, error) { Conflicts: make([]string, 0), Replaces: make([]string, 0), Provides: make([]string, 0), + SplitPackages: make([]*PackageInfo, 0), } err := yaml.Unmarshal([]byte(contents), &pkgInfo) if err != nil { @@ -460,6 +462,13 @@ func CreateReadableInfo(showArchitecture, showType, showPackageRelations bool, p appendArray("Provided packages", pkgInfo.Provides) appendArray("Replaces packages", pkgInfo.Replaces) } + 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) + } ret = append(ret, "Installation Reason: "+string(GetInstallationReason(pkgInfo.Name, rootDir))) return strings.Join(ret, "\n") }