#!/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 () { # 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 () { 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 () { 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 () { 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 () { 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 () { 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 () { if (/^\%makeinstall/) { $installscript .= expandmacros($_,'igbp'); } else { last INSTALLSCRIPT if /^\%/; $installscript .= $_; } } redo LINE; } if (/^\%clean/) { while () { redo LINE if /^\%/; $cleanscript .= $_; } $cleanscript = expandmacros($cleanscript,'gp'); } # pre/post (un)install scripts if (/^\%pre\b/) { while () { redo LINE if /^\%/; $preinstscript .= $_; } } if (/^\%post\b/) { while () { redo LINE if /^\%/; $postinstscript .= $_; } } if (/^\%preun\b/) { while () { redo LINE if /^\%/; $preuninstscript .= $_; } } if (/^\%postun\b/) { while () { 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 () { 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 %{_}-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 () { 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 #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 #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 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