summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoseph Hunkeler <jhunkeler@gmail.com>2019-07-17 15:16:47 -0400
committerJoseph Hunkeler <jhunkeler@gmail.com>2019-07-17 15:16:47 -0400
commit4d96198479e427788656c2d4d6e586908f24d1c6 (patch)
treeefe37c025160642e96c8e4946fb828aed2512d0f
downloadsfpm-4d96198479e427788656c2d4d6e586908f24d1c6.tar.gz
Initial commit
-rwxr-xr-xbin/sfpm62
-rwxr-xr-xbin/sfpm_makepkg78
-rw-r--r--share/sfpm/build.sh347
-rw-r--r--share/sfpm/common.sh17
-rw-r--r--share/sfpm/download.sh109
-rw-r--r--share/sfpm/env.sh233
-rw-r--r--share/sfpm/index.sh129
-rw-r--r--share/sfpm/msg.sh29
-rw-r--r--share/sfpm/pkg_read.sh37
-rw-r--r--share/sfpm/sfpm.sh88
10 files changed, 1129 insertions, 0 deletions
diff --git a/bin/sfpm b/bin/sfpm
new file mode 100755
index 0000000..36bebbf
--- /dev/null
+++ b/bin/sfpm
@@ -0,0 +1,62 @@
+#!/usr/bin/env bash
+source "$(dirname ${BASH_SOURCE[0]})/../share/sfpm/common.sh"
+sfpm_gen_sysroot
+
+# ----
+env_name=
+mode_install=0
+mode_search=0
+mode_update=0
+mode_verify=0
+packages=()
+
+while (( "${#}" )); do
+ case "${1}" in
+ -e|--env)
+ env_name="${2}"
+ shift 2
+ ;;
+ -i|--install)
+ mode_install=1
+ shift
+ ;;
+ -s|--search)
+ mode_search=1
+ shift
+ ;;
+ -u|--update|--upgrade)
+ mode_update=1
+ shift
+ ;;
+ -v|--verify)
+ mode_verify=1
+ shift
+ ;;
+ --)
+ shift
+ break
+ ;;
+ -*|--*)
+ msg_error "Invalid argument: ${1}" >&2
+ exit 1
+ ;;
+ *)
+ packages+=("${2}")
+ shift
+ ;;
+ esac
+done
+
+set -- "${packages[@]}"
+
+if [[ -z ${env_name} ]] && [[ ${mode_install} == 0 ]]; then
+ msg_error "No environment specified. (-e {name}, required)"
+ exit 1
+fi
+
+sfpm_index_create
+sfpm_env_create "${env_name}"
+sfpm_install "${env_name}" $(sfpm_index_search nasm) # "nasm" "2.14.02" "1"
+sfpm_install "${env_name}" $(sfpm_index_search zlib)
+sfpm_install "${env_name}" $(sfpm_index_search expat) # "expat" "2.2.6" "1"
+sfpm_install "${env_name}" $(sfpm_index_search python) # "python" "3.7.2" "1"
diff --git a/bin/sfpm_makepkg b/bin/sfpm_makepkg
new file mode 100755
index 0000000..ff6d2be
--- /dev/null
+++ b/bin/sfpm_makepkg
@@ -0,0 +1,78 @@
+#!/usr/bin/env bash
+
+# Deny execution as root user
+uid=$(id -u)
+username=$(whoami)
+if [[ ${uid} == 0 ]] || [[ ${username} == root ]]; then
+ msg_error "$(basename ${0}) should be executed by a regular user, not with root."
+ exit 1
+fi
+
+unset uid
+unset username
+
+export SFPM_MAKEPKG_ENV=$(basename $(mktemp -u sfpm.makepkg.XXXXXX))
+export sfpm_build_scriptdir=$(pwd)
+
+source "$(dirname ${BASH_SOURCE[0]})/../share/sfpm/common.sh"
+source "${sfpm_internal_include}/build.sh"
+
+# --- Functions
+
+cleanup() {
+ if [[ ${buildroot} == *${sfpm_tmpdir}* ]]; then
+ msg "Removing ${buildroot}"
+ rm -rf "${buildroot}"
+ fi
+
+ if [[ $(sfpm_env_exists ${SFPM_MAKEPKG_ENV}) ]]; then
+ deactivate
+ sfpm_env_remove "${SFPM_MAKEPKG_ENV}"
+ fi
+}
+trap cleanup EXIT SIGINT SIGTERM
+
+# --- Main
+
+# Assimilate build definition
+msg "Sourcing build script"
+source ${1}
+
+# Sanity checks
+msg "Validating build script"
+sfpm_check_required_keys
+
+# Create buildroot
+buildroot=$(sfpm_gen_buildroot)
+root="${buildroot}/root"
+pkgdir="${buildroot}/pkg"
+srcdir="${buildroot}/src"
+
+mkdir -p "${root}" "${pkgdir}" "${srcdir}"
+
+# Generate temporary sfpm build environment
+sfpm_env_create "${SFPM_MAKEPKG_ENV}"
+
+# Activate temporary sfpm build environment (provides ${SFPM_ENV})
+source ${sfpm_envdir}/${SFPM_MAKEPKG_ENV}/bin/activate
+
+# Setup compiler/linker flags
+sfpm_build_cflags="-I${SFPM_ENV}/include ${sfpm_build_cflags}"
+sfpm_build_ldflags="-L${SFPM_ENV}/lib ${sfpm_build_ldflags}"
+
+# Ensure local dependencies resolve during build
+export LD_LIBRARY_PATH="${SFPM_ENV}/lib"
+
+msg "Executing tasks"
+pushd "${root}"
+ sfpm_build_env_do_depends "${SFPM_MAKEPKG_ENV}"
+ sources_fetch
+ sfpm_sources_cmp_sha256
+ sources_extract "${srcdir}"
+ prepare
+ build
+ check
+ package
+ sfpm_gen_packages
+popd
+
diff --git a/share/sfpm/build.sh b/share/sfpm/build.sh
new file mode 100644
index 0000000..9f76d8e
--- /dev/null
+++ b/share/sfpm/build.sh
@@ -0,0 +1,347 @@
+required_keys=(
+ name
+ version
+ release
+)
+
+function sfpm_CPUS() {
+ local count=$(getconf _NPROCESSORS_ONLN)
+
+ ((count--))
+ if (( ${count} <= 0 )); then
+ count=1
+ fi
+
+ echo ${count}
+}
+
+function sfpm_gen_srcdir() {
+ local path="${sfpm_srcdir}/${name}-${version}-${release}"
+ if [[ ! -d ${path} ]]; then
+ mkdir -p ${path}
+ fi
+ echo ${path}
+}
+
+
+function sfpm_rm_srcdir() {
+ local path="${sfpm_srcdir}/${name}-${version}-${release}"
+ if [[ -d ${path} ]]; then
+ rm -rf "${path}"
+ fi
+}
+
+
+function sfpm_check_required_keys() {
+ die=0
+ for key in "${required_keys[@]}"
+ do
+ if [[ ! ${!key} ]]; then
+ msg_error "Package '${key}' undefined!"
+ die=1
+ fi
+ done
+
+ if (( ${die} )); then
+ exit 1
+ fi
+}
+
+
+function sfpm_cmp_sha256() {
+ # is a FILE
+ local sum_file="${1}"
+ if [[ ! -f ${sum_file} ]]; then
+ msg_error "${sum_file} is not a file"
+ exit 1
+ fi
+
+ # sha256 hashes
+ local sum_left=$(sha256sum ${sum_file} | awk '{ print $1 }')
+ local sum_right="${2}"
+
+ # compare hashes
+ if [[ ${sum_left} != ${sum_right} ]]; then
+ return 1
+ fi
+
+ return 0
+}
+
+function sfpm_verify_gpg() {
+ msg_warn "sfpm_verify_gpg(): Not implemented"
+}
+
+function prepare() {
+ msg_warn "prepare() function undefined"
+}
+
+
+function build() {
+ msg_warn "build() function undefined"
+}
+
+function check() {
+ msg_warn "check() function undefined"
+}
+
+function package() {
+ msg_error "package() function undefined"
+ exit 1
+}
+
+
+function sources_fetch() {
+ local path=$(sfpm_gen_srcdir)
+ for src in "${sources[@]}"
+ do
+ fetch --checksum --skip-exists --redirect --output ${path} ${src}
+ done
+}
+
+
+function sources_extract() {
+ local destdir="${1}"
+ if [[ ! ${destdir} ]]; then
+ msg_error "sources_extract() destination undefined: ${destdir}"
+ exit 1
+ fi
+
+ if [[ ! -d ${destdir} ]]; then
+ mkdir -p "${destdir}"
+ fi
+
+ local srcdir="$(sfpm_gen_srcdir)"
+ if [[ ! -d ${srcdir} ]]; then
+ msg_error "${srcdir} does not exist!"
+ exit 1
+ fi
+
+ archives_tar=$(find ${srcdir} -maxdepth 1 -type f \( -name "*.tar*" -o -name "*.t*" \) -and -not -name "*.sha256")
+ archives_zip=$(find ${srcdir} -maxdepth 1 -type f \( -name "*.zip" \) -and -not -name "*.sha256")
+
+ msg2 "Extracting"
+ pushd "${srcdir}"
+ if [[ ${archives_tar} ]]; then
+ for archive in ${archives_tar}
+ do
+ msg3 "${archive}"
+ tar xf "${archive}" -C "${destdir}"
+ done
+ fi
+
+ if [[ ${archives_zip} ]]; then
+ for archive in ${archives_zip}
+ do
+ msg3 "${archive}"
+ unzip "${archive}" -d "${destdir}"
+ done
+ fi
+ popd
+}
+
+
+function sfpm_sources_cmp_sha256() {
+ source_count=${#sources[@]}
+ sha256_count=${#sha256sums[@]}
+
+ if (( ! ${sha256_count} )); then
+ return 0
+ fi
+
+ msg2 "Comparing sha256 checksums"
+ if [[ ${source_count} != ${sha256_count} ]]; then
+ msg_error "Total sources (${source_count}) does not match total of hashes (${sha256_count})" \
+ "HINT: Place 'null' for each source without a hash."
+ exit 1
+ fi
+
+ for url in "${sources[@]}"
+ do
+ for sha in "${sha256sums[@]}"
+ do
+ if [[ ${sha} == null ]] || [[ ${sha} == NULL ]]; then
+ continue
+ fi
+
+ archive="${sfpm_srcdir}/${name}-${version}-${release}/$(basename ${url})"
+ sfpm_cmp_sha256 ${archive} ${sha}
+ if (( ${?} )); then
+ msg_error "${sha} does not match $(basename ${archive})"
+ exit 1
+ fi
+ done
+ done
+}
+
+function sfpm_gen_buildroot() {
+ export buildroot=$(mktemp -d ${TMPDIR}/sfpm.buildroot.XXXXXX)
+ if [[ ! -d ${buildroot} ]]; then
+ msg_error "Failed to create buildroot: ${buildroot}"
+ exit 1
+ fi
+
+ echo ${buildroot}
+}
+
+
+function sfpm_rm_buildroot() {
+ if [[ ${buildroot} == *${sfpm_tmpdir}* ]]; then
+ msg "Removing ${buildroot}"
+ rm -rf "${buildroot}"
+ fi
+}
+
+function sfpm_build_env_do_depends() {
+ local env_name="${1}"
+ if [[ ${depends} ]]; then
+ for dep in "${depends[@]}"
+ do
+ local pkg=$(sfpm_index_search ${dep})
+ sfpm_install "${env_name}" "${pkg}"
+ done
+ fi
+}
+
+function sfpm_gen_package_manifest() {
+ pushd "${pkgdir}"
+ find . -type f -not -name ".SFPM-*" | xargs -I'{}' sha256sum "{}" > .SFPM-MANIFEST
+ popd
+}
+
+
+# Don't use this. Getting rid of it.
+function sfpm_gen_package_sizes() {
+ pushd "${pkgdir}"
+ find . -type f -not -name ".SFPM-*" | xargs -I'{}' -n1 du -b "{}" > .SFPM-SIZES
+ popd
+}
+
+function sfpm_gen_package_depends_manifest() {
+ pushd "${pkgdir}"
+ >.SFPM-DEPENDS
+ for dep in "${depends[@]}"
+ do
+ echo "${dep}" >> .SFPM-DEPENDS
+ done
+ popd
+}
+
+function sfpm_gen_package_rpath() {
+ pushd "${pkgdir}"
+ local rpath_orig
+ local rpath_new
+ local rpath_cache="$(mktemp ${TMPDIR}/sfpm.rpath_cache.XXXXXX)"
+
+ # Assimilate all file paths that contain an RPATH
+ for path in $(find . -type f -not -name '.SFPM-*')
+ do
+ readelf -d "${path}" 2>/dev/null | grep RPATH &>/dev/null
+ if (( $? )); then
+ continue
+ fi
+ echo "${path}" >> "${rpath_cache}"
+ done
+
+ msg2 "Adjusting depth of RPATHs"
+ while read line
+ do
+ rpath_orig="$(readelf -d ${line} | grep RPATH | awk -F'[][]' '{ print $2 }')"
+ rpath_new='$ORIGIN/'"$(sfpm_rpath_nearest ${line})"
+ msg3 "${line}: ${rpath_orig} -> ${rpath_new}"
+ patchelf --set-rpath "${rpath_new}" "${line}"
+ done < "${rpath_cache}"
+ [[ -f "${rpath_cache}" ]] && rm -f "${rpath_cache}"
+ popd
+}
+
+function sfpm_gen_package_prefixes() {
+ msg "Generating build prefix manifest"
+ pushd "${pkgdir}"
+ # Create record files
+ >.SFPM-PREFIX-TEXT
+ >.SFPM-PREFIX-BIN
+
+ # Assimilate file path for anything containing our prefix
+ local count_text=0
+ local count_bin=0
+ local count_total=0
+
+ for path in $(find . -type f -not -name ".SFPM-*")
+ do
+ # Check for prefix
+ grep -l "${sfpm_build_prefix}" "${path}" &>/dev/null
+
+ # Prefix present? (0: yes, 1: no)
+ if (( $? )); then
+ continue
+ fi
+
+ # Get file type
+ local mimetype="$(file -i ${path} | awk -F': ' '{ print $2 }')"
+ local outfile
+
+ # Record prefix data
+ if [[ ${mimetype} = *text/* ]]; then
+ outfile=.SFPM-PREFIX-TEXT
+ (( count_text++ ))
+ else
+ outfile=.SFPM-PREFIX-BIN
+ (( count_bin++ ))
+ fi
+
+ echo "${path}" >> "${outfile}"
+
+ done
+
+ count_total=$(( count_text + count_bin ))
+ if (( ${count_total} )); then
+ msg2 "Text: ${count_text}"
+ msg2 "Binary: ${count_bin}"
+ msg2 "Total: ${count_total}"
+ else
+ msg2 "No prefixes detected"
+ fi
+ popd
+}
+
+function sfpm_gen_packages() {
+ local funcs=$(compgen -A function | grep ^package)
+ local name_old="${name}"
+ local pkgdir_old="${pkgdir}"
+ for fn in ${funcs}
+ do
+ # Don't modify main package() behavior
+ if [[ ${fn} != package ]]; then
+ name=${fn#package_}
+ pkgdir_child=$(mktemp -d ${TMPDIR}/sfpm.pkgdir_child.XXXXXX)
+ pkgdir="${pkgdir_child}"
+ fi
+
+ ${fn}
+ sfpm_gen_package
+
+ if [[ -d ${pkgdir_child} ]]; then
+ rm -rf "${pkgdir_child}"
+ name="${name_old}"
+ pkgdir="${pkgdir_old}"
+ fi
+ done
+}
+
+function sfpm_gen_package() {
+ if [[ ! ${sfpm_build_scriptdir} ]]; then
+ msg_error "Refusing to generate package outside of sfpm_makepkg"
+ exit 1
+ fi
+
+ archive=${name}-${version}-${release}.tar.bz2
+ pushd "${pkgdir}"
+ msg "Generating ${archive}"
+ sfpm_gen_package_manifest
+ sfpm_gen_package_depends_manifest
+ sfpm_gen_package_prefixes
+ sfpm_gen_package_rpath
+ tar cfj "${sfpm_build_scriptdir}/${archive}" .SFPM-* *
+ popd
+}
diff --git a/share/sfpm/common.sh b/share/sfpm/common.sh
new file mode 100644
index 0000000..8b7f33a
--- /dev/null
+++ b/share/sfpm/common.sh
@@ -0,0 +1,17 @@
+# Override default directory stack output behavior
+pushd () {
+ command pushd "$@" > /dev/null
+}
+
+popd () {
+ command popd "$@" > /dev/null
+}
+
+sfpm_root="$(readlink -f $(dirname ${BASH_SOURCE[0]})/../..)"
+sfpm_internal_include=${sfpm_root}/share/sfpm
+source ${sfpm_internal_include}/msg.sh
+source ${sfpm_internal_include}/sfpm.sh
+source ${sfpm_internal_include}/env.sh
+source ${sfpm_internal_include}/pkg_read.sh
+source ${sfpm_internal_include}/index.sh
+source ${sfpm_internal_include}/download.sh
diff --git a/share/sfpm/download.sh b/share/sfpm/download.sh
new file mode 100644
index 0000000..0f3a0ec
--- /dev/null
+++ b/share/sfpm/download.sh
@@ -0,0 +1,109 @@
+DOWNLOADERS=(
+ curl
+ wget
+)
+
+
+function fetch_select()
+{
+ # Good chance this will not be used. cURL is sufficient as it is...
+ for prog in "${DOWNLOADERS[@]}"
+ do
+ fetcher=$(type -p ${prog})
+ if [[ ${fetcher} ]]; then
+ break
+ fi
+ done
+
+ if [[ ! ${fetcher} ]]; then
+ msg_error "Cannot continue; no program available to download files with."
+ exit 1
+ fi
+ echo ${fetcher}
+}
+
+
+function fetch()
+{
+ args=( --fail )
+ while (( "${#}" )); do
+ case "${1}" in
+ -r|--redirect)
+ args+=( -L )
+ shift
+ ;;
+ -O|--remote-name)
+ args+=( -O )
+ shift
+ ;;
+ -o|--output)
+ output="${2}"
+ shift 2
+ ;;
+ -s|--skip-exists)
+ skip=1
+ shift
+ ;;
+ -c|--checksum)
+ checksum=1
+ shift
+ ;;
+ --)
+ shift
+ break
+ ;;
+ -*|--*)
+ msg_error "Invalid argument: ${1}" >&2
+ exit 1
+ ;;
+ *)
+ url="${1}"
+ shift
+ ;;
+ esac
+ done
+
+ set -- "${args[@]}"
+
+ filename="$(basename ${url})"
+ msg "Fetching ${filename}"
+
+ if [[ ${output} ]]; then
+ output="${output}/${filename}"
+ filename_checksum="${output}.sha256"
+
+ if [[ -f ${filename_checksum} ]]; then
+ msg2 "Verifying checksum: ${filename_checksum}"
+ for line in "$(sha256sum -c ${filename_checksum})"
+ do
+ msg3 "${line}"
+ done
+
+ if (( $? )); then
+ exit 1
+ fi
+ fi
+
+ if (( ${skip} )) && [[ -f ${output} ]]; then
+ msg2 "Source exists: ${output}"
+ return 0
+ fi
+ args+=( -o ${output} )
+ fi
+
+ $(fetch_select) ${args[@]} ${url}
+ fetch_retval=$?
+ if (( ${fetch_retval} )); then
+ msg_error "Failed to fetch: (${fetch_retval}): ${url}"
+ exit 1
+ fi
+
+ if [[ ${output} ]]; then
+ if (( ${checksum} )); then
+ if [[ ! -f ${filename_checksum} ]]; then
+ sha256sum "${output}" > "${filename_checksum}"
+ fi
+ fi
+ fi
+}
+
diff --git a/share/sfpm/env.sh b/share/sfpm/env.sh
new file mode 100644
index 0000000..e9127a6
--- /dev/null
+++ b/share/sfpm/env.sh
@@ -0,0 +1,233 @@
+function sfpm_env_exists() {
+ # Returns empty string on failure (implicit)
+ local name="${1}"
+ if [[ ! ${name} ]]; then
+ msg_error "sfpm_env_exists(): missing environment name"
+ exit 1
+ fi
+
+ local path="${sfpm_envdir}/${name}"
+ if [[ ! -d ${path} ]]; then
+ return 1
+ fi
+ echo ${path}
+}
+
+
+function sfpm_env_create() {
+ local name="${1}"
+ local path="${sfpm_envdir}/${name}"
+ if [[ ! $(sfpm_env_exists ${name}) ]]; then
+ msg "Creating environment root: ${name}"
+ sfpm_gen_sysroot "${path}"
+ fi
+
+ if [[ ! -f ${path}/bin/activate ]]; then
+ sed -e "s|__SFPM_ENV__|${path}|g" \
+ ${sfpm_sysconfdir}/sfpm.activate.sh > ${path}/bin/activate
+ fi
+
+}
+
+
+function sfpm_env_remove() {
+ local name="${1}"
+ local path="${sfpm_envdir}/${name}"
+ if [[ $(sfpm_env_exists ${name}) ]]; then
+ msg "Removing environment root: ${name}"
+ rm -rf "${path}"
+ fi
+}
+
+function sfpm_rpath_nearest() {
+ local cwd="$(pwd)"
+ local start=$(dirname $(sfpm_abspath ${1}))
+ local result=
+
+ # Jump to location of file
+ cd "$(dirname ${start})"
+
+ # Scan upward until we find a "lib" directory
+ # OR when:
+ # - Top of filesystem is reached (pretty much total failure [missing local dep])
+ # - Top of active environment is reached (post installation)
+ # - Top of default installation prefix is reached (during packaging)
+ while [[ $(pwd) != / ]]
+ do
+ result+="../"
+ if [[ -d lib ]] || [[ $(pwd) == ${SFPM_ENV} ]] || [[ $(pwd) == *${sfpm_build_prefix} ]]; then
+ result+="lib"
+ break
+ fi
+ cd ..
+ done
+
+ # Sanitize: removing double-slashes (if any)
+ result=${result/\/\//\/}
+
+ # Return to where we were instantiated
+ cd "${cwd}"
+
+ echo "${result}"
+}
+
+
+function sfpm_install() {
+ local env_name="${1}"
+ local env_path=$(sfpm_env_exists "${env_name}")
+ local pkg_name="${2}"
+ local pkg_version="${3}"
+ local pkg_release="${4}"
+ local pkg="${pkg_name}-${pkg_version}-${pkg_release}"
+ local pkg_path
+ local metadata
+ local staging
+ local have_prefix_text
+ local have_prefix_bin
+
+ msg "Installing ${pkg_name} [env: ${env_name}]"
+ if [[ ! ${env_path} ]]; then
+ msg_error "sfpm_install(): Environment does not exist: ${env_name}"
+ exit 1
+ fi
+
+ if [[ ${pkg_name} == */*.tar.bz2 ]]; then
+ pkg_path="${pkg_name}"
+ else
+ pkg_path=$(sfpm_package_exists "${pkg}.tar.bz2")
+ fi
+
+ if [[ ! -f ${pkg_path} ]]; then
+ msg_error "sfpm_install(): Package does not exist: ${pkg}"
+ exit 1
+ fi
+
+
+ msg2 "Extracting metadata"
+ metadata=$(mktemp -d ${TMPDIR}/sfpm.metadata.XXXXXX)
+ tar -xf "${pkg_path}" \
+ -C "${metadata}" \
+ --wildcards "\.SFPM-*"
+
+ msg2 "Extracting package"
+ staging=$(mktemp -d ${TMPDIR}/sfpm.staging.XXXXXX)
+ tar -xf "${pkg_path}" \
+ -C "${staging}" \
+ --strip-components=1 \
+ --wildcards "${sfpm_build_prefix/\//}*"
+
+ have_prefix_text=$(wc -l ${metadata}/.SFPM-PREFIX-TEXT | cut -d ' ' -f 1)
+ if [[ ${have_prefix_text} != 0 ]]; then
+ msg2 "Relocating text paths"
+ while read filename
+ do
+ msg3 "${filename}"
+ sfpm_file_relocate --text --env "${env_name}" --path "${filename}"
+ done < <(sed -e "s|.${sfpm_build_prefix}|${staging}|g" "${metadata}/.SFPM-PREFIX-TEXT")
+ fi
+
+ # TODO: Not implemented
+ #have_prefix_bin=$(wc -l ${metadata}/.SFPM-PREFIX-BIN | cut -d ' ' -f 1)
+ #if [[ ${have_prefix_bin} != 0 ]]; then
+ # msg2 "Relocating binary paths"
+ # while read filename
+ # do
+ # msg3 "${filename}"
+ # sfpm_file_relocate --bin --env "${env_name}" --path "${filename}"
+ # done < <(sed -e "s|.${sfpm_build_prefix}|${staging}|g" "${metadata}/.SFPM-PREFIX-BIN")
+ #fi
+ rsync -a "${staging}/" "${env_path}"
+
+ rm -rf "${staging}"
+ rm -rf "${metadata}"
+}
+
+
+function sfpm_file_relocate() {
+ # TODO: binary relocation
+ # easy peasy... already wrote sfpm_rpath_nearest()
+ #local env_name="${1}"
+ #local env_path=$(sfpm_env_exists "${env_name}")
+ #local path="${2}"
+
+ local path
+ local env_name
+ local mode_text=0
+ local mode_bin=0
+
+ while (( "${#}" )); do
+ case "${1}" in
+ -t|--text)
+ mode_text=1
+ shift
+ ;;
+ -b|--bin)
+ # TODO: Not implemented
+ mode_bin=1
+ shift
+ ;;
+ -e|--env)
+ env_name="${2}"
+ shift 2
+ ;;
+ -p|--path)
+ path="${2}"
+ shift 2
+ ;;
+ --)
+ shift
+ break
+ ;;
+ -*|--*)
+ msg_error "Invalid argument: ${1}" >&2
+ exit 1
+ ;;
+ *)
+ # do nothing with positional args
+ shift
+ ;;
+ esac
+ done
+
+ if (( ${mode_text} )) && (( ${mode_bin} )); then
+ msg_error "-t/--text and -b/--bin are mutually exclusive arguments"
+ exit 1
+ fi
+
+ local env_path=$(sfpm_env_exists "${env_name}")
+ if [[ ! ${env_path} ]]; then
+ msg_error "sfpm_file_relocate(): Environment does not exist: ${env_name}"
+ exit 1
+ fi
+
+ if [[ ! -f ${path} ]] && [[ ! -L ${path} ]]; then
+ msg_warn "sfpm_file_relocate(): ${path}: not a file or symbolic link"
+ return 1
+ fi
+
+ tmpfile=$(mktemp ${TMPDIR}/sfpm.relocate.XXXXXX)
+ if [[ ! -f ${tmpfile} ]]; then
+ msg_error "sfpm_file_relocate(): Failed to create temporary relocation file."
+ exit 1
+ fi
+
+ filemode=$(stat -c '%a' "${path}")
+ if (( ${mode_text} )); then
+ sed -e "s|${sfpm_build_prefix}|${env_path}|g" < "${path}" > "${tmpfile}"
+ elif (( ${mode_bin} )); then
+ # TODO
+ :
+ else
+ msg_error "sfpm_file_relocate(): Invalid modification mode. --text nor --bin specified"
+ exit 1
+ fi
+
+ chmod ${filemode} "${tmpfile}"
+ mv "${tmpfile}" "${path}"
+
+ if (( $? )); then
+ msg_error "sfpm_file_relocate(): Failed to move temporary relocation file. Purging."
+ rm -f "${tmpfile}"
+ fi
+}
+
diff --git a/share/sfpm/index.sh b/share/sfpm/index.sh
new file mode 100644
index 0000000..3d7d112
--- /dev/null
+++ b/share/sfpm/index.sh
@@ -0,0 +1,129 @@
+function sfpm_index_lock() {
+ local hostname=$(hostname)
+ local lockfile=${sfpm_pkgdir}/.LOCK
+
+ if [[ -f ${lockfile} ]]; then
+ local lockhost=$(awk -F':' '{ print $1 }' ${lockfile})
+ local lockpid=$(awk -F':' '{ print $2 }' ${lockfile})
+
+ if [[ ! ${lockhost} ]] || [[ ! ${lockpid} ]]; then
+ msg_error "Invalid lock file detected (contents follow):"
+ msg_error "---- BEGIN ----" "$(cat ${lockfile})" "---- END ----"
+ exit 1
+ fi
+
+ msg_warn "Index locked by ${lockhost} with PID ${lockpid}" \
+ "Waiting for lock to clear..."
+
+ if [[ ${lockhost} == ${hostname} ]]; then
+ if ps -e ${lockpid} > /dev/null; then
+ msg_warn "PID on host is dead" \
+ "Removing stale lock"
+ sfpm_index_unlock
+ fi
+ fi
+
+ while true
+ do
+ [[ ! -f ${lockfile} ]] && break
+ sleep 1
+ done
+ fi
+
+ # Lock file format:
+ # hostname.domain.tld:1234
+ echo "${hostname}:$$" > ${lockfile}
+}
+
+
+function sfpm_index_unlock() {
+ rm -f ${sfpm_pkgdir}/.LOCK
+}
+
+
+function sfpm_index_create() {
+ local index="${sfpm_pkgdir}/.SFPM-INDEX"
+
+ msg "Creating package index"
+ sfpm_index_lock
+ > ${index}
+
+ msg2 "Indexing"
+ # Aggregate list of packages, excluding hidden files
+ local pkgs=$(find ${sfpm_pkgdir} -type f -not -name '.*' 2>/dev/null)
+
+ for pkg in ${pkgs}
+ do
+ local tarball=$(basename ${pkg})
+ local no_ext=${tarball%%.[tTzZ]*}
+ local result=$(echo ${no_ext} | awk -F'-' '{ printf "%s,%s,%s\n", $1, $2, $3 }')
+
+ msg3 "${tarball}"
+ echo "${tarball},${result}" >> "${index}"
+ done
+
+ msg2 "Ordering index"
+ tmpfile=$(mktemp ${TMPDIR}/sfpm.index.sorted.XXXXXX)
+ sort -n "${index}" > ${tmpfile}
+ mv "${tmpfile}" "${index}"
+
+ sfpm_index_unlock
+}
+
+# Sometimes it's just easier...
+# https://stackoverflow.com/questions/229551/how-to-check-if-a-string-contains-a-substring-in-bash#229585
+has_substr() { [ -z "${2##*$1*}" ] && { [ -z "$1" ] || [ -n "$2" ] ;} ; }
+
+function sfpm_index_search() {
+ local index="${sfpm_pkgdir}/.SFPM-INDEX"
+ local name="${1}" # required
+ local version="${2}" # optional
+ local release="${3}" # optional
+
+ if [[ ! ${2} ]]; then
+ verison=
+ fi
+
+ if [[ ! ${3} ]]; then
+ release=
+ fi
+
+ # TODO: Rewrite this... barf
+ local result=
+ while read line
+ do
+ pkg_tarball=$(echo ${line} | awk -F',' '{ print $1 }')
+ pkg_name=$(echo ${line} | awk -F',' '{ print $2 }')
+ pkg_version=$(echo ${line} | awk -F',' '{ print $3 }')
+ pkg_release=$(echo ${line} | awk -F',' '{ print $4 }')
+
+ if has_substr "${name}" "${pkg_name}"; then
+ found_name=1
+ fi
+
+ if (( ${found_name} )); then
+ if has_substr "${version}" "${pkg_version}"; then
+ found_version=1
+ fi
+
+ if has_substr "${release}" "${pkg_release}"; then
+ found_release=1 # Barely...
+ fi
+
+ if (( ${found_name} )); then
+ if [[ ${version} ]] && ! (( ${found_version} )); then
+ continue
+ fi
+
+ if [[ ${release} ]] && ! (( ${found_release} )); then
+ continue;
+ fi
+
+ result=$(sfpm_package_exists "${pkg_tarball}")
+ break
+ fi
+ fi
+ done < "${index}"
+ echo "${result}"
+}
+
diff --git a/share/sfpm/msg.sh b/share/sfpm/msg.sh
new file mode 100644
index 0000000..3f6efb9
--- /dev/null
+++ b/share/sfpm/msg.sh
@@ -0,0 +1,29 @@
+function _msg() {
+ printf "$@"
+}
+
+function plain() {
+ _msg " %s\n" "$@"
+}
+
+function msg() {
+ _msg "==> %s\n" "$@"
+}
+
+
+function msg2() {
+ _msg " -> %s\n" "$@"
+}
+
+
+function msg3() {
+ _msg " . %s\n" "$@"
+}
+
+function msg_error() {
+ _msg "[ERROR] %s\n" "$@" >&2
+}
+
+function msg_warn() {
+ _msg "[WARNING] %s\n" "$@" >&2
+}
diff --git a/share/sfpm/pkg_read.sh b/share/sfpm/pkg_read.sh
new file mode 100644
index 0000000..68f073a
--- /dev/null
+++ b/share/sfpm/pkg_read.sh
@@ -0,0 +1,37 @@
+function sfpm_package_exists() {
+ # Returns empty string on failure (implicit)
+ local path="${sfpm_pkgdir}/${1}"
+ if [[ ! -f ${path} ]]; then
+ return 1
+ fi
+ echo ${path}
+}
+
+function sfpm_package_verify() {
+ local name="$(basename ${1})"
+ local path="${sfpm_pkgdir}/${name}"
+
+ if [[ ! -f $(sfpm_package_exists ${name}) ]]; then
+ msg_error "sfpm_package_verify(): Package does not exist: ${path}"
+ exit 1
+ fi
+
+ local vdir=$(mktemp -d ${TMPDIR}/sfpm.verify.XXXXXX)
+ pushd "${vdir}"
+ msg2 "Verifying Package: ${name}"
+ tar -xf "${path}"
+
+ while read line
+ do
+ msg3 "${line}"
+ done < <(sha256sum -c .SFPM-MANIFEST)
+
+ while read line
+ do
+ msg3 "${line}"
+ done < <(find . -type f | xargs file | grep ELF | awk -F':' '{ print $1 }' | xargs readelf -d | grep rpath)
+ popd
+ if [[ -d ${vdir} ]] && [[ ${vdir} == *${TMPDIR}* ]]; then
+ rm -rf "${vdir}"
+ fi
+}
diff --git a/share/sfpm/sfpm.sh b/share/sfpm/sfpm.sh
new file mode 100644
index 0000000..1e86c5c
--- /dev/null
+++ b/share/sfpm/sfpm.sh
@@ -0,0 +1,88 @@
+sfpm_bindir=${sfpm_root}/bin
+sfpm_sbindir=${sfpm_root}/sbin
+sfpm_sysconfdir=${sfpm_root}/etc
+sfpm_libdir=${sfpm_root}/lib
+sfpm_libexecdir=${sfpm_root}/libexec
+sfpm_datarootdir=${sfpm_root}/share
+sfpm_datadir=${sfpm_datarootdir} # alias for GNU sake
+sfpm_docdir=${sfpm_datarootdir}/doc
+sfpm_mandir=${sfpm_datadir}/man
+sfpm_infodir=${sfpm_datadir}/info
+sfpm_localstatedir=${sfpm_root}/var
+sfpm_pkgdir=${sfpm_localstatedir}/lib/pkgs
+sfpm_cache=${sfpm_localstatedir}/cache
+sfpm_srcdir=${sfpm_cache}/src
+sfpm_runstatedir=${sfpm_localstatedir}/run
+sfpm_includedir=${sfpm_root}/include
+sfpm_tmpdir=${sfpm_root}/tmp
+sfpm_envdir=${sfpm_root}/envs
+
+# Set default prefix. It happens to be the name of the variable...
+sfpm_build_prefix="/sfpm_build_prefix"
+sfpm_build_cflags="-I${sfpm_includedir}"
+sfpm_build_ldflags="-L${sfpm_libdir} -Wl,-rpath="'\$$ORIGIN'/../lib
+
+sfpm_INTERNAL_PATHS=(
+ sfpm_root
+ sfpm_bindir
+ sfpm_sbindir
+ sfpm_sysconfdir
+ sfpm_libdir
+ sfpm_libexecdir
+ sfpm_datarootdir
+ sfpm_datadir
+ sfpm_docdir
+ sfpm_mandir
+ sfpm_infodir
+ sfpm_localstatedir
+ sfpm_pkgdir
+ sfpm_cache
+ sfpm_srcdir
+ sfpm_runstatedir
+ sfpm_includedir
+ sfpm_tmpdir
+ sfpm_envdir
+)
+
+
+export TMPDIR="${sfpm_tmpdir}"
+
+
+function sfpm_abspath() {
+ local filename="${1}"
+ local start="$(dirname ${filename})"
+
+ pushd "${start}" &>/dev/null
+ end="$(pwd)"
+ popd &>/dev/null
+
+ if [[ -f ${filename} ]]; then
+ end="${end}/$(basename ${filename})"
+ fi
+
+ echo "${end}"
+}
+
+
+function sfpm_gen_sysroot() {
+ local env_path="${1}"
+
+ for envpath in "${sfpm_INTERNAL_PATHS[@]}"
+ do
+ p="${!envpath}"
+
+ # For environment creation, override sysroot with new root path
+ if [[ ${env_path} ]]; then
+ # Environments will not be chained
+ if [[ ${envpath} == sfpm_envdir ]]; then
+ continue
+ fi
+ p="${p/${sfpm_root}/${env_path}}"
+ fi
+
+ [[ ! -d ${p} ]] && mkdir -p "${p}"
+ done
+}
+
+
+