2 * \file build/parseSpec.c
3 * Top level dispatcher for spec file parsing.
10 #include <rpmio_internal.h>
14 /*@access FD_t @*/ /* compared with NULL */
18 static struct PartRec {
21 /*@observer@*/ /*@null@*/ const char * token;
23 { PART_PREAMBLE, 0, "%package"},
24 { PART_PREP, 0, "%prep"},
25 { PART_BUILD, 0, "%build"},
26 { PART_INSTALL, 0, "%install"},
27 { PART_CLEAN, 0, "%clean"},
28 { PART_PREUN, 0, "%preun"},
29 { PART_POSTUN, 0, "%postun"},
30 { PART_PRE, 0, "%pre"},
31 { PART_POST, 0, "%post"},
32 { PART_FILES, 0, "%files"},
33 { PART_CHANGELOG, 0, "%changelog"},
34 { PART_DESCRIPTION, 0, "%description"},
35 { PART_TRIGGERPOSTUN, 0, "%triggerpostun"},
36 { PART_TRIGGERUN, 0, "%triggerun"},
37 { PART_TRIGGERIN, 0, "%triggerin"},
38 { PART_TRIGGERIN, 0, "%trigger"},
39 { PART_VERIFYSCRIPT, 0, "%verifyscript"},
45 static inline void initParts(struct PartRec *p)
46 /*@modifies p->len @*/
48 for (; p->token != NULL; p++)
49 p->len = strlen(p->token);
52 rpmParseState isPart(const char *line)
56 if (partList[0].len == 0)
59 for (p = partList; p->token != NULL; p++) {
61 if (xstrncasecmp(line, p->token, p->len))
64 if (c == '\0' || xisspace(c))
68 return (p->token ? p->part : PART_NONE);
73 static int matchTok(const char *token, const char *line)
76 const char *b, *be = line;
77 size_t toklen = strlen(token);
80 while ( *(b = be) != '\0' ) {
86 if (toklen != (be-b) || xstrncasecmp(token, b, (be-b)))
95 void handleComments(char *s)
104 static void forceIncludeFile(Spec spec, const char * fileName)
105 /*@modifies spec->fileStack @*/
109 ofi = newOpenFileInfo();
110 ofi->fileName = xstrdup(fileName);
111 ofi->next = spec->fileStack;
112 spec->fileStack = ofi;
117 static int copyNextLine(Spec spec, OFI_t *ofi, int strip)
118 /*@modifies spec->nextline @*/
123 /* Restore 1st char in (possible) next line */
124 if (spec->nextline != NULL && spec->nextpeekc != '\0') {
125 *spec->nextline = spec->nextpeekc;
126 spec->nextpeekc = '\0';
128 /* Expand next line from file into line buffer */
129 if (!(spec->nextline && *spec->nextline)) {
131 to = last = spec->lbuf;
134 while (*from && ch != '\n')
135 ch = *to++ = *from++;
139 /* Don't expand macros (eg. %define) in false branch of %if clause */
140 if (spec->readStack->reading &&
141 expandMacros(spec, spec->macros, spec->lbuf, sizeof(spec->lbuf))) {
142 rpmError(RPMERR_BADSPEC, _("line %d: %s\n"),
143 spec->lineNum, spec->lbuf);
144 return RPMERR_BADSPEC;
146 spec->nextline = spec->lbuf;
149 /* Find next line in expanded line buffer */
150 spec->line = last = spec->nextline;
152 while (*spec->nextline && ch != '\n') {
153 ch = *spec->nextline++;
155 last = spec->nextline;
158 /* Save 1st char of next line in order to terminate current line. */
159 if (*spec->nextline != '\0') {
160 spec->nextpeekc = *spec->nextline;
161 *spec->nextline = '\0';
164 if (strip & STRIP_COMMENTS)
165 handleComments(spec->line);
167 if (strip & STRIP_TRAILINGSPACE)
173 int readLine(Spec spec, int strip)
181 struct ReadLevelEntry *rl;
182 OFI_t *ofi = spec->fileStack;
186 /* Make sure the current file is open */
187 if (ofi->fd == NULL) {
188 ofi->fd = Fopen(ofi->fileName, "r.fpio");
189 if (ofi->fd == NULL || Ferror(ofi->fd)) {
191 rpmError(RPMERR_BADSPEC, _("Unable to open %s: %s\n"),
192 ofi->fileName, Fstrerror(ofi->fd));
193 return RPMERR_BADSPEC;
195 spec->lineNum = ofi->lineNum = 0;
198 /* Make sure we have something in the read buffer */
199 if (!(ofi->readPtr && *(ofi->readPtr))) {
200 FILE * f = fdGetFp(ofi->fd);
201 if (f == NULL || !fgets(ofi->readBuf, BUFSIZ, f)) {
203 if (spec->readStack->next) {
204 rpmError(RPMERR_UNMATCHEDIF, _("Unclosed %%if\n"));
205 return RPMERR_UNMATCHEDIF;
208 /* remove this file from the stack */
209 spec->fileStack = ofi->next;
210 (void) Fclose(ofi->fd);
211 ofi->fileName = _free(ofi->fileName);
214 /* only on last file do we signal EOF to caller */
215 ofi = spec->fileStack;
219 /* otherwise, go back and try the read again. */
222 ofi->readPtr = ofi->readBuf;
224 spec->lineNum = ofi->lineNum;
226 speclines sl = spec->sl;
227 if (sl->sl_nlines == sl->sl_nalloc) {
228 sl->sl_nalloc += 100;
229 sl->sl_lines = (char **) xrealloc(sl->sl_lines,
230 sl->sl_nalloc * sizeof(*(sl->sl_lines)));
232 sl->sl_lines[sl->sl_nlines++] = xstrdup(ofi->readBuf);
238 rpmGetArchInfo(&arch, NULL);
240 rpmGetOsInfo(&os, NULL);
243 /* Copy next file line into the spec line buffer */
244 if ((rc = copyNextLine(spec, ofi, strip)) != 0)
251 if (! strncmp("%ifarch", s, sizeof("%ifarch")-1)) {
252 const char *arch = rpmExpand("%{_target_cpu}", NULL);
254 match = matchTok(arch, s);
256 } else if (! strncmp("%ifnarch", s, sizeof("%ifnarch")-1)) {
257 const char *arch = rpmExpand("%{_target_cpu}", NULL);
259 match = !matchTok(arch, s);
261 } else if (! strncmp("%ifos", s, sizeof("%ifos")-1)) {
262 const char *os = rpmExpand("%{_target_os}", NULL);
264 match = matchTok(os, s);
266 } else if (! strncmp("%ifnos", s, sizeof("%ifnos")-1)) {
267 const char *os = rpmExpand("%{_target_os}", NULL);
269 match = !matchTok(os, s);
271 } else if (! strncmp("%if", s, sizeof("%if")-1)) {
273 match = parseExpressionBoolean(spec, s);
275 rpmError(RPMERR_UNMATCHEDIF,
276 _("%s:%d: parseExpressionBoolean returns %d\n"),
277 ofi->fileName, ofi->lineNum, match);
278 return RPMERR_BADSPEC;
280 } else if (! strncmp("%else", s, sizeof("%else")-1)) {
282 if (! spec->readStack->next) {
283 /* Got an else with no %if ! */
284 rpmError(RPMERR_UNMATCHEDIF,
285 _("%s:%d: Got a %%else with no %%if\n"),
286 ofi->fileName, ofi->lineNum);
287 return RPMERR_UNMATCHEDIF;
289 spec->readStack->reading =
290 spec->readStack->next->reading && ! spec->readStack->reading;
291 spec->line[0] = '\0';
292 } else if (! strncmp("%endif", s, sizeof("%endif")-1)) {
294 if (! spec->readStack->next) {
295 /* Got an end with no %if ! */
296 rpmError(RPMERR_UNMATCHEDIF,
297 _("%s:%d: Got a %%endif with no %%if\n"),
298 ofi->fileName, ofi->lineNum);
299 return RPMERR_UNMATCHEDIF;
301 rl = spec->readStack;
302 spec->readStack = spec->readStack->next;
304 spec->line[0] = '\0';
305 } else if (! strncmp("%include", s, sizeof("%include")-1)) {
306 char *fileName, *endFileName, *p;
310 if (! xisspace(*fileName)) {
311 rpmError(RPMERR_BADSPEC, _("malformed %%include statement\n"));
312 return RPMERR_BADSPEC;
315 endFileName = fileName;
316 SKIPNONSPACE(endFileName);
320 rpmError(RPMERR_BADSPEC, _("malformed %%include statement\n"));
321 return RPMERR_BADSPEC;
325 forceIncludeFile(spec, fileName);
327 ofi = spec->fileStack;
332 rl = xmalloc(sizeof(struct ReadLevelEntry));
333 rl->reading = spec->readStack->reading && match;
334 rl->next = spec->readStack;
335 spec->readStack = rl;
336 spec->line[0] = '\0';
339 if (! spec->readStack->reading) {
340 spec->line[0] = '\0';
346 void closeSpec(Spec spec)
350 while (spec->fileStack) {
351 ofi = spec->fileStack;
352 spec->fileStack = spec->fileStack->next;
353 if (ofi->fd) (void) Fclose(ofi->fd);
354 ofi->fileName = _free(ofi->fileName);
360 extern int noLang; /* XXX FIXME: pass as arg */
363 /*@todo Skip parse recursion if os is not compatible. @*/
364 int parseSpec(Spec *specp, const char *specFile, const char *rootURL,
365 const char *buildRootURL, int recursing, const char *passPhrase,
366 char *cookie, int anyarch, int force)
368 rpmParseState parsePart = PART_PREAMBLE;
369 int initialPackage = 1;
371 const char *saveArch;
376 /* Set up a new Spec structure with no packages. */
380 * Note: rpmGetPath should guarantee a "canonical" path. That means
381 * that the following pathologies should be weeded out:
384 * /.././../usr/../bin//./sh (XXX FIXME: dots not handled yet)
386 spec->specFile = rpmGetPath(specFile, NULL);
387 spec->fileStack = newOpenFileInfo();
388 spec->fileStack->fileName = xstrdup(spec->specFile);
390 const char * buildRoot;
391 (void) urlPath(buildRootURL, &buildRoot);
392 if (*buildRoot == '\0') buildRoot = "/";
393 if (!strcmp(buildRoot, "/")) {
394 rpmError(RPMERR_BADSPEC,
395 _("BuildRoot can not be \"/\": %s\n"), buildRootURL);
396 return RPMERR_BADSPEC;
398 spec->gotBuildRootURL = 1;
399 spec->buildRootURL = xstrdup(buildRootURL);
400 addMacro(spec->macros, "buildroot", NULL, buildRoot, RMIL_SPEC);
402 fprintf(stderr, "*** PS buildRootURL(%s) %p macro set to %s\n", spec->buildRootURL, spec->buildRootURL, buildRoot);
404 addMacro(NULL, "_docdir", NULL, "%{_defaultdocdir}", RMIL_SPEC);
405 spec->recursing = recursing;
406 spec->anyarch = anyarch;
410 spec->rootURL = xstrdup(rootURL);
412 spec->passPhrase = xstrdup(passPhrase);
414 spec->cookie = xstrdup(cookie);
416 spec->timeCheck = rpmExpandNumeric("%{_timecheck}");
418 /* All the parse*() functions expect to have a line pre-read */
419 /* in the spec's line buffer. Except for parsePreamble(), */
420 /* which handles the initial entry into a spec file. */
422 /*@-infloops@*/ /* LCL: parsePart is modified @*/
423 while (parsePart < PART_LAST && parsePart != PART_NONE) {
426 parsePart = parsePreamble(spec, initialPackage);
428 /*@switchbreak@*/ break;
430 parsePart = parsePrep(spec);
431 /*@switchbreak@*/ break;
435 parsePart = parseBuildInstallClean(spec, parsePart);
436 /*@switchbreak@*/ break;
438 parsePart = parseChangelog(spec);
439 /*@switchbreak@*/ break;
440 case PART_DESCRIPTION:
441 parsePart = parseDescription(spec);
442 /*@switchbreak@*/ break;
448 case PART_VERIFYSCRIPT:
451 case PART_TRIGGERPOSTUN:
452 parsePart = parseScript(spec, parsePart);
453 /*@switchbreak@*/ break;
456 parsePart = parseFiles(spec);
457 /*@switchbreak@*/ break;
459 case PART_NONE: /* XXX avoid gcc whining */
461 case PART_BUILDARCHITECTURES:
462 /*@switchbreak@*/ break;
465 if (parsePart >= PART_LAST) {
466 spec = freeSpec(spec);
470 if (parsePart == PART_BUILDARCHITECTURES) {
476 spec->BASpecs = xcalloc(spec->BACount, sizeof(Spec));
478 if (spec->BANames != NULL)
479 for (x = 0; x < spec->BACount; x++) {
481 /* Skip if not arch is not compatible. */
482 if (!rpmMachineScore(RPM_MACHTABLE_BUILDARCH, spec->BANames[x]))
483 /*@innercontinue@*/ continue;
485 rpmGetMachine(&saveArch, NULL);
486 saveArch = xstrdup(saveArch);
487 rpmSetMachine(spec->BANames[x], NULL);
489 addMacro(NULL, "_target_cpu", NULL, spec->BANames[x], RMIL_RPMRC);
491 spec->BASpecs[index] = NULL;
492 if (parseSpec(&(spec->BASpecs[index]),
493 specFile, spec->rootURL, buildRootURL, 1,
494 passPhrase, cookie, anyarch, force))
496 spec->BACount = index;
497 spec = freeSpec(spec);
498 return RPMERR_BADSPEC;
501 rpmSetMachine(saveArch, NULL);
502 saveArch = _free(saveArch);
504 delMacro(NULL, "_target_cpu");
509 spec->BACount = index;
511 spec = freeSpec(spec);
512 rpmError(RPMERR_BADSPEC,
513 _("No compatible architectures found for build\n"));
514 return RPMERR_BADSPEC;
518 * Return the 1st child's fully parsed Spec structure.
519 * The restart of the parse when encountering BuildArch
520 * causes problems for "rpm -q --specfile". This is
521 * still a hack because there may be more than 1 arch
522 * specified (unlikely but possible.) There's also the
523 * further problem that the macro context, particularly
524 * %{_target_cpu}, disagrees with the info in the header.
526 if (spec->BACount >= 1) {
527 Spec nspec = spec->BASpecs[0];
528 spec->BASpecs = _free(spec->BASpecs);
529 spec = freeSpec(spec);
537 /*@=infloops@*/ /* LCL: parsePart is modified @*/
539 /* Check for description in each package and add arch and os */
542 const char *arch = NULL;
543 const char *os = NULL;
546 rpmGetArchInfo(&arch, NULL);
547 rpmGetOsInfo(&os, NULL);
549 * XXX Capitalizing the 'L' is needed to insure that old
550 * XXX os-from-uname (e.g. "Linux") is compatible with the new
551 * XXX os-from-platform (e.g "linux" from "sparc-*-linux").
552 * XXX A copy of this string is embedded in headers.
554 if (!strcmp(os, "linux")) {
560 const char *arch = rpmExpand("%{_target_cpu}", NULL);
561 const char *os = rpmExpand("%{_target_os}", NULL);
564 for (pkg = spec->packages; pkg != NULL; pkg = pkg->next) {
565 if (!headerIsEntry(pkg->header, RPMTAG_DESCRIPTION)) {
567 (void) headerNVR(pkg->header, &name, NULL, NULL);
568 rpmError(RPMERR_BADSPEC, _("Package has no %%description: %s\n"),
570 spec = freeSpec(spec);
571 return RPMERR_BADSPEC;
574 (void) headerAddEntry(pkg->header, RPMTAG_OS, RPM_STRING_TYPE, os, 1);
575 (void) headerAddEntry(pkg->header, RPMTAG_ARCH, RPM_STRING_TYPE, arch, 1);