openpkg/makeproxy.pl

changeset 428
f880f219c566
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/openpkg/makeproxy.pl	Tue Jul 31 12:23:42 2012 +0200
     1.3 @@ -0,0 +1,411 @@
     1.4 +##
     1.5 +##  makeproxy.pl -- OpenPKG Tool Chain
     1.6 +##  Copyright (c) 2000-2012 OpenPKG GmbH <http://openpkg.com/>
     1.7 +##
     1.8 +##  This software is property of the OpenPKG GmbH, DE MUC HRB 160208.
     1.9 +##  All rights reserved. Licenses which grant limited permission to use,
    1.10 +##  copy, modify and distribute this software are available from the
    1.11 +##  OpenPKG GmbH.
    1.12 +##
    1.13 +##  THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
    1.14 +##  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
    1.15 +##  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
    1.16 +##  IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR
    1.17 +##  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    1.18 +##  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    1.19 +##  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
    1.20 +##  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
    1.21 +##  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
    1.22 +##  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
    1.23 +##  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
    1.24 +##  SUCH DAMAGE.
    1.25 +##
    1.26 +
    1.27 +require 5.003;
    1.28 +
    1.29 +#   OpenPKG instance prefix and RPM
    1.30 +my $my_prefix = $ENV{'OPENPKG_PREFIX'};
    1.31 +my $my_rpm    = "$my_prefix/bin/openpkg rpm";
    1.32 +delete $ENV{'OPENPKG_PREFIX'};
    1.33 +
    1.34 +#   program identification
    1.35 +my $progname  = "makeproxy";
    1.36 +my $progvers  = "2.1.0";
    1.37 +
    1.38 +#   parameters (defaults)
    1.39 +my $help          = 0;
    1.40 +my $version       = 0;
    1.41 +my $verbose       = 0;
    1.42 +my $debug         = 0;
    1.43 +my $slave_prefix  = $my_prefix;
    1.44 +my $master_prefix = '';
    1.45 +my $tmpdir        = ($ENV{TMPDIR} || "/tmp");
    1.46 +my $output        = '.';
    1.47 +my $input         = '-';
    1.48 +
    1.49 +#   cleanup support
    1.50 +my @cleanup = ();
    1.51 +sub cleanup_remember {
    1.52 +    my ($cmd) = @_;
    1.53 +    push(@cleanup, $cmd);
    1.54 +}
    1.55 +sub cleanup_perform {
    1.56 +    foreach my $cmd (reverse @cleanup) {
    1.57 +        runcmd($cmd);
    1.58 +    }
    1.59 +}
    1.60 +
    1.61 +#   exception handling support
    1.62 +$SIG{__DIE__} = sub {
    1.63 +    my ($err) = @_;
    1.64 +    $err =~ s|\s+at\s+.*||s if (not $verbose);
    1.65 +    $err =~ s/\n+$//s;
    1.66 +    print STDERR "openpkg:$progname:ERROR: $err\n";
    1.67 +    cleanup_perform() if (not $verbose);
    1.68 +    exit(1);
    1.69 +};
    1.70 +
    1.71 +#   verbose message printing
    1.72 +sub verbose {
    1.73 +    my ($msg) = @_;
    1.74 +    print STDERR "$msg\n" if ($verbose);
    1.75 +}
    1.76 +
    1.77 +#   execution of external commands
    1.78 +sub runcmd {
    1.79 +    my ($cmd) = @_;
    1.80 +    if ($cmd =~ m/^(.+)\|$/s) {
    1.81 +        print STDERR "\$ $1\n" if ($debug);
    1.82 +        return `$1`;
    1.83 +    }
    1.84 +    else {
    1.85 +        print STDERR "\$ $cmd\n" if ($debug);
    1.86 +        $cmd = "($cmd) >/dev/null 2>&1" if (not $debug);
    1.87 +        return (system($cmd) == 0);
    1.88 +    }
    1.89 +}
    1.90 +
    1.91 +#   expand into a full filesystem path
    1.92 +sub fullpath {
    1.93 +    my ($prog) = @_;
    1.94 +    my $fullprog = '';
    1.95 +    foreach my $path (split(/:/, $ENV{PATH})) {
    1.96 +        if (-x "$path/$prog") {
    1.97 +            $fullprog = "$path/$prog";
    1.98 +            last;
    1.99 +        }
   1.100 +    }
   1.101 +    return $fullprog;
   1.102 +}
   1.103 +
   1.104 +#   convert a subdirectory (a/b/c/)
   1.105 +#   into a corresponding reverse path (../../../)
   1.106 +sub sub2rev {
   1.107 +    my ($sub) = @_;
   1.108 +    my $rev = '';
   1.109 +    $sub =~ s|^/+||s;
   1.110 +    $sub =~ s|/+$||s;
   1.111 +    while ($sub =~ s|/[^/]+||) {
   1.112 +        $rev .= "../";
   1.113 +    }
   1.114 +    if ($sub ne '') {
   1.115 +        $rev .= "../";
   1.116 +    }
   1.117 +    $rev =~ s|/$||s;
   1.118 +    return $rev;
   1.119 +}
   1.120 +
   1.121 +#   create a directory (plus its missing parent dirs)
   1.122 +sub mkdirp {
   1.123 +    my ($dir) = @_;
   1.124 +    my $pdir = $dir;
   1.125 +    $pdir =~ s|/[^/]*$||s;
   1.126 +    if (not -d $pdir) {
   1.127 +        mkdirp($pdir);
   1.128 +    }
   1.129 +    if (not -d $dir) {
   1.130 +        runcmd("mkdir $dir");
   1.131 +    }
   1.132 +}
   1.133 +
   1.134 +#   home-brewn getopt(3) style option parser
   1.135 +sub getopts ($) {
   1.136 +    my ($opts) = @_;
   1.137 +    my (%optf) = map { m/(\w)/; $1 => $_ } $opts =~ m/(\w:|\w)/g;
   1.138 +    my (%opts, @argv, $optarg);
   1.139 +
   1.140 +    foreach (@ARGV) {
   1.141 +        if (@argv) {
   1.142 +            push @argv, $_;
   1.143 +        } elsif (defined $optarg) {
   1.144 +            if (exists $opts{$optarg}) {
   1.145 +                $opts{$optarg} .= " $_";
   1.146 +            } else {
   1.147 +                $opts{$optarg} = $_;
   1.148 +            }
   1.149 +            $optarg = undef;
   1.150 +        } elsif (!/^[-]/) {
   1.151 +            push @argv, $_;
   1.152 +        } else {
   1.153 +            while (/^\-(\w)(.*)/) {
   1.154 +                if (exists $optf{$1}) {
   1.155 +                    if (length($optf{$1}) > 1) {
   1.156 +                        if ($2 ne '') {
   1.157 +                            if (exists $opts{$1}) {
   1.158 +                                $opts{$1} .= " $2";
   1.159 +                            } else {
   1.160 +                                $opts{$1} = $2;
   1.161 +                            }
   1.162 +                        } else {
   1.163 +                            $optarg = $1;
   1.164 +                        }
   1.165 +                        last;
   1.166 +                    } else {
   1.167 +                        $opts{$1} = 1;
   1.168 +                    }
   1.169 +                } else {
   1.170 +                    warn "openpkg:makeproxy:WARNING: unknown option $_\n";
   1.171 +                }
   1.172 +                $_ = "-$2";
   1.173 +            }
   1.174 +        }
   1.175 +    }
   1.176 +    if (defined $optarg) {
   1.177 +        warn "openpkg:makeproxy:WARNING: option $optarg requires an argument\n";
   1.178 +    }
   1.179 +    foreach (keys %opts) {
   1.180 +        eval '$opt_'.$_.' = "'.quotemeta($opts{$_}).'";';
   1.181 +    }
   1.182 +    @ARGV = @argv;
   1.183 +}
   1.184 +
   1.185 +#   command line parsing
   1.186 +getopts("Vhdvm:s:t:o:");
   1.187 +$version       = $opt_V if (defined $opt_V);
   1.188 +$help          = $opt_h if (defined $opt_h);
   1.189 +$debug         = $opt_d if (defined $opt_d);
   1.190 +$verbose       = $opt_v if (defined $opt_v);
   1.191 +$master_prefix = $opt_m if (defined $opt_m);
   1.192 +$slave_prefix  = $opt_s if (defined $opt_s);
   1.193 +$tmpdir        = $opt_t if (defined $opt_t);
   1.194 +$output        = $opt_o if (defined $opt_o);
   1.195 +if ($help) {
   1.196 +    print "Usage: openpkg $progname [options] master-package\n" .
   1.197 +          " -h,--help          print this usage page\n" .
   1.198 +          " -V,--version       print version\n" .
   1.199 +          " -v,--verbose       print verbose run-time information\n" .
   1.200 +          " -d,--debug         print debug information\n" .
   1.201 +          " -m,--master=DIR    filesystem path to master OpenPKG instance\n" .
   1.202 +          " -s,--slave=DIR     filesystem path to slave OpenPKG instance\n" .
   1.203 +          " -t,--tmpdir=DIR    filesystem path to temporary directory\n" .
   1.204 +          " -o,--output=FILE   filesystem path to output proxy OpenPKG RPM package\n";
   1.205 +    exit(0);
   1.206 +}
   1.207 +if ($version) {
   1.208 +    print "OpenPKG $progname $progvers\n";
   1.209 +    exit(0);
   1.210 +}
   1.211 +if ($#ARGV == 0) {
   1.212 +    $input = shift(@ARGV);
   1.213 +}
   1.214 +if ($#ARGV != -1) {
   1.215 +    die "invalid number of command line arguments";
   1.216 +}
   1.217 +
   1.218 +#   prepare temporary location
   1.219 +verbose("++ prepare temporary directory");
   1.220 +$tmpdir = "$tmpdir/$progname.$$";
   1.221 +if (not -d $tmpdir) {
   1.222 +    runcmd("umask 077 && mkdir $tmpdir")
   1.223 +        or die "cannot create temporary directory \"$tmpdir\"";
   1.224 +    cleanup_remember("rm -rf $tmpdir");
   1.225 +}
   1.226 +verbose("-- temporary directory: $tmpdir");
   1.227 +
   1.228 +#   determine input and output RPM
   1.229 +verbose("++ determining OpenPKG RPM package files");
   1.230 +verbose("-- input/master/regular RPM package: $input");
   1.231 +verbose("-- output/slave/proxy   RPM package: $output");
   1.232 +
   1.233 +#   ensure input RPM package is available on the filesystem
   1.234 +if ($input eq '-') {
   1.235 +    $input = "$tmpdir/input.rpm";
   1.236 +    runcmd("cat >$input");
   1.237 +}
   1.238 +
   1.239 +#   determine master and slave OpenPKG instance and RPM commands
   1.240 +if ($master_prefix eq '') {
   1.241 +    if (not -f $input) {
   1.242 +        die "input/master/regular RPM package does not exist: \"$input\"";
   1.243 +    }
   1.244 +    $master_prefix = runcmd("($my_rpm -qp --qf '%{PREFIXES}' $input 2>/dev/null || true) |");
   1.245 +    $master_prefix =~ s/\r?\n$//s;
   1.246 +    if ($master_prefix eq '') {
   1.247 +        die "unable to determine master OpenPKG instance";
   1.248 +    }
   1.249 +}
   1.250 +if ($master_prefix eq $slave_prefix) {
   1.251 +    die "master and slave OpenPKG instances have to be different";
   1.252 +}
   1.253 +my $slave_rpm  = (-f "$slave_prefix/bin/openpkg"  ? "$slave_prefix/bin/openpkg rpm"  : $my_rpm);
   1.254 +my $master_rpm = (-f "$master_prefix/bin/openpkg" ? "$master_prefix/bin/openpkg rpm" : $my_rpm);
   1.255 +verbose("-- tool   OpenPKG instance:    $my_prefix");
   1.256 +verbose("-- tool   OpenPKG RPM command: $my_rpm");
   1.257 +verbose("-- master OpenPKG instance:    $master_prefix");
   1.258 +verbose("-- master OpenPKG RPM command: $master_rpm");
   1.259 +verbose("-- slave  OpenPKG instance:    $slave_prefix");
   1.260 +verbose("-- slave  OpenPKG RPM command: $slave_rpm");
   1.261 +
   1.262 +#   helper function for parsing RPM query outputs
   1.263 +sub parseresponse {
   1.264 +    my ($r, $o) = @_;
   1.265 +    sub parseline {
   1.266 +        my ($r, $t, $k, $v) = @_;
   1.267 +        $v =~ s|^\s+||s;
   1.268 +        $v =~ s|\s+$||s;
   1.269 +        if ($t eq 'S') {     # single-value results
   1.270 +            $r->{$k} = $v;
   1.271 +        }
   1.272 +        elsif ($t eq 'M') {  # multi-value results
   1.273 +            $r->{$k} = [] if (not defined($r->{$k}));
   1.274 +            push(@{$r->{$k}}, $v);
   1.275 +        }
   1.276 +    }
   1.277 +    $o =~ s|([SM])-([^:]+):<(.*?)>\n|parseline($r, $1, $2, $3, '')|egs;
   1.278 +    return $r;
   1.279 +}
   1.280 +
   1.281 +#   query master OpenPKG RPM package
   1.282 +verbose("++ query information from master OpenPKG RPM package");
   1.283 +my $q = '';
   1.284 +foreach my $t (qw(
   1.285 +    NAME SUMMARY URL VENDOR PACKAGER DISTRIBUTION CLASS GROUP LICENSE VERSION RELEASE
   1.286 +    DESCRIPTION
   1.287 +)) {
   1.288 +    $q .= "S-$t:<%{$t}>\\n";
   1.289 +}
   1.290 +$q .= '[M-PREREQ:<%{REQUIRENAME} %|REQUIREFLAGS?{%{REQUIREFLAGS:depflags} %{REQUIREVERSION}}:{}|>\n]';
   1.291 +$q .= '[M-PROVIDES:<%{PROVIDENAME} %|PROVIDEFLAGS?{%{PROVIDEFLAGS:depflags} %{PROVIDEVERSION}}:{}|>\n]';
   1.292 +$q .= '[M-PREFIXES:<%{PREFIXES}>\n]';
   1.293 +my $cmd = sprintf("%s -q %s --qf '$q' %s", $master_rpm, (-f $input ? "-p" : ""), $input);
   1.294 +my $o = runcmd("$cmd|");
   1.295 +if ($o =~ m/package .* is not installed/s) {
   1.296 +    die "master package \"$input\" not installed in master OpenPKG instance \"$master_prefix\"";
   1.297 +}
   1.298 +$o =~ s|M-PREREQ:<rpmlib\(.*?\).*?>\n||gs;
   1.299 +my $r = {};
   1.300 +$r = parseresponse($r, $o);
   1.301 +my $BD = '';
   1.302 +my $ID = '';
   1.303 +my $PR = '';
   1.304 +foreach my $d (@{$r->{PREREQ}}) {
   1.305 +    if ($d =~ m|^OpenPKG|i) {
   1.306 +        $BD .= ", " if ($BD ne '');
   1.307 +        $BD .= $d;
   1.308 +    }
   1.309 +    $ID .= ", " if ($ID ne '');
   1.310 +    $ID .= $d;
   1.311 +}
   1.312 +foreach my $d (@{$r->{PROVIDES}}) {
   1.313 +    $PR .= ", " if ($PR ne '');
   1.314 +    $PR .= $d;
   1.315 +}
   1.316 +
   1.317 +#   prepare build environment
   1.318 +verbose("++ establishing temporary OpenPKG RPM environment");
   1.319 +verbose("-- directory: $tmpdir");
   1.320 +runcmd("rm -rf $tmpdir/src; mkdir $tmpdir/src");
   1.321 +runcmd("rm -rf $tmpdir/bld; mkdir $tmpdir/bld");
   1.322 +cleanup_remember("rm -rf $tmpdir/src");
   1.323 +cleanup_remember("rm -rf $tmpdir/tmp");
   1.324 +cleanup_remember("rm -rf $tmpdir/bld");
   1.325 +cleanup_remember("rm -rf $tmpdir/pkg");
   1.326 +runcmd("mkdir $tmpdir/src/.openpkg");
   1.327 +open(MACRO, ">$tmpdir/src/.openpkg/rpmmacros")
   1.328 +    or die "unable to write file \"$tmpdir/src/.openpkg/rpmmacros\": $!";
   1.329 +print MACRO "\%openpkg_layout macrosfile=\%{macrosfile} layout=local shared=no\n";
   1.330 +close(MACRO);
   1.331 +$ENV{HOME} = $tmpdir;
   1.332 +
   1.333 +#   generate OpenPKG RPM .spec file for proxy OpenPKG RPM package
   1.334 +verbose("++ generating OpenPKG RPM package specification for proxy package");
   1.335 +verbose("-- file: $tmpdir/src/".$r->{NAME}.".spec");
   1.336 +my $S = '';
   1.337 +$S .= "Name:         ".$r->{NAME}."\n";
   1.338 +$S .= "Summary:      ".$r->{SUMMARY}."\n";
   1.339 +$S .= "URL:          ".$r->{URL}."\n";
   1.340 +$S .= "Vendor:       ".$r->{VENDOR}."\n";
   1.341 +$S .= "Packager:     ".$r->{PACKAGER}."\n";
   1.342 +$S .= "Distribution: ".$r->{DISTRIBUTION}."\n";
   1.343 +$S .= "Class:        ".$r->{CLASS}."\n";
   1.344 +$S .= "Group:        ".$r->{GROUP}."\n";
   1.345 +$S .= "License:      ".$r->{LICENSE}."\n";
   1.346 +$S .= "Version:      ".$r->{VERSION}."\n";
   1.347 +$S .= "Release:      ".$r->{RELEASE}."+PROXY\n";
   1.348 +$S .= "\n";
   1.349 +$S .= "BuildPreReq:  $BD\n" if ($BD ne '');
   1.350 +$S .= "PreReq:       $ID\n" if ($ID ne '');
   1.351 +$S .= "Provides:     $PR\n" if ($PR ne '');
   1.352 +$S .= "\n";
   1.353 +$S .= "\%description\n";
   1.354 +$S .= "    ".$r->{DESCRIPTION}."\n";
   1.355 +$S .= "\n";
   1.356 +$S .= "\%install\n";
   1.357 +$S .= "    \%{l_rpmtool} files -v -ofiles -r\$RPM_BUILD_ROOT \%{l_files_std}\n";
   1.358 +$S .= "\n";
   1.359 +$S .= "\%files -f files\n";
   1.360 +$S .= "\n";
   1.361 +open(SPEC, ">$tmpdir/src/".$r->{NAME}.".spec")
   1.362 +   or die "unable to write file \"$tmpdir/src/".$r->{NAME}."\": $!";
   1.363 +print SPEC $S;
   1.364 +close(SPEC);
   1.365 +
   1.366 +#   creating shadow tree of original contents
   1.367 +verbose("++ creating shadow tree from original package contents");
   1.368 +$q = '[%{FILEMODES:perms} %{FILENAMES}\n]';
   1.369 +$cmd = sprintf("%s -q %s --qf '$q' '%s'", $master_rpm, (-f $input ? "-p" : ""), $input);
   1.370 +my @FL = runcmd("$cmd|");
   1.371 +my $FD = [];
   1.372 +my $FR = [];
   1.373 +foreach my $fl (@FL) {
   1.374 +    $fl =~ s|\n$||s;
   1.375 +    if ($fl =~ m|^(d\S+)\s+$master_prefix(.*)$|) {
   1.376 +        mkdirp("$tmpdir/bld$slave_prefix$2");
   1.377 +        verbose("-- | PHYS $1 $slave_prefix$2");
   1.378 +    }
   1.379 +    elsif ($fl =~ m|^(\S+)\s+$master_prefix(.*?)([^/\s]+)$|) {
   1.380 +        my ($subdir, $file) = ($2, $3);
   1.381 +        my $target = sub2rev($subdir)."/.prefix-".$r->{NAME}.$subdir.$file;
   1.382 +        mkdirp("$tmpdir/bld$slave_prefix$subdir");
   1.383 +        runcmd("ln -s $target $tmpdir/bld$slave_prefix$subdir$file");
   1.384 +        verbose("-- | VIRT $1 $slave_prefix$subdir$file");
   1.385 +    }
   1.386 +}
   1.387 +
   1.388 +#   create master-reference symbolic link
   1.389 +runcmd("ln -s $master_prefix $tmpdir/bld$slave_prefix/.prefix-".$r->{NAME});
   1.390 +
   1.391 +#   rolling output proxy RPM package
   1.392 +verbose("++ rolling output proxy RPM package");
   1.393 +runcmd(
   1.394 +    "cd $tmpdir/src && " .
   1.395 +    "$slave_rpm -bb " .
   1.396 +    "--define 'buildroot $tmpdir/bld' " .
   1.397 +    "--define '__spec_install_pre \%{___build_pre}' " .
   1.398 +    "--nodeps ".$r->{NAME}.".spec"
   1.399 +);
   1.400 +
   1.401 +#   providing output
   1.402 +verbose("++ providing output: $output");
   1.403 +if ($output eq '-') {
   1.404 +    runcmd("cat $tmpdir/src/*.rpm");
   1.405 +}
   1.406 +else {
   1.407 +    runcmd("cp $tmpdir/src/*.rpm $output");
   1.408 +}
   1.409 +
   1.410 +#   die gracefully...
   1.411 +verbose("++ cleaning up environment");
   1.412 +cleanup_perform();
   1.413 +exit(0);
   1.414 +

mercurial