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