3 # USAGE: specparser.pl file.spec
10 # except for lines prefixed with: ERROR:
13 # 2 - cannot open spec file
26 sub print_source($$$);
33 $base_spec =~ s|.*/||;
38 print_once( "ERROR: $base_spec: $_[0]" );
43 print "ERROR: $base_spec: $_[0]\n";
56 # expand macros in string
59 my $v = trim_spaces(shift);
60 my $macrotree = shift;
63 while ($v =~ /\%\{([^\}]+)\}/) {
65 if (defined $macrotree->{$1}) {
66 $value = $macrotree->{$1};
68 error("undefined macro $1");
72 $v =~ s/\%\{([^\}]+)\}/$value/;
74 return $v if (length $v > 1000 or $cnt-- <= 0)
77 while ($v =~ s/\%\(\s*echo\s+([^\|]+?)\s*\|\s*tr\s*(-d|)\s+([^\)]+?)\s*\)/\@\@tr-me\@\@/) {
78 my ($what, $d_opt, $how) = ($1, $2, $3);
79 my ($from, $to) = ($how, "");
80 ($from, $to) = ($1, $2)
81 if $how =~ /^([^\s]+)\s+([^\s]+)$/;
82 if ($d_opt and $to ne "") {
83 error("tr -d with second string)");
84 } elsif (($from . $to) =~ /^[a-zA-Z0-9\+_\-\.]+$/) {
86 eval "\$what =~ tr/$from//d;";
88 eval "\$what =~ tr/$from/$to/;";
91 error("illegal characters in tr string(s) '$from' '$to'");
93 $v =~ s/\@\@tr-me\@\@/$what/;
95 return $v if (length $v > 1000 or $cnt-- <= 0)
98 error("unexpanded macros in $v")
99 if ($v =~ /\%[^0-9]/);
104 sub preparse_spec($) # {{{
108 open(F, "< $_[0]") or die("failed opening: " . $_[0]);
111 if (/^\s*(\%(description|package|prep|install|pre|post|files)|BuildRoot|URL)/) {
113 } elsif (/^\s*(\%if.*|\%else|\%endif|\%define\s+.*|Version.*|Name.*)\s*$/) {
115 if ($spec[$#spec] =~ /\%if/) {
117 next; # don't include empty %if-%else
118 } elsif (/\%endif/) {
119 # remove empty %if-%endif
125 } elsif (/^NoSource\s*:\s*(\d+)\s*$/i) {
126 $no_source{ "source" . $1 } = 1;
127 } elsif (my ($patchset) = /^%patchset_source\s+(.+)$/) {
128 use Getopt::Long qw(GetOptionsFromString);
130 my ($ret, $args) = GetOptionsFromString($patchset, 's=s' => \$s, 'f=s' => \$f);
144 # read in 'sources' file
145 sub read_sources_file {
146 my $filename = $_[0] || $sources_file;
147 return () unless $filename and -e $filename;
151 open(my $fh, '<', $filename) or die $!;
154 next unless my ($hash, $filename) = /^([a-f0-9]{32})\s+\*?(.+)$/;
155 $files{$filename} = $hash;
160 sub process_patchset($) {
164 return unless %patchset;
166 # print all files from sources file
167 if ($patchset{filelist}) {
168 my $prefix = expand($patchset{pattern}, $macros);
169 my $filelist = expand($patchset{filelist}, $macros);
170 $checksums = read_sources_file($filelist);
171 while (my($file, $hash) = each %$checksums) {
172 my $url = $prefix . $file;
173 print_source "patchset $file", $hash, $url;
178 # parse sources file sequences
179 $checksums = read_sources_file() or return;
181 # print out patchset entries which source md5 is present in source file
182 my $start = expand($patchset{start}, $macros);
183 my $end = expand($patchset{end}, $macros);
184 my $pattern = expand($patchset{pattern}, $macros);
185 for (my $i = $start; $i <= $end; $i++) {
186 my $url = sprintf($pattern, $i);
187 my ($basename) = $url =~ m{/([^/]+$)};
188 my $hash = $checksums->{$basename} or next;
189 print_source "patchset $i", $hash, $url;
199 my ($spec, $macros) = @_;
201 while ($_ = shift @{$spec}) {
202 if (0 <= index $_, '%if') { # if, ifarch, ifos
205 my @speccopy = @{$spec};
206 my %macroscopy = %{$macros};
207 cont(\@speccopy, \%macroscopy);
210 while ($_ = shift @{$spec}) {
211 if ($level <= 0 and ($_ eq '%else' or $_ eq '%endif')) {
213 } elsif (0 <= index $_, '%if') {
215 } elsif ($_ eq '%endif') {
221 } elsif ($_ eq '%else') {
223 # %else happens only when %if was interpreted
224 # so skip until %endif
227 while ($_ = shift @{$spec}) {
228 if ($level <= 0 and $_ eq '%endif') {
230 } elsif (0 <= index $_, '%if') {
232 } elsif ($_ eq '%endif') {
237 } elsif (/^\s*\%(define|global)\s+([^\s]+)\s+([^\s].*?)\s*$/) {
240 } elsif (/^Version\s*:\s*(.*?)\s*$/i) {
241 $macros->{"version"} = $1;
242 } elsif (/^Name\s*:\s*(.*?)\s*$/i) {
243 $macros->{"name"} = $1;
244 } elsif (!/\%endif/) {
245 warn "unrecognised line: $_\n";
249 # so we have macros now, can parse patchset source
250 process_patchset($macros);
253 foreach my $s (@sources) {
254 my $src = expand( $s->[2], $macros );
257 unless (exists $tried{$src}) {
258 print_source( $s->[0], $s->[1], $src );
264 if (++$total > 10000) {
265 error("maximum number of bcond posibilities exceeded");
274 unless (exists $printed{$l}) {
280 sub print_source($$$) # {{{
282 my ($no, $md5, $s) = @_;
284 if ($s =~ /^([a-z0-9A-Z;:\=\?&\@\+\~\.,\-\/_]|\%[0-9])+(#\/[a-zA-Z0-9\._-]+)?$/) {
285 if ($s =~ /^(ftp|http|https):\/\//) {
287 error("source $no ($s) is directory");
289 if ($s =~ /:\/\/distfiles\.pld-linux\.org\/src/) {
291 print_once( "$md5 no-url-copy://$s" );
293 print_once( "$md5 $s" );
298 print_once( "$md5 no-url://$s");
301 error("source $no url $s is ill-formatted");
305 sub add_md5_to_print($) # {{{
307 open(F, "< $_[0]") or die("failed opening: " . $_[0]);
311 if (/^((?:Source|Patch)\d+)\s*:\s*(.*)/i) {
312 my $sourceno = lc $1;
314 # master.dl is outdated currently
315 # $source =~ s/dl.sourceforge.net/master.dl.sourceforge.net/;
316 $sourcemap{ $sourceno } = $source;
317 } elsif (/^\s*#\s*((?:source|patch)\d+)-md5\s*:\s*([a-f0-9]{32})/i) {
320 if (defined $no_source{$no}) {
321 error("both NoSource: $no and md5 given");
322 } elsif (defined $sourcemap{ $no} ) {
323 my $source = $sourcemap{ $no };
324 push @sources, [$no, $md5, $source];
326 error("source $no not defined (# SourceN-md5: has to be placed just after SourceN:)");
328 } elsif (/^\s*BuildRequires:\s*digest\(%((?:SOURCE|PATCH)\d+)\)\s*=\s*([a-f0-9]{32})/i) {
331 if (defined $no_source{ $no }) {
332 error("both NoSource: $no and md5 given");
333 } elsif (defined $sourcemap{ $no }) {
334 my $source = $sourcemap{ $no };
335 push @sources, [$no, $md5, $source];
337 error("source $no not defined (# Source digest has to be placed after SourceN:)");
343 # find extra sources from detached checksum file
344 my $checksums = read_sources_file();
345 my @mapped = map { $_->[0] } @sources;
346 delete @sourcemap{@mapped};
347 while (my($source, $url) = each %sourcemap) {
348 my ($basename) = $url =~ m{/([^/]+$)};
349 next unless defined $basename;
350 my $hash = $checksums->{$basename} or next;
351 push @sources, [$source, $hash, $url];
356 $sources_file = shift if @ARGV;
357 preparse_spec($spec);
358 add_md5_to_print($spec);
359 cont( \@spec, { "nil" => "" } );
363 # vim: ts=4:sw=4:fdm=marker