xref: /dragonfly/usr.bin/dsynth/status.c (revision 335b9e93)
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