17 Commits
21 changed files with 414 additions and 211 deletions
+19 -7
View File
@@ -8,7 +8,7 @@ BPM Utils provides a number of different helper commands for creating and mainta
## Provided utilities ## Provided utilities
- bpm-setup (Sets up directories for BPM source package creation) - bpm-setup (Sets up directories for BPM source package creation)
- bpm-repo (Allows for easy management of multiple-package repositories) - bpm-repo (Allows for easy management of multiple-package repositories)
- bpm-package (Turns a BPM package directory into a .bpm archive) - bpm-package (Turns a BPM source package directory into a .bpm archive)
## Installation ## Installation
#### Using a package manager #### Using a package manager
@@ -33,7 +33,7 @@ Creating a package for BPM with these utilities is simple
bpm-setup -D my_package bpm-setup -D my_package
``` ```
2) This will create a directory named `my_package` containing all files required for bpm package creation 2) This will create a directory named `my_package` containing all files required for bpm package creation
3) You may wish to edit the pkg.info descriptor file inside the newly created directory to include dependencies or add/change other information. Here's an example of what a descriptor file could look like 3) You may wish to edit the pkg.info metedata file inside the newly created directory to include dependencies or add/change other information. Here's an example of what a metedata file could look like
```yaml ```yaml
name: my_package name: my_package
description: My package's description description: My package's description
@@ -42,11 +42,23 @@ revision: 2 (Optional)
url: https://www.my-website.com/ (Optional) url: https://www.my-website.com/ (Optional)
license: MyLicense (Optional) license: MyLicense (Optional)
architecture: x86_64 architecture: x86_64
depends: ["dependency1","dependency2"] (Optional)
optional_depends: ["optional_depend1","optional_depend2"] (Optional)
make_depends: ["make_depend1","make_depend2"] (Optional)
keep: ["etc/my_config.conf"] (Optional)
type: source type: source
depends:
- dependency1
- dependency2
optional_depends:
- optional_depend1
- optional_depend2
make_depends:
- make_depend1
- make_depend2
keep:
- etc/my_config.conf
downloads:
- url: https://wwww.my-url.com/file.tar.gz
extract_strip_components: 1
extract_to_bpm_source: true
checksum: 9d19c8884cb22a594ba06a4caa6a3088e15ddfd4f3ede8c3b9e8f5cbb5a4a7a8
``` ```
4) If you would like to bundle patches or other files with your package place them in the 'source-files' directory. They will be extracted to the same location as the source.sh file during compilation 4) If you would like to bundle patches or other files with your package place them in the 'source-files' directory. They will be extracted to the same location as the source.sh file during compilation
@@ -55,4 +67,4 @@ type: source
``` ```
bpm-package bpm-package
``` ```
7) The `bpm-package` command will output a binary bpm archive which can be installed by BPM using `bpm install <file.bpm>`. If you are operating inside a BPM repository created using `bpm-repo` the file will automatically be moved to the binary subdirectory of your package repository 7) The `bpm-package` command will output a source bpm archive (and binary if passed the '-c' flag) which can be installed by BPM using `bpm install <file.bpm>`. If you are operating inside a BPM repository created using `bpm-repo` the file will automatically be moved to the binary subdirectory of your package repository
+32
View File
@@ -0,0 +1,32 @@
# This is the source.sh script. It is executed by BPM in a temporary directory when compiling a source package
# BPM Expects the source code to be extracted into the automatically created 'source' directory which can be accessed using $BPM_SOURCE
# BPM Expects the output files to be present in the automatically created 'output' directory which can be accessed using $BPM_OUTPUT
# The prepare function is executed in the root of the temp directory
# This function is used for putting downloaded files to the correct location or applying patches
prepare() {
cd "$BPM_SOURCE"
# Fetch cargo dependencies
cargo fetch --locked --target "$(rustc -vV | sed -n 's/host: //p')"
}
# The build function is executed in the source directory
# This function is used to compile the source code
build() {
cargo build --release --frozen --workspace
}
# The check function is executed in the source directory
# This function is used to run tests to verify the package has been compiled correctly
check() {
cargo test --frozen
}
# The package function is executed in the source directory
# This function is used to move the compiled files into the output directory
package() {
find target/release -maxdepth 1 -type f -executable -exec install -Dm755 -t "$BPM_OUTPUT"/usr/bin {} +
# Install package license
install -Dm644 "$BPM_SOURCE"/LICENSE "$BPM_OUTPUT"/usr/share/licenses/$NAME/LICENSE
}
+6 -17
View File
@@ -2,40 +2,29 @@
# BPM Expects the source code to be extracted into the automatically created 'source' directory which can be accessed using $BPM_SOURCE # BPM Expects the source code to be extracted into the automatically created 'source' directory which can be accessed using $BPM_SOURCE
# BPM Expects the output files to be present in the automatically created 'output' directory which can be accessed using $BPM_OUTPUT # BPM Expects the output files to be present in the automatically created 'output' directory which can be accessed using $BPM_OUTPUT
DOWNLOAD="https://wwww.my-url.com/file.tar.gz"
FILENAME="${DOWNLOAD##*/}"
# The prepare function is executed in the root of the temp directory # The prepare function is executed in the root of the temp directory
# This function is used for downloading files and putting them into the correct location # This function is used for putting downloaded files to the correct location or applying patches
prepare() { prepare() {
wget "$DOWNLOAD" echo "You may remove this function if you do not intend to use it"
tar -xvf "$FILENAME" --strip-components=1 -C "$BPM_SOURCE"
} }
# The build function is executed in the source directory # The build function is executed in the source directory
# This function is used to compile the source code # This function is used to compile the source code
build() { build() {
mkdir build cmake -B build -DCMAKE_INSTALL_PREFIX=/usr
cd build cmake --build build
cmake -DCMAKE_INSTALL_PREFIX=/usr ..
make
} }
# The check function is executed in the source directory # The check function is executed in the source directory
# This function is used to run tests to verify the package has been compiled correctly # This function is used to run tests to verify the package has been compiled correctly
check() { check() {
cd build ctest --test-dir build
make test
} }
# The package function is executed in the source directory # The package function is executed in the source directory
# This function is used to move the compiled files into the output directory # This function is used to move the compiled files into the output directory
package() { package() {
cd build DESTDIR="$BPM_OUTPUT" cmake --install build
make DESTDIR="$BPM_OUTPUT" install
# Install package license # Install package license
install -Dm644 "$BPM_SOURCE"/LICENSE "$BPM_OUTPUT"/usr/share/licenses/$NAME/LICENSE install -Dm644 "$BPM_SOURCE"/LICENSE "$BPM_OUTPUT"/usr/share/licenses/$NAME/LICENSE
+2 -6
View File
@@ -2,14 +2,10 @@
# BPM Expects the source code to be extracted into the automatically created 'source' directory which can be accessed using $BPM_SOURCE # BPM Expects the source code to be extracted into the automatically created 'source' directory which can be accessed using $BPM_SOURCE
# BPM Expects the output files to be present in the automatically created 'output' directory which can be accessed using $BPM_OUTPUT # BPM Expects the output files to be present in the automatically created 'output' directory which can be accessed using $BPM_OUTPUT
DOWNLOAD="https://wwww.my-url.com/file.tar.gz"
FILENAME="${DOWNLOAD##*/}"
# The prepare function is executed in the root of the temp directory # The prepare function is executed in the root of the temp directory
# This function is used for downloading files and putting them into the correct location # This function is used for putting downloaded files to the correct location or applying patches
prepare() { prepare() {
wget "$DOWNLOAD" echo "You may remove this function if you do not intend to use it"
tar -xvf "$FILENAME" --strip-components=1 -C "$BPM_SOURCE"
} }
# The build function is executed in the source directory # The build function is executed in the source directory
+2 -6
View File
@@ -2,14 +2,10 @@
# BPM Expects the source code to be extracted into the automatically created 'source' directory which can be accessed using $BPM_SOURCE # BPM Expects the source code to be extracted into the automatically created 'source' directory which can be accessed using $BPM_SOURCE
# BPM Expects the output files to be present in the automatically created 'output' directory which can be accessed using $BPM_OUTPUT # BPM Expects the output files to be present in the automatically created 'output' directory which can be accessed using $BPM_OUTPUT
DOWNLOAD="https://wwww.my-url.com/file.tar.gz"
FILENAME="${DOWNLOAD##*/}"
# The prepare function is executed in the root of the temp directory # The prepare function is executed in the root of the temp directory
# This function is used for downloading files and putting them into the correct location # This function is used for putting downloaded files to the correct location or applying patches
prepare() { prepare() {
wget "$DOWNLOAD" echo "You may remove this function if you do not intend to use it"
tar -xvf "$FILENAME" --strip-components=1 -C "$BPM_SOURCE"
} }
# The build function is executed in the source directory # The build function is executed in the source directory
+6 -17
View File
@@ -2,40 +2,29 @@
# BPM Expects the source code to be extracted into the automatically created 'source' directory which can be accessed using $BPM_SOURCE # BPM Expects the source code to be extracted into the automatically created 'source' directory which can be accessed using $BPM_SOURCE
# BPM Expects the output files to be present in the automatically created 'output' directory which can be accessed using $BPM_OUTPUT # BPM Expects the output files to be present in the automatically created 'output' directory which can be accessed using $BPM_OUTPUT
DOWNLOAD="https://wwww.my-url.com/file.tar.gz"
FILENAME="${DOWNLOAD##*/}"
# The prepare function is executed in the root of the temp directory # The prepare function is executed in the root of the temp directory
# This function is used for downloading files and putting them into the correct location # This function is used for putting downloaded files to the correct location or applying patches
prepare() { prepare() {
wget "$DOWNLOAD" echo "You may remove this function if you do not intend to use it"
tar -xvf "$FILENAME" --strip-components=1 -C "$BPM_SOURCE"
} }
# The build function is executed in the source directory # The build function is executed in the source directory
# This function is used to compile the source code # This function is used to compile the source code
build() { build() {
mkdir build meson setup build --prefix=/usr
cd build meson compile -C build
meson setup --prefix=/usr ..
meson compile
} }
# The check function is executed in the source directory # The check function is executed in the source directory
# This function is used to run tests to verify the package has been compiled correctly # This function is used to run tests to verify the package has been compiled correctly
check() { check() {
cd build meson test -C build --print-errorlogs
meson test
} }
# The package function is executed in the source directory # The package function is executed in the source directory
# This function is used to move the compiled files into the output directory # This function is used to move the compiled files into the output directory
package() { package() {
cd build meson install -C build --destdir="$BPM_OUTPUT"
meson install --destdir="$BPM_OUTPUT"
# Install package license # Install package license
install -Dm644 "$BPM_SOURCE"/LICENSE "$BPM_OUTPUT"/usr/share/licenses/$NAME/LICENSE install -Dm644 "$BPM_SOURCE"/LICENSE "$BPM_OUTPUT"/usr/share/licenses/$NAME/LICENSE
+2 -6
View File
@@ -2,14 +2,10 @@
# BPM Expects the source code to be extracted into the automatically created 'source' directory which can be accessed using $BPM_SOURCE # BPM Expects the source code to be extracted into the automatically created 'source' directory which can be accessed using $BPM_SOURCE
# BPM Expects the output files to be present in the automatically created 'output' directory which can be accessed using $BPM_OUTPUT # BPM Expects the output files to be present in the automatically created 'output' directory which can be accessed using $BPM_OUTPUT
DOWNLOAD="https://wwww.my-url.com/file.tar.gz"
FILENAME="${DOWNLOAD##*/}"
# The prepare function is executed in the root of the temp directory # The prepare function is executed in the root of the temp directory
# This function is used for downloading files and putting them into the correct location # This function is used for putting downloaded files to the correct location or applying patches
prepare() { prepare() {
wget "$DOWNLOAD" echo "You may remove this function if you do not intend to use it"
tar -xvf "$FILENAME" --strip-components=1 -C "$BPM_SOURCE"
} }
# The build function is executed in the source directory # The build function is executed in the source directory
+2
View File
@@ -8,4 +8,6 @@ require (
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
require github.com/drone/envsubst v1.0.3 // indirect
replace bpm-utils-shared => ../bpm-utils-shared replace bpm-utils-shared => ../bpm-utils-shared
+4
View File
@@ -1,3 +1,7 @@
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/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+54 -18
View File
@@ -2,6 +2,7 @@ package main
import ( import (
bpmutilsshared "bpm-utils-shared" bpmutilsshared "bpm-utils-shared"
"bytes"
"fmt" "fmt"
"io" "io"
"log" "log"
@@ -22,12 +23,12 @@ var skipCheck = flag.BoolP("skip-checks", "s", false, "Skip 'check' function whi
var installDepends = flag.BoolP("depends", "d", false, "Install package dependencies for compilation") var installDepends = flag.BoolP("depends", "d", false, "Install package dependencies for compilation")
var installPackage = flag.BoolP("install", "i", false, "Install compiled BPM package after compilation finishes") var installPackage = flag.BoolP("install", "i", false, "Install compiled BPM package after compilation finishes")
var moveToBinaryDir = flag.BoolP("move", "m", false, "Move output packages to the current repository's binary directory") var moveToBinaryDir = flag.BoolP("move", "m", false, "Move output packages to the current repository's binary directory")
var updateChecksums = flag.BoolP("update-checksums", "u", false, "Update the checksums for all download entries")
var yesAll = flag.BoolP("yes", "y", false, "Accept all confirmation prompts") var yesAll = flag.BoolP("yes", "y", false, "Accept all confirmation prompts")
func main() { func main() {
// Setup flags and help // Setup flags and help
bpmutilsshared.SetupHelp("bpm-package <options>", "Generates source BPM package from current directory") setupFlagsAndHelp("bpm-package <options>", "Generates source BPM package from current directory")
bpmutilsshared.SetupFlags()
// Run checks // Run checks
runChecks() runChecks()
@@ -70,27 +71,52 @@ func createArchive() string {
// Check for package scripts and include them // Check for package scripts and include them
for _, script := range []string{"pre_install.sh", "post_install.sh", "pre_update.sh", "post_update.sh", "pre_remove.sh", "post_remove.sh"} { for _, script := range []string{"pre_install.sh", "post_install.sh", "pre_update.sh", "post_update.sh", "pre_remove.sh", "post_remove.sh"} {
if stat, err := os.Stat(script); err == nil && stat.Mode().IsRegular() { if stat, err := os.Stat(script); err == nil && stat.Mode().IsRegular() {
fmt.Printf("Package script '%s' found", script) fmt.Printf("Package script '%s' found\n", script)
filesToInclude = append(filesToInclude, script) filesToInclude = append(filesToInclude, script)
} }
} }
// Read pkg.info file into basic struct // Read pkg.info file
pkgInfo := struct { pkgInfo, err := bpmutilsshared.ReadPacakgeInfoFromFile("pkg.info")
Name string `yaml:"name"`
Version string `yaml:"version"`
Revision int `yaml:"revision"`
Arch string `yaml:"architecture"`
}{
Revision: 1,
}
data, err := os.ReadFile("pkg.info")
if err != nil { if err != nil {
log.Fatalf("Error: could not read pkg.info file") log.Fatalf("Error: could not read package info: %s", err)
} }
err = yaml.Unmarshal(data, &pkgInfo)
// Update checksums
if *updateChecksums {
for i, download := range pkgInfo.Downloads {
if download.Checksum == "skip" {
continue
}
download.Checksum, err = download.CalculateChecksum(pkgInfo)
if err != nil { if err != nil {
log.Fatalf("Error: could not unmarshal pkg.info file") log.Fatalf("Could not calculate checksum for download entry %d: %s", i+1, err)
}
pkgInfo.Downloads[i] = download
}
// Save yaml back to file
var data bytes.Buffer
encoder := yaml.NewEncoder(&data)
encoder.SetIndent(2)
encoder.Encode(pkgInfo)
if err != nil {
log.Fatalf("Could not marshal package info: %s", err)
}
// Stat pkg.info
stat, err := os.Stat("pkg.info")
if err != nil {
log.Fatalf("Could not stat pkg.info: %s", err)
}
// Write package info back to file
err = os.WriteFile("pkg.info", data.Bytes(), stat.Mode().Perm())
if err != nil {
log.Fatalf("Could not write package info to pkg.info: %s", err)
}
} }
// Remove old BPM archives in current directory // Remove old BPM archives in current directory
@@ -143,7 +169,7 @@ func compilePackage(archive string) {
if *yesAll { if *yesAll {
args = append(args, "-y") args = append(args, "-y")
} }
args = append(args, "--fd=3") args = append(args, "--output-fd=3")
args = append(args, archive) args = append(args, archive)
cmd := exec.Command("bpm", args...) cmd := exec.Command("bpm", args...)
cmd.Stdin = os.Stdin cmd.Stdin = os.Stdin
@@ -182,7 +208,7 @@ func compilePackage(archive string) {
outputPkgs := make(map[string]string) outputPkgs := make(map[string]string)
for _, line := range strings.Split(strings.TrimSpace(string(cmdOutput)), "\n") { for _, line := range strings.Split(strings.TrimSpace(string(cmdOutput)), "\n") {
// Read generated package info // Read generated package info
pkgInfo, err := bpmutilsshared.ReadPacakgeInfo(line) pkgInfo, err := bpmutilsshared.ReadPacakgeInfoFromTarball(line)
if repo := bpmutilsshared.GetRepository(); repo != "" && *moveToBinaryDir { if repo := bpmutilsshared.GetRepository(); repo != "" && *moveToBinaryDir {
// Remove old package from binary dir // Remove old package from binary dir
@@ -244,3 +270,13 @@ func compilePackage(archive string) {
} }
} }
} }
func setupFlagsAndHelp(usage, desc string) {
flag.Usage = func() {
fmt.Println("Usage: " + usage)
fmt.Println("Description: " + desc)
fmt.Println("Options:")
flag.PrintDefaults()
}
flag.Parse()
}
+2
View File
@@ -8,4 +8,6 @@ require (
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
require github.com/drone/envsubst v1.0.3 // indirect
replace bpm-utils-shared => ../bpm-utils-shared replace bpm-utils-shared => ../bpm-utils-shared
+4
View File
@@ -1,3 +1,7 @@
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/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+94 -27
View File
@@ -3,6 +3,7 @@ package main
import ( import (
bpmutilsshared "bpm-utils-shared" bpmutilsshared "bpm-utils-shared"
"bufio" "bufio"
"context"
"fmt" "fmt"
"io/fs" "io/fs"
"log" "log"
@@ -20,20 +21,23 @@ import (
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
var createRepo = flag.BoolP("create", "c", false, "Create a new BPM repository") var currentFlagSet *flag.FlagSet
var updateDatabases = flag.BoolP("update-databases", "u", false, "Update update source and binary databases in current repository")
var checkVersions = flag.BoolP("check-versions", "v", false, "Get the latest version of each package")
var listPackages = flag.BoolP("list", "l", false, "List packages")
var verbose = flag.Bool("verbose", false, "Show additional information about current operation")
var force = flag.BoolP("force", "f", false, "Force current operation to bypass certain conditions")
func main() { func main() {
if len(os.Args) < 2 {
log.Println("Error: no subcommand")
listSubcommands()
os.Exit(1)
}
subcommand := os.Args[1]
switch subcommand {
case "create-repo", "c":
// Setup flags and help // Setup flags and help
bpmutilsshared.SetupHelp("bpm-repo <options>", "Manage BPM repositories and databases") flagset := flag.NewFlagSet("create-repo", flag.ExitOnError)
bpmutilsshared.SetupFlags() setupFlagsAndHelp(flagset, fmt.Sprintf("bpm-repo %s <options>", subcommand), "Create a new BPM repository", os.Args[:1])
if *createRepo {
// Get current database // Get current database
repo := bpmutilsshared.GetRepository() repo := bpmutilsshared.GetRepository()
if repo != "" { if repo != "" {
@@ -53,7 +57,11 @@ func main() {
} }
createRepository(strings.TrimSpace(name), strings.TrimSpace(desc)) createRepository(strings.TrimSpace(name), strings.TrimSpace(desc))
} else if *updateDatabases { case "update-db", "u":
// Setup flags and help
flagset := flag.NewFlagSet("update-db", flag.ExitOnError)
setupFlagsAndHelp(flagset, fmt.Sprintf("bpm-repo %s <options>", subcommand), "Update update source and binary databases in current repository", os.Args[:1])
// Get current database // Get current database
repo := bpmutilsshared.GetRepository() repo := bpmutilsshared.GetRepository()
if repo == "" { if repo == "" {
@@ -61,15 +69,10 @@ func main() {
} }
bpmutilsshared.UpdateDatabases(repo) bpmutilsshared.UpdateDatabases(repo)
} else if *checkVersions { case "list", "l":
// Get current database flagset := flag.NewFlagSet("list", flag.ExitOnError)
repo := bpmutilsshared.GetRepository() setupFlagsAndHelp(flagset, fmt.Sprintf("bpm-repo %s <options>", subcommand), "List packages", os.Args[:1])
if repo == "" {
log.Fatal("Error: this command may only be run inside a BPM repository")
}
checkVersionsFunc(repo)
} else if *listPackages {
// Get current database // Get current database
repo := bpmutilsshared.GetRepository() repo := bpmutilsshared.GetRepository()
if repo == "" { if repo == "" {
@@ -77,8 +80,25 @@ func main() {
} }
listPackagesFunc(repo) listPackagesFunc(repo)
} else { case "check-versions", "v":
bpmutilsshared.ShowHelp() // Setup flags and help
flagset := flag.NewFlagSet("check-versions", flag.ExitOnError)
flagset.BoolP("verbose", "v", false, "Show additional information about the current operation")
flagset.BoolP("force", "f", false, "Force current operation to bypass certain conditions")
setupFlagsAndHelp(flagset, fmt.Sprintf("bpm-repo %s <options>", subcommand), "Manage BPM repositories and databases", os.Args[2:])
currentFlagSet = flagset
// Get current database
repo := bpmutilsshared.GetRepository()
if repo == "" {
log.Fatal("Error: this command may only be run inside a BPM repository")
}
checkVersionsFunc(repo)
default:
log.Println("Error: unknown subcommand")
listSubcommands()
os.Exit(1)
} }
} }
@@ -98,6 +118,10 @@ func createRepository(name, description string) {
} }
func checkVersionsFunc(repo string) { func checkVersionsFunc(repo string) {
// Get flags
verbose, _ := currentFlagSet.GetBool("verbose")
force, _ := currentFlagSet.GetBool("force")
// Read environment files // Read environment files
err := readEnvFile(repo) err := readEnvFile(repo)
if err != nil { if err != nil {
@@ -119,8 +143,8 @@ func checkVersionsFunc(repo string) {
} }
directories := make([]string, 0) directories := make([]string, 0)
if flag.NArg() > 0 { if currentFlagSet.NArg() > 0 {
for _, dir := range flag.Args() { for _, dir := range currentFlagSet.Args() {
if _, err := os.Stat(path.Join(repo, "source", dir, "pkg.info")); err != nil { if _, err := os.Stat(path.Join(repo, "source", dir, "pkg.info")); err != nil {
log.Fatalf("Error: could not find pkg.info file in directory (%s): %s", dir, err) log.Fatalf("Error: could not find pkg.info file in directory (%s): %s", dir, err)
} }
@@ -140,6 +164,7 @@ func checkVersionsFunc(repo string) {
} }
pkgsWithoutScript := make([]string, 0) pkgsWithoutScript := make([]string, 0)
pkgsIgnored := make([]string, 0)
pkgsWithError := make(map[string]error) pkgsWithError := make(map[string]error)
pkgsWithUpdates := make(map[string]struct { pkgsWithUpdates := make(map[string]struct {
OldVersion string OldVersion string
@@ -152,9 +177,13 @@ func checkVersionsFunc(repo string) {
log.Fatalf("Could not read package info: %s", err) log.Fatalf("Could not read package info: %s", err)
} }
if verbose {
fmt.Printf("Checking version for package (%s)...\n", pkgInfo.Name)
}
// Check cached latest version // Check cached latest version
latestVersion := "" latestVersion := ""
if cachedVersion, ok := cachedVersions[pkgInfo.Name]; ok && !*force && time.Since(time.UnixMilli(cachedVersion.Timestamp)).Milliseconds() < 604800000 { if cachedVersion, ok := cachedVersions[pkgInfo.Name]; ok && !force && time.Since(time.UnixMilli(cachedVersion.Timestamp)).Milliseconds() < 604800000 {
latestVersion = cachedVersion.LatestVersion latestVersion = cachedVersion.LatestVersion
} else { } else {
// Check whether check-version.sh script exists // Check whether check-version.sh script exists
@@ -164,7 +193,9 @@ func checkVersionsFunc(repo string) {
} }
// Execute check-version.sh script // Execute check-version.sh script
cmd := exec.Command("bash", "-e", path.Join(dir, "check-version.sh")) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "bash", "-e", path.Join(dir, "check-version.sh"))
cmd.Environ() cmd.Environ()
output, err := cmd.Output() output, err := cmd.Output()
if err != nil { if err != nil {
@@ -173,6 +204,16 @@ func checkVersionsFunc(repo string) {
} }
latestVersion = strings.TrimSpace(string(output)) latestVersion = strings.TrimSpace(string(output))
// Check if package should be ignored
if latestVersion == "ignore" {
pkgsIgnored = append(pkgsIgnored, pkgInfo.Name)
// Remove cached status
delete(cachedVersions, pkgInfo.Name)
continue
}
// Ensure latest version is valid // Ensure latest version is valid
if latestVersion == "" || latestVersion == "null" { if latestVersion == "" || latestVersion == "null" {
pkgsWithError[pkgInfo.Name] = fmt.Errorf("invalid version number \"%s\"", latestVersion) pkgsWithError[pkgInfo.Name] = fmt.Errorf("invalid version number \"%s\"", latestVersion)
@@ -217,11 +258,17 @@ func checkVersionsFunc(repo string) {
fmt.Printf("Update available for package (%s): %s -> %s\n", pkg, pkgsWithUpdates[pkg].OldVersion, pkgsWithUpdates[pkg].NewVersion) fmt.Printf("Update available for package (%s): %s -> %s\n", pkg, pkgsWithUpdates[pkg].OldVersion, pkgsWithUpdates[pkg].NewVersion)
} }
if *verbose { if verbose {
// Print ignored packages
for _, pkg := range pkgsIgnored {
log.Printf("Warning: package (%s) was ignored\n", pkg)
}
// Print packages without check-version.sh script // Print packages without check-version.sh script
for _, pkg := range pkgsWithoutScript { for _, pkg := range pkgsWithoutScript {
log.Printf("Warning: package (%s) has no check-version.sh script\n", pkg) log.Printf("Warning: package (%s) has no check-version.sh script\n", pkg)
} }
}
// Print errors // Print errors
keys = slices.Collect(maps.Keys(pkgsWithError)) keys = slices.Collect(maps.Keys(pkgsWithError))
@@ -229,13 +276,13 @@ func checkVersionsFunc(repo string) {
for _, pkg := range keys { for _, pkg := range keys {
log.Printf("Error: check-version.sh script for package (%s) failed: %s", pkg, pkgsWithError[pkg]) log.Printf("Error: check-version.sh script for package (%s) failed: %s", pkg, pkgsWithError[pkg])
} }
}
// Print summary // Print summary
fmt.Println("----- Summary -----") fmt.Println("----- Summary -----")
fmt.Println("Available updates:", len(pkgsWithUpdates)) fmt.Println("Available updates:", len(pkgsWithUpdates))
fmt.Println("Up to date:", pkgsUpToDate) fmt.Println("Up to date:", pkgsUpToDate)
fmt.Println("Missing script:", len(pkgsWithoutScript)) fmt.Println("Missing script:", len(pkgsWithoutScript))
fmt.Println("Ignored: ", len(pkgsIgnored))
fmt.Println("Errors:", len(pkgsWithError)) fmt.Println("Errors:", len(pkgsWithError))
} }
@@ -306,3 +353,23 @@ func readEnvFile(repo string) error {
return nil return nil
} }
func listSubcommands() {
fmt.Println("Usage: bpm-repo <subcommand> <options>")
fmt.Println("Description: Manage BPM repositories and databases")
fmt.Println("Subcommands:")
fmt.Println(" c, create-repo Create a new BPM repository")
fmt.Println(" u, update-db Update update source and binary databases in current repositor")
fmt.Println(" v, check-versions Manage BPM repositories and databases")
fmt.Println(" l, list List packages")
}
func setupFlagsAndHelp(flagset *flag.FlagSet, usage, desc string, args []string) {
flagset.Usage = func() {
fmt.Println("Usage: " + usage)
fmt.Println("Description: " + desc)
fmt.Println("Options:")
flagset.PrintDefaults()
}
flagset.Parse(args)
}
+2 -1
View File
@@ -5,8 +5,9 @@ go 1.23
require ( require (
bpm-utils-shared v1.0.0 bpm-utils-shared v1.0.0
github.com/spf13/pflag v1.0.10 github.com/spf13/pflag v1.0.10
gopkg.in/yaml.v3 v3.0.1
) )
require gopkg.in/yaml.v3 v3.0.1 // indirect require github.com/drone/envsubst v1.0.3 // indirect
replace bpm-utils-shared => ../bpm-utils-shared replace bpm-utils-shared => ../bpm-utils-shared
+4
View File
@@ -1,3 +1,7 @@
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/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+39 -17
View File
@@ -3,6 +3,7 @@ package main
import ( import (
bpmutilsshared "bpm-utils-shared" bpmutilsshared "bpm-utils-shared"
"bufio" "bufio"
"bytes"
"fmt" "fmt"
"io" "io"
"log" "log"
@@ -13,6 +14,7 @@ import (
"strings" "strings"
flag "github.com/spf13/pflag" flag "github.com/spf13/pflag"
yaml "gopkg.in/yaml.v3"
) )
var directory = flag.StringP("directory", "D", "", "Path to package directory") var directory = flag.StringP("directory", "D", "", "Path to package directory")
@@ -26,13 +28,12 @@ var git = flag.BoolP("git", "g", true, "Create git repository")
func main() { func main() {
// Setup flags and help // Setup flags and help
bpmutilsshared.SetupHelp("bpm-setup <options>", "Sets up files and directories for BPM source package creation") setupFlagsAndHelp("bpm-setup <options>", "Sets up files and directories for BPM source package creation")
bpmutilsshared.SetupFlags()
// Show command help if no directory name is given // Show command help if no directory name is given
if *directory == "" { if *directory == "" {
log.Println("Directory flag is required") log.Println("Error: directory flag is required")
bpmutilsshared.ShowHelp() flag.Usage()
os.Exit(1) os.Exit(1)
} }
@@ -117,21 +118,32 @@ func createDirectory() {
log.Fatalf("Error: could not create directory: %s", err) log.Fatalf("Error: could not create directory: %s", err)
} }
// Create pkg.info contents string // Create package info struct
pkgInfo := "name: " + *name + "\n" pkgInfo := bpmutilsshared.PackageInfo{
pkgInfo += "description: " + *description + "\n" Name: *name,
pkgInfo += "version: " + *version + "\n" Description: *description,
if url != nil && *url != "" { Version: *version,
pkgInfo += "url: " + *url + "\n" Url: *url,
License: *license,
Arch: "any",
Type: "source",
Downloads: []bpmutilsshared.PackageDownload{
{
Url: "https://wwww.my-url.com/file.tar.gz",
ExtractTo: "${BPM_SOURCE}",
ExtractStripComponents: 1,
Checksum: "replaceme",
},
},
} }
if license != nil && *license != "" {
pkgInfo += "license: " + *license + "\n"
}
pkgInfo += "architecture: any\n"
pkgInfo += "type: source\n"
// Write string to file var buffer bytes.Buffer
err = os.WriteFile(path.Join(*directory, "pkg.info"), []byte(pkgInfo), 0644) encoder := yaml.NewEncoder(&buffer)
encoder.SetIndent(2)
encoder.Encode(&pkgInfo)
// Write package info to file
err = os.WriteFile(path.Join(*directory, "pkg.info"), buffer.Bytes(), 0644)
if err != nil { if err != nil {
log.Fatalf("Could not write to pkg.info: %s", err) log.Fatalf("Could not write to pkg.info: %s", err)
} }
@@ -182,3 +194,13 @@ func createDirectory() {
} }
} }
} }
func setupFlagsAndHelp(usage, desc string) {
flag.Usage = func() {
fmt.Println("Usage: " + usage)
fmt.Println("Description: " + desc)
fmt.Println("Options:")
flag.PrintDefaults()
}
flag.Parse()
}
+1 -1
View File
@@ -78,7 +78,7 @@ func GenerateDatabase(path string) error {
} }
// Get package info // Get package info
entry.PackageInfo, err = ReadPacakgeInfo(packagePath) entry.PackageInfo, err = ReadPacakgeInfoFromTarball(packagePath)
if err != nil { if err != nil {
return err return err
} }
+3 -4
View File
@@ -2,7 +2,6 @@ module bpm-utils-shared
go 1.23 go 1.23
require ( require gopkg.in/yaml.v3 v3.0.1
github.com/spf13/pflag v1.0.10
gopkg.in/yaml.v3 v3.0.1 require github.com/drone/envsubst v1.0.3
)
+4 -2
View File
@@ -1,5 +1,7 @@
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 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=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 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/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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
-22
View File
@@ -1,16 +1,11 @@
package bpm_utils_shared package bpm_utils_shared
import ( import (
"fmt"
"os" "os"
flag "github.com/spf13/pflag"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
var usageMsg string
var description string
type BPMUtilsConfig struct { type BPMUtilsConfig struct {
PrivilegeEscalatorCmd string `yaml:"privilege_escalator_cmd"` PrivilegeEscalatorCmd string `yaml:"privilege_escalator_cmd"`
} }
@@ -29,20 +24,3 @@ func ReadBPMUtilsConfig() (*BPMUtilsConfig, error) {
return config, nil return config, nil
} }
func SetupFlags() {
flag.Usage = ShowHelp
flag.Parse()
}
func SetupHelp(usage, desc string) {
usageMsg = usage
description = desc
}
func ShowHelp() {
fmt.Println("Usage: " + usageMsg)
fmt.Println("Description: " + description)
fmt.Println("Options:")
flag.PrintDefaults()
}
+110 -38
View File
@@ -1,48 +1,56 @@
package bpm_utils_shared package bpm_utils_shared
import ( import (
"fmt"
"os" "os"
"os/exec" "os/exec"
"strings"
"github.com/drone/envsubst"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
type PackageInfo struct { type PackageInfo struct {
Name string `yaml:"name,omitempty"` Name string `yaml:"name"`
Description string `yaml:"description,omitempty"` Description string `yaml:"description,omitempty"`
Version string `yaml:"version,omitempty"` Version string `yaml:"version,omitempty"`
Revision int `yaml:"revision,omitempty"` Revision int `yaml:"revision,omitempty"`
Url string `yaml:"url,omitempty"` Url string `yaml:"url,omitempty"`
License string `yaml:"license,omitempty"` License string `yaml:"license,omitempty"`
Arch string `yaml:"architecture,omitempty"` Arch string `yaml:"architecture,omitempty"`
OutputArch string `yaml:"output_architecture,omitempty"`
Type string `yaml:"type,omitempty"` Type string `yaml:"type,omitempty"`
Keep []string `yaml:"keep,omitempty"` Keep []string `yaml:"keep,omitempty"`
Depends []string `yaml:"depends,omitempty"` Depends []string `yaml:"depends,omitempty"`
MakeDepends []string `yaml:"make_depends,omitempty"`
OptionalDepends []string `yaml:"optional_depends,omitempty"` OptionalDepends []string `yaml:"optional_depends,omitempty"`
MakeDepends []string `yaml:"make_depends,omitempty"`
Conflicts []string `yaml:"conflicts,omitempty"` Conflicts []string `yaml:"conflicts,omitempty"`
Replaces []string `yaml:"replaces,omitempty"` Replaces []string `yaml:"replaces,omitempty"`
Provides []string `yaml:"provides,omitempty"` Provides []string `yaml:"provides,omitempty"`
Downloads []PackageDownload `yaml:"downloads,omitempty"`
SplitPackages []*PackageInfo `yaml:"split_packages,omitempty"` SplitPackages []*PackageInfo `yaml:"split_packages,omitempty"`
} }
func ReadPacakgeInfo(path string) (*PackageInfo, error) { type PackageDownload struct {
// Extract package info using tar Url string `yaml:"url"`
cmd := exec.Command("tar", "-x", "-f", path, "pkg.info", "-O") Type string `yaml:"type,omitempty"`
output, err := cmd.Output() Filepath string `yaml:"filepath,omitempty,omitempty"`
if err != nil {
return nil, err // Archive options
NoExtract bool `yaml:"no_extract,omitempty"`
ExtractTo string `yaml:"extract_to,omitempty"`
ExtractStripComponents int `yaml:"extract_strip_components,omitempty"`
// Git options
CloneTo string `yaml:"clone_to,omitempty"`
GitBranch string `yaml:"git_branch,omitempty"`
Checksum string `yaml:"checksum,omitempty"`
} }
func ReadPackageInfo(data []byte) (*PackageInfo, error) {
pkgInfo := &PackageInfo{ pkgInfo := &PackageInfo{
Name: "",
Description: "",
Version: "",
Revision: 1, Revision: 1,
Url: "",
License: "",
Arch: "",
Type: "",
Keep: make([]string, 0), Keep: make([]string, 0),
Depends: make([]string, 0), Depends: make([]string, 0),
MakeDepends: make([]string, 0), MakeDepends: make([]string, 0),
@@ -50,11 +58,28 @@ func ReadPacakgeInfo(path string) (*PackageInfo, error) {
Conflicts: make([]string, 0), Conflicts: make([]string, 0),
Replaces: make([]string, 0), Replaces: make([]string, 0),
Provides: make([]string, 0), Provides: make([]string, 0),
Downloads: make([]PackageDownload, 0),
SplitPackages: make([]*PackageInfo, 0), SplitPackages: make([]*PackageInfo, 0),
} }
// Unmarshal yaml // Unmarshal yaml
err = yaml.Unmarshal(output, pkgInfo) err := yaml.Unmarshal(data, pkgInfo)
if err != nil {
return nil, err
}
return pkgInfo, nil
}
func ReadPacakgeInfoFromTarball(path string) (*PackageInfo, error) {
// Extract package info using tar
cmd := exec.Command("tar", "-x", "-f", path, "pkg.info", "-O")
output, err := cmd.Output()
if err != nil {
return nil, err
}
pkgInfo, err := ReadPackageInfo(output)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -63,36 +88,83 @@ func ReadPacakgeInfo(path string) (*PackageInfo, error) {
} }
func ReadPacakgeInfoFromFile(path string) (*PackageInfo, error) { func ReadPacakgeInfoFromFile(path string) (*PackageInfo, error) {
// Extract package info using tar // Read data from file
output, err := os.ReadFile(path) output, err := os.ReadFile(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
pkgInfo := &PackageInfo{ pkgInfo, err := ReadPackageInfo(output)
Name: "",
Description: "",
Version: "",
Revision: 1,
Url: "",
License: "",
Arch: "",
Type: "",
Keep: make([]string, 0),
Depends: make([]string, 0),
MakeDepends: make([]string, 0),
OptionalDepends: make([]string, 0),
Conflicts: make([]string, 0),
Replaces: make([]string, 0),
Provides: make([]string, 0),
SplitPackages: make([]*PackageInfo, 0),
}
// Unmarshal yaml
err = yaml.Unmarshal(output, pkgInfo)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return pkgInfo, nil return pkgInfo, nil
} }
func (pkgDownload *PackageDownload) CalculateChecksum(pkgInfo *PackageInfo) (string, error) {
switch pkgDownload.Type {
case "", "file":
fmt.Println("Downloading and calculating checksum for file...")
// Replace variables in download url
downloadUrl := pkgDownload.Url
downloadUrl, err := envsubst.Eval(downloadUrl, 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("sh", "-c", fmt.Sprintf("curl -s -L %s | sha256sum | awk '{print $1}'", downloadUrl))
cmd.Stderr = os.Stderr
checksum, err := cmd.Output()
if err != nil {
return "", err
}
return strings.TrimSpace(string(checksum)), err
case "git":
fmt.Println("Calculating checksum for git branch...")
// Replace variables in git branch
gitBranch := pkgDownload.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
}
if pkgDownload.GitBranch == "" {
return "", fmt.Errorf("'git_branch' field cannot be empty")
}
cmd := exec.Command("sh", "-c", fmt.Sprintf("git ls-remote -bt %s | grep -E 'refs/.*/%s(\\^\\{\\})?$' | tail -n1 | awk '{print $1}'", pkgDownload.Url, gitBranch))
cmd.Stderr = os.Stderr
checksum, err := cmd.Output()
if err != nil {
return "", err
}
return strings.TrimSpace(string(checksum)), err
default:
return "", fmt.Errorf("unknown download type (%s)", pkgDownload.Type)
}
}