]> git.pld-linux.org Git - projects/distfiles.git/blobdiff - specparser.pl
- make sure files from dropin are readable on ftp/distfiles
[projects/distfiles.git] / specparser.pl
old mode 100644 (file)
new mode 100755 (executable)
index 94ac681..3b7e787
 #     list of:
 #       <md5><space><url>\n
 #     for given specfile.
-#   stderr:
-#     diagnostics
+#     except for lines prefixed with: ERROR:
 #   exit status:
 #     0 - normal
 #     2 - cannot open spec file
 #
+use strict;
+use warnings;
 
-%macro = ();
+my %no_source;
+my $spec;
+my $base_spec;
+my @spec;
+my @sources;
+my $sources_file;
+my %patchset;
 
-# expand macros in string
-sub expand($)
+sub print_source($$$);
+
+sub next_spec($)
 {
-  my $v = shift;
-  my $cnt = 20;
-  
-  while ($cnt-- > 0 && $v =~ /\%\{([^\{]+)\}/) {
-    my $value;
-    if (defined $macro{$1}) {
-      $value = $macro{$1};
-    } else {
-      $value = "\@undef:$1\@";
-    }
-    $v =~ s/\%\{([^\{]+)\}/$value/;
-    last if (length $v > 1000);
-  }
-  return $v;
+       $spec = shift;
+       @spec = ();
+       $base_spec = $spec;
+       $base_spec =~ s|.*/||;
 }
 
-# define given macro
-sub define($$)
+sub error($)
 {
-  my ($n, $v) = @_;
-  $macro{$n} = expand($v);
+       print_once( "ERROR: $base_spec: $_[0]" );
 }
 
-# sets hash of macros defined with %define or %global
-# also define %{name}, %{version} and %{source_N}
-sub parse_defines($)
+sub warning($)
 {
-  open(F, "< $_[0]") or die;
-  while (<F>) {
-    chomp;
-    if (/^\s*\%(define|global)\s+([^\s]+)\s+([^\s].*)$/) {
-     define($2, $3);
-    } elsif (/^Version\s*:\s*(.*)/i) {
-      define("version", $1);
-    } elsif (/^Name\s*:\s*(.*)/i) {
-      define("name", $1);
-    } elsif (/^Source(\d+)\s*:\s*(.*)/i) {
-      define("source_$1", $2);
-    }
-  }
-  close(F);
+       print "ERROR: $base_spec: $_[0]\n";
 }
 
-sub print_md5($)
+sub trim_spaces($)
 {
-  open(F, "< $_[0]") or die;
-  while (<F>) {
-    chomp;
-    if (/^\s*#\s*source(\d+)-md5\s*:\s*([a-f0-9]{32})/i) {
-      if (defined $macro{"source_$1"}) {
-        my $s = $macro{"source_$1"};
-       my $md5 = $2;
-       if ($s =~ / / || $s =~ /^\%\{/ || $s =~ /\@undef/) {
-         print STDERR "$_[0]: source url $s is ill-formatted\n";
-       } else {
-          print "$md5 $s\n";
+       my $v = shift;
+
+       $v =~ s/\s+$//;
+       $v =~ s/^\s+//;
+
+       return $v;
+}
+
+# expand macros in string
+sub expand($$) # {{{
+{
+       my $v = trim_spaces(shift);
+       my $macrotree = shift;
+       my $cnt = 20;
+
+       while ($v =~ /\%\{([^\}]+)\}/) {
+               my $value;
+               if (defined $macrotree->{$1}) {
+                       $value = $macrotree->{$1};
+               } else {
+                       error("undefined macro $1");
+                       $value = "UNDEFINED";
+                       return undef;
+               }
+               $v =~ s/\%\{([^\}]+)\}/$value/;
+
+               return $v if (length $v > 1000 or $cnt-- <= 0)
+       }
+
+       while ($v =~ s/\%\(\s*echo\s+([^\|]+?)\s*\|\s*tr\s*(-d|)\s+([^\)]+?)\s*\)/\@\@tr-me\@\@/) {
+               my ($what, $d_opt, $how) = ($1, $2, $3);
+               my ($from, $to) = ($how, "");
+               ($from, $to) = ($1, $2)
+                       if $how =~ /^([^\s]+)\s+([^\s]+)$/;
+               if ($d_opt and $to ne "") {
+                       error("tr -d with second string)");
+               } elsif (($from . $to) =~ /^[a-zA-Z0-9\+_\-\.]+$/) {
+                       if ($d_opt) {
+                               eval "\$what =~ tr/$from//d;";
+                       } else {
+                               eval "\$what =~ tr/$from/$to/;";
+                       }
+               } else {
+                       error("illegal characters in tr string(s) '$from' '$to'");
+               }
+               $v =~ s/\@\@tr-me\@\@/$what/;
+
+               return $v if (length $v > 1000 or $cnt-- <= 0)
+       }
+
+       error("unexpanded macros in $v")
+               if ($v =~ /\%[^0-9]/);
+
+       return $v;
+} # }}}
+
+sub preparse_spec($) # {{{
+{
+       @spec = ("");
+
+       open(F, "< $_[0]") or die("failed opening: " . $_[0]);
+       while (<F>) {
+               chomp;
+               if (/^\s*(\%(description|package|prep|install|pre|post|files)|BuildRoot|URL)/) {
+                       last;
+               } elsif (/^\s*(\%if.*|\%else|\%endif|\%define\s+.*|Version.*|Name.*)\s*$/) {
+                       $_ = $1;
+                       if ($spec[$#spec] =~ /\%if/) {
+                               if (/\%else/) {
+                                       next; # don't include empty %if-%else
+                               } elsif (/\%endif/) {
+                                       # remove empty %if-%endif
+                                       pop @spec;
+                                       next;
+                               }
+                       }
+                       push @spec, $_;
+               } elsif (/^NoSource\s*:\s*(\d+)\s*$/i) {
+                       $no_source{ "source" . $1 } = 1;
+               } elsif (my ($patchset) = /^%patchset_source\s+(.+)$/) {
+                       use Getopt::Long qw(GetOptionsFromString);
+                       my ($f, $s);
+                       my ($ret, $args) = GetOptionsFromString($patchset, 's=s' => \$s, 'f=s' => \$f);
+                       %patchset = (
+                               pattern => $f,
+                               filelist => $s,
+                               start => $args->[0],
+                               end => $args->[1],
+                       );
+               }
+       }
+       close(F);
+
+       shift @spec;
+} # }}}
+
+# read in 'sources' file
+sub read_sources_file {
+       my $filename = $_[0] || $sources_file;
+       return () unless $filename and -e $filename;
+
+       my %files;
+
+       open(my $fh, '<', $filename) or die $!;
+       while (<$fh>) {
+               chomp;
+               next unless my ($hash, $filename) = /^([a-f0-9]{32})\s+\*?(.+)$/;
+               $files{$filename} = $hash;
+       }
+       return \%files;
+}
+
+sub process_patchset($) {
+       my $macros = shift;
+       my $checksums;
+
+       return unless %patchset;
+
+       # print all files from sources file
+       if ($patchset{filelist}) {
+               my $prefix = expand($patchset{pattern}, $macros);
+               my $filelist = expand($patchset{filelist}, $macros);
+               $checksums = read_sources_file($filelist);
+               while (my($file, $hash) = each %$checksums) {
+                       my $url = $prefix . $file;
+                       print_source "patchset $file", $hash, $url;
+               }
+               return;
+       }
+
+       # parse sources file sequences
+       $checksums = read_sources_file() or return;
+
+       # print out patchset entries which source md5 is present in source file
+       my $start = expand($patchset{start}, $macros);
+       my $end = expand($patchset{end}, $macros);
+       my $pattern = expand($patchset{pattern}, $macros);
+       for (my $i = $start; $i <= $end; $i++) {
+               my $url = sprintf($pattern, $i);
+               my ($basename) = $url =~ m{/([^/]+$)};
+               my $hash = $checksums->{$basename} or next;
+               print_source "patchset $i", $hash, $url;
+       }
+}
+
+
+my $total = 0;
+
+sub cont($$);
+sub cont($$) # {{{
+{
+       my ($spec, $macros) = @_;
+       local $_;
+       while ($_ = shift @{$spec}) {
+               if (0 <= index $_, '%if') { # if, ifarch, ifos
+
+                       # split spec parsing
+                       my @speccopy = @{$spec};
+                       my %macroscopy = %{$macros};
+                       cont(\@speccopy, \%macroscopy);
+
+                       my $level = 0;
+                       while ($_ = shift @{$spec}) {
+                               if ($level <= 0 and ($_ eq '%else' or $_ eq '%endif')) {
+                                       last;
+                               } elsif (0 <= index $_, '%if') {
+                                       $level++;
+                               } elsif ($_ eq '%endif') {
+                                       $level--;
+                               }
+                       }
+
+                       # continue parsing
+               } elsif ($_ eq '%else') {
+
+                       # %else happens only when %if was interpreted
+                       # so skip until %endif
+
+                       my $level = 0;
+                       while ($_ = shift @{$spec}) {
+                               if ($level <= 0 and $_ eq '%endif') {
+                                       last;
+                               } elsif (0 <= index $_, '%if') {
+                                       $level++;
+                               } elsif ($_ eq '%endif') {
+                                       $level--;
+                               }
+                       }
+
+               } elsif (/^\s*\%(define|global)\s+([^\s]+)\s+([^\s].*?)\s*$/) {
+                       $macros->{$2} = $3;
+
+               } elsif (/^Version\s*:\s*(.*?)\s*$/i) {
+                       $macros->{"version"} = $1;
+               } elsif (/^Name\s*:\s*(.*?)\s*$/i) {
+                       $macros->{"name"} = $1;
+               } elsif (!/\%endif/) {
+                       warn "unrecognised line: $_\n";
+               }
+       }
+
+       # so we have macros now, can parse patchset source
+       process_patchset($macros);
+
+       # the end, yuppie !
+       foreach my $s (@sources) {
+               my $src = expand( $s->[2], $macros );
+               if (defined $src) {
+                       our %tried;
+                       unless (exists $tried{$src}) {
+                               print_source( $s->[0], $s->[1], $src );
+                               $tried{$src} = 1;
+                       }
+               }
+       }
+
+       if (++$total > 10000) {
+               error("maximum number of bcond posibilities exceeded");
+               exit 0;
+       }
+} # }}}
+
+sub print_once($)
+{
+       our %printed;
+       my $l = shift;
+       unless (exists $printed{$l}) {
+               print $l . "\n";
+               $printed{$l} = 1;
        }
-      } else {
-        print STDERR "$_[0]: source $1 not defined\n";
-      }
-    }
-  }
-  close(F);
 }
 
-$spec = shift;
+sub print_source($$$) # {{{
+{
+       my ($no, $md5, $s) = @_;
+
+       if ($s =~ /^([a-z0-9A-Z;:\=\?&\@\+\~\.,\-\/_]|\%[0-9])+(#\/[a-zA-Z0-9\._-]+)?$/) {
+               if ($s =~ /^(ftp|http|https):\/\//) {
+                       if ($s =~ /\/$/) {
+                               error("source $no ($s) is directory");
+                       } else {
+                               if ($s =~ /:\/\/distfiles\.pld-linux\.org\/src/) {
+                                       $s =~ s|.*/||;
+                                       print_once( "$md5 no-url-copy://$s" );
+                               } else {
+                                       print_once( "$md5 $s" );
+                               }
+                       }
+               } else {
+                       $s =~ s|.*/||;
+                       print_once( "$md5 no-url://$s");
+               }
+       } else {
+               error("source $no url $s is ill-formatted");
+       }
+} # }}}
+
+sub add_md5_to_print($) # {{{
+{
+       open(F, "< $_[0]") or die("failed opening: " . $_[0]);
+       my %sourcemap = ();
+       while (<F>) {
+               chomp;
+               if (/^((?:Source|Patch)\d+)\s*:\s*(.*)/i) {
+                       my $sourceno = lc $1;
+                       my $source = $2;
+                       # master.dl is outdated currently
+                       # $source =~ s/dl.sourceforge.net/master.dl.sourceforge.net/;
+                       $sourcemap{ $sourceno } = $source;
+               } elsif (/^\s*#\s*((?:source|patch)\d+)-md5\s*:\s*([a-f0-9]{32})/i) {
+                       my $no = lc $1;
+                       my $md5 = $2;
+                       if (defined $no_source{$no}) {
+                               error("both NoSource: $no and md5 given");
+                       } elsif (defined $sourcemap{ $no} ) {
+                               my $source = $sourcemap{ $no };
+                               push @sources, [$no, $md5, $source];
+                       } else {
+                               error("source $no not defined (# SourceN-md5: has to be placed just after SourceN:)");
+                       }
+               } elsif (/^\s*BuildRequires:\s*digest\(%((?:SOURCE|PATCH)\d+)\)\s*=\s*([a-f0-9]{32})/i) {
+                       my $no = lc $1;
+                       my $md5 = $2;
+                       if (defined $no_source{ $no }) {
+                               error("both NoSource: $no and md5 given");
+                       } elsif (defined $sourcemap{ $no }) {
+                               my $source = $sourcemap{ $no };
+                               push @sources, [$no, $md5, $source];
+                       } else {
+                               error("source $no not defined (# Source digest has to be placed after SourceN:)");
+                       }
+               }
+       }
+       close(F);
+
+       # find extra sources from detached checksum file
+       my $checksums = read_sources_file();
+       my @mapped = map { $_->[0] } @sources;
+       delete @sourcemap{@mapped};
+       while (my($source, $url) = each %sourcemap) {
+               my ($basename) = $url =~ m{/([^/]+$)};
+               next unless defined $basename;
+               my $hash = $checksums->{$basename} or next;
+               push @sources, [$source, $hash, $url];
+       }
+} # }}}
 
-parse_defines($spec);
-print_md5($spec);
+next_spec(shift);
+$sources_file = shift if @ARGV;
+preparse_spec($spec);
+add_md5_to_print($spec);
+cont( \@spec, { "nil" => "" } );
 
 exit(0);
+
+# vim: ts=4:sw=4:fdm=marker
This page took 0.186425 seconds and 4 git commands to generate.