#!/bin/zsh
setopt no_unset warn_create_global extendedglob
PS4='+%B%* %F{cyan}%N%f:%F{yellow}%i%f>%b '
zparseopts -D \
h=ARGS_H -help=ARGS_H \
x=ARGS_X -trace=ARGS_X \
c:=ARGS_C -config:=ARGS_C \
b:=ARGS_B -basedir:=ARGS_B \
-continue=ARGS_CONTINUE \
-pretend=ARGS_PRETEND \
if (($#ARGS_H)); then cat <<END
usage: ${0:t} [options] [target]
options:
-h --help this help
-x --trace enable debugging output
-c --config <file> source a configuration file
-b --basedir <dir> set this directory as basedir
--continue don't erase existing build directories
--pretend only show what build steps would be taken
description:
Flexible Stage Builder
END
exit 0
fi
(($#ARGS_X)) && set -x
for option_flag option_value in $ARGS_B; do
FSB_BASE_DIR:=$option_value
done
FSB_CONFIG=${FSB_BASE_DIR:-${0:a:h:h}}/fsbrc
[[ -a $FSB_CONFIG ]] && source $FSB_CONFIG
for option_flag option_value in $ARGS_C; do
source $option_value || exit 1
: ${FSB_TARGET_NAME:=${option_value:t}}
done
(($#@)) && FSB_TARGET=$1
# parameter defaults
: ${FSB_SNAPSHOTS:=btrfs}
: ${FSB_BASE_DIR:=${0:a:h:h}}
: ${FSB_BUILD_DIR:=$FSB_BASE_DIR/build}
: ${FSB_FUNCTIONS_DIR:=$FSB_BASE_DIR/functions}
: ${FSB_DOWNLOADS_DIR:=$FSB_BASE_DIR/downloads}
: ${FSB_SCRIPTS_DIR:=$FSB_BASE_DIR/scripts}
: ${FSB_TEMPLATE_DIR:=$FSB_BASE_DIR/template}
: ${FSB_GENTOO_MIRROR:=ftp://ftp.fi.muni.cz/pub/linux/gentoo/}
: ${FSB_OVERLAY_BASE:=/home/ccx/bzr}
: ${FSB_PORTAGE_SNAPSHOTS:=/mnt/data/snapshots/portage}
# basic initialisation
path=( $FSB_SCRIPTS_DIR $path )
if ! [[ $terminfo[colors] -ge 8 ]]; then
FSB_NO_COLORS=1
fi
if ! (($+commands[cowsay])); then
FSB_NO_COWSAY=1
fi
setopt extendedglob warncreateglobal
# error reporting
if (($+FSB_NO_COWSAY)); then
if (($+FSB_NO_COLORS)); then
fsb_info() { print -r '[I]' "$@" }
fsb_warn() { print -r '[W]' "$@" }
fsb_error() { print -r '[E]' "$@" }
else
autoload colors
colors
fsb_info() { print -r $fg[green]\*$reset_color "$@" }
fsb_warn() { print -r $fg[yellow]\*$reset_color "$@" }
fsb_error() { print -r $fg[red]\*$reset_color "$@" }
fi
else
if (($+FSB_NO_COLORS)); then
fsb_info() { print -r - "$@" | cowsay -n}
fsb_warn() { print -r - "$@" | cowsay -w -W $COLUMNS }
fsb_error() { print -r - "$@" | cowsay -d -W $COLUMNS }
else
autoload colors
colors
fsb_say() {
local color=$1
shift
cowsay "$@" | sed "1{s/^/${fg[${color}]}/};/^ ---* \$/s/\$/${reset_color}/"
}
fsb_info() { print -r - "$@" | fsb_say green -n }
fsb_warn() { print -r - "$@" | fsb_say yellow -w -W $COLUMNS }
fsb_error() { print -r - "$@" | fsb_say red -d -W $COLUMNS }
fi
fi
die() {
(($#)) && fsb_error "$@"
(($+build_dir)) && print -r 'Interrupted in: '$build_dir
exit 1
}
# function autoloading
fpath+=$FSB_FUNCTIONS_DIR
typeset -gA fsb_targets
load_functions() {
local ppath='' name func
local -a match mbegin mend
for func in $^fpath/fsb_*(N) ; do
if ! [[ -r "$func" ]]; then
fsb_warn "$func is not readable"
continue
fi
autoload -Uz $func:t
if [[ $func = */fsb_target_(#b)(*) ]]; then
# remember all targets
typeset -g "fsb_targets[${match[1]}]"=$func:t
elif [[ $func = */fsb_stage_(#b)(*)_${FSB_SNAPSHOTS} ]]; then
# alias stage functions according to snapshot method
functions[fsb_stage_${match[1]}]=$func:t' "$@"'
fi
done
}
# build functions
typeset -g build_dir build_dir_base
typeset -ga build_stack
target_info() {
local -a info_vars
info_vars=(
FSB_INFO_BASE # base build directory name passed up to calling stage
FSB_INFO_DIR # directory for building this stage
# FSB_INFO_KEEP # whether to keep build directory around
FSB_INFO_SKIP # skip calling build stage of this script
FSB_INFO_RESUME # if this stage can be resumed
FSB_INFO_DEPEND # build this stage first and then clone it's directory
)
typeset -g $info_vars
FSB_INFO_DEPEND='' # sanitize
FSB_INFO_DIR='%s'
FSB_INFO_SKIP=0
FSB_INFO_RESUME=0
(($+fsb_targets[$1])) || die "no such target: ${(q)1}"
$fsb_targets[$1] info || die "$1 info failed"
local param info_name
info_name=TARGET_INFO_${1#fsb_target_}
local -A info
for param in $info_vars; do
# [[ -n ${(P)param} ]] && \
typeset "info[${param#FSB_INFO_}]"=${(P)param}
unset $param
done
info[FUNC]=$1
if (($info[SKIP])); then
target_info $info[DEPEND]
return
fi
build_stack+=$info_name
if [[ $info[BASE] == *'%s'* ]]; then
[[ -n $info[DEPEND] ]] || die "target $1 does use %s in BASE and does not depend on another target"
target_info $info[DEPEND]
build_dir_base=${${info[BASE]}//\%s/${build_dir_base}}
info[DIR]=${${info[DIR]}//\%s/${build_dir_base}}
else
build_dir_base=${info[BASE]}
if [[ $info[DIR] == *'%s'* ]]; then
info[DIR]=${${info[DIR]}//\%s/${build_dir_base}}
fi
build_dir_check ${info[DIR]} ${info[FUNC]}
local ret=$?
if (( $ret == 100 )) ; then
if [[ -n $info[DEPEND] ]]; then
target_info $info[DEPEND] || die "target_info on ${(q)info[DEPEND]} failed"
fi
elif (( $ret )) ; then
die "build_dir_check failed"
fi
build_dir_base=${info[BASE]}
fi
local k v
typeset -gA ${info_name}
for k v in "${(kv@)info}"; do
typeset -g "${info_name}[${k}]"=$v
done
}
target_stack_build() {
local -A info
local k v mark
for k v in "${(kvP@)build_stack[${1:-1}]}"; do
typeset "info[$k]"=$v
done
build_dir=$info[DIR]
build_dir_check $info[DIR] $info[FUNC] && return 0
[[ $? -eq 100 ]] || die "build_dir_check failed"
if [[ -d $build_dir ]]; then
fsb_info "resuming build in $build_dir"
elif [[ -n $info[DEPEND] ]]; then
target_stack_build $(( ${1:-1} + 1 ))
fsb_stage_clone $build_dir $info[DIR]
else
fsb_stage_init $info[DIR]
fi
build_dir=$info[DIR]
${fsb_targets[${info[FUNC]}]} build || die "${info[FUNC]} build failed"
mark=${info[FUNC]#fsb_target_}
grep -qFe $mark $build_dir/.fsb_done || print -r - $mark >> $build_dir/.fsb_done
}
build_target() {
((${+fsb_targets[${1}]})) || die "no such target: $1"
target_info $1
if (($#ARGS_PRETEND)); then
local n k v
for n in $(seq $#build_stack -1 1); do
printf "%d) %s:\n" $[ $#build_stack + 1 - $n ] $build_stack[$n]
for k v in "${(kvP@)build_stack[${n}]}"; do
printf " %s=%s\n" $k ${(qqq)v}
done
done
exit 0
fi
target_stack_build
}
# typeset -ga build_dir_stack
typeset -g build_dir build_dir_done
build_dir_check() {
# check whether there's stage ready inside a directory
# $1 is directory, $2 is target name
local name=${2#fsb_target_}
[[ -a $1 ]] || return 100 # does not exist, proceed building
[[ -d $1 ]] || die "$1 is not a directory, required by target $2"
if [[ -f $1/.fsb_done ]] && grep -qFe $name $1/.fsb_done; then
return 0 # target already completed in this directory
fi
# scrap incomplete build
(($#ARGS_CONTINUE)) || fsb_stage_remove $1
return 100
}
# build_dir_push() {
# [[ -n $build_dir ]] && build_dir_stack+=$build_dir
# build_dir=$1
# [[ -d $1:h ]] || mkdir -p $1:h || die "could not mkdir $1:h for stage $1:t"
# # [[ -d $1 ]] || fsb_stage_init $1 || die "failed to create stage directory: $1"
# }
# build_target() {
# (($+fsb_targets[${1}])) || die "no such target: $1"
# $fsb_targets[${1}] || die "build of target $1 failed"
# [[ -d $build_dir ]] || die "'$build_dir' is not valid stage directory, target $1 failed"
# grep -qFe $1 $build_dir/.fsb_done || print -r - $1 >> $build_dir/.fsb_done
# if (( $#build_dir_stack )); then
# fsb_stage_clone $build_dir $build_dir_stack[-1]
# # pop from stack
# build_dir=$build_dir_stack[-1]
# build_dir_stack=( $build_dir_stack[1,-2] )
# fi
# }
# run!
load_functions
if [[ -z $FSB_TARGET ]]; then
fsb_info "usage: $0:h <target name>"$'\ntargets:\n'${(kF)fsb_targets/#/ - }
exit
fi
mkdir -p $FSB_BUILD_DIR
build_target ${1:-$FSB_TARGET}
fsb_info "build completed into: $build_dir"
if [[ -d $build_dir:h:h/done/$build_dir:t ]]; then
btrfs sub del $build_dir:h:h/done/$build_dir:t || \
die "could not remove done snapshot"
fi
btrfs sub snap $build_dir $build_dir:h:h/done/$build_dir:t
if (($+FSB_TARGET_NAME)); then
local done_link
done_link=$build_dir:h:h/done/$FSB_TARGET_NAME
if [[ -e $done_link ]]; then
rm $done_link || die "could create done-link ${(q)done_link}: already present"
fi
ln -s $build_dir:t $done_link || die "could create done-link ${(q)done_link}"
tar -czpf ${done_link:a}.tar.gz.new -C $done_link . || die "could not create tarball"
if [[ -e $FSB_BASE_DIR/signify/key.sec ]]; then
signify -Sz -s $FSB_BASE_DIR/signify/key.sec -m ${done_link:a}.tar.gz.new -x ${done_link:a}.tar.gz.signed || die
rm ${done_link:a}.tar.gz.new
mv ${done_link:a}.tar.gz.signed ${done_link:a}.tar.gz || die
else
mv ${done_link:a}.tar.gz.new ${done_link:a}.tar.gz || die
fi
fsb_info "target available in: $done_link and ${done_link:a}.tar.gz"
fi