source: BLFS/libs/func_dependencies@ e0bbc6e

ablfs-more legacy trunk
Last change on this file since e0bbc6e was e0bbc6e, checked in by Pierre Labastie <pierre.labastie@…>, 3 years ago

Document some variables in func_dependencies

Better document the $seen global variable used in path_to,
also at places where "path_to" is called.
Document p and b flags in the loop over "after" deps.
Document lr variable in the loop over "first" deps.

  • Property mode set to 100644
File size: 22.4 KB
RevLine 
[3c10176]1#!/bin/bash
2#
3
[2140f22]4#-----------------------------------------------------------------------------#
5# This is a set of (recursive) functions for manipulating a dependency graph. #
6# We use algorithms and definitions from chapter 4 (mainly section 4.2) of #
7# https://algs4.cs.princeton.edu/. The graph we manipulate is the directed #
8# graph of the dependencies: nodes are packages in the BLFS book. A node A is #
9# connected to a node B if package A depends on B. A topological order (rev- #
10# erted) is exactly what we want for a build order. But a topological order #
11# only exists if the graph is acyclic. We'll therefore have to remove cycles. #
12# There are a number of other features we want to consider: #
13# - edges are weighted according to the dependency requirement: #
14# 1 for required #
15# 2 for recommended #
16# 3 for optional #
17# 4 for external #
18# We should consider only edges with weight lower or equal to that #
19# specified by the user, but see below. #
20# - we do not want to build the whole book. The user specifies a set of #
21# packages, and we have to consider only nodes reachable from this set #
22# using edges of weight not exceeding the specified weight. #
23# - when doing the topological sort, we want to consider all the edges and #
24# not only those not exceeding the specified weight: If a package A in the #
25# reachable subgraph depends optionally on another package B in the same #
26# subgraph, we want to build B before A. But this means we'll have to #
27# remove cycles for all weights. #
28# - dependencies have another qualifier: before or after. The problem: if a #
29# package A depends on B with an "after" qualifier, and a package C depends #
30# on A with a "before" qualifier, C may need B to be able to use A. So the #
31# only safe way to consider "after" qualifiers is to consider that they are #
32# "before" deps for any parent of the packages considered. #
33# We'll therefore have a 3 pass procedure. First build the set of nodes #
34# reachable from the root set. Second, remove dangling edges (those pointing #
35# to packages outside the node set), and move "after" edges to "before" edges #
36# originating from the parents. Third remove cycles and generate a #
37# topological sort. #
38# #
[dd9ca56]39# Pass 1: graph generation #
40# ======================== #
41# Data layout for pass 1 #
42# ---------------------- #
[2140f22]43# A node of the tree is represented by a text file <nodeName>.dep. Each edge #
44# starting from this node is represented by a line in this file. We keep #
45# those files in the same directory. We introduce a special node named root, #
46# whose edges point to the list of nodes requested by the user. Each line #
47# contains three fields: #
48# - the weight of the edge #
[dd9ca56]49# - the qualifier: "before" (b), "after" (a), or "first" (f) #
50# - the name of the destination of the edge (without the ".dep" extension) #
51# #
52# Recursive function "generate_subgraph" #
53# -------------------------------------- #
54# This function treats a node of the graph that is not a leaf and that is #
55# seen for the first time in the DFS. The dependencies of this node are #
56# known, and stored in a .dep file. For each dependency in that file, there #
57# are three cases: #
58# - the weight of the edge leading to that dependency is higher than #
59# requested. This dependency is discarded (some information printed) #
60# - the weight of the edge is lower or equal to requested, but the node #
61# has already been visited (the .dep file exists). Discard too (some #
62# information printed) #
63# - the weight of the edge is lower or equal to requested, and the node #
64# has not been seen: then the dependencies of that node are generated, #
65# and there are two cases: #
66# - the node has no dependencies: just create an empty .dep file, so #
67# that we know the node has been visited #
68# - the node has dependencies: call generate_subgraph for that node #
69# #
70# This function takes four parameters: #
71# - The node filename: this is the only one useful for the algorithm #
72# - The depth: number of steps starting from root (for pretty print only) #
73# - The weight of the edge leading to that node (for printing) #
74# - The qualifier (for printing) #
75# #
76# TODO: document other passes #
77# TODO: needs also to document the .tree files #
78# TODO: The following is obsolete #
[d7c07e0]79# Circular dependencies: #
[7dc8595]80# #
[d7c07e0]81# In case we find a cirdular dependency, it has the form : #
82# parent->dependency_0->...->dependency_n->dependency_0 #
83# If we want to build dependency_n before dependency_0, no problem: #
84# we just prune the tree at dependency_n. If we want to build first #
85# dependency_0, we need to put dependency_n as a dependency of parent, #
86# then erase and rebuild the subtree from there. Now, we may have met #
87# another circular dependency in the subtree, and erasing the tree makes #
88# us forget the decision which was made. So, after first generating the #
89# list of dependencies from packages.xml, we keep the generated list in #
90# a file <nodeName>.odep, which we modify according to the decision which #
91# was made. #
[e4b1293]92#---------------------------------------------------------------------------#
93
94# Global variables:
[e576789]95# A string of spaces for indenting:
[3c10176]96declare -a spaceSTR=" "
[2140f22]97# When we are backing up from a circular dependency, `parentNode'
98# contains the node which has an edge entering the cycle
99declare parentNode
[3c10176]100
[2140f22]101#---------------------#
102generate_subgraph() { #
103#---------------------#
[3c10176]104: <<inline_doc
[2140f22]105 function: Create a subgraph of all the nodes reachable from the node
106 represented by the file whose name is $1. The edges considered
107 are those with maximal weight DEP_LEVEL (recursive function).
108 input vars: $1 : file name corresponding to the node whose edges will be
109 followed for the DFS
110 $2 : weight of the edge leading to this node
111 $3 : depth (root is 1)
[dd9ca56]112 $4 : qualifier (a for after, b for before, f for first)
[7ac11f0]113 externals: vars: DEP_LEVEL contains 1 if we want to build the
114 tree only for required dependencies,
115 2 if we want also recommended ones,
[2140f22]116 3 if we want also optional ones, but only
117 for the requested packages,
118 4 if we want all the dependencies
[dd9ca56]119 (excluding external of course)
[7ac11f0]120 MAIL_SERVER contains the name of the MTA we want to use.
121 files: ../xsl/dependencies.xsl: stylesheet for creating the
122 .dep files
[e91a15d]123 ../packages.xml: File containing packages id
[7ac11f0]124 and dependencies
[2140f22]125 returns: 0 if the tree has been successfully created
126 output: files: for each node reachable from $1, a file <node>.dep.
127 on error: nothing
128 on success: nothing
129inline_doc
130
131local depFile=$1
132local -i weight=$2
133local -i depth=$3
134local qualifier=$4
135local -i spacing=0
136local priostring
137local buildstring
138local id_of_dep
139local prio_of_dep
140local build_of_dep
141local dep_level
142
143if (( depth < 10 )); then spacing=1; fi
144case $weight in
145 1) priostring=required ;;
146 2) priostring=recommended ;;
147 3) priostring=optional ;;
148esac
149case $qualifier in
[dd9ca56]150 a) buildstring=runtime ;;
151 b|f) buildstring= ;;
[2140f22]152esac
153dep_level=$DEP_LEVEL
154if [ "$dep_level" = 3 ] && [ "$depth" -gt 2 ]; then dep_level=2; fi
155if [ "$dep_level" -gt 3 ]; then dep_level=3; fi
156echo -en "\nNode: $depth${spaceSTR:0:$(( depth + spacing ))}${RED}${depFile%.dep}${OFF} $priostring $buildstring"
157
158depth=$(( depth + 1 ))
159if (( depth < 10 )); then spacing=1; else spacing=0; fi
160# Start of loop
161{
162while read prio_of_dep build_of_dep id_of_dep; do
[e030049]163 case $prio_of_dep in
[2140f22]164 1) priostring=required ;;
165 2) priostring=recommended ;;
166 3) priostring=optional ;;
167 4) priostring=external ;;
[e030049]168 esac
169 case $build_of_dep in
[dd9ca56]170 a ) buildstring=runtime ;;
171 b|f) buildstring= ;;
[e030049]172 esac
173# Has this entry already been seen?
[dd9ca56]174# TODO: no there is no special case!
[e030049]175# We have a special case here: if the entry has been seen at depth > 2
176# and now depth=2 and DEP_LEVEL=3, optional deps have not been processed.
177# If this is the case, just consider it has not been seen.
[2140f22]178 if [ -f ${id_of_dep}.dep ] ; then
[e030049]179 case $depth$DEP_LEVEL in
180 23) ;;
181 *)
[2140f22]182# Just display it and proceed.
[e030049]183 echo -en "\nEdge: $depth${spaceSTR:0:$((depth + spacing))}${MAGENTA}${id_of_dep}${OFF} $priostring $buildstring"
184 continue
185 ;;
186 esac
[2140f22]187 fi
188# Is the weight higher than requested?
189 if [ "$prio_of_dep" -gt $dep_level ]; then
190# Just display it and proceed.
191 echo -en "\n Out: $depth${spaceSTR:0:$((depth + spacing))}${YELLOW}${id_of_dep}${OFF} $priostring $buildstring"
192 continue
193 fi
194# Otherwise, let's build the corresponding subgraph.
195 xsltproc --stringparam idofdep "$id_of_dep" \
196 --stringparam MTA "$MAIL_SERVER" \
197 -o ${id_of_dep}.dep \
198 ../xsl/dependencies.xsl ../packages.xml
199
200 if [[ -s ${id_of_dep}.dep ]]; then # this dependency has dependencies
201 generate_subgraph ${id_of_dep}.dep $prio_of_dep $depth $build_of_dep
202 else # id_of_dep has no dependencies, just touch the file and display
203 touch ${id_of_dep}.dep
204 echo -en "\nLeaf: $depth${spaceSTR:0:$((depth + spacing))}${CYAN}${id_of_dep}${OFF} $priostring $buildstring"
205 fi
206done
207} <$depFile
208depth=$(( depth - 1 ))
209if (( depth < 10 )); then spacing=1; else spacing=0; fi
210echo -en "\n End: $depth${spaceSTR:0:$((depth + spacing))}${GREEN}${depFile%.dep}${OFF}"
211return 0
212}
213
214#-----------#
215path_to() { #
216#-----------#
217: <<inline_doc
218 function: check whether there is a path from $1 to $2 on the graph
219 input vars: $1 contains the filename of the starting node.
220 $2 contains the name of the node to reach
221 $3 contains the weight above which we do not want to
222 follow an edge
[e0bbc6e]223 $seen (global) contains the list of already seen nodes.
224 It must ve set to " " prior to calling the function
[2140f22]225 returns: 0 if the node has been found
226 1 if not
227 on error: nothing
228 on success: nothing
229inline_doc
230local start=$1
231local seek=$2
232local prio=$3
233local prio_of_dep
234local build_of_dep
235local id_of_dep
236local r
237
238if test "${start%.dep}" = "$seek"; then return 0; fi
[59afe73]239seen="$seen${start%.dep} "
[5afaf7c]240if test -s $start; then
[2140f22]241 {
242 while read prio_of_dep build_of_dep id_of_dep; do
243 if test "$prio" -lt "$prio_of_dep"; then continue; fi
[5afaf7c]244 if ! test "${seen% $id_of_dep *}" = "$seen"; then continue; fi
[2140f22]245 if path_to ${id_of_dep}.dep $seek $prio; then return 0; fi
246 done
247 } < $start
248fi
249return 1
250}
251#------------------#
252clean_subgraph() { #
253#------------------#
254: <<inline_doc
255 function: Remove dangling edges and create groups of deps for "after"
256 deps: A-before->B-after->C becomes:
257 A -before-> Bgroupxx -before-> B
258 \
259 -before-> C
260 the name of the group is chosen so that it is unlikely as
261 a package name (so that it is removed when building the
262 xml book).
263 input vars: None
264 files: <node>.dep files containing dangling edges and
265 "after" qualifiers
266 returns: 0
267 output: files: <node>.dep files containing no dangling edges and
268 no "after" qualifiers
269 on error: nothing
270 on success: nothing
271inline_doc
272
273local node
274local id_of_dep
275local prio_of_dep
276local build_of_dep
277local lines_to_remove
278local lines_to_change
279local parent
280local p
281local b
282local start
283local seen
284
285for node in $(ls *.dep); do
286 if test $node = root.dep; then continue; fi
[28546ae]287 echo Cleaning $node
[2140f22]288 lines_to_remove=
289 {
290 while read prio_of_dep build_of_dep id_of_dep; do
291 if ! test -f ${id_of_dep}.dep; then
292 lines_to_remove="$lines_to_remove $id_of_dep"
293 continue
294 fi
295 done
296 } <$node
297 for id_of_dep in $lines_to_remove; do
298 sed "/\ $id_of_dep\$/d" -i $node
299 done
300done
301for node in $(grep -l ' a ' *.dep); do
302 lines_to_remove=
[28546ae]303 echo Process "runtime" deps in $node
[2140f22]304 if ! [ -e ${node%.dep}groupxx.dep ]; then
[e0bbc6e]305 b=0 # Nothing depends on <node>groupxx
[2140f22]306 for parent in $(grep -l ${node%.dep}\$ *); do
[e0bbc6e]307 p=0 # No "after" dependency depends on this parent
[2140f22]308 for start in $(grep ' a ' $node | cut -d' ' -f3); do
[e0bbc6e]309 seen=" " # global variable used in "path_to"
[2140f22]310 if path_to ${start}.dep ${parent%.dep} 3; then p=1; break; fi
311 done
312 if test $p = 0; then
[f1edc8e]313 b=1
[2140f22]314 sed -i "s/\ ${node%.dep}\$/&groupxx/" $parent
315 fi
316 done
317 echo "1 b ${node%.dep}" > ${node%.dep}groupxx.dep
[f1edc8e]318 if test $b = 0; then echo "1 b ${node%.dep}groupxx" >> root.dep; fi
[2140f22]319 fi
320 {
321 while read prio_of_dep build_of_dep id_of_dep; do
322 if test $build_of_dep = a; then
323 if ! grep -q ${id_of_dep} ${node%.dep}groupxx.dep; then
324 echo "$prio_of_dep b ${id_of_dep}" >> ${node%.dep}groupxx.dep
325 fi
326 lines_to_remove="$lines_to_remove $id_of_dep"
327 fi
328 done
329 } <$node
330 for id_of_dep in $lines_to_remove; do
331 sed "/\ $id_of_dep\$/d" -i $node
332 done
333done
334for node in $(grep -l ' f ' *); do
[28546ae]335 echo Process "first" deps in $node
[2140f22]336 lines_to_change=
337 {
338 while read prio_of_dep build_of_dep id_of_dep; do
339 if test $build_of_dep = f; then
340 if ! test -f ${id_of_dep}-pass1.dep; then
341 cp ${id_of_dep}{,-pass1}.dep;
342 fi
343 lines_to_change="$lines_to_change $id_of_dep"
[e0bbc6e]344 unset lr # lines to remove in -pass1
[2140f22]345 {
346 while read p b start; do
[e0bbc6e]347 seen=" " # global variable used in "path_to"
[2140f22]348 if path_to ${start}.dep ${node%.dep} $p; then
349 lr="$lr $start"
350 fi
351 done
352 } < ${id_of_dep}-pass1.dep
353 for p in $lr; do
354 sed "/\ $p\$/d" -i ${id_of_dep}-pass1.dep
355 done
356 fi
357 done
358 } <$node
359 for id_of_dep in $lines_to_change; do
[d80302b]360 sed "/\ $id_of_dep\$/"'{s/[[:digit:]] f /1 b /;s/$/-pass1/}' -i $node
[2140f22]361 done
362done
363} # End clean_subgraph
364
365#----------------------------#
366generate_dependency_tree() { #
367#----------------------------#
368: <<inline_doc
369 function: Create a subtree of the dependency tree
370 (recursive function)
371 input vars: $1 : file with a list of targets (child nodes)
372 the first line of the file is the link
373 $2 : priority (1=req, 2=rec, 3=opt)
[e4b1293]374 returns: 0 if the tree has been successfully created
375 1 if we are backing up to the parent of a circular dep
[2140f22]376 and there are only required deps in the cycle
377 2 if we are backing up to the parent of a circular dep
378 and there are recommended deps and no optional deps in the
379 cycle
380 3 if we are backing up to the parent of a circular dep
381 and there are optiional deps in the cycle
382 modifies: vars: ParentNode is set when return is not 0
[e4b1293]383 output: files: for each <pkg> with dependencies in $1,
[2140f22]384 a file <pkg>.tree and its dependencies
[3c10176]385 on error: nothing
386 on success: nothing
387inline_doc
388
[2140f22]389local depFile=$1
[7dc8595]390local priority=$2
[e576789]391local -a rootlink
[7dc8595]392local -a priolink
[e576789]393local -a otherlink
394local -i depth
395local -i count=0
396local id_of_dep
[2140f22]397local build_of_dep
398local prio_of_dep
[e576789]399local parent
400local lines_to_remove=
401local srootlink
[7dc8595]402local priostring
403local dpriostring
404local i
[e576789]405
406{
[2140f22]407read -a rootlink
[e576789]408depth=${#rootlink[*]}
[2140f22]409read -a priolink
[e576789]410srootlink="${rootlink[*]} "
[7dc8595]411case $priority in
412 1) priostring=required ;;
413 2) priostring=recommended ;;
414 3) priostring=optional ;;
415esac
[2140f22]416# start of depFile
417echo -en "\nNode: $depth${spaceSTR:0:$depth}${RED}${depFile%.tree}${OFF} $priostring"
[e576789]418
[2140f22]419while read prio_of_dep build_of_dep id_of_dep; do
420 case $prio_of_dep in
[7dc8595]421 1) dpriostring=required ;;
422 2) dpriostring=recommended ;;
423 3) dpriostring=optional ;;
[2140f22]424 esac
[e576789]425# count entries in file
426 (( count++ ))
427# Has this entry already been seen?
[2140f22]428 if [ -f ${id_of_dep}.tree ]; then # found ${id_of_dep}.tree already in tree
429 otherlink=($(head -n 1 ${id_of_dep}.tree))
[2e1c1c3]430 if [ -z "${otherlink[*]}" ]
[2140f22]431 then echo otherlink empty for $id_of_dep.tree
[e576789]432 echo This should not happen, but happens to happen...
433 exit 1
434 fi
[2140f22]435#Do not use "${rootlink[*]}" =~ "${otherlink[*]}": case rootlink=(1 11)
[e576789]436# and otherlink=(1 1)
[e4b1293]437 if [[ ${srootlink#"${otherlink[*]} "} != ${srootlink} ]]; then # cir. dep
[7dc8595]438 echo -en "\nCirc: $((depth+1))${spaceSTR:0:$((depth+1))}${YELLOW}${id_of_dep}${OFF} $dpriostring"
[2140f22]439# Find lowest priority in branch from parent to depFile:
[7dc8595]440 p2=0
441 for (( i=${#otherlink[*]}; i < $depth ; i++ )) ; do
442 if (( ${priolink[i]} > $p2 )); then p2=${priolink[i]}; fi
443 done
444 if (( $prio_of_dep >= $p2 )); then # prune
[e4b1293]445 lines_to_remove="$lines_to_remove $id_of_dep"
[2140f22]446 sed -i "/$id_of_dep/d" ${depFile/.tree/.dep}
447 else # find and set parent, then return lowest priority
448# The parent has the same link without the last entry.
449# We do not need otherlink anymore so just destroy the last element
450 unset otherlink[-1]
451 parentNode=$(grep ^"${otherlink[*]}"\$ -l *)
452 return $p2
[e4b1293]453 fi
[2140f22]454 else # not circular: prune tree (but not .dep, since it may happen that
[7ac11f0]455 # the tree is destroyed and rebuilt in another order)
[e576789]456 lines_to_remove="$lines_to_remove $id_of_dep"
[e4b1293]457 fi # circular or not
458 continue # this dependency has already been seen, and the associated
459 # subtree computed. We are done
460 fi # Has this entry already been seen?
[2140f22]461# So, this entry has not already been seen. Let's build the corresponding
462# subtree. First check there is a subtree...
463# Use -s, because it may happen that after removing lines, .dep exists
[d7c07e0]464# but is empty.
[2140f22]465 if [[ -s ${id_of_dep}.dep ]]; then # this dependency has dependencies
466 sed "1i${rootlink[*]} $count\\
467${priolink[*]} $prio_of_dep" < ${id_of_dep}.dep \
468 > ${id_of_dep}.tree # add link and priolink
469 generate_dependency_tree ${id_of_dep}.tree $prio_of_dep
[e576789]470# Test return value, in case we exchange dependencies
[2140f22]471 p2=$?
472 case $p2 in
473 0) # Normal return
474 ;;
475 $prio_of_dep) # we remove this dep, but since it may become unreachable,
476 # move it to be built later (as a dep of parent).
477 tree_erase ${id_of_dep}.tree
478 lines_to_remove="$lines_to_remove $id_of_dep"
479 sed -i "/${id_of_dep}/d" ${depFile/.tree/.dep}
480 echo "$prio_of_dep b $id_of_dep" >> $parentNode
481# must be added to .dep in case parentNode is destroyed when erasing
482# the tree
483 echo "$prio_of_dep b $id_of_dep" >> ${parentNode/.tree/.dep}
484 continue
485 ;;
486 *) # We are backing up
487 return $p2
488 ;;
489 esac
490 else # id_of_dep has no dependencies, just record the link in a file
491 # and print
492 echo "${rootlink[*]} $count" > ${id_of_dep}.tree
493 echo -en "\nLeaf: $(($depth+1))${spaceSTR:0:$(($depth+1))}${CYAN}${id_of_dep}${OFF} $dpriostring"
494 fi
[e576789]495done
[2140f22]496echo -en "\n End: $depth${spaceSTR:0:$depth}${GREEN}${depFile%.tree}${OFF}"
497} <$depFile
[e576789]498# It may happen that a file is created with several times
499# the same line. Normally, all those lines but one
500# would be flagged to be removed (or all of them if
[e4b1293]501# the dependency appeared before). A simple sed /$line/d
[e576789]502# destroys all the lines. We should instead remove
503# only one for each appearance of it in lines_to_remove.
[e4b1293]504# so first get the position of last line and then delete
[e576789]505# that line
506for line in $lines_to_remove
[2140f22]507 do lineno=$(sed -n /^[[:digit:]]\ b\ $line\$/= $depFile | tail -n1)
508 sed -i ${lineno}d $depFile
[e576789]509done
510return 0
511}
[3c10176]512
[2140f22]513
[e576789]514#---------------#
515tree_browse() { #
516#---------------#
517local file=$1
518local f
519
520#echo file=$file
[7dc8595]521for f in $(grep '[^0-9 ]' $file | sed 's/.* //'); do
[e576789]522# echo f=$f
[2140f22]523 if grep -q '[^0-9 ]' ${f}.tree ; then
524 tree_browse ${f}.tree
[3c10176]525 fi
[e576789]526 echo $f
527done
528}
[3c10176]529
[e576789]530#--------------#
531tree_erase() { #
532#--------------#
533local file=$1
534local f
[6c8095a]535local rootlink
[f37d08b]536local rootlink2
[e576789]537
538#echo file=$file
[6c8095a]539rootlink="$(head -n1 $file) "
[7dc8595]540for f in $(grep '[^0-9 ]' $file | sed 's/.* //'); do
[2140f22]541 if [ -f ${f}.tree ]; then
542 rootlink2="$(head -n1 ${f}.tree) "
[6c8095a]543# We want two things:
544# i) do not erase the file if it is in another branch
545# ii) do not erase the file if there is a circular dependency
546# for case i), we test that rootlink is contained in rootlink2
547# for case ii), we test that rootlink2 is not contained in
548# rootlink.
[f37d08b]549# See comment above about srootlink
[6c8095a]550 if [[ ${rootlink2#${rootlink}} != ${rootlink2} &&
551 ${rootlink#${rootlink2}} == ${rootlink} ]] ; then
[2140f22]552 tree_erase ${f}.tree
[e576789]553 fi
554 fi
555done
556rm -f $file
[3c10176]557}
Note: See TracBrowser for help on using the repository browser.