#!/usr/bin/perl

=head1 NAME

dh_gnustep - perform GNUstep-specific actions for Debian GNUstep packages

=cut

use strict;
use warnings;
use File::Copy;
use File::Find;
use File::Path;
use Debian::Debhelper::Dh_Lib;

=head1 SYNOPSIS

B<dh_gnustep> [S<I<debhelper options>>] [B<--app>] [B<--bug-script>] [B<--game>] [B<--move-to=>I<package>] [B<--no-cleanup>] [B<--no-move>]

=head1 DESCRIPTION

B<dh_gnustep> is a program based on debhelper that is responsible for
doing GNUstep-specific modifications, such as moving files in the
GNUstep hierarchy within the System domain, rooted at
/usr/lib/GNUstep, to Filesystem Hierarchy Standard (FHS)-compliant
locations.  It also adds bug-script symlinks for GNUstep applications
and removes some build system artifacts.

B<dh_gnustep> must be run after L<dh_bugfiles(1)> but before
L<dh_link(1)>.  Normally you would use an F<override_dh_link> target
but you should remember to invoke L<dh_link(1)> as the last command in
the recipe.  This is mandatory so that the created links are made
policy-compliant.  Using the F<execute_before_dh_link> target avoids
this extra step.  This program makes the following changes (all move
operations can be cancelled with the B<--no-move> option):

=over 4

=item arch-indep directories:

Moves architecture-independent directories to /usr/share/GNUstep.

=item library resources:

Moves library resources to /usr/share/GNUstep/Libraries.

=item frameworks:

Moves framework headers to /usr/include/GNUstep/Frameworks, and framework
resources to /usr/share/GNUstep/Frameworks.

=item applications:

Moves application resource bundles to /usr/share/GNUstep.  This is not
done by default and requires specifying the B<--app> option.

=item bug-script symlinks:

By default, B<dh_gnustep> will install a bug-script symlink for any
package with a name ending with F<.app>, pointing to
/usr/share/bug/gnustep-back-common, so that the system and user
GNUstep backends are reported as package-specific information to the
BTS when submitting bugs.  It will also add the appropriate dependency
(via the F<${misc:Depends}> substitution variable).

Note that B<dh_gnustep> will not handle any other bug files and it
expects L<dh_bugfiles(1)> to have completed its job in order to handle
the case when the bug-script symlink must be installed as
/usr/share/bug/<package>/script (when there are bug-control or
bug-presubj files).  If an existing bug script is detected,
B<dh_gnustep> will emit a warning as a package can have only one
bug-script file.  In such situations the only solution is to integrate
the contents of /usr/share/bug/gnustep-back-common into the existing
script.

=item build system artifacts:

By default, B<dh_gnustep> will delete F<stamp.make> files which are
created and (unfortunately) installed by GNUstep Make for many project
types.  Likewise, it will delete F<dependencies> files created and
installed for GNUstep documentation projects.  The number and type of
the deleted files are recorded in the build log if B<DH_QUIET> is not
set.

=back

=head1 OPTIONS

B<dh_gnustep> accepts the common debhelper options, and some specific
ones.  If you use any of the options below, make sure to build-depend
on gnustep-make (>= 2.9.2-2) if that version is not already satisfied
in the current stable/oldstable release.

=over 4

=item B<--app>

Move the resource bundle of a GNUstep application
(/usr/lib/GNUstep/Applications/Foo.app/Resources on Debian systems) to
/usr/share/GNUstep/Foo.app and make the necessary symlink.  The
automatically generated L<.desktop> file is deleted.  If the binary
package contains more than one app, they are processed independently.

Care shhould be taken when using this option because some apps contain
architecture-dependent files in their resource bundle.  Another
important point to pay attention to: if this is an existing package
which does not have its resources moved to /usr/share, a
F<maintscript> is required because dpkg will not switch a directory to
a symlink (and vice-versa).  B<dh_gnustep> does not handle this, see
L<dpkg-maintscript-helper(1)>.

This option is incompatible with B<--no-move> and for multi-binary
packages should be used with the appropriate B<-p> option.

=item B<--bug-script>

Install bug-script symlink for the package even if its name does not
end with F<.app>.  Typically this should be done for apps which are
installed for some reason with tools or other stuff in a package with
a different name (such as F<gnustep-examples> or F<gnustep-dl2>).  It
can also be done for GUI bundles, themes, palettes, etc.  You probably
want to use the B<-p> option as well, otherwise B<dh_gnustep> will add
bug script symlinks for all binary packages.

=item B<--game>

This option must be used together with B<--app>.  It does everything
that B<--app> does as described above, and additionally moves the
symlink to the executable from /usr/bin to /usr/games.  If /usr/bin is
found empty afterwards, the directory is deleted.

