55 Commits
Author SHA1 Message Date
EnumDev 66e1aacc5f other: add package flag support 2026-06-27 17:23:17 +03:00
EnumDev 8034c7d7ea bpm-package: add '--force' flag 2026-06-23 08:23:57 +03:00
EnumDev d781c30146 bpm-package: improve old package removal 2026-06-21 19:38:39 +03:00
EnumDev 8eb771111c bpm-package: add '--yes' option to gpg commands 2026-06-21 18:15:01 +03:00
EnumDev 3f84dd4bc3 Update to new package format 2026-06-10 11:41:08 +03:00
EnumDev e1daa498e8 bpm-repo: inform user about possible package downgrades 2026-05-16 12:20:17 +03:00
EnumDev 68ffb7972f bpm-repo: small changes to be in line with bpm 2026-05-14 17:13:32 +03:00
EnumDev 9114bb4148 bpm-repo: recognize split packages 2026-05-14 17:13:12 +03:00
EnumDev 5dbdfdd4c1 Move package recipes to separate directory 2026-04-25 10:46:10 +03:00
EnumDev eb13b2653d other: update default gitignore 2026-02-25 09:02:34 +02:00
EnumDev 581c864f1d bpm-repo: sign all packages when compiling 2026-02-24 18:52:45 +02:00
EnumDev 7e7b8487ea bpm-package: add package signing 2026-02-24 18:52:08 +02:00
EnumDev a7f751c094 Fix on_hold value being removed when checking versions 2026-02-20 12:51:14 +02:00
EnumDev 54e192f220 bpm-repo: add 'hold' subcommand 2026-02-15 15:33:48 +02:00
EnumDev c508ce6a01 bpm-repo: remove testing println and generate source package in correct directory 2026-02-15 15:06:08 +02:00
EnumDev 2b81dad6cf other: recognize 'check_depends' field 2026-01-28 18:51:03 +02:00
EnumDev 82777ebac1 bpm-package: add config option to add default maintainer on pkg.info update 2026-01-25 17:47:07 +02:00
EnumDev b20766bddb bpm-package: rename 'update-checksums' flag to 'update-info' 2026-01-25 17:31:25 +02:00
EnumDev 0ff9eb9cf2 other: add 'default_maintainer' config option 2026-01-25 17:28:41 +02:00
EnumDev 4371b7f0e3 Recognize 'maintainers' field in pkg.info 2026-01-25 17:04:42 +02:00
EnumDev 65849fdf86 bpm-repo: add 'compile-all' subcommand 2026-01-25 17:02:39 +02:00
EnumDev a64ad39ca7 bpm-repo: generate source package after applying new version 2026-01-12 08:52:17 +02:00
EnumDev bf9874af6b bpm-repo: add 'apply' flag to 'check-versions' subcommand 2026-01-11 11:50:03 +02:00
EnumDev 07a9e8c9d2 Add 'jobs' flag 2026-01-11 10:12:06 +02:00
EnumDev 598e6710f4 bpm-setup: set revision number on package creation 2026-01-04 17:19:49 +02:00
EnumDev 902fcbf296 bpm-repo: create 'source' directory upon repository creation 2026-01-04 15:27:41 +02:00
EnumDev 03bf97b8d8 Add 'RuntimeDepends' field to PackageInfo struct 2026-01-04 14:34:36 +02:00
EnumDev 5240ae4a8c Add rust template 2025-10-28 16:09:14 +02:00
EnumDev 9d03b5b423 Add 'verbose' flag to bpm-package 2025-10-27 18:18:40 +02:00
EnumDev f17bc777d9 Add 'options' field to package info 2025-10-27 18:15:10 +02:00
EnumDev 154f546b7f Fix installed size being wrong 2025-10-25 13:03:36 +03:00
EnumDev c4a0e9f860 Add 'keep' flag 2025-10-17 21:11:16 +03:00
EnumDev b9d7dfd9d0 Update databases only once after compiling split packages 2025-10-17 15:02:30 +03:00
EnumDev 4217626dde Update passed flag to bpm 2025-10-05 20:00:05 +03:00
EnumDevandGitHub d7da13034c Merge pull request #1 from EnumeratedDev/go_rewrite
Rewrite bpm-utils in go
2025-10-05 09:37:55 +00:00
EnumDev cdb044d751 Update pkg.info format 2025-10-05 12:14:54 +03:00
EnumDev 60b0f4489e Improve git branch checksum fetching 2025-10-05 11:00:15 +03:00
EnumDev 65c32d628f Update repository version checking messages 2025-10-03 14:18:53 +03:00
EnumDev 4ff4a74fa3 Improve package info parsing 2025-09-29 10:35:54 +03:00
EnumDev f6ef60aa40 Add cargo template 2025-09-28 18:24:35 +03:00
EnumDev 9625b5d7af Use drone/envsubst module for bash variable expansion 2025-09-28 12:39:30 +03:00
EnumDev d881ac31c3 Update README.md 2025-09-27 21:32:07 +03:00
EnumDev 96a39a6b40 Update default pkg.info content 2025-09-27 21:19:13 +03:00
EnumDev 7a63ad6388 Update templates 2025-09-27 21:18:39 +03:00
EnumDev 10f918034a Add 'update-checksums' flag 2025-09-27 13:39:54 +03:00
EnumDev 59c72d1a40 Add missing newline character 2025-09-21 11:04:20 +03:00
EnumDev 61c01ae88f Add 'print-errorlogs' flag to meson template 2025-09-10 18:52:10 +03:00
EnumDev 0fdef74c73 Update cmake and meson templates 2025-09-10 18:16:25 +03:00
EnumDev d5abad3371 Add 'ignored' status for package version checking 2025-09-09 19:05:57 +03:00
EnumDev 334eb15c94 Improve flag system 2025-09-09 18:59:28 +03:00
EnumDev 7aff954df4 Fix moved BPM archive being removed by bpm-package 2025-09-08 22:12:44 +03:00
EnumDev 2197f78bc0 Add 'check-versions' flag to bpm-repo 2025-09-08 10:16:23 +03:00
EnumDev 1b6585424f Remove old BPM archives when creating new one 2025-09-05 20:56:57 +03:00
EnumDev c9627e1068 Add gitignore template 2025-09-05 20:46:12 +03:00
EnumDev aae5299b8a Add 'list packages' flag to bpm-repo 2025-09-05 19:42:33 +03:00
24 changed files with 1476 additions and 247 deletions
+22 -10
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 info.yml 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,17 +42,29 @@ 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 recipe.sh file during compilation
5) You now need to edit your source.sh file which contains the compilation instructions for your package, the default source template comments should explain the basic process of compiling your program and how to edit it 5) You now need to edit your recipe.sh file which contains the compilation instructions for your package, the default source template comments should explain the basic process of compiling your program and how to edit it
6) When you are done editing your source.sh script run the following command to create a BPM source package archive. You may run the `bpm-package` command with no arguments to get an explanation of what each flag does 6) When you are done editing your recipe.sh script run the following command to create a BPM source package archive. You may run the `bpm-package` command with no arguments to get an explanation of what each flag does
``` ```
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
+3
View File
@@ -0,0 +1,3 @@
# Ignore BPM archives and signatures
*.bpm
*.bpm.sig
+32
View File
@@ -0,0 +1,32 @@
# This is the recipe.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
}
+7 -18
View File
@@ -1,41 +1,30 @@
# This is the source.sh script. It is executed by BPM in a temporary directory when compiling a source package # This is the recipe.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 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
+3 -7
View File
@@ -1,15 +1,11 @@
# This is the source.sh script. It is executed by BPM in a temporary directory when compiling a source package # This is the recipe.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 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
+3 -7
View File
@@ -1,15 +1,11 @@
# This is the source.sh script. It is executed by BPM in a temporary directory when compiling a source package # This is the recipe.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 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
+7 -18
View File
@@ -1,41 +1,30 @@
# This is the source.sh script. It is executed by BPM in a temporary directory when compiling a source package # This is the recipe.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 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
+3 -7
View File
@@ -1,15 +1,11 @@
# This is the source.sh script. It is executed by BPM in a temporary directory when compiling a source package # This is the recipe.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 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
+31
View File
@@ -0,0 +1,31 @@
# This is the recipe.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"
cargo fetch --locked --target "$(rustc --print host-tuple)"
}
# The build function is executed in the source directory
# This function is used to compile the source code
build() {
cargo build --release --locked --frozen
}
# 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 --release --frozen
}
# The package function is executed in the source directory
# This function is used to move the compiled files into the output directory
package() {
# Cargo built packages are not usually packaged manually and not with an 'install' subcommand
# Install package license
install -Dm644 "$BPM_SOURCE"/LICENSE "$BPM_OUTPUT"/usr/share/licenses/$NAME/LICENSE
}
+5
View File
@@ -8,4 +8,9 @@ require (
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
require (
github.com/drone/envsubst v1.0.3 // indirect
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f // indirect
)
replace bpm-utils-shared => ../bpm-utils-shared replace bpm-utils-shared => ../bpm-utils-shared
+6
View File
@@ -1,3 +1,9 @@
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/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=
+244 -52
View File
@@ -2,8 +2,10 @@ package main
import ( import (
bpmutilsshared "bpm-utils-shared" bpmutilsshared "bpm-utils-shared"
"bytes"
"fmt" "fmt"
"io" "io"
"io/fs"
"log" "log"
"maps" "maps"
"os" "os"
@@ -11,6 +13,7 @@ import (
"path" "path"
"path/filepath" "path/filepath"
"slices" "slices"
"strconv"
"strings" "strings"
flag "github.com/spf13/pflag" flag "github.com/spf13/pflag"
@@ -18,16 +21,20 @@ import (
) )
var compile = flag.BoolP("compile", "c", false, "Compile BPM source package") var compile = flag.BoolP("compile", "c", false, "Compile BPM source package")
var skipCheck = flag.BoolP("skip-checks", "s", false, "Skip 'check' function while compiling") var verbose = flag.BoolP("verbose", "v", false, "Show additional information about the compilation process")
var skipChecks = flag.Bool("skip-checks", false, "Skip 'check' function while compiling")
var keepCompilationFiles = flag.BoolP("keep", "k", false, "Keep compilation files after successful package compilation")
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 force = flag.BoolP("force", "f", false, "Bypass warnings during package 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 compilationJobs = flag.IntP("jobs", "j", 0, "Set the amount of concurrent processes to use for source package compilation")
var updateInfo = flag.BoolP("update-info", "u", false, "Update the info.yml file")
var signPackage = flag.BoolP("sign", "s", false, "Sign package using GPG")
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()
@@ -38,25 +45,35 @@ func main() {
if *compile { if *compile {
compilePackage(outputFile) compilePackage(outputFile)
} }
if repo := bpmutilsshared.GetRepository(); repo != "" {
bpmutilsshared.UpdateDatabases(repo)
}
} }
func runChecks() { func runChecks() {
// Check if pkg.info file exists // Check if info.yml file exists
if stat, err := os.Stat("pkg.info"); err != nil || !stat.Mode().IsRegular() { if stat, err := os.Stat("info.yml"); err != nil || !stat.Mode().IsRegular() {
log.Fatalf("Error: pkg.info does not exist or is not a regular file") log.Fatalf("Error: info.yml does not exist or is not a regular file")
} }
// Check if source.sh file exists // Check if recipe.sh file exists
if stat, err := os.Stat("source.sh"); err != nil || !stat.Mode().IsRegular() { if stat, err := os.Stat("recipe.sh"); err != nil || !stat.Mode().IsRegular() {
log.Fatalf("Error: pkg.info does not exist or is not a regular file") log.Fatalf("Error: info.yml does not exist or is not a regular file")
} }
} }
func createArchive() string { func createArchive() string {
// Read BPM utils config
config, err := bpmutilsshared.ReadBPMUtilsConfig()
if err != nil {
log.Fatalf("Error: failed to read config: %s", err)
}
filesToInclude := make([]string, 0) filesToInclude := make([]string, 0)
// Include base files // Include base files
filesToInclude = append(filesToInclude, "pkg.info", "source.sh") filesToInclude = append(filesToInclude, "info.yml", "recipe.sh")
// Check if non-empty source-files directory exists and include it // Check if non-empty source-files directory exists and include it
if stat, err := os.Stat("source-files"); err == nil && stat.IsDir() { if stat, err := os.Stat("source-files"); err == nil && stat.IsDir() {
@@ -70,37 +87,89 @@ 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 info.yml file
pkgInfo := struct { pkgInfo, err := bpmutilsshared.ReadPacakgeInfoFromFile("info.yml")
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 info.yml file
if *updateInfo {
// Update download checksums
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
}
// Add default maintainer
if config.AddDefaultMaintainer {
if !slices.Contains(pkgInfo.Maintainers, config.DefaultMaintainer) {
pkgInfo.Maintainers = append(pkgInfo.Maintainers, config.DefaultMaintainer)
}
}
// 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 info.yml
stat, err := os.Stat("info.yml")
if err != nil {
log.Fatalf("Could not stat info.yml: %s", err)
}
// Write package info back to file
err = os.WriteFile("info.yml", data.Bytes(), stat.Mode().Perm())
if err != nil {
log.Fatalf("Could not write package info to info.yml: %s", err)
}
}
// Remove old package from source dir
if repo := bpmutilsshared.GetRepository(); repo != "" {
removedFiles, err := RemoveSourcePackage(repo, pkgInfo.Name)
for _, file := range removedFiles {
fmt.Println("Removed " + file)
}
if err != nil {
log.Printf("Warning: could not remove old source packages: %s", err)
}
} }
// Create filename // Create filename
filename := fmt.Sprintf("%s-%s-%d-%s-src.bpm", pkgInfo.Name, pkgInfo.Version, pkgInfo.Revision, pkgInfo.Arch) filename := fmt.Sprintf("%s-%s-%d-%s-src.bpm", pkgInfo.Name, pkgInfo.Version, pkgInfo.Revision, pkgInfo.Arch)
if repo := bpmutilsshared.GetRepository(); repo != "" {
// Create source directory
os.MkdirAll(path.Join(repo, "source", pkgInfo.Arch), 0755)
filename = path.Join(repo, "source", pkgInfo.Arch, filename)
}
// Create archive using tar // Create archive using tar
args := make([]string, 0) cmd := exec.Command("tar", "cf", filename,
args = append(args, "-c", "--owner=0", "--group=0", "--no-same-owner", "-f", filename) "--sort=name",
args = append(args, filesToInclude...) "--pax-option=exthdr.name=%d/PaxHeaders/%f,delete=atime,delete=ctime",
cmd := exec.Command("tar", args...) "--mtime=UTC 1970-01-01",
"--owner=0", "--group=0", "--numeric-owner")
cmd.Args = append(cmd.Args, filesToInclude...)
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
err = cmd.Run() err = cmd.Run()
@@ -108,6 +177,19 @@ func createArchive() string {
log.Fatalf("Error: failed to create BPM source archive: %s", err) log.Fatalf("Error: failed to create BPM source archive: %s", err)
} }
// Sign package
if *signPackage {
cmd := exec.Command("gpg", "--yes", "--detach-sign", filename)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
err := cmd.Run()
if err != nil {
log.Fatalf("Error: could not sign package: %s", err)
}
}
// Get absolute path to filename // Get absolute path to filename
absFilepath, err := filepath.Abs(filename) absFilepath, err := filepath.Abs(filename)
if err != nil { if err != nil {
@@ -122,16 +204,19 @@ func compilePackage(archive string) {
// Setup compile command // Setup compile command
args := make([]string, 0) args := make([]string, 0)
args = append(args, "compile") args = append(args, "compile")
if *skipCheck { addArg := func(condition bool, arg string) {
args = append(args, "-s") if condition {
args = append(args, arg)
} }
if *installDepends {
args = append(args, "-d")
} }
if *yesAll { addArg(*verbose, "-v")
args = append(args, "-y") addArg(*skipChecks, "--skip-checks")
} addArg(*keepCompilationFiles, "-k")
args = append(args, "--fd=3") addArg(*installDepends, "-d")
addArg(*compilationJobs > 0, "-j"+strconv.Itoa(*compilationJobs))
addArg(*yesAll, "-y")
addArg(*force, "-f")
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
@@ -170,34 +255,49 @@ 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 {
// Move package to binary dir
if err != nil { if err != nil {
log.Fatalf("Error: could not read package info: %s", err) log.Fatalf("Error: could not read package info: %s", err)
} }
if repo := bpmutilsshared.GetRepository(); repo != "" {
// Remove old package from binary dir
removedFiles, err := RemoveBinaryPackage(repo, pkgInfo.Name)
for _, file := range removedFiles {
fmt.Println("Removed " + file)
}
if err != nil {
log.Printf("Warning: could not remove old binary packages: %s", err)
}
// Move package to binary dir
newPath := path.Join(repo, "binary", pkgInfo.Arch, path.Base(line)) newPath := path.Join(repo, "binary", pkgInfo.Arch, path.Base(line))
os.MkdirAll(path.Dir(newPath), 0755) os.MkdirAll(path.Dir(newPath), 0755)
os.Rename(line, newPath) os.Rename(line, newPath)
outputPkgs[pkgInfo.Name] = newPath outputPkgs[pkgInfo.Name] = newPath
// Remove old package from binary dir
if database, err := bpmutilsshared.ReadDatabase(path.Join(repo, "binary/database.bpmdb")); err == nil {
if entry, ok := database.Entries[pkgInfo.Name]; ok {
pkgFilepath := path.Join(repo, "binary", entry.Filepath)
err := os.Remove(pkgFilepath)
if err != nil {
log.Printf("Warning: could not remove old binary package (%s): %s", pkgFilepath, err)
}
}
bpmutilsshared.UpdateDatabases(repo)
}
} else { } else {
outputPkgs[pkgInfo.Name] = line outputPkgs[pkgInfo.Name] = line
} }
} }
// Sign package
if *signPackage {
for k, v := range outputPkgs {
cmd := exec.Command("gpg", "--yes", "--detach-sign", v)
cmd.Dir = path.Dir(v)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
err := cmd.Run()
if err != nil {
log.Fatalf("Error: could not sign package (%s) at: %s", k, v)
}
}
}
// Print out generated packages // Print out generated packages
for k, v := range outputPkgs { for k, v := range outputPkgs {
fmt.Printf("Package (%s) was successfully compiled! Binary package generated at: %s\n", k, v) fmt.Printf("Package (%s) was successfully compiled! Binary package generated at: %s\n", k, v)
@@ -231,3 +331,95 @@ 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()
}
func RemoveSourcePackage(repo, pkgName string) (removedFiles []string, err error) {
if _, err := os.Stat(path.Join(repo, "source")); err != nil {
return removedFiles, nil
}
err = filepath.Walk(path.Join(repo, "source"), func(packagePath string, info fs.FileInfo, err error) error {
if !strings.HasSuffix(packagePath, ".bpm") {
return nil
}
// Get package info
pkgInfo, err := bpmutilsshared.ReadPacakgeInfoFromTarball(packagePath)
if err != nil {
return err
}
if pkgInfo.Name != pkgName {
return nil
}
// Remove package and its signature
err = os.Remove(packagePath)
if err != nil {
return err
}
if relPath, err := filepath.Rel(repo, packagePath); err != nil {
removedFiles = append(removedFiles, packagePath)
} else {
removedFiles = append(removedFiles, relPath)
}
err = os.Remove(packagePath + ".sig")
if err != nil && !os.IsNotExist(err) {
return err
}
return nil
})
return removedFiles, err
}
func RemoveBinaryPackage(repo, pkgName string) (removedFiles []string, err error) {
if _, err := os.Stat(path.Join(repo, "binary")); err != nil {
return removedFiles, nil
}
err = filepath.Walk(path.Join(repo, "binary"), func(packagePath string, info fs.FileInfo, err error) error {
if !strings.HasSuffix(packagePath, ".bpm") {
return nil
}
// Get package info
pkgInfo, err := bpmutilsshared.ReadPacakgeInfoFromTarball(packagePath)
if err != nil {
return err
}
if pkgInfo.Name != pkgName {
return nil
}
// Remove package and its signature
err = os.Remove(packagePath)
if err != nil {
return err
}
if relPath, err := filepath.Rel(repo, packagePath); err != nil {
removedFiles = append(removedFiles, packagePath)
} else {
removedFiles = append(removedFiles, relPath)
}
err = os.Remove(packagePath + ".sig")
if err != nil && !os.IsNotExist(err) {
return err
}
return nil
})
return removedFiles, err
}
+3 -1
View File
@@ -5,10 +5,12 @@ 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 ( require (
gopkg.in/yaml.v3 v3.0.1 // indirect github.com/drone/envsubst v1.0.3 // indirect
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f // indirect
) )
replace bpm-utils-shared => ../bpm-utils-shared replace bpm-utils-shared => ../bpm-utils-shared
+6
View File
@@ -1,3 +1,9 @@
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/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=
+680 -8
View File
@@ -3,24 +3,43 @@ package main
import ( import (
bpmutilsshared "bpm-utils-shared" bpmutilsshared "bpm-utils-shared"
"bufio" "bufio"
"bytes"
"context"
"fmt" "fmt"
"io/fs"
"log" "log"
"maps"
"os" "os"
"os/exec"
"path" "path"
"path/filepath"
"slices"
"sort"
"strconv"
"strings" "strings"
"time"
flag "github.com/spf13/pflag" flag "github.com/spf13/pflag"
"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")
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 != "" {
@@ -40,7 +59,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 == "" {
@@ -48,8 +71,67 @@ func main() {
} }
bpmutilsshared.UpdateDatabases(repo) bpmutilsshared.UpdateDatabases(repo)
} else { case "list", "l":
bpmutilsshared.ShowHelp() flagset := flag.NewFlagSet("list", flag.ExitOnError)
setupFlagsAndHelp(flagset, fmt.Sprintf("bpm-repo %s <options>", subcommand), "List packages", os.Args[:1])
// Get current database
repo := bpmutilsshared.GetRepository()
if repo == "" {
log.Fatal("Error: this command may only be run inside a BPM repository")
}
listPackagesFunc(repo)
case "check-versions", "v":
// 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")
flagset.BoolP("apply", "a", false, "Apply new versions to packages")
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)
case "hold", "h":
// Setup flags and help
flagset := flag.NewFlagSet("hold", flag.ExitOnError)
flagset.Bool("get", false, "Show current value")
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")
}
holdPackage(repo)
case "compile-all", "a":
// 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("modified", "m", true, "Skip non-modified source packages")
flagset.BoolP("show-order", "o", false, "Show the order in which all packages will be compiled and exit")
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")
}
compileAllPackagesFunc(repo)
default:
log.Println("Error: unknown subcommand")
listSubcommands()
os.Exit(1)
} }
} }
@@ -65,5 +147,595 @@ func createRepository(name, description string) {
log.Fatalf("Error: could not write to file: %s", err) log.Fatalf("Error: could not write to file: %s", err)
} }
err = os.Mkdir(path.Join(name, "recipes"), 0755)
if err != nil {
log.Fatalf("Error: could not create directory: %s", err)
}
fmt.Println("Repository created successfully!") fmt.Println("Repository created successfully!")
} }
func checkVersionsFunc(repo string) {
// Get flags
verbose, _ := currentFlagSet.GetBool("verbose")
force, _ := currentFlagSet.GetBool("force")
apply, _ := currentFlagSet.GetBool("apply")
// Read environment files
err := readEnvFile(repo)
if err != nil {
log.Fatalf("Error: could not read environment file: %s", err)
}
// Read version cache
type CachedVersionEntry struct {
LatestVersion string `yaml:"latest_version"`
Timestamp int64 `yaml:"timestamp"`
OnHold bool `yaml:"on_hold,omitempty"`
}
cachedVersions := make(map[string]CachedVersionEntry)
data, err := os.ReadFile(path.Join(repo, ".version-cache"))
if err == nil {
err := yaml.Unmarshal(data, &cachedVersions)
if err != nil {
cachedVersions = make(map[string]CachedVersionEntry)
}
}
directories := make([]string, 0)
if currentFlagSet.NArg() > 0 {
for _, dir := range currentFlagSet.Args() {
if _, err := os.Stat(path.Join(repo, "recipes", dir, "info.yml")); err != nil {
log.Fatalf("Error: could not find info.yml file in directory (%s): %s", dir, err)
}
directories = append(directories, path.Join(repo, "recipes", dir))
}
} else {
// Loop through each directory with a 'info.yml' file
err := filepath.Walk(path.Join(repo, "recipes"), func(path string, info fs.FileInfo, err error) error {
if filepath.Base(path) == "info.yml" {
directories = append(directories, filepath.Dir(path))
}
return nil
})
if err != nil {
log.Fatalf("Error: could not loop through all packages: %s", err)
}
}
pkgsWithoutScript := make([]string, 0)
pkgsIgnored := make([]string, 0)
pkgsWithError := make(map[string]error)
pkgsWithUpdates := make(map[string]struct {
OldVersion string
NewVersion string
}, 0)
pkgsUpToDate := 0
for _, dir := range directories {
pkgInfo, err := bpmutilsshared.ReadPacakgeInfoFromFile(path.Join(dir, "info.yml"))
if err != nil {
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
latestVersion := ""
if cachedVersion, ok := cachedVersions[pkgInfo.Name]; ok && !force && time.Since(time.UnixMilli(cachedVersion.Timestamp)).Milliseconds() < 604800000 {
latestVersion = cachedVersion.LatestVersion
} else {
// Check whether check-version.sh script exists
if _, err := os.Stat(path.Join(dir, "check-version.sh")); err != nil {
pkgsWithoutScript = append(pkgsWithoutScript, pkgInfo.Name)
continue
}
// Execute check-version.sh script
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()
output, err := cmd.Output()
if err != nil {
pkgsWithError[pkgInfo.Name] = err
continue
}
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
if latestVersion == "" || latestVersion == "null" {
pkgsWithError[pkgInfo.Name] = fmt.Errorf("invalid version number \"%s\"", latestVersion)
continue
}
// Get current on_hold value
onHold := false
if v, ok := cachedVersions[pkgInfo.Name]; ok {
onHold = v.OnHold
}
// Cache latest version
cachedVersions[pkgInfo.Name] = CachedVersionEntry{
LatestVersion: latestVersion,
Timestamp: time.Now().UnixMilli(),
OnHold: onHold,
}
}
// Compare versions
if pkgInfo.Version != latestVersion {
pkgsWithUpdates[pkgInfo.Name] = struct {
OldVersion string
NewVersion string
}{
OldVersion: pkgInfo.Version,
NewVersion: latestVersion,
}
// Apply new version
pkgInfo.Version = latestVersion
pkgInfo.Revision = 1
if apply && !cachedVersions[pkgInfo.Name].OnHold {
var data bytes.Buffer
encoder := yaml.NewEncoder(&data)
encoder.SetIndent(2)
encoder.Encode(pkgInfo)
// Write package information
err := os.WriteFile(path.Join(dir, "info.yml"), data.Bytes(), 0644)
if err != nil {
log.Printf("Warning: could not write new version for package (%s) to file: %s", pkgInfo.Name, err)
}
// Generate source package
cmd := exec.Command("bpm-package")
cmd.Stderr = os.Stderr
cmd.Dir = dir
if err := cmd.Run(); err != nil {
log.Printf("Warning: could not generate source pacakge (%s): %s", pkgInfo.Name, err)
}
}
continue
}
pkgsUpToDate++
}
// Save cached versions to file
data, err = yaml.Marshal(cachedVersions)
if err == nil {
err := os.WriteFile(path.Join(repo, ".version-cache"), data, 0644)
if err != nil {
log.Printf("Warning: could not write cached versions to file: %s", err)
}
}
// Print updates
keys := slices.Collect(maps.Keys(pkgsWithUpdates))
sort.Strings(keys)
for _, pkg := range keys {
if bpmutilsshared.CompareVersions(pkgsWithUpdates[pkg].NewVersion, pkgsWithUpdates[pkg].OldVersion) >= 0 {
fmt.Printf("Update available for package (%s): %s -> %s", pkg, pkgsWithUpdates[pkg].OldVersion, pkgsWithUpdates[pkg].NewVersion)
} else {
fmt.Printf("Downgrade(?) available for package (%s): %s -> %s", pkg, pkgsWithUpdates[pkg].OldVersion, pkgsWithUpdates[pkg].NewVersion)
}
if cachedVersions[pkg].OnHold {
fmt.Print(" (On hold)")
}
fmt.Println()
}
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
for _, pkg := range pkgsWithoutScript {
log.Printf("Warning: package (%s) has no check-version.sh script\n", pkg)
}
}
// Print errors
keys = slices.Collect(maps.Keys(pkgsWithError))
sort.Strings(keys)
for _, pkg := range keys {
log.Printf("Error: check-version.sh script for package (%s) failed: %s", pkg, pkgsWithError[pkg])
}
// Print summary
fmt.Println("----- Summary -----")
fmt.Println("Available updates:", len(pkgsWithUpdates))
fmt.Println("Up to date:", pkgsUpToDate)
fmt.Println("Missing script:", len(pkgsWithoutScript))
fmt.Println("Ignored: ", len(pkgsIgnored))
fmt.Println("Errors:", len(pkgsWithError))
}
func holdPackage(repo string) {
// Get flags
get, _ := currentFlagSet.GetBool("get")
// Get package name
pkgName := ""
if len(currentFlagSet.Args()) < 1 {
log.Fatalf("Error: no package name set")
} else {
pkgName = currentFlagSet.Arg(0)
}
// Ensure package exists
if _, err := os.Stat(path.Join(repo, "recipes", pkgName, "info.yml")); err != nil {
log.Fatalf("Error: could not find info.yml file in directory (%s): %s", pkgName, err)
}
// Read version cache
type CachedVersionEntry struct {
LatestVersion string `yaml:"latest_version"`
Timestamp int64 `yaml:"timestamp"`
OnHold bool `yaml:"on_hold,omitempty"`
}
cachedVersions := make(map[string]*CachedVersionEntry)
data, err := os.ReadFile(path.Join(repo, ".version-cache"))
if err == nil {
err := yaml.Unmarshal(data, &cachedVersions)
if err != nil {
cachedVersions = make(map[string]*CachedVersionEntry)
}
}
// Get current on_hold value
if get {
if cachedVersion, ok := cachedVersions[pkgName]; ok && cachedVersion.OnHold {
fmt.Printf("Package (%s) has been put on hold.\n", pkgName)
} else {
fmt.Printf("Package (%s) has not been put on hold.\n", pkgName)
}
return
}
// Get boolean value
var value bool
if len(currentFlagSet.Args()) < 2 {
log.Fatalf("Error: no boolean value set")
} else {
value, err = strconv.ParseBool(currentFlagSet.Arg(1))
if err != nil {
log.Fatalf("Error: value (%s) is not a boolean", currentFlagSet.Arg(1))
}
}
// Set on_hold value
cachedVersions[pkgName].OnHold = value
// Save cached versions to file
data, err = yaml.Marshal(cachedVersions)
if err == nil {
err := os.WriteFile(path.Join(repo, ".version-cache"), data, 0644)
if err != nil {
log.Printf("Warning: could not write cached versions to file: %s", err)
}
}
if value {
fmt.Printf("Package (%s) was put on hold.\n", pkgName)
} else {
fmt.Printf("Package (%s) is no longer put on hold.\n", pkgName)
}
}
func listPackagesFunc(repo string) {
// Read package recipes
pkgs := bpmutilsshared.ReadRepositoryRecipes(repo)
// Read databases
sourceDatabase, _ := bpmutilsshared.ReadDatabase(path.Join(repo, "source/database.bpmdb"))
binaryDatabase, _ := bpmutilsshared.ReadDatabase(path.Join(repo, "binary/database.bpmdb"))
for _, pkg := range pkgs {
fmt.Printf("%s (%s):\n", pkg.Name, pkg.Version)
// Show source package version
if sourceDatabase != nil {
if entry, ok := sourceDatabase.Entries[pkg.Name]; ok {
if entry.PackageInfo.Version == pkg.Version {
fmt.Printf(" Source package: %s\n", entry.PackageInfo.Version)
} else {
fmt.Printf(" Source package: %s ≠ %s (Version mismatch)\n", entry.PackageInfo.Version, pkg.Version)
}
} else {
fmt.Println(" Source package not found!")
}
}
// Show binary package version
if binaryDatabase != nil {
if entry, ok := binaryDatabase.Entries[pkg.Name]; ok {
if entry.PackageInfo.Version == pkg.Version {
fmt.Printf(" Binary package: %s\n", entry.PackageInfo.Version)
} else {
fmt.Printf(" Binary package: %s ≠ %s (Version mismatch)\n", entry.PackageInfo.Version, pkg.Version)
}
} else {
fmt.Println(" Binary package not found!")
}
}
}
}
func compileAllPackagesFunc(repo string) {
// Get flags
verbose, _ := currentFlagSet.GetBool("verbose")
modifiedOnly, _ := currentFlagSet.GetBool("modified")
showOrder, _ := currentFlagSet.GetBool("show-order")
// Read BPM utils config
config, err := bpmutilsshared.ReadBPMUtilsConfig()
if err != nil {
log.Fatalf("Error: failed to read config: %s", err)
}
// Read package recipes
pkgs := bpmutilsshared.ReadRepositoryRecipes(repo)
pkgsMap := make(map[string]bpmutilsshared.PackageInfo)
for _, pkg := range pkgs {
pkgsMap[pkg.Name] = pkg
// Add split packages
for _, splitPkg := range pkg.SplitPackages {
if _, ok := pkgsMap[splitPkg.Name]; !ok {
pkgsMap[splitPkg.Name] = pkg
}
}
}
// Read databases
sourceDatabase, err := bpmutilsshared.ReadDatabase(path.Join(repo, "source/database.bpmdb"))
binaryDatabase, _ := bpmutilsshared.ReadDatabase(path.Join(repo, "binary/database.bpmdb"))
// Toposort packages using Depth-first search algorithm
sorted := make([]bpmutilsshared.PackageInfo, 0)
marked := make(map[string]int) // 0 = Unmarked, 1 = Temporary mark, 2 = Permanent mark
var visit func(pkgInfo bpmutilsshared.PackageInfo) error
visit = func(pkgInfo bpmutilsshared.PackageInfo) error {
if mark, _ := marked[pkgInfo.Name]; mark == 2 {
return nil
} else if mark == 1 {
return fmt.Errorf("circular dependency found")
}
marked[pkgInfo.Name] = 1
// Get all dependencies
depends := slices.Clone(pkgInfo.Depends)
depends = append(depends, pkgInfo.MakeDepends...)
for _, depend := range depends {
dependName, _, _ := bpmutilsshared.SplitPkgNameAndVersion(depend)
// Find package in repository
dependInfo, ok := pkgsMap[dependName]
if !ok {
// Search for virtual package
for _, pkg := range pkgsMap {
if slices.Contains(pkg.Provides, dependName) {
dependInfo = pkg
ok = true
break
}
}
if !ok {
// Try to see if package exists in BPM's other repositories
err := exec.Command("bpm", "query", "-d", depend).Run()
if err != nil {
return fmt.Errorf("could not find package (%s) in any database", depend)
}
continue
}
}
err := visit(dependInfo)
if err != nil {
if strings.Contains(err.Error(), "circular") {
if verbose {
fmt.Printf("Circular dependency found! (%s -> %s)\n", pkgInfo.Name, dependInfo.Name)
}
} else {
log.Fatalf("Error: could not resolve dependencies: %s", err)
}
}
}
marked[pkgInfo.Name] = 2
sorted = append(sorted, pkgInfo)
return nil
}
for _, pkg := range pkgs {
if mark, _ := marked[pkg.Name]; mark != 2 {
visit(pkg)
}
}
// Compile all packages in order
for _, pkgInfo := range sorted {
skip := false
if modifiedOnly {
skip = true
// Check if required version is valid
for _, depend := range pkgInfo.Depends {
if !strings.ContainsRune(depend, '=') {
continue
}
dependName, _, _ := bpmutilsshared.SplitPkgNameAndVersion(depend)
if !bpmutilsshared.EvaluateDependency(depend, pkgsMap[dependName].Version) {
skip = false
break
}
}
for _, depend := range pkgInfo.MakeDepends {
if !strings.ContainsRune(depend, '=') {
continue
}
dependName, _, _ := bpmutilsshared.SplitPkgNameAndVersion(depend)
if !bpmutilsshared.EvaluateDependency(depend, pkgsMap[dependName].Version) {
skip = false
break
}
}
// Check if source database entry is not synced
if sourceDatabase != nil {
if len(pkgInfo.SplitPackages) != 0 {
sourcePkgInfo, ok := sourceDatabase.Entries[pkgInfo.Name]
if ok {
hasSplitPkg := func(name string) bool {
return slices.ContainsFunc(sourcePkgInfo.PackageInfo.SplitPackages, func(splitPkg *bpmutilsshared.PackageInfo) bool {
return name == splitPkg.Name
})
}
for _, splitPkg := range pkgInfo.SplitPackages {
if !hasSplitPkg(splitPkg.Name) || sourcePkgInfo.PackageInfo.GetFullVersion() != pkgInfo.GetFullVersion() {
skip = false
break
}
}
} else {
skip = false
}
} else {
if sourcePkgInfo, ok := sourceDatabase.Entries[pkgInfo.Name]; !ok || sourcePkgInfo.PackageInfo.GetFullVersion() != pkgInfo.GetFullVersion() {
skip = false
}
}
}
// Check if binary database entry is not synced
if binaryDatabase != nil {
if len(pkgInfo.SplitPackages) != 0 {
for _, splitPkg := range pkgInfo.SplitPackages {
if binaryPkgInfo, ok := binaryDatabase.Entries[splitPkg.Name]; !ok || binaryPkgInfo.PackageInfo.GetFullVersion() != pkgInfo.GetFullVersion() {
skip = false
break
}
}
} else {
if binaryPkgInfo, ok := binaryDatabase.Entries[pkgInfo.Name]; !ok || binaryPkgInfo.PackageInfo.GetFullVersion() != pkgInfo.GetFullVersion() {
skip = false
}
}
}
}
if skip {
continue
}
if showOrder {
fmt.Println(pkgInfo.Name)
continue
}
// Ensure installed packages are up-to-date
cmd := exec.Command(config.PrivilegeEscalatorCmd, "sh", "-c", "bpm u -y")
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
log.Fatalf("Error: could not update packages): %s", err)
}
// Compile source package
cmd = exec.Command("bpm-package", "-cdusy")
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = path.Join(repo, "recipes", pkgInfo.Name)
if err := cmd.Run(); err != nil {
log.Fatalf("Error: could not compile package (%s): %s", pkgInfo.Name, err)
}
}
// Ensure installed packages are up-to-date
if !showOrder {
cmd := exec.Command(config.PrivilegeEscalatorCmd, "sh", "-c", "bpm u -y")
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
log.Fatalf("Error: could not update packages): %s", err)
}
}
}
func readEnvFile(repo string) error {
data, err := os.ReadFile(path.Join(repo, ".env"))
if os.IsNotExist(err) {
return nil
} else if err != nil {
return err
}
for _, line := range strings.Split(string(data), "\n") {
line = strings.TrimSpace(line)
if line == "" || line[0] == '#' {
continue
}
splitLine := strings.Split(line, "=")
if len(splitLine) != 2 {
return fmt.Errorf("invalid format")
}
os.Setenv(splitLine[0], splitLine[1])
}
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(" h, hold Prevent package from being automatically updated")
fmt.Println(" l, list List packages")
fmt.Println(" a, compile-all Compile all packages in the current repository")
}
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)
}
+5 -1
View File
@@ -5,8 +5,12 @@ 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
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f // indirect
)
replace bpm-utils-shared => ../bpm-utils-shared replace bpm-utils-shared => ../bpm-utils-shared
+6
View File
@@ -1,3 +1,9 @@
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/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=
+102 -40
View File
@@ -3,18 +3,19 @@ package main
import ( import (
bpmutilsshared "bpm-utils-shared" bpmutilsshared "bpm-utils-shared"
"bufio" "bufio"
"bytes"
"fmt" "fmt"
"io"
"log" "log"
"os" "os"
"os/exec" "os/exec"
"path" "path"
"path/filepath"
"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 name = flag.StringP("name", "n", "", "Set the package name") var name = flag.StringP("name", "n", "", "Set the package name")
var description = flag.StringP("description", "d", "Default Package Description", "Set the description") var description = flag.StringP("description", "d", "Default Package Description", "Set the description")
var version = flag.StringP("version", "v", "1.0", "Set the package version") var version = flag.StringP("version", "v", "1.0", "Set the package version")
@@ -23,21 +24,35 @@ var license = flag.StringP("license", "l", "", "Set the package licenses")
var template = flag.StringP("template", "t", "gnu-configure", "Set the package template") var template = flag.StringP("template", "t", "gnu-configure", "Set the package template")
var git = flag.BoolP("git", "g", true, "Create git repository") var git = flag.BoolP("git", "g", true, "Create git repository")
var directory = ""
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()
// Trim package name
*name = strings.TrimSpace(*name)
// Show command help if no directory name is given // Show command help if no directory name is given
if *directory == "" { if *name == "" {
log.Println("Directory flag is required") log.Println("Error: name flag is required")
bpmutilsshared.ShowHelp() flag.Usage()
os.Exit(1) os.Exit(1)
} }
// Set package name to directory name if empty // Get current repository
if *name == "" { repo := bpmutilsshared.GetRepository()
*name = filepath.Base(*directory)
// Set directory
if repo != "" {
directory = path.Join(repo, "recipes", *name)
} else {
workDir, err := os.Getwd()
if err != nil {
log.Fatalf("Error: could not get working directory")
}
directory = path.Join(workDir, *name)
} }
// run checks // run checks
@@ -60,25 +75,29 @@ func main() {
} }
func runChecks() { func runChecks() {
if strings.TrimSpace(*directory) == "" { // Check if package name is valid
log.Fatalf("No directory was specified!") if *name == "" {
log.Fatalf("Error: no package name was specified!")
} }
if strings.TrimSpace(*name) == "" { // Check if parent directory is valid
log.Fatalf("No package name was specified!") if _, err := os.Stat(path.Dir(directory)); err != nil {
log.Fatalf("Error: %s is not a valid directory: %s", path.Dir(directory), err)
} }
// Check if package directory exists
if _, err := os.Stat(directory); err == nil {
log.Fatalf("Error: %s already exists", directory)
}
// Check if template file is valid
if stat, err := os.Stat(path.Join("/etc/bpm-utils/templates/", *template)); err != nil || stat.IsDir() { if stat, err := os.Stat(path.Join("/etc/bpm-utils/templates/", *template)); err != nil || stat.IsDir() {
log.Fatalf("%s is not a valid template file!", *template) log.Fatalf("Error: '%s' is not a valid template file!", *template)
} }
} }
func showSummary() { func showSummary() {
absPath, err := filepath.Abs(strings.TrimSpace(*directory)) fmt.Printf("Setting up package directory at %s with the following information:\n", directory)
if err != nil {
log.Fatalf("Failed to determine absolute path: %s", err)
}
fmt.Printf("Setting up package directory at %s with the following information:\n", absPath)
fmt.Printf("Package name: %s\n", strings.TrimSpace(*name)) fmt.Printf("Package name: %s\n", strings.TrimSpace(*name))
fmt.Printf("Package description: %s\n", *description) fmt.Printf("Package description: %s\n", *description)
fmt.Printf("Package version: %s\n", *version) fmt.Printf("Package version: %s\n", *version)
@@ -106,37 +125,54 @@ func replaceVariables(templateContents string) string {
} }
func createDirectory() { func createDirectory() {
// Trim spaces // Read BPM utils config
*directory = strings.TrimSpace(*directory) config, err := bpmutilsshared.ReadBPMUtilsConfig()
*name = strings.TrimSpace(*name) if err != nil {
log.Fatalf("Error: failed to read config: %s", err)
}
// Create directory // Create directory
err := os.Mkdir(*directory, 0755) err = os.Mkdir(directory, 0755)
if err != nil { if err != nil {
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" Revision: 1,
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 != "" { if config.DefaultMaintainer != "" {
pkgInfo += "license: " + *license + "\n" pkgInfo.Maintainers = append(pkgInfo.Maintainers, config.DefaultMaintainer)
} }
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, "info.yml"), 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 info.yml: %s", err)
} }
// Create source-files directory // Create source-files directory
err = os.Mkdir(path.Join(*directory, "source-files"), 0755) err = os.Mkdir(path.Join(directory, "source-files"), 0755)
if err != nil { if err != nil {
log.Fatalf("Error: could not create directory: %s", err) log.Fatalf("Error: could not create directory: %s", err)
} }
@@ -151,7 +187,7 @@ func createDirectory() {
// Replace variables in template file // Replace variables in template file
input = []byte(replaceVariables(string(input))) input = []byte(replaceVariables(string(input)))
err = os.WriteFile(path.Join(*directory, "source.sh"), input, 0644) err = os.WriteFile(path.Join(directory, "recipe.sh"), input, 0644)
if err != nil { if err != nil {
log.Fatalf("Error: could not write to source file: %s", err) log.Fatalf("Error: could not write to source file: %s", err)
return return
@@ -159,9 +195,35 @@ func createDirectory() {
// Create git repository // Create git repository
if git != nil && *git { if git != nil && *git {
err = exec.Command("git", "init", *directory).Run() err = exec.Command("git", "init", directory).Run()
if err != nil { if err != nil {
log.Fatalf("Error: could not initialize git repository: %s", err) log.Fatalf("Error: could not initialize git repository: %s", err)
} }
// Copy default gitignore
defaultGitignoreFile, err := os.Open("/etc/bpm-utils/gitignore.default")
if err != nil {
return
}
defer defaultGitignoreFile.Close()
newGitignoreFile, err := os.OpenFile(path.Join(directory, ".gitignore"), os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatalf("Error: could not create .gitignore file: %s", err)
}
_, err = io.Copy(newGitignoreFile, defaultGitignoreFile)
if err != nil {
log.Fatalf("Error: could not copy data to .gitignore file: %s", err)
} }
} }
}
func setupFlagsAndHelp(usage, desc string) {
flag.Usage = func() {
fmt.Println("Usage: " + usage)
fmt.Println("Description: " + desc)
fmt.Println("Options:")
flag.PrintDefaults()
}
flag.Parse()
}
+25 -12
View File
@@ -58,31 +58,44 @@ func GenerateDatabase(path string) error {
// Initialize database entry // Initialize database entry
entry := BPMDatabaseEntry{} entry := BPMDatabaseEntry{}
entry.DownloadSize = info.Size() entry.DownloadSize = info.Size()
entry.InstalledSize = 0
entry.Filepath, err = filepath.Rel(path, packagePath) entry.Filepath, err = filepath.Rel(path, packagePath)
if err != nil { if err != nil {
return err return err
} }
// Get package installed size // Get package info
cmd := exec.Command("tar", "-t", "-v", "-f", packagePath, "files.tar.gz") entry.PackageInfo, err = ReadPacakgeInfoFromTarball(packagePath)
output, err := cmd.Output()
if err == nil {
entry.InstalledSize, err = strconv.ParseInt(strings.Fields(string(output))[2], 10, 64)
if err != nil { if err != nil {
return err return err
} }
} else if err.(*exec.ExitError).ExitCode() == 2 {
entry.InstalledSize = 0
} else {
return err
}
// Get package info // Get package installed size
entry.PackageInfo, err = ReadPacakgeInfo(packagePath) if entry.PackageInfo.Type == "binary" {
cmd := exec.Command("tar", "xf", packagePath, "files.txt", "-O")
output, err := cmd.Output()
if err != nil { if err != nil {
return err return err
} }
for _, line := range strings.Split(string(output), "\n") {
line = strings.TrimSpace(line)
if line == "" {
continue
}
stringEntry := strings.Split(line, " ")
if len(stringEntry) < 5 {
return fmt.Errorf("files.txt is not formatted correctly")
}
size, err := strconv.ParseInt(stringEntry[len(stringEntry)-1], 10, 64)
if err != nil {
return err
}
entry.InstalledSize += size
}
}
// Add entry to database // Add entry to database
if _, ok := database.Entries[entry.PackageInfo.Name]; ok { if _, ok := database.Entries[entry.PackageInfo.Name]; ok {
return fmt.Errorf("package (%s) has already been added to the database", entry.PackageInfo.Name) return fmt.Errorf("package (%s) has already been added to the database", entry.PackageInfo.Name)
+5 -4
View File
@@ -2,7 +2,8 @@ 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
)
require github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f // indirect
+6 -2
View File
@@ -1,5 +1,9 @@
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=
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 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=
+2 -22
View File
@@ -1,18 +1,15 @@
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"`
DefaultMaintainer string `yaml:"default_maintainer,omitempty"`
AddDefaultMaintainer bool `yaml:"add_default_maintainer,omitempty"`
} }
func ReadBPMUtilsConfig() (*BPMUtilsConfig, error) { func ReadBPMUtilsConfig() (*BPMUtilsConfig, error) {
@@ -29,20 +26,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()
}
+219 -16
View File
@@ -1,62 +1,265 @@
package bpm_utils_shared package bpm_utils_shared
import ( import (
"fmt"
"os"
"os/exec" "os/exec"
"strconv"
"strings"
"github.com/drone/envsubst"
version "github.com/knqyf263/go-rpm-version"
"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"`
Maintainers []string `yaml:"maintainers,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"` RuntimeDepends []string `yaml:"runtime_depends,omitempty"`
OptionalDepends []string `yaml:"optional_depends,omitempty"` OptionalDepends []string `yaml:"optional_depends,omitempty"`
MakeDepends []string `yaml:"make_depends,omitempty"`
CheckDepends []string `yaml:"check_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"`
Options []string `yaml:"options,omitempty"`
Flags []PackageFlag `yaml:"flags,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 PackageFlag struct {
// Extract package info using tar Name string `yaml:"name"`
cmd := exec.Command("tar", "-x", "-f", path, "pkg.info", "-O") DefaultValue string `yaml:"default_value"`
output, err := cmd.Output() BuiltValue string `yaml:"built_value"`
if err != nil { AcceptedValues []PackageAcceptedValue `yaml:"accepted_values,omitempty"`
return nil, err
} }
type PackageAcceptedValue struct {
Value string `yaml:"value"`
Depends []string `yaml:"depends,omitempty"`
RuntimeDepends []string `yaml:"runtime_depends,omitempty"`
OptionalDepends []string `yaml:"optional_depends,omitempty"`
MakeDepends []string `yaml:"make_depends,omitempty"`
CheckDepends []string `yaml:"check_depends,omitempty"`
}
type PackageDownload struct {
Url string `yaml:"url"`
Type string `yaml:"type,omitempty"`
Filepath string `yaml:"filepath,omitempty,omitempty"`
// 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),
RuntimeDepends: make([]string, 0),
OptionalDepends: make([]string, 0), OptionalDepends: make([]string, 0),
Conflicts: make([]string, 0), Conflicts: make([]string, 0),
Replaces: make([]string, 0), Replaces: make([]string, 0),
Provides: make([]string, 0), Provides: make([]string, 0),
Options: 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 { if err != nil {
return nil, err return nil, err
} }
return pkgInfo, nil return pkgInfo, nil
} }
func (pkgInfo *PackageInfo) GetFullVersion() string {
return pkgInfo.Version + "-" + strconv.Itoa(pkgInfo.Revision)
}
func ReadPacakgeInfoFromTarball(path string) (*PackageInfo, error) {
// Extract package info using tar
cmd := exec.Command("tar", "-x", "-f", path, "info.yml", "-O")
output, err := cmd.Output()
if err != nil {
return nil, err
}
pkgInfo, err := ReadPackageInfo(output)
if err != nil {
return nil, err
}
return pkgInfo, nil
}
func ReadPacakgeInfoFromFile(path string) (*PackageInfo, error) {
// Read data from file
output, err := os.ReadFile(path)
if err != nil {
return nil, err
}
pkgInfo, err := ReadPackageInfo(output)
if err != nil {
return nil, err
}
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)
}
}
func CompareVersions(version1, version2 string) int {
v1 := version.NewVersion(version1)
v2 := version.NewVersion(version2)
return v1.Compare(v2)
}
func SplitPkgNameAndVersion(pkg string) (string, string, string) {
if strings.Contains(pkg, ">=") {
pkgSplit := strings.SplitN(pkg, ">=", 2)
pkgName := pkgSplit[0]
pkgVersion := pkgSplit[1]
return pkgName, ">=", pkgVersion
} else if strings.Contains(pkg, ">") {
pkgSplit := strings.SplitN(pkg, ">", 2)
pkgName := pkgSplit[0]
pkgVersion := pkgSplit[1]
return pkgName, ">", pkgVersion
} else if strings.Contains(pkg, "<=") {
pkgSplit := strings.SplitN(pkg, "<=", 2)
pkgName := pkgSplit[0]
pkgVersion := pkgSplit[1]
return pkgName, "<=", pkgVersion
} else if strings.Contains(pkg, "<") {
pkgSplit := strings.SplitN(pkg, "<", 2)
pkgName := pkgSplit[0]
pkgVersion := pkgSplit[1]
return pkgName, "<", pkgVersion
} else if strings.Contains(pkg, "=") {
pkgSplit := strings.SplitN(pkg, "=", 2)
pkgName := pkgSplit[0]
pkgVersion := pkgSplit[1]
return pkgName, "=", pkgVersion
}
return pkg, "", ""
}
func EvaluateDependency(pkg, matchVersion string) bool {
_, comparisonSymbol, pkgVersion := SplitPkgNameAndVersion(pkg)
switch comparisonSymbol {
case ">=":
return CompareVersions(matchVersion, pkgVersion) >= 0
case ">":
return CompareVersions(matchVersion, pkgVersion) > 0
case "<=":
return CompareVersions(matchVersion, pkgVersion) <= 0
case "<":
return CompareVersions(matchVersion, pkgVersion) < 0
case "=":
if cutPkgVersion, ok := strings.CutSuffix(pkgVersion, "*"); ok {
return strings.HasPrefix(matchVersion, cutPkgVersion)
} else {
return CompareVersions(matchVersion, pkgVersion) == 0
}
default:
return true
}
}
+29
View File
@@ -3,6 +3,7 @@ package bpm_utils_shared
import ( import (
"os" "os"
"path" "path"
"sort"
) )
func GetRepository() string { func GetRepository() string {
@@ -23,3 +24,31 @@ func GetRepository() string {
} }
return dir return dir
} }
func ReadRepositoryRecipes(repository string) []PackageInfo {
// Read package recipes
recipeDirs, err := os.ReadDir(path.Join(repository, "recipes"))
if err != nil {
return nil
}
pkgs := make([]PackageInfo, 0)
for _, dir := range recipeDirs {
pkgInfoPath := path.Join(repository, "recipes", dir.Name(), "info.yml")
if _, err := os.Stat(pkgInfoPath); err != nil {
continue
}
pkgInfo, err := ReadPacakgeInfoFromFile(pkgInfoPath)
if err == nil {
pkgs = append(pkgs, *pkgInfo)
}
}
// Sort package recipes
sort.Slice(pkgs, func(i, j int) bool {
return pkgs[i].Name < pkgs[j].Name
})
return pkgs
}