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