#!/usr/bin/perl

# Install Ada libraries from debian/tmp to their -dev and lib packages.
# Also helps /usr/share/ada/debian_packaging.mk.

# Copyright (C) 2012-2022 Nicolas Boulenguez <nicolas@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/>.

use strict;
use warnings;
use Debian::Debhelper::Dh_Lib;

init(options => {
    'export-versions' => \$dh{EXPORT_VERSIONS},
});

my $deb_ada_source_dir = "usr/share/ada/adainclude";
my $deb_lib_dir = 'usr/lib/' . dpkg_architecture_value 'DEB_HOST_MULTIARCH';
my $deb_ada_lib_info_dir = "$deb_lib_dir/ada/adalib";
my $deb_gnat_project_dir = "usr/share/gpr";

my $deb_gnat_version = `gnatmake --version` or error $!;
$deb_gnat_version =~ s/[^\n]* (\d+)\.\d+\.\d+\n.*/$1/s;

# ----------------------------------------------------------------------
# Search for library packages in debian/control.

# Keys are library names.
# lib_pkg and so_version may contain non-Ada libraries.
# dev_pkg and ali_version may only contain versioned -dev
my (%dev_pkg, %lib_pkg, %ali_version, %so_version);

for my $pkg (getpackages 'arch') {
    next unless $pkg =~ m/^lib(.*[^0-9.])(\d+(?:\.\d+)*)(?:-dev)?$/;
    $_ = $1;
    my $version = $2;
    # A dash is inserted when the name ends with a digit.
    s/-$//;
    # Underscores are allowed in projects and forbidden in .deb packages.
    # The reverse holds for dashes.
    tr/-/_/;

    if ($pkg =~ /v$/) {
        defined $dev_pkg{$_} and error "many lib$_-version-dev packages";
        $dev_pkg{$_}     = $pkg;
        $ali_version{$_} = $version;
    } else {
        defined $lib_pkg{$_} and error "many lib$_-versions packages";
        $lib_pkg{$_}    = $pkg;
        $so_version{$_} = $version;
    }
}
for (keys %dev_pkg) {
    defined $lib_pkg{$_} or error "no lib$_ package";
}

# ----------------------------------------------------------------------
# Special mode intended for inclusion in packaging.mk.

if ($dh{EXPORT_VERSIONS}) {
    print "DEB_ADA_SOURCE_DIR:=$deb_ada_source_dir\n";
    print "DEB_LIB_DIR:=$deb_lib_dir\n";
    print "DEB_ADA_LIB_INFO_DIR:=$deb_ada_lib_info_dir\n";
    print "DEB_GNAT_PROJECT_DIR:=$deb_gnat_project_dir\n";
    print "DEB_GNAT_VERSION:=$deb_gnat_version\n";
    for (keys %dev_pkg) {
        print "${_}_DEV_PKG:=$dev_pkg{$_}\n";
        print "${_}_ALI_VERSION:=$ali_version{$_}\n";
        print "${_}_LIB_PKG:=$lib_pkg{$_}\n";
        print "${_}_SO_VERSION:=$so_version{$_}\n";
    }
    exit;
}

# ----------------------------------------------------------------------
# Install libraries.

# Alert users of previous versions of this tool.
-e 'debian/ada_libraries' and error 'debian/ada_libraries is deprecated';
@ARGV and error 'non option command line arguments are deprecated';

# pkg, files..., destination
# Do nothing unless process_pkg.
# destination is a directory relative to tmpdir(pkg)
sub install {
    my $pkg = shift;
    return unless process_pkg $pkg;
    my $dest = tmpdir $pkg . '/' . pop;
    install_dir $dest;
    unshift @_, 'cp', '--reflink=auto', '-a';
    doit @_, $dest;
}

