#!/usr/bin/perl # Copyright (C) 2005, 2006, 2007 Christopher Faylor # # This software is a copyrighted work licensed under the terms of the # GNU General Public License. See http://www.gnu.org/copyleft/gpl.html # for details. # use File::Basename; use Digest::MD5; use Getopt::Long; use POSIX; use strict; sub mywarn(@); sub myerror(@); sub usage(); sub arch_handler(@); my %md5s; if( open F, "md5sums" ) { while() { chomp; my($md5,$s,$t,$f) = split / /; next unless -f $f; $md5s{$f}{md5} = $md5; $md5s{$f}{s} = $s; $md5s{$f}{t} = $t; } close F; } my %licfile; my %licmap; if( open F, "license.map" ) { while() { chomp; my($first,$second,$comment) = split / /; $licmap{$second} = $first; } } my %acceptable; if( open F, "acceptable.lst" ) { while() { chomp; my($id,$comment) = split / /; $acceptable{$id} = 1; } } # For OSGeo4W we don't mind lacking a source file. my @okmissing = qw'message ldesc source license'; my ($outfile, $help, $recursive); my $arch = 'x86'; my $release; my $date; my $startts; GetOptions('okmissing=s'=>\@okmissing, 'output=s'=>\$outfile, 'help'=>\$help, 'release=s'=>\$release, 'arch=s'=>\&arch_handler, 'recursive'=>\$recursive, 'date=s'=>\$date) or usage; if($date) { usage unless $date =~ /^(\d{4})-(\d{2})-(\d{2})$/; $startts = POSIX::mktime( 0, 0, 0, $3, $2-1, $1-1900); usage unless defined $startts; } $help and usage; @main::okmissing{@okmissing} = @okmissing; sub arch_handler (@) { my ($opt_name, $opt_value) = @_; die "invalid arch specified: '$opt_value'" unless $main::valid_arch{lc $opt_value}; $arch = $opt_value; } if (defined($outfile)) { open(STDOUT, '>', $outfile) or die "$0: can't open $outfile - $!\n"; } my %pkg; for my $f (@ARGV) { if (-d "$f/.") { parsedir($f); } else { parse($f); } } print <<'EOF'; # This file is automatically generated. If you edit it, your # edits will be discarded next time the file is generated. # See http://cygwin.com/setup.html for details. # EOF my $ts = time(); print "release: $release\n" if $release; print "arch: $arch\n"; print "setup-timestamp: $ts\n"; print "$main::setup_version\n" if $main::setup_version; undef $main::curfile; for my $p (sort keys %pkg) { print "\n@ $p\n"; for my $key ('sdesc', 'ldesc', 'category', 'requires', 'message') { my $val = $pkg{$p}{''}{$key}; if (!defined($val) && $pkg{$p}{''}{'install'} !~ /_obsolete/o) { mywarn "package $p is missing a $key field" unless defined $main::okmissing{$key}; } else { if ($key eq 'requires') { for my $p1 (split(' ', $val)) { mywarn "package $p requires an unknown package '$p1'" unless $pkg{$p}; } } elsif ($key eq 'category') { for my $c (split(' ', $val)) { mywarn "package $p uses an invalid category '$c'" unless $main::categories{lc $c}; } } print "$key: ", $val, "\n" if defined($val) and $val ne ""; } } for my $what ('', "[prev]\n", "[test]\n") { $pkg{$p}{$what} or next; print "$what"; for my $key ('version', 'install', 'source', 'license') { my $val = $pkg{$p}{$what}{$key} or next; print "$key: ", $val, "\n"; } } } open F, ">md5sums"; for my $f (keys %md5s) { print F $md5s{$f}{md5} . " " . $md5s{$f}{s} . " " . $md5s{$f}{t} . " " . $f . "\n"; } close F; sub get { my $FH = shift; my $keyhint = shift; my $val = shift; if ($keyhint eq 'message') { my ($kw, $rest) = $val =~ /^([^"'\s]+)\s+(.*)$/; return undef unless defined($kw) && defined($rest); return $kw . ' ' . get($FH, 'ldesc', $rest); } elsif (substr($val, 0, 1) ne '"') { $val = '"'. $val . '"' if $keyhint eq 'ldesc' || $keyhint eq 'sdesc'; } else { while (length($val) == 1 || $val !~ /"$/os) { $_ = <$FH>; length or last; chomp; s/(\S)\s+$/$1/; $val .= "\n" . $_; } } $val =~ s/(.)"(.)/$1'$2/mog; return $val; } sub parse { my $f = shift; my $pname = shift; my $what; $main::curfile = $f; $. = 0; open(\*F, '<', $f) or die "$0: couldn't open $f - $!\n"; while () { chomp; s/#.*$//o; s/^\s+//o; s/(\S)\s+$/$1/o; length or next; /^setup-timestamp:/ and do { $main::setup_timestamp = $_; next; }; /^setup-version:/ and do { $main::setup_version = $_; next; }; /^\@\s+(\S+)/ and do { $pname = $1; $what = ''; next; }; /^([^:]+):\s*(.*)$/ and do { my $key = $1; my $val = $2; if ($key !~ /^(?:prev|curr|test)/) { $pkg{$pname}{$what}{$key} = get(\*F, $key, $val); } else { if ($key eq 'curr') { $key = ''; } else { $key = "[$key]\n"; } $pkg{$pname}{$key}{'version'} = $val; } next; }; /^\[[^\]]+\]/ and do { $what = $_ . "\n"; next; }; die "$0: unrecognized input at line file $f, line $.\n"; } } sub compare_versions { my($a, $b) = @_; my @a = split /\./, $a; my @b = split /\./, $b; my $n = @a < @b ? @a : @b; while( @a && @b ) { my $a = shift @a; my $b = shift @b; next if $a eq $b; my ($an) = $a =~ /^(\d+)/; my ($bn) = $b =~ /^(\d+)/; return defined $an && defined $bn ? $an <=> $bn : $an cmp $bn; } return @a ? 1 : @b ? -1 : 0; } sub parsedir { my $d = shift; my $pname = basename($d); delete $pkg{$pname}; if ($recursive) { for my $drecur (glob("$d/*/.")) { last if $drecur =~ /\*/; parsedir(dirname($drecur)); } } my $setup_hint = "$d/setup.hint"; return unless -e $setup_hint; parse("$setup_hint", $pname); my $explicit = 0; for my $what ('', "[prev]\n", "[test]\n") { my $x = $pkg{$pname}{$what}; next unless $x->{'version'}; $explicit = 1; addfiles($pname, $x, $d); } return if $explicit; my @files = sort { my($an,$av,$ap) = ($a =~ /(.*)-([^-]+)-(\d+).tar.bz2$/); my($bn,$bv,$bp) = ($b =~ /(.*)-([^-]+)-(\d+).tar.bz2$/); my $r; if( defined $ap && defined $bp ) { $r = ($an ne $bn) ? $an cmp $bn : $av ne $bv ? compare_versions($av, $bv) : compare_versions($ap, $bp); } else { $r = $a cmp $b; } return $r; } grep { !/-src\.tar.bz2/ } glob("$d/*.tar.bz2"); @files = grep { (stat($_))[9]<=$startts; } @files if $startts; if (!@files) { myerror "not enough package files in $d"; return; } for my $what ('', "[prev]\n") { my $f = pop @files or last; $pkg{$pname}{$what}{-unused} = 1; my $x = $pkg{$pname}{$what}; my $p; ($p, $x->{'version'}) = getver($f); addfiles($p, $x, $d); } } sub addfiles { my $pname = shift; my $x = shift; my $d = shift; my $install = tarball($d, $pname, $x->{'version'}); filer($x, 'install', $install); my $t = license($d, ".txt", $pname, $x->{'version'}); my $p = license($d, ".pdf", $pname, $x->{'version'}); my $r = license($d, ".rtf", $pname, $x->{'version'}); if( -e $p && (! -e $t || (stat($p))[9] > (stat($t))[9] ) ) { system "pdftotext -layout -enc ASCII7 '$p' - >'$t'"; } elsif( -e $r && (! -e $t || (stat($r))[9] > (stat($r))[9] ) ) { system "catdoc -dus-ascii '$r' >'$t'"; } filer($x, 'license', $t) if -e $t; if ($pkg{$pname}{''}{'external-source'}) { $pname = $pkg{$pname}{''}{'external-source'}; $d = finddir($d, $pname) or return; } my $source = tarball($d, $pname, $x->{'version'}, 'src'); filer($x, 'source', $source); } sub getver { my $f = basename($_[0]); my @a = ($f =~ /^(.*?)-(\d.*)\.tar/); return wantarray ? @a : $a[1]; } sub filer { my $x = shift; my $what = shift; my $f = shift; unless( -r $f ) { myerror "can't open $f - $!" unless $main::okmissing{$what}; return undef; } my $digest = $md5s{$f}{md5}; my ($s,$t) = (stat $f)[7,9]; # size and mtime unless( defined $digest && $md5s{$f}{s}==$s && $md5s{$f}{t}==$t ) { open(*F, '<', $f); my $md5 = Digest::MD5->new; $md5->addfile(\*F); $digest = $md5->hexdigest; $md5s{$f}{md5} = $digest; $md5s{$f}{s} = $s; $md5s{$f}{t} = $t; } if( $what eq 'license' ) { $digest = $licmap{$digest} if exists $licmap{$digest}; unless( exists $licfile{$digest} ) { $licfile{$digest} = $f; } else { $f = $licfile{$digest}; } return if exists $acceptable{$digest}; } $x->{$what} = join(' ', $f, -s $f, $digest); } sub tarball { my $d = shift; return "$d/" . join('-', @_) . '.tar.bz2'; } sub license { my $d = shift; my $ext = shift; return "$d/" . join('-', @_) . $ext; } sub fnln { return $main::curfile ? "$main::curfile:$.: " : ''; } sub mywarn(@) { warn "warning: " . fnln . "@_\n"; } sub myerror(@) { warn "error: " . fnln . "@_\n"; } sub finddir { my $d = $_[0]; my $pname = $_[1]; while (($d = dirname($d)) ne '.' && length($d)) { return "$d/$pname" if -d "$d/$pname/."; } myerror "couldn't find package directory for external-source '$pname'"; return undef; } sub usage() { print STDERR <<'EOF'; Usage: genini [--okmissing=key ...] [--recursive] [--output=file] [--help] [--date=yyyy-mm-dd] [setup.ini] [dir ...] Create OSGeo4W setup.ini from setup.ini, setup.hint and tar ball information. --okmissing=key don't warn if key is missing from setup.ini or setup.hint or if some expected `source' or `install' tarballs are missing. Option may be repeated. --okmissing=install is useful if hint files contain `prev' or `test' entries for missing tarballs. --okmissing=source is useful for LOCAL-ONLY srcless install media. --recursive recurse all subdirectories of specified dirs --arch=x86|x86_64 Must be either x86 or x86_64. Defaults to x86. --date=yyyy-mm-dd Generate ini for an older state (all newer files will be ignored) --release=string Optional repository id: cygwin, cygwinports, etc. --output=file output setup.ini info to file --help display this message Report bugs to cygwin mailing list. EOF exit 0; } BEGIN { my @cats = qw' Commandline_Utilities Desktop Libs Web Web_Applications _obsolete _PostInstallLast '; @main::categories{map {lc $_} @cats} = @cats; my @archs = qw'x86 x86_64'; @main::valid_arch{map {lc $_} @archs} = @archs; }