]> git.pld-linux.org Git - projects/distfiles.git/blob - specparser.pl
add support for PACKAGE/sources file containing additional checksum of files
[projects/distfiles.git] / specparser.pl
1 #!/usr/bin/perl
2 # $Id$
3 #
4 # USAGE: specparser.pl file.spec
5 #
6 # Output:
7 #   stdout:
8 #     list of:
9 #       <md5><space><url>\n
10 #     for given specfile.
11 #     except for lines prefixed with: ERROR:
12 #   exit status:
13 #     0 - normal
14 #     2 - cannot open spec file
15 #
16 use strict;
17 use warnings;
18
19 my %no_source;
20 my $spec;
21 my $base_spec;
22 my @spec;
23 my @sources;
24 my $sources;
25 my %patchset;
26
27 sub next_spec($)
28 {
29     $spec = shift;
30     @spec = ();
31     $base_spec = $spec;
32     $base_spec =~ s|.*/||;
33 }
34
35 sub error($)
36 {
37     print_once( "ERROR: $base_spec: $_[0]" );
38 }
39
40 sub warning($)
41 {
42     print "ERROR: $base_spec: $_[0]\n";
43 }
44
45 sub trim_spaces($)
46 {
47     my $v = shift;
48
49     $v =~ s/\s+$//;
50     $v =~ s/^\s+//;
51
52     return $v;
53 }
54
55 # expand macros in string
56 sub expand($$) # {{{
57 {
58     my $v = trim_spaces(shift);
59     my $macrotree = shift;
60     my $cnt = 20;
61
62     while ($v =~ /\%\{([^\}]+)\}/) {
63         my $value;
64         if (defined $macrotree->{$1}) {
65             $value = $macrotree->{$1};
66         } else {
67             error("undefined macro $1");
68             $value = "UNDEFINED";
69             return undef;
70         }
71         $v =~ s/\%\{([^\}]+)\}/$value/;
72
73         return $v if (length $v > 1000 or $cnt-- <= 0)
74     }
75
76     while ($v =~ s/\%\(\s*echo\s+([^\|]+?)\s*\|\s*tr\s*(-d|)\s+([^\)]+?)\s*\)/\@\@tr-me\@\@/) {
77         my ($what, $d_opt, $how) = ($1, $2, $3);
78         my ($from, $to) = ($how, "");
79         ($from, $to) = ($1, $2)
80             if $how =~ /^([^\s]+)\s+([^\s]+)$/;
81         if ($d_opt and $to ne "") {
82             error("tr -d with second string)");
83         } elsif (($from . $to) =~ /^[a-zA-Z0-9\+_\-\.]+$/) {
84             if ($d_opt) {
85                 eval "\$what =~ tr/$from//d;";
86             } else {
87                 eval "\$what =~ tr/$from/$to/;";
88             }
89         } else {
90             error("illegal characters in tr string(s) '$from' '$to'");
91         }
92         $v =~ s/\@\@tr-me\@\@/$what/;
93
94         return $v if (length $v > 1000 or $cnt-- <= 0)
95     }
96
97     error("unexpanded macros in $v")
98         if ($v =~ /\%[^0-9]/);
99
100     return $v;
101 } # }}}
102
103 sub preparse_spec($) # {{{
104 {
105     @spec = ("");
106
107     open(F, "< $_[0]") or die("failed opening: " . $_[0]);
108     while (<F>) {
109         chomp;
110         if (/^\s*(\%(description|package|prep|install|pre|post|files)|BuildRoot|URL)/) {
111             last;
112         } elsif (/^\s*(\%if.*|\%else|\%endif|\%define\s+.*|Version.*|Name.*)\s*$/) {
113             $_ = $1;
114             if ($spec[$#spec] =~ /\%if/) {
115                 if (/\%else/) {
116                     next; # don't include empty %if-%else
117                 } elsif (/\%endif/) {
118                     # remove empty %if-%endif
119                     pop @spec;
120                     next;
121                 }
122             }
123             push @spec, $_;
124         } elsif (/^NoSource\s*:\s*(\d+)\s*$/i) {
125             $no_source{ "source" . $1 } = 1;
126         } elsif (my ($patchset_pattern, $patchset_start, $patchset_end) = /^%patchset_source.+-f\s+(\S+)\s+(\S+)\s+(\S+)/i) {
127             %patchset = (
128                 pattern => $patchset_pattern,
129                 start => $patchset_start,
130                 end => $patchset_end
131             );
132         }
133     }
134     close(F);
135
136     shift @spec;
137 } # }}}
138
139 sub process_patchset($) {
140     my $macros = shift;
141     return unless %patchset or $sources;
142
143     # parse sources file
144     my %files;
145     open(my $fh, '<', $sources) or die $!;
146     while (<$fh>) {
147         chomp;
148         next unless my ($hash, $filename) = /^([a-f0-9]{32})\s+\*?(.+)$/;
149         $files{$filename} = $hash;
150     }
151
152     # print out patchset entries which source md5 is present in source file
153     my $start = expand($patchset{start}, $macros);
154     my $end = expand($patchset{end}, $macros);
155     for (my $i = $start; $i < $end; $i++) {
156         my $url = sprintf($patchset{pattern}, $i);
157         my ($basename) = $url =~ m{/([^/]+$)};
158         my $hash = $files{$basename} or next;
159         printf "%s %s\n", $hash, $url;
160     }
161 }
162
163
164 my $total = 0;
165
166 sub cont($$);
167 sub cont($$) # {{{
168 {
169     my ($spec, $macros) = @_;
170     local $_;
171     while ($_ = shift @{$spec}) {
172         if (0 <= index $_, '%if') { # if, ifarch, ifos
173
174             # split spec parsing
175             my @speccopy = @{$spec};
176             my %macroscopy = %{$macros};
177             cont(\@speccopy, \%macroscopy);
178
179             my $level = 0;
180             while ($_ = shift @{$spec}) {
181                 if ($level <= 0 and ($_ eq '%else' or $_ eq '%endif')) {
182                     last;
183                 } elsif (0 <= index $_, '%if') {
184                     $level++;
185                 } elsif ($_ eq '%endif') {
186                     $level--;
187                 }
188             }
189
190             # continue parsing
191         } elsif ($_ eq '%else') {
192
193             # %else happens only when %if was interpreted
194             # so skip until %endif
195
196             my $level = 0;
197             while ($_ = shift @{$spec}) {
198                 if ($level <= 0 and $_ eq '%endif') {
199                     last;
200                 } elsif (0 <= index $_, '%if') {
201                     $level++;
202                 } elsif ($_ eq '%endif') {
203                     $level--;
204                 }
205             }
206
207         } elsif (/^\s*\%(define|global)\s+([^\s]+)\s+([^\s].*?)\s*$/) {
208             $macros->{$2} = $3;
209
210         } elsif (/^Version\s*:\s*(.*?)\s*$/i) {
211             $macros->{"version"} = $1;
212         } elsif (/^Name\s*:\s*(.*?)\s*$/i) {
213             $macros->{"name"} = $1;
214         } elsif (!/\%endif/) {
215             warn "unrecognised line: $_\n";
216         }
217     }
218
219     # so we have macros now, can parse patchset source
220     process_patchset($macros);
221
222     # the end, yuppie !
223     foreach my $s (@sources) {
224         my $src = expand( $s->[2], $macros );
225         if (defined $src) {
226             our %tried;
227             unless (exists $tried{$src}) {
228                 print_source( $s->[0], $s->[1], $src );
229                 $tried{$src} = 1;
230             }
231         }
232     }
233
234     if (++$total > 10000) {
235         error("maximum number of bcond posibilities exceeded");
236         exit 0;
237     }
238 } # }}}
239
240 sub print_once($)
241 {
242     our %printed;
243     my $l = shift;
244     unless (exists $printed{$l}) {
245         print $l . "\n";
246         $printed{$l} = 1;
247     }
248 }
249
250 sub print_source($$$) # {{{
251 {
252     my ($no, $md5, $s) = @_;
253
254     if ($s =~ /^([a-z0-9A-Z;:\=\?&\@\+\~\.,\-\/_]|\%[0-9])+(#\/[a-zA-Z0-9\._-]+)?$/) {
255         if ($s =~ /^(ftp|http|https):\/\//) {
256             if ($s =~ /\/$/) {
257                 error("source $no ($s) is directory");
258             } else {
259                 if ($s =~ /:\/\/distfiles\.pld-linux\.org\/src/) {
260                     $s =~ s|.*/||;
261                     print_once( "$md5 no-url-copy://$s" );
262                 } else {
263                     print_once( "$md5 $s" );
264                 }
265             }
266         } else {
267             $s =~ s|.*/||;
268             print_once( "$md5 no-url://$s");
269         }
270     } else {
271         error("source $no url $s is ill-formatted");
272     }
273 } # }}}
274
275 sub add_md5_to_print($) # {{{
276 {
277     open(F, "< $_[0]") or die("failed opening: " . $_[0]);
278     my %sourcemap = ();
279     while (<F>) {
280         chomp;
281         if (/^((?:Source|Patch)\d+)\s*:\s*(.*)/i) {
282             my $sourceno = lc $1;
283             my $source = $2;
284             # master.dl is outdated currently
285             # $source =~ s/dl.sourceforge.net/master.dl.sourceforge.net/;
286             $sourcemap{ $sourceno } = $source;
287         } elsif (/^\s*#\s*((?:source|patch)\d+)-md5\s*:\s*([a-f0-9]{32})/i) {
288             my $no = lc $1;
289             my $md5 = $2;
290             if (defined $no_source{$no}) {
291                 error("both NoSource: $no and md5 given");
292             } elsif (defined $sourcemap{ $no} ) {
293                 my $source = $sourcemap{ $no };
294                 push @sources, [$no, $md5, $source];
295             } else {
296                 error("source $no not defined (# SourceN-md5: has to be placed just after SourceN:)");
297             }
298         } elsif (/^\s*BuildRequires:\s*digest\(%((?:SOURCE|PATCH)\d+)\)\s*=\s*([a-f0-9]{32})/i) {
299             my $no = lc $1;
300             my $md5 = $2;
301             if (defined $no_source{ $no }) {
302                 error("both NoSource: $no and md5 given");
303             } elsif (defined $sourcemap{ $no }) {
304                 my $source = $sourcemap{ $no };
305                 push @sources, [$no, $md5, $source];
306             } else {
307                 error("source $no not defined (# Source digest has to be placed after SourceN:)");
308             }
309         }
310     }
311     close(F);
312 } # }}}
313
314 next_spec(shift);
315 $sources = shift if @ARGV;
316 undef $sources if $sources and not -e $sources;
317 preparse_spec($spec);
318 add_md5_to_print($spec);
319 cont( \@spec, { "nil" => "" } );
320
321 exit(0);
322
323 # vim: ts=4:sw=4:fdm=marker
This page took 0.098097 seconds and 4 git commands to generate.