#!/usr/bin/perl
# debbuild script
# Shamlessly steals intreface from rpm's "rpmbuild" to create
# Debian packages.  Please note that such packages are highly
# unlikely to conform to "Debian Policy".
###
# SVN revision info
# $Date: 2006-02-23 17:54:36 +0000 (Thu, 23 Feb 2006) $
# SVN revision $Rev: 40 $
# Last update by $Author: kdeugau $
###

use strict;
use warnings;
use Fcntl;	# for sysopen flags
use Cwd 'abs_path';	# for finding where files really are

# regex debugger
#use re "debug";

# Program flow:
# -> Parse/execute "system" config/macros (if any - should be rare)
# -> Parse/execute "user" config/macros (if any - *my* requirement is %_topdir)
# -> Parse command line for options, spec file/tarball/.src.deb (NB - also accept .src.rpm)

sub expandmacros;

# User's prefs for dirs, environment, etc,etc,etc.
# config file ~/.debmacros
# Default ordered search paths for config/macros:
# /usr/lib/rpm/rpmrc  /usr/lib/rpm/redhat/rpmrc  /etc/rpmrc      ~/.rpmrc
# /usr/lib/rpm/macros /usr/lib/rpm/redhat/macros /etc/rpm/macros ~/.rpmmacros
# **NOTE:  May be possible to (ab)use bits of debhelper

# Build tree
# default is /usr/src/debian/{BUILD,SOURCES,SPECS,DEBS,SDEBS}

# Globals
my $specfile;
my $tarball;
my $srcpkg;
my $cmdbuildroot;
my $tarballdir;	# This should really be initialized, but the coding makes it, um, ugly.
my %specglobals;	# For %define's in specfile, among other things.

# Initialized globals
my $verbosity = 0;
my %cmdopts = (type => '',
		stage => 'a',
		short => 'n'
	);
my $topdir = "/usr/src/debian";
my $buildroot = "%{_tmppath}/%{name}-%{version}-%{release}.root".int(rand(99998)+1);

# "Constants"
my %targets = ('p' => 'Prep',
		'c' => 'Compile',
		'i' => 'Install',
		'l' => 'Verify %files',
		'a' => 'Build binary and source',
		'b' => 'Build binary',
		's' => 'Build source'
	);
my $scriptletbase =
q(#!/bin/sh

  RPM_SOURCE_DIR="%{_topdir}/SOURCES"
  RPM_BUILD_DIR="%{_topdir}/BUILD"
  RPM_OPT_FLAGS="-O2 -g -march=i386 -mcpu=i686"
  RPM_ARCH="i386"
  RPM_OS="linux"
  export RPM_SOURCE_DIR RPM_BUILD_DIR RPM_OPT_FLAGS RPM_ARCH RPM_OS
  RPM_DOC_DIR="/usr/share/doc"
  export RPM_DOC_DIR
  RPM_PACKAGE_NAME="%{name}"
  RPM_PACKAGE_VERSION="%{version}"
  RPM_PACKAGE_RELEASE="%{release}"
  export RPM_PACKAGE_NAME RPM_PACKAGE_VERSION RPM_PACKAGE_RELEASE
  RPM_BUILD_ROOT="%{buildroot}"
  export RPM_BUILD_ROOT
);
foreach (`dpkg-architecture`) {
  s/=(.+)/="$1"/;
  $scriptletbase .= "  $_";
}
$scriptletbase .=
q(
  set -x
  umask 022
  cd %{_topdir}/BUILD
);

# Package data
# This is the form of $pkgdata{pkgname}{meta}
# meta includes Summary, Name, Version, Release, Group, Copyright,
#	Source, URL, Packager, BuildRoot, Description, BuildReq(uires),
#	Requires, Provides
# 10/31/2005 Maybe this should be flatter?  -kgd
my %pkgdata;
my @pkglist = ('main');	#sigh
# Files listing.  Embedding this in %pkgdata would be, um, messy.
my %filelist;
my $buildreq = '';

# Scriptlets
my $prepscript;
my $buildscript;
# %install doesn't need the full treatment from %clean;  just an empty place to install to.
# NB - rpm doesn't do this;  is it really necessary?
my $installscript = '[ "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT'."\n";
my $cleanscript;
# pre/post (un)install scripts.  Note that these will likely barf as-is.  :/
my $preinstscript = '';
my $postinstscript = '';
my $preuninstscript = '';
my $postuninstscript = '';

die "Not enough arguments\n" if #$argv == 0;

# Snag some environment data
my $tmpdir;
if (defined $ENV{TMP} && $ENV{TMP} =~ /^(\/var)?\/tmp$/) {
  $tmpdir = $ENV{TMP};
} else {
  $tmpdir = "/var/tmp";
}

##main

load_userconfig();
parse_cmd();

if ($cmdopts{type} eq 'b') {
  # Need to read the spec file to find the tarball.  Note that
  # this also generates most of the shell script required.
  parse_spec();
  die "Can't build $pkgdata{main}{name}:  build requirements not met.\n"
    if !checkbuildreq();
}

# -> srcpkg if -.s
if ($cmdopts{stage} eq 's') {
  srcpackage();
  exit 0;
}

