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