=item B<--move-to=>I<package>

This option must be used together with B<--app>.  Move the resource
bundle of an application to another architecture-independent
I<package>.  The current implementation will handle more than one app
per binary package but it will fail with a multi-binary source package
containing more than one F<.app> binary package.

Use with caution; see the EXAMPLES section for a working example.

=item B<--no-cleanup>

Do not delete F<stamp.make> and F<dependencies> files.

=item B<--no-move>

Do not perform any move operations.  Setting the DEB_GNUSTEP_NO_MOVE
environment variable to a non-empty, non-zero value has the same
effect.

=back

=head1 EXAMPLES

  execute_before_dh_link:
          dh_gnustep -pgnustep-dl2 --no-move --bug-script
          dh_gnustep --remaining-packages

Do not move any files for the F<gnustep-dl2> package (as that's
already handled manually) but install a bug-script symlink because
the package contains an application and a Gorm palette.

  execute_before_dh_link:
  ifeq (,$(filter lynkeos.app-common,$(shell dh_listpackages)))
          rm -rf debian/tmp$(GNUSTEP_SYSTEM_APPS)/Lynkeos.app/Resources
          dh_gnustep
  else
          dh_gnustep --app --move-to=lynkeos.app-common
  endif

Move the F<lynkeos.app> resource bundle to the
architecture-independent package F<lynkeos.app-common>.  When doing a
full build (arch:all + arch:any), the make conditional is false so the
move is done when operating on F<lynkeos.app>.  During an arch:any
build, no move is being done but the F<lynkeos.app> package is
processed with the the default B<dh_gnustep> behaviour, gaining a
bug-script symlink and cleaning up build artifacts.  The deletion of
the Resources directory is to eliminate a L<dh_missing(1)> error.
During an arch:all build the move is done while processing the
F<lynkeos.app-common> package.

  override_dh_link:
          dh_gnustep --app --game
          dh_link

Move the app's resource bundle to /usr/share and the symlink to the
executable from /usr/bin to /user/games.  Additionally, assuming the
binary package name ends with F<.app>, create a bug-script symlink
pointing to /usr/share/bug/gnustep-back-common.  Finally, delete the
stamp.make file from the app bundle.

=head1 BUGS

Should implement B<-X> option.

Doesn't really do the right thing with info files (should call install-info).

Should do something with Java classes -- make a jar file and move from
Library/Libraries/Java to /usr/share/java -- to comply with Java policy.

=head1 CONFORMS TO

Debian policy, version 3.6.2

FHS, version 2.3

=cut

init (options =>
      {
       "app" => \$dh{APP},
       "bug-script" => \$dh{BUG_SCRIPT},
       "game" => \$dh{GAME},
       "move-to:s" => \$dh{V_FLAG},
       "no-cleanup" => \$dh{NO_CLEANUP},
       "no-move" => \$dh{NO_MOVE},
      });

my $move_to_processed = 0;
my $LIB_ROOT = '/usr/lib/GNUstep';
my $SHARE_ROOT = 'usr/share/GNUstep';
my $INCLUDE_ROOT = 'usr/include/GNUstep';

my @ARCH_INDEP_DIRS = qw(Colors DocTemplates Fonts KeyBindings PostScript Libraries);

