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