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