foreach my $package (@{$dh{DOPACKAGES}})
{
    my $tmp = tmpdir ($package);

    # Some sanity checks.
    if ($dh{V_FLAG_SET} && !$dh{APP})
      {
        error ("Option --move-to requires --app.");
      }
    if ($dh{GAME} && !$dh{APP})
      {
        error ("Option --game requires --app.");
      }

    # Moving stuff around for FHS compliance.
    if ($dh{NO_MOVE} || $ENV{DEB_GNUSTEP_NO_MOVE})
      {
        verbose_print ("Not moving files because --no-move was given.");

        if ($dh{APP})
          {
            error ("--app cannot be used with --no-move.");
          }
        if ($dh{V_FLAG_SET})
          {
            error ("--move-to cannot be used with --no-move.");
          }
      }
    else
      {
        # Move arch-indep directories to usr/share/GNUstep.
        foreach my $directory (@ARCH_INDEP_DIRS)
          {
            if (-d "$tmp/$LIB_ROOT/$directory")
              {
                mkpath "$tmp/$SHARE_ROOT/$directory";
                rename "$tmp/$LIB_ROOT/$directory",
                  "$tmp/$SHARE_ROOT/$directory";
              }
          }

        if (-d "$tmp/$LIB_ROOT/DTDs")
          {
            mkpath "$tmp/usr/share/xml/gnustep";
            rename "$tmp/$LIB_ROOT/DTDs", "$tmp/usr/share/xml/gnustep"
          }

        if (-d "$tmp/$LIB_ROOT/Images")
          {
            mkpath "$tmp/usr/share/pixmaps/GNUstep";
            rename "$tmp/$LIB_ROOT/Images", "$tmp/usr/share/pixmaps/GNUstep"
          }

        if (-d "$tmp/$LIB_ROOT/Sounds")
          {
            mkpath "$tmp/usr/share/sounds/GNUstep";
            rename "$tmp/$LIB_ROOT/Sounds", "$tmp/usr/share/sounds/GNUstep"
          }

        # Move library resources to usr/share/GNUstep/Libraries.
        if (-d "$tmp/$LIB_ROOT/Libraries/Resources")
          {
            mkpath "$tmp/$SHARE_ROOT/Libraries";
            rename "$tmp/$LIB_ROOT/Libraries/Resources",
              "$tmp/$SHARE_ROOT/Libraries";
          }

        # Find frameworks and move resources and headers.
        if (-d "$tmp/$LIB_ROOT/Frameworks")
          {
            opendir FRAMEWORKDIR, "$tmp/$LIB_ROOT/Frameworks";
            my @frameworks = grep !/^\./, readdir FRAMEWORKDIR;
            closedir FRAMEWORKDIR;
            foreach my $framework (@frameworks)
              {
                my $fwdir = "$tmp/$LIB_ROOT/Frameworks/$framework";
                opendir CURFWDIR, "$fwdir/Versions";
                my @versions = grep !/^(\.|Current$)/, readdir CURFWDIR;
                closedir CURFWDIR;
                foreach my $version (@versions)
                  {
                    if (-d "$fwdir/Versions/$version/Headers")
                      {
                        mkpath "$tmp/$INCLUDE_ROOT/Frameworks/$framework/Versions/$version";
                        rename "$tmp/$LIB_ROOT/Frameworks/$framework/Versions/$version/Headers",
                          "$tmp/usr/include/GNUstep/Frameworks/$framework/Versions/$version";
                        symlink "../../../../../../include/GNUstep/Frameworks/$framework/Versions/$version",
                          "$tmp/$LIB_ROOT/Frameworks/$framework/Versions/$version/Headers";
                      }
                    if (-d "$fwdir/Versions/$version/Resources")
                      {
                        mkpath "$tmp/$SHARE_ROOT/Frameworks/$framework/Versions/$version";
                        rename "$tmp/$LIB_ROOT/Frameworks/$framework/Versions/$version/Resources",
                          "$tmp/usr/share/GNUstep/Frameworks/$framework/Versions/$version";
                        symlink "../../../../../../share/GNUstep/Frameworks/$framework/Versions/$version",
                          "$tmp/$LIB_ROOT/Frameworks/$framework/Versions/$version/Resources";
                      }
                  }
                if (-l "$fwdir/Headers")
                  {
                    my $linkend = readlink "$fwdir/Versions/Current";
                    symlink "Versions/$linkend",
                      "$tmp/$INCLUDE_ROOT/Frameworks/$framework/Headers";
                  }
                if (-l "$fwdir/Resources")
                  {
                    my $linkend = readlink "$fwdir/Versions/Current";
                    symlink "Versions/$linkend",
                      "$tmp/$SHARE_ROOT/Frameworks/$framework/Resources";
                  }
              }
          }

        # Apps.
        if ($dh{APP})
          {
            my $appdir = "$tmp/$LIB_ROOT/Applications";
            my $pkgdir = $tmp;

            if ($dh{V_FLAG_SET})
              {
                if ($dh{V_FLAG} eq '')
                  {
                    error ("Option --move-to requires a parameter.");
                  }
                if (!grep {$_ eq $dh{V_FLAG}} getpackages ("indep"))
                  {
                    error ("$dh{V_FLAG} does not exist or is arch:any.");
                  }

                # If --move-to is given, $appdir is likely to contain
                # only the executable so we resort to debian/tmp.
                # This will fail if it's a multi-binary package with
                # several apps in different binary packages but that
                # limitation should be properly documented.
                $appdir =~ s/\Q$package\E/tmp/g;
                $pkgdir = tmpdir ($dh{V_FLAG});
              }

            if ($move_to_processed)
              {
                verbose_print ("$package: --move-to is processed; skipping.");
              }
            elsif (-d $appdir)
              {
                # Package may contain more than one app.
                opendir APPDIR, $appdir;
                my @apps = grep !/^\./, readdir APPDIR;
                closedir APPDIR;
                if (!@apps)
                  {
                    error ("No application found at $appdir.");
                  }
                foreach my $app (@apps)
                  {
                    my $resdir = "$appdir/$app/Resources";
                    my $desktop = $app;

                    # Delete .desktop file generated by pl2link.
                    $desktop =~ s/.app$/.desktop/g;
                    $desktop = "$resdir/$desktop";
                    unlink ($desktop);
                    verbose_print ("rm -f $desktop");

                    # Do the main job.
                    install_dir ("$pkgdir/$SHARE_ROOT/$app");
                    move ($resdir, "$pkgdir/$SHARE_ROOT/$app")
                      or die ("$package: move failed: $!");
                    verbose_print ("mv $resdir $pkgdir/$SHARE_ROOT/$app");
                    make_symlink ("$LIB_ROOT/Applications/$app/Resources",
                                  "/$SHARE_ROOT/$app", $pkgdir);

                    if ($dh{GAME})
                      {
                        my $files = 0;
                        my $executable = $app;
                        $executable =~ s/.app$//g;

                        sub move_and_count
                        {
                          if (-l $_ && basename ($_) eq $executable)
                            {
                              move ($File::Find::name, "$pkgdir/usr/games")
                                or die ("$_: move failed: $!");
                              verbose_print ("mv $_ $pkgdir/usr/games");
                            }
                          $files += 1;
                        }

                        install_dir ("$pkgdir/usr/games");
                        find ({ wanted => \&move_and_count, no_chdir => 1 },
                              "$pkgdir/usr/bin");

                        if ($files < 2)
                          {
                            error ("No symlink found at $pkgdir/usr/bin.");
                          }

                        if (rmdir ("$pkgdir/usr/bin"))
                          {
                            verbose_print ("rmdir $pkgdir/usr/bin");
                          }
                      }

                    if ($dh{V_FLAG})
                      {
                        $move_to_processed = 1;
                        verbose_print ("$package: --move-to processed.");
                      }
                  }
              }
            else
              {
                error ("Directory $appdir does not exist.");
              }
          }
      }

    # Bug-script handling.
    if ($package =~ /.app$/ || $dh{BUG_SCRIPT})
      {
        my $p_dir = "${tmp}/usr/share/bug";
        my $dir = "${p_dir}/$package";
        my $bugscript = "/usr/share/bug/gnustep-back-common";
        my $add_depends = 1;

        if (-f $dir || -f "${dir}/script")
          {
            warning ("Not installing bug script symlink for $package");
            warning ("because a bug script already exists.");
            $add_depends = 0;
          }
        elsif (! -d $dir)
          {
            install_dir ($p_dir);
            make_symlink ("/usr/share/bug/$package", $bugscript, $tmp);
          }
        else
          {
            make_symlink ("/usr/share/bug/$package/script", $bugscript, $tmp);
          }

        if ($add_depends)
          {
            addsubstvar ($package, "misc:Depends",
                         "gnustep-back-common", ">= 0.31.0-3");
          }
      }

    # Cleanup of build artifacts.
    if ($dh{NO_CLEANUP})
      {
        verbose_print ("Not performing cleanup for package $package");
        verbose_print ("because --no-cleanup was given.");
      }
    else
      {
        my $stamps = 0;
        my $deps = 0;

        sub delete_and_count
        {
          my $file = basename ($File::Find::name);

          if ($file eq "stamp.make" && -f $_)
            {
              if (unlink ($File::Find::name))
                {
                  verbose_print ("rm -f $_");
                  $stamps += 1;
                }
              else
                {
                  warning ("Could not delete $_: $!");
                }
            }
          elsif ($file eq "dependencies" && -f $_)
            {
              if (unlink ($File::Find::name))
                {
                  verbose_print ("rm -f $_");
                  $deps += 1;
                }
              else
                {
                  warning ("Could not delete $_: $!");
                }
            }
        }

        find ({ wanted => \&delete_and_count, no_chdir => 1 }, $tmp);

        if ($stamps == 1)
          {
            nonquiet_print ("Deleted 1 stamp.make file in $package.");
          }
        elsif ($stamps > 1)
          {
            nonquiet_print ("Deleted $stamps stamp.make files in $package.");
          }

        if ($deps == 1)
          {
            nonquiet_print ("Deleted 1 dependencies file in $package.");
          }
        elsif ($deps > 1)
          {
            nonquiet_print ("Deleted $deps dependencies files in $package.");
          }
      }
}

=head1 SEE ALSO

L<debhelper(7)>, L<dh_link(1)>, L<dh_bugfiles(1)>

This program is not part of debhelper.

=head1 AUTHORS

Hubert Chan <uhoreg@debian.org>

Yavor Doganov <yavor@gnu.org>

=cut
