diff options
author | Modestas Vainius <modestas@vainius.eu> | 2010-03-16 00:32:16 +0200 |
---|---|---|
committer | Modestas Vainius <modestas@vainius.eu> | 2010-03-16 00:32:16 +0200 |
commit | edd5387961a5b01efba9b63ec3d9187462c246ef (patch) | |
tree | b3437ec2c7fe2ef3c7a0198fffd3bb2fca66973b /perllib | |
parent | d750b7bdd629cbec8560d8fbb0d133e5982c7e79 (diff) | |
download | pkg-kde-tools-edd5387961a5b01efba9b63ec3d9187462c246ef.tar.gz |
Reorganize source package directory structure.
- get rid of symbolshelper, debhelper, vcs subdirectires. Move perl modules
to perllib subdirectory and the rest to the top of the source tree;
- unify how datalibdir is found and exported in the Debian::PkgKde module
({find,setup}_datalibdir()). Make pkgkde-gensymbols and
pkgkde-symbolshelper use these new subroutines;
- change datalib path in pkgkde-deb2symbols;
- rewrite Makefile for reorganized source tree.
Diffstat (limited to 'perllib')
-rw-r--r-- | perllib/Debian/Debhelper/Buildsystem/kde.pm | 63 | ||||
-rw-r--r-- | perllib/Debian/Debhelper/Sequence/kde.pm | 44 | ||||
-rw-r--r-- | perllib/Debian/Debhelper/Sequence/pkgkde_symbolshelper.pm | 8 | ||||
-rw-r--r-- | perllib/Debian/Debhelper/Sequence/sodeps.pm | 3 | ||||
-rw-r--r-- | perllib/Debian/PkgKde.pm | 101 | ||||
-rw-r--r-- | perllib/Debian/PkgKde/SymbolsHelper/CompileTest.pm | 85 | ||||
-rw-r--r-- | perllib/Debian/PkgKde/SymbolsHelper/Patching.pm | 272 | ||||
-rw-r--r-- | perllib/Debian/PkgKde/SymbolsHelper/String.pm | 101 | ||||
-rw-r--r-- | perllib/Debian/PkgKde/SymbolsHelper/Subst.pm | 77 | ||||
-rw-r--r-- | perllib/Debian/PkgKde/SymbolsHelper/Substs.pm | 57 | ||||
-rw-r--r-- | perllib/Debian/PkgKde/SymbolsHelper/Substs/TypeSubst.pm | 399 | ||||
-rw-r--r-- | perllib/Debian/PkgKde/SymbolsHelper/Substs/VirtTable.pm | 112 | ||||
-rw-r--r-- | perllib/Debian/PkgKde/SymbolsHelper/Symbol.pm | 384 | ||||
-rw-r--r-- | perllib/Debian/PkgKde/SymbolsHelper/SymbolFile.pm | 320 | ||||
-rw-r--r-- | perllib/Debian/PkgKde/SymbolsHelper/SymbolFileCollection.pm | 835 |
15 files changed, 2861 insertions, 0 deletions
diff --git a/perllib/Debian/Debhelper/Buildsystem/kde.pm b/perllib/Debian/Debhelper/Buildsystem/kde.pm new file mode 100644 index 0000000..c88961b --- /dev/null +++ b/perllib/Debian/Debhelper/Buildsystem/kde.pm @@ -0,0 +1,63 @@ +# A debhelper build system class for building KDE 4 packages. +# It is based on cmake class but passes KDE 4 flags by default. +# +# Copyright: © 2009 Modestas Vainius +# License: GPL-2+ + +package Debian::Debhelper::Buildsystem::kde; + +use strict; +use warnings; +use Debian::Debhelper::Dh_Lib qw(error); +use base 'Debian::Debhelper::Buildsystem::cmake'; + +sub DESCRIPTION { + "CMake with KDE 4 flags" +} + +sub KDE4_FLAGS_FILE { + my $file = "kde4_flags"; + if (! -r $file) { + $file = "/usr/share/pkg-kde-tools/lib/kde4_flags"; + } + if (! -r $file) { + error "kde4_flags file could not be found"; + } + return $file; +} + +# Use shell for parsing contents of the kde4_flags file +sub get_kde4_flags { + my $this=shift; + my $file = KDE4_FLAGS_FILE; + my ($escaped_flags, @escaped_flags); + my $flags; + + # Read escaped flags from the file + open(KDE4_FLAGS, "<", $file) || error("unable to open KDE 4 flags file: $file"); + @escaped_flags = <KDE4_FLAGS>; + chop @escaped_flags; + $escaped_flags = join(" ", @escaped_flags); + close KDE4_FLAGS; + + # Unescape flags using shell + $flags = `$^X -w -Mstrict -e 'print join("\\x1e", \@ARGV);' -- $escaped_flags`; + return split("\x1e", $flags); +} + +sub configure { + my $this=shift; + my @flags = $this->get_kde4_flags(); + + # Skip RPATH if kdelibs5-dev is older than 4:4.4.0 + my $kdever = `dpkg-query -f='\${Version}\n' -W kdelibs5-dev 2>/dev/null`; + if ($kdever && + system("dpkg", "--compare-versions", $kdever, "lt", "4:4.4.0") == 0) + { + push @flags, "-DCMAKE_SKIP_RPATH:BOOL=ON"; + } + + return $this->SUPER::configure(@flags, @_); +} + +1; diff --git a/perllib/Debian/Debhelper/Sequence/kde.pm b/perllib/Debian/Debhelper/Sequence/kde.pm new file mode 100644 index 0000000..ca25d78 --- /dev/null +++ b/perllib/Debian/Debhelper/Sequence/kde.pm @@ -0,0 +1,44 @@ +{ + package Debian::Debhelper::Sequence::kde; + use Debian::Debhelper::Dh_Version; + use Debian::Debhelper::Dh_Lib qw(error); + + sub ensure_debhelper_version { + my @v = split(/\./, $Debian::Debhelper::Dh_Version::version); + if ($v[0] > $_[0]) { + return 1; + } + elsif ($v[0] == $_[0]) { + if ($v[1] > $_[1]) { + return 1; + } + elsif ($v[1] == $_[1]) { + return $1 >= $_[2] if ($v[2] =~ /^(\d+)/); + } + } + return 0; + } + unless (ensure_debhelper_version(7, 3, 16)) { + error "debhelper addon 'kde' requires debhelper 7.3.16 or later"; + } + + 1; +} + +# Build with kde buildsystem by default +add_command_options("dh_auto_configure", "--buildsystem=kde"); +add_command_options("dh_auto_build", "--buildsystem=kde"); +add_command_options("dh_auto_test", "--buildsystem=kde"); +add_command_options("dh_auto_install", "--buildsystem=kde"); +add_command_options("dh_auto_clean", "--buildsystem=kde"); + +# Omit usr/lib/kde4 from dh_makeshlibs by default +add_command_options("dh_makeshlibs", "-Xusr/lib/kde4/"); + +# Exclude kde documentation from dh_compress by default +add_command_options("dh_compress", + qw(-X.dcl -X.docbook -X-license -X.tag -X.sty -X.el)); + +insert_after("dh_install", "dh_movelibkdeinit"); + +1; diff --git a/perllib/Debian/Debhelper/Sequence/pkgkde_symbolshelper.pm b/perllib/Debian/Debhelper/Sequence/pkgkde_symbolshelper.pm new file mode 100644 index 0000000..3577bc9 --- /dev/null +++ b/perllib/Debian/Debhelper/Sequence/pkgkde_symbolshelper.pm @@ -0,0 +1,8 @@ +use constant PKGKDE_BINDIR => '/usr/share/pkg-kde-tools/bin'; + +# Add /usr/share/pkg-kde-tools/bin to $PATH +if (! grep { PKGKDE_BINDIR eq $_ } split(":", $ENV{PATH})) { + $ENV{PATH} = PKGKDE_BINDIR . ":" . $ENV{PATH}; +} + +1; diff --git a/perllib/Debian/Debhelper/Sequence/sodeps.pm b/perllib/Debian/Debhelper/Sequence/sodeps.pm new file mode 100644 index 0000000..9ae6a03 --- /dev/null +++ b/perllib/Debian/Debhelper/Sequence/sodeps.pm @@ -0,0 +1,3 @@ +insert_after("dh_shlibdeps", "dh_sodeps"); + +1; diff --git a/perllib/Debian/PkgKde.pm b/perllib/Debian/PkgKde.pm new file mode 100644 index 0000000..b9e8b2f --- /dev/null +++ b/perllib/Debian/PkgKde.pm @@ -0,0 +1,101 @@ +package Debian::PkgKde; + +use base qw(Exporter); +our @EXPORT = qw(get_program_name + printmsg info warning errormsg error syserr usageerr); +our @EXPORT_OK = qw(find_datalibdir setup_datalibdir DATALIBDIR); + +# Determine datalib for current script. It depends on the context the script +# was executed from. +use constant DATALIBDIR => '/usr/share/pkg-kde-tools/lib'; + +sub find_datalibdir { + my @hintfiles = @_; + my @dirs; + if ($0 =~ m@^(.+)/[^/]+$@) { + push @dirs, "$1/datalib"; + } + push @dirs, DATALIBDIR; + + # Verify if the dir and hint files exist + my $founddir; + foreach my $dir (@dirs) { + my $ok; + if ($dir && -d $dir) { + $ok = 1; + foreach my $hint (@hintfiles) { + unless (-e "$dir/$hint") { + $ok = 0; + last; + } + } + } + if ($ok) { + $founddir = $dir; + last; + } + } + + return $founddir; +} + +# Add DATALIBDIR to @INC if the script is NOT being run from the source tree. +sub setup_datalibdir { + my $dir = find_datalibdir(@_); + if ($dir) { + unshift @INC, DATALIBDIR if $dir eq DATALIBDIR; + } else { + error("unable to locate pkg-kde-tools library directory"); + } + return $dir; +} + +{ + my $progname; + sub get_program_name { + unless (defined $progname) { + $progname = ($0 =~ m,/([^/]+)$,) ? $1 : $0; + } + return $progname; + } +} + +sub format_message { + my $type = shift; + my $format = shift; + + my $msg = sprintf($format, @_); + return ((defined $type) ? + get_program_name() . ": $type: " : "") . "$msg\n"; +} + +sub printmsg { + print STDERR format_message(undef, @_); +} + +sub info { + print STDERR format_message("info", @_); +} + +sub warning { + warn format_message("warning", @_); +} + +sub syserr { + my $msg = shift; + die format_message("error", "$msg: $!", @_); +} + +sub errormsg { + print STDERR format_message("error", @_); +} + +sub error { + die format_message("error", @_); +} + +sub usageerr { + die format_message("usage", @_); +} + +1; diff --git a/perllib/Debian/PkgKde/SymbolsHelper/CompileTest.pm b/perllib/Debian/PkgKde/SymbolsHelper/CompileTest.pm new file mode 100644 index 0000000..b91dc4a --- /dev/null +++ b/perllib/Debian/PkgKde/SymbolsHelper/CompileTest.pm @@ -0,0 +1,85 @@ +# Copyright (C) 2008-2010 Modestas Vainius <modax@debian.org> +# +# 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 3 of the License, 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. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/> + +package Debian::PkgKde::SymbolsHelper::CompileTest; + +use strict; +use warnings; +use File::Temp qw(tempdir); +use File::Spec; +use Dpkg::ErrorHandling; + +sub new { + my ($cls, $compiler, $lib) = @_; + + my $tmpdir = tempdir(); + my $sourcefile = "testcomp"; + my $out = "testcomp"; + my $cmd; + + error("Unable to create a temporary directory for test compilation") unless $tmpdir; + + if ($compiler =~ /gcc/) { + $sourcefile .= ".c"; + } elsif ($compiler =~ /g\+\+/) { + $sourcefile .= ".cpp"; + } else { + error("Unrecognized compiler: $compiler"); + } + $sourcefile = File::Spec::catfile($tmpdir, $sourcefile); + + if ($lib) { + $cmd = "$compiler -shared -fPIC"; + $out .= ".so"; + } else { + $cmd = "$compiler"; + } + $out = File::Spec::catfile($tmpdir, $out); + + my $self = bless { tmpdir => $tmpdir, + sourcefile => $sourcefile, out => $out }, $cls; + $self->set_cmd($cmd); + return $self; +} + +sub set_cmd { + my ($self, $cmd) = @_; + $self->{cmd} = "$cmd $self->{sourcefile} -o $self->{out}"; +} + +sub compile { + my ($self, $sourcecode) = @_; + + open(SOURCE, ">", $self->{sourcefile}) + or error("Unable to open temporary source file for writing: $self->{sourcefile}"); + print SOURCE $sourcecode + or error("Unable to write to temporary source file $self->{sourcefile}"); + close(SOURCE); + + system($self->{cmd}) == 0 or error("Compilation failed: $self->{cmd}"); + return $self->get_output_file(); +} + +sub get_output_file { + my $self = shift; + return (-f $self->{out}) ? $self->{out} : undef; +} + +sub rm { + my $self = shift; + system("rm -rf $self->{tmpdir}") == 0 or error("Unable to delete temporary directory: $self->{tmpdir}"); +} + +1; diff --git a/perllib/Debian/PkgKde/SymbolsHelper/Patching.pm b/perllib/Debian/PkgKde/SymbolsHelper/Patching.pm new file mode 100644 index 0000000..c1ccd12 --- /dev/null +++ b/perllib/Debian/PkgKde/SymbolsHelper/Patching.pm @@ -0,0 +1,272 @@ +# Copyright (C) 2008-2010 Modestas Vainius <modax@debian.org> +# +# 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 3 of the License, 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. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/> + +package Debian::PkgKde::SymbolsHelper::Patching; + +use strict; +use warnings; +use base 'Exporter'; + +use Dpkg::ErrorHandling; + +our @EXPORT = qw(parse_patches_from_handle parse_patches_from_file); + +sub parse_patches_from_handle { + my ($fh) = @_; + my $reparse_line; + my @patches; + my $patch; + + while ($reparse_line || ($_ = <$fh>)) { + $reparse_line = 0; + if (defined $patch) { + if ($patch->has_header()) { + if (m/^@@ /) { + unless ($patch->is_valid()) { + warning("patch '".$patch->get_name()."' hunk is invalid at line $."); + } + $patch->append_line($_); + } elsif (!$patch->is_valid() && m/^[+ -]/) { + # Patch continues + $patch->append_line($_); + } else { + # Patch ended + if ($patch->complete()) { + push @patches, $patch; + } else { + warning("patch '".$patch->get_name()."' is invalid"); + } + $patch = undef; + $reparse_line = 1; + next; + } + } elsif (defined $patch->{source}) { + if (m/^[+]{3}\s+(\S+)/) { + # Found the patch header portion + $patch->set_target($1); + } else { + $patch = undef; + $reparse_line = 1; + } + } + } elsif (m/^[-]{3}\s+(\S+)(?:\s+\(([^_]+)_([^_]+)_([^_]+)\))?/) { + $patch = Debian::PkgKde::SymbolsHelper::Patch->new(); + $patch->set_source($1); + $patch->set_info($2, $3, $4); + } + } + if (defined $patch) { + if ($patch->complete()) { + push @patches, $patch; + } else { + warning("patch '".$patch->get_name()."' is invalid"); + } + } + return @patches; +} + +sub parse_patches_from_file { + my ($filename) = @_; + open(my $fh, "<", $filename) or error("unable to open patch file '$filename'"); + my @ret = parse_patches_from_handle($fh); + close $fh; + return @ret; +} + +package Debian::PkgKde::SymbolsHelper::Patch; + +use strict; +use warnings; + +use Dpkg::ErrorHandling; +use Dpkg::IPC; + +sub new { + my $class = shift; + return bless { + file => undef, + source => undef, + target => undef, + package => undef, + version => undef, + arch => undef, + patch => undef, + hunk_minus => 0, + hunk_plus => 0, + }, $class; +} + +sub set_source { + my ($self, $srcfile) = @_; + $self->{source} = $srcfile; +} + +sub set_info { + my ($self, $package, $version, $arch) = @_; + $self->{package} = $package; + $self->{version} = $version; + $self->{arch} = $arch; +} + +sub get_info { + my $self = shift; + return ( + package => $self->{package}, + version => $self->{version}, + arch => $self->{arch}, + ); +} + +sub has_info { + my $self = shift; + return defined $self->{package}; +} + +sub set_target { + my ($self, $target) = @_; + $self->{target} = $target; +} + +sub has_header { + my $self = shift; + return defined $self->{source} && defined $self->{target}; +} + +sub get_name { + my $self = shift; + if ($self->{source}) { + if ($self->has_info()) { + return sprintf("%s_%s_%s (--- %s)", $self->{package}, + $self->{version}, $self->{arch}, $self->{source}); + } else { + return sprintf("--- %s +++ %s", $self->{source}, $self->{target}); + } + } else { + return "<empty patch>"; + } +} + +sub is_valid { + my $self = shift; + return (defined $self->{hunk_minus} && + $self->{hunk_minus} + $self->{hunk_plus} == 0); +} + +sub open_patch_fh { + my ($self, $mode) = @_; + my $patch = $self->{patch}; + if (!defined $patch) { + my $var; + $patch = $self->{patch} = \$var; + } + open(my $fh, $mode, $patch) + or systemerr("unable to open in-memory patch file"); + return $fh; +} + +sub append_line { + my ($self, $line) = @_; + my $fh = $self->{fh}; + unless (defined $fh) { + $fh = $self->open_patch_fh(">"); + $self->{fh} = $fh; + } + if (defined $self->{hunk_minus}) { + if ($line =~ /^@@\s*-\d+,(\d+)\s+[+]\d+,(\d+)\s*@@/) { + if ($self->{hunk_minus} + $self->{hunk_plus} == 0) { + $self->{hunk_minus} = $1; + $self->{hunk_plus} = $2; + } else { + # Bogus patch + $self->{hunk_minus} = undef; + $self->{hunk_plus} = undef; + } + } elsif ($line =~ /^-/) { + $self->{hunk_minus}--; + } elsif ($line =~ /^\+/) { + $self->{hunk_plus}--; + } elsif ($line =~ /^ /) { + $self->{hunk_minus}--; + $self->{hunk_plus}--; + } else { + warning("patch ignored. Invalid patch line: $line"); + $self->{hunk_minus} = undef; + $self->{hunk_plus} = undef; + } + } + print $fh $line; +} + +sub complete { + my $self = shift; + close $self->{fh}; + delete $self->{fh}; + return $self->is_valid(); +} + +sub output { + my ($self, $outfh, $filename) = @_; + $filename = $self->{target} unless $filename; + + print $outfh "--- ", $filename, "\n"; + print $outfh "+++ ", $filename, "\n"; + + my $infh = $self->open_patch_fh("<"); + while (<$infh>) { + print $outfh $_; + } + close $infh; +} + +sub apply { + my ($self, $filename) = @_; + + my $outfile = File::Temp->new(TEMPLATE => "${filename}_patch.out.XXXXXX"); + my $to_patch_process; + my $pid = spawn(exec => [ "patch", "--posix", "--force", "-r-", "-p0" ], + from_pipe => \$to_patch_process, + to_handle => $outfile, + error_to_handle => $outfile, + wait_child => 0 + ); + my $ret = $self->output($to_patch_process, $filename); + close $to_patch_process; + wait_child($pid, nocheck => 1); + $ret &&= !$?; + if ($ret) { + $self->{applied} = $filename; + } else { + open(my $outputfd, "<", $outfile->filename) + or syserr("unable to reopen temporary file"); + my $output; + while (<$outputfd>) { + $output .= $_; + } + close $outputfd; + chop $output; + $self->{apply_output} = $output; + } + return $ret; +} + +sub is_applied { + my $self = shift; + return $self->{applied}; +} + +sub get_apply_output { + my $self = shift; + return $self->{apply_output}; +} diff --git a/perllib/Debian/PkgKde/SymbolsHelper/String.pm b/perllib/Debian/PkgKde/SymbolsHelper/String.pm new file mode 100644 index 0000000..477f34b --- /dev/null +++ b/perllib/Debian/PkgKde/SymbolsHelper/String.pm @@ -0,0 +1,101 @@ +# Copyright (C) 2008-2010 Modestas Vainius <modax@debian.org> +# +# 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 3 of the License, 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. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/> + +package Debian::PkgKde::SymbolsHelper::String; + +use strict; +use warnings; + +use overload '""' => \&get_string; + +sub new { + my ($class, $str) = @_; + return bless { str => $str }, $class; +} + +sub init_string2_by_re { + my ($self, $str2, $re, $values) = @_; + my @str2 = split(//, $self->get_string()); + my $offset = 0; + while ($str2 =~ m/$re/g) { + my $key = $1; + my $i = pos($str2) - length($&) - $offset; + $str2[$i] = "$&"; + my $count = $i + length($values->{$key}); + for ($i++; $i < $count; $i++) { + $str2[$i] = undef; + } + $offset += length($&) - length($values->{$key}); + } + $self->{str2} = \@str2; +} + +sub substr { + my ($self, $offset, $length, $repl1, $repl2) = @_; + if (defined $repl2 || exists $self->{str2}) { + # If str2 has not been created yet, create it + if (!exists $self->{str2}) { + $self->{str2} = [ split(//, $self->{str}) ]; + } + # Keep offset information intact with $repl1 + my @repl2; + my $edit_str2 = 1; + if (defined $repl2) { + @repl2 = map { undef } split(//, $repl1); + $repl2[0] = $repl2; + } elsif ($length != length($repl1)) { + if (!defined $repl2) { + for (my $i = 0; $i < length($repl1); $i++) { + if ($i < $length) { + push @repl2, $self->{str2}[$offset+$i]; + } else { + push @repl2, undef; + } + } + } + } else { + $edit_str2 = 0; + } + splice @{$self->{str2}}, $offset, $length, @repl2 if $edit_str2; + } + substr($self->{str}, $offset, $length) = $repl1; +} + +sub get_string { + return shift()->{str}; +} + +sub has_string2 { + return exists shift()->{str2}; +} + +sub get_string2_char { + my ($self, $index) = @_; + return $self->{str2}->[$index]; +} + +sub get_string2 { + my $self = shift; + if (defined $self->{str2}) { + my $str = ""; + foreach my $s (@{$self->{str2}}) { + $str .= $s if defined $s; + } + return $str; + } + return $self->get_string(); +} + +1; diff --git a/perllib/Debian/PkgKde/SymbolsHelper/Subst.pm b/perllib/Debian/PkgKde/SymbolsHelper/Subst.pm new file mode 100644 index 0000000..749c0c0 --- /dev/null +++ b/perllib/Debian/PkgKde/SymbolsHelper/Subst.pm @@ -0,0 +1,77 @@ +# Copyright (C) 2008-2010 Modestas Vainius <modax@debian.org> +# +# 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 3 of the License, 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. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/> + +package Debian::PkgKde::SymbolsHelper::Subst; + +use strict; +use warnings; + +sub new { + my ($class, %opts) = @_; + return bless { cache => {}, %opts }, $class; +} + +sub get_name { + my $self = shift; + # Must be overriden +} + +sub _expand { + my ($self, $arch, $val) = @_; + # Must be overriden +} + +# $subst is here in order to support substs with values +sub expand { + my ($self, $arch, $val) = @_; + my $cache = ($val) ? "${arch}__$val" : $arch; + unless (exists $self->{cache}{$cache}) { + $self->{cache}{$cache} = $self->_expand($arch, $val); + } + return $self->{cache}{$cache}; +} + +# Prepare $rawname before detect()/neutralize() +# my ($self, $rawname, $arch) = @_; +sub prep { +} + +# Make the raw symbol name architecture neutral +# my ($self, $rawname) = @_; +sub neutralize { + return undef; +} + +# Hinted neutralize where $hint is an already "detected" +# SymbolsHelper::String +# my ($self, $rawname, $hint) = @_; +sub hinted_neutralize { + return undef; +} + +# Detect if the substitution can be applied to a bunch of +# arch specific raw names. +# my ($self, $rawname, $arch, $arch_rawnames) = @_; +sub detect { + return 0; +} + +# Verifies if the subst is correct at $pos +# my ($self, $pos, $arch_rawnames) = @_; +sub verify_at { + return undef; +} + +1; diff --git a/perllib/Debian/PkgKde/SymbolsHelper/Substs.pm b/perllib/Debian/PkgKde/SymbolsHelper/Substs.pm new file mode 100644 index 0000000..eed306f --- /dev/null +++ b/perllib/Debian/PkgKde/SymbolsHelper/Substs.pm @@ -0,0 +1,57 @@ +# Copyright (C) 2008-2010 Modestas Vainius <modax@debian.org> +# +# 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 3 of the License, 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. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/> + +package Debian::PkgKde::SymbolsHelper::Substs; + +use strict; +use warnings; +use Debian::PkgKde::SymbolsHelper::Substs::VirtTable; +use Debian::PkgKde::SymbolsHelper::Substs::TypeSubst; +use base 'Exporter'; + +our @EXPORT = qw(%SUBSTS @SUBSTS @STANDALONE_SUBSTS @TYPE_SUBSTS @CPP_TYPE_SUBSTS); + +my $NS = 'Debian::PkgKde::SymbolsHelper::Substs'; + +our @STANDALONE_SUBSTS = ( + "${NS}::VirtTable"->new(), +); + +our @TYPE_SUBSTS = ( + "${NS}::TypeSubst::size_t"->new(), + "${NS}::TypeSubst::ssize_t"->new(), + "${NS}::TypeSubst::int64_t"->new(), + "${NS}::TypeSubst::uint64_t"->new(), + "${NS}::TypeSubst::qptrdiff"->new(), + "${NS}::TypeSubst::quintptr"->new(), + "${NS}::TypeSubst::qreal"->new(), +); + +our @CPP_TYPE_SUBSTS; +foreach my $subst (@TYPE_SUBSTS) { + push @CPP_TYPE_SUBSTS, "${NS}::TypeSubst::Cpp"->new($subst); +} + +our @SUBSTS = ( + @STANDALONE_SUBSTS, + @TYPE_SUBSTS, +); + +our %SUBSTS; +foreach my $subst (@SUBSTS, @CPP_TYPE_SUBSTS) { + $SUBSTS{$subst->get_name()} = $subst; +} + +1; diff --git a/perllib/Debian/PkgKde/SymbolsHelper/Substs/TypeSubst.pm b/perllib/Debian/PkgKde/SymbolsHelper/Substs/TypeSubst.pm new file mode 100644 index 0000000..92af20a --- /dev/null +++ b/perllib/Debian/PkgKde/SymbolsHelper/Substs/TypeSubst.pm @@ -0,0 +1,399 @@ +# Copyright (C) 2008-2010 Modestas Vainius <modax@debian.org> +# +# 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 3 of the License, 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. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/> + +package Debian::PkgKde::SymbolsHelper::Substs::TypeSubst; + +use strict; +use warnings; +use base 'Debian::PkgKde::SymbolsHelper::Subst'; + +use Debian::PkgKde::SymbolsHelper::Substs; + +sub new { + my $class = shift; + my $self = $class->SUPER::new(@_); + $self->{'length'} = 1; # Basic typesubt must be one letter + return $self; +} + +sub get_name { + my $self = shift; + return substr($self->{substvar}, 1, -1); +} + +sub get_types_re { + my $self = shift; + unless (exists $self->{types_re}) { + my $s = '[' . join("", @{$self->{types}}) . ']'; + $self->{types_re} = qr/$s/; + } + return $self->{types_re}; +} + +sub neutralize { + my ($self, $rawname) = @_; + my $ret = 0; + my $str = "$rawname"; + my $l = $self->{'length'}; + my $re = $self->get_types_re(); + + while ($str =~ /$re/g) { + $rawname->substr(pos($str)-$l, $l, $self->{types}->[0]); + $ret = 1; + } + return ($ret) ? $rawname : undef; +} + +sub hinted_neutralize { + my ($self, $rawname, $hint) = @_; + my $hintstr = $hint->{str2}; + my $ret = 1; + my $l = $self->{'length'}; + + for (my $i = 0; $i < @$hintstr; $i++) { + if (defined $hintstr->[$i] && $hintstr->[$i] eq $self->{substvar}) { + $rawname->substr($i, $l, $self->{types}->[0], $self->{substvar}); + $ret = 1; + } + } + return ($ret) ? $rawname : undef; +} + +sub detect { + my ($self, $rawname, $arch, $arch_rawnames) = @_; + + my $l = $self->{'length'}; + my $s1 = $rawname; + my $t1 = $self->expand($arch); + my ($s2, $t2); + + # Find architecture with other type + foreach my $a2 (keys %$arch_rawnames) { + $t2 = $self->expand($a2); + if ($t2 ne $t1) { + $s2 = $arch_rawnames->{$a2}; + last; + } + } + + return 0 unless defined $s2; + + # Verify subst and replace it with types[0] and substvar + my $ret = 0; + search_next: for (my $pos = 0; ($pos = index($s1, $t1, $pos)) != -1; $pos++) { + # Verify on the selected $a2 + if ($t2 eq substr($s2, $pos, $l)) { + # Maybe subst is already there? + if ($rawname->has_string2() && + (my $char = $rawname->get_string2_char($pos))) + { + if ($char eq $self->{substvar}) { + # Nothing to do + $ret = 1; + $pos += $l-1; + next search_next; + } elsif ($char =~ /^{(.*)}$/) { + # Another subst. Verify it + # NOTE: %SUBSTS won't work here due to recursive "use" + my $othersubst = $Debian::PkgKde::SymbolsHelper::Substs::SUBSTS{$1}; + if (defined $othersubst && $othersubst->verify_at($pos, $arch_rawnames)) { + $ret = 1; + next search_next; + } + } + } + # Now verify detection on other arches + if ($self->verify_at($pos, $arch_rawnames)) { + $rawname->substr($pos, $l, $self->{types}->[0], $self->{substvar}); + $ret = 1; + $pos += $l-1; + } + } + } + return $ret; +} + +sub verify_at { + my ($self, $pos, $arch_rawnames) = @_; + my $l = $self->{'length'}; + my $verified = 1; + foreach my $a (keys %$arch_rawnames) { + my $t = $self->expand($a); + if ($t ne substr($arch_rawnames->{$a}, $pos, $l)) { + $verified = 0; + last; + } + } + return $verified; +} + +# Operates on %l% etc. same length types that cannot be present in demanged +# symbols. Used by ::Cpp wrapper. +package Debian::PkgKde::SymbolsHelper::Substs::TypeSubst::CppPrivate; + +use strict; +use warnings; +use base 'Debian::PkgKde::SymbolsHelper::Substs::TypeSubst'; + +sub new { + my ($class, $base) = @_; + my $self = $class->SUPER::new(); + $self->{base} = $base; + $self->{'length'} = 3; # raw type + length('%%') + $self->{substvar} = '{' . $self->get_name() . '}'; + $self->{types} = [ map { '%' . $_ . '%' } @{$base->{types}} ]; + return $self;; +} + +sub get_name { + my $self = shift; + return "c++:" . $self->{base}->get_name(); +} + +sub get_types_re { + my $self = shift; + unless (exists $self->{types_re}) { + my $s = '%[' . join("", @{$self->{base}{types}}) . ']%'; + $self->{types_re} = qr/$s/; + } + return $self->{types_re}; +} + + +sub _expand { + my ($self, $arch) = @_; + return '%'.$self->{base}->_expand($arch).'%'; +} + +package Debian::PkgKde::SymbolsHelper::Substs::TypeSubst::Cpp; + +use strict; +use warnings; +use base 'Debian::PkgKde::SymbolsHelper::Subst'; + +my %CPP_MAP = ( + m => 'unsigned long', + j => 'unsigned int', + i => 'int', + l => 'long', + x => 'long long', + y => 'unsigned long long', + f => 'float', + d => 'double', +); + +my %CPPRE_MAP = ( + '%m%' => qr/\bunsigned long(?! long)\b/, + '%j%' => qr/\bunsigned int\b/, + '%i%' => qr/\b(?<!unsigned )int\b/, + '%l%' => qr/\b(?<!unsigned )long(?! long)\b/, + '%x%' => qr/\b(?<!unsigned )long long\b/, + '%y%' => qr/\bunsigned long long\b/, + '%f%' => qr/\bfloat\b/, + '%d%' => qr/\bdouble\b/, +); + +sub new { + my ($class, $base) = @_; + my $self = $class->SUPER::new(); + $self->{private} = + Debian::PkgKde::SymbolsHelper::Substs::TypeSubst::CppPrivate->new($base); + return $self; +} + +sub _expand { + my ($self, $arch) = @_; + return $CPP_MAP{$self->{private}{base}->_expand($arch)}; +} + +sub get_name { + my $self = shift; + return $self->{private}->get_name(); +} + +# In order for detect()/neutralize() to work, all substs must be of the same +# length. Therefore replace demangled names with %l% etc. +sub prep { + my ($self, $rawname, $arch) = @_; + + # We need to prepare $rawname only once for all Cpp substs + return if exists $rawname->{cpp_prepped}; + + my $str = "$rawname"; + foreach my $key (keys %CPPRE_MAP) { + my $re = $CPPRE_MAP{$key}; + while ($str =~ /$re/g) { + my $l = length($&); + $rawname->substr(pos($str)-$l, $l, $key, $&); + $str = "$rawname" if $l != length($key); + } + } + $rawname->{cpp_prepped} = 1; +} + +sub detect { + my $self = shift; + return $self->{private}->detect(@_); +} + +sub neutralize { + my $self = shift; + return $self->{private}->neutralize(@_); +} + +sub hinted_neutralize { + my $self = shift; + return $self->{private}->hinted_neutralize(@_); +} + +sub verify_at { + my $self = shift; + return $self->{private}->verify_at(@_); +} + +package Debian::PkgKde::SymbolsHelper::Substs::TypeSubst::size_t; + +use strict; +use warnings; +use base 'Debian::PkgKde::SymbolsHelper::Substs::TypeSubst'; + +sub new { + my $class = shift; + my $self = $class->SUPER::new(@_); + $self->{substvar} = "{size_t}"; + $self->{types} = [ qw(m j) ]; # unsigned long / unsigned int + return $self; +} + +sub _expand { + my ($self, $arch) = @_; + return ($arch =~ /amd64|ia64|alpha|s390/) ? "m" : "j"; +} + +package Debian::PkgKde::SymbolsHelper::Substs::TypeSubst::ssize_t; + +use strict; +use warnings; +use base 'Debian::PkgKde::SymbolsHelper::Substs::TypeSubst'; + +sub new { + my $class = shift; + my $self = $class->SUPER::new(@_); + $self->{substvar} = "{ssize_t}"; + $self->{types} = [ qw(l i) ]; # long / int + return $self; +} + +sub _expand { + my ($self, $arch) = @_; + return ($arch =~ /amd64|ia64|alpha|s390/) ? 'l' : 'i'; +} + +package Debian::PkgKde::SymbolsHelper::Substs::TypeSubst::int64_t; + +use strict; +use warnings; +use base 'Debian::PkgKde::SymbolsHelper::Substs::TypeSubst'; + +sub new { + my $class = shift; + my $self = $class->SUPER::new(@_); + $self->{substvar} = "{int64_t}"; + $self->{types} = [ qw(l x) ]; # long / long long + return $self; +} + +sub _expand { + my ($self, $arch) = @_; + return ($arch =~ /amd64|ia64|alpha/) ? 'l' : 'x'; +} + +package Debian::PkgKde::SymbolsHelper::Substs::TypeSubst::uint64_t; + +use strict; +use warnings; +use base 'Debian::PkgKde::SymbolsHelper::Substs::TypeSubst'; + +sub new { + my $class = shift; + my $self = $class->SUPER::new(@_); + $self->{substvar} = "{uint64_t}"; + $self->{types} = [ qw(m y) ]; # unsigned long / unsigned long long + return $self; +} + +sub _expand { + my ($self, $arch) = @_; + return ($arch =~ /amd64|ia64|alpha/) ? 'm' : 'y'; +} + +package Debian::PkgKde::SymbolsHelper::Substs::TypeSubst::qptrdiff; + +use strict; +use warnings; +use base 'Debian::PkgKde::SymbolsHelper::Substs::TypeSubst'; + +sub new { + my $class = shift; + my $self = $class->SUPER::new(@_); + $self->{substvar} = "{qptrdiff}"; + $self->{types} = [ qw(x i) ]; # long long / int + return $self; +} + +sub _expand { + my ($self, $arch) = @_; + return ($arch =~ /amd64|ia64|alpha/) ? 'x' : 'i'; +} + +package Debian::PkgKde::SymbolsHelper::Substs::TypeSubst::quintptr; + +use strict; +use warnings; +use base 'Debian::PkgKde::SymbolsHelper::Substs::TypeSubst'; + +sub new { + my $class = shift; + my $self = $class->SUPER::new(@_); + $self->{substvar} = "{quintptr}"; + $self->{types} = [ qw(y j) ]; # unsigned long long / unsigned int + return $self; +} + +sub _expand { + my ($self, $arch) = @_; + return ($arch =~ /amd64|ia64|alpha/) ? 'y' : 'j'; +} + +package Debian::PkgKde::SymbolsHelper::Substs::TypeSubst::qreal; + +use strict; +use warnings; +use base 'Debian::PkgKde::SymbolsHelper::Substs::TypeSubst'; + +sub new { + my $class = shift; + my $self = $class->SUPER::new(@_); + $self->{substvar} = "{qreal}"; + $self->{types} = [ qw(d f) ]; # double / float + return $self; +} + +sub _expand { + my ($self, $arch) = @_; + return ($arch =~ /arm/) ? 'f' : 'd'; +} + +1; diff --git a/perllib/Debian/PkgKde/SymbolsHelper/Substs/VirtTable.pm b/perllib/Debian/PkgKde/SymbolsHelper/Substs/VirtTable.pm new file mode 100644 index 0000000..4cb0d61 --- /dev/null +++ b/perllib/Debian/PkgKde/SymbolsHelper/Substs/VirtTable.pm @@ -0,0 +1,112 @@ +# Copyright (C) 2008-2010 Modestas Vainius <modax@debian.org> +# +# 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 3 of the License, 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. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/> + +package Debian::PkgKde::SymbolsHelper::Substs::VirtTable; + +use strict; +use warnings; +use base 'Debian::PkgKde::SymbolsHelper::Subst'; + +use Dpkg::ErrorHandling; +use Dpkg::Shlibs::Cppfilt; + +# Expand support (for backwards compatibility) +# Neutralize support + +sub get_name { + "vt"; +} + +sub _expand { + my ($self, $arch, $value) = @_; + + # Mult should be 1 on 32bit arches and 2 on 64bit arches + my $mult = ($arch =~ /amd64|ia64|alpha/) ? 2 : 1; + return $mult * $value; +} + +sub subvt { + my ($self, $rawname, $number, $stroffset) = @_; + $rawname->substr($stroffset, length("$number"), "0", + ($self->{__detect__}) ? $number : undef); + return 1; +} + +sub find_ztc_offset { + my ($self, $rawname) = @_; + $rawname = "$rawname"; + + # The idea behind the algorithm is that c++filt output does not + # change when offset is changed. + # e.g. _ZTCN6KParts15DockMainWindow3E56_NS_8PartBaseE + + my @matches = ($rawname =~ m/(\d+)_/gc); + if (!@matches) { + error("Invalid construction table symbol: $rawname"); + } elsif (@matches == 1) { + # Found it + return (pos($rawname) - length($1) - 1, $1); + } else { + # The idea behind the algorithm is that c++filt output does not + # change when an offset is changed. + $rawname =~ s/@[^@]+$//; + my $demangled = cppfilt_demangle_cpp($rawname, 'auto'); + pos($rawname) = undef; + while ($rawname =~ m/(\d+)_/g) { + my $offset = $1; + my $pos = pos($rawname) - length($offset) - 1; + my $newsymbol = $rawname; + substr($newsymbol, $pos, length($offset)) = $offset + 1234; + my $newdemangled = cppfilt_demangle_cpp($newsymbol, 'auto'); + return ($pos, $offset) if (defined $newdemangled && $newdemangled eq $demangled); + } + error("Unable to determine construction table offset position in symbol '$rawname'"); + } +} + +sub neutralize { + my ($self, $rawname) = @_; + my $ret = 1; + + # construction vtable: e.g. _ZTCN6KParts15DockMainWindow3E56_NS_8PartBaseE + if ($rawname =~ /^_ZTC/) { + my ($pos, $num) = $self->find_ztc_offset($rawname); + $ret = $self->subvt($rawname, $num, $pos) if ($num > 0); + } elsif ($rawname =~ /^_ZThn(\d+)_/) { + # non-virtual base override: e.g. _ZThn8_N6KParts13ReadWritePartD0Ev + my $num = $1; + $ret = $self->subvt($rawname, $num, 5) if ($num > 0); + } elsif ($rawname =~ /^_ZTvn?(\d+)_(n?\d+)/) { + # virtual base override, with vcall offset, e.g. _ZTv0_n12_N6KParts6PluginD0Ev + my $voffset = $1; + my $num = $2; + my $numoffset = 4 + length("$voffset") + 1 + (($num =~ /^n/) ? 1 : 0); + $num =~ s/^n//; + + $ret = $self->subvt($rawname, $voffset, 4) if ($voffset > 0); + $ret = $self->subvt($rawname, $num, $numoffset) || $ret if ($num > 0); + } + return ($ret) ? $rawname : undef; +} + +sub detect { + my $self = shift; + $self->{__detect__} = 1; + my $ret = $self->neutralize(@_); + delete $self->{__detect__}; + return $ret; +} + +1; diff --git a/perllib/Debian/PkgKde/SymbolsHelper/Symbol.pm b/perllib/Debian/PkgKde/SymbolsHelper/Symbol.pm new file mode 100644 index 0000000..1588487 --- /dev/null +++ b/perllib/Debian/PkgKde/SymbolsHelper/Symbol.pm @@ -0,0 +1,384 @@ +# Copyright (C) 2008-2010 Modestas Vainius <modax@debian.org> +# +# 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 3 of the License, 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. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/> + +package Debian::PkgKde::SymbolsHelper::Symbol; + +use strict; +use warnings; +use base 'Dpkg::Shlibs::Symbol'; + +use Dpkg::Gettext; +use Dpkg::Shlibs::Cppfilt; +use Dpkg::Arch qw(get_valid_arches); +use Dpkg::ErrorHandling; +use Debian::PkgKde::SymbolsHelper::Substs; +use Debian::PkgKde::SymbolsHelper::String; + +sub get_h_name { + my $self = shift; + if (!exists $self->{h_name}) { + $self->{h_name} = Debian::PkgKde::SymbolsHelper::String->new( + $self->get_symbolname() + ); + if (exists $self->{substs}) { + # We need to recreate string2 from the templ + $self->{h_name}->init_string2_by_re($self->get_symboltempl(), + qr/\{([^}]+)\}/, $self->{substs}); + if ($self->{h_name}->get_string2() ne $self->get_symboltempl()) { + internerr("unsupported substitutions/alterations in the symbol template '%s'. " . + "Parsed as '%s'. Cannot continue.", + $self->get_symboltempl(), $self->{h_name}->get_string2()); + } + } + } + return $self->{h_name}; +} + +sub reset_h_name { + my ($self, $new_h_name) = @_; + if (defined $new_h_name) { + $self->{h_name} = $new_h_name; + } else { + delete $self->{h_name}; + } +} + +sub resync_name_with_h_name { + my $self = shift; + if (exists $self->{h_name}) { + my $h_name = $self->{h_name}; + $self->set_symbolname($h_name->get_string(), $h_name->get_string2()); + } +} + +# Needed for h_name above +sub get_substs { + my $self = shift; + return $self->{substs}; +} + +sub is_vtt { + return shift()->get_symbolname() =~ /^_ZT[VT]/; +} + +sub initialize { + my ($self, %opts) = @_; + + # Expand substvars + if ($self->has_tag('subst')) { + # Expand substitutions in the symbol name. See below. + if ($self->expand_substitutions(%opts) == 0) { + # Redundant subst tag. Warn. + warning(_g("%s: no valid substitutions, 'subst' tag is redundant"), + $self->get_symbolname()); + } + } + + # NOTE: backwards compatibility with pkgkde-symbolshelper (<< 0.6) + # symbol files. + if ($self->get_symbolname() =~ /\{.+\}/) { + $self->{symbol} =~ s/\{vt:/{vt=/g; + if (defined $self->{symbol_templ}) { + $self->{symbol_templ} =~ s/\{vt:/{vt=/g; + } else { + $self->{symbol_templ} = $self->{symbol}; + } + my @substs = $self->expand_substitutions(%opts); + + my $templ = $self->get_symboltempl(); + my $vt = 0; + foreach my $subst (@substs) { + # Drop obsolete vt subst completely + if ($subst =~ /^vt=/) { + $templ =~ s/\Q{$subst}\E/$self->{substs}{$subst}/g; + $vt++; + } + } + $self->set_symbolname(undef, $templ) if $vt > 0; + if ($vt < scalar(@substs)) { + if ($vt) { + $self->add_tag('subst', 'compat-no-vt'); + } else { + $self->add_tag('subst', 'compat'); + } + } + } + + return $self->SUPER::initialize(%opts); +} + +sub expand_substitutions { + my ($self, %opts) = @_; + my $symbol = $self->get_symbolname(); + my %substs; + + # Collect substitutions in the symbol name + while ($symbol =~ /\{(([^}=]+)(?:=([^}]+))?)\}/g) { + my $subst = $1; + my $name = $2; + my $val = $3; + unless (exists $substs{$name}) { + my $substobj = $SUBSTS{$name}; + if (defined $subst) { + $substs{$subst} = $substobj->expand($opts{arch}, $val); + if (!defined $substs{$subst}) { + error(_g("%s: unable to expand symbol substitution '%s'"), $symbol, $subst); + } + } # If not defined, silently ignore. + } + } + + # Expand substitutions + for my $subst (keys %substs) { + $symbol =~ s/\Q{$subst}\E/$substs{$subst}/g; + } + + $self->{symbol} = $symbol; + $self->{substs} = \%substs; + return keys %substs; +} + +sub get_cppname { + my $self = shift; + unless (exists $self->{cppname}) { + $self->{cppname} = ($self->get_symbolname() =~ /^_Z/) ? + cppfilt_demangle_cpp($self->get_symbolname()) : + undef; + } + return $self->{cppname}; +} + +sub detect_cpp_templinst() { + my $self = shift; + + my $cppname = $self->get_cppname(); + if (defined $cppname) { + # Prepare for tokenizing: wipe out unnecessary spaces + $cppname =~ s/([,<>()])\s+/$1/g; + $cppname =~ s/\s+([,<>()])/$1/g; + $cppname =~ s/\s*((?:(?:un)?signed|volatile|restrict|const|long)[*&]*)\s*/$1/g; + if (my @tokens = split(/\s+/, $cppname)) { + my $func; + if ($tokens[0] =~ /[(]/) { + $func = $tokens[0]; + } elsif ($#tokens >= 1 && $tokens[1] =~ /[(]/) { + # The first token was return type, try the second + $func = $tokens[1]; + } + if (defined $func && $func =~ /<[^>]+>[^(]*[(]/) { + return 1; + } + } + } + return 0; +} + +# Typically template instantiations are not useful public symbols +sub mark_cpp_templinst_as_optional { + my ($self, @tag) = @_; + @tag = ("optional", "templinst") unless @tag; + if (!$self->is_optional() && $self->detect_cpp_templinst()) { + $self->add_tag(@tag); + } +} + +# Converts symbol template to c++ alias converting substitutions as well. +# Returns converted template string or undef in case of failure. +sub convert_templ_to_cpp_alias { + my ($self, $templ) = @_; + $templ = $self->get_symboltempl() unless defined $templ; + my $result; + + return undef unless $templ =~ /^_Z/; + + if (! $self->has_tag('subst')) { + $result = cppfilt_demangle_cpp($templ); + } else { + my (%mangled, @possible_substs); + + # Collect possible symbol variants by expanding on all valid arches + foreach my $arch (get_valid_arches()) { + $self->{symbol} = $templ; + @possible_substs = $self->expand_substitutions(arch => $arch); + push @{$mangled{$self->get_symbolname()}}, $arch; + } + + # Prepare for checking of demangled symbols + my (@demangled, @arches); + foreach my $mangled (keys %mangled) { + my $d = cppfilt_demangle_cpp($mangled); + # Fail immediatelly if couldn't demangle a variant + return undef unless defined $d; + + # Tokenize + push @demangled, [ split(/\b/, $d) ]; + push @arches, $mangled{$mangled}; + } + + # Create a subst expansion result map for $main_arch + my $main_arch = $arches[0][0]; + my %cppmap; + foreach my $subst (@possible_substs) { + my $name = "c++:$subst"; + # Can't handle a subst which does not have a c++ replacement + if (exists $SUBSTS{$name}) { + my $cppsubst = $SUBSTS{$name}; + push @{$cppmap{$cppsubst->expand($main_arch)}}, $cppsubst; + } else { + return undef; + } + } + + # Now do detection + my @result; + my @expanded_size = map { 0 } @demangled; + while (@{$demangled[0]} > 0) { + # Check if the token is not the same in all symbols (i.e. no subst here) + my $token = shift @{$demangled[0]}; + my $ok = 1; + for (my $i = 1; $i < @demangled; $i++) { + if ($token ne $demangled[$i][0]) { + $ok = 0; + last; + } + } + if ($ok) { + # Tokens match. Get next token and push to @result + for (my $i = 1; $i < @demangled; $i++) { + shift @{$demangled[$i]}; + } + push @result, $token; + } else { + # Tokens do not match. We need to guess a subst + my $found_subst; + + # Determine a set of candidate substs by expansion result + # for $main_arch + while (!exists $cppmap{$token}) { + my $next = shift @{$demangled[0]}; + if (!defined $next) { + return undef; + } + # Add up next token to this one and check again + $token .= $next; + } + + # If we are here, a set of candidate substs has been found + my $cand_substs = $cppmap{$token}; + + # Now we need to pick a right candidate from candidates + next_candidate: for my $cand_subst (@$cand_substs) { + # Expansion must be the same on index 0 arches + foreach my $arch (@{$arches[0]}) { + if ($cand_subst->expand($arch) ne $token) { + next next_candidate; + } + } + + # On 1+ arches, $subst->expand() and our $demangled value must match + for (my $i = 1; $i < @arches; $i++) { + my $archset = $arches[$i]; + my $expanded = $cand_subst->expand($archset->[0]); + + # Check if expansion is the same on all arches in the current set + foreach my $arch (@$archset) { + if ($expanded ne $cand_subst->expand($arch)) { + next next_candidate; + } + } + + # Now actually check if $expanded matches what was demangled + $expanded_size[$i] = scalar(my @s = split(/\b/, $expanded)); + if (join("", @{$demangled[$i]}[0..($expanded_size[$i]-1)]) ne $expanded) { + next next_candidate; + } + } + + # If we are here, candidate has been confirmed + $found_subst = $cand_subst; + last; + } + + if (defined $found_subst) { + for (my $i = 1; $i < @demangled; $i++) { + splice @{$demangled[$i]}, 0, ($expanded_size[$i]); + } + push @result, '{' . $found_subst->get_name() . '}'; + } else { + # Unable to find an appropriate subst + return undef; + } + } + } + + foreach my $demangled (@demangled) { + # Fail if demangling was not complete + return undef if @$demangled > 0; + } + + $result = join("", @result); + } + return $result; +} + +sub upgrade_virtual_table_symbol { + my ($self, $arch) = @_; + if ($self->get_symboltempl() =~ /^_ZT[Chv]/) { + my $newtempl = $self->convert_templ_to_cpp_alias(); + if (defined $newtempl) { + $self->set_symbolname($newtempl, $newtempl); + $self->add_tag("c++"); + # Finally, reinitialize + $self->initialize(arch => $arch); + } + return $newtempl; + } + return undef; +} + +sub set_min_version { + my ($self, $version, %opts) = @_; + + $self->{minver} = $version + if ($opts{with_deprecated} || !$self->{deprecated}); +} + +sub normalize_min_version { + my ($self, %opts) = @_; + + if ($opts{with_deprecated} || !$self->{deprecated}) { + my $minver = $self->{minver}; + if ($minver =~ m/-.*[^~]$/) { + unless($minver =~ s/-[01](?:$|[^\d-][^-]*$)//) { + $minver =~ s/([^~])$/$1~/; + } + $self->{minver} = $minver; + } + } +} + +sub handle_min_version { + my ($self, $version, %opts) = @_; + + if (defined $version) { + if ($version) { + return $self->set_min_version($version, %opts); + } else { + return $self->normalize_min_version(%opts); + } + } +} + +1; diff --git a/perllib/Debian/PkgKde/SymbolsHelper/SymbolFile.pm b/perllib/Debian/PkgKde/SymbolsHelper/SymbolFile.pm new file mode 100644 index 0000000..97037a9 --- /dev/null +++ b/perllib/Debian/PkgKde/SymbolsHelper/SymbolFile.pm @@ -0,0 +1,320 @@ +# Copyright (C) 2008-2010 Modestas Vainius <modax@debian.org> +# +# 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 3 of the License, 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. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/> + +package Debian::PkgKde::SymbolsHelper::SymbolFile; + +use strict; +use warnings; +use base 'Dpkg::Shlibs::SymbolFile'; + +use File::Temp qw(); +use File::Copy qw(); +use Storable qw(); +use IO::Handle; +use Dpkg::ErrorHandling; +use Dpkg::Version; +use Debian::PkgKde::SymbolsHelper::Symbol; +use Debian::PkgKde::SymbolsHelper::Substs; + +# Use Debian::PkgKde::SymbolsHelper::Symbol as base symbol +sub parse { + my ($self, $fh, $file, $seen, $obj_ref, $base_symbol) = @_; + unless (defined $base_symbol) { + $base_symbol = 'Debian::PkgKde::SymbolsHelper::Symbol'; + } + if (!defined $seen) { + # Read 'SymbolsHelper-Confirmed' header + open(my $fh, "<", $file) + or error("unable to open symbol file '$file' for reading"); + my $line = <$fh>; + close $fh; + + chop $line; + if ($line =~ /^#\s*SymbolsHelper-Confirmed:\s+(.+)$/) { + $self->set_confirmed(split(/\s+/, $1)); + } + } + return $self->SUPER::parse($fh, $file, $seen, $obj_ref, $base_symbol); +} + +sub set_confirmed { + my ($self, $version, @arches) = @_; + $self->{h_confirmed_version} = $version; + $self->{h_confirmed_arches} = (@arches) ? \@arches : undef; +} + +sub get_confirmed_version { + my $self = shift; + return $self->{h_confirmed_version}; +} + +sub get_confirmed_arches { + my $self = shift; + return (defined $self->{h_confirmed_arches}) ? + @{$self->{h_confirmed_arches}} : (); +} + +sub create_symbol { + my ($self, $spec, %opts) = @_; + $opts{base} = Debian::PkgKde::SymbolsHelper::Symbol->new() + unless exists $opts{base}; + return $self->SUPER::create_symbol($spec, %opts); +} + +sub fork_symbol { + my ($self, $sym, $arch) = @_; + $arch = $self->get_arch() unless $arch; + my $nsym = $sym->clone(symbol => $sym->get_symboltempl()); + $nsym->initialize(arch => $arch); + return $nsym; +} + +sub output { + my ($self, $fh, %opts) = @_; + $opts{with_confirmed} = 1 unless exists $opts{with_confirmed}; + # Write SymbolsHelper-Confirmed header + if ($opts{with_confirmed}) { + my @carches = $self->get_confirmed_arches(); + if (@carches) { + print $fh '# SymbolsHelper-Confirmed: ', $self->get_confirmed_version(), + " ", join(" ", sort @carches), "\n" if defined $fh; + } + } + return $self->SUPER::output($fh, %opts); +} + +sub _resync_symbol_cache { + my ($self, $soname, $cache) = @_; + my %rename; + + foreach my $symkey (keys %$cache) { + my $sym = $cache->{$symkey}; + if ($sym->get_symbolname() ne $symkey) { + $rename{$sym->get_symbolname()} = $sym; + delete $cache->{$symkey}; + } + } + foreach my $newname (keys %rename) { + my $e = $self->get_symbol_object($rename{$newname}, $soname); + if ($e && ! $rename{$newname}->equals($e)) { + warning("caution: newly generated symbol '%s' will replace not exactly equal '%s'. Please readd if unappropriate", + $rename{$newname}->get_symbolspec(1), + $e->get_symbolspec(1)); + } + $self->add_symbol($rename{$newname}, $soname); + } +} + +sub resync_soname_symbol_caches { + my ($self, $soname) = @_; + my $obj = $self->get_object($soname); + + # We need this to avoid removal of symbols which names clash when renaming + $self->_resync_symbol_cache($obj, $obj->{syms}); + + # Resync aliases too + foreach my $alias (values %{$obj->{patterns}{aliases}}) { + $self->_resync_symbol_cache($obj, $alias); + } +} + +sub resync_soname_with_h_name { + my ($self, $soname) = @_; + my $obj = $self->get_object($soname); + + sub _resync_with_h_name { + my $cache = shift; + foreach my $symkey (keys %$cache) { + $cache->{$symkey}->resync_name_with_h_name(); + } + } + + # First resync h_name with symbol name and templ + _resync_with_h_name($obj->{syms}); + foreach my $alias (values %{$obj->{patterns}{aliases}}) { + _resync_with_h_name($alias); + } + return $self->resync_soname_symbol_caches($soname); +} + +# Detects (or just neutralizes) substitutes which can be guessed from the +# symbol name alone. Currently unused. +#sub detect_standalone_substs { +# my ($self, $detect) = @_; +# +# foreach my $sym ($self->get_symbols()) { +# my $str = $sym->get_h_name(); +# foreach my $subst (@STANDALONE_SUBSTS) { +# if ($detect) { +# $subst->detect($str, $self->{arch}); +# } else { +# $subst->neutralize($str); +# } +# } +# } +# foreach my $soname (keys %{$self->{objects}}) { +# # Rename soname object with data in h_name +# $self->resync_soname_with_h_name($soname); +# } +#} + +# Upgrade virtual table symbols. Needed for templating. +sub prepare_for_templating { + my $self = shift; + my %sonames; + + foreach my $soname ($self->get_sonames()) { + foreach my $sym ($self->get_symbols($soname)) { + if ($sym->upgrade_virtual_table_symbol($self->get_arch())) { + $sonames{$soname} = 1; + } + } + } + + foreach my $soname (keys %sonames) { + $self->resync_soname_symbol_caches($soname); + } +} + +sub patch_template { + my ($self, @patches) = @_; + my @symfiles; + my %dumped; + + foreach my $patch (@patches) { + my $package = $patch->{package} || ''; + my $tmpfile; + if (!exists $dumped{$package}) { + $tmpfile = File::Temp->new( + TEMPLATE => "${package}_orig.symbolsXXXXXX", + UNLINK => 0, + ); + $self->output($tmpfile, + package => $package, + template_mode => 1, + with_confirmed => 0, + ); + $tmpfile->close(); + $dumped{$package} = $tmpfile->filename; + } + $tmpfile = File::Temp->new( + TEMPLATE => "${package}_patched.symbolsXXXXXX", + UNLINK => 1, + ); + $tmpfile->close(); + unless (File::Copy::copy($dumped{$package}, $tmpfile->filename)) { + syserror("unable to copy file '%s' to '%s'", + $dumped{$package}, $tmpfile->filename); + } + if ($patch->apply($tmpfile->filename)) { + # Patching was successful. Parse new SymbolFile and return it + my $symfile = Debian::PkgKde::SymbolsHelper::SymbolFile->new( + file => $tmpfile->filename, + arch => $patch->{arch}, + ); + if ($patch->has_info()) { + $symfile->set_confirmed($patch->{version}, $patch->{arch}); + } else { + $symfile->set_confirmed(undef); + } + push @symfiles, $symfile; + last unless wantarray; + } + } + foreach my $file (values %dumped) { + unless ($FILE::Temp::KEEP_ALL) { + unlink $file; + } + } + return (wantarray) ? @symfiles : $symfiles[0]; +} + +sub _dclone_exclude { + my ($target, @exclude) = @_; + my %saved; + foreach my $e (@exclude) { + if (exists $target->{$e}) { + $saved{$e} = $target->{$e}; + delete $target->{$e}; + } + } + my $clone = Storable::dclone($target); + $target->{$_} = $saved{$_} foreach @exclude; + return $clone; +} + +# Forks an empty symbol file (without symbols and patterns) from the current +# one. Other properties are retained. +sub fork_empty { + my $self = shift; + + my $symfile = _dclone_exclude($self, qw(objects)); + $symfile->clear(); + foreach my $soname ($self->get_sonames()) { + $symfile->create_object($soname); + my $obj = $symfile->get_object($soname); + my $cloned = _dclone_exclude($self->get_object($soname), + qw(syms patterns minver_cache)); + $obj->{$_} = $cloned->{$_} foreach keys %$cloned; + } + return $symfile; +} + +sub fork { + my ($self, @optinstances) = @_; + unshift @optinstances, {} unless @optinstances; + @optinstances = ( $optinstances[0] ) unless wantarray; + + my @symfiles; + foreach my $opts (@optinstances) { + my $symfile = $self->fork_empty(); + $symfile->{$_} = $opts->{$_} foreach keys %$opts; + $symfile->{file} = ''; + push @symfiles, $symfile; + } + + # Fork symbols + foreach my $soname ($self->get_sonames()) { + foreach my $sym ($self->get_symbols($soname), + $self->get_patterns($soname)) + { + foreach my $symfile (@symfiles) { + my $nsym = $self->fork_symbol($sym, $symfile->get_arch()); + $nsym->{h_origin_symbol} = $sym; + $symfile->add_symbol($nsym, $soname); + } + } + } + return (wantarray) ? @symfiles : shift @symfiles; +} + + +sub get_highest_version { + my $self = shift; + my $maxver; + + foreach my $sym ($self->get_symbols(), + $self->get_patterns()) { + if (!$sym->{deprecated} && + (!defined $maxver || version_compare($sym->{minver}, $maxver) > 0)) + { + $maxver = $sym->{minver}; + } + } + + return $maxver; +} + +1; diff --git a/perllib/Debian/PkgKde/SymbolsHelper/SymbolFileCollection.pm b/perllib/Debian/PkgKde/SymbolsHelper/SymbolFileCollection.pm new file mode 100644 index 0000000..372942e --- /dev/null +++ b/perllib/Debian/PkgKde/SymbolsHelper/SymbolFileCollection.pm @@ -0,0 +1,835 @@ +# Copyright (C) 2008-2010 Modestas Vainius <modax@debian.org> +# +# 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 3 of the License, 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. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/> + +package Debian::PkgKde::SymbolsHelper::SymbolFileCollection; + +use strict; +use warnings; + +use Dpkg::Arch qw(debarch_is); +use Dpkg::ErrorHandling; +use Dpkg::Version; +use Debian::PkgKde::SymbolsHelper::Substs; +use Debian::PkgKde::SymbolsHelper::String; +use Debian::PkgKde::SymbolsHelper::SymbolFile; + +sub new { + my ($class, $orig_symfile) = @_; + unless ($orig_symfile->get_confirmed_version()) { + error("original symbol file template must have 'Confirmed' header set"); + } + return bless { orig_symfile => $orig_symfile, + new_arches => {}, + new_non_latest => [], + confirmed_arches => [], + symfiles => {}, + versions => {}, + latest => undef }, $class; +} + +sub get_symfiles { + my $self = shift; + return values %{$self->{symfiles}}; +} + +sub get_symfile { + my ($self, $arch) = @_; + if (defined $arch) { + return $self->{symfiles}{$arch}; + } else { + return $self->{orig_symfile}; + } +} + +# NOTE: latest may also include $orig fork()s if no symbol files with higher +# confirmed version have been added. +sub get_latest_version { + my $self = shift; + return $self->{latest}; +} + +sub get_latest_arches { + my $self = shift; + return @{$self->{versions}{$self->{latest}}}; +} + +sub get_new_arches { + my $self = shift; + return keys %{$self->{new_arches}}; +} + +# This will NEVER include $orig fork()s +sub get_new_non_latest_arches { + my $self = shift; + return @{$self->{new_non_latest}}; +} + +sub is_arch_latest { + my ($self, $arch) = @_; + return $self->get_symfile($arch)->get_confirmed_version() eq $self->{latest}; +} + +sub is_arch_new { + my ($self, $arch) = @_; + return exists $self->{new_arches}{$arch}; +} + +sub add_symfiles { + my ($self, @symfiles) = @_; + my $latest = $self->get_latest_version(); + foreach my $symfile (@symfiles) { + my $arch = $symfile->get_arch(); + my $ver = $symfile->get_confirmed_version(); + unless ($ver) { + internerr("problem with %s symbol file: it must have 'Confirmed' header", + $arch); + } + if ($self->get_symfile($arch)) { + error("you cannot add symbol file for the same arch (%s) more than once", + $arch); + } + $self->{symfiles}{$arch} = $symfile; + push @{$self->{versions}{$ver}}, $arch; + if (!defined $latest || + version_compare($ver, $latest) > 0) + { + $latest = $ver; + } + } + $self->{latest} = $latest; +} + +sub add_confirmed_arches { + my ($self, @arches) = @_; + foreach my $arch (@arches) { + if ($self->get_symfile($arch)) { + error("new symbol file has already been added for arch (%s)", $arch); + } + } + push @{$self->{versions}{$self->get_symfile()->get_confirmed_version()}}, + @arches; + push @{$self->{confirmed_arches}}, @arches; + $self->{latest} = $self->get_symfile()->get_confirmed_version() + unless defined $self->{latest}; +} + +sub get_confirmed_arches { + my ($self) = @_; + return @{$self->{confirmed_arches}}; +} + +sub add_new_symfiles { + my ($self, @symfiles) = @_; + $self->{new_arches} = { %{$self->{new_arches}}, + map({ $_->{arch} => $_ } @symfiles) }; + $self->add_symfiles(@symfiles); + + # Recalc new_non_latest + my $ver = $self->get_latest_version(); + my @new_non_latest; + foreach my $arch ($self->get_new_arches()) { + if (! $self->is_arch_latest($arch)) { + push @new_non_latest, $arch; + } + } + $self->{new_non_latest} = \@new_non_latest; +} + +sub fork_orig_symfile { + my ($self, @arches) = @_; + my @symfiles = $self->get_symfile()->fork( + map +{ arch => $_ }, @arches + ); + return map { $_->{arch} => $_ } @symfiles; +} + +sub calc_group_name { + my ($self, $name, $arch, @substs) = @_; + + my $str = Debian::PkgKde::SymbolsHelper::String->new($name); + foreach my $subst (@substs) { + $subst->prep($str, $arch); + $subst->neutralize($str, $arch); + } + return $str->get_string(); +} + +sub get_symbols_regrouped_by_name { + my ($self, $group) = @_; + my $byname = $group->regroup_by_name(); + my @byname; + foreach my $grp (sort values %$byname) { + if (my $sym = $grp->calc_properties($self)) { + push @byname, $sym; + } + } + return sort { $a->get_symboltempl() cmp $b->get_symboltempl() } @byname; +} + +sub select_group { + my ($self, $sym, $soname, $arch, $gsubsts, $gother) = @_; + + # Substitution detection is only supported for regular symbols and c++ + # aliases. + if (! $sym->is_pattern() || $sym->get_alias_type() eq "c++") { + my $substs = ($sym->has_tag("c++")) ? \@CPP_TYPE_SUBSTS : \@TYPE_SUBSTS; + my $groupname = $self->calc_group_name($sym->get_symbolname(), $arch, @$substs); + + unless (exists $gsubsts->{$soname}{$groupname}) { + $gsubsts->{$soname}{$groupname} = + Debian::PkgKde::SymbolsHelper::SymbolFileCollection::Group->new($substs); + } + return $gsubsts->{$soname}{$groupname}; + } else { + # Symbol of some other kind. Then just group by name + my $name = $sym->get_symbolname(); + unless (exists $gother->{$soname}{$name}) { + $gother->{$soname}{$name} = + Debian::PkgKde::SymbolsHelper::SymbolFileCollection::Group->new(); + } + return $gother->{$soname}{$name}; + } +} + +# Create a new template from the collection of symbol files +sub create_template { + my ($self, %opts) = @_; + + my $orig = $self->get_symfile(); + my $orig_arch = $orig->get_arch(); + my $template = $orig->fork_empty(); + + # Prepare original template and other arch specific symbol files (virtual + # table stuff etc.). + $orig->prepare_for_templating(); + foreach my $symfile ($self->get_symfiles()) { + $symfile->prepare_for_templating(); + } + + # Group new symbols by fully arch-neutralized name or, if unsupported, + # simply by name. + my (%gsubsts, %gother); + my %osymfiles = $self->fork_orig_symfile($self->get_new_arches()); + + foreach my $arch ($self->get_new_arches()) { + my $nsymfile = $self->get_symfile($arch); + my $osymfile = $osymfiles{$arch}; + + my @new = $nsymfile->get_new_symbols($osymfile, with_optional => 1); + foreach my $n (@new) { + my $soname = $n->{soname}; + my $nsym = $n->{symbol}; + # Get a reference in the orig symfile if any + my $osym = $osymfile->get_symbol_object($nsym, $soname); + + my $group = $self->select_group($nsym, $soname, $arch, \%gsubsts, \%gother); + + # Add symbol to the group + $group->add_symbol($nsym, $arch); + $group->prep_substs($arch); + + if (defined $osym) { + my $origin = $osym->{h_origin_symbol}; + $group->add_symbol($origin); + # "Touch" the origin symbol + $origin->{h_touched} = 1; + } + } + + my @lost = $nsymfile->get_lost_symbols($osymfile, with_optional => 1); + foreach my $l (@lost) { + my $soname = $l->{soname}; + my $sym = $l->{symbol}; + my $origin = $sym->{h_origin_symbol}; + my $group = $self->select_group($sym, $soname, $arch, \%gsubsts, \%gother); + + $group->add_lost_symbol($sym, $arch); + $group->add_symbol($origin); + # "Touch" the origin symbol + $origin->{h_touched} = 1; + } + } + + # Fork confirmed symbols where it matters + if (my @carches = $self->get_confirmed_arches()) { + # Important for substs detection + foreach my $soname (values %gsubsts) { + foreach my $group (values %$soname) { + if ($group->get_arches() && (my $osym = $group->get_symbol())) { + foreach my $arch (@carches) { + if ($osym->arch_is_concerned($arch)) { + my $nsym = $orig->fork_symbol($osym, $arch); + $group->add_symbol($nsym, $arch); + $group->prep_substs($arch); + } + } + } + } + } + } + + # Readd all untouched symbols in $orig back to the $template + foreach my $soname ($orig->get_sonames()) { + foreach my $sym ($orig->get_symbols($soname), + $orig->get_patterns($soname)) + { + if (!exists $sym->{h_touched}) { + $template->add_symbol($sym, $soname); + } + } + } + + # Process substs groups (%gsubsts) first + foreach my $soname (keys %gsubsts) { + my $groups = $gsubsts{$soname}; + + foreach my $groupname (keys %$groups) { + my $group = $groups->{$groupname}; + +# print "group: $groupname", "\n"; + + # Take care of ambiguous groups + if ($group->is_ambiguous()) { + if (my @byname = $self->get_symbols_regrouped_by_name($group)) { + $template->add_symbol($_, $soname) foreach @byname; + info("ambiguous symbols for subst detection (%s). Processed by name:\n" . + " %s", "$groupname/$soname", + join("\n ", map { $_->get_symbolspec(1) } @byname)); + } + next; + } + # Calculate properties and detect substs. + if (my $sym = $group->calc_properties($self)) { + # Then detect substs (we need two or more arch specific symbols for that) + my $substs_ok = 0; + if (scalar($group->get_arches()) > 1 && ! $group->are_symbols_equal()) { + my $substs_arch = ($group->has_symbol($orig_arch)) ? + $orig_arch : ($group->get_arches())[0]; + if ($group->detect_substs($substs_arch)) { + my $substs_sym = $group->get_symbol($substs_arch); + $sym->add_tag("subst"); + $sym->reset_h_name($substs_sym->get_h_name()); + # Properly handle the case when *some substs have been* + # detected but symbols in the group still differ. Since + # the symbols will be grouped, we need to add a subst + # tag to all of them and reset h_name of the orig + # symbol since it is not touched by substs detection. + unless ($substs_ok = $group->verify_substs()) { + foreach my $sym ($group->get_symbols()) { + $sym->add_tag("subst"); + } + if ($orig_arch eq $substs_arch) { + $group->get_symbol()->add_tag("subst"); + $group->get_symbol()->reset_h_name( + $substs_sym->get_h_name() + ); + } + } + } + } else { + $substs_ok = 1; + } + + if ($substs_ok) { + # Finally add to template + $template->add_symbol($sym, $soname); + } else { + # Substitutions do not verify. Regroup by name what remains + foreach my $sym ($group->get_symbols()) { + $sym->resync_name_with_h_name(); + } + $group->get_symbol()->resync_name_with_h_name() if $group->get_symbol(); + if (my @byname = $self->get_symbols_regrouped_by_name($group)) { + $template->add_symbol($_, $soname) foreach @byname; + info("possible incomplete subst detection (%s). Processed by name:\n" . + " %s", "$groupname/$soname", + join("\n ", map { $_->get_symbolspec(1) } @byname)); + } + } + } + } + } + + # Now process others groups (%gother). Just calculate properties (arch + # tags) and add to the template. + foreach my $soname (keys %gother) { + my $groups = $gother{$soname}; + foreach my $groupname (keys %$groups) { + my $group = $groups->{$groupname}; + if (my $sym = $group->calc_properties($self)) { + $template->add_symbol($sym, $soname); + } + } + } + + # Finally, resync h_names + foreach my $soname ($template->get_sonames()) { + $template->resync_soname_with_h_name($soname); + } + + return $template; +} + +package Debian::PkgKde::SymbolsHelper::SymbolFileCollection::Group; + +sub new { + my ($class, $substs) = @_; + return bless { + arches => {}, + lost => {}, + orig => undef, + result => undef, + substs => $substs}, $class; +} + +sub has_symbol { + my ($self, $arch) = @_; + return (defined $arch) ? exists $self->{arches}{$arch} : $self->{orig}; +} + +sub get_symbol { + my ($self, $arch) = @_; + return (defined $arch) ? $self->{arches}{$arch} : $self->{orig}; +} + +sub get_arches { + my $self = shift; + return keys %{$self->{arches}}; +} + +sub get_symbols { + my $self = shift; + return values %{$self->{arches}}; +} + +sub get_result { + my $self = shift; + return $self->{result}; +} + +# There might be a new version available (e.g. with corrected substs). +sub is_lost { + my ($self, $arch) = @_; + return exists $self->{lost}{$arch} && ! $self->has_symbol($arch); +} + +sub is_new { + my ($self, $arch) = @_; + if (my $osym = $self->get_symbol()) { + return ! $osym->is_legitimate($arch); + } else { + return 1; + } +} + +sub init_result { + my ($self, $based_on_arch) = @_; + $self->{result} = $self->get_symbol($based_on_arch)->clone(); + return $self->{result}; +} + +sub add_symbol { + my ($self, $sym, $arch, $lost) = @_; + my $status = ($lost) ? "lost" : "arches"; + + if (my $esym = ($lost) ? $self->{lost}{$arch} : $self->get_symbol($arch)) { + if ($esym != $sym) { + # Another symbol already exists in this group for $arch. + # Add to other syms + push @{$self->{ambiguous}{$status}{$arch || ''}}, $sym; + } + # Otherwise, don't do anything. This symbol has already been added. + return 0; + } else { + if (defined $arch) { + $self->{$status}{$arch} = $sym; + } else { + $self->{orig} = $sym; + } + return 1; + } +} + +sub add_lost_symbol { + my ($self, $sym, $arch) = @_; + return $self->add_symbol($sym, $arch, 1); +} + +sub dump { + my ($self, $fh) = @_; + $fh = \*STDERR unless defined $fh; + if (my $sym = $self->get_symbol()) { + print $fh "orig:", $sym->get_symbolspec(1), "\n"; + } + foreach my $arch ($self->get_arches()) { + my $sym = $self->get_symbol($arch); + print $fh "arches{$arch}:", $sym->get_symbolspec(1), "\n"; + } + foreach my $arch (keys %{$self->{lost}}) { + my $sym = $self->{lost}{$arch}; + print $fh "lost{$arch}:", $sym->get_symbolspec(1), "\n"; + } + if ($self->is_ambiguous()) { + foreach my $status (sort keys %{$self->{ambiguous}}) { + my $arches = $self->{ambiguous}{$status}; + foreach my $arch (keys %$arches) { + foreach my $sym (@{$arches->{$arch}}) { + print $fh "ambiguous{$status}{$arch}:", $sym->get_symbolspec(1), "\n"; + } + } + } + } +} + +sub is_ambiguous { + my $self = shift; + return exists $self->{ambiguous}; +} + +# Regroup ambiguous symbols by symbol name +sub regroup_by_name { + my $self = shift; + my %groups; + + foreach my $arch (undef, $self->get_arches()) { + my $sym = $self->get_symbol($arch); + if (defined $sym) { + my $name = $sym->get_symbolname(); + unless (exists $groups{$name}) { + $groups{$name} = ref($self)->new(); + } + my $group = $groups{$name}; + $group->add_symbol($sym, $arch, defined $arch && + $self->is_lost($arch)); + } + } + foreach my $arch (keys %{$self->{lost}}) { + my $sym = $self->{lost}{$arch}; + my $name = $sym->get_symbolname(); + unless (exists $groups{$name}) { + $groups{$name} = ref($self)->new(); + } + my $group = $groups{$name}; + $group->add_lost_symbol($sym, $arch); + } + if ($self->is_ambiguous()) { + foreach my $status (keys %{$self->{ambiguous}}) { + my $arches = $self->{ambiguous}{$status}; + my $lost = ($status eq "lost"); + foreach my $arch (keys %$arches) { + foreach my $sym (@{$arches->{$arch}}) { + $arch = undef if ! $arch; + my $name = $sym->get_symbolname(); + unless (exists $groups{$name}) { + $groups{$name} = ref($self)->new(); + } + my $group = $groups{$name}; + $group->add_symbol($sym, $arch, $lost); + } + } + } + } + + return \%groups; +} + +sub are_symbols_equal { + my $self = shift; + my @arches = $self->get_arches(); + my $name; + + $name = ($self->get_symbol()) ? + $self->get_symbol() : $self->get_symbol(shift @arches); + $name = $name->get_symbolname(); + foreach my $arch (@arches) { + if ($self->get_symbol($arch)->get_symbolname() ne $name) { + return 0; + } + } + return 1; +} + +# Verify if all substs have been replaced (i.e. hint-neutralized) +sub verify_substs { + my $self = shift; + my @arches = $self->get_arches(); + my $str = $self->get_symbol(shift @arches)->get_h_name()->get_string(); + foreach my $arch (@arches) { + if ($self->get_symbol($arch)->get_h_name()->get_string() ne $str) { + return 0; + } + } + return 1; +} + +sub verify_result_arches { + my ($self, $add, $deprecate) = @_; + my $result = $self->get_result(); + my $ok = 1; + foreach my $arch (keys %$add) { + unless ($result->arch_is_concerned($arch)) { + $ok = 0; + last; + } + } + if ($ok) { + foreach my $arch (keys %$deprecate) { + if ($result->arch_is_concerned($arch)) { + $ok = 0; + last; + } + } + } + return $ok; +} + +# Gets symbol status on $arch: +# -2 - if symbol got LOST; +# -1 - if symbol is deprecated and has been such (status hasn't changed); +# 0 - symbol is NOT present on $arch and original symbol is not available; +# 1 - symbol is present and has been been such (status hasn't changed); +# 2 - symbol is NEW. +sub get_symbol_arch_status { + my ($self, $arch) = @_; + my $status; + + # If $self->is_lost($arch) returns true, it means a symbol is really + # NOT (or no longer) present on that arch in comparision to original + # symbol file. If $self->has_symbol($arch) returns true, the symbol is + # KNOWN to be have BEEN present on that arch (and it still is if it is + # not deprecated). Otherwise, the symbol the symbols status has not changed + # so it is is either: 1) present on $arch if $osym is legitimate on $arch; + # 2) absent on $arch otherwise. + if ($self->is_lost($arch)) { + $status = -2; + } elsif ($self->has_symbol($arch)) { + if ($self->get_symbol($arch)->{deprecated}) { + $status = -1; + } else { + $status = ($self->is_new($arch)) ? 2 : 1; + } + } elsif ($self->has_symbol()) { + $status = ($self->get_symbol()->is_legitimate($arch)) ? 1 : -1; + } else { + $status = 0; + } + return $status; +} + +sub is_arch_in_db { + my ($self, $arch, $db) = @_; + if ($arch =~ /any/) { # Might be a wildcard + foreach my $adb ((ref $db eq 'ARRAY') ? @$db : keys %$db) { + return 2 if debarch_is($adb, $arch); + } + } elsif (ref $db eq 'ARRAY') { + return 1 if grep { $arch eq $_ } @$db; + } else { + return exists $db->{$arch}; + } + return 0; +} + +# Calculate group properties and instantiates 'result'. At the moment, this +# method will take care of arch tags and deprecated status. "Result" symbol is +# returned if symbol is not useless in the group. +sub calc_properties { + my ($self, $collection) = @_; + + my @latest = $collection->get_latest_arches(); + my @non_latest = $collection->get_new_non_latest_arches(); + my $total_arches = scalar(@latest) + scalar(@non_latest); + my (%present, %absent); + my (@oarches, @narches, $arch_neg); + my $arch_added = 0; + my $osym = $self->get_symbol(); + my $result; + + if (defined $osym) { + # The symbol exists in the template. This might complicate things a lot. + if ($osym->has_tag("arch")) { + @oarches = split(/[\s,]+/, $osym->get_tag_value("arch")) + } + } + + # Calculate status of @latest arches + foreach my $arch (@latest) { + my $status = $self->get_symbol_arch_status($arch); + if ($status > 0) { + $present{$arch} = $status; + } else { + $absent{$arch} = $status; + } + } + + # Initialize $result + if (defined $osym) { + $result = $self->init_result(); # base result on original + } elsif (keys %present) { + $result = $self->init_result((keys %present)[0]); + } else { + return undef; + } + + if (scalar(keys %absent) == scalar(@latest) && + (grep { $absent{$_} == -2 } keys %absent)) + { + if (!$osym->{deprecated} || $osym->is_optional()) { + $result->{deprecated} = $collection->get_latest_version(); + } + } elsif (scalar(keys %present) == scalar(@latest) && + (@oarches == 0 || @latest > 1)) + { + # Do not remove arch tag if we based our findings only on a single + # arch. + $result->{deprecated} = 0; + if (@oarches > 0) { + $result->delete_tag("arch"); + $arch_added += scalar(keys %present); + } + } else { + # We will need to add appropriate arch tag. But in addition, + # collect info from NEW non-latest arches (provided we had + # info about them from latest) + foreach my $arch (@non_latest) { + my $status = $self->get_symbol_arch_status($arch); + if ($status > 0) { + $present{$arch} = $status if keys(%present) > 0 && + ! exists $absent{$arch}; + } else { + $absent{$arch} = $status if keys(%absent) > 0 && + ! exists $present{$arch}; + } + } + + if (keys %present || keys %absent) { + $result->{deprecated} = 0 if keys %present; + if (@oarches > 0) { + # We need to combine original and new data. Filter out hits + # (exact and wildcards) in @oarches first. + my $fail; + foreach my $arch (@oarches) { + my $not_arch = $1 if $arch =~ /^!+(.*)$/; + if (! defined $arch_neg) { + $arch_neg = ($not_arch) ? '!' : ''; + } elsif ($arch_neg ne (($not_arch) ? '!' : '')) { + $fail = 1; + $osym->add_tag("helper-arch", "mixed-arch-tag-not-supported"); + last; + } + if ($not_arch) { + if (! $self->is_arch_in_db($not_arch, \%present)) { + push @narches, $not_arch; + } else { + $arch_added++; + } + } else { + if (! $self->is_arch_in_db($arch, \%absent)) { + push @narches, $arch; + } + } + } + + return $result if $fail; + } + + if (@narches) { + # Now add new arches of the specified type + foreach my $arch (($arch_neg) ? (keys %absent) : (keys %present)) { + if (! $self->is_arch_in_db($arch, \@narches)) { + push @narches, $arch; + $arch_added++ if ! $arch_neg; + } + } + + # Finally set arch tag + $result->add_tag("arch", join(" ", map { "${arch_neg}$_" } sort(@narches))); + } else { # Original symbol has no arch tags + if ($total_arches > 2 && keys(%present) == $total_arches - 1) { + # Use !missing_arch if only a single arch is missing + my $missarch; + if (keys(%absent) == 1) { + $missarch = (keys %absent)[0]; + } else { + foreach my $arch (@latest, @non_latest) { + if (!exists $present{$arch}) { + $missarch = $arch; + last; + } + } + } + $result->add_tag("arch", "!$missarch"); + } elsif ($total_arches > 2 && keys(%absent) == $total_arches - 1) { + # Use arch if only present on a single arch + my $okarch; + if (keys(%present) == 1) { + $okarch = (keys %present)[0]; + } else { + foreach my $arch (@latest, @non_latest) { + if (!exists $absent{$arch}) { + $okarch = $arch; + last; + } + } + } + $result->add_tag("arch", $okarch); + } elsif (scalar(keys %present) <= scalar(keys %absent)) { + $result->add_tag("arch", join(" ", sort keys %present)); + } else { + $result->add_tag("arch", join(" ", map { "!$_" } sort(keys %absent))); + } + } + } + } + + # Bump symbol minver if new arches added + if (defined $result && keys(%present) && (!@oarches || $arch_added) && + ! $result->is_optional()) + { + $result->{minver} = $collection->get_latest_version(); + } + + return $result; +} + +sub prep_substs { + my ($self, $arch) = @_; + my $sym = $self->get_symbol($arch); + my $h_name = $sym->get_h_name(); + foreach my $subst (@{$self->{substs}}) { + $subst->prep($h_name, $arch); + } +} + +sub detect_substs { + my ($self, $main_arch) = @_; + + my %h_names = map { $_ => $self->get_symbol($_)->get_h_name() } $self->get_arches(); + my $h_name = $h_names{$main_arch}; + + my $detected = 0; + foreach my $subst (@{$self->{substs}}) { + if ($subst->detect($h_name, $main_arch, \%h_names)) { + $detected++; + # Make other h_names arch independent with regard to this handler. + foreach my $arch (keys %h_names) { + $subst->hinted_neutralize($h_names{$arch}, $h_name); + } + } + } + return $detected; +} + +1; |