#!/bin/bash hash -r function spm_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}" } SPM_ORIGIN=$(dirname $(spm_abspath $0)) TMPDIR=${TMPDIR:-/tmp} OLD_PATH="${PATH}" default_script=build.sh build_order=${SPM_ORIGIN}/include/build.order build_scripts=$(cat ${build_order}) # TODO: Make this part of MKTC SPM_PROG_RELOC=reloc SPM_RELOC_COUNT=0 SPM_PREFIX_BIN=.SPM_PREFIX_BIN SPM_PREFIX_TEXT=.SPM_PREFIX_TEXT SPM_DEPENDS=.SPM_DEPENDS SPM_VERBOSE=0 export prefix_placeholder=_0123456789_0123456789_0123456789_0123456789_0123456789_0123456789_0123456789_ export prefix="/${prefix_placeholder}" export maxjobs=4 export pkgdir=${SPM_ORIGIN}/pkgs mkdir -p ${pkgdir} source ${SPM_ORIGIN}/include/9999-template.sh function spm_rpath_nearest() { local cwd="$(pwd)" local start=$(dirname $(spm_abspath ${1})) local result= local lib_forced=0 local install_root="${destdir}/${prefix_placeholder}" # Determine if package produced its own "lib" directory. If not, generate a symlink # pointing back to the build's runtime environment, or create a placeholder. if [[ ! -d ${install_root}/lib ]] && [[ ! -L ${install_root}/lib ]]; then pushd "${install_root}" &>/dev/null if [[ -d ${build_runtime}/lib ]]; then ln -sf "${build_runtime}"/lib else mkdir -p "${install_root}"/lib fi popd &>/dev/null lib_forced=1 fi # Jump to location of file cd "$(dirname ${start})" # Scan upward until we find a "lib" directory with shared libraries # 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+="../" echo "spm_rpath_nearest(): @ $(pwd)" >&2 if [[ -d $(pwd)/lib/ ]]; then echo "spm_rpath_nearest(): found lib directory" >&2 # There are edge cases when the lib directory one level above an executable # doesn't contain any shared libraries (i.e. binutils) if (( ! $(find $(pwd)/lib/ -type f \ \( -name "*.so" -o -name "*.dylib" -o -name "*.a" \) \ 2>/dev/null | wc -l) )); then cd .. echo "spm_rpath_nearest(): lib directory was not useful" >&2 continue fi result+="lib" echo "spm_rpath_nearest(): lib directory was useful" >&2 break fi cd .. done # If we created a symlink to the runtime environment, remove it if (( $lib_forced > 0)); then rm -f "${install_root}/lib" lib_forced=0 fi # Sanitize: removing double-slashes (if any) result=${result/\/\//\/} # Return to where we were instantiated cd "${cwd}" echo "${result}" } function spm_gen_package_rpath() { local rpath_orig local rpath_new # Assimilate all file paths that contain an RPATH for path in $(find . -type f -not -name '.SPM_*') do readelf -d "${path}" 2>/dev/null | grep 'R.\?.\?PATH' &>/dev/null if (( $? )); then continue fi rpath_orig="$(readelf -d "${path}" | grep 'R.\?.\?PATH' | awk -F'[][]' '{ print $2 }')" rpath_new='$ORIGIN/'"$(spm_rpath_nearest ${path})" if [[ ${rpath_origin} == ${rpath_new} ]]; then continue fi echo "${path}: ${rpath_orig} -> ${rpath_new}" patchelf --set-rpath "${rpath_new}" "${path}" done } function spm_gen_package_prefixes() { echo "Generating build prefix manifest" # Create record files >${SPM_PREFIX_BIN} >${SPM_PREFIX_TEXT} # Assimilate file path for anything containing our prefix local count_text=0 local count_bin=0 local count_total=0 local prefixes=( ${prefix} ${build_runtime} ${build_root} ${destdir} ) for pkg_prefix in "${prefixes[@]}"; do for path in $(find . -type f -not -name ".SPM_*"); do # Check for prefix grep -l "${pkg_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=${SPM_PREFIX_TEXT} (( count_text++ )) else outfile=${SPM_PREFIX_BIN} (( count_bin++ )) fi echo "#${pkg_prefix}" >> "${outfile}" echo "${path}" >> "${outfile}" done done count_total=$(( count_text + count_bin )) if (( ${count_total} )); then echo "Text: ${count_text}" echo "Binary: ${count_bin}" echo "Total: ${count_total}" else echo "No prefixes detected" fi } function spm_gen_package_depends() { echo "Generating dependency manifest" local outfile="${SPM_DEPENDS}" >${outfile} for dep in "${depends[@]}"; do echo "${dep}" >> "${outfile}" done } _spm_install_depends=() _spm_install_seen=() function spm_install() { local pkg="$(pkg_match $1)" if [[ ! -f ${pkg} ]]; then echo "Package not found: ${pkg}" >&2 exit 1 fi local destroot="$2" if [[ -z ${destroot} ]]; then echo "destination root undefined" >&2 exit 1 elif [[ ! -d ${destroot} ]]; then mkdir -p "${destroot}" fi # extract package into temp directory local pkgtmp=$(mktemp -d ${TMPDIR}/spm.XXXX) pushd "${pkgtmp}" &>/dev/null echo "Unpacking: ${pkg}" tar xf "${pkg}" local prefix_base # relocate binaries if [[ -f ${SPM_PREFIX_BIN} ]]; then while read path; do if [[ -z $path ]]; then continue elif [[ $path =~ ^#.* ]]; then prefix_base="${path#\#}" continue fi if [[ ! -f $path ]]; then echo "WARNING: ${path} does not exist!" >&2 continue fi if [[ $path =~ .pyc$ ]]; then continue fi if (( $SPM_VERBOSE )); then echo "Relocating binary paths: ${path}" fi ${SPM_PROG_RELOC} "${prefix_base}" "${destroot}" "${path}" "${path}" >/dev/null done < "${SPM_PREFIX_BIN}" rm -f ${SPM_PREFIX_BIN} fi prefix_base="" # relocate text if [[ -f ${SPM_PREFIX_TEXT} ]]; then while read path; do if [[ -z $path ]]; then continue elif [[ $path =~ ^# ]]; then prefix_base="${path#\#}" continue fi if [[ ! -f $path ]]; then echo "WARNING: ${path} does not exist!" >&2 continue fi if [[ $path =~ .pyc$ ]]; then continue fi if (( $SPM_VERBOSE )); then echo "Relocating text paths: ${path}" fi sed -i -e "s|${prefix_base}|${destroot}|g" "${path}" done < "${SPM_PREFIX_TEXT}" rm -f ${SPM_PREFIX_TEXT} fi # service package dependencies if [[ -f ${SPM_DEPENDS} ]]; then if [[ -z ${_spm_install_depends} ]]; then _spm_install_depends=($(cat ${SPM_DEPENDS})) else _spm_install_depends+=($(cat ${SPM_DEPENDS})) fi for dep in "${_spm_install_depends[@]}"; do # Track dependencies we have already processed to avoid infinite recursion if [[ ${_spm_install_seen[@]} =~ $dep ]]; then # Pop dependency and do nothing _spm_install_depends=("${_spm_install_depends[@]:1}") continue fi # Pop dependency and process it _spm_install_depends=("${_spm_install_depends[@]:1}") # Stop processing when the array is totally empty if (( ${#_spm_install_depends[@]} < 0 )); then _spm_install_depends=() #_spm_install_seen=() break fi _spm_install_seen+=("${dep}") spm_install "${dep}" "${destroot}" done #FIXME #local prev #for rec in "${_spm_install_depends[@]}"; do prev="$rec $prev"; done #_spm_install_depends=($x) #for dep in "${_spm_install_depends[@]}"; do # spm_install "${dep}" "${destroot}" #done rm -f "${SPM_DEPENDS}" fi # install package echo "Installing: ${pkg}" rsync -a ./ "${destroot}" popd &>/dev/null if [[ -d $pkgtmp ]]; then rm -rf "${pkgtmp}" fi } function builder() { for build_script in ${build_scripts}; do if [[ ${build_script} =~ ^#.* ]]; then continue fi if [[ ! -f ${build_script} ]]; then build_script=$(spm_abspath ${build_script}/build.sh) build_script_root=$(dirname ${build_script}) else build_script_root=$(spm_abspath ${build_script}) fi local output_dir="${SPM_ORIGIN}/output/$(basename ${build_script_root})" export build_sources="${SPM_ORIGIN}/sources/$(basename ${build_script_root})" export build_root="${output_dir}/buildroot" export build_runtime="${output_dir}/runtime" export destdir="${output_dir}/root" export CC=gcc export CXX=g++ export LD_LIBRARY_PATH="${build_runtime}/lib" export CFLAGS="-I${build_runtime}/include" export CPPFLAGS="${CFLAGS}" export LDFLAGS="-L${build_runtime}/lib -Wl,-rpath="'\$$ORIGIN'"/../lib" echo "Building: ${build_script}" if [[ ! -f ${build_script} ]]; then echo "${build_script} does not exist, check ${build_order}" >&2 exit 1 fi if [[ ! -d ${build_sources} ]]; then mkdir -p ${build_sources} fi if [[ -d ${build_root} ]]; then rm -rf ${build_root} fi mkdir -p ${build_root} if [[ -d ${build_runtime} ]]; then rm -rf ${build_runtime} fi mkdir -p ${build_runtime} if [[ -d ${destdir} ]]; then rm -rf ${destdir} fi mkdir -p ${destdir} export PATH="${build_runtime}/bin:${build_runtime}/sbin:$OLD_PATH" export PKG_CONFIG_PATH="${build_runtime}/lib/pkgconfig" pushd ${build_root} &>/dev/null # Assimilate build script's contents source ${build_script} if [[ -f ${pkgdir}/${name}-${version}-${revision}.tar.gz ]]; then echo "Skipping: Package exists" popd &>/dev/null continue fi # Download source files for url in "${sources[@]}"; do archive_path="${build_sources}/$(basename $url)" if [[ -f ${archive_path} ]]; then echo "Cached source: ${archive_path}" continue fi echo "Downloading source: ${url}" curl -L "$url" > "${archive_path}" done # Copy source files to build root for archive in "${build_sources}"/*; do echo "Copying source to build root: ${archive}" cp -a "${archive}" "${build_root}" done # Make base package implicit if [[ -z $disable_base ]]; then base_package=$(pkg_match "base") if [[ -n ${base_package} ]]; then # pkg_match returns a path, so just use "base" instead if its there base_package="base" build_depends+=(${base_package}) depends+=(${base_package}) fi fi # Install build dependencies for dep in "${build_depends[@]}"; do echo "Build depends on: ${dep}" pkg=$(pkg_match "${dep}") if [[ -z ${pkg} ]]; then echo "Package not found" >&2 exit 1 fi spm_install "${pkg}" "${build_runtime}" done # Install package dependencies for dep in "${depends[@]}"; do echo "Depends on: ${dep}" pkg=$(pkg_match "${dep}") if [[ -z ${pkg} ]]; then echo "Package not found" >&2 exit 1 fi spm_install "${pkg}" "${build_runtime}" done # Rehash the runtime environment hash -r # No failures allowed in predefined build stages set -e prepare build package set +e # Post-process the installation root (i.e. make install DESTDIR=${destdir}) if [[ -d ${destdir} ]]; then pushd ${destdir} &>/dev/null if [[ -d ${destdir}/${prefix} ]]; then pushd ${destdir}/${prefix} &>/dev/null fi spm_gen_package_rpath spm_gen_package_prefixes spm_gen_package_depends pkg="${pkgdir}/${name}-${version}-${revision}.tar.gz" echo "Creating package: ${pkg}" pwd ls -la tar cfz "${pkg}" .SPM_* . if [[ -d ${destdir}/${prefix} ]]; then popd &>/dev/null fi popd &>/dev/null fi popd &>/dev/null done } function pkg_match() { if [[ -z $1 ]]; then echo "pkg_match: missing argument, package" >&2 exit 1 fi match=$(find "${pkgdir}" -type f -regex ".*${1}\-?[0-9]+?.*" 2>/dev/null | sort | head -n 1) echo "${match}" } function installer() { local root="" local packages=() while [[ $# != 0 ]]; do case "$1" in --verbose|-v) SPM_VERBOSE=1 ;; --root|-r) root="$2" shift ;; -*|--*) echo "installer: unknown argument: $1" >&2 exit 1 ;; *) # "Most-likely" match the requested package by name p=$(basename $(pkg_match "${1}")) packages+=("$p") ;; esac shift done if [[ -z $root ]]; then echo "missing required argument: --root {destination}" >&2 exit 1 fi export PATH="${root}/bin:${PATH}" for pkg in "${packages[@]}"; do spm_install "${pkg}" "${root}" done } function usage() { echo "Usage: $0 [build|install] {package} Options: --help (-h) this message --verbose (-v) increase verbosity Commands: build build a package install install a package Positional arguments: package package to interact with " } if [[ $# < 2 ]]; then usage exit 1 fi fn= args= while [[ $# != 0 ]]; do case "$1" in --verbose|-v) SPM_VERBOSE=1 ;; --help|-h) usage exit 0 ;; build) fn=builder if [[ $2 != "all" ]]; then build_scripts="$2" if [[ $build_scripts =~ .*/build.sh ]]; then build_scripts=$(dirname ${build_scripts}) fi fi shift 2 break ;; install) fn=installer shift args=("$@") break ;; *) echo "unknown argument: $1" exit 1 ;; esac shift done $fn "${args[@]}"