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