crunchgen.c revision 68569
1/*
2 * Copyright (c) 1994 University of Maryland
3 * All Rights Reserved.
4 *
5 * Permission to use, copy, modify, distribute, and sell this software and its
6 * documentation for any purpose is hereby granted without fee, provided that
7 * the above copyright notice appear in all copies and that both that
8 * copyright notice and this permission notice appear in supporting
9 * documentation, and that the name of U.M. not be used in advertising or
10 * publicity pertaining to distribution of the software without specific,
11 * written prior permission.  U.M. makes no representations about the
12 * suitability of this software for any purpose.  It is provided "as is"
13 * without express or implied warranty.
14 *
15 * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
17 * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
18 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
19 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
20 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
21 *
22 * Author: James da Silva, Systems Design and Analysis Group
23 *			   Computer Science Department
24 *			   University of Maryland at College Park
25 *
26 * $FreeBSD: head/usr.sbin/crunch/crunchgen/crunchgen.c 68569 2000-11-10 15:21:37Z joe $
27 */
28/*
29 * ========================================================================
30 * crunchgen.c
31 *
32 * Generates a Makefile and main C file for a crunched executable,
33 * from specs given in a .conf file.
34 */
35#include <ctype.h>
36#include <err.h>
37#include <stdio.h>
38#include <stdlib.h>
39#include <string.h>
40#include <unistd.h>
41
42#include <sys/types.h>
43#include <sys/stat.h>
44#include <sys/param.h>
45
46#define CRUNCH_VERSION	"0.2"
47
48#define MAXLINELEN	16384
49#define MAXFIELDS 	 2048
50
51
52/* internal representation of conf file: */
53
54/* simple lists of strings suffice for most parms */
55
56typedef struct strlst {
57    struct strlst *next;
58    char *str;
59} strlst_t;
60
61/* progs have structure, each field can be set with "special" or calculated */
62
63typedef struct prog {
64    struct prog *next;		/* link field */
65    char *name;			/* program name */
66    char *ident;		/* C identifier for the program name */
67    char *srcdir;
68    char *objdir;
69    char *objvar;		/* Makefile variable to replace OBJS */
70    strlst_t *objs, *objpaths;
71    strlst_t *buildopts;
72    strlst_t *keeplist;
73    strlst_t *links;
74    int goterror;
75} prog_t;
76
77
78/* global state */
79
80strlst_t *srcdirs = NULL;
81strlst_t *libs    = NULL;
82prog_t   *progs   = NULL;
83
84char line[MAXLINELEN];
85
86char confname[MAXPATHLEN], infilename[MAXPATHLEN];
87char outmkname[MAXPATHLEN], outcfname[MAXPATHLEN], execfname[MAXPATHLEN];
88char tempfname[MAXPATHLEN], cachename[MAXPATHLEN], curfilename[MAXPATHLEN];
89char outhdrname[MAXPATHLEN] ; /* user-supplied header for *.mk */
90int linenum = -1;
91int goterror = 0;
92
93int verbose, readcache;	/* options */
94int reading_cache;
95int makeobj = 0;	/* add 'make obj' rules to the makefile */
96
97int list_mode;
98
99/* general library routines */
100
101void status(char *str);
102void out_of_memory(void);
103void add_string(strlst_t **listp, char *str);
104int is_dir(char *pathname);
105int is_nonempty_file(char *pathname);
106
107/* helper routines for main() */
108
109void usage(void);
110void parse_conf_file(void);
111void gen_outputs(void);
112
113
114int main(int argc, char **argv)
115{
116    char *p;
117    int optc;
118
119    verbose = 1;
120    readcache = 1;
121    *outmkname = *outcfname = *execfname = '\0';
122
123    while((optc = getopt(argc, argv, "lh:m:c:e:foq")) != -1) {
124	switch(optc) {
125	case 'f':	readcache = 0; break;
126	case 'o':	makeobj = 1; break;
127	case 'q':	verbose = 0; break;
128
129	case 'm':	strcpy(outmkname, optarg); break;
130	case 'h':	strcpy(outhdrname, optarg); break;
131	case 'c':	strcpy(outcfname, optarg); break;
132	case 'e':	strcpy(execfname, optarg); break;
133	case 'l':	list_mode++; verbose = 0; break;
134
135	case '?':
136	default:	usage();
137	}
138    }
139
140    argc -= optind;
141    argv += optind;
142
143    if(argc != 1) usage();
144
145    /*
146     * generate filenames
147     */
148
149    strcpy(infilename, argv[0]);
150
151    /* confname = `basename infilename .conf` */
152
153    if((p=strrchr(infilename, '/')) != NULL) strcpy(confname, p+1);
154    else strcpy(confname, infilename);
155    if((p=strrchr(confname, '.')) != NULL && !strcmp(p, ".conf")) *p = '\0';
156
157    if(!*outmkname) sprintf(outmkname, "%s.mk", confname);
158    if(!*outcfname) sprintf(outcfname, "%s.c", confname);
159    if(!*execfname) sprintf(execfname, "%s", confname);
160
161    sprintf(cachename, "%s.cache", confname);
162    sprintf(tempfname, ".tmp_%sXXXXXX", confname);
163    if(mktemp(tempfname) == NULL) {
164	perror(tempfname);
165	exit(1);
166    }
167
168    parse_conf_file();
169    if (list_mode)
170	exit(goterror);
171
172    gen_outputs();
173
174    exit(goterror);
175}
176
177
178void usage(void)
179{
180    fprintf(stderr, "%s\n%s\n",
181		"usage: crunchgen [-foq] [-m <makefile>] [-c <c file>]",
182		"                 [-e <exec file>] <conffile>");
183    exit(1);
184}
185
186
187/*
188 * ========================================================================
189 * parse_conf_file subsystem
190 *
191 */
192
193/* helper routines for parse_conf_file */
194
195void parse_one_file(char *filename);
196void parse_line(char *line, int *fc, char **fv, int nf);
197void add_srcdirs(int argc, char **argv);
198void add_progs(int argc, char **argv);
199void add_link(int argc, char **argv);
200void add_libs(int argc, char **argv);
201void add_special(int argc, char **argv);
202
203prog_t *find_prog(char *str);
204void add_prog(char *progname);
205
206
207void parse_conf_file(void)
208{
209    if(!is_nonempty_file(infilename))
210		errx(1, "fatal: input file \"%s\" not found", infilename);
211    parse_one_file(infilename);
212    if(readcache && is_nonempty_file(cachename)) {
213	reading_cache = 1;
214	parse_one_file(cachename);
215    }
216}
217
218
219void parse_one_file(char *filename)
220{
221    char *fieldv[MAXFIELDS];
222    int fieldc;
223    void (*f)(int c, char **v);
224    FILE *cf;
225
226    sprintf(line, "reading %s", filename);
227    status(line);
228    strcpy(curfilename, filename);
229
230    if((cf = fopen(curfilename, "r")) == NULL) {
231	warn("%s", curfilename);
232	goterror = 1;
233	return;
234    }
235
236    linenum = 0;
237    while(fgets(line, MAXLINELEN, cf) != NULL) {
238	linenum++;
239	parse_line(line, &fieldc, fieldv, MAXFIELDS);
240	if(fieldc < 1) continue;
241	if(!strcmp(fieldv[0], "srcdirs"))	f = add_srcdirs;
242	else if(!strcmp(fieldv[0], "progs"))    f = add_progs;
243	else if(!strcmp(fieldv[0], "ln"))	f = add_link;
244	else if(!strcmp(fieldv[0], "libs"))	f = add_libs;
245	else if(!strcmp(fieldv[0], "special"))	f = add_special;
246	else {
247	    warnx("%s:%d: skipping unknown command `%s'",
248		    curfilename, linenum, fieldv[0]);
249	    goterror = 1;
250	    continue;
251	}
252	if(fieldc < 2) {
253	    warnx("%s:%d: %s command needs at least 1 argument, skipping",
254		    curfilename, linenum, fieldv[0]);
255	    goterror = 1;
256	    continue;
257	}
258	f(fieldc, fieldv);
259    }
260
261    if(ferror(cf)) {
262	warn("%s", curfilename);
263	goterror = 1;
264    }
265    fclose(cf);
266}
267
268
269void parse_line(char *line, int *fc, char **fv, int nf)
270{
271    char *p;
272
273    p = line;
274    *fc = 0;
275    while(1) {
276	while(isspace(*p)) p++;
277	if(*p == '\0' || *p == '#') break;
278
279	if(*fc < nf) fv[(*fc)++] = p;
280	while(*p && !isspace(*p) && *p != '#') p++;
281	if(*p == '\0' || *p == '#') break;
282	*p++ = '\0';
283    }
284    if(*p) *p = '\0';		/* needed for '#' case */
285}
286
287
288void add_srcdirs(int argc, char **argv)
289{
290    int i;
291
292    for(i=1;i<argc;i++) {
293	if(is_dir(argv[i]))
294	    add_string(&srcdirs, argv[i]);
295	else {
296	    warnx("%s:%d: `%s' is not a directory, skipping it",
297		    curfilename, linenum, argv[i]);
298	    goterror = 1;
299	}
300    }
301}
302
303
304void add_progs(int argc, char **argv)
305{
306    int i;
307
308    for(i=1;i<argc;i++)
309	add_prog(argv[i]);
310}
311
312
313void add_prog(char *progname)
314{
315    prog_t *p1, *p2;
316
317    /* add to end, but be smart about dups */
318
319    for(p1 = NULL, p2 = progs; p2 != NULL; p1 = p2, p2 = p2->next)
320	if(!strcmp(p2->name, progname)) return;
321
322    p2 = malloc(sizeof(prog_t));
323    if(p2) {
324	memset(p2, 0, sizeof(prog_t));
325	p2->name = strdup(progname);
326    }
327    if(!p2 || !p2->name)
328	out_of_memory();
329
330    p2->next = NULL;
331    if(p1 == NULL) progs = p2;
332    else p1->next = p2;
333
334    p2->ident = p2->srcdir = p2->objdir = NULL;
335    p2->links = p2->objs = p2->keeplist = NULL;
336    p2->buildopts = NULL;
337    p2->goterror = 0;
338    if (list_mode)
339        printf("%s\n",progname);
340}
341
342
343void add_link(int argc, char **argv)
344{
345    int i;
346    prog_t *p = find_prog(argv[1]);
347
348    if(p == NULL) {
349	warnx("%s:%d: no prog %s previously declared, skipping link",
350		curfilename, linenum, argv[1]);
351	goterror = 1;
352	return;
353    }
354    for(i=2;i<argc;i++) {
355	if (list_mode)
356		printf("%s\n",argv[i]);
357	add_string(&p->links, argv[i]);
358    }
359}
360
361
362void add_libs(int argc, char **argv)
363{
364    int i;
365
366    for(i=1;i<argc;i++)
367	add_string(&libs, argv[i]);
368}
369
370
371void add_special(int argc, char **argv)
372{
373    int i;
374    prog_t *p = find_prog(argv[1]);
375
376    if(p == NULL) {
377	if(reading_cache) return;
378	warnx("%s:%d: no prog %s previously declared, skipping special",
379		curfilename, linenum, argv[1]);
380	goterror = 1;
381	return;
382    }
383
384    if(!strcmp(argv[2], "ident")) {
385	if(argc != 4) goto argcount;
386	if((p->ident = strdup(argv[3])) == NULL)
387	    out_of_memory();
388    }
389    else if(!strcmp(argv[2], "srcdir")) {
390	if(argc != 4) goto argcount;
391	if((p->srcdir = strdup(argv[3])) == NULL)
392	    out_of_memory();
393    }
394    else if(!strcmp(argv[2], "objdir")) {
395	if(argc != 4) goto argcount;
396	if((p->objdir = strdup(argv[3])) == NULL)
397	    out_of_memory();
398    }
399    else if(!strcmp(argv[2], "objs")) {
400	p->objs = NULL;
401	for(i=3;i<argc;i++)
402	    add_string(&p->objs, argv[i]);
403    }
404    else if(!strcmp(argv[2], "objpaths")) {
405	p->objpaths = NULL;
406	for(i=3;i<argc;i++)
407	    add_string(&p->objpaths, argv[i]);
408    }
409    else if(!strcmp(argv[2], "keep")) {
410	p->keeplist = NULL;
411	for(i=3;i<argc;i++)
412	    add_string(&p->keeplist, argv[i]);
413    }
414    else if(!strcmp(argv[2], "objvar")) {
415	if(argc != 4)
416	    goto argcount;
417	if((p->objvar = strdup(argv[3])) == NULL)
418	    out_of_memory();
419    }
420    else if (!strcmp(argv[2], "buildopts")) {
421	p->buildopts = NULL;
422	for (i = 3; i < argc; i++)
423		add_string(&p->buildopts, argv[i]);
424    }
425    else {
426	warnx("%s:%d: bad parameter name `%s', skipping line",
427		curfilename, linenum, argv[2]);
428	goterror = 1;
429    }
430    return;
431
432
433 argcount:
434    warnx("%s:%d: too %s arguments, expected \"special %s %s <string>\"",
435	    curfilename, linenum, argc < 4? "few" : "many", argv[1], argv[2]);
436    goterror = 1;
437}
438
439
440prog_t *find_prog(char *str)
441{
442    prog_t *p;
443
444    for(p = progs; p != NULL; p = p->next)
445	if(!strcmp(p->name, str)) return p;
446
447    return NULL;
448}
449
450
451/*
452 * ========================================================================
453 * gen_outputs subsystem
454 *
455 */
456
457/* helper subroutines */
458
459void remove_error_progs(void);
460void fillin_program(prog_t *p);
461void gen_specials_cache(void);
462void gen_output_makefile(void);
463void gen_output_cfile(void);
464
465void fillin_program_objs(prog_t *p, char *path);
466void top_makefile_rules(FILE *outmk);
467void prog_makefile_rules(FILE *outmk, prog_t *p);
468void output_strlst(FILE *outf, strlst_t *lst);
469char *genident(char *str);
470char *dir_search(char *progname);
471
472
473void gen_outputs(void)
474{
475    prog_t *p;
476
477    for(p = progs; p != NULL; p = p->next)
478	fillin_program(p);
479
480    remove_error_progs();
481    gen_specials_cache();
482    gen_output_cfile();
483    gen_output_makefile();
484    status("");
485    fprintf(stderr,
486	    "Run \"make -f %s objs exe\" to build crunched binary.\n",
487	    outmkname);
488}
489
490/*
491 * run the makefile for the program to find which objects are necessary
492 */
493void fillin_program(prog_t *p)
494{
495    char path[MAXPATHLEN];
496    char *srcparent;
497    strlst_t *s;
498
499    sprintf(line, "filling in parms for %s", p->name);
500    status(line);
501
502    if(!p->ident)
503	p->ident = genident(p->name);
504    if(!p->srcdir) {
505	srcparent = dir_search(p->name);
506	if(srcparent)
507	    sprintf(path, "%s/%s", srcparent, p->name);
508	if(is_dir(path))
509	    p->srcdir = strdup(path);
510    }
511    if(!p->objdir && p->srcdir) {
512	FILE *f;
513
514	sprintf(path, "cd %s && echo -n /usr/obj`/bin/pwd`", p->srcdir);
515        p->objdir = p->srcdir;
516	f = popen(path,"r");
517	if (f) {
518	    fgets(path,sizeof path, f);
519	    if (!pclose(f)) {
520		if(is_dir(path))
521		    p->objdir = strdup(path);
522	    }
523	}
524    }
525/*
526 * XXX look for a Makefile.{name} in local directory first.
527 * This lets us override the original Makefile.
528 */
529    sprintf(path, "Makefile.%s", p->name);
530    if (is_nonempty_file(path)) {
531       sprintf(line, "Using %s for %s", path, p->name);
532       status(line);
533    } else
534    if(p->srcdir) sprintf(path, "%s/Makefile", p->srcdir);
535    if(!p->objs && p->srcdir && is_nonempty_file(path))
536	fillin_program_objs(p, path);
537
538    if(!p->objpaths && p->objdir && p->objs)
539	for(s = p->objs; s != NULL; s = s->next) {
540	    sprintf(line, "%s/%s", p->objdir, s->str);
541	    add_string(&p->objpaths, line);
542	}
543
544    if(!p->srcdir && verbose)
545	warnx("%s: %s: warning: could not find source directory",
546		infilename, p->name);
547    if(!p->objs && verbose)
548	warnx("%s: %s: warning: could not find any .o files",
549		infilename, p->name);
550
551    if(!p->objpaths) {
552	warnx("%s: %s: error: no objpaths specified or calculated",
553		infilename, p->name);
554	p->goterror = goterror = 1;
555    }
556}
557
558void fillin_program_objs(prog_t *p, char *path)
559{
560    char *obj, *cp;
561    int rc;
562    FILE *f;
563    char *objvar="OBJS";
564    strlst_t *s;
565
566    /* discover the objs from the srcdir Makefile */
567
568    if((f = fopen(tempfname, "w")) == NULL) {
569	warn("%s", tempfname);
570	goterror = 1;
571	return;
572    }
573    if (p->objvar)
574	objvar = p->objvar ;
575
576    /*
577     * XXX include outhdrname (e.g. to contain Make variables)
578     */
579    if (outhdrname[0] != '\0')
580	fprintf(f, ".include \"%s\"\n", outhdrname);
581    fprintf(f, ".include \"%s\"\n", path);
582    fprintf(f, ".if defined(PROG) && !defined(%s)\n", objvar);
583    fprintf(f, "%s=${PROG}.o\n", objvar);
584    fprintf(f, ".endif\n");
585    fprintf(f, "loop:\n\t@echo 'OBJS= '${%s}\n", objvar);
586
587    fprintf(f, "crunchgen_objs:\n\t@make -f %s $(OPTS) $(%s_OPTS)",
588	tempfname, p->ident);
589    for (s = p->buildopts; s != NULL; s = s->next)
590        fprintf(f, " %s", s->str);
591    fprintf(f, " loop\n");
592
593    fclose(f);
594
595    sprintf(line, "make -f %s crunchgen_objs 2>&1", tempfname);
596    if((f = popen(line, "r")) == NULL) {
597	warn("submake pipe");
598	goterror = 1;
599	return;
600    }
601
602    while(fgets(line, MAXLINELEN, f)) {
603	if(strncmp(line, "OBJS= ", 6)) {
604	    warnx("make error: %s", line);
605	    goterror = 1;
606	    continue;
607	}
608	cp = line + 6;
609	while(isspace(*cp)) cp++;
610	while(*cp) {
611	    obj = cp;
612	    while(*cp && !isspace(*cp)) cp++;
613	    if(*cp) *cp++ = '\0';
614	    add_string(&p->objs, obj);
615	    while(isspace(*cp)) cp++;
616	}
617    }
618    if((rc=pclose(f)) != 0) {
619	warnx("make error: make returned %d", rc);
620	goterror = 1;
621    }
622    unlink(tempfname);
623}
624
625void remove_error_progs(void)
626{
627    prog_t *p1, *p2;
628
629    p1 = NULL; p2 = progs;
630    while(p2 != NULL) {
631	if(!p2->goterror)
632	    p1 = p2, p2 = p2->next;
633	else {
634	    /* delete it from linked list */
635	    warnx("%s: %s: ignoring program because of errors",
636		    infilename, p2->name);
637	    if(p1) p1->next = p2->next;
638	    else progs = p2->next;
639	    p2 = p2->next;
640	}
641    }
642}
643
644void gen_specials_cache(void)
645{
646    FILE *cachef;
647    prog_t *p;
648
649    sprintf(line, "generating %s", cachename);
650    status(line);
651
652    if((cachef = fopen(cachename, "w")) == NULL) {
653	warn("%s", cachename);
654	goterror = 1;
655	return;
656    }
657
658    fprintf(cachef, "# %s - parm cache generated from %s by crunchgen %s\n\n",
659	    cachename, infilename, CRUNCH_VERSION);
660
661    for(p = progs; p != NULL; p = p->next) {
662	fprintf(cachef, "\n");
663	if(p->srcdir)
664	    fprintf(cachef, "special %s srcdir %s\n", p->name, p->srcdir);
665	if(p->objdir)
666	    fprintf(cachef, "special %s objdir %s\n", p->name, p->objdir);
667	if(p->objs) {
668	    fprintf(cachef, "special %s objs", p->name);
669	    output_strlst(cachef, p->objs);
670	}
671	fprintf(cachef, "special %s objpaths", p->name);
672	output_strlst(cachef, p->objpaths);
673    }
674    fclose(cachef);
675}
676
677
678void gen_output_makefile(void)
679{
680    prog_t *p;
681    FILE *outmk;
682
683    sprintf(line, "generating %s", outmkname);
684    status(line);
685
686    if((outmk = fopen(outmkname, "w")) == NULL) {
687	warn("%s", outmkname);
688	goterror = 1;
689	return;
690    }
691
692    fprintf(outmk, "# %s - generated from %s by crunchgen %s\n\n",
693	    outmkname, infilename, CRUNCH_VERSION);
694    if (outhdrname[0] != '\0')
695	fprintf(outmk, ".include \"%s\"\n", outhdrname);
696
697    top_makefile_rules(outmk);
698
699    for(p = progs; p != NULL; p = p->next)
700	prog_makefile_rules(outmk, p);
701
702    fprintf(outmk, "\n# ========\n");
703    fclose(outmk);
704}
705
706
707void gen_output_cfile(void)
708{
709    extern char *crunched_skel[];
710    char **cp;
711    FILE *outcf;
712    prog_t *p;
713    strlst_t *s;
714
715    sprintf(line, "generating %s", outcfname);
716    status(line);
717
718    if((outcf = fopen(outcfname, "w")) == NULL) {
719	warn("%s", outcfname);
720	goterror = 1;
721	return;
722    }
723
724    fprintf(outcf,
725	  "/* %s - generated from %s by crunchgen %s */\n",
726	    outcfname, infilename, CRUNCH_VERSION);
727
728    fprintf(outcf, "#define EXECNAME \"%s\"\n", execfname);
729    for(cp = crunched_skel; *cp != NULL; cp++)
730	fprintf(outcf, "%s\n", *cp);
731
732    for(p = progs; p != NULL; p = p->next)
733	fprintf(outcf, "extern int _crunched_%s_stub();\n", p->ident);
734
735    fprintf(outcf, "\nstruct stub entry_points[] = {\n");
736    for(p = progs; p != NULL; p = p->next) {
737	fprintf(outcf, "\t{ \"%s\", _crunched_%s_stub },\n",
738		p->name, p->ident);
739	for(s = p->links; s != NULL; s = s->next)
740	    fprintf(outcf, "\t{ \"%s\", _crunched_%s_stub },\n",
741		    s->str, p->ident);
742    }
743
744    fprintf(outcf, "\t{ EXECNAME, crunched_main },\n");
745    fprintf(outcf, "\t{ NULL, NULL }\n};\n");
746    fclose(outcf);
747}
748
749
750char *genident(char *str)
751{
752    char *n,*s,*d;
753
754    /*
755     * generates a Makefile/C identifier from a program name, mapping '-' to
756     * '_' and ignoring all other non-identifier characters.  This leads to
757     * programs named "foo.bar" and "foobar" to map to the same identifier.
758     */
759
760    if((n = strdup(str)) == NULL)
761	return NULL;
762    for(d = s = n; *s != '\0'; s++) {
763	if(*s == '-') *d++ = '_';
764	else if(*s == '_' || isalnum(*s)) *d++ = *s;
765    }
766    *d = '\0';
767    return n;
768}
769
770
771char *dir_search(char *progname)
772{
773    char path[MAXPATHLEN];
774    strlst_t *dir;
775
776    for(dir=srcdirs; dir != NULL; dir=dir->next) {
777	sprintf(path, "%s/%s", dir->str, progname);
778	if(is_dir(path)) return dir->str;
779    }
780    return NULL;
781}
782
783
784void top_makefile_rules(FILE *outmk)
785{
786    prog_t *p;
787
788    fprintf(outmk, "LIBS=");
789    output_strlst(outmk, libs);
790
791    fprintf(outmk, "CRUNCHED_OBJS=");
792    for(p = progs; p != NULL; p = p->next)
793	fprintf(outmk, " %s.lo", p->name);
794    fprintf(outmk, "\n");
795
796    fprintf(outmk, "SUBMAKE_TARGETS=");
797    for(p = progs; p != NULL; p = p->next)
798	fprintf(outmk, " %s_make", p->ident);
799    fprintf(outmk, "\nSUBCLEAN_TARGETS=");
800    for(p = progs; p != NULL; p = p->next)
801	fprintf(outmk, " %s_clean", p->ident);
802    fprintf(outmk, "\n\n");
803
804    fprintf(outmk, "%s: %s.o $(CRUNCHED_OBJS)\n",
805	    execfname, execfname);
806    fprintf(outmk, "\t$(CC) -static -o %s %s.o $(CRUNCHED_OBJS) $(LIBS)\n",
807	    execfname, execfname);
808    fprintf(outmk, "\tstrip %s\n", execfname);
809    fprintf(outmk, "all: objs exe\nobjs: $(SUBMAKE_TARGETS)\n");
810    fprintf(outmk, "exe: %s\n", execfname);
811    fprintf(outmk, "realclean: clean subclean\n");
812    fprintf(outmk, "clean:\n\trm -f %s *.lo *.o *_stub.c\n",
813	    execfname);
814    fprintf(outmk, "subclean: $(SUBCLEAN_TARGETS)\n");
815}
816
817
818void prog_makefile_rules(FILE *outmk, prog_t *p)
819{
820    strlst_t *lst;
821
822    fprintf(outmk, "\n# -------- %s\n\n", p->name);
823
824    if(p->srcdir && p->objs) {
825	fprintf(outmk, "%s_SRCDIR=%s\n", p->ident, p->srcdir);
826	fprintf(outmk, "%s_OBJS=", p->ident);
827	output_strlst(outmk, p->objs);
828	if (p->buildopts != NULL) {
829		fprintf(outmk, "%s_OPTS+=", p->ident);
830		output_strlst(outmk, p->buildopts);
831	}
832	fprintf(outmk, "%s_make:\n", p->ident);
833	fprintf(outmk, "\t(cd $(%s_SRCDIR) && ", p->ident);
834	if (makeobj)
835		fprintf(outmk, "make obj && ");
836	fprintf(outmk, "\\\n");
837	fprintf(outmk, "\t\tmake $(OPTS) $(%s_OPTS) depend && \\\n"
838		"\t\tmake $(OPTS) $(%s_OPTS) $(%s_OBJS))\n",
839		p->ident, p->ident, p->ident);
840	fprintf(outmk, "%s_clean:\n", p->ident);
841	fprintf(outmk, "\t(cd $(%s_SRCDIR) && make clean)\n\n", p->ident);
842    }
843    else
844	fprintf(outmk, "%s_make:\n\t@echo \"** cannot make objs for %s\"\n\n",
845		p->ident, p->name);
846
847    fprintf(outmk,   "%s_OBJPATHS=", p->ident);
848    output_strlst(outmk, p->objpaths);
849
850    fprintf(outmk, "%s_stub.c:\n", p->name);
851    fprintf(outmk, "\techo \""
852	           "int _crunched_%s_stub(int argc, char **argv, char **envp)"
853	           "{return main(argc,argv,envp);}\" >%s_stub.c\n",
854	    p->ident, p->name);
855    fprintf(outmk, "%s.lo: %s_stub.o $(%s_OBJPATHS)\n",
856	    p->name, p->name, p->ident);
857    fprintf(outmk, "\tld -dc -r -o %s.lo %s_stub.o $(%s_OBJPATHS)\n",
858	    p->name, p->name, p->ident);
859    fprintf(outmk, "\tcrunchide -k _crunched_%s_stub ", p->ident);
860    for(lst = p->keeplist; lst != NULL; lst = lst->next)
861      fprintf(outmk, "-k _%s ", lst->str);
862    fprintf(outmk, "%s.lo\n", p->name);
863}
864
865void output_strlst(FILE *outf, strlst_t *lst)
866{
867    for(; lst != NULL; lst = lst->next)
868	fprintf(outf, " %s", lst->str);
869    fprintf(outf, "\n");
870}
871
872
873/*
874 * ========================================================================
875 * general library routines
876 *
877 */
878
879void status(char *str)
880{
881    static int lastlen = 0;
882    int len, spaces;
883
884    if(!verbose) return;
885
886    len = strlen(str);
887    spaces = lastlen - len;
888    if(spaces < 1) spaces = 1;
889
890    fprintf(stderr, " [%s]%*.*s\r", str, spaces, spaces, " ");
891    fflush(stderr);
892    lastlen = len;
893}
894
895
896void out_of_memory(void)
897{
898    errx(1, "%s: %d: out of memory, stopping", infilename, linenum);
899}
900
901
902void add_string(strlst_t **listp, char *str)
903{
904    strlst_t *p1, *p2;
905
906    /* add to end, but be smart about dups */
907
908    for(p1 = NULL, p2 = *listp; p2 != NULL; p1 = p2, p2 = p2->next)
909	if(!strcmp(p2->str, str)) return;
910
911    p2 = malloc(sizeof(strlst_t));
912    if(p2) {
913	memset(p2, 0, sizeof(strlst_t));
914	p2->str = strdup(str);
915    }
916    if(!p2 || !p2->str)
917	out_of_memory();
918
919    p2->next = NULL;
920    if(p1 == NULL) *listp = p2;
921    else p1->next = p2;
922}
923
924
925int is_dir(char *pathname)
926{
927    struct stat buf;
928
929    if(stat(pathname, &buf) == -1)
930	return 0;
931    return S_ISDIR(buf.st_mode);
932}
933
934int is_nonempty_file(char *pathname)
935{
936    struct stat buf;
937
938    if(stat(pathname, &buf) == -1)
939	return 0;
940
941    return S_ISREG(buf.st_mode) && buf.st_size > 0;
942}
943