# Hokay.  Need to:
# -> prep if -.p OR (-.[cilabs] AND !--short-circuit)
if ($cmdopts{stage} eq 'p' || ($cmdopts{stage} =~ /[cilabs]/ && $cmdopts{short} ne 'y')) {
  prep();
}
# -> build if -.c OR (-.[ilabs] AND !--short-circuit)
if ($cmdopts{stage} eq 'c' || ($cmdopts{stage} =~ /[ilabs]/ && $cmdopts{short} ne 'y')) {
  build();
}
# -> install if -.[ilabs]
#if ($cmdopts{stage} eq 'i' || ($cmdopts{stage} =~ /[labs]/ && $cmdopts{short} ne 'y')) {
if ($cmdopts{stage} =~ /[ilabs]/) {
  install();
#foreach my $pkg (@pkglist) {
#  print "files in $pkg:\n ".$filelist{$pkg}."\n";
#}

}
# -> binpkg and srcpkg if -.a
if ($cmdopts{stage} eq 'a') {
  binpackage();
  srcpackage();
}
# -> binpkg if -.b
if ($cmdopts{stage} eq 'b') {
  binpackage();
}

# Just in case.
exit 0;


## load_userconfig()
# Loads user configuration (if any)
# Currently only handles .debmacros
# Needs to handle "other files"
sub load_userconfig {
  my (undef,undef,undef,undef,undef,undef,undef,$homedir,undef) = getpwuid($<);
  if (-e "$homedir/.debmacros") {
    open USERMACROS,"<$homedir/.debmacros";
    while (<USERMACROS>) {
      # And we also only handle a few macros at the moment.
      if (/^\%_topdir/) {
	my (undef,$tmp) = split /\s+/, $_;
	$topdir = $tmp;
      }
    }
  }
} # end load_userconfig()


## parse_cmd()
# Parses command line into global hash %cmdopts, other globals
# Options based on rpmbuild's options
sub parse_cmd {
  # Don't feel like coding my own option parser...
  #use Getopt::Long;
  # ... but I may have to:  (OTOH, rpm uses popt, so maybe we can too.)
  #use Getopt::Popt qw(:all);
  # Or not.  >:(  Stupid Debian lack of findable Perl module names in packages.

  # Stuff it.
  my $prevopt = '';
  foreach (@ARGV) {
    chomp;

    # Is it an option?
    if (/^-/) {

      # Is it a long option?
      if (/^--/) {
	if (/^--short-circuit/) {
	  $cmdopts{short} = 'y';
	} elsif (/^--rebuild/) {
	  $cmdopts{type} = 's';
	} else {
	  print "Long opt $_\n";
	}
      } else {
	# Not a long option
	if (/^-[bt]/) {
	  if ($cmdopts{stage} eq 's') {
	    # Mutually exclusive options.
	    die "Can't use $_ with --rebuild\n";
	  } else {
	    # Capture the type (from "bare" files or tarball) and the stage (prep, build, etc)
	    ($cmdopts{stage}) = (/^-[bt]([pcilabs])/);
	    ($cmdopts{type}) = (/^-([bt])[pcilabs]/);
	  }
	} elsif (/^-v/) {
	  # bump verbosity.  Not sure what I'll actually do here...
	} else {
	  die "Bad option $_\n";
	}
      }

    } else { # Not an option argument

      # --buildroot is the only option that takes an argument
      # Therefore, any *other* bare arguments are the spec file,
      # tarball, or source package we're operating on - depending
      # on which one we meet.
      if ($prevopt eq '--buildroot') {
	$cmdbuildroot = $_;
      } else {
	if ($cmdopts{type} eq 's') {
	  # Source package
 	  if (!/\.src\.(deb|rpm)$/) {
	    die "Can't --rebuild with $_\n";
	  }
	} elsif ($cmdopts{type} eq 'b') {
	  $specfile = $_;
	  # Spec file
	} else {
	  # Tarball
	}
      }
    }
    $prevopt = $_;
  } # foreach @ARGV

  # Some cross-checks.  rpmbuild limits --short-circuit to just
  # the "compile" and "install" targets - with good reason IMO.
  # Note that --short-circuit with -.p is not really an error, just redundant.
  # NB - this is NOT fatal, just ignored!
  if ($cmdopts{short} eq 'y' && $cmdopts{stage} =~ /[labs]/) {
    warn "Can't use --short-circuit for $targets{$cmdopts{stage}} stage.  Ignoring.\n";
    $cmdopts{short} = 'n';
  }

  # Valid options, with example arguments (if any):
# Build from .spec file; mutually exclusive:
  # -bp
  # -bc         
  # -bi         
  # -bl
  # -ba
  # -bb
  # -bs
# Build from tarball; mutually exclusive:
  # -tp
  # -tc
  # -ti
  # -ta
  # -tb
  # -ts
# Build from .src.(deb|rpm)
  # --rebuild
  # --recompile

# General options
  # --buildroot=DIRECTORY
  # --clean
  # --nobuild
  # --nodeps
  # --nodirtokens
  # --rmsource
  # --rmspec
  # --short-circuit
  # --target=CPU-VENDOR-OS

  #my $popt = new Getopt::Popt(argv => \@ARGV, options => \@optionsTable);

} # end parse_cmd()


