4 Commits
7 changed files with 255 additions and 111 deletions
+1 -1
View File
@@ -21,7 +21,7 @@ import (
/* A simple-to-use package manager */ /* A simple-to-use package manager */
/* ------------------------------------------------------- */ /* ------------------------------------------------------- */
var bpmVer = "0.5.0" var bpmVer = "0.6.0"
var subcommand = "help" var subcommand = "help"
var subcommandArgs []string var subcommandArgs []string
+140 -47
View File
@@ -1,11 +1,15 @@
package bpmlib package bpmlib
import ( import (
"crypto/sha256"
"encoding/hex"
"errors" "errors"
"fmt" "fmt"
"io"
"os" "os"
"os/exec" "os/exec"
"path" "path"
"slices"
"strconv" "strconv"
"strings" "strings"
"syscall" "syscall"
@@ -31,12 +35,6 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
return nil, 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 nil, err
}
// Get HOME directory // Get HOME directory
homeDir, err := os.UserHomeDir() homeDir, err := os.UserHomeDir()
if err != nil { if err != nil {
@@ -122,6 +120,12 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
return nil, err return nil, err
} }
// Download files
err = downloadPackageFiles(bpmpkg.PkgInfo, tempDirectory)
if err != nil {
return nil, err
}
// Setup environment for commands // Setup environment for commands
env := os.Environ() env := os.Environ()
env = append(env, "HOME="+tempDirectory) env = append(env, "HOME="+tempDirectory)
@@ -131,12 +135,7 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
env = append(env, "BPM_PKG_NAME="+bpmpkg.PkgInfo.Name) env = append(env, "BPM_PKG_NAME="+bpmpkg.PkgInfo.Name)
env = append(env, "BPM_PKG_VERSION="+bpmpkg.PkgInfo.Version) env = append(env, "BPM_PKG_VERSION="+bpmpkg.PkgInfo.Version)
env = append(env, "BPM_PKG_REVISION="+strconv.Itoa(bpmpkg.PkgInfo.Revision)) env = append(env, "BPM_PKG_REVISION="+strconv.Itoa(bpmpkg.PkgInfo.Revision))
// Check for architecture override in compilation options env = append(env, "BPM_PKG_ARCH="+bpmpkg.PkgInfo.OutputArch)
if val, ok := compilationOptions["ARCH"]; ok {
env = append(env, "BPM_PKG_ARCH="+val)
} else {
env = append(env, "BPM_PKG_ARCH="+GetArch())
}
env = append(env, CompilationBPMConfig.CompilationEnvironment...) env = append(env, CompilationBPMConfig.CompilationEnvironment...)
// Execute prepare and build functions in source.sh script // Execute prepare and build functions in source.sh script
@@ -259,14 +258,12 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
pkgInfo.Type = "binary" pkgInfo.Type = "binary"
// Set package architecture // Set package architecture
if val, ok := compilationOptions["ARCH"]; ok { pkgInfo.Arch = pkg.OutputArch
pkgInfo.Arch = val pkgInfo.OutputArch = ""
} else {
pkgInfo.Arch = GetArch()
}
// Remove split package field // Remove split package and downloads fields
pkgInfo.SplitPackages = nil pkgInfo.SplitPackages = nil
pkgInfo.Downloads = nil
// Marshal package info // Marshal package info
pkgInfoBytes, err := yaml.Marshal(pkgInfo) pkgInfoBytes, err := yaml.Marshal(pkgInfo)
@@ -338,46 +335,142 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
return outputBpmPackages, nil return outputBpmPackages, nil
} }
func readCompilationOptionsFile() (options map[string]string, err error) { func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string) error {
// Initialize options map for _, download := range pkgInfo.Downloads {
options = make(map[string]string) // Replace variables in download url
downloadUrl := download.Url
downloadUrl = os.Expand(downloadUrl, func(s string) string {
switch s {
case "BPM_PKG_VERSION":
return pkgInfo.Version
case "BPM_PKG_NAME":
return pkgInfo.Name
default:
return ""
}
})
// Check if file compilation options file exists switch download.Type {
stat, err := os.Stat(".compilation-options") case "", "file":
filepath := path.Join(tempDirectory, path.Base(downloadUrl))
if download.Filepath != "" {
filepath = download.Filepath
}
err := downloadFile(downloadUrl, filepath, 0644)
if err != nil { if err != nil {
return nil, nil return err
} }
// Ensure it is a regular file if download.Checksum != "skip" {
if !stat.Mode().IsRegular() { f, err := os.Open(filepath)
return nil, fmt.Errorf("%s is not a regular file", stat.Name())
}
// Read file data
data, err := os.ReadFile(stat.Name())
if err != nil { if err != nil {
return nil, err return err
}
defer f.Close()
h := sha256.New()
_, err = io.Copy(h, f)
if err != nil {
return err
} }
for _, line := range strings.Split(string(data), "\n") { if hex.EncodeToString(h.Sum(nil)) != download.Checksum {
// Trim line fmt.Printf("Downloaded file checksum: %x\n", h.Sum(nil))
line = strings.TrimSpace(line) return fmt.Errorf("downloaded file checksums did not match")
}
// Skip empty lines } else {
if line == "" { fmt.Println("Skipping checksum checking...")
continue
} }
// Split line if !download.NoExtract && (strings.Contains(filepath, ".tar") || strings.HasSuffix(filepath, ".tgz")) {
split := strings.SplitN(line, "=", 2) cmd := exec.Command("tar", "xvf", filepath, "--strip-components="+strconv.Itoa(download.ExtractStripComponents))
if download.ExtractToBPMSource {
// Throw error if line isn't valid cmd.Args = append(cmd.Args, "-C", path.Join(tempDirectory, "source"))
if len(split) < 2 {
return nil, fmt.Errorf("invalid line in compilation-options file: '%s'", line)
} }
options[split[0]] = split[1] cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
return err
}
} else if !download.NoExtract && strings.HasSuffix(filepath, ".zip") {
cmd := exec.Command("unzip", filepath)
if download.ExtractToBPMSource {
cmd.Args = append(cmd.Args, "-d", path.Join(tempDirectory, "source"))
} else {
err := os.Mkdir(path.Join(tempDirectory, strings.TrimSuffix(path.Base(filepath), ".zip")), 0755)
if err != nil {
return err
} }
return options, nil cmd.Args = append(cmd.Args, "-d", path.Join(tempDirectory, strings.TrimSuffix(path.Base(filepath), ".zip")))
}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
return err
}
}
case "git":
// Replace variables in git branch
gitBranch := download.GitBranch
gitBranch = os.Expand(gitBranch, func(s string) string {
switch s {
case "BPM_PKG_VERSION":
return pkgInfo.Version
case "BPM_PKG_NAME":
return pkgInfo.Name
default:
return ""
}
})
cmd := exec.Command("git", "clone", "--depth=1", downloadUrl)
if gitBranch != "" {
cmd.Args = slices.Insert(cmd.Args, len(cmd.Args)-1, "--branch="+gitBranch)
}
if download.ExtractToBPMSource {
cmd.Args = append(cmd.Args, path.Join(tempDirectory, "source"))
}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
return err
}
if download.Checksum != "skip" {
cmd := exec.Command("git", "rev-parse", "HEAD")
if download.ExtractToBPMSource {
cmd.Dir = path.Join(tempDirectory, "source")
} else {
cmd.Dir = path.Join(tempDirectory, strings.TrimSuffix(path.Base(downloadUrl), ".git"))
}
branchChecksum, err := cmd.Output()
if err != nil {
return err
}
if strings.TrimSpace(string(branchChecksum)) != download.Checksum {
fmt.Printf("Git branch checksum: %s\n", strings.TrimSpace(string(branchChecksum)))
return fmt.Errorf("Cloned git repository checksum did not match")
}
} else {
fmt.Println("Skipping checksum checking...")
}
default:
return fmt.Errorf("unknown download type (%s)", download.Type)
}
}
return nil
} }
+8 -21
View File
@@ -3,8 +3,6 @@ package bpmlib
import ( import (
"errors" "errors"
"fmt" "fmt"
"io"
"net/http"
"net/url" "net/url"
"os" "os"
"path" "path"
@@ -132,14 +130,10 @@ func (db *configDatabase) SyncLocalDatabaseFile() error {
} }
// Retrieve data from URL // Retrieve data from URL
resp, err := http.Get(u) buffer, err := retrieveUrlData(u)
if err != nil { if err != nil {
return err return err
} }
defer resp.Body.Close()
// Load data into byte buffer
buffer, err := io.ReadAll(resp.Body)
// Unmarshal data to ensure it is a valid BPM database // Unmarshal data to ensure it is a valid BPM database
err = yaml.Unmarshal(buffer, &BPMDatabase{}) err = yaml.Unmarshal(buffer, &BPMDatabase{})
@@ -233,30 +227,23 @@ func ResolveVirtualPackage(vpkg string) *BPMDatabaseEntry {
} }
func (db *BPMDatabase) FetchPackage(pkg string) (string, error) { func (db *BPMDatabase) FetchPackage(pkg string) (string, error) {
// Check if package exists in database
if !db.ContainsPackage(pkg) { if !db.ContainsPackage(pkg) {
return "", errors.New("could not fetch package '" + pkg + "'") return "", errors.New("could not fetch package '" + pkg + "'")
} }
// Get package url from database
entry := db.Entries[pkg] entry := db.Entries[pkg]
URL, err := url.JoinPath(db.Source, entry.Filepath) u, err := url.JoinPath(db.Source, entry.Filepath)
if err != nil { if err != nil {
return "", err return "", err
} }
resp, err := http.Get(URL)
if err != nil {
return "", err
}
defer resp.Body.Close()
err = os.MkdirAll("/var/cache/bpm/fetched/", 0755) // Download package from url
err = downloadFile(u, path.Join("/var/cache/bpm/fetched/", path.Base(entry.Filepath)), 0644)
if err != nil { if err != nil {
return "", err return "", err
} }
out, err := os.Create("/var/cache/bpm/fetched/" + path.Base(entry.Filepath))
if err != nil {
return "", err
}
defer out.Close()
_, err = io.Copy(out, resp.Body) return path.Join("/var/cache/bpm/fetched/", path.Base(entry.Filepath)), nil
return "/var/cache/bpm/fetched/" + path.Base(entry.Filepath), nil
} }
+5 -5
View File
@@ -164,14 +164,14 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
if rootDir != "/" { if rootDir != "/" {
sourcePackages := make([]string, 0) sourcePackages := make([]string, 0)
for _, action := range operation.Actions { for _, action := range operation.Actions {
switch action.(type) { switch action := action.(type) {
case *InstallPackageAction: case *InstallPackageAction:
if action.(*InstallPackageAction).BpmPackage.PkgInfo.Type == "source" { if action.BpmPackage.PkgInfo.Type == "source" {
sourcePackages = append(sourcePackages, action.(*InstallPackageAction).BpmPackage.PkgInfo.Name) sourcePackages = append(sourcePackages, action.BpmPackage.PkgInfo.Name)
} }
case *FetchPackageAction: case *FetchPackageAction:
if action.(*FetchPackageAction).DatabaseEntry.Info.Type == "source" { if action.DatabaseEntry.Info.Type == "source" {
sourcePackages = append(sourcePackages, action.(*FetchPackageAction).DatabaseEntry.Info.Name) sourcePackages = append(sourcePackages, action.DatabaseEntry.Info.Name)
} }
} }
} }
+2 -6
View File
@@ -1,7 +1,6 @@
package bpmlib package bpmlib
import ( import (
"errors"
"fmt" "fmt"
"os" "os"
"path" "path"
@@ -169,14 +168,11 @@ func GetAllPackageFiles(rootDir string, excludePackages ...string) (map[string][
} }
bpmpkg := GetPackage(pkgName, rootDir) bpmpkg := GetPackage(pkgName, rootDir)
if bpmpkg == nil { if bpmpkg == nil {
return nil, errors.New(fmt.Sprintf("could not get BPM package (%s)", pkgName)) return nil, fmt.Errorf("could not get BPM package (%s)", pkgName)
} }
for _, entry := range bpmpkg.PkgFiles { for _, entry := range bpmpkg.PkgFiles {
if _, ok := ret[entry.Path]; ok {
ret[entry.Path] = append(ret[entry.Path], bpmpkg) ret[entry.Path] = append(ret[entry.Path], bpmpkg)
} else {
ret[entry.Path] = []*BPMPackage{bpmpkg}
}
} }
} }
+61
View File
@@ -0,0 +1,61 @@
package bpmlib
import (
"fmt"
"io"
"net/http"
"os"
"path"
"strings"
)
func retrieveUrlData(u string) ([]byte, error) {
resp, err := http.Get(u)
if err != nil {
return nil, err
}
defer resp.Body.Close()
// Load data into byte buffer
buffer, err := io.ReadAll(resp.Body)
return buffer, nil
}
func downloadFile(u, filepath string, perm os.FileMode) error {
if strings.HasSuffix(filepath, "/") {
return fmt.Errorf("Filepath must not end in '/'")
}
data, err := retrieveUrlData(u)
if err != nil {
return err
}
// Create parent directories
err = os.MkdirAll(path.Dir(filepath), 0755)
if err != nil {
return err
}
// Create file
file, err := os.Create(filepath)
if err != nil {
return err
}
defer file.Close()
// Copy data
_, err = file.Write(data)
if err != nil {
return err
}
// Set file permissions
err = file.Chmod(perm)
if err != nil {
return err
}
return nil
}
+19 -12
View File
@@ -26,13 +26,14 @@ type BPMPackage struct {
} }
type PackageInfo struct { type PackageInfo struct {
Name string `yaml:"name,omitempty"` Name string `yaml:"name"`
Description string `yaml:"description,omitempty"` Description string `yaml:"description"`
Version string `yaml:"version,omitempty"` Version string `yaml:"version"`
Revision int `yaml:"revision,omitempty"` Revision int `yaml:"revision,omitempty"`
Url string `yaml:"url,omitempty"` Url string `yaml:"url,omitempty"`
License string `yaml:"license,omitempty"` License string `yaml:"license,omitempty"`
Arch string `yaml:"architecture,omitempty"` Arch string `yaml:"architecture,omitempty"`
OutputArch string `yaml:"output_architecture,omitempty"`
Type string `yaml:"type,omitempty"` Type string `yaml:"type,omitempty"`
Keep []string `yaml:"keep,omitempty"` Keep []string `yaml:"keep,omitempty"`
Depends []string `yaml:"depends,omitempty"` Depends []string `yaml:"depends,omitempty"`
@@ -41,9 +42,21 @@ type PackageInfo struct {
Conflicts []string `yaml:"conflicts,omitempty"` Conflicts []string `yaml:"conflicts,omitempty"`
Replaces []string `yaml:"replaces,omitempty"` Replaces []string `yaml:"replaces,omitempty"`
Provides []string `yaml:"provides,omitempty"` Provides []string `yaml:"provides,omitempty"`
Downloads []PackageDownload `yaml:"downloads,omitempty"`
SplitPackages []*PackageInfo `yaml:"split_packages,omitempty"` SplitPackages []*PackageInfo `yaml:"split_packages,omitempty"`
} }
type PackageDownload struct {
Url string `yaml:"url"`
Type string `yaml:"type"`
NoExtract bool `yaml:"no_extract"`
ExtractToBPMSource bool `yaml:"extract_to_bpm_source"`
ExtractStripComponents int `yaml:"extract_strip_components"`
GitBranch string `yaml:"git_branch"`
Filepath string `yaml:"filepath,omitempty"`
Checksum string `yaml:"checksum"`
}
type PackageFileEntry struct { type PackageFileEntry struct {
Path string Path string
OctalPerms uint32 OctalPerms uint32
@@ -177,10 +190,10 @@ func ReadPackage(filename string) (*BPMPackage, error) {
} }
file, err := os.Open(filename) file, err := os.Open(filename)
defer file.Close()
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer file.Close()
tr := tar.NewReader(file) tr := tar.NewReader(file)
for { for {
@@ -303,14 +316,6 @@ func ReadPackageScripts(filename string) (map[string]string, error) {
return ret, nil return ret, nil
} }
type packageOperation uint8
const (
packageOperationInstall packageOperation = 0
packageOperationUpdate = 1
packageOperationRemove = 2
)
func executePackageScript(pkg, rootDir string, verbose bool, packageScript string) error { func executePackageScript(pkg, rootDir string, verbose bool, packageScript string) error {
var bpmpkg *BPMPackage var bpmpkg *BPMPackage
var err error var err error
@@ -420,6 +425,7 @@ func ReadPackageInfo(contents string) (*PackageInfo, error) {
Url: "", Url: "",
License: "", License: "",
Arch: "", Arch: "",
OutputArch: GetArch(),
Type: "", Type: "",
Keep: make([]string, 0), Keep: make([]string, 0),
Depends: make([]string, 0), Depends: make([]string, 0),
@@ -428,6 +434,7 @@ func ReadPackageInfo(contents string) (*PackageInfo, error) {
Conflicts: make([]string, 0), Conflicts: make([]string, 0),
Replaces: make([]string, 0), Replaces: make([]string, 0),
Provides: make([]string, 0), Provides: make([]string, 0),
Downloads: make([]PackageDownload, 0),
SplitPackages: make([]*PackageInfo, 0), SplitPackages: make([]*PackageInfo, 0),
} }
err := yaml.Unmarshal([]byte(contents), &pkgInfo) err := yaml.Unmarshal([]byte(contents), &pkgInfo)