Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:adrianSuSE
build
Build.obscpio
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File Build.obscpio of Package build
07070100000000000081a400000000000000000000000163e504b900000759000000000000000000000000000000000000001200000000Build/Appimage.pm################################################################ # # Copyright (c) 2017 SUSE Linux Products GmbH # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program (see the file COPYING); if not, write to the # Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA # ################################################################ package Build::Appimage; use strict; use Build::Deb; use Build::Rpm; eval { require YAML::XS; $YAML::XS::LoadBlessed = 0; }; *YAML::XS::LoadFile = sub {die("YAML::XS is not available\n")} unless defined &YAML::XS::LoadFile; sub parse { my ($cf, $fn) = @_; my $yml; eval { $yml = YAML::XS::LoadFile($fn); }; return {'error' => "Failed to parse yml file"} unless $yml; my $ret = {}; $ret->{'name'} = $yml->{'app'}; $ret->{'version'} = $yml->{'version'} || "0"; my @packdeps; if ($yml->{'ingredients'}) { for my $pkg (@{$yml->{'ingredients'}->{'packages'} || {}}) { push @packdeps, $pkg; } } if ($yml->{'build'} && $yml->{'build'}->{'packages'}) { for my $pkg (@{$yml->{'build'}->{'packages'}}) { push @packdeps, $pkg; } } $ret->{'deps'} = \@packdeps; my @sources; if ($yml->{'build'} && $yml->{'build'}->{'files'}) { for my $source (@{$yml->{'build'}->{'files'}}) { push @sources, $source; } } $ret->{'sources'} = \@sources; return $ret; } 1; 07070100000001000081a400000000000000000000000163e504b900002b62000000000000000000000000000000000000000e00000000Build/Arch.pm################################################################ # # Copyright (c) 1995-2014 SUSE Linux Products GmbH # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program (see the file COPYING); if not, write to the # Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA # ################################################################ package Build::Arch; use strict; use Digest::MD5; eval { require Archive::Tar; }; *Archive::Tar::new = sub {die("Archive::Tar is not available\n")} unless defined &Archive::Tar::new; # Archlinux support, based on the GSoC work of Nikolay Rysev <mad.f3ka@gmail.com> # parse a PKGBUILD file sub expandvars { my ($str, $vars) = @_; $str =~ s/\$([a-zA-Z0-9_]+|\{([^\}]+)\})/join(' ', @{$vars->{$2 || $1} || []})/ge; return $str; } sub quote { my ($str, $q, $vars) = @_; $str = expandvars($str, $vars) if $q ne "'" && $str =~ /\$/; $str =~ s/([ \t\"\'\$])/sprintf("%%%02X", ord($1))/ge; return $str; } sub unquotesplit { my ($str, $vars) = @_; $str =~ s/%/%25/g; $str =~ s/^[ \t]+//; while ($str =~ /([\"\'])/) { my $q = $1; last unless $str =~ s/$q(.*?)$q/quote($1, $q, $vars)/e; } $str = expandvars($str, $vars) if $str =~ /\$/; my @args = split(/[ \t]+/, $str); for (@args) { s/%([a-fA-F0-9]{2})/chr(hex($1))/ge } return @args; } sub get_assets { my ($vars, $asuf) = @_; my @digests; for my $digesttype ('sha512', 'sha256', 'sha1', 'md5') { @digests = map {$_ eq 'SKIP' ? $_ : "$digesttype:$_"} @{$vars->{"${digesttype}sums$asuf"} || []}; last if @digests; } # work around bug in source parser my @sources; for (@{$vars->{"source$asuf"} || []}) { push @sources, $_; splice(@sources, -1, 1, $1, "$1.sig") if /(.*)\{,\.sig\}$/; } my @assets; for my $s (@sources) { my $digest = shift @digests; next unless $s =~ /^https?:\/\/.*\/([^\.\/][^\/]+)$/s; my $asset = { 'url' => $s }; $asset->{'digest'} = $digest if $digest && $digest ne 'SKIP'; push @assets, $asset; } return @assets; } sub parse { my ($config, $pkgbuild) = @_; my $ret; local *PKG; if (!open(PKG, '<', $pkgbuild)) { $ret->{'error'} = "$pkgbuild: $!"; return $ret; } my %vars; my @ifs; while (<PKG>) { chomp; next if /^\s*$/; next if /^\s*#/; s/^\s+//; if (/^(el)?if\s+(?:(?:test|\[)\s+(-n|-z)\s+)?(.*?)\s*\]?\s*;\s*then\s*$/) { if ($1) { $ifs[-1] += 1; next if $ifs[-1] != 1; pop @ifs; } my $flag = $2 || '-n'; my $t = join('', unquotesplit($3, \%vars)); $t = $t eq '' ? 'true' : '' if $flag eq '-z'; push @ifs, $t ne '' ? 1 : 0; next; } if (@ifs) { if (/^fi\s*$/) { pop @ifs; next; } elsif (/^else\s*$/) { $ifs[-1] += 1; next; } next if grep {$_ != 1} @ifs; } last unless /^([a-zA-Z0-9_]*)(\+?)=(\(?)(.*?)$/; my $var = $1; my $app = $2; my $val = $4; if ($3) { while ($val !~ s/\)\s*(?:#.*)?$//s) { my $nextline = <PKG>; last unless defined $nextline; chomp $nextline; $val .= ' ' . $nextline; } } if ($app) { push @{$vars{$var}}, unquotesplit($val, \%vars); } else { $vars{$var} = [ unquotesplit($val, \%vars) ]; } } close PKG; $ret->{'name'} = $vars{'pkgname'}->[0] if $vars{'pkgname'}; $ret->{'version'} = $vars{'pkgver'}->[0] if $vars{'pkgver'}; $ret->{'deps'} = []; push @{$ret->{'deps'}}, @{$vars{$_} || []} for qw{makedepends checkdepends depends}; # get arch from macros my ($arch) = Build::gettargetarchos($config); # map to arch linux name and add arch dependent $arch = 'i686' if $arch =~ /^i[345]86$/; push @{$ret->{'deps'}}, @{$vars{"${_}_$arch"} || []} for qw{makedepends checkdepends depends}; # Maintain architecture-specific sources for officially supported architectures for my $asuf ('', '_i686', '_x86_64') { $ret->{"source$asuf"} = $vars{"source$asuf"} if $vars{"source$asuf"}; } # find remote assets for my $asuf ('', "_$arch") { next unless @{$vars{"source$asuf"} || []}; my @assets = get_assets(\%vars, $asuf); push @{$ret->{'remoteassets'}}, @assets if @assets; } return $ret; } sub islzma { my ($fn) = @_; local *F; return 0 unless open(F, '<', $fn); my $h; return 0 unless read(F, $h, 5) == 5; close F; return $h eq "\3757zXZ"; } sub iszstd { my ($fn) = @_; local *F; return 0 unless open(F, '<', $fn); my $h; return 0 unless read(F, $h, 4) == 4; close F; return $h eq "(\265\057\375"; } sub lzmadec { my ($fn) = @_; my $nh; my $pid = open($nh, '-|'); return undef unless defined $pid; if (!$pid) { $SIG{'PIPE'} = 'DEFAULT'; exec('xzdec', '-dc', $fn); die("xzdec: $!\n"); } return $nh; } sub zstddec { my ($fn) = @_; my $nh; my $pid = open($nh, '-|'); return undef unless defined $pid; if (!$pid) { $SIG{'PIPE'} = 'DEFAULT'; exec('zstdcat', $fn); die("zstdcat $!\n"); } return $nh; } sub queryvars { my ($handle) = @_; if (ref($handle)) { die("arch pkg query not implemented for file handles\n"); } if ($handle =~ /\.zst$/ || iszstd($handle)) { $handle = zstddec($handle); } elsif ($handle =~ /\.xz$/ || islzma($handle)) { $handle = lzmadec($handle); } my $tar = Archive::Tar->new; my @read = $tar->read($handle, 1, {'filter' => '^\.PKGINFO$', 'limit' => 1}); die("$handle: not an arch package file\n") unless @read == 1; my $pkginfo = $read[0]->get_content; die("$handle: not an arch package file\n") unless $pkginfo; my %vars; $vars{'_pkginfo'} = $pkginfo; for my $l (split('\n', $pkginfo)) { next unless $l =~ /^(.*?) = (.*)$/; push @{$vars{$1}}, $2; } return \%vars; } sub queryfiles { my ($handle) = @_; if (ref($handle)) { die("arch pkg query not implemented for file handles\n"); } if ($handle =~ /\.zst$/ || iszstd($handle)) { $handle = zstddec($handle); } elsif ($handle =~ /\.xz$/ || islzma($handle)) { $handle = lzmadec($handle); } my @files; my $tar = Archive::Tar->new; # we use filter_cb here so that Archive::Tar skips the file contents $tar->read($handle, 1, {'filter_cb' => sub { my ($entry) = @_; push @files, $entry->name unless $entry->is_longlink || (@files && $files[-1] eq $entry->name); return 0; }}); shift @files if @files && $files[0] eq '.PKGINFO'; return \@files; } sub query { my ($handle, %opts) = @_; my $vars = queryvars($handle); my $ret = {}; $ret->{'name'} = $vars->{'pkgname'}->[0] if $vars->{'pkgname'}; $ret->{'hdrmd5'} = Digest::MD5::md5_hex($vars->{'_pkginfo'}); $ret->{'provides'} = $vars->{'provides'} || []; $ret->{'requires'} = $vars->{'depend'} || []; if ($vars->{'pkgname'} && $opts{'addselfprovides'}) { my $selfprovides = $vars->{'pkgname'}->[0]; $selfprovides .= "=$vars->{'pkgver'}->[0]" if $vars->{'pkgver'}; push @{$ret->{'provides'}}, $selfprovides unless @{$ret->{'provides'} || []} && $ret->{'provides'}->[-1] eq $selfprovides; } if ($opts{'evra'}) { if ($vars->{'pkgver'}) { my $evr = $vars->{'pkgver'}->[0]; if ($evr =~ /^([0-9]+):(.*)$/) { $ret->{'epoch'} = $1; $evr = $2; } $ret->{'version'} = $evr; if ($evr =~ /^(.*)-(.*?)$/) { $ret->{'version'} = $1; $ret->{'release'} = $2; } } $ret->{'arch'} = $vars->{'arch'}->[0] if $vars->{'arch'}; } if ($opts{'description'}) { $ret->{'description'} = $vars->{'pkgdesc'}->[0] if $vars->{'pkgdesc'}; } if ($opts{'conflicts'}) { $ret->{'conflicts'} = $vars->{'conflict'} if $vars->{'conflict'}; $ret->{'obsoletes'} = $vars->{'replaces'} if $vars->{'replaces'}; } if ($opts{'weakdeps'}) { my @suggests = @{$vars->{'optdepend'} || []}; s/:.*// for @suggests; $ret->{'suggests'} = \@suggests if @suggests; } # arch packages don't seem to have a source :( # fake it so that the package isn't confused with a src package $ret->{'source'} = $ret->{'name'} if defined $ret->{'name'}; $ret->{'buildtime'} = $vars->{'builddate'}->[0] if $opts{'buildtime'} && $vars->{'builddate'}; return $ret; } sub queryhdrmd5 { my ($handle) = @_; if (ref($handle)) { die("arch pkg query not implemented for file handles\n"); } if ($handle =~ /\.zst$/ || iszstd($handle)) { $handle = zstddec($handle); } elsif ($handle =~ /\.xz$/ || islzma($handle)) { $handle = lzmadec($handle); } my $tar = Archive::Tar->new; my @read = $tar->read($handle, 1, {'filter' => '^\.PKGINFO$', 'limit' => 1}); die("$handle: not an arch package file\n") unless @read == 1; my $pkginfo = $read[0]->get_content; die("$handle: not an arch package file\n") unless $pkginfo; return Digest::MD5::md5_hex($pkginfo); } sub parserepodata { my ($d, $data) = @_; $d ||= {}; $data =~ s/^\n+//s; my @parts = split(/\n\n+/s, $data); for my $part (@parts) { my @p = split("\n", $part); my $p = shift @p; if ($p eq '%NAME%') { $d->{'name'} = $p[0]; } elsif ($p eq '%VERSION%') { $d->{'version'} = $p[0]; } elsif ($p eq '%ARCH%') { $d->{'arch'} = $p[0]; } elsif ($p eq '%BUILDDATE%') { $d->{'buildtime'} = $p[0]; } elsif ($p eq '%FILENAME%') { $d->{'filename'} = $p[0]; } elsif ($p eq '%PROVIDES%') { push @{$d->{'provides'}}, @p; } elsif ($p eq '%DEPENDS%') { push @{$d->{'requires'}}, @p; } elsif ($p eq '%OPTDEPENDS%') { push @{$d->{'suggests'}}, @p; } elsif ($p eq '%CONFLICTS%') { push @{$d->{'conflicts'}}, @p; } elsif ($p eq '%REPLACES%') { push @{$d->{'obsoletes'}}, @p; } elsif ($p eq '%MD5SUM%') { $d->{'checksum_md5'} = $p[0]; } elsif ($p eq '%SHA256SUM%') { $d->{'checksum_sha256'} = $p[0]; } } return $d; } sub queryinstalled { my ($root, %opts) = @_; $root = '' if !defined($root) || $root eq '/'; local *D; local *F; opendir(D, "$root/var/lib/pacman/local") || return []; my @pn = sort(grep {!/^\./} readdir(D)); closedir(D); my @pkgs; for my $pn (@pn) { next unless open(F, '<', "$root/var/lib/pacman/local/$pn/desc"); my $data = ''; 1 while sysread(F, $data, 8192, length($data)); close F; my $d = parserepodata(undef, $data); next unless defined $d->{'name'}; my $q = {}; for (qw{name arch buildtime version}) { $q->{$_} = $d->{$_} if defined $d->{$_}; } $q->{'epoch'} = $1 if $q->{'version'} =~ s/^(\d+)://s; $q->{'release'} = $1 if $q->{'version'} =~ s/-([^-]*)$//s; push @pkgs, $q; } return \@pkgs; } 1; 07070100000002000081a400000000000000000000000163e504b900000d87000000000000000000000000000000000000001200000000Build/Archrepo.pm################################################################ # # Copyright (c) 1995-2014 SUSE Linux Products GmbH # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program (see the file COPYING); if not, write to the # Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA # ################################################################ package Build::Archrepo; use strict; use Build::Arch; eval { require Archive::Tar; }; if (!defined &Archive::Tar::iter) { *Archive::Tar::iter = sub { my ($class, $filename) = @_; die("Archive::Tar is not available\n") unless defined &Archive::Tar::new; Archive::Tar->new(); my $handle = $class->_get_handle($filename, 1, 'rb') or return undef; my @data; return sub { return shift(@data) if !$handle || @data; my $files = $class->_read_tar($handle, { limit => 1 }); @data = @$files if (ref($files) || '') eq 'ARRAY'; undef $handle unless @data; return shift @data; }; }; } sub addpkg { my ($res, $data, $options) = @_; return unless defined $data->{'version'}; if ($options->{'addselfprovides'}) { my $selfprovides = $data->{'name'}; $selfprovides .= "=$data->{'version'}" if defined $data->{'version'}; push @{$data->{'provides'}}, $selfprovides unless @{$data->{'provides'} || []} && $data->{'provides'}->[-1] eq $selfprovides; } if ($options->{'normalizedeps'}) { # our normalized dependencies have spaces around the op for my $dep (qw {provides requires conflicts obsoletes suggests}) { next unless $data->{$dep}; s/ ?([<=>]+) ?/ $1 / for @{$data->{$dep}}; } } if (defined($data->{'version'})) { # split version into evr $data->{'epoch'} = $1 if $data->{'version'} =~ s/^(\d+)://s; $data->{'release'} = $1 if $data->{'version'} =~ s/-([^-]*)$//s; } $data->{'location'} = delete($data->{'filename'}) if exists $data->{'filename'}; if ($options->{'withchecksum'}) { for (qw {md5 sha1 sha256}) { my $c = delete($data->{"checksum_$_"}); $data->{'checksum'} = "$_:$c" if $c; } } else { delete $data->{"checksum_$_"} for qw {md5 sha1 sha256}; } if (ref($res) eq 'CODE') { $res->($data); } else { push @$res, $data; } } sub parse { my ($in, $res, %options) = @_; $res ||= []; die("Build::Archrepo::parse needs a filename\n") if ref($in); die("$in: $!\n") unless -e $in; my $repodb = Archive::Tar->iter($in, 1); die("$in is not a tar archive\n") unless $repodb; my $e; my $lastfn = ''; my $d; while ($e = $repodb->()) { next unless $e->type() == Archive::Tar::Constant::FILE(); my $fn = $e->name(); next unless $fn =~ s/\/(?:depends|desc|files)$//s; if ($lastfn ne $fn) { addpkg($res, $d, \%options) if $d->{'name'}; $d = {}; $lastfn = $fn; } Build::Arch::parserepodata($d, $e->get_content()); } addpkg($res, $d, \%options) if $d->{'name'}; return $res; } 1; 07070100000003000081a400000000000000000000000163e504b900000645000000000000000000000000000000000000001000000000Build/Collax.pm# # Copyright 2015 Zarafa B.V. and its licensors # # This program is free software; you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation; either version 2 or (at your option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # package Build::Collax; use strict; sub parse { my($buildconf, $fn) = @_; my @bscript; if (ref($fn) eq "ARRAY") { @bscript = @$fn; $fn = undef; } elsif (ref($fn) ne "") { die "Unhandled ref type in collax"; } else { local *FH; if (!open(FH, "<", $fn)) { return {"error" => "$fn: $!"}; } @bscript = <FH>; chomp(@bscript); close(FH); } my $ret = {"deps" => []}; for (my $i = 0; $i <= $#bscript; ++$i) { next unless $bscript[$i] =~ m{^\w+=}; my $key = lc(substr($&, 0, -1)); my $value = $'; if ($value =~ m{^([\'\"])}) { $value = substr($value, 1); while ($value !~ m{[\'\"]}) { my @cut = splice(@bscript, $i + 1, 1); $value .= $cut[0]; } $value =~ s{[\'\"]}{}s; $value =~ s{\n}{ }gs; } if ($key eq "package") { $ret->{"name"} = $value; } elsif ($key eq "version") { $ret->{$key} = $value; } elsif ($key eq "builddepends" || $key eq "extradepends") { $value =~ s{^\s+}{}gs; $value =~ s{\s+$}{}gs; $value =~ s{,}{ }gs; push(@{$ret->{"deps"}}, split(/\s+/, $value)); } } return $ret; } 1; 07070100000004000081a400000000000000000000000163e504b9000038e5000000000000000000000000000000000000000d00000000Build/Deb.pm################################################################ # # Copyright (c) 1995-2014 SUSE Linux Products GmbH # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program (see the file COPYING); if not, write to the # Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA # ################################################################ package Build::Deb; use strict; use Digest::MD5; my $have_zlib; my $have_rawlzma; eval { require Compress::Zlib; $have_zlib = 1; }; eval { require Compress::Raw::Lzma; $have_rawlzma = 1; }; my %obs2debian = ( "i486" => "i386", "i586" => "i386", "i686" => "i386", "ppc" => "powerpc", "ppc64le" => "ppc64el", "x86_64" => "amd64", "armv4l" => "armel", "armv5l" => "armel", "armv6l" => "armel", "armv7el" => "armel", "armv7l" => "armhf", "armv7hl" => "armhf", "aarch64" => "arm64", ); sub basearch { my ($arch) = @_; return 'all' if !defined($arch) || $arch eq 'noarch'; return $obs2debian{$arch} || $arch; } sub obsarch { my ($arch) = @_; my @obs = grep {$obs2debian{$_} eq $arch} sort keys %obs2debian; return @obs if @obs; return $arch; } sub parse { my ($bconf, $fn) = @_; # get arch and os from macros and map to debian names my ($arch, $os) = Build::gettargetarchos($bconf); $os = 'linux' unless defined $os; $arch = basearch($arch); my @control; if (ref($fn) eq 'ARRAY') { @control = @$fn; } else { local *F; return { 'error' => "$fn: $!" } unless open(F, '<', $fn); @control = <F>; close F; chomp @control; } splice(@control, 0, 3) if @control > 3 && $control[0] =~ /^-----BEGIN/; my $name; my $version; my @deps; my @exclarch; while (@control) { my $c = shift @control; last if $c eq ''; # new paragraph my ($tag, $data) = split(':', $c, 2); next unless defined $data; $tag = uc($tag); while (@control && $control[0] =~ /^\s/) { $data .= "\n".substr(shift @control, 1); } $data =~ s/^\s+//s; $data =~ s/\s+$//s; if ($tag eq 'VERSION') { $version = $data; $version =~ s/-[^-]+$//; } elsif ($tag eq 'ARCHITECTURE') { my @archs = split('\s+', $data); map { s/\Q$os\E-//; s/any-// } @archs; next if grep { $_ eq "any" || $_ eq "all" } @archs; @exclarch = map { obsarch($_) } @archs; # unify my %exclarch = map {$_ => 1} @exclarch; @exclarch = sort keys %exclarch; } elsif ($tag eq 'SOURCE') { $name = $data; } elsif ($tag eq 'BUILD-DEPENDS' || $tag eq 'BUILD-CONFLICTS' || $tag eq 'BUILD-IGNORE' || $tag eq 'BUILD-DEPENDS-INDEP' || $tag eq 'BUILD-DEPENDS-ARCH' || $tag eq 'BUILD-CONFLICTS-ARCH' ) { my @d = split(/\s*,\s*/, $data); for my $d (@d) { my @alts = split('\s*\|\s*', $d); my @needed; for my $c (@alts) { if ($c =~ /\s+<[^>]+>$/) { my @build_profiles; # Empty for now my $bad = 1; while ($c =~ s/\s+<([^>]+)>$//) { next if (!$bad); my $list_valid = 1; for my $term (split(/\s+/, $1)) { my $isneg = ($term =~ s/^\!//); my $profile_match = grep(/^$term$/, @build_profiles); if (( $profile_match && $isneg) || (!$profile_match && !$isneg)) { $list_valid = 0; last; } } $bad = 0 if ($list_valid); } next if ($bad); } if ($c =~ /^(.*?)\s*\[(.*)\]$/) { $c = $1; my $isneg = 0; my $bad; for my $q (split('[\s,]', $2)) { $isneg = 1 if $q =~ s/^\!//; $bad = 1 if !defined($bad) && !$isneg; if ($isneg) { if ($q eq $arch || $q eq 'any' || $q eq "$os-$arch" || $q eq "$os-any" || $q eq "any-$arch") { $bad = 1; last; } } elsif ($q eq $arch || $q eq 'any' || $q eq "$os-$arch" || $q eq "$os-any" || $q eq "any-$arch") { $bad = 0; } } next if ($bad); } $c =~ s/^([^:\s]*):(any|native)(.*)$/$1$3/; push @needed, $c; } next unless @needed; $d = join(' | ', @needed); $d =~ s/ \(([^\)]*)\)/ $1/g; $d =~ s/>>/>/g; $d =~ s/<</</g; if ($tag eq 'BUILD-DEPENDS' || $tag eq 'BUILD-DEPENDS-INDEP' || $tag eq 'BUILD-DEPENDS-ARCH') { push @deps, $d; } else { push @deps, "-$d"; } } } } my $ret = {}; $ret->{'name'} = $name; $ret->{'version'} = $version; $ret->{'deps'} = \@deps; $ret->{'exclarch'} = \@exclarch if @exclarch; return $ret; } sub uncompress_rawlzma { my ($in) = @_; return undef unless defined($in) && $in ne ''; my $out = ''; my ($lz, $status) = Compress::Raw::Lzma::AutoDecoder->new('ConsumeInput' => 1, 'AppendOutput' => 1); return undef unless $lz; while (1) { $status = $lz->code($in, $out); return $out if $status == Compress::Raw::Lzma::LZMA_STREAM_END(); return undef if $status != Compress::Raw::Lzma::LZMA_OK(); } } sub uncompress { my ($data, $tool) = @_; return $data if $tool eq 'cat'; return Compress::Zlib::memGunzip($data) if $have_zlib && $tool eq 'gunzip'; return uncompress_rawlzma($data) if $have_rawlzma && $tool eq 'unxz'; local (*TMP, *TMP2); open(TMP, "+>", undef) or die("could not open tmpfile\n"); syswrite TMP, $data; my $pid = open(TMP2, "-|"); die("fork: $!\n") unless defined $pid; if (!$pid) { open(STDIN, "<&TMP"); seek(STDIN, 0, 0); # these two lines are a workaround for a perl bug mixing up FD sysseek(STDIN, 0, 0); exec($tool); die("$tool: $!\n"); } close(TMP); $data = ''; 1 while sysread(TMP2, $data, 1024, length($data)) > 0; if (!close(TMP2)) { warn("$tool error: $?\n"); return undef; } return $data; } sub control2res { my ($control) = @_; my %res; my @control = split("\n", $control); while (@control) { my $c = shift @control; last if $c eq ''; # new paragraph my ($tag, $data) = split(':', $c, 2); next unless defined $data; $tag = uc($tag); while (@control && $control[0] =~ /^\s/) { $data .= "\n".substr(shift @control, 1); } $data =~ s/^\s+//s; $data =~ s/\s+$//s; $res{$tag} = $data; } return %res; } sub debq { my ($fn) = @_; local *DEBF; if (ref($fn) eq 'GLOB') { *DEBF = *$fn; } elsif (!open(DEBF, '<', $fn)) { warn("$fn: $!\n"); return (); } my $data = ''; sysread(DEBF, $data, 4096); if (length($data) < 8+60) { warn("$fn: not a debian package - header too short\n"); close DEBF unless ref $fn; return (); } if (substr($data, 0, 8+16) ne "!<arch>\ndebian-binary " && substr($data, 0, 8+16) ne "!<arch>\ndebian-binary/ ") { close DEBF unless ref $fn; return (); } my $len = substr($data, 8+48, 10); $len += $len & 1; if (length($data) < 8+60+$len+60) { my $r = 8+60+$len+60 - length($data); $r -= length($data); if ((sysread(DEBF, $data, $r < 4096 ? 4096 : $r, length($data)) || 0) < $r) { warn("$fn: unexpected EOF\n"); close DEBF unless ref $fn; return (); } } $data = substr($data, 8 + 60 + $len); my $controlname = substr($data, 0, 16); my $decompressor; if ($controlname eq 'control.tar.gz ' || $controlname eq 'control.tar.gz/ ') { $decompressor = 'gunzip'; } elsif ($controlname eq 'control.tar.xz ' || $controlname eq 'control.tar.xz/ ') { $decompressor = 'unxz'; } elsif ($controlname eq 'control.tar.zst ' || $controlname eq 'control.tar.zst/') { $decompressor = 'unzstd'; } elsif ($controlname eq 'control.tar ' || $controlname eq 'control.tar/ ') { $decompressor = 'cat'; } else { warn("$fn: control.tar is not second ar entry\n"); close DEBF unless ref $fn; return (); } $len = substr($data, 48, 10); if (length($data) < 60+$len) { my $r = 60+$len - length($data); if ((sysread(DEBF, $data, $r, length($data)) || 0) < $r) { warn("$fn: unexpected EOF\n"); close DEBF unless ref $fn; return (); } } close DEBF unless ref($fn); $data = substr($data, 60, $len); my $controlmd5 = Digest::MD5::md5_hex($data); # our header signature $data = uncompress($data, $decompressor); if (!$data) { warn("$fn: corrupt control.tar file\n"); return (); } my $control; while (length($data) >= 512) { my $n = substr($data, 0, 100); $n =~ s/\0.*//s; my $len = oct('00'.substr($data, 124,12)); my $blen = ($len + 1023) & ~511; if (length($data) < $blen) { warn("$fn: corrupt control.tar file\n"); return (); } if ($n eq './control' || $n eq "control") { $control = substr($data, 512, $len); last; } $data = substr($data, $blen); } my %res = control2res($control); $res{'CONTROL_MD5'} = $controlmd5; return %res; } sub query { my ($handle, %opts) = @_; my %res = debq($handle); return undef unless %res; my $name = $res{'PACKAGE'}; my @provides = split(',\s*', $res{'PROVIDES'} || ''); if ($opts{'addselfprovides'}) { push @provides, "$name (= $res{'VERSION'})"; } my @depends = split(',\s*', $res{'DEPENDS'} || ''); push @depends, split(',\s*', $res{'PRE-DEPENDS'} || ''); my $data = { name => $name, hdrmd5 => $res{'CONTROL_MD5'}, provides => \@provides, requires => \@depends, }; my $src = $name; if ($res{'SOURCE'}) { $src = $res{'SOURCE'}; $data->{'sourcedep'} = $src; $src =~ s/\s.*$//; } $data->{'source'} = $src if $src ne ''; if ($opts{'conflicts'}) { my @conflicts = split(',\s*', $res{'CONFLICTS'} || ''); push @conflicts, split(',\s*', $res{'BREAKS'} || ''); $data->{'conflicts'} = \@conflicts if @conflicts; } if ($opts{'weakdeps'}) { for my $dep ('SUGGESTS', 'RECOMMENDS', 'ENHANCES') { $data->{lc($dep)} = [ split(',\s*', $res{$dep} || '') ] if defined $res{$dep}; } } if ($opts{'evra'}) { $res{'VERSION'} =~ /^(?:(\d+):)?(.*?)(?:-([^-]*))?$/s; $data->{'epoch'} = $1 if defined $1; $data->{'version'} = $2; $data->{'release'} = $3 if defined $3; $data->{'arch'} = $res{'ARCHITECTURE'}; $data->{'multiarch'} = $res{'MULTI-ARCH'} if $res{'MULTI-ARCH'}; } if ($opts{'description'}) { $data->{'description'} = $res{'DESCRIPTION'}; } if ($opts{'normalizedeps'}) { for my $dep (qw{provides requires conflicts suggests enhances recommends}) { next unless $data->{$dep}; for (@{$data->{$dep}}) { s/ \(([^\)]*)\)/ $1/g; s/<</</g; s/>>/>/g; } } } return $data; } sub queryhdrmd5 { my ($bin) = @_; local *F; open(F, '<', $bin) || die("$bin: $!\n"); my $data = ''; sysread(F, $data, 4096); if (length($data) < 8+60) { warn("$bin: not a debian package - header too short\n"); close F; return undef; } if (substr($data, 0, 8+16) ne "!<arch>\ndebian-binary " && substr($data, 0, 8+16) ne "!<arch>\ndebian-binary/ ") { warn("$bin: not a debian package - no \"debian-binary\" entry\n"); close F; return undef; } my $len = substr($data, 8+48, 10); $len += $len & 1; if (length($data) < 8+60+$len+60) { my $r = 8+60+$len+60 - length($data); $r -= length($data); if ((sysread(F, $data, $r < 4096 ? 4096 : $r, length($data)) || 0) < $r) { warn("$bin: unexpected EOF\n"); close F; return undef; } } $data = substr($data, 8 + 60 + $len); my $controlname = substr($data, 0, 16); if ($controlname ne 'control.tar.gz ' && $controlname ne 'control.tar.gz/ ' && $controlname ne 'control.tar.xz ' && $controlname ne 'control.tar.xz/ ' && $controlname ne 'control.tar.zst ' && $controlname ne 'control.tar.zst/' && $controlname ne 'control.tar ' && $controlname ne 'control.tar/ ') { warn("$bin: control.tar is not second ar entry\n"); close F; return undef; } $len = substr($data, 48, 10); if (length($data) < 60+$len) { my $r = 60+$len - length($data); if ((sysread(F, $data, $r, length($data)) || 0) < $r) { warn("$bin: unexpected EOF\n"); close F; return undef; } } close F; $data = substr($data, 60, $len); return Digest::MD5::md5_hex($data); } sub verscmp_part { my ($s1, $s2) = @_; return 0 if $s1 eq $s2; $s1 =~ s/([0-9]+)/substr("00000000000000000000000000000000$1", -32, 32)/ge; $s2 =~ s/([0-9]+)/substr("00000000000000000000000000000000$1", -32, 32)/ge; $s1 .= "\0"; $s2 .= "\0"; $s1 =~ tr[\176\000-\037\060-\071\101-\132\141-\172\040-\057\072-\100\133-\140\173-\175][\000-\176]; $s2 =~ tr[\176\000-\037\060-\071\101-\132\141-\172\040-\057\072-\100\133-\140\173-\175][\000-\176]; return $s1 cmp $s2; } sub verscmp { my ($s1, $s2) = @_; my ($e1, $v1, $r1) = $s1 =~ /^(?:(\d+):)?(.*?)(?:-([^-]*))?$/s; $e1 = 0 unless defined $e1; my ($e2, $v2, $r2) = $s2 =~ /^(?:(\d+):)?(.*?)(?:-([^-]*))?$/s; $e2 = 0 unless defined $e2; if ($e1 ne $e2) { my $r = verscmp_part($e1, $e2); return $r if $r; } my $r = verscmp_part($v1, $v2); return $r if $r; $r1 = '' unless defined $r1; $r2 = '' unless defined $r2; return verscmp_part($r1, $r2); } sub queryinstalled { my ($root, %opts) = @_; $root = '' if !defined($root) || $root eq '/'; my @pkgs; local *F; if (open(F, '<', "$root/var/lib/dpkg/status")) { my $ctrl = ''; while(<F>) { if ($_ eq "\n") { my %res = control2res($ctrl); if (defined($res{'PACKAGE'})) { my $data = {'name' => $res{'PACKAGE'}}; $res{'VERSION'} =~ /^(?:(\d+):)?(.*?)(?:-([^-]*))?$/s; $data->{'epoch'} = $1 if defined $1; $data->{'version'} = $2; $data->{'release'} = $3 if defined $3; $data->{'arch'} = $res{'ARCHITECTURE'}; push @pkgs, $data; } $ctrl = ''; next; } $ctrl .= $_; } close F; } return \@pkgs; } 1; 07070100000005000081a400000000000000000000000163e504b900001489000000000000000000000000000000000000001100000000Build/Debrepo.pm################################################################ # # Copyright (c) 1995-2014 SUSE Linux Products GmbH # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program (see the file COPYING); if not, write to the # Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA # ################################################################ package Build::Debrepo; use strict; sub addpkg { my ($res, $data, $options) = @_; return unless defined $data->{'version'}; my $selfprovides; $selfprovides = "= $data->{'version'}" if $options->{'addselfprovides'}; # split version into evr $data->{'epoch'} = $1 if $data->{'version'} =~ s/^(\d+)://s; $data->{'release'} = $1 if $data->{'version'} =~ s/-([^-]*)$//s; for my $d (qw{provides requires conflicts recommends suggests enhances breaks prerequires}) { next unless $data->{$d}; if ($options->{'normalizedeps'}) { $data->{$d} =~ s/\(([^\)]*)\)/$1/g; $data->{$d} =~ s/<</</g; $data->{$d} =~ s/>>/>/g; } $data->{$d} = [ split(/\s*,\s*/, $data->{$d}) ]; } push @{$data->{'requires'}}, @{$data->{'prerequires'}} if $data->{'prerequires'}; delete $data->{'prerequires'}; push @{$data->{'conflicts'}}, @{$data->{'breaks'}} if $data->{'breaks'}; delete $data->{'breaks'}; if (defined($selfprovides)) { $selfprovides = "($selfprovides)" unless $options->{'normalizedeps'}; $selfprovides = "$data->{'name'} $selfprovides"; push @{$data->{'provides'}}, $selfprovides unless @{$data->{'provides'} || []} && $data->{'provides'}->[-1] eq $selfprovides; } if ($options->{'withchecksum'}) { for (qw {md5 sha1 sha256}) { my $c = delete($data->{"checksum_$_"}); $data->{'checksum'} = "$_:$c" if $c; } } $data->{'sourcedep'} = $data->{'source'} if $data->{'source'}; $data->{'source'} =~ s/\s.*// if $data->{'source'}; if (ref($res) eq 'CODE') { $res->($data); } else { push @$res, $data; } } my %tmap = ( 'package' => 'name', 'version' => 'version', 'architecture' => 'arch', 'provides' => 'provides', 'depends' => 'requires', 'pre-depends' => 'prerequires', 'conflicts' => 'conflicts', 'breaks' => 'breaks', 'recommends' => 'recommends', 'suggests' => 'suggests', 'enhances' => 'enhances', 'filename' => 'location', 'source' => 'source', 'multi-arch' => 'multiarch', ); my %tmap_checksums = ( 'md5sum' => 'checksum_md5', 'sha1' => 'checksum_sha1', 'sha256' => 'checksum_sha256', ); sub parse { my ($in, $res, %options) = @_; $res ||= []; my $fd; if (ref($in)) { $fd = $in; } else { if ($in =~ /\.gz$/) { open($fd, '-|', "gzip", "-dc", $in) || die("$in: $!\n"); } else { open($fd, '<', $in) || die("$in: $!\n"); } } my $pkg = {}; my $tag; my %ltmap = %tmap; %ltmap = (%ltmap, %tmap_checksums) if $options{'withchecksum'}; while (<$fd>) { chomp; if ($_ eq '') { addpkg($res, $pkg, \%options) if %$pkg; $pkg = {}; next; } if (/^\s/) { next unless $tag; $pkg->{$tag} .= "\n".substr($_, 1); next; } my $data; ($tag, $data) = split(':', $_, 2); next unless defined $data; $tag = $ltmap{lc($tag)}; next unless $tag; $data =~ s/^\s*//; $pkg->{$tag} = $data; } addpkg($res, $pkg, \%options) if %$pkg; if (!ref($in)) { close($fd) || die("close $in: $!\n"); } return $res; } sub urldecode { my ($str, $iscgi) = @_; $str =~ tr/+/ / if $iscgi; $str =~ s/%([a-fA-F0-9]{2})/chr(hex($1))/sge; return $str; } sub parserepourl { my ($url) = @_; my @components; my $baseurl; if ($url =~ /\?/) { my ($base, $query) = split(/\?/, $url, 2); if ("&$query" =~ /\&dist=/) { my $dist; for my $querypart (split('&', $query)) { my ($k, $v) = split('=', $querypart, 2); $k = urldecode($k, 1); $v = urldecode($v, 1); $dist = $v if $k eq 'dist'; push @components, split(/[,+]/, $v) if $k eq 'component'; } $baseurl = $base; $baseurl .= '/' unless $baseurl =~ /\/$/; $url = "${baseurl}dists/${dist}/"; push @components, 'main' unless @components; } } if (@components) { ; # all done above } elsif ($url =~ /^(.*\/)\.(\/.*)?$/) { # flat repo $baseurl = $1; @components = ('.'); $url = defined($2) ? "$1$2" : $1; $url .= '/' unless $url =~ /\/$/; } else { if ($url =~ /([^\/]+)$/) { @components = split(/[,+]/, $1); $url =~ s/([^\/]+)$//; } push @components, 'main' unless @components; $url .= '/' unless $url =~ /\/$/; $baseurl = $url; $url =~ s/([^\/]+\/)$/dists\/$1/; $baseurl =~ s/([^\/]+\/)$//; } return $baseurl, $url, \@components; } 1; 07070100000006000081a400000000000000000000000163e504b900003a3d000000000000000000000000000000000000001000000000Build/Docker.pm################################################################ # # Copyright (c) 2017 SUSE Linux Products GmbH # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program (see the file COPYING); if not, write to the # Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA # ################################################################ package Build::Docker; use Build::SimpleXML; # to parse the annotation use Build::SimpleJSON; use strict; sub unify { my %h = map {$_ => 1} @_; return grep(delete($h{$_}), @_); } sub gettargetarch { my ($config) = @_; my $arch = 'noarch'; for (@{$config->{'macros'} || []}) { $arch = $1 if /^%define _target_cpu (\S+)/; } return $arch; } sub slurp { my ($fn) = @_; local *F; return undef unless open(F, '<', $fn); local $/ = undef; # Perl slurp mode my $content = <F>; close F; return $content; } sub expandvar_cplx { my ($cplx, $vars) = @_; if ($cplx =~ /^\!([a-zA-Z0-9_]+)/) { return [] unless @{$vars->{$1} || []}; my $n = $vars->{$1}->[0]; return $vars->{$n} || []; } return [] unless $cplx =~ /^([^\}:]+):([-+])([^}]*)$/s; my ($n, $m, $v) = ($1, $2, $3); $v = expandvars($v, $vars) if $v =~ /\$/; my $o = join(' ', @{$vars->{$n} || []}); return $o ne '' ? $vars->{$n} : [ $v ] if $m eq '-'; return $o ne '' ? [ $v ] : [] if $m eq '+'; return []; } sub expandvars { my ($str, $vars) = @_; $str =~ s/\$([a-zA-Z0-9_]+)|\$\{([^\}:\!]+)\}|\$\{([^}]+)\}/join(' ', @{$3 ? expandvar_cplx($3, $vars) : $vars->{$2 || $1} || []})/ge; return $str; } sub quote { my ($str, $q, $vars) = @_; $str = expandvars($str, $vars) if $vars && $q ne "'" && $str =~ /\$/; $str =~ s/([ \t\"\'\$\(\)])/sprintf("%%%02X", ord($1))/ge; return $str; } sub addrepo { my ($ret, $url, $prio) = @_; unshift @{$ret->{'imagerepos'}}, { 'url' => $url }; $ret->{'imagerepos'}->[0]->{'priority'} = $prio if defined $prio; if (defined($Build::Kiwi::urlmapper) && !$Build::Kiwi::urlmapper) { unshift @{$ret->{'path'}}, { %{$ret->{'imagerepos'}->[0]} }; return 1; } if ($Build::Kiwi::urlmapper) { my $prp = $Build::Kiwi::urlmapper->($url); if (!$prp) { $ret->{'error'} = "cannot map '$url' to obs"; return undef; } my ($projid, $repoid) = split('/', $prp, 2); unshift @{$ret->{'path'}}, {'project' => $projid, 'repository' => $repoid}; $ret->{'path'}->[0]->{'priority'} = $prio if defined $prio; return 1; } else { # this is just for testing purposes... $url =~ s/^\/+$//; $url =~ s/:\//:/g; my @url = split('/', $url); unshift @{$ret->{'path'}}, {'project' => $url[-2], 'repository' => $url[-1]} if @url >= 2; $ret->{'path'}->[0]->{'priority'} = $prio if defined $prio; return 1; } } sub cmd_zypper { my ($ret, @args) = @_; # skip global options while (@args && $args[0] =~ /^-/) { shift @args if $args[0] eq '-R' || $args[0] eq '--root' || $args[0] eq '--installroot'; shift @args; } return unless @args; if ($args[0] eq 'in' || $args[0] eq 'install') { shift @args; while (@args && $args[0] =~ /^-/) { shift @args if $args[0] =~ /^--(?:from|repo|type)$/ || $args[0] =~ /^-[tr]$/; shift @args; } my @deps = grep {/^[a-zA-Z_0-9]/} @args; s/^([^<=>]+)([<=>]+)/$1 $2 / for @deps; push @{$ret->{'deps'}}, @deps; } elsif ($args[0] eq 'ar' || $args[0] eq 'addrepo') { my $prio; shift @args; while (@args && $args[0] =~ /^-/) { if ($args[0] eq '-p' || $args[0] eq '--priority') { $prio = 99 - $args[1]; splice(@args, 0, 2); next; } shift @args if $args[0] =~ /^--(?:repo|type)$/ || $args[0] =~ /^-[rt]$/; shift @args; } if (@args) { my $path = $args[0]; $path =~ s/\/[^\/]*\.repo$//; addrepo($ret, $path, $prio); } } } sub cmd_obs_pkg_mgr { my ($ret, @args) = @_; return unless @args; if ($args[0] eq 'add_repo') { shift @args; addrepo($ret, $args[0]) if @args; } elsif ($args[0] eq 'install') { shift @args; push @{$ret->{'deps'}}, @args; } } sub cmd_dnf { my ($ret, @args) = @_; # skip global options shift @args while @args && $args[0] =~ /^-/; return unless @args; if ($args[0] eq 'in' || $args[0] eq 'install') { shift @args; while (@args && $args[0] =~ /^-/) { shift @args; } push @{$ret->{'deps'}}, grep {/^[a-zA-Z_0-9]/} @args; } } sub cmd_apt_get { my ($ret, @args) = @_; shift @args while @args && $args[0] =~ /^-/; return unless @args; if ($args[0] eq 'install') { shift @args; push @{$ret->{'deps'}}, grep {/^[a-zA-Z_0-9]/} @args; } } sub cmd_curl { my ($ret, @args) = @_; my @urls; while (@args) { my $arg = shift @args; if ($arg eq '--url') { $arg = shift @args; push @urls, $arg if $arg =~ /^https?:\/\//; } elsif ($arg =~ /^-/) { shift @args if $arg eq '-d' || $arg =~ /^--data/ || $arg eq '-F' || $arg =~ /^--form/ || $arg eq '-m' || $arg =~ /^--max/ || $arg eq '-o' || $arg eq '--output' || $arg =~ /^--retry/ || $arg eq '-u' || $arg eq '--user' || $arg eq '-A' || $arg eq '--user-agent' || $arg eq '-H' || $arg eq '--header'; } else { push @urls, $arg if $arg =~ /^https?:\/\//; } } for my $url (@urls) { my $asset = { 'url' => $url, 'type' => 'webcache' }; push @{$ret->{'remoteassets'}}, $asset; } } sub parse { my ($cf, $fn) = @_; my $unorderedrepos; my $useobsrepositories; my $nosquash; my $dockerfile_data; my $remoteasset; if (ref($fn) eq 'SCALAR') { $dockerfile_data = $$fn; } else { $dockerfile_data = slurp($fn); return { 'error' => 'could not open Dockerfile' } unless defined $dockerfile_data; } my @lines = split(/\r?\n/, $dockerfile_data); my $ret = { 'deps' => [], 'path' => [], 'imagerepos' => [], }; my %build_vars; if ($cf->{'buildflags:dockerarg'}) { for (@{$cf->{'buildflags'} || []}) { $build_vars{$1} = [ $2 ] if /^dockerarg:(.*?)=(.*)$/s; } } my $excludedline; my $vars = {}; my $vars_env = {}; my $vars_meta = {}; my %as_container; my $from_seen; my @requiredarch; my @badarch; my @containerrepos; while (@lines) { my $line = shift @lines; $line =~ s/^\s+//; if ($line =~ /^#/) { if ($line =~ /^#!BuildTag:\s*(.*?)$/) { my @tags = split(' ', $1); push @{$ret->{'containertags'}}, @tags if @tags; } if ($line =~ /^#!BuildName:\s*(\S+)\s*$/) { $ret->{'name'} = $1; } if ($line =~ /^#!BuildVersion:\s*(\S+)\s*$/) { $ret->{'version'} = $1; } if ($line =~ /^#!UnorderedRepos\s*$/) { $unorderedrepos = 1; } if ($line =~ /^#!UseOBSRepositories\s*$/) { $useobsrepositories = 1; } if ($line =~ /^#!NoSquash\s*$/) { $nosquash = 1; } if ($line =~ /^#!Milestone:\s*(\S+)\s*$/) { $ret->{'milestone'} = $1; } if ($line =~ /^#!ExcludeArch:\s*(.*?)$/) { push @badarch, split(' ', $1) ; } if ($line =~ /^#!ExclusiveArch:\s*(.*?)$/) { push @requiredarch, split(' ', $1) ; } if ($line =~ /^#!ArchExclusiveLine:\s*(.*?)$/) { my $arch = gettargetarch($cf); $excludedline = (grep {$_ eq $arch} split(' ', $1)) ? undef : 1; } if ($line =~ /^#!ArchExcludedLine:\s*(.*?)$/) { my $arch = gettargetarch($cf); $excludedline = (grep {$_ eq $arch} split(' ', $1)) ? 1 : undef; } if ($line =~ /^\#\!RemoteAssetUrl:\s*(\S+)\s*$/i) { $remoteasset->{'url'} = $1; push @{$ret->{'remoteassets'}}, $remoteasset; $remoteasset = undef; } next; } $ret->{'exclarch'} = [ unify(@requiredarch) ] if @requiredarch; $ret->{'badarch'} = [ unify(@badarch) ] if @badarch; # add continuation lines while (@lines && $line =~ s/\\[ \t]*$//) { shift @lines while @lines && $lines[0] =~ /^\s*#/; $line .= shift(@lines) if @lines; } $line =~ s/^\s+//; $line =~ s/\s+$//; next unless $line; if ($excludedline) { undef $excludedline; next; } my ($cmd, @args); ($cmd, $line) = split(' ', $line, 2); $cmd = uc($cmd); # escape and unquote $line =~ s/%/%25/g; $line =~ s/\\(.)/sprintf("%%%02X", ord($1))/ge; while ($line =~ /([\"\'])/) { my $q = $1; last unless $line =~ s/$q(.*?)$q/quote($1, $q, $vars)/e; } if ($cmd eq 'FROM') { $vars = { %$vars_meta }; # reset vars } # split into args then expand @args = split(/[ \t]+/, $line); for my $arg (@args) { $arg = expandvars($arg, $vars) if $arg =~ /\$/; $arg =~ s/%([a-fA-F0-9]{2})/chr(hex($1))/ge; } # process commands if ($cmd eq 'FROM') { shift @args if @args && $args[0] =~ /^--platform=/; if (@args && !$as_container{$args[0]}) { $as_container{$args[2]} = $args[0] if @args > 2 && lc($args[1]) eq 'as'; my $container = $args[0]; if ($container ne 'scratch') { if ($Build::Kiwi::urlmapper && $container =~ /^([^\/]+\.[^\/]+)\/[a-zA-Z0-9]/) { my $prp = $Build::Kiwi::urlmapper->("registry://$1/"); push @containerrepos, $prp if $prp; } $container .= ':latest' unless $container =~ /:[^:\/]+$/; $container = "container:$container"; if ($container && !grep {$_ eq $container} @{$ret->{'deps'}}) { push @{$ret->{'deps'}}, $container; } } } $vars_env = {}; # should take env from base container $vars = { %$vars_env }; $from_seen = 1; } elsif ($cmd eq 'RUN') { $line =~ s/#.*//; # get rid of comments for my $l (split(/(?:\||\|\||\&|\&\&|;|\)|\()/, $line)) { $l =~ s/^\s+//; $l =~ s/\s+$//; $l = expandvars($l, $vars) if $l =~ /\$/; @args = split(/[ \t]+/, $l); s/%([a-fA-F0-9]{2})/chr(hex($1))/ge for @args; next unless @args; my $rcmd = shift @args; $rcmd = shift @args if @args && ($rcmd eq 'then' || $rcmd eq 'else' || $rcmd eq 'elif' || $rcmd eq 'if' || $rcmd eq 'do'); if ($rcmd eq 'zypper') { cmd_zypper($ret, @args); } elsif ($rcmd eq 'yum' || $rcmd eq 'dnf') { cmd_dnf($ret, @args); } elsif ($rcmd eq 'apt-get') { cmd_apt_get($ret, @args); } elsif ($rcmd eq 'curl') { cmd_curl($ret, @args); } elsif ($rcmd eq 'obs_pkg_mgr') { cmd_obs_pkg_mgr($ret, @args); } } } elsif ($cmd eq 'ENV') { @args=("$args[0]=$args[1]") if @args == 2 && $args[0] !~ /=/; for (@args) { next unless /^(.*?)=(.*)$/; $vars->{$1} = [ $2 ]; $vars_env->{$1} = [ $2 ]; } } elsif ($cmd eq 'ARG') { for (@args) { next unless /^([^=]+)(?:=(.*))?$/; next if $vars_env->{$1}; $vars->{$1} = $build_vars{$1} || (defined($2) ? [ $2 ] : $vars_meta->{$1} || []); $vars_meta->{$1} = $vars->{$1} unless $from_seen; } } } push @{$ret->{'deps'}}, '--unorderedimagerepos' if $unorderedrepos; my $version = $ret->{'version'}; my $release = $cf->{'buildrelease'}; for (@{$ret->{'containertags'} || []}) { s/<VERSION>/$version/g if defined $version; s/<RELEASE>/$release/g if defined $release; } $ret->{'name'} = 'docker' if !defined($ret->{'name'}) && !$cf->{'__dockernoname'}; $ret->{'path'} = [ { 'project' => '_obsrepositories', 'repository' => '' } ] if $useobsrepositories; $ret->{'nosquash'} = 1 if $nosquash; if (@containerrepos) { for (unify(@containerrepos)) { my @s = split('/', $_, 2); push @{$ret->{'containerpath'}}, {'project' => $s[0], 'repository' => $s[1] }; } } return $ret; } sub showcontainerinfo { my ($disturl, $release); while (@ARGV) { if (@ARGV > 2 && $ARGV[0] eq '--disturl') { (undef, $disturl) = splice(@ARGV, 0, 2); } elsif (@ARGV > 2 && $ARGV[0] eq '--release') { (undef, $release) = splice(@ARGV, 0, 2); } else { last; } } my ($fn, $image, $taglist, $annotationfile) = @ARGV; local $Build::Kiwi::urlmapper = sub { return $_[0] }; my $cf = { '__dockernoname' => 1 }; $cf->{'buildrelease'} = $release if defined $release; my $d = {}; $d = parse($cf, $fn) if $fn; die("$d->{'error'}\n") if $d->{'error'}; $image =~ s/.*\/// if defined $image; my @tags = split(' ', $taglist); for (@tags) { $_ .= ':latest' unless /:[^:\/]+$/; if (/:([0-9][^:]*)$/) { $d->{'version'} = $1 unless defined $d->{'version'}; } } my @repos = @{$d->{'imagerepos'} || []}; if ($annotationfile) { my $annotation = slurp($annotationfile); $annotation = Build::SimpleXML::parse($annotation) if $annotation; $annotation = $annotation && ref($annotation) eq 'HASH' ? $annotation->{'annotation'} : undef; $annotation = $annotation && ref($annotation) eq 'ARRAY' ? $annotation->[0] : undef; my $annorepos = $annotation && ref($annotation) eq 'HASH' ? $annotation->{'repo'} : undef; $annorepos = undef unless $annorepos && ref($annorepos) eq 'ARRAY'; for my $annorepo (@{$annorepos || []}) { next unless $annorepo && ref($annorepo) eq 'HASH' && $annorepo->{'url'}; push @repos, { 'url' => $annorepo->{'url'}, '_type' => {'priority' => 'number'} }; $repos[-1]->{'priority'} = $annorepo->{'priority'} if defined $annorepo->{'priority'}; } } my $buildtime = time(); my $containerinfo = { 'buildtime' => $buildtime, '_type' => {'buildtime' => 'number'}, }; $containerinfo->{'tags'} = \@tags if @tags; $containerinfo->{'repos'} = \@repos if @repos; $containerinfo->{'file'} = $image if defined $image; $containerinfo->{'disturl'} = $disturl if defined $disturl; $containerinfo->{'name'} = $d->{'name'} if defined $d->{'name'}; $containerinfo->{'version'} = $d->{'version'} if defined $d->{'version'}; $containerinfo->{'release'} = $release if defined $release; $containerinfo->{'milestone'} = $d->{'milestone'} if defined $d->{'milestone'}; print Build::SimpleJSON::unparse($containerinfo)."\n"; } sub show { my ($release); while (@ARGV) { if (@ARGV > 2 && $ARGV[0] eq '--release') { (undef, $release) = splice(@ARGV, 0, 2); } else { last; } } my ($fn, $field) = @ARGV; local $Build::Kiwi::urlmapper = sub { return $_[0] }; my $cf = {}; $cf->{'buildrelease'} = $release if defined $release; my $d = {}; $d = parse($cf, $fn) if $fn; die("$d->{'error'}\n") if $d->{'error'}; my $x = $d->{$field}; $x = [ $x ] unless ref $x; print "@$x\n"; } 1; 07070100000007000081a400000000000000000000000163e504b9000016b6000000000000000000000000000000000000001200000000Build/Download.pm################################################################ # # Copyright (c) 2021 SUSE LLC # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program (see the file COPYING); if not, write to the # Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA # ################################################################ package Build::Download; use strict; use LWP::UserAgent; use Digest::MD5 (); use Digest::SHA (); # # Create a user agent used to access remote servers # sub create_ua { my (%opt) = @_; my $agent = $opt{'agent'} || 'openSUSE build script'; my $timeout = $opt{'timeout'} || 60; my $ssl_opts = $opt{'ssl_opts'} || { verify_hostname => 1 }; my $ua = LWP::UserAgent->new(agent => $agent, timeout => $timeout, ssl_opts => $ssl_opts); $ua->env_proxy; return $ua; } # # Create a hash context from a digest # sub digest2ctx { my ($digest) = @_; return Digest::SHA->new(1) if $digest =~ /^sha1?:/i; return Digest::SHA->new($1) if $digest =~ /^sha(\d+):/i; return Digest::MD5->new() if $digest =~ /^md5:/i; return undef; } # # Verify that some data matches a digest # sub checkdigest { my ($data, $digest) = @_; my $ctx = digest2ctx($digest); die("unsupported digest algo '$digest'\n") unless $ctx; $ctx->add($data); my $hex = $ctx->hexdigest(); if (lc($hex) ne lc((split(':', $digest, 2))[1])) { die("digest mismatch: $digest, got $hex\n"); } } # # Call the get method with the correct max size setting # sub ua_get { my ($ua, $url, $maxsize, @hdrs) = @_; my $res; if (defined($maxsize)) { my $oldmaxsize = $ua->max_size($maxsize); $res = $ua->get($url, @hdrs); $ua->max_size($oldmaxsize); die("download of $url failed: ".$res->header('X-Died')) if $res->header('Client-Aborted') && $res->header('X-Died'); die("download of $url failed: max size exceeded\n") if $res->header('Client-Aborted'); } else { $res = $ua->get($url, @hdrs); } return $res; } sub ua_head { my ($ua, $url, $maxsize, @hdrs) = @_; return $ua->head($url, @hdrs); } # # Download a file with the correct max size setting # sub ua_mirror { my ($ua, $url, $dest, $maxsize, @hdrs) = @_; my $res = ua_get($ua, $url, $maxsize, ':content_file', $dest, @hdrs); die("download of $url failed: ".$res->header('X-Died')) if $res->header('X-Died'); if ($res->is_success) { my @s = stat($dest); die("download of $url did not create $dest: $!\n") unless @s; my ($cl) = $res->header('Content-length'); die("download of $url size mismatch: $cl != $s[7]\n") if defined($cl) && $cl != $s[7]; } return $res; } # # Download data from a server # sub fetch { my ($url, %opt) = @_; my $ua = $opt{'ua'} || create_ua(); my $retry = $opt{'retry'} || 0; my $res; my @accept; @accept = ('Accept', join(', ', @{$opt{'accept'}})) if $opt{'accept'}; while (1) { $res = ua_get($ua, $url, $opt{'maxsize'}, @accept, @{$opt{'headers'} || []}); last if $res->is_success; return undef if $opt{'missingok'} && $res->code == 404; my $status = $res->status_line; die("download of $url failed: $status\n") unless $retry-- > 0 && $res->previous; warn("retrying $url\n"); } my $data = $res->decoded_content; my $ct = $res->header('content_type'); checkdigest($data, $opt{'digest'}) if $opt{'digest'}; return ($data, $ct); } # # Do a HEAD request # sub head { my ($url, %opt) = @_; my $ua = $opt{'ua'} || create_ua(); my $retry = $opt{'retry'} || 0; my $res; my @accept; @accept = ('Accept', join(', ', @{$opt{'accept'}})) if $opt{'accept'}; while (1) { $res = ua_head($ua, $url, $opt{'maxsize'}, @accept, @{$opt{'headers'} || []}); last if $res->is_success; return undef if $opt{'missingok'} && $res->code == 404; my $status = $res->status_line; die("head request of $url failed: $status\n") unless $retry-- > 0 && $res->previous; warn("retrying $url\n"); } my $data = { $res->flatten() }; $data = { map {lc($_) => $data->{$_}} sort keys %$data }; my $ct = $res->header('content_type'); return ($data, $ct); } # # Verify that the content of a file matches a digest # sub checkfiledigest { my ($file, $digest) = @_; my $ctx = digest2ctx($digest); die("$file: unsupported digest algo '$digest'\n") unless $ctx; my $fd; open ($fd, '<', $file) || die("$file: $!\n"); $ctx->addfile($fd); close($fd); my $hex = $ctx->hexdigest(); if (lc($hex) ne lc((split(':', $digest, 2))[1])) { die("$file: digest mismatch: $digest, got $hex\n"); } } # # Download a file from a server # sub download { my ($url, $dest, $destfinal, %opt) = @_; my $ua = $opt{'ua'} || create_ua(); my $retry = $opt{'retry'} || 0; while (1) { unlink($dest); # just in case my $res = eval { ua_mirror($ua, $url, $dest, $opt{'maxsize'}, @{$opt{'headers'} || []}) }; if ($@) { unlink($dest); die($@); } last if $res->is_success; return undef if $opt{'missingok'} && $res->code == 404; my $status = $res->status_line; die("download of $url failed: $status\n") unless $retry-- > 0 && $res->previous; warn("retrying $url\n"); } checkfiledigest($dest, $opt{'digest'}) if $opt{'digest'}; if ($destfinal) { rename($dest, $destfinal) || die("rename $dest $destfinal: $!\n"); } return 1; } 1; 07070100000008000081a400000000000000000000000163e504b900004d97000000000000000000000000000000000000001000000000Build/Expand.pm################################################################ # # Copyright (c) 1995-2014 SUSE Linux Products GmbH # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program (see the file COPYING); if not, write to the # Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA # ################################################################ package Build::Expand; use strict; our $expand_dbg; # XXX: should also check the package EVR sub nevrmatch { my ($config, $r, @p) = @_; my $rn = $r; $rn =~ s/\s*([<=>]{1,2}).*$//; return grep {$_ eq $rn} @p; } # check if package $q has a conflict against an installed package. # if yes, add message to @$eq and return true sub checkconflicts { my ($config, $ins, $q, $eq, @r) = @_; my $whatprovides = $config->{'whatprovidesh'}; my $ret = 0; for my $r (@r) { if ($r =~ /^\(.*\)$/) { # note the []: we ignore errors here. they will be reported if the package is chosen. my $n = normalizerich($config, $q, $r, 1, []); $ret = 1 if check_conddeps_notinst($q, $n, $eq, $ins); next; } my @eq = grep {$ins->{$_}} @{$whatprovides->{$r} || Build::addproviders($config, $r)}; next unless @eq; push @$eq, map {"(provider $q conflicts with $_)"} @eq; $ret = 1; } return $ret; } sub checkobsoletes { my ($config, $ins, $q, $eq, @r) = @_; my $whatprovides = $config->{'whatprovidesh'}; my $ret = 0; for my $r (@r) { my @eq = grep {$ins->{$_}} nevrmatch($config, $r, @{$whatprovides->{$r} || Build::addproviders($config, $r)}); next unless @eq; push @$eq, map {"(provider $q obsoletes $_)"} @eq; $ret = 1; } return $ret; } sub todo2recommended { my ($config, $recommended, $todo) = @_; my $whatprovides = $config->{'whatprovidesh'}; my $pkgrecommends = $config->{'recommendsh'} || {}; for my $p (splice @$todo) { for my $r (@{$pkgrecommends->{$p} || []}) { $recommended->{$_} = 1 for @{$whatprovides->{$r} || Build::addproviders($config, $r)} } } } sub cplx_mix { my ($q1, $q2, $todnf) = @_; my @q; for my $qq1 (@$q1) { for my $qq2 (@$q2) { my %qq = map {$_ => 1} (@$qq1, @$qq2); my @qq = sort keys %qq; push @q, \@qq unless grep {$qq{"-$_"}} @qq; } } return $todnf ? 0 : 1 unless @q; return (-1, @q); } sub cplx_inv { my ($f, @q) = @_; return 1 - $f if $f == 0 || $f == 1; my @iq; for my $q (@q) { $q = [ map {"-$_"} @$q ]; s/^--// for @$q; } return (-1, @q); } sub normalize_cplx_rec { my ($c, $r, $todnf) = @_; if ($r->[0] == 0) { my $ri = (split(/[ <=>]/, $r->[1], 2))[0]; my ($config, $p, $ignore, $xignore) = @$c; if (!$todnf) { return 1 if $ignore->{$ri} || $xignore->{$ri}; return 1 if defined($p) && ($ignore->{"$p:$ri"} || $xignore->{"$p:$ri"}); } my $whatprovides = $config->{'whatprovidesh'}; my @q = @{$whatprovides->{$r->[1]} || Build::addproviders($config, $r->[1])}; return 0 unless @q; if ($todnf) { return (-1, map { [ $_ ] } @q); } else { return (-1, [ @q ]); } } if ($r->[0] == 3 && @$r == 4) { # complex if/else case: A IF (B ELSE C) -> (A OR ~B) AND (C OR B) my ($n1, @q1) = normalize_cplx_rec($c, [3, $r->[1], $r->[2]], $todnf); my ($n2, @q2) = normalize_cplx_rec($c, [2, $r->[2], $r->[3]], $todnf); return 0 if $n1 == 0 || $n2 == 0; return ($n2, @q2) if $n1 == 1; return ($n1, @q1) if $n2 == 1; if (!$todnf) { return (-1, @q1, @q2); } else { return cplx_mix(\@q1, \@q2, $todnf); } } if ($r->[0] == 4 && @$r == 4) { # complex unless/else case: A UNLESS (B ELSE C) -> (A AND ~B) OR (C AND B) my ($n1, @q1) = normalize_cplx_rec($c, [4, $r->[1], $r->[2]], $todnf); my ($n2, @q2) = normalize_cplx_rec($c, [1, $r->[2], $r->[3]], $todnf); return 1 if $n1 == 1 || $n2 == 1; return ($n2, @q2) if $n1 == 0; return ($n1, @q1) if $n2 == 0; if ($todnf) { return (-1, @q1, @q2); } else { return cplx_mix(\@q1, \@q2, $todnf); } } if ($r->[0] == 1 || $r->[0] == 4) { # and / unless my $todnf2 = $r->[0] == 4 ? !$todnf : $todnf; my ($n1, @q1) = normalize_cplx_rec($c, $r->[1], $todnf); my ($n2, @q2) = normalize_cplx_rec($c, $r->[2], $todnf); ($n2, @q2) = cplx_inv($n2, @q2) if $r->[0] == 4; return 0 if $n1 == 0 || $n2 == 0; return ($n2, @q2) if $n1 == 1; return ($n1, @q1) if $n2 == 1; if (!$todnf) { return (-1, @q1, @q2); } else { return cplx_mix(\@q1, \@q2, $todnf); } } if ($r->[0] == 2 || $r->[0] == 3) { # or / if my $todnf2 = $r->[0] == 3 ? !$todnf : $todnf; my ($n1, @q1) = normalize_cplx_rec($c, $r->[1], $todnf); my ($n2, @q2) = normalize_cplx_rec($c, $r->[2], $todnf2); ($n2, @q2) = cplx_inv($n2, @q2) if $r->[0] == 3; return 1 if $n1 == 1 || $n2 == 1; return ($n2, @q2) if $n1 == 0; return ($n1, @q1) if $n2 == 0; if ($todnf) { return (-1, @q1, @q2); } else { return cplx_mix(\@q1, \@q2, $todnf); } } if ($r->[0] == 6 || $r->[0] == 7) { # with / without my ($n1, @q1) = normalize_cplx_rec($c, $r->[1], 0); my ($n2, @q2) = normalize_cplx_rec($c, $r->[2], 0); if ($n2 == 0 && $r->[0] == 7) { @q2 = ( [] ); $n2 = -1; } return 0 if $n1 != -1 || $n2 != -1; return 0 if @q1 != 1 || @q2 != 1; @q1 = @{$q1[0]}; @q2 = @{$q2[0]}; return 0 if grep {/^-/} @q1; return 0 if grep {/^-/} @q2; my %q2 = map {$_ => 1} @q2; my @q; if ($r->[0] == 6) { @q = grep {$q2{$_}} @q1; } else { @q = grep {!$q2{$_}} @q1; } return 0 unless @q; if ($todnf) { return (-1, map { [ $_ ] } @q); } else { return (-1, [ @q ]); } } return 0; } sub normalizerich { my ($config, $p, $dep, $deptype, $error, $ignore, $xignore) = @_; my $r = Build::Rpm::parse_rich_dep($dep); if (!$r) { if (defined($p)) { push @$error, "cannot parse dependency $dep from $p"; } else { push @$error, "cannot parse dependency $dep"; } return []; } my $c = [$config, $p, $ignore || {}, $xignore || {}]; my ($n, @q); if ($deptype == 0) { ($n, @q) = normalize_cplx_rec($c, $r); return () if $n == 1; if (!$n) { if (defined($p)) { push @$error, "nothing provides $dep needed by $p"; } else { push @$error, "nothing provides $dep"; } return []; } } else { ($n, @q) = normalize_cplx_rec($c, $r, 1); ($n, @q) = cplx_inv($n, @q); if (!$n) { if (defined($p)) { push @$error, "$p conflicts with always true $dep"; } else { push @$error, "conflict with always true $dep"; } } } for my $q (@q) { my @neg = @$q; @neg = grep {s/^-//} @neg; @neg = grep {$_ ne $p} @neg if defined $p; @$q = grep {!/^-/} @$q; $q = [$dep, $deptype, \@neg, @$q]; } return \@q; } # handle a normalized rich dependency from install of package p # todo_cond is undef if we are re-checking the cond queue sub check_conddeps_inst { my ($p, $n, $error, $installed, $aconflicts, $todo, $todo_cond) = @_; for my $c (@$n) { my ($r, $rtype, $cond, @q) = @$c; next unless defined $cond; # already handled? next if grep {$installed->{$_}} @q; # already fulfilled my @cx = grep {!$installed->{$_}} @$cond; # open conditions if (!@cx) { $c->[2] = undef; # mark as handled to avoid dups if (@q) { push @$todo, $c, $p; } elsif (@$cond) { if (!$rtype) { if (defined($p)) { push @$error, "nothing provides $r needed by $p"; } else { push @$error, "nothing provides $r"; } next; } if (defined($p)) { push @$error, map {"$p conflicts with $_"} sort(@$cond); } else { push @$error, map {"conflicts with $_"} sort(@$cond); } } } else { if (!@q && @cx == 1) { if (defined($p)) { $aconflicts->{$cx[0]} = "is in conflict with $p"; } else { $aconflicts->{$cx[0]} = "is in conflict"; } } elsif ($todo_cond) { push @{$todo_cond->{$_}}, [ $c, $p ] for @cx; } } } } # handle a normalized rich dependency from a not-yet installed package # (we just check conflicts) sub check_conddeps_notinst { my ($p, $n, $eq, $installed) = @_; my $ret = 0; for my $c (@$n) { my ($r, $rtype, $cond, @q) = @$c; next if @q || !@$cond || grep {!$installed->{$_}} @$cond; push @$eq, map {"(provider $p conflicts with $_)"} sort(@$cond); $ret = 1; } return $ret; } sub extractnative { my ($config, $r, $p, $foreign) = @_; my $ma = $config->{'multiarchh'}->{$p} || ''; if ($ma eq 'foreign' || ($ma eq 'allowed' && $r =~ /:any/)) { if ($expand_dbg && !grep {$r eq $_} @$foreign) { print "added $r to foreign dependencies\n"; } push @$foreign, $r; return 1; } return 0; } sub expand { my ($config, @p) = @_; my $conflicts = $config->{'conflicth'}; my $pkgconflicts = $config->{'pkgconflictsh'} || {}; my $pkgobsoletes = $config->{'pkgobsoletesh'} || {}; my $prefer = $config->{'preferh'}; my $ignore = $config->{'ignoreh'}; my $ignoreconflicts = $config->{'expandflags:ignoreconflicts'}; my $ignoreignore; my $userecommendsforchoices = 1; my $whatprovides = $config->{'whatprovidesh'}; my $requires = $config->{'requiresh'}; my $xignore = { map {substr($_, 1) => 1} grep {/^-/} @p }; $ignoreconflicts = 1 if $xignore->{'-ignoreconflicts--'}; $ignore = {} if $xignore->{'-ignoreignore--'}; if ($ignoreignore) { $xignore = {}; $ignore = {}; } my @directdepsend; if ($xignore->{'-directdepsend--'}) { delete $xignore->{'-directdepsend--'}; @directdepsend = @p; for my $p (splice @p) { last if $p eq '--directdepsend--'; push @p, $p; } @directdepsend = grep {!/^-/} splice(@directdepsend, @p + 1); } my $extractnative; (undef, $extractnative) = splice(@p, 0, 2) if @p > 1 && $p[0] eq '--extractnative--' && ref($p[1]); undef $extractnative if $extractnative && !%{$config->{'multiarchh'} || {}}; my %p; # expanded packages my @todo; # dependencies to install my @todo_inst; # packages we decided to install my %todo_cond; my %recommended; # recommended by installed packages my @rec_todo; # installed todo my @error; my %aconflicts; # packages we are conflicting with my @native; # handle direct conflicts for (grep {/^!/} @p) { my $r = /^!!/ ? substr($_, 2) : substr($_, 1); if ($r =~ /^\(.*\)$/) { my $n = normalizerich($config, undef, $r, 1, \@error); my %naconflicts; check_conddeps_inst(undef, $n, \@error, \%p, \%naconflicts, \@todo, \%todo_cond); push @{$aconflicts{$_}}, $naconflicts{$_} for keys %naconflicts; next; } my @q = @{$whatprovides->{$r} || Build::addproviders($config, $r)}; @q = nevrmatch($config, $r, @q) if /^!!/; push @{$aconflicts{$_}}, "is in conflict" for @q; } @p = grep {!/^[-!]/} @p; # add direct dependency packages. this is different from below, # because we add packages even if the dep is already provided and # we break ambiguities if the name is an exact match. for my $r (splice @p) { if ($r =~ /^\(.*\)$/) { push @p, $r; # rich deps are never direct next; } my @q = @{$whatprovides->{$r} || Build::addproviders($config, $r)}; my $pn = $r; $pn =~ s/ .*//; @q = grep {$_ eq $pn} @q; if (@q != 1) { push @p, $r; next; } my $p = $q[0]; next if $extractnative && extractnative($config, $r, $p, \@native); print "added $p because of $r (direct dep)\n" if $expand_dbg; push @todo_inst, $p; } for my $r (@p, @directdepsend) { if ($r =~ /^\(.*\)$/) { # rich dep. normalize, put on todo. my $n = normalizerich($config, undef, $r, 0, \@error); my %naconflicts; check_conddeps_inst(undef, $n, \@error, \%p, \%naconflicts, \@todo, \%todo_cond); push @{$aconflicts{$_}}, $naconflicts{$_} for keys %naconflicts; } else { push @todo, $r, undef; } } for my $p (@todo_inst) { push @error, map {"$p $_"} @{$aconflicts{$p}} if $aconflicts{$p}; } return (undef, @error) if @error; push @native, '--directdepsend--' if $extractnative; while (@todo || @todo_inst) { # install a set of chosen packages # ($aconficts must not be set for any of them) if (@todo_inst) { if (@todo_inst > 1) { my %todo_inst = map {$_ => 1} @todo_inst; @todo_inst = grep {delete($todo_inst{$_})} @todo_inst; } # check aconflicts (just in case) for my $p (@todo_inst) { push @error, map {"$p $_"} @{$aconflicts{$p}} if $aconflicts{$p}; } return (undef, @error) if @error; # check against old cond dependencies. we do this step by step so we don't get dups. for my $p (@todo_inst) { $p{$p} = 1; if ($todo_cond{$p}) { for my $c (@{delete $todo_cond{$p}}) { my %naconflicts; check_conddeps_inst($c->[1], [ $c->[0] ], \@error, \%p, \%naconflicts, \@todo); push @{$aconflicts{$_}}, $naconflicts{$_} for keys %naconflicts; } } delete $aconflicts{$p}; # no longer needed } return undef, @error if @error; # now check our own dependencies for my $p (@todo_inst) { my %naconflicts; my %naobsoletes; $naconflicts{$_} = "is in conflict with $p" for @{$conflicts->{$p} || []}; for my $r (@{$requires->{$p} || []}) { if ($r =~ /^\(.*\)$/) { my $n = normalizerich($config, $p, $r, 0, \@error, $ignore, $xignore); check_conddeps_inst($p, $n, \@error, \%p, \%naconflicts, \@todo, \%todo_cond); next; } my $ri = (split(/[ <=>]/, $r, 2))[0]; next if $ignore->{"$p:$ri"} || $xignore->{"$p:$ri"}; next if $ignore->{$ri} || $xignore->{$ri}; next if $ri =~ /^rpmlib\("/; next if $ri =~ /^\// && !@{$whatprovides->{$ri} || []}; push @todo, ($r, $p); } if (!$ignoreconflicts) { for my $r (@{$pkgconflicts->{$p}}) { if ($r =~ /^\(.*\)$/) { my $n = normalizerich($config, $p, $r, 1, \@error); check_conddeps_inst($p, $n, \@error, \%p, \%naconflicts, \@todo, \%todo_cond); next; } $naconflicts{$_} = "is in conflict with $p" for @{$whatprovides->{$r} || Build::addproviders($config, $r)}; } for my $r (@{$pkgobsoletes->{$p}}) { $naobsoletes{$_} = "is obsoleted by $p" for nevrmatch($config, $r, @{$whatprovides->{$r} || Build::addproviders($config, $r)}); } } if (%naconflicts) { push @error, map {"$p conflicts with $_"} grep {$_ ne $p && $p{$_}} sort keys %naconflicts; push @{$aconflicts{$_}}, $naconflicts{$_} for keys %naconflicts; } if (%naobsoletes) { push @error, map {"$p obsoletes $_"} grep {$_ ne $p && $p{$_}} sort keys %naobsoletes; push @{$aconflicts{$_}}, $naobsoletes{$_} for keys %naobsoletes; } push @rec_todo, $p if $userecommendsforchoices; } return undef, @error if @error; @todo_inst = (); } for my $pass (0, 1, 2, 3, 4, 5) { my @todo_next; while (@todo) { my ($r, $p) = splice(@todo, 0, 2); my $rtodo = $r; my @q; if (ref($r)) { ($r, undef, undef, @q) = @$r; } else { @q = @{$whatprovides->{$r} || Build::addproviders($config, $r)}; } next if grep {$p{$_}} @q; my $pp = defined($p) ? "$p:" : ''; my $pn = defined($p) ? " needed by $p" : ''; if (defined($p) && !$ignoreignore) { next if grep {$ignore->{$_} || $xignore->{$_}} @q; next if grep {$ignore->{"$pp$_"} || $xignore->{"$pp$_"}} @q; } if (!@q) { next if defined($p) && ($r =~ /^\// || $r =~ /^rpmlib\(/); push @error, "nothing provides $r$pn"; next; } if (@q > 1 && $pass == 0) { push @todo_next, $rtodo, $p; next; } # pass 0: only one provider # pass 1: conflict pruning my $nq = @q; my @eq; for my $q (@q) { push @eq, map {"(provider $q $_)"} @{$aconflicts{$q}} if $aconflicts{$q}; } @q = grep {!$aconflicts{$_}} @q; if (!$ignoreconflicts) { for my $q (splice @q) { push @q, $q unless @{$pkgconflicts->{$q} || []} && checkconflicts($config, \%p, $q, \@eq, @{$pkgconflicts->{$q}}); } for my $q (splice @q) { push @q, $q unless @{$pkgobsoletes->{$q} || []} && checkobsoletes($config, \%p, $q, \@eq, @{$pkgobsoletes->{$q}}); } } if (!@q) { push @error, "conflict for providers of $r$pn", sort(@eq); next; } if (@q == 1) { next if $extractnative && extractnative($config, $r, $q[0], \@native); print "added $q[0] because of $pp$r\n" if $expand_dbg; push @todo_inst, $q[0]; next; } # pass 2: prune neg prefers and simple pos prefers if ($pass < 2) { print "undecided about $pp$r: @q\n" if $expand_dbg; push @todo_next, $rtodo, $p; next; } if (@q > 1) { my @pq = grep {!$prefer->{"-$_"} && !$prefer->{"-$pp$_"}} @q; @q = @pq if @pq; @pq = grep {$prefer->{$_} || $prefer->{"$pp$_"}} @q; @q = @pq if @pq == 1; } if (@q == 1) { next if $extractnative && extractnative($config, $r, $q[0], \@native); push @todo_inst, $q[0]; print "added $q[0] because of $pp$r\n" if $expand_dbg; next; } # pass 3: prune pos prefers and debian choice deps if ($pass < 3) { push @todo_next, $rtodo, $p; next; } if (@q > 1) { my @pq = grep {$prefer->{$_} || $prefer->{"$pp$_"}} @q; if (@pq > 1) { my %pq = map {$_ => 1} @pq; @q = (grep {$pq{$_}} @{$config->{'prefer'}})[0]; } elsif (@pq == 1) { @q = @pq; } } if (@q > 1 && $r =~ /\|/) { # choice op, implicit prefer of first match... my %pq = map {$_ => 1} @q; for my $rr (split(/\s*\|\s*/, $r)) { next unless $whatprovides->{$rr}; my @pq = grep {$pq{$_}} @{$whatprovides->{$rr}}; next unless @pq; @q = @pq; last; } } if (@q == 1) { next if $extractnative && extractnative($config, $r, $q[0], \@native); push @todo_inst, $q[0]; print "added $q[0] because of $pp$r\n" if $expand_dbg; next; } # pass 4: prune recommends if ($pass < 4) { push @todo_next, $rtodo, $p; next; } todo2recommended($config, \%recommended, \@rec_todo) if @rec_todo; my @pq = grep {$recommended{$_}} @q; print "recommended [@pq] among [@q]\n" if $expand_dbg; @q = @pq if @pq; if (@q == 1) { next if $extractnative && extractnative($config, $r, $q[0], \@native); push @todo_inst, $q[0]; print "added $q[0] because of $pp$r\n" if $expand_dbg; next; } # pass 5: record error if ($pass < 5) { push @todo_next, $rtodo, $p; next; } @q = sort(@q); if (defined($p)) { push @error, "have choice for $r needed by $p: @q"; } else { push @error, "have choice for $r: @q"; } } @todo = @todo_next; last if @todo_inst; } return undef, @error if @error; } if ($extractnative && @native) { my %rdone; for my $r (splice @native) { next if $rdone{$r}++; if ($r eq '--directdepsend--') { push @native, $r; next; } my @q = @{$whatprovides->{$r} || Build::addproviders($config, $r)}; push @native, $r unless grep {$p{$_}} @q; } pop @native if @native && $native[-1] eq '--directdepsend--'; push @$extractnative, @native; } return 1, (sort keys %p); } 1; 07070100000009000081a400000000000000000000000163e504b9000005eb000000000000000000000000000000000000001100000000Build/Fissile.pm################################################################ # # Copyright (c) 2017 SUSE Linux Products GmbH # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program (see the file COPYING); if not, write to the # Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA # ################################################################ package Build::Fissile; use strict; eval { require YAML::XS; }; *YAML::XS::LoadFile = sub {die("YAML::XS is not available\n")} unless defined &YAML::XS::LoadFile; sub parse { my ($cf, $fn) = @_; my $yml; eval { $yml = YAML::XS::LoadFile($fn); }; return {'error' => "Failed to parse yml file"} unless $yml; my $ret = {}; $ret->{'name'} = $yml->{'Name'} || 'fissile'; $ret->{'version'} = $yml->{'Version'} if $yml->{'Version'}; my @deps; for (@{$yml->{'DockerImageDeps'} || []}) { # This generates something like: "container:fissile-dev:201707081450" push @deps, "container:$_"; } $ret->{'deps'} = \@deps; return $ret; } 1; 0707010000000a000081a400000000000000000000000163e504b9000012d6000000000000000000000000000000000000001100000000Build/Flatpak.pm################################################################ # # Copyright (c) 2017 SUSE Linux Products GmbH # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program (see the file COPYING); if not, write to the # Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA # ################################################################ package Build::Flatpak; use strict; use warnings; use Build::Deb; use Build::Rpm; #use URI; # not installed in kvm? #use JSON::PP; # not installed in kvm? my $yamlxs = eval { require YAML::XS; $YAML::XS::LoadBlessed = 0; return 1 }; my $yamlpp = eval { require YAML::PP; return YAML::PP->new }; sub _have_yaml_parser { return $yamlpp || $yamlxs ? 1 : undef; } sub _load_yaml { my ($yaml) = @_; my $data; if ($yamlpp) { $data = eval { $yamlpp->load_string($yaml) }; return $data; } if ($yamlxs) { eval { $data = YAML::XS::Load($yaml) }; return $data; } die "Neither YAML::PP nor YAML::XS available\n"; } sub _load_yaml_file { my ($fn) = @_; my $data; if ($yamlpp) { $data = eval { $yamlpp->load_file($fn) }; return $data; } if ($yamlxs) { eval { $data = YAML::XS::LoadFile($fn) }; return $data; } die "Neither YAML::PP nor YAML::XS available\n"; } sub _read_manifest { my ($fn) = @_; my $data; if ($fn =~ m/\.ya?ml\z/) { $data = _load_yaml_file($fn); return { error => "Failed to parse YAML file '$fn'" } unless defined $data; } elsif ($fn =~ m/\.json\z/) { # We don't have JSON::PP, but YAML is a superset of JSON anyway $data = _load_yaml_file($fn); return { error => "Failed to parse JSON file '$fn'" } unless defined $data; } elsif (ref($fn) eq 'SCALAR') { $data = _load_yaml($$fn); # used in the unit test return { error => "Failed to parse '$fn'" } unless defined $data; } else { $data = _load_yaml_file($fn); return { error => "Failed to parse file '$fn'" } unless defined $data; } return $data; } sub parse { my ($cf, $fn) = @_; my $version = ''; my @lines; if (ref($fn) eq 'SCALAR') { @lines = split m/(?<=\n)/, $$fn; } else { open my $fh, '<', $fn or return { error => "Failed to open file '$fn'" }; @lines = <$fh>; close $fh; } for my $line (@lines) { if ($line =~ m/^#!BuildVersion: (\S+)/) { my $string = $1; if ($string =~ m/^[0-9.]+$/) { $version = $string; } else { return { error => "Invalid BuildVersion" }; } } } my $data = _read_manifest($fn); my $ret = {}; $ret->{version} = $version if $version; $ret->{name} = $data->{'app-id'} or die "Flatpak file is missing 'app-id'"; my $runtime = $data->{runtime}; my $runtime_version = $data->{'runtime-version'}; my $sdk = $data->{sdk}; my @packdeps; push @packdeps, "$sdk-v$runtime_version"; push @packdeps, "$runtime-v$runtime_version"; $ret->{deps} = \@packdeps; my @sources; if (my $modules = $data->{modules}) { for my $module (@$modules) { if (my $sources = $module->{sources}) { for my $source (@$sources) { if ($source->{type} eq 'archive') { push @sources, $source->{url}; } } } } } $ret->{sources} = \@sources; return $ret; } sub show { my ($fn, $field) = @ARGV; my $cf = {}; my $d = parse($cf, $fn); die "$d->{error}\n" if $d->{error}; my $value = $d->{ $field }; if (ref $value eq 'ARRAY') { print "$_\n" for @$value; } else { print "$value\n"; } } # This replaces http urls with local file urls because during build # flatpak-builder has no network sub rewrite { my ($fn) = @ARGV; my $data = _read_manifest($fn); if (my $modules = $data->{modules}) { for my $module (@$modules) { if (my $sources = $module->{sources}) { for my $source (@$sources) { if ($source->{type} eq 'archive') { my $path = $source->{url}; $path =~ s{.*/}{}; # Get filename $source->{url} = "file:///usr/src/packages/SOURCES/$path"; } } } } } my $yaml = ''; if ($yamlpp) { # YAML::PP would allow us to keep key order $yaml = $yamlpp->dump_string($data); } elsif ($yamlxs) { $yaml = YAML::XS::Dump($data); } print $yaml; } 1; 0707010000000b000081a400000000000000000000000163e504b900001283000000000000000000000000000000000000000e00000000Build/Helm.pm################################################################ # # Copyright (c) 2020 SUSE LLC # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program (see the file COPYING); if not, write to the # Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA # ################################################################ package Build::Helm; use strict; use Build::SimpleJSON; eval { require YAML::XS; $YAML::XS::LoadBlessed = 0; }; *YAML::XS::LoadFile = sub {die("YAML::XS is not available\n")} unless defined &YAML::XS::LoadFile; sub verify_config { my ($d) = @_; die("bad config\n") unless ref($d) eq 'HASH'; for my $k ('name', 'version') { die("missing element '$k'\n") unless defined $d->{$k}; die("bad element '$k'\n") unless ref($d->{$k}) eq ''; die("empty element '$k'\n") if $d->{$k} eq ''; die("bad element '$k'\n\n") if $d->{$k} =~ /[\/\000-\037]/; } die("bad name\n") if $d->{'name'} =~ /^[-\.]/; } sub parse { my ($cf, $fn) = @_; my $d; my $fd; return {'error' => "$fn: $!"} unless open($fd, '<', $fn); my @tags; while (<$fd>) { chomp; next if /^\s*$/; last unless /^\s*#/; push @tags, split(' ', $1) if /^#!BuildTag:\s*(.*?)$/; } close($fd); eval { $d = YAML::XS::LoadFile($fn); verify_config($d); }; if ($@) { my $err = $@; chomp $@; return {'error' => "Failed to parse yml file: $err"}; } my $res = {}; $res->{'name'} = $d->{'name'}; $res->{'version'} = $d->{'version'}; $res->{'deps'} = []; my $release = $cf->{'buildrelease'}; for (@tags) { s/<NAME>/$d->{'name'}/g; s/<VERSION>/$d->{'version'}/g; s/<RELEASE>/$release/g; } $res->{'containertags'} = \@tags if @tags; return $res; } sub show { my ($release, $disturl, $chart, $origrecipe); while (@ARGV) { if (@ARGV > 2 && $ARGV[0] eq '--release') { (undef, $release) = splice(@ARGV, 0, 2); } elsif (@ARGV > 2 && $ARGV[0] eq '--disturl') { (undef, $disturl) = splice(@ARGV, 0, 2); } elsif (@ARGV > 2 && $ARGV[0] eq '--chart') { (undef, $chart) = splice(@ARGV, 0, 2); } elsif (@ARGV > 2 && $ARGV[0] eq '--origrecipe') { (undef, $origrecipe) = splice(@ARGV, 0, 2); } else { last; } } my ($fn, $field) = @ARGV; my $d = {}; $d->{'buildrelease'} = $release if defined $release; $d = parse({}, $fn) if $fn; die("$d->{'error'}\n") if $d->{'error'}; if ($field eq 'helminfo') { my $containertags = $d->{'containertags'}; if ($origrecipe) { # we need to parse the original recipe to get the tags my $origd = parse({}, $origrecipe); $containertags = $origd->{'containertags'}; } my $config_yaml = ''; my $fd; die("$fn: $!\n") unless open($fd, '<', $fn); 1 while sysread($fd, $config_yaml, 8192, length($config_yaml)); close($fd); my $config = YAML::XS::Load($config_yaml); verify_config($config); my $config_json = Build::SimpleJSON::unparse($config)."\n"; my $helminfo = {}; $helminfo->{'name'} = $d->{'name'}; $helminfo->{'version'} = $d->{'version'}; $helminfo->{'release'} = $release if $release; $helminfo->{'tags'} = $containertags if $containertags; $helminfo->{'disturl'} = $disturl if $disturl; $helminfo->{'buildtime'} = time(); if ($chart) { require Digest::SHA; $helminfo->{'chart'} = $chart; $helminfo->{'chart'} =~ s/.*\///; my $ctx = Digest::SHA->new(256); my $cfd; die("$chart: $!\n") unless open($cfd, '<', $chart); my @s = stat($cfd); $ctx->addfile($cfd); close($cfd); $helminfo->{'chart_sha256'} = $ctx->hexdigest; $helminfo->{'chart_size'} = $s[7]; } $helminfo->{'config_json'} = $config_json; $helminfo->{'config_yaml'} = $config_yaml; $helminfo->{'_order'} = [ qw{name version release tags disturl buildtime chart config_json config_yaml chart_sha256 chart_size} ]; $helminfo->{'_type'} = {'buildtime' => 'number', 'chart_size' => 'number' }; print Build::SimpleJSON::unparse($helminfo)."\n"; exit(0); } $d->{'nameversion'} = "$d->{'name'}-$d->{'version'}"; # convenience my $x = $d->{$field}; $x = [ $x ] unless ref $x; print "@$x\n"; } 1; 0707010000000c000081a400000000000000000000000163e504b9000016b0000000000000000000000000000000000000001100000000Build/Intrepo.pm################################################################ # # Copyright (c) 2020 SUSE LLC # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program (see the file COPYING); if not, write to the # Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA # ################################################################ package Build::Intrepo; use strict; use Build::Rpm; # This implements reading and writing of build's internal repo format. # # The format looks like this: # # P:bash.x86_64-1526160002/1526160023/0: bash = 4.4-lp150.7.8 # # i.e.: type:name.arch-mtime/size/inode: dep dep dep # # P:provides C:conflicts R:requires O:Obsoletes # r:recommends s:supplements # # There is also: # F:...: filename # I:...: ident where ident == name-evr buildtime-arch # # The format is so weird because init_buildsystem of the old autobuild # system used to create such repos by doing a single 'rpm --qf' call. # sub addpkg { my ($res, $pkg, $pkgid, $options) = @_; return unless $pkgid =~ /^(.*)\.(.*)-\d+\/\d+\/\d+$/s; $pkg->{'name'} = $1; $pkg->{'arch'} = $2 unless $pkg->{'arch'}; # extract evr from self provides if there was no 'I' line if (!defined($pkg->{'version'})) { my @sp = grep {/^\Q$pkg->{'name'}\E\s*=\s*/} @{$pkg->{'provides'} || []}; if (@sp) { my $evr = $sp[-1]; $evr =~ s/^\Q$pkg->{'name'}\E\s*=\s*//; $pkg->{'epoch'} = $1 if $evr =~ s/^(\d+)://; $pkg->{'release'} = $1 if $evr =~ s/-([^-]*)$//; $pkg->{'version'} = $evr; } } if (ref($res) eq 'CODE') { $res->($pkg); } else { push @$res, $pkg; } } sub parse { my ($in, $res, %options) = @_; my $nofiledeps = $options{'nofiledeps'}; my $testcaseformat = $options{'testcaseformat'}; if (ref($in)) { *F = $in; } else { open(F, '<', $in) || die("$in: $!\n"); } $res ||= []; my $lastpkgid; my $pkg = {}; while (<F>) { my @s = split(' ', $_); my $s = shift @s; next unless $s && $s =~ /^([a-zA-Z]):(.+):$/s; my ($tag, $pkgid) = ($1, $2); if ($lastpkgid && $pkgid ne $lastpkgid) { addpkg($res, $pkg, $lastpkgid, \%options) if %$pkg; $pkg = {}; } $lastpkgid = $pkgid; if ($tag eq 'I') { next unless $pkgid =~ /^(.*)\.(.*)-\d+\/\d+\/\d+:$/; my $name = $1; my $evr = $s[0]; $pkg->{'arch'} = $1 if $s[1] && $s[1] =~ s/-(.*)$//; $pkg->{'buildtime'} = $s[1] if $s[1]; if ($evr =~ s/^\Q$name\E-//) { $pkg->{'epoch'} = $1 if $evr =~ s/^(\d+)://; $pkg->{'release'} = $1 if $evr =~ s/-([^-]*)$//; $pkg->{'version'} = $evr; } next; } if ($tag eq 'F') { chomp; my $loc = (split(' ', $_, 2))[1]; $pkg->{'location'} = $loc if defined $loc; next; } if ($tag eq 'M') { chomp; my $multiarch = (split(' ', $_, 2))[1]; $pkg->{'multiarch'} = $multiarch if $multiarch; next; } my @ss; while (@s) { if ($nofiledeps && $s[0] =~ /^\//) { shift @s; next; } if ($s[0] =~ /^rpmlib\(/) { splice(@s, 0, 3); next; } if ($s[0] =~ /^\(/) { push @ss, Build::Rpm::shiftrich(\@s); $ss[-1] = Build::Rpm::testcaseformat($ss[-1]) if $testcaseformat; next; } push @ss, shift @s; while (@s && $s[0] =~ /^\(?[<=>|]/) { $ss[-1] .= " $s[0] $s[1]"; $ss[-1] =~ s/ \((.*)\)/ $1/; $ss[-1] =~ s/(<|>){2}/$1/; splice(@s, 0, 2); } } my %ss; @ss = grep {!$ss{$_}++} @ss; # unify $pkg->{'provides'} = \@ss if $tag eq 'P'; $pkg->{'requires'} = \@ss if $tag eq 'R'; $pkg->{'conflicts'} = \@ss if $tag eq 'C'; $pkg->{'obsoletes'} = \@ss if $tag eq 'O'; $pkg->{'recommends'} = \@ss if $tag eq 'r'; $pkg->{'supplements'} = \@ss if $tag eq 's'; } addpkg($res, $pkg, $lastpkgid, \%options) if $lastpkgid && %$pkg; close F unless ref($in); return $res; } sub getbuildid { my ($q) = @_; my $evr = $q->{'version'}; $evr = "$q->{'epoch'}:$evr" if $q->{'epoch'}; $evr .= "-$q->{'release'}" if defined $q->{'release'}; my $buildtime = $q->{'buildtime'} || 0; $buildtime .= "-$q->{'arch'}" if defined $q->{'arch'}; return "$q->{'name'}-$evr $buildtime"; } my $writepkg_inode = 0; sub writepkg { my ($fh, $pkg, $locprefix, $inode) = @_; return unless defined($pkg->{'name'}) && defined($pkg->{'arch'}); return if $pkg->{'arch'} eq 'src' || $pkg->{'arch'} eq 'nosrc'; $locprefix = '' unless defined $locprefix; my $id = $pkg->{'id'}; if (!$id) { $inode = $writepkg_inode++ unless defined $inode; $id = ($pkg->{'buildtime'} || 0)."/".($pkg->{'filetime'} || 0)."/$inode"; } $id = "$pkg->{'name'}.$pkg->{'arch'}-$id: "; print $fh "F:$id$locprefix$pkg->{'location'}\n"; print $fh "P:$id".join(' ', @{$pkg->{'provides'} || []})."\n"; print $fh "R:$id".join(' ', @{$pkg->{'requires'}})."\n" if $pkg->{'requires'}; print $fh "C:$id".join(' ', @{$pkg->{'conflicts'}})."\n" if $pkg->{'conflicts'}; print $fh "O:$id".join(' ', @{$pkg->{'obsoletes'}})."\n" if $pkg->{'obsoletes'}; print $fh "r:$id".join(' ', @{$pkg->{'recommends'}})."\n" if $pkg->{'recommends'}; print $fh "s:$id".join(' ', @{$pkg->{'supplements'}})."\n" if $pkg->{'supplements'}; print $fh "M:$id$pkg->{'multiarch'}\n" if $pkg->{'multiarch'}; print $fh "I:$id".getbuildid($pkg)."\n"; } 1; 0707010000000d000081a400000000000000000000000163e504b90000613c000000000000000000000000000000000000000e00000000Build/Kiwi.pm################################################################ # # Copyright (c) 1995-2014 SUSE Linux Products GmbH # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program (see the file COPYING); if not, write to the # Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA # ################################################################ package Build::Kiwi; use strict; use Build::SimpleXML; use Build::SimpleJSON; our $bootcallback; our $urlmapper; our $repoextras = 0; # priority, flags, ... sub unify { my %h = map {$_ => 1} @_; return grep(delete($h{$_}), @_); } sub expandFallBackArchs { my ($fallbackArchXML, @archs) = @_; my %fallbacks; for (@{$fallbackArchXML->{'arch'} || []}) { $fallbacks{$_->{'id'}} = $_->{'fallback'} if $_->{id} && $_->{'fallback'}; } my @out; while (@archs) { push @out, shift @archs; push @archs, delete $fallbacks{$out[-1]} if $fallbacks{$out[-1]}; } return unify(@out); } # sles10 perl does not have the version.pm # implement own hack sub versionstring { my ($str) = @_; my $result = 0; $result = $result * 100 + $_ for split (/\./, $str, 2); return $result; } my $schemaversion56 = versionstring('5.6'); sub kiwiparse_product { my ($kiwi, $xml, $arch, $buildflavor) = @_; my $ret = {}; my @repos; my %repoprio; # XXX: unused my @packages; my @requiredarch; my @badarch; my $obsexclusivearch; my $obsexcludearch; $obsexclusivearch = $1 if $xml =~ /^\s*<!--\s+OBS-ExclusiveArch:\s+(.*)\s+-->\s*$/im; $obsexcludearch = $1 if $xml =~ /^\s*<!--\s+OBS-ExcludeArch:\s+(.*)\s+-->\s*$/im; $ret->{'milestone'} = $1 if $xml =~ /^\s*<!--\s+OBS-Milestone:\s+(.*)\s+-->\s*$/im; $ret->{'name'} = $kiwi->{'name'} if $kiwi->{'name'}; $ret->{'filename'} = $kiwi->{'name'} if $kiwi->{'name'}; my $description = (($kiwi->{'description'} || [])->[0]) || {}; if (!$ret->{'name'} && $description->{'specification'}) { $ret->{'name'} = $description->{'specification'}->[0]->{'_content'}; } # parse the preferences section my $preferences = $kiwi->{'preferences'} || []; die("products must have exactly one preferences element\n") unless @$preferences == 1; # take default version setting if ($preferences->[0]->{'version'}) { $ret->{'version'} = $preferences->[0]->{'version'}->[0]->{'_content'}; } die("products must have exactly one type element in the preferences\n") unless @{$preferences->[0]->{'type'} || []} == 1; my $preftype = $preferences->[0]->{'type'}->[0]; if (defined $preftype->{'image'}) { # for kiwi 4.1 and 5.x die("products must use type 'product'\n") unless $preftype->{'image'} eq 'product'; } else { # for kiwi 3.8 and before die("products must use type 'product'\n") unless $preftype->{'_content'} eq 'product'; } push @packages, "kiwi-filesystem:$preftype->{'filesystem'}" if $preftype->{'filesystem'}; die("boot type not supported in products\n") if defined $preftype->{'boot'}; my $instsource = ($kiwi->{'instsource'} || [])->[0]; die("products must have an instsource element\n") unless $instsource; # get repositories for my $repository (sort {$a->{priority} <=> $b->{priority}} @{$instsource->{'instrepo'} || []}) { my $kiwisource = ($repository->{'source'} || [])->[0]; if ($kiwisource->{'path'} eq 'obsrepositories:/') { push @repos, '_obsrepositories/'; # special case, OBS will expand it. } elsif ($kiwisource->{'path'} =~ /^obs:\/\/\/?([^\/]+)\/([^\/]+)\/?$/) { push @repos, "$1/$2"; } else { my $prp; $prp = $urlmapper->($kiwisource->{'path'}) if $urlmapper; die("instsource repo url not using obs:/ scheme: $kiwisource->{'path'}\n") unless $prp; push @repos, $prp; } } $ret->{'sourcemedium'} = -1; $ret->{'debugmedium'} = -1; if ($instsource->{'productoptions'}) { my $productoptions = $instsource->{'productoptions'}->[0] || {}; for my $po (@{$productoptions->{'productvar'} || []}) { $ret->{'drop_repository'} = $po->{'_content'} if $po->{'name'} eq 'DROP_REPOSITORY'; $ret->{'version'} = $po->{'_content'} if $po->{'name'} eq 'VERSION'; } for my $po (@{$productoptions->{'productoption'} || []}) { $ret->{'sourcemedium'} = $po->{'_content'} if $po->{'name'} eq 'SOURCEMEDIUM'; $ret->{'debugmedium'} = $po->{'_content'} if $po->{'name'} eq 'DEBUGMEDIUM'; $ret->{'milestone'} = $po->{'_content'} if $po->{'name'} eq 'BETA_VERSION'; } } if ($instsource->{'architectures'}) { my $architectures = $instsource->{'architectures'}->[0] || {}; for my $ra (@{$architectures->{'requiredarch'} || []}) { push @requiredarch, $ra->{'ref'} if defined $ra->{'ref'}; } } # Find packages and possible additional required architectures my @additionalarchs; my @pkgs; push @pkgs, @{$instsource->{'metadata'}->[0]->{'repopackage'} || []} if $instsource->{'metadata'}; push @pkgs, @{$instsource->{'repopackages'}->[0]->{'repopackage'} || []} if $instsource->{'repopackages'}; @pkgs = unify(@pkgs); for my $package (@pkgs) { # filter packages, which are not targeted for the wanted plattform if ($package->{'arch'}) { my $valid; for my $ma (@requiredarch) { for my $pa (split(',', $package->{'arch'})) { $valid = 1 if $ma eq $pa; } } next unless $valid; } # not nice, but optimizes our build dependencies # FIXME: design a real blacklist option in kiwi if ($package->{'onlyarch'} && $package->{'onlyarch'} eq 'skipit') { push @packages, "-$package->{'name'}"; next; } push @packages, "-$package->{'replaces'}" if $package->{'replaces'}; # we need this package push @packages, $package->{'name'}; # find the maximal superset of possible required architectures push @additionalarchs, split(',', $package->{'addarch'}) if $package->{'addarch'}; push @additionalarchs, split(',', $package->{'onlyarch'}) if $package->{'onlyarch'}; } @requiredarch = unify(@requiredarch, @additionalarchs); #### FIXME: kiwi files have no informations where to get -32bit packages from push @requiredarch, "i586" if grep {/^ia64/} @requiredarch; push @requiredarch, "i586" if grep {/^x86_64/} @requiredarch; push @requiredarch, "ppc" if grep {/^ppc64/} @requiredarch; push @requiredarch, "s390" if grep {/^s390x/} @requiredarch; @requiredarch = expandFallBackArchs($instsource->{'architectures'}->[0], @requiredarch); push @packages, "kiwi-packagemanager:instsource"; push @requiredarch, split(' ', $obsexclusivearch) if $obsexclusivearch; push @badarch , split(' ', $obsexcludearch) if $obsexcludearch; $ret->{'exclarch'} = [ unify(@requiredarch) ] if @requiredarch; $ret->{'badarch'} = [ unify(@badarch) ] if @badarch; $ret->{'deps'} = [ unify(@packages) ]; $ret->{'path'} = [ unify(@repos) ]; $ret->{'imagetype'} = [ 'product' ]; for (@{$ret->{'path'} || []}) { my @s = split('/', $_, 2); $_ = {'project' => $s[0], 'repository' => $s[1]}; $_->{'priority'} = $repoprio{"$s[0]/$s[1]"} if $repoextras && defined $repoprio{"$s[0]/$s[1]"}; } return $ret; } sub unify_repo { my ($repo) = @_; my @r; my %seen; for (@{$repo || []}) { if ($_->{'url'}) { next if $seen{$_->{'url'}}; $seen{$_->{'url'}} = 1; } push @r, $_; } return \@r; } sub kiwiparse { my ($xml, $arch, $buildflavor, $release, $count) = @_; $count ||= 0; die("kiwi config inclusion depth limit reached\n") if $count++ > 10; my $kiwi = Build::SimpleXML::parse($xml); die("not a kiwi config\n") unless $kiwi && $kiwi->{'image'}; $kiwi = $kiwi->{'image'}->[0]; # check if this is a product, we currently test for the 'instsource' element return kiwiparse_product($kiwi, $xml, $arch, $buildflavor) if $kiwi->{'instsource'}; my $ret = {}; my @types; my @repos; my @imagerepos; my @bootrepos; my @containerrepos; my @packages; my @extrasources; my $obsexclusivearch; my $obsexcludearch; my $obsprofiles; my $unorderedrepos; my @ignorepackages; $obsexclusivearch = $1 if $xml =~ /^\s*<!--\s+OBS-ExclusiveArch:\s+(.*)\s+-->\s*$/im; $obsexcludearch = $1 if $xml =~ /^\s*<!--\s+OBS-ExcludeArch:\s+(.*)\s+-->\s*$/im; $obsprofiles = $1 if $xml =~ /^\s*<!--\s+OBS-Profiles:\s+(.*)\s+-->\s*$/im; $ret->{'milestone'} = $1 if $xml =~ /^\s*<!--\s+OBS-Milestone:\s+(.*)\s+-->\s*$/im; if ($obsprofiles) { $obsprofiles = [ grep {defined($_)} map {$_ eq '@BUILD_FLAVOR@' ? $buildflavor : $_} split(' ', $obsprofiles) ]; } $unorderedrepos = 1 if $xml =~ /^\s*<!--\s+OBS-UnorderedRepos\s+-->\s*$/im; $ret->{'donotappendprofiletocontainername'} = 1 if $xml =~ /^\s*<!--\s+OBS-DoNotAppendProfileToContainername\s+-->\s*$/im; for ($xml =~ /^\s*<!--\s+OBS-Imagerepo:\s+(.*)\s+-->\s*$/img) { push @imagerepos, { 'url' => $_ }; } for ($xml =~ /^\s*<!--\s+OBS-IgnorePackage:\s+(.*)\s+-->\s*$/img) { push @ignorepackages, split(' ', $_); } for ($xml =~ /^\s*<!--\s+OBS-RemoteAsset:\s+(.*)\s+-->\s*$/img) { my @s = split(' ', $_); my $remoteasset = { 'url' => $s[0] }; $remoteasset->{'digest'} = $s[1] if $s[1] && $s[1] =~ /^[a-z0-9]+:[0-9a-f]+$/; push @{$ret->{'remoteassets'}}, $remoteasset; } for ($xml =~ /^\s*<!--\s+OBS-CopyToImage:\s+(.*)\s+-->\s*$/img) { my @s = split(' ', $_); next if !$s[0] || $s[0] eq '.' || $s[0] eq '..' || $s[0] =~ /\// || $s[0] eq 'root'; push @s, $s[0] unless @s > 1; push @{$ret->{'copytoimage'}}, "$s[0] $s[1]"; } my $schemaversion = $kiwi->{'schemaversion'} ? versionstring($kiwi->{'schemaversion'}) : 0; $ret->{'name'} = $kiwi->{'name'} if $kiwi->{'name'}; $ret->{'filename'} = $kiwi->{'name'} if $kiwi->{'name'}; my $description = (($kiwi->{'description'} || [])->[0]) || {}; if (!$ret->{'name'} && $description->{'specification'}) { $ret->{'name'} = $description->{'specification'}->[0]->{'_content'}; } # usedprofiles also include direct wanted profile targets and indirect required profiles my %usedprofiles; # obsprofiles arch filtering if ($obsprofiles && $arch && $kiwi->{'profiles'} && $kiwi->{'profiles'}->[0]->{'profile'}) { # reduce set of profiles to the ones matching our architecture my @validprofiles; for my $prof (@{$kiwi->{'profiles'}[0]->{'profile'}}) { next unless $prof->{'name'}; if (!$prof->{'arch'}) { push @validprofiles, $prof; } else { my $ma = $arch; $ma =~ s/i[456]86/i386/; for my $pa (split(",", $prof->{'arch'})) { $pa =~ s/i[456]86/i386/; next unless $ma eq $pa; push @validprofiles, $prof; last; } } } my %validprofiles = map {$_->{'name'} => 1} @validprofiles; $obsprofiles = [ grep {$validprofiles{$_}} @$obsprofiles ]; my %obsprofiles = map {$_ => 1} @$obsprofiles; my @todo = grep {$obsprofiles{$_->{'name'}}} @validprofiles; while (@todo) { my $prof = shift @todo; next if $usedprofiles{$prof->{'name'}}; # already done $usedprofiles{$prof->{'name'}} = 1; for my $req (@{$prof->{'requires'} || []}) { push @todo, grep {$_->{'name'} eq $req->{'profile'}} @validprofiles; } } } # take default version setting my $preferences = ($kiwi->{'preferences'} || []); if ($preferences->[0]->{'version'}) { $ret->{'version'} = $preferences->[0]->{'version'}->[0]->{'_content'}; } # add extra tags my @extratags; if ($xml =~ /^\s*<!--\s+OBS-AddTag:\s+(.*)\s+-->\s*$/im) { for (split(' ', $1)) { s/<VERSION>/$ret->{'version'}/g if $ret->{'version'}; s/<RELEASE>/$release/g if $release; $_ = "$_:latest" unless /:[^\/]+$/; push @extratags, $_; } } my $containerconfig; for my $pref (@{$preferences || []}) { if ($obsprofiles && $pref->{'profiles'}) { next unless grep {$usedprofiles{$_}} split(",", $pref->{'profiles'}); } for my $type (@{$pref->{'type'} || []}) { next unless @{$pref->{'type'}} == 1 || !$type->{'optional'}; if (defined $type->{'image'}) { # for kiwi 4.1 and 5.x push @types, $type->{'image'}; push @packages, "kiwi-image:$type->{'image'}" if $schemaversion >= $schemaversion56; } else { # for kiwi 3.8 and before push @types, $type->{'_content'}; } # save containerconfig so that we can retrieve the tag $containerconfig = $type->{'containerconfig'}->[0] if $type->{'containerconfig'}; # add derived container dependency if ($type->{'derived_from'}) { my $derived = $type->{'derived_from'}; my ($name, $prp); if ($derived =~ /^obs:\/{1,3}([^\/]+)\/([^\/]+)\/(.*?)(?:#([^\#\/]+))?$/) { $name = defined($4) ? "$3:$4" : "$3:latest"; $prp = "$1/$2"; $prp = "obs:/$prp" if defined($urlmapper) && !$urlmapper; } elsif ($derived =~ /^obsrepositories:\/{1,3}([^\/].*?)(?:#([^\#\/]+))?$/) { $name = defined($2) ? "$1:$2" : "$1:latest"; } elsif ($derived =~ /^file:/) { next; # just ignore and hope } elsif (defined($urlmapper) && !$urlmapper) { if ($derived =~ /^https:\/\/([^\/]+)\/(.*?)(?:#([^\#\/]+))?$/) { $prp = $1; $name = defined($3) ? "$2:$3" : "$2:latest"; } elsif ($derived =~ /([^\/]+\.[^\/]+)\/(.*?)(?:#([^\#\/]+))?$/) { $prp = "https://$1"; $name = defined($3) ? "$2:$3" : "$2:latest"; } elsif ($derived =~ /^(.*?)(?:#([^\#\/]+))?$/) { $name = defined($2) ? "$1:$2" : "$1:latest"; } else { die("cannot decode derived_from: $derived\n"); } } elsif ($derived =~ /^(.*)\/([^\/]+?)(?:#([^\#\/]+))?$/) { my $url = $1; $name = defined($3) ? "$2:$3" : "$2:latest"; $prp = $urlmapper->($url) if $urlmapper; # try again with one element moved from url to name if (!$prp && $derived =~ /^(.*)\/([^\/]+\/[^\/]+?)(?:#([^\#\/]+))?$/) { $url = $1; $name = defined($3) ? "$2:$3" : "$2:latest"; $prp = $urlmapper->($url) if $urlmapper; } undef $name unless $prp; } die("derived_from url not using obs:/ scheme: $derived\n") unless defined $name; push @packages, "container:$name"; push @containerrepos, $prp if $prp; } push @packages, "kiwi-filesystem:$type->{'filesystem'}" if $type->{'filesystem'}; if (defined $type->{'boot'}) { if ($type->{'boot'} =~ /^obs:\/\/\/?([^\/]+)\/([^\/]+)\/?$/) { next unless $bootcallback; my ($bootxml, $xsrc) = $bootcallback->($1, $2); next unless $bootxml; push @extrasources, $xsrc if $xsrc; my $bret = kiwiparse($bootxml, $arch, $buildflavor, $release, $count); if (defined($urlmapper) && !$urlmapper) { push @bootrepos, @{$bret->{'path'} || []}; } else { push @bootrepos, map {"$_->{'project'}/$_->{'repository'}"} @{$bret->{'path'} || []}; } push @packages, @{$bret->{'deps'} || []}; push @extrasources, @{$bret->{'extrasource'} || []}; } else { die("bad boot reference: $type->{'boot'}\n") unless $type->{'boot'} =~ /^([^\/]+)\/([^\/]+)$/; push @packages, "kiwi-boot:$1"; } } } } die("image contains 'product' type\n") if grep {$_ eq 'product'} @types; my $packman = $preferences->[0]->{'packagemanager'}->[0]->{'_content'} || ''; # calculate priority for sorting for (@{$kiwi->{'repository'} || []}) { $_->{'sortprio'} = 0; if (defined($_->{'priority'})) { $_->{'sortprio'} = $packman eq 'smart' ? $_->{'priority'} : 99 - $_->{'priority'}; } } my @repositories = sort {$b->{'sortprio'} <=> $a->{'sortprio'}} @{$kiwi->{'repository'} || []}; my %repoprio; for my $repository (@repositories) { my $kiwisource = ($repository->{'source'} || [])->[0]; next unless $kiwisource; # huh? my $url = $kiwisource->{'path'}; next if !$url || $url eq '/var/lib/empty'; # grr if ($repository->{'imageonly'} || $repository->{'imageinclude'}) { # this repo will be configured in the image. Save so that we can write it into the containerinfo push @imagerepos, { 'url' => $url }; $imagerepos[-1]->{'priority'} = $repository->{'sortprio'} if defined $repository->{'priority'}; } next if $repository->{'imageonly'}; if (defined($urlmapper) && !$urlmapper) { # urlmapping disabled push @repos, { 'url' => $url }; $repos[-1]->{'priority'} = $repository->{'sortprio'} if $repoextras && defined $repository->{'priority'}; next; } my $prp; if ($url eq 'obsrepositories:/') { $prp = '_obsrepositories/'; } elsif ($url =~ /^obs:\/{1,3}([^\/]+)\/([^\/]+)\/?$/) { $prp = "$1/$2"; } else { $prp = $urlmapper->($url) if $urlmapper; die("repo url not using obs:/ scheme: $kiwisource->{'path'}\n") unless $prp; } push @repos, $prp; $repoprio{$prp} = $repository->{'sortprio'} if defined $repository->{'priority'}; } # Find packages for the image my @pkgs; my $patterntype; for my $packages (@{$kiwi->{'packages'}}) { next if $packages->{'type'} && $packages->{'type'} ne 'image' && $packages->{'type'} ne 'bootstrap'; # we could skip the sections also when no profile is used, # but don't to stay backward compatible if ($obsprofiles && $packages->{'profiles'}) { my @section_profiles = split(",", $packages->{'profiles'}); next unless grep {$usedprofiles{$_}} @section_profiles; } $patterntype ||= $packages->{'patternType'}; push @pkgs, @{$packages->{'package'}} if $packages->{'package'}; for my $pattern (@{$packages->{'namedCollection'} || []}) { push @pkgs, { %$pattern, 'name' => "pattern() = $pattern->{'name'}" } if $pattern->{'name'}; } for my $product (@{$packages->{'product'} || []}) { push @pkgs, { %$product, 'name' => "product() = $product->{'name'}" } if $product->{'name'}; } for my $pattern (@{$packages->{'opensusePatterns'} || []}) { push @pkgs, { %$pattern, 'name' => "pattern() = $pattern->{'name'}" } if $pattern->{'name'}; } for my $product (@{$packages->{'opensuseProduct'} || []}) { push @pkgs, { %$product, 'name' => "product() = $product->{'name'}" } if $product->{'name'}; } } $patterntype ||= 'onlyRequired'; @pkgs = unify(@pkgs); for my $package (@pkgs) { # filter packages which are not targeted for the wanted plattform if ($package->{'arch'}) { my $valid; my $ma = $arch; $ma =~ s/i[456]86/i386/; for my $pa (split(",", $package->{'arch'})) { $pa =~ s/i[456]86/i386/; $valid = 1 if $ma eq $pa; } next unless $valid; } # handle replaces as buildignore push @packages, "-$package->{'replaces'}" if $package->{'replaces'}; # we need this package push @packages, $package->{'name'}; } push @packages, map {"-$_"} @ignorepackages; push @packages, "kiwi-packagemanager:$packman" if $packman; push @packages, "--dorecommends--", "--dosupplements--" if $patterntype && $patterntype eq 'plusRecommended'; push @packages, '--unorderedimagerepos', if $unorderedrepos; $ret->{'exclarch'} = [ unify(split(' ', $obsexclusivearch)) ] if $obsexclusivearch; $ret->{'badarch'} = [ unify(split(' ', $obsexcludearch)) ] if $obsexcludearch; $ret->{'deps'} = [ unify(@packages) ]; $ret->{'path'} = [ unify(@repos, @bootrepos) ]; $ret->{'containerpath'} = [ unify(@containerrepos) ] if @containerrepos; $ret->{'imagetype'} = [ unify(@types) ]; $ret->{'extrasource'} = \@extrasources if @extrasources; if (defined($urlmapper) && !$urlmapper) { $ret->{'path'} = [ unify_repo($ret->{'path'}) ] if @{$ret->{'path'} || []}; $ret->{'containerpath'} = [ unify_repo($ret->{'containerpath'}) ] if @{$ret->{'containerpath'} || []}; } else { for (@{$ret->{'path'} || []}) { my @s = split('/', $_, 2); $_ = {'project' => $s[0], 'repository' => $s[1]}; $_->{'priority'} = $repoprio{"$s[0]/$s[1]"} if $repoextras && defined $repoprio{"$s[0]/$s[1]"}; } for (@{$ret->{'containerpath'} || []}) { my @s = split('/', $_, 2); $_ = {'project' => $s[0], 'repository' => $s[1]}; } } $ret->{'imagerepos'} = \@imagerepos if @imagerepos; if ($containerconfig) { my $containername = $containerconfig->{'name'}; my @containertags; if (defined $containername) { push @containertags, $containerconfig->{'tag'} if defined $containerconfig->{'tag'}; push @containertags, 'latest' unless @containertags; if (defined($containerconfig->{'additionaltags'})) { push @containertags, split(',', $containerconfig->{'additionaltags'}); } @containertags = map {"$containername:$_"} @containertags; } push @containertags, @extratags if @extratags; $ret->{'container_tags'} = [ unify(@containertags) ] if @containertags; } if ($obsprofiles) { if (@$obsprofiles) { $ret->{'profiles'} = [ unify(@$obsprofiles) ]; } else { $ret->{'exclarch'} = []; # all profiles excluded } } return $ret; } sub parse { my ($cf, $fn) = @_; local *F; open(F, '<', $fn) || die("$fn: $!\n"); my $xml = ''; 1 while sysread(F, $xml, 4096, length($xml)) > 0; close F; $cf ||= {}; my $d; eval { $d = kiwiparse($xml, ($cf->{'arch'} || ''), $cf->{'buildflavor'}, $cf->{'buildrelease'}, 0) }; if ($@) { my $err = $@; chomp $err; return {'error' => $err}; } return $d; } sub show { my ($fn, $field, $arch, $buildflavor) = @ARGV; local $urlmapper = sub { return $_[0] }; my $cf = {'arch' => $arch}; $cf->{'buildflavor'} = $buildflavor if defined $buildflavor; my $d = parse($cf, $fn); die("$d->{'error'}\n") if $d->{'error'}; if ($field eq 'profiles' && $d->{'exclarch'} && !@{$d->{'exclarch'}}) { print "__excluded\n"; return; } my $x = $d->{$field}; $x = [ $x ] unless ref $x; if ($field eq 'copytoimage') { print "$_\n" for @$x; } else { print "@$x\n"; } } sub showcontainerinfo { my ($disturl, $arch, $buildflavor, $release); while (@ARGV) { if (@ARGV > 2 && $ARGV[0] eq '--disturl') { (undef, $disturl) = splice(@ARGV, 0, 2); } elsif (@ARGV > 2 && $ARGV[0] eq '--arch') { (undef, $arch) = splice(@ARGV, 0, 2); } elsif (@ARGV > 2 && $ARGV[0] eq '--buildflavor') { (undef, $buildflavor) = splice(@ARGV, 0, 2); } elsif (@ARGV > 2 && $ARGV[0] eq '--release') { (undef, $release) = splice(@ARGV, 0, 2); } else { last; } } my ($fn, $image) = @ARGV; local $urlmapper = sub { return $_[0] }; my $cf = {}; $cf->{'arch'} = $arch if defined $arch; $cf->{'buildflavor'} = $buildflavor if defined $buildflavor; $cf->{'buildrelease'} = $release if defined $release; my $d = parse($cf, $fn); die("$d->{'error'}\n") if $d->{'error'}; $image =~ s/.*\/// if defined $image; my $profile; if (@{$d->{'profiles'} || []} > 1 && $image) { # try to figure out the used profile from the image name my @matches = grep {$image =~ /-\Q$_\E-/} @{$d->{'profiles'}}; if (@matches) { $profile = $matches[0]; # XXX: should re-parse with the correct profile now } } if (!defined($profile) && @{$d->{'profiles'} || []}) { $profile = $d->{'profiles'}->[0]; } my @repos; for my $repo (@{$d->{'imagerepos'} || []}) { push @repos, { 'url' => $repo->{'url'}, '_type' => {'priority' => 'number'} }; $repos[-1]->{'priority'} = $repo->{'priority'} if defined $repo->{'priority'}; } my $buildtime = time(); my $containerinfo = { 'name' => $d->{'name'}, 'buildtime' => $buildtime, '_type' => {'buildtime' => 'number'}, }; $containerinfo->{'name'} .= "-$profile" if !$d->{'donotappendprofiletocontainername'} && defined($profile) && $profile ne ''; $containerinfo->{'version'} = $d->{'version'} if defined $d->{'version'}; $containerinfo->{'release'} = $release if defined $release; $containerinfo->{'tags'} = $d->{'container_tags'} if @{$d->{'container_tags'} || []}; $containerinfo->{'repos'} = \@repos if @repos; $containerinfo->{'file'} = $image if defined $image; $containerinfo->{'disturl'} = $disturl if defined $disturl; $containerinfo->{'milestone'} = $d->{'milestone'} if defined $d->{'milestone'}; print Build::SimpleJSON::unparse($containerinfo)."\n"; } # not implemented yet. sub queryiso { my ($handle, %opts) = @_; return {}; } sub queryhdrmd5 { my ($bin) = @_; die("Build::Kiwi::queryhdrmd5 unimplemented.\n"); } 1; 0707010000000e000081a400000000000000000000000163e504b900000b51000000000000000000000000000000000000001300000000Build/LiveBuild.pm################################################################ # # Copyright (c) 2014 Brocade Communications Systems, Inc. # Author: Jan Blunck <jblunck@infradead.org> # # This file is part of build. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program (see the file COPYING); if not, write to the # Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA # ################################################################# package Build::LiveBuild; use strict; eval { require Archive::Tar; }; *Archive::Tar::new = sub {die("Archive::Tar is not available\n")} unless defined &Archive::Tar::new; sub filter { my ($content) = @_; return '' unless defined $content; $content =~ s/^#.*$//mg; $content =~ s/^!.*$//mg; $content =~ s/^\s*//mg; return $content; } sub parse_package_list { my ($content) = @_; my @packages = split /\n/, filter($content); return @packages; }; sub parse_archive { my ($content) = @_; my @repos; my @lines = split /\n/, filter($content); for (@lines) { next if /^deb-src /; die("bad path using not obs:/ URL: $_\n") unless $_ =~ /^deb\s+obs:\/\/\/?([^\s\/]+)\/([^\s\/]+)\/?\s+.*$/; push @repos, "$1/$2"; } return @repos; } sub unify { my %h = map {$_ => 1} @_; return grep(delete($h{$_}), @_); } sub parse { my ($config, $filename, @args) = @_; my $ret = {}; # check that filename is a tar my $tar = Archive::Tar->new; unless($tar->read($filename)) { warn("$filename: " . $tar->error . "\n"); $ret->{'error'} = "$filename: " . $tar->error; return $ret; } # check that directory layout matches live-build directory structure for my $file ($tar->list_files('')) { next unless $file =~ /^(.*\/)?config\/archives\/.*\.list.*/; warn("$filename: config/archives/*.list* files not allowed!\n"); $ret->{'error'} = "$filename: config/archives/*.list* files not allowed!"; return $ret; } # always require the list of packages required by live-boot for # bootstrapping the target distribution image (e.g. with debootstrap) my @packages = ( 'live-build-desc' ); for my $file ($tar->list_files('')) { next unless $file =~ /^(.*\/)?config\/package-lists\/.*\.list.*/; push @packages, parse_package_list($tar->get_content($file)); } ($ret->{'name'} = $filename) =~ s/\.[^.]+$//; $ret->{'deps'} = [ unify(@packages) ]; return $ret; } 1; 0707010000000f000081a400000000000000000000000163e504b900000e55000000000000000000000000000000000000001100000000Build/Mdkrepo.pm################################################################ # # Copyright (c) 2015 SUSE Linux Products GmbH # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program (see the file COPYING); if not, write to the # Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA # ################################################################ package Build::Mdkrepo; use strict; #use Data::Dumper; sub addpkg { my ($res, $data, $options) = @_; if ($options->{'addselfprovides'} && defined($data->{'name'}) && defined($data->{'version'})) { if (($data->{'arch'} || '') ne 'src' && ($data->{'arch'} || '') ne 'nosrc') { my $evr = $data->{'version'}; $evr = "$data->{'epoch'}:$evr" if $data->{'epoch'}; $evr = "$evr-$data->{'release'}" if defined $data->{'release'}; my $s = "$data->{'name'} = $evr"; push @{$data->{'provides'}}, $s unless grep {$_ eq $s} @{$data->{'provides'} || []}; } } if (ref($res) eq 'CODE') { $res->($data); } else { push @$res, $data; } } sub parsedeps { my ($d) = @_; my @d = split('@', $d); for (@d) { s/\[\*\]//s; s/\[(.*)\]$/ $1/s; s/ == / = /; } return \@d; } sub parse { my ($in, $res, %options) = @_; $res ||= []; my $fd; if (ref($in)) { $fd = $in; } else { if ($in =~ /\.[gc]z$/) { # we need to probe, as mageia uses xz for compression open($fd, '<', $in) || die("$in: $!\n"); my $probe; sysread($fd, $probe, 5); close($fd); if ($probe && $probe eq "\xFD7zXZ") { open($fd, '-|', "xzdec", "-dc", $in) || die("$in: $!\n"); } else { open($fd, '-|', "gzip", "-dc", $in) || die("$in: $!\n"); } } else { open($fd, '<', $in) || die("$in: $!\n"); } } my $s = {}; while (<$fd>) { chomp; if (/^\@summary\@/) { $s->{'summary'} = substr($_, 9); } elsif (/^\@provides\@/) { $s->{'provides'} = parsedeps(substr($_, 10)); } elsif (/^\@requires\@/) { $s->{'requires'} = parsedeps(substr($_, 10)); } elsif (/^\@suggests\@/) { $s->{'suggests'} = parsedeps(substr($_, 10)); } elsif (/^\@recommends\@/) { $s->{'recommends'} = parsedeps(substr($_, 12)); } elsif (/^\@obsoletes\@/) { $s->{'obsoletes'} = parsedeps(substr($_, 11)); } elsif (/^\@conflicts\@/) { $s->{'conflicts'} = parsedeps(substr($_, 11)); } elsif (/^\@info\@/) { $s ||= {}; my @s = split('@', substr($_, 6)); $s->{'location'} = "$s[0].rpm"; my $arch; if ($s[0] =~ /\.([^\.]+)$/) { $arch = $1; $s[0] =~ s/\.[^\.]+$//; } $s->{'epoch'} = $s[1] if $s[1]; $s[0] =~ s/-\Q$s[4]\E[^-]*$//s if defined($s[4]) && $s[4] ne ''; # strip disttag $s[0] .= ":$s[5]" if defined($s[5]) && $s[5] ne ''; # add distepoch $s->{'arch'} = $arch || 'noarch'; if ($s[0] =~ /^(.*)-([^-]+)-([^-]+)$/s) { ($s->{'name'}, $s->{'version'}, $s->{'release'}) = ($1, $2, $3); # fake source entry for now... $s->{'source'} = $s->{'name'} if $s->{'arch'} ne 'src' && $s->{'arch'} ne 'nosrc'; addpkg($res, $s, \%options); } $s = {}; } } return $res; } 1; 07070100000010000081a400000000000000000000000163e504b90000092d000000000000000000000000000000000000000f00000000Build/Mkosi.pm# # mkosi specific functions. # ################################################################ # # Copyright (c) 2022 Luca Boccassi <bluca@debian.org> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program (see the file COPYING); if not, write to the # Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA # ################################################################ package Build::Mkosi; use strict; eval { require Config::IniFiles; }; *Config::IniFiles::new = sub {die("Config::IniFiles is not available\n")} unless defined &Config::IniFiles::new; sub parse { my ($bconf, $fn) = @_; my $ret = {}; my $file_content = ""; open my $fh, "<", $fn; unless($fn) { warn("Cannot open $fn\n"); $ret->{'error'} = "Cannot open $fn\n"; return $ret; } # mkosi supports multi-value keys, separated by newlines, so we need to mangle the file # in order to make Config::IniFiles happy. # Remove the previous newline if the next line doesn't have a '=' or '[' character. while( my $line = <$fh>) { $line =~ s/#.*$//; if ((index $line, '=') == -1 && (index $line, '[') == -1) { chomp $file_content; } $file_content .= $line; } close $fh; my $cfg = Config::IniFiles->new( -file => \$file_content ); unless($cfg) { warn("$fn: " . @Config::IniFiles::errors ? ":\n@Config::IniFiles::errors\n" : "\n"); $ret->{'error'} = "$fn: " . @Config::IniFiles::errors ? ":\n@Config::IniFiles::errors\n" : "\n"; return $ret; } my @packages; if (length $cfg->val('Content', 'Packages')) { push(@packages, split /\s+/, $cfg->val('Content', 'Packages')); } if (length $cfg->val('Content', 'BuildPackages')) { push(@packages, split /\s+/, $cfg->val('Content', 'BuildPackages')); } $ret->{'name'} = $fn; $ret->{'deps'} = \@packages; return $ret; } 1; 07070100000011000081a400000000000000000000000163e504b900000ca2000000000000000000000000000000000000001200000000Build/Modulemd.pm################################################################ # # Copyright (c) 2021 SUSE Linux Products GmbH # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program (see the file COPYING); if not, write to the # Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA # ################################################################ package Build::Modulemd; use Build::SimpleYAML; use strict; # This module provides a modulemd data to yaml converter. It supports # both the 'modulemd' and 'modulemd-defaults' formats. my $mdtemplate = { '_order' => [ 'document', 'version', 'data' ], 'version' => 'number', 'data' => { '_order' => [ 'name', 'stream', 'version', 'context', 'arch', 'summary', 'description', 'servicelevels', 'license', 'xmd', 'dependencies', 'references', 'profiles', 'api', 'filter', 'buildopts', 'components', 'artifacts' ], 'version' => 'number', 'description' => 'folded', 'license' => { '_order' => [ 'module', 'content' ], }, 'components' => { '_order' => [ 'rpms', 'modules' ], 'rpms' => { '*' => { '_order' => [ 'rationale', 'name', 'repository', 'cache', 'ref', 'buildroot', 'srpm-buildroot', 'buildorder', 'buildafter', 'buildonly', 'arches', 'multilib' ], 'buildroot' => 'bool', 'srpm-buildroot' => 'bool', 'buildorder' => 'number', 'buildonly' => 'bool', 'arches' => 'inline', 'multilib' => 'inline', }, }, 'modules' => { '*' => { '_order' => [ 'rationale', 'repository', 'ref', 'buildorder', 'buildafter', 'buildonly' ], 'buildorder' => 'number', 'buildonly' => 'bool', }, }, }, 'buildopts' => { '_order' => [ 'rpms', 'arches' ], 'rpms' => { '_order' => [ 'macros', 'whitelist' ], 'macros' => 'literal', }, 'arches' => 'inline', }, 'dependencies' => { 'requires' => { '*' => 'inline', }, 'buildrequires' => { '*' => 'inline', }, }, }, }; my $mddefaultstemplate = { '_order' => [ 'document', 'version', 'data' ], 'version' => 'number', 'data' => { '_order' => [ 'module', 'modified', 'stream', 'profiles', 'intents' ], 'modified' => 'number', 'profiles' => { '*' => 'inline', }, 'intents' => { '*' => { '_order' => [ 'stream', 'profiles' ], 'profiles' => { '*' => 'inline', }, }, }, }, }; sub mdtoyaml { my ($md) = @_; my $template = $md && $md->{'document'} eq 'modulemd-defaults' ? $mddefaultstemplate : $mdtemplate; return Build::SimpleYAML::unparse($md, 'template' => $template); } 1; 07070100000012000081a400000000000000000000000163e504b900000c39000000000000000000000000000000000000001100000000Build/Modules.pm################################################################ # # Copyright (c) 2019 SUSE Linux Products GmbH # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program (see the file COPYING); if not, write to the # Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA # ################################################################ package Build::Modules; use strict; eval { require YAML::XS; $YAML::XS::LoadBlessed = 0; }; *YAML::XS::LoadFile = sub {die("YAML::XS is not available\n")} unless defined &YAML::XS::LoadFile; # # return a hash that maps NEVRA to modules # sub parse { my ($in, $res, %options) = @_; $res ||= {}; # YAML::XS only alows a GLOB, so we need to do this old fashioned local *FD; my $fd; if (ref($in)) { *FD = $in; } else { if ($in =~ /\.gz$/) { open(FD, '-|', "gzip", "-dc", $in) || die("$in: $!\n"); } else { open(FD, '<', $in) || die("$in: $!\n"); } } my %mods; my @mod = YAML::XS::LoadFile(\*FD); for my $mod (@mod) { next unless $mod->{'document'} eq 'modulemd'; my $data = $mod->{'data'}; next unless $data && ref($data) eq 'HASH'; my $name = $data->{'name'}; my $stream = $data->{'stream'}; my $context = $data->{'context'}; my $module = "$name-$stream"; my @reqs; my $dependencies = $data->{'dependencies'}; $dependencies = $dependencies->[0] if ref($dependencies) eq 'ARRAY'; if ($dependencies && ref($dependencies) eq 'HASH') { my $requires = $dependencies->{'requires'}; if ($requires && ref($requires) eq 'HASH') { for my $r (sort keys %$requires) { my $rs = $requires->{$r}; $rs = $rs->[0] unless ref($rs) eq 'ARRAY'; if (@$rs) { push @reqs, "$r-$rs->[0]"; # XXX: what about the rest? } else { push @reqs, $r; # unversioned } } } } my $moduleinfo = { 'name' => $module, 'stream' => $data->{'stream'} }; $moduleinfo->{'context'} = $context if $context; $moduleinfo->{'requires'} = \@reqs if @reqs; $mods{"$module\@$context"} = $moduleinfo; next unless $data->{'artifacts'}; my $rpms = $data->{'artifacts'}->{'rpms'}; next unless $rpms && ref($rpms) eq 'ARRAY'; for my $rpm (@$rpms) { my $nrpm = $rpm; $nrpm =~ s/-0:([^-]*-[^-]*\.[^\.]*)$/-$1/; # normalize the epoch push @{$res->{$nrpm}}, $module; push @{$res->{$nrpm}}, "$module\@$context" if $context; } } # unify for (values %$res) { $_ = [ sort keys %{ { map {$_ => 1} @$_ } } ] if @$_ > 1; } # add moduleinfo $res->{'/moduleinfo'} = [ map {$mods{$_}} sort keys %mods ]; return $res; } 1; 07070100000013000081a400000000000000000000000163e504b900000922000000000000000000000000000000000000001100000000Build/Options.pm################################################################ # # Copyright (c) 2021 SUSE LLC # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program (see the file COPYING); if not, write to the # Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA # ################################################################ package Build::Options; use strict; sub getarg { my ($origopt, $args, $optional) = @_; return ${shift @$args} if @$args && ref($args->[0]); return shift @$args if @$args && $args->[0] !~ /^-/; die("Option $origopt needs an argument\n") unless $optional; return undef; } sub parse_options { my ($known_options, @args) = @_; my %opts; my @back; while (@args) { my $opt = shift @args; if ($opt !~ /^-/) { push @back, $opt; next; } if ($opt eq '--') { push @back, @args; last; } my $origopt = $opt; $opt =~ s/^--?//; unshift @args, \"$1" if $opt =~ s/=(.*)$//; my $ko = $known_options->{$opt}; die("Unknown option '$origopt'. Exit.\n") unless defined $ko; $ko = "$opt$ko" if !ref($ko) && ($ko eq '' || $ko =~ /^:/); if (ref($ko)) { $ko->(\%opts, $opt, $origopt, \@args); } elsif ($ko =~ s/(:.*)//) { my $arg = getarg($origopt, \@args); if ($1 eq '::') { push @{$opts{$ko}}, $arg; } else { $opts{$ko} = $arg; } } else { my $arg = 1; if (@args && ref($args[0])) { $arg = getarg($origopt, \@args); $arg = 0 if $arg =~ /^(?:0|off|false|no)$/i; $arg = 1 if $arg =~ /^(?:1|on|true|yes)$/i; die("Bad boolean argument for option $origopt: '$arg'\n") unless $arg eq '0' || $arg eq '1'; } $opts{$ko} = $arg; } die("Option $origopt does not take an argument\n") if @args && ref($args[0]); } return (\%opts, @back); } 1; 07070100000014000081a400000000000000000000000163e504b9000007da000000000000000000000000000000000000000e00000000Build/Repo.pm################################################################ # # Copyright (c) 1995-2014 SUSE Linux Products GmbH # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program (see the file COPYING); if not, write to the # Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA # ################################################################ package Build::Repo; use strict; our $do_rpmmd; our $do_deb; our $do_arch; our $do_susetags; our $do_mdk; sub import { for (@_) { $do_rpmmd = 1 if $_ eq ':rpmmd'; $do_deb = 1 if $_ eq ':deb'; $do_arch = 1 if $_ eq ':arch'; $do_susetags = 1 if $_ eq ':susetags'; $do_mdk = 1 if $_ eq ':mdk'; } $do_rpmmd = $do_deb = $do_arch = $do_susetags = $do_mdk = 1 unless $do_rpmmd || $do_deb || $do_arch || $do_susetags || $do_mdk; if ($do_rpmmd) { require Build::Rpmmd; } if ($do_susetags) { require Build::Susetags; } if ($do_deb) { require Build::Debrepo; } if ($do_arch) { require Build::Archrepo; } if ($do_mdk) { require Build::Mdkrepo; } } sub parse { my ($type, @args) = @_; return Build::Rpmmd::parse(@args) if $do_rpmmd && $type eq 'rpmmd'; return Build::Susetags::parse(@args) if $do_susetags && $type eq 'susetags'; return Build::Debrepo::parse(@args) if $do_deb && $type eq 'deb'; return Build::Archrepo::parse(@args) if $do_arch && $type eq 'arch'; return Build::Mdkrepo::parse(@args) if $do_arch && $type eq 'mdk'; die("parse repo: unknown type '$type'\n"); } 1; 07070100000015000081a400000000000000000000000163e504b90000c8a5000000000000000000000000000000000000000d00000000Build/Rpm.pm################################################################ # # Copyright (c) 1995-2014 SUSE Linux Products GmbH # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program (see the file COPYING); if not, write to the # Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA # ################################################################ package Build::Rpm; our $unfilteredprereqs = 0; our $conflictdeps = 0; our $includecallback; use strict; use Digest::MD5; sub expr_boolify { my ($v) = @_; return !defined($v) || $v eq '"' || $v =~ /^(?:0*$|v)/s ? 0 : 1; } sub expr_vcmp { my ($v1, $v2, $rr) = @_; my $r = verscmp(substr($v1, 1), substr($v2, 1)); return ($r < 0 ? 1 : $r > 0 ? 4 : 2) & $rr ? 1 : 0; } sub expr_dummyexpander { return substr($_[0], 0, 1); } sub expr_expand { my ($v, $expr, $xp, $r) = @_; while (1) { if ($expr =~ /^%%/s) { $v .= substr($expr, 0, 2, ''); } elsif ($expr =~ /^%/s) { my $m = macroend($expr); $v .= substr($expr, 0, length($m), ''); } elsif ($expr =~ /$r/) { $v .= substr($expr, 0, length($1), ''); } else { return ($xp->($v), $expr); } } } sub expr { my ($expr, $lev, $xp) = @_; $lev ||= 0; my ($v, $v2); $expr =~ s/^\s+//; my $t = substr($expr, 0, 1); if ($t eq '(') { ($v, $expr) = expr(substr($expr, 1), 0, $xp); return undef unless defined $v; return undef unless $expr =~ s/^\)//; } elsif ($t eq '!') { ($v, $expr) = expr(substr($expr, 1), 6, $xp); return undef unless defined $v; $v = expr_boolify($v) ? 0 : 1; } elsif ($t eq '-') { ($v, $expr) = expr(substr($expr, 1), 6, $xp); return undef unless defined $v; $v = -$v; } elsif ($expr =~ /^([0-9]+)(.*?)$/s || ($xp && $expr =~ /^(%)(.*)$/)) { $v = $1; $expr = $2; ($v, $expr) = expr_expand('0', "$1$2", $xp, qr/^([0-9]+)/) if $xp; $v = 0 + $v; } elsif ($expr =~ /^v\"(.*?)(\".*)$/s) { $v = "v$1"; # version $expr = $2; ($v, $expr) = expr_expand('v', substr("$1$2", 2), $xp, qr/^([^%\"]+)/) if $xp; $expr =~ s/^\"//s; } elsif ($expr =~ /^(\".*?)(\".*)$/s) { $v = $1; $expr = $2; ($v, $expr) = expr_expand('"', substr("$1$2", 1), $xp, qr/^([^%\"]+)/) if $xp; $expr =~ s/^\"//s; } elsif ($expr =~ /^([a-zA-Z][a-zA-Z_0-9]*)(.*)$/s) { # actually no longer supported with new rpm versions $v = "\"$1"; $expr = $2; } else { return; } return ($v, $expr) if $lev >= 6; while (1) { $expr =~ s/^\s+//; if ($expr =~ /^&&/) { return ($v, $expr) if $lev >= 2; my $b = expr_boolify($v); ($v2, $expr) = expr(substr($expr, 2), 2, $xp && !$b ? \&expr_dummyexpander : $xp); return undef unless defined $v2; $v = $v2 if $b; } elsif ($expr =~ /^\|\|/) { return ($v, $expr) if $lev >= 2; my $b = expr_boolify($v); ($v2, $expr) = expr(substr($expr, 2), 2, $xp && $b ? \&expr_dummyexpander : $xp); return undef unless defined $v2; $v = $v2 unless $b; } elsif ($expr =~ /^>=/) { return ($v, $expr) if $lev >= 3; ($v2, $expr) = expr(substr($expr, 2), 3, $xp); return undef unless defined $v2; $v = (($v =~ /^v/) ? expr_vcmp($v, $v2, 6) : ($v =~ /^\"/) ? $v ge $v2 : $v >= $v2) ? 1 : 0; } elsif ($expr =~ /^>/) { return ($v, $expr) if $lev >= 3; ($v2, $expr) = expr(substr($expr, 1), 3, $xp); return undef unless defined $v2; $v = (($v =~ /^v/) ? expr_vcmp($v, $v2, 4) : ($v =~ /^\"/) ? $v gt $v2 : $v > $v2) ? 1 : 0; } elsif ($expr =~ /^<=/) { return ($v, $expr) if $lev >= 3; ($v2, $expr) = expr(substr($expr, 2), 3, $xp); return undef unless defined $v2; $v = (($v =~ /^v/) ? expr_vcmp($v, $v2, 3) : ($v =~ /^\"/) ? $v le $v2 : $v <= $v2) ? 1 : 0; } elsif ($expr =~ /^</) { return ($v, $expr) if $lev >= 3; ($v2, $expr) = expr(substr($expr, 1), 3, $xp); return undef unless defined $v2; $v = (($v =~ /^v/) ? expr_vcmp($v, $v2, 1) : ($v =~ /^\"/) ? $v lt $v2 : $v < $v2) ? 1 : 0; } elsif ($expr =~ /^==/) { return ($v, $expr) if $lev >= 3; ($v2, $expr) = expr(substr($expr, 2), 3, $xp); return undef unless defined $v2; $v = (($v =~ /^v/) ? expr_vcmp($v, $v2, 2) : ($v =~ /^\"/) ? $v eq $v2 : $v == $v2) ? 1 : 0; } elsif ($expr =~ /^!=/) { return ($v, $expr) if $lev >= 3; ($v2, $expr) = expr(substr($expr, 2), 3, $xp); return undef unless defined $v2; $v = (($v =~ /^v/) ? expr_vcmp($v, $v2, 5) : ($v =~ /^\"/) ? $v ne $v2 : $v != $v2) ? 1 : 0; } elsif ($expr =~ /^\+/) { return ($v, $expr) if $lev >= 4; ($v2, $expr) = expr(substr($expr, 1), 4, $xp); return undef unless defined $v2; if ($v =~ /^\"/ && $v2 =~ s/^\"//) { $v .= $v2; } else { $v += $v2; } } elsif ($expr =~ /^-/) { return ($v, $expr) if $lev >= 4; ($v2, $expr) = expr(substr($expr, 1), 4, $xp); return undef unless defined $v2; $v -= $v2; } elsif ($expr =~ /^\*/) { ($v2, $expr) = expr(substr($expr, 1), 5, $xp); return undef unless defined $v2; $v *= $v2; } elsif ($expr =~ /^\//) { ($v2, $expr) = expr(substr($expr, 1), 5, $xp); return undef unless defined $v2 && 0 + $v2; $v /= $v2; } elsif ($expr =~ /^\?/) { return ($v, $expr) if $lev > 1; my $b = expr_boolify($v); ($v, $expr) = expr(substr($expr, 1), 1, $xp && !$b ? \&expr_dummyexpander : $xp); warn("syntax error while parsing ternary in $_[0]\n") unless defined($v) && $expr =~ s/^://; ($v2, $expr) = expr($expr, 1, $xp && $b ? \&expr_dummyexpander : $xp); return undef unless defined $v2; $v = $v2 unless $b; } elsif ($expr =~ /^([=&|])/) { warn("syntax error while parsing $1$1\n"); return ($v, $expr); } else { return ($v, $expr); } } } sub adaptmacros { my ($macros, $optold, $optnew) = @_; for (keys %$optold) { delete $macros->{$_}; } for (keys %$optnew) { $macros->{$_} = $optnew->{$_}; } return $optnew; } sub grabargs { my ($macname, $getopt, @args) = @_; my %m; $m{'0'} = $macname; $m{'**'} = join(' ', @args); my %go; %go = ($getopt =~ /(.)(:*)/sg) if defined $getopt; while (@args && $args[0] =~ s/^-//) { my $o = shift @args; last if $o eq '-'; while ($o =~ /^(.)(.*)$/) { if ($go{$1}) { my $arg = $2; $arg = shift(@args) if @args && $arg eq ''; $m{"-$1"} = "-$1 $arg"; $m{"-$1*"} = $arg; last; } $m{"-$1"} = "-$1"; $o = $2; } } $m{'#'} = scalar(@args); my $i = 1; for (@args) { $m{$i} = $_; $i++; } $m{'*'} = join(' ', @args); return \%m; } # no support for most special chars yet sub luapattern { my ($pat) = @_; $pat = "\Q$pat\E"; $pat =~ s/^\\\^/\^/; $pat =~ s/\\\$/\$/; $pat =~ s/\\\./\./; $pat =~ s/\\\*/\*/; $pat =~ s/\\\-/\*\?/; $pat =~ s/\\\?/\?/; $pat =~ s/\\\+/\+/; $pat =~ s/\\%([%ds])/\\$1/g; return $pat; } sub luamacro { my ($macname, @args) = @_; push @args, '' unless @args; return lc($args[0]) if $macname eq 'lower'; return uc($args[0]) if $macname eq 'upper'; return length($args[0]) if $macname eq 'len'; return reverse($args[0]) if $macname eq 'reverse'; return $args[0] x $args[1] if $macname eq 'rep'; if ($macname eq 'sub') { push @args, 1 if @args < 2; push @args, -1 if @args < 3; $args[1] -= 1 if $args[1] > 0; $args[1] = length($args[0]) + $args[1] if $args[1] < 0; $args[1] = 0 if $args[1] < 0; $args[2] -= 1 if $args[2] > 0; $args[2] = length($args[0]) + $args[2] if $args[2] < 0; return $args[1] <= $args[2] ? substr($args[0], $args[1], $args[2] - $args[1] + 1) : ''; } if ($macname eq 'gsub') { return unless @args >= 3; my $pat = luapattern($args[1]); my $rep = $args[3]; eval { if (!defined($rep)) { $args[0] =~ s/$pat/$args[2]/g; } else { $args[0] =~ s/$pat(?(?{$rep--<=0})(*F))/$args[2]/g; } }; return $@ ? '' : $args[0]; } return ''; } sub initmacros { my ($config, $macros, $macros_args) = @_; for my $line (@{$config->{'macros'} || []}) { next unless $line =~ /^%define\s*([0-9a-zA-Z_]+)(?:\(([^\)]*)\))?\s*(.*?)$/s; my $macname = $1; my $macargs = $2; my $macbody = $3; if (defined $macargs) { $macros_args->{$macname} = $macargs; } else { delete $macros_args->{$macname}; } $macros->{$macname} = $macbody; } } sub macroend { my ($expr) = @_; if ($expr =~ /^%([\(\{\[])/s) { my $o = $1; my $c = $o eq '[' ? ']' : $o eq '(' ? ')' : '}'; my $m = substr($expr, 0, 2, ''); my $cnt = 1; my $r = qr/^(.*?)([$o$c\\])/s; while ($expr =~ /$r/) { $m .= substr($expr, 0, length($1) + 1, ''); if ($2 eq '\\') { $m .= substr($expr, 0, 1, ''); } elsif ($2 eq $o) { $cnt++; } elsif ($2 eq $c) { return $m if --$cnt == 0; } } return "$m$expr"; } return $1 if $expr =~ /^(%[?!]*-?[a-zA-Z0-9_]*(?:\*|\*\*|\#)?)/s; } sub expandmacros { my ($config, $line, $lineno, $macros, $macros_args, $tries) = @_; if (!$macros) { $macros = {}; $macros_args = {}; initmacros($config, $macros, $macros_args); } my $expandedline = ''; $tries ||= 0; my @expandstack; my $optmacros = {}; # newer perls: \{((?:(?>[^{}]+)|(?2))*)\} reexpand: while ($line =~ /^(.*?)%(\{([^\}]+)\}|[\?\!]*[0-9a-zA-Z_]+|%|\*\*?|#|\(|\[)(.*?)\z/s) { if ($tries++ > 1000) { print STDERR "Warning: spec file parser ",($lineno?" line $lineno":''),": macro too deeply nested\n" if $config->{'warnings'}; $line = 'MACRO'; last; } $expandedline .= $1; $line = $4; if ($2 eq '%') { $expandedline .= '%'; next; } my $macname = defined($3) ? $3 : $2; my $macorig = $2; my $macdata; my $macalt; if (defined($3)) { if ($macname =~ /[{\\]/) { # tricky, use macroend $macname = macroend("%$macorig$line"); $line = substr("%$macorig$line", length($macname)); $macorig = substr($macname, 1); $macname =~ s/^%\{//s; $macname =~ s/\}\z//s; } $macdata = ''; if ($macname =~ /^([^\s:]+)([\s:])(.*)\z/s) { $macname = $1; if ($2 eq ':') { $macalt = $3; } else { $macdata = $3; } } } my $mactest = 0; if ($macname =~ /^\!\?/s || $macname =~ /^\?\!/s) { $mactest = -1; } elsif ($macname =~ /^\?/s) { $mactest = 1; } $macname =~ s/^[\!\?]+//s; if ($macname eq '(') { print STDERR "Warning: spec file parser",($lineno?" line $lineno":''),": can't expand %(...)\n" if $config->{'warnings'}; $line = 'MACRO'; last; } elsif ($macname eq '[') { $macalt = macroend("%[$line"); $line = substr($line, length($macalt) - 2); $macalt =~ s/^%\[//; $macalt =~ s/\]$//; my $xp = sub {expandmacros($config, $_[0], $lineno, $macros, $macros_args, $tries)}; $macalt = (expr($macalt, 0, $xp))[0]; $macalt =~ s/^[v\"]//; # stringify $expandedline .= $macalt; } elsif ($macname eq 'define' || $macname eq 'global') { my $isglobal = $macname eq 'global' ? 1 : 0; if ($line =~ /^\s*([0-9a-zA-Z_]+)(?:\(([^\)]*)\))?\s*(.*?)$/) { my $macname = $1; my $macargs = $2; my $macbody = $3; $macbody = expandmacros($config, $macbody, $lineno, $macros, $macros_args, $tries) if $isglobal; if (defined $macargs) { $macros_args->{$macname} = $macargs; } else { delete $macros_args->{$macname}; } $macros->{$macname} = $macbody; } $line = ''; last; } elsif ($macname eq 'defined' || $macname eq 'with' || $macname eq 'undefined' || $macname eq 'without' || $macname eq 'bcond_with' || $macname eq 'bcond_without') { my @args; if ($macorig =~ /^\{(.*)\}$/) { @args = split(' ', $1); shift @args; } else { @args = split(' ', $line); $line = ''; } next unless @args; if ($macname eq 'bcond_with') { $macros->{"with_$args[0]"} = 1 if exists $macros->{"_with_$args[0]"}; next; } if ($macname eq 'bcond_without') { $macros->{"with_$args[0]"} = 1 unless exists $macros->{"_without_$args[0]"}; next; } $args[0] = "with_$args[0]" if $macname eq 'with' || $macname eq 'without'; $line = ((exists($macros->{$args[0]}) ? 1 : 0) ^ ($macname eq 'undefined' || $macname eq 'without' ? 1 : 0)).$line; } elsif ($macname eq 'expand') { $macalt = $macros->{$macname} unless defined $macalt; $macalt = '' if $mactest == -1; push @expandstack, ($expandedline, $line, undef); $line = $macalt; $expandedline = ''; } elsif ($macname eq 'expr') { $macalt = $macros->{$macname} unless defined $macalt; $macalt = '' if $mactest == -1; $macalt = expandmacros($config, $macalt, $lineno, $macros, $macros_args, $tries); $macalt = (expr($macalt))[0]; $macalt =~ s/^[v\"]//; # stringify $expandedline .= $macalt; } elsif ($macname eq 'gsub' || $macname eq 'len' || $macname eq 'lower' || $macname eq 'upper' || $macname eq 'rep' || $macname eq 'reverse' || $macname eq 'sub') { my @args; if (defined $macalt) { push @args, $macalt; } elsif (defined $macdata) { push @args, split(' ', $macdata); } else { $line =~ /^\s*([^\n]*).*$/; push @args, split(' ', $1); $line = ''; } $_ = expandmacros($config, $_, $lineno, $macros, $macros_args, $tries) for @args; $expandedline .= luamacro($macname, @args); } elsif (exists($macros->{$macname})) { if (!defined($macros->{$macname})) { print STDERR "Warning: spec file parser",($lineno?" line $lineno":''),": can't expand '$macname'\n" if $config->{'warnings'}; $line = 'MACRO'; last; } if (defined($macros_args->{$macname})) { # macro with args! if (!defined($macdata)) { $line =~ /^\s*([^\n]*).*$/; $macdata = $1; $line = ''; } push @expandstack, ($expandedline, $line, $optmacros); $optmacros = adaptmacros($macros, $optmacros, grabargs($macname, $macros_args->{$macname}, split(' ', $macdata))); $line = $macros->{$macname}; $expandedline = ''; next; } $macalt = $macros->{$macname} unless defined $macalt; $macalt = '' if $mactest == -1; if ($macalt =~ /%/) { push @expandstack, ('', $line, 1) if $line ne ''; $line = $macalt; } else { $expandedline .= $macalt; } } elsif ($mactest) { $macalt = '' if !defined($macalt) || $mactest == 1; if ($macalt =~ /%/) { push @expandstack, ('', $line, 1) if $line ne ''; $line = $macalt; } else { $expandedline .= $macalt; } } else { $expandedline .= "%$macorig" unless $macname =~ /^-/; } } $line = $expandedline . $line; if (@expandstack) { my $m = pop(@expandstack); if ($m) { $optmacros = adaptmacros($macros, $optmacros, $m) if ref $m; $expandstack[-2] .= $line; $line = pop(@expandstack); $expandedline = pop(@expandstack); } else { my $todo = pop(@expandstack); $expandedline = pop(@expandstack); push @expandstack, ('', $todo, 1) if $todo ne ''; } goto reexpand; } return $line; } sub splitexpansionresult { my ($line, $includelines) = @_; my @l = split("\n", $line); $line = shift @l; s/%/%%/g for @l; unshift @$includelines, @l; return $line; } # see rpm's copyNextLineFromOFI() function in build/parseSpec.c sub needmorelines { my ($line) = @_; my ($bc, $pc, $xc, $nc) = (0, 0, 0, 0); while (1) { $line =~ s/^[^\\\n%\{\}\(\)\[\]]*//s; last if $line eq ''; if ($line =~ s/^%\{//s) { $bc++; } elsif ($line =~ s/^%\(//s) { $pc++; } elsif ($line =~ s/^%\[//s) { $xc++; } elsif ($line =~ s/^%%?//s) { next; } elsif ($line =~ s/^\n//s) { $nc = 0; } elsif ($line =~ s/^\\\n//s) { $nc = 1; } elsif ($line =~ s/^\\.?//s) { next; } elsif ($line =~ s/^([\{\}])//s) { $bc += $1 eq '{' ? 1 : -1 if $bc; } elsif ($line =~ s/^([\(\)])//s) { $pc += $1 eq '(' ? 1 : -1 if $pc; } elsif ($line =~ s/^([\[\]])//s) { $xc += $1 eq '[' ? 1 : -1 if $xc; } } return $pc || $bc || $xc || $nc ? 1 : 0; } sub splitdeps { my ($d) = @_; my @deps; $d =~ s/^[\s,]+//; while ($d ne '') { if ($d =~ /^\(/) { my @s = split(' ', $d); push @deps, shiftrich(\@s), undef, undef; $d = join(' ', @s); } else { last unless $d =~ s/([^\s\[,]+)(\s+[<=>]+\s+[^\s\[,]+)?(\s+\[[^\]]+\])?[\s,]*//; push @deps, $1, $2, $3; } } return @deps; } # xspec may be passed as array ref to return the parsed spec files # an entry in the returned array can be # - a string: verbatim line from the original file # - a two element array ref: # - [0] original line # - [1] undef: line unused due to %if # - [1] scalar: line after macro expansion. Only set if it's a build deps # line and build deps got modified or 'save_expanded' is set in # config sub parse { my ($config, $specfile, $xspec) = @_; my $packname; my $exclarch; my $badarch; my @subpacks; my @packdeps; my @prereqs; my @alsonative; my @onlynative; my $hasnfb; my $nfbline; my %macros; my %macros_args; my $ret = {}; my $ifdeps; my %autonum = (patch => 0, source => 0); my $specdata; local *SPEC; if (ref($specfile) eq 'GLOB') { *SPEC = *$specfile; } elsif (ref($specfile) eq 'ARRAY') { $specdata = [ @$specfile ]; } elsif (!open(SPEC, '<', $specfile)) { warn("$specfile: $!\n"); $ret->{'error'} = "open $specfile: $!"; return $ret; } initmacros($config, \%macros, \%macros_args); my $skip = 0; my $main_preamble = 1; my $preamble = 1; my $hasif = 0; my $lineno = 0; my @includelines; my $includenum = 0; my $obspackage = defined($config->{'obspackage'}) ? $config->{'obspackage'} : '@OBS_PACKAGE@'; my $buildflavor = defined($config->{'buildflavor'}) ? $config->{'buildflavor'} : ''; my $remoteasset; my $multilinedefine; my $multilinecondition; while (1) { my $line; my $doxspec = $xspec ? 1 : 0; if (@includelines) { $line = shift(@includelines); $includenum = 0 unless @includelines; $doxspec = 0; # only record lines from main file } elsif ($specdata) { last unless @$specdata; $line = shift @$specdata; ++$lineno; if (ref $line) { $line = $line->[0]; # verbatim line, used for macro collection push @$xspec, $line if $doxspec; $xspec->[-1] = [ $line, undef ] if $doxspec && $skip; next; } } else { $line = <SPEC>; last unless defined $line; chomp $line; ++$lineno; } push @$xspec, $line if $doxspec; if ($line =~ /^#\s*neededforbuild\s*(\S.*)$/) { if (defined $hasnfb) { $xspec->[-1] = [ $xspec->[-1], undef ] if $doxspec; next; } $hasnfb = $1; $nfbline = \$xspec->[-1] if $doxspec; next; } if ($multilinedefine || $line =~ /^\s*%(?:define|global)\s/s) { # is this a multi-line macro definition? $line = "$multilinedefine\n$line" if defined $multilinedefine; undef $multilinedefine; if ($line =~ /\\\z/s) { $multilinedefine = $line; # we need another line! next; } } if ($multilinecondition || $line =~ /^\s*\%\{[?!]*-?[0-9a-zA-Z_]+:\s*$/s) { # is this a multi-line macro condition? $line = "$multilinecondition\n$line" if defined $multilinecondition; undef $multilinecondition; if (needmorelines($line)) { $multilinecondition = $line; # we need another line! next; } } if ($line =~ /^\s*#/) { next unless $line =~ /^#!/; } if (!$skip && ($line =~ /%/)) { $line = expandmacros($config, $line, $lineno, \%macros, \%macros_args); $line = splitexpansionresult($line, \@includelines) if $line =~ /\n/s; } if ($line =~ /^\s*%(?:elif|elifarch|elifos)\b/) { $skip = 1 if !$skip; $skip = 2 - $skip if $skip <= 2; next if $skip; $line =~ s/^(\s*%)el/$1/; $line = expandmacros($config, $line, $lineno, \%macros, \%macros_args); $line = splitexpansionresult($line, \@includelines) if $line =~ /\n/s; } if ($line =~ /^\s*%else\b/) { $skip = 2 - $skip if $skip <= 2; next; } if ($line =~ /^\s*%endif\b/) { $skip = $skip > 2 ? $skip - 2 : 0; next; } if ($skip) { $skip += 2 if $line =~ /^\s*%if/; $xspec->[-1] = [ $xspec->[-1], undef ] if $doxspec; $ifdeps = 1 if $line =~ /^(BuildRequires|BuildPrereq|BuildConflicts|\#\!BuildIgnore|\#\!BuildConflicts|\#\!BuildRequires)\s*:\s*(\S.*)$/i; next; } if ($line =~ /\@/) { $line =~ s/\@BUILD_FLAVOR\@/$buildflavor/g; $line =~ s/\@OBS_PACKAGE\@/$obspackage/g; } if ($line =~ /^\s*%ifarch(.*)$/) { my $arch = $macros{'_target_cpu'} || 'unknown'; my @archs = grep {$_ eq $arch} split(/\s+/, $1); $skip = 2 if !@archs; $hasif = 1; next; } if ($line =~ /^\s*%ifnarch(.*)$/) { my $arch = $macros{'_target_cpu'} || 'unknown'; my @archs = grep {$_ eq $arch} split(/\s+/, $1); $skip = 2 if @archs; $hasif = 1; next; } if ($line =~ /^\s*%ifos(.*)$/) { my $os = $macros{'_target_os'} || 'unknown'; my @oss = grep {$_ eq $os} split(/\s+/, $1); $skip = 2 if !@oss; $hasif = 1; next; } if ($line =~ /^\s*%ifnos(.*)$/) { my $os = $macros{'_target_os'} || 'unknown'; my @oss = grep {$_ eq $os} split(/\s+/, $1); $skip = 2 if @oss; $hasif = 1; next; } if ($line =~ /^\s*%if(.*)$/) { my ($v, $r) = expr($1); $v = expr_boolify($v); $skip = 2 unless $v; $hasif = 1; next; } if ($includecallback && $line =~ /^\s*%include\s+(.*)\s*$/) { if ($includenum++ < 10) { my $data = $includecallback->($1); unshift @includelines, split("\n", $data) if $data; } else { warn("%include statment level too high, ignored\n") if $config->{'warnings'}; } } if ($main_preamble) { if ($line =~ /^(Name|Epoch|Version|Release|Disttag)\s*:\s*(\S+)/i) { $ret->{lc $1} = $2; $macros{lc $1} = $2; } elsif ($line =~ /^ExclusiveArch\s*:\s*(.*)/i) { $exclarch ||= []; push @$exclarch, split(' ', $1); } elsif ($line =~ /^ExcludeArch\s*:\s*(.*)/i) { $badarch ||= []; push @$badarch, split(' ', $1); } } if (@subpacks && $preamble && exists($ret->{'version'}) && $line =~ /^Version\s*:\s*(\S+)/i) { $ret->{'multiversion'} = 1 if $ret->{'version'} ne $1; } if ($preamble && $line =~ /^\#\!ForceMultiVersion\s*$/i) { $ret->{'multiversion'} = 1; } if ($preamble && $line =~ /^\#\!NativeBuild\s*$/i) { $ret->{'nativebuild'} = 1; } if ($preamble && $line =~ /^\#\!RemoteAsset(?::\s*([a-z0-9]+:[0-9a-f]+))?\s*$/i) { $remoteasset = {}; $remoteasset->{'digest'} = $1 if $1; } if ($preamble && $line =~ /^\#\!RemoteAssetUrl:\s*(\S+)\s*$/i) { $remoteasset->{'url'} = $1; push @{$ret->{'remoteassets'}}, $remoteasset; $remoteasset = undef; } if ($preamble && $line =~ /^\#\!BuildTarget:\s*(\S+)\s*$/i) { my $bt = $1; if ($bt =~ s/(.*?)://) { $bt = '' if $1 ne '' && $1 ne ($macros{'_target_cpu'} || 'unknown'); } delete $ret->{'buildtarget'}; $ret->{'buildtarget'} = $bt if $bt; } if ($preamble && $line =~ /^\#\!BuildConstraint:\s*(\S.+?)\s*$/i) { push @{$ret->{'buildconstraint'}}, $1; } if ($line =~ /^(?:Requires\(pre\)|Requires\(post\)|PreReq)\s*:\s*(\S.*)$/i) { my $deps = $1; my @deps; if (" $deps" =~ /[\s,]\(/) { @deps = splitdeps($deps); } else { @deps = $deps =~ /([^\s\[,]+)(\s+[<=>]+\s+[^\s\[,]+)?(\s+\[[^\]]+\])?[\s,]*/g; } while (@deps) { my ($pack, $vers, $qual) = splice(@deps, 0, 3); next if $pack eq 'MACRO'; # hope for the best... if (!$unfilteredprereqs && $pack =~ /^\//) { $ifdeps = 1; next unless $config->{'fileprovides'}->{$pack}; } push @prereqs, $pack unless grep {$_ eq $pack} @prereqs; } next; } if ($preamble && ($line =~ /^(BuildRequires|BuildPrereq|BuildConflicts|\#\!BuildIgnore|\#\!BuildConflicts|\#\!BuildRequires|\#\!AlsoNative|\#\!OnlyNative)\s*:\s*(\S.*)$/i)) { my $what = $1; my $deps = $2; $ifdeps = 1 if $hasif; # XXX: weird syntax addition. can append arch or project to dependency # BuildRequire: foo > 17 [i586,x86_64] # BuildRequire: foo [home:bar] # BuildRequire: foo [!home:bar] my @deps; if (" $deps" =~ /[\s,]\(/) { # we need to be careful, there could be a rich dep @deps = splitdeps($deps); } else { @deps = $deps =~ /([^\s\[,]+)(\s+[<=>]+\s+[^\s\[,]+)?(\s+\[[^\]]+\])?[\s,]*/g; } my $replace = 0; my @ndeps = (); while (@deps) { my ($pack, $vers, $qual) = splice(@deps, 0, 3); if (defined($qual)) { $replace = 1; my $arch = $macros{'_target_cpu'} || ''; my $proj = $macros{'_target_project'} || ''; $qual =~ s/^\s*\[//; $qual =~ s/\]$//; my $isneg = 0; my $bad; for my $q (split('[\s,]', $qual)) { $isneg = 1 if $q =~ s/^\!//; $bad = 1 if !defined($bad) && !$isneg; if ($isneg) { if ($q eq $arch || $q eq $proj) { $bad = 1; last; } } elsif ($q eq $arch || $q eq $proj) { $bad = 0; } } next if $bad; } $vers = '' unless defined $vers; $vers =~ s/=(>|<)/$1=/; push @ndeps, "$pack$vers"; } $replace = 1 if grep {/^-/} @ndeps; my $lcwhat = lc($what); if ($lcwhat eq '#!alsonative') { push @alsonative, @ndeps; next; } if ($lcwhat eq '#!onlynative') { push @onlynative, @ndeps; next; } if ($lcwhat ne 'buildrequires' && $lcwhat ne 'buildprereq' && $lcwhat ne '#!buildrequires') { if ($conflictdeps && $what =~ /conflict/i) { push @packdeps, map {"!$_"} @ndeps; next; } push @packdeps, map {"-$_"} @ndeps; next; } if (defined($hasnfb)) { if ((grep {$_ eq 'glibc' || $_ eq 'rpm' || $_ eq 'gcc' || $_ eq 'bash'} @ndeps) > 2) { # ignore old generated BuildRequire lines. $xspec->[-1] = [ $xspec->[-1], undef ] if $doxspec; next; } } push @packdeps, @ndeps; next unless $doxspec; if ($replace) { my @cndeps = grep {!/^-/} @ndeps; if (@cndeps) { $xspec->[-1] = [ $xspec->[-1], "$what: ".join(' ', @cndeps) ]; } else { $xspec->[-1] = [ $xspec->[-1], '']; } } next; } elsif ($preamble && $line =~ /^(Source\d*|Patch\d*|Url|Icon)\s*:\s*(\S+)/i) { my ($tag, $val) = (lc($1), $2); $macros{$tag} = $val if $tag eq 'url'; # associate url and icon tags with the corresponding subpackage $tag .= scalar @subpacks if ($tag eq 'url' || $tag eq 'icon') && @subpacks; if ($tag =~ /icon/) { # there can be a gif and xpm icon push @{$ret->{$tag}}, $val; } else { if ($tag =~ /^(source|patch)(\d+)?$/) { my $num = defined($2) ? 0 + $2 : $autonum{$1}; $tag = "$1$num"; if ($tag eq 'patch0' && exists($ret->{$tag})) { # gross hack. Before autonumbering "Patch" and "Patch0" could # exist. So take out the previous patch and add it back # without number. This does not exactly work as old rpms # but hopefully good enough :-) $ret->{'patch'} = delete $ret->{$tag}; } print STDERR "Warning: spec file parser: $tag already exists\n" if exists($ret->{$tag}) && $config->{'warnings'}; $autonum{$1} = $num + 1 if $num >= $autonum{$1}; $macros{uc($1) . "URL$num"} = $val; } $ret->{$tag} = $val; } if ($remoteasset && $tag =~ /^(?:source|patch)/) { $remoteasset->{'url'} = $val; push @{$ret->{'remoteassets'}}, $remoteasset; } $remoteasset = undef; } elsif (!$preamble && ($line =~ /^(Source\d*|Patch\d*|Url|Icon|BuildRequires|BuildPrereq|BuildConflicts|\#\!BuildIgnore|\#\!BuildConflicts|\#\!BuildRequires)\s*:\s*(\S.*)$/i)) { print STDERR "Warning: spec file parser ".($lineno ? " line $lineno" : '').": Ignoring $1 used beyond the preamble.\n" if $config->{'warnings'}; } if ($line =~ /^\s*%package\s+(-n\s+)?(\S+)/) { if ($1) { push @subpacks, $2; } else { push @subpacks, $ret->{'name'}.'-'.$2 if defined $ret->{'name'}; } $preamble = 1; $main_preamble = 0; } if ($line =~ /^\s*%(prep|build|install|check|clean|preun|postun|pretrans|posttrans|pre|post|files|changelog|description|triggerpostun|triggerun|triggerin|trigger|verifyscript)/) { $main_preamble = 0; $preamble = 0; } # do this always? if ($doxspec && $config->{'save_expanded'}) { $xspec->[-1] = [ $xspec->[-1], $line ]; } } close SPEC unless ref $specfile; if (defined($hasnfb)) { if (!@packdeps) { @packdeps = split(' ', $hasnfb); } elsif ($nfbline) { $$nfbline = [$$nfbline, undef ]; } } unshift @subpacks, $ret->{'name'} if defined $ret->{'name'}; $ret->{'subpacks'} = \@subpacks; $ret->{'exclarch'} = $exclarch if defined $exclarch; $ret->{'badarch'} = $badarch if defined $badarch; $ret->{'deps'} = \@packdeps; $ret->{'prereqs'} = \@prereqs if @prereqs; $ret->{'onlynative'} = \@onlynative if @onlynative; $ret->{'alsonative'} = \@alsonative if @alsonative; $ret->{'configdependent'} = 1 if $ifdeps; return $ret; } ########################################################################### my %rpmstag = ( "SIGMD5" => 261, "SIGTAG_SIZE" => 1000, # Header+Payload size in bytes. */ "SIGTAG_PGP" => 1002, # RSA signature over Header+Payload "SIGTAG_MD5" => 1004, # MD5 hash over Header+Payload "SIGTAG_GPG" => 1005, # DSA signature over Header+Payload "NAME" => 1000, "VERSION" => 1001, "RELEASE" => 1002, "EPOCH" => 1003, "SUMMARY" => 1004, "DESCRIPTION" => 1005, "BUILDTIME" => 1006, "VENDOR" => 1011, "LICENSE" => 1014, "ARCH" => 1022, "OLDFILENAMES" => 1027, "FILEMODES" => 1030, "FILEDIGESTS" => 1035, "SOURCERPM" => 1044, "PROVIDENAME" => 1047, "REQUIREFLAGS" => 1048, "REQUIRENAME" => 1049, "REQUIREVERSION" => 1050, "NOSOURCE" => 1051, "NOPATCH" => 1052, "EXCLUDEARCH" => 1059, "EXCLUDEOS" => 1060, "EXCLUSIVEARCH" => 1061, "EXCLUSIVEOS" => 1062, "SOURCEPACKAGE" => 1106, "PROVIDEFLAGS" => 1112, "PROVIDEVERSION" => 1113, "DIRINDEXES" => 1116, "BASENAMES" => 1117, "DIRNAMES" => 1118, "DISTURL" => 1123, "CONFLICTFLAGS" => 1053, "CONFLICTNAME" => 1054, "CONFLICTVERSION" => 1055, "OBSOLETENAME" => 1090, "OBSOLETEFLAGS" => 1114, "OBSOLETEVERSION" => 1115, "OLDSUGGESTSNAME" => 1156, "OLDSUGGESTSVERSION" => 1157, "OLDSUGGESTSFLAGS" => 1158, "OLDENHANCESNAME" => 1159, "OLDENHANCESVERSION" => 1160, "OLDENHANCESFLAGS" => 1161, "FILEDIGESTALGO" => 5011, "RECOMMENDNAME" => 5046, "RECOMMENDVERSION" => 5047, "RECOMMENDFLAGS" => 5048, "SUGGESTNAME" => 5049, "SUGGESTVERSION" => 5050, "SUGGESTFLAGS" => 5051, "SUPPLEMENTNAME" => 5052, "SUPPLEMENTVERSION" => 5053, "SUPPLEMENTFLAGS" => 5054, "ENHANCENAME" => 5055, "ENHANCEVERSION" => 5056, "ENHANCEFLAGS" => 5057, "MODULARITYLABEL" => 5096, ); sub rpmq { my ($rpm, @stags) = @_; my @sigtags = grep {/^SIGTAG_/} @stags; @stags = grep {!/^SIGTAG_/} @stags; my $dosigs = @sigtags && !@stags; @stags = @sigtags if $dosigs; my $need_filenames = grep { $_ eq 'FILENAMES' } @stags; push @stags, 'BASENAMES', 'DIRNAMES', 'DIRINDEXES', 'OLDFILENAMES' if $need_filenames; @stags = grep { $_ ne 'FILENAMES' } @stags if $need_filenames; my %stags = map {0 + ($rpmstag{$_} || $_) => $_} @stags; my ($magic, $sigtype, $headmagic, $cnt, $cntdata, $lead, $head, $index, $data, $tag, $type, $offset, $count); local *RPM; my $forcebinary; if (ref($rpm) eq 'ARRAY') { ($headmagic, $cnt, $cntdata) = unpack('N@8NN', $rpm->[0]); if ($headmagic != 0x8eade801) { warn("Bad rpm\n"); return (); } if (length($rpm->[0]) < 16 + $cnt * 16 + $cntdata) { warn("Bad rpm\n"); return (); } $index = substr($rpm->[0], 16, $cnt * 16); $data = substr($rpm->[0], 16 + $cnt * 16, $cntdata); } else { if (ref($rpm) eq 'GLOB') { *RPM = *$rpm; } elsif (!open(RPM, '<', $rpm)) { warn("$rpm: $!\n"); return (); } if (read(RPM, $lead, 96) != 96) { warn("Bad rpm $rpm\n"); close RPM unless ref($rpm); return (); } ($magic, $sigtype) = unpack('N@78n', $lead); if ($magic != 0xedabeedb || $sigtype != 5) { warn("Bad rpm $rpm\n"); close RPM unless ref($rpm); return (); } $forcebinary = 1 if unpack('@6n', $lead) != 1; if (read(RPM, $head, 16) != 16) { warn("Bad rpm $rpm\n"); close RPM unless ref($rpm); return (); } ($headmagic, $cnt, $cntdata) = unpack('N@8NN', $head); if ($headmagic != 0x8eade801) { warn("Bad rpm $rpm\n"); close RPM unless ref($rpm); return (); } if (read(RPM, $index, $cnt * 16) != $cnt * 16) { warn("Bad rpm $rpm\n"); close RPM unless ref($rpm); return (); } $cntdata = ($cntdata + 7) & ~7; if (read(RPM, $data, $cntdata) != $cntdata) { warn("Bad rpm $rpm\n"); close RPM unless ref($rpm); return (); } } my %res = (); if (@sigtags && !$dosigs) { %res = &rpmq(["$head$index$data"], @sigtags); } if (ref($rpm) eq 'ARRAY' && !$dosigs && @$rpm > 1) { my %res2 = &rpmq([ $rpm->[1] ], @stags); %res = (%res, %res2); return %res; } if (ref($rpm) ne 'ARRAY' && !$dosigs) { if (read(RPM, $head, 16) != 16) { warn("Bad rpm $rpm\n"); close RPM unless ref($rpm); return (); } ($headmagic, $cnt, $cntdata) = unpack('N@8NN', $head); if ($headmagic != 0x8eade801) { warn("Bad rpm $rpm\n"); close RPM unless ref($rpm); return (); } if (read(RPM, $index, $cnt * 16) != $cnt * 16) { warn("Bad rpm $rpm\n"); close RPM unless ref($rpm); return (); } if (read(RPM, $data, $cntdata) != $cntdata) { warn("Bad rpm $rpm\n"); close RPM unless ref($rpm); return (); } } close RPM unless ref($rpm); # return %res unless @stags; while($cnt-- > 0) { ($tag, $type, $offset, $count, $index) = unpack('N4a*', $index); $tag = 0+$tag; if ($stags{$tag} || !@stags) { eval { my $otag = $stags{$tag} || $tag; if ($type == 0) { $res{$otag} = [ '' ]; } elsif ($type == 1) { $res{$otag} = [ unpack("\@${offset}c$count", $data) ]; } elsif ($type == 2) { $res{$otag} = [ unpack("\@${offset}c$count", $data) ]; } elsif ($type == 3) { $res{$otag} = [ unpack("\@${offset}n$count", $data) ]; } elsif ($type == 4) { $res{$otag} = [ unpack("\@${offset}N$count", $data) ]; } elsif ($type == 5) { $res{$otag} = [ undef ]; } elsif ($type == 6) { $res{$otag} = [ unpack("\@${offset}Z*", $data) ]; } elsif ($type == 7) { $res{$otag} = [ unpack("\@${offset}a$count", $data) ]; } elsif ($type == 8 || $type == 9) { my $d = unpack("\@${offset}a*", $data); my @res = split("\0", $d, $count + 1); $res{$otag} = [ splice @res, 0, $count ]; } else { $res{$otag} = [ undef ]; } }; if ($@) { warn("Bad rpm $rpm: $@\n"); return (); } } } if ($forcebinary && $stags{1044} && !$res{$stags{1044}} && !($stags{1106} && $res{$stags{1106}})) { $res{$stags{1044}} = [ '(none)' ]; # like rpm does... } if ($need_filenames) { if ($res{'OLDFILENAMES'}) { $res{'FILENAMES'} = [ @{$res{'OLDFILENAMES'}} ]; } else { my $i = 0; $res{'FILENAMES'} = [ map {"$res{'DIRNAMES'}->[$res{'DIRINDEXES'}->[$i++]]$_"} @{$res{'BASENAMES'}} ]; } } return %res; } sub add_flagsvers { my ($res, $name, $flags, $vers) = @_; return unless $res && $res->{$name}; my @flags = @{$res->{$flags} || []}; my @vers = @{$res->{$vers} || []}; for (@{$res->{$name}}) { if (@flags && ($flags[0] & 0xe) && @vers) { $_ .= ' '; $_ .= '<' if $flags[0] & 2; $_ .= '>' if $flags[0] & 4; $_ .= '=' if $flags[0] & 8; $_ .= " $vers[0]"; } shift @flags; shift @vers; } } sub filteroldweak { my ($res, $name, $flags, $data, $strong, $weak) = @_; return unless $res && $res->{$name}; my @flags = @{$res->{$flags} || []}; my @strong; my @weak; for (@{$res->{$name}}) { if (@flags && ($flags[0] & 0x8000000)) { push @strong, $_; } else { push @weak, $_; } shift @flags; } $data->{$strong} = \@strong if @strong; $data->{$weak} = \@weak if @weak; } sub verscmp_part { my ($s1, $s2) = @_; if (!defined($s1)) { return defined($s2) ? -1 : 0; } return 1 if !defined $s2; return 0 if $s1 eq $s2; while (1) { $s1 =~ s/^[^a-zA-Z0-9~\^]+//; $s2 =~ s/^[^a-zA-Z0-9~\^]+//; if ($s1 =~ s/^~//) { next if $s2 =~ s/^~//; return -1; } return 1 if $s2 =~ /^~/; if ($s1 =~ s/^\^//) { next if $s2 =~ s/^\^//; return $s2 eq '' ? 1 : -1; } return $s1 eq '' ? -1 : 1 if $s2 =~ /^\^/; if ($s1 eq '') { return $s2 eq '' ? 0 : -1; } return 1 if $s2 eq ''; my ($x1, $x2, $r); if ($s1 =~ /^([0-9]+)(.*?)$/) { $x1 = $1; $s1 = $2; $s2 =~ /^([0-9]*)(.*?)$/; $x2 = $1; $s2 = $2; return 1 if $x2 eq ''; $x1 =~ s/^0+//; $x2 =~ s/^0+//; $r = length($x1) - length($x2) || $x1 cmp $x2; } elsif ($s1 ne '' && $s2 ne '') { $s1 =~ /^([a-zA-Z]*)(.*?)$/; $x1 = $1; $s1 = $2; $s2 =~ /^([a-zA-Z]*)(.*?)$/; $x2 = $1; $s2 = $2; return -1 if $x1 eq '' || $x2 eq ''; $r = $x1 cmp $x2; } return $r > 0 ? 1 : -1 if $r; } } sub verscmp { my ($s1, $s2, $dtest) = @_; return 0 if $s1 eq $s2; my ($e1, $v1, $r1) = $s1 =~ /^(?:(\d+):)?(.*?)(?:-([^-]*))?$/s; $e1 = 0 unless defined $e1; my ($e2, $v2, $r2) = $s2 =~ /^(?:(\d+):)?(.*?)(?:-([^-]*))?$/s; $e2 = 0 unless defined $e2; if ($e1 ne $e2) { my $r = verscmp_part($e1, $e2); return $r if $r; } return 0 if $dtest && ($v1 eq '' || $v2 eq ''); if ($v1 ne $v2) { my $r = verscmp_part($v1, $v2); return $r if $r; } $r1 = '' unless defined $r1; $r2 = '' unless defined $r2; return 0 if $dtest && ($r1 eq '' || $r2 eq ''); if ($r1 ne $r2) { return verscmp_part($r1, $r2); } return 0; } sub query { my ($handle, %opts) = @_; my @tags = qw{NAME SOURCERPM NOSOURCE NOPATCH SIGTAG_MD5 PROVIDENAME PROVIDEFLAGS PROVIDEVERSION REQUIRENAME REQUIREFLAGS REQUIREVERSION SOURCEPACKAGE}; push @tags, qw{EPOCH VERSION RELEASE ARCH}; push @tags, qw{FILENAMES} if $opts{'filelist'}; push @tags, qw{SUMMARY DESCRIPTION} if $opts{'description'}; push @tags, qw{DISTURL} if $opts{'disturl'}; push @tags, qw{BUILDTIME} if $opts{'buildtime'}; push @tags, qw{LICENSE} if $opts{'license'}; push @tags, qw{CONFLICTNAME CONFLICTVERSION CONFLICTFLAGS OBSOLETENAME OBSOLETEVERSION OBSOLETEFLAGS} if $opts{'conflicts'}; push @tags, qw{RECOMMENDNAME RECOMMENDVERSION RECOMMENDFLAGS SUGGESTNAME SUGGESTVERSION SUGGESTFLAGS SUPPLEMENTNAME SUPPLEMENTVERSION SUPPLEMENTFLAGS ENHANCENAME ENHANCEVERSION ENHANCEFLAGS OLDSUGGESTSNAME OLDSUGGESTSVERSION OLDSUGGESTSFLAGS OLDENHANCESNAME OLDENHANCESVERSION OLDENHANCESFLAGS} if $opts{'weakdeps'}; push @tags, qw{MODULARITYLABEL} if $opts{'modularitylabel'}; push @tags, qw{EXCLUDEARCH EXCLUDEOS EXCLUSIVEARCH EXCLUSIVEOS} if $opts{'excludearch'}; my %res = rpmq($handle, @tags); return undef unless %res; my $src = $res{'SOURCERPM'}->[0]; $res{'sourcerpm'} = $src if $src; $src = '' unless defined $src; $src =~ s/-[^-]*-[^-]*\.[^\.]*\.rpm//; add_flagsvers(\%res, 'PROVIDENAME', 'PROVIDEFLAGS', 'PROVIDEVERSION'); add_flagsvers(\%res, 'REQUIRENAME', 'REQUIREFLAGS', 'REQUIREVERSION'); my $data = { name => $res{'NAME'}->[0], hdrmd5 => unpack('H32', $res{'SIGTAG_MD5'}->[0]), }; if ($opts{'alldeps'}) { $data->{'provides'} = [ @{$res{'PROVIDENAME'} || []} ]; $data->{'requires'} = [ @{$res{'REQUIRENAME'} || []} ]; } elsif ($opts{'filedeps'}) { $data->{'provides'} = [ grep {!/^rpmlib\(/} @{$res{'PROVIDENAME'} || []} ]; $data->{'requires'} = [ grep {!/^rpmlib\(/} @{$res{'REQUIRENAME'} || []} ]; } else { $data->{'provides'} = [ grep {!/^rpmlib\(/ && !/^\//} @{$res{'PROVIDENAME'} || []} ]; $data->{'requires'} = [ grep {!/^rpmlib\(/ && !/^\//} @{$res{'REQUIRENAME'} || []} ]; } if ($opts{'conflicts'}) { add_flagsvers(\%res, 'CONFLICTNAME', 'CONFLICTFLAGS', 'CONFLICTVERSION'); add_flagsvers(\%res, 'OBSOLETENAME', 'OBSOLETEFLAGS', 'OBSOLETEVERSION'); $data->{'conflicts'} = [ @{$res{'CONFLICTNAME'}} ] if $res{'CONFLICTNAME'}; $data->{'obsoletes'} = [ @{$res{'OBSOLETENAME'}} ] if $res{'OBSOLETENAME'}; } if ($opts{'weakdeps'}) { for (qw{RECOMMEND SUGGEST SUPPLEMENT ENHANCE}) { next unless $res{"${_}NAME"}; add_flagsvers(\%res, "${_}NAME", "${_}FLAGS", "${_}VERSION"); $data->{lc($_)."s"} = [ @{$res{"${_}NAME"}} ]; } if ($res{'OLDSUGGESTSNAME'}) { add_flagsvers(\%res, 'OLDSUGGESTSNAME', 'OLDSUGGESTSFLAGS', 'OLDSUGGESTSVERSION'); filteroldweak(\%res, 'OLDSUGGESTSNAME', 'OLDSUGGESTSFLAGS', $data, 'recommends', 'suggests'); } if ($res{'OLDENHANCESNAME'}) { add_flagsvers(\%res, 'OLDENHANCESNAME', 'OLDENHANCESFLAGS', 'OLDENHANCESVERSION'); filteroldweak(\%res, 'OLDENHANCESNAME', 'OLDENHANCESFLAGS', $data, 'supplements', 'enhances'); } } # rpm3 compatibility: retrofit missing self provides if ($src ne '') { my $haveselfprovides; if (@{$data->{'provides'}}) { if ($data->{'provides'}->[-1] =~ /^\Q$res{'NAME'}->[0]\E =/) { $haveselfprovides = 1; } elsif (@{$data->{'provides'}} > 1 && $data->{'provides'}->[-2] =~ /^\Q$res{'NAME'}->[0]\E =/) { $haveselfprovides = 1; } } if (!$haveselfprovides) { my $evr = "$res{'VERSION'}->[0]-$res{'RELEASE'}->[0]"; $evr = "$res{'EPOCH'}->[0]:$evr" if $res{'EPOCH'} && $res{'EPOCH'}->[0]; push @{$data->{'provides'}}, "$res{'NAME'}->[0] = $evr"; } } $data->{'source'} = $src eq '(none)' ? $data->{'name'} : $src if $src ne ''; if ($opts{'evra'}) { my $arch = $res{'ARCH'}->[0]; $arch = $res{'NOSOURCE'} || $res{'NOPATCH'} ? 'nosrc' : 'src' unless $src ne ''; $data->{'version'} = $res{'VERSION'}->[0]; $data->{'release'} = $res{'RELEASE'}->[0]; $data->{'arch'} = $arch; $data->{'epoch'} = $res{'EPOCH'}->[0] if exists $res{'EPOCH'}; } if ($opts{'filelist'}) { $data->{'filelist'} = $res{'FILENAMES'}; } if ($opts{'description'}) { $data->{'summary'} = $res{'SUMMARY'}->[0]; $data->{'description'} = $res{'DESCRIPTION'}->[0]; } $data->{'buildtime'} = $res{'BUILDTIME'}->[0] if $opts{'buildtime'}; $data->{'disturl'} = $res{'DISTURL'}->[0] if $opts{'disturl'} && $res{'DISTURL'}; $data->{'license'} = $res{'LICENSE'}->[0] if $opts{'license'} && $res{'LICENSE'}; $data->{'modularitylabel'} = $res{'MODULARITYLABEL'}->[0] if $opts{'modularitylabel'} && $res{'MODULARITYLABEL'}; if ($opts{'excludearch'}) { for (qw{EXCLUDEARCH EXCLUDEOS EXCLUSIVEARCH EXCLUSIVEOS}) { $data->{lc($_)} = $res{$_} if @{$res{$_} || []}; } } return $data; } sub queryhdrmd5 { my ($bin, $leadsigp) = @_; local *F; open(F, '<', $bin) || die("$bin: $!\n"); my $buf = ''; my $l; while (length($buf) < 96 + 16) { $l = sysread(F, $buf, 4096, length($buf)); if (!$l) { warn("$bin: read error\n"); close(F); return undef; } } my ($magic, $sigtype) = unpack('N@78n', $buf); if ($magic != 0xedabeedb || $sigtype != 5) { warn("$bin: not a rpm (bad magic of header type)\n"); close(F); return undef; } my ($headmagic, $cnt, $cntdata) = unpack('@96N@104NN', $buf); if ($headmagic != 0x8eade801) { warn("$bin: not a rpm (bad sig header magic)\n"); close(F); return undef; } my $hlen = 96 + 16 + $cnt * 16 + $cntdata; $hlen = ($hlen + 7) & ~7; while (length($buf) < $hlen) { $l = sysread(F, $buf, 4096, length($buf)); if (!$l) { warn("$bin: read error\n"); close(F); return undef; } } close F; $$leadsigp = Digest::MD5::md5_hex(substr($buf, 0, $hlen)) if $leadsigp; my $idxarea = substr($buf, 96 + 16, $cnt * 16); if ($idxarea !~ /\A(?:.{16})*\000\000\003\354\000\000\000\007(....)\000\000\000\020/s) { warn("$bin: no md5 signature header\n"); return undef; } my $md5off = unpack('N', $1); if ($md5off >= $cntdata) { warn("$bin: bad md5 offset\n"); return undef; } $md5off += 96 + 16 + $cnt * 16; return unpack("\@${md5off}H32", $buf); } sub queryinstalled { my ($root, %opts) = @_; $root = '' if !defined($root) || $root eq '/'; local *F; my $dochroot = $root ne '' && !$opts{'nochroot'} && !$< && (-x "$root/usr/bin/rpm" || -x "$root/bin/rpm") ? 1 : 0; my $pid = open(F, '-|'); die("fork: $!\n") unless defined $pid; if (!$pid) { if ($dochroot && chroot($root)) { chdir('/') || die("chdir: $!\n"); $root = ''; } my @args; unshift @args, '--nodigest', '--nosignature' if -e "$root/usr/bin/rpmquery "; unshift @args, '--dbpath', "$root/var/lib/rpm" if $root ne ''; push @args, '--qf', '%{NAME}/%{ARCH}/%|EPOCH?{%{EPOCH}}:{0}|/%{VERSION}/%{RELEASE}/%{BUILDTIME}\n'; if (-x "$root/usr/bin/rpm") { exec("$root/usr/bin/rpm", '-qa', @args); die("$root/usr/bin/rpm: $!\n"); } if (-x "$root/bin/rpm") { exec("$root/bin/rpm", '-qa', @args); die("$root/bin/rpm: $!\n"); } die("rpm: command not found\n"); } my @pkgs; while (<F>) { chomp; my @s = split('/', $_); next unless @s >= 5; my $q = {'name' => $s[0], 'arch' => $s[1], 'version' => $s[3], 'release' => $s[4]}; $q->{'epoch'} = $s[2] if $s[2]; $q->{'buildtime'} = $s[5] if $s[5]; push @pkgs, $q; } if (!close(F)) { return queryinstalled($root, %opts, 'nochroot' => 1) if !@pkgs && $dochroot; die("rpm: exit status $?\n"); } return \@pkgs; } # return (lead, sighdr, hdr [, hdrmd5]) of a rpm sub getrpmheaders { my ($path, $withhdrmd5) = @_; my $hdrmd5; local *F; open(F, '<', $path) || die("$path: $!\n"); my $buf = ''; my $l; while (length($buf) < 96 + 16) { $l = sysread(F, $buf, 4096, length($buf)); die("$path: read error\n") unless $l; } die("$path: not a rpm\n") unless unpack('N', $buf) == 0xedabeedb && unpack('@78n', $buf) == 5; my ($headmagic, $cnt, $cntdata) = unpack('@96N@104NN', $buf); die("$path: not a rpm (bad sig header)\n") unless $headmagic == 0x8eade801 && $cnt < 16384 && $cntdata < 1048576; my $hlen = 96 + 16 + $cnt * 16 + $cntdata; $hlen = ($hlen + 7) & ~7; while (length($buf) < $hlen + 16) { $l = sysread(F, $buf, 4096, length($buf)); die("$path: read error\n") unless $l; } if ($withhdrmd5) { my $idxarea = substr($buf, 96 + 16, $cnt * 16); die("$path: no md5 signature header\n") unless $idxarea =~ /\A(?:.{16})*\000\000\003\354\000\000\000\007(....)\000\000\000\020/s; my $md5off = unpack('N', $1); die("$path: bad md5 offset\n") unless $md5off; $md5off += 96 + 16 + $cnt * 16; $hdrmd5 = unpack("\@${md5off}H32", $buf); } ($headmagic, $cnt, $cntdata) = unpack('N@8NN', substr($buf, $hlen)); die("$path: not a rpm (bad header)\n") unless $headmagic == 0x8eade801 && $cnt < 1048576 && $cntdata < 33554432; my $hlen2 = $hlen + 16 + $cnt * 16 + $cntdata; while (length($buf) < $hlen2) { $l = sysread(F, $buf, 4096, length($buf)); die("$path: read error\n") unless $l; } close F; return (substr($buf, 0, 96), substr($buf, 96, $hlen - 96), substr($buf, $hlen, $hlen2 - $hlen), $hdrmd5); } sub getnevr_rich { my ($d) = @_; my $n = ''; my $bl = 0; while ($d =~ /^([^ ,\(\)]*)/) { $n .= $1; $d = substr($d, length($1)); last unless $d =~ /^([\(\)])/; $bl += $1 eq '(' ? 1 : -1; last if $bl < 0; $n .= $1; $d = substr($d, 1); } return $n; } my %richops = ( 'and' => 1, 'or' => 2, 'if' => 3, 'unless' => 4, 'else' => 5, 'with' => 6, 'without' => 7, ); sub parse_rich_rec { my ($dep, $chainop) = @_; my $d = $dep; $chainop ||= 0; return ($d, undef) unless $d =~ s/^\(\s*//; my ($r, $r2); if ($d =~ /^\(/) { ($d, $r) = parse_rich_rec($d); return ($d, undef) unless $r; } else { return ($d, undef) if $d =~ /^\)/; my $n = getnevr_rich($d); $d = substr($d, length($n)); $d =~ s/^ +//; if ($d =~ /^([<=>]+)/) { $n .= " $1 "; $d =~ s/^[<=>]+ +//; my $evr = getnevr_rich($d); $d = substr($d, length($evr)); $n .= $evr; } $r = [0, $n]; } $d =~ s/^\s+//; return ($d, undef) unless $d ne ''; return ($d, $r) if $d =~ s/^\)//; return ($d, undef) unless $d =~ s/([a-z]+)\s+//; my $op = $richops {$1}; return ($d, undef) unless $op; return ($d, undef) if $op == 5 && $chainop != 3 && $chainop != 4; $chainop = 0 if $op == 5; return ($d, undef) if $chainop && (($chainop != 1 && $chainop != 2 && $chainop != 6) || $op != $chainop); ($d, $r2) = parse_rich_rec("($d", $op); return ($d, undef) unless $r2; if (($op == 3 || $op == 4) && $r2->[0] == 5) { $r = [$op, $r, $r2->[1], $r2->[2]]; } else { $r = [$op, $r, $r2]; } return ($d, $r); } sub parse_rich_dep { my ($dep) = @_; my ($d, $r) = parse_rich_rec($dep); return undef if !$r || $d ne ''; return $r; } my @testcaseops = ('', '&', '|', '<IF>', '<UNLESS>', '<ELSE>', '+', '-'); sub testcaseformat_rec { my ($r, $addparens) = @_; my $op = $r->[0]; return $r->[1] unless $op; my $top = $testcaseops[$op]; my $r1 = testcaseformat_rec($r->[1], 1); if (($op == 3 || $op == 4) && @$r == 4) { $r1 = "$r1 $top " . testcaseformat_rec($r->[2], 1); $top = '<ELSE>'; } my $addparens2 = 1; $addparens2 = 0 if $r->[2]->[0] == $op && ($op == 1 || $op == 2 || $op == 6); my $r2 = testcaseformat_rec($r->[-1], $addparens2); return $addparens ? "($r1 $top $r2)" : "$r1 $top $r2"; } sub testcaseformat { my ($dep) = @_; my $r = parse_rich_dep($dep); return $dep unless $r; return testcaseformat_rec($r); } sub shiftrich { my ($s) = @_; # FIXME: do this right! my $dep = shift @$s; while (@$s && ($dep =~ y/\(/\(/) > ($dep =~ y/\)/\)/)) { $dep .= ' ' . shift(@$s); } return $dep; } 1; 07070100000016000081a400000000000000000000000163e504b900001ec8000000000000000000000000000000000000000f00000000Build/Rpmmd.pm################################################################ # # Copyright (c) 1995-2014 SUSE Linux Products GmbH # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program (see the file COPYING); if not, write to the # Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA # ################################################################ package Build::Rpmmd; use strict; use Build::Rpm; use XML::Parser; sub generic_parse { my ($how, $in, $res, %options) = @_; $res ||= []; my @cursor = ([undef, $how, undef, $res, undef, \%options]); my $p = new XML::Parser(Handlers => { Start => sub { my ($p, $el) = @_; my $h = $cursor[-1]->[1]; return unless exists $h->{$el}; $h = $h->{$el}; push @cursor, [$el, $h]; $cursor[-1]->[2] = '' if $h->{'_text'}; $h->{'_start'}->($h, \@cursor, @_) if exists $h->{'_start'}; }, End => sub { my ($p, $el) = @_; if ($cursor[-1]->[0] eq $el) { my $h = $cursor[-1]->[1]; $h->{'_end'}->($h, \@cursor, @_) if exists $h->{'_end'}; pop @cursor; } }, Char => sub { my ($p, $text) = @_; $cursor[-1]->[2] .= $text if defined $cursor[-1]->[2]; }, ExternEnt => sub { undef }, }, ErrorContext => 2); if (ref($in)) { $p->parse($in); } else { $p->parsefile($in); } return $res; } sub generic_store_text { my ($h, $c, $p, $el) = @_; my $data = $c->[0]->[4]; $data->{$h->{'_tag'}} = $c->[-1]->[2] if defined $c->[-1]->[2]; } sub generic_store_attr { my ($h, $c, $p, $el, %attr) = @_; my $data = $c->[0]->[4]; $data->{$h->{'_tag'}} = $attr{$h->{'_attr'}} if defined $attr{$h->{'_attr'}}; } sub generic_new_data { my ($h, $c, $p, $el, %attr) = @_; $c->[0]->[4] = {}; generic_store_attr(@_) if $h->{'_attr'}; } sub generic_add_result { my ($h, $c, $p, $el) = @_; my $data = $c->[0]->[4]; return unless $data; my $res = $c->[0]->[3]; if (ref($res) eq 'CODE') { $res->($data); } else { push @$res, $data; } undef $c->[0]->[4]; } my $repomdparser = { repomd => { data => { _start => \&generic_new_data, _attr => 'type', _tag => 'type', _end => \&generic_add_result, location => { _start => \&generic_store_attr, _attr => 'href', _tag => 'location'}, checksum => { _start => \&generic_store_attr, _attr => 'type', _tag => 'checksum', _text => 1, _end => \&primary_handle_checksum }, size => { _text => 1, _end => \&generic_store_text, _tag => 'size'}, }, }, }; my $primaryparser = { metadata => { 'package' => { _start => \&generic_new_data, _attr => 'type', _tag => 'type', _end => \&primary_add_result, name => { _text => 1, _end => \&generic_store_text, _tag => 'name' }, arch => { _text => 1, _end => \&generic_store_text, _tag => 'arch' }, version => { _start => \&primary_handle_version }, checksum => { _start => \&generic_store_attr, _attr => 'type', _tag => 'checksum', _text => 1, _end => \&primary_handle_checksum }, 'time' => { _start => \&primary_handle_time }, format => { 'rpm:provides' => { 'rpm:entry' => { _start => \&primary_handle_dep , _tag => 'provides' }, }, 'rpm:requires' => { 'rpm:entry' => { _start => \&primary_handle_dep , _tag => 'requires' }, }, 'rpm:conflicts' => { 'rpm:entry' => { _start => \&primary_handle_dep , _tag => 'conflicts' }, }, 'rpm:recommends' => { 'rpm:entry' => { _start => \&primary_handle_dep , _tag => 'recommends' }, }, 'rpm:suggests' => { 'rpm:entry' => { _start => \&primary_handle_dep , _tag => 'suggests' }, }, 'rpm:supplements' => { 'rpm:entry' => { _start => \&primary_handle_dep , _tag => 'supplements' }, }, 'rpm:enhances' => { 'rpm:entry' => { _start => \&primary_handle_dep , _tag => 'enhances' }, }, 'rpm:obsoletes' => { 'rpm:entry' => { _start => \&primary_handle_dep , _tag => 'obsoletes' }, }, 'rpm:buildhost' => { _text => 1, _end => \&generic_store_text, _tag => 'buildhost' }, 'rpm:vendor' => { _text => 1, _end => \&generic_store_text, _tag => 'vendor' }, 'rpm:license' => { _text => 1, _end => \&generic_store_text, _tag => 'license' }, 'rpm:sourcerpm' => { _text => 1, _end => \&primary_handle_sourcerpm , _tag => 'source' }, ### currently commented out, as we ignore file provides in expanddeps # file => { _text => 1, _end => \&primary_handle_file_end, _tag => 'provides' }, }, location => { _start => \&generic_store_attr, _attr => 'href', _tag => 'location'}, }, }, }; sub primary_handle_sourcerpm { my ($h, $c, $p, $el, %attr) = @_; my $data = $c->[0]->[4]; return unless defined $c->[-1]->[2]; $data->{'sourcerpm'} = $data->{'source'} = $c->[-1]->[2]; $data->{'source'} =~ s/-[^-]*-[^-]*\.[^\.]*\.rpm$//; } sub primary_handle_version { my ($h, $c, $p, $el, %attr) = @_; my $data = $c->[0]->[4]; $data->{'epoch'} = $attr{'epoch'} if $attr{'epoch'}; $data->{'version'} = $attr{'ver'}; $data->{'release'} = $attr{'rel'}; } sub primary_handle_time { my ($h, $c, $p, $el, %attr) = @_; my $data = $c->[0]->[4]; $data->{'filetime'} = $attr{'file'} if $attr{'file'}; $data->{'buildtime'} = $attr{'build'} if $attr{'build'}; } sub primary_handle_checksum { my ($h, $c, $p, $el) = @_; my $data = $c->[0]->[4]; my $type = lc(delete($data->{$h->{'_tag'}}) || ''); $type = 'sha1' if $type eq 'sha'; if ($type eq 'md5' || $type eq 'sha1' || $type eq 'sha256' || $type eq 'sha512') { $data->{$h->{'_tag'}} = "$type:$c->[-1]->[2]" if defined $c->[-1]->[2]; } } sub primary_handle_file_end { my ($h, $c, $p, $el) = @_; primary_handle_dep($h, $c, $p, $el, 'name', $c->[-1]->[2]); } my %flagmap = ( EQ => '=', LE => '<=', GE => '>=', GT => '>', LT => '<', NE => '!=' ); sub primary_handle_dep { my ($h, $c, $p, $el, %attr) = @_; my $dep = $attr{'name'}; return if $dep =~ /^rpmlib\(/; if(exists $attr{'flags'}) { my $evr = $attr{'ver'}; return unless defined($evr) && exists($flagmap{$attr{'flags'}}); $evr = "$attr{'epoch'}:$evr" if $attr{'epoch'}; $evr .= "-$attr{'rel'}" if defined $attr{'rel'}; $dep .= " $flagmap{$attr{'flags'}} $evr"; } $dep = Build::Rpm::testcaseformat($dep) if ($dep =~ /^\(/) && ($c->[0]->[5] || {})->{'testcaseformat'}; my $data = $c->[0]->[4]; push @{$data->{$h->{'_tag'}}}, $dep; } sub primary_add_result { my ($h, $c, $p, $el) = @_; my $options = $c->[0]->[5] || {}; my $data = $c->[0]->[4]; if ($options->{'addselfprovides'} && defined($data->{'name'}) && defined($data->{'version'})) { if (($data->{'arch'} || '') ne 'src' && ($data->{'arch'} || '') ne 'nosrc') { my $evr = $data->{'version'}; $evr = "$data->{'epoch'}:$evr" if $data->{'epoch'}; $evr = "$evr-$data->{'release'}" if defined $data->{'release'}; my $s = "$data->{'name'} = $evr"; push @{$data->{'provides'}}, $s unless grep {$_ eq $s} @{$data->{'provides'} || []}; } } delete $data->{'checksum'} unless $options->{'withchecksum'}; delete $data->{'license'} unless $options->{'withlicense'}; delete $data->{'vendor'} unless $options->{'withvendor'}; return generic_add_result(@_); } sub parse_repomd { return generic_parse($repomdparser, @_); } sub parse { return generic_parse($primaryparser, @_); } 1; 07070100000017000081a400000000000000000000000163e504b900000d2a000000000000000000000000000000000000001400000000Build/SimpleJSON.pm################################################################ # # Copyright (c) 2018 SUSE Linux Products GmbH # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program (see the file COPYING); if not, write to the # Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA # ################################################################ package Build::SimpleJSON; use strict; sub unparse_keys { my ($d, $order, $keepspecial) = @_; my @k = sort keys %$d; if (!$keepspecial) { @k = grep {$_ ne '_start' && $_ ne '_end' && $_ ne '_order' && $_ ne '_type'} @k; $order = $d->{'_order'} if $d->{'_order'}; } return @k unless $order; my %k = map {$_ => 1} @k; my @ko; for (@$order) { push @ko, $_ if delete $k{$_}; } return (@ko, grep {$k{$_}} @k); } my %specialescapes = ( '"' => '\\"', '\\' => '\\\\', '/' => '\\/', "\b" => '\\b', "\f" => '\\f', "\n" => '\\n', "\r" => '\\r', "\t" => '\\t', ); sub unparse_string { my ($d) = @_; $d =~ s/([\"\\\000-\037])/$specialescapes{$1} || sprintf('\\u%04d', ord($1))/ge; return "\"$d\""; } sub unparse_bool { my ($d) = @_; return $d ? 'true' : 'false'; } sub unparse_number { my ($d) = @_; return sprintf("%.f", $d) if $d == int($d); return sprintf("%g", $d); } sub unparse { my ($d, %opts) = @_; my $r = ''; my $template = delete $opts{'template'}; $opts{'_type'} ||= $template if $template && !ref($template); undef $template unless ref($template) eq 'HASH'; if (ref($d) eq 'ARRAY') { return '[]' unless @$d; $opts{'template'} = $template if $template; my $indent = $opts{'ugly'} ? '' : $opts{'indent'} || ''; my $nl = $opts{'ugly'} ? '' : "\n"; my $sp = $opts{'ugly'} ? '' : " "; my $first = 0; for my $dd (@$d) { $r .= ",$nl" if $first++; $r .= "$indent$sp$sp$sp".unparse($dd, %opts, 'indent' => " $indent"); } return "\[$nl$r$nl$indent\]"; } if (ref($d) eq 'HASH') { my $keepspecial = $opts{'keepspecial'}; my @k = unparse_keys($d, $template ? $template->{'_order'} : undef, $keepspecial); return '{}' unless @k; my $indent = $opts{'ugly'} ? '' : $opts{'indent'} || ''; my $nl = $opts{'ugly'} ? '' : "\n"; my $sp = $opts{'ugly'} ? '' : " "; my $first = 0; for my $k (@k) { $opts{'template'} = $template->{$k} || $template->{'*'} if $template; $r .= ",$nl" if $first++; my $dd = $d->{$k}; my $type = $keepspecial ? undef : $d->{'_type'}; $r .= "$indent$sp$sp$sp".unparse_string($k)."$sp:$sp".unparse($dd, %opts, 'indent' => " $indent", '_type' => ($type || {})->{$k}); } return "\{$nl$r$nl$indent\}"; } return 'null' unless defined $d; my $type = $opts{'_type'} || ''; return unparse_bool($d) if $type eq 'bool'; return unparse_number($d) if $type eq 'number'; return unparse_string($d); } 1; 07070100000018000081a400000000000000000000000163e504b9000013b6000000000000000000000000000000000000001300000000Build/SimpleXML.pm################################################################ # # Copyright (c) 1995-2016 SUSE Linux Products GmbH # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program (see the file COPYING); if not, write to the # Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA # ################################################################ package Build::SimpleXML; use strict; # very simple xml parser, just good enough to parse kiwi and _service files... # can't use standard XML parsers, unfortunatelly, as the build script # must not rely on external libraries # sub parse { my ($xml, %opts) = @_; my $record = $opts{'record'}; my $order = $opts{'order'}; my @nodestack; my $node = {}; my $c = ''; my $xmllen = length($xml); $xml =~ s/^\s*\<\?.*?\?\>//s; while ($xml =~ /^(.*?)\</s) { if ($1 ne '') { $c .= $1; $xml = substr($xml, length($1)); } if (substr($xml, 0, 4) eq '<!--') { die("bad xml, missing end of comment\n") unless $xml =~ s/.*?-->//s; next; } my $elstart = length($xml); die("bad xml\n") unless $xml =~ /(.*?\>)/s; my $tag = $1; $xml = substr($xml, length($tag)); my $mode = 0; if ($tag =~ s/^\<\///s) { chop $tag; $mode = 1; # end } elsif ($tag =~ s/\/\>$//s) { $mode = 2; # start & end $tag = substr($tag, 1); } else { $tag = substr($tag, 1); chop $tag; } my @tag = split(/(=(?:\"[^\"]*\"|\'[^\']*\'|[^\"\s]*))?\s+/, "$tag "); $tag = shift @tag; shift @tag; push @tag, undef if @tag & 1; my %atts = @tag; for (values %atts) { next unless defined $_; s/^=\"([^\"]*)\"$/=$1/s or s/^=\'([^\']*)\'$/=$1/s; s/^=//s; s/</</g; s/>/>/g; s/&/&/g; s/'/\'/g; s/"/\"/g; } if ($mode == 0 || $mode == 2) { my $n = {}; if ($record) { $n->{'_start'} = $xmllen - $elstart; $n->{'_end'} = $xmllen - length($xml) if $mode == 2; } if ($order) { push @{$node->{'_order'}}, $tag; push @{$n->{'_order'}}, (splice(@tag, 0, 2))[0] while @tag; } push @{$node->{$tag}}, $n; $n->{$_} = $atts{$_} for sort keys %atts; if ($mode == 0) { push @nodestack, [ $tag, $node, $c ]; $c = ''; $node = $n; } } else { die("element '$tag' closes without open\n") unless @nodestack; die("element '$tag' closes, but I expected '$nodestack[-1]->[0]'\n") unless $nodestack[-1]->[0] eq $tag; if (!$opts{'notrim'}) { $c =~ s/^\s*//s; $c =~ s/\s*$//s; $c = undef if $c eq ''; } $node->{'_content'} = $c if defined $c; $node->{'_end'} = $xmllen - length($xml) if $record; $node = $nodestack[-1]->[1]; $c = $nodestack[-1]->[2]; pop @nodestack; } } $c .= $xml; if (!$opts{'notrim'}) { $c =~ s/^\s*//s; $c =~ s/\s*$//s; $c = undef if $c eq ''; } $node->{'_content'} = $c if defined $c; return $node; } sub unparse_keys { my ($d) = @_; my @k = grep {$_ ne '_start' && $_ ne '_end' && $_ ne '_order' && $_ ne '_content'} sort keys %$d; return @k unless $d->{'_order'}; my %k = map {$_ => 1} @k; my @ko; for (@{$d->{'_order'}}) { push @ko, $_ if delete $k{$_}; } return (@ko, grep {$k{$_}} @k); } sub unparse_escape { my ($d) = @_; $d =~ s/&/&/sg; $d =~ s/</</sg; $d =~ s/>/>/sg; $d =~ s/"/"/sg; return $d; } sub unparse { my ($d, %opts) = @_; my $r = ''; my $indent = $opts{'ugly'} ? '' : $opts{'indent'} || ''; my $nl = $opts{'ugly'} ? '' : "\n"; my @k = unparse_keys($d); my @e = grep {ref($d->{$_}) ne ''} @k; for my $e (@e) { my $en = unparse_escape($e); my $de = $d->{$e}; $de = [ $de ] unless ref($de) eq 'ARRAY'; for my $se (@$de) { if (defined($se) && ref($se) eq '') { $r .= "$indent<$en>".unparse_escape($se)."</$en>$nl"; next; } if (!$se || !%$se) { $r .= "$indent<$en/>$nl"; next; } my @sk = unparse_keys($se); my @sa = grep {ref($se->{$_}) eq ''} @sk; my @se = grep {ref($se->{$_}) ne ''} @sk; $r .= "$indent<$en"; for my $sa (@sa) { $r .= " ".unparse_escape($sa); $r .= '="'.unparse_escape($se->{$sa}).'"' if defined $se->{$sa}; } $r .= ">"; $r .= unparse_escape($se->{'_content'}) if defined $se->{'_content'}; $r .= $nl . unparse($se, %opts, 'indent' => " $indent") . "$indent" if @se; $r .= "</$en>$nl"; } } return $r; } 1; 07070100000019000081a400000000000000000000000163e504b9000019c5000000000000000000000000000000000000001400000000Build/SimpleYAML.pm################################################################ # # Copyright (c) 2021 SUSE Linux Products GmbH # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program (see the file COPYING); if not, write to the # Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA # ################################################################ package Build::SimpleYAML; use strict; use Scalar::Util; sub unparse_keys { my ($d, $order) = @_; my @k = grep {$_ ne '_start' && $_ ne '_end' && $_ ne '_order' && $_ ne '_type'} sort keys %$d; return @k unless $d->{'_order'} || $order; my %k = map {$_ => 1} @k; my @ko; for (@{$d->{'_order'} || $order}) { push @ko, $_ if delete $k{$_}; } return (@ko, grep {$k{$_}} @k); } my %specialescapes = ( "\0" => '\\0', "\a" => '\\a', "\b" => '\\b', "\t" => '\\t', "\n" => '\\n', "\013" => '\\v', "\f" => '\\f', "\r" => '\\r', "\e" => '\\e', "\x85" => '\\N', ); sub unparse_string { my ($d, $inline) = @_; return "''" unless length $d; return "\"$d\"" if Scalar::Util::looks_like_number($d); if ($d =~ /[\x00-\x1f\x7f-\x9f\']/) { $d =~ s/\\/\\\\/g; $d =~ s/\"/\\\"/g; $d =~ s/([\x00-\x1f\x7f-\x9f])/$specialescapes{$1} || '\x'.sprintf("%X",ord($1))/ge; return "\"$d\""; } elsif ($d =~ /^[\!\&*{}[]|>@`"'#%, ]/s) { return "'$d'"; } elsif ($inline && $d =~ /[,\[\]\{\}]/) { return "'$d'"; } elsif ($d =~ /: / || $d =~ / #/ || $d =~ /[: \t]\z/) { return "'$d'"; } elsif ($d eq '~' || $d eq 'null' || $d eq 'true' || $d eq 'false' && $d =~ /^(?:---|\.\.\.)/s) { return "'$d'"; } elsif ($d =~ /^[-?:](?:\s|\z)/s) { return "'$d'"; } else { return $d; } } sub unparse_literal { my ($d, $indent) = @_; return unparse_string($d) if !defined($d) || $d eq '' || $d =~ /[\x00-\x09\x0b-\x1f\x7f-\x9f]/; my @lines = split("\n", $d, -1); return "''" unless @lines; my $r = '|'; my @nonempty = grep {$_ ne ''} @lines; $r .= '2' if @nonempty && $nonempty[0] =~ /^ /; if ($lines[-1] ne '') { $r .= '-'; } else { pop @lines; $r .= '+' if @lines && $lines[-1] eq ''; } $r .= $_ ne '' ? "\n$indent$_" : "\n" for @lines; return $r; } sub unparse_folded { my ($d, $indent) = @_; return unparse_string($d) if !defined($d) || $d eq '' || $d =~ /[\x00-\x09\x0b-\x1f\x7f-\x9f]/; my @lines = split("\n", $d, -1); return "''" unless @lines; my $r = '>'; my @nonempty = grep {$_ ne ''} @lines; $r .= '2' if @nonempty && $nonempty[0] =~ /^ /; if ($lines[-1] ne '') { $r .= '-'; } else { pop @lines; $r .= '+' if @lines && $lines[-1] eq ''; } my $neednl; my $ll = 78 - length($indent); $ll = 40 if $ll < 40; for (splice(@lines)) { if ($_ =~ /^ /) { push @lines, $_; $neednl = 0; next; } push @lines, '' if $neednl; while (length($_) > $ll && (/^(.{1,$ll}[^ ]) [^ ]/s || /^(..*?[^ ]) [^ ]/s)) { push @lines, $1; $_ = substr($_, length($1) + 1); } push @lines, $_; $neednl = 1; } $r .= $_ ne '' ? "\n$indent$_" : "\n" for @lines; return $r; } sub unparse_bool { my ($d) = @_; return $d ? 'true' : 'false'; } sub unparse_number { my ($d) = @_; return $1 if $d =~ /\A0*([1-9][0-9]*)\z/s; return sprintf("%.f", $d) if $d == int($d); return sprintf("%g", $d); } sub unparse { my ($d, %opts) = @_; return "---\n".unparse($d, %opts, 'noheader' => 1)."\n...\n" unless $opts{'noheader'}; my $r = ''; my $template = delete $opts{'template'}; $opts{'_type'} ||= $template if $template && !ref($template); undef $template unless ref($template) eq 'HASH'; if (ref($d) eq 'ARRAY') { return '[]' unless @$d; $opts{'template'} = $template if $template; $opts{'inline'} = 1 if $opts{'_type'} && $opts{'_type'} =~ s/^inline_?//; if ($opts{'inline'}) { my $first = 0; for my $dd (@$d) { $r .= ", " if $first++; $r .= unparse($dd, %opts); } return "\[$r\]"; } my $indent = $opts{'indent'} || ''; my $first = 0; for my $dd (@$d) { $r .= "\n$indent" if $first++; $r .= "- ".unparse($dd, %opts, 'indent' => " $indent"); } return $r; } if (ref($d) eq 'HASH') { my @k = unparse_keys($d, $template ? $template->{'_order'} : undef); return '{}' unless @k; $opts{'inline'} = 1 if $opts{'_type'} && $opts{'_type'} =~ s/^inline_?//; if ($opts{'inline'}) { my $first = 0; for my $k (@k) { $opts{'template'} = $template->{$k} || $template->{'*'} if $template; $r .= ", " if $first++; my $dd = $d->{$k}; my $type = ($d->{'_type'} || {})->{$k}; $r .= unparse_string($k).": ".unparse($dd, %opts, '_type' => $type); } return "\{$r\}"; } my $indent = $opts{'indent'} || ''; my $first = 0; for my $k (@k) { $opts{'template'} = $template->{$k} || $template->{'*'} if $template; my $dd = $d->{$k}; my $type = ($d->{'_type'} || {})->{$k} || ($d->{'_type'} || {})->{'*'}; $type = $opts{'template'} if !$type && $opts{'template'} && !ref($opts{'template'}); $r .= "\n$indent" if $first++; $r .= unparse_string($k).":"; if (ref($dd) eq 'ARRAY' && @$dd && !($type && $type =~ /^inline_?/)) { $r .= "\n$indent"; $r .= unparse($dd, %opts, 'indent' => "$indent", '_type' => $type); } elsif (ref($dd) eq 'HASH' && %$dd && !($type && $type =~ /^inline_?/)) { $r .= "\n$indent "; $r .= unparse($dd, %opts, 'indent' => " $indent", '_type' => $type); } else { $r .= " ".unparse($dd, %opts, 'indent' => " $indent", '_type' => $type); } } return $r; } my $type = $opts{'_type'} || ''; return '~' unless defined $d; return unparse_bool($d) if $type eq 'bool'; return unparse_number($d) if $type eq 'number'; return unparse_literal($d, $opts{'indent'} || '') if $type eq 'literal' && !$opts{'inline'}; return unparse_folded($d, $opts{'indent'} || '') if $type eq 'folded' && !$opts{'inline'}; return unparse_string($d, $opts{'inline'}); } 1; 0707010000001a000081a400000000000000000000000163e504b900000a4a000000000000000000000000000000000000001300000000Build/Snapcraft.pm################################################################ # # Copyright (c) 1995-2014 SUSE Linux Products GmbH # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program (see the file COPYING); if not, write to the # Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA # ################################################################ package Build::Snapcraft; use strict; use Build::Deb; eval { require YAML::XS; $YAML::XS::LoadBlessed = 0; }; *YAML::XS::LoadFile = sub {die("YAML::XS is not available\n")} unless defined &YAML::XS::LoadFile; sub parse { my ($cf, $fn) = @_; my $yaml; eval {$yaml = YAML::XS::LoadFile($fn);}; return {'error' => "Failed to parse yaml file"} unless $yaml; my $ret = {}; $ret->{'name'} = $yaml->{'name'}; $ret->{'version'} = $yaml->{'version'}; $ret->{'epoch'} = $yaml->{'epoch'} if $yaml->{'epoch'}; # how should we report the built apps? my @packdeps; for my $p (@{$yaml->{'stage-packages'} || []}) { push @packdeps, $p; } for my $p (@{$yaml->{'build-packages'} || []}) { push @packdeps, $p; } for my $p (@{$yaml->{'after'} || []}) { push @packdeps, "snapcraft-part:$p"; } for my $key (sort keys(%{$yaml->{'parts'} || {}})) { my $part = $yaml->{'parts'}->{$key}; push @packdeps, "snapcraft-plugin:$part->{plugin}" if defined $part->{plugin}; for my $p (@{$part->{'stage-packages'} || []}) { push @packdeps, $p; } for my $p (@{$part->{'build-packages'} || []}) { push @packdeps, $p; } for my $p (@{$part->{'after'} || []}) { next if $yaml->{'parts'}->{$p}; push @packdeps, "build-snapcraft-part-$p"; } } my %exclarchs; for my $arch (@{$yaml->{architectures} || []}) { my @obsarchs = Build::Deb::obsarch($arch); push @obsarchs, $arch unless @obsarchs; $exclarchs{$_} = 1 for @obsarchs; } $ret->{'exclarch'} = [ sort keys %exclarchs ] if %exclarchs; # $ret->{'badarch'} = $badarch if defined $badarch; $ret->{'deps'} = \@packdeps; # $ret->{'prereqs'} = \@prereqs if @prereqs; # $ret->{'configdependent'} = 1 if $ifdeps; return $ret; } 1; 0707010000001b000081a400000000000000000000000163e504b900001113000000000000000000000000000000000000001200000000Build/Susetags.pm################################################################ # # Copyright (c) 1995-2014 SUSE Linux Products GmbH # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program (see the file COPYING); if not, write to the # Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA # ################################################################ package Build::Susetags; use strict; use Build::Rpm; # compatibility to old OBS code sub parse_obs_compat { my ($file, undef, undef, @arches) = @_; $file = "$file.gz" if ! -e $file && -e "$file.gz"; my $pkgs = {}; parse($file, sub { my ($data) = @_; my $medium = delete($data->{'medium'}); my $loc = delete($data->{'location'}); if (defined($medium) && defined($loc)) { $loc =~ s/^\Q$data->{'arch'}\E\///; $data->{'path'} = "$medium $loc"; } return unless !@arches || grep { /$data->{'arch'}/ } @arches; $pkgs->{"$data->{'name'}-$data->{'version'}-$data->{'release'}-$data->{'arch'}"} = $data; }, 'addselfprovides' => 1); return $pkgs; } my %tmap = ( 'Pkg' => '', 'Loc' => 'location', 'Src' => 'source', 'Prv' => 'provides', 'Req' => 'requires', 'Con' => 'conflicts', 'Obs' => 'obsoletes', 'Rec' => 'recommends', 'Sug' => 'suggests', 'Sup' => 'supplements', 'Enh' => 'enhances', 'Tim' => 'buildtime', 'Cks' => 'checksum', ); sub addpkg { my ($res, $data, $options) = @_; # fixup location and source if (exists($data->{'location'})) { my ($medium, $dir, $loc) = split(' ', $data->{'location'}, 3); $data->{'medium'} = $medium; $data->{'location'} = defined($loc) ? "$dir/$loc" : "$data->{'arch'}/$dir"; } $data->{'source'} =~ s/\s.*// if exists $data->{'source'}; if ($options->{'addselfprovides'} && defined($data->{'name'}) && defined($data->{'version'})) { if (($data->{'arch'} || '') ne 'src' && ($data->{'arch'} || '') ne 'nosrc') { my $evr = $data->{'version'}; $evr = "$data->{'epoch'}:$evr" if $data->{'epoch'}; $evr = "$evr-$data->{'release'}" if defined $data->{'release'}; my $s = "$data->{'name'} = $evr"; push @{$data->{'provides'}}, $s unless grep {$_ eq $s} @{$data->{'provides'} || []}; } } if ($options->{'withchecksum'} && $data->{'checksum'}) { my ($ctype, $csum) = split(' ', delete($data->{'checksum'})); $ctype = lc($ctype || ''); $data->{'checksum'} = "$ctype:$csum" if $csum && ($ctype eq 'md5' || $ctype eq 'sha1' || $ctype eq 'sha256' || $ctype eq 'sha512'); } if (ref($res) eq 'CODE') { $res->($data); } else { push @$res, $data; } } sub parse { return parse_obs_compat(@_) if @_ > 2 && !defined $_[2]; my ($in, $res, %options) = @_; $res ||= []; my $fd; if (ref($in)) { $fd = $in; } else { if ($in =~ /\.gz$/) { open($fd, '-|', "gzip", "-dc", $in) || die("$in: $!\n"); } else { open($fd, '<', $in) || die("$in: $!\n"); } } my $cur; my @tmap = sort keys %tmap; @tmap = grep {$_ ne 'Cks'} @tmap unless $options{'withchecksum'}; my $r = join('|', @tmap); $r = qr/^([\+=])($r):\s*(.*)/; while (<$fd>) { chomp; next unless /$r/; my ($multi, $tag, $data) = ($1, $2, $3); if ($multi eq '+') { while (<$fd>) { chomp; last if /^-\Q$tag\E/; next if $tag eq 'Req' && /^rpmlib\(/; $_ = Build::Rpm::testcaseformat($_) if /^\(/ && $options{'testcaseformat'}; push @{$cur->{$tmap{$tag}}}, $_; } } elsif ($tag eq 'Pkg') { addpkg($res, $cur, \%options) if $cur; $cur = {}; ($cur->{'name'}, $cur->{'version'}, $cur->{'release'}, $cur->{'arch'}) = split(' ', $data); $cur->{'epoch'} = $1 if $cur->{'version'} =~ s/^(\d+)://; } else { $cur->{$tmap{$tag}} = $data; } } addpkg($res, $cur, \%options) if $cur; if (!ref($in)) { close($fd) || die("close $in: $!\n"); } return $res; } 1; 0707010000001c000081a400000000000000000000000163e504b9000009a8000000000000000000000000000000000000000e00000000Build/Zypp.pm################################################################ # # Copyright (c) 1995-2014 SUSE Linux Products GmbH # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program (see the file COPYING); if not, write to the # Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA # ################################################################ package Build::Zypp; use strict; our $root = ''; sub parsecfg { my ($repocfg, $reponame, $allrepos) = @_; local *REPO; open(REPO, '<', "$root/etc/zypp/repos.d/$repocfg") or return undef; my $name; my $repo = {}; while (<REPO>) { chomp; next if /^\s*#/; s/\s+$//; if (/^\[(.+)\]/) { if ($allrepos && defined($name)) { $repo->{'description'} = $repo->{'name'} if defined $repo->{'name'}; $repo->{'name'} = $name; push @$allrepos, $repo; undef $name; $repo = {}; } last if defined $name; $name = $1 if !defined($reponame) || $reponame eq $1; } elsif (defined($name)) { my ($key, $value) = split(/=/, $_, 2); $repo->{$key} = $value if defined $key; } } close(REPO); return undef unless defined $name; $repo->{'description'} = $repo->{'name'} if defined $repo->{'name'}; $repo->{'name'} = $name; push @$allrepos, $repo if $allrepos; return $repo; } sub repofiles { local *D; return () unless opendir(D, "/etc/zypp/repos.d"); my @r = grep {!/^\./ && /.repo$/} readdir(D); closedir D; return sort(@r); } sub parseallrepos { my @r; for my $r (repofiles()) { parsecfg($r, undef, \@r); } return @r; } sub parserepo($) { my ($reponame) = @_; # first try matching .repo file if (-e "$root/etc/zypp/repos.d/$reponame.repo") { my $repo = parsecfg("$reponame.repo", $reponame); return $repo if $repo; } # then try all repo files for my $r (repofiles()) { my $repo = parsecfg($r, $reponame); return $repo if $repo; } die("could not find repo '$reponame'\n"); } 1; # vim: sw=2 0707010000001d000041ed00000000000000000000000163e504b900000000000000000000000000000000000000000000000600000000Build07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000b00000000TRAILER!!!
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor