# Copyright © 2022 Felix Lechner <felix.lechner@lease-up.com>
#
# 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 2 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, you can find it on the World Wide
# Web at http://www.gnu.org/copyleft/gpl.html, or write to the Free
# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
# MA 02110-1301, USA.

package Debian::Debhelper::Buildsystem::Haskell::Recipes;

use v5.20;
use warnings;
use utf8;

use Exporter qw(import);

our @EXPORT_OK;

BEGIN {

    @EXPORT_OK = qw(
      run
      cpu
      os
      ghcjs_version
      ghcjs_ghc_version
      package_prefix
      package_hc
      package_ext
      packages_hc
      hc_libdir
      package_libdir
      hc_pkgdir
      package_pkgdir
      hc_prefix
      hc_haddock
      hc_docdir
      hc_htmldir
      hc_hoogle
      strip_hash
      sort_uniq
      dependency
      ghc_pkg_field
      providing_package_for_ghc
      providing_package_for_ghc_prof
      cabal_package_ids
      cabal_depends
      hashed_dependency
      depends_for_ghc
      depends_for_ghc_prof
      usable_ghc_pkg
      tmp_package_db
      provides_for_ghc
      provides_for_ghc_prof
      package_id_to_virtual_package
      depends_for_hugs
      find_config_for_ghc
      clean_recipe
      make_setup_recipe
      configure_recipe
      build_recipe
      check_recipe
      haddock_recipe
      extra_depends_recipe
      install_dev_recipe
      install_prof_recipe
      install_doc_recipe
    );
}

use Const::Fast;
use Cwd;
use Date::Parse qw(str2time);
use File::Basename;
use IPC::Run3;
use List::SomeUtils qw(uniq any first_value);
use Path::Tiny;
use Unicode::UTF8 qw(encode_utf8 decode_utf8);

const my $EMPTY => q{};
const my $SPACE => q{ };
const my $PLUS => q{+};
const my $HYPHEN => q{-};
const my $COMMA => q{,};
const my $SEMICOLON => q{;};
const my $LESS_THAN => q{<};
const my $DOUBLE_LESS_THAN => $LESS_THAN x 2;
const my $NEWLINE => qq{\n};

const my $WAIT_STATUS_SHIFT => 8;

const my $MINIMUM_GHC_VERSION => 8;
const my $CABAL_VERSION_IMPLYING_SIMPLE_BUILDS => 2.2;

=head1 NAME

Debian::Debhelper::Buildsystem::Haskell::Recipes -- Recipes for the Haskell build system in Debhelper

=head1 SYNOPSIS

 Debian::Debhelper::Buildsystem::Haskell::Recipes;

=head1 DESCRIPTION

A library with recipes for the Haskell build system in Debhelper.

=head1 SUBROUTINES

=over 4

=item run_quiet

=cut

