mirror of
https://github.com/EnumeratedDev/bpm.git
synced 2026-09-16 10:36:12 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45c536b326
|
||
|
|
705aec8e6e
|
||
|
|
34fb7ce1d6
|
||
|
|
fee0e6a96f
|
||
|
|
c5f48becf8
|
||
|
|
ac1ba2ada3
|
||
|
|
5fb8211933
|
||
|
|
4110ad9c5c
|
||
|
|
dcff59d560
|
||
|
|
1594eec5ce
|
||
|
|
0c30d85589
|
||
|
|
768c75c9bd
|
||
|
|
abc6674b97
|
||
|
|
1d858076fd
|
||
|
|
5909370407
|
||
|
|
5a78fd3002
|
||
|
|
a7c03c272e
|
||
|
|
8ba209cf35
|
||
|
|
6f27f39065
|
||
|
|
91479e58f1
|
||
|
|
b6d055a38d
|
||
|
|
0c754a7e0f
|
||
|
|
eeb9a01e31
|
||
|
|
53e08ce7f9
|
||
|
|
6f124e0586
|
||
|
|
93921d4093
|
||
|
|
bc89db8cef
|
||
|
|
ea6d722fa8
|
||
|
|
264a0f87ba
|
||
|
|
926b0e35fc
|
||
|
|
a4d1365e55 | ||
|
|
bc3fe6fd7e | ||
|
|
85d5befbda | ||
|
|
27713e468c | ||
|
|
da184ecb19 | ||
|
|
812caa1485 | ||
|
|
5181c43ecf | ||
|
|
17af8dbaaf | ||
|
|
6501105dd1 | ||
|
|
c01ca62e34 | ||
|
|
998d4a2252 | ||
|
|
57231fdec5 | ||
|
|
713eafb708 | ||
|
|
38b6ea0175 | ||
|
|
ef5697321b | ||
|
|
e63398c853 | ||
|
|
6492fd028f | ||
|
|
4bfc325c66 | ||
|
|
8a627254c9 | ||
|
|
458c091ac2 | ||
|
|
966d351a80 | ||
|
|
815d0bb29a | ||
|
|
bf2b4e95ac | ||
|
|
d26d878308 | ||
|
|
f20d7b66d1 | ||
|
|
5f03a6a1ad | ||
|
|
f2bc7c8968 | ||
|
|
97d5a06ce7 | ||
|
|
f0709ff410 | ||
|
|
a32dfba54a | ||
|
|
b07df8c7df | ||
|
|
1329109810 | ||
|
|
4a8c4e22ec | ||
|
|
eb8db1bc99 | ||
|
|
4f1eeeb11d | ||
|
|
2e416b9e6f | ||
|
|
d8a42c780d | ||
|
|
b1bb8de661 | ||
|
|
c425b263fe | ||
|
|
5776784021 | ||
|
|
903c7dce3e | ||
|
|
5085981f52 | ||
|
|
9cdb3d29aa | ||
|
|
d8146cb3f3 | ||
|
|
c8c1fded76 | ||
|
|
4bf9ac9c60 | ||
|
|
b73519ba9c | ||
|
|
382a3fc8b6 | ||
|
|
1843bceef5 | ||
|
|
6af3b77d69 | ||
|
|
8b6ef5fa63 | ||
|
|
e8d5f0a565 | ||
|
|
7b0a8bf1d6 | ||
|
|
3a62f23db5 | ||
|
|
d9d85b4943 | ||
|
|
877fba8767 | ||
|
|
0858a49636 | ||
|
|
b27137da29 | ||
|
|
78dce34b64 | ||
|
|
f74372f13b | ||
|
|
dd41369e05 | ||
|
|
fa3f59d8ba | ||
|
|
46f7d96acf | ||
|
|
2f8d6a7ea9 | ||
|
|
c767f26937 | ||
|
|
3f2d5cf7a1 | ||
|
|
14fe71842e | ||
|
|
d1a2d28cff | ||
|
|
151de2112e | ||
|
|
e60381beb1 | ||
|
|
0801612166 | ||
|
|
9485248d8e | ||
|
|
68291e2666 | ||
|
|
05f273d687 | ||
|
|
fcb2ef1515 | ||
|
|
97de746a7d | ||
|
|
a26737859c | ||
|
|
90d5a23fcc | ||
|
|
7104441727 | ||
|
|
5f8e4f00ea | ||
|
|
87c492a30c | ||
|
|
e94b2a8816 | ||
|
|
4f9d2cdecd | ||
|
|
e2e2629dc9 | ||
|
|
c3981ad95c | ||
|
|
852e615806 | ||
|
|
e285fd792e | ||
|
|
2accc30390 | ||
|
|
b625fe97ef | ||
|
|
6e2bb86ef0 | ||
|
|
e30a32c568 | ||
|
|
a2da73e26b | ||
|
|
2c7c4eeb73 | ||
|
|
6e11f937a6 | ||
|
|
db6a776763 | ||
|
|
e888fe06e8 | ||
|
|
fe928dbbd6 | ||
|
|
0b439bcfed | ||
|
|
4aed0fe5b0 | ||
|
|
a054717b23 | ||
|
|
918ff984ca | ||
|
|
a7b025d92d | ||
|
|
ef16fa8195 | ||
|
|
1aec288013 | ||
|
|
7816d0072c | ||
|
|
1bd57110a0 | ||
|
|
76efa42bcf | ||
|
|
c8939614b1 | ||
|
|
4793424f3d | ||
|
|
e1726ddce5 | ||
|
|
e6cfd112ea | ||
|
|
2d35ac12a1 | ||
|
|
a3d1167358 | ||
|
|
3ff01093a0 | ||
|
|
c427499fb6 | ||
|
|
108e355e94 | ||
|
|
7b491874eb | ||
|
|
82d3c8bd51 | ||
|
|
7a489af220 | ||
|
|
6d9157e878 | ||
|
|
fd6ddbfc41 | ||
|
|
368b098888 | ||
|
|
5e2fc138e9 | ||
|
|
bc489ebd23 | ||
|
|
6247c6eff7 | ||
|
|
c24b7c85e3 | ||
|
|
2fd01a3fc2 | ||
|
|
747c770499 | ||
|
|
26500d670d | ||
|
|
59df2324e6 | ||
|
|
12d5e7580e | ||
|
|
123697e1dc | ||
|
|
743918702a | ||
|
|
7d2caa542c | ||
|
|
ab75193022 | ||
|
|
7d577a8dc2 | ||
|
|
c85c9b5d1c | ||
|
|
721414679d | ||
|
|
c20e74203c | ||
|
|
23f9a13939 | ||
|
|
1d41236740 | ||
|
|
e445863dca | ||
|
|
f9ef17cd66 | ||
|
|
124802ecc1 |
@@ -0,0 +1,5 @@
|
|||||||
|
# IDE Directories
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Build directory
|
||||||
|
build
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2024 CapCreeperGR
|
Copyright (c) 2024 EnumDev
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# Installation paths
|
||||||
|
PREFIX ?= /usr/local
|
||||||
|
SBINDIR ?= $(PREFIX)/sbin
|
||||||
|
SYSCONFDIR := $(PREFIX)/etc
|
||||||
|
|
||||||
|
# Compilers and tools
|
||||||
|
GO ?= go
|
||||||
|
|
||||||
|
# Build-time variables
|
||||||
|
ROOT_COMPILATION_UID ?= 65534
|
||||||
|
ROOT_COMPILATION_GID ?= 65534
|
||||||
|
|
||||||
|
build:
|
||||||
|
mkdir -p build
|
||||||
|
cd src/bpm; $(GO) build $(GOFLAGS) -ldflags "-w -X 'git.enumerated.dev/bubble-package-manager/bpm/src/bpmlib.rootCompilationUID=$(ROOT_COMPILATION_UID)' -X 'git.enumerated.dev/bubble-package-manager/bpm/src/bpmlib.rootCompilationGID=$(ROOT_COMPILATION_GID)'" -o ../../build/bpm git.enumerated.dev/bubble-package-manager/bpm/src/bpm
|
||||||
|
|
||||||
|
install: build/bpm config/
|
||||||
|
# Create directory
|
||||||
|
install -dm755 $(DESTDIR)$(SBINDIR)
|
||||||
|
|
||||||
|
# Install binary
|
||||||
|
install -Dm755 build/bpm $(DESTDIR)$(SBINDIR)/bpm
|
||||||
|
|
||||||
|
install-config:
|
||||||
|
# Create directory
|
||||||
|
install -dm755 $(DESTDIR)$(SYSCONFDIR)
|
||||||
|
|
||||||
|
# Install files
|
||||||
|
install -Dm644 config/bpm.conf $(DESTDIR)$(SYSCONFDIR)/bpm.conf
|
||||||
|
install -Dm644 config/bpm-compilation.conf $(DESTDIR)$(SYSCONFDIR)/bpm-compilation.conf
|
||||||
|
|
||||||
|
uninstall:
|
||||||
|
-rm -f $(DESTDIR)$(SBINDIR)/bpm
|
||||||
|
-rm -f $(DESTDIR)$(SYSCONFDIR)/bpm.conf
|
||||||
|
-rm -f $(DESTDIR)$(SYSCONFDIR)/bpm-compilation.conf
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -r build/
|
||||||
|
|
||||||
|
.PHONY: build install install-config uninstall clean
|
||||||
@@ -1,29 +1,26 @@
|
|||||||
# Bubble Package Manager (BPM)
|
# Bubble Package Manager (BPM)
|
||||||
## _A simple package manager_
|
## _The official package manager for Tide Linux_
|
||||||
|
|
||||||
BPM is a simple package manager for Linux systems
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
- Simple to use subcommands
|
- Simple to use subcommands
|
||||||
- Can install binary and source packages
|
- Can install both binary and source packages
|
||||||
- Can be easily installed on practically any system
|
|
||||||
- No bloat
|
|
||||||
|
|
||||||
## Information
|
## Information
|
||||||
|
BPM is still in very early development. There may be bugs that could completely break installations. For now it is recommended you use this only in Virtual Machines, containers or in a Tide Linux installation
|
||||||
BPM is still in very early development. It should not be installed on any system you use seriously. I recommend trying this out in a VM or container. In addition to this, this is one of the first projects I have made using the go programming language so code quality may not be the best. This project was made to help me learn go and how linux systems work better. It is not meant to replace the big package managers in any way
|
|
||||||
|
|
||||||
## Build from source
|
## Build from source
|
||||||
|
|
||||||
BPM requires go 1.22 or above to be built properly
|
- Download `go` from your package manager or from the go website
|
||||||
|
- Download `make` from your package manager
|
||||||
|
- Run the following command to compile the project. You may need to set the `GO` environment variable if your Go installation is not in your PATH
|
||||||
```sh
|
```sh
|
||||||
git clone https://gitlab.com/bubble-package-manager/bpm.git
|
make
|
||||||
cd bpm
|
```
|
||||||
mkdir build
|
- Run the following command to install BPM into your system. You may also append a DESTDIR variable at the end of this line if you wish to install in a different location
|
||||||
go build -o ./build/bpm capcreepergr.me/bpm
|
```sh
|
||||||
|
make install PREFIX=/usr SYSCONFDIR=/etc
|
||||||
|
make install-config PREFIX=/usr SYSCONFDIR=/etc
|
||||||
```
|
```
|
||||||
You are now able to copy the executable in the ./build directory in a VM or container's /usr/bin/ directory
|
|
||||||
|
|
||||||
## How to use
|
## How to use
|
||||||
|
|
||||||
@@ -31,24 +28,38 @@ You are able to install bpm packages by typing the following:
|
|||||||
```sh
|
```sh
|
||||||
bpm install /path/to/package.bpm
|
bpm install /path/to/package.bpm
|
||||||
```
|
```
|
||||||
You may also use the -y flag as shown below to bypass the installation confirmation prompt
|
You can also use the package name directly if using databases
|
||||||
|
```sh
|
||||||
|
bpm install package_name
|
||||||
|
```
|
||||||
|
The -y flag may be used as shown below to bypass the confirmation prompt
|
||||||
```sh
|
```sh
|
||||||
bpm install -y /path/to/package.bpm
|
bpm install -y /path/to/package.bpm
|
||||||
```
|
```
|
||||||
Flags must strictly be typed before the first package path otherwise they'll be read as package locations themselves
|
Flags must strictly be typed before the first package path or name, otherwise they'll be read as package locations themselves
|
||||||
|
|
||||||
You can remove an installed package by typing the following
|
You can remove an installed package by typing the following
|
||||||
```sh
|
```sh
|
||||||
bpm remove package_name
|
bpm remove package_name
|
||||||
```
|
```
|
||||||
The -y flag applies here as well if you wish to skip the removal confirmation prompt
|
|
||||||
|
To remove all unused dependencies and clean cached files try using the cleanup command
|
||||||
|
```sh
|
||||||
|
bpm cleanup
|
||||||
|
```
|
||||||
|
|
||||||
|
If using databases, all packages can be updated using this simple command
|
||||||
|
```sh
|
||||||
|
bpm update
|
||||||
|
```
|
||||||
|
|
||||||
For information on the rest of the commands simply use the help command or pass in no arguments at all
|
For information on the rest of the commands simply use the help command or pass in no arguments at all
|
||||||
```
|
```sh
|
||||||
bpm help
|
bpm help
|
||||||
```
|
```
|
||||||
|
|
||||||
## Package Creation
|
## Package Creation
|
||||||
|
|
||||||
Package creation is simplified using the bpm-utils package which contains helper scripts for creating and archiving packages. \
|
Package creation is simplified using the bpm-utils package which contains helper scripts for creating packages
|
||||||
Learn more here: https://gitlab.com/bubble-package-manager/bpm-utils
|
|
||||||
|
Learn more here: https://git.enumerated.dev/bubble-package-manager/bpm-utils
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
package bpm_utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
type BPMConfigStruct struct {
|
|
||||||
CompilationEnv []string `yaml:"compilation_env"`
|
|
||||||
SilentCompilation bool `yaml:"silent_compilation"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var BPMConfig BPMConfigStruct = BPMConfigStruct{
|
|
||||||
CompilationEnv: make([]string, 0),
|
|
||||||
SilentCompilation: false}
|
|
||||||
|
|
||||||
func ReadConfig() {
|
|
||||||
if _, err := os.Stat("/etc/bpm.conf"); os.IsNotExist(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
bytes, err := os.ReadFile("/etc/bpm.conf")
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = yaml.Unmarshal(bytes, &BPMConfig)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
package bpm_utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetArch() string {
|
|
||||||
output, err := exec.Command("/usr/bin/uname", "-m").Output()
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return strings.TrimSpace(string(output))
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyFileContents(src, dst string) (err error) {
|
|
||||||
in, err := os.Open(src)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer in.Close()
|
|
||||||
out, err := os.Create(dst)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
cerr := out.Close()
|
|
||||||
if err == nil {
|
|
||||||
err = cerr
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
if _, err = io.Copy(out, in); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = out.Sync()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func stringSliceRemove(s []string, r string) []string {
|
|
||||||
for i, v := range s {
|
|
||||||
if v == r {
|
|
||||||
return append(s[:i], s[i+1:]...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func stringSliceRemoveEmpty(s []string) []string {
|
|
||||||
var r []string
|
|
||||||
for _, str := range s {
|
|
||||||
if str != "" {
|
|
||||||
r = append(r, str)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
|||||||
|
privilege_escalator_cmd: "sudo"
|
||||||
|
compilation_env: []
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
ignore_packages: []
|
||||||
|
show_source_package_contents: always
|
||||||
|
cleanup_make_dependencies: true
|
||||||
|
databases:
|
||||||
|
- name: example-database
|
||||||
|
source: https://my-database.xyz/
|
||||||
|
disabled: true
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
module capcreepergr.me/bpm
|
|
||||||
|
|
||||||
go 1.22
|
|
||||||
|
|
||||||
require gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
@@ -1,401 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"capcreepergr.me/bpm/bpm_utils"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"slices"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
/* ---BPM | Bubble Package Manager--- */
|
|
||||||
/* Made By CapCreeperGR */
|
|
||||||
/* A simple-to-use package manager */
|
|
||||||
/* ---------------------------------- */
|
|
||||||
|
|
||||||
var bpmVer = "0.3.0"
|
|
||||||
|
|
||||||
var subcommand = "help"
|
|
||||||
var subcommandArgs []string
|
|
||||||
|
|
||||||
// Flags
|
|
||||||
var rootDir = "/"
|
|
||||||
var yesAll = false
|
|
||||||
var buildSource = false
|
|
||||||
var skipCheck = false
|
|
||||||
var keepTempDir = false
|
|
||||||
var forceInstall = false
|
|
||||||
var pkgListNumbers = false
|
|
||||||
var pkgListNames = false
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
bpm_utils.ReadConfig()
|
|
||||||
resolveFlags()
|
|
||||||
resolveCommand()
|
|
||||||
}
|
|
||||||
|
|
||||||
type commandType uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
help commandType = iota
|
|
||||||
version
|
|
||||||
info
|
|
||||||
list
|
|
||||||
install
|
|
||||||
remove
|
|
||||||
file
|
|
||||||
)
|
|
||||||
|
|
||||||
func getCommandType() commandType {
|
|
||||||
switch subcommand {
|
|
||||||
case "version":
|
|
||||||
return version
|
|
||||||
case "info":
|
|
||||||
return info
|
|
||||||
case "list":
|
|
||||||
return list
|
|
||||||
case "install":
|
|
||||||
return install
|
|
||||||
case "remove":
|
|
||||||
return remove
|
|
||||||
case "file":
|
|
||||||
return file
|
|
||||||
default:
|
|
||||||
return help
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func resolveCommand() {
|
|
||||||
switch getCommandType() {
|
|
||||||
case version:
|
|
||||||
fmt.Println("Bubble Package Manager (BPM)")
|
|
||||||
fmt.Println("Version: " + bpmVer)
|
|
||||||
case info:
|
|
||||||
packages := subcommandArgs
|
|
||||||
if len(packages) == 0 {
|
|
||||||
fmt.Println("No packages were given")
|
|
||||||
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
|
|
||||||
}
|
|
||||||
fmt.Print("----------------\n" + bpm_utils.CreateInfoFile(*info, true))
|
|
||||||
if n == len(packages)-1 {
|
|
||||||
fmt.Println("----------------")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case list:
|
|
||||||
packages, err := bpm_utils.GetInstalledPackages(rootDir)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Could not get installed packages\nError: %s", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if pkgListNumbers {
|
|
||||||
fmt.Println(len(packages))
|
|
||||||
} else if pkgListNames {
|
|
||||||
for _, pkg := range packages {
|
|
||||||
fmt.Println(pkg)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if len(packages) == 0 {
|
|
||||||
fmt.Println("No packages have been installed")
|
|
||||||
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
|
|
||||||
}
|
|
||||||
fmt.Print("----------------\n" + bpm_utils.CreateInfoFile(*info, true))
|
|
||||||
if n == len(packages)-1 {
|
|
||||||
fmt.Println("----------------")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case install:
|
|
||||||
if os.Getuid() != 0 {
|
|
||||||
fmt.Println("This subcommand needs to be run with superuser permissions")
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
files := subcommandArgs
|
|
||||||
if len(files) == 0 {
|
|
||||||
fmt.Println("No files were given to install")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, file := range files {
|
|
||||||
pkgInfo, err := bpm_utils.ReadPackage(file)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Could not read package\nError: %s\n", err)
|
|
||||||
}
|
|
||||||
fmt.Print("----------------\n" + bpm_utils.CreateInfoFile(*pkgInfo, true))
|
|
||||||
fmt.Println("----------------")
|
|
||||||
verb := "install"
|
|
||||||
if pkgInfo.Type == "source" {
|
|
||||||
verb = "build"
|
|
||||||
}
|
|
||||||
if !forceInstall {
|
|
||||||
if pkgInfo.Arch != "any" && pkgInfo.Arch != bpm_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 {
|
|
||||||
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 rootDir != "/" {
|
|
||||||
fmt.Println("Warning: Operating in " + rootDir)
|
|
||||||
}
|
|
||||||
if !yesAll {
|
|
||||||
reader := bufio.NewReader(os.Stdin)
|
|
||||||
if pkgInfo.Type == "source" {
|
|
||||||
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)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Could not read source script\nError: %s\n", err)
|
|
||||||
}
|
|
||||||
fmt.Println(script)
|
|
||||||
fmt.Println("-------EOF-------")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if bpm_utils.IsPackageInstalled(pkgInfo.Name, rootDir) {
|
|
||||||
if !yesAll {
|
|
||||||
installedInfo := bpm_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] ")
|
|
||||||
} else if strings.Compare(pkgInfo.Version, installedInfo.Version) < 0 {
|
|
||||||
fmt.Println("This file contains an older version of this package (" + installedInfo.Version + " -> " + pkgInfo.Version + ")")
|
|
||||||
fmt.Print("Do you wish to downgrade this package? (Not recommended) [y\\N] ")
|
|
||||||
} else if strings.Compare(pkgInfo.Version, installedInfo.Version) == 0 {
|
|
||||||
fmt.Println("This package is already installed on the system and is up to date")
|
|
||||||
fmt.Printf("Do you wish to re%s this package? [y\\N] ", verb)
|
|
||||||
}
|
|
||||||
reader := bufio.NewReader(os.Stdin)
|
|
||||||
text, _ := reader.ReadString('\n')
|
|
||||||
if strings.TrimSpace(strings.ToLower(text)) != "y" && strings.TrimSpace(strings.ToLower(text)) != "yes" {
|
|
||||||
fmt.Printf("Skipping package (%s)...\n", pkgInfo.Name)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if !yesAll {
|
|
||||||
reader := bufio.NewReader(os.Stdin)
|
|
||||||
fmt.Printf("Do you wish to %s this package? [y\\N] ", verb)
|
|
||||||
text, _ := reader.ReadString('\n')
|
|
||||||
if strings.TrimSpace(strings.ToLower(text)) != "y" && strings.TrimSpace(strings.ToLower(text)) != "yes" {
|
|
||||||
fmt.Printf("Skipping package (%s)...\n", pkgInfo.Name)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = bpm_utils.InstallPackage(file, rootDir, 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)
|
|
||||||
}
|
|
||||||
log.Fatalf("Could not install package\nError: %s\n", err)
|
|
||||||
}
|
|
||||||
fmt.Printf("Package (%s) was successfully installed!\n", pkgInfo.Name)
|
|
||||||
if pkgInfo.Type == "source" && keepTempDir {
|
|
||||||
fmt.Println("** It is recommended you delete the temporary bpm folder in /var/tmp **")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case remove:
|
|
||||||
if os.Getuid() != 0 {
|
|
||||||
fmt.Println("This subcommand needs to be run with superuser permissions")
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
packages := subcommandArgs
|
|
||||||
if len(packages) == 0 {
|
|
||||||
fmt.Println("No packages were given")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, pkg := range packages {
|
|
||||||
pkgInfo := bpm_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("----------------")
|
|
||||||
if rootDir != "/" {
|
|
||||||
fmt.Println("Warning: Operating in " + rootDir)
|
|
||||||
}
|
|
||||||
if !yesAll {
|
|
||||||
reader := bufio.NewReader(os.Stdin)
|
|
||||||
fmt.Print("Do you wish to remove this package? [y\\N] ")
|
|
||||||
text, _ := reader.ReadString('\n')
|
|
||||||
if strings.TrimSpace(strings.ToLower(text)) != "y" && strings.TrimSpace(strings.ToLower(text)) != "yes" {
|
|
||||||
fmt.Printf("Skipping package (%s)...\n", pkgInfo.Name)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err := bpm_utils.RemovePackage(pkg, rootDir)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Could not remove package\nError: %s\n", err)
|
|
||||||
}
|
|
||||||
fmt.Printf("Package (%s) was successfully removed!\n", pkgInfo.Name)
|
|
||||||
}
|
|
||||||
case file:
|
|
||||||
files := subcommandArgs
|
|
||||||
if len(files) == 0 {
|
|
||||||
fmt.Println("No files were given to get which packages manage it")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, file := range files {
|
|
||||||
absFile, err := filepath.Abs(file)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Could not get absolute path of %s", file)
|
|
||||||
}
|
|
||||||
stat, err := os.Stat(absFile)
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
log.Fatalf(absFile + " does not exist!")
|
|
||||||
}
|
|
||||||
pkgs, err := bpm_utils.GetInstalledPackages(rootDir)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Could not get installed packages. Error %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.HasPrefix(absFile, rootDir) {
|
|
||||||
log.Fatalf("Could not get relative path of %s to root path", absFile)
|
|
||||||
}
|
|
||||||
absFile, err = filepath.Rel(rootDir, absFile)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Could not get relative path of %s to root path", absFile)
|
|
||||||
}
|
|
||||||
absFile = strings.TrimPrefix(absFile, "/")
|
|
||||||
if stat.IsDir() {
|
|
||||||
absFile = absFile + "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
var pkgList []string
|
|
||||||
for _, pkg := range pkgs {
|
|
||||||
if slices.Contains(bpm_utils.GetPackageFiles(pkg, rootDir), absFile) {
|
|
||||||
pkgList = append(pkgList, pkg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(pkgList) == 0 {
|
|
||||||
fmt.Println(absFile + " is not managed by any packages")
|
|
||||||
} else {
|
|
||||||
fmt.Println(absFile + " is managed by the following packages:")
|
|
||||||
for _, pkg := range pkgList {
|
|
||||||
fmt.Println("- " + pkg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
printHelp()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func printHelp() {
|
|
||||||
fmt.Println("\033[1m---- Command Format ----\033[0m")
|
|
||||||
fmt.Println("-> command format: bpm <subcommand> [-flags]...")
|
|
||||||
fmt.Println("-> flags will be read if passed right after the subcommand otherwise they will be read as subcommand arguments")
|
|
||||||
fmt.Println("\033[1m---- Command List ----\033[0m")
|
|
||||||
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=<root_path> lets you define the root path which will be used")
|
|
||||||
fmt.Println("-> bpm list [-R, -c, -n] | lists all installed packages")
|
|
||||||
fmt.Println(" -R=<root_path> 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, -b] <files...> | installs the following files")
|
|
||||||
fmt.Println(" -R=<root_path> lets you define the root path which will be used")
|
|
||||||
fmt.Println(" -y skips the confirmation prompt")
|
|
||||||
fmt.Println(" -f skips dependency and architecture checking")
|
|
||||||
fmt.Println(" -b creates a binary package for a source package after compilation and saves it in /var/lib/bpm/compiled")
|
|
||||||
fmt.Println(" -k keeps the temp directory created by BPM after source package installation")
|
|
||||||
fmt.Println("-> bpm remove [-R, -y] <packages...> | removes the following packages")
|
|
||||||
fmt.Println(" -R=<root_path> lets you define the root path which will be used")
|
|
||||||
fmt.Println(" -y skips the confirmation prompt")
|
|
||||||
fmt.Println("-> bpm file [-R] <files...> | shows what packages the following packages are managed by")
|
|
||||||
fmt.Println(" -R=<root_path> lets you define the root path which will be used")
|
|
||||||
fmt.Println("\033[1m----------------\033[0m")
|
|
||||||
}
|
|
||||||
|
|
||||||
func resolveFlags() {
|
|
||||||
// List flags
|
|
||||||
listFlagSet := flag.NewFlagSet("List flags", flag.ExitOnError)
|
|
||||||
listFlagSet.Usage = printHelp
|
|
||||||
listFlagSet.StringVar(&rootDir, "R", "/", "Set the destination root")
|
|
||||||
listFlagSet.BoolVar(&pkgListNumbers, "c", false, "List the number of all packages installed with BPM")
|
|
||||||
listFlagSet.BoolVar(&pkgListNames, "n", false, "List the names of all packages installed with BPM")
|
|
||||||
// Info flags
|
|
||||||
infoFlagSet := flag.NewFlagSet("Info flags", flag.ExitOnError)
|
|
||||||
infoFlagSet.StringVar(&rootDir, "R", "/", "Set the destination root")
|
|
||||||
infoFlagSet.Usage = printHelp
|
|
||||||
// Install flags
|
|
||||||
installFlagSet := flag.NewFlagSet("Install flags", flag.ExitOnError)
|
|
||||||
installFlagSet.StringVar(&rootDir, "R", "/", "Set the destination root")
|
|
||||||
installFlagSet.BoolVar(&yesAll, "y", false, "Skip confirmation prompts")
|
|
||||||
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")
|
|
||||||
installFlagSet.BoolVar(&forceInstall, "f", false, "Force installation by skipping architecture and dependency resolution")
|
|
||||||
installFlagSet.Usage = printHelp
|
|
||||||
// Remove flags
|
|
||||||
removeFlagSet := flag.NewFlagSet("Remove flags", flag.ExitOnError)
|
|
||||||
removeFlagSet.StringVar(&rootDir, "R", "/", "Set the destination root")
|
|
||||||
removeFlagSet.BoolVar(&yesAll, "y", false, "Skip confirmation prompts")
|
|
||||||
removeFlagSet.Usage = printHelp
|
|
||||||
// File flags
|
|
||||||
fileFlagSet := flag.NewFlagSet("Remove flags", flag.ExitOnError)
|
|
||||||
fileFlagSet.StringVar(&rootDir, "R", "/", "Set the destination root")
|
|
||||||
fileFlagSet.Usage = printHelp
|
|
||||||
if len(os.Args[1:]) <= 0 {
|
|
||||||
subcommand = "help"
|
|
||||||
} else {
|
|
||||||
subcommand = os.Args[1]
|
|
||||||
subcommandArgs = os.Args[2:]
|
|
||||||
if getCommandType() == list {
|
|
||||||
err := listFlagSet.Parse(subcommandArgs)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
subcommandArgs = listFlagSet.Args()
|
|
||||||
} else if getCommandType() == info {
|
|
||||||
err := infoFlagSet.Parse(subcommandArgs)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
subcommandArgs = infoFlagSet.Args()
|
|
||||||
} else if getCommandType() == install {
|
|
||||||
err := installFlagSet.Parse(subcommandArgs)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
subcommandArgs = installFlagSet.Args()
|
|
||||||
} else if getCommandType() == remove {
|
|
||||||
err := removeFlagSet.Parse(subcommandArgs)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
subcommandArgs = removeFlagSet.Args()
|
|
||||||
} else if getCommandType() == file {
|
|
||||||
err := fileFlagSet.Parse(subcommandArgs)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
subcommandArgs = fileFlagSet.Args()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
module git.enumerated.dev/bubble-package-manager/bpm/src/bpm
|
||||||
|
|
||||||
|
go 1.23
|
||||||
|
|
||||||
|
toolchain go1.23.7
|
||||||
|
|
||||||
|
require (
|
||||||
|
git.enumerated.dev/bubble-package-manager/bpm/src/bpmlib v0.5.0
|
||||||
|
github.com/adrg/strutil v0.3.1
|
||||||
|
github.com/spf13/pflag v1.0.10
|
||||||
|
)
|
||||||
|
|
||||||
|
replace git.enumerated.dev/bubble-package-manager/bpm/src/bpmlib => ../bpmlib
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/drone/envsubst v1.0.3 // indirect
|
||||||
|
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
github.com/adrg/strutil v0.3.1 h1:OLvSS7CSJO8lBii4YmBt8jiK9QOtB9CzCzwl4Ic/Fz4=
|
||||||
|
github.com/adrg/strutil v0.3.1/go.mod h1:8h90y18QLrs11IBffcGX3NW/GFBXCMcNg4M7H6MspPA=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g=
|
||||||
|
github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g=
|
||||||
|
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f h1:xt29M2T6STgldg+WEP51gGePQCsQvklmP2eIhPIBK3g=
|
||||||
|
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f/go.mod h1:i4sF0l1fFnY1aiw08QQSwVAFxHEm311Me3WsU/X7nL0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||||
|
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
+1231
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,590 @@
|
|||||||
|
package bpmlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/drone/envsubst"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
var rootCompilationUID = "65534"
|
||||||
|
var rootCompilationGID = "65534"
|
||||||
|
|
||||||
|
func CompileSourcePackage(archiveFilename, outputDirectory 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 nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure package type is 'source'
|
||||||
|
if bpmpkg.PkgInfo.Type != "source" {
|
||||||
|
return nil, errors.New("cannot compile a non-source package")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get HOME directory
|
||||||
|
homeDir, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 nil, fmt.Errorf("could not convert UID '%s' to int", rootCompilationUID)
|
||||||
|
}
|
||||||
|
_gid, err := strconv.ParseInt(rootCompilationGID, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not convert GID '%s' to int", rootCompilationGID)
|
||||||
|
}
|
||||||
|
uid = int(_uid)
|
||||||
|
gid = int(_gid)
|
||||||
|
} else {
|
||||||
|
uid = os.Getuid()
|
||||||
|
gid = os.Getgid()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set temporary directory
|
||||||
|
var tempDirectory string
|
||||||
|
if os.Getuid() == 0 {
|
||||||
|
tempDirectory = path.Join("/var/cache/bpm/compilation/", bpmpkg.PkgInfo.Name)
|
||||||
|
} else {
|
||||||
|
tempDirectory = path.Join(homeDir, ".cache/bpm/compilation/", bpmpkg.PkgInfo.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure temporary directory does not exist
|
||||||
|
if _, err := os.Stat(tempDirectory); err == nil {
|
||||||
|
err := os.RemoveAll(tempDirectory)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create temporary directory
|
||||||
|
err = os.MkdirAll(tempDirectory, 0755)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change temporary directory owner
|
||||||
|
err = os.Chown(tempDirectory, uid, gid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract source.sh file
|
||||||
|
err = extractTarballFile(archiveFilename, "source.sh", tempDirectory, uid, gid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get package scripts and extract them
|
||||||
|
packageScripts := getPackageScripts(archiveFilename)
|
||||||
|
for _, script := range packageScripts {
|
||||||
|
err = extractTarballFile(archiveFilename, script, tempDirectory, uid, gid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract source files
|
||||||
|
err = extractTarballDirectory(archiveFilename, "source-files", tempDirectory, uid, gid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create source directory
|
||||||
|
err = os.Mkdir(path.Join(tempDirectory, "source"), 0755)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change source directory owner
|
||||||
|
err = os.Chown(path.Join(tempDirectory, "source"), uid, gid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download files
|
||||||
|
err = downloadPackageFiles(bpmpkg.PkgInfo, tempDirectory)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup environment for commands
|
||||||
|
env := os.Environ()
|
||||||
|
env = append(env, "HOME="+tempDirectory)
|
||||||
|
env = append(env, "BPM_WORKDIR="+tempDirectory)
|
||||||
|
env = append(env, "BPM_SOURCE="+path.Join(tempDirectory, "source"))
|
||||||
|
env = append(env, "BPM_PKG_NAME="+bpmpkg.PkgInfo.Name)
|
||||||
|
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_ARCH="+bpmpkg.PkgInfo.OutputArch)
|
||||||
|
env = append(env, CompilationBPMConfig.CompilationEnvironment...)
|
||||||
|
|
||||||
|
// Execute prepare and build functions in source.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"+
|
||||||
|
"[[ $(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
|
||||||
|
"exit 0")
|
||||||
|
cmd.Dir = tempDirectory
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
cmd.Env = env
|
||||||
|
if os.Getuid() == 0 {
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
||||||
|
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
|
||||||
|
}
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute check function in source.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"+
|
||||||
|
"[[ $(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")
|
||||||
|
cmd.Dir = tempDirectory
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
cmd.Env = env
|
||||||
|
if os.Getuid() == 0 {
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
||||||
|
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
|
||||||
|
}
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all packages to compile
|
||||||
|
packagesToCompile := bpmpkg.PkgInfo.SplitPackages
|
||||||
|
if !bpmpkg.PkgInfo.IsSplitPackage() {
|
||||||
|
packagesToCompile = append(packagesToCompile, bpmpkg.PkgInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create output directories for each package
|
||||||
|
for _, pkg := range packagesToCompile {
|
||||||
|
// Create new output directory
|
||||||
|
err = os.Mkdir(path.Join(tempDirectory, "output_"+pkg.Name), 0755)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change output directory owner
|
||||||
|
err = os.Chown(path.Join(tempDirectory, "output_"+pkg.Name), uid, gid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile each package
|
||||||
|
for _, pkg := range packagesToCompile {
|
||||||
|
// Get package function name
|
||||||
|
packageFunctionName := "package"
|
||||||
|
if bpmpkg.PkgInfo.IsSplitPackage() {
|
||||||
|
packageFunctionName = "package_" + pkg.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 = append(env, "BPM_OUTPUT="+path.Join(tempDirectory, "output_"+pkg.Name))
|
||||||
|
if os.Getuid() == 0 {
|
||||||
|
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", 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.Dir = tempDirectory
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
cmd.Env = env
|
||||||
|
if os.Getuid() == 0 {
|
||||||
|
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
|
||||||
|
pkgInfo := *pkg
|
||||||
|
|
||||||
|
// Set package type to binary
|
||||||
|
pkgInfo.Type = "binary"
|
||||||
|
|
||||||
|
// Set package architecture
|
||||||
|
pkgInfo.Arch = pkg.OutputArch
|
||||||
|
pkgInfo.OutputArch = ""
|
||||||
|
|
||||||
|
// Remove split package and downloads fields
|
||||||
|
pkgInfo.SplitPackages = nil
|
||||||
|
pkgInfo.Downloads = 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)
|
||||||
|
if os.Getuid() == 0 {
|
||||||
|
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
|
||||||
|
outputFilename := path.Join(outputDirectory, fmt.Sprintf("%s-%s-%d-%s.bpm", pkgInfo.Name, pkgInfo.Version, pkgInfo.Revision, pkgInfo.Arch))
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove output directory
|
||||||
|
err = os.RemoveAll(path.Join(tempDirectory, "output_"+pkg.Name))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
outputBpmPackages[pkgInfo.Name] = outputFilename
|
||||||
|
}
|
||||||
|
|
||||||
|
return outputBpmPackages, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string) error {
|
||||||
|
for _, download := range pkgInfo.Downloads {
|
||||||
|
// Replace variables
|
||||||
|
replaceVars := func(s string) string {
|
||||||
|
switch s {
|
||||||
|
case "BPM_PKG_VERSION":
|
||||||
|
return pkgInfo.Version
|
||||||
|
case "BPM_PKG_NAME":
|
||||||
|
return pkgInfo.Name
|
||||||
|
case "BPM_SOURCE":
|
||||||
|
return path.Join(tempDirectory, "source/")
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadUrl, err := envsubst.Eval(strings.TrimSpace(download.Url), replaceVars)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
extractTo, err := envsubst.Eval(strings.TrimSpace(download.ExtractTo), replaceVars)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cloneTo, err := envsubst.Eval(strings.TrimSpace(download.CloneTo), replaceVars)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make relative paths absolute
|
||||||
|
if extractTo != "" && extractTo[0] != '/' {
|
||||||
|
extractTo = path.Join(tempDirectory, extractTo)
|
||||||
|
}
|
||||||
|
// Make relative paths absolute
|
||||||
|
if cloneTo != "" && cloneTo[0] != '/' {
|
||||||
|
cloneTo = path.Join(tempDirectory, cloneTo)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch download.Type {
|
||||||
|
case "", "file":
|
||||||
|
filepath := path.Join(tempDirectory, path.Base(downloadUrl))
|
||||||
|
if download.Filepath != "" && download.Filepath[0] != '/' {
|
||||||
|
filepath = path.Join(tempDirectory, download.Filepath)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := downloadFile(downloadUrl, filepath, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if download.Checksum != "skip" {
|
||||||
|
f, err := os.Open(filepath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
h := sha256.New()
|
||||||
|
_, err = io.Copy(h, f)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if hex.EncodeToString(h.Sum(nil)) != download.Checksum {
|
||||||
|
fmt.Printf("Downloaded file checksum: %x\n", h.Sum(nil))
|
||||||
|
return fmt.Errorf("downloaded file checksums did not match")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Println("Skipping checksum checking...")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !download.NoExtract && (strings.Contains(filepath, ".tar") || strings.HasSuffix(filepath, ".tgz")) {
|
||||||
|
cmd := exec.Command("tar", "xvf", filepath, "--strip-components="+strconv.Itoa(download.ExtractStripComponents))
|
||||||
|
cmd.Dir = tempDirectory
|
||||||
|
if extractTo != "" {
|
||||||
|
err := os.MkdirAll(extractTo, 0755)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cmd.Args = append(cmd.Args, "-C", extractTo)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 extractTo != "" {
|
||||||
|
err := os.MkdirAll(extractTo, 0755)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cmd.Args = append(cmd.Args, "-d", extractTo)
|
||||||
|
} else {
|
||||||
|
err := os.Mkdir(path.Join(tempDirectory, strings.TrimSuffix(path.Base(filepath), ".zip")), 0755)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
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, err := envsubst.Eval(gitBranch, func(s string) string {
|
||||||
|
switch s {
|
||||||
|
case "BPM_PKG_VERSION":
|
||||||
|
return pkgInfo.Version
|
||||||
|
case "BPM_PKG_NAME":
|
||||||
|
return pkgInfo.Name
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command("git", "clone", "--depth=1", downloadUrl)
|
||||||
|
cmd.Dir = tempDirectory
|
||||||
|
if gitBranch != "" {
|
||||||
|
cmd.Args = slices.Insert(cmd.Args, len(cmd.Args)-1, "--branch="+gitBranch)
|
||||||
|
}
|
||||||
|
if cloneTo != "" {
|
||||||
|
err := os.MkdirAll(cloneTo, 0755)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cmd.Args = append(cmd.Args, cloneTo)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 cloneTo != "" {
|
||||||
|
cmd.Dir = cloneTo
|
||||||
|
} 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
|
||||||
|
}
|
||||||
|
|
||||||
|
func showPackageFiles(archiveFilename string) error {
|
||||||
|
// Read BPM archive
|
||||||
|
bpmpkg, err := ReadPackage(archiveFilename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure package type is 'source'
|
||||||
|
if bpmpkg.PkgInfo.Type != "source" {
|
||||||
|
return fmt.Errorf("cannot compile a non-source package")
|
||||||
|
}
|
||||||
|
|
||||||
|
printTarballContent := func(filename string) error {
|
||||||
|
// Get tarball reader for file
|
||||||
|
reader, err := readTarballFile(archiveFilename, filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read file content
|
||||||
|
data, err := io.ReadAll(reader.tarReader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print file name and content
|
||||||
|
fmt.Println("==============================")
|
||||||
|
fmt.Printf("%s: %s\n", bpmpkg.PkgInfo.Name, filename)
|
||||||
|
fmt.Println("==============================")
|
||||||
|
fmt.Printf("%s\n", data)
|
||||||
|
|
||||||
|
// Close reader
|
||||||
|
err = reader.file.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print pkg.info content
|
||||||
|
err = printTarballContent("pkg.info")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print source files contents
|
||||||
|
files, err := listTarballContent(archiveFilename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, file := range files {
|
||||||
|
if !strings.HasPrefix(file, "source-files/") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print source file content
|
||||||
|
err = printTarballContent(file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print source.sh content
|
||||||
|
err = printTarballContent("source.sh")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package bpmlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MainBPMConfigStruct struct {
|
||||||
|
IgnorePackages []string `yaml:"ignore_packages"`
|
||||||
|
ShowSourcePackageContents string `yaml:"show_source_package_contents"`
|
||||||
|
CleanupMakeDependencies bool `yaml:"cleanup_make_dependencies"`
|
||||||
|
Databases []configDatabase `yaml:"databases"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type configDatabase struct {
|
||||||
|
Name string `yaml:"name"`
|
||||||
|
Source string `yaml:"source"`
|
||||||
|
Disabled *bool `yaml:"disabled"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CompilationBPMConfigStruct struct {
|
||||||
|
PrivilegeEscalatorCmd string `yaml:"privilege_escalator_cmd"`
|
||||||
|
CompilationEnvironment []string `yaml:"compilation_env"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var MainBPMConfig MainBPMConfigStruct
|
||||||
|
var CompilationBPMConfig CompilationBPMConfigStruct
|
||||||
|
|
||||||
|
func ReadConfig() (err error) {
|
||||||
|
var file *os.File
|
||||||
|
|
||||||
|
// Set default config options
|
||||||
|
MainBPMConfig = MainBPMConfigStruct{
|
||||||
|
ShowSourcePackageContents: "always",
|
||||||
|
CleanupMakeDependencies: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read main BPM config
|
||||||
|
file, err = os.Open("/etc/bpm.conf")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = yaml.NewDecoder(file).Decode(&MainBPMConfig)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
file.Close()
|
||||||
|
|
||||||
|
// Read compilation BPM config
|
||||||
|
if _, err := os.Stat("/etc/bpm-compilation.conf"); err == nil {
|
||||||
|
file, err = os.Open("/etc/bpm-compilation.conf")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = yaml.NewDecoder(file).Decode(&CompilationBPMConfig)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
file.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove disabled databases from memory
|
||||||
|
for i := len(MainBPMConfig.Databases) - 1; i >= 0; i-- {
|
||||||
|
if MainBPMConfig.Databases[i].Disabled != nil && *MainBPMConfig.Databases[i].Disabled {
|
||||||
|
MainBPMConfig.Databases = append(MainBPMConfig.Databases[:i], MainBPMConfig.Databases[i+1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,249 @@
|
|||||||
|
package bpmlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BPMDatabase struct {
|
||||||
|
DatabaseVersion int `yaml:"database_version"`
|
||||||
|
Entries map[string]*BPMDatabaseEntry `yaml:"entries"`
|
||||||
|
VirtualPackages map[string][]string
|
||||||
|
Source string
|
||||||
|
}
|
||||||
|
|
||||||
|
type BPMDatabaseEntry struct {
|
||||||
|
Info *PackageInfo `yaml:"info"`
|
||||||
|
Filepath string `yaml:"filepath"`
|
||||||
|
DownloadSize uint64 `yaml:"download_size"`
|
||||||
|
InstalledSize uint64 `yaml:"installed_size"`
|
||||||
|
Database *BPMDatabase
|
||||||
|
}
|
||||||
|
|
||||||
|
var BPMDatabases = make(map[string]*BPMDatabase)
|
||||||
|
|
||||||
|
func (db *BPMDatabase) ContainsPackage(pkg string) bool {
|
||||||
|
_, ok := db.Entries[pkg]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *configDatabase) ReadLocalDatabase() error {
|
||||||
|
dbFile := "/var/lib/bpm/databases/" + db.Name + ".bpmdb"
|
||||||
|
if _, err := os.Stat(dbFile); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes, err := os.ReadFile(dbFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal yaml
|
||||||
|
database := &BPMDatabase{}
|
||||||
|
err = yaml.Unmarshal(bytes, database)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize struct values
|
||||||
|
database.VirtualPackages = make(map[string][]string)
|
||||||
|
database.Source = db.Source
|
||||||
|
|
||||||
|
entriesToRemove := make([]string, 0)
|
||||||
|
for entryName, entry := range database.Entries {
|
||||||
|
entry.Database = database
|
||||||
|
|
||||||
|
if entry.Info.IsSplitPackage() {
|
||||||
|
// Handle split packages
|
||||||
|
for _, splitPkg := range entry.Info.SplitPackages {
|
||||||
|
// Turn split package into json data
|
||||||
|
splitPkgJson, err := yaml.Marshal(splitPkg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone all main package fields onto split package
|
||||||
|
splitPkgClone := *entry.Info
|
||||||
|
|
||||||
|
// Set split package field of split package to nil
|
||||||
|
splitPkgClone.SplitPackages = nil
|
||||||
|
|
||||||
|
// Unmarshal json data back to struct
|
||||||
|
err = yaml.Unmarshal(splitPkgJson, &splitPkgClone)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force set split package version, revision and URL
|
||||||
|
splitPkgClone.Version = entry.Info.Version
|
||||||
|
splitPkgClone.Revision = entry.Info.Revision
|
||||||
|
splitPkgClone.Url = entry.Info.Url
|
||||||
|
|
||||||
|
// Create entry for split package
|
||||||
|
database.Entries[splitPkg.Name] = &BPMDatabaseEntry{
|
||||||
|
Info: &splitPkgClone,
|
||||||
|
Filepath: entry.Filepath,
|
||||||
|
DownloadSize: entry.DownloadSize,
|
||||||
|
InstalledSize: 0,
|
||||||
|
Database: database,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add virtual packages to database
|
||||||
|
for _, p := range splitPkg.Provides {
|
||||||
|
database.VirtualPackages[p] = append(database.VirtualPackages[p], splitPkg.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add current entry to list for removal
|
||||||
|
entriesToRemove = append(entriesToRemove, entryName)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Add virtual packages to database
|
||||||
|
for _, p := range entry.Info.Provides {
|
||||||
|
database.VirtualPackages[p] = append(database.VirtualPackages[p], entry.Info.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove entries
|
||||||
|
for _, entryName := range entriesToRemove {
|
||||||
|
delete(database.Entries, entryName)
|
||||||
|
}
|
||||||
|
|
||||||
|
BPMDatabases[db.Name] = database
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *configDatabase) SyncLocalDatabaseFile() error {
|
||||||
|
dbFile := "/var/lib/bpm/databases/" + db.Name + ".bpmdb"
|
||||||
|
|
||||||
|
// Get URL to database
|
||||||
|
u, err := url.JoinPath(db.Source, "database.bpmdb")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve data from URL
|
||||||
|
buffer, err := retrieveUrlData(u)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal data to ensure it is a valid BPM database
|
||||||
|
err = yaml.Unmarshal(buffer, &BPMDatabase{})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not decode database: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create parent directories to database file
|
||||||
|
err = os.MkdirAll(path.Dir(dbFile), 0755)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create file and save database data
|
||||||
|
out, err := os.Create(dbFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
_, err = out.Write(buffer)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadLocalDatabaseFiles() (err error) {
|
||||||
|
for _, db := range MainBPMConfig.Databases {
|
||||||
|
// Read database
|
||||||
|
err = db.ReadLocalDatabase()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDatabaseEntry(str string) (*BPMDatabaseEntry, *BPMDatabase, error) {
|
||||||
|
split := strings.Split(str, "/")
|
||||||
|
if len(split) == 1 {
|
||||||
|
pkgName := strings.TrimSpace(split[0])
|
||||||
|
if pkgName == "" {
|
||||||
|
return nil, nil, errors.New("could not find database entry for this package")
|
||||||
|
}
|
||||||
|
for _, db := range BPMDatabases {
|
||||||
|
if db.ContainsPackage(pkgName) {
|
||||||
|
return db.Entries[pkgName], db, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil, errors.New("could not find database entry for this package")
|
||||||
|
} 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")
|
||||||
|
}
|
||||||
|
db := BPMDatabases[dbName]
|
||||||
|
if db == nil || !db.ContainsPackage(pkgName) {
|
||||||
|
return nil, nil, errors.New("could not find database entry for this package")
|
||||||
|
}
|
||||||
|
return db.Entries[pkgName], db, nil
|
||||||
|
} else {
|
||||||
|
return nil, nil, errors.New("could not find database entry for this package")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FindReplacement(pkg string) *BPMDatabaseEntry {
|
||||||
|
for _, db := range BPMDatabases {
|
||||||
|
for _, entry := range db.Entries {
|
||||||
|
for _, replaced := range entry.Info.Replaces {
|
||||||
|
if replaced == pkg {
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
return "", errors.New("could not fetch package '" + pkg + "'")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get package url from database
|
||||||
|
entry := db.Entries[pkg]
|
||||||
|
u, err := url.JoinPath(db.Source, entry.Filepath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download package from url
|
||||||
|
err = downloadFile(u, path.Join("/var/cache/bpm/fetched/", path.Base(entry.Filepath)), 0644)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return path.Join("/var/cache/bpm/fetched/", path.Base(entry.Filepath)), nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,222 @@
|
|||||||
|
package bpmlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 GetPackageDependants(pkgName string, rootDir string) ([]string, error) {
|
||||||
|
ret := make([]string, 0)
|
||||||
|
|
||||||
|
// Get BPM package
|
||||||
|
pkg := GetPackage(pkgName, rootDir)
|
||||||
|
if pkg == nil {
|
||||||
|
return nil, errors.New("package not found: " + pkgName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get installed package names
|
||||||
|
pkgs, err := GetInstalledPackages(rootDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("could not get installed packages")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop through all installed packages
|
||||||
|
for _, installedPkgName := range pkgs {
|
||||||
|
// Get installed BPM package
|
||||||
|
installedPkg := GetPackage(installedPkgName, rootDir)
|
||||||
|
if installedPkg == nil {
|
||||||
|
return nil, errors.New("package not found: " + installedPkgName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip iteration if comparing the same packages
|
||||||
|
if installedPkg.PkgInfo.Name == pkgName {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add installed package to list if its dependencies include pkgName
|
||||||
|
if slices.ContainsFunc(installedPkg.PkgInfo.Depends, func(n string) bool {
|
||||||
|
return n == pkgName
|
||||||
|
}) {
|
||||||
|
ret = append(ret, installedPkgName)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop through each virtual package
|
||||||
|
for _, vpkg := range pkg.PkgInfo.Provides {
|
||||||
|
// Add installed package to list if its dependencies contain a provided virtual package
|
||||||
|
if slices.ContainsFunc(installedPkg.PkgInfo.Depends, func(n string) bool {
|
||||||
|
return n == vpkg
|
||||||
|
}) {
|
||||||
|
ret = append(ret, installedPkgName)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package bpmlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PackageNotFoundErr struct {
|
||||||
|
packages []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e PackageNotFoundErr) Error() string {
|
||||||
|
return "The following packages were not found in any databases: " + strings.Join(e.packages, ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
type DependencyNotFoundErr struct {
|
||||||
|
dependencies []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e DependencyNotFoundErr) Error() string {
|
||||||
|
return "The following dependencies were not found in any databases: " + strings.Join(e.dependencies, ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
type PackageConflictErr struct {
|
||||||
|
pkg string
|
||||||
|
conflicts []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e PackageConflictErr) Error() string {
|
||||||
|
return fmt.Sprintf("Package (%s) is in conflict with the following packages: %s", e.pkg, strings.Join(e.conflicts, ", "))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type PackageScriptErr struct {
|
||||||
|
err error
|
||||||
|
packageName string
|
||||||
|
packageScript string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e PackageScriptErr) Error() string {
|
||||||
|
return fmt.Sprintf("could not execute package script (%s) for package (%s): %s", e.packageScript, e.packageName, e.err)
|
||||||
|
}
|
||||||
|
|
||||||
|
type PackageRemovalDependencyErr struct {
|
||||||
|
RequiredPackages map[string][]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e PackageRemovalDependencyErr) Error() string {
|
||||||
|
return "removing these package would break other installed packages"
|
||||||
|
}
|
||||||
@@ -0,0 +1,452 @@
|
|||||||
|
package bpmlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"maps"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"slices"
|
||||||
|
"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) {
|
||||||
|
|
||||||
|
// Setup operation struct
|
||||||
|
operation = &BPMOperation{
|
||||||
|
Actions: make([]OperationAction, 0),
|
||||||
|
UnresolvedDepends: make([]string, 0),
|
||||||
|
Changes: make(map[string]string),
|
||||||
|
RootDir: rootDir,
|
||||||
|
compiledPackages: make(map[string]string),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve packages
|
||||||
|
pkgsNotFound := make([]string, 0)
|
||||||
|
for _, pkg := range packages {
|
||||||
|
if stat, err := os.Stat(pkg); err == nil && !stat.IsDir() {
|
||||||
|
bpmpkg, err := ReadPackage(pkg)
|
||||||
|
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() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set package installation reason
|
||||||
|
installationReason := forceInstallationReason
|
||||||
|
if installationReason == InstallationReasonUnknown {
|
||||||
|
if IsPackageInstalled(splitPkg.Name, rootDir) {
|
||||||
|
installationReason = GetInstallationReason(splitPkg.Name, rootDir)
|
||||||
|
} else {
|
||||||
|
installationReason = InstallationReasonManual
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
operation.AppendAction(&InstallPackageAction{
|
||||||
|
File: pkg,
|
||||||
|
InstallationReason: installationReason,
|
||||||
|
BpmPackage: bpmpkg,
|
||||||
|
SplitPackageToInstall: splitPkg.Name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if reinstallMethod == ReinstallMethodNone && IsPackageInstalled(bpmpkg.PkgInfo.Name, rootDir) && GetPackageInfo(bpmpkg.PkgInfo.Name, rootDir).GetFullVersion() == bpmpkg.PkgInfo.GetFullVersion() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set package installation reason
|
||||||
|
installationReason := forceInstallationReason
|
||||||
|
if installationReason == InstallationReasonUnknown {
|
||||||
|
if IsPackageInstalled(bpmpkg.PkgInfo.Name, rootDir) {
|
||||||
|
installationReason = GetInstallationReason(bpmpkg.PkgInfo.Name, rootDir)
|
||||||
|
} else {
|
||||||
|
installationReason = InstallationReasonManual
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
operation.AppendAction(&InstallPackageAction{
|
||||||
|
File: pkg,
|
||||||
|
InstallationReason: installationReason,
|
||||||
|
BpmPackage: bpmpkg,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
var entry *BPMDatabaseEntry
|
||||||
|
|
||||||
|
if e, _, err := GetDatabaseEntry(pkg); err == nil {
|
||||||
|
entry = e
|
||||||
|
} else if isVirtual, p := IsVirtualPackage(pkg, rootDir); isVirtual {
|
||||||
|
entry, _, err = GetDatabaseEntry(p)
|
||||||
|
if err != nil {
|
||||||
|
pkgsNotFound = append(pkgsNotFound, pkg)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else if e := ResolveVirtualPackage(pkg); e != nil {
|
||||||
|
entry = e
|
||||||
|
} else {
|
||||||
|
pkgsNotFound = append(pkgsNotFound, pkg)
|
||||||
|
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)
|
||||||
|
} else {
|
||||||
|
installationReason = InstallationReasonManual
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
operation.AppendAction(&FetchPackageAction{
|
||||||
|
InstallationReason: installationReason,
|
||||||
|
DatabaseEntry: entry,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return error if not all packages are found
|
||||||
|
if len(pkgsNotFound) != 0 {
|
||||||
|
return nil, PackageNotFoundErr{pkgsNotFound}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve dependencies
|
||||||
|
err = operation.ResolveDependencies(reinstallMethod == ReinstallMethodAll, installOptionalDependencies, verbose)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not resolve dependencies: %s", err)
|
||||||
|
}
|
||||||
|
if len(operation.UnresolvedDepends) != 0 {
|
||||||
|
if !forceInstallation {
|
||||||
|
return nil, DependencyNotFoundErr{operation.UnresolvedDepends}
|
||||||
|
} else if verbose {
|
||||||
|
log.Printf("Warning: %s", DependencyNotFoundErr{operation.UnresolvedDepends})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace obsolete packages
|
||||||
|
operation.ReplaceObsoletePackages()
|
||||||
|
|
||||||
|
// Check for conflicts
|
||||||
|
conflicts, err := operation.CheckForConflicts()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not complete package conflict check: %s", err)
|
||||||
|
}
|
||||||
|
if len(conflicts) > 0 {
|
||||||
|
err = nil
|
||||||
|
for pkg, conflict := range conflicts {
|
||||||
|
err = errors.Join(err, PackageConflictErr{pkg, conflict})
|
||||||
|
}
|
||||||
|
if !forceInstallation {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
log.Printf("Warning: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether compiling source packages on different root directory
|
||||||
|
if rootDir != "/" {
|
||||||
|
sourcePackages := make([]string, 0)
|
||||||
|
for _, action := range operation.Actions {
|
||||||
|
switch action := action.(type) {
|
||||||
|
case *InstallPackageAction:
|
||||||
|
if action.BpmPackage.PkgInfo.Type == "source" {
|
||||||
|
sourcePackages = append(sourcePackages, action.BpmPackage.PkgInfo.Name)
|
||||||
|
}
|
||||||
|
case *FetchPackageAction:
|
||||||
|
if action.DatabaseEntry.Info.Type == "source" {
|
||||||
|
sourcePackages = append(sourcePackages, action.DatabaseEntry.Info.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return error if source packages are present in the operation
|
||||||
|
if len(sourcePackages) != 0 {
|
||||||
|
return nil, fmt.Errorf("cannot compile source packages in different root directory: %s", strings.Join(sourcePackages, ", "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return operation, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemovePackages removes the specified packages from the given root directory
|
||||||
|
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),
|
||||||
|
RootDir: rootDir,
|
||||||
|
compiledPackages: make(map[string]string),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for packages
|
||||||
|
for _, pkg := range packages {
|
||||||
|
bpmpkg := GetPackage(pkg, rootDir)
|
||||||
|
if isVirutal, vpkg := IsVirtualPackage(pkg, rootDir); isVirutal {
|
||||||
|
bpmpkg = GetPackage(vpkg, rootDir)
|
||||||
|
}
|
||||||
|
if bpmpkg == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
operation.AppendAction(&RemovePackageAction{BpmPackage: bpmpkg})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do package cleanup
|
||||||
|
if cleanupDependencies {
|
||||||
|
err := operation.Cleanup(MainBPMConfig.CleanupMakeDependencies)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not perform cleanup for operation: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return error if other packages depend on removed ones
|
||||||
|
if !force {
|
||||||
|
// Get packages and their dependants
|
||||||
|
packageDepndants := make(map[string][]string, 0)
|
||||||
|
for _, action := range operation.Actions {
|
||||||
|
dependants, err := GetPackageDependants(action.(*RemovePackageAction).BpmPackage.PkgInfo.Name, rootDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not get package dependants: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
packageDepndants[action.(*RemovePackageAction).BpmPackage.PkgInfo.Name] = dependants
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove dependant packages from map if they are to be removed by this operation
|
||||||
|
for pkg, required := range packageDepndants {
|
||||||
|
required = slices.DeleteFunc(required, func(pkgName string) bool {
|
||||||
|
_, ok := packageDepndants[pkgName]
|
||||||
|
return ok
|
||||||
|
})
|
||||||
|
packageDepndants[pkg] = required
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove empty keys from map
|
||||||
|
maps.DeleteFunc(packageDepndants, func(pkg string, required []string) bool {
|
||||||
|
return len(required) == 0
|
||||||
|
})
|
||||||
|
|
||||||
|
// Return error
|
||||||
|
if len(packageDepndants) != 0 {
|
||||||
|
return nil, PackageRemovalDependencyErr{RequiredPackages: packageDepndants}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return operation, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanupPackages finds packages installed as dependencies which are no longer required by the rest of the system in the given root directory
|
||||||
|
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),
|
||||||
|
RootDir: rootDir,
|
||||||
|
compiledPackages: make(map[string]string),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do package cleanup
|
||||||
|
err = operation.Cleanup(cleanupMakeDepends)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not perform cleanup for operation: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return operation, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CleanupCache(rootDir string, cleanupCompilationFiles, cleanupCompiledPackages, cleanupFetchedPackages, verbose bool) error {
|
||||||
|
if cleanupCompilationFiles {
|
||||||
|
globalCompilationCacheDir := path.Join(rootDir, "var/cache/bpm/compilation")
|
||||||
|
|
||||||
|
// Ensure path exists and is a directory
|
||||||
|
if stat, err := os.Stat(globalCompilationCacheDir); err == nil && stat.IsDir() {
|
||||||
|
// Delete directory
|
||||||
|
if verbose {
|
||||||
|
log.Printf("Removing directory (%s)\n", globalCompilationCacheDir)
|
||||||
|
}
|
||||||
|
err = os.RemoveAll(globalCompilationCacheDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get home directories of users in root directory
|
||||||
|
homeDirs, err := os.ReadDir(path.Join(rootDir, "home"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop through all home directories
|
||||||
|
for _, homeDir := range homeDirs {
|
||||||
|
// Skip if not a directory
|
||||||
|
if !homeDir.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
localCompilationDir := path.Join(rootDir, "home", homeDir.Name(), ".cache/bpm/compilation")
|
||||||
|
|
||||||
|
// Ensure path exists and is a directory
|
||||||
|
if stat, err := os.Stat(localCompilationDir); err != nil || !stat.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete directory
|
||||||
|
if verbose {
|
||||||
|
log.Printf("Removing directory (%s)\n", localCompilationDir)
|
||||||
|
}
|
||||||
|
err = os.RemoveAll(localCompilationDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cleanupCompiledPackages {
|
||||||
|
dirToRemove := path.Join(rootDir, "var/cache/bpm/compiled")
|
||||||
|
|
||||||
|
// Ensure path exists and is a directory
|
||||||
|
if stat, err := os.Stat(dirToRemove); err == nil && stat.IsDir() {
|
||||||
|
// Delete directory
|
||||||
|
if verbose {
|
||||||
|
log.Printf("Removing directory (%s)\n", dirToRemove)
|
||||||
|
}
|
||||||
|
err = os.RemoveAll(dirToRemove)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cleanupFetchedPackages {
|
||||||
|
dirToRemove := path.Join(rootDir, "var/cache/bpm/fetched")
|
||||||
|
|
||||||
|
// Ensure path exists and is a directory
|
||||||
|
if stat, err := os.Stat(dirToRemove); err == nil && stat.IsDir() {
|
||||||
|
// Delete directory
|
||||||
|
if verbose {
|
||||||
|
log.Printf("Removing directory (%s)\n", dirToRemove)
|
||||||
|
}
|
||||||
|
err = os.RemoveAll(dirToRemove)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatePackages fetches the newest versions of all installed packages from
|
||||||
|
func UpdatePackages(rootDir string, syncDatabase bool, installOptionalDependencies, forceInstallation, verbose bool) (operation *BPMOperation, err error) {
|
||||||
|
// Sync databases
|
||||||
|
if syncDatabase {
|
||||||
|
err := SyncDatabase(verbose)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not sync local database: %s", err)
|
||||||
|
}
|
||||||
|
if verbose {
|
||||||
|
fmt.Println("All package databases synced successfully!")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reload config and local databases
|
||||||
|
err = ReadConfig()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not read BPM config: %s", err)
|
||||||
|
}
|
||||||
|
err = ReadLocalDatabaseFiles()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not read local databases: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get installed packages and check for updates
|
||||||
|
pkgs, err := GetInstalledPackages(rootDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not get installed packages: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
operation = &BPMOperation{
|
||||||
|
Actions: make([]OperationAction, 0),
|
||||||
|
UnresolvedDepends: make([]string, 0),
|
||||||
|
Changes: make(map[string]string),
|
||||||
|
RootDir: rootDir,
|
||||||
|
compiledPackages: make(map[string]string),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for packages
|
||||||
|
for _, pkg := range pkgs {
|
||||||
|
if slices.Contains(MainBPMConfig.IgnorePackages, pkg) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
installedInfo := GetPackageInfo(pkg, rootDir)
|
||||||
|
if installedInfo == nil {
|
||||||
|
return nil, fmt.Errorf("could not get package info for package (%s)", pkg)
|
||||||
|
} else {
|
||||||
|
comparison := ComparePackageVersions(*entry.Info, *installedInfo)
|
||||||
|
if comparison > 0 {
|
||||||
|
operation.AppendAction(&FetchPackageAction{
|
||||||
|
InstallationReason: GetInstallationReason(pkg, rootDir),
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
if len(operation.UnresolvedDepends) != 0 {
|
||||||
|
if !forceInstallation {
|
||||||
|
return nil, DependencyNotFoundErr{operation.UnresolvedDepends}
|
||||||
|
} else if verbose {
|
||||||
|
log.Printf("Warning: %s", DependencyNotFoundErr{operation.UnresolvedDepends})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace obsolete packages
|
||||||
|
operation.ReplaceObsoletePackages()
|
||||||
|
return operation, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyncDatabase syncs all databases declared in /etc/bpm.conf
|
||||||
|
func SyncDatabase(verbose bool) (err error) {
|
||||||
|
for _, db := range MainBPMConfig.Databases {
|
||||||
|
if verbose {
|
||||||
|
fmt.Printf("Fetching package database file for database (%s)...\n", db.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := db.SyncLocalDatabaseFile()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
module git.enumerated.dev/bubble-package-manager/bpm/src/bpmlib
|
||||||
|
|
||||||
|
go 1.23
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/drone/envsubst v1.0.3
|
||||||
|
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f
|
||||||
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
|
)
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g=
|
||||||
|
github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g=
|
||||||
|
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f h1:xt29M2T6STgldg+WEP51gGePQCsQvklmP2eIhPIBK3g=
|
||||||
|
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f/go.mod h1:i4sF0l1fFnY1aiw08QQSwVAFxHEm311Me3WsU/X7nL0=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
package bpmlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BPMHook struct {
|
||||||
|
SourcePath string
|
||||||
|
SourceContent string
|
||||||
|
TriggerOperations []string `yaml:"trigger_operations"`
|
||||||
|
TargetType string `yaml:"target_type"`
|
||||||
|
Targets []string `yaml:"targets"`
|
||||||
|
Depends []string `yaml:"depends"`
|
||||||
|
Run string `yaml:"run"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// createHook returns a BPMHook instance based on the content of the given string
|
||||||
|
func createHook(sourcePath string) (*BPMHook, error) {
|
||||||
|
// Read hook from source path
|
||||||
|
bytes, err := os.ReadFile(sourcePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create base hook structure
|
||||||
|
hook := &BPMHook{
|
||||||
|
SourcePath: sourcePath,
|
||||||
|
SourceContent: string(bytes),
|
||||||
|
TriggerOperations: nil,
|
||||||
|
TargetType: "",
|
||||||
|
Targets: nil,
|
||||||
|
Depends: nil,
|
||||||
|
Run: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal yaml string
|
||||||
|
err = yaml.Unmarshal(bytes, hook)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure hook is valid
|
||||||
|
if err := hook.IsValid(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return hook, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValid ensures hook is valid
|
||||||
|
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 {
|
||||||
|
return errors.New("no trigger operations specified")
|
||||||
|
}
|
||||||
|
for _, operation := range hook.TriggerOperations {
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return nil as hook is valid
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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...)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if any targets are met
|
||||||
|
targetMet := false
|
||||||
|
for _, target := range hook.Targets {
|
||||||
|
if targetMet {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if hook.TargetType == "package" {
|
||||||
|
for change, operation := range packageChanges {
|
||||||
|
if target == change && slices.Contains(hook.TriggerOperations, operation) {
|
||||||
|
targetMet = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} 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 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the command
|
||||||
|
splitCommand := strings.Split(hook.Run, " ")
|
||||||
|
cmd := exec.Command(splitCommand[0], splitCommand[1:]...)
|
||||||
|
// Setup subprocess environment
|
||||||
|
cmd.Dir = "/"
|
||||||
|
// 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, " "))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,239 @@
|
|||||||
|
package bpmlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var localPackageInformation map[string]map[string]*BPMPackage = make(map[string]map[string]*BPMPackage)
|
||||||
|
|
||||||
|
func initializeLocalPackageInformation(rootDir string) (err error) {
|
||||||
|
// Return if information is already initialized
|
||||||
|
if _, ok := localPackageInformation[rootDir]; ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
tempPackageInformation := make(map[string]*BPMPackage)
|
||||||
|
|
||||||
|
// Get path to installed package information directory
|
||||||
|
installedDir := path.Join(rootDir, "var/lib/bpm/installed/")
|
||||||
|
|
||||||
|
// Get directory content
|
||||||
|
items, err := os.ReadDir(installedDir)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
localPackageInformation[rootDir] = make(map[string]*BPMPackage)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop through each subdirectory
|
||||||
|
for _, item := range items {
|
||||||
|
// Skip if not a directory
|
||||||
|
if !item.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read package info
|
||||||
|
infoData, err := os.ReadFile(path.Join(installedDir, item.Name(), "info"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
info, err := ReadPackageInfo(string(infoData))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read package files
|
||||||
|
files := getPackageFiles(info.Name, rootDir)
|
||||||
|
|
||||||
|
// Add package to slice
|
||||||
|
tempPackageInformation[info.Name] = &BPMPackage{
|
||||||
|
PkgInfo: info,
|
||||||
|
PkgFiles: files,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
localPackageInformation[rootDir] = tempPackageInformation
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetInstalledPackages(rootDir string) (ret []string, err error) {
|
||||||
|
// Initialize local package information
|
||||||
|
err = initializeLocalPackageInformation(rootDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop through each package and add it to slice
|
||||||
|
for _, bpmpkg := range localPackageInformation[rootDir] {
|
||||||
|
ret = append(ret, bpmpkg.PkgInfo.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsPackageInstalled(pkg, rootDir string) bool {
|
||||||
|
// Initialize local package information
|
||||||
|
err := initializeLocalPackageInformation(rootDir)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := localPackageInformation[rootDir][pkg]; !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPackageInfo(pkg string, rootDir string) *PackageInfo {
|
||||||
|
// Get BPM package
|
||||||
|
bpmpkg := GetPackage(pkg, rootDir)
|
||||||
|
|
||||||
|
// Return nil if not found
|
||||||
|
if bpmpkg == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return bpmpkg.PkgInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsVirtualPackage(pkg, rootDir string) (bool, string) {
|
||||||
|
pkgs, err := GetInstalledPackages(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, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPackage(pkg, rootDir string) *BPMPackage {
|
||||||
|
err := initializeLocalPackageInformation(rootDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
bpmpkg := localPackageInformation[rootDir][pkg]
|
||||||
|
|
||||||
|
return bpmpkg
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAllPackageFiles(rootDir string, excludePackages ...string) (map[string][]*BPMPackage, error) {
|
||||||
|
ret := make(map[string][]*BPMPackage)
|
||||||
|
|
||||||
|
pkgNames, err := GetInstalledPackages(rootDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pkgName := range pkgNames {
|
||||||
|
if slices.Contains(excludePackages, pkgName) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
bpmpkg := GetPackage(pkgName, rootDir)
|
||||||
|
if bpmpkg == nil {
|
||||||
|
return nil, fmt.Errorf("could not get BPM package (%s)", pkgName)
|
||||||
|
}
|
||||||
|
for _, entry := range bpmpkg.PkgFiles {
|
||||||
|
ret[entry.Path] = append(ret[entry.Path], bpmpkg)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
if _, err := os.Stat(installedDir); os.IsNotExist(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(pkgDir); os.IsNotExist(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
bs, err := os.ReadFile(files)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, line := range strings.Split(string(bs), "\n") {
|
||||||
|
if strings.TrimSpace(line) == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
stringEntry := strings.Split(strings.TrimSpace(line), " ")
|
||||||
|
if len(stringEntry) < 5 {
|
||||||
|
pkgFiles = append(pkgFiles, &PackageFileEntry{
|
||||||
|
Path: strings.TrimSuffix(line, "/"),
|
||||||
|
OctalPerms: 0,
|
||||||
|
UserID: 0,
|
||||||
|
GroupID: 0,
|
||||||
|
SizeInBytes: 0,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
uid, err := strconv.ParseInt(stringEntry[len(stringEntry)-4], 0, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
octalPerms, err := strconv.ParseInt(stringEntry[len(stringEntry)-3], 0, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
gid, err := strconv.ParseInt(stringEntry[len(stringEntry)-2], 0, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
size, err := strconv.ParseUint(stringEntry[len(stringEntry)-1], 0, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
pkgFiles = append(pkgFiles, &PackageFileEntry{
|
||||||
|
Path: strings.TrimSuffix(strings.Join(stringEntry[:len(stringEntry)-4], " "), "/"),
|
||||||
|
OctalPerms: uint32(octalPerms),
|
||||||
|
UserID: int(uid),
|
||||||
|
GroupID: int(gid),
|
||||||
|
SizeInBytes: size,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return pkgFiles
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -0,0 +1,667 @@
|
|||||||
|
package bpmlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"text/tabwriter"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BPMOperation struct {
|
||||||
|
Actions []OperationAction
|
||||||
|
UnresolvedDepends []string
|
||||||
|
Changes map[string]string
|
||||||
|
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() uint64 {
|
||||||
|
var ret uint64 = 0
|
||||||
|
for _, action := range operation.Actions {
|
||||||
|
if action.GetActionType() == "fetch" {
|
||||||
|
ret += action.(*FetchPackageAction).DatabaseEntry.DownloadSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (operation *BPMOperation) GetTotalInstalledSize() uint64 {
|
||||||
|
var ret uint64 = 0
|
||||||
|
for _, action := range operation.Actions {
|
||||||
|
if action.GetActionType() == "install" {
|
||||||
|
ret += action.(*InstallPackageAction).BpmPackage.GetInstalledSize()
|
||||||
|
} else if action.GetActionType() == "fetch" {
|
||||||
|
ret += action.(*FetchPackageAction).DatabaseEntry.InstalledSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (operation *BPMOperation) GetFinalActionSize(rootDir string) int64 {
|
||||||
|
var ret int64 = 0
|
||||||
|
for _, action := range operation.Actions {
|
||||||
|
if action.GetActionType() == "install" {
|
||||||
|
ret += int64(action.(*InstallPackageAction).BpmPackage.GetInstalledSize())
|
||||||
|
if IsPackageInstalled(action.(*InstallPackageAction).BpmPackage.PkgInfo.Name, rootDir) {
|
||||||
|
ret -= int64(GetPackage(action.(*InstallPackageAction).BpmPackage.PkgInfo.Name, rootDir).GetInstalledSize())
|
||||||
|
}
|
||||||
|
} else if action.GetActionType() == "fetch" {
|
||||||
|
ret += int64(action.(*FetchPackageAction).DatabaseEntry.InstalledSize)
|
||||||
|
} else if action.GetActionType() == "remove" {
|
||||||
|
ret -= int64(action.(*RemovePackageAction).BpmPackage.GetInstalledSize())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (operation *BPMOperation) ResolveDependencies(reinstallDependencies, installOptionalDependencies, verbose bool) error {
|
||||||
|
pos := 0
|
||||||
|
for _, value := range slices.Clone(operation.Actions) {
|
||||||
|
var pkgInfo *PackageInfo
|
||||||
|
if value.GetActionType() == "install" {
|
||||||
|
action := value.(*InstallPackageAction)
|
||||||
|
pkgInfo = action.BpmPackage.PkgInfo
|
||||||
|
} else if value.GetActionType() == "fetch" {
|
||||||
|
action := value.(*FetchPackageAction)
|
||||||
|
pkgInfo = action.DatabaseEntry.Info
|
||||||
|
} else {
|
||||||
|
pos++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
resolved, unresolved := ResolveAllPackageDependenciesFromDatabases(pkgInfo, pkgInfo.Type == "source", installOptionalDependencies, !reinstallDependencies, verbose, operation.RootDir)
|
||||||
|
|
||||||
|
operation.UnresolvedDepends = append(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{
|
||||||
|
InstallationReason: resolvedPkg.InstallationReason,
|
||||||
|
DatabaseEntry: entry,
|
||||||
|
})
|
||||||
|
pos++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pos++
|
||||||
|
}
|
||||||
|
|
||||||
|
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 pkg, action := range removeActions {
|
||||||
|
dependants, err := GetPackageDependants(action.BpmPackage.PkgInfo.Name, operation.RootDir)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("could not get dependant packages for package (" + pkg + ")")
|
||||||
|
}
|
||||||
|
dependants = slices.DeleteFunc(dependants, func(d string) bool {
|
||||||
|
if _, ok := removeActions[d]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
if len(dependants) != 0 {
|
||||||
|
operation.RemoveAction(pkg, action.GetActionType())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (operation *BPMOperation) Cleanup(cleanupMakeDepends bool) error {
|
||||||
|
// Get all installed packages
|
||||||
|
installedPackageNames, err := GetInstalledPackages(operation.RootDir)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not get installed packages: %s", err)
|
||||||
|
}
|
||||||
|
installedPackages := make([]*PackageInfo, len(installedPackageNames))
|
||||||
|
for i, value := range installedPackageNames {
|
||||||
|
bpmpkg := GetPackage(value, operation.RootDir)
|
||||||
|
if bpmpkg == nil {
|
||||||
|
return errors.New("could not find installed package (" + value + ")")
|
||||||
|
}
|
||||||
|
installedPackages[i] = bpmpkg.PkgInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get packages to remove
|
||||||
|
removeActions := make(map[string]*RemovePackageAction)
|
||||||
|
for _, action := range slices.Clone(operation.Actions) {
|
||||||
|
if action.GetActionType() == "remove" {
|
||||||
|
removeActions[action.(*RemovePackageAction).BpmPackage.PkgInfo.Name] = action.(*RemovePackageAction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not resolve dependencies or add package to keepPackages slice if package removal action exists for it
|
||||||
|
if _, ok := removeActions[pkg.Name]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
keepPackages = append(keepPackages, pkg.Name)
|
||||||
|
resolved := pkg.GetDependenciesRecursive(!cleanupMakeDepends, operation.RootDir)
|
||||||
|
for _, value := range resolved {
|
||||||
|
if !slices.Contains(keepPackages, value) {
|
||||||
|
keepPackages = append(keepPackages, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all installed packages that are not in the keepPackages slice and add them to the BPM operation
|
||||||
|
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) {
|
||||||
|
bpmpkg := GetPackage(pkg, operation.RootDir)
|
||||||
|
if bpmpkg == nil {
|
||||||
|
return errors.New("Error: could not find installed package (" + pkg + ")")
|
||||||
|
}
|
||||||
|
operation.Actions = append(operation.Actions, &RemovePackageAction{BpmPackage: bpmpkg})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (operation *BPMOperation) ReplaceObsoletePackages() {
|
||||||
|
for _, value := range slices.Clone(operation.Actions) {
|
||||||
|
var pkgInfo *PackageInfo
|
||||||
|
if value.GetActionType() == "install" {
|
||||||
|
action := value.(*InstallPackageAction)
|
||||||
|
pkgInfo = action.BpmPackage.PkgInfo
|
||||||
|
|
||||||
|
} else if value.GetActionType() == "fetch" {
|
||||||
|
action := value.(*FetchPackageAction)
|
||||||
|
pkgInfo = action.DatabaseEntry.Info
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, r := range pkgInfo.Replaces {
|
||||||
|
if bpmpkg := GetPackage(r, operation.RootDir); bpmpkg != nil && !operation.ActionsContainPackage(bpmpkg.PkgInfo.Name) {
|
||||||
|
operation.InsertActionAt(0, &RemovePackageAction{
|
||||||
|
BpmPackage: bpmpkg,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (operation *BPMOperation) CheckForConflicts() (map[string][]string, error) {
|
||||||
|
conflicts := make(map[string][]string)
|
||||||
|
installedPackages, err := GetInstalledPackages(operation.RootDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
allPackages := make([]*PackageInfo, len(installedPackages))
|
||||||
|
for i, value := range installedPackages {
|
||||||
|
bpmpkg := GetPackage(value, operation.RootDir)
|
||||||
|
if bpmpkg == nil {
|
||||||
|
return nil, fmt.Errorf("could not find installed package (%s)", value)
|
||||||
|
}
|
||||||
|
allPackages[i] = bpmpkg.PkgInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all new packages to the allPackages slice
|
||||||
|
for _, value := range slices.Clone(operation.Actions) {
|
||||||
|
if value.GetActionType() == "install" {
|
||||||
|
action := value.(*InstallPackageAction)
|
||||||
|
pkgInfo := action.BpmPackage.PkgInfo
|
||||||
|
allPackages = append(allPackages, pkgInfo)
|
||||||
|
} else if value.GetActionType() == "fetch" {
|
||||||
|
action := value.(*FetchPackageAction)
|
||||||
|
pkgInfo := action.DatabaseEntry.Info
|
||||||
|
allPackages = append(allPackages, pkgInfo)
|
||||||
|
} else if value.GetActionType() == "remove" {
|
||||||
|
action := value.(*RemovePackageAction)
|
||||||
|
pkgInfo := action.BpmPackage.PkgInfo
|
||||||
|
for i := len(allPackages) - 1; i >= 0; i-- {
|
||||||
|
info := allPackages[i]
|
||||||
|
if info.Name == pkgInfo.Name {
|
||||||
|
allPackages = append(allPackages[:i], allPackages[i+1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, value := range allPackages {
|
||||||
|
for _, conflict := range value.Conflicts {
|
||||||
|
if slices.ContainsFunc(allPackages, func(info *PackageInfo) bool {
|
||||||
|
return info.Name == conflict
|
||||||
|
}) {
|
||||||
|
conflicts[value.Name] = append(conflicts[value.Name], conflict)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return conflicts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (operation *BPMOperation) ShowOperationSummary() {
|
||||||
|
if len(operation.Actions) == 0 {
|
||||||
|
fmt.Println("No action needs to be taken")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writer := tabwriter.NewWriter(os.Stdout, 6, 4, 6, ' ', 0)
|
||||||
|
fmt.Fprintln(writer, "Name\tVersion\tAction\tInstallation Reason\tFrom Source")
|
||||||
|
|
||||||
|
for _, value := range operation.Actions {
|
||||||
|
var pkgInfo *PackageInfo
|
||||||
|
var installationReason = InstallationReasonUnknown
|
||||||
|
if value.GetActionType() == "install" {
|
||||||
|
installationReason = value.(*InstallPackageAction).InstallationReason
|
||||||
|
pkgInfo = value.(*InstallPackageAction).BpmPackage.PkgInfo
|
||||||
|
if value.(*InstallPackageAction).SplitPackageToInstall != "" {
|
||||||
|
pkgInfo = pkgInfo.GetSplitPackageInfo(value.(*InstallPackageAction).SplitPackageToInstall)
|
||||||
|
}
|
||||||
|
} else if value.GetActionType() == "fetch" {
|
||||||
|
installationReason = value.(*FetchPackageAction).InstallationReason
|
||||||
|
pkgInfo = value.(*FetchPackageAction).DatabaseEntry.Info
|
||||||
|
} else {
|
||||||
|
pkgInfo = value.(*RemovePackageAction).BpmPackage.PkgInfo
|
||||||
|
fmt.Fprintf(writer, "%s\t%s\t%s\t%s\t%s\n", pkgInfo.Name, pkgInfo.GetFullVersion(), "Remove", "-", "-")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
installationReasonStr := ""
|
||||||
|
switch installationReason {
|
||||||
|
case InstallationReasonManual:
|
||||||
|
installationReasonStr = "Manual"
|
||||||
|
case InstallationReasonDependency:
|
||||||
|
installationReasonStr = "Dependency"
|
||||||
|
case InstallationReasonMakeDependency:
|
||||||
|
installationReasonStr = "Make dependency"
|
||||||
|
default:
|
||||||
|
installationReasonStr = "Unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
} else {
|
||||||
|
comparison := ComparePackageVersions(*pkgInfo, *installedInfo)
|
||||||
|
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")
|
||||||
|
} 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")
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(writer, "%s\t%s\t%s\t%s\t%t\n", pkgInfo.Name, pkgInfo.GetFullVersion(), "Reinstall", installationReasonStr, pkgInfo.Type == "source")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Flush()
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
if operation.RootDir != "/" {
|
||||||
|
fmt.Println("Warning: Operating in " + operation.RootDir)
|
||||||
|
}
|
||||||
|
if operation.GetTotalDownloadSize() > 0 {
|
||||||
|
fmt.Printf("%s will be downloaded to complete this operation\n", unsignedBytesToHumanReadable(operation.GetTotalDownloadSize()))
|
||||||
|
}
|
||||||
|
if operation.GetFinalActionSize(operation.RootDir) > 0 {
|
||||||
|
fmt.Printf("A total of %s will be installed after the operation finishes\n", bytesToHumanReadable(operation.GetFinalActionSize(operation.RootDir)))
|
||||||
|
} else if operation.GetFinalActionSize(operation.RootDir) < 0 {
|
||||||
|
fmt.Printf("A total of %s will be freed after the operation finishes\n", strings.TrimPrefix(bytesToHumanReadable(operation.GetFinalActionSize(operation.RootDir)), "-"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (operation *BPMOperation) ShowSourcePackageContent() (sourcePackagesShown int, err error) {
|
||||||
|
// Fetch packages
|
||||||
|
if !operation.hasFetchedPackages {
|
||||||
|
err = operation.FetchPackages()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, action := range operation.Actions {
|
||||||
|
if action.GetActionType() != "install" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if action.(*InstallPackageAction).BpmPackage.PkgInfo.Type != "source" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err = showPackageFiles(action.(*InstallPackageAction).File)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sourcePackagesShown++
|
||||||
|
}
|
||||||
|
|
||||||
|
return sourcePackagesShown, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (operation *BPMOperation) RunHooks(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)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = hook.Execute(operation.Changes, 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) FetchPackages() (err error) {
|
||||||
|
// Fetch packages from databases
|
||||||
|
if slices.ContainsFunc(operation.Actions, func(action OperationAction) bool {
|
||||||
|
return action.GetActionType() == "fetch"
|
||||||
|
}) {
|
||||||
|
fmt.Println("Fetching packages from available databases...")
|
||||||
|
|
||||||
|
// Create map for fetched packages
|
||||||
|
fetchedPackages := make(map[string]string)
|
||||||
|
|
||||||
|
for i, action := range operation.Actions {
|
||||||
|
if action.GetActionType() != "fetch" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get database entry
|
||||||
|
entry := action.(*FetchPackageAction).DatabaseEntry
|
||||||
|
|
||||||
|
// Create bpmpkg variable
|
||||||
|
var bpmpkg *BPMPackage
|
||||||
|
|
||||||
|
// Check if package has already been fetched from download link
|
||||||
|
if _, ok := fetchedPackages[entry.Filepath]; !ok {
|
||||||
|
// Fetch package from database
|
||||||
|
fetchedPackage, err := entry.Database.FetchPackage(entry.Info.Name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not fetch package (%s): %s\n", entry.Info.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read fetched package
|
||||||
|
bpmpkg, err = ReadPackage(fetchedPackage)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not fetch package (%s): %s\n", entry.Info.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add fetched package to map
|
||||||
|
fetchedPackages[entry.Filepath] = fetchedPackage
|
||||||
|
|
||||||
|
fmt.Printf("Package (%s) was successfully fetched!\n", entry.Info.Name)
|
||||||
|
} else {
|
||||||
|
// Read fetched package
|
||||||
|
bpmpkg, err = ReadPackage(fetchedPackages[entry.Filepath])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not read package (%s): %s\n", entry.Info.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Package (%s) was successfully fetched!\n", entry.Info.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if bpmpkg.PkgInfo.IsSplitPackage() {
|
||||||
|
operation.Actions[i] = &InstallPackageAction{
|
||||||
|
File: fetchedPackages[entry.Filepath],
|
||||||
|
InstallationReason: action.(*FetchPackageAction).InstallationReason,
|
||||||
|
BpmPackage: bpmpkg,
|
||||||
|
SplitPackageToInstall: entry.Info.Name,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
operation.Actions[i] = &InstallPackageAction{
|
||||||
|
File: fetchedPackages[entry.Filepath],
|
||||||
|
InstallationReason: action.(*FetchPackageAction).InstallationReason,
|
||||||
|
BpmPackage: bpmpkg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
operation.hasFetchedPackages = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (operation *BPMOperation) Execute(verbose, force bool) (err error) {
|
||||||
|
// Fetch packages
|
||||||
|
if !operation.hasFetchedPackages {
|
||||||
|
err = operation.FetchPackages()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine words to be used for the following message
|
||||||
|
words := make([]string, 0)
|
||||||
|
if slices.ContainsFunc(operation.Actions, func(action OperationAction) bool {
|
||||||
|
return action.GetActionType() == "install"
|
||||||
|
}) {
|
||||||
|
words = append(words, "Installing")
|
||||||
|
}
|
||||||
|
|
||||||
|
if slices.ContainsFunc(operation.Actions, func(action OperationAction) bool {
|
||||||
|
return action.GetActionType() == "remove"
|
||||||
|
}) {
|
||||||
|
words = append(words, "Removing")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(words) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
fmt.Printf("%s packages...\n", strings.Join(words, "/"))
|
||||||
|
|
||||||
|
// Installing/Removing packages from system
|
||||||
|
for _, action := range operation.Actions {
|
||||||
|
if action.GetActionType() == "remove" {
|
||||||
|
pkgInfo := action.(*RemovePackageAction).BpmPackage.PkgInfo
|
||||||
|
err := removePackage(pkgInfo.Name, verbose, operation.RootDir)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not remove package (%s): %s\n", pkgInfo.Name, err)
|
||||||
|
}
|
||||||
|
} else if action.GetActionType() == "install" {
|
||||||
|
value := action.(*InstallPackageAction)
|
||||||
|
fileToInstall := value.File
|
||||||
|
bpmpkg := value.BpmPackage
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Compile package if type is 'source'
|
||||||
|
if bpmpkg.PkgInfo.Type == "source" {
|
||||||
|
// Get path to compiled package directory
|
||||||
|
compiledDir := path.Join(operation.RootDir, "/var/cache/bpm/compiled/")
|
||||||
|
|
||||||
|
// Create compiled package directory if not exists
|
||||||
|
if _, err := os.Stat(compiledDir); err != nil {
|
||||||
|
err := os.MkdirAll(compiledDir, 0755)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get package name to install
|
||||||
|
pkgNameToInstall := bpmpkg.PkgInfo.Name
|
||||||
|
if bpmpkg.PkgInfo.IsSplitPackage() {
|
||||||
|
pkgNameToInstall = value.SplitPackageToInstall
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile source package if not compiled already
|
||||||
|
if _, ok := operation.compiledPackages[pkgNameToInstall]; !ok {
|
||||||
|
outputBpmPackages, err := CompileSourcePackage(value.File, compiledDir, false)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not compile source package (%s): %s\n", value.File, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add compiled packages to slice
|
||||||
|
for pkgName, pkgFile := range outputBpmPackages {
|
||||||
|
operation.compiledPackages[pkgName] = pkgFile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set values
|
||||||
|
fileToInstall = operation.compiledPackages[pkgNameToInstall]
|
||||||
|
bpmpkg, err = ReadPackage(fileToInstall)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not read package (%s): %s\n", fileToInstall, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if value.InstallationReason != InstallationReasonManual {
|
||||||
|
err = installPackage(fileToInstall, operation.RootDir, verbose, true)
|
||||||
|
} else {
|
||||||
|
err = installPackage(fileToInstall, 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.Printf("Package (%s) was successfully installed\n", bpmpkg.PkgInfo.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println("Operation complete!")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type OperationAction interface {
|
||||||
|
GetActionType() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type InstallPackageAction struct {
|
||||||
|
File string
|
||||||
|
InstallationReason InstallationReason
|
||||||
|
SplitPackageToInstall string
|
||||||
|
BpmPackage *BPMPackage
|
||||||
|
}
|
||||||
|
|
||||||
|
func (action *InstallPackageAction) GetActionType() string {
|
||||||
|
return "install"
|
||||||
|
}
|
||||||
|
|
||||||
|
type FetchPackageAction struct {
|
||||||
|
InstallationReason InstallationReason
|
||||||
|
DatabaseEntry *BPMDatabaseEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
func (action *FetchPackageAction) GetActionType() string {
|
||||||
|
return "fetch"
|
||||||
|
}
|
||||||
|
|
||||||
|
type RemovePackageAction struct {
|
||||||
|
BpmPackage *BPMPackage
|
||||||
|
}
|
||||||
|
|
||||||
|
func (action *RemovePackageAction) GetActionType() string {
|
||||||
|
return "remove"
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,209 @@
|
|||||||
|
package bpmlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tarballFileReader struct {
|
||||||
|
tarReader *tar.Reader
|
||||||
|
file *os.File
|
||||||
|
}
|
||||||
|
|
||||||
|
func listTarballContent(tarballPath string) (content []string, err error) {
|
||||||
|
file, err := os.Open(tarballPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
tr := tar.NewReader(file)
|
||||||
|
for {
|
||||||
|
header, err := tr.Next()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch header.Typeflag {
|
||||||
|
case tar.TypeDir:
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
content = append(content, header.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return content, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readTarballFile(tarballPath, fileToExtract string) (*tarballFileReader, error) {
|
||||||
|
file, err := os.Open(tarballPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tr := tar.NewReader(file)
|
||||||
|
for {
|
||||||
|
header, err := tr.Next()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if header.Name == fileToExtract {
|
||||||
|
if header.Typeflag != tar.TypeReg {
|
||||||
|
return nil, errors.New("file to extract must be a regular file")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &tarballFileReader{
|
||||||
|
tarReader: tr,
|
||||||
|
file: file,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("could not file in tarball")
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractTarballFile(tarballPath, fileToExtract string, workingDirectory string, uid, gid int) (err error) {
|
||||||
|
file, err := os.Open(tarballPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
tr := tar.NewReader(file)
|
||||||
|
for {
|
||||||
|
header, err := tr.Next()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if filename does not match
|
||||||
|
if header.Name != fileToExtract {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim directory name from header name
|
||||||
|
header.Name = strings.Split(header.Name, "/")[len(strings.Split(header.Name, "/"))-1]
|
||||||
|
outputPath := path.Join(workingDirectory, header.Name)
|
||||||
|
|
||||||
|
switch header.Typeflag {
|
||||||
|
case tar.TypeReg:
|
||||||
|
// Create file and set permissions
|
||||||
|
file, err = os.Create(outputPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err := file.Chmod(header.FileInfo().Mode())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if uid >= 0 && gid >= 0 {
|
||||||
|
err = file.Chown(uid, gid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy data to file
|
||||||
|
_, err = io.Copy(file, tr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close file
|
||||||
|
file.Close()
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractTarballDirectory(tarballPath, directoryToExtract, workingDirectory string, uid, gid int) (err error) {
|
||||||
|
file, err := os.Open(tarballPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
tr := tar.NewReader(file)
|
||||||
|
for {
|
||||||
|
header, err := tr.Next()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(header.Name, directoryToExtract+"/") {
|
||||||
|
// Skip directory to extract
|
||||||
|
if strings.TrimRight(header.Name, "/") == workingDirectory {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim directory name from header name
|
||||||
|
header.Name = strings.TrimPrefix(header.Name, directoryToExtract+"/")
|
||||||
|
outputPath := path.Join(workingDirectory, header.Name)
|
||||||
|
|
||||||
|
switch header.Typeflag {
|
||||||
|
case tar.TypeDir:
|
||||||
|
// Create directory
|
||||||
|
err := os.MkdirAll(outputPath, 0755)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set directory owner
|
||||||
|
if uid >= 0 && gid >= 0 {
|
||||||
|
err = os.Chown(outputPath, uid, gid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case tar.TypeReg:
|
||||||
|
// Create file and set permissions
|
||||||
|
file, err = os.Create(outputPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err := file.Chmod(header.FileInfo().Mode())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if uid >= 0 && gid >= 0 {
|
||||||
|
err = file.Chown(uid, gid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy data to file
|
||||||
|
_, err = io.Copy(file, tr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close file
|
||||||
|
file.Close()
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
package bpmlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BPMLock struct {
|
||||||
|
file *os.File
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lock *BPMLock) Unlock() error {
|
||||||
|
err := lock.file.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Remove(lock.path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create file
|
||||||
|
f, err := os.Create(path.Join(rootDir, "var/lib/bpm/bpm.lock"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get exclusive file lock on file
|
||||||
|
err = syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &BPMLock{f, path.Join(rootDir, "var/lib/bpm/bpm.lock")}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetArch() string {
|
||||||
|
uname := syscall.Utsname{}
|
||||||
|
err := syscall.Uname(&uname)
|
||||||
|
if err != nil {
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
var byteString [65]byte
|
||||||
|
var indexLength int
|
||||||
|
for ; uname.Machine[indexLength] != 0; indexLength++ {
|
||||||
|
byteString[indexLength] = uint8(uname.Machine[indexLength])
|
||||||
|
}
|
||||||
|
return string(byteString[:indexLength])
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringSliceRemove(s []string, r string) []string {
|
||||||
|
for i, v := range s {
|
||||||
|
if v == r {
|
||||||
|
return append(s[:i], s[i+1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func unsignedBytesToHumanReadable(b uint64) string {
|
||||||
|
bf := float64(b)
|
||||||
|
for _, unit := range []string{"", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"} {
|
||||||
|
if math.Abs(bf) < 1024.0 {
|
||||||
|
return fmt.Sprintf("%3.1f%sB", bf, unit)
|
||||||
|
}
|
||||||
|
bf /= 1024.0
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%.1fYiB", bf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func bytesToHumanReadable(b int64) string {
|
||||||
|
bf := float64(b)
|
||||||
|
for _, unit := range []string{"", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"} {
|
||||||
|
if math.Abs(bf) < 1024.0 {
|
||||||
|
return fmt.Sprintf("%3.1f%sB", bf, unit)
|
||||||
|
}
|
||||||
|
bf /= 1024.0
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%.1fYiB", bf)
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -1,3 +0,0 @@
|
|||||||
---
|
|
||||||
compilation_env: []
|
|
||||||
silent_compilation: false
|
|
||||||
Binary file not shown.
@@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2024 CapCreeperGR
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
name: bpm
|
|
||||||
description: The Bubble Package Manager
|
|
||||||
version: 0.3.0
|
|
||||||
url: https://gitlab.com/bubble-package-manager/bpm/
|
|
||||||
license: GPL3
|
|
||||||
architecture: x86_64
|
|
||||||
type: binary
|
|
||||||
keep: etc/bpm.conf
|
|
||||||
Binary file not shown.
Binary file not shown.
@@ -1,5 +0,0 @@
|
|||||||
name: hello
|
|
||||||
description: A simple hello world program
|
|
||||||
version: 1.0
|
|
||||||
architecture: x86_64
|
|
||||||
type: binary
|
|
||||||
Reference in New Issue
Block a user