1 /* 2 * Copyright (c) 2019 The DragonFly Project. All rights reserved. 3 * 4 * This code is derived from software contributed to The DragonFly Project 5 * by Matthew Dillon <dillon@backplane.com> 6 * 7 * This code uses concepts and configuration based on 'synth', by 8 * John R. Marino <draco@marino.st>, which was written in ada. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 2. Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in 18 * the documentation and/or other materials provided with the 19 * distribution. 20 * 3. Neither the name of The DragonFly Project nor the names of its 21 * contributors may be used to endorse or promote products derived 22 * from this software without specific, prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 25 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 26 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 27 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 28 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 29 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, 30 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 31 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 32 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 33 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 34 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 35 * SUCH DAMAGE. 36 */ 37 /* 38 * Figure out what would have to be built [N]ew, [R]ebuild, [U]pgrade 39 * but do not perform any action. 40 */ 41 #include "dsynth.h" 42 43 static int status_find_leaves(pkg_t *parent, pkg_t *pkg, pkg_t ***build_tailp, 44 int *app, int *hasworkp, int level, int first); 45 static void status_clear_trav(pkg_t *pkg); 46 static void startstatus(pkg_t **build_listp, pkg_t ***build_tailp); 47 48 void 49 DoStatus(pkg_t *pkgs) 50 { 51 pkg_t *build_list = NULL; 52 pkg_t **build_tail = &build_list; 53 pkg_t *scan; 54 int haswork = 1; 55 int first = 1; 56 57 /* 58 * Count up all the packages, do not include dummy packages. 59 */ 60 for (scan = pkgs; scan; scan = scan->bnext) { 61 if ((scan->flags & PKGF_DUMMY) == 0) 62 ++BuildTotal; 63 } 64 65 /* 66 * Nominal bulk build sequence 67 */ 68 while (haswork) { 69 haswork = 0; 70 fflush(stdout); 71 for (scan = pkgs; scan; scan = scan->bnext) { 72 ddprintf(0, "SCANLEAVES %08x %s\n", 73 scan->flags, scan->portdir); 74 scan->flags |= PKGF_BUILDLOOP; 75 /* 76 * NOTE: We must still find dependencies if PACKAGED 77 * to fill in the gaps, as some of them may 78 * need to be rebuilt. 79 */ 80 if (scan->flags & (PKGF_SUCCESS | PKGF_FAILURE | 81 PKGF_ERROR | PKGF_NOBUILD)) { 82 #if 0 83 ddprintf(0, "%s: already built\n", 84 scan->portdir); 85 #endif 86 } else { 87 int ap = 0; 88 status_find_leaves(NULL, scan, &build_tail, 89 &ap, &haswork, 0, first); 90 ddprintf(0, "TOPLEVEL %s %08x\n", 91 scan->portdir, ap); 92 } 93 scan->flags &= ~PKGF_BUILDLOOP; 94 status_clear_trav(scan); 95 } 96 first = 0; 97 fflush(stdout); 98 if (haswork == 0) 99 break; 100 startstatus(&build_list, &build_tail); 101 } 102 printf("Total packages that would be built: %d/%d\n", 103 BuildSuccessCount, BuildTotal); 104 } 105 106 /* 107 * Traverse the packages (pkg) depends on recursively until we find 108 * a leaf to build or report as unbuildable. Calculates and assigns a 109 * dependency count. Returns all parallel-buildable packages. 110 * 111 * (pkg) itself is only added to the list if it is immediately buildable. 112 */ 113 static 114 int 115 status_find_leaves(pkg_t *parent, pkg_t *pkg, pkg_t ***build_tailp, 116 int *app, int *hasworkp, int level, int first) 117 { 118 pkglink_t *link; 119 pkg_t *scan; 120 int idep_count = 0; 121 int apsub; 122 123 /* 124 * Already on build list, possibly in-progress, tell caller that 125 * it is not ready. 126 */ 127 ddprintf(level, "sbuild_find_leaves %d %s %08x {\n", 128 level, pkg->portdir, pkg->flags); 129 if (pkg->flags & PKGF_BUILDLIST) { 130 ddprintf(level, "} (already on build list)\n"); 131 *app |= PKGF_NOTREADY; 132 return (pkg->idep_count); 133 } 134 135 /* 136 * Check dependencies 137 */ 138 ++level; 139 PKGLIST_FOREACH(link, &pkg->idepon_list) { 140 scan = link->pkg; 141 142 if (scan == NULL) 143 continue; 144 ddprintf(level, "check %s %08x\t", scan->portdir, scan->flags); 145 146 /* 147 * When accounting for a successful build, just bump 148 * idep_count by one. scan->idep_count will heavily 149 * overlap packages that we count down multiple branches. 150 * 151 * We must still recurse through PACKAGED packages as 152 * some of their dependencies might be missing. 153 */ 154 if (scan->flags & PKGF_SUCCESS) { 155 ddprintf(0, "SUCCESS - OK\n"); 156 ++idep_count; 157 continue; 158 } 159 if (scan->flags & PKGF_ERROR) { 160 ddprintf(0, "ERROR - OK (propagate failure upward)\n"); 161 *app |= PKGF_NOBUILD_S; 162 continue; 163 } 164 if (scan->flags & PKGF_NOBUILD) { 165 ddprintf(0, "NOBUILD - OK " 166 "(propagate failure upward)\n"); 167 *app |= PKGF_NOBUILD_S; 168 continue; 169 } 170 171 /* 172 * If already on build-list this dependency is not ready. 173 */ 174 if (scan->flags & PKGF_BUILDLIST) { 175 ddprintf(0, " [BUILDLIST]"); 176 *app |= PKGF_NOTREADY; 177 } 178 179 /* 180 * If not packaged this dependency is not ready for 181 * the caller. 182 */ 183 if ((scan->flags & PKGF_PACKAGED) == 0) { 184 ddprintf(0, " [NOT_PACKAGED]"); 185 *app |= PKGF_NOTREADY; 186 } 187 188 /* 189 * Reduce search complexity, if we have already processed 190 * scan in the traversal it will either already be on the 191 * build list or it will not be buildable. Either way 192 * the parent is not buildable. 193 */ 194 if (scan->flags & PKGF_BUILDTRAV) { 195 ddprintf(0, " [BUILDTRAV]\n"); 196 *app |= PKGF_NOTREADY; 197 continue; 198 } 199 200 /* 201 * Assert on dependency loop 202 */ 203 ++idep_count; 204 if (scan->flags & PKGF_BUILDLOOP) { 205 dfatal("pkg dependency loop %s -> %s", 206 parent->portdir, scan->portdir); 207 } 208 scan->flags |= PKGF_BUILDLOOP; 209 apsub = 0; 210 ddprintf(0, " SUBRECURSION {\n"); 211 idep_count += status_find_leaves(pkg, scan, build_tailp, 212 &apsub, hasworkp, 213 level + 1, first); 214 scan->flags &= ~PKGF_BUILDLOOP; 215 *app |= apsub; 216 if (apsub & PKGF_NOBUILD) { 217 ddprintf(level, "} (sub-nobuild)\n"); 218 } else if (apsub & PKGF_ERROR) { 219 ddprintf(level, "} (sub-error)\n"); 220 } else if (apsub & PKGF_NOTREADY) { 221 ddprintf(level, "} (sub-notready)\n"); 222 } else { 223 ddprintf(level, "} (sub-ok)\n"); 224 } 225 } 226 --level; 227 pkg->idep_count = idep_count; 228 pkg->flags |= PKGF_BUILDTRAV; 229 230 /* 231 * Incorporate scan results into pkg state. 232 */ 233 if ((pkg->flags & PKGF_NOBUILD) == 0 && (*app & PKGF_NOBUILD)) { 234 *hasworkp = 1; 235 } else if ((pkg->flags & PKGF_ERROR) == 0 && (*app & PKGF_ERROR)) { 236 *hasworkp = 1; 237 } 238 pkg->flags |= *app & ~PKGF_NOTREADY; 239 240 /* 241 * Clear PACKAGED bit if sub-dependencies aren't clean 242 */ 243 if ((pkg->flags & PKGF_PACKAGED) && 244 (pkg->flags & (PKGF_NOTREADY|PKGF_ERROR|PKGF_NOBUILD))) { 245 pkg->flags &= ~PKGF_PACKAGED; 246 ddassert(pkg->pkgfile); 247 *hasworkp = 1; 248 } 249 250 /* 251 * Handle propagated flags 252 */ 253 if (pkg->flags & PKGF_ERROR) { 254 ddprintf(level, "} (ERROR - %s)\n", pkg->portdir); 255 } else if (pkg->flags & PKGF_NOBUILD) { 256 ddprintf(level, "} (SKIPPED - %s)\n", pkg->portdir); 257 } else if (*app & PKGF_NOTREADY) { 258 /* 259 * We don't set PKGF_NOTREADY in the pkg, it is strictly 260 * a transient flag propagated via build_find_leaves(). 261 * 262 * Just don't add the package to the list. 263 */ 264 ; 265 } else if (pkg->flags & PKGF_SUCCESS) { 266 ddprintf(level, "} (SUCCESS - %s)\n", pkg->portdir); 267 } else if (pkg->flags & PKGF_DUMMY) { 268 /* 269 * Just mark dummy packages as successful when all of their 270 * sub-depends (flavors) complete successfully. Note that 271 * dummy packages are not counted in the total, so do not 272 * decrement BuildTotal. 273 */ 274 ddprintf(level, "} (DUMMY/META - SUCCESS)\n"); 275 pkg->flags |= PKGF_SUCCESS; 276 *hasworkp = 1; 277 if (first) { 278 dlog(DLOG_ALL | DLOG_FILTER, 279 "[XXX] %s META-ALREADY-BUILT\n", 280 pkg->portdir); 281 } else { 282 dlog(DLOG_SUCC | DLOG_FILTER, 283 "[XXX] %s meta-node complete\n", 284 pkg->portdir); 285 } 286 } else if (pkg->flags & PKGF_PACKAGED) { 287 /* 288 * We can just mark the pkg successful. If this is 289 * the first pass, we count this as an initial pruning 290 * pass and reduce BuildTotal. 291 */ 292 ddprintf(level, "} (PACKAGED - SUCCESS)\n"); 293 pkg->flags |= PKGF_SUCCESS; 294 *hasworkp = 1; 295 if (first) { 296 dlog(DLOG_ALL | DLOG_FILTER, 297 "[XXX] %s ALREADY-BUILT\n", 298 pkg->portdir); 299 --BuildTotal; 300 } 301 } else { 302 /* 303 * All dependencies are successful, queue new work 304 * and indicate not-ready to the parent (since our 305 * package has to be built). 306 */ 307 *hasworkp = 1; 308 ddprintf(level, "} (ADDLIST - %s)\n", pkg->portdir); 309 pkg->flags |= PKGF_BUILDLIST; 310 **build_tailp = pkg; 311 *build_tailp = &pkg->build_next; 312 *app |= PKGF_NOTREADY; 313 } 314 315 return idep_count; 316 } 317 318 static 319 void 320 status_clear_trav(pkg_t *pkg) 321 { 322 pkglink_t *link; 323 pkg_t *scan; 324 325 pkg->flags &= ~PKGF_BUILDTRAV; 326 PKGLIST_FOREACH(link, &pkg->idepon_list) { 327 scan = link->pkg; 328 if (scan && (scan->flags & PKGF_BUILDTRAV)) 329 status_clear_trav(scan); 330 } 331 } 332 333 /* 334 * This is a fake startbuild() which just marks the build list as built, 335 * allowing us to resolve the build tree. 336 */ 337 static 338 void 339 startstatus(pkg_t **build_listp, pkg_t ***build_tailp) 340 { 341 pkg_t *pkg; 342 343 /* 344 * Nothing to do 345 */ 346 if (*build_listp == NULL) 347 return; 348 349 /* 350 * Sort 351 */ 352 for (pkg = *build_listp; pkg; pkg = pkg->build_next) { 353 if ((pkg->flags & (PKGF_SUCCESS | PKGF_FAILURE | 354 PKGF_ERROR | PKGF_NOBUILD | 355 PKGF_RUNNING)) == 0) { 356 pkg->flags |= PKGF_SUCCESS; 357 ++BuildSuccessCount; 358 ++BuildCount; 359 pkg->flags &= ~PKGF_BUILDLIST; 360 printf(" N => %s\n", pkg->portdir); 361 /* XXX [R]ebuild and [U]pgrade */ 362 } 363 364 } 365 *build_listp = NULL; 366 *build_tailp = build_listp; 367 } 368