xref: /dragonfly/usr.bin/dsynth/status.c (revision 21265a85c83831667accd97d8d4861452cec8538)
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
DoStatus(pkg_t * pkgs)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
status_find_leaves(pkg_t * parent,pkg_t * pkg,pkg_t *** build_tailp,int * app,int * hasworkp,int level,int first)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
status_clear_trav(pkg_t * pkg)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
startstatus(pkg_t ** build_listp,pkg_t *** build_tailp)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