86 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
EnumDev 908b9ccc12 Update README.md 2025-09-04 19:16:10 +03:00
EnumDev 162f76b697 Add flag to bpm-package for moving output packages to the binary
directory
2025-09-04 18:08:12 +03:00
EnumDev f656006710 Add bpm-repo functionality 2025-09-04 18:07:44 +03:00
EnumDev 1d43614fde Remove bpm-convert 2025-09-04 13:02:39 +03:00
EnumDev ad691cd78e Switch from Go's flag package to pflag 2025-09-04 12:21:23 +03:00
EnumDev 3cfb314333 Improve package compilation and installation handling 2025-09-04 12:01:59 +03:00
EnumDev 3b5c8de115 Update README.md 2025-09-04 12:01:09 +03:00
EnumDev 3dbb24fd15 Update Makefile 2025-09-03 18:36:46 +03:00
EnumDev 5a7995d226 Move help message and flag setup to bpm-utils-shared package 2025-05-04 17:52:29 +03:00
EnumDev a6578fb414 Remove snake-case from bpm-utils-shared import 2025-05-04 17:33:44 +03:00
EnumDev 354d284289 Make Makefile POSIX-compliant 2025-05-04 17:32:23 +03:00
EnumDev 4235252172 Add bpm-package functionality 2025-05-04 17:29:00 +03:00
EnumDev 2dec7c28e6 Update README.md 2025-04-09 17:25:55 +03:00
EnumDev 368c599ea6 Replace placeholders in template files 2025-04-09 13:51:46 +03:00
EnumDev e147d74137 Add command description to bpm-setup help message 2025-04-08 14:16:32 +03:00
EnumDev d395004658 Add a few template files 2025-04-08 14:11:57 +03:00
EnumDev a0aff54e95 Fix error message 2025-04-08 13:48:16 +03:00
EnumDev e021e814f4 Trim spaces from directory path and package name 2025-04-08 13:47:56 +03:00
EnumDev 57a58a18a7 Add checks to bpm-setup 2025-04-08 13:43:55 +03:00
EnumDev 23b5f0eee1 Add bpm-setup functionality 2025-04-07 22:05:04 +03:00
EnumDev c60bec2c1a Setup flag system for bpm-setup 2025-04-07 21:40:32 +03:00
EnumDev e02d64b67e Setup basic repository structure 2025-04-07 21:22:24 +03:00
EnumDev 5076c48001 Update LICENSE 2025-04-07 21:10:56 +03:00
EnumDev 7df29661c3 Moved over to new BPM file format 2024-10-26 18:04:04 +03:00
EnumDev 66c8dd5f91 Directory name will now be used as the package name if not set in bpm-setup and updated README.md 2024-10-08 10:32:58 +03:00
EnumDev 9372acba3f pre_update.sh package scripts will now be included in bpm files 2024-10-08 10:08:48 +03:00
EnumDev efb904fa37 bpm-convert will now make sure dependencies are met if bpm is installed 2024-10-08 09:45:49 +03:00
EnumDev 430a233879 It is no longer necessary to type the output name of the source bpm file 2024-10-08 09:42:49 +03:00
EnumDev 9ef4699c0d Added make dependency detection for bpm-convert 2024-09-18 11:39:22 +00:00
EnumDev c2de62c343 Fixed 'deep' repository data creation not using the updated database format 2024-09-14 14:24:37 +00:00
EnumDev e4a9622ff2 Fixed package scripts not being included in bpm file 2024-09-14 14:23:36 +00:00
32 changed files with 2271 additions and 556 deletions
+5
View File
@@ -0,0 +1,5 @@
# IDE Directories
.idea
# Build directory
build
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2024 CapCreeperGR Copyright (c) 2024 EnumDev
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
+35
View File
@@ -0,0 +1,35 @@
# Installation paths
PREFIX ?= /usr/local
BINDIR ?= $(PREFIX)/bin
SYSCONFDIR := $(PREFIX)/etc
# Compilers and tools
GO ?= go
build:
mkdir -p build
cd src/bpm-package; $(GO) build -ldflags "-w" -o ../../build/bpm-package git.enumerated.dev/bubble-package-manager/bpm-utils/src/bpm-package
cd src/bpm-repo; $(GO) build -ldflags "-w" -o ../../build/bpm-repo git.enumerated.dev/bubble-package-manager/bpm-utils/src/bpm-repo
cd src/bpm-setup; $(GO) build -ldflags "-w" -o ../../build/bpm-setup git.enumerated.dev/bubble-package-manager/bpm-utils/src/bpm-setup
install:
# Create directory
install -dm755 $(DESTDIR)$(BINDIR)
# Install binary
install -Dm755 build/bpm-* -t $(DESTDIR)$(BINDIR)/
install-config:
# Create directory
install -dm755 $(DESTDIR)$(SYSCONFDIR)
# Install files
install -dm755 $(DESTDIR)$(SYSCONFDIR)/bpm-utils/
cp -r config/* -t $(DESTDIR)$(SYSCONFDIR)/bpm-utils/
uninstall:
-rm -f $(DESTDIR)$(BINDIR)/bpm-{package,repo,setup}
-rm -rf $(DESTDIR)$(SYSCONFDIR)/bpm-utils/
clean:
rm -r build/
.PHONY: build install install-config uninstall clean
+50 -34
View File
@@ -1,54 +1,70 @@
# BPM Utils # BPM Utils
### _Package creation and editing utilities for BPM_ ### ### _Creation and maintenance utilities for BPM packages and repositories_ ###
## Information ## Information
BPM Utils provides a number of different helper commands for creating and maintaining BPM packages or repositories
BPM Utils is a package providing a number of different helper scripts for setting up and archiving BPM packages ## Provided utilities
- bpm-setup (Sets up directories for BPM source package creation)
## Provided Scripts - bpm-repo (Allows for easy management of multiple-package repositories)
- bpm-setup (Creates a directory with the required files for a BPM package) - bpm-package (Turns a BPM source package directory into a .bpm archive)
- bpm-package (Turns a BPM package directory into a .bpm archive)
- bpm-convert (Converts source packages to binary ones)
- create-repository-data (Generates a repository package data list for unpac)
## Installation ## Installation
#### Using a package manager
Currently all BPM Utilities are simple bash scripts. This means you are able to simply clone this repository and place these scripts wherever you would like - Tide Linux: Tide linux provides a `bpm-utils` package which can be installed using `bpm install bpm-utils`
#### Building from source
- Download `go` from your package manager or from the go website
- Download `make` from your package manager
- Run the following command to compile the project
```
make
```
- Run the following command to install bpm-utils to your system. You may also append a DESTDIR variable at the end of this line if you wish to install the files to a different location
```
make install PREFIX=/usr SYSCONFDIR=/etc
make install-config PREFIX=/usr SYSCONFDIR=/etc
```
## Package Creation using BPM Utils ## Package Creation using BPM Utils
Creating a package for BPM with these utilities is simple Creating a package for BPM with these utilities is simple
2) Run the following command (You can run the comamnd with no arguments to see available options) 1) Run the following command (You can run the command with no arguments to see all available options)
``` ```
bpm-setup -D my_bpm_package -t <binary/source> bpm-setup -D my_package
```
3) This will create a directory named 'my_bpm_package' under the current directory with all the required files for the chosen package type
4) You are able to edit the pkg.info descriptor file inside the newly created directory to your liking. Here's an example of what a descriptor file could look like
``` ```
2) This will create a directory named `my_package` containing all files required for bpm package creation
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
name: my_package name: my_package
description: My package's description description: My package's description
version: 1.0 version: 1.0
architecture: x86_64 revision: 2 (Optional)
url: https://www.my-website.com/ (Optional) url: https://www.my-website.com/ (Optional)
license: MyLicense (Optional) license: MyLicense (Optional)
type: <binary/source> architecture: x86_64
depends: dependency1,dependency2 (Optional) type: source
make_depends: make_depend1,make_depend2 (Optional) 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
``` ```
### Binary Packages
3) If you are making a binary package, copy all your binaries along with the directories they reside in (i.e files/usr/bin/my_binary) 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
6) Run the following to create a package archive 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 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 <filename.bpm> bpm-package
``` ```
7) It's done! You now hopefully have a working BPM package! 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
### Source Packages
3) If you would like to bundle patches or other files with your source package place them in the 'source-files' directory. They will be extracted to the same location as the source.sh file during compilation
4) You need to edit your 'source.sh' file, the default source.sh template should explain the basic process of compiling your program
5) Your goal is to download your program's source code with either git, wget, curl, etc. and put the binaries under a folder called 'output' in the root of the temp directory. There is a simple example script with helpful comments in the htop-src test package
6) When you are done making your source.sh script run the following to create a package archive. You may also append the -c flag to compile the package and create a binary package as well
```
bpm-package <filename.bpm>
```
7) That's it! Your source package should now be compiling correctly!
-170
View File
@@ -1,170 +0,0 @@
#!/bin/bash
if [ -f .compilation-options ]; then
source ./.compilation-options
fi
echo "$ARCH"
while getopts "ksa:" o; do
case "${o}" in
a) ARCH="$OPTARG";;
k) KEEP=true;;
s) SKIPCHECK=true;;
*) exit 1;;
esac
done
PACKAGE="${@:$OPTIND:1}"
DIR="$PWD"
if [ -z "$ARCH" ]; then
ARCH=$(uname -m)
fi
if ! [ -f "$PACKAGE" ]; then
echo "$PACKAGE is not a path to a file"
exit 1
fi
if ! file "$PACKAGE" | grep -q 'gzip compressed data'; then
echo "$PACKAGE is not a BPM package"
exit 1
fi
if ! tar -tf "$PACKAGE" | grep -q 'source.sh'; then
echo "$PACKAGE is not a BPM source package"
exit 1
fi
echo "$Converting $PACKAGE..."
declare -A PKGINFO
# Declare and run Read Package Information function
function ReadPkgInfo() {
local BACK="$PWD"
cd "$DIR"
if [ $# -eq 0 ]; then
FILE=pkg.info
PKGINFO_FILE=$(tar -xf "$PACKAGE" pkg.info -O)
else
FILE=pkg.info."$1"
fi
if ! tar -tf "$PACKAGE" "$FILE"; then
echo "Could not find $FILE in $PACKAGE"
exit 1
fi
PKGINFO_FILE=$(tar -xf "$PACKAGE" "$FILE" -O)
while read line; do
PKGINFO[$(echo -n "$line" | cut -d":" -f1 | xargs)]=$(echo -n "$line" | cut -d":" -f2 | xargs)
done < <(tar -xf "$PACKAGE" "$FILE" -O)
cd "$BACK"
}
ReadPkgInfo
# Creating temporary compilation directory structure
TEMPDIR="/var/tmp/bpm_source_${PKGINFO[name]}"
if [ -d "$TEMPDIR" ] && [ -z "$KEEP" ]; then
rm -rf "$TEMPDIR"
fi
mkdir -p "$TEMPDIR"
mkdir -p "$TEMPDIR"/source
mkdir -p "$TEMPDIR"/output
tar -xf "$PACKAGE" -C "$TEMPDIR" source.sh
if tar -xf "$PACKAGE" -C "$TEMPDIR" source-files &> /dev/null; then
mv "$TEMPDIR"/source-files/* "$TEMPDIR"/
rm -d "$TEMPDIR"/source-files
fi
cd "$TEMPDIR"
# Declare and run Set Variables function
function SetVariables() {
export BPM_PKG_NAME="${PKGINFO[name]}"
export BPM_PKG_DESC="${PKGINFO[description]}"
export BPM_PKG_VERSION="${PKGINFO[version]}"
export BPM_PKG_URL="${PKGINFO[url]}"
export BPM_PKG_ARCH="${PKGINFO[architecture]}"
IFS=',' read -r -a BPM_PKG_DEPENDS <<< "${PKGINFO[depends]}"
IFS=',' read -r -a BPM_PKG_MAKE_DEPENDS <<< "${PKGINFO[make_depends]}"
export BPM_PKG_DEPENDS
export BPM_PKG_MAKE_DEPENDS
export BPM_WORKDIR="$TEMPDIR"
export BPM_SOURCE="$TEMPDIR"/source
export BPM_OUTPUT="$TEMPDIR"/output
}
SetVariables
# Declare Run Package Function function
function RunPkgFunction() {
if [ $# -eq 0 ]; then
echo "No function name given"
exit 1
fi
func="$1"
if [[ $(type -t "$func") == function ]]; then
echo "Running ${func}() function..."
bash -e -c "$func"
if [ $? -ne 0 ]; then
echo "Failed to run ${func}() function in source.sh"
exit 1
fi
fi
}
# Read source.sh file and source functions
set -a
source source.sh
set +a
cd "$BPM_WORKDIR"
RunPkgFunction prepare
cd "$BPM_SOURCE"
RunPkgFunction build
cd "$BPM_SOURCE"
if [ -z "$SKIPCHECK" ]; then
RunPkgFunction check
fi
# Packaging all packages
for func in $(typeset -F | awk '{print $3}' | grep '^package'); do
cd "$BPM_SOURCE"
if [[ "$func" == "package" ]]; then
pkgname="$BPM_PKG_NAME"
ReadPkgInfo
else
pkgname="${func##package-}"
ReadPkgInfo "$pkgname"
fi
SetVariables
echo "Running ${func}() function..."
touch "$TEMPDIR"/fakeroot_file_"$pkgname"
fakeroot -s "$TEMPDIR"/fakeroot_file_"$pkgname" bash -e -c "$func"
if [ $? -ne 0 ]; then
echo "Failed to run ${func}() function in source.sh"
exit 1
fi
cd "$BPM_WORKDIR"
touch pkg.info
if [[ "$pkgname" == "$BPM_PKG_NAME" ]]; then
echo "${PKGINFO_FILE}" > pkg.info
else
echo "${PKGINFO_FILE}" > pkg.info
fi
echo "${PKGINFO_FILE}" > pkg.info
sed -i "s/architecture:.*/architecture: ${ARCH}/g" pkg.info
sed -i 's/type:.*/type: binary/g' pkg.info
fakeroot -i "$TEMPDIR"/fakeroot_file_"$pkgname" tar -czpf "$pkgname".tar.gz pkg.info output --transform 's/output/files/'
mv "$pkgname".tar.gz "$DIR"/"$pkgname"-"$BPM_PKG_VERSION"-"$ARCH".bpm
echo "Packaged ${pkgname} successfully!"
rm "$TEMPDIR"/fakeroot_file_"$pkgname"
rm -r output/
mkdir output
rm pkg.info
done
if [ -z "$KEEP" ]; then
rm -rf "$TEMPDIR"
fi
-99
View File
@@ -1,99 +0,0 @@
#!/bin/bash
if [ $# -eq 0 ]
then
echo "No output package name given!"
exit 1
fi
if [ -f .compilation-options ]; then
source ./.compilation-options
fi
while getopts "cska:" flag; do
case "$flag" in
c) CONVERT=true;;
k) KEEP=true;;
s) SKIPCHECK=true;;
a) ARCH="${OPTARG}";;
*) exit 1;;
esac
done
output="${@:$OPTIND:1}"
if [[ ! "$output" =~ ^[a-z.A-Z0-9_-]{1,}$ ]]; then
echo "Invalid output name! The name must only contain letters, numbers, hyphens or underscores!"
exit 1
fi
type="binary"
toCompress=()
echo "Creating package with the name $output..."
if [ -d files ]; then
echo "files/ directory found"
toCompress+=("files/")
else
if [ -f source.sh ]; then
type="source"
echo "source.sh file found"
toCompress+=("source.sh")
if [ -f pre_update.sh ]; then
echo "pre_update.sh file found"
toCompress+=("pre_update.sh")
fi
if [ -f post_update.sh ]; then
echo "post_update.sh file found"
toCompress+=("post_update.sh")
fi
if [ -f pre_install.sh ]; then
echo "pre_install.sh file found"
toCompress+=("pre_install.sh")
fi
if [ -f post_install.sh ]; then
echo "post_install.sh file found"
toCompress+=("post_install.sh")
fi
if [ -f post_remove.sh ]; then
echo "post_remove.sh file found"
toCompress+=("post_remove.sh")
fi
if [ -d source-files ]; then
if [ ! -z "$(ls -A source-files)" ]; then
echo "source-files/ directory found"
toCompress+=("source-files/")
fi
fi
else
echo "files/ directory or source.sh file not found in $PWD"
exit 1
fi
fi
if [ -f pkg.info ]; then
echo "pkg.info file found"
else
echo "pkg.info file not found in $PWD"
exit 1
fi
for pkginfo in ./pkg.inf*; do
pkginfo=$(basename "$pkginfo")
echo "${pkginfo} file found"
toCompress+=("$pkginfo")
done
echo "Creating $type package as $output"
tar -c --owner=0 --group=0 --no-same-owner -zf "$output" "${toCompress[@]}"
if [ ! -z "$CONVERT" ] && "$CONVERT" && [[ "$type" == "source" ]]; then
args=()
if ! [ -z "$KEEP" ]; then
args+=("-a" "$ARCH")
fi
if ! [ -z "$SKIPCHECK" ]; then
args+=("-s")
fi
bpm-convert "${args[@]}" -a "$ARCH" "$output"
fi
-173
View File
@@ -1,173 +0,0 @@
#!/bin/bash
usage () {
echo "------BPM-Setup options------"
echo "bpm-setup -D <directory> | Path to package directory"
echo "bpm-setup -y | Skips confirmation prompt"
echo "bpm-setup -n <name> | Set the package name (Defaults to \"package-name\")"
echo "bpm-setup -d <description> | Set the package description (Defaults to \"Default package description\")"
echo "bpm-setup -v <version> | Set the package version (Defaults to \"1.0\")"
echo "bpm-setup -u <url> | Set the package URL (Optional)"
echo "bpm-setup -l <licenses> | Set the package licenses (Optional)"
echo "bpm-setup -t <binary/source> | Set the package type to binary or source (Defaults to binary)"
echo "bpm-setup -s <source template file> | Use a default template file (Defaults to /etc/bpm-utils/source.default)"
echo "bpm-setup -g <true/false> | Create git repository (Defaults to true)"
}
if [ $# -eq 0 ]; then
usage
exit
fi
NAME="package-name"
DESCRIPTION="Default package description"
VERSION="1.0"
#URL="https://my.project.url/ (Optional)"
#LICENSE="Your project's license (Optional)"
TYPE="binary"
SOURCE_FILE="/etc/bpm-utils/source.default"
GIT=true
while getopts "D:n:d:v:u:l:t:s:g:y" o; do
case "${o}" in
D)
DIRECTORY="${OPTARG}"
;;
y)
CONFIRM=yes
;;
n)
NAME="${OPTARG}"
;;
d)
DESCRIPTION="${OPTARG}"
;;
v)
VERSION="${OPTARG}"
;;
u)
URL="${OPTARG}"
;;
l)
LICENSE="${OPTARG}"
;;
t)
TYPE="${OPTARG}"
;;
s)
SOURCE_FILE="$(realpath ${OPTARG})"
;;
g)
if [[ "${OPTARG}" == true ]]; then
GIT=TRUE
elif [[ "${OPTARG}" == false ]]; then
GIT=false
else
echo "${OPTARG} is not a true or false value!"
exit 1
fi
;;
*)
usage
exit 1
;;
esac
done
if [ -z "${DIRECTORY}" ]; then
echo "Required directory argument missing. Try 'bpm-setup -D <directory> [other options...]"
exit 1
fi
if [[ ! "${DIRECTORY}" == "/"* ]]; then
DIRECTORY="${PWD}/${DIRECTORY}"
fi
if [ -e "${DIRECTORY}" ]; then
echo "This path already exists"
exit 1
fi
if [[ "$TYPE" != "binary" ]] && [[ "$TYPE" != "source" ]]; then
echo "Invalid package type! Package type must be either 'binary' or 'source'"
exit 1
fi
if [ -z "${CONFIRM}" ]; then
echo "Setting up package working directory at ${DIRECTORY} with the following information:"
echo "Package name: $NAME"
echo "Package description: $DESCRIPTION"
echo "Package version: $VERSION"
if [ -z "${URL}" ]; then
echo "Package URL: Not set"
else
echo "Package URL: $URL"
fi
if [ -z "${LICENSE}" ]; then
echo "Package license: Not set"
else
echo "Package license: $LICENSE"
fi
echo "Package type: $TYPE"
if [[ "$TYPE" == "source" ]]; then
echo "Create Git repository: $GIT"
fi
read -p "Create package directory? [y/N]: " CREATE
case $CREATE in
[Yy]* )
;;
*)
echo "Exiting bpm-setup..."
exit
;;
esac
fi
if ! mkdir -pv $DIRECTORY; then
echo "Could not create $DIRECTORY!"
exit 1
fi
cd $DIRECTORY
touch pkg.info
echo "name: ${NAME}" >> pkg.info
echo "description: ${DESCRIPTION}" >> pkg.info
echo "version: ${VERSION}" >> pkg.info
if [ ! -z "${URL}" ]; then echo "url: ${URL}" >> pkg.info; fi
if [ ! -z "${LICENSE}" ]; then echo "license: ${LICENSE}" >> pkg.info; fi
if [[ "$TYPE" == "binary" ]]; then
echo "architecture: $(uname -m)" >> pkg.info
echo "type: binary" >> pkg.info
mkdir -pv files
echo "Package directory created successfully!"
echo "Make sure to edit the pkg.info file with the appropriate information for your package"
echo "Add your binaries under the 'files' directory. For example a binary called 'my_binary' should go under files/usr/bin/my_binary"
echo "You can turn your package into a .bpm file use the 'bpm-create <name>' command"
else
echo "architecture: any" >> pkg.info
echo "type: source" >> pkg.info
mkdir -pv source-files
if [ -f "${SOURCE_FILE}" ]; then
touch source.temp
cat "${SOURCE_FILE}" > source.temp
export NAME DESCRIPTION VERSION URL LICENSE TYPE
envsubst '$NAME:$DESCRIPTION:$VERSION:$URL:$LICENSE:$TYPE' < source.temp > source.sh
rm source.temp
else
echo "Source file at ${SOURCE_FILE} does not exist! Creating empty source.sh instead..."
touch source.sh
fi
if $GIT ; then
git init
echo -e "*.bpm\n.gitignore" > .gitignore
fi
echo "Package directory created successfully!"
echo "Make sure to edit the pkg.info file with the appropriate information for your package"
echo "Add your compilation code in the source.sh file. Follow the instructions on the template file on how to properly create your compilation script"
echo "You can add additional files that will be used during compilation to the 'source-files' directory"
echo "You can turn your package into a .bpm file use the 'bpm-package <filename>' command"
fi
+1
View File
@@ -0,0 +1 @@
privilege_escalator_cmd: "sudo"
+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
}
+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() {
echo "You may remove this function if you do not intend to use it"
}
# The build function is executed in the source directory
# This function is used to compile the source code
build() {
cmake -B build -DCMAKE_INSTALL_PREFIX=/usr
cmake --build build
}
# 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() {
ctest --test-dir build
}
# The package function is executed in the source directory
# This function is used to move the compiled files into the output directory
package() {
DESTDIR="$BPM_OUTPUT" cmake --install build
# Install package license
install -Dm644 "$BPM_SOURCE"/LICENSE "$BPM_OUTPUT"/usr/share/licenses/$NAME/LICENSE
}
@@ -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
@@ -29,4 +25,7 @@ check() {
# 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() {
make DESTDIR="$BPM_OUTPUT" install make DESTDIR="$BPM_OUTPUT" install
# Install package license
install -Dm644 "$BPM_SOURCE"/LICENSE "$BPM_OUTPUT"/usr/share/licenses/$NAME/LICENSE
} }
+30
View File
@@ -0,0 +1,30 @@
# 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() {
echo "You may remove this function if you do not intend to use it"
}
# The build function is executed in the source directory
# This function is used to compile the source code
build() {
make PREFIX=/usr
}
# 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() {
make check
}
# The package function is executed in the source directory
# This function is used to move the compiled files into the output directory
package() {
make DESTDIR="$BPM_OUTPUT" PREFIX=/usr install
# Install package license
install -Dm644 "$BPM_SOURCE"/LICENSE "$BPM_OUTPUT"/usr/share/licenses/$NAME/LICENSE
}
+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() {
echo "You may remove this function if you do not intend to use it"
}
# The build function is executed in the source directory
# This function is used to compile the source code
build() {
meson setup build --prefix=/usr
meson compile -C build
}
# 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() {
meson test -C build --print-errorlogs
}
# The package function is executed in the source directory
# This function is used to move the compiled files into the output directory
package() {
meson install -C build --destdir="$BPM_OUTPUT"
# Install package license
install -Dm644 "$BPM_SOURCE"/LICENSE "$BPM_OUTPUT"/usr/share/licenses/$NAME/LICENSE
}
+30
View File
@@ -0,0 +1,30 @@
# 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() {
echo "You may remove this function if you do not intend to use it"
}
# The build function is executed in the source directory
# This function is used to compile the source code
build() {
python3 -m build --wheel --no-isolation .
}
# 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() {
# Python module testing command varies from module to module
}
# The package function is executed in the source directory
# This function is used to move the compiled files into the output directory
package() {
python3 -m installer --destdir="$BPM_OUTPUT" dist/*.whl
# Install package license
install -Dm644 "$BPM_SOURCE"/LICENSE "$BPM_OUTPUT"/usr/share/licenses/$NAME/LICENSE
}
+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
}
-72
View File
@@ -1,72 +0,0 @@
#!/bin/bash
containsElement () {
local e match="$1"
shift
for e; do [[ "$e" == "$match" ]] && return 0; done
return 1
}
SCRIPT_PATH=$(realpath "$0")
SCRIPT_DIR=$(dirname "$SCRIPT_PATH")
DEEP=false
PKGS=()
while getopts "d" flag; do
case "$flag" in
d) DEEP=true
shopt -s globstar;;
*) echo "Unknown flag ${flag}"
exit 2;;
esac
done
if [ -z "${@:$OPTIND:1}" ]; then
echo "You have not specified a repository"
exit 1
fi
if ! [ -d "${@:$OPTIND:1}" ]; then
echo "The given path is not a directory"
exit 1
fi
if realpath "${@:$OPTIND:1}" &> /dev/null; then
REPO=$(realpath "${@:$OPTIND:1}")
fi
shopt -s nullglob
if ! $DEEP; then
for arch in "$REPO"/*/; do
for pkg in "$arch"/*.bpm; do
info="info:"$'\n'
info+=$(tar -xf "$pkg" pkg.info -O | sed 's/^/ /g')
package=$(echo "$info" | grep 'name: ' | xargs | awk '{print $2}')
if containsElement "$package" "${PKGS[@]}"; then
echo "The following package was found in more than 1 package archives: ${package}"
exit 1
fi
PKGS+=("$package")
file="$(realpath -s --relative-to="$REPO" "$pkg")"
info+=$'\n'"download: ${file}"
info+=$'\n---'
echo "$info"
done
done
else
for arch in "$REPO"/*/; do
for pkg in "$arch"/**/*.bpm; do
info=$(tar -xf "$pkg" pkg.info -O)
package=$(echo "$info" | grep 'name: ' | awk '{print $2}')
if [[ ${PKGS[@]} =~ "$package" ]]; then
echo "The following package was found in more than 1 package archives: ${package}"
fi
PKGS+=("$package")
file="$(realpath -s --relative-to="$REPO" "$pkg")"
info+=$'\n'"file: ${file}"
info+=$'\n---'
echo "$info"
done
done
fi
+16
View File
@@ -0,0 +1,16 @@
module git.enumerated.dev/bubble-package-manager/bpm-utils/src/bpm-package
go 1.23
require (
bpm-utils-shared v1.0.0
github.com/spf13/pflag v1.0.10
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
+12
View File
@@ -0,0 +1,12 @@
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/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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+425
View File
@@ -0,0 +1,425 @@
package main
import (
bpmutilsshared "bpm-utils-shared"
"bytes"
"fmt"
"io"
"io/fs"
"log"
"maps"
"os"
"os/exec"
"path"
"path/filepath"
"slices"
"strconv"
"strings"
flag "github.com/spf13/pflag"
"gopkg.in/yaml.v3"
)
var compile = flag.BoolP("compile", "c", false, "Compile BPM source package")
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 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 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")
func main() {
// Setup flags and help
setupFlagsAndHelp("bpm-package <options>", "Generates source BPM package from current directory")
// Run checks
runChecks()
// Create BPM archive
outputFile := createArchive()
if *compile {
compilePackage(outputFile)
}
if repo := bpmutilsshared.GetRepository(); repo != "" {
bpmutilsshared.UpdateDatabases(repo)
}
}
func runChecks() {
// Check if info.yml file exists
if stat, err := os.Stat("info.yml"); err != nil || !stat.Mode().IsRegular() {
log.Fatalf("Error: info.yml does not exist or is not a regular file")
}
// Check if recipe.sh file exists
if stat, err := os.Stat("recipe.sh"); err != nil || !stat.Mode().IsRegular() {
log.Fatalf("Error: info.yml does not exist or is not a regular file")
}
}
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)
// Include base files
filesToInclude = append(filesToInclude, "info.yml", "recipe.sh")
// Check if non-empty source-files directory exists and include it
if stat, err := os.Stat("source-files"); err == nil && stat.IsDir() {
dir, err := os.ReadDir("source-files")
if err == nil && len(dir) != 0 {
fmt.Println("Non-empty 'source-files' directory found")
filesToInclude = append(filesToInclude, "source-files")
}
}
// 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"} {
if stat, err := os.Stat(script); err == nil && stat.Mode().IsRegular() {
fmt.Printf("Package script '%s' found\n", script)
filesToInclude = append(filesToInclude, script)
}
}
// Read info.yml file
pkgInfo, err := bpmutilsshared.ReadPacakgeInfoFromFile("info.yml")
if err != nil {
log.Fatalf("Error: could not read package info: %s", err)
}
// 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 {
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
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
cmd := exec.Command("tar", "cf", filename,
"--sort=name",
"--pax-option=exthdr.name=%d/PaxHeaders/%f,delete=atime,delete=ctime",
"--mtime=UTC 1970-01-01",
"--owner=0", "--group=0", "--numeric-owner")
cmd.Args = append(cmd.Args, filesToInclude...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
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
absFilepath, err := filepath.Abs(filename)
if err != nil {
log.Fatalf("Error: failed to get absolute path of BPM source archive: %s", err)
}
fmt.Printf("BPM source archive created at: %s\n", absFilepath)
return absFilepath
}
func compilePackage(archive string) {
// Setup compile command
args := make([]string, 0)
args = append(args, "compile")
addArg := func(condition bool, arg string) {
if condition {
args = append(args, arg)
}
}
addArg(*verbose, "-v")
addArg(*skipChecks, "--skip-checks")
addArg(*keepCompilationFiles, "-k")
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)
cmd := exec.Command("bpm", args...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
// Set output pipe for file descriptor 3
cmdOutputReader, cmdOutputWriter, err := os.Pipe()
if err != nil {
log.Fatalf("Error: failed to create pipe: %s", err)
}
defer cmdOutputReader.Close()
defer cmdOutputWriter.Close()
cmd.ExtraFiles = append(cmd.ExtraFiles, cmdOutputWriter)
// Run command
err = cmd.Start()
if err != nil {
log.Fatalf("Error: failed to compile BPM source package: %s", err)
}
// Wait for process to complete
err = cmd.Wait()
if err != nil {
log.Fatalf("Error: failed to compile BPM source package: %s", err)
}
// Read cmd output
cmdOutputWriter.Close()
cmdOutput, err := io.ReadAll(cmdOutputReader)
if err != nil {
log.Fatalf("Error: failed to get cmd output: %s", err)
}
// Put output file into slice
outputPkgs := make(map[string]string)
for _, line := range strings.Split(strings.TrimSpace(string(cmdOutput)), "\n") {
// Read generated package info
pkgInfo, err := bpmutilsshared.ReadPacakgeInfoFromTarball(line)
if err != nil {
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))
os.MkdirAll(path.Dir(newPath), 0755)
os.Rename(line, newPath)
outputPkgs[pkgInfo.Name] = newPath
} else {
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
for k, v := range outputPkgs {
fmt.Printf("Package (%s) was successfully compiled! Binary package generated at: %s\n", k, v)
}
// Install compiled packages
if *installPackage && len(outputPkgs) != 0 {
// Read BPM utils config
config, err := bpmutilsshared.ReadBPMUtilsConfig()
if err != nil {
log.Fatalf("Error: failed to read config: %s", err)
}
// Setup install command
args = make([]string, 0)
args = append(args, "bpm", "install", "--reinstall")
if *yesAll {
args = append(args, "-y")
}
args = append(args, slices.Collect(maps.Values(outputPkgs))...)
cmd = exec.Command(config.PrivilegeEscalatorCmd, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
// Run command
err = cmd.Run()
if err != nil {
log.Fatalf("Error: failed to install compiled BPM packages: %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()
}
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
}
+16
View File
@@ -0,0 +1,16 @@
module git.enumerated.dev/bubble-package-manager/bpm-utils/src/bpm-repo
go 1.23
require (
bpm-utils-shared v1.0.0
github.com/spf13/pflag v1.0.10
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
+12
View File
@@ -0,0 +1,12 @@
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/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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+741
View File
@@ -0,0 +1,741 @@
package main
import (
bpmutilsshared "bpm-utils-shared"
"bufio"
"bytes"
"context"
"fmt"
"io/fs"
"log"
"maps"
"os"
"os/exec"
"path"
"path/filepath"
"slices"
"sort"
"strconv"
"strings"
"time"
flag "github.com/spf13/pflag"
"gopkg.in/yaml.v3"
)
var currentFlagSet *flag.FlagSet
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
flagset := flag.NewFlagSet("create-repo", flag.ExitOnError)
setupFlagsAndHelp(flagset, fmt.Sprintf("bpm-repo %s <options>", subcommand), "Create a new BPM repository", os.Args[:1])
// Get current database
repo := bpmutilsshared.GetRepository()
if repo != "" {
log.Fatal("Error: this command cannot be run inside a BPM repository")
}
scanner := bufio.NewReader(os.Stdin)
fmt.Print("Repository name: ")
name, err := scanner.ReadString('\n')
if err != nil {
log.Fatalf("Error: could not read user input: %s", err)
}
fmt.Print("Repository description: ")
desc, err := scanner.ReadString('\n')
if err != nil {
log.Fatalf("Error: could not read user input: %s", err)
}
createRepository(strings.TrimSpace(name), strings.TrimSpace(desc))
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
repo := bpmutilsshared.GetRepository()
if repo == "" {
log.Fatal("Error: this command may only be run inside a BPM repository")
}
bpmutilsshared.UpdateDatabases(repo)
case "list", "l":
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)
}
}
func createRepository(name, description string) {
err := os.Mkdir(name, 0755)
if err != nil {
log.Fatalf("Error: could not create directory: %s", err)
}
contents := fmt.Sprintf("name: %s\ndescription: %s\n", name, description)
err = os.WriteFile(path.Join(name, "bpm-repo.conf"), []byte(contents), 0644)
if err != nil {
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!")
}
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)
}
+16
View File
@@ -0,0 +1,16 @@
module git.enumerated.dev/bubble-package-manager/bpm-utils/src/bpm-setup
go 1.23
require (
bpm-utils-shared v1.0.0
github.com/spf13/pflag v1.0.10
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
+12
View File
@@ -0,0 +1,12 @@
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/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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+229
View File
@@ -0,0 +1,229 @@
package main
import (
bpmutilsshared "bpm-utils-shared"
"bufio"
"bytes"
"fmt"
"io"
"log"
"os"
"os/exec"
"path"
"strings"
flag "github.com/spf13/pflag"
yaml "gopkg.in/yaml.v3"
)
var name = flag.StringP("name", "n", "", "Set the package name")
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 url = flag.StringP("url", "u", "", "Set the package URL")
var license = flag.StringP("license", "l", "", "Set the package licenses")
var template = flag.StringP("template", "t", "gnu-configure", "Set the package template")
var git = flag.BoolP("git", "g", true, "Create git repository")
var directory = ""
func main() {
// Setup flags and help
setupFlagsAndHelp("bpm-setup <options>", "Sets up files and directories for BPM source package creation")
// Trim package name
*name = strings.TrimSpace(*name)
// Show command help if no directory name is given
if *name == "" {
log.Println("Error: name flag is required")
flag.Usage()
os.Exit(1)
}
// Get current repository
repo := bpmutilsshared.GetRepository()
// 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
runChecks()
// Show summary
showSummary()
// Confirmation prompt
fmt.Printf("Create package directory? [y\\N]: ")
reader := bufio.NewReader(os.Stdin)
text, _ := reader.ReadString('\n')
if strings.TrimSpace(strings.ToLower(text)) != "y" && strings.TrimSpace(strings.ToLower(text)) != "yes" {
fmt.Println("Cancelling package directory creation...")
os.Exit(1)
}
// Create package directory
createDirectory()
}
func runChecks() {
// Check if package name is valid
if *name == "" {
log.Fatalf("Error: no package name was specified!")
}
// Check if parent directory is valid
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() {
log.Fatalf("Error: '%s' is not a valid template file!", *template)
}
}
func showSummary() {
fmt.Printf("Setting up package directory at %s with the following information:\n", directory)
fmt.Printf("Package name: %s\n", strings.TrimSpace(*name))
fmt.Printf("Package description: %s\n", *description)
fmt.Printf("Package version: %s\n", *version)
if url != nil && *url != "" {
fmt.Printf("Package URL: %s\n", *url)
} else {
fmt.Printf("Package URL: Not Set\n")
}
if license != nil && *license != "" {
fmt.Printf("Package license: %s\n", *license)
} else {
fmt.Printf("Package license: Not Set\n")
}
fmt.Printf("Template file: %s\n", *template)
fmt.Printf("Create git repository: %t\n", *git)
}
func replaceVariables(templateContents string) string {
templateContents = strings.ReplaceAll(templateContents, "$NAME", *name)
templateContents = strings.ReplaceAll(templateContents, "$DESCRIPTION", *description)
templateContents = strings.ReplaceAll(templateContents, "$VERSION", *version)
templateContents = strings.ReplaceAll(templateContents, "$URL", *url)
return templateContents
}
func createDirectory() {
// Read BPM utils config
config, err := bpmutilsshared.ReadBPMUtilsConfig()
if err != nil {
log.Fatalf("Error: failed to read config: %s", err)
}
// Create directory
err = os.Mkdir(directory, 0755)
if err != nil {
log.Fatalf("Error: could not create directory: %s", err)
}
// Create package info struct
pkgInfo := bpmutilsshared.PackageInfo{
Name: *name,
Description: *description,
Version: *version,
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 config.DefaultMaintainer != "" {
pkgInfo.Maintainers = append(pkgInfo.Maintainers, config.DefaultMaintainer)
}
var buffer bytes.Buffer
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 {
log.Fatalf("Could not write to info.yml: %s", err)
}
// Create source-files directory
err = os.Mkdir(path.Join(directory, "source-files"), 0755)
if err != nil {
log.Fatalf("Error: could not create directory: %s", err)
}
// Copy source template file
input, err := os.ReadFile(path.Join("/etc/bpm-utils/templates", *template))
if err != nil {
log.Fatalf("Error: could not read template file: %s", err)
return
}
// Replace variables in template file
input = []byte(replaceVariables(string(input)))
err = os.WriteFile(path.Join(directory, "recipe.sh"), input, 0644)
if err != nil {
log.Fatalf("Error: could not write to source file: %s", err)
return
}
// Create git repository
if git != nil && *git {
err = exec.Command("git", "init", directory).Run()
if err != nil {
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()
}
+140
View File
@@ -0,0 +1,140 @@
package bpm_utils_shared
import (
"fmt"
"io/fs"
"log"
"os"
"os/exec"
"path"
"path/filepath"
"strconv"
"strings"
"gopkg.in/yaml.v3"
)
type BPMDatabase struct {
DatabaseVersion int `yaml:"database_version"`
Entries map[string]BPMDatabaseEntry `yaml:"entries"`
}
type BPMDatabaseEntry struct {
PackageInfo *PackageInfo `yaml:"info"`
Filepath string `yaml:"filepath"`
DownloadSize int64 `yaml:"download_size"`
InstalledSize int64 `yaml:"installed_size"`
}
func ReadDatabase(path string) (*BPMDatabase, error) {
// Read database file
output, err := os.ReadFile(path)
if err != nil {
return nil, err
}
database := &BPMDatabase{}
// Unmarshal yaml
err = yaml.Unmarshal(output, &database)
if err != nil {
return nil, err
}
return database, nil
}
func GenerateDatabase(path string) error {
database := BPMDatabase{
DatabaseVersion: 2,
Entries: make(map[string]BPMDatabaseEntry),
}
err := filepath.Walk(path, func(packagePath string, info fs.FileInfo, err error) error {
if !strings.HasSuffix(packagePath, ".bpm") {
return nil
}
// Initialize database entry
entry := BPMDatabaseEntry{}
entry.DownloadSize = info.Size()
entry.InstalledSize = 0
entry.Filepath, err = filepath.Rel(path, packagePath)
if err != nil {
return err
}
// Get package info
entry.PackageInfo, err = ReadPacakgeInfoFromTarball(packagePath)
if err != nil {
return err
}
// Get package installed size
if entry.PackageInfo.Type == "binary" {
cmd := exec.Command("tar", "xf", packagePath, "files.txt", "-O")
output, err := cmd.Output()
if err != nil {
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
if _, ok := database.Entries[entry.PackageInfo.Name]; ok {
return fmt.Errorf("package (%s) has already been added to the database", entry.PackageInfo.Name)
}
database.Entries[entry.PackageInfo.Name] = entry
return nil
})
if err != nil {
return err
}
data, err := yaml.Marshal(&database)
if err != nil {
return err
}
err = os.WriteFile(filepath.Join(path, "database.bpmdb"), data, 0644)
if err != nil {
return err
}
return nil
}
func UpdateDatabases(repo string) {
if _, err := os.Stat(path.Join(repo, "source")); err == nil {
err = GenerateDatabase(path.Join(repo, "source"))
if err != nil {
log.Fatalf("Error: could not generate source directory database: %s", err)
}
fmt.Println("Source directory database was generated successfully!")
}
if _, err := os.Stat(path.Join(repo, "binary")); err == nil {
err = GenerateDatabase(path.Join(repo, "binary"))
if err != nil {
log.Fatalf("Error: could not generate binary directory database: %s", err)
}
fmt.Println("Binary directory database was generated successfully!")
}
}
+9
View File
@@ -0,0 +1,9 @@
module bpm-utils-shared
go 1.23
require 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
+10
View File
@@ -0,0 +1,10 @@
github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g=
github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f h1:xt29M2T6STgldg+WEP51gGePQCsQvklmP2eIhPIBK3g=
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f/go.mod h1:i4sF0l1fFnY1aiw08QQSwVAFxHEm311Me3WsU/X7nL0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+28
View File
@@ -0,0 +1,28 @@
package bpm_utils_shared
import (
"os"
"gopkg.in/yaml.v3"
)
type BPMUtilsConfig struct {
PrivilegeEscalatorCmd string `yaml:"privilege_escalator_cmd"`
DefaultMaintainer string `yaml:"default_maintainer,omitempty"`
AddDefaultMaintainer bool `yaml:"add_default_maintainer,omitempty"`
}
func ReadBPMUtilsConfig() (*BPMUtilsConfig, error) {
data, err := os.ReadFile("/etc/bpm-utils/bpm-utils.conf")
if err != nil {
return nil, err
}
config := &BPMUtilsConfig{}
err = yaml.Unmarshal(data, config)
if err != nil {
return nil, err
}
return config, nil
}
+265
View File
@@ -0,0 +1,265 @@
package bpm_utils_shared
import (
"fmt"
"os"
"os/exec"
"strconv"
"strings"
"github.com/drone/envsubst"
version "github.com/knqyf263/go-rpm-version"
"gopkg.in/yaml.v3"
)
type PackageInfo struct {
Name string `yaml:"name"`
Description string `yaml:"description,omitempty"`
Version string `yaml:"version,omitempty"`
Revision int `yaml:"revision,omitempty"`
Url string `yaml:"url,omitempty"`
License string `yaml:"license,omitempty"`
Maintainers []string `yaml:"maintainers,omitempty"`
Arch string `yaml:"architecture,omitempty"`
OutputArch string `yaml:"output_architecture,omitempty"`
Type string `yaml:"type,omitempty"`
Keep []string `yaml:"keep,omitempty"`
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"`
Conflicts []string `yaml:"conflicts,omitempty"`
Replaces []string `yaml:"replaces,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"`
}
type PackageFlag struct {
Name string `yaml:"name"`
DefaultValue string `yaml:"default_value"`
BuiltValue string `yaml:"built_value"`
AcceptedValues []PackageAcceptedValue `yaml:"accepted_values,omitempty"`
}
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{
Revision: 1,
Keep: make([]string, 0),
Depends: make([]string, 0),
MakeDepends: make([]string, 0),
RuntimeDepends: make([]string, 0),
OptionalDepends: make([]string, 0),
Conflicts: make([]string, 0),
Replaces: make([]string, 0),
Provides: make([]string, 0),
Options: make([]string, 0),
Downloads: make([]PackageDownload, 0),
SplitPackages: make([]*PackageInfo, 0),
}
// Unmarshal yaml
err := yaml.Unmarshal(data, pkgInfo)
if err != nil {
return nil, err
}
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
}
}
+54
View File
@@ -0,0 +1,54 @@
package bpm_utils_shared
import (
"os"
"path"
"sort"
)
func GetRepository() string {
dir, err := os.Getwd()
if err != nil {
return ""
}
for dir != "/" {
if _, err := os.Stat(path.Join(dir, "bpm-repo.conf")); err == nil {
return dir
}
dir = path.Dir(dir)
}
if dir == "/" {
return ""
}
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
}