#!/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: 2005-11-02 19:37:45 +0000 (Wed, 02 Nov 2005) $ # SVN revision $Rev: 9 $ # Last update by $Author: kdeugau $ ### use strict; use warnings; use Fcntl; # for sysopen flags # 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) # 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. # Initialized globals my $verbosity = 0; my %cmdopts = (type => '', stage => 'a', short => 'n' ); my $topdir = "/usr/src/debian"; my $buildroot = "/var/tmp/%{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 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 # 10/31/2005 Maybe this should be flatter? -kgd my %pkgdata; # Scriptlets my $prepscript; my $buildscript; my $installscript; my $cleanscript; # Functions sub prep; sub parse_spec; die "Not enough arguments\n" if #$argv == 0; ##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(); } # Hokay. Need to: # Execute prep if *not* --short-circuit if ($cmdopts{stage} eq 'p' || ($cmdopts{stage} =~ /[cilabs]/ && $cmdopts{short} ne 'y')) { prep(); } if ($cmdopts{stage} eq 'c' || ($cmdopts{stage} =~ /[ilabs]/ && $cmdopts{short} ne 'y')) { build(); } if ($cmdopts{stage} =~ /[ilabs]/) { install(); } if ($cmdopts{stage} eq 'a') { binpackage(); srcpackage(); } if ($cmdopts{stage} eq 'b') { binpackage(); } if ($cmdopts{stage} eq 's') { srcpackage(); } # 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 { if (/^-[bt]/) { if ($cmdopts{stage} eq 's') { # Mutually exclusive options. die "Can't use $_ with --rebuild\n"; } else { ($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 { # --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. # NB - this is NOT fatal, just ignored! if ($cmdopts{short} eq 'y') { 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 once we've.... uh.... sub parse_spec { open SPECFILE,"<$specfile"; LINE: while () { next if /^#/; next if /^\s+$/; if (/^\%/) { # A macro that needs further processing. if (/^\%description/) { $pkgdata{main}{desc} .= $_; while () { redo LINE if /^\%/; $pkgdata{main}{desc} .= " $_"; } } 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} =~ s/\%\{name\}/$pkgdata{main}{name}/; $pkgdata{main}{source} =~ s/\%\{version\}/$pkgdata{main}{version}/; PREPSCRIPT: while () { if (/^\%setup/) { # Parse out the %setup macro. Note that we aren't supporting # most 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" : "" ). "x".( /\s+-q\s+/ ? '' : 'vv' )."f ". "$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/) { print "patch macro\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"; while () { redo LINE if /^\%/; $buildscript .= $_; } } if (/^\%install/) { $installscript .= "cd $tarballdir\n"; while () { redo LINE if /^\%/; $installscript .= $_; } } if (/^\%clean/) { while () { redo LINE if /^\%/; $cleanscript .= $_; } } if (/^\%post/) { } if (/^\%preun/) { } if (/^\%files/) { # Danger! Danger! } } else { if (/^summary:\s+(.+)/i) { $pkgdata{main}{summary} = $1; } elsif (/^name:\s+(.+)/i) { $pkgdata{main}{name} = $1; } elsif (/^version:\s+(.+)/i) { $pkgdata{main}{version} = $1; } elsif (/^release:\s+(.+)/i) { $pkgdata{main}{release} = $1; } 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)$/; } #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 macros # Start with the ones I use # $pkgdata{main}{source} =~ s/\%\{name\}/$pkgdata{main}{name}/; # $pkgdata{main}{source} =~ s/\%\{version\}/$pkgdata{main}{version}/; # Scriptlet core. Use /g to make sure all occurrences get tweaked. # "real" globals (apply to all packages the same way) $scriptletbase =~ s/\%\{_topdir\}/$topdir/g; # "fake" globals (may be tweaked by package) $buildroot = $cmdbuildroot if $cmdbuildroot; $scriptletbase =~ s/\%\{buildroot\}/$buildroot/g; # sub-package(compatible) stuff $scriptletbase =~ s/\%\{name\}/$pkgdata{main}{name}/g; $scriptletbase =~ s/\%\{version\}/$pkgdata{main}{version}/g; $scriptletbase =~ s/\%\{release\}/$pkgdata{main}{release}/g; # Some more macro substitution. Nrgh. $buildroot =~ s/\%\{name\}/$pkgdata{main}{name}/g; $buildroot =~ s/\%\{version\}/$pkgdata{main}{version}/g; $buildroot =~ s/\%\{release\}/$pkgdata{main}{release}/g; } # end parse_spec() ## prep() # Unpacks the tarball from the SOURCES directory to the BUILD directory. # Speaks gzip and bzip2. # Finishes by applying patches in %prep section of spec file sub prep { { # create script filename my $prepscriptfile = "/var/tmp/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: $!\nalso: $?"; # and clean up unlink $prepscriptfile; } } # end prep() ## build() # Execute commands provided as a shell script. It may prove simpler # to do as rpm does and actually create a little shell script. sub build { # create script filename my $buildscriptfile = "/var/tmp/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: $!\nalso: $?"; # and clean up unlink $buildscriptfile; } # end build() ## install() # Creates and executes %install script(let) sub install { # create script filename my $installscriptfile = "/var/tmp/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: $!\nalso: $?"; # and clean up unlink $installscriptfile; } # end install() ## binpackage() # Creates the binary .deb package from the installed tree in $buildroot. sub binpackage { # Some checks on the .deb file location if (!-e "$topdir/DEBS/i386") { # "if [ -e $topdir/DEBS/i386 ]; then\n\tmkdir $topdir/DEBS/i386\nfi\n". mkdir "$topdir/DEBS/i386"; } # Gotta do this first, otherwise the control file has nowhere to go. >:( mkdir "$buildroot/DEBIAN"; # create script filename my $debscriptfile = "/var/tmp/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 "tree $buildroot\n". "fakeroot dpkg-deb -b $buildroot $topdir/DEBS/i386/$pkgdata{main}{name}_$pkgdata{main}{version}-$pkgdata{main}{release}_i386.deb\n"; close DEBSCRIPT; my $control = "Package: $pkgdata{main}{name}\n". "Version: $pkgdata{main}{version}-$pkgdata{main}{release}\n". "Section: unknown\n". "Priority: optional\n". "Architecture: i386\n". "Maintainer: $pkgdata{main}{packager}\n". "Description: $pkgdata{main}{desc}\n"; eval { open CONTROL, ">$buildroot/DEBIAN/control"; print CONTROL $control; close CONTROL; }; if ($@) { print $@; } #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 "Creating .deb for $pkgdata{main}{name}...\n"; system("/bin/sh -e $debscriptfile") == 0 or die "Can't exec: $!\nalso: $?"; # and clean up unlink $debscriptfile; } # end binpackage() sub srcpackage {}