]>
Commit | Line | Data |
---|---|---|
0db58532 MM |
1 | #!/usr/bin/perl |
2 | # | |
3 | # USAGE: specparser.pl file.spec | |
4 | # | |
5 | # Output: | |
6 | # stdout: | |
7 | # list of: | |
8 | # <md5><space><url>\n | |
9 | # for given specfile. | |
1b5ce8c2 | 10 | # except for lines prefixed with: ERROR: |
0db58532 MM |
11 | # exit status: |
12 | # 0 - normal | |
13 | # 2 - cannot open spec file | |
14 | # | |
8079fcc5 | 15 | use strict; |
16 | use warnings; | |
17 | ||
18 | my %no_source; | |
19 | my $spec; | |
20 | my $base_spec; | |
21 | my @spec; | |
22 | my @sources; | |
1db641df | 23 | my $sources_file; |
8b8c50a0 | 24 | my %patchset; |
0db58532 | 25 | |
ca6092e6 ER |
26 | sub print_source($$$); |
27 | ||
0dd6294b MM |
28 | sub next_spec($) |
29 | { | |
e3413dab ER |
30 | $spec = shift; |
31 | @spec = (); | |
32 | $base_spec = $spec; | |
33 | $base_spec =~ s|.*/||; | |
0dd6294b | 34 | } |
0db58532 | 35 | |
0dd6294b | 36 | sub error($) |
0db58532 | 37 | { |
e3413dab | 38 | print_once( "ERROR: $base_spec: $_[0]" ); |
0dd6294b MM |
39 | } |
40 | ||
41 | sub warning($) | |
42 | { | |
e3413dab | 43 | print "ERROR: $base_spec: $_[0]\n"; |
0dd6294b | 44 | } |
51a652ef | 45 | |
0dd6294b MM |
46 | sub trim_spaces($) |
47 | { | |
e3413dab | 48 | my $v = shift; |
62c5617b | 49 | |
e3413dab ER |
50 | $v =~ s/\s+$//; |
51 | $v =~ s/^\s+//; | |
62c5617b | 52 | |
e3413dab | 53 | return $v; |
0dd6294b MM |
54 | } |
55 | ||
56 | # expand macros in string | |
8079fcc5 | 57 | sub expand($$) # {{{ |
0dd6294b | 58 | { |
e3413dab ER |
59 | my $v = trim_spaces(shift); |
60 | my $macrotree = shift; | |
61 | my $cnt = 20; | |
62 | ||
63 | while ($v =~ /\%\{([^\}]+)\}/) { | |
64 | my $value; | |
65 | if (defined $macrotree->{$1}) { | |
66 | $value = $macrotree->{$1}; | |
67 | } else { | |
68 | error("undefined macro $1"); | |
69 | $value = "UNDEFINED"; | |
70 | return undef; | |
71 | } | |
72 | $v =~ s/\%\{([^\}]+)\}/$value/; | |
73 | ||
74 | return $v if (length $v > 1000 or $cnt-- <= 0) | |
75 | } | |
76 | ||
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\+_\-\.]+$/) { | |
85 | if ($d_opt) { | |
86 | eval "\$what =~ tr/$from//d;"; | |
87 | } else { | |
88 | eval "\$what =~ tr/$from/$to/;"; | |
89 | } | |
90 | } else { | |
91 | error("illegal characters in tr string(s) '$from' '$to'"); | |
92 | } | |
93 | $v =~ s/\@\@tr-me\@\@/$what/; | |
94 | ||
95 | return $v if (length $v > 1000 or $cnt-- <= 0) | |
96 | } | |
97 | ||
98 | error("unexpanded macros in $v") | |
99 | if ($v =~ /\%[^0-9]/); | |
100 | ||
101 | return $v; | |
8079fcc5 | 102 | } # }}} |
103 | ||
104 | sub preparse_spec($) # {{{ | |
0db58532 | 105 | { |
e3413dab ER |
106 | @spec = (""); |
107 | ||
108 | open(F, "< $_[0]") or die("failed opening: " . $_[0]); | |
109 | while (<F>) { | |
110 | chomp; | |
111 | if (/^\s*(\%(description|package|prep|install|pre|post|files)|BuildRoot|URL)/) { | |
112 | last; | |
113 | } elsif (/^\s*(\%if.*|\%else|\%endif|\%define\s+.*|Version.*|Name.*)\s*$/) { | |
114 | $_ = $1; | |
115 | if ($spec[$#spec] =~ /\%if/) { | |
116 | if (/\%else/) { | |
117 | next; # don't include empty %if-%else | |
118 | } elsif (/\%endif/) { | |
119 | # remove empty %if-%endif | |
120 | pop @spec; | |
121 | next; | |
122 | } | |
123 | } | |
124 | push @spec, $_; | |
125 | } elsif (/^NoSource\s*:\s*(\d+)\s*$/i) { | |
126 | $no_source{ "source" . $1 } = 1; | |
736b2879 ER |
127 | } elsif (my ($patchset) = /^%patchset_source\s+(.+)$/) { |
128 | use Getopt::Long qw(GetOptionsFromString); | |
129 | my ($f, $s); | |
130 | my ($ret, $args) = GetOptionsFromString($patchset, 's=s' => \$s, 'f=s' => \$f); | |
e3413dab | 131 | %patchset = ( |
736b2879 ER |
132 | pattern => $f, |
133 | filelist => $s, | |
134 | start => $args->[0], | |
135 | end => $args->[1], | |
e3413dab ER |
136 | ); |
137 | } | |
138 | } | |
139 | close(F); | |
140 | ||
141 | shift @spec; | |
8079fcc5 | 142 | } # }}} |
143 | ||
1db641df | 144 | # read in 'sources' file |
736b2879 ER |
145 | sub read_sources_file { |
146 | my $filename = $_[0] || $sources_file; | |
147 | return () unless $filename and -e $filename; | |
e3413dab | 148 | |
736b2879 | 149 | my %files; |
1db641df | 150 | |
736b2879 | 151 | open(my $fh, '<', $filename) or die $!; |
e3413dab ER |
152 | while (<$fh>) { |
153 | chomp; | |
154 | next unless my ($hash, $filename) = /^([a-f0-9]{32})\s+\*?(.+)$/; | |
155 | $files{$filename} = $hash; | |
156 | } | |
1db641df ER |
157 | return \%files; |
158 | } | |
159 | ||
160 | sub process_patchset($) { | |
161 | my $macros = shift; | |
736b2879 ER |
162 | my $checksums; |
163 | ||
1db641df ER |
164 | return unless %patchset; |
165 | ||
736b2879 ER |
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; | |
174 | } | |
175 | return; | |
176 | } | |
177 | ||
178 | # parse sources file sequences | |
179 | $checksums = read_sources_file() or return; | |
e3413dab ER |
180 | |
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{/([^/]+$)}; | |
1db641df | 188 | my $hash = $checksums->{$basename} or next; |
e3413dab ER |
189 | print_source "patchset $i", $hash, $url; |
190 | } | |
8b8c50a0 ER |
191 | } |
192 | ||
8079fcc5 | 193 | |
194 | my $total = 0; | |
195 | ||
196 | sub cont($$); | |
197 | sub cont($$) # {{{ | |
198 | { | |
e3413dab ER |
199 | my ($spec, $macros) = @_; |
200 | local $_; | |
201 | while ($_ = shift @{$spec}) { | |
202 | if (0 <= index $_, '%if') { # if, ifarch, ifos | |
203 | ||
204 | # split spec parsing | |
205 | my @speccopy = @{$spec}; | |
206 | my %macroscopy = %{$macros}; | |
207 | cont(\@speccopy, \%macroscopy); | |
208 | ||
209 | my $level = 0; | |
210 | while ($_ = shift @{$spec}) { | |
211 | if ($level <= 0 and ($_ eq '%else' or $_ eq '%endif')) { | |
212 | last; | |
213 | } elsif (0 <= index $_, '%if') { | |
214 | $level++; | |
215 | } elsif ($_ eq '%endif') { | |
216 | $level--; | |
217 | } | |
218 | } | |
219 | ||
220 | # continue parsing | |
221 | } elsif ($_ eq '%else') { | |
222 | ||
223 | # %else happens only when %if was interpreted | |
224 | # so skip until %endif | |
225 | ||
226 | my $level = 0; | |
227 | while ($_ = shift @{$spec}) { | |
228 | if ($level <= 0 and $_ eq '%endif') { | |
229 | last; | |
230 | } elsif (0 <= index $_, '%if') { | |
231 | $level++; | |
232 | } elsif ($_ eq '%endif') { | |
233 | $level--; | |
234 | } | |
235 | } | |
236 | ||
237 | } elsif (/^\s*\%(define|global)\s+([^\s]+)\s+([^\s].*?)\s*$/) { | |
238 | $macros->{$2} = $3; | |
239 | ||
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"; | |
246 | } | |
247 | } | |
248 | ||
249 | # so we have macros now, can parse patchset source | |
250 | process_patchset($macros); | |
251 | ||
252 | # the end, yuppie ! | |
253 | foreach my $s (@sources) { | |
254 | my $src = expand( $s->[2], $macros ); | |
255 | if (defined $src) { | |
256 | our %tried; | |
257 | unless (exists $tried{$src}) { | |
258 | print_source( $s->[0], $s->[1], $src ); | |
259 | $tried{$src} = 1; | |
260 | } | |
261 | } | |
262 | } | |
263 | ||
264 | if (++$total > 10000) { | |
265 | error("maximum number of bcond posibilities exceeded"); | |
266 | exit 0; | |
267 | } | |
8079fcc5 | 268 | } # }}} |
269 | ||
270 | sub print_once($) | |
0db58532 | 271 | { |
e3413dab ER |
272 | our %printed; |
273 | my $l = shift; | |
274 | unless (exists $printed{$l}) { | |
275 | print $l . "\n"; | |
276 | $printed{$l} = 1; | |
277 | } | |
0db58532 MM |
278 | } |
279 | ||
8079fcc5 | 280 | sub print_source($$$) # {{{ |
0db58532 | 281 | { |
e3413dab ER |
282 | my ($no, $md5, $s) = @_; |
283 | ||
284 | if ($s =~ /^([a-z0-9A-Z;:\=\?&\@\+\~\.,\-\/_]|\%[0-9])+(#\/[a-zA-Z0-9\._-]+)?$/) { | |
285 | if ($s =~ /^(ftp|http|https):\/\//) { | |
286 | if ($s =~ /\/$/) { | |
287 | error("source $no ($s) is directory"); | |
288 | } else { | |
289 | if ($s =~ /:\/\/distfiles\.pld-linux\.org\/src/) { | |
290 | $s =~ s|.*/||; | |
291 | print_once( "$md5 no-url-copy://$s" ); | |
292 | } else { | |
293 | print_once( "$md5 $s" ); | |
294 | } | |
295 | } | |
296 | } else { | |
297 | $s =~ s|.*/||; | |
298 | print_once( "$md5 no-url://$s"); | |
299 | } | |
300 | } else { | |
301 | error("source $no url $s is ill-formatted"); | |
302 | } | |
8079fcc5 | 303 | } # }}} |
304 | ||
305 | sub add_md5_to_print($) # {{{ | |
306 | { | |
e3413dab ER |
307 | open(F, "< $_[0]") or die("failed opening: " . $_[0]); |
308 | my %sourcemap = (); | |
309 | while (<F>) { | |
310 | chomp; | |
311 | if (/^((?:Source|Patch)\d+)\s*:\s*(.*)/i) { | |
312 | my $sourceno = lc $1; | |
313 | my $source = $2; | |
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) { | |
318 | my $no = lc $1; | |
319 | my $md5 = $2; | |
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]; | |
325 | } else { | |
326 | error("source $no not defined (# SourceN-md5: has to be placed just after SourceN:)"); | |
327 | } | |
328 | } elsif (/^\s*BuildRequires:\s*digest\(%((?:SOURCE|PATCH)\d+)\)\s*=\s*([a-f0-9]{32})/i) { | |
329 | my $no = lc $1; | |
330 | my $md5 = $2; | |
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]; | |
336 | } else { | |
337 | error("source $no not defined (# Source digest has to be placed after SourceN:)"); | |
338 | } | |
339 | } | |
340 | } | |
341 | close(F); | |
0de41088 ER |
342 | |
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{/([^/]+$)}; | |
164cf5c1 | 349 | next unless defined $basename; |
0de41088 ER |
350 | my $hash = $checksums->{$basename} or next; |
351 | push @sources, [$source, $hash, $url]; | |
352 | } | |
8079fcc5 | 353 | } # }}} |
0db58532 | 354 | |
0dd6294b | 355 | next_spec(shift); |
1db641df | 356 | $sources_file = shift if @ARGV; |
8079fcc5 | 357 | preparse_spec($spec); |
358 | add_md5_to_print($spec); | |
359 | cont( \@spec, { "nil" => "" } ); | |
0db58532 MM |
360 | |
361 | exit(0); | |
8079fcc5 | 362 | |
363 | # vim: ts=4:sw=4:fdm=marker |