#!/usr/bin/perl

=head1 NAME

dh_varnishabi - calculate the Varnish ABI dependencies

=cut

use warnings;
use strict;

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

=head1 SYNOPSIS

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

=head1 DESCRIPTION

B<dh_varnishabi> is a debhelper program that adds appropriate
I<varnishabi-*> dependencies on packages that build varnish modules.

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

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

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

Substvars entries are only added if a Varnish 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 Varnish vmods directory and containing a
symbol named B<vmod_version>.

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

=head1 OPTIONS

=over 4

=item B<--force>

Do not try to detect Varnish 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_varnishabi> should be run before B<dh_gencontrol>.
The B<varnishabi> sequence addon for B<dh> does the right thing, this
note is only relevant when you are calling B<dh_varnishabi> 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/[^/]+/varnish/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 Varnish " .
				"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 varnish source package
	my $include = $dh{FIRSTPACKAGE} eq 'varnish'
		? '-Iinclude' : '-I/usr/include/varnish';

	{
		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>
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 Varnish ABI version in the headers')
			if not $version;
		$version =~ s/[U ]//g;
		push(@substvars, "varnishabi-$version");
		verbose_print("Detected the ABI version varnishabi-$version");

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

	return;
}

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

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

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

	build_substvars();

	foreach (@substvars) {
		next if /^varnish-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 Varnish.

=cut