## parse_spec()
# Parse the .spec file.
sub parse_spec {
  open SPECFILE,"<$specfile";

LINE: while (<SPECFILE>) {
    next if /^#/;	# Ignore comments...
    next if /^\s+$/;	# ... and blank lines.

    if (/^\%/) {
      # A macro that needs further processing.

      if (/^\%define\s+([^\s]+)\s+([^\s]+)/) {
        $specglobals{$1} = expandmacros($2,'g');
      }

      if (/^\%description(?:\s+(?:-n\s+)?([a-zA-Z0-9_.-]+))?/) {
	my $subname = "main";
	if ($1) {
	  if (/-n/) { $subname = $1; } else { $subname = "$pkgdata{main}{name}-$1"; }
	}
        while (<SPECFILE>) {
	  next if /^#/;    # Messy.  Should be possible to do better.  :/
          redo LINE if /^\%/;
          $pkgdata{$subname}{desc} .= " $_";
        }
      }
      if (/^\%package\s+(?:-n\s+)?([a-zA-Z0-9_.-]+)/) {
	my $subname;
	if (/-n/) { $subname = $1; } else { $subname = "$pkgdata{main}{name}-$1"; }
	push @pkglist, $subname;
	$pkgdata{$subname}{name} = $subname;
	$pkgdata{$subname}{version} = $pkgdata{main}{version};
	while (<SPECFILE>) {
	  redo LINE if /^\%/;
	  if (my ($dname,$dvalue) = (/^(Summary|Group|Version|Requires|Provides):\s+(.+)$/i)) {
	    $dname =~ tr/[A-Z]/[a-z]/;
	    $pkgdata{$subname}{$dname} = $dvalue;
	  }
	}
      }

      if (/^\%prep/) {
	# %prep section.  May have %setup macro;  may include %patch tags,
	# may be just a bare shell script.

	# This really should be local-ish, but we need just the filename for the source
	$pkgdata{main}{source} =~ s|.+/([^/]+)$|$1|;

	# Replace some core macros
	$pkgdata{main}{source} = expandmacros($pkgdata{main}{source},'gp');

PREPSCRIPT: while (<SPECFILE>) {
	  if (/^\%setup/) {
	    # Parse out the %setup macro.  Note that we aren't supporting
	    # many of RPM's %setup features.
	    $prepscript .= "cd $topdir/BUILD\n";
	    if ( /\s+-n\s+([^\s]+)\s+/ ) {
	      $tarballdir = $1;
	    } else {
	      $tarballdir = "$pkgdata{main}{name}-$pkgdata{main}{version}";
	    }
	    $prepscript .= "rm -rf $tarballdir\ntar -".
		( $pkgdata{main}{source} =~ /\.tar\.gz$/ ? "z" : "" ).
		( $pkgdata{main}{source} =~ /\.tar\.bz2$/ ? "j" : "" ).
		( /\s+-q\s+/ ? '' : 'vv' )."xf ".
		"$topdir/SOURCES/$pkgdata{main}{source}\n".
		qq(STATUS=\$?\nif [ \$STATUS -ne 0 ]; then\n  exit \$STATUS\nfi\n).
		( /\s+-n\s+([^\s]+)\s+/ ?
		  "cd $1\n" : "cd $pkgdata{main}{name}-$pkgdata{main}{version}\n" ).
		qq([ `/usr/bin/id -u` = '0' ] && /bin/chown -Rhf root .\n).
		qq([ `/usr/bin/id -u` = '0' ] && /bin/chgrp -Rhf root .\n).
		qq(/bin/chmod -Rf a+rX,g-w,o-w .\n);
	  } elsif (/^\%patch([^:]+)\s+(.+)$/) {
	    $prepscript .= "patch $2 <$topdir/SOURCES/".$pkgdata{main}{"patch$1"}."\n";
	  } else {
	    last PREPSCRIPT if /^\%/;
	    $prepscript .= $_;
	  }
	}
	redo LINE;
      }
      if (/^\%build/) {
	# %build.  This is pretty much just a shell script.  There
	# *are* a few macros, but we're not going to deal with them yet.
	$buildscript .= "cd $tarballdir\n";
BUILDSCRIPT: while (<SPECFILE>) {
	  if (/^\%configure/) {
	    $buildscript .= expandmacros($_,'cgbp');
	  } elsif (/^\%\{__make\}/) {
	    $buildscript .= expandmacros($_,'mgbp');
	  } else {
	    last BUILDSCRIPT if /^\%[^{]/;
	    $buildscript .= $_;
	  }
	}
	redo LINE;
      }
      if (/^\%install/) {
	$installscript .= "cd $tarballdir\n";
INSTALLSCRIPT: while (<SPECFILE>) {
	  if (/^\%makeinstall/) {
            $installscript .= expandmacros($_,'igbp');
          } else {
            last INSTALLSCRIPT if /^\%/;
            $installscript .= $_;
          }
        }
	redo LINE;
      }
      if (/^\%clean/) {
	while (<SPECFILE>) {
	  redo LINE if /^\%/;
	  $cleanscript .= $_;
	}
	$cleanscript = expandmacros($cleanscript,'gp');
      }

    # pre/post (un)install scripts
      if (/^\%pre\b/) {
	while (<SPECFILE>) {
	  redo LINE if /^\%/;
	  $preinstscript .= $_;
	}
      }
      if (/^\%post\b/) {
	while (<SPECFILE>) {
	  redo LINE if /^\%/;
	  $postinstscript .= $_;
	}
      }
      if (/^\%preun\b/) {
	while (<SPECFILE>) {
	  redo LINE if /^\%/;
	  $preuninstscript .= $_;
	}
      }
      if (/^\%postun\b/) {
	while (<SPECFILE>) {
	  redo LINE if /^\%/;
	  $postuninstscript .= $_;
	}
      }
    # done %pre/%post scripts

      if (/^\%files(?:\s+(?:-n\s+)?([a-zA-z0-9]+))?/) {
	my $pkgname = 'main';
	if ($1) {	# Magic to add entries to the right list of files
	  if (/-n/) { $pkgname = $1; } else { $pkgname = "$pkgdata{main}{name}-$1"; }
	}

	# Set this now, so it can be flipped a bit later, and used much later.
	#$pkgdata{$pkgname}{conffiles} = 0;

	while (<SPECFILE>) {
	  chomp;
	  # need to update this to deal (properly) with %dir, %attr, etc
	  next if /^\%dir/;
	  next if /^\%attr/;
	  next if /^\%defattr/;

	  # Debian dpkg doesn't speak "%docdir".  Meh.
	  next if /^\%docdir/;

	  # Conffiles.  Note that Debian and RH have similar, but not
	  # *quite* identical ideas of what constitutes a conffile.  Nrgh.
	  if (/^\%config\s+(.+)$/) {
	    $pkgdata{$pkgname}{conffiles} = 1;	# Flag it for later
	    my $tmp = $1;	# Now we can mangleificationate it.  And we probably need to.  :/
	    if ($tmp !~ /\s+/) {
	      # Simplest case, just a file.  Whew.
	      push @{$pkgdata{$pkgname}{conflist}}, $tmp;
	      $filelist{$pkgname} .= " $tmp";
	    } else {
	      # Wot?  Spaces?  That means extra %-macros.  Which, for the most part, can be ignored.
	      ($tmp) = ($tmp =~ /.+\s([^\s]+)/);  # Strip everything before the last space
	      $tmp = expandmacros($tmp, 'gp');  # Expand common macros
	      push @{$pkgdata{$pkgname}{conflist}}, $tmp;
	      $filelist{$pkgname} .= " $tmp";
	    }
	    next;
	  }

	  # and finally we can fall through %{_<FHS>}-prefixed locations...
	  if (/^\%\{_/) {
	    $filelist{$pkgname} .= " $_";
	    next;
	  }
	# EW.  Necessary to clear up %define expansions before we exit with redo.
	  $_ = expandmacros $_, 'g';

	  # ... unknown or "next section" % directives ...
	  redo LINE if /^\%/;

	  # ... and "normal" files
	  $filelist{$pkgname} .= " $_";
	}
	$filelist{$pkgname} = expandmacros($filelist{$pkgname}, 'g');
      } # done %file section

      if (/^\%changelog/) {
	$pkgdata{main}{changelog} = '';
	while (<SPECFILE>) {
	  redo LINE if /^\%/;
	  $pkgdata{main}{changelog} .= $_;
	}
      }

    } else {	# Data from the spec file "header"

      if (/^summary:\s+(.+)/i) {
	$pkgdata{main}{summary} = $1;
      } elsif (/^name:\s+(.+)/i) {
	$pkgdata{main}{name} = expandmacros($1,'g');
      } elsif (/^version:\s+(.+)/i) {
	$pkgdata{main}{version} = expandmacros($1,'g');
      } elsif (/^release:\s+(.+)/i) {
	$pkgdata{main}{release} = expandmacros($1,'g');
      } elsif (/^group:\s+(.+)/i) {
	$pkgdata{main}{group} = $1;
      } elsif (/^copyright:\s+(.+)/i) {
	$pkgdata{main}{copyright} = $1;
      } elsif (/^url:\s+(.+)/i) {
	$pkgdata{main}{url} = $1;
      } elsif (/^packager:\s+(.+)/i) {
	$pkgdata{main}{packager} = $1;
      } elsif (/^buildroot:\s+(.+)/i) {
	$buildroot = $1;
      } elsif (/^source:\s+(.+)/i) {
	$pkgdata{main}{source} = $1;
	die "Unknown tarball format $1\n" if $1 !~ /\.tar\.(?:gz|bz2)$/;
      } elsif (/^source([0-9]+):\s+(.+)/i) {
	$pkgdata{sources}{$1} = $2;
      } elsif (/^patch([^:]+):\s+(.+)$/i) {
	my $patchname = "patch$1";
	$pkgdata{main}{$patchname} = $2;
	if ($pkgdata{main}{$patchname} =~ /\//) {
	  # URL-style patch.  Rare but not unheard-of.
	  my @patchbits = split '/', $pkgdata{main}{$patchname};
	  $pkgdata{main}{$patchname} = $patchbits[$#patchbits];
	}
      } elsif (/^buildreq(?:uires)?:\s+(.+)/i) {
	$buildreq .= ", $1";
      } elsif (/^requires:\s+(.+)/i) {
	$pkgdata{main}{requires} .= ", $1";
      } elsif (/^provides:\s+(.+)/i) {
	$pkgdata{main}{provides} .= ", $1";
      }
#Name: suwrap
#Version: 0.04
#Release: 3
#Group: Applications/System
#Copyright: WebHart internal ONLY.  :(
#BuildArchitectures: i386
#BuildRoot: /tmp/%{name}-%{version}
#Url: http://virtual.webhart.net
#Packager: Kris Deugau <kdeugau@deepnet.cx>
#Source: ftp://virtual.webhart.net/%{name}-%{version}.tar.gz

    }
  }

  # Parse and replace some more macros.  More will be replaced even later.

  # Expand macros as necessary.
  $scriptletbase = expandmacros($scriptletbase,'gp');

  $buildroot = $cmdbuildroot if $cmdbuildroot;
  $buildroot = expandmacros($buildroot,'gp');

  close SPECFILE;
} # end parse_spec()


## prep()
# Writes and executes the %prep script (mostly) built while reading the spec file.
sub prep {
  # Replace some things here just to make sure.
  $prepscript = expandmacros($prepscript,'gp');

#print $prepscript; exit 0;

  # create script filename
  my $prepscriptfile = "$tmpdir/deb-tmp.prep.".int(rand(99998)+1);
  sysopen(PREPSCRIPT, $prepscriptfile, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW)
	or die $!;
  print PREPSCRIPT $scriptletbase;
  print PREPSCRIPT $prepscript;
  close PREPSCRIPT;

  # execute
  print "Calling \%prep script $prepscriptfile...\n";
  system("/bin/sh -e $prepscriptfile") == 0
	or die "Can't exec: $!\n";

  # and clean up
  unlink $prepscriptfile;
} # end prep()


## build()
# Writes and executes the %build script (mostly) built while reading the spec file.
sub build {
  # Expand the macros
  $buildscript = expandmacros($buildscript,'cgbp');

  # create script filename
  my $buildscriptfile = "$tmpdir/deb-tmp.build.".int(rand(99998)+1);
  sysopen(BUILDSCRIPT, $buildscriptfile, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW)
	or die $!;
  print BUILDSCRIPT $scriptletbase;
  print BUILDSCRIPT $buildscript;
  close BUILDSCRIPT;

  # execute
  print "Calling \%build script $buildscriptfile...\n";
  system("/bin/sh -e $buildscriptfile") == 0
	or die "Can't exec: $!\n";

  # and clean up
  unlink $buildscriptfile;
} # end build()


## install()
# Writes and executes the %install script (mostly) built while reading the spec file.
sub install {
  # Expand the macros
  $installscript = expandmacros($installscript,'igbp');

  # create script filename
  my $installscriptfile = "$tmpdir/deb-tmp.inst.".int(rand(99998)+1);
  sysopen(INSTSCRIPT, $installscriptfile, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW)
	or die $!;
  print INSTSCRIPT $scriptletbase;
#  print INSTSCRIPT $cleanscript;	# Clean up our install target before installing into it.
  print INSTSCRIPT $installscript;
  close INSTSCRIPT;

  # execute
  print "Calling \%install script $installscriptfile...\n";
  system("/bin/sh -e $installscriptfile") == 0
	or die "Can't exec: $!\n";

  # and clean up
  unlink $installscriptfile;
} # end install()


## binpackage()
# Creates the binary .deb package from the installed tree in $buildroot.
# Writes and executes a shell script to do so.
# Creates miscellaneous files required by dpkg-deb to actually build the package file.
# Should handle simple subpackages
sub binpackage {
  # Make sure we have somewhere to write the .deb file
  if (!-e "$topdir/DEBS/i386") {
    mkdir "$topdir/DEBS/i386";
  }

##work
  foreach my $pkg (@pkglist) {

    # Gotta do this first, otherwise we don't have a place to move files from %files
    mkdir "$buildroot/$pkg";

    # Eliminate any lingering % macros
    $filelist{$pkg} = expandmacros $filelist{$pkg}, 'g';

    my @pkgfilelist = split ' ', $filelist{$pkg};
    foreach my $pkgfile (@pkgfilelist) {
      $pkgfile = expandmacros($pkgfile, 'gp');
      my @filepath = ($pkgfile =~ m|(.+)/([^/]+)$|);
      qx { mkdir -p $buildroot/$pkg$filepath[0] }
	if $filepath[0] ne '';
      qx { mv $buildroot$pkgfile $buildroot/$pkg$filepath[0] };
    }

    # Get the "Depends" (Requires) a la RPM.  Ish.  We strip the leading
    # comma and space here (if needed) in case there were "Requires" specified
    # in the spec file - those would precede these.
    ($pkgdata{$pkg}{depends} .= getreqs("$buildroot/$pkg")) =~ s/^, //;

    # Do this here since we're doing {depends}...
    $pkgdata{$pkg}{provides} =~ s/^, // if defined($pkgdata{$pkg}{provides});

    # Gotta do this next, otherwise the control file has nowhere to go.  >:(
    mkdir "$buildroot/$pkg/DEBIAN";

    # Hack the filename for the package into a Debian-tool-compatible format.  GRRRRRR!!!!!
    # Have I mentioned I hate Debian Policy?
    $pkgdata{$pkg}{name} =~ tr/_/-/;

    # create script filename
    my $debscriptfile = "$tmpdir/deb-tmp.pkg.".int(rand(99998)+1);
    sysopen(DEBSCRIPT, $debscriptfile, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW)
	or die $!;
    print DEBSCRIPT $scriptletbase;
    print DEBSCRIPT "fakeroot dpkg-deb -b $buildroot/$pkg $topdir/DEBS/i386/".
	"$pkgdata{$pkg}{name}_$pkgdata{$pkg}{version}-$pkgdata{main}{release}_i386.deb\n";
      # %$&$%@#@@#%@@@ Debian and their horrible ugly package names.  >:(
    close DEBSCRIPT;

    my $control = "Package: $pkgdata{$pkg}{name}\n".
	"Version: $pkgdata{$pkg}{version}-$pkgdata{main}{release}\n".
	"Section: $pkgdata{$pkg}{group}\n".
	"Priority: optional\n".
	"Architecture: i386\n".
	"Maintainer: $pkgdata{main}{packager}\n".
	"Depends: $pkgdata{$pkg}{depends}\n".
	( defined($pkgdata{$pkg}{provides}) ? "Provides: $pkgdata{$pkg}{provides}\n" : '' ).
	"Description: $pkgdata{$pkg}{summary}\n";
    $control .= "$pkgdata{$pkg}{desc}\n";

    open CONTROL, ">$buildroot/$pkg/DEBIAN/control";
    print CONTROL $control;
    close CONTROL;

    # Iff there are conffiles (as specified in the %files list(s), add'em
    # in so dpkg-deb can tag them.
    if ($pkgdata{$pkg}{conffiles}) {
      open CONFLIST, ">$buildroot/$pkg/DEBIAN/conffiles";
      foreach my $conffile (@{$pkgdata{$pkg}{conflist}}) {
	print CONFLIST "$conffile\n";
      }
      close CONFLIST;
    }

    # Can't see much point in scripts on subpackages... although since
    # it's *possible* I should support it at some point.
    if ($pkg eq 'main') {
      if ($preinstscript ne '') {
	$preinstscript = expandmacros($preinstscript,'g');
	open PREINST, ">$buildroot/$pkg/DEBIAN/preinst";
	print PREINST "#!/bin/sh\nset -e\n\n";
	print PREINST $preinstscript;
	close PREINST;
	`chmod 0755 $buildroot/$pkg/DEBIAN/preinst`;
      }
      if ($postinstscript ne '') {
	$postinstscript = expandmacros($postinstscript,'g');
	open POSTINST, ">$buildroot/$pkg/DEBIAN/postinst";
	print POSTINST "#!/bin/sh\nset -e\n\n";
	print POSTINST $postinstscript;
	close POSTINST;
	`chmod 0755 $buildroot/$pkg/DEBIAN/postinst`;
      }
      if ($preuninstscript ne '') {
	$preuninstscript = expandmacros($preuninstscript,'g');
	open PREUNINST, ">$buildroot/$pkg/DEBIAN/prerm";
	print PREUNINST "#!/bin/sh\nset -e\n\n";
	print PREUNINST $preuninstscript;
	close PREUNINST;
	`chmod 0755 $buildroot/$pkg/DEBIAN/prerm`;
      }
      if ($postuninstscript ne '') {
	$postuninstscript = expandmacros($postuninstscript,'g');
	open POSTUNINST, ">$buildroot/$pkg/DEBIAN/postrm";
	print POSTUNINST "#!/bin/sh\nset -e\n\n";
	print POSTUNINST $postuninstscript;
	close POSTUNINST;
	`chmod 0755 $buildroot/$pkg/DEBIAN/postrm`;
      }
    }

#print `ls -l $buildroot/DEBIAN`;

#Package: httpd
#Version: 2.0.54-7via
#Section: unknown
#Priority: optional
#Architecture: i386
#Depends: libc6 (>= 2.3.2.ds1-21), libdb4.2, libexpat1 (>= 1.95.8), libssl0.9.7, libapr0
#Replaces: apache2
#Installed-Size: 3076
#Maintainer: Kris Deugau <kdeugau@vianet.ca>
#Description: apache2 for ViaNet
# apache2 for ViaNet.  Includes per-vhost setuid patches from
# http://home.samfundet.no/~sesse/mpm-itk/.

    # execute
    print "Calling package creation script $debscriptfile for $pkgdata{$pkg}{name}...\n";
    system("/bin/sh -e $debscriptfile") == 0
	or die "Can't exec: $!\n";

    # and clean up
    unlink $debscriptfile;

  } # subpackage loop

} # end binpackage()


## srcpackage()
# Builds a .src.deb source package.  Note that Debian's idea of
# a "source package" is seriously flawed IMO, because you can't
# easily copy it as-is.
# Not quite identical to RPM, but Good Enough (TM).
sub srcpackage {
  my $pkgsrcname = "$pkgdata{main}{name}-$pkgdata{main}{version}-$pkgdata{main}{release}.sdeb";

  my $paxcmd;

  # We'll definitely need this later, and *may* need it sooner.
  (my $barespec = $specfile) =~ s|.+/([^/]+)$|$1|;

  # Copy the specfile to the build tree, but only if it's not there already.
##buglet: need to deal with silly case where silly user has put the spec
# file in a subdir of %{_topdir}/SPECS.  Ewww.  Silly user!
  if (abs_path($specfile) !~ /^$topdir\/SPECS/) {
    $paxcmd .= "cp $specfile %{_topdir}/SPECS/; \n"
  }

  # use pax -w [file] [file] ... >outfile.sdeb
  $paxcmd = "cd $topdir; pax -w ";

# tweak source entry into usable form.  Need it locally somewhere along the line.
  (my $pkgsrc = $pkgdata{main}{source}) =~ s|.+/([^/]+)$|$1|;
  $paxcmd .= "SOURCES/$pkgsrc ";

  # create file list:  Source[nn], Patch[nn]
  foreach my $specbit (keys %{$pkgdata{main}} ) {
    next if $specbit eq 'source';
    $paxcmd .= "SOURCES/$pkgdata{main}{$specbit} " if $specbit =~ /^(source|patch)/;
##buglet: need to deal with case where patches are listed as URLs?
#  or other extended pathnames?  Silly !@$%^&!%%!%!! user!
  }

  # add the spec file, source package destination, and cd back where we came from.
  $paxcmd .= "SPECS/$barespec > $topdir/SDEBS/$pkgsrcname; cd -";

  # In case of %-macros...
  $paxcmd = expandmacros($paxcmd,'gp');
  
  system "$paxcmd";
  print "Wrote source package $pkgsrcname in $topdir/SDEBS.\n";
}


## checkbuildreq()
# Checks the build requirements (if any)
# Spits out a rude warning and returns a true-false error if any
# requirements are not met.
sub checkbuildreq {
  return 1 if $buildreq eq '';	# No use doing extra work.

  my $reqflag = 1;  # unset iff a buildreq is missing

  $buildreq =~ s/^, //;	# Strip the leading comma and space
  my @reqlist = split /,\s+/, $buildreq;

  foreach my $req (@reqlist) {
    my ($pkg,$rel,$ver);

    # We have two classes of requirements - versioned and unversioned.
    if ($req =~ /[><=]/) {
      # Pick up the details of versioned buildreqs
      ($pkg,$rel,$ver) = ($req =~ /([a-z0-9._-]+)\s+([><=]+)\s+([a-z0-9._-]+)/);
    } else {
      # And the unversioned ones.
      $pkg = $req;
      $rel = '>=';
      $ver = 0;
    }

    my @pkglist = qx { dpkg-query --showformat '\${status}\t\${version}\n' -W $pkg };
# need to check if no lines returned - means a bad buildreq
    my ($reqstat,undef,undef,$reqver) = split /\s+/, $pkglist[0];
    if ($reqstat !~ /install/) {
      print " * Missing build-dependency $pkg!\n";
      $reqflag = 0;
    } else {
# gotta be a better way to do this... :/
      if ($rel eq '>=' && !($reqver ge $ver)) {
	print " * Buildreq $pkg is installed, but wrong version ($reqver):  Need $ver\n";
	$reqflag = 0;
      }
      if ($rel eq '>' && !($reqver gt $ver)) {
	print " * Buildreq $pkg is installed, but wrong version ($reqver):  Need $ver\n";
	$reqflag = 0;
      }
      if ($rel eq '<=' && !($reqver le $ver)) {
	print " * Buildreq $pkg is installed, but wrong version ($reqver):  Need $ver\n";
	$reqflag = 0;
      }
      if ($rel eq '<' && !($reqver lt $ver)) {
	print " * Buildreq $pkg is installed, but wrong version ($reqver):  Need $ver\n";
	$reqflag = 0;
      }
      if ($rel eq '=' && !($reqver eq $ver)) {
	print " * Buildreq $pkg is installed, but wrong version ($reqver):  Need $ver\n";
	$reqflag = 0;
      }
    } # end not installed/installed check
  } # end req loop

  return $reqflag;
} # end checkbuildreq()


## getreqs()
# Find out which libraries/packages are required for any
# executables and libs in a given file tree.
# (Debian doesn't have soname-level deps;  just package-level)
sub getreqs() {
  my $pkgtree = $_[0];

  print "Checking library requirements...\n";
  my @reqlist = qx { find $pkgtree -type f -perm 755 | grep -v $pkgtree/etc | xargs ldd };

  my %reqs;
  my $reqlibs;

  foreach (@reqlist) {
    next if /^$pkgtree/;
    next if m|/lib/ld-linux.so|;
    my ($req) = (/^\s+([a-z0-9._-]+)/);

    $reqlibs .= " $req";
  }

  foreach (qx { dpkg -S $reqlibs }) {
    my ($libpkg,undef) = split /:\s+/;
    $reqs{$libpkg} = 1;
  }

  my $deplist;
  foreach (keys %reqs) {
    $deplist .= ", $_";
  }

# For now, we're done.  We're not going to meddle with versions yet.
# Among other things, it's messier than handling "simple" yes/no "do
# we have this lib?" deps.  >:(

  return $deplist;
} # end getreqs()


## expandmacros()
# Expands all %{blah} macros in the passed string
# Split up a bit with some sections so we don't spend time trying to
# expand macros that are only used in a few specific places.
sub expandmacros {
  my $macrostring = shift;
  my $section = shift;

  # To allow the FHS-ish %configure and %makeinstall to work The Right Way.
  # (Without clobbering the global $buildroot.)
  my $prefix = '';

  if ($section =~ /c/) {
    # %configure macro
# Don't know what it's for, don't have a useful default replacement
# 	--program-prefix=%{_program_prefix} \
    $macrostring =~ s'%configure'./configure --host=$DEB_HOST_GNU_TYPE \
	--build=$DEB_BUILD_GNU_TYPE \
	--prefix=%{_prefix} \
	--exec-prefix=%{_exec_prefix} \
	--bindir=%{_bindir} \
	--sbindir=%{_sbindir} \
	--sysconfdir=%{_sysconfdir} \
	--datadir=%{_datadir} \
	--includedir=%{_includedir} \
	--libdir=%{_libdir} \
	--libexecdir=%{_libexecdir} \
	--localstatedir=%{_localstatedir} \
	--sharedstatedir=%{_sharedstatedir} \
	--mandir=%{_mandir} \
	--infodir=%{_infodir} ';
  } # done %configure

  if ($section =~ /m/) {
    $macrostring =~ s'%{__make}'make ';
  } # done make

  if ($section =~ /i/) {
    # This is where we need to mangle $prefix.
    $macrostring =~ s'%makeinstall'make %{fhs} install';
    $prefix = $buildroot;
  } # done %install and/or %makeinstall

  # Build data
  # Note that these are processed in reverse order to get the substitution order right
  if ($section =~ /b/) {
#    $macrostring =~ s'%{fhs}'host=$DEB_HOST_GNU_TYPE \
#	build=$DEB_BUILD_GNU_TYPE \
    $macrostring =~ s'%{fhs}'prefix=%{_prefix} \
	exec-prefix=%{_exec_prefix} \
	bindir=%{_bindir} \
	sbindir=%{_sbindir} \
	sysconfdir=%{_sysconfdir} \
	datadir=%{_datadir} \
	includedir=%{_includedir} \
	libdir=%{_libdir} \
	libexecdir=%{_libexecdir} \
	localstatedir=%{_localstatedir} \
	sharedstatedir=%{_sharedstatedir} \
	mandir=%{_mandir} \
	infodir=%{_infodir} \
';

    # Note that the above regex terminates with the extra space
    # "Just In Case" of user additions, which will then get neatly
    # tagged on the end where they take precedence (supposedly)
    # over the "default" ones.

    # Now we cascade the macros introduced above.  >_<
									# Wot ot to go theah:
    $macrostring =~ s|%{_mandir}|%{_datadir}/man|g;			#/usr/share/man
    $macrostring =~ s|%{_infodir}|%{_datadir}/info|g;			#/usr/share/info
    $macrostring =~ s|%{_oldincludedir}|/usr/include|g;		#/usr/include
    $macrostring =~ s|%{_includedir}|%{_prefix\}/include|g;	#/usr/include
    $macrostring =~ s|%{_libdir}|%{_exec_prefix}/%{_lib}|g;	#/usr/lib
    $macrostring =~ s|%{_lib}|lib|g;					#?
    $macrostring =~ s|%{_localstatedir}|/var|g;			#/var
    $macrostring =~ s|%{_sharedstatedir}|%{_prefix}/com|g;	#/usr/com WTF?
    $macrostring =~ s|%{_sysconfdir}|/etc|g;			#/etc
    $macrostring =~ s|%{_datadir}|%{_prefix}/share|g;		#/usr/share
    $macrostring =~ s|%{_libexecdir}|%{_exec_prefix}/libexec|g;	#/usr/libexec
    $macrostring =~ s|%{_sbindir}|%{_exec_prefix}/sbin|g;	#/usr/sbin
    $macrostring =~ s|%{_bindir}|%{_exec_prefix}/bin|g;		#/usr/bin
    $macrostring =~ s|%{_exec_prefix}|%{_prefix}|g;		#/usr
    $macrostring =~ s|%{_prefix}|/usr|g;				#/usr
  } # done with config section

  # Package data
  if ($section =~ /p/) {
    $macrostring =~ s/\%\{buildroot\}/$buildroot/gi;
    foreach my $source (keys %{$pkgdata{sources}}) {
      $macrostring =~ s/\%\{source$source\}/$topdir\/SOURCES\/$pkgdata{sources}{$source}/gi;
    }
    $macrostring =~ s/\%\{name\}/$pkgdata{main}{name}/gi;
    $macrostring =~ s/\%\{version\}/$pkgdata{main}{version}/gi;
    $macrostring =~ s/\%\{release\}/$pkgdata{main}{release}/gi;
  }

  # Globals, and not-so-globals
  if ($section =~ /g/) {
    $macrostring =~ s|%{_builddir}|%{_topdir}/BUILD|g;
    $macrostring =~ s|%{_topdir}|$topdir|g;
    $macrostring =~ s|%{_tmppath}|$tmpdir|g;
    $macrostring =~ s'%{_docdir}'/usr/share/doc'g;

    # Standard FHS locations.  More or less.
    $macrostring =~ s'%{_bindir}'/usr/bin'g;
    $macrostring =~ s'%{_sbindir}'/usr/sbin'g;
    $macrostring =~ s'%{_mandir}'/usr/share/man'g;
    $macrostring =~ s'%{_includedir}'/usr/include'g;
    $macrostring =~ s'%{_libdir}'/usr/lib'g;
    $macrostring =~ s'%{_sysconfdir}'/etc'g;
    $macrostring =~ s'%{_localstatedir}'/var'g;

    # %define's
    foreach my $key (keys %specglobals) {
      $macrostring =~ s|%{$key}|$specglobals{$key}|g;
    }

    # system programs.  RPM uses a global config file for these;  we'll just
    # ASS-U-ME and make life a little simpler.
    if ($macrostring =~ /\%\{\_\_([a-z0-9_-]+)\}/) {
      $macrostring =~ s|%{__([a-z0-9_-]+)}|$1|g;
    }
  } # done with globals section

  return $macrostring;
} # end expandmacros()



__END__



=head1 NAME

debbuild - Build Debian-compatible packages from RPM spec files

=head1 SYNOPSIS

 debbuild {-ba|-bb|-bp|-bc|-bi|-bl|-bs} [build-options] file.spec

 debbuild {-ta|-tb|-tp|-tc|-ti|-tl|-ts} [build-options] file.tar.{gz|bz2}

 debbuild --rebuild file.src.{rpm|deb}

=head1 DESCRIPTION

This script attempts to build Debian-friendly semi-native packages
from RPM spec files, RPM-friendly tarballs, and RPM source packages
(.src.rpm).  It accepts I<most> of the options rpmbuild does, and
should be able to interpret most spec files usefully.  Perl modules
should be handled via CPAN+dh-make-perl instead;  Debian's conventions
for such things do not lend themselves to automated conversion.

As far as possible, the command-line options are identical to those
from rpmbuild, although several rpmbuild options are not supported.

=cut
