/* Copyright (C) 1993, 1996, 1997, 1998, 1999, 2000 artofcode LLC. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA, 02111-1307. */ /*$Id: genconf.c,v 1.4.4.1.2.1 2003/01/17 00:49:02 giles Exp $ */ /* Generate configuration files */ #include "stdpre.h" #include #include #include #include /* for calloc */ #include /* * This program reads .dev files, which contain definitions of modules, * and generates merged configuration files. * * A .dev file specifies a list of "resources" of various kinds (.obj/.o * files, other .dev files to be merged in, PostScript files, driver names, * compiled fonts, etc.) that make up the module. (This is similar in * spirit to the Macintosh resource fork, and to PostScript resources, but * has nothing to do directly with the latter.) * * A .dev file consists of a series of switches and resource names. Most * switches specifies the type of the following resources, until the next * type switch; there are also a few other switches. * * genconf recognizes the following resource type switches in .dev files. * See the next section on command line switches for the meaning of * . * * -dev * * Adds device_(_device) to . * Used for ordinary devices. * * -dev2 * * Adds device2_(_device) to . * Used for some newer devices with potentially different structures * or conventions. * * -emulator * * Adds emulator_("",), where len is the number of * characters in , to . * Used for names that should appear as instances of the PostScript * Emulator resource category. * * -font * * Adds an entry to , as described below. * * -functiontype * * Adds function_type_(,build_function_) * to . * Used for instances of the PostScript FunctionType resource category, * and also to generate a table of procedures for processing * Function dictionaries. * * -halftone * * Adds halftone_(dht_) to . * Used for halftones that will be compiled into the executable. * * -imageclass * * Adds image_class_(image_class_) to . * Used internally for the various cases of rendering images. * * -imagetype * * Adds image_type_(,image_type_) to * . * Used for instances of the PostScript ImageType resource category, * and also to generate a table of procedures for handling the various * types of images. * * -include * * Reads the file named (or .dev if doesn't * end with ".dev") as though it were part of the current .dev file. * Used when one module requires the presence of another. * * -init * * Adds init_(_init) to . * Used for initialization procedures to be called when Ghostscript * is started. By convention, is (almost always) the name * of the source file in which the procedure is defined. * * -iodev * * Adds io_device_(iodev_) to . * Used for instances of the PostScript IODevice resource category. * * -lib * * Adds to the list of libraries to be written by -l. * * -libpath * * Adds to the list of library paths to be written by -l. * * -link * * Adds to the list of switches to be passed to the linker, * to be written by -l. * * -obj * * Adds to the list of files to be linked, to be written by -o. * * -oper * * Adds oper_(_op_defs) to . * Used for tables of PostScript operators. By convention, * is (usually) the name of the source file in which the * table appears. * * -ps * * Adds psfile_(".ps",), where is the number of * character in , to . * Used for PostScript files that should be read at initialization. * * genconf recognizes the following other switches in .dev files: * * -replace * * This switch declares that has been replaced by another * module (presumably the one defined by the .dev file in which the * switch appears), and should be removed from consideration. * Modules that can be -replaced should not -include other modules. * * genconf writes the following files if the corresponding switch is used: * * -h * * Writes a list of all the resources as described above. Each * sublist is surrounded by an #ifdef/#endif in case the * referenced macro (e.g., device_, oper_) is undefined. * Duplicates are eliminated. * * -f * * Writes a list of all the -font resources, in the form * font_("0.font_",f_,zf_] * * Prefixes to the names of all .dev files read in, * and to the names of all .obj/.o files written in . * The default file_prefix is the empty string. * This switch should probably be removed, since nothing in the * current Ghostscript makefiles uses it. * * -e * * Sets the escape character for patterns. See below. * * -n [ | -] * * Prefixes , or the empty string, to certain items in * the output, as indicated above. * The default name_prefix is "gs_". * * -p[l|L][u][e] * * Sets the pattern (format string) used for writing certain items in * the output, as indicated above. '%' in the pattern indicates * substitution of the variable data, as for printf except that the * '%' is not followed by a format character -- the data are always * inserted literally. See below for more information about patterns. * * -l[o] * * Writes the list of library paths, links, and library file names * on . -lo also writes the list of object file names, * as for -o. * * -o[l] * * Writes the list object file names on . -ol also writes * the list of library paths, links, and library file names as for -l. * * Usage summary: * * genconf [-Z] [-C prefix] [-e escapechar] [-n [name_prefix | -]] * [@]xxx.dev* [-f gconfigf.h] [-h gconfig.h] * [-p[l|L][u][e] pattern] [-l|o|lo|ol lib.tr] * * Patterns: * * The default escape character is &. When this character appears in a * pattern, it acts as follows: * &p produces a %; * &s produces a space; * && (i.e., the escape character twice) produces a \; * &- produces a -; * &x, for any other character x, is an error. */ #define DEFAULT_FILE_PREFIX "" #define DEFAULT_NAME_PREFIX "gs_" #define MAX_STR 120 /* Structures for accumulating information. */ typedef struct string_item_s { const char *str; int file_index; /* index of file containing this item */ int index; } string_item_t; /* The values of uniq_mode are bit masks. */ typedef enum { uniq_all = 1, /* keep all occurrences (default) */ uniq_first = 2, /* keep only first occurrence */ uniq_last = 4 /* keep only last occurrence */ } uniq_mode_t; typedef struct string_list_s { /* The following are set at creation time. */ const char *list_name; /* only for debugging */ int max_count; uniq_mode_t mode; /* The following are updated dynamically. */ int count; string_item_t *items; } string_list_t; #define MAX_PATTERN 60 typedef struct string_pattern_s { bool upper_case; bool drop_extn; char pattern[MAX_PATTERN + 1]; } string_pattern_t; typedef struct config_s { int debug; const char *name_prefix; const char *file_prefix; /* Special "resources" */ string_list_t file_names; string_list_t file_contents; string_list_t replaces; /* Real resources */ union ru_ { struct nu_ { /* These names must parallel config_lists below. */ string_list_t sorted_resources; string_list_t resources; string_list_t devs; /* also includes devs2 */ string_list_t fonts; string_list_t libs; string_list_t libpaths; string_list_t links; string_list_t objs; } named; #define NUM_RESOURCE_LISTS 8 string_list_t indexed[NUM_RESOURCE_LISTS]; } lists; string_pattern_t lib_p; string_pattern_t libpath_p; string_pattern_t obj_p; } config_t; /* * These lists grow automatically if needed, so we could start out with * small allocations. */ static const config_t init_config = { 0, /* debug */ DEFAULT_NAME_PREFIX, /* name_prefix */ DEFAULT_FILE_PREFIX, /* file_prefix */ {"file name", 200}, /* file_names */ {"file contents", 200}, /* file_contents */ {"-replace", 50} }; static const string_list_t init_config_lists[] = { {"resource", 100, uniq_first}, {"sorted_resource", 20, uniq_first}, {"-dev", 100, uniq_first}, {"-font", 50, uniq_first}, {"-lib", 20, uniq_last}, {"-libpath", 10, uniq_first}, {"-link", 10, uniq_first}, {"-obj", 500, uniq_first} }; /* Forward definitions */ private void *mrealloc(P3(void *, size_t, size_t)); int alloc_list(P1(string_list_t *)); void dev_file_name(P1(char *)); int process_replaces(P1(config_t *)); int read_dev(P2(config_t *, const char *)); int read_token(P3(char *, int, const char **)); int add_entry(P4(config_t *, char *, const char *, int)); string_item_t *add_item(P3(string_list_t *, const char *, int)); void sort_uniq(P2(string_list_t *, bool)); void write_list(P3(FILE *, const string_list_t *, const char *)); void write_list_pattern(P3(FILE *, const string_list_t *, const string_pattern_t *)); bool var_expand(P3(char *, char [MAX_STR], const config_t *)); void add_definition(P4(const char *, const char *, string_list_t *, bool)); string_item_t *lookup(P2(const char *, const string_list_t *)); int main(int argc, char *argv[]) { config_t conf; char escape = '&'; int i; /* Allocate string lists. */ conf = init_config; memcpy(conf.lists.indexed, init_config_lists, sizeof(conf.lists.indexed)); alloc_list(&conf.file_names); alloc_list(&conf.file_contents); alloc_list(&conf.replaces); for (i = 0; i < NUM_RESOURCE_LISTS; ++i) alloc_list(&conf.lists.indexed[i]); /* Initialize patterns. */ conf.lib_p.upper_case = false; conf.lib_p.drop_extn = false; strcpy(conf.lib_p.pattern, "%s\n"); conf.libpath_p = conf.lib_p; conf.obj_p = conf.lib_p; /* Process command line arguments. */ for (i = 1; i < argc; i++) { const char *arg = argv[i]; FILE *out; int lib = 0, obj = 0; if (*arg != '-') { read_dev(&conf, arg); continue; } if (i == argc - 1) { fprintf(stderr, "Missing argument after %s.\n", arg); exit(1); } switch (arg[1]) { case 'C': /* change directory, by analogy with make */ conf.file_prefix = (argv[i + 1][0] == '-' ? "" : argv[i + 1]); ++i; continue; case 'e': escape = argv[i + 1][0]; ++i; continue; case 'n': conf.name_prefix = (argv[i + 1][0] == '-' ? "" : argv[i + 1]); ++i; continue; case 'p': { string_pattern_t *pat; switch (*(arg += 2)) { case 'l': pat = &conf.lib_p; break; case 'L': pat = &conf.libpath_p; break; default: pat = &conf.obj_p; arg--; } pat->upper_case = false; pat->drop_extn = false; if (argv[i + 1][0] == '-') strcpy(pat->pattern, "%s\n"); else { char *p, *q; for (p = pat->pattern, q = argv[++i]; (*p++ = *q++) != 0; ) if (p[-1] == escape) switch (*q) { case 'p': p[-1] = '%'; q++; break; case 's': p[-1] = ' '; q++; break; case '-': p[-1] = '-'; q++; break; default: if (*q == escape) { p[-1] = '\\'; q++; break; } fprintf(stderr, "%c not followed by p|s|%c|-: &%c\n", escape, escape, *q); exit(1); } p[-1] = '\n'; *p = 0; } for (;;) { switch (*++arg) { case 'u': pat->upper_case = true; break; case 'e': pat->drop_extn = true; break; case 0: goto pbreak; default: fprintf(stderr, "Unknown switch %s.\n", arg); exit(1); } } pbreak:if (pat == &conf.obj_p) { conf.lib_p = *pat; conf.libpath_p = *pat; } continue; } case 'Z': conf.debug = 1; continue; } /* Must be an output file. */ out = fopen(argv[++i], "w"); if (out == 0) { fprintf(stderr, "Can't open %s for output.\n", argv[i]); exit(1); } switch (arg[1]) { case 'f': process_replaces(&conf); fputs("/* This file was generated automatically by genconf.c. */\n", out); fputs("/* For documentation, see gsconfig.c. */\n", out); { char template[80]; sprintf(template, "font_(\"0.font_%%s\",%sf_%%s,zf_%%s)\n", conf.name_prefix); write_list(out, &conf.lists.named.fonts, template); } break; case 'h': process_replaces(&conf); fputs("/* This file was generated automatically by genconf.c. */\n", out); write_list(out, &conf.lists.named.devs, "%s\n"); sort_uniq(&conf.lists.named.resources, true); write_list(out, &conf.lists.named.resources, "%s\n"); sort_uniq(&conf.lists.named.sorted_resources, false); write_list(out, &conf.lists.named.sorted_resources, "%s\n"); break; case 'l': lib = 1; obj = arg[2] == 'o'; goto lo; case 'o': obj = 1; lib = arg[2] == 'l'; lo:process_replaces(&conf); if (obj) { sort_uniq(&conf.lists.named.objs, true); write_list_pattern(out, &conf.lists.named.objs, &conf.obj_p); } if (lib) { sort_uniq(&conf.lists.named.libs, true); sort_uniq(&conf.lists.named.links, true); write_list_pattern(out, &conf.lists.named.libpaths, &conf.libpath_p); write_list_pattern(out, &conf.lists.named.links, &conf.obj_p); write_list_pattern(out, &conf.lists.named.libs, &conf.lib_p); } break; default: fclose(out); fprintf(stderr, "Unknown switch %s.\n", argv[i]); exit(1); } fclose(out); } return 0; } /* * We would like to use the real realloc, but it doesn't work on all systems * (e.g., some Linux versions). Also, this procedure does the right thing * if old_ptr = NULL. */ private void * mrealloc(void *old_ptr, size_t old_size, size_t new_size) { void *new_ptr = malloc(new_size); if (new_ptr == NULL) return NULL; /* We have to pass in the old size, since we have no way to */ /* determine it otherwise. */ if (old_ptr) memcpy(new_ptr, old_ptr, min(old_size, new_size)); return new_ptr; } /* Allocate and initialize a string list. */ int alloc_list(string_list_t * list) { list->count = 0; list->items = (string_item_t *) calloc(list->max_count, sizeof(string_item_t)); assert(list->items != NULL); return 0; } /* If necessary, convert a .dev name to its file name. */ void dev_file_name(char *str) { int len = strlen(str); if (len <= 4 || strcmp(".dev", str + len - 4)) strcat(str, ".dev"); } /* Delete any files that are named as -replace "resources". */ int process_replaces(config_t * pconf) { char bufname[MAX_STR]; int i; for (i = 0; i < pconf->replaces.count; ++i) { int j; strcpy(bufname, pconf->replaces.items[i].str); /* See if the file being replaced was included. */ dev_file_name(bufname); for (j = 0; j < pconf->file_names.count; ++j) { const char *fname = pconf->file_names.items[j].str; if (!strcmp(fname, bufname)) { if (pconf->debug) printf("Replacing file %s.\n", fname); /* Delete all resources associated with this file. */ { int rn; for (rn = 0; rn < NUM_RESOURCE_LISTS; ++rn) { string_item_t *items = pconf->lists.indexed[rn].items; int count = pconf->lists.indexed[rn].count; int tn; for (tn = 0; tn < count; ++tn) { if (items[tn].file_index == j) { /* * Delete the item. Since we haven't sorted * the items yet, just replace this item * with the last one, but make sure we don't * miss scanning it. */ if (pconf->debug) printf("Replacing %s %s.\n", pconf->lists.indexed[rn].list_name, items[tn].str); items[tn--] = items[--count]; } } pconf->lists.indexed[rn].count = count; } } pconf->file_names.items[j].str = ""; break; } } } /* Don't process the replaces again. */ pconf->replaces.count = 0; return 0; } /* * Read an entire file into memory. * We use the 'index' of the file_contents string_item_t to record the union * of the uniq_mode_ts of all (direct and indirect) items in the file. * Return the file_contents item for the file. */ private string_item_t * read_file(config_t * pconf, const char *fname) { char *cname = malloc(strlen(fname) + strlen(pconf->file_prefix) + 1); int i; FILE *in; int end, nread; char *cont; string_item_t *item; if (cname == 0) { fprintf(stderr, "Can't allocate space for file name %s%s.\n", pconf->file_prefix, fname); exit(1); } strcpy(cname, pconf->file_prefix); strcat(cname, fname); for (i = 0; i < pconf->file_names.count; ++i) if (!strcmp(pconf->file_names.items[i].str, cname)) { free(cname); return &pconf->file_contents.items[i]; } /* Try to open the file in binary mode, to avoid the overhead */ /* of unnecessary EOL conversion in the C library. */ in = fopen(cname, "rb"); if (in == 0) { in = fopen(cname, "r"); if (in == 0) { fprintf(stderr, "Can't read %s.\n", cname); exit(1); } } fseek(in, 0L, 2 /*SEEK_END */ ); end = ftell(in); cont = malloc(end + 1); if (cont == 0) { fprintf(stderr, "Can't allocate %d bytes to read %s.\n", end + 1, cname); exit(1); } rewind(in); nread = fread(cont, 1, end, in); fclose(in); cont[nread] = 0; if (pconf->debug) printf("File %s = %d bytes.\n", cname, nread); add_item(&pconf->file_names, cname, -1); item = add_item(&pconf->file_contents, cont, -1); item->index = 0; /* union of uniq_mode_ts */ return item; } /* Read and parse a .dev file. Return the union of all its uniq_mode_ts. */ int read_dev(config_t * pconf, const char *arg) { string_item_t *item; const char *in; #define MAX_TOKEN 256 char *token = malloc(MAX_TOKEN + 1); char *category = malloc(MAX_TOKEN + 1); int file_index; int len; if (pconf->debug) printf("Reading %s;\n", arg); item = read_file(pconf, arg); if (item->index == uniq_first) { /* Don't need to read the file again. */ if (pconf->debug) printf("Skipping duplicate file.\n"); return uniq_first; } in = item->str; file_index = item - pconf->file_contents.items; strcpy(category, "obj"); while ((len = read_token(token, MAX_TOKEN, &in)) > 0) item->index |= add_entry(pconf, category, token, file_index); free(category); #undef MAX_TOKEN if (len < 0) { fprintf(stderr, "Token too long: %s.\n", token); exit(1); } if (pconf->debug) printf("Finished %s.\n", arg); free(token); return item->index; } /* Read a token from a string that contains the contents of a file. */ int read_token(char *token, int max_len, const char **pin) { const char *in = *pin; int len = 0; while (len < max_len) { char ch = *in; if (ch == 0) break; ++in; if (isspace(ch)) { if (len > 0) break; continue; } token[len++] = ch; } token[len] = 0; *pin = in; return (len >= max_len ? -1 /* token too long */ : len); } /* Add an entry to a configuration. Return its uniq_mode_t. */ int add_entry(config_t * pconf, char *category, const char *item, int file_index) { if (item[0] == '-' && islower(item[1])) { /* set category */ strcpy(category, item + 1); return 0; } else { /* add to current category */ char str[MAX_STR]; char template[80]; const char *pat = 0; string_list_t *list = &pconf->lists.named.resources; if (pconf->debug) printf("Adding %s %s;\n", category, item); /* Handle a few resources specially; just queue the rest. */ switch (category[0]) { #define IS_CAT(str) !strcmp(category, str) case 'd': if (IS_CAT("dev")) pat = "device_(%s%%s_device)"; else if (IS_CAT("dev2")) pat = "device2_(%s%%s_device)"; else goto err; list = &pconf->lists.named.devs; pre: sprintf(template, pat, pconf->name_prefix); pat = template; break; case 'e': if (IS_CAT("emulator")) { sprintf(str, "emulator_(\"%s\",%u)", item, (uint)strlen(item)); item = str; break; } goto err; case 'f': if (IS_CAT("font")) { list = &pconf->lists.named.fonts; break; } else if (IS_CAT("functiontype")) { pat = "function_type_(%%s,%sbuild_function_%%s)"; } else goto err; goto pre; case 'h': if (IS_CAT("halftone")) { pat = "halftone_(%sdht_%%s)"; } else goto err; goto pre; case 'i': if (IS_CAT("imageclass")) { list = &pconf->lists.named.sorted_resources; pat = "image_class_(%simage_class_%%s)"; } else if (IS_CAT("imagetype")) { pat = "image_type_(%%s,%simage_type_%%s)"; } else if (IS_CAT("include")) { strcpy(str, item); dev_file_name(str); return read_dev(pconf, str); } else if (IS_CAT("init")) { pat = "init_(%s%%s_init)"; } else if (IS_CAT("iodev")) { pat = "io_device_(%siodev_%%s)"; } else goto err; goto pre; case 'l': if (IS_CAT("lib")) { list = &pconf->lists.named.libs; break; } else if (IS_CAT("libpath")) { list = &pconf->lists.named.libpaths; break; } else if (IS_CAT("link")) { list = &pconf->lists.named.links; break; } goto err; case 'o': if (IS_CAT("obj")) { list = &pconf->lists.named.objs; strcpy(template, pconf->file_prefix); strcat(template, "%s"); pat = template; break; } if (IS_CAT("oper")) { pat = "oper_(%s_op_defs)"; break; } goto err; case 'p': if (IS_CAT("ps")) { sprintf(str, "psfile_(\"%s.ps\",%u)", item, (uint)(strlen(item) + 3)); item = str; break; } goto err; case 'r': if (IS_CAT("replace")) { list = &pconf->replaces; break; } goto err; #undef IS_CAT default: err: fprintf(stderr, "Definition not recognized: %s %s.\n", category, item); exit(1); } if (pat) { sprintf(str, pat, item, item); assert(strlen(str) < MAX_STR); add_item(list, str, file_index); } else add_item(list, item, file_index); return list->mode; } } /* Add an item to a list. */ string_item_t * add_item(string_list_t * list, const char *str, int file_index) { char *rstr = malloc(strlen(str) + 1); int count = list->count; string_item_t *item; if (count >= list->max_count) { list->max_count <<= 1; if (list->max_count < 20) list->max_count = 20; list->items = (string_item_t *) mrealloc(list->items, (list->max_count >> 1) * sizeof(string_item_t), list->max_count * sizeof(string_item_t)); assert(list->items != NULL); } item = &list->items[count]; item->str = rstr; item->index = count; item->file_index = file_index; strcpy(rstr, str); list->count++; return item; } /* * Remove duplicates from a list of string_item_ts. * In case of duplicates, remove all but the earliest (if last = false) * or the latest (if last = true). */ private int cmp_index(const void *p1, const void *p2) { const string_item_t *const psi1 = (const string_item_t *)p1; const string_item_t *const psi2 = (const string_item_t *)p2; int cmp = psi1->index - psi2->index; return (cmp < 0 ? -1 : cmp > 0 ? 1 : 0); } private int cmp_str(const void *p1, const void *p2) { const string_item_t *const psi1 = (const string_item_t *)p1; const string_item_t *const psi2 = (const string_item_t *)p2; return strcmp(psi1->str, psi2->str); } void sort_uniq(string_list_t * list, bool by_index) { string_item_t *strlist = list->items; int count = list->count; const string_item_t *from; string_item_t *to; int i; bool last = list->mode == uniq_last; if (count == 0) return; qsort((char *)strlist, count, sizeof(string_item_t), cmp_str); for (from = to = strlist + 1, i = 1; i < count; from++, i++) if (strcmp(from->str, to[-1].str)) *to++ = *from; else if ((last ? from->index > to[-1].index : from->index < to[-1].index) ) to[-1] = *from; count = to - strlist; list->count = count; if (by_index) qsort((char *)strlist, count, sizeof(string_item_t), cmp_index); } /* Write a list of strings using a template. */ void write_list(FILE * out, const string_list_t * list, const char *pstr) { string_pattern_t pat; pat.upper_case = false; pat.drop_extn = false; strcpy(pat.pattern, pstr); write_list_pattern(out, list, &pat); } void write_list_pattern(FILE * out, const string_list_t * list, const string_pattern_t * pat) { int i; char macname[40]; int plen = strlen(pat->pattern); *macname = 0; for (i = 0; i < list->count; i++) { const char *lstr = list->items[i].str; int len = strlen(lstr); char *str = malloc(len + 1); int xlen = plen + len * 3; char *xstr = malloc(xlen + 1); char *alist; strcpy(str, lstr); if (pat->drop_extn) { char *dot = str + len; while (dot > str && *dot != '.') dot--; if (dot > str) *dot = 0, len = dot - str; } if (pat->upper_case) { char *ptr = str; for (; *ptr; ptr++) if (islower(*ptr)) *ptr = toupper(*ptr); } /* We repeat str for the benefit of patterns that */ /* need the argument substituted in more than one place. */ sprintf(xstr, pat->pattern, str, str, str); /* Check to make sure the item is within the scope of */ /* an appropriate #ifdef, if necessary. */ alist = strchr(xstr, '('); if (alist != 0 && alist != xstr && alist[-1] == '_') { *alist = 0; if (strcmp(xstr, macname)) { if (*macname) fputs("#endif\n", out); fprintf(out, "#ifdef %s\n", xstr); strcpy(macname, xstr); } *alist = '('; } else { if (*macname) { fputs("#endif\n", out); *macname = 0; } } fputs(xstr, out); free(xstr); free(str); } if (*macname) fputs("#endif\n", out); }