for my $name (keys %dev_pkg) {

    my (@dev_log, @lib_log);

    # ALI files
    my $ali_glob = "debian/tmp/$deb_ada_lib_info_dir/$name/*.ali";
    my @ali_files = glob $ali_glob or error "$ali_glob are misssing";
    push @dev_log, @ali_files;
    install $dev_pkg{$name}, @ali_files, "$deb_ada_lib_info_dir/$name";
    doit 'sed', '-i', '/^A -f[a-z]\+-prefix-map=/d',
        glob tmpdir $dev_pkg{$name} . "/$deb_ada_lib_info_dir/$name/*.ali"
        if process_pkg $dev_pkg{$name};

    # Sources
    my $src_glob = "debian/tmp/$deb_ada_source_dir/$name/*";
    my @src_files = glob $src_glob or error "$src_glob are missing";
    push @dev_log, @src_files;
    install $dev_pkg{$name}, @src_files, "$deb_ada_source_dir/$name";

    # Static archive
    my $a = "debian/tmp/$deb_lib_dir/lib$name.a";
    -e $a or error "$a is missing";
    push @dev_log, $a;
    install $dev_pkg{$name}, $a, $deb_lib_dir;

    # Project
    my $gpr = "debian/tmp/$deb_gnat_project_dir/$name.gpr";
    -e $gpr or error "$gpr is missing";
    push @dev_log, $gpr;
    install $dev_pkg{$name}, $gpr, $deb_gnat_project_dir;

    # Development symbolic link
    my $so = "debian/tmp/$deb_lib_dir/lib$name.so";
    -l $so or error "$so is missing or not a symbolic link";
    push @dev_log, $so;
    install $dev_pkg{$name}, $so, $deb_lib_dir;

    # Shared library
    # Dereference the development symbolic link as many times as necessary.
    # libfoo.so -> concrete file                       (libtool)
    # libfoo.so -> libfoo.so.5 -> concrete file        (cmake)
    # libfoo.so -> ../DEB_HOST_MULTIARCH/concrete file (gprinstall)
    my $shared_base;
    my $shared = $so;
    do {
        $shared_base = basename (readlink $shared or error $!);
        $shared = "debian/tmp/$deb_lib_dir/$shared_base";
    } while (-l $shared);
    -e $shared or error "$so must link to a file in the same directory";
    push @lib_log, $shared;
    install $lib_pkg{$name}, $shared, $deb_lib_dir;

    # Extract the shared objet name.
    my $soname;
    open(my $fh, "-|", "readelf -d $shared") or error $!;
    while (my $line = <$fh>) {
        $soname = $1 if ($line =~ /.*SONAME.*\[(.*)\]$/);
    }
    close $fh or error $!;

    # Check the target of the development symbolic link.
    my $so_deref = basename (readlink $so or error $!);
    $so_deref eq $shared_base or $so_deref eq $soname
        or error "$so -> $so_deref, expected $shared_base or $soname";

    # Ldconfig symbolic link
    if ($shared_base ne $soname) {
        my $ldcfg = "debian/tmp/$deb_lib_dir/$soname";
        -l $ldcfg or error "$ldcfg is missing or not a symbolic link";
        my $ldcfg_deref = readlink $ldcfg or error $!;
        $ldcfg_deref eq $shared_base
            or error "$ldcfg -> $ldcfg_deref, expected $shared_base";
        push @lib_log, $ldcfg;
        install $lib_pkg{$name}, $ldcfg, $deb_lib_dir;
    }

    # Static files unwantedly added by gprinstall.
    # They are logged for each -dev package, but this does not seem to hurt.
    my $gprtrash = 'debian/tmp/usr/unwantedly_gprinstalled';
    if (-d $gprtrash) {
        push @dev_log, $gprtrash;
        verbose_print "Not installing $gprtrash" if process_pkg $dev_pkg{$name};
    }

    # Libtool .la file
    my $la = "debian/tmp/$deb_lib_dir/lib$name.la";
    if (-e $la) {
        push @dev_log, $la;
        verbose_print "Not installing $la" if process_pkg $dev_pkg{$name};
    }

    # Log files for dh_missing.
    log_installed_files $lib_pkg{$name}, @lib_log;
    log_installed_files $dev_pkg{$name}, @dev_log;

    # ada:Depends
    if (process_pkg $dev_pkg{$name}) {
        addsubstvar $dev_pkg{$name}, 'ada:Depends',
                    $lib_pkg{$name}, '= ${binary:Version}';

        addsubstvar $dev_pkg{$name}, 'ada:Depends',
            'gnat', ">= $deb_gnat_version";
        addsubstvar $dev_pkg{$name}, 'ada:Depends',
            'gnat', '<< ' . ($deb_gnat_version + 1);

        open (my $fh, q{<}, $gpr) or error $!;
        while (<$fh>) {
            next unless m/^with "([a-z0-9_]+)(?:\.gpr)?";$/;
            # Fresh packages take priority over installed versions.
            if ($dev_pkg{$1}) {
                addsubstvar $dev_pkg{$name}, 'ada:Depends',
                            $dev_pkg{$1}, '= ${binary:Version}';
                next;
            }
            my @p = `dpkg-query --search /$deb_gnat_project_dir/$1.gpr`;
            if (@p) {
                chomp @p;
                addsubstvar $dev_pkg{$name}, 'ada:Depends', $p[0] =~ s/:.*//r;
                next;
            }
            warning "$gpr needs $1.gpr, no -dev package found";
        }
        close $fh;
    }
}
