#!/usr/bin/perl -w
#
# dh_coq - debhelper which computes Coq md5sums
#
# Copyright: 2022 Julien Puydt <jpuydt@debian.org>
#
# Created: 2022-06-02
#
# This is free software, you can redistribute it and/or modify it under the
# terms of the GNU General Public License version 2 or above 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; if not, write to the Free Software Foundation, Inc., 59 Temple
# Place, Suite 330, Boston, MA  02111-1307  USA

=head1 NAME

dh_coq - computes Coq packages provides, and partial dependencies

=cut

use strict;

use Debian::Debhelper::Dh_Lib;
use Digest::MD5;
use Dpkg::Control;
use Dpkg::Deps;
use File::Find;

init();

=head1 SYNOPSIS

B<dh_coq> [S<I<debhelper options>>]

=head1 DESCRIPTION

dh_coq is a debhelper program that is responsible for filling the
${coq:Provides} and ${coq:Depends} substitutions and adding them
to substvars files.

It only acts on a single type of binary packages: those shipping
Coq theories. Those are usually names libcoq-XXXX, but neither
libcoq-XXX-ocaml nor libcoq-XXX-ocaml-dev: those are OCaml library
packages and managed by dh_ocaml.

On those packages it takes responsibility for, it will look of the Coq
compiled files and compute a checksum. This checksum is stored in the
registry in /var/lib/coq/md5sums/, and already allows to say the
current package provides a checksummed-versioned virtual package. The
build-depends are then scanned, and their checksums are retrieved, so
the current package depends also on checksummed virtual packages.

The reason for this setup is that without the checksum, with just
dependencies on libcoq-XXX, ABI breakage can happen, because a new
libcoq-XXX won't be seen as incompatible, be installed and trigger
incoherent assumptions errors. With fully checksummed packages,
apt-get will not upgrade anything until all packages have migrated to
the new ABI.

The dependencies aren't extracted automatically from the compiled
project, but from the build-depends of the built package, so it's
crucial that those are accurate.

=head1 FILES

=over 4

=item I<debian/libcoq-XXX.volist>

By default, the list of Coq compiled files shipped by your package is
automatically computed, but if you need to override this, this files
makes it possible to fix the list. Files are considered relative to
the package build directory.

=back

=cut

umask 0022;

my $md5dir = "/var/lib/coq/md5sums";
my $md5ext = ".checksum";

sub is_coqpackage
{
    ($_) = @_;
    if (/^libcoq(.*)-ocaml-dev$/) {
	return 0;
    };
    if (/^libcoq(.*)-ocaml$/) {
	return 0;
    };
    if (/^libcoq(.*)$/) {
	return 1;
    };
    return 0;
};

sub compute_checksum
{
    my ($filelist) = @_;
    my $ctx = Digest::MD5->new;
    open FL, $filelist;
    foreach (<FL>)
    {
	open FH, $_;
	$ctx->addfile(*FH);
    };
    my $hash = $ctx->hexdigest();
    # now we do like in ocaml-md5sums: some base 36 encoding of the first digits,
    # so it's shorter
    my $sig = hex(substr($hash, 0, 6));
    my $digit;
    my @accu;
    for (my $i = 0; $i < 5; $i++)
    {
	$digit = $sig % 36;
	$sig = $sig / 36;
	push(@accu, (chr ($digit + ($digit < 10 ? (ord '0') : ((ord 'a')-10)))));
    };
    return join "", @accu;
};

verbose_print "Detect Coq packages";
my @coq_packages;
foreach (@{$dh{DOPACKAGES}}) {
    if (is_coqpackage($_)) {
	push(@coq_packages, $_);
    };
};

verbose_print "Compute the list of their .vo files";
my %volist;
foreach my $package (@coq_packages)
{
    my $volist_fn = "debian/".(pkgext $package)."volist.debhelper";
    $volist{$package} = $volist_fn;
    # remove existing file we could have built
    if ($volist_fn)
    {
	verbose_print("\tfirst remove previous $volist_fn");
	unlink $volist_fn unless $dh{NO_ACT};
    };
    # if developer provided a volist file, use that
    if (pkgfile($package, "volist"))
    {
	if (!$dh{NO_ACT})
	{
	    open(FIN, "<", pkgfile($package, "volist"));
	    open(FOUT, ">", $volist_fn);
	    foreach (<FIN>)
	    {
		print FOUT (tmpdir $package)."/$_\n";
	    };
	    close(FIN);
	    close(FOUT);
	};
    }
    else # no list provided, autodetect
    {
	if (!$dh{NO_ACT})
	{
	    my @search_path = tmpdir $package;
	    my @file_list;
	    find {
		'wanted' => sub { (-f $_) && (/\.vo$/)
				      && push(@file_list, $File::Find::name) }
	    }, @search_path;

	    open(VOLIST, ">", $volist_fn);
	    for my $fn (sort @file_list)
	    {
		print VOLIST $fn."\n"
	    }
	    close(VOLIST);
	    doit('cat', $volist_fn) if $dh{VERBOSE};
	};
    };
}

verbose_print "Compute the checksums of the coq packages";
my %checksums;
foreach my $package (@coq_packages)
{
    my $md5sum_fn = (tmpdir $package)."/$md5dir/$package$md5ext";
    doit(qw/mkdir -p/, dirname $md5sum_fn);
    my $checksum = compute_checksum($volist{$package});
    open FH, ">", $md5sum_fn;
    print FH $checksum;
    $checksums{$package} = $checksum;
};

verbose_print "Compute coq:Depends' value";
my @deps;
my $control = Dpkg::Control->new(type => CTRL_INFO_SRC);
if ($control->load('debian/control'))
{
    for my $field (grep /^Build-Depends/, keys %{$control})
    {
	for my $dep (split ", ", deps_parse($control->{$field}))
	{
	    if (is_coqpackage($dep))
	    {
		verbose_print $dep;
		if (-f "$md5dir/$dep$md5ext")
		{
		    open CK, "$md5dir/$dep$md5ext";
		    my $checksum = <CK>;
		    push @deps, "$dep-$checksum";
		}
		else
		{
		    push @deps, $dep;
		}
	    };
	};
    };
};
my $deps = join(', ', @deps);

verbose_print "Export the subsvars";
foreach my $package (@coq_packages)
{
    delsubstvar $package, "coq:Provides";
    delsubstvar $package, "coq:Depends";
    addsubstvar $package, "coq:Provides", "$package-".$checksums{$package};
    addsubstvar $package, "coq:Depends", "$deps";
};

=head1 SEE ALSO

L<debhelper(7)>

This program is a part of debhelper.

=head1 AUTHORS

Julien Puydt <jpuydt@debian.org>

=cut
