openpkg-index.pl 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968
  1. ##
  2. ## openpkg-index.pl -- create index from spec files
  3. ##
  4. ## Copyright (c) 2000-2003 Cable & Wireless Deutschland GmbH
  5. ## Copyright (c) 2000-2003 The OpenPKG Project <http://www.openpkg.org/>
  6. ## Copyright (c) 2000-2003 Ralf S. Engelschall <rse@engelschall.com>
  7. ##
  8. ## Permission to use, copy, modify, and distribute this software for
  9. ## any purpose with or without fee is hereby granted, provided that
  10. ## the above copyright notice and this permission notice appear in all
  11. ## copies.
  12. ##
  13. ## THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
  14. ## WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  15. ## MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  16. ## IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR
  17. ## CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  18. ## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  19. ## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  20. ## USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  21. ## ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  22. ## OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  23. ## OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  24. ## SUCH DAMAGE.
  25. ##
  26. require 5;
  27. use strict;
  28. use Getopt::Std;
  29. getopts('r:p:C:o:ci');
  30. use vars qw/$opt_r $opt_p $opt_C $opt_o $opt_c $opt_i/;
  31. use FileHandle;
  32. use DirHandle;
  33. my $l_prefix = '@l_prefix@';
  34. my $RPM = "$l_prefix/bin/rpm";
  35. my $R2C = "$l_prefix/bin/rpm2cpio";
  36. my $BZ = "$l_prefix/lib/openpkg/bzip2 -9";
  37. #########################################################################
  38. #
  39. # escape XML special characters for output in RDF file
  40. #
  41. # remove trailing whitespace
  42. # remove common leading whitespace
  43. #
  44. sub e ($) {
  45. my($s) = @_;
  46. my($i);
  47. $s =~ s/\n+$//sg;
  48. $s =~ s/[^\S\n]+$//mg;
  49. $i = undef;
  50. while ($s =~ /^([^\S\n]+)/mg) {
  51. $i = $1 if !defined $i || length($1) < length($i);
  52. }
  53. $s =~ s/^\Q$i\E//mg if defined $i;
  54. $s =~ s/&/&amp;/sg;
  55. $s =~ s/</&lt;/sg;
  56. $s =~ s/>/&gt;/sg;
  57. return $s;
  58. }
  59. my %attrname = (
  60. '==' => 'equ',
  61. '=' => 'equ',
  62. '>=' => 'geq',
  63. '=>' => 'geq',
  64. '<=' => 'leq',
  65. '=<' => 'leq',
  66. '>' => 'gt',
  67. '<' => 'lt'
  68. );
  69. my($opreg) = join '|',
  70. map {
  71. "\Q$_\E"
  72. } sort {
  73. length($b) <=> length($a) ||
  74. $b cmp $a
  75. } keys %attrname;
  76. sub make_resource ($) {
  77. my($s) = @_;
  78. if ($s =~ /(\S+)\s*($opreg)\s*(.*?)\s*$/o) {
  79. return {
  80. resource => $1,
  81. attrname => $attrname{$2},
  82. attrval => $3
  83. }
  84. }
  85. return {
  86. resource => $s
  87. }
  88. }
  89. sub commasep ($$) {
  90. my($k,$v) = @_;
  91. if ($k =~ /^(NoSource)$/) {
  92. return split(/\s*,\s*/, $v);
  93. } elsif ($k =~ /^(PreReq|BuildPreReq|Provides|Conflicts)$/) {
  94. return map { make_resource($_) }
  95. split(/\s*,\s*/, $v);
  96. }
  97. return $v;
  98. }
  99. sub optesc ($) {
  100. my($s) = @_;
  101. $s =~ s/([\x00-\x1f\x80-\xbf\s\%])/sprintf("%%%02x",ord($1))/eg;
  102. return $s;
  103. }
  104. sub vsub ($$) {
  105. my($var,$v) = @_;
  106. $v =~ s/\%\{([^}]+)\}/exists $var->{$1} ? $var->{$1} : '%{'.$1.'}'/emg;
  107. return $v;
  108. }
  109. sub upn ($) {
  110. my($t) = @_;
  111. my(@tok) = $t =~ /(\(|\)|\&\&|\|\||\!|\S+)/g;
  112. my(@out,$op,$o);
  113. my(@save);
  114. $op = [];
  115. foreach (@tok) {
  116. if ($_ eq '(') {
  117. push @save, $op;
  118. $op = [];
  119. } elsif ($_ eq ')') {
  120. die "FATAL: unresolved operators in: @tok\n" if @$op;
  121. $op = pop @save
  122. or die "FATAL: parenthesis stack underflow in: @tok\n";
  123. while ($o = pop @$op) {
  124. push @out, $o->[0];
  125. last if $o->[1];
  126. }
  127. } elsif ($_ eq '&&') {
  128. push @$op, [ '+', 1 ] ;
  129. } elsif ($_ eq '||') {
  130. push @$op, [ '|', 1 ] ;
  131. } elsif ($_ eq '!') {
  132. push @$op, [ '!', 0 ];
  133. } elsif (/^\%\{(\S*?)\}$/) {
  134. push @out, $1;
  135. while ($o = pop @$op) {
  136. push @out, $o->[0];
  137. last if $o->[1]; # binop
  138. }
  139. }
  140. }
  141. return join (' ',@out);
  142. }
  143. #
  144. # deduce external variables from description
  145. #
  146. # before openpkg-20021230
  147. #
  148. sub find_options ($) {
  149. my($descr) = @_;
  150. my $evar = {};
  151. $descr =~ s/--define\s*'(\S+)\s*\%\{\1\}'/$evar->{$1} = '%{'.$1.'}', ''/sge;
  152. return $evar;
  153. }
  154. #
  155. # translate default section from spec-file
  156. # into a hash
  157. # %if/%ifdef/%define... are translated to #if/#ifdef/#define
  158. #
  159. # #defines are interpolated (correct ?)
  160. #
  161. # #if/#ifdef/... sections are stripped
  162. # result is the same as if all conditions evaluate false (!)
  163. #
  164. # all attributes are of the form key: value
  165. # repeated attributes are coalesced into a list
  166. #
  167. sub package2data ($$) {
  168. my($s,$ovar) = @_;
  169. my(%evar,%var);
  170. my(@term, $term);
  171. my(%attr);
  172. my($l, $v, $cond, $d, $p);
  173. my($re,@defs);
  174. # combine multilines
  175. $s =~ s/\\\n/ /sg;
  176. #
  177. # map conditional variable macros
  178. #
  179. $s =~ s/^#\{\!\?([^:]*):\s*%(.*?)\s*\}\s*$/#ifndef $1\n#$2\n#endif/mg;
  180. $s =~ s/^#\{\!\?([^:]*):\s*(.*?)\s*\}\s*$/#ifndef $1\n$2\n#endif/mg;
  181. #
  182. # map option macro
  183. #
  184. $s =~ s/^#option\s+(\S+)\s*(.*?)\s*$/#ifndef $1\n#define $1 $2\n#endif\n#provides $1 $2/mg;
  185. #
  186. # use option variables for interpolation
  187. #
  188. %evar = %$ovar;
  189. #
  190. # guess more external parameters by scanning for "default" sections.
  191. #
  192. $re = '^\#ifndef\s+[\w\_]+\s*\n((?:\#define\s+[\w\_]+\s.*\n)+)\#endif\n';
  193. @defs = $s =~ /$re/gm;
  194. foreach (@defs) {
  195. while (/^\#define\s+([\w\_]+)\s(.*?)\s*$/mg) {
  196. $ovar->{$1} = $2;
  197. $evar{$1} = '%{'.$1.'}';
  198. }
  199. }
  200. $s =~ s/$re//gm;
  201. #
  202. # add everything looking like a with_ variable
  203. #
  204. $re = '%{(with\_[\w\_]+)}';
  205. @defs = $s =~ /$re/gm;
  206. foreach (@defs) {
  207. next if exists $ovar->{$1};
  208. $ovar->{$1} = '%{'.$1.'}';
  209. $evar{$1} = '%{'.$1.'}';
  210. }
  211. #
  212. # extract all conditional sections
  213. #
  214. @term = ();
  215. %var = ();
  216. $cond = '';
  217. foreach $l (split(/\n/, $s)) {
  218. $v = vsub(\%var,$l);
  219. if (($p) = $v =~ /^\#if\s+(.*?)\s*$/) {
  220. #
  221. # normalize #if expressions
  222. # "%{variable}" == "yes"
  223. # "%{variable}" == "no"
  224. # operators ! && ||
  225. #
  226. $term = '';
  227. while ($p =~ /(!=)|(\!|\|\||\&\&|\(|\))|"\%\{([^}]+)\}"\s*==\s*"(yes|no)"|(\S+)/g) {
  228. if (defined $1) {
  229. warn "WARNING: unknown token '$1':\n< $l\n> $v\n";
  230. } elsif (defined $5) {
  231. warn "WARNING: unknown token '$5':\n< $l\n> $v\n";
  232. } elsif (defined $2) {
  233. $term .= " $2 ";
  234. } elsif (exists $evar{$3}) {
  235. $term .= ($4 eq 'no' ? '! ' : '').vsub(\%evar,'%{'.$3.'}');
  236. } else {
  237. warn "WARNING: unknown conditional '$2':\n< $l\n> $v\n";
  238. }
  239. }
  240. #
  241. # join with previous conditions for this #if/#endif block
  242. #
  243. if ($term ne '') {
  244. push @term, "( $term )";
  245. $cond = join(' && ', grep { $_ ne '' } @term).'';
  246. } else {
  247. push @term, '';
  248. }
  249. } elsif ($v =~ /^\#else\s*$/) {
  250. #
  251. # reverse last condition
  252. #
  253. if (@term) {
  254. $term[-1] = ' ! '.$term[-1];
  255. $cond = join(' && ', grep { $_ ne '' } @term).'';
  256. } else {
  257. die "FATAL: else without if\n";
  258. }
  259. } elsif ($v =~ /^\#endif\s*$/) {
  260. #
  261. # unwind last #if expression
  262. #
  263. pop @term;
  264. $cond = join(' && ', grep { $_ ne '' } @term).'';
  265. } elsif ($v =~ /^\#(?:define)\s*(\S+)\s*(.*?)\s*$/) {
  266. #
  267. # define conditional variables
  268. # truth-value becomes current condition
  269. #
  270. # define internal variables
  271. # -> store for subsequent substitution
  272. #
  273. if (exists $evar{$1}) {
  274. if ($2 eq 'yes') {
  275. $evar{$1} = "( \%\{$1\} || ( $cond ) )";
  276. } elsif ($2 eq 'no') {
  277. $evar{$1} = "( %\{$1\} && ! ( $cond ) )";
  278. } else {
  279. warn "WARNING: logic too complex for '$1':\n< $l\n> $v\n";
  280. }
  281. } else {
  282. $var{$1} = $2;
  283. }
  284. } elsif ($v =~ /^\#(?:undefine)\s*(\S+)\s*$/) {
  285. if (exists $evar{$1}) {
  286. $evar{$1} = "\%\{$1\}";
  287. } else {
  288. delete $var{$1};
  289. }
  290. } elsif ($v =~ /^\#(?:provides)\s*(\S+)\s*(.*?)\s*$/) {
  291. #
  292. # store option for current condition
  293. #
  294. if (exists $attr{'Name'}->{''}) {
  295. push @{$attr{'Provides'}->{$cond}}, {
  296. resource => $attr{'Name'}->{''}->[0].'::'.$1,
  297. attrname => 'equ',
  298. attrval => optesc($2)
  299. }
  300. } else {
  301. warn "ERROR: no package name set for option $1 = $2\n";
  302. }
  303. } elsif ($v =~ /^\#NoSource\s*(.*?)\s*$/) {
  304. #
  305. # store conditional NoSource attribute
  306. #
  307. push @{$attr{'NoSource'}->{$cond}}, commasep('NoSource',$1);
  308. } elsif ($v =~ /^\s*([^\#]\S*)\s*:\s*(.*?)\s*$/) {
  309. #
  310. # store attribute=value for current condition
  311. #
  312. push @{$attr{$1}->{$cond}}, commasep($1,$2);
  313. }
  314. }
  315. return \%attr;
  316. }
  317. #
  318. # split spec file into sections starting with a %word
  319. #
  320. # concatenate extended lines
  321. # strip comment lines
  322. # map %command to #command
  323. # split sections
  324. #
  325. # return package2data from default section.
  326. #
  327. sub spec2data ($) {
  328. my($s) = @_;
  329. my(%map);
  330. my($a,$o);
  331. my $spec = $s;
  332. # remove comments
  333. $s =~ s/^\s*#.*?\n//mg;
  334. # map commands
  335. $s =~ s/^%(ifdef|ifndef|if|NoSource|option|undefine|define|else|endif|\{)/#$1/mg;
  336. # split sections
  337. foreach (split(/^(?=%\w+\s*\n)/m, $s)) {
  338. if (/^%(\w+)\s*\n/) {
  339. $map{$1} .= $';
  340. } else {
  341. $map{'*'} .= $_;
  342. }
  343. }
  344. if (exists $map{'description'}) {
  345. $o = find_options($map{'description'});
  346. $a = package2data($map{'*'}, $o );
  347. $a->{'Description'} = { '' => [ $map{'description'} ] };
  348. } else {
  349. $a = package2data($map{'*'}, {});
  350. }
  351. return $a;
  352. }
  353. ##########################################################################
  354. #
  355. # start of XML file
  356. #
  357. sub xml_head ($$) {
  358. my($fh,$res) = @_;
  359. print $fh <<EOFEOF;
  360. <?xml version="1.0" encoding="iso-8859-1"?>
  361. <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
  362. xmlns="http://www.openpkg.org/xml-rdf-index/0.9">
  363. <Repository rdf:resource="$res">
  364. EOFEOF
  365. }
  366. #
  367. # end of XML file, corresponds with start tags
  368. #
  369. sub xml_foot ($) {
  370. my($fh) = @_;
  371. print $fh <<EOFEOF;
  372. </Repository>
  373. </rdf:RDF>
  374. EOFEOF
  375. }
  376. sub n($$) {
  377. my($a,$k) = @_;
  378. return unless $a->{$k};
  379. return unless $a->{$k}->{''};
  380. return $a->{$k}->{''}->[0];
  381. }
  382. #
  383. # send out $a->{$k} as text-style tag
  384. #
  385. sub xml_text ($$$;$) {
  386. my($i,$a,$k,$tag) = @_;
  387. my($out);
  388. return "" unless exists $a->{$k};
  389. $tag = $k unless defined $tag;
  390. $i = ' ' x $i;
  391. $out = e(n($a,$k));
  392. return if $out eq '';
  393. return "$i<$tag>\n$out\n$i</$tag>\n";
  394. }
  395. #
  396. # send out @{$a->{$k}} as body of an XML tag
  397. # $k is the name of the tag unless overridden by $tag
  398. # $i denotes the depth of indentation to form nicely
  399. # looking files.
  400. #
  401. # all data from the list is flattened into a single
  402. # body, separated by LF and escaped for XML metachars.
  403. #
  404. sub xml_tag ($$$;$) {
  405. my($i,$a,$k,$tag) = @_;
  406. my($out,$cond,$upn);
  407. return "" unless exists $a->{$k};
  408. $tag = $k unless defined $tag;
  409. $out = '';
  410. $i = ' ' x $i;
  411. foreach $cond (sort keys %{$a->{$k}}) {
  412. $upn = e(upn($cond));
  413. $out .= $i.
  414. ($cond ne '' ? "<$tag cond=\"$upn\">" : "<$tag>").
  415. join("\n", map { e($_) } @{$a->{$k}->{$cond}}).
  416. "</$tag>\n";
  417. }
  418. return $out;
  419. }
  420. #
  421. # send out @{$a->{$k}} as a rdf:bag
  422. # $k is the name of the outer tag unless overriden by $tag
  423. # $i denotes the depth of indentation, inner tags are indented
  424. # 2 or 4 more character positions.
  425. #
  426. # each element of the bag is listed
  427. #
  428. sub xml_bag ($$$;$) {
  429. my($i,$a,$k,$tag) = @_;
  430. my($out,$cond,$upn);
  431. return "" unless exists $a->{$k};
  432. $tag = $k unless defined $tag;
  433. $out = '';
  434. $i = ' ' x $i;
  435. foreach $cond (sort keys %{$a->{$k}}) {
  436. next unless @{$a->{$k}->{$cond}};
  437. $upn = e(upn($cond));
  438. $out .= $i.
  439. ($cond ne '' ? "<$tag cond=\"$upn\">\n" : "<$tag>\n").
  440. "$i <rdf:bag>\n".
  441. join("",
  442. map {
  443. ref $_
  444. ? "$i <resource".
  445. ( exists $_->{attrname}
  446. ? " $_->{attrname}=\"".e($_->{attrval})."\""
  447. : ""
  448. ).
  449. ">".e($_->{resource})."</resource>\n"
  450. : "$i <rdf:li>".e($_)."</rdf:li>\n"
  451. }
  452. @{$a->{$k}->{$cond}}).
  453. "$i </rdf:bag>\n".
  454. "$i</$tag>\n";
  455. }
  456. return $out;
  457. }
  458. #
  459. # send out reference to another RDF
  460. #
  461. sub xml_reference ($$$) {
  462. my($fh, $res, $href) = @_;
  463. print $fh <<EOFEOF;
  464. <Repository rdf:resource="$res" href="$href"/>
  465. EOFEOF
  466. }
  467. #
  468. # translate attributes from %$a as generated by package2data
  469. # into XML and write to file $fh
  470. #
  471. sub xml_record ($$$) {
  472. my($fh, $a, $href) = @_;
  473. my($maj,$min,$rel,$about);
  474. $about =
  475. n($a,'Name').'-'.
  476. n($a,'Version').'-'.
  477. n($a,'Release');
  478. unless (defined $href) {
  479. # guess location from Information in Specfile
  480. if (exists $a->{'NoSource'}) {
  481. $href = "$about.nosrc.rpm";
  482. } else {
  483. $href = "$about.src.rpm";
  484. }
  485. ($maj,$min,$rel) = n($a,'Release') =~ /^(\d+)\.(\d+)\.(\d+)/;
  486. if (defined $min) {
  487. if ($maj > 1 || ($maj == 1 && $min > 0)) {
  488. # 1.1 or later
  489. if (n($a,'Distribution') =~ /\[PLUS\]/) {
  490. $href = 'PLUS/'.$href;
  491. }
  492. }
  493. if ($maj > 1 || ($maj == 1 && $min >= 0)) {
  494. # 1.0 or later
  495. if ($rel > 0) {
  496. $href = 'UPD/'.$href;
  497. }
  498. }
  499. } else {
  500. # current
  501. }
  502. }
  503. print $fh <<EOFEOF;
  504. <rdf:Description about="$about" href="$href">
  505. EOFEOF
  506. # fake Source attribute from Source\d attribtutes
  507. # XXX only default conditional
  508. $a->{'Source'} = { '' => [
  509. map {
  510. s/\Q%{name}\E/n($a,'Name')/esg;
  511. s/\Q%{version}\E/n($a,'Version')/esg;
  512. s/\Q%{release}\E/n($a,'Release')/esg;
  513. #s/.*\///;
  514. $_;
  515. }
  516. map {
  517. $a->{$_}->{''} ? @{$a->{$_}->{''}} : ()
  518. }
  519. sort {
  520. my($x) = $a =~ /^(\d*)$/;
  521. my($y) = $b =~ /^(\d*)$/;
  522. return $x <=> $y;
  523. }
  524. grep {
  525. /^Source\d*$/
  526. } keys %$a
  527. ]};
  528. delete $a->{'Source'} unless @{$a->{'Source'}->{''}};
  529. print $fh
  530. xml_tag(6, $a, 'Name'),
  531. xml_tag(6, $a, 'Version'),
  532. xml_tag(6, $a, 'Release'),
  533. xml_tag(6, $a, 'Distribution'),
  534. xml_tag(6, $a, 'Group'),
  535. xml_tag(6, $a, 'License'),
  536. xml_tag(6, $a, 'Packager'),
  537. xml_tag(6, $a, 'Summary'),
  538. xml_tag(6, $a, 'URL'),
  539. xml_tag(6, $a, 'Vendor'),
  540. xml_tag(6, $a, 'SourceRPM'),
  541. xml_tag(6, $a, 'Arch'),
  542. xml_tag(6, $a, 'Os'),
  543. xml_tag(6, $a, 'BuildRoot'),
  544. xml_tag(6, $a, 'BuildHost'),
  545. xml_tag(6, $a, 'BuildSystem'),
  546. xml_tag(6, $a, 'BuildTime'),
  547. xml_tag(6, $a, 'Relocations'),
  548. xml_tag(6, $a, 'Size'),
  549. xml_tag(6, $a, 'Prefixes'),
  550. xml_tag(6, $a, 'Platform'),
  551. xml_tag(6, $a, 'SigSize'),
  552. xml_tag(6, $a, 'SigMD5'),
  553. xml_tag(6, $a, 'SigPGP'),
  554. xml_tag(6, $a, 'SigGPG'),
  555. xml_bag(6, $a, 'BuildPreReq'),
  556. xml_bag(6, $a, 'PreReq'),
  557. xml_bag(6, $a, 'Provides'),
  558. xml_bag(6, $a, 'Conflicts'),
  559. xml_bag(6, $a, 'Source'),
  560. xml_bag(6, $a, 'NoSource'),
  561. xml_bag(6, $a, 'Filenames'),
  562. xml_text(6, $a, 'Description');
  563. print $fh <<EOFEOF;
  564. </rdf:Description>
  565. EOFEOF
  566. }
  567. #####################################################################
  568. sub rpm2spec ($) {
  569. my($fn) = @_;
  570. my($pipe) = new FileHandle "$R2C '$fn' |"
  571. or die "FATAL: cannot read '$fn' ($!)\n";
  572. my($buf,@hdr,$n,$m,$name,$step);
  573. my($spec);
  574. while (read($pipe,$buf,110) == 110) {
  575. @hdr = unpack('a6a8a8a8a8a8a8a8a8a8a8a8a8a8',$buf);
  576. $n = hex($hdr[12]); # filename length
  577. $m = int(($n+5)/4)*4-2; # filename size (padded)
  578. last unless read($pipe,$buf,$m) == $m;
  579. $name = substr($buf,0,$n-1);
  580. $n = hex($hdr[7]); # file length
  581. $m = int(($n+3)/4)*4; # file size (padded)
  582. if ($name !~ /.spec$/) {
  583. while ($m > 0) {
  584. $step = $m > 8192 ? 8192 : $m;
  585. last unless read($pipe,$buf,$step);
  586. $m -= length($buf);
  587. }
  588. } else {
  589. if (read($pipe,$buf,$n) == $n) {
  590. $spec = $buf;
  591. }
  592. last;
  593. }
  594. }
  595. $pipe->close;
  596. return $spec;
  597. }
  598. #####################################################################
  599. sub rpm2data ($$) {
  600. my($fn,$platform) = @_;
  601. my($q,$pipe,%a);
  602. my($t,$v);
  603. $q = <<EOFEOF;
  604. Name %{Name}
  605. Version %{Version}
  606. Release %{Release}
  607. URL %{URL}
  608. Summary %{Summary}
  609. Copyright %{Copyright}
  610. License %{License}
  611. Distribution %{Distribution}
  612. Vendor %{Vendor}
  613. Group %{Group}
  614. Packager %{Packager}
  615. Prefixes %{Prefixes}
  616. BuildRoot %{BuildRoot}
  617. BuildHost %{BuildHost}
  618. BuildTime %{BuildTime}
  619. Arch %{Arch}
  620. Os %{Os}
  621. Size %{Size}
  622. SigSize %{SigSize}
  623. SigMD5 %{SigMD5}
  624. SigPGP %{SigPGP}
  625. SigGPG %{SigGPG}
  626. SourceRPM %{SourceRPM}
  627. [Patch %{Patch}
  628. ]
  629. [Source %{Source}
  630. ]
  631. [Filenames %{Filenames}
  632. ]
  633. [Conflicts %{CONFLICTNAME} %|CONFLICTFLAGS?{%{CONFLICTFLAGS:depflags} %{CONFLICTVERSION}}:{}|
  634. ]
  635. [PreReq %{REQUIRENAME} %|REQUIREFLAGS?{%{REQUIREFLAGS:depflags} %{REQUIREVERSION}}:{}|
  636. ]
  637. [Provides %{PROVIDENAME} %|PROVIDEFLAGS?{%{PROVIDEFLAGS:depflags} %{PROVIDEVERSION}}:{}|
  638. ]
  639. Description %{Description}
  640. EOFEOF
  641. $pipe = new FileHandle "$RPM -qp --qf '$q' '$fn' |"
  642. or die "FATAL: cannot read '$fn' ($!)\n";
  643. while (<$pipe>) {
  644. if (/^(\S+)\s+(.*?)\s*$/) {
  645. $t = $1;
  646. $v = $2;
  647. } elsif (/^(\s+.+?)\s*$/) {
  648. next unless defined $t;
  649. $v = $1;
  650. } else {
  651. $t = undef;
  652. next;
  653. }
  654. if (exists $a{$t}) {
  655. $a{$t} .= "\n$v";
  656. } else {
  657. $a{$t} = $v;
  658. }
  659. }
  660. $pipe->close;
  661. %a = map { $_ => $a{$_} }
  662. grep { $a{$_} ne '(none)' }
  663. keys %a;
  664. if ($a{'Relocations'} eq '(non relocatable)') {
  665. delete $a{'Relocations'};
  666. }
  667. if ($a{'SigMD5'} eq '(unknown type)') {
  668. delete $a{'SigMD5'};
  669. }
  670. $a{'Platform'} = "$a{'Arch'}-$platform-$a{'Os'}";
  671. $a{'PreReq'} =~ s/^rpmlib\(.*$//mg;
  672. $a{'Description'} = [ $a{'Description'} ];
  673. return { map {
  674. $_ => { '' => (ref $a{$_} ? $a{$_} : [ split(/\n+/, $a{$_}) ]) }
  675. } keys %a };
  676. }
  677. #####################################################################
  678. sub getindex ($) {
  679. my($dir) = @_;
  680. my(@idx) = sort { -M $a <=> -M $b; }
  681. grep { -f $_ }
  682. ( <$dir/00INDEX.rdf>, <$dir/00INDEX.rdf.*> );
  683. return unless @idx;
  684. return $idx[0];
  685. }
  686. sub list_specdir ($) {
  687. my($dir) = @_;
  688. my($dh,$d,$path);
  689. my(@list);
  690. $dh = new DirHandle($dir);
  691. while ($d = $dh->read) {
  692. next if $d =~ /^\./;
  693. $path = "$dir/$d/$d.spec";
  694. push @list, $path if -f $path;
  695. }
  696. return \@list;
  697. }
  698. sub list_rpmdir ($) {
  699. my($dir) = @_;
  700. my($dh,$d,$path);
  701. my(@list,$idx,$sub);
  702. $dh = new DirHandle($dir);
  703. while ($d = $dh->read) {
  704. next if $d =~ /^\./;
  705. $path = "$dir/$d";
  706. if (-d $path) {
  707. $idx = getindex($path);
  708. if (defined $idx) {
  709. push @list, $idx;
  710. } else {
  711. $sub = list_rpmdir($path);
  712. push @list, @$sub;
  713. undef $sub;
  714. }
  715. } else {
  716. next unless $d =~ /\.rpm$/ && -f $path;
  717. push @list, $path;
  718. }
  719. }
  720. return \@list;
  721. }
  722. #####################################################################
  723. sub readfile ($) {
  724. my($fn) = @_;
  725. my($fh) = new FileHandle "< $fn"
  726. or die "FATAL: cannot read '$fn' ($!)\n";
  727. my(@l) = <$fh>;
  728. $fh->close;
  729. return join('',@l);
  730. }
  731. sub relpath ($$) {
  732. my($prefix,$path) = @_;
  733. $path =~ s/^\Q$prefix\E\///s;
  734. return $path;
  735. }
  736. sub dirname ($) {
  737. my($path) = @_;
  738. $path =~ s/\/[^\/]*$//s;
  739. return $path.'/';
  740. }
  741. sub getresource ($) {
  742. my($fn) = @_;
  743. my($fh, $buf);
  744. if ($fn =~ /\.bz2$/) {
  745. $fh = new FileHandle "$BZ -dc $fn |"
  746. or die "FATAL: cannot read '$fn' ($!)\n";
  747. } else {
  748. $fh = new FileHandle "< $fn"
  749. or die "FATAL: cannot read '$fn' ($!)\n";
  750. }
  751. $fh->read($buf, 1024);
  752. $fh->close;
  753. if ($buf =~ /<Repository.*?rdf:resource="([^"]+)"/) {
  754. return $1;
  755. }
  756. return undef;
  757. }
  758. #####################################################################
  759. sub write_index ($$$$$$) {
  760. my($fh,$prefix,$resource,$platform,$list,$cache) = @_;
  761. my($a,$h,$r,$spec);
  762. my($mtime);
  763. foreach (@$list) {
  764. $a = undef;
  765. $h = undef;
  766. $r = undef;
  767. if (/\.spec$/) {
  768. $spec = readfile($_);
  769. $a = spec2data($spec);
  770. } elsif (/([^\/]+\.(?:no)?src\.rpm)$/) {
  771. $h = relpath($prefix, $_);
  772. if ($cache) {
  773. $mtime = (stat $_)[9];
  774. if (exists $cache->{"M$_"} &&
  775. $cache->{"M$_"} == $mtime) {
  776. $spec = $cache->{"S$_"};
  777. } else {
  778. $spec = rpm2spec($_);
  779. $cache->{"S$_"} = $spec;
  780. $cache->{"M$_"} = $mtime;
  781. }
  782. } else {
  783. $spec = rpm2spec($_);
  784. }
  785. $a = spec2data($spec);
  786. } elsif (/([^\/]+\.rpm)$/) {
  787. $h = relpath($prefix, $_);
  788. $a = rpm2data($_, $platform);
  789. } elsif (/([^\/]+\.rdf[^\/]*)$/) {
  790. $h = relpath($prefix, $_);
  791. $r = getresource($_) || $resource.dirname($h);
  792. }
  793. if ($a) {
  794. xml_record($fh, $a, $h);
  795. } elsif ($r) {
  796. xml_reference($fh, $r, $h);
  797. } else {
  798. warn "ERROR: cannot process $_\n";
  799. }
  800. }
  801. }
  802. #####################################################################
  803. my($prefix,$list,$fh,%cache,$tmpo);
  804. if ($#ARGV < 0) {
  805. print "openpkg:index:USAGE: $0 [-r resource] [-p platform] [-C cache.db] [-o index.rdf] [-c] [-i] dir ...\n";
  806. die "\n";
  807. }
  808. if ($opt_C) {
  809. require DB_File;
  810. tie %cache, 'DB_File', $opt_C, O_CREAT|O_RDWR, 0666, $DB_File::DB_HASH
  811. or die "FATAL: cannot tie cache '$opt_C' ($!)\n";
  812. }
  813. $opt_r = 'OpenPKG-CURRENT/Source/' unless defined $opt_r;
  814. $opt_p = 'unknown' unless defined $opt_p;
  815. if (defined $opt_o) {
  816. $tmpo = $opt_o . '.tmp';
  817. if ($opt_c) {
  818. $fh = new FileHandle "| $BZ -c > '$tmpo'"
  819. or die "FATAL: cannot write '$tmpo' ($!)\n";
  820. } else {
  821. $fh = new FileHandle "> $tmpo"
  822. or die "FATAL: cannot write '$tmpo' ($!)\n";
  823. }
  824. } else {
  825. if ($opt_c) {
  826. $fh = new FileHandle "| $BZ -c"
  827. or die "FATAL: cannot write to stdout ($!)\n";
  828. } else {
  829. $fh = new FileHandle ">&=1"
  830. or die "FATAL: cannot write to stdout ($!)\n";
  831. }
  832. }
  833. xml_head($fh, $opt_r);
  834. foreach $prefix (@ARGV) {
  835. die "FATAL: $prefix is not a directory\n" unless -d $prefix;
  836. if ($opt_i) {
  837. $list = list_rpmdir($prefix);
  838. } else {
  839. $list = list_specdir($prefix);
  840. }
  841. write_index($fh, $prefix, $opt_r, $opt_p, $list, $opt_C ? \%cache : undef);
  842. }
  843. xml_foot($fh);
  844. $fh->close
  845. or die "FATAL: write error on output ($!)\n";
  846. if (defined $tmpo) {
  847. rename $tmpo,$opt_o
  848. or die "FATAL: cannot rename $tmpo to $opt_o ($!)\n";
  849. }