#!/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 Debian::Debhelper::Dh_Lib;

=head1 SYNOPSIS

B<dh_gnustep> [S<I<debhelper options>>] [B<--app>] [B<--appsupport>] [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.

=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 application support:

Moves application support (/usr/lib/GNUstep/ApplicationSupport)
bundles' resources to /usr/share/GNUstep.  This is not done by default
and requires specifying the B<--appsupport> 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<--appsupport>

Move the Resources directories of all bundles found in
/usr/lib/GNUstep/ApplicationSupport to /usr/share, as subdirectories
to the parent directory.  If the package ships several bundles under
/usr/lib/GNUstep/ApplicationSupport/Palettes, they are moved as
subdirectories to /usr/share/GNUstep/Palettes.

Note that bundles' resources sometimes contain architecture-dependent
files in which case the best approach is to move the
architecture-independent files manually.

This option was introduced in gnustep-make/2.9.2-3 and like B<--app>
it is incompatible with B<--no-move>.

=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> or B<--appsupport> (or
both).  If B<--app> is specified, move the resource bundle of an
application to another architecture-independent I<package>.  If
B<--appsupport> is given, move the Resources directories of all
bundles found in /usr/lib/GNUstep/ApplicationSupport to
/usr/share/GNUstep in the specified I<package>.  If both B<--app> and
B<--appsupport> are specified, all resource bundles are moved to the
package specified by the option's argument, there is no way to split
them in different packages.  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.  Likewise, if the package contains more than one
ApplicationSupport directory, they will be processed independently as
expected but the build is likely to fail if they are in different
binary packages.

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},
       "appsupport" => \$dh{APPSUPPORT},
       "bug-script" => \$dh{BUG_SCRIPT},
       "game" => \$dh{GAME},
       "move-to:s" => \$dh{MOVE_TO},
       "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{MOVE_TO} && !$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} || $dh{APPSUPPORT})
          {
            error ("--app or --appsupport cannot be used with --no-move.");
          }
        if ($dh{MOVE_TO})
          {
            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")
              {
                install_dir ("$tmp/$SHARE_ROOT/$directory");
                move ("$tmp/$LIB_ROOT/$directory",
                      "$tmp/$SHARE_ROOT/$directory")
                  or die "$package: move failed: $!";
                verbose_print ("mv $tmp/$LIB_ROOT/$directory $tmp/$SHARE_ROOT/$directory");
              }
          }

        if (-d "$tmp/$LIB_ROOT/DTDs")
          {
            install_dir ("$tmp/usr/share/xml/gnustep");
            move ("$tmp/$LIB_ROOT/DTDs", "$tmp/usr/share/xml/gnustep")
              or die "$package: move failed: $!";
            verbose_print ("mv $tmp/$LIB_ROOT/DTDs $tmp/usr/share/xml/gnustep");
          }

        if (-d "$tmp/$LIB_ROOT/Images")
          {
            install_dir ("$tmp/usr/share/pixmaps/GNUstep");
            move ("$tmp/$LIB_ROOT/Images", "$tmp/usr/share/pixmaps/GNUstep")
              or die "$package: move failed: $!";
            verbose_print ("mv $tmp/$LIB_ROOT/Images $tmp/usr/share/pixmaps/GNUstep");
          }

        if (-d "$tmp/$LIB_ROOT/Sounds")
          {
            install_dir ("$tmp/usr/share/sounds/GNUstep");
            move ("$tmp/$LIB_ROOT/Sounds", "$tmp/usr/share/sounds/GNUstep")
              or die "$package: move failed: $!";
            verbose_print ("mv $tmp/$LIB_ROOT/Sounds $tmp/usr/share/sounds/GNUstep");
          }

        # Move library resources to usr/share/GNUstep/Libraries.
        if (-d "$tmp/$LIB_ROOT/Libraries/Resources")
          {
            install_dir ("$tmp/$SHARE_ROOT/Libraries");
            move ("$tmp/$LIB_ROOT/Libraries/Resources",
                  "$tmp/$SHARE_ROOT/Libraries")
              or die "$package: move failed: $!";
            verbose_print ("mv $tmp/$LIB_ROOT/Libraries/Resources $tmp/$SHARE_ROOT/Libraries");
          }

        # $LIB_ROOT may be end up empty after these operations so
        # attempt to delete it (otherwise lintian will emit
        # package-contains-empty-directory).
        if (rmdir ("$tmp/$LIB_ROOT"))
          {
            verbose_print ("rmdir $tmp/$LIB_ROOT");
            # So may be the case with /usr/lib.
            if (rmdir ("$tmp/usr/lib"))
              {
                verbose_print ("rmdir $tmp/usr/lib");
              }
          }

        # 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")
                      {
                        install_dir ("$tmp/$INCLUDE_ROOT/Frameworks/$framework/Versions/$version");
                        move ("$tmp/$LIB_ROOT/Frameworks/$framework/Versions/$version/Headers",
                              "$tmp/$INCLUDE_ROOT/Frameworks/$framework/Versions/$version")
                          or die "$package: move failed: $!";
                        verbose_print ("mv $tmp/$LIB_ROOT/Frameworks/$framework/Versions/$version/Headers $tmp/$INCLUDE_ROOT/Frameworks/$framework/Versions/$version");
                        make_symlink ("$LIB_ROOT/Frameworks/$framework/Versions/$version/Headers",
                                      "$INCLUDE_ROOT/Frameworks/$framework/Versions/$version",
                                      $tmp);
                      }
                    if (-d "$fwdir/Versions/$version/Resources")
                      {
                        install_dir ("$tmp/$SHARE_ROOT/Frameworks/$framework/Versions/$version");
                        move ("$tmp/$LIB_ROOT/Frameworks/$framework/Versions/$version/Resources",
                              "$tmp/$SHARE_ROOT/Frameworks/$framework/Versions/$version")
                          or die "$package: move failed: $!";
                        verbose_print ("mv $tmp/$LIB_ROOT/Frameworks/$framework/Versions/$version/Resources $tmp/$SHARE_ROOT/Frameworks/$framework/Versions/$version");
                        make_symlink ("$LIB_ROOT/Frameworks/$framework/Versions/$version/Resources",
                                      "$SHARE_ROOT/Frameworks/$framework/Versions/$version",
                                      $tmp);
                      }
                  }
                if (-l "$fwdir/Headers")
                  {
                    my $linkend = readlink "$fwdir/Versions/Current";
                    if ($linkend)
                      {
                        symlink ("Versions/$linkend",
                                 "$tmp/$INCLUDE_ROOT/Frameworks/$framework/Headers");
                        verbose_print ("ln -s $tmp/$INCLUDE_ROOT/Frameworks/$framework/Headers Versions/$linkend");
                      }
                  }
                if (-l "$fwdir/Resources")
                  {
                    my $linkend = readlink "$fwdir/Versions/Current";
                    if ($linkend)
                      {
                        symlink ("Versions/$linkend",
                                 "$tmp/$SHARE_ROOT/Frameworks/$framework/Resources");
                        verbose_print ("ln -s $tmp/$SHARE_ROOT/Frameworks/$framework/Resources Versions/$linkend");
                      }
                  }
              }
          }

        # Move apps' resource bundles to /usr/share and also
        # /usr/lib/GNUstep/ApplicationSupport arch-independent stuff.
        if ($dh{APP} || $dh{APPSUPPORT})
          {
            my $appdir = "$tmp/$LIB_ROOT/Applications";
            my $appsupportdir = "$tmp/$LIB_ROOT/ApplicationSupport";
            my $pkgdir = $tmp;

            if ($dh{MOVE_TO})
              {
                if (!grep {$_ eq $dh{MOVE_TO}} getpackages ("indep"))
                  {
                    error ("$dh{MOVE_TO} does not exist or is arch:any.");
                  }

                # If --move-to is given, $appdir or $appsupportdir are
                # likely to contain only the executables so we resort
                # to debian/tmp.  This will fail miserably 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;
                $appsupportdir =~ s/\Q$package\E/tmp/g;
                $pkgdir = tmpdir ($dh{MOVE_TO});
              }

            if ($move_to_processed)
              {
                verbose_print ("$package: --move-to is processed; skipping.");
              }
            elsif (-d $appdir && $dh{APP})
              {
                # 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{MOVE_TO})
                    {
                      $move_to_processed = 1;
                      verbose_print ("$package: --move-to processed for app.");
                    }
                }
              elsif (! -d $appdir && $dh{APP})
                {
                  error ("Directory $appdir does not exist.");
                }

              if (-d $appsupportdir && $dh{APPSUPPORT}
                  && $move_to_processed < 2)
                {
                  # Theoretically the package may contain more than
                  # one Application Support item.
                  opendir APPSUPPORTDIR, $appsupportdir;
                  my @items = grep !/^\./, readdir APPSUPPORTDIR;
                  closedir APPSUPPORTDIR;
                  if (!@items)
                    {
                      error ("No items found at $appsupportdir.");
                    }
                  foreach my $item (@items)
                    {
                      opendir ITEMDIR, "$appsupportdir/$item";
                      my @bundles = grep !/^\./, readdir ITEMDIR;
                      closedir ITEMDIR;
                      if (!@bundles)
                        {
                          error ("No bundles found at $appsupportdir/$item.");
                        }
                      install_dir ("$pkgdir/$SHARE_ROOT/$item");
                      foreach my $bundle (@bundles)
                        {
                          my $resdir = "$appsupportdir/$item/$bundle/Resources";
                          move ($resdir, "$pkgdir/$SHARE_ROOT/$item/$bundle")
                            or die "$package: move failed: $!";
                          verbose_print ("mv $resdir $pkgdir/$SHARE_ROOT/$item/$bundle");
                          make_symlink ("$LIB_ROOT/ApplicationSupport/$item/$bundle/Resources",
                                        "/$SHARE_ROOT/$item/$bundle", $pkgdir);
                        }
                    }
                  if ($dh{MOVE_TO})
                    {
                      $move_to_processed = 2;
                      verbose_print ("$package: --move-to processed.");
                    }
                }
              elsif (! -d $appsupportdir && $dh{APPSUPPORT})
                {
                  error ("Directory $appsupportdir does not exist.");
                }
            }
          }

    log_installed_files ($package, '');

    # 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
