mirror of
https://github.com/EnumeratedDev/bpm-utils.git
synced 2026-09-16 11:06:11 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
66e1aacc5f
|
||
|
|
8034c7d7ea
|
||
|
|
d781c30146
|
||
|
|
8eb771111c
|
||
|
|
3f84dd4bc3
|
||
|
|
e1daa498e8
|
||
|
|
68ffb7972f
|
||
|
|
9114bb4148
|
||
|
|
5dbdfdd4c1
|
||
|
|
eb13b2653d
|
||
|
|
581c864f1d
|
||
|
|
7e7b8487ea
|
||
|
|
a7f751c094
|
||
|
|
54e192f220
|
||
|
|
c508ce6a01
|
||
|
|
2b81dad6cf
|
||
|
|
82777ebac1
|
||
|
|
b20766bddb
|
||
|
|
0ff9eb9cf2
|
||
|
|
4371b7f0e3
|
||
|
|
65849fdf86
|
||
|
|
a64ad39ca7
|
||
|
|
bf9874af6b
|
||
|
|
07a9e8c9d2
|
||
|
|
598e6710f4
|
||
|
|
902fcbf296
|
||
|
|
03bf97b8d8
|
||
|
|
5240ae4a8c
|
||
|
|
9d03b5b423
|
||
|
|
f17bc777d9
|
||
|
|
154f546b7f
|
||
|
|
c4a0e9f860
|
||
|
|
b9d7dfd9d0
|
||
|
|
4217626dde
|
||
|
|
d7da13034c | ||
|
|
cdb044d751
|
||
|
|
60b0f4489e
|
||
|
|
65c32d628f
|
||
|
|
4ff4a74fa3
|
||
|
|
f6ef60aa40
|
||
|
|
9625b5d7af
|
||
|
|
d881ac31c3
|
||
|
|
96a39a6b40
|
||
|
|
7a63ad6388
|
||
|
|
10f918034a
|
||
|
|
59c72d1a40
|
||
|
|
61c01ae88f | ||
|
|
0fdef74c73 | ||
|
|
d5abad3371 | ||
|
|
334eb15c94 | ||
|
|
7aff954df4 | ||
|
|
2197f78bc0 | ||
|
|
1b6585424f | ||
|
|
c9627e1068 | ||
|
|
aae5299b8a | ||
|
|
908b9ccc12 | ||
|
|
162f76b697 | ||
|
|
f656006710 | ||
|
|
1d43614fde | ||
|
|
ad691cd78e | ||
|
|
3cfb314333 | ||
|
|
3b5c8de115 | ||
|
|
3dbb24fd15 | ||
|
|
5a7995d226 | ||
|
|
a6578fb414 | ||
|
|
354d284289 | ||
|
|
4235252172 | ||
|
|
2dec7c28e6 | ||
|
|
368c599ea6 | ||
|
|
e147d74137 | ||
|
|
d395004658 | ||
|
|
a0aff54e95 | ||
|
|
e021e814f4 | ||
|
|
57a58a18a7 | ||
|
|
23b5f0eee1 | ||
|
|
c60bec2c1a | ||
|
|
e02d64b67e | ||
|
|
5076c48001 | ||
|
|
7df29661c3 |
@@ -0,0 +1,5 @@
|
||||
# IDE Directories
|
||||
.idea
|
||||
|
||||
# Build directory
|
||||
build
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 CapCreeperGR
|
||||
Copyright (c) 2024 EnumDev
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -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
|
||||
@@ -1,56 +1,70 @@
|
||||
|
||||
# BPM Utils
|
||||
### _Package creation and editing utilities for BPM_ ###
|
||||
### _Creation and maintenance utilities for BPM packages and repositories_ ###
|
||||
|
||||
## 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 Scripts
|
||||
- bpm-setup (Creates a directory with the required files for a BPM package)
|
||||
- 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)
|
||||
## Provided utilities
|
||||
- bpm-setup (Sets up directories for BPM source package creation)
|
||||
- bpm-repo (Allows for easy management of multiple-package repositories)
|
||||
- bpm-package (Turns a BPM source package directory into a .bpm archive)
|
||||
|
||||
## Installation
|
||||
|
||||
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
|
||||
#### Using a package manager
|
||||
- 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
|
||||
|
||||
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_package -t <binary/source>
|
||||
```
|
||||
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
|
||||
bpm-setup -D my_package
|
||||
```
|
||||
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
|
||||
description: My package's description
|
||||
version: 1.0
|
||||
revision: 2 (Optional)
|
||||
architecture: x86_64
|
||||
url: https://www.my-website.com/ (Optional)
|
||||
license: MyLicense (Optional)
|
||||
type: <binary/source>
|
||||
depends: ["dependency1","dependency2"] (Optional)
|
||||
optional_depends: ["optional_depend1","optional_depend2"]
|
||||
make_depends: ["make_depend1","make_depend2"] (Optional)
|
||||
architecture: x86_64
|
||||
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
|
||||
```
|
||||
### 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)
|
||||
6) Run the following to create a package archive
|
||||
|
||||
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 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!
|
||||
### 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!
|
||||
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
|
||||
|
||||
-214
@@ -1,214 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ -f .compilation-options ]; then
|
||||
source ./.compilation-options
|
||||
fi
|
||||
|
||||
echo "$ARCH"
|
||||
|
||||
while getopts "ksfa:" o; do
|
||||
case "${o}" in
|
||||
a) ARCH="$OPTARG";;
|
||||
k) KEEP=true;;
|
||||
s) SKIPCHECK=true;;
|
||||
f) FORCE=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
|
||||
|
||||
if [ -z "$FORCE" ] && command -v bpm &> /dev/null && [ -n "$PKGINFO[depends]" ]; then
|
||||
MISSING=()
|
||||
for depend in $(echo "${PKGINFO[depends]}" | tr -d '[]' | tr ',' '\n' ); do
|
||||
if ! bpm info "$depend" &> /dev/null; then
|
||||
MISSING+=("$depend")
|
||||
fi
|
||||
done
|
||||
if [ "${#MISSING[@]}" -ne 0 ]; then
|
||||
echo "The following dependencies could not be resolved: ${MISSING[@]}"
|
||||
EXIT=true
|
||||
fi
|
||||
elif ! command -v bpm &> /dev/null; then
|
||||
echo "BPM not in PATH. Skipping dependency resolution"
|
||||
elif [ -n "$FORCE" ]; then
|
||||
echo "Force compilation enabled. Skipping dependency resolution"
|
||||
fi
|
||||
|
||||
if [ -z "$FORCE" ] && command -v bpm &> /dev/null && [ -n "$PKGINFO[make_depends]" ]; then
|
||||
MISSING=()
|
||||
for depend in $(echo "${PKGINFO[make_depends]}" | tr -d '[]' | tr ',' '\n' ); do
|
||||
if ! bpm info "$depend" &> /dev/null; then
|
||||
MISSING+=("$depend")
|
||||
fi
|
||||
done
|
||||
if [ "${#MISSING[@]}" -ne 0 ]; then
|
||||
echo "The following make dependencies could not be resolved: ${MISSING[@]}"
|
||||
EXIT=true
|
||||
fi
|
||||
elif ! command -v bpm &> /dev/null; then
|
||||
echo "BPM not in PATH. Skipping make dependency resolution"
|
||||
elif [ -n "$FORCE" ]; then
|
||||
echo "Force compilation enabled. Skipping make dependency resolution"
|
||||
fi
|
||||
|
||||
if [ -n "$EXIT" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 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
|
||||
[ -d "$TEMPDIR"/output ] && rm -rf "$TEMPDIR"/output
|
||||
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
|
||||
PACKAGE_SCRIPTS=()
|
||||
while read line; do
|
||||
PACKAGE_SCRIPTS+=("$line")
|
||||
done < <(tar -tf "$PACKAGE" | grep -v -E 'source.sh|pkg.info|source-files')
|
||||
tar -xf "$PACKAGE" -C "$TEMPDIR" ${PACKAGE_SCRIPTS[@]} &> /dev/null
|
||||
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 ${PACKAGE_SCRIPTS[@]} 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
@@ -1,99 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ -f .compilation-options ]; then
|
||||
source ./.compilation-options
|
||||
fi
|
||||
|
||||
while getopts "cskfa:" flag; do
|
||||
case "$flag" in
|
||||
c) CONVERT=true;;
|
||||
k) KEEP=true;;
|
||||
s) SKIPCHECK=true;;
|
||||
f) FORCE=true;;
|
||||
a) ARCH="${OPTARG}";;
|
||||
*) exit 1;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -f pkg.info ]; then
|
||||
echo "pkg.info file found"
|
||||
else
|
||||
echo "pkg.info file not found in $PWD"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
output="${@:$OPTIND:1}"
|
||||
if [ -z "$output" ]; then
|
||||
pkgname=$(grep "^name: " pkg.info)
|
||||
output=$(echo "$pkgname" | cut -d' ' -f2)"-src.bpm"
|
||||
fi
|
||||
if [ -z "$output" ] || [[ ! "$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 pre_remove.sh ]; then
|
||||
echo "pre_remove.sh file found"
|
||||
toCompress+=("pre_remove.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
|
||||
|
||||
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=()
|
||||
[ -n "$KEEP" ] && args+=("-k")
|
||||
[ -n "$SKIPCHECK" ] && args+=("-s")
|
||||
[ -n "$FORCE" ] && args+=("-f")
|
||||
bpm-convert "${args[@]}" -a "$ARCH" "$output"
|
||||
fi
|
||||
@@ -1,176 +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
|
||||
|
||||
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 [ -z "${NAME}" ]; then
|
||||
NAME="${DIRECTORY}"
|
||||
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
|
||||
@@ -0,0 +1 @@
|
||||
privilege_escalator_cmd: "sudo"
|
||||
@@ -0,0 +1,3 @@
|
||||
# Ignore BPM archives and signatures
|
||||
*.bpm
|
||||
*.bpm.sig
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 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
|
||||
# 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() {
|
||||
wget "$DOWNLOAD"
|
||||
tar -xvf "$FILENAME" --strip-components=1 -C "$BPM_SOURCE"
|
||||
echo "You may remove this function if you do not intend to use it"
|
||||
}
|
||||
|
||||
# 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
|
||||
package() {
|
||||
make DESTDIR="$BPM_OUTPUT" install
|
||||
|
||||
# Install package license
|
||||
install -Dm644 "$BPM_SOURCE"/LICENSE "$BPM_OUTPUT"/usr/share/licenses/$NAME/LICENSE
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,74 +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
|
||||
|
||||
create_entry() {
|
||||
if [ $# -eq 0 ]; then
|
||||
return 0
|
||||
fi
|
||||
if ! [ -f "$1" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
if ! $DEEP; then
|
||||
for arch in "$REPO"/*/; do
|
||||
for pkg in "$arch"/*.bpm; do
|
||||
create_entry "$pkg"
|
||||
done
|
||||
done
|
||||
else
|
||||
for arch in "$REPO"/*/; do
|
||||
for pkg in "$arch"/**/*.bpm; do
|
||||
create_entry "$pkg"
|
||||
done
|
||||
done
|
||||
fi
|
||||
@@ -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
|
||||
@@ -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=
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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=
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
@@ -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=
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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!")
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,10 @@
|
||||
github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g=
|
||||
github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g=
|
||||
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f h1:xt29M2T6STgldg+WEP51gGePQCsQvklmP2eIhPIBK3g=
|
||||
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f/go.mod h1:i4sF0l1fFnY1aiw08QQSwVAFxHEm311Me3WsU/X7nL0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
@@ -0,0 +1,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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user