source: BLFS/libs/func_dependencies@ 7dc8595

2.4 ablfs-more legacy new_features trunk
Last change on this file since 7dc8595 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
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 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.
71 returns: 0 if the tree has been successfully created
72 1 if we are backing up to the parent of a circular dep
73 modifies: vars: exchange_triplet contains the triplet when return is 1
74 output: files: for each <pkg> with dependencies in $1,
75 a file <pkg>.dep and its dependencies
76 on error: nothing
77 on success: nothing
78inline_doc
79
80local DepFile=$1
81local priority=$2
82local -a rootlink
83local -a priolink
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
92local priostring
93local dpriostring
94local i
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[*]}
100read -u6 -a priolink
101dep_level=$DEP_LEVEL
102# For now, process optional deps only for the root packages.
103if (( $DEP_LEVEL > 2 )) && (( $depth > 1 )); then dep_level=2; fi
104srootlink="${rootlink[*]} "
105case $priority in
106 1) priostring=required ;;
107 2) priostring=recommended ;;
108 3) priostring=optional ;;
109esac
110# start of DepFile
111echo -en "\nNode: $depth${spaceSTR:0:$depth}${RED}$DepFile${OFF} $priostring"
112
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
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)
131 if [[ ${srootlink#"${otherlink[*]} "} != ${srootlink} ]]; then # cir. dep
132 echo -en "\nCirc: $((depth+1))${spaceSTR:0:$((depth+1))}${YELLOW}${id_of_dep}${OFF} $dpriostring"
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
136 unset otherlink[-1]
137 parent=$(grep ^"${otherlink[*]}"\$ -l *)
138 parent=${parent%.dep}
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
145 lines_to_remove="$lines_to_remove $id_of_dep"
146 sed -i "/$id_of_dep/d" ${DepFile/.dep/.odep}
147 else #backup with prio priority
148 exchange_triplet=($parent $id_of_dep ${DepFile%.dep})
149 return $priority
150 fi
151 else # not circular: prune tree (but not .odep, since it may happen that
152 # the tree is destroyed and rebuilt in another order
153 lines_to_remove="$lines_to_remove $id_of_dep"
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.
161 flag=true
162 while [ $flag = true ]; do
163 flag=false
164 if [ ! -f "${id_of_dep}.odep" ]; then
165 xsltproc --stringparam dependencies ${dep_level} \
166 --stringparam idofdep $id_of_dep \
167 -o ${id_of_dep}.odep \
168 ../xsl/dependencies.xsl ../packages.xml
169 fi
170
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
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
178# Test return value, in case we exchange dependencies
179 p2=$?
180 case $p2 in
181 0) # Normal return
182 ;;
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
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.
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
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
204 id_of_dep=${exchange_triplet[2]}
205 flag=true # we have to regenerate the tree for the new dependency
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
216 fi
217 ;;
218 esac
219 else # id_of_dep has no dependencies, just record the link in a file
220 # and print
221 echo "${rootlink[*]} $count" > ${id_of_dep}.dep
222 echo -en "\nLeaf: $(($depth+1))${spaceSTR:0:$(($depth+1))}${CYAN}${id_of_dep}${OFF} $dpriostring"
223 fi
224 done
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
231# the dependency appeared before). A simple sed /$line/d
232# destroys all the lines. We should instead remove
233# only one for each appearance of it in lines_to_remove.
234# so first get the position of last line and then delete
235# that line
236for line in $lines_to_remove
237 do lineno=$(sed -n /^[[:digit:]]\ $line\$/= $DepFile | tail -n1)
238 sed -i ${lineno}d $DepFile
239done
240return 0
241}
242
243#---------------#
244tree_browse() { #
245#---------------#
246local file=$1
247local f
248
249#echo file=$file
250for f in $(grep '[^0-9 ]' $file | sed 's/.* //'); do
251# echo f=$f
252 if grep -q '[^0-9 ]' ${f}.dep ; then
253 tree_browse ${f}.dep
254 fi
255 echo $f
256done
257}
258
259#--------------#
260tree_erase() { #
261#--------------#
262local file=$1
263local f
264local -a rootlink
265local -a rootlink2
266
267#echo file=$file
268rootlink=($(head -n1 $file))
269for f in $(grep '[^0-9 ]' $file | sed 's/.* //'); do
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
279}
Note: See TracBrowser for help on using the repository browser.