Below please find the `equivs-build-multi' script I use to implement this. It simply parses a control file into individual temporary equivs control files, and invokes equivs-build on each in turn.
Note, however, that the license of the attached script is not GPL. I'll be happy to discuss licensing options (dual-licensed BSD/GPL if at all possible, or even changing the license to GPL if that will help get the script included in the equivs package proper) -- but at this point, I'm making the script available under the BSD license so as to save others from reinventing the wheel. Ideally, maybe the current equivs-build could be made into a library and the below script refactored and adopted as the main entry point to the current equivs-build functionality. Having a single script to build equivs files with one or multiple package definitions in them would seem like the best option from a usability point of view.
#!/usr/bin/perl # # equivs-build-multi: build multiple packages from a single equivs control file # era Fri Jan 26 23:07:01 2007 # # Depends: # equivs # perl-base (perl, Getopt::Long) # perl-modules (File::Temp) ######## TODO: i18n =head1 NAME equivs-build-multi - build multiple packages from one equivs file =head1 SYNOPSIS B<equivs-build-multi> I<controlfile> =head1 DESCRIPTION B<equivs-build-multi> is a wrapper for L<equivs-build(1)> to build multiple packages from a single input file of package descriptions. The input file is similar to the basic B<equivs> file format, with the differences described below. =over 2 =item * The file may contain multiple sections. Empty lines (one or more) are section dividers. =item * The first of these sections is the "global" section, which defines defaults which will apply to all subsequent sections. It may not contain a B<Package> field, nor B<Description>; it must contain a B<Source> field; and it will usually define B<Standards-Version>, B<Section>, and B<Priority> for all packages defined in the file in the subsequent sections. B<Maintainer>, B<Architecture>, B<Copyright> and B<Changelog> are also usually global, if they are specified, but B<equivs-build> will fill in the required fields with its own defaults if they are missing (do check, though, whether those defaults make sense for you), and optional fields are, well, optional. =item * Each subsequent section is a "package" section. It is required to have at least the B<Package> and B<Description> fields, and in practice, it should contain dependencies, too, (B<Depends>, B<Suggests>, B<Recommends>, and/or the B<Pre-> and B<Build-> variants of these) in order to be at all meaningful. =back Each package section is merged with the global section into a separate control file for B<equivs-build>, as described in more detail in the next paragraph, and B<equivs-build> is invoked on that file to actually build that package. The temporary B<equivs-build> control file is removed after the build, regardless of whether the build was successful. =for a bit later ######## TODO: make global and package dependency fields additive? The merge algorithm for global and package-specific fields is straightforward: if there is a conflict, the package-specific value wins. In other words, a package section is allowed to override values specified in the global section. However, a warning is generated whenever a global field value is overridden. >From a readability and maintainability perspective, it is probably better to avoid overrides, and instead specify the field's value in each package section individually. Each package section results in a temporary control file which is passed to B<equivs-build> and then removed after B<equivs-build> finishes building it. =head1 OPTIONS B<equivs-build-multi> offers the following options: =over 4 =item --quiet | -q Quiet mode. Currently merely disables warnings whenever package sections override global field values. =item --keep | -k Keep the generated B<equivs-build> input files. Normally they are removed after they are used. The names of the generated files are printed; they are automatically generated in order to be unique and never overwrite an existing file. =item --generate-only | -g Only generate the B<equivs-build> file(s) and exit. Implies the B<--keep> option. =item --help | -h Print a brief help message and exit. =item --version | -v Print the version number of B<equivs-build-multi> and exit. =back =head1 EXAMPLE The following B<equivs-build-multi> file will result in two runs of B<equivs-build> on two different files. Both will contain the global values, and then one each of the package sections. Source: example-local-equivs Section: misc Priority: optional Standards-Version: 3.5.10 Changelog: changelog Version: 0.01 Architecture: all Maintainer: era eriksson <[EMAIL PROTECTED]> Copyright: /usr/share/common-licenses/BSD Package: first-example Depends: deborphan, debconf-utils Description: An example which doesn't do anything very useful Well, it pulls in deborphan and debconf-utils, if you should want them on your system. Package: second-example Copyright: /usr/share/common-licenses/GPL Description: Another fairly useless example Just for demonstration purposes, it overrides the Copyright: from the global section (GPL vs BSD). . The description field is also a good place for comments Pre-Depends: first-example X-Random-Comment: arbitrary reasonably unique field names can be used to embed comments. . For visibility, however, putting them in the Description: field might be better, For regular B<equivs> packages, it is perhaps not very useful to have detailed per-package control over fields such as architecture (by nature, an equivs package is usually "all"), copyright etc. but if you find you need it, the mechanism is there. =cut use strict; use warnings; package Debian::Equivs::Multi; use Carp; sub new { my ($class, $file, $options) = @_; my $self = bless { file => $file, opt => $options }, $class || ref $class; $self->_init() || return undef; return $self; } sub _init { my ($self) = @_; my ($file, $ctl) = $self->{file}; unless (open ($ctl, $file)) { carp "Could not open file $file: $!"; return undef; } # else $self->{handle} = $ctl; $self->readpackages ($ctl) || return undef; close ($ctl); return $self; } sub readpackages { my ($self, $handle) = @_; $self->readglobal ($handle) || return undef; $self->{packages} = []; while (! eof $handle) { $self->readpkg ($handle); } return $self; } sub readglobal { my ($self, $handle) = @_; my %global = $self->readsection($handle); return undef unless %global; croak $self->{file}, ":$.: global section must specify 'Source:' field" unless (defined $global{'source'}); croak $self->{file}, ":$.: global section may not contain 'Package:' field" if (defined $global{'package'}); croak $self->{file}, ":$.: global section may not contain 'Description:' field" if (defined $global{'description'}); $self->{global} = \%global; } sub readpkg { my ($self, $handle) = @_; my %section = $self->readsection ($handle); return undef unless %section; croak $self->{file}, ":$.: previous section did not declare Package: field" unless (defined $section{'package'}); ######## TODO: maybe turn packages into subordinate objects my $package = $section{'package'}; chomp $package; $package =~ s/\APackage:\s+//i; croak $self->{file}, ":$.: package $package already defined earlier" if (defined $self->{packagehash}->{$package}); carp $self->{file}, ":$.: package $package has no description" unless (defined $section{'description'}); ######## TODO: make some head keys read-only, changing them fatal? unless ($self->{opt}->{'quiet'}) { map { carp "package $package overrides ", "global $_ value '$self->{global}->{$_}' with '$section{$_}'" if (defined $self->{global}->{$_}); } keys %section; } # Merge globals and %section into final hash %package my %package = %{$self->{global}}; map { $package{$_} = $section{$_} } keys %section; $package{"_packagename"} = $package; push @{$self->{packages}}, \%package; $self->{packagehash}->{$package} = \%package; return %package; } sub readsection { my ($self, $handle) = @_; my (%field, $field); while ($_ = <$handle>) { last if ($_ eq "\n"); if (m/^\s/) { chomp, croak $self->{file}, ":$.: Malformed input line '$_'" unless $field; $field{$field} .= $_; } elsif (m/^(([A-Za-z][-0-9A-Za-z]*):.*\n?)$/) { $field = lc($2); croak $self->{file}, ":$.: section already defined $field earlier" if (defined $field{$field}); $field{$field} = $1; } else { croak $self->{file}, ":$.: Malformed input file"; } } return %field; } sub _equivs_build { my ($self, $file) = @_; my $exitcode = system ("equivs-build", $file); unlink $file unless ($self->{opt}->{'keep'}); croak "equivs-build failed (exit code ", $exitcode >> 8, ")" unless ($exitcode == 0); } sub build { my ($self, @packages) = @_; if (@packages) { # Check that all requested packages are also available my %requested = map { $_ => 1 } @packages; map { delete $requested{$_} } keys %{$self->{packagehash}}; croak "Cannot build package", (keys %requested > 1 ? "s" : ""), " ", join (", ", keys %requested) if %requested; } else { @packages = keys %{$self->{packagehash}}; } my %build = map { $_ => 1 } @packages; for my $pkg (@{$self->{packages}}) { next unless $build{$pkg->{'_packagename'}}; my $file = $self->writepackage ($pkg); print "$file\n" if ($self->{opt}->{'generate-only'} || $self->{opt}->{'keep'}); $self->_equivs_build($file) unless ($self->{opt}->{'generate-only'}); } } use File::Temp qw(tempfile); sub writepackage { my ($self, $pkgref) = @_; my ($package) = $pkgref->{"_packagename"}; my ($template) = join (".", "equivs", $package, "tmpXXXXX"); ######## TODO: option to use a different directory for temporary files my ($handle, $filename) = tempfile($template, DIR => "."); $self->writesection ($handle, $pkgref, [qw(Source Section Priority Standards-Version)], [qw(Changelog Copyright)]); print $handle "\n"; $self->writesection ($handle, $pkgref, [qw(Package Version)], [qw(Architecture Maintainer Depends Description)]); map { print $handle $pkgref->{$_} unless $_ eq '_packagename' } keys %{$pkgref}; close ($handle); return $filename; } sub writesection { my ($self, $handle, $keyhash, $reqarray, $optarray) = @_; for my $field (map { lc } @{$reqarray}) { if (defined $keyhash->{$field}) { print $handle $keyhash->{$field}; delete $keyhash->{$field}; } else { carp "package $keyhash->{_packagename}: ", "required field $field missing"; } } for my $field (map { lc } @{$optarray}) { if (defined $keyhash->{$field}) { print $handle $keyhash->{$field}; delete $keyhash->{$field}; } } } our $VERSION = 0.01; package main; unless (caller) { our $me = $0; $me =~ s,.*/,,; my $syntax = "Syntax: $me [-gkq] controlfile [ packages ] | -[vh]\n"; use Getopt::Long; Getopt::Long::Configure ("bundling", "no_ignore_case"); my %opt = (); GetOptions (\%opt, "keep|k", "quiet|q", "generate-only|g", "version|v", "help|h") || die $syntax; if ($opt{version}) { print "$me version $Debian::Equivs::Multi::VERSION\n"; exit 1; } # else if ($opt{help}) { print $syntax; exit 1; } # else my $file = shift || die $syntax; my $packages = Debian::Equivs::Multi->new ($file, \%opt); die "$me: Invalid input file $file, no packages defined\n" unless (defined $packages); $packages->build (@ARGV); } else { 1; } ######## TODO: FILES?, ENVIRONMENT? =head1 BUGS Proper changelog generation is currently blocked by equivs bug #409557 L<http://bugs.debian.org/409557> =head1 AUTHOR era eriksson L<http://www.iki.fi/era/> =head1 LICENSE "New-style" BSD (no advertising clause). =cut ######## FIXME: include a license in the source #See the source for a copy of the full license text.