#!/bin/bash # # $Id$ # #---------------------------------------------------------------------------# # This is a set of (recursive) functions for manipulating a dependency # # tree. Everything would be "simple" without circular dependencies. We # # would just have to build the tree using the packages.xml file, and to # # provide a function for browsing it. But we need to be able to detect # # circular dependencies and to possibly change the tree depending on the # # user # decision. This is why we keep with each node a record of the path # # from the root to the node, which we call *link*. # # Layout of the tree: # # Each node is a file .dep, which contains the names of the # # child nodes, one per line, except the first line is the *link*: # # the link is a series of numbers (n1 n2 ... nN), describing the path from # # the root to : n1 is the position of the first node of the path # # in root.dep, n2 is the position of the second node of the path in # # .dep and so on. The link is not needed for normal tree operations # # (building a subtree or browsing the tree), but it allows to # # check whether a dependency is circular, and to find its parent. # # Circular dependencies: # # In case we find a cirdular dependency, it has the form : # # parent->dependency_0->...->dependency_n->dependency_0 # # If we want to build dependency_n before dependency_0, no problem: # # we just prune the tree at dependency_n. If we want to build first # # dependency_0, we need to put dependency_n as a dependency of parent, # # then erase and rebuild the subtree from there. Now, we may have met # # another circular dependency in the subtree, and erasing the tree makes # # us forget the decision which was made. So, after first generating the # # list of dependencies from packages.xml, we keep the generated list in # # a file .odep, which we modify according to the decision which # # was made. # #---------------------------------------------------------------------------# # Global variables: # A string of spaces for indenting: declare -a spaceSTR=" " declare -a exchange_triplet # When we are backing up from a circular dependency, `exchange_triplet' # shall contain (parent dependency_0 dependency_n) #----------------------------# generate_dependency_tree() { # #----------------------------# : < with dependencies in $1, a file .dep and its dependencies on error: nothing on success: nothing inline_doc local DepFile=$1 local -a rootlink local -a otherlink local -i depth local -i count=0 local id_of_dep local parent local lines_to_remove= local srootlink local dep_level { # We use fd number 6 for input from DepFile, because we need 0 for user input read -u6 -a rootlink depth=${#rootlink[*]} dep_level=$DEP_LEVEL # For now, process optional deps only for the root packages. if (( $DEP_LEVEL > 2 )) && (( $depth > 1 )); then dep_level=2; fi srootlink="${rootlink[*]} " # start of DepFile echo -en "\nNode: $depth${spaceSTR:0:$depth}${RED}$DepFile${OFF}" while read -u6 id_of_dep; do # count entries in file (( count++ )) # Has this entry already been seen? if [ -f ${id_of_dep}.dep ]; then # found ${id_of_dep}.dep already in tree otherlink=($(head -n 1 ${id_of_dep}.dep)) if [ -z "${otherlink[*]}" ] then echo otherlink empty for $id_of_dep.dep echo This should not happen, but happens to happen... exit 1 fi # Do not use "${rootlink[*]}" =~ "${otherlink[*]}": case rootlink=(1 11) # and otherlink=(1 1) if [[ ${srootlink#"${otherlink[*]} "} != ${srootlink} ]]; then # cir. dep # First look for the other parent of this dependency. # The parent has the same link without the last entry. # We do not need otherlink anymore so just destroy the last element unset otherlink[${#otherlink[*]}-1] parent=$(grep ^"${otherlink[*]}"\$ -l *) parent=${parent%.dep} echo -en "\nCirc: $depth${spaceSTR:0:$depth}${BLUE}Circular dependency detected:${OFF}" echo -en "\nCirc: $depth${spaceSTR:0:$depth}${BOLD}${id_of_dep}${OFF} is a dependency \ of ${BOLD}${parent}${OFF}" echo -en "\nCirc: $depth${spaceSTR:0:$depth}${BOLD}${DepFile%.dep}${OFF} is a dependency \ of ${BOLD}${id_of_dep}${OFF}" echo -en "\nCirc: $depth${spaceSTR:0:$depth}${BOLD}${id_of_dep}${OFF} is a dependency \ of ${BOLD}${DepFile%.dep}${OFF}" # we propose exchange always echo -en "\nCirc: $depth${spaceSTR:0:$depth}Do you want to build ${id_of_dep} first? yes/no (no):" read ANSWER if [ x$ANSWER = "xyes" ] ; then # exchange: # set triplet and return 1 exchange_triplet=($parent $id_of_dep ${DepFile%.dep}) return 1 else # no exchange: prune and remove the line(s) from odep... lines_to_remove="$lines_to_remove $id_of_dep" sed -i "/$id_of_dep/d" ${DepFile/.dep/.odep} fi else # not circular: prune lines_to_remove="$lines_to_remove $id_of_dep" fi # circular or not continue # this dependency has already been seen, and the associated # subtree computed. We are done fi # Has this entry already been seen? # So, this entry has not already been seen. Let's build the corresponding # subtree. Since decisions about circular deps can lead us to start again # dependencies, we restart until the flag is false. flag=true while [ $flag = true ]; do flag=false if [ ! -f "${id_of_dep}.odep" ]; then xsltproc --stringparam dependencies ${dep_level} \ --stringparam idofdep $id_of_dep \ -o ${id_of_dep}.odep \ ../xsl/dependencies.xsl ../packages.xml fi # Use -s, because it may happen that after removing lines, .odep exists # but is empty. if [[ -s ${id_of_dep}.odep ]]; then # this dependency has dependencies sed "1i${rootlink[*]} $count" < ${id_of_dep}.odep \ > ${id_of_dep}.dep # add the link generate_dependency_tree ${id_of_dep}.dep # Test return value, in case we exchange dependencies case $? in 0) # Normal return ;; 1) # We are backing up to parent if [[ ${exchange_triplet} == ${DepFile%.dep} ]] # we are the parent then tree_erase ${id_of_dep}.dep # We want that our direct dep be ${exchange_triplet[2]} and that id_of_dep # be pulled in as an indirect dep, so exchange. # Just doing a sed -i "s@${id_of_dep}@${exchange_triplet[2]}@" $DepFile # is not good if $DepFile contains several times the same line # so first find the first line and then sed lineno=$(sed -n /${id_of_dep}/= $DepFile | head -n1) sed -i "${lineno}s@${id_of_dep}@${exchange_triplet[2]}@" $DepFile # Do the same for the odep file: local OdepFile=${DepFile/.dep/.odep} lineno=$(sed -n /${id_of_dep}/= $OdepFile | head -n1) sed -i "${lineno}s@${id_of_dep}@${exchange_triplet[2]}@" $OdepFile id_of_dep=${exchange_triplet[2]} flag=true # we have to regenerate the tree for the new dependency else # we are not the parent. let's just back up one step # echo backing up to ${exchange_triplet} at ${DepFile%.dep} return 1 fi ;; esac else # id_of_dep has no dependencies, just record the link in a file # and print echo "${rootlink[*]} $count" > ${id_of_dep}.dep echo -en "\nLeaf: $(($depth+1))${spaceSTR:0:$(($depth+1))}${CYAN}${id_of_dep}${OFF}" fi done done echo -en "\n End: $depth${spaceSTR:0:$depth}${GREEN}$DepFile${OFF}" } 6<$DepFile # It may happen that a file is created with several times # the same line. Normally, all those lines but one # would be flagged to be removed (or all of them if # the dependency appeared before). A simple sed /$line/d # destroys all the lines. We should instead remove # only one for each appearance of it in lines_to_remove. # so first get the position of last line and then delete # that line for line in $lines_to_remove do lineno=$(sed -n /^$line\$/= $DepFile | tail -n1) sed -i ${lineno}d $DepFile done return 0 } #---------------# tree_browse() { # #---------------# local file=$1 local f #echo file=$file for f in $(grep '[^0-9 ]' $file); do # echo f=$f if grep -q '[^0-9 ]' ${f}.dep ; then tree_browse ${f}.dep fi echo $f done } #--------------# tree_erase() { # #--------------# local file=$1 local f local -a rootlink local -a rootlink2 #echo file=$file rootlink=($(head -n1 $file)) for f in $(grep '[^0-9 ]' $file); do # echo " f"=$f if [ -f ${f}.dep ]; then rootlink2=($(head -n1 ${f}.dep)) if [[ "${rootlink2[*]}" =~ "${rootlink[*]}" ]] ; then tree_erase ${f}.dep fi fi done rm -f $file }