sub run_quiet {
    my (@command) = @_;

    my @command_bytes = map { encode_utf8($_) } @command;

    my $stdout_bytes;
    my $stderr_bytes;
    run3(\@command_bytes, \undef, \$stdout_bytes, \$stderr_bytes);

    my $wait_status = $?;
    my $exitcode = ($wait_status >> $WAIT_STATUS_SHIFT);

    # already in UTF-8
    die encode_utf8("Non-zero exit code $exitcode.")
      . $NEWLINE
      . $stdout_bytes
      . $stderr_bytes
      if $exitcode;

    my $output = decode_utf8($stdout_bytes // $EMPTY);
    chomp $output;

    return $output;
}

=item run

=cut

sub run {
    my (@command) = @_;

    my @filtered = grep { length } @command;

    say {*STDERR} encode_utf8("Running @filtered")
      if !$ENV{DH_QUIET} || $ENV{DH_VERBOSE};

    my $output = run_quiet(@filtered);

    say {*STDERR} encode_utf8($output)
      if !$ENV{DH_QUIET} || $ENV{DH_VERBOSE};

    return $output;
}

=item cpu

=cut

sub cpu {
    my () = @_;

    my $output = run(qw{ghc -e}, 'putStr System.Info.arch');

    return $output;
}

=item os

=cut

sub os {
    my () = @_;

    my $output = run(qw{ghc -e}, 'putStr System.Info.os');

    return $output;
}

=item ghcjs_version

=cut

sub ghcjs_version {
    my () = @_;

    my $output = run(qw{ghcjs --numeric-ghcjs-version});

    return $output;
}

=item ghcjs_ghc_version

=cut

sub ghcjs_ghc_version {
    my () = @_;

    my $output = run(qw{ghcjs --numeric-ghc-version});

    return $output;
}

=item package_prefix

=cut

sub package_prefix {
    my ($installable) = @_;

    my $prefix = $installable;

    # strip hackage name
    $prefix =~ s{ - .* $}{}x;

    return $prefix;
}

=item package_hc

=cut

sub package_hc {
    my ($installable) = @_;

    return 'ghc'
      if $installable =~ m{^ ghc (:? -prof )? $};

    my $compiler = $installable;

    # strip hackage name
    $compiler =~ s{ - .* $}{}x;

    # strip lib prefix
    $compiler =~ s{^ lib }{}x;

    return $compiler;
}

=item package_ext

=cut

sub package_ext {
    my ($installable) = @_;

    return 'dev'
      if $installable eq 'ghc';

    return 'prof'
      if $installable eq 'ghc-prof';

    my $suffix = $installable;

    # strip hackage name
    $suffix =~ s{^ .* - }{}x;

    return $suffix;
}

=item packages_hc

=cut

sub packages_hc {
    my () = @_;

    # fix sort order
    local $ENV{LC_ALL} = 'C';

    # should be in UTF-8
    my @installables = split($SPACE, $ENV{DEB_PACKAGES} // $EMPTY);

    my @compilers = uniq map { package_hc($_) } @installables;

    die encode_utf8(
        'Multiple compilers not supported: ' . join($SPACE, (sort @compilers)))
      if @compilers > 1;

    return $ENV{DEB_DEFAULT_COMPILER}
      if !@compilers;

    return $compilers[0];
}

=item hc_libdir

=cut

sub hc_libdir {
    my ($compiler) = @_;

    return 'usr/lib/haskell-packages/ghc/lib'
      if $compiler eq 'ghc';

    return 'usr/lib/ghcjs/.cabal/lib'
      if $compiler eq 'ghcjs';

    die encode_utf8("Don't know package_libdir for $compiler");
}

=item package_libdir

=cut

sub package_libdir {
    my ($installable) = @_;

    my $compiler = package_hc($installable);

    return hc_libdir($compiler);
}

=item hc_pkgdir

=cut

sub hc_pkgdir {
    my ($compiler) = @_;

    return 'var/lib/ghc/package.conf.d'
      if $compiler eq 'ghc';

    if ($compiler eq 'ghcjs') {

        my $quadruplet
          = cpu()
          . $HYPHEN
          . os()
          . $HYPHEN
          . ghcjs_version()
          . $HYPHEN
          . ghcjs_ghc_version();

        return "usr/lib/ghcjs/.ghcjs/$quadruplet/ghcjs/package.conf.d";
    }

    die encode_utf8("Don't know pkgdir for $compiler");
}

=item package_pkgdir

=cut

sub package_pkgdir {
    my ($installable) = @_;

    my $compiler = package_hc($installable);

    return hc_pkgdir($compiler);
}

=item hc_prefix

=cut

sub hc_prefix {
    my ($compiler) = @_;

    return 'usr'
      if $compiler eq 'ghc';

    return 'usr/lib/ghcjs'
      if $compiler eq 'ghcjs';

    die encode_utf8("Don't know prefix for $compiler");
}

=item hc_haddock

=cut

sub hc_haddock {
    my ($compiler) = @_;

    return 'haddock'
      if $compiler eq 'ghc';

    return 'haddock-ghcjs'
      if $compiler eq 'ghcjs';

    die encode_utf8("Don't know haddock command for $compiler");
}

=item hc_docdir

=cut

sub hc_docdir {
    my ($compiler, $hackage_name_version) = @_;

    return "usr/lib/$compiler-doc/haddock/$hackage_name_version/";
}

=item hc_htmldir

=cut

sub hc_htmldir {
    my ($compiler, $hackage_name) = @_;

    $ENV{CABAL_PACKAGE} = $hackage_name;

    return "usr/share/doc/lib$compiler-$hackage_name-doc/html/";
}

=item hc_hoogle

=cut

sub hc_hoogle {
    my ($compiler) = @_;

    return "/usr/lib/$compiler-doc/hoogle/";
}

=item strip_hash

=cut

sub strip_hash {
    my ($with_hash) = @_;

    my $without_hash = $with_hash;
    $without_hash =~ s{ - [^-]{32} $}{}x;

    return $without_hash;
}

=item sort_uniq

=cut

sub sort_uniq {
    my (@items) = @_;

    # fix sort order
    local $ENV{LC_ALL} = 'C';

    my @sorted = sort +uniq @items;

    return @sorted;
}

=item dependency

=cut

sub dependency {
    my ($installable) = @_;

    my $version
      = run(qw{dpkg-query --showformat=${Version} --show}, $installable);

    my $next_upstream_version = $version;
    $next_upstream_version =~ s{ - [^-]* $}{}x;
    $next_upstream_version .= $PLUS;

    return
      "$installable (>= $version), $installable (<< $next_upstream_version)";
}

=item ghc_pkg_field

=cut

sub ghc_pkg_field {
    my ($compiler, $hackage_name, $field) = @_;

    my $output
      = run("$compiler-pkg", qw{--global field}, $hackage_name, $field);

    # may not process multi-line fields correctly
    my ($value) = split($NEWLINE, $output, 2);

    return ($value // $EMPTY);
}

=item providing_package_for_ghc

=cut

sub providing_package_for_ghc {
    my ($compiler, $package_id, $extension) = @_;

    $extension //= $EMPTY;

    my $ghc_version= run(qw{dpkg-query --showformat=${Version} --show ghc});

    my $hackage_name;
    if (
        system(
            qw{dpkg --compare-versions}, $ghc_version,
            $DOUBLE_LESS_THAN, $MINIMUM_GHC_VERSION
        ) == 0
    ) {
        $hackage_name = strip_hash($package_id);
    } else {
        $hackage_name = $package_id;
    }

    my $directory_line
      = ghc_pkg_field($compiler, $hackage_name, 'library-dirs');
    my (undef, $directory_list) = split(m{ \s* : \s* }x, $directory_line, 2);
    my @library_dirs = split(m{ \s* , \s* }x, $directory_list);

    my $library_line = ghc_pkg_field($compiler, $hackage_name, 'hs-libraries');
    my (undef, $library_list) = split(m{ \s* : \s* }x, $library_line, 2);
    my @libraries = split(m{ \s* , \s* }x, $library_list);

    # look only at the first one
    my $library = $libraries[0];

    for my $directory (@library_dirs) {

        my $library_path = "$directory/lib$library$extension.a";
        next
          unless -e $library_path;

        my $line = run(qw{dpkg-query --search}, $library_path);
        my ($installable) = split(m{ \s* : \s* }x, $line, 2);

        return $installable;
    }

    return ();
}

=item providing_package_for_ghc_prof

=cut

sub providing_package_for_ghc_prof {
    my ($compiler, $package_id) = @_;

    return providing_package_for_ghc($compiler, $package_id, '_p');
}

=item cabal_pkg_ids

=cut

sub cabal_package_ids {
    my (@configs) = @_;

    my @ghc_pkg = tmp_package_db(@configs);

    my @package_ids;
    for my $config (@configs) {

        my $name = path($config)->basename(qr{ [.]conf $}x);
        push(@package_ids,
            run(@ghc_pkg, qw{--simple-output field}, $name, 'id'));
    }

    return @package_ids;
}

=item cabal_depends

=cut

sub cabal_depends {
    my (@configs) = @_;

    # fix sort order
    local $ENV{LC_ALL} = 'C';

    my @ghc_pkg = tmp_package_db(@configs);

    my @constraints;
    for my $config (@configs) {

        my $name = path($config)->basename(qr{ [.]conf $}x);
        my $depends
          = run(@ghc_pkg, qw{--simple-output field}, $name, 'depends');
        push(@constraints, split($SPACE, $depends // $EMPTY));
    }

    my @have = sort +uniq @constraints;
    my @exclude_patterns = split($SPACE, $ENV{DH_EXCLUDES} // $EMPTY);

    # not sure this complies with Debhelper expectations
    # excluded installables matching the patterns with or without version
    # the versions should probably be dropped by the caller
    s{ - [0-9] [.0-9a-zA-Z]* $}{}x for @exclude_patterns;

    my @retained;
    for my $constraint (@have) {

        next
          if any { $constraint =~ m{\Q$_\E} } @exclude_patterns;

        push(@retained, $constraint);
    }

    return @retained;
}

=item hashed_dependency

=cut

sub hashed_dependency {
    my ($compiler, $type, $package_id) = @_;

    my @ghc_pkg = usable_ghc_pkg();
    my $virtual_pkg
      = package_id_to_virtual_package($compiler, $type, $package_id, @ghc_pkg);

    # as a transition measure, check if dpkg knows about this virtual package
    return $virtual_pkg
      if system("dpkg-query --show $virtual_pkg > /dev/null 2> /dev/null")== 0;

    return ();
}

=item composite_constraints

=cut

sub composite_constraints {
    my ($compiler, $type, @configs) = @_;

    my @package_ids = cabal_depends(@configs);

    my @constraints;
    for my $package_id (@package_ids) {

        my $constraint = hashed_dependency($compiler, $type, $package_id);

        if (!length $constraint) {

            my $installable= providing_package_for_ghc($compiler, $package_id);

            $constraint = dependency($installable);
            if (!length $constraint) {

                warn encode_utf8(
"WARNING: No Debian package provides haskell package $package_id.\n"
                );
                next;
            }
        }

        push(@constraints, $constraint);
    }

    return join($COMMA . $SPACE, @constraints);
}

=item depends_for_ghc_prof

=cut

sub depends_for_ghc {
    my ($compiler, @configs) = @_;

    return composite_constraints($compiler, 'dev', @configs);
}

=item depends_for_ghc_prof

=cut

sub depends_for_ghc_prof {
    my ($compiler, @configs) = @_;

    return composite_constraints($compiler, 'prof', @configs);
}

=item usable_ghc_pkg

=cut

sub usable_ghc_pkg {
    my () = @_;

    my @ghc_pkg;
    my $version;

    my $inplace_ghc_pkg = 'inplace/bin/ghc-pkg';
    if (-x $inplace_ghc_pkg) {

        # we are building ghc and need to use the new ghc-pkg
        @ghc_pkg = ($inplace_ghc_pkg);
        $version = run(qw{dpkg-parsechangelog --show-field Version});

    } else {
        @ghc_pkg = qw{ghc-pkg};
        $version= run(qw{dpkg-query --showformat=${Version} --show ghc});
    }

    # ghc-pkg prior to version 8 is unusable for our purposes.
    return ()
      if system(qw{dpkg --compare-versions},
        $version, $DOUBLE_LESS_THAN, $MINIMUM_GHC_VERSION)== 0;

    return @ghc_pkg;
}

=item tmp_package_db

=cut

sub tmp_package_db {
    my (@configs) = @_;

    my @ghc_pkg = usable_ghc_pkg();
    return ()
      unless @ghc_pkg;

    if (!-e 'debian/tmp-db/package.cache') {

        run(qw{mkdir debian/tmp-db});
        run(qw{cp}, @configs, 'debian/tmp-db/');

        # Silence GHC 8.4's warning
        # "ignoring (possibly broken) abi-depends field for packages"
        # see also https://ghc.haskell.org/trac/ghc/ticket/14381
        run(@ghc_pkg, qw{--package-db debian/tmp-db/ recache});
    }

    return (@ghc_pkg, qw{--package-db debian/tmp-db});
}

=item composite_provides

=cut

sub composite_provides {
    my ($compiler, $type, @configs) = @_;

    my @ghc_pkg = tmp_package_db(@configs);
    my @package_ids = cabal_package_ids(@configs);

    my @virtual;
    push(@virtual,
        package_id_to_virtual_package($compiler, $type, $_, @ghc_pkg))
      for @package_ids;

    return join($COMMA . $SPACE, @virtual);
}

=item provide_for_ghc

=cut

sub provides_for_ghc {
    my ($compiler, @configs) = @_;

    return composite_provides($compiler, 'dev', @configs);
}

=item provide_for_ghc_prof

=cut

sub provides_for_ghc_prof {
    my ($compiler, @configs) = @_;

    return composite_provides($compiler, 'prof', @configs);
}

=item package_id_to_virtual_package

=cut

sub package_id_to_virtual_package {
    my ($compiler, $type, $package_id, @ghc_pkg) = @_;

    my $name;
    my $version;
    my $long_abi;

    if (@ghc_pkg) {

        $name = run(@ghc_pkg, qw{--simple-output --unit-id field},
            $package_id, 'name');
        $version = run(@ghc_pkg, qw{--simple-output --unit-id field},
            $package_id, 'version');
        $long_abi = run(@ghc_pkg, qw{--simple-output --unit-id field},
            $package_id, 'abi');

    } else {

        # no usable ghc-pkg; parse package id
        my $lowercase = lc $package_id;

        ($name, $version, $long_abi)
          = ($lowercase =~ m{^ ([a-z0-9-]+) - ([0-9.]+) - (\S{32}) $}x);
    }

    # retain only the first five hex digits from abi out of 32
    my ($short_abi) = ($long_abi =~ m{^ (\S{5}) }x);

    my $virtual = lc "lib$compiler-$name-$type-$version-$short_abi";

    return $virtual;
}

=item depends_for_hugs

=cut

sub depends_for_hugs {
    my () = @_;

    my $version= run(qw{dpkg-query --showformat=${Version} --show hugs});
    my $upstream_version = $version;
    $upstream_version =~ s{ - [^-]* $}{}x;

    return "hugs (>= $upstream_version)";
}

=item find_config_for_ghc

=cut

sub find_config_for_ghc {
    my ($installable) = @_;

    my $pkgdir = package_pkgdir($installable);

    if ($installable =~ s{ - prof $}{}x) {

        $installable .= '-dev'
          unless $installable eq 'ghc';
    }

    my @configs = grep { $_->is_file }
      path("debian/$installable/$pkgdir")->children(qr{ [.]conf $}x);

    return @configs;
}

=item clean_recipe

=cut

sub clean_recipe {
    my () = @_;

    run($ENV{DEB_SETUP_BIN_NAME}, qw{clean})
      if -x $ENV{DEB_SETUP_BIN_NAME};

    run(
        qw{rm -rf dist dist-ghc dist-ghcjs dist-hugs},
        $ENV{DEB_SETUP_BIN_NAME},
        qw{Setup.hi Setup.ho Setup.o},
        glob('.*config*'));
    run(
        qw{rm -f configure-ghc-stamp configure-ghcjs-stamp build-ghc-stamp build-ghcjs-stamp build-hugs-stamp build-haddock-stamp}
    );
    run(qw{rm -rf debian/tmp-inst-ghc debian/tmp-inst-ghcjs});
    run(qw{rm -f debian/extra-depends-ghc debian/extra-depends-ghcjs});

    if (-e $ENV{DEB_LINTIAN_OVERRIDES_FILE}) {
        run(
            qw{sed -i},
            '/binary-or-shlib-defines-rpath/ d',
            $ENV{DEB_LINTIAN_OVERRIDES_FILE});
        run('find',$ENV{DEB_LINTIAN_OVERRIDES_FILE},qw{-empty -delete});
    }

    run(qw{rm -f}, $ENV{MAKEFILE});
    run(qw{rm -rf debian/dh_haskell_shlibdeps});
    run(qw{rm -rf debian/tmp-db});

    return;
}

=item make_setup_recipe

=cut

sub make_setup_recipe {
    my () = @_;

    my $shipped_setup = first_value { -e } qw{Setup.lhs Setup.hs};

    if (length $shipped_setup) {

        run(qw{ghc --make}, $shipped_setup, '-o',$ENV{DEB_SETUP_BIN_NAME});
        return;
    }

# Having a Setup.hs is considered good practice, but there are a few
# Haskell packages that don't, since cabal does not use it for build types
# other than 'Custom'. Find out the build type and use the corresponding
# standardized Setup.hs file. For more information, see
# https://www.haskell.org/cabal/users-guide/developing-packages.html#pkg-field-build-type

    my $cabal_path = "$ENV{CABAL_PACKAGE}.cabal";
    my $cabal_contents = path($cabal_path)->slurp_utf8;

    my $cabal_version= first_value { length }
    ($cabal_contents
          =~ m{^ cabal-version: (?: \s+ [<>=]+ )? \s+ (\S+) \s* $}mix);
    die encode_utf8("No cabal-version in $cabal_path")
      unless length $cabal_version;

    my $build_type= first_value { length }
    ($cabal_contents =~ m{^ build-type: \s+ (\w+) \s* }mix);

  # https://cabal.readthedocs.io/en/3.4/cabal-package.html#pkg-field-build-type
    my $default_build_type = 'Simple';
    $default_build_type = 'Custom'
      if $cabal_version < $CABAL_VERSION_IMPLYING_SIMPLE_BUILDS
      || $cabal_contents =~ m{^ custom-setup \s+ }mi;

    $build_type //= $default_build_type;

    my $stock_setup = "/usr/share/haskell-devscripts/Setup-$build_type.hs";
    die encode_utf8("Could not find a suitable Setup.hs file for $build_type")
      unless -e $stock_setup;

    run(
        qw{ghc --make}, $stock_setup, '-o',
        $ENV{DEB_SETUP_BIN_NAME},
        qw{-outputdir debian/tmp-setup-hs}
    );

    return;
}

=item configure_recipe

=cut

sub configure_recipe {
    my () = @_;

    # deleted when out of scope
    my $time_reference = Path::Tiny->tempfile;
    $time_reference->touch(str2time('1975-01-01', 'UTC'));

    # dak does not like file timestamps older than 1975
    # new tarballs from Hackage have files with mtimes at the
    # beginning of the epoch, so set old mtimes to 1998
    run(
        qw{find . ! -newer},
        $time_reference->stringify,
        qw{-exec touch -d "1998-01-01 UTC" {} ; }
    );

    my $compiler = packages_hc();

    my @installables = split($SPACE, $ENV{DEB_PACKAGES} // $EMPTY);
    my @extensions = map { package_ext($_) } @installables;

    my $profiling;
    $profiling = '--enable-library-profiling'
      if any { $_ eq 'prof' } @extensions;

    my $ldflags_line = run(qw{dpkg-buildflags --get LDFLAGS});
    my @ldflags = split($SPACE, $ldflags_line);
    my @ghc_options = map { "--ghc-option=-optl$_" } @ldflags;

    my $pkgdir = hc_pkgdir($compiler);
    my $prefix = hc_prefix($compiler);
    my $libdir = hc_libdir($compiler);
    my $docdir
      = hc_docdir($compiler, "$ENV{CABAL_PACKAGE}-$ENV{CABAL_VERSION}");
    my $htmldir = hc_htmldir($compiler, $ENV{CABAL_PACKAGE});

    # DEB_SETUP_GHC_CONFIGURE_ARGS can contain multiple arguments
    # with their own quoting so run through shell a expansion
    my $ghc_configure_args = run_quiet(qw{sh -c echo -n},
        split($SPACE, $ENV{DEB_SETUP_GHC_CONFIGURE_ARGS} // $EMPTY));

    # the versioned form DEB_SETUP_GHC6_CONFIGURE_ARGS should perhaps be
    # abandoned in favor of the unversioned DEB_SETUP_GHC_CONFIGURE_ARGS

    run(
        $ENV{DEB_SETUP_BIN_NAME},
        'configure',
        "--$compiler",
        '-v2',
        "--package-db=/$pkgdir",
        "--prefix=/$prefix",
        "--libdir=/$libdir",
        '--libexecdir=/usr/lib',
        "--builddir=dist-$compiler",
        @ghc_options,
        "--haddockdir=/$docdir",
        "--datasubdir=$ENV{CABAL_PACKAGE}",
        "--htmldir=/$htmldir",
        $profiling,
        $ENV{NO_GHCI_FLAG},
        split($SPACE, $ENV{DEB_SETUP_GHC6_CONFIGURE_ARGS} // $EMPTY),
        split($SPACE, $ghc_configure_args // $EMPTY),
        split($SPACE, $ENV{OPTIMIZATION} // $EMPTY),
        split($SPACE, $ENV{TESTS} // $EMPTY));

    return;
}

=item build_recipe

=cut

sub build_recipe {
    my () = @_;

    my $compiler = packages_hc();
    my $output
      = run($ENV{DEB_SETUP_BIN_NAME},'build', "--builddir=dist-$compiler");

    return;
}

=item check_recipe

=cut

sub check_recipe {
    my () = @_;

    my $compiler = packages_hc();
    my $output = run($ENV{DEB_SETUP_BIN_NAME},
        'test', "--builddir=dist-$compiler", '--show-details=direct');

    return;
}

=item haddock_recipe

=cut

sub haddock_recipe {
    my () = @_;

    my $compiler = packages_hc();
    my $haddock = hc_haddock($compiler);

    return
      unless -x "/usr/bin/$haddock";

    run(
        $ENV{DEB_SETUP_BIN_NAME},
        'haddock',
        "--builddir=dist-$compiler",
        "--with-haddock=/usr/bin/$haddock",
        "--with-ghc=$compiler",
        '--verbose=2',
        split($SPACE, $ENV{DEB_HADDOCK_OPTS} // $EMPTY));

    return;
}

=item extra_depends_recipe

=cut

sub extra_depends_recipe {
    my ($compiler) = @_;

    local $ENV{LC_ALL} = 'C.UTF-8';

    my $output = run($ENV{DEB_SETUP_BIN_NAME},
        'register', "--builddir=dist-$compiler",
        qw{--gen-pkg-config --verbose=verbose+nowrap});

    die encode_utf8('Cannot get name of package registration file.')
      unless $output
      =~ m{^Creating \s package \s registration \s file: \s+ (\S+) $}mx;

    my $pkg_config = $1;

    run('dh_haskell_extra_depends', $compiler, $pkg_config);
    run(qw{rm -f}, $pkg_config);

    return;
}

=item install_dev_recipe

=cut

sub install_dev_recipe {
    my ($installable) = @_;

    local $ENV{LC_ALL} = 'C.UTF-8';

    my $compiler = package_hc($installable);
    my $libdir = package_libdir($installable);
    my $pkgdir = package_pkgdir($installable);

    my $savedir = Cwd::getcwd;
    chdir("debian/tmp-inst-$compiler")
      or warn encode_utf8("Cannot change folder to debian/tmp-inst-$compiler");

    run(qw{mkdir --parents}, $libdir);
    run(
        'find', $libdir,
        qw{ ( ! -name *_p.a ! -name *.p_hi ! -type d ) },
        qw{-exec install -Dm 644},
        '{}', "../$installable/{}", $SEMICOLON
    );

    chdir($savedir)
      or warn encode_utf8("Cannot change folder to $savedir");

    my $output = run($ENV{DEB_SETUP_BIN_NAME},
        'register', "--builddir=dist-$compiler",
        qw{--gen-pkg-config --verbose=verbose+nowrap});

    die encode_utf8('Cannot get name of package registration file.')
      unless $output
      =~ m{^Creating \s package \s registration \s file: \s+ (\S+) $}mx;

    my $pkg_config = $1;

    run(qw{sed -i}, 's/^exposed: True$/exposed: False/', $pkg_config)
      if length $ENV{HASKELL_HIDE_PACKAGES};

    run(qw{install -Dm 644},
        $pkg_config, "debian/$installable/$pkgdir/$pkg_config");
    run(qw{rm -f}, $pkg_config);

    if (length $ENV{DEB_GHC_EXTRA_PACKAGES}) {

        my $EP_DIR
          = "debian/$installable/usr/lib/haskell-packages/extra-packages";
        run(qw{mkdir --parents}, $EP_DIR);

        path("$EP_DIR/$ENV{CABAL_PACKAGE}-$ENV{CABAL_VERSION}")
          ->spew_utf8($ENV{DEB_GHC_EXTRA_PACKAGES});
    }

    path($ENV{DEB_LINTIAN_OVERRIDES_FILE})
      ->append_utf8('binary-or-shlib-defines-rpath' . $NEWLINE)
      unless -e $ENV{DEB_LINTIAN_OVERRIDES_FILE}
      && path($ENV{DEB_LINTIAN_OVERRIDES_FILE})->slurp_utf8
      =~ m{^ binary-or-shlib-defines-rpath }x;

    run('dh_haskell_provides', "--package=$installable");
    run('dh_haskell_depends', "--package=$installable");
    run('dh_haskell_shlibdeps', "--package=$installable");

    return;
}

=item install_prof_recipe

=cut

sub install_prof_recipe {
    my ($installable) = @_;

    my $compiler = package_hc($installable);
    my $libdir = package_libdir($installable);

    my $savedir = Cwd::getcwd;
    chdir("debian/tmp-inst-$compiler")
      or warn encode_utf8("Cannot change folder to debian/tmp-inst-$compiler");

    run(qw{mkdir --parents}, $libdir);
    run(
        'find', $libdir,
        qw{ ( -name *_p.a -o -name *.p_hi ) },
        qw{-exec install -Dm 644},
        '{}', "../$installable/{}", $SEMICOLON
    );

    chdir($savedir)
      or warn encode_utf8("Cannot change folder to $savedir");

    run('dh_haskell_provides', "--package=$installable");
    run('dh_haskell_depends', "--package=$installable");

    return;
}

=item install_doc_recipe

=cut

sub install_doc_recipe {
    my ($installable) = @_;

    my $compiler = package_hc($installable);

    my $htmldir = hc_htmldir($compiler, $ENV{CABAL_PACKAGE});
    run(qw{mkdir --parents}, "debian/$installable/$htmldir");

    my $savedir = Cwd::getcwd;
    chdir("debian/tmp-inst-$compiler")
      or warn encode_utf8("Cannot change folder to debian/tmp-inst-$compiler");

    run(
        'find', "./$htmldir",
        qw{ ! -name *.haddock ! -type d},
        qw{-exec install -Dm 644},
        '{}', "../$installable/{}", $SEMICOLON
    );

    chdir($savedir)
      or warn encode_utf8("Cannot change folder to $savedir");

    my $docdir
      = hc_docdir($compiler, "$ENV{CABAL_PACKAGE}-$ENV{CABAL_VERSION}");

    my $tmp_inst_docdir = "debian/tmp-inst-$compiler/$docdir";
    my $installable_docdir = "debian/$installable/$docdir";

    run(qw{mkdir --parents}, $installable_docdir);

    run(qw{cp --recursive}, $_->stringify, $installable_docdir)
      for path($tmp_inst_docdir)->children(qr{ [.]haddock $}x);

    if ($ENV{DEB_ENABLE_HOOGLE} eq 'yes') {

        # We cannot just invoke dh_link here because that acts on
        # either libghc-*-dev or all the binary packages, neither of
        # which is desirable (see dh_link (1)).  So we just create a
        # (policy-compliant) symlink ourselves

        my $target = "debian/$installable/$htmldir/$ENV{CABAL_PACKAGE}.txt";
        if (-e $target) {

            my $hoogle = hc_hoogle($compiler);
            my $name = "debian/$installable$hoogle$installable.txt";

            run(qw{mkdir --parents}, dirname($name));
            run(qw{ln --symbolic --relative --no-target-directory},
                $target, $name);
        }
    }

    run('dh_haskell_depends', "--package=$installable");

    return;
}

=head1 AUTHOR

Written by Felix Lechner <felix.lechner@lease-up.com> for Haskell Devscripts.
Based on Dh_Haskell.sh.

=head1 SEE ALSO

Debian::Debhelper::Buildsystem::haskell(3pm)

=cut

1;

# Local Variables:
# indent-tabs-mode: nil
# cperl-indent-level: 4
# End:
# vim: syntax=perl sw=4 sts=4 sr et
