source: BLFS/libs/func_dependencies@ dc7fd7b

ablfs-more legacy trunk
Last change on this file since dc7fd7b was 6c8095a, checked in by Pierre Labastie <pierre@…>, 7 years ago

Avoid infinite recursion in "tree-erase" if there is a circular dependency

  • Property mode set to 100644
File size: 12.6 KB
Line 
1#!/bin/bash
2#
3# $Id$
4#
5
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 #
10# provide a function for browsing it. But we need to be able to detect #
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# #
16# Layout of the tree: #
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# #
36# Circular dependencies: #
37# #
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. #
49#---------------------------------------------------------------------------#
50
51# Global variables:
52# A string of spaces for indenting:
53declare -a spaceSTR=" "
54# When we are backing up from a circular dependency, `exchange_triplet'
55# shall contain (parent dependency_0 dependency_n):
56declare -a exchange_triplet
57
58#----------------------------#
59generate_dependency_tree() { #
60#----------------------------#
61: <<inline_doc
62 function: Create a subtree of the dependency tree
63 (recursive function)
64 input vars: $1 : file with a list of targets (child nodes)
65 the first line of the file is the link
66 $2 : priority (1=req, 2=rec, 3=opt)
67 externals: vars: DEP_LEVEL contains 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.
71 MAIL_SERVER contains the name of the MTA we want to use.
72 files: ../xsl/dependencies.xsl: stylesheet for creating the
73 .dep files
74 ../packages.xml: File containing packages id
75 and dependencies
76 returns: 0 if the tree has been successfully created
77 1 if we are backing up to the parent of a circular dep
78 modifies: vars: exchange_triplet contains the triplet when return is 1
79 output: files: for each <pkg> with dependencies in $1,
80 a file <pkg>.dep and its dependencies
81 on error: nothing
82 on success: nothing
83inline_doc
84
85local DepFile=$1
86local priority=$2
87local -a rootlink
88local -a priolink
89local -a otherlink
90local -i depth
91local -i count=0
92local id_of_dep
93local parent
94local lines_to_remove=
95local srootlink
96local dep_level
97local priostring
98local dpriostring
99local i
100
101{
102# We use fd number 6 for input from DepFile, because we need 0 for user input
103read -u6 -a rootlink
104depth=${#rootlink[*]}
105read -u6 -a priolink
106dep_level=$DEP_LEVEL
107# For now, process optional deps only for the root packages.
108if (( $DEP_LEVEL > 2 )) && (( $depth > 1 )); then dep_level=2; fi
109srootlink="${rootlink[*]} "
110case $priority in
111 1) priostring=required ;;
112 2) priostring=recommended ;;
113 3) priostring=optional ;;
114 4) priostring=external ;;
115esac
116# start of DepFile
117echo -en "\nNode: $depth${spaceSTR:0:$depth}${RED}$DepFile${OFF} $priostring"
118
119while read -u6 prio_of_dep id_of_dep; do
120case $prio_of_dep in
121 1) dpriostring=required ;;
122 2) dpriostring=recommended ;;
123 3) dpriostring=optional ;;
124 4) dpriostring=external ;;
125esac
126# count entries in file
127 (( count++ ))
128# Has this entry already been seen?
129 if [ -f ${id_of_dep}.dep ]; then # found ${id_of_dep}.dep already in tree
130 otherlink=($(head -n 1 ${id_of_dep}.dep))
131 if [ -z "${otherlink[*]}" ]
132 then echo otherlink empty for $id_of_dep.dep
133 echo This should not happen, but happens to happen...
134 exit 1
135 fi
136# Do not use "${rootlink[*]}" =~ "${otherlink[*]}": case rootlink=(1 11)
137# and otherlink=(1 1)
138 if [[ ${srootlink#"${otherlink[*]} "} != ${srootlink} ]]; then # cir. dep
139 echo -en "\nCirc: $((depth+1))${spaceSTR:0:$((depth+1))}${YELLOW}${id_of_dep}${OFF} $dpriostring"
140# First look for the other parent of this dependency.
141# The parent has the same link without the last entry.
142# We do not need otherlink anymore so just destroy the last element
143 unset otherlink[-1]
144 parent=$(grep ^"${otherlink[*]}"\$ -l *)
145 parent=${parent%.dep}
146# Find lowest priority in branch from parent to DepFile:
147 p2=0
148 for (( i=${#otherlink[*]}; i < $depth ; i++ )) ; do
149 if (( ${priolink[i]} > $p2 )); then p2=${priolink[i]}; fi
150 done
151 if (( $prio_of_dep >= $p2 )); then # prune
152 lines_to_remove="$lines_to_remove $id_of_dep"
153 sed -i "/$id_of_dep/d" ${DepFile/.dep/.odep}
154 else #backup with prio priority
155 exchange_triplet=($parent $id_of_dep ${DepFile%.dep})
156 return $priority
157 fi
158 else # not circular: prune tree (but not .odep, since it may happen that
159 # the tree is destroyed and rebuilt in another order)
160 lines_to_remove="$lines_to_remove $id_of_dep"
161 fi # circular or not
162 continue # this dependency has already been seen, and the associated
163 # subtree computed. We are done
164 fi # Has this entry already been seen?
165# So, this entry has not already been seen.
166# If this is an external dep, just display it and go to next dep:
167 if [ "$prio_of_dep" -eq 4 ]; then
168 echo "${rootlink[*]} $count" > ${id_of_dep}.dep
169 echo -en "\nLeaf: $(($depth+1))${spaceSTR:0:$(($depth+1))}${CYAN}${id_of_dep}${OFF} $dpriostring"
170 continue
171 fi
172# Otherwise, let's build the corresponding
173# subtree. Since decisions about circular deps can lead us to start again
174# dependencies, we restart until the flag is false.
175 flag=true
176 while [ $flag = true ]; do
177 flag=false
178 if [ ! -f "${id_of_dep}.odep" ]; then
179 xsltproc --stringparam dependencies ${dep_level} \
180 --stringparam idofdep $id_of_dep \
181 --stringparam MTA $MAIL_SERVER \
182 -o ${id_of_dep}.odep \
183 ../xsl/dependencies.xsl ../packages.xml
184 fi
185
186# Use -s, because it may happen that after removing lines, .odep exists
187# but is empty.
188 if [[ -s ${id_of_dep}.odep ]]; then # this dependency has dependencies
189 sed "1i${rootlink[*]} $count\\
190${priolink[*]} $prio_of_dep" < ${id_of_dep}.odep \
191 > ${id_of_dep}.dep # add link and priolink
192 generate_dependency_tree ${id_of_dep}.dep $prio_of_dep
193# Test return value, in case we exchange dependencies
194 p2=$?
195 case $p2 in
196 0) # Normal return
197 ;;
198 [123]) # We are backing up to parent
199 if [[ ${exchange_triplet} == ${DepFile%.dep} ]] ; then
200# We are the parent!
201# First, we have to find the parent of our new direct dep, and remove
202# the new direct dep from the parent.odep:
203 otherlink=($(head -n1 ${exchange_triplet[2]}.dep))
204 unset otherlink[-1]
205 parent=$(grep -l ^"${otherlink[*]}"\$ *.dep)
206 sed -i /[[:digit:]]\ ${exchange_triplet[2]}\$/d ${parent/.dep/.odep}
207 tree_erase ${id_of_dep}.dep
208# We want that our direct dep be ${exchange_triplet[2]} and that id_of_dep
209# be pulled in as an indirect dep, so exchange.
210# Just doing a sed -i "s@${id_of_dep}@${exchange_triplet[2]}@" $DepFile
211# is not good if $DepFile contains several times the same line
212# so first find the first line and then sed
213 lineno=$(sed -n /${id_of_dep}/= $DepFile | head -n1)
214 sed -i "${lineno}s@${id_of_dep}@${exchange_triplet[2]}@" $DepFile
215# Do the same for the odep file:
216 local OdepFile=${DepFile/.dep/.odep}
217 lineno=$(sed -n /${id_of_dep}/= $OdepFile | head -n1)
218 sed -i "${lineno}s@${id_of_dep}@${exchange_triplet[2]}@" $OdepFile
219 id_of_dep=${exchange_triplet[2]}
220 flag=true # we have to regenerate the tree for the new dependency
221 else
222# We are not the parent. If our priority is greater than p2
223# we have to change the triplet and return priority, else return current p2.
224# echo (DEBUG) backing up to ${exchange_triplet} at ${DepFile%.dep}
225 if (( $priority > $p2 )); then
226 exchange_triplet[2]=${DepFile%.dep}
227 return $priority
228 else
229 return $p2
230 fi
231 fi
232 ;;
233 esac
234 else # id_of_dep has no dependencies, just record the link in a file
235 # and print
236 echo "${rootlink[*]} $count" > ${id_of_dep}.dep
237 echo -en "\nLeaf: $(($depth+1))${spaceSTR:0:$(($depth+1))}${CYAN}${id_of_dep}${OFF} $dpriostring"
238 fi
239 done
240done
241echo -en "\n End: $depth${spaceSTR:0:$depth}${GREEN}$DepFile${OFF}"
242} 6<$DepFile
243# It may happen that a file is created with several times
244# the same line. Normally, all those lines but one
245# would be flagged to be removed (or all of them if
246# the dependency appeared before). A simple sed /$line/d
247# destroys all the lines. We should instead remove
248# only one for each appearance of it in lines_to_remove.
249# so first get the position of last line and then delete
250# that line
251for line in $lines_to_remove
252 do lineno=$(sed -n /^[[:digit:]]\ $line\$/= $DepFile | tail -n1)
253 sed -i ${lineno}d $DepFile
254done
255return 0
256}
257
258#---------------#
259tree_browse() { #
260#---------------#
261local file=$1
262local f
263
264#echo file=$file
265for f in $(grep '[^0-9 ]' $file | sed 's/.* //'); do
266# echo f=$f
267 if grep -q '[^0-9 ]' ${f}.dep ; then
268 tree_browse ${f}.dep
269 fi
270 echo $f
271done
272}
273
274#--------------#
275tree_erase() { #
276#--------------#
277local file=$1
278local f
279local rootlink
280local rootlink2
281
282#echo file=$file
283rootlink="$(head -n1 $file) "
284for f in $(grep '[^0-9 ]' $file | sed 's/.* //'); do
285 if [ -f ${f}.dep ]; then
286 rootlink2="$(head -n1 ${f}.dep) "
287# We want two things:
288# i) do not erase the file if it is in another branch
289# ii) do not erase the file if there is a circular dependency
290# for case i), we test that rootlink is contained in rootlink2
291# for case ii), we test that rootlink2 is not contained in
292# rootlink.
293# See comment above about srootlink
294 if [[ ${rootlink2#${rootlink}} != ${rootlink2} &&
295 ${rootlink#${rootlink2}} == ${rootlink} ]] ; then
296 tree_erase ${f}.dep
297 fi
298 fi
299done
300rm -f $file
301}
Note: See TracBrowser for help on using the repository browser.