#!/usr/bin/perl

=head1 NAME

dh_vinylabi - calculate the Vinyl Cache ABI dependencies

=cut

use warnings;
use strict;

use Debian::Debhelper::Dh_Lib;
use Dpkg::Shlibs::Objdump;
use Dpkg::Version;
use File::Find;
use IPC::Open2;

=head1 SYNOPSIS

B<dh_vinylabi> [S<I<debhelper options>>] [B<--strict>] [B<--force>]

=head1 DESCRIPTION

B<dh_vinylabi> is a debhelper program that adds appropriate
I<vinylabi-*> dependencies on packages that build Vinyl Cache modules.

By default, a I<vinylabi-*> entry is added to B<misc:Depends> to
ensure that packages which contain modules that are loaded by Vinyl Cache
depend on an appropriate version of the I<vinyl-cache> package.

If B<--strict> is specified, then a dependency on I<vinylabi-strict-*>
is added too.

When building the vinyl-cache binary package the program will generate instead
B<misc:Provides> entries for I<vinylabi-*> and I<vinylabi-strict-*>.

Substvars entries are only added if a Vinyl Cache module is detected in
the build products, unless B<--force> is specified.
Modules are detected by searching a package's build products for
libraries installed in the Vinyl Cache vmods directory and containing a
symbol named B<vmod_version>.

Please note there is a B<dh> addon named B<vinylabi> which can be used to
automatically invoke B<dh_vinylabi> for you.

=head1 OPTIONS

=over 4

=item B<--force>

Do not try to detect Vinyl Cache modules in the package, and always
assume that a module is present. This will cause B<misc:Depends> to
always be populated.

=back

=head1 NOTES

Note that this command is not idempotent. L<dh_prep(1)> should be called
between invocations of this command (with the same arguments). Otherwise, it
may cause multiple instances of the same text to be added to the substition
variables.

Note that B<dh_vinylabi> should be run before B<dh_gencontrol>.
The B<vinylabi> sequence addon for B<dh> does the right thing, this
note is only relevant when you are calling B<dh_vinylabi> manually.

=cut

init(options => {
	force	=> \$dh{FORCE},
	strict	=> \$dh{STRICT},
});

sub detect_modules {
	my ($package) = @_;

	my $tmpdir = tmpdir($package);

	my @shared_objects;
	find({
		wanted => sub {
			my $name = $File::Find::name;
			return unless -f $name;
			return unless $name =~ m{^$tmpdir/usr/lib/[^/]+/vinyl-cache/vmods/libvmod_[^/]+\.so$};
			push(@shared_objects, $name);
		},
		no_chdir => 1,
	}, $tmpdir);

	my $od = Dpkg::Shlibs::Objdump->new;

	my @modules;
	foreach my $so (@shared_objects) {
		verbose_print("Scanning $so for symbol information");

		my $objid = $od->analyze($so);
		if (not $objid) {
			warning("Dpkg::Shlibs::Objdump couldn't parse $so");
			next;
		}

		my $object = $od->get_object($objid);
		my @symbols = $object->get_undefined_dynamic_symbols;
		my $vmod = grep { /^VAS_Fail$/ } map { $_->{name} } @symbols;
		if (not $vmod) {
			verbose_print("File $so does not look like a Vinyl Cache " .
				"module. Ignoring.");
			next;
		}

		push(@modules, $so);
	}

	return @modules;
}

my @substvars;
sub build_substvars {
	return if @substvars;

	# use the local headers if we are building the vinyl-cache source package
	my $include = $dh{FIRSTPACKAGE} eq 'vinyl-cache'
		? '-Iinclude' : '-I/usr/include/vinyl-cache';

	{
		my $pid = open2(my $in, my $out, 'cpp', '-', $include);
		print $out <<'END';
#include "vdef.h"
#include "vrt.h"
#include "vmod_abi.h"
VERSION=<VRT_MAJOR_VERSION.VRT_MINOR_VERSION>
VMOD_ABI=<VMOD_ABI_Version>
DEBIAN_VERSION=<VMOD_Debian_Version>
END
		close $out;

		my $cpp_output;
		{ local $/ = undef; $cpp_output = <$in>; }
		close $in;

		waitpid($pid, 0);
		error_exitcode('cpp') if $?;

		my ($version) = $cpp_output =~ /VERSION=<([0-9U\. ]+)>/;
		error('Could not find the Vinyl Cache ABI version in the headers')
			if not $version;
		$version =~ s/[U ]//g;

		my ($debversion) = $cpp_output =~ /DEBIAN_VERSION=<"([0-9a-z\.~+-]+)">/;
		error('Could not find the Debian package version in the headers')
			if not $debversion;
		# For these releases, Vinyl Cache breaks the ABI without incrementing
		# the declared ABI version.
		if ($version == '21.0') {
			if (version_compare_relation($debversion, REL_GE, '7.7.1')) {
				$version .= '-2';
			}
		}

		push(@substvars, "vinylabi-$version");
		verbose_print("Detected the ABI version vinylabi-$version");

		($version) = $cpp_output =~ /VMOD_ABI=<"Vinyl Cache \S+ ([a-f0-9]{40})">/;
		error('Could not find the Vinyl Cache strict ABI version in the headers')
			if not $version;
		push(@substvars, "vinylabi-strict-$version");
		verbose_print("Detected the strict ABI version vinylabi-strict-$version");
	}

	return;
}

# we are building the vinyl-cache source package
if ($dh{FIRSTPACKAGE} eq 'vinyl-cache') {
	build_substvars();

	foreach (@substvars) {
		addsubstvar('vinyl-cache', 'misc:Provides', $_, undef);
	}
	exit;
}

foreach my $package (@{$dh{DOPACKAGES}}) {
	next unless $dh{FORCE} or detect_modules($package);

	build_substvars();

	foreach (@substvars) {
		next if /^vinylabi-strict-/ and not $dh{STRICT};
		addsubstvar($package, 'misc:Depends', $_, undef);
	}
}

=head1 SEE ALSO

L<debhelper(7)>

=head1 AUTHOR

Originally written by Chris Boot <bootc@debian.org> as L<dh_ppp(7)>,
then Marco d'Itri <md@linux.it> modified it for Vinyl Cache.

=cut
