xref: /dragonfly/usr.bin/dsynth/status.c (revision 0e32b8c5)
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 	for (scan = pkgs; scan; scan = scan->bnext)
58 		++BuildTotal;
59 
60 	/*
61 	 * Nominal bulk build sequence
62 	 */
63 	while (haswork) {
64 		haswork = 0;
65 		fflush(stdout);
66 		for (scan = pkgs; scan; scan = scan->bnext) {
67 			ddprintf(0, "SCANLEAVES %08x %s\n",
68 				 scan->flags, scan->portdir);
69 			scan->flags |= PKGF_BUILDLOOP;
70 			/*
71 			 * NOTE: We must still find dependencies if PACKAGED
72 			 *	 to fill in the gaps, as some of them may
73 			 *	 need to be rebuilt.
74 			 */
75 			if (scan->flags & (PKGF_SUCCESS | PKGF_FAILURE |
76 					   PKGF_ERROR | PKGF_NOBUILD)) {
77 #if 0
78 				ddprintf(0, "%s: already built\n",
79 					 scan->portdir);
80 #endif
81 			} else {
82 				int ap = 0;
83 				status_find_leaves(NULL, scan, &build_tail,
84 						  &ap, &haswork, 0, first);
85 				ddprintf(0, "TOPLEVEL %s %08x\n",
86 					 scan->portdir, ap);
87 			}
88 			scan->flags &= ~PKGF_BUILDLOOP;
89 			status_clear_trav(scan);
90 		}
91 		first = 0;
92 		fflush(stdout);
93 		if (haswork == 0)
94 			break;
95 		startstatus(&build_list, &build_tail);
96 	}
97 	printf("Total packages that would be built: %d/%d\n",
98 	       BuildSuccessCount, BuildTotal);
99 }
100 
101 /*
102  * Traverse the packages (pkg) depends on recursively until we find
103  * a leaf to build or report as unbuildable.  Calculates and assigns a
104  * dependency count.  Returns all parallel-buildable packages.
105  *
106  * (pkg) itself is only added to the list if it is immediately buildable.
107  */
108 static
109 int
110 status_find_leaves(pkg_t *parent, pkg_t *pkg, pkg_t ***build_tailp,
111 		  int *app, int *hasworkp, int level, int first)
112 {
113 	pkglink_t *link;
114 	pkg_t *scan;
115 	int idep_count = 0;
116 	int apsub;
117 
118 	/*
119 	 * Already on build list, possibly in-progress, tell caller that
120 	 * it is not ready.
121 	 */
122 	ddprintf(level, "sbuild_find_leaves %d %s %08x {\n",
123 		 level, pkg->portdir, pkg->flags);
124 	if (pkg->flags & PKGF_BUILDLIST) {
125 		ddprintf(level, "} (already on build list)\n");
126 		*app |= PKGF_NOTREADY;
127 		return (pkg->idep_count);
128 	}
129 
130 	/*
131 	 * Check dependencies
132 	 */
133 	++level;
134 	PKGLIST_FOREACH(link, &pkg->idepon_list) {
135 		scan = link->pkg;
136 
137 		if (scan == NULL)
138 			continue;
139 		ddprintf(level, "check %s %08x\t", scan->portdir, scan->flags);
140 
141 		/*
142 		 * When accounting for a successful build, just bump
143 		 * idep_count by one.  scan->idep_count will heavily
144 		 * overlap packages that we count down multiple branches.
145 		 *
146 		 * We must still recurse through PACKAGED packages as
147 		 * some of their dependencies might be missing.
148 		 */
149 		if (scan->flags & PKGF_SUCCESS) {
150 			ddprintf(0, "SUCCESS - OK\n");
151 			++idep_count;
152 			continue;
153 		}
154 		if (scan->flags & PKGF_ERROR) {
155 			ddprintf(0, "ERROR - OK (propogate failure upward)\n");
156 			*app |= PKGF_NOBUILD_S;
157 			continue;
158 		}
159 		if (scan->flags & PKGF_NOBUILD) {
160 			ddprintf(0, "NOBUILD - OK "
161 				    "(propogate failure upward)\n");
162 			*app |= PKGF_NOBUILD_S;
163 			continue;
164 		}
165 
166 		/*
167 		 * If already on build-list this dependency is not ready.
168 		 */
169 		if (scan->flags & PKGF_BUILDLIST) {
170 			ddprintf(0, " [BUILDLIST]");
171 			*app |= PKGF_NOTREADY;
172 		}
173 
174 		/*
175 		 * If not packaged this dependency is not ready for
176 		 * the caller.
177 		 */
178 		if ((scan->flags & PKGF_PACKAGED) == 0) {
179 			ddprintf(0, " [NOT_PACKAGED]");
180 			*app |= PKGF_NOTREADY;
181 		}
182 
183 		/*
184 		 * Reduce search complexity, if we have already processed
185 		 * scan in the traversal it will either already be on the
186 		 * build list or it will not be buildable.  Either way
187 		 * the parent is not buildable.
188 		 */
189 		if (scan->flags & PKGF_BUILDTRAV) {
190 			ddprintf(0, " [BUILDTRAV]\n");
191 			*app |= PKGF_NOTREADY;
192 			continue;
193 		}
194 
195 		/*
196 		 * Assert on dependency loop
197 		 */
198 		++idep_count;
199 		if (scan->flags & PKGF_BUILDLOOP) {
200 			dfatal("pkg dependency loop %s -> %s",
201 				parent->portdir, scan->portdir);
202 		}
203 		scan->flags |= PKGF_BUILDLOOP;
204 		apsub = 0;
205 		ddprintf(0, " SUBRECURSION {\n");
206 		idep_count += status_find_leaves(pkg, scan, build_tailp,
207 						&apsub, hasworkp,
208 						level + 1, first);
209 		scan->flags &= ~PKGF_BUILDLOOP;
210 		*app |= apsub;
211 		if (apsub & PKGF_NOBUILD) {
212 			ddprintf(level, "} (sub-nobuild)\n");
213 		} else if (apsub & PKGF_ERROR) {
214 			ddprintf(level, "} (sub-error)\n");
215 		} else if (apsub & PKGF_NOTREADY) {
216 			ddprintf(level, "} (sub-notready)\n");
217 		} else {
218 			ddprintf(level, "} (sub-ok)\n");
219 		}
220 	}
221 	--level;
222 	pkg->idep_count = idep_count;
223 	pkg->flags |= PKGF_BUILDTRAV;
224 
225 	/*
226 	 * Incorporate scan results into pkg state.
227 	 */
228 	if ((pkg->flags & PKGF_NOBUILD) == 0 && (*app & PKGF_NOBUILD)) {
229 		*hasworkp = 1;
230 	} else if ((pkg->flags & PKGF_ERROR) == 0 && (*app & PKGF_ERROR)) {
231 		*hasworkp = 1;
232 	}
233 	pkg->flags |= *app & ~PKGF_NOTREADY;
234 
235 	/*
236 	 * Clear PACKAGED bit if sub-dependencies aren't clean
237 	 */
238 	if ((pkg->flags & PKGF_PACKAGED) &&
239 	    (pkg->flags & (PKGF_NOTREADY|PKGF_ERROR|PKGF_NOBUILD))) {
240 		pkg->flags &= ~PKGF_PACKAGED;
241 		ddassert(pkg->pkgfile);
242 		*hasworkp = 1;
243 	}
244 
245 	/*
246 	 * Handle propagated flags
247 	 */
248 	if (pkg->flags & PKGF_ERROR) {
249 		ddprintf(level, "} (ERROR - %s)\n", pkg->portdir);
250 	} else if (pkg->flags & PKGF_NOBUILD) {
251 		ddprintf(level, "} (SKIPPED - %s)\n", pkg->portdir);
252 	} else if (*app & PKGF_NOTREADY) {
253 		/*
254 		 * We don't set PKGF_NOTREADY in the pkg, it is strictly
255 		 * a transient flag propagated via build_find_leaves().
256 		 *
257 		 * Just don't add the package to the list.
258 		 */
259 		;
260 	} else if (pkg->flags & PKGF_SUCCESS) {
261 		ddprintf(level, "} (SUCCESS - %s)\n", pkg->portdir);
262 	} else if (pkg->flags & PKGF_DUMMY) {
263 		ddprintf(level, "} (DUMMY/META - SUCCESS)\n");
264 		pkg->flags |= PKGF_SUCCESS;
265 		*hasworkp = 1;
266 		if (first) {
267 			dlog(DLOG_ALL | DLOG_FILTER,
268 			     "[XXX] %s META-ALREADY-BUILT\n",
269 			     pkg->portdir);
270 			--BuildTotal;
271 		} else {
272 			dlog(DLOG_SUCC | DLOG_FILTER,
273 			     "[XXX] %s meta-node complete\n",
274 			     pkg->portdir);
275 		}
276 	} else if (pkg->flags & PKGF_PACKAGED) {
277 		/*
278 		 * We can just mark the pkg successful.  If this is
279 		 * the first pass, we count this as an initial pruning
280 		 * pass and reduce BuildTotal.
281 		 */
282 		ddprintf(level, "} (PACKAGED - SUCCESS)\n");
283 		pkg->flags |= PKGF_SUCCESS;
284 		*hasworkp = 1;
285 		if (first) {
286 			dlog(DLOG_ALL | DLOG_FILTER,
287 			    "[XXX] %s ALREADY-BUILT\n",
288 			     pkg->portdir);
289 			--BuildTotal;
290 		}
291 	} else {
292 		/*
293 		 * All dependencies are successful, queue new work
294 		 * and indicate not-ready to the parent (since our
295 		 * package has to be built).
296 		 */
297 		*hasworkp = 1;
298 		ddprintf(level, "} (ADDLIST - %s)\n", pkg->portdir);
299 		pkg->flags |= PKGF_BUILDLIST;
300 		**build_tailp = pkg;
301 		*build_tailp = &pkg->build_next;
302 		*app |= PKGF_NOTREADY;
303 	}
304 
305 	return idep_count;
306 }
307 
308 static
309 void
310 status_clear_trav(pkg_t *pkg)
311 {
312 	pkglink_t *link;
313 	pkg_t *scan;
314 
315 	pkg->flags &= ~PKGF_BUILDTRAV;
316 	PKGLIST_FOREACH(link, &pkg->idepon_list) {
317 		scan = link->pkg;
318 		if (scan && (scan->flags & PKGF_BUILDTRAV))
319 			status_clear_trav(scan);
320 	}
321 }
322 
323 /*
324  * This is a fake startbuild() which just marks the build list as built,
325  * allowing us to resolve the build tree.
326  */
327 static
328 void
329 startstatus(pkg_t **build_listp, pkg_t ***build_tailp)
330 {
331 	pkg_t *pkg;
332 
333 	/*
334 	 * Nothing to do
335 	 */
336 	if (*build_listp == NULL)
337 		return;
338 
339 	/*
340 	 * Sort
341 	 */
342 	for (pkg = *build_listp; pkg; pkg = pkg->build_next) {
343 		if ((pkg->flags & (PKGF_SUCCESS | PKGF_FAILURE |
344 				   PKGF_ERROR | PKGF_NOBUILD |
345 				   PKGF_RUNNING)) == 0) {
346 			pkg->flags |= PKGF_SUCCESS;
347 			++BuildSuccessCount;
348 			++BuildCount;
349 			pkg->flags &= ~PKGF_BUILDLIST;
350 			printf("  N => %s\n", pkg->portdir);
351 			/* XXX [R]ebuild and [U]pgrade */
352 		}
353 
354 	}
355 	*build_listp = NULL;
356 	*build_tailp = build_listp;
357 }
358