sm-commit AT lists.ibiblio.org
Subject: Source Mage code commit list
List archive
[[SM-Commit] ] GIT changes to master sorcery by Ismael Luceno (8cbc23e7543eefd6d0e6d35bee2b1910a5e20426)
- From: Ismael Luceno <scm AT sourcemage.org>
- To: sm-commit AT lists.ibiblio.org, sm-commit AT lists.sourcemage.org
- Subject: [[SM-Commit] ] GIT changes to master sorcery by Ismael Luceno (8cbc23e7543eefd6d0e6d35bee2b1910a5e20426)
- Date: Sat, 15 Aug 2026 19:21:19 +0000
GIT changes to master sorcery by Ismael Luceno <ismael AT sourcemage.org>:
etc/sorcery/config | 1
usr/sbin/alter | 63 +-
usr/sbin/cast | 71 +-
usr/sbin/cleanse | 165 ++++--
usr/sbin/confmeld | 48 +
usr/sbin/delve | 28 -
usr/sbin/dispel | 72 ++
usr/sbin/gaze | 200 +++++--
usr/sbin/resurrect | 19
usr/sbin/sorcery | 96 ++-
usr/share/man/man1/gaze.1 | 48 +
usr/share/man/man5/grimoire.5 | 131 ++++
usr/share/man/man8/alter.8 | 25
usr/share/man/man8/cast.8 | 24
usr/share/man/man8/cleanse.8 | 16
usr/share/man/man8/confmeld.8 | 18
usr/share/man/man8/dispel.8 | 27 -
usr/share/man/man8/resurrect.8 | 21
usr/share/man/man8/sorcery.8 | 22
var/lib/sorcery/modules/build_api/api1 | 2
var/lib/sorcery/modules/build_api/api2 | 8
var/lib/sorcery/modules/build_api/common | 116 ++--
var/lib/sorcery/modules/libcast | 78 +-
var/lib/sorcery/modules/libcodex | 360 ++++++++++++-
var/lib/sorcery/modules/libdepends | 411 ++++++++++++---
var/lib/sorcery/modules/libdepengine | 14
var/lib/sorcery/modules/libdispel | 80 +-
var/lib/sorcery/modules/libgrimoire | 29 -
var/lib/sorcery/modules/libmisc | 4
var/lib/sorcery/modules/libresurrect | 143 ++---
var/lib/sorcery/modules/libsecurity | 25
var/lib/sorcery/modules/libslot | 799 +++++++++++++++++++++++++++++
var/lib/sorcery/modules/libsorcery | 70 +-
var/lib/sorcery/modules/libstage | 10
var/lib/sorcery/modules/libstate | 834
++++++++++++++++++++++++++-----
var/lib/sorcery/modules/libsummon | 17
var/lib/sorcery/modules/libtablet | 51 +
var/lib/sorcery/modules/libtrack | 7
var/lib/sorcery/modules/libtriggers | 142 ++++-
39 files changed, 3605 insertions(+), 690 deletions(-)
New commits:
commit 8cbc23e7543eefd6d0e6d35bee2b1910a5e20426
Author: Ismael Luceno <ismael AT sourcemage.org>
Commit: Ismael Luceno <ismael AT sourcemage.org>
Document slots implementation
commit 95630f58f090f79c6af36b57346cae802d872a8d
Author: Ismael Luceno <ismael AT sourcemage.org>
Commit: Ismael Luceno <ismael AT sourcemage.org>
slots: Implement fast path for queue
commit 50f98c1de04e938c7223281a690eb3e48d293ca4
Author: Ismael Luceno <ismael AT sourcemage.org>
Commit: Ismael Luceno <ismael AT sourcemage.org>
Initial slots implementation
diff --git a/etc/sorcery/config b/etc/sorcery/config
index c555750..83f0b3c 100755
--- a/etc/sorcery/config
+++ b/etc/sorcery/config
@@ -142,6 +142,7 @@
GRIMOIRE_MANIFEST_ALGORITHM=${GRIMOIRE_MANIFEST_ALGORITHM:-sha1}
PROVIDE_INDEX_FILE=${PROVIDE_INDEX_FILE:-provides.index}
KEYWORD_INDEX_FILE=${KEYWORD_INDEX_FILE:-keyword.index}
VERSION_INDEX_FILE=${VERSION_INDEX_FILE:-version.index}
+ SLOT_INDEX_FILE=${SLOT_INDEX_FILE:-slot.index}
LOCK_DIR=${LOCK_DIR:-/tmp/liblock-$UID}
LOCK_TRANSACTIONS=${LOCK_TRANSACTIONS:-"$LOCK_DIR/liblock.locklist"}
diff --git a/usr/sbin/alter b/usr/sbin/alter
index f15f1ba..787e20e 100755
--- a/usr/sbin/alter
+++ b/usr/sbin/alter
@@ -23,23 +23,28 @@ EOF
source /etc/sorcery/config
SPELL=${SPELL:=alter}
- lock_resources "cast" "$SPELL"
- VERSION="$(gaze -q installed "$SPELL" | grep -v 'not installed')"
+ # -n may name a slot, and then the logs and the cache belong to that
+ # one install, not to every slot of the spell
+ state_canon_instance "$SPELL" SPELL_INSTANCE &&
+ slot_instance_tag "$SPELL_INSTANCE" SPELL_DISK_TAG &&
+ slot_split "$SPELL_INSTANCE" SPELL || return 1
+ lock_resources "cast" "$SPELL_INSTANCE"
+ VERSION="$(gaze -q installed "$SPELL_INSTANCE" | grep -v 'not installed')"
[ -z "$VERSION" ] && VERSION=$$
- TMP_LOG=$TMP_DIR/$SPELL-$VERSION
- IW_LOG=$TMP_DIR/$SPELL-$VERSION.iw
- INST_LOG=$INSTALL_LOGS/$SPELL-$VERSION
- MD5_LOG=$MD5SUM_LOGS/$SPELL-$VERSION
- C_LOG=$TMP_DIR/$SPELL-$VERSION.compile.log
- C_LOG_COMP=$COMPILE_LOGS/$SPELL-$VERSION$EXTENSION
- construct_cache_name "$INSTALL_CACHE/$SPELL-$VERSION-$HOST" CACHE
+ TMP_LOG=$TMP_DIR/$SPELL_DISK_TAG-$VERSION
+ IW_LOG=$TMP_DIR/$SPELL_DISK_TAG-$VERSION.iw
+ INST_LOG=$INSTALL_LOGS/$SPELL_DISK_TAG-$VERSION
+ MD5_LOG=$MD5SUM_LOGS/$SPELL_DISK_TAG-$VERSION
+ C_LOG=$TMP_DIR/$SPELL_DISK_TAG-$VERSION.compile.log
+ C_LOG_COMP=$COMPILE_LOGS/$SPELL_DISK_TAG-$VERSION$EXTENSION
+ construct_cache_name "$INSTALL_CACHE/$SPELL_DISK_TAG-$VERSION-$HOST" CACHE
CACHE_COMP=${CACHE}${EXTENSION}
rm -f "$C_LOG" 2> /dev/null
touch "$C_LOG"
- message "${MESSAGE_COLOR}Gathering metadata for
${SPELL_COLOR}$SPELL${DEFAULT_COLOR}${MESSAGE_COLOR}.${DEFAULT_COLOR}"
- DATA="$(gaze compile $SPELL 2> /dev/null)" && COM_DATA="$DATA" ||
COM_DATA=""
- DATA="$(gaze install $SPELL 2> /dev/null)" && INS_DATA="$DATA" ||
INS_DATA=""
- DATA="$(gaze md5sum $SPELL 2> /dev/null)" && MD5_DATA="$DATA" ||
MD5_DATA=""
+ message "${MESSAGE_COLOR}Gathering metadata for
${SPELL_COLOR}$SPELL_INSTANCE${DEFAULT_COLOR}${MESSAGE_COLOR}.${DEFAULT_COLOR}"
+ DATA="$(gaze compile $SPELL_INSTANCE 2> /dev/null)" && COM_DATA="$DATA" ||
COM_DATA=""
+ DATA="$(gaze install $SPELL_INSTANCE 2> /dev/null)" && INS_DATA="$DATA" ||
INS_DATA=""
+ DATA="$(gaze md5sum $SPELL_INSTANCE 2> /dev/null)" && MD5_DATA="$DATA" ||
MD5_DATA=""
[ -n "$COM_DATA" ] && echo "$COM_DATA" > "$C_LOG"
message "${MESSAGE_COLOR}Altering...${DEFAULT_COLOR}"
@@ -68,16 +73,16 @@ EOF
create_compile_log
create_md5list $INST_LOG $MD5_LOG
create_cache_archive $INST_LOG $CACHE $CACHE_COMP
- add_spell "$SPELL" installed "$VERSION"
+ add_spell "$SPELL_INSTANCE" installed "$VERSION"
OLD_VERSION=$VERSION
(
- [[ $SPELL != alter ]] && codex_set_current_spell_by_name $SPELL
- add_version_cache "$VERSION_STATUS" "$SPELL" "$OLD_VERSION"
"${PATCHLEVEL:-0}" "${SECURITY_PATCH:-0}" "${UPDATED:-0}"
+ [[ $SPELL != alter ]] && codex_set_current_spell_by_name $SPELL_INSTANCE
+ add_version_cache "$VERSION_STATUS" "$SPELL_INSTANCE" "$OLD_VERSION"
"${PATCHLEVEL:-0}" "${SECURITY_PATCH:-0}" "${UPDATED:-0}"
)
rm $C_LOG
report_install
- unlock_resources "cast" "$SPELL"
+ unlock_resources "cast" "$SPELL_INSTANCE"
return 0
@@ -89,12 +94,15 @@ alter_md5_mend() {
STORE_BASE=${STORE_BASE:=/var/log/sorcery/tmp} # */tmp are often small
# by default for the backup
SPELL="$1"
+ # the md5 logs are named after the install, so an argument naming a
+ # slot has to become a disk tag before it names a file
+ slot_instance_tag "$SPELL" SPELL_DISK_TAG || return 1
VERSION="$(gaze -q installed "$SPELL")"
MD5S="$(
(
- echo "$MASTER_MD5S" | grep -v " $SPELL-$VERSION$"
- cat "$MD5SUM_LOGS/$SPELL-$VERSION" | cut -c35- | sed -e 's/.*/& -/'
+ echo "$MASTER_MD5S" | grep -v " $SPELL_DISK_TAG-$VERSION$"
+ cat "$MD5SUM_LOGS/$SPELL_DISK_TAG-$VERSION" | cut -c35- | sed -e
's/.*/& -/'
)
)"
@@ -127,7 +135,7 @@ alter_md5_mend() {
TOTSP="$(echo "$SPELLS" | wc -l)"
FILES="$(echo "$RESULTS" | cut -d' ' -f2 | sort -u)"
if [ "$ARCHIVE" != "off" ]; then
- STORE=$STORE_BASE/$SPELL-$VERSION.$$
+ STORE=$STORE_BASE/$SPELL_DISK_TAG-$VERSION.$$
mkdir -p "$STORE"
mkdir -p "$STORE/tmp/sorcery"
message "${MESSAGE_COLOR}Cleaning $TOTAL old md5sums from $TOTSP
spells and creating $SPELL-undo-$VERSION store.${DEFAULT_COLOR}" > /dev/stderr
@@ -165,15 +173,18 @@ alter_md5_mend() {
message "${MESSAGE_COLOR}Creating
${SPELL_COLOR}$SPELL-undo${DEFAULT_COLOR}${MESSAGE_COLOR} spell for
${SPELL_COLOR}$SPELL-$VERSION${DEFAULT_COLOR}${MESSAGE_COLOR}.${DEFAULT_COLOR}"
> /dev/stderr
ORIGSPELL="$SPELL"
SPELL="$SPELL-undo"
+ # the undo spell is one per install, so it is the tag that grows
+ # the suffix; "gcc:15:native-undo" would name no file at all
+ SPELL_DISK_TAG="$SPELL_DISK_TAG-undo"
cd "$STORE"
INST_LOG="$STORE.log"
mkdir -p "$STORE$MD5SUM_LOGS"
- MD5S_LOG="$STORE$MD5SUM_LOGS/$SPELL-$VERSION"
+ MD5S_LOG="$STORE$MD5SUM_LOGS/$SPELL_DISK_TAG-$VERSION"
touch "$MD5S_LOG"
- echo -n "$RESULTS" > "$STORE/tmp/sorcery/$SPELL-$VERSION-backup.md5"
+ echo -n "$RESULTS" >
"$STORE/tmp/sorcery/$SPELL_DISK_TAG-$VERSION-backup.md5"
find * > "$INST_LOG"
create_md5list "$INST_LOG" "$MD5S_LOG" # relative to STORE instead of
absolute
- construct_cache_name "$INSTALL_CACHE/$SPELL-$VERSION-$HOST" CACHE
+ construct_cache_name "$INSTALL_CACHE/$SPELL_DISK_TAG-$VERSION-$HOST"
CACHE
CACHE_COMP="${CACHE}${EXTENSION}"
# No install log nor compile cache needed or wanted since we are
# only doing md5sum deletions from other spells, the undeleted
@@ -199,9 +210,13 @@ alter_gather_md5s() {
MASTER_MD5S="$(
(
COUNT=0
+ # an instance holds colons of its own, so the name of its md5 log
+ # is the fields before the date joined with '@', and the version
for i in $(
gaze installed |
- cut -d: -f1,4 | tr : -
+ awk -F: '{ tag = $1
+ for (i = 2; i <= NF - 3; i++) tag = tag "@" $i
+ print tag "-" $NF }'
); do
cat "$MD5SUM_LOGS/$i" | sed -e 's/.*/'"$i"':&/'
progress_bar $((++COUNT)) $TOTAL 60 > /dev/stderr
diff --git a/usr/sbin/cast b/usr/sbin/cast
index 51441ef..ae9c3dc 100755
--- a/usr/sbin/cast
+++ b/usr/sbin/cast
@@ -216,7 +216,7 @@ cast_spell() { (
run_details
load_build_api || return 1
- dispel_conflicts $SPELL || return 1
+ dispel_conflicts $SPELL_INSTANCE || return 1
# show_downloading tail -f's (essentially) the download log as it
# comes in, or if downloading is complete, cats the download log, in
@@ -224,7 +224,7 @@ cast_spell() { (
# a ${download_log}.done file. If SEPARATE is set, then all downloading
# is done first, so there is no need to wait for it to complete.
if ! [ "$SEPARATE" ]; then
- show_downloading $SPELL
+ show_downloading $SPELL_INSTANCE
fi
verify_sources || return 1
@@ -234,10 +234,10 @@ cast_spell() { (
# all this needs to reorganized and stuff...later
- export IW_LOG="$TMP_DIR/$SPELL.iw"
+ export IW_LOG="$TMP_DIR/$SPELL_DISK_TAG.iw"
#some minor discussion occured about having this, i'll leave it out for now
- activity_log "cast" "$SPELL" "$VERSION" "start"
+ activity_log "cast" "$SPELL_INSTANCE" "$VERSION" "start"
# must declare OPTS before sourcing config
local OPTS
@@ -247,11 +247,11 @@ cast_spell() { (
libtrack_init
local spell_depends spell_sub_depends
- get_uncommitted_depends_file $SPELL spell_depends
+ get_uncommitted_depends_file "$SPELL_INSTANCE" spell_depends
test -e $spell_depends &&
- OPTS="$OPTS $(get_depends_options $spell_depends $SPELL)"
+ OPTS="$OPTS $(get_depends_options $spell_depends "$SPELL_INSTANCE")"
- get_uncommitted_sub_depends_file $SPELL spell_sub_depends
+ get_uncommitted_sub_depends_file "$SPELL_INSTANCE" spell_sub_depends
test -e $spell_sub_depends &&
local PROCESSED_SUB_DEPENDS=$(cut -f3 -d: $spell_sub_depends|tr '\n' ' ')
@@ -286,7 +286,7 @@ cast_spell() { (
fi
- unlock_resources "cast" "$SPELL"
+ unlock_resources "cast" "${SPELL_INSTANCE:-$SPELL}"
#It's OK to try to release a lock you don't have
unlock_resources "solo" "cast"
@@ -499,9 +499,9 @@ pass_two() {
}
#---------------------------------------------------------------------
-## @param spellname
-## @Globals SPELL
-## Sets SPELL to the spellname and calls summon_spell with spellname
+## @param spell instance
+## @Globals SPELL SPELL_INSTANCE
+## Sets SPELL to the spellname and calls summon_spell with the instance
## as argument. This is equivalent to calling summon, but without the
## overhead.
## (Is being called by make)
@@ -511,17 +511,19 @@ pass_three() {
debug "cast" "pass_three - $*"
unset CAST_PASS
trap "exit 1" INT TERM
- local SPELL=$1
+ local SPELL_INSTANCE=$1
+ local SPELL
+ slot_split "$1" SPELL || { slot_bad_token_message "$1"; return 1; }
# download_log is removed with it's .done in libcast
- local download_log=$(get_spell_dl_log $SPELL)
+ local download_log=$(get_spell_dl_log $SPELL_INSTANCE)
if [[ $SCREEN_NAME ]] && [ ! -p $TMP_DIR/download.fifo ] ; then
# Create the summon window
mkfifo $TMP_DIR/download.fifo
# Neither tail not cat work properly on FIFOs so a while loop will
# have to be used
- screen_new_window "$SCREEN_NAME" $SCREEN_SUMMON_WIN "Summon $SPELL" \
+ screen_new_window "$SCREEN_NAME" $SCREEN_SUMMON_WIN "Summon
$SPELL_INSTANCE" \
/bin/bash -c 'while : ; do
read LINE && echo $LINE || sleep 0.1 ;
done < '$TMP_DIR/download.fifo
@@ -529,7 +531,7 @@ pass_three() {
fi
if [[ $SCREEN_NAME ]] ; then
# A summon window is already up, just change the name
- screen_name_window "$SCREEN_NAME" $SCREEN_SUMMON_WIN "Summon $SPELL"
+ screen_name_window "$SCREEN_NAME" $SCREEN_SUMMON_WIN "Summon
$SPELL_INSTANCE"
# symlink the d/l log to the fifo
ln -s $TMP_DIR/download.fifo $download_log
else
@@ -540,10 +542,10 @@ pass_three() {
# check if this is being resurrected and if so, don't summon
# this will go away when resurrect support and dependency resolution merge
if ! [[ $COMPILE ]] ; then
- VERSION=$(codex_set_current_spell_by_name $SPELL &> /dev/null;echo
$VERSION)
- if [[ $VERSION ]] && can_resurrect $SPELL $VERSION &> /dev/null ; then
+ VERSION=$(codex_set_current_spell_by_name $SPELL_INSTANCE &>
/dev/null;echo $VERSION)
+ if [[ $VERSION ]] && can_resurrect $SPELL_INSTANCE $VERSION &> /dev/null
; then
# The 2>/dev/null is necessary because the dir may not exist
- echo "pass_three thought that $SPELL was being resurrected" \
+ echo "pass_three thought that $SPELL_INSTANCE was being resurrected" \
> $download_log 2>/dev/null
# touch the 'log' file just in case the spell disagreed so cast doesnt
# hang forever
@@ -553,10 +555,10 @@ pass_three() {
fi
if [ "$SEPARATE" ]; then
- summon_spell "$SPELL" &> "$download_log"
+ summon_spell "$SPELL_INSTANCE" &> "$download_log"
else
lock_file $download_log
- summon_spell "$SPELL" &> "$download_log"
+ summon_spell "$SPELL_INSTANCE" &> "$download_log"
unlock_file $download_log
fi
@@ -566,9 +568,9 @@ pass_three() {
}
#---------------------------------------------------------------------
-## @param spellname
-## @Globals SPELL COMPILE
-## Sets SPELL to spellname.
+## @param spell instance
+## @Globals SPELL SPELL_INSTANCE COMPILE
+## Sets SPELL to the spellname and SPELL_INSTANCE to the whole token.
## Calls trigger "pre_cast"
## If COMPILE is not set, the spell doesn't need an update and it can
## be resurrected (can_resurrect SPELL) it calls resurrect with SPELL.
@@ -582,18 +584,19 @@ pass_four() {
local do_resurrect=no
local VERSION
unset CAST_PASS
- SPELL=$1
+ SPELL_INSTANCE=$1
+ slot_split "$1" SPELL || { slot_bad_token_message "$1"; return 1; }
trigger "pre_cast"
if ! [[ $COMPILE ]] ; then
# hacky way to get the latest version
- VERSION=$(codex_set_current_spell_by_name $SPELL &> /dev/null;echo
$VERSION)
+ VERSION=$(codex_set_current_spell_by_name $SPELL_INSTANCE &>
/dev/null;echo $VERSION)
if [[ ! $VERSION ]] ; then
message "Can't find spell version for some reason, is $SPELL a spell?"
else
- can_resurrect $SPELL $VERSION &> /dev/null &&
- ! does_spell_need_update $SPELL &> /dev/null &&
+ can_resurrect $SPELL_INSTANCE $VERSION &> /dev/null &&
+ ! does_spell_need_update $SPELL_INSTANCE &> /dev/null &&
do_resurrect=yes
fi
fi
@@ -601,22 +604,22 @@ pass_four() {
# this is a hacky way to tell what version we're updating from
# if any, we can use it to find md5 logs for installing config type
# files, it mostly is here for libresurrect.real_install_config_file
- local OLD_SPELL_VERSION=$(installed_version $SPELL)
+ local OLD_SPELL_VERSION=$(installed_version $SPELL_INSTANCE)
local rc
if [[ "$do_resurrect" == "yes" ]] ; then
- resurrect_spell $SPELL $VERSION
+ resurrect_spell $SPELL_INSTANCE $VERSION
rc=$?
# this is here for safety in case resurrect bails out somewhere
# we didnt expect it to
unlock_resources "libgrimoire" "install"
- unlock_resources "cast" "$SPELL"
+ unlock_resources "cast" "$SPELL_INSTANCE"
unlock_resources "solo" "cast"
return $rc
else
cast_spell $*
fi
- debug "cast" "pass_four done for $SPELL"
+ debug "cast" "pass_four done for $SPELL_INSTANCE"
}
@@ -755,10 +758,12 @@ pass_five() {
run_hook cast_final "" $rc
# check if confmeld should be run
- local spell deferred_list
+ local spell tag deferred_list
while read spell; do
+ # the list holds instances, the stage is filed under the disk tag
+ slot_instance_tag "$spell" tag || tag=$spell
# ignore the date - previously deferred files should be merged too
- if [[ -d $CONFIG_STAGE_DIRECTORY/$spell ]]; then
+ if [[ -d $CONFIG_STAGE_DIRECTORY/$tag ]]; then
deferred_list="$spell $deferred_list"
fi
done < $SUCCESS_LIST
diff --git a/usr/sbin/cleanse b/usr/sbin/cleanse
index b9aa23d..be39c5e 100755
--- a/usr/sbin/cleanse
+++ b/usr/sbin/cleanse
@@ -228,13 +228,17 @@ delint()
generic_remove_stale "$DEPENDS_STATUS" depends two
message "Pass three (duplicate depends entries)"
- for LINE in $( sed 's/^\([^:]*\):\([^:]*\):.*$/\1:\2/;tp;d;:p;s/+/\\+/'
"$DEPENDS_STATUS" | sort | uniq -d ) ; do
+ # a row is one requester's word about one dependency, and either may
+ # name a slot -- which lives at the end of the row, so the key is not
+ # a prefix of it and nothing here can be done by anchored greps
+ for LINE in $( awk -F: -- '{print $1":"$2":"$7":"$8}' "$DEPENDS_STATUS" |
+ sort | uniq -d ) ; do
message
options=()
while read v opt; do
options[$v]="$opt"
- done < <(awk -v line="^$LINE:" -- '
- $0 ~ line && !($0 in seen) {print count++, $0; seen[$0]}
+ done < <(awk -F: -v key="$LINE" -- '
+ key == $1":"$2":"$7":"$8 && !($0 in seen) {print count++, $0;
seen[$0]}
' "$DEPENDS_STATUS")
if [ ${#options[@]} -gt 1 ] ; then
@@ -244,7 +248,8 @@ delint()
choice="${options[0]}"
message "Identical lines removed: $choice"
fi
- grep -Ev "^$LINE:" $DEPENDS_STATUS > $TMP_DIR/delint.3
+ awk -F: -v key="$LINE" -- 'key != $1":"$2":"$7":"$8' \
+ $DEPENDS_STATUS > $TMP_DIR/delint.3
echo "$choice" >> $TMP_DIR/delint.3
mv $TMP_DIR/delint.3 $DEPENDS_STATUS
done
@@ -284,13 +289,35 @@ generic_remove_stale() {
message "Pass $3 (stale $2 entries)"
local tmp=$TMP_DIR/delint.$2.stale
local t
- awk -F: -v status="$SPELL_STATUS" '
+ # a row is stale when the instance that owns it is not installed, and
+ # the instance is not the first field: a depends row keeps the slot in
+ # its seventh, a sub-depends row in its fourth, and a trigger row
+ # spells it out, colons and all, in front of the cause
+ local slotf=0
+ case $2 in
+ depends) slotf=7 ;;
+ sub_depends) slotf=4 ;;
+ esac
+ awk -F: -v status="$SPELL_STATUS" -v slotf="$slotf" -v kind="$2" '
+ function inst(name, slot) {
+ return (slot == "" || slot == "0") ? name : name "@" slot
+ }
+ function owner( i, j, s) {
+ if (kind != "trigger")
+ return inst($1, slotf ? $slotf : "")
+ for (i = 1; i <= NF; i++)
+ if ($i ~ /^on_(pre_)?(cast|dispel)$/) break
+ if (i > NF) return $1
+ s = $1
+ for (j = 2; j <= i - 2; j++) s = s "@" $j
+ return s
+ }
BEGIN {
- while (getline < status)
- Installed[$1]
+ while ((getline < status) > 0)
+ Installed[inst($1, $5)]
}
{
- if (!($1 in Installed))
+ if (!(owner() in Installed))
print $0
}' "$1" |tee $tmp
t=$(wc -l $tmp | awk '{print $1}')
@@ -308,6 +335,46 @@ generic_remove_stale() {
}
#---------------------
+## Revise the row two instances share: either turn the dependency off,
+## or hand it to another provider. The slots sit at the end of a row,
+## so an anchored sed can no longer tell one row from its siblings.
+## @param off or provider
+## @param the requester, an instance
+## @param the dependency, an instance
+## @param the dependency's type, when turning it off; its provider
+## tag, when handing it over
+## @param the new provider, when handing it over
+#---------------------
+prune_revise()
+{
+ local mode=$1 arg=$4 new=$5
+ local req_spell req_slot dep_spell dep_slot
+
+ slot_split "$2" req_spell req_slot &&
+ slot_split "$3" dep_spell dep_slot || return 1
+
+ if slot_is_default "$req_slot"; then req_slot=""
+ else slot_pack "$req_slot" req_slot; fi
+ if slot_is_default "$dep_slot"; then dep_slot=""
+ else slot_pack "$dep_slot" dep_slot; fi
+
+ # a row that says nothing about slots leaves both fields off the end
+ awk -F: -v OFS=: -v mode="$mode" -v arg="$arg" -v new="$new" \
+ -v req="$req_spell" -v rs="$req_slot" \
+ -v dep="$dep_spell" -v ds="$dep_slot" '
+ function norm(s) { return (s == "0") ? "" : s }
+ function name(f) { sub(/[(].*/, "", f); return f }
+ $1 == req && norm($7) == rs && norm($8) == ds && name($2) == dep {
+ if (mode == "off" && $3 == "on" && $4 == arg)
+ $3 = "off"
+ else if (mode == "provider" && $2 == dep "(" arg ")")
+ $2 = new "(" arg ")"
+ }
+ 1' $DEPENDS_STATUS > $TMP_DIR/prune.1 &&
+ mv $TMP_DIR/prune.1 $DEPENDS_STATUS
+}
+
+#---------------------
## Find missing spells, do the actions of "doit" is specified
## @param if set to "doit" it'll actual cast and dispel
## @Stdout questions about what to do about problems
@@ -331,10 +398,17 @@ prune_depends()
# Get a list of bad dependencies
# Output is in the form "SPELL DEPENDENCY optional/reqired PROVIDER"
+ # both ends of a row are instances, with their slots at the end of it;
+ # what is missing is one slot of a spell, and the other slots of that
+ # spell are no answer to it
bad=( $( awk -F: -v status="$SPELL_STATUS" '
+ function inst(name, slot) {
+ return (slot == "" || slot == "0") ? name : name "@" slot
+ }
+ function show(t) { gsub(/@/, ":", t); return t }
BEGIN {
- while (getline < status == 1)
- Installed[$1]
+ while ((getline < status) > 0)
+ Installed[inst($1, $5)]
}
$3 == "on" {
if (match($2, /[(][^):]*[)]/)) {
@@ -344,8 +418,8 @@ prune_depends()
prov = "__BLANK__"
dep = $2
}
- if ($1 in Installed && dep != "" && !(dep in Installed))
- print $1, dep, $4, prov
+ if (inst($1, $7) in Installed && dep != "" && !(inst(dep, $8) in
Installed))
+ print show(inst($1, $7)), show(inst(dep, $8)), $4, prov
}' $DEPENDS_STATUS ) )
unlock_file $SPELL_STATUS
@@ -400,15 +474,12 @@ prune_depends()
C*) echo $2 >> $TMP_DIR/prune.cast ;;
R*) echo $1 >> $TMP_DIR/prune.cast
# Revise choice in the depends file
- sed 's/^\('$1':'$2':\)on\(:optional:.*\)$/\1off\2/' $DEPENDS_STATUS
> $TMP_DIR/prune.1
- mv $TMP_DIR/prune.1 $DEPENDS_STATUS
+ prune_revise off "$1" "$2" optional
;;
Disable*)
- sed 's/^\('$1':'$2':\)on\(:suggest:.*\)$/\1off\2/' $DEPENDS_STATUS >
$TMP_DIR/prune.1
- mv $TMP_DIR/prune.1 $DEPENDS_STATUS
+ prune_revise off "$1" "$2" suggest
;;
- S*) sed 's/^\('$1':\)'$2'\(('$4'):.*\)$/\1'$provider'\2/'
$DEPENDS_STATUS > $TMP_DIR/prune.1
- mv $TMP_DIR/prune.1 $DEPENDS_STATUS
+ S*) prune_revise provider "$1" "$2" "$4" "$provider"
if [[ $3 == required ]] || [[ $3 == optional ]]; then
echo $1 >>$TMP_DIR/prune.cast
fi
@@ -459,9 +530,11 @@ packages()
lock_file $SPELL_STATUS
message "Pass 1 (bad lines)"
- sed -n '/^[^:]*:[^:]*:[^:]*:[^:]*$/!p' $SPELL_STATUS >
$TMP_DIR/packages.bad
+ # a slot, and then a subslot, may follow the version
+ local good='/^[^:]*:[^:]*:[^:]*:[^:]*\(:[^:]*\)\{0,2\}$/'
+ sed -n "$good"'!p' $SPELL_STATUS > $TMP_DIR/packages.bad
if [ -s $TMP_DIR/packages.bad ] ; then
- sed -n '/^[^:]*:[^:]*:[^:]*:[^:]*$/p' $SPELL_STATUS >
$TMP_DIR/packages.good
+ sed -n "$good"p $SPELL_STATUS > $TMP_DIR/packages.good
echo "$(wc -l $TMP_DIR/packages.bad) bad lines, $(wc -l
$TMP_DIR/packages.good) good lines in your packages file."
t=$(date +%Y%m%d%s)
message "\tSaving bad lines to $BACKUPDIR/packages.1.bad.$t"
@@ -487,13 +560,14 @@ packages()
fi
message "Pass 3 (duplicate entries)"
+ # two rows of one spell in two slots are not duplicates of each other
t=$(
- for LINE in $( sed -n 's/^\([^:]*\):\([^:]*\):.*$/\1/p' $SPELL_STATUS |
sort | uniq -d ) ; do
- grep "^$LINE:" $SPELL_STATUS | sed 's/^/\t/'
- done
+ awk -F: -- 'FNR == NR { Count[$1":"$5]++; next }
+ Count[$1":"$5] > 1 { print "\t" $0 }' \
+ $SPELL_STATUS $SPELL_STATUS
)
if [[ $t ]] && query "\tRemove all but one of each set of duplicates?" "n"
; then
- awk -F: '!($1 in LINES){LINES[$1]; print}' $SPELL_STATUS >
$TMP_DIR/packages.3
+ awk -F: '!($1":"$5 in LINES){LINES[$1":"$5]; print}' $SPELL_STATUS >
$TMP_DIR/packages.3
message "\tRemoving bad entries"
mv $TMP_DIR/packages.3 $SPELL_STATUS
fi
@@ -721,7 +795,9 @@ cleanse_fix_find_check() {
fi
local SPELL=$1
- local I_LOG="$INSTALL_LOGS/$SPELL-$2"
+ local tag
+ slot_instance_tag "$SPELL" tag || tag=$SPELL
+ local I_LOG="$INSTALL_LOGS/$tag-$2"
local BADLIST=""
local LINE
@@ -779,13 +855,14 @@ cleanse_fix_init_ldd_check() {
return 0
fi
local SPELL
- local I_LOG
+ local I_LOG tag
local size count
let count=0
message "Doing initialization for the ldd check"
size=$#
for SPELL in $* ; do
- I_LOG=$INSTALL_LOGS/$SPELL-$(private_installed_version $SPELL)
+ slot_instance_tag "$SPELL" tag || tag=$SPELL
+ I_LOG=$INSTALL_LOGS/$tag-$(private_installed_version $SPELL)
hash_put ldd_hash $SPELL "$(grep '\.so$' $I_LOG|get_dirnames|sort -u|tr
'\n' :)"
progress_bar $count $size 50
let count++
@@ -807,7 +884,9 @@ cleanse_fix_ldd_check() { (
message "\tLibrary test"
local SPELL=$1
- local I_LOG=$INSTALL_LOGS/$SPELL-$2
+ local tag
+ slot_instance_tag "$SPELL" tag || tag=$SPELL
+ local I_LOG=$INSTALL_LOGS/$tag-$2
local BADLIST=""
local LINE
@@ -877,7 +956,9 @@ cleanse_fix_sym_check() {
return 0
fi
local SPELL=$1
- local I_LOG="$INSTALL_LOGS/$SPELL-$2"
+ local tag
+ slot_instance_tag "$SPELL" tag || tag=$SPELL
+ local I_LOG="$INSTALL_LOGS/$tag-$2"
local BADLIST=""
local LINE
@@ -937,11 +1018,13 @@ cleanse_fix_md5sum_check() {
return 0
fi
local SPELL=$1
- local MD5_LOG="$MD5SUM_LOGS/$SPELL-$2"
+ local tag
+ slot_instance_tag "$SPELL" tag || tag=$SPELL
+ local MD5_LOG="$MD5SUM_LOGS/$tag-$2"
local BADLIST
- local TMP_MD5_LOG=$TMP_DIR/cleanse.md5sum.tmp.$SPELL.$$
- local BROKEN1=$TMP_DIR/cleanse.md5sum.broken1.$SPELL.$$
- local BROKEN2=$TMP_DIR/cleanse.md5sum.broken2.$SPELL.$$
+ local TMP_MD5_LOG=$TMP_DIR/cleanse.md5sum.tmp.$tag.$$
+ local BROKEN1=$TMP_DIR/cleanse.md5sum.broken1.$tag.$$
+ local BROKEN2=$TMP_DIR/cleanse.md5sum.broken2.$tag.$$
local A=$'\a' # magic sed seperator, assume files dont have bells in the
name
# Ignore standard paths we know will change, the install log, file log,
@@ -998,7 +1081,11 @@ cleanse_fix_md5sum_check() {
# this actually isnt a very good assumption but works enough
# of the time
each_spell=$(smgl_basename $each|awk -F- '{for
(i=1;i<NF-1;i++) {printf "%s-",$i}printf "%s\n",$i}')
- each_version=$(private_installed_version $each_spell)
+ # what the log is named after is an install, and what is
+ # installed is asked about by instance
+ slot_tag_instance "$each_spell" each_instance ||
+ each_instance=$each_spell
+ each_version=$(private_installed_version $each_instance)
[[ $each_version ]] &&
[[ $MD5SUM_LOGS/${each_spell}-${each_version} == $each ]] &&
echo $each
@@ -1016,7 +1103,7 @@ cleanse_fix_md5sum_check() {
if [ $FIX == fix ] || [ $FIX == quick ]; then
message "Removing from $SPELL's install and md5sum logs."
- INSTALL_LOG=$INSTALL_LOGS/$SPELL-$2
+ INSTALL_LOG=$INSTALL_LOGS/$tag-$2
# there is a slight bug with all these transactions due to
# the fact that we have no stdin, if a transaction fails the user
# is prompted, but since we're in this funny loop, it'll read from
@@ -1035,13 +1122,13 @@ cleanse_fix_md5sum_check() {
test -d $POSSESSED_LOGS || mkdir -p $POSSESSED_LOGS
- possessed=$POSSESSED_LOGS/$SPELL-$2
+ possessed=$POSSESSED_LOGS/$tag-$2
lock_start_transaction "$possessed" tfile
echo $LINE >> $tfile
# remove duplicates
- cp $tfile $TMP_DIR/$SPELL.possessed.$$
- sort -u $TMP_DIR/$SPELL.possessed.$$ > $tfile
- rm $TMP_DIR/$SPELL.possessed.$$
+ cp $tfile $TMP_DIR/$tag.possessed.$$
+ sort -u $TMP_DIR/$tag.possessed.$$ > $tfile
+ rm $TMP_DIR/$tag.possessed.$$
lock_commit_transaction $possessed
message -n '.'
diff --git a/usr/sbin/confmeld b/usr/sbin/confmeld
index 309e7f3..9bd2c98 100755
--- a/usr/sbin/confmeld
+++ b/usr/sbin/confmeld
@@ -35,7 +35,9 @@ use-new backup the old config and install
the new one in its plac
Optional arguments:
You can invoke confmeld with a spell name or a staged config as an argument.
-Confmeld will then work only on that argument.
+Confmeld will then work only on that argument. A spell name may carry a
+slot -- confmeld gcc:14:native -- and one that does not is every slot of
+that spell with configs staged.
EOF
exit 1
@@ -64,9 +66,14 @@ all_uniq_config_files()
}
#--------------------------------------------------------------------
-## list all spells that have staged configs
+## list all installs that have staged configs
##
-## @stdout list of spells that have staged configs
+## @stdout list of the disk tags of the installs with staged configs
+##
+## The stage is filed under a disk tag, so that two slots of a spell
+## staging the same config file do not stage it over each other. What
+## comes out here names a file and not a spell -- see slot_tag_instance
+## for the other half.
#--------------------------------------------------------------------
all_spells_with_staged_configs()
{
@@ -93,7 +100,9 @@ maybe_column() {
#--------------------------------------------------------------------
list_spells_with_staged_configs()
{
- local spells=$(all_spells_with_staged_configs)
+ # a tag differs from the instance it names in its separator alone,
+ # and '@' is a character no spell name holds
+ local spells=$(all_spells_with_staged_configs | tr @ :)
if [[ -n $spells ]]; then
message "${MESSAGE_COLOR}Spells with staged configuration
files:$SPELL_COLOR"
@@ -127,9 +136,12 @@ list_staged_configs()
#--------------------------------------------------------------------
list_all()
{
- local spell config
+ local spell config instance
for spell in $(all_spells_with_staged_configs); do
- message "${SPELL_COLOR}$spell:$FILE_COLOR"
+ # the directory is named after an install: by its tag on disk, and
+ # by its instance to the user
+ slot_tag_instance "$spell" instance || instance=$spell
+ message "${SPELL_COLOR}$instance:$FILE_COLOR"
all_config_files |
sed -n "s,^$CONFIG_STAGE_DIRECTORY/$spell/[^/]*,,p" |
maybe_column
@@ -142,7 +154,7 @@ list_all()
## parses the path to get specific information about the config file
##
## @param config file path
-## @param upvar for spell name
+## @param upvar for the disk tag of the install that staged it
## @param upvar for spell success time
## @param upvar for actual path to file
#--------------------------------------------------------------------
@@ -196,19 +208,22 @@ final_cleanup()
#--------------------------------------------------------------------
merge_single_file()
{
- local file=$1 spell sdate target nice_date
+ local file=$1 spell sdate target nice_date instance
parse_stage_config_path $file spell sdate target &&
if [[ -f $file && $target ]]
then
+ # what staged the file is an install: the tag names its stage, the
+ # instance names its tablet
+ slot_tag_instance "$spell" instance || instance=$spell
# make the $sdate human readable
# date -d can't use the whole original, so we have to do it in steps
nice_date=$(date -d ${sdate:0:8} "+%a %d %b %Y")
nice_date="$nice_date $(date -d ${sdate:8:4} "+%X %Z")"
- message "${SPELL_COLOR}$spell$DEFAULT_COLOR cast on $nice_date has
config"
+ message "${SPELL_COLOR}$instance$DEFAULT_COLOR cast on $nice_date has
config"
message "${FILE_COLOR}$file$DEFAULT_COLOR"
message "to be merged with $FILE_COLOR/$target${DEFAULT_COLOR}\n"
- tablet_set_spell $spell &&
+ tablet_set_spell $instance &&
install_config_file $file $INSTALL_ROOT/$target
recursive_config_stage_cleanup $file $spell
echo -e "\n"
@@ -223,12 +238,17 @@ merge_single_file()
#--------------------------------------------------------------------
confmeld_partial()
{
- local thing config configs rc=0
+ local thing config configs rc=0 tag
for thing in "$@"; do
- if codex_does_spell_exist $thing &> /dev/null; then
- # we got a spell
- configs=$(find $CONFIG_STAGE_DIRECTORY/$thing -type f)
+ if codex_does_spell_exist $thing &> /dev/null &&
+ slot_instance_tag $thing tag; then
+ # we got a spell. A name that leaves the slot out is every slot
+ # of it with something staged, since the stage is filed under the
+ # tag of one install and which of them was meant is not ours to
+ # guess
+ configs=$(all_config_files |
+ grep "^$CONFIG_STAGE_DIRECTORY/$tag\(@[^/]*\)*/")
else
# we got a config or three
configs=$(find $CONFIG_STAGE_DIRECTORY -type f -wholename "*$thing")
diff --git a/usr/sbin/delve b/usr/sbin/delve
index 1c1360a..9791b5f 100755
--- a/usr/sbin/delve
+++ b/usr/sbin/delve
@@ -85,26 +85,28 @@ delve_log_helper() {
local OPTS
run_spell_config
- get_uncommitted_depends_file $SPELL spell_depends
- lock_file $ABANDONED_DEPENDS/$SPELL
- if test -e $spell_depends && test -e $ABANDONED_DEPENDS/$SPELL ; then
- cp $ABANDONED_DEPENDS/$SPELL $spell_depends
+ get_uncommitted_depends_file "$SPELL_INSTANCE" spell_depends
+ lock_file $ABANDONED_DEPENDS/$SPELL_DISK_TAG
+ if test -e $spell_depends && test -e $ABANDONED_DEPENDS/$SPELL_DISK_TAG
; then
+ cp $ABANDONED_DEPENDS/$SPELL_DISK_TAG $spell_depends
fi
- unlock_file $ABANDONED_DEPENDS/$SPELL
+ unlock_file $ABANDONED_DEPENDS/$SPELL_DISK_TAG
test -e $spell_depends &&
- OPTS="$OPTS $(get_depends_options $spell_depends $SPELL)"
+ OPTS="$OPTS $(get_depends_options $spell_depends "$SPELL_INSTANCE")"
fi
save_bin_lib_paths
- # these have to go somewhere where future delves can find them
- C_LOG=${C_LOG:="/tmp/sorcery/delve/$SPELL.compile.log"}
- CASTFS_DBGLOG=${CASTFS_DBGLOG:="/tmp/sorcery/delve/$SPELL.iwdbglog"}
+ # these have to go somewhere where future delves can find them,
+ # and one slot delved is not another
+ local tag=${SPELL_DISK_TAG:-$SPELL}
+ C_LOG=${C_LOG:="/tmp/sorcery/delve/$tag.compile.log"}
+ CASTFS_DBGLOG=${CASTFS_DBGLOG:="/tmp/sorcery/delve/$tag.iwdbglog"}
C_FIFO=${C_FIFO:="/dev/stdout"}
- IW_LOG=${IW_LOG:="/tmp/sorcery/delve/$SPELL.iw"}
+ IW_LOG=${IW_LOG:="/tmp/sorcery/delve/$tag.iw"}
VOYEUR_STDOUT=${VOYEUR_STDOUT:="/dev/stdout"}
VOYEUR_STDERR=${VOYEUR_STDERR:="/dev/stderr"}
- S_PWD=${S_PWD:="/tmp/sorcery/delve/$SPELL.pwd"}
+ S_PWD=${S_PWD:="/tmp/sorcery/delve/$tag.pwd"}
touch $C_LOG
}
@@ -195,7 +197,7 @@ delve_install() {
then
run_install || return 2
else
- $CHROOT_CMD delve $SPELL INSTALL
+ $CHROOT_CMD delve $SPELL_INSTANCE INSTALL
fi
) 3> >(tee -a $C_LOG >> /dev/stdout) \
2> >(tee -a $C_LOG 1>&2 > $VOYEUR_STDERR) \
@@ -219,7 +221,7 @@ delve_install_extras() {
then
run_install_extras || return 2
else
- $CHROOT_CMD delve $SPELL INSTALL_EXTRAS
+ $CHROOT_CMD delve $SPELL_INSTANCE INSTALL_EXTRAS
fi
) 3> >(tee -a $C_LOG >> /dev/stdout) \
2> >(tee -a $C_LOG 1>&2 > $VOYEUR_STDERR) \
diff --git a/usr/sbin/dispel b/usr/sbin/dispel
index 0c25b37..440d959 100755
--- a/usr/sbin/dispel
+++ b/usr/sbin/dispel
@@ -470,9 +470,20 @@ dispel_depends_engine() {
# spell, the spell is not recastable
# note: this doesnt take into account the spell changing out from
# under us. Oh well.
+ # a row names both spells bare and both slots further along, so
+ # the tail pattern is what tells one instance's rows from another's
+ local parent_spell parent_slot
+ state_split_query "$parent" parent_spell parent_slot ||
+ { parent_spell=$parent; parent_slot=""; }
lock_file $DEPENDS_STATUS
for child in $borkers; do
- type=$(grep -m 1 "$parent:$child\(([^:]*)\)\?:on"
$DEPENDS_STATUS|cut -f4 -d:)
+ local child_spell child_slot dtail
+ state_split_query "$child" child_spell child_slot ||
+ { child_spell=$child; child_slot=""; }
+ state_depends_tail_pattern dtail "$parent_slot" "$child_slot" ""
+ type=$(grep -m 1 \
+ "^$parent_spell:$child_spell\(([^:]*)\)\?:on:.*$dtail\$" \
+ $DEPENDS_STATUS|cut -f4 -d:)
if [[ "$type" == required ]]; then
recastable=no
list_add non_optional_borkers $child
@@ -498,8 +509,10 @@ dispel_depends_engine() {
# remove any uncommitted data, store our new data in a new
abandonded
# dir with borked optionals disabled, remove those deps
# from master file so that recasting will see them
- depends_file=$ABANDONED_DEPENDS/$SPELL
- rm -f $UNCOMMITTED_DEPENDS/$parent $depends_file
+ depends_file=$ABANDONED_DEPENDS/${SPELL_DISK_TAG:-$SPELL}
+ local parent_tag
+ slot_instance_tag "$parent" parent_tag || parent_tag=$parent
+ rm -f $UNCOMMITTED_DEPENDS/$parent_tag $depends_file
touch $depends_file
local depline
search_depends_status $DEPENDS_STATUS $parent|while read depline;
do
@@ -514,7 +527,16 @@ dispel_depends_engine() {
remove_depends_status $DEPENDS_STATUS $parent \
"${deparray[1]}\(([^:]*)\)\?"
fi
- add_depends $depends_file "${deparray[@]}"
+ # not "${deparray[@]}": a row's trailing fields are the
+ # requester's slot, the dependency's slot and the bound
+ # subslot, but add_depends wants the requester's slot folded
+ # into the instance it is given first
+ local dep_requester
+ slot_unpack "${deparray[6]}" dep_requester
+ slot_join "${deparray[0]}" "$dep_requester" dep_requester
+ add_depends $depends_file "$dep_requester" "${deparray[1]}" \
+ "${deparray[2]}" "${deparray[3]}" "${deparray[4]}" \
+ "${deparray[5]}" "${deparray[7]}" "${deparray[8]}"
done
list_add NEW_CASTS $parent
@@ -558,6 +580,41 @@ dispel_depends_engine() {
}
#---------------------------------------------------------------------
+## @param spell, or spell:slot, possibly with '*' axes
+## @param variable to add the instance to
+##
+## Works out which install a token names. Dispelling is about what is
+## installed, so a token naming no slot is the installed instance and
+## not slot 0 -- but only if there is just the one: which of two slots
+## the user meant is not for sorcery to guess.
+##
+## @return 1 if the token names no install, or more than one
+#---------------------------------------------------------------------
+dispel_resolve_instance() {
+ # __ prefixed: the caller hands us the name of a list to add to, and
+ # its name is the obvious one
+ local __instances
+ __instances=$(spell_instances "$1")
+
+ case $(echo $__instances | wc -w) in
+ 1) list_add "$2" "$__instances"
+ return 0
+ ;;
+ 0) # not installed: dispel_spell says so itself, and says it about
+ # the token the user typed
+ list_add "$2" "$1"
+ return 0
+ ;;
+ esac
+
+ message "${PROBLEM_COLOR}More than one slot of" \
+ "${SPELL_COLOR}$1${PROBLEM_COLOR} is installed;" \
+ "name the one to dispel:${DEFAULT_COLOR}"
+ message "${SPELL_COLOR}$(echo $__instances)${DEFAULT_COLOR}"
+ return 1
+}
+
+#---------------------------------------------------------------------
## Main loop for dispel
## @Args Parameters
#---------------------------------------------------------------------
@@ -570,7 +627,12 @@ main() {
if [ "$DOWNGRADE_SPELL" ]; then
downgrade "$DOWNGRADE_SPELL" "$DOWNGRADE_VERSION"
fi
- SPELLS=$(strip_parameters "$@")
+ local spell instances=""
+ for spell in $(strip_parameters "$@"); do
+ dispel_resolve_instance "$spell" instances ||
+ DISPEL_EXIT_STATUS=${DISPEL_EXIT_STATUS:-1}
+ done
+ SPELLS=$instances
if [ "$NONORPHAN_DEFAULT" ] || [ "$ORPHAN_DEFAULT" ]; then
CHILD_DEPS=yes
diff --git a/usr/sbin/gaze b/usr/sbin/gaze
index 262840c..db404b5 100755
--- a/usr/sbin/gaze
+++ b/usr/sbin/gaze
@@ -40,6 +40,8 @@ installed [spell] Views installed spells.
version spell Views version of spell installed,
and version in the grimoire.
versions spell Views version of spell in all grimoires
+slots spell[:slot] Lists the slots a spell offers, the
+ installed ones marked
patchlevels spell Views PATCHLEVEL and SECURITY_PATCH value
of spell in all grimoires
license spell Views the license of the given spell(s)
@@ -654,7 +656,11 @@ show_from() {
$0 ~ str && $0 !~ xclude {
found = 1
if (!verbose) exit
- printf("%s: %s\n", FILENAME, $0)
+ # a log is filed under the disk tag, and nothing but a
+ # packed slot puts an "@" in one
+ fn = FILENAME
+ gsub(/@/, ":", fn)
+ printf("%s: %s\n", fn, $0)
if (whole) nextfile
}
END {
@@ -721,6 +727,9 @@ real_name_search() {
pfx = pat " -> "
pat = tolower(pat)
}
+ # the index carries a row per slot as well, for the queue to
+ # read; a search is for spells, and one gcc is enough
+ index($1, ":") { next }
tolower($1) ~ pat {
grimoire = FILENAME
sub("/[^/]*$", "", grimoire)
@@ -1056,6 +1065,26 @@ gaze_show_section() {
#---------------------------------------------------------------------
##
+## Expands a token that names no slot into one token per slot the
+## spell has: asked after a spell, a table that answers for one of its
+## slots and says nothing of the others is answering the wrong
+## question. A token that names a slot, and a spell that has none,
+## come back as they went in.
+## @param Spell, or spell:slot
+## @param Name to look the slots up under, if the first is a directory
+## @Stdout one token per slot
+##
+#---------------------------------------------------------------------
+gaze_expand_slots() {
+ local name slot slots s
+ slot_split "$1" name slot || { echo "$1"; return 1; }
+ slot_is_default "$slot" && slots=$(codex_get_spell_slots "${2:-$name}")
+ [[ $slots ]] || { echo "$1"; return 0; }
+ for s in $slots; do slot_join "$name" "$s"; done
+}
+
+#---------------------------------------------------------------------
+##
## Shows a table with the installed version of a spell and the
## versions available in the specified grimoires.
## @param Grimoires space-separated list of grimoires to check, or "-"
@@ -1065,18 +1094,23 @@ gaze_show_section() {
##
#---------------------------------------------------------------------
gaze_show_spell_version_table() {
- local grimoires installed_grimoire installed_ver
+ local grimoires installed_grimoire installed_ver instance slot
grimoires=($1)
shift
[[ $# != 0 ]] || return 0
(
echo "Grimoire|Section|Spell|Grimoire Version|Installed Version"
echo "--------|-------|-----|----------------|-----------------"
- for SPELL in "$@"; do
+ for instance in "$@"; do
unset installed_grimoire
+ # an argument may name a slot: the grimoire is looked up by the
+ # bare name, DETAILS branches on the slot, and what is printed and
+ # what the state database is asked about is the instance
+ slot_split "$instance" SPELL slot || { slot_bad_token_message
"$instance"
+ continue; }
if [[ $grimoires != - ]]; then
- codex_does_spell_exist $SPELL || continue
- if tablet_find_spell_dir "$SPELL" tablet; then
+ codex_does_spell_exist $instance || continue
+ if tablet_find_spell_dir "$instance" tablet; then
# TODO change this if it will be stored in the less volatile
# state/packages file.
tablet_get_grimoire_name $tablet installed_grimoire
@@ -1089,17 +1123,19 @@ gaze_show_spell_version_table() {
else
SPELL_DIRECTORY=$(codex_cache_spell_lookup $SPELL $grimoire)
fi &&
- codex_set_current_spell "$SPELL_DIRECTORY" || continue
+ codex_set_current_spell "$SPELL_DIRECTORY" "$slot" || continue
# XXX old tablet/state files may not hold installed_grimoire,
# so match the first.
: ${installed_grimoire:=$GRIMOIRE_NAME}
case "$VERSION:$GRIMOIRE_NAME" in
# Always match the installed-from grimoire
(*:"$installed_grimoire")
- installed_ver="$(installed_version "$SPELL")"
+ # the instance the codex settled on, since an argument may
+ # have been a directory rather than a name
+ installed_ver="$(installed_version "$SPELL_INSTANCE")"
;&
(?*:*)
- echo
"$GRIMOIRE_NAME|${SECTION:--}|$SPELL|${VERSION:--}|${installed_ver:--}"
+ echo
"$GRIMOIRE_NAME|${SECTION:--}|$SPELL_INSTANCE|${VERSION:--}|${installed_ver:--}"
;;
esac
done
@@ -1117,14 +1153,20 @@ gaze_show_spell_version_table() {
##
#---------------------------------------------------------------------
gaze_show_version() {
- local spells unknown
+ local spells unknown name slot
for spell_or_section in "$@"; do
- if codex_find_spell_or_section_by_name "$spell_or_section"; then
+ # the codex is asked about the spell, the table about the slot; what
+ # comes back is a directory, and a slot rides on it the same way
+ if slot_split "$spell_or_section" name slot &&
+ codex_find_spell_or_section_by_name "$name"; then
[ -n "$CODEX_FOUND_SECTION" ] &&
gaze_show_spell_version_table - \
$(codex_get_spells_in_section "$CODEX_FOUND_SECTION")
+ # the slots are looked up by name, and ride on the directory the
+ # codex found the same way one slot does
[ -n "$CODEX_FOUND_SPELL" ] &&
- spells+=("$CODEX_FOUND_SPELL")
+ spells+=($(gaze_expand_slots "$(slot_join "$CODEX_FOUND_SPELL"
"$slot")" \
+ "$name"))
else
unknown+=("$spell_or_section")
fi
@@ -1147,7 +1189,47 @@ gaze_show_version() {
##
#---------------------------------------------------------------------
gaze_show_versions() {
- gaze_show_spell_version_table "$(codex_get_all_grimoires)" "$@"
+ local spell spells=()
+ # a bare name asks after the spell, which may live in more than one
+ # slot; the tokens are the user's own, so the codex knows them
+ for spell in "$@"; do spells+=($(gaze_expand_slots "$spell")); done
+ gaze_show_spell_version_table "$(codex_get_all_grimoires)" "${spells[@]}"
+}
+
+#---------------------------------------------------------------------
+##
+## Shows the slots a spell offers, the installed ones marked.
+## @param Spell
+## @param ...
+##
+#---------------------------------------------------------------------
+gaze_show_slots() {
+ local spell name request slot slots mark
+
+ for spell in "$@"; do
+ slot_split "$spell" name request || { slot_bad_token_message "$spell"
+ continue; }
+ codex_find_spell_by_name "$name" > /dev/null ||
+ { message "${PROBLEM_COLOR}No such spell:" \
+ "${SPELL_COLOR}$name${DEFAULT_COLOR}"
+ continue; }
+
+ # a bare name asks after every slot the spell has, not after the
+ # default one slot_split filled in for it
+ slot_is_default "$request" && request=
+
+ # a spell that declares no slots still has slot 0, and saying so is
+ # more use than saying nothing
+ slots=$(codex_get_spell_slots "$name" "$request")
+ [[ $slots ]] || slots=0
+
+ for slot in $slots; do
+ if spell_ok "$(slot_join "$name" "$slot")"; then mark="installed"
+ else mark=""; fi
+ message "${SPELL_COLOR}$(slot_join "$name" "$slot")" \
+ "${MESSAGE_COLOR}$mark${DEFAULT_COLOR}"
+ done
+ done
}
#---------------------------------------------------------------------
@@ -1356,21 +1438,34 @@ gaze_show_license() {
#-----
gaze_show_installed() {
- local total_rc=0
+ local total_rc=0 arg instances
if [ -z "$1" ]
then
- grep -v ':exiled:[^:]*$' $SPELL_STATUS | $PAGER
+ # a row keeps the slot in a field of its own and the status is no
+ # longer the last thing before the version, so neither the join nor
+ # the exiles can be left to a pattern over the whole line
+ awk -F: -v OFS=: '$3 != "exiled" {
+ s = $5; gsub(/@/, ":", s)
+ print (s == "" || s == "0" ? $1 : $1 ":" s), $2, $3,
$4
+ }' $SPELL_STATUS | $PAGER
else
- for spell in "$@"; do
- ! [[ $GAZE_VERBOSE == false ]] &&
- message "${SPELL_COLOR}${spell}:${DEFAULT_COLOR}"
- VERSION=$(installed_version $spell)
- if [ -n "$VERSION" ]; then
- echo "$VERSION"
- else
- total_rc=1
- message "not installed"
- fi
+ for arg in "$@"; do
+ # what is installed under a bare name may be several slots, and
+ # answering for one of them is answering the wrong question; a
+ # name nothing is installed under is asked after as it was typed,
+ # so that the "not installed" below names it
+ instances=$(spell_instances "$arg")
+ for spell in ${instances:-$arg}; do
+ ! [[ $GAZE_VERBOSE == false ]] &&
+ message "${SPELL_COLOR}${spell}:${DEFAULT_COLOR}"
+ VERSION=$(installed_version $spell)
+ if [ -n "$VERSION" ]; then
+ echo "$VERSION"
+ else
+ total_rc=1
+ message "not installed"
+ fi
+ done
done
fi
return $total_rc
@@ -1385,8 +1480,12 @@ gaze_show_installed() {
#-----
gaze_show_short_description() {
+ local name
for spell in $@; do
- if codex_find_spell_by_name $spell > /dev/null; then
+ # the grimoire is looked up by name; the slot is DETAILS' business,
+ # and codex_set_current_spell_by_name reads it off the argument
+ slot_split "$spell" name || { slot_bad_token_message "$spell"; continue;
}
+ if codex_find_spell_by_name $name > /dev/null; then
! [[ $GAZE_VERBOSE == false ]] &&
message "${SPELL_COLOR}${spell}:${DEFAULT_COLOR}"
(
@@ -1412,8 +1511,11 @@ gaze_show_short_description() {
#-----
gaze_show_long_description() {
+ local name
for spell in $@; do
- if codex_find_spell_by_name $spell > /dev/null; then
+ # as above: the name finds the spell, the slot picks the details
+ slot_split "$spell" name || { slot_bad_token_message "$spell"; continue;
}
+ if codex_find_spell_by_name $name > /dev/null; then
! [[ $GAZE_VERBOSE == false ]] &&
message "${SPELL_COLOR}${spell}:${DEFAULT_COLOR}"
(
@@ -1450,11 +1552,14 @@ gaze_show_where() {
shift
fi
+ local name
for spell in $@; do
+ # a slot is a slot of one spell, and one spell lives in one section
+ slot_split "$spell" name || { slot_bad_token_message "$spell"; continue;
}
if [[ $SHOW_PATH == true ]]; then
- SECTION="$(codex_find_spell_by_name $spell)"
+ SECTION="$(codex_find_spell_by_name $name)"
else
- SECTION="$(codex_get_spell_section_name $spell)"
+ SECTION="$(codex_get_spell_section_name $name)"
fi
if ! [[ $GAZE_VERBOSE == false ]]; then
@@ -1623,16 +1728,20 @@ gaze_show_dependencies() {
##
#-----
gaze_show_size_all() {
- local each date status version install_log
+ local each version slot install_log
local current_spell=0 current_size=0 current_info
local max_size=0 max_spell total_size=0 total_files=0
- local installed_spells=$(all_spell_status | sed -n '/:exiled:/! s,:, ,gp')
+ # the slot has a field of its own, so the row is picked apart by
+ # field and not by turning every ':' into a space; packed, it is
+ # already the suffix the log's name carries
+ local installed_spells=$(all_spell_status |
+ awk -F: '$3 != "exiled" { print $1, $4, $5 }')
local spell_count=$(wc -l <<< "$installed_spells")
echo "This will take a while and cause lots of disk I/O..."
- while read each date status version; do
+ while read each version slot; do
let current_spell++
- install_log="$INSTALL_LOGS/$each-$version"
+ install_log="$INSTALL_LOGS/$each${slot:+@$slot}-$version"
current_info=$(
while read entry; do
test -f "$entry" && echo "'$entry'"
@@ -1642,7 +1751,7 @@ gaze_show_size_all() {
current_size=${current_info[0]}
if [[ $current_size -gt $max_size ]]; then
max_size=$current_size
- max_spell=$each
+ max_spell=$each${slot:+:${slot//@/:}}
fi
let total_size+=$current_size
let total_files+=${current_info[1]}
@@ -1685,7 +1794,9 @@ gaze_show_size() {
continue
fi
local version=$(installed_version $spell)
- local file="$INSTALL_LOGS/$spell-$version"
+ local tag
+ slot_instance_tag "$spell" tag || tag=$spell
+ local file="$INSTALL_LOGS/$tag-$version"
if ! test -e $file ; then
message "No install log for $spell ($file)"
continue
@@ -1706,7 +1817,7 @@ gaze_show_size() {
##
## displays a spell's install log
##
-## @param spell - the spell name
+## @param spell - the spell instance
## @param version - version for that spell
## @param type - If empty show the log without state files, if "spell"
## show log without sorcery log files eitther, if "full" show the
entire
@@ -1717,8 +1828,10 @@ gaze_install() {
local SPELL=$1
local VERSION=$2
local TYPE=$3
+ local tag
+ slot_instance_tag "$SPELL" tag || tag=$SPELL
- local log=$INSTALL_LOGS/$SPELL-$VERSION
+ local log=$INSTALL_LOGS/$tag-$VERSION
test -f $log || {
message "Install log for $SPELL does not exist"
return 1
@@ -2261,9 +2374,13 @@ parse() {
done
- SPELL=$2
+ # the argument may name a slot: the codex is asked after the spell,
+ # the state database after the instance, and the log files a spell
+ # leaves behind are named for its disk tag
+ slot_split "$2" SPELL SLOT_ARG || SPELL=$2
+ slot_disk_tag "$SPELL" "$SLOT_ARG" SPELL_DISK_TAG
SECTION=$2
- VERSION=$(installed_version $SPELL)
+ VERSION=$(installed_version "$2")
case $1 in
@@ -2304,6 +2421,7 @@ parse() {
section) shift; gaze_show_section $@ ;;
version) shift; gaze_show_version $@ ;;
versions) shift; gaze_show_versions $@ ;;
+ slots) shift; gaze_show_slots "$@" ;;
patchlevels) shift; gaze_show_patchlevels $@ ;;
license) shift; gaze_show_license $@ ;;
alien) alien ;;
@@ -2342,7 +2460,7 @@ parse() {
elif [ -z "$VERSION" ]; then
codex_set_current_spell_by_name "$1"
fi
- display "$COMPILE_LOGS/$SPELL-$VERSION$EXTENSION" \
+ display
"$COMPILE_LOGS/${SPELL_DISK_TAG:-$SPELL}-$VERSION$EXTENSION" \
"Compile log for $SPELL-$VERSION does not exist"
;;
@@ -2354,13 +2472,13 @@ parse() {
gaze_install $SPELL $VERSION spell ;;
sum) [ -n "$SPELL" ] &&
- checksum "$INSTALL_LOGS/$SPELL-$VERSION" ||
+ checksum "$INSTALL_LOGS/${SPELL_DISK_TAG:-$SPELL}-$VERSION" ||
checksum "$INSTALL_LOGS/*"
;;
md5sum) [ -n "$SPELL" ] &&
- md5sum_files "$INSTALL_LOGS/$SPELL-$VERSION" ||
+ md5sum_files
"$INSTALL_LOGS/${SPELL_DISK_TAG:-$SPELL}-$VERSION" ||
md5sum_files "$INSTALL_LOGS/*"
;;
diff --git a/usr/sbin/resurrect b/usr/sbin/resurrect
index 44f74d7..4ba5d16 100755
--- a/usr/sbin/resurrect
+++ b/usr/sbin/resurrect
@@ -51,6 +51,8 @@ do_resurrect() {
local spell=$1
local requested_version=$USE_VERSION
local cache
+ local tag
+ slot_instance_tag "$spell" tag || tag=$spell
local error_base="${PROBLEM_COLOR}Unable to resurrect $SPELL_COLOR$spell"
error_base="$error_base$DEFAULT_COLOR because$PROBLEM_COLOR"
@@ -58,7 +60,7 @@ do_resurrect() {
# use the version for only one spell in case more were specified
(illegal)
unset USE_VERSION
- find_cache "$INSTALL_CACHE/$spell-$requested_version-$HOST" cache
+ find_cache "$INSTALL_CACHE/$tag-$requested_version-$HOST" cache
if [[ ! -f $cache ]]; then
error_message "$error_base no cache for version $requested_version" \
"was found.$DEFAULT_COLOR"
@@ -82,8 +84,8 @@ do_resurrect() {
else
# find the available versions, regardless of compression
local version versions
- versions=$(find "$INSTALL_CACHE/" -name "$spell-*-$HOST.*" -printf
"%f\n" |
- sed "s,^$spell-\(\S*\)-$HOST\.\S*$,\1," |
+ versions=$(find "$INSTALL_CACHE/" -name "$tag-*-$HOST.*" -printf "%f\n" |
+ sed "s,^$tag-\(\S*\)-$HOST\.\S*$,\1," |
while read version; do
# we need to ignore other spells with similar names due to laxed
matching
# for example, wine-gecko would match when searching for wine, but
we can
@@ -110,7 +112,7 @@ do_resurrect() {
chosen_version=$(
for version in $versions; do
# output: mtime version
- stat -c "%Y $version" $INSTALL_CACHE/$spell-$version-$HOST.*
+ stat -c "%Y $version" $INSTALL_CACHE/$tag-$version-$HOST.*
done | sort -g -r | head -n1 | cut -d" " -f2
)
else
@@ -160,9 +162,14 @@ direct_resurrect() {
tmp=$(tar tOfOO "$cache" 2>&1 $tablet_path | sed -n "1
s,$tablet_path/,,p; q")
tmp=${tmp%/}
- spell=${tmp%/*}
+ local tag=${tmp%/*}
date=${tmp#*/}
- version=$(tar fOx "$cache" 2>&1 $tablet_path/$spell/$date/version)
+ version=$(tar fOx "$cache" 2>&1 $tablet_path/$tag/$date/version)
+ # the tablet is filed under the disk tag, the rest of resurrect wants
+ # the instance token
+ local tag_spell tag_slot
+ slot_undisk_tag "$tag" tag_spell tag_slot &&
+ slot_join "$tag_spell" "$tag_slot" spell
fi
if [[ -z $spell ]]; then
diff --git a/usr/sbin/sorcery b/usr/sbin/sorcery
index e8a02d5..a0dc364 100755
--- a/usr/sbin/sorcery
+++ b/usr/sbin/sorcery
@@ -158,18 +158,28 @@ show_spells() { (
for SPELL in $SPELLS; do
+ local SPELL_DIR=$SPELL
smgl_basename $SPELL SPELLNAME
- if ! spell_installed $SPELLNAME &&
- ! spell_held $SPELLNAME &&
- ! spell_exiled $SPELLNAME
- then
+ # a slotted spell is offered a slot at a time: another slot is
+ # another install, and one of them being installed says nothing
+ # about the rest. An unslotted spell is its one default slot.
+ local SLOTS=$(codex_get_spell_slots $SPELLNAME)
+ for SLOTNAME in ${SLOTS:-0}; do
+
+ slot_join $SPELLNAME $SLOTNAME INSTANCE
+ if ! spell_installed $INSTANCE &&
+ ! spell_held $INSTANCE &&
+ ! spell_exiled $INSTANCE
+ then
- codex_set_current_spell $SPELL
- echo -en '"'${SPELL:="Unknown"}'"'
- echo -en '\t"'${VERSION:="-"}'"'
- echo -e '\t"'${SHORT:="Description Unavailable"}'"'
+ codex_set_current_spell $SPELL_DIR $SLOTNAME
+ echo -en '"'${SPELL_INSTANCE:="Unknown"}'"'
+ echo -en '\t"'${VERSION:="-"}'"'
+ echo -e '\t"'${SHORT:="Description Unavailable"}'"'
- fi
+ fi
+
+ done
done
@@ -267,15 +277,18 @@ file_menu() {
show_installed_spells() {
- local spell date status version
+ local spell date status version slot subslot instance
- while read spell date status version; do
+ # the row's fifth field is the packed slot, which is what tells one
+ # install of a spell from another; it is empty for slot 0
+ while read spell date status version slot subslot; do
if [[ $status == "installed" ]] ||
[[ $status == "held" ]]; then
- if codex_set_current_spell_by_name $spell; then
+ slot_tag_instance "$spell${slot:+@$slot}" instance || instance=$spell
+ if codex_set_current_spell_by_name $instance; then
SHORT=${SHORT:-"Description unavailable"}
- echo -en '"'${spell:="Unknown"}'"'
+ echo -en '"'${instance:="Unknown"}'"'
echo -en '\t"'${version:="-"}'"'
echo -e '\t"'${SHORT//\"/\'}'"'
fi
@@ -484,21 +497,30 @@ make_checklist() {
for SPELL in $SPELLS; do
- if codex_set_current_spell $SPELL; then
+ local SPELL_DIR=$SPELL SPELLNAME
+ smgl_basename $SPELL SPELLNAME
+ # one line per slot, each installed or not on its own; an unslotted
+ # spell is its one default slot
+ local SLOTS=$(codex_get_spell_slots $SPELLNAME)
+ for SLOTNAME in ${SLOTS:-0}; do
+
+ if codex_set_current_spell $SPELL_DIR $SLOTNAME; then
- STATUS="OFF"
+ STATUS="OFF"
- if spell_ok $SPELL
- then STATUS="on"
- else STATUS="off"
- fi
+ if spell_ok $SPELL_INSTANCE
+ then STATUS="on"
+ else STATUS="off"
+ fi
- echo -en '"'${SPELL:="Unknown"}'"'
- echo -en '\t"'${VERSION:="-"}'"'
- echo -en '\t"'$STATUS'"'
- echo -e '\t"'${SHORT:="Description Unavailable"}'"'
+ echo -en '"'${SPELL_INSTANCE:="Unknown"}'"'
+ echo -en '\t"'${VERSION:="-"}'"'
+ echo -en '\t"'$STATUS'"'
+ echo -e '\t"'${SHORT:="Description Unavailable"}'"'
- fi
+ fi
+
+ done
done
@@ -514,11 +536,19 @@ process_section() {
for SPELL in $SPELLS; do
smgl_basename "$SPELL" LINE
- if echo -e "$KEEP_LIST" |
- grep -q "^$LINE\$"
- then push_install_queue $LINE
- else push_remove_queue $LINE
- fi
+ # the checklist offered an install per slot, so the answers come
+ # back as instances and every slot is committed on its own
+ local SLOTS=$(codex_get_spell_slots $LINE)
+ for SLOTNAME in ${SLOTS:-0}; do
+
+ slot_join $LINE $SLOTNAME INSTANCE
+ if echo -e "$KEEP_LIST" |
+ grep -q "^$INSTANCE\$"
+ then push_install_queue $INSTANCE
+ else push_remove_queue $INSTANCE
+ fi
+
+ done
done
@@ -561,6 +591,7 @@ make_hold_checklist() {
SPELL="${array[0]}"
STATUS="${array[2]}"
VERSION="${array[3]}"
+ SLOT="${array[4]}"
if [ "$STATUS" == "installed" ] ||
[ "$STATUS" == "held" ]; then
@@ -569,9 +600,12 @@ make_hold_checklist() {
HELD="on" ||
HELD="off"
- if codex_set_current_spell_by_name $SPELL; then
+ # the slot field is packed, and a hold is on one install of a
+ # spell, not on every slot of it
+ slot_tag_instance "$SPELL${SLOT:+@$SLOT}" INSTANCE || INSTANCE=$SPELL
+ if codex_set_current_spell_by_name $INSTANCE; then
- echo -en "\"${SPELL:="Unknown"}\""
+ echo -en "\"${INSTANCE:="Unknown"}\""
echo -en "\t\"${VERSION:="-"}\""
echo -en "\t\"$HELD\""
echo -e "\t\"${SHORT:="Description Unavailable"}\""
diff --git a/usr/share/man/man1/gaze.1 b/usr/share/man/man1/gaze.1
index b5171ff..d878f01 100644
--- a/usr/share/man/man1/gaze.1
+++ b/usr/share/man/man1/gaze.1
@@ -131,6 +131,19 @@ shows the installed version of the spell and lists all
available versions
in all grimoires. If used without a spell name, then lists order of available
grimoires.
.PP
+.SS slots <spell>[:<slot>]
+.IP
+lists the slots the spell offers, marking the ones that are installed. A
+slot given along with the spell narrows the listing to the slots matching
+it, so
+.I gaze slots gcc:*:arm\-none\-eabi
+lists every gcc slot targeting arm. A spell that declares no slots has the
+single default slot,
+.BR 0 .
+See
+.BR grimoire (5)
+for what a slot is.
+.PP
.SS license <spell>|<section>|<license>
.IP
view the license(s) of the given spell(s), or spells in given section(s),
@@ -269,8 +282,41 @@ If --full is specified, then all the calculations will
be shown for each spell.
.IP
shows the time the whole system needed to get cast. If --no-orphans is
specified
orphaned spells are skipped.
+.SH "SLOTS"
+.PP
+Wherever
+.I gaze
+names an installed spell it prints an
+.IR instance ,
+which is the spell and its slot:
+.IR gcc:15:native .
+A spell in the default slot is printed as its bare name, exactly as before
+slots existed, so
+.I gaze installed
+looks unchanged on a system with no slotted spells. A file installed by a
+slotted spell is likewise reported against its instance, though on disk its
+logs are named
+.I gcc@15@native
+\- a
+.B :
+in a file name is unsafe.
+.PP
+Anywhere a
+.I <spell>
+argument names something installed it may carry a slot, and a query may
+leave axes of that slot off the end or spell them
+.BR * :
+.I gaze installed gcc:14
+answers about that slot alone. Arguments that only name a spell in the
+grimoire \-
+.IR what ,
+.IR short ,
+.IR where ,
+.I website
+\- accept a slot too and ignore it, since the grimoire holds one entry for
+all of a spell's slots.
.SH "AUTHOR"
-Original version written by Brian Peterson, modified by Kyle Sallee and
updated
+Original version written by Brian Peterson, modified by Kyle Sallee and
updated
by Thomas Stewart and Karsten Behrmann
.PP
Maintained by the Source Mage GNU Linux Team (http://www.sourcemage.org)
diff --git a/usr/share/man/man5/grimoire.5 b/usr/share/man/man5/grimoire.5
index 82ea9ce..76741ee 100644
--- a/usr/share/man/man5/grimoire.5
+++ b/usr/share/man/man5/grimoire.5
@@ -84,10 +84,21 @@ This file is required and describes the URL for step 1.
It also contains version information,
package name, source name, source directory, and a description of the
package.
.PP
+.I SLOTS
+.IP
+This file declares the slots the spell offers, so that several versions
+(or builds) of it can be installed side by side. See
+.B SLOTS
+below.
+.PP
.I CONFLICTS
.IP
This file contains a list of spells conflicting with this one.
Usually only used when two spells install the same file(s).
+A spell listed without a slot conflicts with every installed slot of it;
+listing
+.I spell:slot
+conflicts with that one alone.
.PP
.I CONFIGURE
.IP
@@ -191,6 +202,126 @@ e.g. stopping the daemon installed by it.
.IP
This file contains instructions to perform after successfully removing the
package. This might be used to undo changes done in FINAL.
+.SH "SLOTS"
+.PP
+A spell normally has one installed copy. A spell that declares
+.I slots
+has one per slot, installed side by side and upgraded independently:
+.IP
+$
+.B cast gcc:14
+.IP
+$
+.B cast gcc:15
+.PP
+casts two spells that do not replace each other, and
+.I dispel gcc:14
+removes only that one. The
+.I spell:slot
+pair is called an
+.I instance
+and is what you type and what sorcery prints wherever it names an
+installed copy.
+.PP
+The default slot is
+.BR 0 ,
+and
+.I spell:0
+and
+.I spell
+are the same thing: a spell that says nothing about slots lives in slot 0
+forever, and so does every spell written before slots existed.
+.SS "The SLOTS file"
+.PP
+A slot need not be a single value. A spell declares one or more
+.IR axes ,
+and its slots are the matrix they span \- a version and a target triplet,
+for a cross toolchain. An axis is a name, a colon, and its values with the
+default first; values may run over as many lines as is convenient,
+\fB#\fP starts a comment, and a field beginning with \fB!\fP excludes a
+combination the matrix would otherwise imply:
+.IP
+.nf
+# grimoire/devel/gcc/SLOTS
+version: 15 14 13
+target: native arm\-none\-eabi i386\-elf
+
+!13:i386\-elf # 13 was never built for that target
+.fi
+.PP
+A spell whose slots need no explaining leaves the axis unnamed and just
+lists them, which is what most spells will look like:
+.IP
+.nf
+# grimoire/devel/binutils/SLOTS
+2.47 2.46
+.fi
+.PP
+An axis value is drawn from
+.BR [A\-Za\-z0\-9_.+\-]+ .
+It may not contain
+.BR : ,
+.BR / ,
+.BR = ,
+.BR @ ,
+whitespace or parentheses, all of which sorcery has other uses for.
+.SS "Writing DETAILS for a slotted spell"
+.PP
+.B $SLOT
+and one
+.BI $SLOT_ AXIS
+per named axis are set before DETAILS is sourced, so DETAILS decides what
+the slot means:
+.IP
+.nf
+case $SLOT_VERSION in
+ 15) VERSION=15.1.0 ;;
+ 14) VERSION=14.2.0 ;;
+ 13) VERSION=13.3.0 ;;
+esac
+[[ $SLOT_TARGET == native ]] || OPTS="\-\-target=$SLOT_TARGET $OPTS"
+ SUBSLOT=${VERSION%%.*}
+.fi
+.PP
+A spell with no SLOTS file may still pin itself to one fixed slot by
+setting
+.B SLOT
+in DETAILS, which is the python2/python3 case. A spell that does neither
+is in slot 0.
+.PP
+Slots must not collide on the filesystem. That is the spell writer's job:
+install to versioned paths, use slot\-suffixed binary names, and so on.
+Sorcery only refuses to let one slot silently overwrite another.
+.SS "Subslots"
+.PP
+A
+.I subslot
+is the ABI a spell's dependents were built against \- usually a soname.
+It is not part of the spell's identity: a new subslot
+.I replaces
+what is installed rather than joining it, and it appears in no file name.
+It exists so that dependents can ask to be recast when it moves.
+.B $SUBSLOT
+is set in DETAILS and defaults to the slot, which never moves.
+.SS "Depending on a slot"
+.PP
+A dependency in DEPENDS may name a slot, a subslot, or neither:
+.IP
+.nf
+depends gcc # any slot, the installed one if there is one
+depends gcc:14:native # that slot
+depends gcc:*:arm\-none\-eabi # any gcc targeting arm
+depends gcc:14 # gcc 14 targeting anything: an axis left
+ # off the end is the same as a "*" one
+depends glibc:= # bind to glibc's ABI as it is now, and
+ # recast me when it changes
+depends glibc:2.6/2.6.1 # one exact slot and ABI
+.fi
+.PP
+When a spell is
+.I cast
+rather than depended on, an axis left unconstrained takes its default
+value instead of matching any: a cast has to pick one point in the matrix.
.SH "FINAL NOTES"
The best way to learn the software catalogue is to examine some of the
description files yourself. Most software catalogue entries are simple. For
diff --git a/usr/share/man/man8/alter.8 b/usr/share/man/man8/alter.8
index d425c7c..30123ce 100644
--- a/usr/share/man/man8/alter.8
+++ b/usr/share/man/man8/alter.8
@@ -23,10 +23,13 @@ md5sum log.
.I parameters
.RS
-\fB\-n\fP, \fB\-\-spell\fP \fIspell\fP
+\fB\-n\fP, \fB\-\-spell\fP \fIspell\fP[:\fIslot\fP]
.RS
specify which spell to alter. If not given, the pseudo\-spell "alter" is
-altered. If the spell does not exist yet, it is created.
+altered. If the spell does not exist yet, it is created. A slotted
+spell is altered one slot at a time; see
+.B SLOTS
+below.
.RE
.PP
.B \-v, \-\-version
@@ -117,6 +120,24 @@ caches of 1\-2G each.
For casts, however, it may be intelligent to turn ARCHIVE back on so
there is an easy rollback in case something fails.
+.SH "SLOTS"
+.PP
+A spell that declares slots has one install per slot, each with logs and
+a cache of its own, so alter has to be told which one it is altering:
+.RS
+.B alter \-n
+.I gcc:14:native
+.B touch /usr/bin/gcc\-14
+.RE
+.PP
+A bare name is the default slot,
+.BR 0 ,
+which is where an unslotted spell lives \- so nothing changes for a
+spell that says nothing about slots. Names given to
+.B \-\-md5mend
+may carry a slot too. See
+.BR grimoire (5)
+for what slots are.
.SH "WARNING"
.B alter is still very unstable.
It is
diff --git a/usr/share/man/man8/cast.8 b/usr/share/man/man8/cast.8
index 17d9b0f..b0a120a 100644
--- a/usr/share/man/man8/cast.8
+++ b/usr/share/man/man8/cast.8
@@ -2,7 +2,7 @@
.SH NAME
cast \- installs software packages
.SH SYNOPSIS
-\fBcast\fP [\fIoptions\fP] [\fIpackage\fP ...]
+\fBcast\fP [\fIoptions\fP] [\fIpackage\fP[:\fIslot\fP] ...]
.SH DESCRIPTION
.B cast
is part of the
@@ -111,13 +111,31 @@ The default option can be set via the sorcery menu.
The environment is set using the sorcery configuration files. See
.BR sorcery_config (8).
.IP
+.SH SLOTS
+A spell that declares slots is cast one
+.I slot
+at a time, and its slots live side by side:
+.I cast gcc:14
+does not disturb an installed
+.IR gcc:15 .
+A slot left off, or an axis of it left off, takes its default value \-
+casting has to pick one \- so
+.I cast gcc
+casts the default slot and nothing else. See
+.BR grimoire (5)
+for what a slot is and how a spell declares one, and
+.I gaze slots
+.I <spell>
+for the ones a spell offers.
.SH NOTES
Do not include
.SM version
or
.SM section
-name when specifying a package. \fBgaze\fP can provide a catalog of
-both. Refer to
+name when specifying a package. A
+.SM slot
+is not a version: it is part of the name of what gets installed.
+\fBgaze\fP can provide a catalog of both. Refer to
.BR gaze (1)
for such functionality.
.P
diff --git a/usr/share/man/man8/cleanse.8 b/usr/share/man/man8/cleanse.8
index d1cf32b..e827a9f 100644
--- a/usr/share/man/man8/cleanse.8
+++ b/usr/share/man/man8/cleanse.8
@@ -90,6 +90,22 @@ Similar to \-\-nofix but does not check dependent spells.
Uses the same four met
.TP
.B "\-h, \-\-help"
Display short help.
+.SH "SLOTS"
+Each slot of a spell is an install of its own, with its own install and
+md5sum logs, and cleanse checks and fixes them one at a time \- fixing
+.B gcc:14:native
+recasts that slot alone. The logs are named after the install:
+.IP
+/var/log/sorcery/install/gcc@14@native\-14.2.0
+.PP
+where the
+.B @
+is what the
+.B :
+of a slot becomes in a file name. Spells that say nothing about slots
+keep the names they always had. See
+.BR grimoire (5)
+for what slots are.
.SH "FILES"
The config files can be edited by hand, but it is easier to edit them with
sorcery.
.TP
diff --git a/usr/share/man/man8/confmeld.8 b/usr/share/man/man8/confmeld.8
index ecffc8d..19a689b 100644
--- a/usr/share/man/man8/confmeld.8
+++ b/usr/share/man/man8/confmeld.8
@@ -63,6 +63,24 @@ leaves the old config in its place and backups the new one
to $old_config.$date.
.SS use-new
backups the old config to $old_config.$date.old and installs the new one in
its place
.PP
+.SH "SLOTS"
+Each slot of a spell stages its configuration files on its own, so
+listings name the install and not the spell:
+.IP
+.B confmeld -s
+.br
+gcc:14:native gcc:15:native
+.PP
+An argument may name a slot,
+.IP
+.B confmeld gcc:14:native
+.PP
+and one that does not \- plain
+.B gcc
+\- is every slot of that spell with configs staged. A spell that says
+nothing about slots is unaffected. See
+.BR grimoire (5)
+for what slots are.
.SH "AUTHOR"
Original version by Jaka Kranjc.
.PP
diff --git a/usr/share/man/man8/dispel.8 b/usr/share/man/man8/dispel.8
index 25cdf00..22214f7 100644
--- a/usr/share/man/man8/dispel.8
+++ b/usr/share/man/man8/dispel.8
@@ -3,7 +3,7 @@
dispel \- removes software packages
.SH SYNOPSIS
.B dispel
-[options] [package1] ... [package2] ...
+[options] [package1[:slot]] ... [package2[:slot]] ...
.PP
.B dispel -e|--exile|-d|--downgrade
<package> [version]
@@ -115,6 +115,31 @@ Use defaults for dependency following from the sorcery
menu for child and/or par
.SS "-h, --help"
outputs short help
+.SH "SLOTS"
+A spell that declares slots has one installed copy per slot, and
+.I dispel
+removes one of them:
+.I dispel gcc:14
+leaves an installed
+.I gcc:15
+alone. See
+.BR grimoire (5)
+for what a slot is, and
+.I gaze slots <spell>
+for the ones that are installed.
+.PP
+A package named without a slot is the installed one, whichever slot that
+is \- it is not slot 0 \- because dispelling is about what is installed. If
+more than one slot of it is installed,
+.I dispel
+says so and asks you to name one rather than guess; a slot with an axis
+left off, or spelled
+.BR * ,
+still has to pick out a single install.
+.PP
+.I /var/lib/sorcery/sustained
+is read the same way round: a bare spell name in it sustains every slot of
+that spell, as it always has, and naming an instance sustains just the one.
.SH "FILES"
.SS /var/lib/sorcery/excluded
List of regexps that are excluded during a cast or dispel.
diff --git a/usr/share/man/man8/resurrect.8 b/usr/share/man/man8/resurrect.8
index 278be55..02c2891 100644
--- a/usr/share/man/man8/resurrect.8
+++ b/usr/share/man/man8/resurrect.8
@@ -67,6 +67,27 @@ usually, sorcery will refuse to dispel things it vitally
needs (gcc, tar, bash,
This flag overrides that protection. It can be used with any of the above
options.
.SS "-h, --help"
outputs short help
+.SH "SLOTS"
+A spell that declares slots is resurrected a slot at a time, and the
+spell arguments above may name one:
+.IP
+.B resurrect \-l gcc:14:native
+.PP
+Each slot has caches of its own, named after the install rather than
+the spell:
+.IP
+/var/cache/sorcery/gcc@14@native\-14.2.0\-i686\-pc\-linux\-gnu.tar.bz2
+.PP
+where the
+.B @
+is what the
+.B :
+of a slot becomes in a file name \- which is also the name to give
+.BR "\-\-from" .
+A bare spell name looks for caches of that name alone, so an install
+of a slotted spell has to be named with its slot. See
+.BR grimoire (5)
+for what slots are.
.SH "EXIT STATUS"
.TP
.I "0"
diff --git a/usr/share/man/man8/sorcery.8 b/usr/share/man/man8/sorcery.8
index 6814db0..e4f8991 100644
--- a/usr/share/man/man8/sorcery.8
+++ b/usr/share/man/man8/sorcery.8
@@ -163,6 +163,28 @@ To select whichever button is highlighted press
.PP
To select/deselect items from a checklist press
.I <Space>
+.SH "SLOTS"
+A spell that declares slots has one install per slot, and the menus and
+the queues name installs, not spells:
+.IP
+[X] gcc:14:native 14.2.0
+.br
+[ ] gcc:15:native 15.1.0
+.PP
+So the
+.IR Add ,
+.I Remove
+and
+.I Select
+menus offer each slot on a line of its own, holding is per slot, and
+.B sorcery queue
+compares each installed slot against the version its own slot offers.
+A spell that says nothing about slots is listed once, exactly as
+before. See
+.BR grimoire (5)
+for what slots are, and
+.BR cast (8)
+for casting one.
.SH "AUTHOR"
Original version by Kyle Sallee, and updated Thomas Stewart
and Karsten Behrmann.
diff --git a/var/lib/sorcery/modules/build_api/api1
b/var/lib/sorcery/modules/build_api/api1
index ad90377..10610cd 100755
--- a/var/lib/sorcery/modules/build_api/api1
+++ b/var/lib/sorcery/modules/build_api/api1
@@ -25,7 +25,7 @@
#---------------------------------------------------------------------
run_build_spell() {
debug "build_api1" "run_build_spell"
- C_LOG=$TMP_DIR/$SPELL.compile.log
+ C_LOG=$TMP_DIR/${SPELL_DISK_TAG:-$SPELL}.compile.log
C_FIFO=/dev/stdout
STAGED_INSTALL=off
rm -f $C_LOG
diff --git a/var/lib/sorcery/modules/build_api/api2
b/var/lib/sorcery/modules/build_api/api2
index abfceea..7b2d2ff 100755
--- a/var/lib/sorcery/modules/build_api/api2
+++ b/var/lib/sorcery/modules/build_api/api2
@@ -29,7 +29,7 @@ run_build_spell() {
local CHROOT_CHECK=""
- export C_LOG=$TMP_DIR/$SPELL.compile.log
+ export C_LOG=$TMP_DIR/${SPELL_DISK_TAG:-$SPELL}.compile.log
export CASTFS_DBGLOG=$C_LOG.castfs.dbglog
export C_FIFO=/dev/stdout # not needed for api2 anymore, but just in case
export S_PWD=$TMP_DIR/build.saved.pwd
@@ -85,8 +85,10 @@ run_build_spell() {
then
invoke_installwatch
fi &&
- delve ${OVERRIDE_GRIMOIRES+-g "$OVERRIDE_GRIMOIRES"} $SPELL INSTALL &&
- delve ${OVERRIDE_GRIMOIRES+-g "$OVERRIDE_GRIMOIRES"} $SPELL
INSTALL_EXTRAS &&
+ # the instance, since delve loads the spell afresh and the bare
+ # name would load the default slot rather than the one being cast
+ delve ${OVERRIDE_GRIMOIRES+-g "$OVERRIDE_GRIMOIRES"} $SPELL_INSTANCE
INSTALL &&
+ delve ${OVERRIDE_GRIMOIRES+-g "$OVERRIDE_GRIMOIRES"} $SPELL_INSTANCE
INSTALL_EXTRAS &&
run_post_install || return 2
run_transfer &&
devoke_installwatch &&
diff --git a/var/lib/sorcery/modules/build_api/common
b/var/lib/sorcery/modules/build_api/common
index 2f81dcc..c92cfeb 100755
--- a/var/lib/sorcery/modules/build_api/common
+++ b/var/lib/sorcery/modules/build_api/common
@@ -35,10 +35,14 @@ run_config_loc () {
mkdir --parents --mode=0755 $SM_CONFIG_OPTION_CACHE
fi
- if [ -f $SM_CONFIG_OPTION_CACHE/$SPELL ] ; then
- message "${MESSAGE_COLOR}These are your current -- config options
for spell ${SPELL_COLOR}$SPELL"
- message "${FILE_COLOR}($SM_CONFIG_OPTION_CACHE/$SPELL)"
- cat $SM_CONFIG_OPTION_CACHE/$SPELL | column
+ # a slot is configured on its own, so the cache is keyed on the
+ # tag; an unslotted spell keeps the file it always had
+ local option_cache=$SM_CONFIG_OPTION_CACHE/${SPELL_DISK_TAG:-$SPELL}
+
+ if [ -f $option_cache ] ; then
+ message "${MESSAGE_COLOR}These are your current -- config options
for spell ${SPELL_COLOR}${SPELL_INSTANCE:-$SPELL}"
+ message "${FILE_COLOR}($option_cache)"
+ cat $option_cache | column
fi
if query "Do you wish to add -- options to ./configure?" n ; then
@@ -55,16 +59,16 @@ run_config_loc () {
sedit 's/^/# /' $F_TMP
fi
- cat $SM_CONFIG_OPTION_CACHE/$SPELL >> $F_TMP 2>/dev/null
+ cat $option_cache >> $F_TMP 2>/dev/null
edit_file $F_TMP
- rm -f $SM_CONFIG_OPTION_CACHE/$SPELL
+ rm -f $option_cache
sedit '/^#.*$/d' $F_TMP
- mv $F_TMP $SM_CONFIG_OPTION_CACHE/$SPELL
+ mv $F_TMP $option_cache
fi
# load custom OPTS
- if [ -f $SM_CONFIG_OPTION_CACHE/$SPELL ]; then
- OPTS="$OPTS $(< $SM_CONFIG_OPTION_CACHE/$SPELL)"
+ if [ -f $option_cache ]; then
+ OPTS="$OPTS $(< $option_cache)"
message "${MESSAGE_COLOR} OPTS= ${DEFAULT_COLOR}$OPTS"
fi
@@ -91,17 +95,20 @@ real_prepare_install() { (
debug "libgrimoire" "Running prepare_install() on $SPELL"
message "${MESSAGE_COLOR}Preparing to install" \
- "${SPELL_COLOR}${SPELL}${DEFAULT_COLOR}"
+ "${SPELL_COLOR}${SPELL_INSTANCE}${DEFAULT_COLOR}"
lock_resources "libgrimoire" "install"
- if spell_ok $SPELL
+ # the instance, not the spell: casting one slot of a spell replaces
+ # that slot and leaves the others installed, which is the whole
+ # point of having them
+ if spell_ok $SPELL_INSTANCE
then
trap "spell_recover" SIGINT
save_libraries
save_binaries
- dispel --notriggers --nosustain --noqueue $SPELL
+ dispel --notriggers --nosustain --noqueue $SPELL_INSTANCE
fi
) &&
@@ -143,7 +150,10 @@ real_gather_docs() { (
debug "libgrimoire" "Running gather_docs() on $SPELL DOCS=$DOCS"
- DEST_DIR=$DOCUMENT_DIRECTORY/$SPELL
+ # the disk tag rather than the spell, so that two slots of the same
+ # spell do not file their documentation in the one directory and
+ # dispel each other's; for a spell without slots the two are equal
+ DEST_DIR=$DOCUMENT_DIRECTORY/${SPELL_DISK_TAG:-$SPELL}
mkdir -p $DEST_DIR
cd $SOURCE_DIRECTORY
@@ -203,65 +213,85 @@ install_pam_confs() {
run_spell_success() {
debug "build_api_common" "cast of $SPELL-$VERSION was successful"
- local INST_LOG="$INSTALL_LOGS/$SPELL-$VERSION"
- local MD5_LOG="$MD5SUM_LOGS/$SPELL-$VERSION"
- local TMP_INST_LOG="$TMP_DIR/$SPELL-$VERSION.install"
- local TMP_MD5_LOG="$TMP_DIR/$SPELL-$VERSION.md5"
- local C_LOG_COMP="$COMPILE_LOGS/$SPELL-$VERSION$EXTENSION"
+ local INST_LOG="$INSTALL_LOGS/$SPELL_DISK_TAG-$VERSION"
+ local MD5_LOG="$MD5SUM_LOGS/$SPELL_DISK_TAG-$VERSION"
+ local TMP_INST_LOG="$TMP_DIR/$SPELL_DISK_TAG-$VERSION.install"
+ local TMP_MD5_LOG="$TMP_DIR/$SPELL_DISK_TAG-$VERSION.md5"
+ local C_LOG_COMP="$COMPILE_LOGS/$SPELL_DISK_TAG-$VERSION$EXTENSION"
run_hook SUCCESS pre
local CACHE
- construct_cache_name "$INSTALL_CACHE/$SPELL-$VERSION-$HOST" CACHE
+ construct_cache_name "$INSTALL_CACHE/$SPELL_DISK_TAG-$VERSION-$HOST"
CACHE
local CACHE_COMP=$CACHE$EXTENSION
tablet_check_version_cache "$VERSION_STATUS" &&
(
- codex_set_current_spell_by_name $SPELL
- add_version_cache "$VERSION_STATUS" "$SPELL" "$VERSION" "$PATCHLEVEL"
"$SECURITY_PATCH" "$UPDATED"
+ codex_set_current_spell_by_name "$SPELL_INSTANCE"
+ add_version_cache "$VERSION_STATUS" "$SPELL_INSTANCE" "$VERSION"
"$PATCHLEVEL" "$SECURITY_PATCH" "$UPDATED"
)
# announce to everyone the spell succeeded
- activity_log "cast" "$SPELL" "$VERSION" "success"
- add_spell $SPELL installed $VERSION
- echo $SPELL >> $SUCCESS_LIST
- pop_install_queue "$SPELL"
+ activity_log "cast" "$SPELL_INSTANCE" "$VERSION" "success"
+ add_spell "$SPELL_INSTANCE" installed "$VERSION" "$SUBSLOT"
+ # the queue and the lists the depends engine reads are between
+ # instances, like everything else that names an install
+ echo $SPELL_INSTANCE >> $SUCCESS_LIST
+ pop_install_queue "$SPELL_INSTANCE"
# update depends info
debug "build_api_common" "Merging depends info"
# none of the old values are valid, only the new, uncommitted values are
- remove_depends_status $DEPENDS_STATUS $SPELL
+ remove_depends_status $DEPENDS_STATUS "$SPELL_INSTANCE"
local spell_depends spell_sub_depends spell_rsub_depends
local t_DEPENDS_STATUS
lock_start_transaction "$DEPENDS_STATUS" t_DEPENDS_STATUS
- get_uncommitted_depends_file $SPELL spell_depends
+ get_uncommitted_depends_file "$SPELL_INSTANCE" spell_depends
+ # everything we depend on is installed by now, so a ':=' dependency
+ # can be told which ABI it got
+ bind_depends_subslots "$spell_depends"
if [ -e "$spell_depends" ] ; then
cat $spell_depends >> $t_DEPENDS_STATUS
fi
lock_commit_transaction $DEPENDS_STATUS
+ # what bound itself to the ABI we had was built against something
+ # that is gone; the install queue is how sorcery says "cast this
+ # again", and push_install_queue is no use here because it refuses
+ # what is installed, which every one of these is
+ local stale
+ for stale in $(search_depends_stale_subslot "$DEPENDS_STATUS" \
+ "$SPELL_INSTANCE" "$SUBSLOT")
+ do
+ spell_ok "$stale" || continue
+ message "${MESSAGE_COLOR}The ABI of ${SPELL_COLOR}$SPELL_INSTANCE" \
+ "${MESSAGE_COLOR}moved, queueing" \
+ "${SPELL_COLOR}$stale${DEFAULT_COLOR}"
+ push_queue $INSTALL_QUEUE "$stale"
+ done
+
# since the database is now accurate, no reason to keep old answers
# floating around
- test -e "$ABANDONED_DEPENDS/$SPELL" &&
- rm -f $ABANDONED_DEPENDS/$SPELL &>/dev/null
+ test -e "$ABANDONED_DEPENDS/$SPELL_DISK_TAG" &&
+ rm -f $ABANDONED_DEPENDS/$SPELL_DISK_TAG &>/dev/null
# none of the old values are valid, only the new, uncommitted values are
- remove_sub_depends $SUB_DEPENDS_STATUS "" "$SPELL"
+ remove_sub_depends $SUB_DEPENDS_STATUS "" "$SPELL_INSTANCE"
local spell_sub_depends
local t_SUB_DEPENDS_STATUS
lock_start_transaction "$SUB_DEPENDS_STATUS" t_SUB_DEPENDS_STATUS
- get_uncommitted_sub_depends_file $SPELL spell_sub_depends
+ get_uncommitted_sub_depends_file "$SPELL_INSTANCE" spell_sub_depends
if [ -e "$spell_sub_depends" ] ; then
cat $spell_sub_depends >> $t_SUB_DEPENDS_STATUS
fi
lock_commit_transaction $SUB_DEPENDS_STATUS
- get_uncommitted_rsub_depends_file $SPELL spell_rsub_depends
+ get_uncommitted_rsub_depends_file "$SPELL_INSTANCE" spell_rsub_depends
# compile log
create_compile_log
view_compile_log
- local tablet_dir=$(tablet_get_path $SPELL)
+ local tablet_dir=$(tablet_get_path "$SPELL_INSTANCE")
if [ "$?" != 0 ] ; then
message "failed to make a tablet directory: $tablet_dir" \
"this may be a bug"
@@ -272,7 +302,7 @@ run_spell_success() {
"${FILE_COLOR}${tablet_dir}${DEFAULT_COLOR}"
tablet_install_spell_files $tablet_dir
local TABLET_SPELL_DIR=$tablet_dir spell_config_stage
- spell_config_stage=$CONFIG_STAGE_DIRECTORY/$SPELL
+ spell_config_stage=$CONFIG_STAGE_DIRECTORY/$SPELL_DISK_TAG
lock_file "$spell_config_stage"
if [[ $STAGED_INSTALL != "off" && -d $spell_config_stage/current ]]
then
@@ -290,7 +320,7 @@ run_spell_success() {
-prune -o -type f -wholename "*/$quasi_basename" -print |
while read file; do
if cmp -s $deferred_file $file; then
- recursive_config_stage_cleanup $deferred_file $SPELL
+ recursive_config_stage_cleanup $deferred_file $SPELL_DISK_TAG
continue 2
fi &>/dev/null
done
@@ -303,7 +333,7 @@ run_spell_success() {
fi
unlock_file "$spell_config_stage"
# this data is now doubly stale, remove it
- rm -f $ABANDONED_PERSIST/$SPELL.p
+ rm -f $ABANDONED_PERSIST/$SPELL_DISK_TAG.p
fi
# when delving late stages the installwatch log may not exist
@@ -328,7 +358,7 @@ run_spell_success() {
# save the md5sums of the files in the stage and not the target
# ones on the system, otherwise config merging breaks #13835
# for this we need an "install" log of what is in the stage
- local STAGE_INST_LOG="$TMP_DIR/$SPELL-$VERSION.stage.install"
+ local STAGE_INST_LOG="$TMP_DIR/$SPELL_DISK_TAG-$VERSION.stage.install"
create_stage_install_log $TMP_INST_LOG $STAGE_INST_LOG
if [[ $tablet_dir ]] ; then
find $tablet_dir >> $STAGE_INST_LOG
@@ -381,8 +411,8 @@ run_spell_failure() {
local reason
get_failure_reason reason
- activity_log "cast" "$SPELL" "$VERSION" "failure" "$reason"
- echo $SPELL >> $FAILED_LIST
+ activity_log "cast" "$SPELL_INSTANCE" "$VERSION" "failure" "$reason"
+ echo $SPELL_INSTANCE >> $FAILED_LIST
# if it failed after PRE_BUILD make a compile log
if [ "$rc" != 1 ] ; then
@@ -393,8 +423,8 @@ run_spell_failure() {
# put the answers somewhere where they can still be used later
mkdir -p $ABANDONED_DEPENDS
local spell_depends
- get_uncommitted_depends_file $SPELL spell_depends
- [ -e "$spell_depends" ] && mv $spell_depends $ABANDONED_DEPENDS/$SPELL
+ get_uncommitted_depends_file "$SPELL_INSTANCE" spell_depends
+ [ -e "$spell_depends" ] && mv $spell_depends
$ABANDONED_DEPENDS/$SPELL_DISK_TAG
# This may have been locked if there was a failure during the install
unlock_resources "libgrimoire" "install"
@@ -407,8 +437,8 @@ run_spell_failure() {
rm -f $IW_LOG $C_LOG
local spell_sub_depends spell_rsub_depends
- get_uncommitted_sub_depends_file $SPELL spell_sub_depends
- get_uncommitted_rsub_depends_file $SPELL spell_rsub_depends
+ get_uncommitted_sub_depends_file "$SPELL_INSTANCE" spell_sub_depends
+ get_uncommitted_rsub_depends_file "$SPELL_INSTANCE" spell_rsub_depends
rm -f "$spell_sub_depends" "$spell_rsub_depends"
if [[ $CLEAN_SOURCE == on ]]
diff --git a/var/lib/sorcery/modules/libcast b/var/lib/sorcery/modules/libcast
index 4590f84..4832fd2 100755
--- a/var/lib/sorcery/modules/libcast
+++ b/var/lib/sorcery/modules/libcast
@@ -72,7 +72,7 @@ load_libcompat() {
#---------------------------------------------------------------------
prepare_spell_config() {
- SPELL_CONFIG=$DEPENDS_CONFIG/$SPELL
+ SPELL_CONFIG=$DEPENDS_CONFIG/${SPELL_DISK_TAG:-$SPELL}
if ! [ -x $SPELL_CONFIG ]; then
touch $SPELL_CONFIG
chmod a+x $SPELL_CONFIG
@@ -93,38 +93,42 @@ prepare_spell_config() {
## @return 1 or if there was a problem parsing the CONFLICTS file
#---------------------------------------------------------------------
query_conflicts() {
- debug "build_api_common" "Starting query_conflicts() on $SPELL"
+ debug "build_api_common" "Starting query_conflicts() on
${SPELL_INSTANCE:-$SPELL}"
local ignore_conflict=$1
local output=${2:-$CONFLICT_LIST}
+ local instance=${SPELL_INSTANCE:-$SPELL}
# skip if the spell was already processed (avoids a double query for
in-cast resurrects)
- [[ -s $output ]] && awk -v s=$SPELL '{ if ($1 == s) {rc=1; exit} } END
{exit !rc}' $output && return 0
+ [[ -s $output ]] && awk -v s="$instance" '{ if ($1 == s) {rc=1; exit} }
END {exit !rc}' $output && return 0
if [[ -x $SCRIPT_DIRECTORY/CONFLICTS ]]; then
local CONFLICTS=$(run_spell_file CONFLICTS conflicts) || return 1
local tmp conflicts to_dispel=""
for tmp in $CONFLICTS; do
- local spell_and_default
- explode $tmp ':' spell_and_default
+ # what conflicts is an instance, and an instance holds colons of
+ # its own, so the answer is taken off the end rather than counted
+ # to from the front
+ local conflict=${tmp%:*}
+ local answer=${tmp##*:}
# skip the spells we were told to ignore
- list_find "$ignore_conflict" ${spell_and_default[0]} && continue
+ list_find "$ignore_conflict" "$conflict" && continue
- local text="${SPELL_COLOR}${SPELL}${MESSAGE_COLOR} conflicts with"
- text="$text
${SPELL_COLOR}${spell_and_default[0]}${DEFAULT_COLOR}${QUERY_COLOR}."
+ local text="${SPELL_COLOR}${instance}${MESSAGE_COLOR} conflicts with"
+ text="$text ${SPELL_COLOR}${conflict}${DEFAULT_COLOR}${QUERY_COLOR}."
text="$text Dispel"
- text="$text ${SPELL_COLOR}${spell_and_default[0]}${MESSAGE_COLOR}?"
+ text="$text ${SPELL_COLOR}${conflict}${MESSAGE_COLOR}?"
- if query "$text" ${spell_and_default[1]}; then
- list_add conflicts "${spell_and_default[0]}"
+ if query "$text" $answer; then
+ list_add conflicts "$conflict"
else
log_failure_reason "conflicts"
return 1
fi
done
if [[ $conflicts ]]; then
- echo $SPELL $conflicts >> $output
+ echo $instance $conflicts >> $output
fi
fi
@@ -133,7 +137,7 @@ query_conflicts() {
#---------------------------------------------------------------------
## Dispels the spells that are scheduled for a dispel due to conflicting
-## @param spell whose conflicts to dispel
+## @param instance whose conflicts to dispel
## @param file containing the list of conflicts
#---------------------------------------------------------------------
dispel_conflicts() {
@@ -647,28 +651,46 @@ install_initd() {
#---------------------------------------------------------------------
+## Says whether the spell asks to be cast alone. An empty SOLO asks it
+## for the whole spell, as it always did; a SOLO that names slots asks
+## it for those only, so that a spell whose system slot must not share
+## the machine may still have slots beside it that happily do.
+##
+## @return 0 if this instance is solo
+#---------------------------------------------------------------------
+spell_is_solo() {
+ [[ -f $SCRIPT_DIRECTORY/SOLO ]] || return 1
+ [[ -s $SCRIPT_DIRECTORY/SOLO ]] || return 0
+ grep -q -e "^${SLOT:-$SLOT_DEFAULT}$" "$SCRIPT_DIRECTORY/SOLO"
+}
+
+#---------------------------------------------------------------------
## Gets the cast lock for us and ensures that we've waited for all
## solo/non-solo casts to complete
#---------------------------------------------------------------------
acquire_cast_lock() {
+ # two slots of a spell are two builds and may run at once, so the lock
+ # is on the instance; for slot 0 that is the bare name, as before
+ local instance=${SPELL_INSTANCE:-$SPELL}
# locking - blocks normal spells if a solo cast is running,
# solo casts if any cast is running
message "${MESSAGE_COLOR}Waiting for any${DEFAULT_COLOR}${FILE_COLOR}" \
"Solo${DEFAULT_COLOR} ${MESSAGE_COLOR}casts to complete..." \
"${DEFAULT_COLOR}"
lock_resources "solo" "cast"
- if test -f $SOLO && grep -q "^$SPELL$" $SOLO ||
- [ -f $SCRIPT_DIRECTORY/SOLO ]
+ # SOLO reads spell-wide, like SUSTAINED, but one slot may be listed alone
+ if test -f $SOLO && grep -q -e "^$SPELL$" -e "^$instance$" $SOLO ||
+ spell_is_solo
then #SOLO spell
message -n "${MESSAGE_COLOR}Waiting for${DEFAULT_COLOR}" \
"${SPELL_COLOR}all other${DEFAULT_COLOR}
${MESSAGE_COLOR}spells to" \
"complete...${DEFAULT_COLOR}"
- excllock_resources "cast" "$SPELL"
+ excllock_resources "cast" "$instance"
else
message -n "${MESSAGE_COLOR}Waiting for any other casts of" \
- "${DEFAULT_COLOR}${SPELL_COLOR}$SPELL${DEFAULT_COLOR}" \
+ "${DEFAULT_COLOR}${SPELL_COLOR}$instance${DEFAULT_COLOR}" \
"${MESSAGE_COLOR}to complete...${DEFAULT_COLOR}"
- lock_resources "cast" "$SPELL"
+ lock_resources "cast" "$instance"
unlock_resources "solo" "cast"
fi
message " done."
@@ -680,7 +702,7 @@ acquire_cast_lock() {
#---------------------------------------------------------------------
create_compile_log() {
message "${MESSAGE_COLOR}Creating compile log" \
- "${FILE_COLOR}$COMPILE_LOGS/$SPELL-$VERSION$EXTENSION" \
+ "${FILE_COLOR}$COMPILE_LOGS/$SPELL_DISK_TAG-$VERSION$EXTENSION" \
"${DEFAULT_COLOR}"
{
# remove any nplicated lines (happens due to all the redirection we do)
@@ -693,21 +715,21 @@ create_compile_log() {
} |
# install the compile log
if [ -z "$EXTENSION" ] || ! "$COMPRESSBIN" </dev/null &>/dev/null; then
- cat > "$COMPILE_LOGS/$SPELL-$VERSION"
+ cat > "$COMPILE_LOGS/$SPELL_DISK_TAG-$VERSION"
else
- "$COMPRESSBIN" -c > "$COMPILE_LOGS/$SPELL-$VERSION$EXTENSION"
+ "$COMPRESSBIN" -c > "$COMPILE_LOGS/$SPELL_DISK_TAG-$VERSION$EXTENSION"
fi
# install the castfs log
if [[ $STAGED_INSTALL == on ]] ; then
message "${MESSAGE_COLOR}Creating castfs debug log" \
-
"${FILE_COLOR}$COMPILE_LOGS/$SPELL-$VERSION.castfs.dbglog$EXTENSION" \
+
"${FILE_COLOR}$COMPILE_LOGS/$SPELL_DISK_TAG-$VERSION.castfs.dbglog$EXTENSION"
\
"${DEFAULT_COLOR}"
if [ -z "$EXTENSION" ]
then
- cp $CASTFS_DBGLOG $COMPILE_LOGS/$SPELL-$VERSION.castfs.dbglog
+ cp $CASTFS_DBGLOG $COMPILE_LOGS/$SPELL_DISK_TAG-$VERSION.castfs.dbglog
else
- $COMPRESSBIN -c $CASTFS_DBGLOG >
"$COMPILE_LOGS/$SPELL-$VERSION.castfs.dbglog$EXTENSION"
+ $COMPRESSBIN -c $CASTFS_DBGLOG >
"$COMPILE_LOGS/$SPELL_DISK_TAG-$VERSION.castfs.dbglog$EXTENSION"
fi
rm -f $CASTFS_DBGLOG
fi
@@ -790,11 +812,15 @@ show_downloading() {
#---------------------------------------------------------------------
## Gets the filename of the download log file
-## @param Spell
+## @param spell instance
## @Stdout file name
+##
+## One log per instance: two slots of a spell fetch different sources.
#---------------------------------------------------------------------
get_spell_dl_log() {
- echo "$TMP_DIR/download.$1"
+ local tag
+ slot_instance_tag "$1" tag || tag=$1
+ echo "$TMP_DIR/download.$tag"
}
#---------------------------------------------------------------------
diff --git a/var/lib/sorcery/modules/libcodex
b/var/lib/sorcery/modules/libcodex
index 5b5b067..7fd2531 100755
--- a/var/lib/sorcery/modules/libcodex
+++ b/var/lib/sorcery/modules/libcodex
@@ -491,23 +491,32 @@ codex_is_directory_a_spell() {
#---------------------------------------------------------------------
-## @param spell
-## @param [spell ...]
+## @param spell[:slot]
+## @param [spell[:slot] ...]
##
-## @return 0 if all the specified spells exist
+## @return 0 if all the specified spells exist, and offer the requested slot
## @return 1 othterwise
##
#---------------------------------------------------------------------
codex_does_spell_exist() {
- local i
+ local i spell slot
local retValue=0
[[ "$#" -lt 1 ]] && return 1
for i in "$@" ; do
if [[ "$i" == "" ]] ; then
message "${PROBLEM_COLOR}Empty string is not a spell!${DEFAULT_COLOR}"
retValue=1
- elif ! [[ $(codex_find_spell_by_name "$i") ]] ; then
- message "${SPELL_COLOR}$i${DEFAULT_COLOR}${PROBLEM_COLOR} is not a
spell!${DEFAULT_COLOR}"
+ elif ! slot_split "$i" spell slot ; then
+ slot_bad_token_message "$i"
+ retValue=1
+ elif ! [[ $(codex_find_spell_by_name "$spell") ]] ; then
+ message "${SPELL_COLOR}$spell${DEFAULT_COLOR}${PROBLEM_COLOR} is not a
spell!${DEFAULT_COLOR}"
+ retValue=1
+ # resolved rather than matched, since what came off the command line
+ # is a request: axes left off it stand for their defaults
+ elif ! codex_resolve_slot "$spell" "$slot" slot ; then
+ message "${SPELL_COLOR}$spell${DEFAULT_COLOR}${PROBLEM_COLOR} has no" \
+ "slot ${DEFAULT_COLOR}$slot${PROBLEM_COLOR}!${DEFAULT_COLOR}"
retValue=1
fi
done
@@ -515,7 +524,6 @@ codex_does_spell_exist() {
}
-
#---------------------------------------------------------------------
## @param spell name
## @Stdout spell name
@@ -624,12 +632,68 @@ codex_clear_current_spell() {
SPELL_DIRECTORY SCRIPT_DIRECTORY SPELL_DESCRIPTION \
VERSION SHORT UPDATED WEB_SITE ENTERED MAINTAINER \
MD5 LICENSE BUILD_API SECURITY_PATCH PATCHLEVEL \
+ SLOT SUBSLOT SLOT_SPEC SPELL_INSTANCE SPELL_DISK_TAG \
$(compgen -v SOURCE|grep -E '^SOURCE[0-9]*(_URL)?$')
+ slot_clear_axis_vars
# FIXME compgen depends on bash containing readline support
}
#---------------------------------------------------------------------
## @param spell directory
+## @param slot (optional)
+##
+## @Globals SLOT SLOT_AXES SLOT_<AXIS> SLOT_SPEC
+##
+## Works out which slot is being operated on and exports $SLOT and one
+## variable per named axis, so that DETAILS may branch on them. With
+## no slot given the spell's default slot -- the first value of every
+## axis -- is used.
+##
+## @return 0 if the slot was set
+## @return 1 if the spell does not offer the requested slot
+##
+#---------------------------------------------------------------------
+codex_set_current_slot() {
+ local slot
+
+ SLOT_SPEC=""
+ test -f "$1/SLOTS" && SLOT_SPEC=$(codex_parse_slots_file "$1/SLOTS")
+
+ slot_spec_resolve "$SLOT_SPEC" "$2" slot || return 1
+ slot_set_axis_vars "$slot" "$SLOT_SPEC"
+ return 0
+}
+
+#---------------------------------------------------------------------
+## Canonicalizes $SLOT and $SUBSLOT and derives the names the instance
+## is known by, after DETAILS has had its say -- a spell with no SLOTS
+## file may still pin itself to a fixed slot by setting SLOT in DETAILS,
+## and SUBSLOT is only ever set there, usually from VERSION or a soname.
+##
+## The subslot is deliberately absent from both names: an ABI change
+## replaces an instance, it does not create another one.
+##
+## @Globals SLOT SUBSLOT SLOT_AXES SLOT_<AXIS> SPELL SPELL_INSTANCE
SPELL_DISK_TAG
+##
+#---------------------------------------------------------------------
+codex_set_current_instance() {
+ slot_valid "$SLOT" || SLOT=$SLOT_DEFAULT
+ # DETAILS may have moved us, so the axis variables are redone here
+ slot_set_axis_vars "$SLOT" "$SLOT_SPEC"
+
+ # gentoo's default: a spell that names no ABI has the one ABI its
+ # slot stands for, and so never asks anyone to recast
+ slot_axis_valid "$SUBSLOT" || SUBSLOT=$SLOT
+ export SUBSLOT
+
+ slot_join "$SPELL" "$SLOT" SPELL_INSTANCE
+ slot_disk_tag "$SPELL" "$SLOT" SPELL_DISK_TAG
+ SPELL_CONFIG="$DEPENDS_CONFIG/$SPELL_DISK_TAG"
+}
+
+#---------------------------------------------------------------------
+## @param spell directory
+## @param slot (optional)
## @Globals All vars set in a spell
## Sets the GRIMOIRE, SECTION, SECTION_DIRECTORY, SPELL_DIRECTORY,
## SCRIPT_DIRECTORY global variables for the
@@ -637,6 +701,8 @@ codex_clear_current_spell() {
##
## Assumes the directory passed in is a valid spell directory.
##
+## @return 1 if the spell does not offer the requested slot
+##
#---------------------------------------------------------------------
codex_set_current_spell() {
@@ -646,6 +712,13 @@ codex_set_current_spell() {
codex_get_spell_paths $1
+ codex_set_current_slot "$1" "$2" || return 1
+ # the config is read before DETAILS has had its say, so a spell that
+ # pins its own SLOT there reads slot 0's config here and its own from
+ # then on; a spell with a SLOTS file already has the right slot
+ slot_disk_tag "$SPELL" "$SLOT" SPELL_DISK_TAG
+ SPELL_CONFIG="$DEPENDS_CONFIG/$SPELL_DISK_TAG"
+
if [ -f $SPELL_CONFIG ]; then
. $SPELL_CONFIG > /dev/null 2> /dev/null
fi
@@ -662,7 +735,12 @@ codex_set_current_spell() {
. $SPELL_DIRECTORY/DETAILS 1>/dev/null 2>&1
persistent_clear
- STAGE_DIRECTORY=$BUILD_DIRECTORY/stage-$SPELL-$VERSION
+ codex_set_current_instance
+
+ # two slots may well be one version -- gcc:15:native and
+ # gcc:15:arm-none-eabi are -- so the tag, not the name, keeps their
+ # staging areas apart
+ STAGE_DIRECTORY=$BUILD_DIRECTORY/stage-$SPELL_DISK_TAG-$VERSION
# set a default build api if there isn't one already
# this isn't strictly necessary as other code should be able to handle the
@@ -672,6 +750,9 @@ codex_set_current_spell() {
}
#---------------------------------------------------------------------
+## @param spell directory
+## @param slot (optional)
+##
## Load just the very basics for a spell, skip build api, libcompat
## and other debug stuff. Intended for use in tight loops.
#---------------------------------------------------------------------
@@ -680,10 +761,16 @@ codex_set_current_spell_quick() {
codex_get_spell_paths $1
+ codex_set_current_slot "$1" "$2" || return 1
+ slot_disk_tag "$SPELL" "$SLOT" SPELL_DISK_TAG
+ SPELL_CONFIG="$DEPENDS_CONFIG/$SPELL_DISK_TAG"
+
test -f "$SPELL_CONFIG" && . "$SPELL_CONFIG" &> /dev/null
persistent_load
. $1/DETAILS &> /dev/null
persistent_clear
+
+ codex_set_current_instance
true
}
@@ -716,19 +803,25 @@ codex_get_spell_paths() {
}
#---------------------------------------------------------------------
-## @param spell name
+## @param spell name, or spell:slot
+## @param slot (optional, overridden by a slot in the first argument)
## Sets the GRIMOIRE, SECTION, SECTION_DIRECTORY, SPELL_DIRECTORY,
## SCRIPT_DIRECTORY global variables for the given spell name.
##
-## @return 1 if the given name is not a spell.
+## @return 1 if the given name is not a spell, or does not offer the
+## requested slot.
##
#---------------------------------------------------------------------
codex_set_current_spell_by_name() {
debug "libcodex" "codex_set_current_spell_by_name -- $1"
- local SPELL_NAME=$(codex_find_spell_by_name "$1")
+ local spell slot
+ slot_split "$1" spell slot || return 1
+ slot_is_default "$slot" && slot=$2
+
+ local SPELL_NAME=$(codex_find_spell_by_name "$spell")
debug "libcodex" $SPELL_NAME
- [ -n "$SPELL_NAME" ] && codex_set_current_spell $SPELL_NAME
+ [ -n "$SPELL_NAME" ] && codex_set_current_spell "$SPELL_NAME"
"$slot"
}
#---------------------------------------------------------------------
@@ -800,6 +893,167 @@ find_providers() {
}
+##############################SLOT FUNCTIONS##########################
+
+#---------------------------------------------------------------------
+## @param path to a SLOTS file
+##
+## @Stdout the slot spec it declares
+##
+## A SLOTS file declares the axes a spell's slots are the matrix of.
+## An axis is a name, a colon, and its values, default value first;
+## values may run over as many lines as is convenient, '#' starts a
+## comment, and a field starting with '!' excludes a combination:
+##
+## : version: 15 14 13
+## : target: native arm-none-eabi i386-elf
+## : !13:arm-none-eabi
+##
+## A spell whose slots need no explaining can leave the axis unnamed,
+## and just list its slots:
+##
+## : 15 14 13
+##
+## @See <@function var.lib.sorcery.modules.libslot.html,slot_spec_has>
+## for what the resulting spec looks like.
+##
+#---------------------------------------------------------------------
+codex_parse_slots_file() {
+ awk -v anon="$SLOT_ANONYMOUS_AXIS" '
+ BEGIN { name = anon }
+ { sub(/#.*/, "") }
+ {
+ for (i = 1; i <= NF; i++) {
+ field = $i
+ if (substr(field, 1, 1) == "!") { excludes[++ne] = substr(field,
2); continue }
+ # "target: native" and "target:native" both name an axis
+ colon = index(field, ":")
+ if (colon > 1) {
+ name = substr(field, 1, colon - 1)
+ field = substr(field, colon + 1)
+ if (field == "") continue
+ } else if (colon == 1)
+ continue
+ # not "values[name] = (name in values) ? values[name] "," field
+ # : field": some awks create the subscript on the left before
+ # they evaluate the right, and the test comes out true
+ if (name in values)
+ values[name] = values[name] "," field
+ else {
+ order[++n] = name
+ values[name] = field
+ }
+ }
+ }
+ END {
+ for (i = 1; i <= n; i++)
+ spec = spec (spec ? " " : "") order[i] "=" values[order[i]]
+ for (i = 1; i <= ne; i++)
+ spec = spec " !" excludes[i]
+ if (spec) print spec
+ }
+ ' "$1"
+}
+
+#---------------------------------------------------------------------
+## @param spell name
+##
+## @Stdout the spell's slot spec, empty if it is not slotted
+##
+## The slot index is only a cache: a SLOTS file written since the last
+## reindex, or a grimoire indexed by an older sorcery, is still read
+## correctly, just a little slower.
+##
+#---------------------------------------------------------------------
+codex_get_spell_slot_spec() {
+ local spell=$1
+ local spell_path section_dir grimoire index
+
+ spell_path=$(codex_find_spell_by_name "$spell") || return 1
+ [[ $spell_path ]] || return 1
+
+ smgl_dirname "$spell_path" section_dir
+ smgl_dirname "$section_dir" grimoire
+ index=$grimoire/$SLOT_INDEX_FILE
+
+ if [[ -f $index ]] &&
+ awk -v s="$spell" -v d="$section_dir" -- \
+ '$1 == s && $2 == d { found = 1
+ for (i = 3; i <= NF; i++)
+ printf "%s%s", (i > 3 ? " " : ""), $i
+ print ""
+ exit }
+ END { exit !found }' "$index"
+ then
+ return 0
+ fi
+
+ test -f "$spell_path/SLOTS" && codex_parse_slots_file "$spell_path/SLOTS"
+ return 0
+}
+
+#---------------------------------------------------------------------
+## @param spell name
+## @param request to limit the listing to (optional)
+##
+## @Stdout the slots the spell offers, one per line, default slot first
+##
+## Nothing is listed for a spell that declares no slots -- such a spell
+## has the single default slot, which is the case for every spell that
+## does not know about slots at all.
+##
+#---------------------------------------------------------------------
+codex_get_spell_slots() {
+ local spec
+ spec=$(codex_get_spell_slot_spec "$1") || return 1
+ slot_spec_expand "$spec" "$2"
+}
+
+#---------------------------------------------------------------------
+## @param spell name
+##
+## @Stdout the spell's default slot
+##
+#---------------------------------------------------------------------
+codex_get_default_slot() {
+ local spec
+ spec=$(codex_get_spell_slot_spec "$1") || return 1
+ slot_spec_default "$spec"
+}
+
+#---------------------------------------------------------------------
+## @param spell name
+## @param slot
+##
+## @return 0 if the spell can be installed in the given slot
+##
+## A spell that declares no slots only has the default slot.
+##
+#---------------------------------------------------------------------
+codex_spell_has_slot() {
+ local spec
+ spec=$(codex_get_spell_slot_spec "$1") || return 1
+ slot_spec_has "$spec" "$2"
+}
+
+#---------------------------------------------------------------------
+## Works out which slot a request refers to. An empty request, and any
+## axis left off the end of one, means that axis's default value.
+##
+## @param spell name
+## @param requested slot, may be empty
+## @param variable to set to the resolved slot
+##
+## @return 0 if the request could be satisfied
+## @return 1 if the spell does not offer the requested slot
+##
+#---------------------------------------------------------------------
+codex_resolve_slot() {
+ local __spec
+ __spec=$(codex_get_spell_slot_spec "$1") || return 1
+ slot_spec_resolve "$__spec" "$2" "$3"
+}
+
##############################CACHE FUNCTIONS#########################
#---------------------------------------------------------------------
## @param spell name
@@ -870,6 +1124,11 @@ codex_create_cache() {
sort > "$1/$SPELL_INDEX_FILE"
codex_list_provides "$1" > "$1/$PROVIDE_INDEX_FILE"
+ # unlike the two above this index is only an optimization -- see
+ # codex_get_spell_slots, which falls back to the SLOTS files
+ # themselves. That is what keeps grimoires indexed by an older
+ # sorcery working.
+ codex_list_slots "$1" > "$1/$SLOT_INDEX_FILE"
shift
done
}
@@ -954,6 +1213,34 @@ codex_list_provides() {
#---------------------------------------------------------------------
## @param grimoire-path
+## @Stdout spell section-directory slot-spec
+## Lists every slotted spell in grimoire together with the axes its
+## slots span, for instance: <br>
+## gcc /home/martin/p4/grimoire/devel version=15,14,13
target=native,arm-none-eabi
+##
+## The axes are indexed rather than the matrix they span, so a spell
+## with a lot of slots costs no more here than a spell with two.
+##
+## Spells with no SLOTS file are not listed at all -- they only have
+## the default slot.
+##
+#---------------------------------------------------------------------
+codex_list_slots() {
+ local grimoire=$1
+ local file spell_dir section_dir spell spec
+
+ for file in $(codex_find_in_grimoire "$grimoire" "SLOTS"); do
+ spec=$(codex_parse_slots_file "$file")
+ [[ $spec ]] || continue
+ smgl_dirname "$file" spell_dir
+ smgl_dirname "$spell_dir" section_dir
+ smgl_basename "$spell_dir" spell
+ echo "$spell $section_dir $spec"
+ done | sort
+}
+
+#---------------------------------------------------------------------
+## @param grimoire-path
## Creates keyword index
##
#---------------------------------------------------------------------
@@ -974,9 +1261,16 @@ codex_create_keyword_cache() { (
##
## Multiversioned spells will a dummy entry. Persistent
## variables are ignored.
+##
+## Slotted spells get a row per slot, keyed on the instance -- see
+## codex_create_slot_version_cache.
#---------------------------------------------------------------------
codex_create_version_cache() {
- xargs < <(codex_find_in_grimoire "$1" DETAILS) \
+ # what version a slotted spell is depends on the slot asked for, and
+ # reading the text of its DETAILS cannot say -- so those spells are
+ # left out here and their rows made by running it, further down
+ local slotted=$(codex_find_in_grimoire "$1" SLOTS | sed
's,SLOTS$,DETAILS,')
+ { xargs < <(codex_find_in_grimoire "$1" DETAILS | grep -vxF "$slotted") \
awk -F"[\"'= ]+" '
function p(v) { return v in f ? f[v] : 0 }
{ sub(/^[\t ]+/, "") }
@@ -996,10 +1290,48 @@ codex_create_version_cache() {
nextfile
}
{ f[$1] = $2 }
- ' | sort > "$1/$VERSION_INDEX_FILE"
+ '
+ codex_create_slot_version_cache "$1"
+ } | sort > "$1/$VERSION_INDEX_FILE"
}
#---------------------------------------------------------------------
+## @param grimoire-path
+## @Stdout instance version patchlevel security_patch updated
+##
+## Prints the version index rows of every slot of every slotted spell
+## in grimoire. These are the only rows of the index made by running a
+## DETAILS rather than reading it: which version a slot is only shows
+## once $SLOT is set, and no amount of staring at the text will say.
+## Spells with no SLOTS file pay nothing for this.
+##
+## The rows are keyed on the instance, which is how the state db names
+## an install, so that the queue can find them without loading a codex
+## per slot.
+##
+## A slot whose DETAILS leaves VERSION unset is called multiversioned,
+## as an unslotted spell computing its version at cast time is, and is
+## rechecked the slow way.
+#---------------------------------------------------------------------
+codex_create_slot_version_cache() { (
+ grimoire=$1
+
+ while read spell section_dir spec; do
+ for slot in $(slot_spec_expand "$spec"); do
+ # a DETAILS that reads its stdin would eat the list otherwise
+ codex_set_current_spell "$section_dir/$spell" "$slot" \
+ < /dev/null || continue
+ if [[ $VERSION ]]; then
+ echo "$SPELL_INSTANCE $VERSION ${PATCHLEVEL:-0}" \
+ "${SECURITY_PATCH:-0} ${UPDATED:-0}"
+ else
+ echo "$SPELL_INSTANCE multiversioned"
+ fi
+ done
+ done < <(codex_list_slots "$grimoire")
+) }
+
+#---------------------------------------------------------------------
## @param servicename
## @return 0 if service exists
## @return 1 otherwise
diff --git a/var/lib/sorcery/modules/libdepends
b/var/lib/sorcery/modules/libdepends
index bbd2f4f..42c9633 100755
--- a/var/lib/sorcery/modules/libdepends
+++ b/var/lib/sorcery/modules/libdepends
@@ -181,13 +181,11 @@ run_our_sub_depends() {
local sub_depends pair
# do all sub-depends on us from live-table first, they get priority
# in case there is a conflict
- for triple in $(search_sub_depends "$SUB_DEPENDS_STATUS" '.*' "$SPELL") ;
do
- # parse the line out, eventually these silly variable expansions should
- # be in functions or benchmarked against explode
- tmp=${triple%:*};
- requester=${tmp%:*};
- sub_dependee=${tmp#*:};
- sub_depends=${triple##*:}
+ for triple in $(search_sub_depends "$SUB_DEPENDS_STATUS" '.*'
"$SPELL_INSTANCE") ; do
+ # a row is between instances and its slots sit past the sub-depends,
+ # so it is read back rather than picked apart here
+ state_split_sub_depends_row "$triple" requester sub_dependee sub_depends
||
+ continue
# if the requester has been processed skip any sub-depends its
# previous instance requested, bug 11865
@@ -199,11 +197,13 @@ run_our_sub_depends() {
done
# do all sub-depends from sub_dep_r_hash (things that we provide)
- for pair in $(hash_get sub_dep_r_hash $SPELL) ; do
+ for pair in $(hash_get sub_dep_r_hash $SPELL_INSTANCE) ; do
+ # the sub-depends is the last field, the instance is all that is
+ # before it and may hold ':' of its own
requester=${pair%:*}
- sub_depends=${pair#*:}
+ sub_depends=${pair##*:}
- process_sub_depends "$requester" "$SPELL" "$sub_depends" || return 1
+ process_sub_depends "$requester" "$SPELL_INSTANCE" "$sub_depends" ||
return 1
done
}
@@ -213,18 +213,19 @@ run_our_sub_depends() {
run_other_sub_depends() {
$STD_DEBUG
local this_spell=$1
- local SPELL sub_dependee sub_depends pair
+ local SPELL SPELL_INSTANCE sub_dependee sub_depends pair
local sub_dependee_looked_at
# do all sub-depends from sub_dep_f_hash (things we request)
for pair in $(hash_get sub_dep_f_hash $this_spell) ; do
# FIXME, do this one spell at a time, rather than in whatever order we
# see them it will reduce the number of run_details
+ # the sub-depends is the last field, the instance is the rest
sub_dependee=${pair%:*}
- sub_depends=${pair#*:}
+ sub_depends=${pair##*:}
hash_get_ref depends_looked_at $sub_dependee sub_dependee_looked_at
if [[ $sub_dependee_looked_at == done ]]; then
# load the spell and clear out any special variables
- SPELL=$sub_dependee
+ SPELL_INSTANCE=$sub_dependee
run_details
local NEW_SUB_DEPENDEES=""
# afk 5-28-06: bash 3.0 breaks local foo=(), have to make this two
lines
@@ -296,20 +297,31 @@ __comp_depends_aux() {
awk -F: -v reverse="$1" \
-v pattern="required|optional${3:+|runtime|suggest}" \
-v status="${4:-on}" '
+ # the tree is between instances: a row names the spell in one
+ # field and its slot in another, and one slot of a spell can
+ # depend on something another slot of it does not
+ function instance(spell, slot) {
+ gsub(/@/, ":", slot)
+ return (slot == "" || slot == "0") ? spell : spell ":" slot
+ }
FNR == 1 { file++ }
file == 1 {
if ($3 == status && $4 ~ pattern) {
# Remove provider name
sub(/[(].*/, "", $2)
+ req = instance($1, $7)
+ dep = instance($2, $8)
if (reverse)
- depmap[$2] = depmap[$2] " " $1 " "
- else depmap[$1] = depmap[$1] " " $2 " "
+ depmap[dep] = depmap[dep] " " req " "
+ else depmap[req] = depmap[req] " " dep " "
}
next
}
{
- if ($3 == "installed" || $3 == "held")
- print $1, depmap[$1]
+ if ($3 == "installed" || $3 == "held") {
+ inst = instance($1, $5)
+ print inst, depmap[inst]
+ }
}
' "$DEPENDS_STATUS" "$SPELL_STATUS"
)
@@ -340,6 +352,62 @@ compute_reverse_installed_depends() {
}
#---------------------------------------------------------------------
+## Reports dependencies that cannot all be met at once, and drops the
+## spells that asked for them.
+##
+## Two slots of a spell live side by side happily -- that is what slots
+## are for -- so a slot never conflicts with a slot. A subslot does: it
+## is an ABI, and it replaces rather than joins, so two spells asking
+## one instance for two ABIs are asking for something the system cannot
+## hold. So is one spell asking an instance for an ABI other than the
+## one it is installed with, when nothing in this cast is going to
+## rebuild it.
+##
+## An instance that /is/ being rebuilt is left alone: what ABI it will
+## come out with is not known until it has been cast, and the trigger
+## that fires when a subslot moves is the thing that answers for it.
+#---------------------------------------------------------------------
+private_check_slot_conflicts() {
+ local instance want requester
+ local wants wanted requesters installed_sub looked_at
+
+ for instance in $(hash_get_table_fields slot_subslot_hash); do
+ hash_get_ref slot_subslot_hash "$instance" wants
+
+ wanted=""
+ requesters=""
+ while read -r want requester; do
+ [[ $want ]] || continue
+ real_list_add wanted "$want"
+ real_list_add requesters "$requester"
+ done <<< "$wants"
+
+ hash_get_ref depends_looked_at "$instance" looked_at
+ if [[ $looked_at != done ]]; then
+ # nothing here is going to rebuild it, so the ABI it has is the
+ # ABI it is going to keep
+ query_spell_subslot "$instance" installed_sub
+ [[ $installed_sub ]] && real_list_add wanted "$installed_sub"
+ fi
+
+ # one word, one ABI, nothing to argue about
+ [[ $wanted == "${wanted%% *}" ]] && continue
+
+ message "${PROBLEM_COLOR}Cannot give${DEFAULT_COLOR}" \
+ "${SPELL_COLOR}$requesters${DEFAULT_COLOR}" \
+ "${PROBLEM_COLOR}the${DEFAULT_COLOR}" \
+ "${SPELL_COLOR}$instance${DEFAULT_COLOR}" \
+ "${PROBLEM_COLOR}they ask for: subslots${DEFAULT_COLOR}" \
+ "$wanted${PROBLEM_COLOR}, and an instance has one at a" \
+ "time.${DEFAULT_COLOR}"
+ for requester in $requesters; do
+ log_failure_reason "subslot conflict" "$requester"
+ private_remove_dependees "$requester"
+ done
+ done
+}
+
+#---------------------------------------------------------------------
## calling this will accomplish several things:
## <ol>
## <li> most importantly it finds the closure of all spells that need to
@@ -399,7 +467,10 @@ compute_uninstalled_depends()
debug "libdepends" "already looked at ${spells[$_idx]}, skipping"
fi
done
+ private_check_slot_conflicts
+
# we no longer need these, no sense in keeping them around
+ hash_reset slot_subslot_hash
hash_unset depends_looked_at
hash_unset sub_dep_f_hash
hash_unset sub_dep_r_hash
@@ -415,12 +486,18 @@ compute_uninstalled_depends()
#---------------------------------------------------------------------
## A private function for running a spell's DEPENDS script.
## No functions except libdepends functions should use this.
-## @param Spell
+## @param spell instance
#---------------------------------------------------------------------
private_run_depends()
{
debug "libdepends" "$FUNCNAME - $*"
- local SPELL=$1
+ # run_details below puts the bare name in $SPELL and the instance back
+ # in $SPELL_INSTANCE, but the depends file and the spell config are
+ # wanted before it runs
+ local SPELL_INSTANCE=$1
+ local SPELL SPELL_DISK_TAG
+ slot_split "$1" SPELL || { slot_bad_token_message "$1"; return 1; }
+ slot_instance_tag "$1" SPELL_DISK_TAG
# special accumulators for the current spell
local NEW_DEPENDS=""
@@ -431,65 +508,66 @@ private_run_depends()
local spell_depends
local spell_sub_depends
- hash_put "depends_looked_at" "$SPELL" "start"
+ hash_put "depends_looked_at" "$SPELL_INSTANCE" "start"
# We only need to run the stuff if we are going to be casting.
# It only needs to be added to the casting hash table if we are
# really casting it
- if private_should_cast $SPELL ; then
+ if private_should_cast $SPELL_INSTANCE ; then
# this cant go in private_should_cast because then the dependee wont
# have a chance at being fixed
# we check already here, so a specified spell isn't needlessly run
if spell_exiled $1; then
- depends_message "${SPELL}" "is exiled and will not be cast."
+ depends_message "${SPELL_INSTANCE}" "is exiled and will not be cast."
log_failure_reason exiled $1
return 1
fi
- get_uncommitted_depends_file $SPELL spell_depends
+ get_uncommitted_depends_file "$SPELL_INSTANCE" spell_depends
spell_sub_depends=$spell_depends.sub
if [ -n "$RECONFIGURE" ]; then
local t_abandonded_p
- rm -f $DEPENDS_CONFIG/$SPELL
- test -f $DEPENDS_CONFIG/$SPELL.p &&
+ local tag=${SPELL_DISK_TAG:-$SPELL}
+ rm -f $DEPENDS_CONFIG/$tag
+ test -f $DEPENDS_CONFIG/$tag.p &&
mkdir -p $ABANDONED_PERSIST &&
- lock_file "$DEPENDS_CONFIG/$SPELL.p" &&
- lock_start_transaction "$ABANDONED_PERSIST/$SPELL.p" t_abandonded_p &&
- mv -f "$DEPENDS_CONFIG/$SPELL.p" "$t_abandonded_p" &&
- unlock_file "$DEPENDS_CONFIG/$SPELL.p" &&
- lock_commit_transaction "$ABANDONED_PERSIST/$SPELL.p"
+ lock_file "$DEPENDS_CONFIG/$tag.p" &&
+ lock_start_transaction "$ABANDONED_PERSIST/$tag.p" t_abandonded_p &&
+ mv -f "$DEPENDS_CONFIG/$tag.p" "$t_abandonded_p" &&
+ unlock_file "$DEPENDS_CONFIG/$tag.p" &&
+ lock_commit_transaction "$ABANDONED_PERSIST/$tag.p"
fi
prepare_spell_config
SCRIPT_DIRECTORY=$(codex_find_spell_by_name $SPELL)
- run_prepare $SPELL &&
+ run_prepare $SPELL_INSTANCE &&
run_details &&
run_configure $SPELL &&
run_other $SPELL &&
run_depends $SPELL &&
run_up_triggers $SPELL &&
- run_our_sub_depends $SPELL &&
+ run_our_sub_depends $SPELL_INSTANCE &&
# possibly recast things that depend on us if option is set (-B)
private_upward_depends $SPELL &&
- private_recast_optionals $SPELL &&
+ private_recast_optionals $SPELL_INSTANCE &&
private_add_triggerees &&
private_add_depends ||
- { debug "libdepends" "$FUNCNAME failed to process $SPELL." ; return 1; }
+ { debug "libdepends" "$FUNCNAME failed to process $SPELL_INSTANCE." ;
return 1; }
# no point in keeping the file around if its empty...
test -s $spell_depends || rm -f $spell_depends $spell_sub_depends
- if real_list_find "${RUNTIME_DEPENDS[*]}" "$SPELL"; then
+ if real_list_find "${RUNTIME_DEPENDS[*]}" "$SPELL_INSTANCE"; then
local k=${#BONUS_SPELLS[@]}
- BONUS_SPELLS[$k]=$SPELL
+ BONUS_SPELLS[$k]=$SPELL_INSTANCE
fi
# this processes any sub-depends we requested and the sub-dependee
# has already been processed, so we must process them on their behalf
# this must be done after everything else!
- run_other_sub_depends "$SPELL" || return 1
+ run_other_sub_depends "$SPELL_INSTANCE" || return 1
else
- depends_message "${SPELL}" "No work to do."
- hash_put "depends_looked_at" "$SPELL" "ignore"
+ depends_message "${SPELL_INSTANCE}" "No work to do."
+ hash_put "depends_looked_at" "$SPELL_INSTANCE" "ignore"
fi
# if there weren't any depends no sense in keeping the file around
@@ -548,7 +626,11 @@ private_upward_depends() {
# and/or move the weird pattern into library functions...
# (afrayedknot 2005-10-02)
lock_file "$DEPENDS_STATUS"
- tmp=$(grep "^.*:$1\(([^:]*)\)\?:" $DEPENDS_STATUS|cut -f1 -d:|tr "\n" "
")
+ # what depends on us is an instance, and its slot is at the end of
+ # the row rather than in the field cut(1) would hand back; -B stays
+ # deliberately broad about which slot of us was depended on
+ tmp=$(grep "^[^:]*:${1%%:*}\(([^:]*)\)\?:" $DEPENDS_STATUS |
+ state_depends_requesters | tr "\n" " ")
unlock_file "$DEPENDS_STATUS"
local j k each
let j=${#NEW_UP_DEPENDS[*]}
@@ -586,16 +668,23 @@ private_recast_optionals_sub() {
private_recast_optionals() {
if [[ $RECAST_OPTIONALS ]] && [[ $RECAST_OPTIONALS != ignore ]] ; then
local spell j enabled
+ # both ends of a row are instances: the dependency that is off is one
+ # slot of us, and what has it off is a slot of itself, which is at
+ # the end of the row rather than in its first field
+ local dep_spell dep_slot
+ slot_split "$1" dep_spell dep_slot || { dep_spell=$1; dep_slot=""; }
+ slot_is_default "$dep_slot" && dep_slot=""
let j=${#NEW_UP_DEPENDS[*]}
for spell in $(search_depends_status_exact $DEPENDS_STATUS \
- '.*' "$1\(([^:]*)\)\?" off optional '.*' '.*'|cut -f1 -d:);
do
+ '.*' "$dep_spell\(([^:]*)\)\?" off optional '.*' '.*' \
+ "$dep_slot" | state_depends_requesters); do
private_recast_optionals_sub $spell \
"$spell has a disabled optional dependency on $1"
if [[ $enabled ]] ; then
NEW_UP_DEPENDS[$j]=$spell
let j++
# toggle the dependency, so the user doesn't get requeried
- toggle_depends_status $DEPENDS_STATUS $spell "$1(\([^:]*\))?"
+ toggle_depends_status $DEPENDS_STATUS $spell "$dep_spell(\([^:]*\))?"
spells=( ${spells[*]} $spell )
fi
done
@@ -603,13 +692,14 @@ private_recast_optionals() {
# also check for disabled features that $1 is a provider of
local features feature_regex
# get the features and build a regex for quicker lookup
- features=$(codex_find_spell_provides $1)
+ features=$(codex_find_spell_provides $dep_spell)
for feature in $features; do
feature_regex="$feature\|$feature_regex"
done
feature_regex=${feature_regex%\\|}
for spell in $(search_depends_status_exact $DEPENDS_STATUS \
- '.*' "($feature_regex)" off optional '.*' '.*'|cut -f1
-d:|sort -u); do
+ '.*' "($feature_regex)" off optional '.*' '.*' |
+ state_depends_requesters | sort -u); do
private_recast_optionals_sub $spell \
"$spell has a disabled optional dependency on $1 as a provider"
if [[ $enabled ]] ; then
@@ -617,7 +707,7 @@ private_recast_optionals() {
let j++
# toggle all the features that $1 provides and set $1 as their
provider
for feature in $features; do
- change_spell_provider $DEPENDS_STATUS $spell "$1" "$feature" "on"
+ change_spell_provider $DEPENDS_STATUS $spell "$dep_spell"
"$feature" "on"
done
spells=( ${spells[*]} $spell )
fi
@@ -625,6 +715,68 @@ private_recast_optionals() {
fi
}
+#---------------------------------------------------------------------
+## Works out which instance of a spell a dependency atom asks for.
+##
+## An installed instance that answers the atom is the answer: a
+## dependency that leaves an axis unconstrained must not drag a second
+## slot in when what is already on the system will do. Failing that,
+## the unconstrained axes take their default values -- whatever this
+## resolves to is about to be cast, and a cast has to name one point in
+## the matrix.
+##
+## @param spell name
+## @param slot request, may be empty or hold '*' axes
+## @param subslot request, may be empty
+## @param variable to set to the instance
+##
+## @return 1 if the spell offers no slot answering the request
+#---------------------------------------------------------------------
+depends_resolve_atom() {
+ # every local is __res_ prefixed: slot_split, query_spell_subslot,
+ # codex_resolve_slot and slot_join all upvar into a name the caller
+ # chose, and would write here instead if the names met
+ local __res_inst __res_slot __res_subslot
+
+ for __res_inst in $(spell_instances "$1${2:+:$2}"); do
+ slot_split "$__res_inst" "" __res_slot || continue
+ query_spell_subslot "$__res_inst" __res_subslot
+ slot_atom_matches "$2" "$3" "$__res_slot" "$__res_subslot" || continue
+ upvar "$4" "$__res_inst"
+ return 0
+ done
+
+ if ! codex_resolve_slot "$1" "$2" __res_slot; then
+ # a spell that pins its slot in DETAILS rather than declaring a
+ # SLOTS file has no spec to resolve against, so a concrete request
+ # is taken at its word and DETAILS has the last one
+ [[ $(codex_get_spell_slot_spec "$1") ]] && return 1
+ case $2 in
+ ''|*'*'*) return 1 ;;
+ esac
+ __res_slot=$2
+ fi
+ slot_join "$1" "$__res_slot" "$4"
+}
+
+#---------------------------------------------------------------------
+## Puts the slot a depends row recorded back onto the spell name from
+## it, so that the answer the row remembers names the instance it named
+## when it was written.
+##
+## @param variable holding the spell name, and to set to the instance
+## @param the row's packed dependency slot, may be empty
+#---------------------------------------------------------------------
+depends_row_instance() {
+ local __row_spell __row_slot
+
+ [[ $2 ]] || return 0
+ eval "__row_spell=\$$1"
+ [[ $__row_spell ]] || return 0
+ slot_unpack "$2" __row_slot
+ slot_join "$__row_spell" "$__row_slot" "$1"
+}
+
###################BEGIN CALLBACKS FROM OUTSIDE#######################
#---------------------------------------------------------------------
@@ -656,9 +808,19 @@ real_generic_required_depends()
return 1
fi
+ # a dependency is an atom -- spell[:slot][/subslot][=] -- and only the
+ # spell name of it means anything to the codex. The rest rides down
+ # to private_common_depends in these locals, which is dynamic scope,
+ # but is how $spell_depends and $spells get down there too
+ local dep_spell dep_slot dep_subslot dep_bind
+ if ! slot_atom_split "$1" dep_spell dep_slot dep_subslot dep_bind; then
+ slot_bad_token_message "$1"
+ return 1
+ fi
+
# determine if we're dealing with a provider or a spell
local is_provider
- if ! codex_does_spell_exist $1 &> /dev/null; then
+ if ! codex_does_spell_exist $dep_spell &> /dev/null; then
is_provider=yes
fi
@@ -735,12 +897,26 @@ real_generic_required_depends()
fi
fi
+ # past here the atom is spoken for by one instance -- or, if it names
+ # a feature, by the bare feature name, whose slot is applied to
+ # whichever provider gets chosen
+ local dep_target=$dep_spell
+ if [[ ! $is_provider ]] &&
+ ! depends_resolve_atom "$dep_spell" "$dep_slot" "$dep_subslot"
dep_target
+ then
+ message "${PROBLEM_COLOR}No slot of ${SPELL_COLOR}$dep_spell" \
+ "${PROBLEM_COLOR}answers ${SPELL_COLOR}$1${DEFAULT_COLOR}"
+ return 1
+ fi
+ shift
+ set -- "$dep_target" "$@"
+
local target_spell
if [[ $is_provider ]]; then
work_depends_provider "$failure_ok" "$article" \
"$query_term" "$database_term" "$@" &&
if [[ "$requested_sub_depends" ]]; then
- target_spell=$(get_spell_provider "$SPELL" "$1")
+ target_spell=$(get_spell_provider "$SPELL_INSTANCE" "$1")
fi
else
work_depends_spell "$failure_ok" "$article" \
@@ -799,9 +975,17 @@ real_generic_optional_depends()
return 1
fi
+ # as in real_generic_required_depends: the atom is split here, and its
+ # slot and subslot ride down to private_common_depends in locals
+ local dep_spell dep_slot dep_subslot dep_bind
+ if ! slot_atom_split "$1" dep_spell dep_slot dep_subslot dep_bind; then
+ slot_bad_token_message "$1"
+ return 1
+ fi
+
# determine if we're dealing with a provider or a spell
local is_provider
- if ! codex_does_spell_exist $1 &> /dev/null; then
+ if ! codex_does_spell_exist $dep_spell &> /dev/null; then
is_provider=yes
fi
@@ -871,20 +1055,32 @@ real_generic_optional_depends()
message "${PROBLEM_COLOR}no grimoire for $1 was
retrieved${DEFAULT_COLOR}"
message "Assuming dependency is off because it could not be met"
if [[ $is_provider ]]; then
- private_common_depends "($1)" "off" "$database_term" "$2" "$3"
+ private_common_depends "($dep_spell)" "off" "$database_term" "$2"
"$3"
else
- private_common_depends "$1" "off" "$database_term" "$2" "$3"
+ private_common_depends "$dep_spell" "off" "$database_term" "$2" "$3"
fi
return 0
fi
fi
+ # as in real_generic_required_depends
+ local dep_target=$dep_spell
+ if [[ ! $is_provider ]] &&
+ ! depends_resolve_atom "$dep_spell" "$dep_slot" "$dep_subslot"
dep_target
+ then
+ message "${PROBLEM_COLOR}No slot of ${SPELL_COLOR}$dep_spell" \
+ "${PROBLEM_COLOR}answers ${SPELL_COLOR}$1${DEFAULT_COLOR}"
+ return 1
+ fi
+ shift
+ set -- "$dep_target" "$@"
+
local target_spell
if [[ $is_provider ]]; then
work_optional_depends_provider "$failure_ok" "$article" \
"$query_term" "$database_term" "$@" &&
if [[ "$requested_sub_depends" ]]; then
- target_spell=$(get_spell_provider "$SPELL" "$1")
+ target_spell=$(get_spell_provider "$SPELL_INSTANCE" "$1")
fi
else
work_optional_depends_spell "$failure_ok" "$article" \
@@ -893,7 +1089,7 @@ real_generic_optional_depends()
fi &&
if [[ "$requested_sub_depends" ]] &&
[[ "$target_spell" ]] &&
- is_depends_enabled "$SPELL" "$target_spell"; then
+ is_depends_enabled "$SPELL_INSTANCE" "$target_spell"; then
for sub_depend in $requested_sub_depends; do
sub_depends "$target_spell" "$sub_depend" || return 1
done
@@ -961,8 +1157,9 @@ work_depends_provider()
# provider case, if the user chose none, we would fall out during
spell_ok
# and we transparently switch between optional and required without
# anyone noticing
- explode "$(search_depends_status $DEPENDS_STATUS "$SPELL" ".*($1)")" ":"
"status"
+ explode "$(search_depends_status $DEPENDS_STATUS
"${SPELL_INSTANCE:-$SPELL}" ".*($1)")" ":" "status"
tmp=${status[1]%(*} # Name of spell which provides $1
+ depends_row_instance tmp "${status[7]}"
if spell_ok $tmp; then
message "${MESSAGE_COLOR}Using ${SPELL_COLOR}$tmp${DEFAULT_COLOR}"
private_common_depends "$tmp($1)" "on" "$database_term" "$2" "$3"
@@ -971,8 +1168,8 @@ work_depends_provider()
fi
# check if theres an abandoned answer, but only if its still a provider
- if [[ ! $default ]] && [ -e $ABANDONED_DEPENDS/$SPELL ] ; then
- tmp=$(search_depends_status $ABANDONED_DEPENDS/$SPELL "$SPELL"
".*($1)"|awk -F: '{print $2;exit}')
+ if [[ ! $default ]] && [ -e $ABANDONED_DEPENDS/${SPELL_DISK_TAG:-$SPELL} ]
; then
+ tmp=$(search_depends_status $ABANDONED_DEPENDS/${SPELL_DISK_TAG:-$SPELL}
"${SPELL_INSTANCE:-$SPELL}" ".*($1)"|awk -F: '{print $2;exit}')
[[ $tmp ]] && real_list_find "$CANDIDATES" "$tmp" && default=$tmp
fi
@@ -999,7 +1196,7 @@ work_depends_provider()
if [[ $choices == ${choices##* } ]]; then
default=$choices
else
- explode "$(search_depends_status $DEPENDS_STATUS "$SPELL" ".*($1)")"
":" "status"
+ explode "$(search_depends_status $DEPENDS_STATUS
"${SPELL_INSTANCE:-$SPELL}" ".*($1)")" ":" "status"
local old_provider=${status[1]%(*} # Name of spell which provides $1
if real_list_find "$choices" $old_provider; then
default=$old_provider
@@ -1009,10 +1206,19 @@ work_depends_provider()
fi
fi
- local provider
+ local provider provider_instance
select_provider "provider" "$default" 0 $CANDIDATES
- private_common_depends "$provider($1)" "on" "$database_term" "$2" ""
+ # a provider may be slotted itself, and the atom's slot asked for one
+ # of its slots: "depends X11:2" wants a provider of X11 in slot 2
+ if ! depends_resolve_atom "$provider" "$dep_slot" "$dep_subslot" \
+ provider_instance; then
+ message "${PROBLEM_COLOR}No slot of ${SPELL_COLOR}$provider" \
+ "${PROBLEM_COLOR}provides ${SPELL_COLOR}$1${DEFAULT_COLOR}"
+ return 1
+ fi
+
+ private_common_depends "$provider_instance($1)" "on" "$database_term" "$2"
""
}
#---------------------------------------------------------------------
@@ -1075,15 +1281,16 @@ work_optional_depends_provider()
local CANDIDATES=$( find_providers $1)
# if not reconfiguring check if theres already an answer in DEPENDS_STATUS
if [[ ! $RECONFIGURE ]]; then
- explode "$(search_depends_status_simple $DEPENDS_STATUS "$SPELL"
".*($1)" "on")" ":" "status"
+ explode "$(search_depends_status_simple $DEPENDS_STATUS
"${SPELL_INSTANCE:-$SPELL}" ".*($1)" "on")" ":" "status"
local tmp=${status[1]%(*} # Name of spell which provides $1
+ depends_row_instance tmp "${status[7]}"
if [[ "$tmp" ]] && spell_ok "$tmp"; then
message "${MESSAGE_COLOR}Using ${SPELL_COLOR}$tmp${DEFAULT_COLOR}"
private_common_depends "$tmp($1)" "on" "$database_term" "$2" "$3"
return 0
fi
if [[ "$(search_depends_status_simple $DEPENDS_STATUS \
- "$SPELL" ".*($1)" "off")" ]]; then
+ "${SPELL_INSTANCE:-$SPELL}" ".*($1)" "off")" ]];
then
message "${MESSAGE_COLOR}Using ${SPELL_COLOR}[none]${DEFAULT_COLOR}"
private_common_depends "$tmp($1)" "off" "$database_term" "$2" "$3"
return 0
@@ -1091,12 +1298,12 @@ work_optional_depends_provider()
fi
# check if theres an abandoned answer, but only if its still a provider
- if [[ ! $default ]] && [ -e $ABANDONED_DEPENDS/$SPELL ] ; then
- tmp=$(search_depends_status_simple $ABANDONED_DEPENDS/$SPELL "$SPELL"
".*($1)" "on"|awk -F: '{print $2;exit}')
+ if [[ ! $default ]] && [ -e $ABANDONED_DEPENDS/${SPELL_DISK_TAG:-$SPELL} ]
; then
+ tmp=$(search_depends_status_simple
$ABANDONED_DEPENDS/${SPELL_DISK_TAG:-$SPELL} "${SPELL_INSTANCE:-$SPELL}"
".*($1)" "on"|awk -F: '{print $2;exit}')
if [[ $tmp ]] && real_list_find "$CANDIDATES" "$tmp"; then
default=$tmp
else
- tmp=$(search_depends_status_simple $ABANDONED_DEPENDS/$SPELL "$SPELL"
".*($1)" "off")
+ tmp=$(search_depends_status_simple
$ABANDONED_DEPENDS/${SPELL_DISK_TAG:-$SPELL} "${SPELL_INSTANCE:-$SPELL}"
".*($1)" "off")
[[ $tmp ]] && default=none
fi
fi
@@ -1145,7 +1352,7 @@ work_optional_depends_provider()
default=$choices
else
# only search for enabled dependencies since none is already the
default
- explode "$(search_depends_status_simple $DEPENDS_STATUS "$SPELL"
".*($1)" "on")" ":" "status"
+ explode "$(search_depends_status_simple $DEPENDS_STATUS
"${SPELL_INSTANCE:-$SPELL}" ".*($1)" "on")" ":" "status"
local old_provider=${status[1]%(*} # Name of spell which provides $1
if real_list_find "$choices" $old_provider; then
default=$old_provider
@@ -1155,13 +1362,20 @@ work_optional_depends_provider()
fi
fi
- local provider
+ local provider provider_instance
select_provider "provider" "$default" 1 $CANDIDATES
if [ $provider == "none" ] ; then
private_common_depends "($1)" "off" "$database_term" "$2" "$3"
else
- private_common_depends "$provider($1)" "on" "$database_term" "$2" "$3"
+ # as in work_depends_provider, the provider may be slotted
+ if ! depends_resolve_atom "$provider" "$dep_slot" "$dep_subslot" \
+ provider_instance; then
+ message "${PROBLEM_COLOR}No slot of ${SPELL_COLOR}$provider" \
+ "${PROBLEM_COLOR}provides ${SPELL_COLOR}$1${DEFAULT_COLOR}"
+ return 1
+ fi
+ private_common_depends "$provider_instance($1)" "on" "$database_term"
"$2" "$3"
fi
}
@@ -1254,7 +1468,7 @@ work_optional_depends_spell()
# not reconfiguring...
# example: icewm:imlib:off:optional:--with-imlib:--with-xpm
local status=()
- explode "$(search_depends_status $DEPENDS_STATUS "$SPELL" "$1")" ":"
"status"
+ explode "$(search_depends_status $DEPENDS_STATUS
"${SPELL_INSTANCE:-$SPELL}" "$1")" ":" "status"
if [[ ${status[2]} ]] ; then
# ah there are! use them
if [[ ${status[2]} == "on" ]] ; then
@@ -1279,9 +1493,9 @@ work_optional_depends_spell()
# check for abandoned answers
- if [ -e $ABANDONED_DEPENDS/$SPELL ] ; then
+ if [ -e $ABANDONED_DEPENDS/${SPELL_DISK_TAG:-$SPELL} ] ; then
debug "libdepends" "Checking in abandoned depends"
- default=$(search_depends_status $ABANDONED_DEPENDS/$SPELL "$SPELL"
"$1"|awk -F: '{print $3;exit}')
+ default=$(search_depends_status
$ABANDONED_DEPENDS/${SPELL_DISK_TAG:-$SPELL} "${SPELL_INSTANCE:-$SPELL}"
"$1"|awk -F: '{print $3;exit}')
fi
# check the defaults file...
@@ -1401,7 +1615,9 @@ real_up_trigger() {
"${SPELL_COLOR}${2}${DEFAULT_COLOR}" \
"${CHECK_COLOR}on${DEFAULT_COLOR}" \
"${SPELL_COLOR}${1}${DEFAULT_COLOR}"
- private_up_trigger $SPELL "$1" "$2"
+ # it is one instance of us that triggers, and the tree the engine
+ # walks is between instances
+ private_up_trigger ${SPELL_INSTANCE:-$SPELL} "$1" "$2"
}
#---------------------------------------------------------------------
@@ -1528,7 +1744,36 @@ real_force_depends() {
private_common_depends()
{
debug "libdepends" "$FUNCNAME - $SPELL - $@"
- add_depends $spell_depends "$SPELL" "$@"
+
+ # the dependency's slot is a field of its own, so the name field holds
+ # the bare spell -- with the feature still parenthesised after it,
+ # since that is the shape get_spell_provider reads back
+ local dep_name=$1 dep_feature="" dep_row_slot=""
+ if [[ $1 == *'('* ]]; then
+ dep_name=${1%%(*}
+ dep_feature="(${1#*(}"
+ fi
+ if [[ $dep_name ]]; then
+ slot_split "$dep_name" dep_name dep_row_slot || return 1
+ slot_is_default "$dep_row_slot" && dep_row_slot=""
+ fi
+
+ # '=' is not a legal subslot, so it stands for one not bound yet: an
+ # atom asking to be told when an ABI moves is written before the
+ # dependency it asks about has been cast, and bind_depends_subslots
+ # fills the answer in on the way to $DEPENDS_STATUS
+ local dep_bound=""
+ [[ $dep_bind == yes ]] && [[ $2 == on ]] && dep_bound="="
+
+ # an atom that pins an ABI has a claim on the one instance, and two
+ # such claims can disagree; private_check_slot_conflicts settles that
+ # once every spell has had its say
+ if [[ $2 == on ]] && [[ $dep_subslot ]] && [[ $dep_subslot != '*' ]] ; then
+ hash_append slot_subslot_hash "${1%%(*}" "$dep_subslot $SPELL_INSTANCE"
$'\n'
+ fi
+
+ add_depends $spell_depends "$SPELL_INSTANCE" "$dep_name$dep_feature" \
+ "$2" "$3" "$4" "$5" "$dep_row_slot" "$dep_bound"
# runtime and suggested depends have no formal dependency info
if [[ $2 == on ]] ; then
@@ -1574,7 +1819,7 @@ private_add_triggerees() {
run_spell_file TRIGGER_CHECK trigger_check
running_trigger=$?
if [[ $running_trigger == 0 ]] ; then
- private_up_trigger "$SPELL" "$TARGET" "$ACTION"
+ private_up_trigger "$SPELL_INSTANCE" "$TARGET" "$ACTION"
fi
}
@@ -1584,12 +1829,14 @@ private_add_triggerees() {
private_add_triggerees_sub1
}
+ # what waits on us waits on an instance of us, and what it names is an
+ # instance too -- one slot may be triggered where another is not
for ACTION in cast_self check_self dispel_self run_script; do
- for TARGET in $(get_triggerees $SPELL on_cast $ACTION); do
+ for TARGET in $(get_triggerees $SPELL_INSTANCE on_cast $ACTION); do
spell_ok $TARGET || continue
if [[ $ACTION == run_script ]] ; then
iterate private_add_triggerees_sub2 $'\n' \
- "$(get_run_script_triggers $SPELL on_cast $TARGET)"
+ "$(get_run_script_triggers $SPELL_INSTANCE on_cast
$TARGET)"
else
private_add_triggerees_sub1
fi
@@ -1604,19 +1851,21 @@ private_add_triggerees() {
#---------------------------------------------------------------------
private_add_depends()
{
- debug "libdepends" "$FUNCNAME: SPELL=$SPELL, NEW_DEPENDS=${NEW_DEPENDS[*]}"
- hash_append "$CAST_HASH" "$SPELL" "${NEW_DEPENDS[*]}"
+ # the tables are between instances: one slot of a spell may depend on
+ # something another slot of it does not
+ debug "libdepends" "$FUNCNAME: SPELL=$SPELL_INSTANCE,
NEW_DEPENDS=${NEW_DEPENDS[*]}"
+ hash_append "$CAST_HASH" "$SPELL_INSTANCE" "${NEW_DEPENDS[*]}"
for child in ${NEW_DEPENDS[*]}; do
- hash_append "$BACK_CAST_HASH" "$child" "$SPELL"
+ hash_append "$BACK_CAST_HASH" "$child" "$SPELL_INSTANCE"
done
# force implied basesystem dependency in the depends tree
if [[ $FORCE_BASESYSTEM_DEPENDS == on ]] &&
[[ $SPELL != basesystem ]] &&
! real_list_find "$base_deps" "$SPELL"; then
- hash_append "$CAST_HASH" "$SPELL" "basesystem"
- hash_append "$BACK_CAST_HASH" "basesystem" "$SPELL"
+ hash_append "$CAST_HASH" "$SPELL_INSTANCE" "basesystem"
+ hash_append "$BACK_CAST_HASH" "basesystem" "$SPELL_INSTANCE"
fi
spells=( ${spells[*]} ${NEW_DEPENDS[*]}
@@ -1630,7 +1879,7 @@ private_add_depends()
UP_DEPENDS=( ${UP_DEPENDS[*]} ${NEW_UP_DEPENDS[*]} )
- hash_put "depends_looked_at" "$SPELL" "done"
+ hash_put "depends_looked_at" "$SPELL_INSTANCE" "done"
}
@@ -1792,7 +2041,7 @@ uncommitted_upward_depends() {
run_spell_config()
{
- SPELL_CONFIG=$DEPENDS_CONFIG/$SPELL
+ SPELL_CONFIG=$DEPENDS_CONFIG/${SPELL_DISK_TAG:-$SPELL}
debug "libdepends" "run_spell_config() - DEPENDS_CONFIG=$DEPENDS_CONFIG
SPELL=$SPELL, SPELL_CONFIG=$SPELL_CONFIG DEPENDS_STATUS=$DEPENDS_STATUS"
if [ -x $SPELL_CONFIG ]; then
diff --git a/var/lib/sorcery/modules/libdepengine
b/var/lib/sorcery/modules/libdepengine
index 70b3832..aab712b 100755
--- a/var/lib/sorcery/modules/libdepengine
+++ b/var/lib/sorcery/modules/libdepengine
@@ -289,13 +289,14 @@ execute_triggers() {
local spell=$1
local failed=$2
local trigger rc
- local trg_array trg_action trg_target stuff trg_color
+ local trg_action trg_target stuff trg_color
function execute_triggers_sub() {
trigger=$1
- explode "$trigger" : trg_array
- trg_target=${trg_array[0]}
- trg_action=${trg_array[1]}
+ # the action is the last field, the target is all that is before it
+ # and may hold a slot of its own
+ trg_target=${trigger%:*}
+ trg_action=${trigger##*:}
# get the other spells that trigger this action
trg_color=$(dpgn_get_spell_color $trg_target)
@@ -329,7 +330,10 @@ execute_triggers() {
# run any other trigger besides the two above
# but not if we're summoning
local action=cast
- do_trigger "$trg_target:$spell:on_cast:$trg_action"
+ # a cause is packed in a trigger row, here as much as on disk
+ local trg_cause
+ slot_instance_tag "$spell" trg_cause || trg_cause=$spell
+ do_trigger "$trg_target:$trg_cause:on_cast:$trg_action"
fi
# a brown trigger is a loop, drop it
diff --git a/var/lib/sorcery/modules/libdispel
b/var/lib/sorcery/modules/libdispel
index f1a199c..cf29119 100755
--- a/var/lib/sorcery/modules/libdispel
+++ b/var/lib/sorcery/modules/libdispel
@@ -89,10 +89,10 @@ reap_modified_file() {
## Removes spell config stages if there are any
#---------------------------------------------------------------------
reap_spell_config_stage() {
- lock_file "$CONFIG_STAGE_DIRECTORY/$SPELL"
+ lock_file "$CONFIG_STAGE_DIRECTORY/$SPELL_DISK_TAG"
# it's locked - we also don't have to care about the unlikely "current"
stage
- rm -fr "$CONFIG_STAGE_DIRECTORY/$SPELL"
- unlock_file "$CONFIG_STAGE_DIRECTORY/$SPELL"
+ rm -fr "$CONFIG_STAGE_DIRECTORY/$SPELL_DISK_TAG"
+ unlock_file "$CONFIG_STAGE_DIRECTORY/$SPELL_DISK_TAG"
}
#---------------------------------------------------------------------
@@ -103,16 +103,16 @@ reap_depends() {
# recasts as the default
mkdir -p $ABANDONED_DEPENDS
local t_file
- lock_start_transaction "$ABANDONED_DEPENDS/$SPELL" t_file
- search_depends_status $DEPENDS_STATUS $SPELL > $t_file
- lock_commit_transaction "$ABANDONED_DEPENDS/$SPELL"
+ lock_start_transaction "$ABANDONED_DEPENDS/$SPELL_DISK_TAG" t_file
+ search_depends_status $DEPENDS_STATUS $SPELL_INSTANCE > $t_file
+ lock_commit_transaction "$ABANDONED_DEPENDS/$SPELL_DISK_TAG"
# This conditional is here because the iso team wants to be able to
# save dependencies after dispel (bug 8109), average users are expected
# to always run this code to remove old depends.
if [[ $NO_REAP_DEPENDS != "on" ]] ; then
- remove_depends_status $DEPENDS_STATUS $SPELL
- remove_sub_depends "$SUB_DEPENDS_STATUS" "$SPELL" ".*"
+ remove_depends_status $DEPENDS_STATUS $SPELL_INSTANCE
+ remove_sub_depends "$SUB_DEPENDS_STATUS" "$SPELL_INSTANCE" ".*"
fi
}
@@ -239,8 +239,8 @@ reap_state_files() {
#-----
dispel_not_possible() {
- if ! spell_ok $SPELL ; then
- message "${SPELL_COLOR}${SPELL}${DEFAULT_COLOR}" \
+ if ! spell_ok $SPELL_INSTANCE ; then
+ message "${SPELL_COLOR}${SPELL_INSTANCE}${DEFAULT_COLOR}" \
"${PROBLEM_COLOR}is not installed${DEFAULT_COLOR}."
else
false
@@ -250,13 +250,16 @@ dispel_not_possible() {
#-----
## Does the sustained checks for spells
+##
+## A bare spell name in $SUSTAINED sustains every slot of it, which is
+## what the file has always meant; naming an instance sustains the one.
#-----
dispel_sustained() {
if [ "$SUSTAIN" == "on" ] &&
- grep -q "^$SPELL$" $SUSTAINED
+ grep -q -e "^$SPELL$" -e "^$SPELL_INSTANCE$" $SUSTAINED
then
- message "${SPELL_COLOR}${SPELL}${DEFAULT_COLOR}" \
+ message "${SPELL_COLOR}${SPELL_INSTANCE}${DEFAULT_COLOR}" \
"${PROBLEM_COLOR}is sustained${DEFAULT_COLOR}."
else
false
@@ -266,20 +269,25 @@ dispel_sustained() {
#-----
## Find out where a spell is located
+## @param spell instance
## @Globals SPELL
#-----
load_spell() {
- local SPELL=$1
- if tablet_set_spell $SPELL ; then
+ local SPELL_INSTANCE=$1
+ local SPELL slot
+ slot_split "$1" SPELL slot || return 1
+ if tablet_set_spell $SPELL_INSTANCE ; then
load_functions_file
else
SCRIPT_DIRECTORY="$(codex_find_spell_by_name $SPELL)"
- VERSION=$(private_installed_version $SPELL)
- INST_LOG=$INSTALL_LOGS/$SPELL-$VERSION
- MD5_LOG=$MD5SUM_LOGS/$SPELL-$VERSION
+ VERSION=$(private_installed_version $SPELL_INSTANCE)
+ INST_LOG=$INSTALL_LOGS/$SPELL_DISK_TAG-$VERSION
+ MD5_LOG=$MD5SUM_LOGS/$SPELL_DISK_TAG-$VERSION
if [[ $SCRIPT_DIRECTORY ]] ; then
- codex_set_current_spell $SCRIPT_DIRECTORY
+ # the slot is the one being dispelled, not the spell's default:
+ # DETAILS branches on it, and PRE_REMOVE may too
+ codex_set_current_spell $SCRIPT_DIRECTORY "$slot"
load_functions_file
else
message "Spell is missing from grimoires"
@@ -315,19 +323,29 @@ post_remove() {
#---------------------------------------------------------------------
## Dispel a spell
+## @param spell instance
+##
+## One instance goes; the other slots of the spell are none of this
+## function's business. $SPELL is the bare name, for the grimoire and
+## for anything that reads a spell rather than an install of one, and
+## the disk tag is set here rather than in load_spell because
+## reap_depends runs out here, outside the subshell.
#---------------------------------------------------------------------
dispel_spell() {
- local SPELL=$1
+ local SPELL_INSTANCE=$1
+ local SPELL SPELL_DISK_TAG
+ slot_split "$1" SPELL || { slot_bad_token_message "$1"; return 1; }
+ slot_instance_tag "$1" SPELL_DISK_TAG
run_hook dispel pre
# fake dispel, only remove sorcery metadata
if [[ $DEBUG_DISPEL ]] ; then
- echo "pretending to dispel $SPELL"
- load_spell $SPELL &&
- remove_spell $SPELL &&
+ echo "pretending to dispel $SPELL_INSTANCE"
+ load_spell $SPELL_INSTANCE &&
+ remove_spell $SPELL_INSTANCE &&
reap_depends &&
message "${DISPEL_COLOR}Partly dispelled spell:" \
- "${SPELL_COLOR}${SPELL}${DEFAULT_COLOR}"
+ "${SPELL_COLOR}${SPELL_INSTANCE}${DEFAULT_COLOR}"
return
fi
@@ -340,26 +358,26 @@ dispel_spell() {
(
# this function messes with the environment a lot leave it in a
subshell
- load_spell $SPELL &&
+ load_spell $SPELL_INSTANCE &&
pre_remove &&
reaper $INST_LOG $MD5_LOG &&
post_remove &&
reap_state_files $INST_LOG &&
reap_spell_config_stage &&
- remove_spell $SPELL &&
- remove_version_cache "$VERSION_STATUS" $SPELL &&
+ remove_spell $SPELL_INSTANCE &&
+ remove_version_cache "$VERSION_STATUS" $SPELL_INSTANCE &&
{ [[ $TRIGGER == off ]] || trigger "dispel";} &&
- remove_triggers $SPELL &&
- { [[ $DEQUEUE == off ]] || pop_install_queue $SPELL; true; } &&
+ remove_triggers $SPELL_INSTANCE &&
+ { [[ $DEQUEUE == off ]] || pop_install_queue $SPELL_INSTANCE; true; }
&&
message "${DISPEL_COLOR}Dispelled spell:" \
- "${SPELL_COLOR}${SPELL}${DEFAULT_COLOR}" &&
- activity_log "dispel" "$SPELL" "$VERSION" "success"
+ "${SPELL_COLOR}${SPELL_INSTANCE}${DEFAULT_COLOR}" &&
+ activity_log "dispel" "$SPELL_INSTANCE" "$VERSION" "success"
) &&
reap_depends
local rc=$?
if [[ "$rc" != 0 ]] ; then
message "${PROBLEM_COLOR}Dispel of${DEFAULT_COLOR}" \
- "${SPELL_COLOR}$SPELL${DEFAULT_COLOR}" \
+ "${SPELL_COLOR}$SPELL_INSTANCE${DEFAULT_COLOR}" \
"${PROBLEM_COLOR}failed!${DEFAULT_COLOR}"
return $rc
fi
diff --git a/var/lib/sorcery/modules/libgrimoire
b/var/lib/sorcery/modules/libgrimoire
index d61c5b6..dbd29c3 100755
--- a/var/lib/sorcery/modules/libgrimoire
+++ b/var/lib/sorcery/modules/libgrimoire
@@ -209,31 +209,46 @@ real_installed_version() {
#---------------------------------------------------------------------
## @Type API
-## @param spell
+## @param spell instance
## Returns the current version of the given spell
## Doesnt do sanity check for spell_ok, used internally for speed.
## Behavior is undefined is spell is not installed.
##
#---------------------------------------------------------------------
private_installed_version() {
- awk -F: -v spell="$1" '$1 == spell { print $4; exit }' $SPELL_STATUS
2>/dev/null
+ local spell request
+
+ # a bare spell name asks about every slot of it, and the first row
+ # answers, exactly as this has always done
+ state_split_query "$1" spell request || return 1
+ state_spell_status_rows "$SPELL_STATUS" "$spell" "$request" "" |
+ cut -f4 -d: | head -n 1
}
#---------------------------------------------------------------------
-## @param spell
+## @param spell, or spell:slot
## @param default answer to dispel query
## @Type API
## If the default answer is anything other than 'y' then 'n' is assumed.
## returns the given spellname if it is installed
##
+## A conflict is with an install, not with a spell, so a name given
+## without a slot conflicts with every installed slot of it -- and each
+## is named in full, since what comes back here is what gets dispelled.
+## A spell may name one of its own slots: gcc:14 and gcc:15 living side
+## by side is the point of slots, but two slots of something else may
+## well be one file each in the same place.
+##
#---------------------------------------------------------------------
real_conflicts() {
debug "libgrimoire" "Running conflicts() on $1. Default query answer $2."
- if spell_ok "$1"; then
- case "$2" in y) a=y;; *) a=n;; esac
- echo "$1:$a"
- fi
+ local instance
+ case "$2" in y) a=y;; *) a=n;; esac
+
+ for instance in $(spell_instances "$1"); do
+ echo "$instance:$a"
+ done
}
#---------------------------------------------------------------------
diff --git a/var/lib/sorcery/modules/libmisc b/var/lib/sorcery/modules/libmisc
index c4ba9c6..95e6db5 100755
--- a/var/lib/sorcery/modules/libmisc
+++ b/var/lib/sorcery/modules/libmisc
@@ -977,8 +977,8 @@ config_get_last_option() {
fi
local pfile
- if test -f $ABANDONED_PERSIST/$SPELL.p; then
- pfile=$ABANDONED_PERSIST/$SPELL.p
+ if test -f $ABANDONED_PERSIST/${SPELL_DISK_TAG:-$SPELL}.p; then
+ pfile=$ABANDONED_PERSIST/${SPELL_DISK_TAG:-$SPELL}.p
# leave this out for now until theres a way to disable it
# having it enabled will make it difficult to get back to the true defaults
#else
diff --git a/var/lib/sorcery/modules/libresurrect
b/var/lib/sorcery/modules/libresurrect
index 60c7fe4..b747659 100755
--- a/var/lib/sorcery/modules/libresurrect
+++ b/var/lib/sorcery/modules/libresurrect
@@ -18,8 +18,12 @@ can_resurrect() {
debug "libresurrect" "can_resurrect - $*"
local SPELL=$1
local VERSION=$2
+ # the tag comes off the argument, not the environment: callers ask
+ # about instances they are not currently loaded as
+ local tag
+ slot_instance_tag "$SPELL" tag || tag=$SPELL
- find_cache "$INSTALL_CACHE/$SPELL-$VERSION-$HOST" || return 1
+ find_cache "$INSTALL_CACHE/$tag-$VERSION-$HOST" || return 1
debug "libresurrect" "I am able to resurrect."
return 0
}
@@ -67,11 +71,16 @@ can_resurrect() {
#------------------------------------------------------------------------
resurrect_spell() { (
debug "libresurrect" "resurrect - $*"
- local SPELL=$1
+ # as in dispel_spell, two names: the instance for anything that names
+ # an install, the bare spell for anything that names a spell
+ local SPELL_INSTANCE=$1
+ local SPELL SPELL_DISK_TAG
+ slot_split "$1" SPELL || { slot_bad_token_message "$1"; return 1; }
+ slot_instance_tag "$1" SPELL_DISK_TAG
local VERSION=$2
local CACHE_COMP=$3
- spell_held $SPELL && {
+ spell_held $SPELL_INSTANCE && {
message "Refusing to resurrect a held spell, please unhold it first"
return 1
}
@@ -83,7 +92,7 @@ resurrect_spell() { (
# 1) if no cache file exists fail
if [[ -z $CACHE_COMP ]]; then
- CACHE_COMP=$(can_resurrect $SPELL $VERSION) || {
+ CACHE_COMP=$(can_resurrect $SPELL_INSTANCE $VERSION) || {
message "No cache file could be found"
return 1
}
@@ -99,7 +108,7 @@ resurrect_spell() { (
# 3) setup resurrect sandbox
- local RESURRECT_DIR=$BUILD_DIRECTORY/resurrect-$SPELL-$VERSION
+ local RESURRECT_DIR=$BUILD_DIRECTORY/resurrect-$SPELL_DISK_TAG-$VERSION
mkdir -p $BUILD_DIRECTORY &&
mk_source_dir $RESURRECT_DIR || {
@@ -127,30 +136,30 @@ resurrect_spell() { (
# 5) if the spell is installed, take its install/md5 logs, and normalize
them
# every way possible so I dont have to think about it later
local installed
- if spell_ok $SPELL ; then
+ if spell_ok $SPELL_INSTANCE ; then
installed=yes
# note if installed is not yes, none of this should be assumed to exist
# welcome to the first ring of pathname adjustment hell
- local OLD_SPELL_VERSION=$(private_installed_version $SPELL)
- local OLD_INSTALL_LOG=${INSTALL_LOGS}/$SPELL-$OLD_SPELL_VERSION
- local OLD_MD5_LOG=${MD5SUM_LOGS}/$SPELL-$OLD_SPELL_VERSION
+ local OLD_SPELL_VERSION=$(private_installed_version $SPELL_INSTANCE)
+ local OLD_INSTALL_LOG=${INSTALL_LOGS}/$SPELL_DISK_TAG-$OLD_SPELL_VERSION
+ local OLD_MD5_LOG=${MD5SUM_LOGS}/$SPELL_DISK_TAG-$OLD_SPELL_VERSION
- local OLD_INSTALL_LOG_F=$TMP_DIR/old.install.$SPELL.filterable
- local OLD_INSTALL_LOG_R=$TMP_DIR/old.install.$SPELL.root
+ local OLD_INSTALL_LOG_F=$TMP_DIR/old.install.$SPELL_DISK_TAG.filterable
+ local OLD_INSTALL_LOG_R=$TMP_DIR/old.install.$SPELL_DISK_TAG.root
- local OLD_DATA_F=$TMP_DIR/old.data.$SPELL.filterable
- local OLD_DATA_R=$TMP_DIR/old.data.$SPELL.root
- local OLD_DATA_F_C=$TMP_DIR/old.data.$SPELL.filterable.config
- local OLD_DATA_R_C=$TMP_DIR/old.data.$SPELL.root.config
- local OLD_DATA_F_NC=$TMP_DIR/old.data.$SPELL.filterable.nonconfig
- local OLD_DATA_R_NC=$TMP_DIR/old.data.$SPELL.root.nonconfig
+ local OLD_DATA_F=$TMP_DIR/old.data.$SPELL_DISK_TAG.filterable
+ local OLD_DATA_R=$TMP_DIR/old.data.$SPELL_DISK_TAG.root
+ local OLD_DATA_F_C=$TMP_DIR/old.data.$SPELL_DISK_TAG.filterable.config
+ local OLD_DATA_R_C=$TMP_DIR/old.data.$SPELL_DISK_TAG.root.config
+ local
OLD_DATA_F_NC=$TMP_DIR/old.data.$SPELL_DISK_TAG.filterable.nonconfig
+ local OLD_DATA_R_NC=$TMP_DIR/old.data.$SPELL_DISK_TAG.root.nonconfig
- local OLD_MDATA_F=$TMP_DIR/old.mdata.$SPELL.filterable
- local OLD_MDATA_R=$TMP_DIR/old.mdata.$SPELL.root
+ local OLD_MDATA_F=$TMP_DIR/old.mdata.$SPELL_DISK_TAG.filterable
+ local OLD_MDATA_R=$TMP_DIR/old.mdata.$SPELL_DISK_TAG.root
- local OLD_MD5_LOG_F=$TMP_DIR/old.md5.$SPELL.filterable
- local OLD_MD5_LOG_R=$TMP_DIR/old.md5.$SPELL.root
+ local OLD_MD5_LOG_F=$TMP_DIR/old.md5.$SPELL_DISK_TAG.filterable
+ local OLD_MD5_LOG_R=$TMP_DIR/old.md5.$SPELL_DISK_TAG.root
if test -e $OLD_INSTALL_LOG ; then
log_adjuster $OLD_INSTALL_LOG $OLD_INSTALL_LOG_F log filterable
@@ -182,11 +191,11 @@ resurrect_spell() { (
local SPELL_CONFIG_FILTER
local SECTION_CONFIG_FILTER
local GRIMOIRE_CONFIG_FILTER
- tablet_find_resurrect_dir $SPELL OLD_TABLET_DIR
+ tablet_find_resurrect_dir $SPELL_INSTANCE OLD_TABLET_DIR
local TMP_VERSION=$VERSION
if [[ $OLD_TABLET_DIR ]] && test -d $OLD_TABLET_DIR ; then
debug "libresurrect" "loading tablet at $OLD_TABLET_DIR"
- tablet_set_spell $SPELL $OLD_TABLET_DIR || {
+ tablet_set_spell $SPELL_INSTANCE $OLD_TABLET_DIR || {
message "something is wrong with $OLD_TABLET_DIR"
returrect_fail
return 1
@@ -197,7 +206,7 @@ resurrect_spell() { (
else
unset OLD_TABLET_DIR
debug "libresurrect" "no tablet"
- codex_set_current_spell_by_name $SPELL
+ codex_set_current_spell_by_name $SPELL_INSTANCE
SPELL_CONFIG_FILTER=$SCRIPT_DIRECTORY/configs
SECTION_CONFIG_FILTER=$SECTION_DIRECTORY/configs
GRIMOIRE_CONFIG_FILTER=$GRIMOIRE/configs
@@ -217,7 +226,7 @@ resurrect_spell() { (
find . | sed 's/^.//'|
log_adjuster /dev/stdin $NEW_INSTALL_LOG filterable log
else
- local NEW_INSTALL_LOG=${INSTALL_LOGS#$STATE_ROOT/}/$SPELL-$VERSION
+ local
NEW_INSTALL_LOG=${INSTALL_LOGS#$STATE_ROOT/}/$SPELL_DISK_TAG-$VERSION
test -f $NEW_INSTALL_LOG || {
message "No install log found in unpacked cache, expected
$NEW_INSTALL_LOG"
resurrect_fail
@@ -226,18 +235,18 @@ resurrect_spell() { (
fi
# welcome back, did you get cold outside?
- local NEW_INSTALL_LOG_F=$TMP_DIR/new.install.$SPELL.filterable
- local NEW_INSTALL_LOG_R=$TMP_DIR/new.install.$SPELL.root
+ local NEW_INSTALL_LOG_F=$TMP_DIR/new.install.$SPELL_DISK_TAG.filterable
+ local NEW_INSTALL_LOG_R=$TMP_DIR/new.install.$SPELL_DISK_TAG.root
- local NEW_DATA_F=$TMP_DIR/new.data.$SPELL.filterable
- local NEW_DATA_R=$TMP_DIR/new.data.$SPELL.root
- local NEW_DATA_F_C=$TMP_DIR/new.data.$SPELL.config.filterable
- local NEW_DATA_R_C=$TMP_DIR/new.data.$SPELL.config.root
- local NEW_DATA_F_NC=$TMP_DIR/new.data.$SPELL.nonconfig.filterable
- local NEW_DATA_R_NC=$TMP_DIR/new.data.$SPELL.nonconfig.root
+ local NEW_DATA_F=$TMP_DIR/new.data.$SPELL_DISK_TAG.filterable
+ local NEW_DATA_R=$TMP_DIR/new.data.$SPELL_DISK_TAG.root
+ local NEW_DATA_F_C=$TMP_DIR/new.data.$SPELL_DISK_TAG.config.filterable
+ local NEW_DATA_R_C=$TMP_DIR/new.data.$SPELL_DISK_TAG.config.root
+ local NEW_DATA_F_NC=$TMP_DIR/new.data.$SPELL_DISK_TAG.nonconfig.filterable
+ local NEW_DATA_R_NC=$TMP_DIR/new.data.$SPELL_DISK_TAG.nonconfig.root
- local NEW_MDATA_F=$TMP_DIR/new.mdata.$SPELL.filterable
- local NEW_MDATA_R=$TMP_DIR/new.mdata.$SPELL.root
+ local NEW_MDATA_F=$TMP_DIR/new.mdata.$SPELL_DISK_TAG.filterable
+ local NEW_MDATA_R=$TMP_DIR/new.mdata.$SPELL_DISK_TAG.root
log_adjuster $NEW_INSTALL_LOG $NEW_INSTALL_LOG_F log filterable
log_adjuster $NEW_INSTALL_LOG $NEW_INSTALL_LOG_R log root
@@ -285,12 +294,16 @@ resurrect_spell() { (
# TODO: unpack just this file instead if it turns out there are problems
local tmp="$SCRIPT_DIRECTORY"
SCRIPT_DIRECTORY=$(codex_find_spell_by_name $SPELL)
- query_conflicts $SPELL "$conflicts"
+ # the self-conflict is with this instance: another slot of this spell
+ # is somebody else, and may well be in the way
+ query_conflicts $SPELL_INSTANCE "$conflicts"
local rc=$?
if [ -f "$conflicts" ]; then
- sed -i "/^$SPELL /d" "$conflicts"
+ local esc_instance
+ esc_str "$SPELL_INSTANCE" esc_instance
+ sed -i "/^$esc_instance /d" "$conflicts"
fi
- if [ 0 != "$rc" ] || ! dispel_conflicts "$SPELL" "$conflicts"; then
+ if [ 0 != "$rc" ] || ! dispel_conflicts "$SPELL_INSTANCE" "$conflicts";
then
resurrect_fail
return 1
fi
@@ -308,7 +321,7 @@ resurrect_spell() { (
resurrect_success
unlock_resources "libgrimoire" "install"
- unlock_resources "cast" "$SPELL"
+ unlock_resources "cast" "$SPELL_INSTANCE"
unlock_resources "solo" "cast"
# do a cleanse --fix if requested (FIXME make this turn off-able)
@@ -390,23 +403,23 @@ resurrect_sub() {
while (getline < ARGV[2] ) { fc[$1]=1; }
while (getline < ARGV[1] ) { if( fc[$1] != 1 ) { print $1; } }
}' $OLD_DATA_F_NC $NEW_DATA_F_NC |
- log_adjuster /dev/stdin $TMP_DIR/leftover.$SPELL.non-config filterable
root
+ log_adjuster /dev/stdin $TMP_DIR/leftover.$SPELL_DISK_TAG.non-config
filterable root
# clear out old files/symlinks
- remove_files_and_dirs $TMP_DIR/leftover.$SPELL.non-config "$INSTALL_ROOT"
+ remove_files_and_dirs $TMP_DIR/leftover.$SPELL_DISK_TAG.non-config
"$INSTALL_ROOT"
# this handles configs which must be backed up if they've changed
awk -F : 'BEGIN {
while (getline < ARGV[2] ) { fc[$1]=1; }
while (getline < ARGV[1] ) { if( fc[$1] != 1 ) { print $1; } }
}' $OLD_DATA_F_C $NEW_DATA_F_C |
- log_adjuster /dev/stdin $TMP_DIR/leftover.$SPELL.config filterable root
- touch $TMP_DIR/leftover.$SPELL.config
+ log_adjuster /dev/stdin $TMP_DIR/leftover.$SPELL_DISK_TAG.config
filterable root
+ touch $TMP_DIR/leftover.$SPELL_DISK_TAG.config
while read file; do
if check_if_modified "$OLD_MD5_LOG"; then
reap_modified_file $file
fi
- done < $TMP_DIR/leftover.$SPELL.config
+ done < $TMP_DIR/leftover.$SPELL_DISK_TAG.config
fi
# 14) if theres a POST_RESURRECT run it
@@ -437,16 +450,16 @@ real_install_config_file() {
local old_md5_log
if [[ $OLD_SPELL_VERSION ]] ; then
- old_md5_log=$MD5SUM_LOGS/$SPELL-$OLD_SPELL_VERSION
+ old_md5_log=$MD5SUM_LOGS/$SPELL_DISK_TAG-$OLD_SPELL_VERSION
# log must be in filterable form
- log_adjuster "$old_md5_log" "$TMP_DIR/$SPELL.md5" filterable root
+ log_adjuster "$old_md5_log" "$TMP_DIR/$SPELL_DISK_TAG.md5" filterable
root
else
old_md5_log=/dev/null
fi
local savetime=${3:-$(date +'%Y%m%d%H%M')}
- internal_install_config_file "$1" "$2" $savetime $TMP_DIR/$SPELL.md5
+ internal_install_config_file "$1" "$2" $savetime
$TMP_DIR/$SPELL_DISK_TAG.md5
rc=$?
- [[ $OLD_SPELL_VERSION ]] && rm $TMP_DIR/$SPELL.md5
+ [[ $OLD_SPELL_VERSION ]] && rm $TMP_DIR/$SPELL_DISK_TAG.md5
return $rc
}
@@ -576,11 +589,11 @@ real_handle_changed_config() {
resurrect_success() {
- local INSTALL_LOG=$INSTALL_LOGS/$SPELL-$VERSION
- local MD5_LOG=$MD5SUM_LOGS/$SPELL-$VERSION
- local TMP_INSTALL_LOG_STATE=$TMP_DIR/$SPELL.tmp.ilog.state
- local TMP_INSTALL_LOG_DATA=$TMP_DIR/$SPELL.tmp.ilog.data
- local TMP_INSTALL_LOG=$TMP_DIR/$SPELL.tmp.ilog.all
+ local INSTALL_LOG=$INSTALL_LOGS/$SPELL_DISK_TAG-$VERSION
+ local MD5_LOG=$MD5SUM_LOGS/$SPELL_DISK_TAG-$VERSION
+ local TMP_INSTALL_LOG_STATE=$TMP_DIR/$SPELL_DISK_TAG.tmp.ilog.state
+ local TMP_INSTALL_LOG_DATA=$TMP_DIR/$SPELL_DISK_TAG.tmp.ilog.data
+ local TMP_INSTALL_LOG=$TMP_DIR/$SPELL_DISK_TAG.tmp.ilog.all
# 14) copy compile log
# this file is not regenerated unlike its friends install and md5sum
@@ -593,7 +606,7 @@ resurrect_success() {
if [[ $OLD_TABLET_DIR ]] ; then
# depends info if it exists
# none of the old values are valid, only the new, uncommitted values are
- remove_depends_status $DEPENDS_STATUS $SPELL
+ remove_depends_status $DEPENDS_STATUS $SPELL_INSTANCE
local spell_depends=$OLD_TABLET_DIR/depends
local t_DEPENDS_STATUS t_SUB_DEPENDS_STATUS
lock_start_transaction "$DEPENDS_STATUS" t_DEPENDS_STATUS
@@ -602,7 +615,7 @@ resurrect_success() {
fi
lock_commit_transaction $DEPENDS_STATUS
- remove_sub_depends $SUB_DEPENDS_STATUS ".*" $SPELL
+ remove_sub_depends $SUB_DEPENDS_STATUS ".*" $SPELL_INSTANCE
local spell_sub_depends=$OLD_TABLET_DIR/sub_depends
if [[ -e $spell_sub_depends ]]; then
lock_start_transaction "$SUB_DEPENDS_STATUS" t_SUB_DEPENDS_STATUS
@@ -611,7 +624,7 @@ resurrect_success() {
fi
# if theres a tablet dir, store it with a new timestamp
- if new_tablet=$(tablet_get_path $SPELL) ; then
+ if new_tablet=$(tablet_get_path "$SPELL_INSTANCE") ; then
cp -Rp $OLD_TABLET_DIR/* $new_tablet
ln $INSTALL_LOG -sf $new_tablet/logs/install
ln $MD5_LOG -sf $new_tablet/logs/md5sum
@@ -650,29 +663,29 @@ resurrect_success() {
PATCHLEVEL=0
SECURITY_PATCH=0
fi
- add_version_cache "$VERSION_STATUS" "$SPELL" "$VERSION" "$PATCHLEVEL"
"$SECURITY_PATCH" "$UPDATED"
+ add_version_cache "$VERSION_STATUS" "$SPELL_INSTANCE" "$VERSION"
"$PATCHLEVEL" "$SECURITY_PATCH" "$UPDATED"
)
# add packages line
- add_spell "$SPELL" "installed" "$VERSION"
+ add_spell "$SPELL_INSTANCE" "installed" "$VERSION" "$SUBSLOT"
- pop_install_queue "$SPELL"
- echo $SPELL >> $SUCCESS_LIST
+ pop_install_queue "$SPELL_INSTANCE"
+ echo $SPELL_INSTANCE >> $SUCCESS_LIST
popd &>/dev/null
rm_source_dir $RESURRECT_DIR
clear_line
- activity_log "resurrect" "$SPELL" "$VERSION" "success"
- message "${RESURRECT_COLOR}Resurrected spell:
${SPELL_COLOR}${SPELL}${DEFAULT_COLOR}" \
+ activity_log "resurrect" "$SPELL_INSTANCE" "$VERSION" "success"
+ message "${RESURRECT_COLOR}Resurrected spell:
${SPELL_COLOR}${SPELL_INSTANCE}${DEFAULT_COLOR}" \
"version ${VERSION_COLOR}${VERSION}${DEFAULT_COLOR}"
}
resurrect_fail() {
message "${PROBLEM_COLOR}Resurrect failed for spell:" \
- "${SPELL_COLOR}${SPELL}${DEFAULT_COLOR} version" \
+ "${SPELL_COLOR}${SPELL_INSTANCE}${DEFAULT_COLOR} version" \
"${VERSION_COLOR}${VERSION}${DEFAULT_COLOR}"
#if [[ $1 == 1 ]] ; then
@@ -683,12 +696,12 @@ resurrect_fail() {
#fi
unlock_resources "libgrimoire" "install"
- unlock_resources "cast" "$SPELL"
+ unlock_resources "cast" "$SPELL_INSTANCE"
unlock_resources "solo" "cast"
[[ $CLEAN_SOURCE == on ]] && rm_source_dir $RESURRECT_DIR 2>/dev/null
- echo $SPELL >> $FAILED_LIST
+ echo $SPELL_INSTANCE >> $FAILED_LIST
}
#---------------------------------------------------------------------
diff --git a/var/lib/sorcery/modules/libsecurity
b/var/lib/sorcery/modules/libsecurity
index de09b08..b68a098 100755
--- a/var/lib/sorcery/modules/libsecurity
+++ b/var/lib/sorcery/modules/libsecurity
@@ -40,9 +40,9 @@ gaze_checkmd5() {
SECTION=$(echo $i | sed -e 's/\/[^\/]*\/DETAILS//' -e 's/\/.*\///')
if test "$SECTION" == "$SPELL" || test "$SECTION" == "DETAILS"
then
- echo -en "${SPELL_COLOR}$SPELL${DEFAULT_COLOR} "
+ echo -en "${SPELL_COLOR}${SPELL_INSTANCE:-$SPELL}${DEFAULT_COLOR} "
else
- echo -en "$GRIMOIRE: $SECTION: ${SPELL_COLOR}$SPELL${DEFAULT_COLOR} "
+ echo -en "$GRIMOIRE: $SECTION:
${SPELL_COLOR}${SPELL_INSTANCE:-$SPELL}${DEFAULT_COLOR} "
fi
REALSOURCE=
@@ -217,10 +217,14 @@ gaze_checkmd5syntax() {
#---------------------------------------------------------------------
## @param spelldirectory
+## @param slot (optional)
##
## spelldirectory is the spell's directory.
## Checks the md5s of all source files in spelldirectory.
##
+## A slot has its own sources and its own config, since DETAILS branches
+## on $SLOT to name them, so it is checked a slot at a time.
+##
#---------------------------------------------------------------------
gaze_checkmd5s() {
spellroot=$1
@@ -231,10 +235,17 @@ gaze_checkmd5s() {
unset SOURCE
unset MD5 2> /dev/null
unset LICENSE 2> /dev/null
- SPELL_CONFIG=$DEPENDS_CONFIG/$spellname
+ codex_set_current_slot "$spellroot" "$2" || return 1
+ slot_disk_tag "$spellname" "$SLOT" spelltag
+ SPELL_CONFIG=$DEPENDS_CONFIG/$spelltag
source $spellpath > /dev/null 2> /dev/null
+ # DETAILS may have pinned a slot of its own, and what is reported is
+ # the install the sources belong to
+ slot_valid "$SLOT" || SLOT=$SLOT_DEFAULT
+ slot_join "$SPELL" "$SLOT" SPELL_INSTANCE
+
gaze_checkmd5 $spellroot ''
unset SOURCE
@@ -305,7 +316,13 @@ gaze_md5check() {
done
for i in $SPELLS ; do
- gaze_checkmd5s $i
+ # a slotted spell is checked a slot at a time; an unslotted one is
+ # its one default slot
+ smgl_basename "$i" spellname
+ slots=$(codex_get_spell_slots $spellname)
+ for slot in ${slots:-0} ; do
+ gaze_checkmd5s $i $slot
+ done
done
if [ -n "$UNKNOWN" ] ; then
diff --git a/var/lib/sorcery/modules/libslot b/var/lib/sorcery/modules/libslot
new file mode 100644
index 0000000..44f6d99
--- /dev/null
+++ b/var/lib/sorcery/modules/libslot
@@ -0,0 +1,799 @@
+#!/bin/bash
+#---------------------------------------------------------------------
+## @Synopsis Functions for dealing with spell slots.
+##
+## A spell may offer several I<slots>, each pinning its own VERSION and
+## sources. Different slots of the same spell install side by side and
+## are upgraded independently, much like portage's SLOT.
+##
+## A slot is not necessarily one number. A spell declares one or more
+## I<axes> and its slots are the matrix they span -- a toolchain has a
+## version axis and a target axis, so gcc:14:arm-none-eabi and
+## gcc:14:native are two slots of gcc 14. A slot token is the axis
+## values joined with ':', in the order the spell declares them.
+##
+## A spell instance is named C<spell:slot>. The default slot is C<0>,
+## and C<spell:0> is always rendered as plain C<spell>, so a spell that
+## never mentions slots -- which is every spell that exists today --
+## keeps its current name in every state file, log and cache.
+##
+## In a dependency or query, an axis may be C<*>, meaning "any value",
+## and axes may be left off the end, meaning "any value" as well.
+##
+## A spell also has a I<subslot>, gentoo's: the ABI its dependents were
+## built against, written after the slot and a '/'. Unlike a slot, a
+## subslot is not part of a spell's identity -- glibc:2.6/2.6.1 is not
+## installed beside glibc:2.6/2.6.2, it is replaced by it. What the
+## subslot does is tell dependents that they have to be recast, which
+## they ask for by ending the atom in '=':
+##
+## : depends glibc:= # recast me when glibc's ABI moves
+## : depends glibc:2.6/2.6.1 # only that ABI will do
+##
+## Spells set SUBSLOT in DETAILS, usually to a soname; it defaults to
+## the slot, which never moves, so nothing is recast unless a spell
+## asks to be.
+##
+## The whole grammar, then, is
+##
+## : spell[:axis[:axis...]][/subslot][=]
+##
+## The functions here are pure: they work on tokens and on the
+## I<slot spec> libcodex parses out of a spell's SLOTS file, and touch
+## neither the grimoire nor the state database. A spec looks like
+##
+## : version=15,14,13 target=native,arm-none-eabi !13:arm-none-eabi
+##
+## with the axes in declaration order, and a C<!> field for every
+## combination the spell excludes. The spec of an unslotted spell is
+## the empty string.
+##
+## @Copyright Copyright 2026 Ismael Luceno
+#---------------------------------------------------------------------
+
+# The slot every unslotted spell lives in.
+SLOT_DEFAULT=0
+
+# The axis a SLOTS file gets when it does not name one. It is the only
+# axis such a spell has, so $SLOT already says everything about it and
+# no per-axis variable is exported for it.
+SLOT_ANONYMOUS_AXIS=slot
+
+
+###############################TOKENS#################################
+
+#---------------------------------------------------------------------
+## @param slot
+##
+## @return 0 if the slot is the default slot (or is empty)
+#---------------------------------------------------------------------
+slot_is_default() {
+ [[ -z $1 || $1 == "$SLOT_DEFAULT" ]]
+}
+
+#---------------------------------------------------------------------
+## An axis value -- or a subslot, which is a single value too -- may
+## not contain ':' (the axis separator), '/' (the subslot separator),
+## '=' (the bind operator), '@' (the disk tag separator), whitespace,
+## or the parentheses used by the provider syntax, spell(FEATURE).
+##
+## @param axis value
+##
+## @return 0 if the value is well formed
+#---------------------------------------------------------------------
+slot_axis_valid() {
+ [[ $1 ]] || return 1
+ case $1 in
+ *[!A-Za-z0-9_.+-]*) return 1 ;;
+ esac
+ return 0
+}
+
+#---------------------------------------------------------------------
+## @param slot
+##
+## @return 0 if every axis of the slot is well formed
+#---------------------------------------------------------------------
+slot_valid() {
+ [[ $1 ]] || return 1
+ [[ $1 == *::* || $1 == :* || $1 == *: ]] && return 1
+
+ case $1 in
+ *[!A-Za-z0-9_.+:-]*) return 1 ;;
+ esac
+ return 0
+}
+
+#---------------------------------------------------------------------
+## Like slot_valid, but also accepts '*' for an axis, which is only
+## meaningful when requesting or matching a slot.
+##
+## @param slot
+##
+## @return 0 if the slot is usable as a request
+#---------------------------------------------------------------------
+slot_valid_request() {
+ local axis rest=$1
+
+ [[ $1 ]] || return 1
+ [[ $1 == *::* || $1 == :* || $1 == *: ]] && return 1
+
+ while [[ $rest ]]; do
+ axis=${rest%%:*}
+ if [[ $rest == *:* ]]; then rest=${rest#*:}; else rest=; fi
+ [[ $axis == '*' ]] && continue
+ slot_axis_valid "$axis" || return 1
+ done
+ return 0
+}
+
+#---------------------------------------------------------------------
+## Splits a C<spell:slot> token into its parts. A token with no slot
+## suffix yields the default slot. Neither variable is touched when the
+## token is malformed.
+##
+## @param token spell, or spell:slot
+## @param variable to set to the spell name (optional)
+## @param variable to set to the slot (optional)
+## @param "request" to also accept wildcard axes (optional)
+##
+## @Stdout "spell slot", if no variables were given
+##
+## @return 0 on success
+## @return 1 if the token is malformed
+#---------------------------------------------------------------------
+slot_split() {
+ local __token=$1
+ local __spell __slot
+
+ [[ $__token ]] || return 1
+
+ if [[ $__token == *:* ]]; then
+ __spell=${__token%%:*}
+ __slot=${__token#*:}
+ else
+ __spell=$__token
+ __slot=$SLOT_DEFAULT
+ fi
+
+ [[ $__spell ]] || return 1
+ if [[ $4 == request ]]; then
+ slot_valid_request "$__slot" || return 1
+ else
+ slot_valid "$__slot" || return 1
+ fi
+
+ if [[ -z $2 ]] && [[ -z $3 ]]; then
+ echo "$__spell $__slot"
+ else
+ [[ $2 ]] && upvar "$2" "$__spell"
+ [[ $3 ]] && upvar "$3" "$__slot"
+ fi
+ return 0
+}
+
+#---------------------------------------------------------------------
+## Builds the canonical C<spell:slot> token. The default slot is
+## dropped, so slot 0 always renders as a bare spell name.
+##
+## @param spell
+## @param slot (optional, defaults to the default slot)
+## @param variable to set (optional)
+##
+## @Stdout the token, if no variable was given
+#---------------------------------------------------------------------
+slot_join() {
+ local __token=$1
+
+ slot_is_default "$2" || __token="$1:$2"
+
+ if [[ -z $3 ]]; then
+ echo "$__token"
+ else
+ upvar "$3" "$__token"
+ fi
+}
+
+#---------------------------------------------------------------------
+## Squeezes a slot into a token with no ':' in it.
+##
+## Two places cannot take the ':' a slot is joined with: the state
+## database, whose rows are ':' delimited, and any filename that
+## reaches tar(1) via -P, where "foo:bar" is a remote host:path. '@'
+## is not a legal axis character, so it stands in for the separator in
+## both.
+##
+## @param slot
+## @param variable to set (optional)
+##
+## @Stdout the packed slot, if no variable was given
+#---------------------------------------------------------------------
+slot_pack() {
+ local __packed=${1//:/@}
+
+ if [[ -z $2 ]]; then
+ echo "$__packed"
+ else
+ upvar "$2" "$__packed"
+ fi
+}
+
+#---------------------------------------------------------------------
+## Inverse of slot_pack. An empty packed slot is the default slot,
+## which is how a state row written before slots existed reads back.
+##
+## @param packed slot
+## @param variable to set (optional)
+##
+## @Stdout the slot, if no variable was given
+#---------------------------------------------------------------------
+slot_unpack() {
+ local __slot=${1//@/:}
+
+ [[ $__slot ]] || __slot=$SLOT_DEFAULT
+
+ if [[ -z $2 ]]; then
+ echo "$__slot"
+ else
+ upvar "$2" "$__slot"
+ fi
+}
+
+#---------------------------------------------------------------------
+## Builds the name a spell instance is known by on disk -- install logs,
+## md5 logs, compile logs, install caches, tablets, spell configs.
+##
+## As with slot_join, the default slot is dropped, which is what keeps
+## existing logs and caches valid.
+##
+## @param spell
+## @param slot (optional, defaults to the default slot)
+## @param variable to set (optional)
+##
+## @Stdout the disk tag, if no variable was given
+#---------------------------------------------------------------------
+slot_disk_tag() {
+ local __tag=$1
+ # not __packed: that is what slot_pack calls its own local, and upvar
+ # would write to it instead of to ours
+ local __disk_slot
+
+ if ! slot_is_default "$2"; then
+ slot_pack "$2" __disk_slot
+ __tag="$1@$__disk_slot"
+ fi
+
+ if [[ -z $3 ]]; then
+ echo "$__tag"
+ else
+ upvar "$3" "$__tag"
+ fi
+}
+
+#---------------------------------------------------------------------
+## slot_disk_tag for callers holding a whole instance token rather than
+## a spell and a slot -- what a state row or a queue hands you.
+##
+## @param spell, or spell:slot
+## @param variable to set (optional)
+##
+## @return 1 if the instance token is malformed
+## @Stdout the disk tag, if no variable was given
+#---------------------------------------------------------------------
+slot_instance_tag() {
+ # not __spell and __slot: those are what slot_split calls its own
+ # locals, and upvar would write to them instead of to ours
+ local __tag_spell __tag_slot
+
+ slot_split "$1" __tag_spell __tag_slot || return 1
+ slot_disk_tag "$__tag_spell" "$__tag_slot" "$2"
+}
+
+#---------------------------------------------------------------------
+## Inverse of slot_disk_tag.
+##
+## @param disk tag
+## @param variable to set to the spell name (optional)
+## @param variable to set to the slot (optional)
+##
+## @Stdout "spell slot", if no variables were given
+#---------------------------------------------------------------------
+slot_undisk_tag() {
+ local __tag=$1
+ local __spell=${__tag%%@*}
+ local __slot=$SLOT_DEFAULT
+
+ [[ $__spell ]] || return 1
+ if [[ $__tag == *@* ]]; then
+ __slot=${__tag#*@}
+ __slot=${__slot//@/:}
+ fi
+ slot_valid "$__slot" || return 1
+
+ if [[ -z $2 ]] && [[ -z $3 ]]; then
+ echo "$__spell $__slot"
+ else
+ [[ $2 ]] && upvar "$2" "$__spell"
+ [[ $3 ]] && upvar "$3" "$__slot"
+ fi
+ return 0
+}
+
+#---------------------------------------------------------------------
+## Inverse of slot_instance_tag, for callers that want the instance
+## token back in one piece -- what a tablet directory or a trigger row
+## hands you.
+##
+## @param disk tag
+## @param variable to set (optional)
+##
+## @return 1 if the tag does not name an instance
+## @Stdout the instance token, if no variable was given
+#---------------------------------------------------------------------
+slot_tag_instance() {
+ # not __spell and __slot: those are what slot_undisk_tag calls its own
+ # locals, and upvar would write to them instead of to ours
+ local __tag_spell __tag_slot
+
+ slot_undisk_tag "$1" __tag_spell __tag_slot || return 1
+ slot_join "$__tag_spell" "$__tag_slot" "$2"
+}
+
+#---------------------------------------------------------------------
+## Matches a slot against a request. An axis of '*' matches anything,
+## and so does an axis the request leaves off the end -- "gcc:14" asks
+## for gcc 14 on any target.
+##
+## @param request
+## @param slot
+##
+## @return 0 if the slot satisfies the request
+#---------------------------------------------------------------------
+slot_match() {
+ local request=$1 slot=$2
+ local want have
+
+ # the axes are walked with parameter expansion rather than split into
+ # an array: a request may contain '*', and unquoted word splitting
+ # would hand that to the globber
+ while [[ $request ]]; do
+ want=${request%%:*}
+ if [[ $request == *:* ]]; then request=${request#*:}; else request=; fi
+
+ # the request asks about an axis the slot does not have
+ [[ $slot ]] || return 1
+ have=${slot%%:*}
+ if [[ $slot == *:* ]]; then slot=${slot#*:}; else slot=; fi
+
+ [[ $want == '*' ]] && continue
+ [[ $have == "$want" ]] || return 1
+ done
+ return 0
+}
+
+
+###############################ATOMS##################################
+
+#---------------------------------------------------------------------
+## Splits a dependency atom, C<spell[:slot][/subslot][=]>, into its
+## parts. Unlike slot_split, which parses the name of one installed
+## instance, everything but the spell is optional here and an empty
+## slot or subslot means "any" rather than "the default".
+##
+## Wildcards are allowed, so C<gcc:*:arm-none-eabi> asks for any gcc
+## targeting arm, and C<glibc:=> for any slot of glibc, recasting the
+## dependent whenever its subslot moves.
+##
+## No variable is touched when the atom is malformed.
+##
+## @param atom
+## @param variable to set to the spell name (optional)
+## @param variable to set to the slot (optional)
+## @param variable to set to the subslot (optional)
+## @param variable to set to "yes" if the atom binds the subslot (optional)
+##
+## @Stdout "spell slot subslot bind", if no variables were given
+##
+## @return 0 on success
+## @return 1 if the atom is malformed
+#---------------------------------------------------------------------
+slot_atom_split() {
+ local __atom=$1
+ local __spell __rest __slot __subslot
+ local __bind=no
+
+ [[ $__atom ]] || return 1
+
+ if [[ $__atom == *:* ]]; then
+ __spell=${__atom%%:*}
+ __rest=${__atom#*:}
+ else
+ # ':' is what introduces a slot, and '=' binds one, so neither can
+ # appear in a bare spell name
+ case $__atom in
+ *[=/]*) return 1 ;;
+ esac
+ __spell=$__atom
+ fi
+ [[ $__spell ]] || return 1
+
+ if [[ $__rest == *= ]]; then
+ __bind=yes
+ __rest=${__rest%=}
+ fi
+
+ if [[ $__rest == */* ]]; then
+ __subslot=${__rest##*/}
+ __rest=${__rest%/*}
+ [[ $__subslot == '*' ]] || slot_axis_valid "$__subslot" || return 1
+ fi
+
+ if [[ $__rest ]]; then
+ slot_valid_request "$__rest" || return 1
+ __slot=$__rest
+ fi
+
+ if [[ -z $2$3$4$5 ]]; then
+ echo "$__spell $__slot $__subslot $__bind"
+ else
+ [[ $2 ]] && upvar "$2" "$__spell"
+ [[ $3 ]] && upvar "$3" "$__slot"
+ [[ $4 ]] && upvar "$4" "$__subslot"
+ [[ $5 ]] && upvar "$5" "$__bind"
+ fi
+ return 0
+}
+
+#---------------------------------------------------------------------
+## Renders a dependency atom. Inverse of slot_atom_split.
+##
+## @param spell
+## @param slot (optional)
+## @param subslot (optional)
+## @param "yes" to bind the subslot (optional)
+## @param variable to set (optional)
+##
+## @Stdout the atom, if no variable was given
+#---------------------------------------------------------------------
+slot_atom_join() {
+ local __atom=$1
+ local __slot=$2
+
+ # a subslot has to hang off a slot, so an unconstrained one becomes
+ # the wildcard when a subslot is asked for
+ [[ $3 && -z $__slot ]] && __slot='*'
+
+ [[ $__slot ]] && __atom="$__atom:$__slot"
+ [[ $3 ]] && __atom="$__atom/$3"
+ if [[ $4 == yes ]]; then
+ [[ $__slot ]] || __atom="$__atom:"
+ __atom="$__atom="
+ fi
+
+ if [[ -z $5 ]]; then
+ echo "$__atom"
+ else
+ upvar "$5" "$__atom"
+ fi
+}
+
+#---------------------------------------------------------------------
+## Decides whether an installed instance answers an atom. An empty or
+## '*' slot or subslot in the atom matches anything, as does an axis
+## the atom leaves off the end of its slot.
+##
+## @param atom's slot
+## @param atom's subslot
+## @param instance's slot
+## @param instance's subslot
+##
+## @return 0 if the instance satisfies the atom
+#---------------------------------------------------------------------
+slot_atom_matches() {
+ slot_match "$1" "$3" || return 1
+ [[ -z $2 || $2 == '*' || $2 == "$4" ]]
+}
+
+#---------------------------------------------------------------------
+## Complains about a malformed spell instance token.
+##
+## @param token
+#---------------------------------------------------------------------
+slot_bad_token_message() {
+ message "${SPELL_COLOR}$1${DEFAULT_COLOR}${PROBLEM_COLOR} is not a" \
+ "valid spell name; expected spell or spell:slot, where a slot" \
+ "is one or more ':' separated axes made" \
+ "of${DEFAULT_COLOR} A-Z a-z 0-9 _ . + -" \
+ "${PROBLEM_COLOR}.${DEFAULT_COLOR}"
+}
+
+
+################################SPECS#################################
+
+#---------------------------------------------------------------------
+## @param slot spec
+##
+## @Stdout the axis names, in declaration order
+#---------------------------------------------------------------------
+slot_spec_axes() {
+ local field names
+
+ for field in $1; do
+ case $field in
+ !*) continue ;;
+ *=*) names="$names ${field%%=*}" ;;
+ esac
+ done
+ echo ${names# }
+}
+
+#---------------------------------------------------------------------
+## @param slot spec
+##
+## @Stdout how many axes the spell has, 0 if it is unslotted
+#---------------------------------------------------------------------
+slot_spec_axis_count() {
+ local axes
+ axes=$(slot_spec_axes "$1")
+ set -- $axes
+ echo $#
+}
+
+#---------------------------------------------------------------------
+## @param slot spec
+## @param axis number, counting from 1
+##
+## @Stdout the values that axis may take, default first
+##
+## @return 1 if the spell has no such axis
+#---------------------------------------------------------------------
+slot_spec_values() {
+ local field
+ local i=0
+
+ for field in $1; do
+ case $field in
+ !*) continue ;;
+ *=*) i=$((i + 1))
+ if [[ $i == "$2" ]]; then
+ echo "${field#*=}" | tr , ' '
+ return 0
+ fi ;;
+ esac
+ done
+ return 1
+}
+
+#---------------------------------------------------------------------
+## @param slot spec
+##
+## @Stdout the excluded slot patterns
+#---------------------------------------------------------------------
+slot_spec_excludes() {
+ local field patterns
+
+ for field in $1; do
+ case $field in
+ !*) patterns="$patterns ${field#!}" ;;
+ esac
+ done
+ echo ${patterns# }
+}
+
+#---------------------------------------------------------------------
+## @param slot spec
+##
+## @Stdout the spell's default slot -- the first value of every axis
+#---------------------------------------------------------------------
+slot_spec_default() {
+ local field slot value
+
+ for field in $1; do
+ case $field in
+ !*) continue ;;
+ *=*) value=${field#*=}
+ slot="$slot:${value%%,*}" ;;
+ esac
+ done
+
+ if [[ $slot ]]; then
+ echo "${slot#:}"
+ else
+ echo "$SLOT_DEFAULT"
+ fi
+}
+
+#---------------------------------------------------------------------
+## @param slot spec
+## @param slot
+##
+## @return 0 if the slot is one the spell offers
+##
+## Every axis must be given -- a slot is a point in the matrix, not a
+## request. A spell with no spec only has the default slot.
+#---------------------------------------------------------------------
+slot_spec_has() {
+ local axis pattern values
+ local rest=$2
+ local i=0
+
+ if [[ -z $1 ]]; then
+ slot_is_default "$2"
+ return
+ fi
+
+ slot_valid "$2" || return 1
+
+ while [[ $rest ]]; do
+ axis=${rest%%:*}
+ if [[ $rest == *:* ]]; then rest=${rest#*:}; else rest=; fi
+ i=$((i + 1))
+ values=$(slot_spec_values "$1" $i) || return 1
+ real_list_find "$values" "$axis" || return 1
+ done
+
+ [[ $i == $(slot_spec_axis_count "$1") ]] || return 1
+
+ for pattern in $(slot_spec_excludes "$1"); do
+ slot_match "$pattern" "$2" && return 1
+ done
+ return 0
+}
+
+#---------------------------------------------------------------------
+## Turns a request into the one slot it names. Axes left off the end,
+## and axes given as '*', take that axis's default value -- casting has
+## to pick a single point in the matrix, so "unconstrained" becomes
+## "default" here, unlike in a dependency, where it stays "any".
+##
+## @param slot spec
+## @param request, may be empty
+## @param variable to set to the resolved slot
+##
+## @return 0 if the request names a slot the spell offers
+## @return 1 otherwise, including when it names an excluded combination
+#---------------------------------------------------------------------
+slot_spec_resolve() {
+ # every local here is __ prefixed: the caller hands us the name of a
+ # variable to fill in, and upvar would otherwise write to our own
+ local __axis __n __slot __values
+ local __request=$2
+ local __i=0
+
+ if [[ -z $1 ]]; then
+ slot_is_default "$2" || return 1
+ upvar "$3" "$SLOT_DEFAULT"
+ return 0
+ fi
+
+ # a bare spell name arrives here as a request for the default slot,
+ # since that is what slot_split makes of a name with no slot on it;
+ # for a spell that has slots it means every axis's default, the same
+ # as no request at all
+ slot_is_default "$__request" && __request=
+
+ __n=$(slot_spec_axis_count "$1")
+
+ for ((__i = 1; __i <= __n; __i++)); do
+ __values=$(slot_spec_values "$1" $__i)
+ __axis=${__request%%:*}
+ if [[ $__request == *:* ]]; then __request=${__request#*:}; else
__request=; fi
+
+ if [[ -z $__axis || $__axis == '*' ]]; then
+ __axis=${__values%% *}
+ else
+ real_list_find "$__values" "$__axis" || return 1
+ fi
+ __slot="$__slot:$__axis"
+ done
+
+ # the request named more axes than the spell has
+ [[ -z $__request ]] || return 1
+
+ __slot=${__slot#:}
+ slot_spec_has "$1" "$__slot" || return 1
+ upvar "$3" "$__slot"
+ return 0
+}
+
+#---------------------------------------------------------------------
+## @param slot spec
+## @param request to limit the expansion to (optional)
+##
+## @Stdout every slot the spell offers, default first, one per line
+##
+## Nothing is printed for a spell with no spec -- it is not slotted,
+## and so has no slots to list.
+#---------------------------------------------------------------------
+slot_spec_expand() {
+ local n slot value values
+ local i=0
+ local slots=""
+
+ n=$(slot_spec_axis_count "$1")
+ [[ $n -gt 0 ]] || return 0
+
+ for ((i = 1; i <= n; i++)); do
+ values=$(slot_spec_values "$1" $i)
+ local grown=""
+ if [[ -z $slots ]]; then
+ grown=$values
+ else
+ for slot in $slots; do
+ for value in $values; do
+ grown="$grown $slot:$value"
+ done
+ done
+ fi
+ slots=${grown# }
+ done
+
+ for slot in $slots; do
+ slot_spec_has "$1" "$slot" || continue
+ [[ $2 ]] && ! slot_match "$2" "$slot" && continue
+ echo "$slot"
+ done
+ return 0
+}
+
+#---------------------------------------------------------------------
+## Exports $SLOT and one variable per named axis, so that DETAILS can
+## branch on them: axis "target" of slot "14:arm-none-eabi" arrives as
+## $SLOT_TARGET. The anonymous axis gets no variable of its own, $SLOT
+## being all there is to say about it.
+##
+## $SLOT_AXES remembers the names, so slot_clear_axis_vars can undo it.
+##
+## @param slot
+## @param slot spec
+#---------------------------------------------------------------------
+slot_set_axis_vars() {
+ local name value variable
+ local rest=$1
+
+ slot_clear_axis_vars
+
+ SLOT=$1
+ export SLOT
+
+ for name in $(slot_spec_axes "$2"); do
+ value=${rest%%:*}
+ if [[ $rest == *:* ]]; then rest=${rest#*:}; else rest=; fi
+
+ [[ $name == "$SLOT_ANONYMOUS_AXIS" ]] && continue
+ variable=SLOT_$(echo "$name" | tr '[:lower:]-' '[:upper:]_')
+ eval "$variable=\$value"
+ export "$variable"
+ SLOT_AXES="$SLOT_AXES $variable"
+ done
+ SLOT_AXES=${SLOT_AXES# }
+ return 0
+}
+
+#---------------------------------------------------------------------
+## Unsets whatever slot_set_axis_vars exported last.
+#---------------------------------------------------------------------
+slot_clear_axis_vars() {
+ [[ $SLOT_AXES ]] && unset $SLOT_AXES
+ unset SLOT_AXES
+ return 0
+}
+
+#---------------------------------------------------------------------
+##
+## This software is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This software is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this software; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+#---------------------------------------------------------------------
+# vim: filetype=sh:tabstop=4:shiftwidth=4:expandtab
diff --git a/var/lib/sorcery/modules/libsorcery
b/var/lib/sorcery/modules/libsorcery
index 30be820..831b9f4 100755
--- a/var/lib/sorcery/modules/libsorcery
+++ b/var/lib/sorcery/modules/libsorcery
@@ -253,12 +253,12 @@ save_libraries() {
OLD_LIBS=$SOURCE_DIRECTORY/old.libraries
mkdir -p "$OLD_LIBS"
- SAVED=$OLD_LIBS/$SPELL.saved.libraries
+ SAVED=$OLD_LIBS/$SPELL_DISK_TAG.saved.libraries
lock_file "$SAVED"
rm -rf "$SAVED"
- OLD_VERSION=$(installed_version $SPELL)
- OLD_LOG=$INSTALL_LOGS/$SPELL-$OLD_VERSION
+ OLD_VERSION=$(installed_version $SPELL_INSTANCE)
+ OLD_LOG=$INSTALL_LOGS/$SPELL_DISK_TAG-$OLD_VERSION
grep "^/lib/\|^/usr/lib" "$OLD_LOG" |
while read LINE; do
@@ -306,12 +306,12 @@ save_binaries() {
OLD_BINS=$SOURCE_DIRECTORY/old.binaries
mkdir -p "$OLD_BINS"
- SAVED=$OLD_BINS/$SPELL.saved.binaries
+ SAVED=$OLD_BINS/$SPELL_DISK_TAG.saved.binaries
lock_file "$SAVED"
rm -rf "$SAVED"
- OLD_VERSION=$(installed_version $SPELL)
- OLD_LOG=$INSTALL_LOGS/$SPELL-$OLD_VERSION
+ OLD_VERSION=$(installed_version $SPELL_INSTANCE)
+ OLD_LOG=$INSTALL_LOGS/$SPELL_DISK_TAG-$OLD_VERSION
grep "^/bin/\|^/sbin/\|^/usr/bin/\|^/usr/sbin/" "$OLD_LOG" |
while read LINE; do
@@ -358,15 +358,15 @@ spell_recover() {
debug "libsorcery" "Running spell_recover()"
- message "${MESSAGE_COLOR}Aborting dispel of
${SPELL_COLOR}${SPELL}${DEFAULT_COLOR}" \
+ message "${MESSAGE_COLOR}Aborting dispel of
${SPELL_COLOR}${SPELL_INSTANCE}${DEFAULT_COLOR}" \
"${MESSAGE_COLOR}and recovering...${DEFAULT_COLOR}"
unlock_resources "libgrimoire" "install"
- unlock_resources "cast" "$SPELL"
+ unlock_resources "cast" "$SPELL_INSTANCE"
unlock_resources "solo" "cast"
- local current_version=$(installed_version $SPELL)
+ local current_version=$(installed_version $SPELL_INSTANCE)
SUCCESS_LIST=/dev/null
- resurrect_spell $SPELL $current_version
+ resurrect_spell $SPELL_INSTANCE $current_version
exit 130
}
@@ -791,8 +791,11 @@ fix_installed_spells() {
##
#---------------------------------------------------------------------
log_list() {
- local spell version
- while read spell version; do
+ local spell version slot
+ # field 5 is the packed slot, which is already the disk tag's suffix,
+ # and is empty for slot 0 -- so is the tag it makes
+ while read spell version slot; do
+ spell=$spell${slot:+@$slot}
# the install and md5sum logs are not compressed
echo "$INSTALL_LOGS/$spell-$version"
echo "$MD5SUM_LOGS/$spell-$version"
@@ -800,7 +803,7 @@ log_list() {
# FIXME $EXTENSION may be the wrong one if the user changed it
echo "$COMPILE_LOGS/$spell-$version$EXTENSION"
echo "$COMPILE_LOGS/$spell-$version.castfs.dbglog$EXTENSION"
- done < <(all_spell_status | cut -d: -f1,4 --output-delimiter=" ")
+ done < <(all_spell_status | cut -d: -f1,4,5 --output-delimiter=" ")
}
@@ -853,8 +856,10 @@ activity_log() {
##
#---------------------------------------------------------------------
run_details() {
- debug "libsorcery" "starting run_details for $SPELL"
- if ! codex_set_current_spell_by_name $SPELL; then
+ debug "libsorcery" "starting run_details for ${SPELL_INSTANCE:-$SPELL}"
+ # the instance says which slot to load; the codex puts the bare name
+ # back in $SPELL and the instance back in $SPELL_INSTANCE
+ if ! codex_set_current_spell_by_name ${SPELL_INSTANCE:-$SPELL}; then
local GRIMOIRES=$(codex_get_all_grimoires)
message "${PROBLEM_COLOR}Unable to find spell" \
"${SPELL_COLOR}${SPELL}${DEFAULT_COLOR}${PROBLEM_COLOR}" \
@@ -969,21 +974,40 @@ generate_keep_list() {
local source_keep="$1"
local cache_keep="$2"
local type="$3"
+ # a spell is kept once per slot: each slot has its own version, and so
+ # its own cache
if [[ "$type" == installed_only ]] ; then
get_all_spells_with_status ok |
sort -u |
- while read spell; do
- codex_find_spell_by_name "$spell"
+ while read instance; do
+ local spell slot dir
+ slot_split "$instance" spell slot || continue
+ codex_find_spell_by_name "$spell" |
+ while read dir; do
+ echo "$dir $slot"
+ done
done
else
- codex_get_all_spells
+ codex_get_all_spells |
+ while read dir; do
+ local slot spec
+ spec=""
+ test -f "$dir/SLOTS" && spec=$(codex_parse_slots_file "$dir/SLOTS")
+ if [[ $spec ]] ; then
+ for slot in $(slot_spec_expand "$spec"); do
+ echo "$dir $slot"
+ done
+ else
+ echo "$dir"
+ fi
+ done
fi |
(
echo sorcery-$SORCERY_BRANCH.tar.bz2
- while read SPELL_DIRECTORY ; do
- codex_set_current_spell_quick "$SPELL_DIRECTORY" &>/dev/null
+ while read SPELL_DIRECTORY slot ; do
+ codex_set_current_spell_quick "$SPELL_DIRECTORY" "$slot" &>/dev/null
get_spell_files 2>/dev/null
- ls $INSTALL_CACHE/$SPELL-$VERSION-* 1>&2 2>/dev/null
+ ls $INSTALL_CACHE/$SPELL_DISK_TAG-$VERSION-* 1>&2 2>/dev/null
unset VERSION SPELL ${!SOURCE*}
done
) 2> $cache_keep > $source_keep
@@ -1130,7 +1154,9 @@ invoke_build_dir() {
##
#---------------------------------------------------------------------
verify_sources() {
- verify_source $(sources $SPELL)
+ # which files a spell has depends on the slot: the instance names the
+ # one being cast, and the bare name would name the default slot
+ verify_source $(sources ${SPELL_INSTANCE:-$SPELL})
}
diff --git a/var/lib/sorcery/modules/libstage
b/var/lib/sorcery/modules/libstage
index 4212a82..abf95c4 100755
--- a/var/lib/sorcery/modules/libstage
+++ b/var/lib/sorcery/modules/libstage
@@ -29,10 +29,10 @@ transfer_staged_spell()
rm -f "$hlist"
fi
- lock_file "$CONFIG_STAGE_DIRECTORY/$SPELL"
- mkdir -p "$CONFIG_STAGE_DIRECTORY/$SPELL/current"
+ lock_file "$CONFIG_STAGE_DIRECTORY/${SPELL_DISK_TAG:-$SPELL}"
+ mkdir -p "$CONFIG_STAGE_DIRECTORY/${SPELL_DISK_TAG:-$SPELL}/current"
stage_install_configs
- unlock_file "$CONFIG_STAGE_DIRECTORY/$SPELL"
+ unlock_file "$CONFIG_STAGE_DIRECTORY/${SPELL_DISK_TAG:-$SPELL}"
}
filter_spell_configs()
@@ -129,7 +129,7 @@ stage_install_file()
local GROUP=${stat[2]}
local link_count=${stat[3]}
local inode=${stat[4]}
- local SAVE="$CONFIG_STAGE_DIRECTORY/$SPELL/current/$FILE"
+ local
SAVE="$CONFIG_STAGE_DIRECTORY/${SPELL_DISK_TAG:-$SPELL}/current/$FILE"
message "${FILE_COLOR}$PERMISSION $OWNER:$GROUP $FILE${DEFAULT_COLOR}"
case $2 in
config)
@@ -287,7 +287,7 @@ stage_install_list()
## to $CONFIG_STAGE_DIRECTORY if they are empty
##
## @param file
-## @param spell owner of the file, so we don't need to look that up
+## @param disk tag of the owner, so we don't need to look that up
#--------------------------------------------------------------------
recursive_config_stage_cleanup()
{
diff --git a/var/lib/sorcery/modules/libstate
b/var/lib/sorcery/modules/libstate
index 7b42d19..b18708b 100755
--- a/var/lib/sorcery/modules/libstate
+++ b/var/lib/sorcery/modules/libstate
@@ -13,18 +13,20 @@
## 3rd or 4th fields are not valid.
##
## @param Depends status file
-## @param Name of the "parent" spell
+## @param Instance of the "parent" spell
## @param Name of the spell the parent spell depends on
## @param "on" or "off" depending on whether the user installed the
dependency
## @param Type of dependency (required or optional)
## @param option to pass to C<configure> if the dependency is
installed
## @param option to pass to C<configure> if the dependency is not
installed
+## @param slot the dependency asked for (optional)
+## @param subslot the dependency was bound to (optional)
##
## @Example add_depends kdelibs alsa-driver on optional --with-alsa
--without-alsa
##
#---------------------------------------------------------------------
add_depends()
-{ # $1=depends file $2=spell, $3=depends, $4=on/off, $5=optional/required,
$6=on arg, $7=off arg
+{ # $1=depends file $2=spell instance, $3=depends, $4=on/off,
$5=optional/required, $6=on arg, $7=off arg, $8=(OPT)dep slot, $9=(OPT)bound
subslot
debug "libstate" "add_depends() - $*"
#already here for some reason
@@ -41,9 +43,14 @@ add_depends()
search_depends_status "$depends_status" "$1" "$2" >/dev/null &&
remove_depends_status "$depends_status" "$1" "$2"
+ local spell slot dep_slot extra
+ state_split_instance "$1" spell slot || return 1
+ slot_pack "$7" dep_slot
+ state_depends_tail "$slot" "$dep_slot" "$8" extra
+
local t_status
# lock_start_transaction "$depends_status" t_status
- echo "$1:$2:$3:$4:$5:$6" >> "$depends_status"
+ echo "$spell:$2:$3:$4:$5:$6$extra" >> "$depends_status"
# lock_commit_transaction "$depends_status"
return 0
@@ -52,6 +59,47 @@ add_depends()
#---------------------------------------------------------------------
##
+## Fills in the subslots that C<spell:=> dependencies asked to be bound
+## to.
+##
+## Such a row is written while its dependency is still uncast -- the
+## depends run comes before any casting -- so it goes out with '=' in
+## the subslot field, which is not a legal subslot, and this rewrites it
+## on the way into $DEPENDS_STATUS, by which time the dependency is
+## installed and has an ABI to name.
+##
+## A dependency that is somehow still not installed leaves the field
+## empty: nothing was bound, and there is nothing to compare against
+## when it moves.
+##
+## @param depends file, rewritten in place
+##
+#---------------------------------------------------------------------
+bind_depends_subslots() {
+ local line dep_slot instance subslot
+ local row=()
+
+ [[ -e $1 ]] || return 0
+
+ while IFS= read -r line; do
+ # explode writes over an array rather than replacing it, and a short
+ # row after a long one would otherwise read back the long one's tail
+ row=()
+ explode "$line" ":" row
+ if [[ ${row[8]} != "=" ]] ; then
+ echo "$line"
+ continue
+ fi
+ slot_unpack "${row[7]}" dep_slot
+ slot_join "${row[1]%%(*}" "$dep_slot" instance
+ query_spell_subslot "$instance" subslot
+ echo "${line%:*}:$subslot"
+ done < "$1" > "$1.bound" &&
+ mv -f "$1.bound" "$1"
+}
+
+#---------------------------------------------------------------------
+##
## In case you want to search in all the fields of the $1
##
## Arguments can be regexp
@@ -59,12 +107,14 @@ add_depends()
## Prints out the matching line(s)
## @Stdout the matching line(s)
## @param depends file
-## @param spell
+## @param spell instance
## @param depends
## @param on/off
## @param optional/required
## @param on arg
## @param off arg
+## @param dependency slot (optional)
+## @param bound subslot (optional)
##
## @return 0 if entry was found
## @return 1 if entry was not found
@@ -72,19 +122,21 @@ add_depends()
##
#---------------------------------------------------------------------
search_depends_status_exact()
-{ # $1=depends file $2=spell, $3=depends, $4=on/off, $5=optional/required,
$6=on arg, $7=off arg
+{ # $1=depends file $2=spell instance, $3=depends, $4=on/off,
$5=optional/required, $6=on arg, $7=off arg, $8=(OPT)dep slot, $9=(OPT)bound
subslot
local depends_status=$1
shift
- local a1 a2 a3 a4 a5 a6
- esc_str "$1" a1; esc_str "$3" a3
+ local a1 a2 a3 a4 a5 a6 spell slot dtail
+ state_split_query "$1" spell slot || { spell=$1; slot=""; }
+ esc_str "$spell" a1; esc_str "$3" a3
esc_str "$4" a4; esc_str "$5" a5; esc_str "$6" a6
esc_provider_str "$2" a2
- debug "libstate" "search_depends_status_exact: [$a1:$a2:$a3:$a4:$a5:$a6]"
+ state_depends_tail_pattern dtail "$slot" "$7" "$8"
+ debug "libstate" "search_depends_status_exact:
[$a1:$a2:$a3:$a4:$a5:$a6$dtail]"
lock_file "$depends_status"
- grep "^$a1:$a2:$a3:$a4:$a5:$a6$" "$depends_status"
+ grep "^$a1:$a2:$a3:$a4:$a5:$a6$dtail$" "$depends_status"
rc=$?
unlock_file "$depends_status"
@@ -100,70 +152,126 @@ search_depends_status_exact()
## Prints out the matching line(s)
## @Stdout the matching line(s)
## @param depends file
-## @param spell
+## @param spell instance
## @param depends
## @param on/off (optional)
## @param optional/required (optional)
## @param on arg (optional)
## @param off arg (optional)
+## @param dependency slot (optional)
+## @param bound subslot (optional)
##
#---------------------------------------------------------------------
search_depends_status_simple()
-{ # $1=depends file $2=spell, $3=depends, $4=on/off, $5=optional/required,
$6=on arg, $7=off arg
+{ # $1=depends file $2=spell instance, $3=depends, $4=on/off,
$5=optional/required, $6=on arg, $7=off arg, $8=(OPT)dep slot, $9=(OPT)bound
subslot
local depends_status=$1
shift
- local a1 a2 a3 a4 a5 a6
- esc_str "$1" a1; esc_provider_str "$2" a2
+ local a1 a2 a3 a4 a5 a6 spell slot dtail
+ state_split_query "$1" spell slot || { spell=$1; slot=""; }
+ esc_str "$spell" a1; esc_provider_str "$2" a2
a3=${3:-.*} ; a4=${4:-.*}
esc_str "$5" a5; esc_str "$6" a6
a5=${5:-.*} ; a6=${6:-.*}
+ state_depends_tail_pattern dtail "$slot" "$7" "$8"
lock_file "$depends_status"
- grep "^$a1:$a2\(([^:]*)\)\?:$a3:$a4:$a5:$a6$" "$depends_status"
+ grep "^$a1:$a2\(([^:]*)\)\?:$a3:$a4:$a5:$a6$dtail$" "$depends_status"
unlock_file "$depends_status"
}
#---------------------------------------------------------------------
## @param depends file
-## @param spell
+## @param spell instance
## @param depends (optional)
## @Stdout matching lines
## In case you want to search by spell or dependency in $1
##
## Arguments can be regexp
##
-## Prints out the matching line(s)
+## Prints out the matching line(s). A bare spell name matches every
+## slot of it; the end of the row is not anchored here, so the slot is
+## filtered afterwards rather than folded into the pattern.
##
#---------------------------------------------------------------------
search_depends_status()
-{ # $1=depends file $2=spell, $3=(opt)depends
+{ # $1=depends file $2=spell instance, $3=(opt)depends
[[ -z $2 ]] && return 1
local depends_status=$1
shift
- local spell
- esc_str "$1" spell
+ local spell slot rows
+ state_split_query "$1" spell slot || { spell=$1; slot=""; }
+ esc_str "$spell" spell
# lock_file "$depends_status"
if [[ -z $2 ]]; then
- grep "^$spell:" "$depends_status"
+ rows=$(grep "^$spell:" "$depends_status")
else
local depends
esc_provider_str "$2" depends
- grep "^$spell:$depends:" "$depends_status"
+ rows=$(grep "^$spell:$depends:" "$depends_status")
fi
# unlock_file "$depends_status"
-}
+ [[ $rows ]] || return 1
+ if [[ $slot ]]; then
+ rows=$(state_filter_depends_slot "$slot" <<< "$rows")
+ [[ $rows ]] || return 1
+ fi
+ echo "$rows"
+}
+#---------------------------------------------------------------------
+##
+## Finds everything that bound itself to an instance's ABI and would no
+## longer agree with it.
+##
+## A C<spell:=> dependency remembers the subslot its dependency had when
+## it was cast. When the dependency comes back wearing another one,
+## whatever bound to it was built against an ABI that is gone and has to
+## be cast again -- which is the whole reason to write ':=' rather than
+## ':'. A row that bound nothing has nothing to go stale.
+##
+## @param depends file
+## @param dependee instance
+## @param subslot the dependee has now
+##
+## @Stdout the requester instances, one per line
+##
+#---------------------------------------------------------------------
+search_depends_stale_subslot() {
+ local depends_status=$1
+ local spell slot a_spell a_slot line requester
+ local row=()
+
+ [[ -e $depends_status ]] || return 1
+ slot_split "$2" spell slot || return 1
+ slot_pack "$slot" a_slot
+ slot_is_default "$slot" && a_slot=""
+ esc_str "$spell" a_spell
+
+ # the dependency may be named through a provider, and the row it is
+ # read out of has the slots at its end
+ grep "^[^:]*:$a_spell\(([^:]*)\)\?:" "$depends_status" |
+ while IFS= read -r line; do
+ row=()
+ explode "$line" ":" row
+ [[ ${row[7]} == "$a_slot" ]] || continue
+ [[ ${row[8]} ]] || continue
+ [[ ${row[8]} == "$3" ]] && continue
+ slot_unpack "${row[6]}" slot
+ slot_join "${row[0]}" "$slot" requester
+ echo "$requester"
+ done | sort -u
+}
#---------------------------------------------------------------------
##
## @param depends file
-## @param spell
+## @param spell instance
## @param dependency spell
## @Stdout list of options
##
@@ -183,11 +291,15 @@ get_depends_options()
return
}
- local START
- esc_str "$1" START
- debug "libstate" "get_depends_options() - START=$START"
+ local START slot
+ state_split_query "$1" START slot || { START=$1; slot=""; }
+ debug "libstate" "get_depends_options() - START=$START:$slot"
lock_file "$depends_status"
- awk -F ':' -v start=$START '$1 == start { OPTS = OPTS " " ($3 == "on" ? $5
: $6) } END { print OPTS }' $depends_status 2> /dev/null
+ awk -F ':' -v start="$START" -v slot="$slot" -- '
+ function rowslot( s) { s = $7; gsub(/@/, ":", s); return (s == "" ?
"0" : s) }
+ $1 == start && (slot == "" || rowslot() == slot) {
+ OPTS = OPTS " " ($3 == "on" ? $5 : $6) }
+ END { print OPTS }' $depends_status 2> /dev/null
unlock_file "$depends_status"
}
@@ -197,26 +309,29 @@ get_depends_options()
## Arguments can be regexp, and all but the spell are optional
##
## @param depends file
-## @param spell
+## @param spell instance
## @param depends
## @param on/off
## @param optional/required
## @param on arg
## @param off arg
+## @param dependency slot (optional)
+## @param bound subslot (optional)
##
#---------------------------------------------------------------------
remove_depends_status()
-{ # $1=depends file $2=spell, $3=(OPT)depends, $4=(OPT)on/off,
$5=(OPT)optional/required, $6=(OPT)on arg, $7=(OPT)off arg
+{ # $1=depends file $2=spell instance, $3=(OPT)depends, $4=(OPT)on/off,
$5=(OPT)optional/required, $6=(OPT)on arg, $7=(OPT)off arg, $8=(OPT)dep slot,
$9=(OPT)bound subslot
local depends_status=$1
shift
- local a1 a2 a3 a4 a5 a6
+ local a1 a2 a3 a4 a5 a6 spell slot dtail
# arguments 1 and 2 are spell names, which might have characters that
# look like regexps, 5 and 6 might like funny too
# contrast 3 and 4 which are "on", "off", "required" or "optional"
# if they are regexps its probably desired so dont escape those
- esc_str "$1" a1; esc_str "$2" a2
+ state_split_query "$1" spell slot || { spell=$1; slot=""; }
+ esc_str "$spell" a1; esc_str "$2" a2
a1=${a1:-.*}; a2=${a2:-.*}
a3=${3:-.*} ; a4=${4:-.*}
@@ -225,9 +340,11 @@ remove_depends_status()
a5=${a5:-.*}; a6=${a6:-.*}
[[ "$a5" == "[none]" ]] && a5="\\[none\\]"
+ state_depends_tail_pattern dtail "$slot" "$7" "$8"
+
local t_status
lock_start_transaction "$depends_status" t_status
- sedit "/^$a1:$a2:$a3:$a4:$a5:$a6$/D" "$t_status"
+ sedit "/^$a1:$a2:$a3:$a4:$a5:$a6$dtail$/D" "$t_status"
lock_commit_transaction "$depends_status"
}
@@ -235,7 +352,7 @@ remove_depends_status()
## Toggle optional and suggested dependencies on/off
##
## @param depends file
-## @param spell
+## @param spell instance
## @param dependency
##
#---------------------------------------------------------------------
@@ -243,8 +360,9 @@ toggle_depends_status() {
local depends_status=$1
shift
- local spell dependency
- esc_str "$1" spell; esc_str "$2" dependency
+ local spell dependency slot
+ state_split_query "$1" spell slot || spell=$1
+ esc_str "$spell" spell; esc_str "$2" dependency
local t_status
lock_start_transaction "$depends_status" t_status
@@ -260,7 +378,7 @@ toggle_depends_status() {
## toggle it to be enabled or disabled.
##
## @param depends file
-## @param spell
+## @param spell instance
## @param new provider
## @param feature
## @param on/off
@@ -270,8 +388,9 @@ change_spell_provider() {
local depends_status=$1
shift
- local spell new_provider feature enabled
- esc_str "$1" spell
+ local spell new_provider feature enabled slot
+ state_split_query "$1" spell slot || spell=$1
+ esc_str "$spell" spell
esc_str "$2" new_provider
esc_str "$3" feature
enabled=$4
@@ -293,17 +412,22 @@ change_spell_provider() {
## The uncommitted_hash hash table should be hash_export'ed to
## make it through the call to make.
##
-## @param spell
+## @param spell instance
## @param variable name to put the filename name in (pass by reference)
##
#---------------------------------------------------------------------
get_uncommitted_depends_file() {
- local SPELL=$1
+ # canonical, so that a caller with only a spell name and one with the
+ # instance it belongs to agree on the hash key
+ local SPELL
+ state_canon_instance "$1" SPELL || SPELL=$1
+ local tag
+ slot_instance_tag "$SPELL" tag || tag=$SPELL
local temp_spell_depends
hash_get_ref "uncommitted_hash" $SPELL temp_spell_depends
if ! [[ $temp_spell_depends ]] ; then
- temp_spell_depends="$UNCOMMITTED_DEPENDS/$SPELL"
+ temp_spell_depends="$UNCOMMITTED_DEPENDS/$tag"
hash_put "uncommitted_hash" "$SPELL" "$temp_spell_depends"
mkdir -p "$ABANDONED_DEPENDS"
@@ -360,8 +484,8 @@ get_uncommitted_rsub_depends_file() {
## Add a sub-dependency.
##
## @param sub-depends file
-## @param requester file
-## @param sub-dependee
+## @param requester instance
+## @param sub-dependee instance
## @param sub-depends
#---------------------------------------------------------------------
add_sub_depends() {
@@ -370,9 +494,14 @@ add_sub_depends() {
local file=$1
shift
+ local requester rslot dependee dslot extra
+ state_split_instance "$1" requester rslot || return 1
+ state_split_instance "$2" dependee dslot || return 1
+ state_sub_depends_tail "$rslot" "$dslot" extra
+
local t_file
lock_start_transaction "$file" t_file
- echo "$1:$2:$3" >> "$t_file"
+ echo "$requester:$dependee:$3$extra" >> "$t_file"
lock_commit_transaction "$file"
}
@@ -380,22 +509,25 @@ add_sub_depends() {
## Search in sub-depends file.
##
## @param sub-depends file
-## @param requester file (optional)
-## @param sub-dependee (optional)
+## @param requester instance (optional)
+## @param sub-dependee instance (optional)
## @param sub-depends (optional)
#---------------------------------------------------------------------
search_sub_depends() {
$STD_DEBUG
local file=$1
shift
- local a1 a2 a3
- esc_str "$1" a1; esc_str "$2" a2; esc_str "$3" a3
+ local a1 a2 a3 requester rslot dependee dslot stail
+ state_split_query "$1" requester rslot || { requester=$1; rslot=""; }
+ state_split_query "$2" dependee dslot || { dependee=$2; dslot=""; }
+ esc_str "$requester" a1; esc_str "$dependee" a2; esc_str "$3" a3
a1=${a1:-.*}
a2=${a2:-.*}
a3=${a3:-.*}
+ state_sub_depends_tail_pattern stail "$rslot" "$dslot"
lock_file "$file"
- grep "^$a1:$a2:$a3$" "$file"
+ grep "^$a1:$a2:$a3$stail$" "$file"
unlock_file "$file"
}
@@ -403,8 +535,8 @@ search_sub_depends() {
## Remove a sub-depends.
##
## @param sub-depends file
-## @param requester
-## @param sub-dependee
+## @param requester instance
+## @param sub-dependee instance
## @param sub-depends
#---------------------------------------------------------------------
remove_sub_depends() {
@@ -414,13 +546,16 @@ remove_sub_depends() {
test -f $file || return
- local a1 a2 a3
- esc_str "$1" a1; esc_str "$2" a2; esc_str "$3" a3
+ local a1 a2 a3 requester rslot dependee dslot stail
+ state_split_query "$1" requester rslot || { requester=$1; rslot=""; }
+ state_split_query "$2" dependee dslot || { dependee=$2; dslot=""; }
+ esc_str "$requester" a1; esc_str "$dependee" a2; esc_str "$3" a3
a1=${a1:-.*}; a2=${a2:-.*}; a3=${a3:-.*}
+ state_sub_depends_tail_pattern stail "$rslot" "$dslot"
local t_file
lock_start_transaction "$file" t_file
- sedit "/^$a1:$a2:$a3$/D" "$t_file"
+ sedit "/^$a1:$a2:$a3$stail$/D" "$t_file"
lock_commit_transaction "$file"
}
@@ -595,21 +730,28 @@ search_default_provider() {
#---------------------------------------------------------------------
## @param cache file
-## @param spell name
+## @param spell instance
##
## Removes the spell info from the passed file ($VERSION_STATUS)
##
+## A version cache line is space delimited, so unlike the ':' delimited
+## databases it can hold the instance token whole; all that is needed
+## is to canonicalise it, so that "gcc:0" and "gcc" are one line.
+##
#---------------------------------------------------------------------
remove_version_cache() {
local file=$1
+ local spell
+ state_canon_instance "$2" spell || spell=$2
+
lock_start_transaction "$file" t_file
- sed -i "/^$2 /d" $t_file
+ sed -i "/^$(esc_str "$spell") /d" $t_file
lock_commit_transaction $file
}
#---------------------------------------------------------------------
## @param cache file
-## @param spell name
+## @param spell instance
## @param version
## @param patchlevel
## @param security patch
@@ -622,13 +764,355 @@ remove_version_cache() {
add_version_cache() {
local file=$1
shift
- remove_version_cache "$file" $1
+ remove_version_cache "$file" "$1"
+
+ local spell
+ state_canon_instance "$1" spell || spell=$1
+ shift
lock_start_transaction "$file" t_file
- echo "$1 ${2:-0} ${3:-0} ${4:-0} ${5:-0}" >> $t_file
+ echo "$spell ${1:-0} ${2:-0} ${3:-0} ${4:-0}" >> $t_file
lock_commit_transaction $file
}
+##########################SPELL INSTANCES#############################
+
+#---------------------------------------------------------------------
+## Splits the spell instance a state entry is about. Everything here
+## that used to take a spell name takes an instance -- "spell", or
+## "spell:slot" -- and the database keeps the bare spell name in the
+## field it always did, with the slot in a trailing one, packed by
+## slot_pack because the rows are ':' delimited.
+##
+## A row with no slot field is in slot 0, so nothing written before
+## slots existed has to be migrated; and slot 0 is still written with
+## no slot field, so nothing written now looks any different.
+##
+## Reads and writes read a bare spell name differently, and this is the
+## write. A write names one instance, so a bare name is slot 0.
+##
+## @param spell, or spell:slot
+## @param variable to set to the spell name
+## @param variable to set to the packed slot, empty for slot 0
+##
+## @return 1 if the instance token is malformed
+#---------------------------------------------------------------------
+state_split_instance() {
+ # not __spell and __slot: those are what slot_split calls its own
+ # locals, and upvar would write to them instead of to ours
+ local __inst_spell __inst_slot
+
+ slot_split "$1" __inst_spell __inst_slot || return 1
+ slot_is_default "$__inst_slot" && __inst_slot=""
+ slot_pack "$__inst_slot" __inst_slot
+
+ upvar "$2" "$__inst_spell"
+ upvar "$3" "$__inst_slot"
+ return 0
+}
+
+#---------------------------------------------------------------------
+## Splits the spell instance a query is about. A query asks a question
+## -- "is anything providing gcc installed?" -- so a bare spell name is
+## any slot here, which is portage's reading of an unslotted atom too.
+##
+## The slot is left as it was written, wildcards and all, for slot_match
+## to interpret rather than slot_pack.
+##
+## @param spell, or spell:slot, possibly with '*' axes
+## @param variable to set to the spell name
+## @param variable to set to the slot request, empty for any slot
+##
+## @return 1 if the token is malformed
+#---------------------------------------------------------------------
+state_split_query() {
+ local __inst_spell=$1
+ local __inst_slot=""
+
+ if [[ $1 == *:* ]]; then
+ slot_split "$1" __inst_spell __inst_slot request || return 1
+ fi
+ [[ $__inst_spell ]] || return 1
+
+ upvar "$2" "$__inst_spell"
+ upvar "$3" "$__inst_slot"
+ return 0
+}
+
+#---------------------------------------------------------------------
+## Canonicalises a spell instance token, dropping a slot 0 suffix so
+## that "gcc:0" and "gcc" name the same line of a space delimited file.
+##
+## @param spell, or spell:slot
+## @param variable to set
+##
+## @return 1 if the token is malformed
+#---------------------------------------------------------------------
+state_canon_instance() {
+ # not __spell and __slot: those are what slot_split calls its own
+ # locals, and upvar would write to them instead of to ours
+ local __inst_spell __inst_slot
+
+ slot_split "$1" __inst_spell __inst_slot || return 1
+ slot_join "$__inst_spell" "$__inst_slot" "$2"
+ return 0
+}
+
+#---------------------------------------------------------------------
+## Turns a slot request into a regexp matching the packed slots that
+## answer it. A '*' axis matches any value, and the axes the request
+## leaves off the end are free, so "14" also matches "14@arm-none-eabi".
+##
+## This is slot_match's job done in grep, which is what the depends
+## searches need: they are one regexp over a whole row.
+##
+## @param variable to set
+## @param slot request, may be empty
+##
+## @Stdout nothing; an empty request gives an empty pattern, which the
+## caller is expected to widen to [^:]* itself
+#---------------------------------------------------------------------
+state_slot_pattern() {
+ local __dep_axis __dep_esc
+ local __dep_rest=$2
+ local __dep_pattern=""
+
+ while [[ $__dep_rest ]]; do
+ __dep_axis=${__dep_rest%%:*}
+ if [[ $__dep_rest == *:* ]]; then
+ __dep_rest=${__dep_rest#*:}
+ else
+ __dep_rest=
+ fi
+
+ if [[ $__dep_axis == '*' ]]; then
+ __dep_esc='[^:@]*'
+ else
+ esc_str "$__dep_axis" __dep_esc
+ fi
+ __dep_pattern="$__dep_pattern${__dep_pattern:+@}$__dep_esc"
+ done
+
+ [[ $__dep_pattern ]] && __dep_pattern="$__dep_pattern\\(@[^:]*\\)*"
+ upvar "$1" "$__dep_pattern"
+}
+
+#---------------------------------------------------------------------
+## The tail a depends row grew for slots:
+##
+## : spell:dep(provider):on/off:type:on arg:off arg:slot:dep slot:subslot
+##
+## with both slots packed -- the dependency's being the slot its atom
+## resolved to, since what a dependent breaks on is the instance that
+## went away rather than the wording of the atom -- and the subslot the
+## one the dependency was bound to at cast time, empty unless the atom
+## asked, with '=', to be told when the ABI moves.
+##
+## The tail is written only when it says something, so a dependency
+## between two unslotted spells is still the row sorcery always wrote.
+##
+## @param requester's packed slot, empty for slot 0
+## @param dependency's packed slot request, empty for any
+## @param bound subslot, empty for none
+## @param variable to set
+#---------------------------------------------------------------------
+state_depends_tail() {
+ local __dep_tail=""
+
+ [[ $1 || $2 || $3 ]] && __dep_tail=":${1:-$SLOT_DEFAULT}:$2:$3"
+ upvar "$4" "$__dep_tail"
+}
+
+#---------------------------------------------------------------------
+## The regexp for that tail.
+##
+## A row from before slots has no tail at all, and it stands for the
+## unslotted case: requester in slot 0, dependency unconstrained,
+## nothing bound. A query asking for no more than that has to match
+## such a row, so the whole tail goes optional; a query that asks for
+## anything else must not.
+##
+## @param variable to set
+## @param requester's slot request, empty for any
+## @param dependency's slot request, empty for any
+## @param bound subslot, empty for any
+#---------------------------------------------------------------------
+state_depends_tail_pattern() {
+ local __dep_a7 __dep_a8 __dep_a9
+ local __dep_tail='\(:[^:]*:[^:]*:[^:]*\)\?'
+
+ if [[ $2 || $3 || $4 ]]; then
+ state_slot_pattern __dep_a7 "$2"
+ state_slot_pattern __dep_a8 "$3"
+ esc_str "$4" __dep_a9
+ __dep_a7=${__dep_a7:-[^:]*}
+ __dep_a8=${__dep_a8:-[^:]*}
+ __dep_a9=${__dep_a9:-[^:]*}
+ __dep_tail=":$__dep_a7:$__dep_a8:$__dep_a9"
+
+ if [[ -z $2 || $2 == "$SLOT_DEFAULT" ]] && [[ -z $3 ]] && [[ -z $4 ]];
then
+ __dep_tail="\\($__dep_tail\\)\\?"
+ fi
+ fi
+ upvar "$1" "$__dep_tail"
+}
+
+#---------------------------------------------------------------------
+## The same two things for a sub-depends row, which grew a tail of two
+## slots -- the requester's and the sub-dependee's -- rather than three.
+##
+## @param requester's packed slot, empty for slot 0
+## @param sub-dependee's packed slot, empty for slot 0
+## @param variable to set
+#---------------------------------------------------------------------
+state_sub_depends_tail() {
+ local __dep_tail=""
+
+ [[ $1 || $2 ]] && __dep_tail=":${1:-$SLOT_DEFAULT}:${2:-$SLOT_DEFAULT}"
+ upvar "$3" "$__dep_tail"
+}
+
+#---------------------------------------------------------------------
+## @param variable to set
+## @param requester's slot request, empty for any
+## @param sub-dependee's slot request, empty for any
+#---------------------------------------------------------------------
+state_sub_depends_tail_pattern() {
+ local __dep_a4 __dep_a5
+ local __dep_tail='\(:[^:]*:[^:]*\)\?'
+
+ if [[ $2 || $3 ]]; then
+ state_slot_pattern __dep_a4 "$2"
+ state_slot_pattern __dep_a5 "$3"
+ __dep_a4=${__dep_a4:-[^:]*}
+ __dep_a5=${__dep_a5:-[^:]*}
+ __dep_tail=":$__dep_a4:$__dep_a5"
+
+ if [[ -z $2 || $2 == "$SLOT_DEFAULT" ]] &&
+ [[ -z $3 || $3 == "$SLOT_DEFAULT" ]]; then
+ __dep_tail="\\($__dep_tail\\)\\?"
+ fi
+ fi
+ upvar "$1" "$__dep_tail"
+}
+
+#---------------------------------------------------------------------
+## Reads a sub-depends row back into the two instances it is between.
+## The slots live at the end of the row and are missing altogether on a
+## row between unslotted spells, which is what the callers that used to
+## cut the row up by hand cannot see.
+##
+## @param row
+## @param variable to set to the requester instance (optional)
+## @param variable to set to the sub-dependee instance (optional)
+## @param variable to set to the sub-depends (optional)
+##
+## @return 1 if the row is not a sub-depends row
+#---------------------------------------------------------------------
+state_split_sub_depends_row() {
+ local __row_fields __row_rslot __row_dslot
+ # explode does not clear the array it is handed
+ __row_fields=()
+ explode "$1" ":" __row_fields
+ [[ ${#__row_fields[*]} -ge 3 ]] || return 1
+
+ slot_unpack "${__row_fields[3]}" __row_rslot
+ slot_unpack "${__row_fields[4]}" __row_dslot
+
+ [[ $2 ]] && slot_join "${__row_fields[0]}" "$__row_rslot" "$2"
+ [[ $3 ]] && slot_join "${__row_fields[1]}" "$__row_dslot" "$3"
+ [[ $4 ]] && upvar "$4" "${__row_fields[2]}"
+ return 0
+}
+
+#---------------------------------------------------------------------
+## Filters depends rows on stdin down to those whose requester is in a
+## slot the request accepts. For the searches that cannot anchor the
+## end of the row, and so cannot use state_depends_tail_pattern.
+##
+## @param slot request
+#---------------------------------------------------------------------
+state_filter_depends_slot() {
+ local line slot
+
+ awk -F: '{ s = $7; gsub(/@/, ":", s); print (s == "" ? "0" : s) " " $0 }' |
+ while read -r slot line; do
+ slot_match "$1" "$slot" || continue
+ echo "$line"
+ done
+}
+
+#---------------------------------------------------------------------
+## Turns depends rows on stdin into the instances that asked for them.
+## The requester is the first field of a row and the slot it is in is
+## the seventh, which is why cut(1) cannot answer this.
+#---------------------------------------------------------------------
+state_depends_requesters() {
+ local __row_line __row_slot __row_fields
+
+ while IFS= read -r __row_line; do
+ # explode does not clear the array it is handed
+ __row_fields=()
+ explode "$__row_line" ":" __row_fields
+ [[ ${__row_fields[0]} ]] || continue
+ slot_unpack "${__row_fields[6]}" __row_slot
+ slot_join "${__row_fields[0]}" "$__row_slot"
+ done
+}
+
+#---------------------------------------------------------------------
+## The same for the other end of the row: the instances that were
+## depended on. The provider a dependency was answered through is
+## parenthesised after the name and is not part of it.
+#---------------------------------------------------------------------
+state_depends_dependees() {
+ local __row_line __row_slot __row_fields
+
+ while IFS= read -r __row_line; do
+ __row_fields=()
+ explode "$__row_line" ":" __row_fields
+ [[ ${__row_fields[1]} ]] || continue
+ slot_unpack "${__row_fields[7]}" __row_slot
+ slot_join "${__row_fields[1]%%(*}" "$__row_slot"
+ done
+}
+
+#---------------------------------------------------------------------
+## The one place that knows the shape of a SPELL_STATUS row:
+##
+## : spell:date:status:version:slot:subslot
+##
+## with the slot packed, empty for slot 0, and the subslot empty when
+## it is the slot -- which is its default, and all an unslotted spell
+## that says nothing about ABIs will ever have.
+##
+## Prints the rows matching a query, in file order. The caller holds
+## the lock.
+##
+## @param spell status file
+## @param spell name
+## @param slot request, empty for any slot
+## @param version, empty for any version
+#---------------------------------------------------------------------
+state_spell_status_rows() {
+ local line slot
+
+ # awk unpacks the slot and puts it up front, where read can have it
+ # without a field having to be empty -- read eats a leading separator
+ # when the separator is whitespace, and keeping a tab out of the way
+ # of $IFS is more trouble than a slot never containing a space is
+ awk -F: -v spell="$2" -v version="$4" -- \
+ '$1 == spell && (version == "" || $4 == version) {
+ slot = $5
+ gsub(/@/, ":", slot)
+ print (slot == "" ? "0" : slot) " " $0 }' \
+ "$1" 2>/dev/null |
+ while read -r slot line; do
+ [[ -z $3 ]] || slot_match "$3" "$slot" || continue
+ echo "$line"
+ done
+}
+
#---------------------------------------------------------------------
##
## Adds an entry to the SPELL_STATUS file
@@ -636,110 +1120,181 @@ add_version_cache() {
## Arguments may either be SPELL, ACTION, VERSIONS, or just
## ACTION if there exists SPELL and VERSIONS variables already
## set.
-## @param spell OR action
+## @param spell instance OR action
## @param (if spell) action
## @param (if spell) version
+## @param (if spell) subslot (optional, defaults to the slot)
##
#---------------------------------------------------------------------
add_spell_status()
-{ #$1=spell, $2=action, $3=version, OR $1=action.
+{ #$1=spell[:slot], $2=action, $3=version, $4=(OPT)subslot, OR $1=action.
- local spell action version date
+ local spell action version date slot subslot extra
if [ $# -eq 1 ] ; then
spell="$SPELL"
action="$1"
version="$VERSION"
+ slot_is_default "$SLOT" || slot_pack "$SLOT" slot
+ subslot="$SUBSLOT"
else
- spell=$1
+ state_split_instance "$1" spell slot || return 1
action=$2
version=$3
+ subslot=$4
fi
[[ $spell ]] && [[ $action ]] && [[ $version ]] || return 1
+
+ # a spell whose ABI is just its slot has nothing to say, and neither
+ # has slot 0, so the row of an unslotted spell comes out byte for byte
+ # what it has always been
+ [[ $subslot == "$(slot_unpack "$slot")" ]] && subslot=""
+ [[ $slot || $subslot ]] && extra=":$slot"
+ [[ $subslot ]] && extra="$extra:$subslot"
+
date=$(date +%Y%m%d)
lock_start_transaction "$SPELL_STATUS" tSPELL_STATUS
- echo "$spell:$date:$action:$version" >> $tSPELL_STATUS
+ echo "$spell:$date:$action:$version$extra" >> $tSPELL_STATUS
lock_commit_transaction $SPELL_STATUS
}
#---------------------------------------------------------------------
-## @param spell name
+## @param spell instance
+## @param status
+## @param version
+## @param subslot (optional)
##
## Given a spell, status (installed, held, etc), and version, add_spell
## adds the spell to the /var/state/sorcery/packages file
##
#---------------------------------------------------------------------
add_spell() {
- remove_spell $1
- add_spell_status $1 $2 $3
+ remove_spell "$1"
+ add_spell_status "$1" "$2" "$3" "$4"
}
#---------------------------------------------------------------------
## @param spell status file
-## @param spell
+## @param spell instance
## @param version (optional)
## @Stdout matching lines
##
-## Arguments can be regexp
-##
-## Prints out the matching line(s)
+## Prints out the matching line(s). A bare spell name matches every
+## slot of it.
##
#---------------------------------------------------------------------
search_spell_status() {
[[ -z $2 ]] && return 1
local spell_status=$1
- shift
+ local spell request rows
+
+ state_split_query "$2" spell request || return 1
- local spell
- esc_str "$1" spell
lock_file "$spell_status"
- if [[ -z $2 ]]; then
- grep "^$spell:" $spell_status
- else
- local version
- esc_str "$2" version
- grep "^$spell:$version:" $spell_status
- fi
+ rows=$(state_spell_status_rows "$spell_status" "$spell" "$request" "$3")
unlock_file "$spell_status"
+
+ [[ $rows ]] || return 1
+ echo "$rows"
}
#---------------------------------------------------------------------
##
-## @param spell ( [A-Za-z0-9_-] )
-## @param version (optional and may not contain spaces or colons; '.' and
'*' have special meaning)
+## @param spell instance ( spell, or spell:slot with optional '*' axes )
+## @param version (optional and may not contain spaces or colons)
## @param variable to set
-## @Stdout last matching line ( only one even if there are more )
##
## Searches the SPELL_STATUS file for a spell and optionally
-## a version.
+## a version. A bare spell name asks about every slot of it, and the
+## last matching row wins, as it always has.
##
-## Prints out the matching spell's status
## @return 1 only for critical problems ( missing SPELL_STATUS file )
## @return 0 for all other cases ( even when spell is not found )
##
#---------------------------------------------------------------------
query_spell_status()
-{ #$1=spell, $2=(OPT)version
+{ #$1=spell[:slot], $2=(OPT)version, $3=variable
- local return_var __status
+ local __return_var __row __spell __request
+ local __status="" __version=""
[[ -e $SPELL_STATUS ]] || return 1
- lock_file "$SPELL_STATUS"
if [[ -z $3 ]]; then
[[ -z $2 ]] && return 0
- return_var="$2"
- __status=$(awk -F: -v spell="$1" '$1 == spell { status = $3 } END {
print status }' "$SPELL_STATUS" 2>/dev/null)
+ __return_var=$2
else
- return_var="$3"
- __status=$(awk -F: -v spell="$1" -v version="$2" '$1 == spell && $4 ==
version { status = $3 } END { print status }' "$SPELL_STATUS" 2>/dev/null)
+ __return_var=$3
+ __version=$2
fi
+
+ state_split_query "$1" __spell __request || return 1
+
+ lock_file "$SPELL_STATUS"
+ __row=$(state_spell_status_rows "$SPELL_STATUS" "$__spell" "$__request" \
+ "$__version" | tail -n 1)
+ unlock_file "$SPELL_STATUS"
+
+ [[ $__row ]] && __status=$(echo "$__row" | cut -f3 -d:)
+ upvar "$__return_var" "$__status"
+ return 0
+}
+
+#---------------------------------------------------------------------
+##
+## @param spell instance
+## @param variable to set
+##
+## The ABI the instance was installed with, empty if it is not
+## installed. A row naming no subslot has the one its slot stands for.
+##
+## @return 1 only for critical problems ( missing SPELL_STATUS file )
+#---------------------------------------------------------------------
+query_spell_subslot() {
+ local __row __spell __request __slot
+ local __subslot=""
+ [[ -e $SPELL_STATUS ]] || return 1
+
+ state_split_query "$1" __spell __request || return 1
+
+ lock_file "$SPELL_STATUS"
+ __row=$(state_spell_status_rows "$SPELL_STATUS" "$__spell" "$__request" \
+ "" | tail -n 1)
unlock_file "$SPELL_STATUS"
- eval "$return_var=\"\$__status\""
+ if [[ $__row ]]; then
+ __subslot=$(echo "$__row" | cut -f6 -d:)
+ [[ $__subslot ]] || slot_unpack "$(echo "$__row" | cut -f5 -d:)"
__subslot
+ fi
+ upvar "$2" "$__subslot"
return 0
}
#---------------------------------------------------------------------
+##
+## @param spell instance ( spell, or spell:slot with optional '*' axes )
+## @Stdout the installed instances of the spell, one per line
+##
+## Only installed and held instances are listed: an exiled row is a
+## note about a spell, not an install of one.
+##
+#---------------------------------------------------------------------
+spell_instances() {
+ local slot spell request status
+ [[ -e $SPELL_STATUS ]] || return 1
+
+ state_split_query "$1" spell request || return 1
+
+ lock_file "$SPELL_STATUS"
+ state_spell_status_rows "$SPELL_STATUS" "$spell" "$request" "" |
+ while IFS=: read -r spell _ status _ slot _; do
+ [[ $status == installed || $status == held ]] || continue
+ slot_unpack "$slot" slot
+ slot_join "$spell" "$slot"
+ done | sort -u
+ unlock_file "$SPELL_STATUS"
+}
+
+#---------------------------------------------------------------------
## @Type API
## @param spell name
##
@@ -784,8 +1339,10 @@ real_spell_ok() {
## @return 0 if any provider of $1 is installed
#---------------------------------------------------------------------
real_provider_ok() {
+ # a provider is provided by an instance: the slot is at the end of the
+ # row, not in the field cut(1) would hand back
for spell in $(search_depends_status_exact $DEPENDS_STATUS '.*' ".*($1)" \
- 'on' '.*' '.*' |cut -f2 -d:|cut -f1 -d\(|sort -u); do
+ 'on' '.*' '.*' | state_depends_dependees | sort -u); do
spell_ok $spell && return 0
done
return 1
@@ -803,14 +1360,18 @@ real_provider_ok() {
#---------------------------------------------------------------------
real_get_spell_provider() {
local dep_status
+ # the uncommitted file is filed under the canonical instance, so a
+ # caller holding a bare name finds the same one
+ local instance
+ state_canon_instance "$1" instance || instance=$1
if [[ $3 ]] ; then
dep_status=$DEPENDS_STATUS
else
- hash_get_ref uncommitted_hash $1 dep_status
+ hash_get_ref uncommitted_hash "$instance" dep_status
[[ $dep_status ]] || dep_status=$DEPENDS_STATUS
fi
- search_depends_status_exact $dep_status "$1" ".*($2)" \
- 'on' '.*' '.*' '.*' |cut -f2 -d:|cut -f1 -d\(|sort -u
+ search_depends_status_exact $dep_status "$instance" ".*($2)" \
+ 'on' '.*' '.*' '.*' | state_depends_dependees | sort -u
}
#---------------------------------------------------------------------
@@ -826,13 +1387,15 @@ real_get_spell_provider() {
#---------------------------------------------------------------------
real_is_depends_enabled() {
local dep_status
+ local instance
+ state_canon_instance "$1" instance || instance=$1
if [[ $3 ]] ; then
dep_status=$DEPENDS_STATUS
else
- hash_get_ref uncommitted_hash $1 dep_status
+ hash_get_ref uncommitted_hash "$instance" dep_status
[[ $dep_status ]] || dep_status=$DEPENDS_STATUS
fi
- [[ -n $(search_depends_status_simple $dep_status "$1" "$2" 'on' ) ]]
+ [[ -n $(search_depends_status_simple $dep_status "$instance" "$2" 'on' ) ]]
}
@@ -850,21 +1413,28 @@ real_spell_exiled() {
#---------------------------------------------------------------------
-## @param spell to remove status of
+## @param spell instance to remove status of
## Removes all specified offending entries in SPELL_STATUS
##
+## Only the named instance goes: casting gcc:15 must not forget that
+## gcc:14 is installed.
+##
#---------------------------------------------------------------------
remove_spell_status()
-{ #$1=spell to remove status of
+{ #$1=spell instance to remove status of
+
+ local spell slot
+ state_split_instance "$1" spell slot || return 1
lock_start_transaction "$SPELL_STATUS" tSPELL_STATUS
- grep -v "^$1:" $SPELL_STATUS > $tSPELL_STATUS
+ awk -F: -v spell="$spell" -v slot="$slot" -- \
+ '!($1 == spell && $5 == slot)' $SPELL_STATUS > $tSPELL_STATUS
lock_commit_transaction $SPELL_STATUS
}
#---------------------------------------------------------------------
-## @param spell name
+## @param spell instance
##
## Removes the given spell from the /var/state/sorcery/packages file.
## if C<EXILE> is set, the spell is changed to "exiled" in the file.
@@ -872,9 +1442,9 @@ remove_spell_status()
#---------------------------------------------------------------------
remove_spell() {
debug "libsorcery" "Running remove_spell() on $1"
- remove_spell_status $1
+ remove_spell_status "$1"
if [ -n "$EXILE" ]; then
- add_spell_status $1 "exiled" "0.0"
+ add_spell_status "$1" "exiled" "0.0"
fi
}
@@ -894,15 +1464,38 @@ all_spell_status()
#---------------------------------------------------------------------
-## @param spells
+## @param spell status file
+## @param spell name
+## @param packed slot, empty for every slot of the spell
+## @param status to look for
+## @param status to set instead
+##
+## The caller is inside the transaction and hands us its temp file.
+#---------------------------------------------------------------------
+state_reset_status() {
+ local file=$1
+
+ awk -F: -v OFS=: -v spell="$2" -v slot="$3" -v from="$4" -v to="$5" -- \
+ '$1 == spell && (slot == "" || $5 == slot) && $3 == from { $3 = to }
+ { print }' "$file" > "$file.status" &&
+ mv -f "$file.status" "$file"
+}
+
+#---------------------------------------------------------------------
+## @param spell instances
## sets <spells>'s status to held
##
+## A bare spell name holds every slot of it -- the user is naming a
+## spell, not conjuring an instance into being.
+##
#---------------------------------------------------------------------
set_held() {
- local t_status
+ local t_status to_hold spell slot
lock_start_transaction "$SPELL_STATUS" t_status
for to_hold in $@; do
- sedit 's/\(^'$(esc_str $to_hold)':[^:]*:\)installed/\1held/'
$t_status
+ state_split_query "$to_hold" spell slot || continue
+ slot_pack "$slot" slot
+ state_reset_status "$t_status" "$spell" "$slot" installed held
done
lock_commit_transaction $SPELL_STATUS
}
@@ -969,15 +1562,17 @@ set_unexiled() {
#---------------------------------------------------------------------
-## @param spells
+## @param spell instances
## sets <spells>'s status to installed
##
#---------------------------------------------------------------------
set_unheld() {
- local t_status
+ local t_status to_hold spell slot
lock_start_transaction "$SPELL_STATUS" t_status
for to_hold in $@; do
- sedit 's/\(^'$(esc_str $to_hold)':[^:]*:\)held/\1installed/'
$t_status
+ state_split_query "$to_hold" spell slot || continue
+ slot_pack "$slot" slot
+ state_reset_status "$t_status" "$spell" "$slot" held installed
done
lock_commit_transaction $SPELL_STATUS
}
@@ -985,18 +1580,21 @@ set_unheld() {
#---------------------------------------------------------------------
## @param status (installed, held, exiled or ok[for the first two])
-## @Stdout spells
+## @Stdout spell instances
## returns all spells that are in that status
##
+## Slotted spells come out as "spell:slot"; everything else, which is
+## to say everything today, comes out as its bare name.
+##
#---------------------------------------------------------------------
get_all_spells_with_status() {
local status=$1
lock_file $SPELL_STATUS
- if [[ $status != ok ]]; then
- awk -F: -v status="$status" '$3 == status { print $1 }' $SPELL_STATUS
- else
- awk -F: '$3 == "installed" || $3 == "held" { print $1 }' $SPELL_STATUS
- fi
+ awk -F: -v status="$status" -- \
+ '$3 == status || (status == "ok" && ($3 == "installed" || $3 ==
"held")) {
+ slot = $5
+ gsub(/@/, ":", slot)
+ print $1 (slot == "" ? "" : ":" slot) }' $SPELL_STATUS
unlock_file $SPELL_STATUS
}
diff --git a/var/lib/sorcery/modules/libsummon
b/var/lib/sorcery/modules/libsummon
index 69a940d..2ba5683 100755
--- a/var/lib/sorcery/modules/libsummon
+++ b/var/lib/sorcery/modules/libsummon
@@ -28,10 +28,15 @@
#---------------------------------------------------------------------
summon_spell() {
$STD_DEBUG
- local SPELL=$1
+ # run_details below reads the instance, so it is set here rather than
+ # left to whatever spell was loaded last
+ local SPELL_INSTANCE=$1
+ local SPELL tag
+ slot_split "$1" SPELL || { slot_bad_token_message "$1"; return 1; }
+ slot_instance_tag "$1" tag
local rc=1
- if [ -n "${SPELL}" ] && lock_resources "summon" "${SPELL}"; then
- local dl_dir=$TMP_DIR/${SPELL:-none}
+ if [ -n "${SPELL}" ] && lock_resources "summon" "${SPELL_INSTANCE}"; then
+ local dl_dir=$TMP_DIR/${tag:-none}
debug "libsummon" "dl_dir is $dl_dir"
mkdir -p $dl_dir && pushd $dl_dir &>/dev/null &&
@@ -43,9 +48,9 @@ summon_spell() {
rc=$?
if [[ $rc == 0 ]]; then
- activity_log "summon" "$SPELL" "$VERSION" "success"
+ activity_log "summon" "${SPELL_INSTANCE:-$SPELL}" "$VERSION"
"success"
else
- activity_log "summon" "$SPELL" "$VERSION" "failure" "download
failed"
+ activity_log "summon" "${SPELL_INSTANCE:-$SPELL}" "$VERSION"
"failure" "download failed"
fi
# this actually only returns from the subshell, not the function
@@ -53,7 +58,7 @@ summon_spell() {
) && popd &>/dev/null
rc=$?
rm -rf $dl_dir
- unlock_resources "summon" "${SPELL}"
+ unlock_resources "summon" "${SPELL_INSTANCE}"
fi
return $rc
}
diff --git a/var/lib/sorcery/modules/libtablet
b/var/lib/sorcery/modules/libtablet
index 04e01c9..d6c82d2 100755
--- a/var/lib/sorcery/modules/libtablet
+++ b/var/lib/sorcery/modules/libtablet
@@ -377,12 +377,13 @@ tablet_get_spell_name_from_path() {
#---------------------------------------------------------------------
## setup a unique tablet directory
-## @param spell name
+## @param spell instance
## @stdout path to unique tablet directory
## @return 0 if the directory was made, 1 if not
#---------------------------------------------------------------------
tablet_get_path() {
- local SPELL=$1
+ local SPELL
+ slot_instance_tag "$1" SPELL || SPELL=$1
local tb_dir=$TABLET_PATH/$SPELL/$(date +%Y%m%d%H%M%S)
local sleep_time
local made
@@ -405,7 +406,7 @@ tablet_get_path() {
}
#----------------------------------------------------------------------
-## @param spell name
+## @param spell instance
## @param upvar
## @param timestamp (optional)
##
@@ -425,12 +426,14 @@ tablet_find_spell_dir() {
return 0
fi
local SPELL=$1
+ local tb_tag
+ slot_instance_tag "$SPELL" tb_tag || tb_tag=$SPELL
if ! test -d $TABLET_PATH; then
mkdir -p $TABLET_PATH
return 1
fi
local __spell_dir
- local base_dir=$TABLET_PATH/$SPELL
+ local base_dir=$TABLET_PATH/$tb_tag
if ! [[ $base_dir ]] || ! test -d $base_dir ; then
return 1
fi
@@ -464,13 +467,14 @@ tablet_find_spell_dir() {
}
#----------------------------------------------------------------------
-## @param spell name
+## @param spell instance
## @param upvar
## quicker dirtier version of above without special checks since the
## tablet in a cache tarball has nothing to do with the installed system
#----------------------------------------------------------------------
tablet_find_resurrect_dir() {
- local SPELL=$1
+ local SPELL
+ slot_instance_tag "$1" SPELL || SPELL=$1
if ! test -d $TABLET_PATH; then
return 1
fi
@@ -528,15 +532,15 @@ tablet_install_spell_files() {
test -e $SPELL_CONFIG.p && cp ${SPELL_CONFIG}.p spell_config.p
# depends info
- get_uncommitted_depends_file $SPELL spell_depends &&
+ get_uncommitted_depends_file "$SPELL_INSTANCE" spell_depends &&
test -e $spell_depends && cp $spell_depends depends
# sub-depends we provide
local sub_depends_file rsub_depends_file
- get_uncommitted_sub_depends_file $SPELL sub_depends_file &&
+ get_uncommitted_sub_depends_file "$SPELL_INSTANCE" sub_depends_file &&
test -e $sub_depends_file && cp $sub_depends_file sub_depends
# get sub-depend we request
- get_uncommitted_rsub_depends_file $SPELL sub_depends_file &&
+ get_uncommitted_rsub_depends_file "$SPELL_INSTANCE" sub_depends_file &&
test -e $rsub_depends_file && cp $sub_depends_file rsub_depends
# logs
@@ -570,12 +574,16 @@ tablet_install_spell_files() {
## efficiency.
#---------------------------------------------------------------------
tablet_set_spell() {
+ # a page is a page of one instance, so the slot comes off the token
+ # here and is put back by codex_set_current_instance below, the same
+ # way codex_set_current_spell does it from a spell directory
+ local tb_slot tb_config
codex_clear_current_spell
- SPELL=$1
+ slot_split "$1" SPELL tb_slot || return 1
if [[ $2 ]] && test -d $2; then
SPELL_DIRECTORY=$2
else
- tablet_find_spell_dir $SPELL SPELL_DIRECTORY || return 1
+ tablet_find_spell_dir "$1" SPELL_DIRECTORY || return 1
TABLET_PAGE=$SPELL_DIRECTORY
fi
@@ -597,10 +605,20 @@ tablet_set_spell() {
. $SPELL_CONFIG > /dev/null 2> /dev/null
fi
+ # the tablet carries a copy of the spell directory, SLOTS and all, so
+ # DETAILS branches on the same $SLOT_<AXIS> it was cast with
+ codex_set_current_slot "$SCRIPT_DIRECTORY" "$tb_slot"
+
persistent_load
. $SPELL_DIRECTORY/DETAILS 1>/dev/null 2>&1
persistent_clear
+ # codex_set_current_instance would point SPELL_CONFIG at the system
+ # copy, and a tablet's own is the one this spell was cast with
+ tb_config=$SPELL_CONFIG
+ codex_set_current_instance
+ SPELL_CONFIG=$tb_config
+
BUILD_API=$(<$SPELL_DIRECTORY/build_api)
# if BUILD_API isnt set something is wrong, but just be safe
[[ -z $BUILD_API ]] && BUILD_API=2
@@ -646,6 +664,8 @@ tablet_import_repair_files_page() {
local spell spell_dir
tablet_get_spell_name_from_path "$page" spell
+ # a chapter is named after the install; the codex knows spells
+ slot_undisk_tag "$spell" spell
hash_get_ref $2 $spell spell_dir
codex_get_spell_paths "$spell_dir"
@@ -834,11 +854,16 @@ tablet_cleanse_chapter() {
local do_repair=$3
local spell
smgl_basename "$sdir" spell
+ # the chapter is named after an install, and what is installed is
+ # asked about by instance and looked up in the codex by name
+ local instance name
+ slot_tag_instance "$spell" instance || instance=$spell
+ slot_split "$instance" name || name=$spell
local page rc
mkdir -p "$backup_dir/duplicates"
mkdir -p "$backup_dir/chapter_files"
- tablet_fix_duplicates "$spell" "$sdir" "$backup_dir/duplicates"
+ tablet_fix_duplicates "$instance" "$sdir" "$backup_dir/duplicates"
test -d "$sdir" || return 0
@@ -849,7 +874,7 @@ tablet_cleanse_chapter() {
elif [[ $do_repair == yes ]] ; then
( # pretend we're tablet_import_repair_files
shopt -s nullglob
- hash_put spell_lookup_hash "$spell" "$(codex_find_spell_by_name
$spell)"
+ hash_put spell_lookup_hash "$name" "$(codex_find_spell_by_name
$name)"
tablet_import_repair_files_page "$page" spell_lookup_hash
)
fi
diff --git a/var/lib/sorcery/modules/libtrack
b/var/lib/sorcery/modules/libtrack
index 641e250..e0ffa7f 100755
--- a/var/lib/sorcery/modules/libtrack
+++ b/var/lib/sorcery/modules/libtrack
@@ -310,9 +310,9 @@ check_if_modified() {
local to=$1
local md5_log=$2
if ! [[ $2 ]] ; then
- md5_log="$TMP_DIR/$SPELL.md5"
+ md5_log="$TMP_DIR/${SPELL_DISK_TAG:-$SPELL}.md5"
if [[ $OLD_SPELL_VERSION ]] ; then
- old_md5_log="$MD5SUM_LOGS/$SPELL-$OLD_SPELL_VERSION"
+ old_md5_log="$MD5SUM_LOGS/$SPELL_DISK_TAG-$OLD_SPELL_VERSION"
# log must be in filterable form
log_adjuster "$old_md5_log" "$md5_log" filterable root
else
@@ -401,7 +401,8 @@ create_cache_archive() {
# gather the queuing factors, so we can include them in the label, saving
us the
# need to compute the spell name and a few untars when dealing with just a
cache
local label
- label="$SPELL ${VERSION:-0} ${PATCHLEVEL:-0} ${SECURITY_PATCH:-0}
${UPDATED:-0}"
+ # the instance, so that resurrect -f knows which slot the cache holds
+ label="${SPELL_INSTANCE:-$SPELL} ${VERSION:-0} ${PATCHLEVEL:-0}
${SECURITY_PATCH:-0} ${UPDATED:-0}"
local TMP_DATA=$TMP_DIR/foo.data
local TMP_MDATA=$TMP_DIR/foo.mdata
diff --git a/var/lib/sorcery/modules/libtriggers
b/var/lib/sorcery/modules/libtriggers
index 08d1a1d..1deb18e 100755
--- a/var/lib/sorcery/modules/libtriggers
+++ b/var/lib/sorcery/modules/libtriggers
@@ -23,60 +23,123 @@ run_triggers() {
}
#---------------------------------------------------------------------
-## @param spell
+## @param spell instance
##
## Remove's all of a spell's triggers from the list of registered
-## triggers.
+## triggers. A trigger belongs to the instance that registered it, so
+## one slot going away leaves the others' triggers where they are.
##
#---------------------------------------------------------------------
remove_triggers()
{
debug "libtriggers" "remove_triggers - $*"
+ local spell
+ esc_str "$1" spell
+
[ -f $TRIGGER_LIST ] || return 0
lock_start_transaction "$TRIGGER_LIST" tTRIGGER_LIST
- grep -v "^$SPELL:" $TRIGGER_LIST > $tTRIGGER_LIST
+ # the subject may hold a slot of its own, so "gcc" here is gcc's slot 0
+ # and not every slot of gcc; the cause after it never holds one
+ grep -v "^$spell:[^:]*:on_" $TRIGGER_LIST > $tTRIGGER_LIST
lock_commit_transaction $TRIGGER_LIST
return $?
}
#---------------------------------------------------------------------
+## Reads a trigger row back into the four things it is made of. The
+## subject may hold a slot, so the row is found by its event rather than
+## counted off from the front; the cause is written packed and the
+## action is whatever is left, script arguments and all.
+##
+## @param row
+## @param variable to set to the subject instance (optional)
+## @param variable to set to the packed cause (optional)
+## @param variable to set to the event (optional)
+## @param variable to set to the action (optional)
+##
+## @return 1 if the row is not a trigger row
+#---------------------------------------------------------------------
+trigger_split_row() {
+ local __trg_row=$1 __trg_head __trg_event
+
+ for __trg_event in on_cast on_pre_cast on_dispel on_pre_dispel; do
+ [[ $__trg_row == *":$__trg_event:"* ]] && break
+ __trg_event=""
+ done
+ [[ $__trg_event ]] || return 1
+
+ __trg_head=${__trg_row%%:$__trg_event:*}
+ [[ $__trg_head == *:* ]] || return 1
+
+ [[ $2 ]] && upvar "$2" "${__trg_head%:*}"
+ [[ $3 ]] && upvar "$3" "${__trg_head##*:}"
+ [[ $4 ]] && upvar "$4" "$__trg_event"
+ [[ $5 ]] && upvar "$5" "${__trg_row#*:$__trg_event:}"
+ return 0
+}
+
+#---------------------------------------------------------------------
## @param event
-## @param [spell]
-## @Globals SPELL if \$2 is omitted
+## @param [spell instance]
+## @Globals SPELL SPELL_INSTANCE if \$2 is omitted
##
## @Stdout Query and warnings
## @Stdin y/n
## Triggers an event and performs necessary actions. Argument 2 is
## optional. If omitted, the value of SPELL will be used.
##
+## A trigger waits on a spell or on one slot of it, so casting an
+## instance fires the rows that named the slot and the rows that did
+## not, the way $SUSTAINED reads a bare name as every slot.
+##
#---------------------------------------------------------------------
trigger()
{
debug "libtriggers" "trigger - $*"
- local spell TRIGGER action
+ local spell instance action
action=$1
- esc_str "${SPELL:-$2}" spell
+ slot_split "${SPELL:-$2}" spell || spell=${SPELL:-$2}
+ slot_instance_tag "${SPELL_INSTANCE:-${SPELL:-$2}}" instance ||
instance=$spell
+ esc_str "$spell" spell
+ esc_str "$instance" instance
[ -f $TRIGGER_LIST ] || return 0
# no locking of $TRIGGER_LIST since the triggered actions may need it
- iterate do_trigger $'\n' "$(grep "^[^:]*:$spell:on_$action" $TRIGGER_LIST)"
+ # the subject in front of the cause may hold a slot, so the row cannot
+ # be anchored at its start
+ iterate do_trigger $'\n' \
+ "$(grep -e ":$spell:on_$action:" -e ":$instance:on_$action:" \
+ $TRIGGER_LIST)"
}
#---------------------------------------------------------------------
-## Get the triggerees of a given spell event and action
+## @param spell instance
+## @param event
+## @param actions
+##
+## Get the triggerees of a given spell event and action. Both what
+## waits and what is waited on may hold a slot: the subject comes off
+## the row whole rather than as its first field, and a cause naming no
+## slot is woken by every slot.
#---------------------------------------------------------------------
get_triggerees() {
- local spell=$1
+ local instance=$1
local event=$2
- local each
+ local each spell cause row subject
# get all the cast/check_self triggers
[ -f $TRIGGER_LIST ] || return 0
+ slot_split "$instance" spell || spell=$instance
+ slot_instance_tag "$instance" cause || cause=$spell
+
lock_file $TRIGGER_LIST
for each in $3 ; do
- grep "[^:]*:$spell:$event:$each" $TRIGGER_LIST|cut -f1 -d:
+ grep -e ":$spell:$event:$each" -e ":$cause:$event:$each" $TRIGGER_LIST |
+ while read row; do
+ trigger_split_row "$row" subject && echo "$subject"
+ done
done|sort -u|grep -v '^$'
unlock_file $TRIGGER_LIST
}
@@ -84,14 +147,28 @@ get_triggerees() {
#---------------------------------------------------------------------
## Get the run_script triggers from $spell on $target when theres a
## $action
+##
+## @param spell instance
+## @param event
+## @param target
#---------------------------------------------------------------------
get_run_script_triggers() {
- local spell=$1
+ local instance=$1
local event=$2
local target=$3
+ local spell cause row action
+
+ slot_split "$instance" spell || spell=$instance
+ slot_instance_tag "$instance" cause || cause=$spell
lock_file $TRIGGER_LIST
- grep "$target:$spell:$event:run_script" $TRIGGER_LIST|cut -f4 -d:
+ # the action is the tail of the row, not its fourth field: the subject
+ # in front of it may hold a slot
+ grep -e "^$target:$spell:$event:run_script" \
+ -e "^$target:$cause:$event:run_script" $TRIGGER_LIST |
+ while read row; do
+ trigger_split_row "$row" "" "" "" action && echo "$action"
+ done
unlock_file $TRIGGER_LIST
}
@@ -129,8 +206,14 @@ set_trigger()
esac
#perhaps a check to make sure that $2 exists?
- spell=${SPELL:-$4}
- str="$spell:$2:$1:$3"
+ # the row belongs to the instance registering it -- for slot 0 that is
+ # the bare spell name, so the file reads as it always has
+ spell=${SPELL_INSTANCE:-${SPELL:-$4}}
+ # the cause is written packed, like a slot in the state database, so
+ # that only one field of the row can hold a ':'
+ local cause
+ slot_instance_tag "$2" cause || cause=$2
+ str="$spell:$cause:$1:$3"
lock_file $TRIGGER_LIST
if ! test -f $TRIGGER_LIST || ! grep -q "$str" $TRIGGER_LIST ; then
@@ -189,13 +272,16 @@ real_on_pre_dispel() {
do_trigger() {
- TRIGGER=()
- explode "$1" ":" TRIGGER
+ # a row is read back rather than exploded: the subject may hold a slot
+ # and the action may hold a script
+ local subject cause todo
+ trigger_split_row "$1" subject cause "" todo || return
+ slot_tag_instance "$cause" cause
local DC="$DEFAULT_COLOR"
- message "${SPELL_COLOR}${TRIGGER[1]}${DC}${QUERY_COLOR}" \
+ message "${SPELL_COLOR}${cause}${DC}${QUERY_COLOR}" \
"being $action has triggered a" \
- "\"${DC}${FILE_COLOR}${TRIGGER[3]}${DC}${QUERY_COLOR}\"" \
- "on spell ${DC}${SPELL_COLOR}${TRIGGER[0]}${DC}."
+ "\"${DC}${FILE_COLOR}${todo}${DC}${QUERY_COLOR}\"" \
+ "on spell ${DC}${SPELL_COLOR}${subject}${DC}."
query "Proceed? " "y"
[[ $? != 0 ]] && return
@@ -203,21 +289,21 @@ do_trigger() {
unset CAST_PASS D_LOG
function run_script() { eval $@ ; }
#Ok, now do the required action:
- case ${TRIGGER[3]} in
+ case $todo in
cast_self)
- cast -c "${TRIGGER[0]}"
+ cast -c "$subject"
;;
dispel_self)
- dispel --noqueue --nosustain "${TRIGGER[0]}"
+ dispel --noqueue --nosustain "$subject"
;;
check_self)
- cleanse --nofix_quick "${TRIGGER[0]}" ||
- cast -c "${TRIGGER[0]}"
+ cleanse --nofix_quick "$subject" ||
+ cast -c "$subject"
;;
run_script*)
- eval "${TRIGGER[3]}"
+ eval "$todo"
;;
- *) message "${PROBLEM_COLOR}${TRIGGER[3]} is not" \
+ *) message "${PROBLEM_COLOR}$todo is not" \
"a known kind of trigger.${DEFAULT_COLOR}"
esac
)
- [[SM-Commit] ] GIT changes to master sorcery by Ismael Luceno (8cbc23e7543eefd6d0e6d35bee2b1910a5e20426), Ismael Luceno, 08/15/2026
Archive powered by MHonArc 2.6.24.