Use actual rpmTags in place of the old HEADER_FOO defines everywhere
[platform/upstream/rpm.git] / build / parseSpec.c
1 /** \ingroup rpmbuild
2  * \file build/parseSpec.c
3  *  Top level dispatcher for spec file parsing.
4  */
5
6 #include "system.h"
7
8 #include <errno.h>
9
10 #include <rpm/rpmtypes.h>
11 #include <rpm/rpmlib.h>         /* RPM_MACHTABLE & related */
12 #include <rpm/rpmds.h>
13 #include <rpm/rpmts.h>
14 #include <rpm/rpmlog.h>
15 #include <rpm/rpmfileutil.h>
16 #include "build/rpmbuild_internal.h"
17 #include "build/rpmbuild_misc.h"
18 #include "debug.h"
19
20 #define SKIPSPACE(s) { while (*(s) && risspace(*(s))) (s)++; }
21 #define SKIPNONSPACE(s) { while (*(s) && !risspace(*(s))) (s)++; }
22
23 #define LEN_AND_STR(_tag) (sizeof(_tag)-1), (_tag)
24
25 typedef struct OpenFileInfo {
26     char * fileName;
27     FILE *fp;
28     int lineNum;
29     char readBuf[BUFSIZ];
30     char * readPtr;
31     struct OpenFileInfo * next;
32 } OFI_t;
33
34 static const struct PartRec {
35     int part;
36     size_t len;
37     const char * token;
38 } const partList[] = {
39     { PART_PREAMBLE,      LEN_AND_STR("%package")},
40     { PART_PREP,          LEN_AND_STR("%prep")},
41     { PART_BUILD,         LEN_AND_STR("%build")},
42     { PART_INSTALL,       LEN_AND_STR("%install")},
43     { PART_CHECK,         LEN_AND_STR("%check")},
44     { PART_CLEAN,         LEN_AND_STR("%clean")},
45     { PART_PREUN,         LEN_AND_STR("%preun")},
46     { PART_POSTUN,        LEN_AND_STR("%postun")},
47     { PART_PRETRANS,      LEN_AND_STR("%pretrans")},
48     { PART_POSTTRANS,     LEN_AND_STR("%posttrans")},
49     { PART_PRE,           LEN_AND_STR("%pre")},
50     { PART_POST,          LEN_AND_STR("%post")},
51     { PART_FILES,         LEN_AND_STR("%files")},
52     { PART_CHANGELOG,     LEN_AND_STR("%changelog")},
53     { PART_DESCRIPTION,   LEN_AND_STR("%description")},
54     { PART_TRIGGERPOSTUN, LEN_AND_STR("%triggerpostun")},
55     { PART_TRIGGERPREIN,  LEN_AND_STR("%triggerprein")},
56     { PART_TRIGGERUN,     LEN_AND_STR("%triggerun")},
57     { PART_TRIGGERIN,     LEN_AND_STR("%triggerin")},
58     { PART_TRIGGERIN,     LEN_AND_STR("%trigger")},
59     { PART_VERIFYSCRIPT,  LEN_AND_STR("%verifyscript")},
60     { PART_POLICIES,      LEN_AND_STR("%sepolicy")},
61     {0, 0, 0}
62 };
63
64 rpmParseState isPart(const char *line)
65 {
66     const struct PartRec *p;
67
68     for (p = partList; p->token != NULL; p++) {
69         char c;
70         if (rstrncasecmp(line, p->token, p->len))
71             continue;
72         c = *(line + p->len);
73         if (c == '\0' || risspace(c))
74             break;
75     }
76
77     return (p->token ? p->part : PART_NONE);
78 }
79
80 /**
81  */
82 static int matchTok(const char *token, const char *line)
83 {
84     const char *b, *be = line;
85     size_t toklen = strlen(token);
86     int rc = 0;
87
88     while ( *(b = be) != '\0' ) {
89         SKIPSPACE(b);
90         be = b;
91         SKIPNONSPACE(be);
92         if (be == b)
93             break;
94         if (toklen != (be-b) || rstrncasecmp(token, b, (be-b)))
95             continue;
96         rc = 1;
97         break;
98     }
99
100     return rc;
101 }
102
103 void handleComments(char *s)
104 {
105     SKIPSPACE(s);
106     if (*s == '#')
107         *s = '\0';
108 }
109
110 static struct OpenFileInfo * newOpenFileInfo(void)
111 {
112     struct OpenFileInfo *ofi;
113
114     ofi = xmalloc(sizeof(*ofi));
115     ofi->fp = NULL;
116     ofi->fileName = NULL;
117     ofi->lineNum = 0;
118     ofi->readBuf[0] = '\0';
119     ofi->readPtr = NULL;
120     ofi->next = NULL;
121
122     return ofi;
123 }
124
125 /**
126  */
127 static void forceIncludeFile(rpmSpec spec, const char * fileName)
128 {
129     OFI_t * ofi;
130
131     ofi = newOpenFileInfo();
132     ofi->fileName = xstrdup(fileName);
133     ofi->next = spec->fileStack;
134     spec->fileStack = ofi;
135 }
136
137 static int restoreFirstChar(rpmSpec spec)
138 {
139     /* Restore 1st char in (possible) next line */
140     if (spec->nextline != NULL && spec->nextpeekc != '\0') {
141         *spec->nextline = spec->nextpeekc;
142         spec->nextpeekc = '\0';
143         return 1;
144     }
145     return 0;
146 }
147
148 /* Return zero on success, 1 if we need to read more and -1 on errors. */
149 static int copyNextLineFromOFI(rpmSpec spec, OFI_t *ofi)
150 {
151     char ch;
152
153     /* Expand next line from file into line buffer */
154     if (!(spec->nextline && *spec->nextline)) {
155         int pc = 0, bc = 0, nc = 0;
156         char *from, *to, *p;
157         to = spec->lbufPtr ? spec->lbufPtr : spec->lbuf;
158         from = ofi->readPtr;
159         ch = ' ';
160         while (from && *from && ch != '\n')
161             ch = *to++ = *from++;
162         spec->lbufPtr = to;
163         *to++ = '\0';
164         ofi->readPtr = from;
165
166         /* Check if we need another line before expanding the buffer. */
167         for (p = spec->lbuf; *p; p++) {
168             switch (*p) {
169                 case '\\':
170                     switch (*(p+1)) {
171                         case '\n': p++, nc = 1; break;
172                         case '\0': break;
173                         default: p++; break;
174                     }
175                     break;
176                 case '\n': nc = 0; break;
177                 case '%':
178                     switch (*(p+1)) {
179                         case '{': p++, bc++; break;
180                         case '(': p++, pc++; break;
181                         case '%': p++; break;
182                     }
183                     break;
184                 case '{': if (bc > 0) bc++; break;
185                 case '}': if (bc > 0) bc--; break;
186                 case '(': if (pc > 0) pc++; break;
187                 case ')': if (pc > 0) pc--; break;
188             }
189         }
190         
191         /* If it doesn't, ask for one more line. */
192         if (pc || bc || nc ) {
193             spec->nextline = "";
194             return 1;
195         }
196         spec->lbufPtr = spec->lbuf;
197
198         /* Don't expand macros (eg. %define) in false branch of %if clause */
199         if (spec->readStack->reading &&
200             expandMacros(spec, spec->macros, spec->lbuf, sizeof(spec->lbuf))) {
201                 rpmlog(RPMLOG_ERR, _("line %d: %s\n"),
202                         spec->lineNum, spec->lbuf);
203                 return -1;
204         }
205         spec->nextline = spec->lbuf;
206     }
207     return 0;
208 }
209
210 static void copyNextLineFinish(rpmSpec spec, int strip)
211 {
212     char *last;
213     char ch;
214
215     /* Find next line in expanded line buffer */
216     spec->line = last = spec->nextline;
217     ch = ' ';
218     while (*spec->nextline && ch != '\n') {
219         ch = *spec->nextline++;
220         if (!risspace(ch))
221             last = spec->nextline;
222     }
223
224     /* Save 1st char of next line in order to terminate current line. */
225     if (*spec->nextline != '\0') {
226         spec->nextpeekc = *spec->nextline;
227         *spec->nextline = '\0';
228     }
229     
230     if (strip & STRIP_COMMENTS)
231         handleComments(spec->line);
232     
233     if (strip & STRIP_TRAILINGSPACE)
234         *last = '\0';
235 }
236
237 static int readLineFromOFI(rpmSpec spec, OFI_t *ofi)
238 {
239 retry:
240     /* Make sure the current file is open */
241     if (ofi->fp == NULL) {
242         ofi->fp = fopen(ofi->fileName, "r");
243         if (ofi->fp == NULL || ferror(ofi->fp)) {
244             /* XXX Fstrerror */
245             rpmlog(RPMLOG_ERR, _("Unable to open %s: %s\n"),
246                      ofi->fileName, strerror(errno));
247             return PART_ERROR;
248         }
249         spec->lineNum = ofi->lineNum = 0;
250     }
251
252     /* Make sure we have something in the read buffer */
253     if (!(ofi->readPtr && *(ofi->readPtr))) {
254         if (!fgets(ofi->readBuf, BUFSIZ, ofi->fp)) {
255             /* EOF */
256             if (spec->readStack->next) {
257                 rpmlog(RPMLOG_ERR, _("Unclosed %%if\n"));
258                 return PART_ERROR;
259             }
260
261             /* remove this file from the stack */
262             spec->fileStack = ofi->next;
263             fclose(ofi->fp);
264             ofi->fileName = _free(ofi->fileName);
265             ofi = _free(ofi);
266
267             /* only on last file do we signal EOF to caller */
268             ofi = spec->fileStack;
269             if (ofi == NULL)
270                 return 1;
271
272             /* otherwise, go back and try the read again. */
273             goto retry;
274         }
275         ofi->readPtr = ofi->readBuf;
276         ofi->lineNum++;
277         spec->lineNum = ofi->lineNum;
278         if (spec->sl) {
279             speclines sl = spec->sl;
280             if (sl->sl_nlines == sl->sl_nalloc) {
281                 sl->sl_nalloc += 100;
282                 sl->sl_lines = (char **) xrealloc(sl->sl_lines, 
283                         sl->sl_nalloc * sizeof(*(sl->sl_lines)));
284             }
285             sl->sl_lines[sl->sl_nlines++] = xstrdup(ofi->readBuf);
286         }
287     }
288     return 0;
289 }
290
291 int readLine(rpmSpec spec, int strip)
292 {
293     char  *s;
294     int match;
295     struct ReadLevelEntry *rl;
296     OFI_t *ofi = spec->fileStack;
297     int rc;
298
299     if (!restoreFirstChar(spec)) {
300     retry:
301         if ((rc = readLineFromOFI(spec, ofi)) != 0)
302             return rc;
303         ofi = spec->fileStack;
304
305         /* Copy next file line into the spec line buffer */
306         rc = copyNextLineFromOFI(spec, ofi);
307         if (rc > 0) {
308             goto retry;
309         } else if (rc < 0) {
310             return PART_ERROR;
311         }
312     }
313
314     copyNextLineFinish(spec, strip);
315
316     s = spec->line;
317     SKIPSPACE(s);
318
319     match = -1;
320     if (!spec->readStack->reading && rstreqn("%if", s, sizeof("%if")-1)) {
321         match = 0;
322     } else if (rstreqn("%ifarch", s, sizeof("%ifarch")-1)) {
323         char *arch = rpmExpand("%{_target_cpu}", NULL);
324         s += 7;
325         match = matchTok(arch, s);
326         arch = _free(arch);
327     } else if (rstreqn("%ifnarch", s, sizeof("%ifnarch")-1)) {
328         char *arch = rpmExpand("%{_target_cpu}", NULL);
329         s += 8;
330         match = !matchTok(arch, s);
331         arch = _free(arch);
332     } else if (rstreqn("%ifos", s, sizeof("%ifos")-1)) {
333         char *os = rpmExpand("%{_target_os}", NULL);
334         s += 5;
335         match = matchTok(os, s);
336         os = _free(os);
337     } else if (rstreqn("%ifnos", s, sizeof("%ifnos")-1)) {
338         char *os = rpmExpand("%{_target_os}", NULL);
339         s += 6;
340         match = !matchTok(os, s);
341         os = _free(os);
342     } else if (rstreqn("%if", s, sizeof("%if")-1)) {
343         s += 3;
344         match = parseExpressionBoolean(spec, s);
345         if (match < 0) {
346             rpmlog(RPMLOG_ERR,
347                         _("%s:%d: parseExpressionBoolean returns %d\n"),
348                         ofi->fileName, ofi->lineNum, match);
349             return PART_ERROR;
350         }
351     } else if (rstreqn("%else", s, sizeof("%else")-1)) {
352         s += 5;
353         if (! spec->readStack->next) {
354             /* Got an else with no %if ! */
355             rpmlog(RPMLOG_ERR,
356                         _("%s:%d: Got a %%else with no %%if\n"),
357                         ofi->fileName, ofi->lineNum);
358             return PART_ERROR;
359         }
360         spec->readStack->reading =
361             spec->readStack->next->reading && ! spec->readStack->reading;
362         spec->line[0] = '\0';
363     } else if (rstreqn("%endif", s, sizeof("%endif")-1)) {
364         s += 6;
365         if (! spec->readStack->next) {
366             /* Got an end with no %if ! */
367             rpmlog(RPMLOG_ERR,
368                         _("%s:%d: Got a %%endif with no %%if\n"),
369                         ofi->fileName, ofi->lineNum);
370             return PART_ERROR;
371         }
372         rl = spec->readStack;
373         spec->readStack = spec->readStack->next;
374         free(rl);
375         spec->line[0] = '\0';
376     } else if (rstreqn("%include", s, sizeof("%include")-1)) {
377         char *fileName, *endFileName, *p;
378
379         s += 8;
380         fileName = s;
381         if (! risspace(*fileName)) {
382             rpmlog(RPMLOG_ERR, _("malformed %%include statement\n"));
383             return PART_ERROR;
384         }
385         SKIPSPACE(fileName);
386         endFileName = fileName;
387         SKIPNONSPACE(endFileName);
388         p = endFileName;
389         SKIPSPACE(p);
390         if (*p != '\0') {
391             rpmlog(RPMLOG_ERR, _("malformed %%include statement\n"));
392             return PART_ERROR;
393         }
394         *endFileName = '\0';
395
396         forceIncludeFile(spec, fileName);
397
398         ofi = spec->fileStack;
399         goto retry;
400     }
401
402     if (match != -1) {
403         rl = xmalloc(sizeof(*rl));
404         rl->reading = spec->readStack->reading && match;
405         rl->next = spec->readStack;
406         spec->readStack = rl;
407         spec->line[0] = '\0';
408     }
409
410     if (! spec->readStack->reading) {
411         spec->line[0] = '\0';
412     }
413
414     /* FIX: spec->readStack->next should be dependent */
415     return 0;
416 }
417
418 void closeSpec(rpmSpec spec)
419 {
420     OFI_t *ofi;
421
422     while (spec->fileStack) {
423         ofi = spec->fileStack;
424         spec->fileStack = spec->fileStack->next;
425         if (ofi->fp) (void) fclose(ofi->fp);
426         ofi->fileName = _free(ofi->fileName);
427         ofi = _free(ofi);
428     }
429 }
430
431 static const rpmTag sourceTags[] = {
432     RPMTAG_NAME,
433     RPMTAG_VERSION,
434     RPMTAG_RELEASE,
435     RPMTAG_EPOCH,
436     RPMTAG_SUMMARY,
437     RPMTAG_DESCRIPTION,
438     RPMTAG_PACKAGER,
439     RPMTAG_DISTRIBUTION,
440     RPMTAG_DISTURL,
441     RPMTAG_VENDOR,
442     RPMTAG_LICENSE,
443     RPMTAG_GROUP,
444     RPMTAG_OS,
445     RPMTAG_ARCH,
446     RPMTAG_CHANGELOGTIME,
447     RPMTAG_CHANGELOGNAME,
448     RPMTAG_CHANGELOGTEXT,
449     RPMTAG_URL,
450     RPMTAG_BUGURL,
451     RPMTAG_HEADERI18NTABLE,
452     0
453 };
454
455 static void initSourceHeader(rpmSpec spec)
456 {
457     HeaderIterator hi;
458     struct rpmtd_s td;
459     struct Source *srcPtr;
460
461     spec->sourceHeader = headerNew();
462     /* Only specific tags are added to the source package header */
463     headerCopyTags(spec->packages->header, spec->sourceHeader, sourceTags);
464
465     /* Add the build restrictions */
466     hi = headerInitIterator(spec->buildRestrictions);
467     while (headerNext(hi, &td)) {
468         if (rpmtdCount(&td) > 0) {
469             (void) headerPut(spec->sourceHeader, &td, HEADERPUT_DEFAULT);
470         }
471         rpmtdFreeData(&td);
472     }
473     hi = headerFreeIterator(hi);
474
475     if (spec->BANames && spec->BACount > 0) {
476         headerPutStringArray(spec->sourceHeader, RPMTAG_BUILDARCHS,
477                   spec->BANames, spec->BACount);
478     }
479
480     /* Add tags for sources and patches */
481     for (srcPtr = spec->sources; srcPtr != NULL; srcPtr = srcPtr->next) {
482         if (srcPtr->flags & RPMBUILD_ISSOURCE) {
483             headerPutString(spec->sourceHeader, RPMTAG_SOURCE, srcPtr->source);
484             if (srcPtr->flags & RPMBUILD_ISNO) {
485                 headerPutUint32(spec->sourceHeader, RPMTAG_NOSOURCE,
486                                 &srcPtr->num, 1);
487             }
488         }
489         if (srcPtr->flags & RPMBUILD_ISPATCH) {
490             headerPutString(spec->sourceHeader, RPMTAG_PATCH, srcPtr->source);
491             if (srcPtr->flags & RPMBUILD_ISNO) {
492                 headerPutUint32(spec->sourceHeader, RPMTAG_NOPATCH,
493                                 &srcPtr->num, 1);
494             }
495         }
496     }
497 }
498
499 static void addTargets(Package Pkgs)
500 {
501     char *platform = rpmExpand("%{_target_platform}", NULL);
502     char *arch = rpmExpand("%{_target_cpu}", NULL);
503     char *os = rpmExpand("%{_target_os}", NULL);
504
505     for (Package pkg = Pkgs; pkg != NULL; pkg = pkg->next) {
506         headerPutString(pkg->header, RPMTAG_OS, os);
507         /* noarch subpackages already have arch set here, leave it alone */
508         if (!headerIsEntry(pkg->header, RPMTAG_ARCH)) {
509             headerPutString(pkg->header, RPMTAG_ARCH, arch);
510         }
511         headerPutString(pkg->header, RPMTAG_PLATFORM, platform);
512
513         pkg->ds = rpmdsThis(pkg->header, RPMTAG_REQUIRENAME, RPMSENSE_EQUAL);
514     }
515     free(platform);
516     free(arch);
517     free(os);
518 }
519
520 static rpmSpec parseSpec(const char *specFile, rpmSpecFlags flags,
521                          const char *buildRoot, int recursing)
522 {
523     rpmParseState parsePart = PART_PREAMBLE;
524     int initialPackage = 1;
525     rpmSpec spec;
526     
527     /* Set up a new Spec structure with no packages. */
528     spec = newSpec();
529
530     spec->specFile = rpmGetPath(specFile, NULL);
531     spec->fileStack = newOpenFileInfo();
532     spec->fileStack->fileName = xstrdup(spec->specFile);
533     /* If buildRoot not specified, use default %{buildroot} */
534     if (buildRoot) {
535         spec->buildRoot = xstrdup(buildRoot);
536     } else {
537         spec->buildRoot = rpmGetPath("%{?buildroot:%{buildroot}}", NULL);
538     }
539     addMacro(NULL, "_docdir", NULL, "%{_defaultdocdir}", RMIL_SPEC);
540     spec->recursing = recursing;
541     spec->flags = flags;
542
543     /* All the parse*() functions expect to have a line pre-read */
544     /* in the spec's line buffer.  Except for parsePreamble(),   */
545     /* which handles the initial entry into a spec file.         */
546     
547     while (parsePart != PART_NONE) {
548         int goterror = 0;
549         switch (parsePart) {
550         /* XXX Trap unexpected RPMRC_FAIL returns for now */
551         case RPMRC_FAIL:
552             rpmlog(RPMLOG_ERR, "FIXME: got RPMRC_FAIL from spec parse\n");
553             abort();
554         case PART_ERROR: /* fallthrough */
555         default:
556             goterror = 1;
557             break;
558         case PART_PREAMBLE:
559             parsePart = parsePreamble(spec, initialPackage);
560             initialPackage = 0;
561             break;
562         case PART_PREP:
563             parsePart = parsePrep(spec);
564             break;
565         case PART_BUILD:
566         case PART_INSTALL:
567         case PART_CHECK:
568         case PART_CLEAN:
569             parsePart = parseBuildInstallClean(spec, parsePart);
570             break;
571         case PART_CHANGELOG:
572             parsePart = parseChangelog(spec);
573             break;
574         case PART_DESCRIPTION:
575             parsePart = parseDescription(spec);
576             break;
577
578         case PART_PRE:
579         case PART_POST:
580         case PART_PREUN:
581         case PART_POSTUN:
582         case PART_PRETRANS:
583         case PART_POSTTRANS:
584         case PART_VERIFYSCRIPT:
585         case PART_TRIGGERPREIN:
586         case PART_TRIGGERIN:
587         case PART_TRIGGERUN:
588         case PART_TRIGGERPOSTUN:
589             parsePart = parseScript(spec, parsePart);
590             break;
591
592         case PART_FILES:
593             parsePart = parseFiles(spec);
594             break;
595
596         case PART_POLICIES:
597             parsePart = parsePolicies(spec);
598             break;
599
600         case PART_NONE:         /* XXX avoid gcc whining */
601         case PART_LAST:
602         case PART_BUILDARCHITECTURES:
603             break;
604         }
605
606         if (goterror || parsePart >= PART_LAST) {
607             goto errxit;
608         }
609
610         if (parsePart == PART_BUILDARCHITECTURES) {
611             int index;
612             int x;
613
614             closeSpec(spec);
615
616             spec->BASpecs = xcalloc(spec->BACount, sizeof(*spec->BASpecs));
617             index = 0;
618             if (spec->BANames != NULL)
619             for (x = 0; x < spec->BACount; x++) {
620
621                 /* Skip if not arch is not compatible. */
622                 if (!rpmMachineScore(RPM_MACHTABLE_BUILDARCH, spec->BANames[x]))
623                     continue;
624                 addMacro(NULL, "_target_cpu", NULL, spec->BANames[x], RMIL_RPMRC);
625                 spec->BASpecs[index] = parseSpec(specFile, flags, buildRoot, 1);
626                 if (spec->BASpecs[index] == NULL) {
627                         spec->BACount = index;
628                         goto errxit;
629                 }
630                 delMacro(NULL, "_target_cpu");
631                 index++;
632             }
633
634             spec->BACount = index;
635             if (! index) {
636                 rpmlog(RPMLOG_ERR,
637                         _("No compatible architectures found for build\n"));
638                 goto errxit;
639             }
640
641             /*
642              * Return the 1st child's fully parsed Spec structure.
643              * The restart of the parse when encountering BuildArch
644              * causes problems for "rpm -q --specfile". This is
645              * still a hack because there may be more than 1 arch
646              * specified (unlikely but possible.) There's also the
647              * further problem that the macro context, particularly
648              * %{_target_cpu}, disagrees with the info in the header.
649              */
650             if (spec->BACount >= 1) {
651                 rpmSpec nspec = spec->BASpecs[0];
652                 spec->BASpecs = _free(spec->BASpecs);
653                 spec = rpmSpecFree(spec);
654                 spec = nspec;
655             }
656
657             goto exit;
658         }
659     }
660
661     if (spec->clean == NULL) {
662         char *body = rpmExpand("%{?buildroot: %{__rm} -rf %{buildroot}}", NULL);
663         spec->clean = newStringBuf();
664         appendLineStringBuf(spec->clean, body);
665         free(body);
666     }
667
668     /* Check for description in each package */
669     for (Package pkg = spec->packages; pkg != NULL; pkg = pkg->next) {
670         if (!headerIsEntry(pkg->header, RPMTAG_DESCRIPTION)) {
671             rpmlog(RPMLOG_ERR, _("Package has no %%description: %s\n"),
672                    headerGetString(pkg->header, RPMTAG_NAME));
673             goto errxit;
674         }
675     }
676
677     /* Add arch, os and platform for each package */
678     addTargets(spec->packages);
679
680     closeSpec(spec);
681 exit:
682     /* Assemble source header from parsed components */
683     initSourceHeader(spec);
684
685     return spec;
686
687 errxit:
688     spec = rpmSpecFree(spec);
689     return NULL;
690 }
691
692 rpmSpec rpmSpecParse(const char *specFile, rpmSpecFlags flags,
693                      const char *buildRoot)
694 {
695     return parseSpec(specFile, flags, buildRoot, 0);
696 }