Initial revision
[sw-tools] / src / sw_build.c
1 /* -*-c-*-
2 *
3 * $Id: sw_build.c,v 1.1 1999/06/02 16:53:34 mdw Exp $
4 *
5 * Management of build processes
6 *
7 * (c) 1999 EBI
8 */
9
10 /*----- Licensing notice --------------------------------------------------*
11 *
12 * This file is part of sw-tools.
13 *
14 * sw-tools is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 2 of the License, or
17 * (at your option) any later version.
18 *
19 * sw-tools is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with sw-tools; if not, write to the Free Software Foundation,
26 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27 */
28
29 /*----- Revision history --------------------------------------------------*
30 *
31 * $Log: sw_build.c,v $
32 * Revision 1.1 1999/06/02 16:53:34 mdw
33 * Initial revision
34 *
35 */
36
37 /*----- Header files ------------------------------------------------------*/
38
39 #include "config.h"
40
41 #include <ctype.h>
42 #include <errno.h>
43 #include <signal.h>
44 #include <stdarg.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <string.h>
48
49 #include <sys/types.h>
50 #include <sys/time.h>
51 #include <sys/select.h>
52 #include <sys/stat.h>
53 #include <unistd.h>
54 #include <fcntl.h>
55 #include <sys/wait.h>
56
57 #ifndef DECL_ENVIRON
58 extern char **environ;
59 #endif
60
61 #include <mLib/alloc.h>
62 #include <mLib/dstr.h>
63 #include <mLib/exc.h>
64 #include <mLib/quis.h>
65 #include <mLib/report.h>
66
67 #include "sw.h"
68 #include "sw_arch.h"
69 #include "sw_build.h"
70 #include "sw_info.h"
71 #include "sw_rsh.h"
72
73 #define PRES_LINK 0
74 #define PRES_DEFAULT 0
75 #define PRES_PREFERENCE 0
76 #include "pres_plain.h"
77 #include "pres_curses.h"
78
79 /*----- Data structures ---------------------------------------------------*/
80
81 /*----- Static variables --------------------------------------------------*/
82
83 static pres *preslist = PRES_LINK;
84
85 /*----- Main code ---------------------------------------------------------*/
86
87 /* --- @swbuild_archlist@ --- *
88 *
89 * Arguments: @swinfo *sw@ = pointer to the build information block
90 *
91 * Returns: A list of architectures which are to be built.
92 *
93 * Use: Decides which architectures need building, and returns them
94 * in a list.
95 */
96
97 archcons *swbuild_archlist(swinfo *sw)
98 {
99 archcons *a = arch_readtab();
100 const char *only = 0;
101 unsigned and = 0, xor = 0;
102
103 /* --- Restrict the architecture list appropriately --- */
104
105 only = opt_arch ? opt_arch : sw->only_arch;
106
107 /* --- Apply the built flags --- */
108
109 if (!(opt_flags & optFlag_force) && sw->arch) {
110 archcons *aa = arch_filter(a, sw->arch, 0, 0);
111 archcons *c;
112 for (c = aa; c; c = c->cdr)
113 c->car->flags |= archFlag_built;
114 arch_free(aa);
115 and |= archFlag_built;
116 }
117
118 return (arch_filter(a, only, and, xor));
119 }
120
121 /*----- Main build command ------------------------------------------------*/
122
123 /* --- @sw_run@ --- *
124 *
125 * Arguments: @int argc@ = number of command line arguments
126 * @char *argv[]@ = array of command line arguments
127 *
128 * Returns: Zero on success (all builds OK) or nonzero for failure.
129 *
130 * Use: Runs a multi-architecture build.
131 */
132
133 int sw_run(int argc, char *argv[])
134 {
135 swinfo sw;
136 archcons *a;
137 pres *p = PRES_DEFAULT;
138 fd_set fdin;
139 int active = 0;
140 int maxfd = 0;
141 int rc = 0;
142
143 /* --- Handle help on output styles --- */
144
145 if (opt_output && strcmp(opt_output, "help") == 0) {
146 printf("Presentation styles supported:");
147 for (p = preslist; p; p = p->next)
148 printf(" %s", p->name);
149 putc('\n', stdout);
150 printf("The default presentation style is %s\n", (PRES_DEFAULT)->name);
151 exit(0);
152 }
153
154 /* --- Validate arguments --- */
155
156 if (!argv[1])
157 die(1, "Usage: run COMMAND [ARG...]");
158
159 /* --- Choose an output presentation style --- */
160
161 if (opt_output) {
162 pres *q;
163 size_t sz = strlen(opt_output);
164 p = 0;
165
166 for (q = preslist; q; q = q->next) {
167 if (strncmp(opt_output, q->name, sz) == 0) {
168 if (q->name[sz] == 0) {
169 p = q;
170 break;
171 } else if (p)
172 die(1, "ambiguous output style `%s'", opt_output);
173 else
174 p = q;
175 }
176 }
177
178 if (!p)
179 die(1, "unknown output style `%s'", opt_output);
180 }
181
182 if (p->ok && !p->ok()) {
183 moan("output style `%s' can't run; using `plain' instead", p->name);
184 p = &pres_plain;
185 }
186
187 /* --- Decide on an architecture --- */
188
189 if (swinfo_fetch(&sw)) {
190 die(1, "couldn't read build status: %s (try running setup)",
191 strerror(errno));
192 }
193 swinfo_sanity(&sw);
194 a = swbuild_archlist(&sw);
195
196 if (!a) {
197 moan("All desired architectures already built OK.");
198 moan("(Perhaps you forgot `--force', or want to say `%s reset'.)", QUIS);
199 return (0);
200 }
201
202 /* --- Tie on remote context blocks, and crank up the presentation --- */
203
204 {
205 archcons *aa;
206
207 for (aa = a; aa; aa = aa->cdr)
208 aa->car->r = xmalloc(sizeof(sw_remote));
209 if (p->init && p->init(a))
210 die(1, "presentation style refused to start: %s", strerror(errno));
211 }
212
213 signal(SIGINT, SIG_IGN);
214 signal(SIGQUIT, SIG_IGN);
215
216 /* --- Trap any exceptions coming this way --- *
217 *
218 * It's important, for example, that a curses-based presentation system
219 * reset the terminal flags appropriately.
220 */
221
222 TRY {
223 /* --- Run remote build processes on the remote hosts --- */
224
225 {
226 archcons *aa;
227
228 FD_ZERO(&fdin);
229 for (aa = a; aa; aa = aa->cdr) {
230 archent *e = aa->car;
231 sw_remote *r = e->r;
232 if (swrsh(r, e->flags & archFlag_home ? 0 : e->host,
233 "build", argv + 1)) {
234 dstr d = DSTR_INIT;
235 dstr_putf(&d, "%s: couldn't start build for architecture `%s': %s",
236 QUIS, e->arch, strerror(errno));
237 p->output(e, d.buf, d.len);
238 free(r);
239 e->r = 0;
240 dstr_destroy(&d);
241 } else {
242 int fd = r->fdin;
243 if (fd > maxfd)
244 maxfd = fd;
245 active++;
246 FD_SET(fd, &fdin);
247 }
248 }
249 }
250
251 /* --- Watch the builds until they do something interesting --- */
252
253 maxfd++;
254 while (active) {
255 fd_set f;
256 int n;
257 archcons *aa;
258
259 /* --- Find out what interesting things are happening --- */
260
261 memcpy(&f, &fdin, sizeof(f));
262 n = select(maxfd, &f, 0, 0, 0);
263 if (n < 0) {
264 if (errno == EINTR || errno == EAGAIN)
265 continue;
266 else
267 THROW(EXC_ERRNO, errno);
268 }
269
270 /* --- Scan through for jobs which need attention --- */
271
272 for (aa = a; aa; aa = aa->cdr) {
273 archent *e = aa->car;
274 sw_remote *r = e->r;
275 int t;
276
277 if (!FD_ISSET(r->fdin, &f))
278 continue;
279
280 switch (t = pkrecv(r)) {
281
282 case PKTYPE_DATA:
283 p->output(e, r->buf, r->sz);
284 break;
285
286 case PKTYPE_STATUS: {
287 dstr d = DSTR_INIT;
288 int ok = 1;
289 if (r->sz != 1) {
290 r->buf[r->sz] = 0;
291 dstr_putf(&d, "\nTerminated by signal: %s.\n", r->buf);
292 ok = 0;
293 rc = 1;
294 } else if (r->buf[0]) {
295 dstr_putf(&d, "\nExited with status %u.\n",
296 (unsigned char)r->buf[0]);
297 ok = 0;
298 rc = 1;
299 } else if (opt_flags & optFlag_install)
300 e->flags |= archFlag_built;
301 if (d.len)
302 p->output(e, d.buf, d.len);
303 dstr_destroy(&d);
304 if (p->close)
305 p->close(e, ok);
306 FD_CLR(r->fdin, &fdin);
307 close(r->fdin);
308 active--;
309 } break;
310
311 case PKTYPE_EOF: {
312 const static char msg[] = "\nUnexpected exit.\n";
313 p->output(e, msg, sizeof(msg) - 1);
314 if (p->close)
315 p->close(e, 0);
316 rc = 1;
317 FD_CLR(r->fdin, &fdin);
318 close(r->fdin);
319 active--;
320 } break;
321
322 default: {
323 const static char msg[] = "\n[Unexpected packet, type %i]\n";
324 p->output(e, msg, sizeof(msg) - 1);
325 } break;
326 }
327 }
328 }
329 }
330
331 /* --- Handle any exceptions coming this way --- *
332 *
333 * I could do more cleanup here (freeing buffers and so) but it's not worth
334 * it. Nobody's bothering to catch exceptions anyway.
335 */
336
337 CATCH {
338 if (p->abort)
339 p->abort(a);
340 switch (exc_type) {
341 case EXC_ERRNO:
342 die(1, "unexpected error: %s", exc_i);
343 break;
344 default:
345 RETHROW;
346 break;
347 }
348 } END_TRY;
349
350 /* --- Tell the presentation that everything's done --- */
351
352 if (p->done)
353 p->done(a);
354 else if (opt_flags & optFlag_beep)
355 putchar('\a');
356
357 /* --- Clean up the unwanted remote contexts --- */
358
359 {
360 archcons *aa;
361 for (aa = a; aa; aa = aa->cdr)
362 free(a->car->r);
363 }
364
365 /* --- Tidy away the architecture list --- */
366
367 arch_free(a);
368
369 /* --- Mark built architectures as having been completed now --- */
370
371 if (opt_flags & optFlag_install) {
372 swinfo skel;
373 dstr d = DSTR_INIT;
374 swinfo_clear(&skel);
375 arch_toText(&d, arch_readtab(), archFlag_built, archFlag_built);
376 skel.arch = d.buf;
377 swinfo_update(&sw, &skel);
378 dstr_destroy(&d);
379 if (swinfo_put(&sw))
380 die(1, "error writing build status: %s", strerror(errno));
381 }
382 return (rc);
383 }
384
385 /*----- Main remote entry point -------------------------------------------*/
386
387 /* --- @putf@ --- *
388 *
389 * Arguments: @sw_remote *r@ = pointer to remote context
390 * @FILE *fp@ = log file handle
391 * @const char *fmt@ = format string
392 * @...@ = other arguments
393 *
394 * Returns: ---
395 *
396 * Use: Reports a string to the log file and the remote controller
397 * process.
398 */
399
400 static void putf(sw_remote *r, FILE *fp, const char *fmt, ...)
401 {
402 va_list ap;
403 dstr d = DSTR_INIT;
404 va_start(ap, fmt);
405 dstr_vputf(&d, fmt, ap);
406 va_end(ap);
407 if (r)
408 pksend(r, PKTYPE_DATA, d.buf, d.len);
409 if (fp)
410 fwrite(d.buf, 1, d.len, fp);
411 dstr_destroy(&d);
412 }
413
414 /* --- @swrsh_build@ --- *
415 *
416 * Arguments: @sw_remote *r@ = pointer to remote context
417 * @char *argv[]@ = pointer to argument list
418 * @char *env[]@ = pointer to environment list
419 *
420 * Returns: Doesn't.
421 *
422 * Use: Runs a remote build command.
423 */
424
425 void swrsh_build(sw_remote *r, char *argv[], char *env[])
426 {
427 FILE *logfp;
428 int fd[2];
429 pid_t kid;
430
431 /* --- Validate arguments --- */
432
433 if (!argv[0])
434 swdie(r, 1, "Usage: build COMMAND [ARG...]");
435
436 /* --- Change into architecture directory --- */
437
438 if (mkdir(ARCH, 0775) && errno != EEXIST)
439 swdie(r, 1, "mkdir(`%s') failed: %s", ARCH, strerror(errno));
440 if (chdir(ARCH)) {
441 swdie(r, 1, "couldn't change directory to `%s': %s",
442 ARCH, strerror(errno));
443 }
444 if (pipe(fd))
445 swdie(r, 1, "couldn't create pipe: %s", strerror(errno));
446
447 /* --- Open the log file --- */
448
449 {
450 int logfd = open(".build-log", O_WRONLY | O_APPEND | O_CREAT, 0664);
451 time_t t;
452 struct tm *tm;
453 char buf[64];
454 char **p;
455
456 if (logfd < 0)
457 swdie(r, 1, "couldn't open `.build-log' file: %s", strerror(errno));
458 if ((logfp = fdopen(logfd, "a")) == 0) {
459 swdie(r, 1, "couldn't open stream on `.build-log' file: %s",
460 strerror(errno));
461 }
462 t = time(0);
463 tm = localtime(&t);
464 strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", tm);
465 fprintf(logfp, "\n\n*** %s: started build: %s", buf, argv[0]);
466 for (p = argv + 1; *p; p++)
467 fprintf(logfp, " %s", *p);
468 fputs("\n\n", logfp);
469 }
470
471 /* --- Start off the child process --- */
472
473 kid = fork();
474 if (kid == 0) {
475 int nullfd;
476 close(fd[0]);
477 dup2(fd[1], 1);
478 dup2(fd[1], 2);
479 if (fd[1] > 2)
480 close(fd[1]);
481 close(0);
482 nullfd = open("/dev/null", O_RDONLY);
483 if (nullfd > 0) {
484 dup2(nullfd, 0);
485 close(nullfd);
486 }
487 environ = env;
488 execvp(argv[0], argv);
489 fprintf(stderr, "failed to start `%s': %s\n", argv[0], strerror(errno));
490 _exit(127);
491 }
492
493 /* --- Read from the pipe, and write to the socket and logfile --- */
494
495 close(fd[1]);
496 for (;;) {
497 ssize_t n = read(fd[0], r->buf, PKMAX);
498 if (!n)
499 break;
500 if (n < 0) {
501 putf(r, logfp, "*** error reading from pipe: %s\n", strerror(errno));
502 kill(kid, SIGTERM);
503 break;
504 }
505 fwrite(r->buf, 1, n, logfp);
506 pksend(r, PKTYPE_DATA, r->buf, n);
507 }
508 close(fd[0]);
509
510 /* --- Reap exit status and produce final report --- */
511
512 {
513 int status;
514 if (waitpid(kid, &status, 0) < 0)
515 putf(r, logfp, "*** error reading exit status: %s\n", strerror(errno));
516 else {
517 if (WIFSIGNALED(status))
518 fprintf(logfp, "*** exited on signal %i\n", WTERMSIG(status));
519 else if (WIFEXITED(status))
520 fprintf(logfp, "*** exited with status %i\n", WEXITSTATUS(status));
521 else
522 fprintf(logfp, "*** reaped, but didn't exit. Strange\n");
523 }
524 fclose(logfp);
525 swwait(r, status);
526 }
527 }
528
529 /*----- Syntactic sugar ---------------------------------------------------*/
530
531 /*
532 * `Syntactic sugar causes cancer of the semicolon.'
533 * -- Alan Perlis, `Epigrams in Programming'
534 */
535
536 /* --- @build@ --- *
537 *
538 * Arguments: @char **u@ = first segment
539 * @char **v@ = second segment
540 *
541 * Returns: Return code from the build.
542 *
543 * Use: Combines two @argv@-style arrays and then runs a build on the
544 * result.
545 */
546
547 static int build(char **u, char **v)
548 {
549 size_t i, j;
550 char **p;
551
552 for (i = 0, p = u; *p; p++, i++) ;
553 for (j = 0, p = v; *p; p++, j++) ;
554 p = xmalloc((i + j + 2) * sizeof(char **));
555 memcpy(p + 1, u, i * sizeof(char **));
556 memcpy(p + i + 1, v, j * sizeof(char **));
557 p[0] = p[i + j + 1] = 0;
558 return (sw_run(i + j + 1, p));
559 }
560
561 /* --- @sw_make@ --- */
562
563 int sw_make(int argc, char *argv[])
564 {
565 static char *mk[] = { 0, 0 };
566 if (!mk[0]) {
567 char *m;
568 if ((m = getenv("SW_MAKE")) == 0 &&
569 (m = getenv("MAKE")) == 0)
570 m = "make";
571 mk[0] = m;
572 }
573 return (build(mk, argv + 1));
574 }
575
576 /* --- @sw_conf@ --- */
577
578 int sw_conf(int argc, char *argv[])
579 {
580 static char *cf[] = { "../configure", "--prefix=" PREFIX, 0 };
581 return (build(cf, argv + 1));
582 }
583
584 /*----- Other subcommands -------------------------------------------------*/
585
586 /* --- @sw_reset@ --- */
587
588 int sw_reset(int argc, char *argv[])
589 {
590 swinfo sw, skel;
591 if (argc != 1)
592 die(1, "Usage: reset");
593 if (swinfo_fetch(&sw)) {
594 die(1, "couldn't read build status: %s (try running setup)",
595 strerror(errno));
596 }
597 swinfo_sanity(&sw);
598 swinfo_clear(&skel);
599 skel.arch = "";
600 swinfo_update(&sw, &skel);
601 if (swinfo_put(&sw))
602 die(1, "couldn't write build status: %s", strerror(errno));
603 return (0);
604 }
605
606 /*----- That's all, folks -------------------------------------------------*/