chiark / gitweb /
Include `%'-escape substitution.
[sw-tools] / src / sw_build.c
CommitLineData
3315e8b3 1/* -*-c-*-
2 *
1efab4fe 3 * $Id: sw_build.c,v 1.3 1999/09/10 15:27:33 mdw Exp $
3315e8b3 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 $
1efab4fe 32 * Revision 1.3 1999/09/10 15:27:33 mdw
33 * Include `%'-escape substitution.
34 *
5636c0ce 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.
3315e8b3 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>
1efab4fe 62#include <sys/utsname.h>
3315e8b3 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
90static 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
104archcons *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
140int 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;
1efab4fe 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 --- */
3315e8b3 251
252 FD_ZERO(&fdin);
253 for (aa = a; aa; aa = aa->cdr) {
254 archent *e = aa->car;
255 sw_remote *r = e->r;
1efab4fe 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
3315e8b3 318 if (swrsh(r, e->flags & archFlag_home ? 0 : e->host,
1efab4fe 319 "build", av)) {
3315e8b3 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 }
1efab4fe 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 }
3315e8b3 345 }
1efab4fe 346
347 if (opt_flags & optFlag_percent)
348 free(av);
3315e8b3 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;
5636c0ce 391 dstr_putf(&d, "failed (%s)", r->buf);
3315e8b3 392 ok = 0;
393 rc = 1;
394 } else if (r->buf[0]) {
5636c0ce 395 dstr_putf(&d, "failed (status %u)", (unsigned char)r->buf[0]);
3315e8b3 396 ok = 0;
397 rc = 1;
5636c0ce 398 } else {
399 dstr_puts(&d, "finished");
400 if (opt_flags & optFlag_install)
401 e->flags |= archFlag_built;
402 }
3315e8b3 403 if (p->close)
5636c0ce 404 p->close(e, ok, d.buf);
405 dstr_destroy(&d);
3315e8b3 406 FD_CLR(r->fdin, &fdin);
407 close(r->fdin);
408 active--;
409 } break;
410
411 case PKTYPE_EOF: {
3315e8b3 412 if (p->close)
5636c0ce 413 p->close(e, 0, "unexpected exit");
3315e8b3 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:
5636c0ce 440 die(1, "unexpected error: %s", strerror(exc_i));
3315e8b3 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
498static 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
523void swrsh_build(sw_remote *r, char *argv[], char *env[])
524{
525 FILE *logfp;
526 int fd[2];
527 pid_t kid;
528
1efab4fe 529 /* --- Validate the arguments --- */
3315e8b3 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;
1efab4fe 553 struct utsname u;
3315e8b3 554
1efab4fe 555 if (uname(&u))
556 swdie(r, 1, "couldn't get hostname: %s", strerror(errno));
3315e8b3 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);
1efab4fe 566 fprintf(logfp, "\n\n*** %s: %s started build: %s",
567 buf, u.nodename, argv[0]);
3315e8b3 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) {
1efab4fe 603 putf(r, logfp, "\n*** error reading from pipe: %s\n", strerror(errno));
3315e8b3 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;
1efab4fe 616 if (waitpid(kid, &status, 0) < 0) {
617 putf(r, logfp, "\n*** error reading exit status: %s\n",
618 strerror(errno));
619 } else {
3315e8b3 620 if (WIFSIGNALED(status))
1efab4fe 621 fprintf(logfp, "\n*** exited on signal %i\n", WTERMSIG(status));
3315e8b3 622 else if (WIFEXITED(status))
1efab4fe 623 fprintf(logfp, "\n*** exited with status %i\n", WEXITSTATUS(status));
3315e8b3 624 else
1efab4fe 625 fprintf(logfp, "\n*** reaped, but didn't exit. Strange\n");
3315e8b3 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
650static 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
666int 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
681int 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
691int 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 -------------------------------------------------*/