source: BLFS/libs/func_dependencies@ cf8e060

2.4 ablfs-more legacy new_features trunk
Last change on this file since cf8e060 was 7dc8595, checked in by Pierre Labastie <pierre@…>, 9 years ago

Automate the process of choosing action when there are circular dependencies.
This has to be tested, but hopefully, it should allow to find a coherent
build order when there are not too many circular dependencies

  • Property mode set to 100644
File size: 11.5 KB
RevLine 
[3c10176]1#!/bin/bash
2#
3# $Id$
4#
5
[e4b1293]6#---------------------------------------------------------------------------#
7# This is a set of (recursive) functions for manipulating a dependency #
8# tree. Everything would be "simple" without circular dependencies. We #
9# would just have to build the tree using the packages.xml file, and to #
[7f9fa78]10# provide a function for browsing it. But we need to be able to detect #
[7dc8595]11# circular dependencies and to possibly change the tree depending on #
12# priorities. This is why we keep with each node a record of the path #
13# from the root to the node, which we call *link* and a record of the #
14# successive priorities which we call *priolink*. #
15# #
[e4b1293]16# Layout of the tree: #
[7dc8595]17# #
18# A node of the tree is represented by a file <nodeName>.dep. We keep all #
19# those files in the same directory. The root node file is "root.dep". #
20# Files <nodeName>.dep have the following layout: #
21# - the first line is the link: the link is an array of numbers #
22# (n1 n2 ... nN), describing the path from the root to <nodeName>: n1 #
23# is the position of the first node of the path in root.dep, n2 is the #
24# position of the second node of the path in <node1>.dep and so on. The #
25# link is not needed for normal tree operations (building a subtree or #
26# browsing the tree), but it allows to check whether a dependency is #
27# circular, and to find its parent. #
28# - the second line is an array of priorities (p1 p2 ... pN), giving the #
29# priority (1=required, 2=recommended, 3=optional) of each dependency #
30# in the link. #
31# - each subsequent line is of the form "p <depname>", where p is the #
32# priority as above, and <depname> is the name of the dependency. The #
33# position which is recorded in the link is the number of the line #
34# minus 2. #
35# #
[d7c07e0]36# Circular dependencies: #
[7dc8595]37# #
[d7c07e0]38# In case we find a cirdular dependency, it has the form : #
39# parent->dependency_0->...->dependency_n->dependency_0 #
40# If we want to build dependency_n before dependency_0, no problem: #
41# we just prune the tree at dependency_n. If we want to build first #
42# dependency_0, we need to put dependency_n as a dependency of parent, #
43# then erase and rebuild the subtree from there. Now, we may have met #
44# another circular dependency in the subtree, and erasing the tree makes #
45# us forget the decision which was made. So, after first generating the #
46# list of dependencies from packages.xml, we keep the generated list in #
47# a file <nodeName>.odep, which we modify according to the decision which #
48# was made. #
[e4b1293]49#---------------------------------------------------------------------------#
50
51# Global variables:
[e576789]52# A string of spaces for indenting:
[3c10176]53declare -a spaceSTR=" "
[d7c07e0]54# When we are backing up from a circular dependency, `exchange_triplet'
[7dc8595]55# shall contain (parent dependency_0 dependency_n):
56declare -a exchange_triplet
[3c10176]57
58#----------------------------#
59generate_dependency_tree() { #
60#----------------------------#
61: <<inline_doc
[e576789]62 function: Create a subtree of the dependency tree
63 (recursive function)
[7f9fa78]64 input vars: $1 : file with a list of targets (child nodes)
[e4b1293]65 the first line of the file is the link
[7dc8595]66 $2 : priority (1=req, 2=rec, 3=opt)
67 externals: vars: DEP_LEVEL contains the 1 if we want to build the
68 tree only for required dependencies,
69 2 if we want also recommended ones,
70 and 3 if we want optional ones too.
[e4b1293]71 returns: 0 if the tree has been successfully created
72 1 if we are backing up to the parent of a circular dep
[7f9fa78]73 modifies: vars: exchange_triplet contains the triplet when return is 1
[e4b1293]74 output: files: for each <pkg> with dependencies in $1,
75 a file <pkg>.dep and its dependencies
[3c10176]76 on error: nothing
77 on success: nothing
78inline_doc
79
[e576789]80local DepFile=$1
[7dc8595]81local priority=$2
[e576789]82local -a rootlink
[7dc8595]83local -a priolink
[e576789]84local -a otherlink
85local -i depth
86local -i count=0
87local id_of_dep
88local parent
89local lines_to_remove=
90local srootlink
91local dep_level
[7dc8595]92local priostring
93local dpriostring
94local i
[e576789]95
96{
97# We use fd number 6 for input from DepFile, because we need 0 for user input
98read -u6 -a rootlink
99depth=${#rootlink[*]}
[7dc8595]100read -u6 -a priolink
[e576789]101dep_level=$DEP_LEVEL
[1670a20]102# For now, process optional deps only for the root packages.
[e576789]103if (( $DEP_LEVEL > 2 )) && (( $depth > 1 )); then dep_level=2; fi
104srootlink="${rootlink[*]} "
[7dc8595]105case $priority in
106 1) priostring=required ;;
107 2) priostring=recommended ;;
108 3) priostring=optional ;;
109esac
[d7c07e0]110# start of DepFile
[7dc8595]111echo -en "\nNode: $depth${spaceSTR:0:$depth}${RED}$DepFile${OFF} $priostring"
[e576789]112
[7dc8595]113while read -u6 prio_of_dep id_of_dep; do
114case $prio_of_dep in
115 1) dpriostring=required ;;
116 2) dpriostring=recommended ;;
117 3) dpriostring=optional ;;
118esac
[e576789]119# count entries in file
120 (( count++ ))
121# Has this entry already been seen?
122 if [ -f ${id_of_dep}.dep ]; then # found ${id_of_dep}.dep already in tree
123 otherlink=($(head -n 1 ${id_of_dep}.dep))
124 if [ -z "${otherlink[*]}" ]
125 then echo otherlink empty for $id_of_dep.dep
126 echo This should not happen, but happens to happen...
127 exit 1
128 fi
129# Do not use "${rootlink[*]}" =~ "${otherlink[*]}": case rootlink=(1 11)
130# and otherlink=(1 1)
[e4b1293]131 if [[ ${srootlink#"${otherlink[*]} "} != ${srootlink} ]]; then # cir. dep
[7dc8595]132 echo -en "\nCirc: $((depth+1))${spaceSTR:0:$((depth+1))}${YELLOW}${id_of_dep}${OFF} $dpriostring"
[e576789]133# First look for the other parent of this dependency.
134# The parent has the same link without the last entry.
135# We do not need otherlink anymore so just destroy the last element
[7dc8595]136 unset otherlink[-1]
[e576789]137 parent=$(grep ^"${otherlink[*]}"\$ -l *)
138 parent=${parent%.dep}
[7dc8595]139# Find lowest priority in branch from parent to DepFile:
140 p2=0
141 for (( i=${#otherlink[*]}; i < $depth ; i++ )) ; do
142 if (( ${priolink[i]} > $p2 )); then p2=${priolink[i]}; fi
143 done
144 if (( $prio_of_dep >= $p2 )); then # prune
[e4b1293]145 lines_to_remove="$lines_to_remove $id_of_dep"
[d7c07e0]146 sed -i "/$id_of_dep/d" ${DepFile/.dep/.odep}
[7dc8595]147 else #backup with prio priority
148 exchange_triplet=($parent $id_of_dep ${DepFile%.dep})
149 return $priority
[e4b1293]150 fi
[7dc8595]151 else # not circular: prune tree (but not .odep, since it may happen that
152 # the tree is destroyed and rebuilt in another order
[e576789]153 lines_to_remove="$lines_to_remove $id_of_dep"
[e4b1293]154 fi # circular or not
155 continue # this dependency has already been seen, and the associated
156 # subtree computed. We are done
157 fi # Has this entry already been seen?
158# So, this entry has not already been seen. Let's build the corresponding
159# subtree. Since decisions about circular deps can lead us to start again
160# dependencies, we restart until the flag is false.
[e576789]161 flag=true
162 while [ $flag = true ]; do
163 flag=false
[d7c07e0]164 if [ ! -f "${id_of_dep}.odep" ]; then
165 xsltproc --stringparam dependencies ${dep_level} \
[e576789]166 --stringparam idofdep $id_of_dep \
[d7c07e0]167 -o ${id_of_dep}.odep \
[e576789]168 ../xsl/dependencies.xsl ../packages.xml
[d7c07e0]169 fi
[e576789]170
[d7c07e0]171# Use -s, because it may happen that after removing lines, .odep exists
172# but is empty.
173 if [[ -s ${id_of_dep}.odep ]]; then # this dependency has dependencies
[7dc8595]174 sed "1i${rootlink[*]} $count\\
175${priolink[*]} $prio_of_dep" < ${id_of_dep}.odep \
176 > ${id_of_dep}.dep # add link and priolink
177 generate_dependency_tree ${id_of_dep}.dep $prio_of_dep
[e576789]178# Test return value, in case we exchange dependencies
[7dc8595]179 p2=$?
180 case $p2 in
[e576789]181 0) # Normal return
182 ;;
[7dc8595]183 [123]) # We are backing up to parent
184 if [[ ${exchange_triplet} == ${DepFile%.dep} ]] ; then
185# We are the parent!
186# First, we have to find the parent of our new direct dep, and remove
187# the new direct dep from the parent.odep:
188 otherlink=($(head -n1 ${exchange_triplet[2]}.dep))
189 unset otherlink[-1]
190 parent=$(grep -l ^"${otherlink[*]}"\$ *.dep)
191 sed -i /[[:digit:]]\ ${exchange_triplet[2]}\$/d ${parent/.dep/.odep}
192 tree_erase ${id_of_dep}.dep
[e4b1293]193# We want that our direct dep be ${exchange_triplet[2]} and that id_of_dep
194# be pulled in as an indirect dep, so exchange.
[e576789]195# Just doing a sed -i "s@${id_of_dep}@${exchange_triplet[2]}@" $DepFile
196# is not good if $DepFile contains several times the same line
197# so first find the first line and then sed
198 lineno=$(sed -n /${id_of_dep}/= $DepFile | head -n1)
199 sed -i "${lineno}s@${id_of_dep}@${exchange_triplet[2]}@" $DepFile
[d7c07e0]200# Do the same for the odep file:
201 local OdepFile=${DepFile/.dep/.odep}
202 lineno=$(sed -n /${id_of_dep}/= $OdepFile | head -n1)
203 sed -i "${lineno}s@${id_of_dep}@${exchange_triplet[2]}@" $OdepFile
[e576789]204 id_of_dep=${exchange_triplet[2]}
[e4b1293]205 flag=true # we have to regenerate the tree for the new dependency
[7dc8595]206 else
207# We are not the parent. If our priority is greater than p2
208# we have to change the triplet and return priority, else return current p2.
209# echo (DEBUG) backing up to ${exchange_triplet} at ${DepFile%.dep}
210 if (( $priority > $p2 )); then
211 exchange_triplet[2]=${DepFile%.dep}
212 return $priority
213 else
214 return $p2
215 fi
[e576789]216 fi
217 ;;
[3c10176]218 esac
[e4b1293]219 else # id_of_dep has no dependencies, just record the link in a file
220 # and print
[e576789]221 echo "${rootlink[*]} $count" > ${id_of_dep}.dep
[7dc8595]222 echo -en "\nLeaf: $(($depth+1))${spaceSTR:0:$(($depth+1))}${CYAN}${id_of_dep}${OFF} $dpriostring"
[3c10176]223 fi
224 done
[e576789]225done
226echo -en "\n End: $depth${spaceSTR:0:$depth}${GREEN}$DepFile${OFF}"
227} 6<$DepFile
228# It may happen that a file is created with several times
229# the same line. Normally, all those lines but one
230# would be flagged to be removed (or all of them if
[e4b1293]231# the dependency appeared before). A simple sed /$line/d
[e576789]232# destroys all the lines. We should instead remove
233# only one for each appearance of it in lines_to_remove.
[e4b1293]234# so first get the position of last line and then delete
[e576789]235# that line
236for line in $lines_to_remove
[7dc8595]237 do lineno=$(sed -n /^[[:digit:]]\ $line\$/= $DepFile | tail -n1)
[e576789]238 sed -i ${lineno}d $DepFile
239done
240return 0
241}
[3c10176]242
[e576789]243#---------------#
244tree_browse() { #
245#---------------#
246local file=$1
247local f
248
249#echo file=$file
[7dc8595]250for f in $(grep '[^0-9 ]' $file | sed 's/.* //'); do
[e576789]251# echo f=$f
252 if grep -q '[^0-9 ]' ${f}.dep ; then
253 tree_browse ${f}.dep
[3c10176]254 fi
[e576789]255 echo $f
256done
257}
[3c10176]258
[e576789]259#--------------#
260tree_erase() { #
261#--------------#
262local file=$1
263local f
264local -a rootlink
265local -a rootlink2
266
267#echo file=$file
268rootlink=($(head -n1 $file))
[7dc8595]269for f in $(grep '[^0-9 ]' $file | sed 's/.* //'); do
[e576789]270# echo " f"=$f
271 if [ -f ${f}.dep ]; then
272 rootlink2=($(head -n1 ${f}.dep))
273 if [[ "${rootlink2[*]}" =~ "${rootlink[*]}" ]] ; then
274 tree_erase ${f}.dep
275 fi
276 fi
277done
278rm -f $file
[3c10176]279}
Note: See TracBrowser for help on using the repository browser.