mirror of
https://github.com/protobuf-c/protobuf-c.git
synced 2024-12-28 14:48:18 +08:00
a46640c36e
git-svn-id: https://protobuf-c.googlecode.com/svn/trunk@69 00440858-1255-0410-a3e6-75ea37f81c3a
3734 lines
104 KiB
Perl
Executable File
3734 lines
104 KiB
Perl
Executable File
#! /usr/bin/perl -w
|
||
|
||
# pkgwrite 0.0.8: Build native packages from source code
|
||
# and pkgwriteinfo files.
|
||
#
|
||
# This file contains POD documentation: try `perldoc pkgwrite' for help.
|
||
#
|
||
# Copyright Dave Benson <daveb@ffem.org>, 2000-2001.
|
||
|
||
# Requirements:
|
||
# perl 5.004 or higher,
|
||
# and native packaging tools for the output packages, that is:
|
||
# For redhat `rpm'. And for debian `dpkg' and `dpkg-buildpackage'.
|
||
|
||
# pkgwrite
|
||
#
|
||
# This program is free software; you can redistribute it and/or
|
||
# modify it under the terms of the GNU General Public
|
||
# License as published by the Free Software Foundation; either
|
||
# version 2 of the License, or (at your option) any later version.
|
||
#
|
||
# This program is distributed in the hope that it will be useful,
|
||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
# General Public License for more details.
|
||
#
|
||
# You should have received a copy of the GNU General Public
|
||
# License along with this program; if not, write to the Free Software
|
||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||
#
|
||
# ---
|
||
#
|
||
# This software may also be licensed under the terms of
|
||
# the GNU Lesser General Public License, version 2 or higher.
|
||
# (This is so that the pkgwrite script may be legally included
|
||
# with LGPL'd software.)
|
||
#
|
||
# ---
|
||
#
|
||
# Furthermore, the packages and intermediary files generated
|
||
# with pkgwrite are specifically excluded from the terms of
|
||
# this license. Hence, pkgwrite adds no additional restrictions to
|
||
# the package's licensing, but they must comply with the
|
||
# licensing on the other source code, libraries and tools used
|
||
# to generate the package.
|
||
|
||
# Finally, this contains the rpmrc that is distributed with
|
||
# rpm 4.0: CVS entry: rpmrc.in 2.28.2.2 2000/09/13.
|
||
# That file had no copyright info. The RPM source code,
|
||
# like pkgwrite, is licenced under both the LGPL and the GPL.
|
||
|
||
require 5.004;
|
||
use Carp;
|
||
use Config; # for signal names
|
||
use Symbol qw(gensym);
|
||
|
||
# Table of Contents of the Source Code.
|
||
# Section 0: Initialization & Configuration.
|
||
# Section 1: Helper functions.
|
||
# Section 2: parse_pkgwriteinfo_file: Return package information.
|
||
# Section 3: Verify that a package is correctly formed.
|
||
# Section 4: ChangeLog parsing code.
|
||
# Section 5: Redhat tape.
|
||
# Section 6: Debian tape.
|
||
# Section 7: High-level API: Make a package from a tarball.
|
||
# Section 8: Usage message.
|
||
# Section 9: Main program.
|
||
# Section 10: POD Documention.
|
||
|
||
|
||
#=====================================================================
|
||
# Section 0: Initialization & Configuration.
|
||
#=====================================================================
|
||
# --- function prototypes ---
|
||
sub dump_list ($); # print a builtin table to stdout
|
||
sub initialize_packaging_tables (); # redhat/debian package mgnt specifics
|
||
sub initialize_signal_tables (); # signal number to id mappings
|
||
sub add_automatic_conflicts ($); # add conflicts if file sets intersect
|
||
|
||
# --- global initialization ---
|
||
$initial_dir = `pwd`;
|
||
chomp ($initial_dir);
|
||
initialize_packaging_tables ();
|
||
initialize_signal_tables ();
|
||
$PKGWRITE_VERSION = '0.0.8';
|
||
|
||
# --- configuration ---
|
||
# set to 0 for debugging: don't delete working directories.
|
||
$do_cleanup = 1;
|
||
$do_cleanup = $ENV{DO_CLEANUP} if defined($ENV{DO_CLEANUP});
|
||
|
||
# force the changelog to refer to this exact version.
|
||
$strict_changelog_checking = 1;
|
||
|
||
# whether to perform an extra packaging sanity test phase.
|
||
$do_sanity_check = 1;
|
||
|
||
# for the changelog, which distribution should be listed?
|
||
# XXX: hm, what if a single package changes dist? look at
|
||
# packages in stable for an example, i guess... (actually
|
||
# this isn't a problem if you *always* use pkgwrite,
|
||
# but that's annoying.)
|
||
$debian_dist = 'unstable';
|
||
|
||
# options to pass to ./configure (these differ due on different
|
||
# distros due to different standard directories -- most likely,
|
||
# the redhat usage will converge to the debian/fhsstnd.)
|
||
@common_config_flags = ( '--sysconfdir=/etc',
|
||
'--prefix=/usr',
|
||
'--quiet' );
|
||
$redhat_config_flags = join(' ',
|
||
@common_config_flags
|
||
);
|
||
$debian_config_flags = join(' ',
|
||
@common_config_flags,
|
||
'--mandir=/usr/share/man',
|
||
'--infodir=/usr/share/info',
|
||
'--datadir=/usr/share');
|
||
|
||
# Directories where shared libraries are kept: if we install a library
|
||
# in any of these directories we need to run `ldconfig'.
|
||
@ldconfig_dirs = ( '/usr/lib', '/lib', '/usr/X11/lib',
|
||
'/usr/X11R6/lib', '/usr/lib/X11' );
|
||
|
||
# --- configuration ---
|
||
# gzip/gunzip programs + arguments that can act as filters.
|
||
$gzip = 'gzip -9 -c -';
|
||
$gunzip = 'gzip -d -c -';
|
||
|
||
# flags which cause `tar' to extract a file to stdout from an archive.
|
||
# usage: tar $untar_to_stdout ARCHIVE FILE
|
||
# or zcat ARCHIVE | tar $untar_to_stdout - FILE
|
||
# (perhaps this isn't portable?)
|
||
$untar_to_stdout = "xOf";
|
||
|
||
# Location of the optional system-wide rpmrc file.
|
||
$system_rpmrc_file = "/var/lib/rpm/rpmrc";
|
||
|
||
# Standard build architectures. Yes, hardcoding this sucks...
|
||
$default_redhat_archs = "i386 alpha sparc sparc64";
|
||
|
||
# --- configure-time substitutions (!) ---
|
||
# The RHS of these assignments is a autoconf-substitution;
|
||
# in pkgwrite.in there are autoconf AC_SUBST()d variables,
|
||
# and in pkgwrite these is a 0 or 1 to indicate
|
||
# whether `configure' detected a suitable version of these
|
||
# packaging systems.
|
||
$has_rpm_support = 1;
|
||
$has_dpkg_support = 1;
|
||
|
||
# --- build hash tables of fixed strings ---
|
||
sub initialize_packaging_tables ()
|
||
{
|
||
# --- Debian packaging values ---
|
||
# valid values for the Priority: field.
|
||
for (qw(required important standard optional extra))
|
||
{
|
||
$DPKG_PRIORITY_LEVELS{$_} = $_;
|
||
}
|
||
|
||
# valid values for the Section: field.
|
||
for (qw(admin base comm contrib devel doc editors electronics
|
||
games graphics hamradio interpreters libs mail
|
||
math misc net news non-free oldlibs otherosfs
|
||
shells sound tex text utils web x11))
|
||
{
|
||
$DPKG_SECTIONS{$_} = $_;
|
||
}
|
||
|
||
# --- Redhat packaging values ---
|
||
for ('Amusements/Games', 'Amusements/Graphics', 'Applications/Archiving',
|
||
'Applications/Communications', 'Applications/Databases',
|
||
'Applications/Editors', 'Applications/Emulators',
|
||
'Applications/Engineering', 'Applications/File',
|
||
'Applications/Internet', 'Applications/Multimedia',
|
||
'Applications/Productivity', 'Applications/Publishing',
|
||
'Applications/System', 'Applications/Text',
|
||
'Development/Debuggers', 'Development/Languages',
|
||
'Development/Libraries', 'Development/System',
|
||
'Development/Tools', 'Documentation',
|
||
'System Environment/Base', 'System Environment/Daemons',
|
||
'System Environment/Kernel', 'System Environment/Libraries',
|
||
'System Environment/Shells', 'User Interface/Desktops',
|
||
'User Interface/X', 'User Interface/X Hardware Support')
|
||
{
|
||
$RPM_GROUPS{lc($_)} = $_;
|
||
}
|
||
}
|
||
|
||
# initialize_signal_tables: Build a lookup-table mapping from
|
||
# signal number to the signal's name (the part which follows
|
||
# SIG in the C macro: for example: HUP, KILL, TERM, SEGV, ABRT, etc)
|
||
# based on perl's configuration.
|
||
sub initialize_signal_tables ()
|
||
{
|
||
defined $Config{sig_name} || die "No sigs?";
|
||
my $i = 0;
|
||
foreach $name (split(' ', $Config{sig_name}))
|
||
{
|
||
#$signo{$name} = $i;
|
||
$signame[$i] = $name;
|
||
$i++;
|
||
}
|
||
}
|
||
|
||
|
||
#=====================================================================
|
||
# Section 1: Helper functions.
|
||
#=====================================================================
|
||
# --- run: run a program or die ---
|
||
sub run($)
|
||
{
|
||
my $command = $_[0];
|
||
print STDERR "<PKGWRITE> running: $command\n";
|
||
my $rv = system ($command);
|
||
if ($rv != 0)
|
||
{
|
||
if ($rv < 256)
|
||
{
|
||
die "command `$command' killed by signal " . $signame[$rv];
|
||
}
|
||
else
|
||
{
|
||
die "command `$command' exited with status " . ($rv >> 8);
|
||
}
|
||
}
|
||
}
|
||
|
||
# safe_mkdir: make a directory if it doesn't exist,
|
||
# or die with an error message.
|
||
sub safe_mkdir($)
|
||
{
|
||
croak "safe_mkdir(undef) called" unless defined $_[0];
|
||
mkdir ($_[0], 0755) or croak "mkdir($_[0]) failed";
|
||
}
|
||
|
||
# check_field(OBJECT, HASH-ENTRY, FIELD)
|
||
#
|
||
# Verify that OBJECT->{HASH-ENTRY} is value, or die with a diagnostic.
|
||
sub check_field($$$)
|
||
{
|
||
if (!defined($_[0]->{$_[1]}))
|
||
{
|
||
my $type = $_[0]->{type};
|
||
my $name = $_[0]->{name};
|
||
$name = "(unknown name)" unless defined $name;
|
||
$type = "packaging" unless defined $type;
|
||
die "field `$_[2]' required in $type for $name";
|
||
}
|
||
}
|
||
|
||
# undef_or_empty: Test if an array reference is undefined or of zero length.
|
||
sub undef_or_empty ($)
|
||
{
|
||
return 1 unless defined $_[0];
|
||
my $array = $_[0];
|
||
return 1 unless scalar (@$array);
|
||
return 0;
|
||
}
|
||
|
||
# write_entries: Write out optional field entries from a hash-table $object.
|
||
# $field_list and $hash_entries are parallel lists.
|
||
sub write_entries ($$$$)
|
||
{
|
||
my ($fh, $object, $field_list, $hash_entries) = @_;
|
||
my $count = scalar(@$field_list);
|
||
my $i;
|
||
for ($i = 0; $i < $count; $i++)
|
||
{
|
||
my $hentry = $hash_entries->[$i];
|
||
my $fname = $field_list->[$i];
|
||
if (defined($object->{$hentry}))
|
||
{
|
||
print $fh $fname, ": ", $object->{$hentry}, "\n";
|
||
}
|
||
}
|
||
}
|
||
|
||
# make a full path from a possibly relative path specified
|
||
# on the command line.
|
||
sub make_absolute($)
|
||
{
|
||
if ($_[0] !~ m,^/,)
|
||
{
|
||
return "$initial_dir/" . $_[0];
|
||
}
|
||
else
|
||
{
|
||
return $_[0];
|
||
}
|
||
}
|
||
|
||
# write_list_to_file(\@LIST, $FNAME)
|
||
# Write each element of LIST on a separate line to a file named $FNAME.
|
||
sub write_list_to_file ($$)
|
||
{
|
||
my ($list, $fname) = @_;
|
||
open Z, ">$fname" or die "write_list_to_file: couldn't create $fname";
|
||
for (@$list)
|
||
{
|
||
print Z "$_\n";
|
||
}
|
||
close Z;
|
||
}
|
||
|
||
# make_file: Create a file with the contents of a given string.
|
||
sub make_file ($$)
|
||
{
|
||
my ($fname, $contents) = @_;
|
||
open Z, ">$fname" or die "make_file: couldn't create $fname";
|
||
print Z $contents;
|
||
close Z;
|
||
}
|
||
|
||
# maybe_print_make_dirs(\%MADE_DIRECTORIES, $FILE_HANDLE, $DIRECTORY):
|
||
#
|
||
# Print a script to make the directories and subdirectories
|
||
# of $dir, recording made directories in MADE_DIRECTORIES
|
||
# to avoid duplication.
|
||
sub maybe_print_make_dirs ($$$)
|
||
{
|
||
my ($made_dirs, $fh, $dir) = @_;
|
||
my $cur = '';
|
||
$cur = ($dir =~ s,^/+,,) ? '' : '.';
|
||
for (split /\//, $dir)
|
||
{
|
||
$cur .= "/$_";
|
||
if (!defined($made_dirs->{$cur}))
|
||
{
|
||
print $fh "\ttest -d $cur || mkdir $cur\n";
|
||
$made_dirs->{$cur} = 1;
|
||
}
|
||
}
|
||
}
|
||
|
||
# string_to_boolean: take the normal yes/no/false/true/0/1 mess
|
||
# and output 0, 1, or undef.
|
||
sub string_to_boolean ($)
|
||
{
|
||
my $s = $_[0];
|
||
return 0 if ($s eq '0'
|
||
|| $s eq 'f' || $s eq 'F'
|
||
|| $s eq 'n' || $s eq 'N'
|
||
|| $s eq 'no' || $s eq 'NO'
|
||
|| $s eq 'false' || $s eq 'FALSE');
|
||
return 1 if ($s eq '1'
|
||
|| $s eq 't' || $s eq 'T'
|
||
|| $s eq 'y' || $s eq 'Y'
|
||
|| $s eq 'yes' || $s eq 'YES'
|
||
|| $s eq 'true' || $s eq 'TRUE');
|
||
return undef;
|
||
}
|
||
|
||
# Read lines from FILE-HEADER until a non-empty line is encountered.
|
||
# Return undef if no nonempty line is encountered.
|
||
sub skip_empty_lines ($)
|
||
{
|
||
my $fh = $_[0];
|
||
while (<$fh>)
|
||
{
|
||
chomp;
|
||
return $_ if /\S/;
|
||
}
|
||
return undef;
|
||
}
|
||
|
||
# From the basename of a manpage (eg zshall.1.gz or exit.3tcl)
|
||
# find the man-section (resp. 1 or 3).
|
||
sub get_manpage_section ($)
|
||
{
|
||
my $b = $_[0];
|
||
$b =~ s/\.gz$//;
|
||
$b =~ s/\.bz2$//;
|
||
$b =~ s/\.Z$//;
|
||
$b =~ s/^.*\.//;
|
||
if ($b =~ /^(\d+)/)
|
||
{
|
||
return $b;
|
||
}
|
||
die "Couldn't figure out what man-section $_[0] was in";
|
||
}
|
||
|
||
|
||
|
||
# --- Creating and destroying the temporary working area ---
|
||
sub get_tmp_dir() {
|
||
return $ENV{'TMPDIR'} || $ENV{'TEMPDIR'} || "/tmp";
|
||
}
|
||
sub get_work_dir ()
|
||
{
|
||
# Make a working directory.
|
||
if (!defined($global_work_dir))
|
||
{
|
||
$global_work_dir = get_tmp_dir() . "/mkpkg-$$-$ENV{USER}";
|
||
mkdir ($global_work_dir, 0755)
|
||
or die "couldn't make tmp directory $global_work_dir";
|
||
}
|
||
$SIG{__DIE__} = sub { remove_work_dir (); };
|
||
return $global_work_dir;
|
||
}
|
||
|
||
sub remove_work_dir ()
|
||
{
|
||
if (defined($global_work_dir))
|
||
{
|
||
$SIG{__DIE__} = sub {};
|
||
run ("rm -rf $global_work_dir") if $do_cleanup;
|
||
undef($global_work_dir);
|
||
}
|
||
}
|
||
|
||
|
||
|
||
#=====================================================================
|
||
# Section 2: parse_pkgwriteinfo_file: Return package information.
|
||
#=====================================================================
|
||
# DATA STRUCTURES
|
||
#
|
||
# The return value is a Package, a hash-table with the following fields:
|
||
# name => NAME-OF-PACKAGE
|
||
# output_name => NAME-OF-PACKAGE [sometimes mangled for co-installation]
|
||
# section => DEBIAN-SECTION
|
||
# group => REDHAT-GROUP
|
||
# priority => DEBIAN-PRIORITY
|
||
# author => AUTHOR
|
||
# builds => \@BUILDS
|
||
# targets => \@TARGETS
|
||
# url => URL
|
||
# summary => SUMMARY
|
||
# license => LICENSE
|
||
# version => UPSTREAM-VERSION
|
||
# release => RELEASE
|
||
# packager => PACKAGER_FULLNAME
|
||
# packager_email = PACKAGER_EMAIL
|
||
# changelog => CHANGELOG-FILE
|
||
# fullname => PACKAGE-VERSION-RELEASE
|
||
# upstream_fullname => PACKAGE-VERSION
|
||
# needs_noarch => [01]
|
||
# needs_arch_specific => [01]
|
||
# upstream_is_packager => [01] Whether the packager and author are the same.
|
||
# (The default is 1, meaning TRUE)
|
||
# changelog_file => changelog.gz or changelog.Debian.gz
|
||
#
|
||
# Each Target's information is a hash-table with the following fields:
|
||
# name => TARGET-NAME [without PACKAGE-, or {MAIN}]
|
||
# summary => SUMMARY
|
||
# description => DESCRIPTION
|
||
# section => DEBIAN-SECTION [default to package's section]
|
||
# group => REDHAT-GROUP [default to package's group]
|
||
# files => \@PATTERNS
|
||
# conffiles => \@PATTERNS
|
||
# manpages => \@MANPAGES [just the basename -- no path]
|
||
# arch_indep => [01] [whether this target contains compiled code]
|
||
# build_name => BUILD-NAME
|
||
# build => BUILD
|
||
# installed_docs => \@DOCS [docs the package installs]
|
||
# source_docs => \@DOCS [docs from the package tarball]
|
||
#
|
||
# Each Build's information is a hash-table with the following fields:
|
||
# name => BUILD-NAME [or {MAIN} for the default]
|
||
# configure_flags => FLAGS [addl flags to pass to the configure script]
|
||
# configure_envars => ENVARS [NAME=VALUE pairs...]
|
||
# make_flags => FLAGS [addl flags to pass to make & make install]
|
||
# build_flags => FLAGS [addl flags to pass to make]
|
||
# install_flags => FLAGS [addl flags to pass to make install]
|
||
# extra_build_targets => \@T [addl `make' targets to build]
|
||
# extra_install_targets => \@T [addl `make' targets to install]
|
||
|
||
sub parse_pkgwriteinfo_file($)
|
||
{
|
||
open PINFO, "$_[0]" or die "couldn't open $_[0]";
|
||
my $package;
|
||
my @lines = ();
|
||
|
||
# ---
|
||
# Break the line up into sections starting with Target, Package, Build,
|
||
# and call the appropriate parser on those blocks,
|
||
# which are functions named handle_{target,package,build}.
|
||
#
|
||
# Each of those functions takes a list of lines as arguments
|
||
# and returns a hash-table reference of the appropriate {'type'}.
|
||
|
||
# Parse the first block, which is always Package:.
|
||
while (<PINFO>)
|
||
{
|
||
# Ignore whitespace.
|
||
next if /^\s*$/;
|
||
|
||
# Ignore comments.
|
||
next if /^\s*#/;
|
||
|
||
chomp;
|
||
$line = $_;
|
||
if ($line =~ /^Target:/ || $line =~ /^Build:/)
|
||
{
|
||
# Clear out the current lines as the main package entry.
|
||
$package = handle_package (@lines);
|
||
die unless defined $package;
|
||
|
||
# This line begins the new block.
|
||
@lines = ( $line );
|
||
last;
|
||
}
|
||
else
|
||
{
|
||
push @lines, $_;
|
||
}
|
||
}
|
||
while (<PINFO>)
|
||
{
|
||
# Ignore whitespace.
|
||
next if /^\s*$/;
|
||
|
||
# Ignore comments.
|
||
next if /^\s*#/;
|
||
|
||
chomp;
|
||
$line = $_;
|
||
|
||
# Are we are a block boundary?
|
||
if (($line =~ /^Target:/) || ($line =~ /^Build:/))
|
||
{
|
||
# What type of block did we just complete?
|
||
if ($lines[0] =~ /^Target:/)
|
||
{
|
||
# Parse the Target.
|
||
my $target = handle_target (@lines);
|
||
die unless defined $target;
|
||
$target->{package} = $package;
|
||
my $targets = $package->{targets};
|
||
push @$targets, $target;
|
||
}
|
||
else
|
||
{
|
||
# Parse the Build.
|
||
my $build = handle_build (@lines);
|
||
die unless defined $build;
|
||
$build->{package} = $package;
|
||
my $builds = $package->{builds};
|
||
push @$builds, $build;
|
||
|
||
# Compute the name of the src rpm, which will also
|
||
# be the prefix for all the other packages.
|
||
my $name = $package->{output_name};
|
||
$name .= ("-" . $build->{name}) if ($build->{name} ne '{MAIN}');
|
||
$build->{package_name} = $name;
|
||
$build->{package} = $package;
|
||
}
|
||
@lines = ( $line );
|
||
next;
|
||
}
|
||
else
|
||
{
|
||
push @lines, $line;
|
||
}
|
||
}
|
||
if (scalar (@lines) > 0)
|
||
{
|
||
die "The last block in a pkgwriteinfo file must always be a target"
|
||
unless ($lines[0] =~ /^Target:/i);
|
||
|
||
$target = handle_target (@lines);
|
||
die unless defined $target;
|
||
my $targets = $package->{targets};
|
||
push @$targets, $target;
|
||
}
|
||
{
|
||
my $targets = $package->{targets};
|
||
for my $target (@$targets)
|
||
{
|
||
for my $inherited (qw(group))
|
||
{
|
||
if (!defined($target->{$inherited}))
|
||
{
|
||
$target->{$inherited} = $package->{$inherited}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
my %BUILDS_BY_NAME = ();
|
||
{
|
||
my $targets = $package->{targets};
|
||
my $builds = $package->{builds};
|
||
die "malformed package: no targets" if scalar (@$targets) == 0;
|
||
die "malformed package: no builds" if scalar (@$builds) == 0;
|
||
for (@$builds)
|
||
{
|
||
$BUILDS_BY_NAME{$_->{name}} = $_;
|
||
}
|
||
my $needs_noarch = 0;
|
||
my $needs_arch_specific = 0;
|
||
for my $target (@$targets)
|
||
{
|
||
my $build = $target->{build_name};
|
||
my $name = $target->{name};
|
||
$build = '{MAIN}' unless defined $build;
|
||
if (!defined $BUILDS_BY_NAME{$build})
|
||
{
|
||
die "no build for target $name named $build";
|
||
}
|
||
if ($target->{arch_indep})
|
||
{
|
||
$needs_noarch = 1;
|
||
}
|
||
else
|
||
{
|
||
$needs_arch_specific = 1;
|
||
}
|
||
$target->{build} = $BUILDS_BY_NAME{$build};
|
||
$target->{package_name} = $package->{output_name};
|
||
$target->{package} = $package;
|
||
if ($name ne '{MAIN}')
|
||
{
|
||
$target->{package_name} .= "-$name";
|
||
}
|
||
}
|
||
$package->{needs_noarch} = $needs_noarch;
|
||
$package->{needs_arch_specific} = $needs_arch_specific;
|
||
}
|
||
|
||
# Add conflicts based purely on `Files:' lists.
|
||
add_automatic_conflicts ($package);
|
||
return $package;
|
||
}
|
||
|
||
# --- handle_package: create a $package from a pkgwrite intro ---
|
||
sub handle_package
|
||
{
|
||
my $package = {};
|
||
$package->{type} = 'package';
|
||
$package->{targets} = [];
|
||
$package->{builds} = [];
|
||
$package->{upstream_is_packager} = 1;
|
||
my $in_description = 0;
|
||
for (@_)
|
||
{
|
||
if ($in_description)
|
||
{
|
||
if (/^\S/)
|
||
{
|
||
$in_description = 0;
|
||
}
|
||
else
|
||
{
|
||
s/^\s//;
|
||
$package->{description} .= "\n$_";
|
||
next;
|
||
}
|
||
}
|
||
|
||
if (/^Package:\s*(.*)/)
|
||
{
|
||
$package->{name} = $1;
|
||
}
|
||
elsif (/^Output-Package:\s*(.*)/)
|
||
{
|
||
$package->{output_name} = $1;
|
||
}
|
||
elsif (/^Section:\s*(.*)/)
|
||
{
|
||
$package->{section} = $1;
|
||
}
|
||
elsif (/^Group:\s*(.*)/)
|
||
{
|
||
$package->{group} = $1;
|
||
}
|
||
elsif (/^Priority:\s*(.*)/)
|
||
{
|
||
$package->{priority} = $1;
|
||
}
|
||
elsif (/^Home-Page:\s*(.*)/)
|
||
{
|
||
$package->{home_page} = $1;
|
||
}
|
||
elsif (/^Source-Url:\s*(.*)/)
|
||
{
|
||
$package->{url} = $1;
|
||
}
|
||
elsif (/^Version:\s*(.*)/)
|
||
{
|
||
$package->{version} = $1;
|
||
}
|
||
elsif (/^Release:\s*(.*)/)
|
||
{
|
||
$package->{release} = $1;
|
||
}
|
||
elsif (/^Change[lL]og:\s*(.*)/)
|
||
{
|
||
$package->{changelog} = $1;
|
||
}
|
||
elsif (/^Author:\s*(.*)/)
|
||
{
|
||
my $author = $1;
|
||
$package->{authors} = [] unless defined $package->{authors};
|
||
my $authors = $package->{authors};
|
||
push @$authors, $author;
|
||
}
|
||
elsif (/^Description:\s*(.*)/)
|
||
{
|
||
$package->{description} = $1;
|
||
$in_description = 1;
|
||
}
|
||
elsif (/^Synopsis:\s*(.*)/)
|
||
{
|
||
$package->{summary} = $1;
|
||
}
|
||
elsif (/^License:\s*(.*)/)
|
||
{
|
||
$package->{license} = $1;
|
||
}
|
||
elsif (/^Packager:\s*(.*)/)
|
||
{
|
||
$package->{packager} = $1;
|
||
}
|
||
elsif (/^Packager-Email:\s*(.*)/)
|
||
{
|
||
$package->{packager_email} = $1;
|
||
}
|
||
elsif (/^Upstream-is-Packager:\s*(.*)/i)
|
||
{
|
||
$package->{upstream_is_packager} = $1;
|
||
}
|
||
else
|
||
{
|
||
chomp;
|
||
die "unparsable line in pkgwriteinfo file: $_";
|
||
}
|
||
}
|
||
|
||
$package->{changelog_file}
|
||
= $package->{upstream_is_packager} ? "changelog.gz" : "changelog.Debian.gz";
|
||
|
||
# check that all the needed fields have been found.
|
||
check_field ($package, 'name', 'Package');
|
||
check_field ($package, 'section', 'Section');
|
||
check_field ($package, 'version', 'Version');
|
||
check_field ($package, 'release', 'Release');
|
||
check_field ($package, 'priority', 'Priority');
|
||
check_field ($package, 'authors', 'Author');
|
||
check_field ($package, 'description', 'Description');
|
||
check_field ($package, 'summary', 'Synopsis');
|
||
check_field ($package, 'license', 'License');
|
||
check_field ($package, 'packager', 'Packager');
|
||
check_field ($package, 'packager_email', 'Packager-Email');
|
||
|
||
$package->{output_name} = $package->{name} unless defined $package->{output_name};
|
||
$package->{upstream_fullname} = $package->{name} . '-' . $package->{version};
|
||
$package->{fullname} = $package->{output_name}
|
||
. '-' . $package->{version}
|
||
. '-' . $package->{release};
|
||
$package->{lcname} = lc($package->{name});
|
||
|
||
return $package;
|
||
}
|
||
|
||
# --- handle_target: create a $target from a pkgwriteinfo entry ---
|
||
sub handle_target
|
||
{
|
||
my $target = {};
|
||
my $in_description = 0;
|
||
$target->{type} = 'target';
|
||
$target->{manpages} = [];
|
||
$target->{source_docs} = [];
|
||
$target->{installed_docs} = [];
|
||
$target->{arch_indep} = 0;
|
||
$target->{debian_data} = {};
|
||
|
||
for (@_)
|
||
{
|
||
if ($in_description)
|
||
{
|
||
if (/^\S/)
|
||
{
|
||
$in_description = 0;
|
||
}
|
||
else
|
||
{
|
||
s/^\s//;
|
||
$target->{description} .= "\n$_";
|
||
next;
|
||
}
|
||
}
|
||
|
||
if (/^Target:\s*(.*)/)
|
||
{
|
||
$target->{name} = $1;
|
||
}
|
||
elsif (/^Depends:\s*(.*)/)
|
||
{
|
||
my $dep = $1;
|
||
if (defined($target->{depends}))
|
||
{
|
||
$target->{depends} = $target->{depends} . ", " . $dep;
|
||
}
|
||
else
|
||
{
|
||
$target->{depends} = $dep;
|
||
}
|
||
}
|
||
elsif (/^Redhat-Requires:\s*(.*)/)
|
||
{
|
||
$target->{redhat_requires} = $1;
|
||
}
|
||
elsif (/^Conflicts:\s*(.*)/)
|
||
{
|
||
my $conflict = $1;
|
||
if (defined($target->{conflicts}))
|
||
{
|
||
$target->{conflicts} = $target->{conflicts} . ", " . $conflict;
|
||
}
|
||
else
|
||
{
|
||
$target->{conflicts} = $conflict;
|
||
}
|
||
}
|
||
elsif (/^Redhat-Conflicts:\s*(.*)/)
|
||
{
|
||
$target->{redhat_conflicts} = $1;
|
||
}
|
||
elsif (/^Synopsis:\s*(.*)/)
|
||
{
|
||
$target->{summary} = $1;
|
||
}
|
||
elsif (/^Files:\s*(.*)/)
|
||
{
|
||
my $pattern = $1;
|
||
$target->{files} = [] unless defined $target->{files};
|
||
my $files = $target->{files};
|
||
push @$files, $pattern;
|
||
}
|
||
elsif (/^Description:\s*(.*)/)
|
||
{
|
||
$target->{description} = $1;
|
||
$in_description = 1;
|
||
}
|
||
elsif (/^Man-Page:\s*(.*)/)
|
||
{
|
||
my $manpage = $1;
|
||
my $manpages = $target->{manpages};
|
||
push @$manpages, $manpage;
|
||
}
|
||
elsif (/^Doc:\s*(.*)/)
|
||
{
|
||
my $doc = $1;
|
||
my $docs = $target->{installed_docs};
|
||
push @$docs, $doc;
|
||
}
|
||
elsif (/^Source-Doc:\s*(.*)/)
|
||
{
|
||
my $doc = $1;
|
||
my $docs = $target->{source_docs};
|
||
push @$docs, $doc;
|
||
}
|
||
elsif (/^Platform-Independent:\s*(.*)/)
|
||
{
|
||
$target->{arch_indep} = string_to_boolean ($1);
|
||
}
|
||
elsif (/^Needs-[lD]dconfig:\s*(.*)/)
|
||
{
|
||
$target->{needs_ldconfig} = string_to_boolean ($1);
|
||
}
|
||
elsif (/^Which-Build:\s*(.*)/)
|
||
{
|
||
$target->{build_name} = $1;
|
||
}
|
||
else
|
||
{
|
||
chomp;
|
||
die "unparsable line in pkgwriteinfo file: $_";
|
||
}
|
||
}
|
||
|
||
# check that all the needed fields have been found.
|
||
check_field ($target, 'name', 'Target');
|
||
|
||
if (undef_or_empty ($target->{installed_docs})
|
||
&& undef_or_empty ($target->{source_docs})
|
||
&& undef_or_empty ($target->{files}))
|
||
{
|
||
die "either Files, Doc, Source-Doc are required but missing in target "
|
||
. $target->{name};
|
||
}
|
||
|
||
if ($target->{name} ne '{MAIN}')
|
||
{
|
||
check_field ($target, 'description', 'Description');
|
||
check_field ($target, 'summary', 'Synopsis');
|
||
}
|
||
|
||
# --- Figure out other information about this target. ---
|
||
|
||
# Figure out if ldconfig must be run after this package
|
||
# is installed.
|
||
if (!defined ($target->{needs_ldconfig}))
|
||
{
|
||
my $needs_ldconfig = 0;
|
||
my $files = $target->{files};
|
||
$files = [] unless defined $files;
|
||
PER_FILE: for my $file (@$files)
|
||
{
|
||
my $tmp = $file;
|
||
$tmp =~ s,/[^/]+$,,;
|
||
for my $dir (@ldconfig_dirs)
|
||
{
|
||
if ($tmp eq $dir)
|
||
{
|
||
$needs_ldconfig = 1;
|
||
last PER_FILE;
|
||
}
|
||
}
|
||
}
|
||
$target->{needs_ldconfig} = $needs_ldconfig;
|
||
}
|
||
|
||
return $target;
|
||
}
|
||
|
||
# --- handle_build: create a $build from a pkgwriteinfo entry ---
|
||
sub handle_build
|
||
{
|
||
my $build = {};
|
||
$build->{type} = 'build';
|
||
$build->{extra_build_targets} = [];
|
||
$build->{extra_install_targets} = [];
|
||
for (@_)
|
||
{
|
||
if (/^Build:\s*(.*)/)
|
||
{
|
||
$build->{name} = $1;
|
||
}
|
||
elsif (/^Configure-Flags:\s*(.*)/)
|
||
{
|
||
$build->{configure_flags} = $1;
|
||
}
|
||
elsif (/^Configure-Envars:\s*(.*)/)
|
||
{
|
||
$build->{configure_envars} = $1;
|
||
}
|
||
elsif (/^Make-Flags:\s*(.*)/)
|
||
{
|
||
$build->{make_flags} = $1;
|
||
}
|
||
elsif (/^Install-Flags:\s*(.*)/)
|
||
{
|
||
$build->{install_flags} = $1;
|
||
}
|
||
elsif (/^Build-Flags:\s*(.*)/)
|
||
{
|
||
$build->{build_flags} = $1;
|
||
}
|
||
elsif (/^Extra-Build-Targets:\s*(.*)/)
|
||
{
|
||
my $list = $build->{extra_build_targets};
|
||
push @$list, $1;
|
||
}
|
||
elsif (/^Extra-Install-Targets:\s*(.*)/)
|
||
{
|
||
my $list = $build->{extra_install_targets};
|
||
push @$list, $1;
|
||
}
|
||
else
|
||
{
|
||
die "unrecognized line under Build ($_)";
|
||
}
|
||
}
|
||
return $build;
|
||
}
|
||
|
||
# --- add_automatic_conflicts ---
|
||
# Add packages to eachothers conflict lists whenever they
|
||
# have Files: entries that are the same and don't have wildcards.
|
||
sub add_automatic_conflicts ($)
|
||
{
|
||
my ($package) = @_;
|
||
my $targets = $package->{targets};
|
||
|
||
# a table: defined ($conflict_table->{A}->{B}) => A and B conflict.
|
||
my $conflict_table = {};
|
||
|
||
# a table mapping a filename that doesn't contain
|
||
# wildcards, to a list of packages containing that file.
|
||
my $by_installed_file = {};
|
||
|
||
# Traverse all the targets, flush one $conflict_table.
|
||
#
|
||
# Basically, if any two packages are in the same list,
|
||
# they must have a conflict.
|
||
for my $target (@$targets)
|
||
{
|
||
my $files = $target->{files};
|
||
for my $file (@$files)
|
||
{
|
||
next if $file =~ /[\*\?\[\]]/;
|
||
my $list = $by_installed_file->{$file};
|
||
$list = $by_installed_file->{$file} = [] unless defined ($list);
|
||
push @$list, $target;
|
||
}
|
||
}
|
||
|
||
my %name_to_target = ();
|
||
|
||
# Build a table of all the conflicted package pairs,
|
||
# by scanning the above lists.
|
||
for my $conflicted_targets (values %$by_installed_file)
|
||
{
|
||
my $count = scalar (@$conflicted_targets);
|
||
next if ($count < 2);
|
||
for (my $i = 0; $i < $count; $i++)
|
||
{
|
||
my $t1 = $conflicted_targets->[$i];
|
||
my $p1 = $t1->{package_name};
|
||
$name_to_target{$p1} = $t1;
|
||
for (my $j = 0; $j < $count; $j++)
|
||
{
|
||
next if $i == $j;
|
||
my $t2 = $conflicted_targets->[$j];
|
||
my $p2 = $t2->{package_name};
|
||
my $t = $conflict_table->{$p1};
|
||
$t = $conflict_table->{$p1} = {} unless defined $t;
|
||
$t->{$p2} = 1;
|
||
}
|
||
}
|
||
}
|
||
|
||
# Add those conflicts, unless they have been explicitly mentioned already.
|
||
my $file_a;
|
||
my $hash_b;
|
||
while (($name_a, $hash_b) = each %$conflict_table)
|
||
{
|
||
my $target_a = $name_to_target{$name_a};
|
||
my $cstring = $target_a->{conflicts};
|
||
|
||
# Compute the initial list of conflicted packages.
|
||
my @conflicts;
|
||
if (!defined ($cstring) || $cstring eq '')
|
||
{
|
||
@conflicts = ();
|
||
}
|
||
else
|
||
{
|
||
@conflicts = map { s/^\s+//; s/\s+$//; $_ } (split /,/, $cstring);
|
||
}
|
||
|
||
for my $name_b (keys %$hash_b)
|
||
{
|
||
my $preconflicted = 0;
|
||
my $target_b = $name_to_target{$name_a};
|
||
for (@conflicts)
|
||
{
|
||
if (/^$target_b / || ($_ eq $target_b))
|
||
{
|
||
$preconflicted = 1;
|
||
last;
|
||
}
|
||
}
|
||
|
||
# Add a conflict if needed.
|
||
unless ($preconflicted)
|
||
{
|
||
if (!defined ($cstring) || $cstring eq '')
|
||
{
|
||
$target_a->{conflicts} = $name_b;
|
||
}
|
||
else
|
||
{
|
||
$target_a->{conflicts} .= ", $name_b";
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
#=====================================================================
|
||
# Section 3: Verify that a package is correctly formed.
|
||
#=====================================================================
|
||
|
||
# Run a user-specified function (a predicate) on a package and all its targets.
|
||
# (only possible b/c they are both hash-tables).
|
||
#
|
||
# Return the first package for which the predicate returns true,
|
||
# or `undef' if none is found.
|
||
sub search_package_and_targets ($$)
|
||
{
|
||
my ($package, $func) = @_;
|
||
|
||
return $package if &$func ($package);
|
||
|
||
my $targets = $package->{targets};
|
||
for my $target (@$targets)
|
||
{
|
||
return $target if &$func ($target);
|
||
}
|
||
return undef;
|
||
}
|
||
|
||
# check that a package is valid.
|
||
sub sanity_check_package ($)
|
||
{
|
||
my $package = $_[0];
|
||
my $targets = $package->{targets};
|
||
my $builds = $package->{builds};
|
||
|
||
# Verify that $package->{group} and $package->{section} are valid.
|
||
my $invalid = search_package_and_targets
|
||
($package,
|
||
sub {
|
||
my $p = $_[0];
|
||
return 0 unless defined $p->{group};
|
||
return 0 if defined $RPM_GROUPS{lc($p->{group})};
|
||
print STDERR "WARNING: "
|
||
. "The Group: `" . $p->{group}
|
||
. "'is unknown.\n";
|
||
return 1;
|
||
});
|
||
if (defined($invalid))
|
||
{
|
||
die "try pkgwrite --query-list=rpm-groups";
|
||
}
|
||
$invalid = search_package_and_targets
|
||
($package,
|
||
sub {
|
||
my $p = $_[0];
|
||
return 0 unless defined $p->{section};
|
||
my $s = lc ($p->{section});
|
||
return 0 if defined $DPKG_SECTIONS{$s};
|
||
print STDERR "WARNING: "
|
||
. "The Section: `" . $p->{section}
|
||
. "'is unknown.\n";
|
||
return 1;
|
||
});
|
||
if (defined($invalid))
|
||
{
|
||
die "invalid Section: try pkgwrite --query-list=deb-sections for a list";
|
||
}
|
||
|
||
# Verify that documentation doesn't contain wildcards.
|
||
$invalid = search_package_and_targets
|
||
($package,
|
||
sub {
|
||
my $p = $_[0];
|
||
my @docs;
|
||
my $sd = $p->{source_docs};
|
||
my $id = $p->{installed_docs};
|
||
@docs = ( ((defined $id) ? @$id : ()),
|
||
((defined $sd) ? @$sd : ()) );
|
||
for (@docs)
|
||
{
|
||
if (/\*/ || /\?/)
|
||
{
|
||
print STDERR "WARNING: "
|
||
. "documentation specifications "
|
||
. "may not contain wildcards."
|
||
. "($_)\n";
|
||
return 1;
|
||
}
|
||
}
|
||
return 0;
|
||
});
|
||
if (defined($invalid))
|
||
{
|
||
die $invalid->{type} . " " . $invalid->{name}
|
||
. " had an invalid documentation entry";
|
||
}
|
||
|
||
# Verify that the manpages are in valid sections.
|
||
$invalid = search_package_and_targets
|
||
($package,
|
||
sub {
|
||
my $p = $_[0];
|
||
my $m = $p->{manpages};
|
||
my @manpages = (defined ($m) ? @$m : ());
|
||
for (@manpages)
|
||
{
|
||
if (!defined (get_manpage_section ($_)))
|
||
{
|
||
print STDERR "WARNING: "
|
||
. "manpage $_ was not in any "
|
||
. "known man section.\n";
|
||
return 1;
|
||
}
|
||
if (/\//)
|
||
{
|
||
print STDERR "ERROR: "
|
||
. "manpage $_ contained a /; it "
|
||
. "should be a bare filename.\n";
|
||
return 1;
|
||
}
|
||
}
|
||
return 0;
|
||
});
|
||
if (defined ($invalid))
|
||
{
|
||
die "bad manpage section in $invalid->{type} $invalid->{name}"
|
||
}
|
||
|
||
# Verify that none of the packages have the same names.
|
||
my %used_names = ();
|
||
for my $t (@$targets)
|
||
{
|
||
my $pname = $t->{package_name};
|
||
die "two packages are named $pname" if defined $used_names{$pname};
|
||
$used_names{$pname} = 1;
|
||
}
|
||
|
||
# Verify that no two installed documents in a single package
|
||
# have the same name.
|
||
for my $t (@$targets)
|
||
{
|
||
my $pname = $t->{package_name};
|
||
my @installed_docs = ();
|
||
my $d1 = $t->{installed_docs};
|
||
my $d2 = $t->{source_docs};
|
||
for my $d (@$d1, @$d2)
|
||
{
|
||
next unless $d =~ m,([^/]+)/?$,;
|
||
my $base = $1;
|
||
if (defined ($used_names{$base}))
|
||
{
|
||
die "two documents in $pname are named $base";
|
||
}
|
||
$used_names{$base} = 1;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
#=====================================================================
|
||
# Section 4: ChangeLog parsing code.
|
||
#=====================================================================
|
||
# create a new changelog parser.
|
||
# doesn't parse any entries.
|
||
#
|
||
# you may pass in anything that may be open()d for reading: a filename
|
||
# or `program |'.
|
||
sub changelog_parser_new ($)
|
||
{
|
||
my $fh = gensym ();
|
||
open $fh, "$_[0]" or return undef;
|
||
my $cp = {};
|
||
$cp->{fh} = $fh;
|
||
$cp->{filename} = $_[0];
|
||
$cp->{lineno} = 0;
|
||
return $cp;
|
||
}
|
||
|
||
# skip empty lines, but also keep around the line number for error reporting.
|
||
sub _cp_skip_empty_lines ($)
|
||
{
|
||
my $rv = skip_empty_lines ($_[0]->{fh});
|
||
$_[0]->{lineno} = $.;
|
||
return $rv;
|
||
}
|
||
|
||
# parse one entry: store it in the public fields of $changelog_parser:
|
||
# {package} => PACKAGE_NAME
|
||
# {version} => VERSION-RELEASE
|
||
# {change_text} => raw text of the changes
|
||
# {full_name} => NAME-OF-AUTHOR
|
||
# {email} => EMAIL-OF-AUTHOR
|
||
# {date} => ENTRY-DATE (in rfc 822, eg date -R)
|
||
# return whether this fields are valid.
|
||
sub changelog_parser_advance ($)
|
||
{
|
||
my $cp = $_[0];
|
||
my $package_line = _cp_skip_empty_lines ($cp);
|
||
return 0 unless defined $package_line;
|
||
if ($package_line !~ /^([a-zA-Z0-9\-_]+)\s\(([^\(\)]+)\)/)
|
||
{
|
||
die "error parsing changelog package line ($package_line) ("
|
||
. $cp->{filename} . ", line " . $cp->{lineno} . ")";
|
||
}
|
||
$cp->{package} = $1;
|
||
$cp->{version} = $2;
|
||
|
||
my $byline;
|
||
|
||
# grab the text.
|
||
my $text = _cp_skip_empty_lines ($cp);
|
||
my $was_empty = 0;
|
||
|
||
# got a header line, but nothing else: that's an error.
|
||
if (!defined ($text))
|
||
{
|
||
die "no changelog entry was encountered: premature eof at "
|
||
. $cp->{filename} . ", line " . $cp->{lineno};
|
||
}
|
||
$text .= "\n";
|
||
|
||
# Data to be parsed from the packager's byline
|
||
# (which we will do in order to detect the end-of-record anyway).
|
||
my $full_name;
|
||
my $email;
|
||
my $date;
|
||
my $got_byline = 0;
|
||
my $fh = $cp->{fh};
|
||
while (defined ($text))
|
||
{
|
||
my $line = scalar (<$fh>);
|
||
last unless defined $line;
|
||
|
||
if ($line =~ /^\s+-- ([^<>]+) <([^<>]+)>\s+(.*)/)
|
||
{
|
||
$full_name = $1;
|
||
$email = $2;
|
||
$date = $3;
|
||
$got_byline = 1;
|
||
last;
|
||
}
|
||
$text .= $line;
|
||
}
|
||
$text =~ s/\n\n+$/\n/s;
|
||
|
||
# parse the byline.
|
||
if (!$got_byline)
|
||
{
|
||
die "missing byline at " . $cp->{filename} . ", line " . $cp->{lineno};
|
||
}
|
||
$cp->{change_text} = $text;
|
||
$cp->{full_name} = $full_name;
|
||
$cp->{email} = $email;
|
||
$cp->{date} = $date;
|
||
|
||
return 1;
|
||
}
|
||
|
||
# Write a debian-style changelog entry.
|
||
# (on a debian system, look in /usr/share/doc/packaging-manual/manual.text.gz)
|
||
sub changelog_write_debian_style ($$)
|
||
{
|
||
my ($cp, $ofh) = @_;
|
||
my $lcpackage = lc($cp->{package});
|
||
print $ofh $lcpackage,
|
||
" (", $cp->{version}, ") $debian_dist; urgency=low\n\n",
|
||
$cp->{change_text},
|
||
"\n -- ", $cp->{full_name},
|
||
" <", $cp->{email}, "> ", $cp->{date}, "\n\n";
|
||
}
|
||
|
||
# Write a redhat-style changelog entry.
|
||
# (see http://www.rpm.org/RPM-HOWTO/build.html#CHANGELOG)
|
||
sub changelog_write_redhat_style ($$)
|
||
{
|
||
my ($cp, $ofh) = @_;
|
||
|
||
# Convert the date from
|
||
# `date -R' format (complies with rfc 822)
|
||
# to
|
||
# `date +"%a %b %d %Y"' format
|
||
# HMM: it'd be nice to support an rfc 822 date parser here,
|
||
# but i'm hesitant unless such a beast comes with perl.
|
||
if ($cp->{date} !~ /^([A-Z][a-z][a-z]),\s+(\d+) ([A-Z][a-z][a-z]) (\d+)/)
|
||
{
|
||
die "could not parse date " . $cp->{date};
|
||
}
|
||
my ($day_of_week, $day_of_month, $month_shortname, $year) = ($1, $2, $3, $4);
|
||
$day_of_month = "0$day_of_month" unless $day_of_month >= 10;
|
||
my $short_date = "$day_of_week $month_shortname $day_of_month $year";
|
||
|
||
print $ofh "* $short_date ", $cp->{full_name}, " <", $cp->{email}, ">\n\n";
|
||
|
||
# XXX: not part of any packaging standard.
|
||
print $ofh " Version ", $cp->{version}, "\n";
|
||
|
||
for (split /\n/, $cp->{change_text})
|
||
{
|
||
# hmm, convert * to - for readability (?!?)
|
||
s/^ \*/ -/;
|
||
print $ofh $_, "\n";
|
||
}
|
||
print $ofh "\n\n";
|
||
}
|
||
|
||
# destroy the changelog parser.
|
||
sub changelog_parser_destroy ($)
|
||
{
|
||
my $cp = $_[0];
|
||
if (! close ($cp->{fh}))
|
||
{
|
||
die "error running $cp->{filename}: $?";
|
||
}
|
||
}
|
||
|
||
|
||
#=====================================================================
|
||
# Section 5: Redhat tape.
|
||
#=====================================================================
|
||
# if $make_script == 1,
|
||
# return a list of commands (newline-terminated)
|
||
# to concatenate into a script which will copy said
|
||
# docs into /usr/doc/PACKAGE-VERSION-RELEASE.
|
||
#
|
||
# if $make_script == 0, just return a list of files,
|
||
# `${prefix}/doc/PACKAGE-VERSION-RELEASE/FILENAME'.
|
||
sub redhat_make_install_doc_section ($$)
|
||
{
|
||
my ($package, $build) = @_;
|
||
my @cmds = ();
|
||
my $targets = $package->{targets};
|
||
my $docs = $target->{installed_docs};
|
||
my $buildfullname = $build->{package_name} . '-' . $package->{version};
|
||
my $docdir = "%{_topdir}/%{prefix}/doc/" . join ('-',
|
||
$package->{name},
|
||
$package->{version},
|
||
$package->{release});
|
||
|
||
# the union of all source docs for this build.
|
||
my %source_docs = ();
|
||
|
||
# the union of all installed docs for this build.
|
||
my %installed_docs = ();
|
||
|
||
for my $target (@$targets)
|
||
{
|
||
# skip unrelated targets.
|
||
next unless $target->{build} == $build;
|
||
|
||
my $doc_list;
|
||
|
||
$doc_list = $target->{source_docs};
|
||
for (@$doc_list) { $source_docs{$_} = 1 }
|
||
|
||
$doc_list = $target->{installed_docs};
|
||
for (@$doc_list) { $installed_docs{$_} = 1 }
|
||
}
|
||
|
||
# if there is no documentation to install, skip out rather than make
|
||
# an empty doc directory.
|
||
if (scalar(keys(%source_docs)) == 0 && scalar(keys(%installed_docs)) == 0)
|
||
{
|
||
return;
|
||
}
|
||
|
||
# mkdir -p isn't generally portable, but it works under redhat. hmm...
|
||
push @cmds, "test -d $docdir || mkdir -p $docdir\n";
|
||
|
||
for my $doc (sort keys %source_docs)
|
||
{
|
||
# Produce commands to copy $src_doc from the unpacked tarball
|
||
# to the $docdir.
|
||
my $base;
|
||
$doc =~ m,([^/]+/?)$,;
|
||
$base = $1;
|
||
if ($doc =~ m,/$,)
|
||
{
|
||
# directory
|
||
push @cmds, "rm -rf $docdir/$base\n",
|
||
"cp -dpr %{_builddir}/$buildfullname/$doc $docdir/$base\n";
|
||
}
|
||
else
|
||
{
|
||
# file.
|
||
push @cmds, "cp -dpf %{_builddir}/$buildfullname/$doc $docdir/\n";
|
||
}
|
||
}
|
||
|
||
for my $doc (sort keys %installed_docs)
|
||
{
|
||
# Produce commands to copy $installed_doc from the installed
|
||
# area to the $docdir.
|
||
my $base;
|
||
$doc =~ m,([^/]+/?)$,;
|
||
$base = $1;
|
||
|
||
# Try to avoid copying a file over itself.
|
||
my $srcpath = "%{_topdir}/$doc";
|
||
my $dstpath = "$docdir/$base";
|
||
$srcpath =~ s,/+$,,; $srcpath =~ s,//+,/,;
|
||
$dstpath =~ s,/+$,,; $dstpath =~ s,//+,/,;
|
||
next if $srcpath eq $dstpath;
|
||
|
||
# Write the script if needed.
|
||
if ($doc =~ m,/$,)
|
||
{
|
||
# directory
|
||
push @cmds, "rm -rf $docdir/$base\n",
|
||
"cp -r %{_topdir}/$doc $docdir/$base\n";
|
||
}
|
||
else
|
||
{
|
||
# file.
|
||
push @cmds, "cp -dpf %{_topdir}/$doc $docdir/\n";
|
||
}
|
||
}
|
||
return @cmds;
|
||
}
|
||
|
||
# output a list of file including their path.
|
||
sub redhat_make_doc_section_list ($$$)
|
||
{
|
||
my ($fh, $package, $target) = @_;
|
||
my $docs;
|
||
my $docdir = "%{prefix}/doc/" . join ('-',
|
||
$package->{name},
|
||
$package->{version},
|
||
$package->{release});
|
||
$docs = $target->{installed_docs};
|
||
for (@$docs)
|
||
{
|
||
next unless m/([^\/]+\/?)$/;
|
||
push @docs, "$docdir/$1";
|
||
}
|
||
$docs = $target->{source_docs};
|
||
for (@$docs)
|
||
{
|
||
next unless m/([^\/]+\/?)$/;
|
||
push @docs, "$docdir/$1";
|
||
}
|
||
return @docs;
|
||
}
|
||
|
||
# Print either an opening or closing architecture conditional
|
||
# for a certain target.
|
||
sub print_arch_conditional ($$$)
|
||
{
|
||
my ($fh, $target, $is_open) = @_;
|
||
my $arch_indep = $target->{arch_indep};
|
||
if ($is_open)
|
||
{
|
||
print $fh ($arch_indep ? "%ifarch noarch\n" : "%ifnarch noarch\n");
|
||
}
|
||
else
|
||
{
|
||
print $fh "%endif # ", ($arch_indep ? "noarch" : "!noarch"), "\n";
|
||
}
|
||
}
|
||
|
||
sub make_redhat_specfile($$$)
|
||
{
|
||
my ($package, $build, $spec_file_out) = @_;
|
||
|
||
open SPECFILE, ">$spec_file_out" or die "could not create $spec_file_out: $!";
|
||
|
||
my $rh_requires = compute_redhat_requires ($package);
|
||
my $download_url = $package->{url};
|
||
print SPECFILE "\%define prefix /usr\n";
|
||
|
||
# For now, disable this "fascist" option.
|
||
# For some reason, our .tar.gz winds up in the list of
|
||
# files, which kills us.
|
||
print SPECFILE "\%define _unpackaged_files_terminate_build 0\n";
|
||
|
||
write_entries ('SPECFILE', $build,
|
||
[qw(Name)],
|
||
[qw(package_name)]);
|
||
write_entries ('SPECFILE', $package,
|
||
[qw(Version Release Copyright Vendor URL
|
||
Group Summary Provides License)],
|
||
[qw(version release copyright vendor home_page
|
||
group summary output_name license)]);
|
||
|
||
|
||
# Table of all the manpages we will need to gzip.
|
||
my %build_manpages = ();
|
||
|
||
# The primary target, that is, the target whose name is the
|
||
# build's name (the same as the .src.rpm will assume).
|
||
my $primary_target;
|
||
|
||
# Compute the primary target and build_manpages.
|
||
my $targets = $package->{targets};
|
||
for my $target (@$targets)
|
||
{
|
||
next unless $target->{build} == $build;
|
||
|
||
if ($target->{name} eq $build->{name})
|
||
{
|
||
$primary_target = $target;
|
||
}
|
||
my $manpages = $target->{manpages};
|
||
for my $manpage (@$manpages)
|
||
{
|
||
$build_manpages{$manpage} = 1;
|
||
}
|
||
}
|
||
|
||
my $tarball = $package->{name} . '-' . $package->{version} . ".tar.gz";
|
||
print SPECFILE "Source: $tarball\n";
|
||
#print SPECFILE "Packager: $packager_id\n";
|
||
print SPECFILE "Prefix: /usr\n";
|
||
print SPECFILE "BuildRoot: %{_topdir}\n";
|
||
# Print flags that actually pertain to the primary target.
|
||
|
||
my $needs_arch_conditionals = 0;
|
||
if ($package->{needs_noarch})
|
||
{
|
||
if ($package->{needs_arch_specific})
|
||
{
|
||
print SPECFILE "BuildArch: noarch $default_redhat_archs\n";
|
||
$needs_arch_conditionals = 1;
|
||
}
|
||
else
|
||
{
|
||
print SPECFILE "BuildArch: noarch\n";
|
||
}
|
||
}
|
||
if (defined ($primary_target))
|
||
{
|
||
# Print dependencies and conflicts.
|
||
my $rh_requires = compute_redhat_requires ($primary_target);
|
||
print SPECFILE "Requires: $rh_requires\n" if ($rh_requires ne '');
|
||
|
||
my $rh_conflicts = compute_redhat_conflicts ($primary_target);
|
||
print SPECFILE "Conflicts: $rh_conflicts\n" if ($rh_conflicts ne '');
|
||
}
|
||
|
||
print SPECFILE "%description\n",
|
||
make_debian_description ($package->{description}),
|
||
"\n";
|
||
|
||
my $config_flags;
|
||
$config_flags = $redhat_config_flags
|
||
. " " .
|
||
(defined ($build->{configure_flags})
|
||
? $build->{configure_flags}
|
||
: '');
|
||
my $env_settings = (defined ($build->{configure_envars})
|
||
? "$build->{configure_envars} "
|
||
: "");
|
||
my $make_flags = defined ($build->{make_flags})
|
||
? $build->{make_flags} : '';
|
||
my $build_flags = defined ($build->{build_flags})
|
||
? $build->{build_flags} : '';
|
||
my $install_flags = defined ($build->{install_flags})
|
||
? $build->{install_flags} : '';
|
||
$build_flags .= " $make_flags";
|
||
$install_flags .= " $make_flags";
|
||
my $full_main_package = "$package->{name}-$package->{version}";
|
||
my $full_target_package = "$build->{package_name}-$package->{version}";
|
||
print SPECFILE
|
||
"%prep\n",
|
||
"rm -rf \$RPM_BUILD_DIR/$full_main_package\n",
|
||
"rm -rf \$RPM_BUILD_DIR/$full_target_package\n",
|
||
"zcat \$RPM_SOURCE_DIR/$full_main_package.tar.gz | tar -xvf -\n",
|
||
($full_main_package eq $full_target_package
|
||
? ''
|
||
: "mv $full_main_package $full_target_package\n"),
|
||
"%build\n",
|
||
"test -d \$RPM_BUILD_DIR || mkdir -p \$RPM_BUILD_DIR\n",
|
||
"cd \$RPM_BUILD_DIR/$full_target_package\n",
|
||
"$env_settings ./configure $config_flags\n",
|
||
"make PREFIX=/usr $build_flags\n\n";
|
||
|
||
my $make_targets = $build->{extra_build_targets};
|
||
for (@$make_targets)
|
||
{
|
||
print SPECFILE "make $_ PREFIX=/usr $build_flags\n";
|
||
}
|
||
print SPECFILE
|
||
"%install\n",
|
||
"cd \$RPM_BUILD_DIR/$full_target_package\n",
|
||
"make install PREFIX=/usr DESTDIR=%{_topdir} $install_flags\n";
|
||
$make_targets = $build->{extra_install_targets};
|
||
for (@$make_targets)
|
||
{
|
||
print SPECFILE "make $_ PREFIX=/usr DESTDIR=%{_topdir} $install_flags\n";
|
||
}
|
||
|
||
print SPECFILE redhat_make_install_doc_section ($package, $build);
|
||
|
||
for my $manpage (keys %build_manpages)
|
||
{
|
||
my $man_section = get_manpage_section ($manpage);
|
||
print SPECFILE "gzip -9 -f %{_topdir}/usr/man/man$man_section/$manpage\n";
|
||
}
|
||
print SPECFILE "\n";
|
||
|
||
print SPECFILE
|
||
"%clean\n",
|
||
"rm -rf %{_builddir}\n",
|
||
"mkdir %{_builddir}\n", # without the mkdir, this doesn't run twice...
|
||
"\n\n";
|
||
|
||
if (defined ($primary_target))
|
||
{
|
||
if ($needs_arch_conditionals)
|
||
{
|
||
print_arch_conditional ('SPECFILE', $primary_target, 1)
|
||
}
|
||
if ($primary_target->{needs_ldconfig})
|
||
{
|
||
# This is from the gtk+.spec file.
|
||
# Usually they are all pretty godly, but i'll be damned
|
||
# if this works :) Furthermore all documentation alludes
|
||
# to the next method; -p is wholy undocumented as far as i can tell.
|
||
#print SPECFILE "%post -p /sbin/ldconfig\n\n",
|
||
# "%postun -p /sbin/ldconfig\n\n";
|
||
|
||
print SPECFILE "%post\n",
|
||
"/sbin/ldconfig\n\n",
|
||
"%postun\n",
|
||
"/sbin/ldconfig\n\n",
|
||
}
|
||
write_redhat_files_section ('SPECFILE', $primary_target);
|
||
if ($needs_arch_conditionals)
|
||
{
|
||
print_arch_conditional ('SPECFILE', $primary_target, 0)
|
||
}
|
||
}
|
||
|
||
for my $target (@$targets)
|
||
{
|
||
my $rh_requires = compute_redhat_requires ($target);
|
||
my $target_name = $target->{package_name};
|
||
|
||
# Only process $target if it pertains to the current $build.
|
||
next if $target->{build} != $build;
|
||
|
||
# For the main package, the remaining data are
|
||
# handled in the main preamble.
|
||
next if ($target->{name} eq $build->{name});
|
||
|
||
if ($needs_arch_conditionals)
|
||
{
|
||
print_arch_conditional ('SPECFILE', $target, 1);
|
||
}
|
||
|
||
# Various standard fields for the non-major package.
|
||
# XXX: should probably try to avoid needless `-n' flags.
|
||
print SPECFILE "%package -n $target_name\n";
|
||
write_entries ('SPECFILE', $target,
|
||
[qw(Group)],
|
||
[qw(group)]);
|
||
print SPECFILE "Requires: $rh_requires\n" if ($rh_requires ne '');
|
||
|
||
my $rh_conflicts = compute_redhat_conflicts ($target);
|
||
print SPECFILE "Conflicts: $rh_conflicts\n" if ($rh_conflicts ne '');
|
||
|
||
# Summary is required for Redhat packages,
|
||
# so we checked the availability of the Synopsis:
|
||
# field on input, so an undef'd $summary should never occur.
|
||
my $summary = $target->{summary};
|
||
die unless defined $summary;
|
||
print SPECFILE "Summary: $summary\n";
|
||
|
||
if ($target->{needs_ldconfig})
|
||
{
|
||
print SPECFILE "%post -n $target_name -p /sbin/ldconfig\n\n",
|
||
"%postun -n $target_name -p /sbin/ldconfig\n\n";
|
||
}
|
||
|
||
# Likewise, Description is mandatory.
|
||
my $desc = $target->{description};
|
||
if (!defined($desc)) { $desc = $package->{description}; }
|
||
die unless defined $desc;
|
||
print SPECFILE "%description -n $target_name\n$desc\n";
|
||
|
||
# Print the %files section (Based on Files: in the pkgwriteinfo file.)
|
||
write_redhat_files_section ('SPECFILE', $target);
|
||
if ($needs_arch_conditionals)
|
||
{
|
||
print_arch_conditional ('SPECFILE', $target, 0);
|
||
}
|
||
print SPECFILE "\n\n";
|
||
}
|
||
|
||
# If there is a changelog, include it in the specfile.
|
||
# This is annoying b/c there is not necessarily any unpacked
|
||
# source code around...
|
||
my $changelog = $package->{changelog};
|
||
if (defined ($changelog))
|
||
{
|
||
my $cl_file;
|
||
if ($package->{package_source_dir})
|
||
{
|
||
$cl_file = $package->{package_source_dir} . "/$changelog";
|
||
}
|
||
else
|
||
{
|
||
# perform the necessary `tar' command.
|
||
$cl_file = "$gunzip < " . $package->{package_tarball}
|
||
. " | tar $untar_to_stdout - "
|
||
. $package->{upstream_fullname} . "/$changelog |";
|
||
}
|
||
my $cp = changelog_parser_new ($cl_file);
|
||
print SPECFILE "\n%changelog\n";
|
||
my $first_time = 1;
|
||
while (changelog_parser_advance ($cp))
|
||
{
|
||
if ($first_time)
|
||
{
|
||
my $ver = $package->{version} . '-' . $package->{release};
|
||
if ($strict_changelog_checking && $cp->{version} ne $ver)
|
||
{
|
||
die "package version in changelog (" . $cp->{version} . ")"
|
||
. " and in pkgwriteinfo file ($ver) do not match!";
|
||
}
|
||
$first_time = 0;
|
||
}
|
||
changelog_write_redhat_style ($cp, 'SPECFILE');
|
||
}
|
||
changelog_parser_destroy ($cp);
|
||
}
|
||
close SPECFILE;
|
||
}
|
||
|
||
sub write_redhat_files_section($$)
|
||
{
|
||
my ($fh, $target) = @_;
|
||
print $fh '%files -n ', $target->{package_name}, "\n";
|
||
my $files_list = $target->{files};
|
||
if (defined($files_list))
|
||
{
|
||
print $fh '%defattr(-, root, root)', "\n";
|
||
for my $wildcard (@$files_list)
|
||
{
|
||
my $tmp = $wildcard;
|
||
$tmp =~ s,^/usr/,%{prefix}/,;
|
||
print $fh "$tmp\n";
|
||
}
|
||
}
|
||
my $manpages = $target->{manpages};
|
||
for my $manpage (@$manpages)
|
||
{
|
||
my $section = get_manpage_section ($manpage);
|
||
print $fh "%{prefix}/man/man$section/$manpage.gz\n"
|
||
}
|
||
for my $doc (redhat_make_doc_section_list ($fh, $target->{package}, $target))
|
||
{
|
||
print $fh "%doc $doc\n";
|
||
}
|
||
}
|
||
|
||
# --- print canned rpmrc ---
|
||
# This is based on the spec file distributed with rpm 4.0.
|
||
# See comments in the legal section of pkgwrite.
|
||
sub print_canned_rpmrc ($)
|
||
{
|
||
my $fh = $_[0];
|
||
for (qw(i386 i486 i586 i686 athlon))
|
||
{ print $fh "optflags: $_ -O2 -march=$_\n" }
|
||
for (qw(ia64 mipseb mipsel ia64))
|
||
{ print $fh "optflags: $_ -O2\n" }
|
||
print $fh "optflags: alpha -O2 -mieee\n";
|
||
for (qw(ev5 ev56 pca56 ev6 ev67))
|
||
{ print $fh "optflags: alpha$_ -O2 -mieee -mcpu=$_\n" }
|
||
print $fh "optflags: sparc -O2 -m32 -mtune=ultrasparc\n";
|
||
print $fh "optflags: sparcv9 -O2 -m32 -mcpu=ultrasparc\n";
|
||
print $fh "optflags: sparc64 -O2 -m64 -mcpu=ultrasparc\n";
|
||
print $fh "optflags: ppc -O2 -fsigned-char\n";
|
||
for (qw(parisc hppa1.0 hppa1.1 hppa1.2 hppa2.0))
|
||
{ print $fh "optflags: $_ -O2 -mpa-risc-1-0\n" }
|
||
for (qw(armv4b armv4l))
|
||
{ print $fh "optflags: $_ -O2 -fsigned-char -fomit-frame-pointer\n" }
|
||
for (qw(m68k atarist atariste ataritt falcon atariclone milan hades))
|
||
{ print $fh "optflags: $_ -O2 -fomit-frame-pointer\n" }
|
||
for (qw(athlon i686 i586 i486 i386))
|
||
{ print $fh "arch_canon: $_: $_ 1\n" }
|
||
for (qw(alpha alphaev5 alphaev56 alphapca56 alphaev6 alphaev67))
|
||
{ print $fh "arch_canon: $_: $_ 2\n" }
|
||
for (qw(alpha alphaev5 alphaev56 alphapca56 alphaev6 alphaev67))
|
||
{ print $fh "arch_canon: $_: sparc 3\n" }
|
||
for (qw(sparcv9))
|
||
{ print $fh "arch_canon: $_: $_ 3\n" }
|
||
print $fh <<'EOF';
|
||
arch_canon: mipseb: mipseb 4
|
||
arch_canon: ppc: ppc 5
|
||
arch_canon: m68k: m68k 6
|
||
arch_canon: IP: sgi 7
|
||
arch_canon: rs6000: rs6000 8
|
||
arch_canon: ia64: ia64 9
|
||
arch_canon: sparc64:sparc64 10
|
||
arch_canon: sun4u: sparc64 10
|
||
arch_canon: mipsel: mipsel 11
|
||
arch_canon: armv4b: armv4b 12
|
||
arch_canon: armv4l: armv4l 12
|
||
EOF
|
||
for (qw(atarist atariste ataritt falcon atariclone milan hades))
|
||
{ print $fh "arch_canon: $_: m68kmint 13\n" }
|
||
for (qw(s398 i370))
|
||
{ print $fh "arch_canon: $_: $_ 14\n" }
|
||
|
||
print $fh <<'EOF';
|
||
# Canonical OS names and numbers
|
||
|
||
os_canon: Linux: Linux 1
|
||
os_canon: IRIX: Irix 2
|
||
# This is wrong
|
||
os_canon: SunOS5: solaris 3
|
||
os_canon: SunOS4: SunOS 4
|
||
|
||
os_canon: AmigaOS: AmigaOS 5
|
||
os_canon: AIX: AIX 5
|
||
os_canon: HP-UX: hpux10 6
|
||
os_canon: OSF1: osf1 7
|
||
os_canon: osf4.0: osf1 7
|
||
os_canon: osf3.2: osf1 7
|
||
os_canon: FreeBSD: FreeBSD 8
|
||
os_canon: SCO_SV: SCO_SV3.2v5.0.2 9
|
||
os_canon: IRIX64: Irix64 10
|
||
os_canon: NEXTSTEP: NextStep 11
|
||
os_canon: BSD/OS: BSD_OS 12
|
||
os_canon: machten: machten 13
|
||
os_canon: CYGWIN32_NT: cygwin32 14
|
||
os_canon: CYGWIN32_95: cygwin32 15
|
||
os_canon: UNIX_SV: MP_RAS: 16
|
||
os_canon: MiNT: FreeMiNT 17
|
||
os_canon: OS/390: OS/390 18
|
||
os_canon: VM/ESA: VM/ESA 19
|
||
os_canon: Linux/390: OS/390 20
|
||
os_canon: Linux/ESA: VM/ESA 20
|
||
EOF
|
||
|
||
for (qw(s398 i370))
|
||
{ print $fh "arch_canon: $_: $_ 14\n" }
|
||
for (qw(osfmach3_i686 osfmach3_i586 osfmach3_i486 osfmach3_i386
|
||
athlon i686 i586 i486 i386))
|
||
{ print $fh "buildarchtranslate: $_: i386\n" }
|
||
for (qw(ia64))
|
||
{ print $fh "buildarchtranslate: $_: ia64\n" }
|
||
for (qw(osfmach3_ppc powerpc powerppc))
|
||
{ print $fh "buildarchtranslate: $_: ppc\n" }
|
||
for (qw(alphaev5 alphaev56 alphapca56 alphaev6 alphaev67))
|
||
{ print $fh "buildarchtranslate: $_: alpha\n" }
|
||
for (qw(sun4c sun4d sun4m sparcv9))
|
||
{ print $fh "buildarchtranslate: $_: sparc\n" }
|
||
for (qw(sun4u))
|
||
{ print $fh "buildarchtranslate: $_: sparc64\n" }
|
||
for (qw(atarist atariste ataritt falcon atariclone milan hades))
|
||
{ print $fh "buildarchtranslate: $_: m68kmint\n" }
|
||
for ('alphaev67: alphaev6', 'alphaev6: alphapca56', 'alphapca56: alphaev56',
|
||
'alphaev56: alphaev5', 'alphaev5: alpha', 'alpha: axp noarch',
|
||
'ia64: noarch', 'athlon: i686', 'i686: i586', 'i586: i486',
|
||
'i486: i386', 'i386: noarch', 'osfmach3_i686: i686 osfmach3_i586',
|
||
'osfmach3_i586: i586 osfmach3_i486',
|
||
'osfmach3_i486: i486 osfmach3_i386', 'osfmach3_i386: i486',
|
||
'osfmach3_ppc: ppc', 'powerpc: ppc', 'powerppc: ppc',
|
||
'sun4c: sparc', 'sun4d: sparc', 'sun4m: sparc', 'sun4u: sparc64',
|
||
'sparc64: sparcv9', 'sparcv9: sparc', 'sparc: noarch',
|
||
'ppc: rs6000', 'rs6000: noarch', 'mipseb: noarch', 'mipsel: noarch',
|
||
'hppa2.0: hppa1.2', 'hppa1.2: hppa1.1', 'hppa1.1: hppa1.0',
|
||
'hppa1.0: parisc', 'parisc: noarch',
|
||
'armv4b: noarch', 'armv4l: noarch',
|
||
'atarist: m68kmint noarch', 'atariste: m68kmint noarch',
|
||
'ataritt: m68kmint noarch', 'falcon: m68kmint noarch',
|
||
'atariclone: m68kmint noarch', 'milan: m68kmint noarch',
|
||
'hades: m68kmint noarch', 's390: i370', 'i370: noarch',
|
||
'ia64: noarch')
|
||
{ print $fh "arch_compat: $_\n" }
|
||
print $fh <<'EOF';
|
||
os_compat: IRIX64: IRIX
|
||
os_compat: solaris2.7: solaris2.3 solaris2.4 solaris2.5 solaris2.6
|
||
os_compat: solaris2.6: solaris2.3 solaris2.4 solaris2.5
|
||
os_compat: solaris2.5: solaris2.3 solaris2.4
|
||
os_compat: solaris2.4: solaris2.3
|
||
|
||
os_compat: hpux11.00: hpux10.30
|
||
os_compat: hpux10.30: hpux10.20
|
||
os_compat: hpux10.20: hpux10.10
|
||
os_compat: hpux10.10: hpux10.01
|
||
os_compat: hpux10.01: hpux10.00
|
||
os_compat: hpux10.00: hpux9.07
|
||
os_compat: hpux9.07: hpux9.05
|
||
os_compat: hpux9.05: hpux9.04
|
||
|
||
os_compat: osf4.0: osf3.2 osf1
|
||
|
||
os_compat: ncr-sysv4.3: ncr-sysv4.2
|
||
|
||
os_compat: FreeMiNT: mint MiNT TOS
|
||
os_compat: MiNT: FreeMiNT mint TOS
|
||
os_compat: mint: FreeMiNT MiNT TOS
|
||
os_compat: TOS: FreeMiNT MiNT mint
|
||
|
||
buildarch_compat: ia64: noarch
|
||
|
||
buildarch_compat: athlon: i686
|
||
buildarch_compat: i686: i586
|
||
buildarch_compat: i586: i486
|
||
buildarch_compat: i486: i386
|
||
buildarch_compat: i386: noarch
|
||
|
||
buildarch_compat: sun4c: noarch
|
||
buildarch_compat: sun4d: noarch
|
||
buildarch_compat: sun4m: noarch
|
||
buildarch_compat: sun4u: noarch
|
||
buildarch_compat: sparc64: noarch
|
||
buildarch_compat: sparcv9: sparc
|
||
buildarch_compat: sparc: noarch
|
||
|
||
buildarch_compat: alphaev67: alphaev6
|
||
buildarch_compat: alphaev6: alphapca56
|
||
buildarch_compat: alphapca56: alphaev56
|
||
buildarch_compat: alphaev56: alphaev5
|
||
buildarch_compat: alphaev5: alpha
|
||
buildarch_compat: alpha: noarch
|
||
|
||
buildarch_compat: m68k: noarch
|
||
buildarch_compat: ppc: noarch
|
||
buildarch_compat: mipsel: noarch
|
||
buildarch_compat: mipseb: noarch
|
||
buildarch_compat: armv4b: noarch
|
||
buildarch_compat: armv4l: noarch
|
||
buildarch_compat: parisc: noarch
|
||
|
||
buildarch_compat: atarist: m68kmint noarch
|
||
buildarch_compat: atariste: m68kmint noarch
|
||
buildarch_compat: ataritt: m68kmint noarch
|
||
buildarch_compat: falcon: m68kmint noarch
|
||
buildarch_compat: atariclone: m68kmint noarch
|
||
buildarch_compat: milan: m68kmint noarch
|
||
buildarch_compat: hades: m68kmint noarch
|
||
|
||
buildarch_compat: ia64: noarch
|
||
buildarch_compat: s390: noarch
|
||
EOF
|
||
}
|
||
|
||
# --- make redhat dir ---
|
||
#
|
||
# Make a bunch of files needed to produce an rpm
|
||
# and writes them in $dir, which it creates.
|
||
#
|
||
# Return a list of commands that should be run to build
|
||
# the RPM given this directory.
|
||
#
|
||
# Also needs a distribution tarball (made by `make dist')
|
||
# and an architecture.
|
||
sub make_redhat_dir ($$$$)
|
||
{
|
||
my ($package, $dir, $tarball, $arch) = @_;
|
||
|
||
run ("rm -rf $dir");
|
||
safe_mkdir ("$dir");
|
||
safe_mkdir ("$dir/rpm-tmp");
|
||
safe_mkdir ("$dir/rpm-tmp/usr");
|
||
safe_mkdir ("$dir/rpm-tmp/usr/src");
|
||
safe_mkdir ("$dir/rpm-tmp/usr/src/redhat");
|
||
safe_mkdir ("$dir/tmp");
|
||
for (qw(SOURCES SPECS BUILD RPMS SRPMS))
|
||
{
|
||
safe_mkdir ("$dir/rpm-tmp/usr/src/redhat/$_");
|
||
}
|
||
run ("cp $tarball $dir/rpm-tmp/usr/src/redhat/SOURCES");
|
||
chdir ($dir) or die;
|
||
$dir = `pwd`;
|
||
chomp($dir);
|
||
#$REDHAT_DIR = $dir;
|
||
$BUILD_TOP = "$dir/tmp";
|
||
$STAGING_TOP = "$dir/rpm-tmp";
|
||
|
||
my $tmp_rpm_macros_fname = "$dir/rpmmacros.tmp";
|
||
my $tmp_rpm_rc_fname = "$dir/rpmrc.tmp";
|
||
|
||
open T, ">$tmp_rpm_macros_fname"
|
||
or die "couldn't create $tmp_rpm_macros_fname";
|
||
print T <<"EOF";
|
||
%_builddir ${BUILD_TOP}
|
||
%_buildshell /bin/sh
|
||
%_dbpath %{_var}/lib/rpm
|
||
%_defaultdocdir %{_usr}/doc
|
||
%_instchangelog 5
|
||
%_rpmdir ${STAGING_TOP}/usr/src/redhat/RPMS
|
||
%_rpmfilename %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm
|
||
%_signature none
|
||
%_sourcedir ${STAGING_TOP}/usr/src/redhat/SOURCES
|
||
%_specdir ${STAGING_TOP}/usr/src/redhat/SPECS
|
||
%_srpmdir ${STAGING_TOP}/usr/src/redhat/SRPMS
|
||
%_srcrpmdir ${STAGING_TOP}/usr/src/redhat/SRPMS
|
||
%_tmppath /tmp
|
||
%_topdir ${STAGING_TOP}
|
||
EOF
|
||
close T;
|
||
|
||
|
||
open (RPMRC, ">$tmp_rpm_rc_fname") or die "couldn't create $tmp_rpm_rc_fname";
|
||
print RPMRC "macrofiles: ",
|
||
join (':', '/usr/lib/rpm/macros',
|
||
'/usr/lib/rpm/%{_target}/macros',
|
||
'/etc/rpm/macros',
|
||
'/etc/rpm/%{_target}/macros',
|
||
$tmp_rpm_macros_fname);
|
||
print RPMRC "\n\n\n";
|
||
|
||
# If available, include the system-installed version
|
||
# (w/o its `macrofiles:' lines), instead of the "canned" one;
|
||
# (which actually just comes from rpm 4.0, see above);
|
||
# the system version should be /var/lib/rpm/rpmrc.
|
||
if (-r $system_rpmrc_file)
|
||
{
|
||
open SYSRPMRC, $system_rpmrc_file or die "internal error: " .
|
||
"couldn't open $system_rpmrc_file";
|
||
while (<SYSRPMRC>)
|
||
{
|
||
next if /^\s*macrofiles:/i;
|
||
print RPMRC $_;
|
||
}
|
||
close SYSRPMRC;
|
||
}
|
||
else
|
||
{
|
||
print_canned_rpmrc ('RPMRC');
|
||
}
|
||
|
||
close RPMRC;
|
||
|
||
my $builds = $package->{builds};
|
||
my @commands = ();
|
||
my $targets = $package->{targets};
|
||
for my $build (@$builds)
|
||
{
|
||
my $tmp_specfile_name;
|
||
if ($build->{name} eq '{MAIN}')
|
||
{
|
||
$tmp_specfile_name = "$dir/" . $package->{name} . ".spec";
|
||
}
|
||
else
|
||
{
|
||
$tmp_specfile_name = "$dir/" . $package->{name} .
|
||
'-' . $build->{name} . ".spec";
|
||
}
|
||
|
||
make_redhat_specfile($package, $build, $tmp_specfile_name);
|
||
push @commands, "test -d $BUILD_TOP || mkdir $BUILD_TOP";
|
||
|
||
my $cmd = join (' ',
|
||
"rpmbuild",
|
||
"--target=$arch",
|
||
"--rcfile $tmp_rpm_rc_fname",
|
||
"-ba",
|
||
"$tmp_specfile_name");
|
||
push @commands, $cmd;
|
||
}
|
||
return @commands;
|
||
}
|
||
|
||
|
||
# --- Automatic Depedency Conversions (Guessing involved) ---
|
||
sub deb_to_rh_package_spec_list ($)
|
||
{
|
||
my $depends = $_[0];
|
||
my @pieces = ();
|
||
for (split /,/, $depends)
|
||
{
|
||
s/^\s+//;
|
||
s/\s+$//;
|
||
s/[()]//g;
|
||
push @pieces, $_;
|
||
}
|
||
return join(', ', @pieces);
|
||
}
|
||
|
||
#
|
||
# convert debian-style dependencies to redhat-style.
|
||
# (redhat calls the line `Requires')
|
||
# debian/pkgwrite format:
|
||
#Depends: libglade0, libglib1.2 (>= 1.2.0), libgtk1.2 (>= 1.2.5), libxml1, libz1, xlib6g, mpg123
|
||
#Requires: libglade >= 0.11, gtk+ >= 1.2, libxml >= 1.7, mpg123
|
||
# As you can see, this cannot necessarily be automated,
|
||
# so we support Redhat-Requires: for override purposes.
|
||
sub compute_redhat_requires($)
|
||
{
|
||
my $object = $_[0];
|
||
if (defined($object->{redhat_requires}))
|
||
{
|
||
return $object->{redhat_requires};
|
||
}
|
||
elsif (defined($object->{depends}))
|
||
{
|
||
return deb_to_rh_package_spec_list ($object->{depends});
|
||
}
|
||
else
|
||
{
|
||
return '';
|
||
}
|
||
}
|
||
sub compute_redhat_conflicts($)
|
||
{
|
||
my $object = $_[0];
|
||
if (defined($object->{redhat_conflicts}))
|
||
{
|
||
return $object->{redhat_conflicts};
|
||
}
|
||
elsif (defined($object->{conflicts}))
|
||
{
|
||
return deb_to_rh_package_spec_list ($object->{conflicts});
|
||
}
|
||
else
|
||
{
|
||
return '';
|
||
}
|
||
}
|
||
|
||
|
||
#=====================================================================
|
||
# Section 6: Debian tape.
|
||
#=====================================================================
|
||
sub copy_docs ($$$$)
|
||
{
|
||
my ($fh, $basedir, $list, $targetdocdir) = @_;
|
||
my @rv = ();
|
||
|
||
for my $doc (@$list)
|
||
{
|
||
my $docbase;
|
||
next unless $doc =~ m,([^/]+)/?$,;
|
||
$docbase = $1;
|
||
if ($doc =~ /\/$/)
|
||
{
|
||
print $fh "\t( cd $basedir/$doc/.. && \\\n",
|
||
"\t tar cf - $docbase | \\\n",
|
||
"\t $gzip || exit 1 \\\n",
|
||
"\t) > $targetdocdir/$docbase.tar.gz\n";
|
||
push @rv, $docbase;
|
||
}
|
||
elsif ($doc =~ /\.gz$/)
|
||
{
|
||
print $fh "\tcp -dp $basedir/$doc $targetdocdir\n";
|
||
push @rv, $docbase;
|
||
}
|
||
else
|
||
{
|
||
print $fh "\t$gzip < $basedir/$doc > $targetdocdir/$docbase.gz\n";
|
||
push @rv, "$docbase.gz";
|
||
}
|
||
}
|
||
}
|
||
|
||
# debian_docs_from_list: return a list of full paths to documentation.
|
||
sub debian_docs_from_list ($)
|
||
{
|
||
my $list = $_[0];
|
||
|
||
# Treat directories, gzipped and non-gzipped files differently.
|
||
return map { my $base;
|
||
if (/\//)
|
||
{
|
||
$base = $_;
|
||
}
|
||
elsif (/\.gz$/)
|
||
{
|
||
s/^.*\///;
|
||
$base = $_;
|
||
}
|
||
else
|
||
{
|
||
s/^.*\///;
|
||
$base = "$_.gz";
|
||
}
|
||
"/usr/share/doc/" . $target->{package_name} . "/$base";
|
||
} @$list;
|
||
}
|
||
|
||
# --- Create a debian `rules' file. ---
|
||
sub write_rules_file ($$)
|
||
{
|
||
my ($fname, $package) = @_;
|
||
open RULES, ">$fname" or die "couldn't create rules file ($fname)";
|
||
print RULES <<'EOF';
|
||
#!/usr/bin/make -f
|
||
EOF
|
||
print RULES "CONFIG_OPTS = $debian_config_flags\n";
|
||
|
||
print RULES <<'EOF';
|
||
# --- make the configure script itself (only needed from CVS) ---
|
||
configure:
|
||
if test -x configure ; then \
|
||
true ; \
|
||
else \
|
||
if test -x bootstrap ; then \
|
||
./bootstrap ; \
|
||
elif test -x autogen.sh ; then \
|
||
./autogen.sh ; \
|
||
fi \
|
||
fi
|
||
test -x configure
|
||
EOF
|
||
|
||
# Different "builds" -- these are completely rebuilt versions
|
||
# of the source code, usually with just different configure flags, etc.
|
||
my $builds = $package->{builds};
|
||
my $install_targets = '';
|
||
my $binary_package_targets = '';
|
||
for my $build (@$builds)
|
||
{
|
||
my $suffix; my $path;
|
||
if ($build->{name} eq '{MAIN}')
|
||
{
|
||
$suffix = '';
|
||
$path = 'MAIN';
|
||
}
|
||
else
|
||
{
|
||
$path = $build->{name};
|
||
$suffix = "-$path";
|
||
}
|
||
my $cfg_envars = $build->{configure_envars};
|
||
my $cfg_flags = $build->{configure_flags};
|
||
|
||
print RULES '#', ('='x69), "\n",
|
||
'# Methods for building', $build->{name}, "\n",
|
||
'#', ('='x69), "\n";
|
||
|
||
# figure out how to run configure.
|
||
print RULES <<"EOF";
|
||
configured$suffix: configured$suffix.pkgwrite-stamp
|
||
configured$suffix.pkgwrite-stamp: configure
|
||
EOF
|
||
$has_configure = 1;
|
||
if ($has_configure)
|
||
{
|
||
print RULES "\ttest -d debian/BUILDS || mkdir debian/BUILDS\n";
|
||
print RULES "\trm -rf debian/BUILDS/$path\n";
|
||
print RULES "\tmkdir debian/BUILDS/$path\n";
|
||
print RULES "\tcd debian/BUILDS/$path && \\\n";
|
||
print RULES "\t$cfg_envars \\\n" if defined $cfg_envars;
|
||
$cfg_flags = '' unless defined ($cfg_flags);
|
||
$cfg_flags .= " $debian_config_flags";
|
||
print RULES "\t../../../configure $cfg_flags\n";
|
||
print RULES "\ttouch configured$suffix.pkgwrite-stamp\n";
|
||
}
|
||
print RULES "\n";
|
||
|
||
# figure out how to build.
|
||
my $build_flags = join (' ',
|
||
$build->{make_flags} || '',
|
||
$build->{build_flags} || '');
|
||
print RULES <<"EOF";
|
||
build$suffix: build$suffix.pkgwrite-stamp
|
||
build$suffix.pkgwrite-stamp: configured$suffix.pkgwrite-stamp
|
||
cd debian/BUILDS/$path && \$(MAKE) PREFIX=/usr $build_flags
|
||
EOF
|
||
my $make_targets = $build->{extra_build_targets};
|
||
for (@$make_targets)
|
||
{
|
||
print RULES "\tcd debian/BUILDS/$path && \\\n",
|
||
"\t\t\$(MAKE) PREFIX=/usr $_ $build_flags\n";
|
||
}
|
||
|
||
# figure out how to install.
|
||
my $make_flags = $build->{make_flags};
|
||
my $install_flags = $build->{install_flags};
|
||
$install_flags = '' unless defined $install_flags;
|
||
$install_flags .= " $make_flags" if defined $make_flags;
|
||
|
||
print RULES <<"EOF";
|
||
install$suffix: install$suffix.pkgwrite-stamp
|
||
install$suffix.pkgwrite-stamp: build$suffix.pkgwrite-stamp
|
||
test -d debian/INSTALLS || mkdir debian/INSTALLS
|
||
test -d debian/INSTALLS/$path || mkdir debian/INSTALLS/$path
|
||
cd debian/BUILDS/$path && \\
|
||
\$(MAKE) PREFIX=/usr DESTDIR=`pwd`/../../INSTALLS/$path \\
|
||
$install_flags install
|
||
EOF
|
||
$make_targets = $build->{extra_install_targets};
|
||
for (@$make_targets)
|
||
{
|
||
print RULES "\tcd debian/BUILDS/$path && \\\n",
|
||
"\t\t\$(MAKE) PREFIX=/usr \\\n",
|
||
"\t\tDESTDIR=`pwd`/../../INSTALLS/$path \\\n",
|
||
"\t\t$install_flags $_\n";
|
||
}
|
||
print RULES <<"EOF";
|
||
touch install$suffix.pkgwrite-stamp
|
||
rm -f target-dist.pkgwrite-stamp
|
||
EOF
|
||
$install_stamp_targets .= " install$suffix.pkgwrite-stamp";
|
||
$install_targets .= " install$suffix";
|
||
}
|
||
|
||
|
||
print RULES '#', ('='x69), "\n",
|
||
"# Copying files into per-target directories.\n",
|
||
'#', ('='x69), "\n",
|
||
"target-dist: target-dist.pkgwrite-stamp\n",
|
||
"target-dist.pkgwrite-stamp: $install_stamp_targets\n";
|
||
my $targets = $package->{targets};
|
||
my %MADE_DIRS = ();
|
||
for my $target (@$targets)
|
||
{
|
||
my $suffix = '';
|
||
|
||
my $pname = $target->{package_name};
|
||
# Move file from the build area to a target directory.
|
||
my $files = $target->{files};
|
||
|
||
# figure out the target's build's directory name.
|
||
my $buildname = $target->{build}->{name};
|
||
if ($buildname eq '{MAIN}')
|
||
{
|
||
$buildname = 'MAIN';
|
||
}
|
||
|
||
# find the string to suffix makefile targets with.
|
||
if ($target->{name} ne '{MAIN}')
|
||
{
|
||
$suffix = "-" . $target->{name};
|
||
}
|
||
for my $pattern (@$files)
|
||
{
|
||
my $dir = $pattern;
|
||
my $cp_command = 'cp -dp';
|
||
$cp_command = 'cp -dpr' if $dir =~ s,/$,,;
|
||
|
||
# Are there any wild-cards in the directory part
|
||
# of the pattern: they will require a different
|
||
# strategy.
|
||
if ($pattern =~ /.*[\?\*].*\/.*/)
|
||
{
|
||
print RULES "\tset -e ; (cd debian/INSTALLS/$buildname ; tar cf - ./$pattern) | ( cd debian/TARGETS/$pname ; tar xf -)\n";
|
||
next;
|
||
}
|
||
|
||
$dir =~ s,/[^/]+$,,;
|
||
maybe_print_make_dirs (\%MADE_DIRS,
|
||
'RULES',
|
||
"debian/TARGETS/$pname/$dir");
|
||
print RULES "\t$cp_command debian/INSTALLS/$buildname/$pattern debian/TARGETS/$pname/$dir\n";
|
||
}
|
||
my $manpages = $target->{manpages};
|
||
for my $manpage (@$manpages)
|
||
{
|
||
my $section = get_manpage_section ($manpage);
|
||
maybe_print_make_dirs (\%MADE_DIRS, 'RULES',
|
||
"debian/TARGETS/$pname/usr/share/man/man$section");
|
||
my $syspath = "usr/share/man/man$section/$manpage";
|
||
print RULES "\tgzip -9 -c < debian/INSTALLS/$buildname/$syspath > debian/TARGETS/$pname/$syspath.gz\n";
|
||
}
|
||
my $inst_doc_dir = "debian/INSTALLS/$buildname/usr/share/doc";
|
||
my $target_doc_dir = "debian/TARGETS/$pname/usr/share/doc";
|
||
$target_doc_dir .= "/" . $target->{package_name};
|
||
maybe_print_make_dirs (\%MADE_DIRS, 'RULES', $target_doc_dir);
|
||
copy_docs ('RULES', "debian/INSTALLS/$buildname",
|
||
$target->{installed_docs}, $target_doc_dir);
|
||
copy_docs ('RULES', ".", $target->{source_docs}, $target_doc_dir);
|
||
|
||
# Copy the Changelog, if any, and add it to the list
|
||
# of files for this target.
|
||
print RULES "\tgzip -9 -c < debian/changelog > ",
|
||
"$target_doc_dir/", $package->{changelog_file}, "\n";
|
||
|
||
$binary_package_targets .= " binary-package-target$suffix";
|
||
}
|
||
print RULES "\ttouch target-dist.pkgwrite-stamp\n";
|
||
print RULES "\trm -f $binary_package_targets\n";
|
||
|
||
print RULES "\n\n";
|
||
print RULES '#', ('='x69), "\n",
|
||
"# Methods for creating binary packages.\n",
|
||
'#', ('='x69), "\n";
|
||
|
||
# I don't think there's ever any reason to build
|
||
# the arch-indep files separately, but someday we
|
||
# might conceivable support a Platform-Independent flag
|
||
# for builds; then they could be placed here.
|
||
print RULES "# Build architecture-independent files here.\n",
|
||
"binary-indep:\n",
|
||
"\ttrue\n\n";
|
||
|
||
print RULES "# Build architecture-dependent files here.\n";
|
||
for my $target (@$targets)
|
||
{
|
||
my $pname = $target->{package_name};
|
||
my $build = $target->{build};
|
||
$pname = 'MAIN' if $pname eq '{MAIN}';
|
||
my $lcpname = lc($pname);
|
||
|
||
my $suffix = '';
|
||
$suffix = ("-" . $target->{name}) if ($target->{name} ne '{MAIN}');
|
||
|
||
# build this binary package.
|
||
print RULES <<"EOF";
|
||
binary-package-target$suffix: binary-package-target$suffix.pkgwrite-stamp
|
||
binary-package-target$suffix.pkgwrite-stamp: target-dist.pkgwrite-stamp
|
||
# Compose DEBIAN directory (in debian/TARGETS/$pname)
|
||
test -d debian/TARGETS/$pname/DEBIAN || mkdir debian/TARGETS/$pname/DEBIAN
|
||
chmod o-w debian/TARGETS/$pname/DEBIAN
|
||
dpkg-gencontrol -p$lcpname -Pdebian/TARGETS/$pname
|
||
EOF
|
||
my $ddata = $target->{debian_data};
|
||
|
||
# copy various worker scripts into the DEBIAN directory.
|
||
for my $variant (qw(preinst postinst prerm postrm))
|
||
{
|
||
if (defined ($ddata->{"needs_" . $variant}))
|
||
{
|
||
my $script = $target->{package_name} . ".$variant";
|
||
print RULES "\tcp debian/$script debian/TARGETS/$pname/DEBIAN\n";
|
||
print RULES "\tchmod +x debian/TARGETS/$pname/DEBIAN/$script\n";
|
||
}
|
||
}
|
||
|
||
print RULES <<"EOF";
|
||
# Build the package.
|
||
dpkg-deb --build debian/TARGETS/$pname ..
|
||
touch binary-package-target$suffix.pkgwrite-stamp
|
||
EOF
|
||
}
|
||
|
||
print RULES "# Debian standard targets.\n";
|
||
print RULES "binary-package: $binary_package_targets\n";
|
||
print RULES "binary-arch: $binary_package_targets\n";
|
||
print RULES "binary: binary-indep binary-arch\n\n";
|
||
print RULES "# these files may not be created by targets of their name.\n";
|
||
print RULES ".PHONY: build clean binary-indep binary-arch binary\n";
|
||
print RULES ".PHONY:$binary_package_targets\n";
|
||
print RULES ".PHONY:$install_targets\n";
|
||
|
||
close (RULES);
|
||
chmod (0755, $fname) or die "could not make `$fname' executable: $!";
|
||
}
|
||
|
||
sub make_debian_description($)
|
||
{
|
||
my @new_desc = ();
|
||
my $old_desc = $_[0];
|
||
for (split /\n/, $old_desc)
|
||
{
|
||
if (/^\S/)
|
||
{ push @new_desc, " $_" }
|
||
elsif (/\S/)
|
||
{ push @new_desc, $_ }
|
||
else
|
||
{ push @new_desc, " ." }
|
||
}
|
||
return join ("\n", @new_desc);
|
||
}
|
||
|
||
sub make_debian_directory($$)
|
||
{
|
||
my ($package, $source_dir) = @_;
|
||
my $output_dir = "$source_dir/debian";
|
||
|
||
mkdir("$output_dir", 0755)
|
||
or die "couldn't make the directory `$output_dir': $!";
|
||
|
||
# --- Create control file ---
|
||
open CONTROL, ">$output_dir/control" or die "couldn't create control file";
|
||
write_entries ('CONTROL', $package,
|
||
[qw(Source Section Priority)],
|
||
[qw(lcname section priority)]);
|
||
if (defined($package->{authors}))
|
||
{
|
||
my $author0 = $package->{authors}->[0];
|
||
print CONTROL "Maintainer: $author0\n";
|
||
}
|
||
print CONTROL "Standards-Version: 3.0.1\n\n";
|
||
|
||
my $targets = $package->{targets};
|
||
for my $target (@$targets)
|
||
{
|
||
my $target_name = $target->{package_name};
|
||
my $deb_description;
|
||
if (defined($target->{description}))
|
||
{
|
||
$deb_description = make_debian_description ($target->{description});
|
||
}
|
||
else
|
||
{
|
||
$deb_description = make_debian_description ($package->{description});
|
||
}
|
||
my $target_arch = 'any';
|
||
$target_arch = 'all' if $target->{arch_indep};
|
||
print CONTROL "Package: " . lc($target_name) . "\n",
|
||
"Architecture: ",
|
||
($target->{arch_indep} ? "all" : "any"),
|
||
"\n";
|
||
my $depends = $target->{depends};
|
||
print CONTROL "Depends: $depends\n" if defined $depends;
|
||
my $conflicts = $target->{conflicts};
|
||
print CONTROL "Conflicts: $conflicts\n" if defined $conflicts;
|
||
print CONTROL "Description: $deb_description\n\n";
|
||
}
|
||
close CONTROL;
|
||
|
||
for my $target (@$targets)
|
||
{
|
||
my $target_name = $target->{package_name};
|
||
my $subname_dot;
|
||
if ($target->{name} eq '{MAIN}')
|
||
{
|
||
$subname_dot = "";
|
||
}
|
||
else
|
||
{
|
||
$subname_dot = "$target_name.";
|
||
}
|
||
|
||
# Create the list of all installed files (including docs).
|
||
my $list = $target->{files};
|
||
my $docdir = "/usr/share/doc/" . $target->{package_name};
|
||
$list = [] unless defined $list;
|
||
my @files = (
|
||
@$list,
|
||
debian_docs_from_list ($target->{source_docs}),
|
||
debian_docs_from_list ($target->{installed_docs}),
|
||
"$docdir/" . $package->{changelog_file}
|
||
);
|
||
|
||
# --- Create the .files files ---
|
||
write_list_to_file (\@files, "$output_dir/$target_name.files");
|
||
|
||
if (defined $target->{conffiles})
|
||
{
|
||
# --- Create the .conffile files ---
|
||
write_list_to_file ($target->{conffiles},
|
||
"$output_dir/$target_name.conffiles");
|
||
}
|
||
|
||
# --- If ldconfig is needed, create .prerm and .postinst files. ---
|
||
# XXX: i gotta learn if prerm is really correct.
|
||
if ($target->{needs_ldconfig})
|
||
{
|
||
my $do_ldconfig_script = <<'EOF';
|
||
#! /bin/sh
|
||
|
||
if test -x /sbin/ldconfig ; then
|
||
/sbin/ldconfig
|
||
else
|
||
ldconfig
|
||
fi
|
||
EOF
|
||
make_file ("$output_dir/$target_name.postinst",
|
||
$do_ldconfig_script);
|
||
make_file ("$output_dir/$target_name.prerm",
|
||
$do_ldconfig_script);
|
||
$target->{debian_data}->{needs_prerm} = 1;
|
||
$target->{debian_data}->{needs_postinst} = 1;
|
||
}
|
||
}
|
||
|
||
my $prefab_entry;
|
||
{
|
||
my $package_name = $package->{name};
|
||
my $version = $package->{version};
|
||
my $release = $package->{release};
|
||
my $packager = $package->{packager};
|
||
my $packager_email = $package->{packager_email};
|
||
my $date = `date -R` ; chomp ($date);
|
||
die unless defined $package_name;
|
||
die unless defined $version;
|
||
die unless defined $release;
|
||
die unless defined $packager;
|
||
die unless defined $packager_email;
|
||
$prefab_entry =
|
||
"$package_name ($version-$release) $debian_dist; urgency=low\n\n" .
|
||
" * packaged by pkgwrite\n" .
|
||
"\n" .
|
||
" -- $packager <$packager_email> $date\n";
|
||
}
|
||
$changelog = $package->{changelog};
|
||
if (defined($changelog))
|
||
{
|
||
$changelog = "$source_dir/$changelog";
|
||
my $cp = changelog_parser_new ($changelog);
|
||
die "couldn't read $changelog" unless defined $cp;
|
||
my $got_first = 0;
|
||
open OCL, ">$output_dir/changelog" or die "couldn't create changelog";
|
||
while (changelog_parser_advance ($cp))
|
||
{
|
||
# Verify that the changelog matches package/version.
|
||
unless ($got_first)
|
||
{
|
||
if ($cp->{package} ne $package->{name})
|
||
{
|
||
die "package name from changelog (" . $cp->{package} . ") "
|
||
. "and according to the pkgwriteinfo "
|
||
. "(" . $package->{name} . ")"
|
||
. " do not match!";
|
||
}
|
||
my $ver = $package->{version} . '-' . $package->{release};
|
||
if ($cp->{version} ne $ver)
|
||
{
|
||
if ($strict_changelog_checking)
|
||
{
|
||
die "package version in changelog (".$cp->{version}.")"
|
||
. " and in pkgwriteinfo file ($ver) do not match!";
|
||
}
|
||
else
|
||
{
|
||
print OCL $prefab_entry;
|
||
}
|
||
}
|
||
$got_first = 1;
|
||
}
|
||
|
||
changelog_write_debian_style ($cp, 'OCL');
|
||
}
|
||
close OCL;
|
||
changelog_parser_destroy ($cp);
|
||
}
|
||
else
|
||
{
|
||
make_file ("$output_dir/changelog", $prefab_entry);
|
||
}
|
||
|
||
# --- Create rules file ---
|
||
write_rules_file ("$output_dir/rules", $package);
|
||
}
|
||
|
||
|
||
#=====================================================================
|
||
# Section 7: Make a package from a tarball.
|
||
#=====================================================================
|
||
sub make_package($$$$$)
|
||
{
|
||
my ($package, $tarball, $vendor, $arch, $output_dir) = @_;
|
||
|
||
my $workdir = get_work_dir ();
|
||
chdir ($workdir) or die "couldn't cd $workdir";
|
||
|
||
# Assert: Various paths cannot be relative.
|
||
die unless $tarball =~ m,^/,;
|
||
die unless $output_dir =~ m,^/,;
|
||
|
||
# Make the packages.
|
||
if ($vendor eq 'redhat')
|
||
{
|
||
my $rhdir = "$workdir/redhat";
|
||
my @rpm_commands = make_redhat_dir ($package, $rhdir, $tarball, $arch);
|
||
chdir "$rhdir" or die "couldn't cd to $rhdir";
|
||
for my $rpm_command (@rpm_commands)
|
||
{
|
||
run ("$rpm_command");
|
||
}
|
||
my $rpmdir = "$workdir/redhat/rpm-tmp/usr/src/redhat";
|
||
run ("cp $rpmdir/SRPMS/*.rpm $rpmdir/RPMS/*.rpm $output_dir");
|
||
}
|
||
elsif ($vendor eq 'debian')
|
||
{
|
||
my $ddir = "$workdir/debian";
|
||
mkdir ($ddir, 0775) or die "couldn't make the directory $ddir";
|
||
|
||
# Untar.
|
||
chdir ($ddir) or die "couldn't cd to $ddir";
|
||
run ("zcat $tarball | tar xf -");
|
||
|
||
# Make the packaging directory.
|
||
my $dir;
|
||
$dir = "$ddir/" . $package->{name} . '-' . $package->{version};
|
||
|
||
make_debian_directory ($package, "$dir");
|
||
chdir ($dir) or die "couldn't cd to $dir";
|
||
run ("cd $dir && dpkg-buildpackage -rfakeroot -uc -us");
|
||
run ("mv $ddir/*.deb $output_dir");
|
||
my $dprefix = $package->{name}
|
||
. "_" . $package->{version}
|
||
. "-" . $package->{release};
|
||
|
||
# Move the debian source package.
|
||
run ("mv $ddir/$dprefix.tar.gz $output_dir");
|
||
run ("mv $ddir/$dprefix*.changes $output_dir");
|
||
run ("mv $ddir/$dprefix*.dsc $output_dir");
|
||
}
|
||
else
|
||
{
|
||
die "i can only make `redhat' and `debian' packages";
|
||
}
|
||
}
|
||
|
||
|
||
#=====================================================================
|
||
# Section 8: Usage message.
|
||
#=====================================================================
|
||
sub usage
|
||
{
|
||
print STDERR <<"EOF";
|
||
usage: pkgwrite (--tarball=TARBALL | --srcdir=SRCDIR)
|
||
[--pkgwriteinfo-file=pkgwriteinfo]
|
||
[--no-cleanup]
|
||
[--no-strict-changelog]
|
||
[--debian-dist=DIST]
|
||
--format=(redhat|debian|all)
|
||
--output=OUTPUT
|
||
--arch=ARCH
|
||
|
||
Make either redhat or debian packages for source code and
|
||
a pkgwriteinfo file.
|
||
|
||
(Run pkgwrite --extra-help for unusual command-line flags.)
|
||
|
||
EOF
|
||
print STDERR "The system supports .rpm building.\n" if $has_rpm_support;
|
||
print STDERR "The system supports .deb building.\n" if $has_dpkg_support;
|
||
exit (1);
|
||
}
|
||
|
||
sub extra_usage
|
||
{
|
||
print STDERR <<"EOF";
|
||
usage: pkgwrite [flags]
|
||
|
||
Querying hardcoded lists of values in pkgwrite:
|
||
|
||
pkgwrite --query-list={deb-sections,rpm-groups,deb-priorities}
|
||
EOF
|
||
exit (1);
|
||
}
|
||
|
||
sub version
|
||
{
|
||
print "This is pkgwrite, version $PKGWRITE_VERSION.\n";
|
||
exit 0;
|
||
}
|
||
|
||
|
||
|
||
|
||
#=====================================================================
|
||
# Section 9: Main program.
|
||
#=====================================================================
|
||
my $pkg_tarball;
|
||
my $pkg_sourcedir;
|
||
my $pkgwriteinfo_file;
|
||
my $pkg_format;
|
||
my $pkg_arch;
|
||
my $output_dir;
|
||
|
||
# --- Process arguments ---
|
||
while (defined($arg=shift(@ARGV)))
|
||
{
|
||
# Arguments that cause us to print something and exit.
|
||
if ($arg eq '--help') { usage () }
|
||
if ($arg eq '--extra-help') { extra_usage () }
|
||
if ($arg eq '--version') { version () }
|
||
if ($arg =~ s/--query-list=//) { dump_list ($arg); }
|
||
|
||
# Other flags.
|
||
if ($arg eq '--no-strict-changelog') { $strict_changelog_checking = 0; }
|
||
elsif ($arg eq '--skip-sanity-check') { $do_sanity_check = 0; }
|
||
elsif ($arg eq '--no-cleanup') { $do_cleanup = 0; }
|
||
elsif ($arg =~ s/--tarball=//) { $pkg_tarball = $arg; }
|
||
elsif ($arg =~ s/--source-dir=//) { $pkg_sourcedir = $arg; }
|
||
elsif ($arg =~ s/--output=//) { $output_dir = $arg; }
|
||
elsif ($arg =~ s/--pkgwriteinfo-file=//){ $pkgwriteinfo_file = $arg; }
|
||
elsif ($arg =~ s/--debian-dist=//) { $debian_dist = $arg; }
|
||
elsif ($arg =~ s/--arch=//) { $pkg_arch = $arg; }
|
||
elsif ($arg =~ s/--format=//) { $pkg_format = $arg; }
|
||
elsif (($arg eq '--tarball')
|
||
|| ($arg eq '--source-dir')
|
||
|| ($arg eq '--output')
|
||
|| ($arg eq '--pkgwriteinfo-file')
|
||
|| ($arg eq '--format')
|
||
|| ($arg eq '--arch')
|
||
|| ($arg eq '--debian-dist')
|
||
|| ($arg eq '--query-list'))
|
||
{
|
||
my $newarg = shift(@ARGV);
|
||
die "$arg requires a parameter" unless defined $newarg;
|
||
if ($arg eq '--tarball') { $pkg_tarball = $newarg; }
|
||
elsif ($arg eq '--source-dir') { $pkg_sourcedir = $newarg; }
|
||
elsif ($arg eq '--pkgwriteinfo-file'){ $pkgwriteinfo_file = $newarg; }
|
||
elsif ($arg eq '--output') { $output_dir = $newarg; }
|
||
elsif ($arg eq '--arch') { $pkg_arch = $newarg; }
|
||
elsif ($arg eq '--debian-dist') { $debian_dist = $newarg; }
|
||
elsif ($arg eq '--format') { $pkg_format = $newarg; }
|
||
elsif ($arg eq '--query-list') { dump_list ($newarg); }
|
||
else { die "internal error" }
|
||
}
|
||
else
|
||
{
|
||
warn "unrecognized argument: $arg";
|
||
usage ();
|
||
}
|
||
}
|
||
|
||
unless (defined($pkg_tarball) || defined($pkg_sourcedir))
|
||
{
|
||
die "either --tarball or --source-dir must be specified"
|
||
}
|
||
unless (defined ($pkg_format))
|
||
{
|
||
die "--format must be specified";
|
||
}
|
||
unless (defined ($output_dir))
|
||
{
|
||
die "--output must be specified";
|
||
}
|
||
unless (defined ($pkg_arch))
|
||
{
|
||
die "--arch must be specified";
|
||
}
|
||
if ($pkg_format ne 'all'
|
||
&& $pkg_format ne 'debian'
|
||
&& $pkg_format ne 'redhat')
|
||
{
|
||
my $valid = join (', ', qw(all debian redhat));
|
||
die "format `$pkg_format' was invalid: only $valid are known";
|
||
}
|
||
if (($pkg_format eq 'all' || $pkg_format eq 'redhat')
|
||
&& !$has_rpm_support)
|
||
{
|
||
die "cannot make RPMs on this system";
|
||
}
|
||
if (($pkg_format eq 'all' || $pkg_format eq 'debian')
|
||
&& !$has_dpkg_support)
|
||
{
|
||
die "cannot make Debian packages on this system";
|
||
}
|
||
|
||
|
||
# Make all paths absolute.
|
||
#
|
||
# We are about to `chdir' all over the place.
|
||
# While technically `make_absolute' is cheesy
|
||
# (b/c eg it doesn't work if `pwd` has been deleted),
|
||
# it least it works in practice,
|
||
# and makes the programming much easier.
|
||
if (defined($pkg_tarball))
|
||
{ $pkg_tarball = make_absolute ($pkg_tarball) }
|
||
if (defined($pkg_sourcedir))
|
||
{ $pkg_sourcedir = make_absolute ($pkg_sourcedir) }
|
||
if (defined($pkgwriteinfo_file))
|
||
{ $pkgwriteinfo_file = make_absolute ($pkgwriteinfo_file) }
|
||
if (defined($output_dir))
|
||
{ $output_dir = make_absolute ($output_dir);
|
||
run ("mkdir -p $output_dir"); }
|
||
if (defined($debian_changelog))
|
||
{ $debian_changelog = make_absolute ($debian_changelog) }
|
||
|
||
|
||
# Create pkgwriteinfo_file if necessary.
|
||
if (!defined($pkgwriteinfo_file))
|
||
{
|
||
# Build a temporary working area to monkey with the source code.
|
||
my $workdir = get_work_dir ();
|
||
my $edir = "$workdir/pkgwriteinfo-tmp-extract";
|
||
mkdir ($edir, 0755) or die;
|
||
my $tmp_source_dir;
|
||
|
||
# Extract the source code, to (hopefully) find a pkgwriteinfo{,.in} file.
|
||
if (defined($pkg_tarball))
|
||
{
|
||
run ("cd $edir ; $gunzip < $pkg_tarball | tar xf -");
|
||
|
||
# find the tmp source directory inside $edir.
|
||
opendir X, "$edir" or die "error scanning $edir";
|
||
for (readdir X)
|
||
{
|
||
next if $_ eq '.';
|
||
next if $_ eq '..';
|
||
next unless -d "$edir/$_";
|
||
$tmp_source_dir = "$edir/$_";
|
||
last;
|
||
}
|
||
closedir X;
|
||
}
|
||
else
|
||
{
|
||
$tmp_source_dir = $pkg_sourcedir;
|
||
}
|
||
die "could not find directory in tarball" unless defined $tmp_source_dir;
|
||
|
||
# If there isn't a pkgwriteinfo file, try running configure.
|
||
if (!-e "$tmp_source_dir/pkgwriteinfo")
|
||
{
|
||
if (-e "$tmp_source_dir/pkgwriteinfo.in")
|
||
{
|
||
if (! -x "$tmp_source_dir/configure")
|
||
{
|
||
# Uh, maybe `bootstrap' or `autogen.sh' will help?
|
||
if (-x "$tmp_source_dir/bootstrap")
|
||
{
|
||
run ("cd $tmp_source_dir; ./bootstrap");
|
||
run ("cd $tmp_source_dir; ./configure --quiet");
|
||
}
|
||
elsif (-x "$tmp_source_dir/autogen.sh")
|
||
{
|
||
run ("cd $tmp_source_dir; ./autogen.sh --quiet");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
# Excellent: configure it.
|
||
run ("cd $tmp_source_dir; ./configure --quiet");
|
||
}
|
||
unless (-e "$tmp_source_dir/pkgwriteinfo.in")
|
||
{
|
||
warn "pkgwriteinfo is probably needed in AC_OUTPUT";
|
||
}
|
||
}
|
||
else
|
||
{
|
||
die "could not find pkgwriteinfo{.in} in the tarball";
|
||
}
|
||
}
|
||
|
||
# If there isn't a pkgwriteinfo file, it's an error.
|
||
if (!-e "$tmp_source_dir/pkgwriteinfo")
|
||
{
|
||
die "couldn't make pkgwriteinfo file";
|
||
}
|
||
|
||
# Copy the pkgwriteinfo file about and remove $edir.
|
||
$pkgwriteinfo_file = "$workdir/pkgwriteinfo";
|
||
run ("cp $tmp_source_dir/pkgwriteinfo $pkgwriteinfo_file");
|
||
run ("rm -rf $edir");
|
||
}
|
||
|
||
# --- Build the package(s) ---
|
||
my $package = parse_pkgwriteinfo_file ($pkgwriteinfo_file);
|
||
die "couldn't parse package from $pkgwriteinfo_file" unless defined $package;
|
||
|
||
# Sanity check the package.
|
||
sanity_check_package ($package) if ($do_sanity_check);
|
||
|
||
$package->{package_tarball} = $pkg_tarball;
|
||
$package->{package_source_dir} = $pkg_sourcedir;
|
||
|
||
# Produce a list of formats.
|
||
my @format_list;
|
||
if ($pkg_format eq 'all')
|
||
{
|
||
@format_list = qw(debian redhat)
|
||
}
|
||
else
|
||
{
|
||
@format_list = ( $pkg_format );
|
||
}
|
||
|
||
# Make all the desired formats.
|
||
for my $format (@format_list)
|
||
{
|
||
make_package ($package, $pkg_tarball, $format, $pkg_arch, $output_dir);
|
||
}
|
||
|
||
# Clean up.
|
||
remove_work_dir ();
|
||
|
||
exit (0);
|
||
|
||
# --- Miscellaneous "main" helper functions ---
|
||
|
||
# dump_table: Print the values of a hash-table reference to standard-output.
|
||
sub dump_table ($)
|
||
{
|
||
my $table = $_[0];
|
||
for (sort keys %$table)
|
||
{
|
||
print $table->{$_}, "\n";
|
||
}
|
||
exit (0);
|
||
}
|
||
|
||
# dump_list: dump one of the standard builtin hashtables to standard output.
|
||
sub dump_list ($)
|
||
{
|
||
my $list = $_[0];
|
||
if ($list eq 'deb-sections') { dump_table (\%DPKG_SECTIONS); }
|
||
elsif ($list eq 'deb-priorities') { dump_table (\%DPKG_PRIORITY_LEVELS); }
|
||
elsif ($list eq 'rpm-groups') { dump_table (\%RPM_GROUPS); }
|
||
else { die "unknown list to dump (to --query-list): $list" }
|
||
}
|
||
|
||
|
||
#=====================================================================
|
||
# Section 10: POD Documention.
|
||
#=====================================================================
|
||
=pod
|
||
|
||
=head1 NAME
|
||
|
||
pkgwrite - Make RedHat and Debian packages from the same source.
|
||
|
||
=head1 SYNOPSIS
|
||
|
||
pkgwrite (--tarball=TARBALL | --srcdir=SRCDIR) \
|
||
[--pkgwriteinfo-file=pkgwriteinfo] \
|
||
--arch=ARCH \
|
||
--format={redhat,debian}
|
||
[options]
|
||
|
||
|
||
pkgwrite --query-list=LISTNAME
|
||
|
||
=head1 DESCRIPTION
|
||
|
||
pkgwrite takes a standard automake package, either from a source directory
|
||
or from a distributed tarball, and a C<pkgwrite> input file
|
||
and makes either RedHat or Debian packages.
|
||
|
||
The actual package source code must be specified either by
|
||
directory (using --srcdir) or from a tarball created with `make dist'
|
||
(using --tarball).
|
||
|
||
Additional packaging information is taken from pkgwriteinfo.
|
||
If --pkgwriteinfo-file is omitted, pkgwriteinfo from the source
|
||
directory or tarball is taken instead. (after configure is run,
|
||
so you might generally use a pkgwriteinfo.in).
|
||
|
||
There are a few command-line parameters which affect the package-making:
|
||
|
||
=over 4
|
||
|
||
=item --no-strict-changelog
|
||
|
||
Don't force the user's changelog and pkgwriteinfo file to
|
||
have the same version. (If the packaging system
|
||
requires that the changelog's latest entry be equal to
|
||
the package's version, then pkgwrite will generate a
|
||
changelog entry. This happens under Debian.)
|
||
|
||
=item --no-cleanup
|
||
|
||
Don't remove the temporary directory (which will
|
||
be C</tmp/mkpkg-###-$USER>). Useful for debugging.
|
||
|
||
=item --debian-dist=DIST
|
||
|
||
Generate packaging for the given debian distribution:
|
||
this mostly affects the changelog so
|
||
setting DIST to C<stable> or C<unstable> is recommended.
|
||
|
||
=item --skip-sanity-check
|
||
|
||
Disable the usual checks that the package is valid.
|
||
The package may still partially work, even if a sanity-check
|
||
would normally fail.
|
||
|
||
=back
|
||
|
||
=head1 TERMINOLOGY
|
||
|
||
=over 4
|
||
|
||
=item package
|
||
|
||
A family of packages for various distributions
|
||
which all come from one tarball and one pkgwriteinfo file.
|
||
|
||
=item build
|
||
|
||
A compilation of the source code into binaries.
|
||
Some packages require multiple builds, for example, to make debugging
|
||
and nondebugging versions of a libraries.
|
||
Normally you just use the C<{MAIN}> build.
|
||
|
||
=item target
|
||
|
||
A single set of installed files in a package.
|
||
Simple packages only have a single target C<{MAIN}>
|
||
because the package is an all-or-nothing proposition.
|
||
|
||
Some packages contain many parts, not all applicable to all users.
|
||
These packages should be broken in to different targets.
|
||
|
||
For example, a client/server based application might be
|
||
conveniently packaged C<foo-server>, C<foo-client-curses>, C<foo-client-gtk>.
|
||
That way, users without X can use the curses version without
|
||
installing gtk+, often the clients and servers are run exclusively on
|
||
different machines, so installing both is a waste of disk space.
|
||
|
||
=back
|
||
|
||
The resulting system-dependent binary's name is just
|
||
the main C<Package:> name if the C<Target:> is C<{MAIN}>;
|
||
otherwise it will be the C<Package> and C<Target> separated by
|
||
a hyphen (C<->).
|
||
|
||
=head1 MAINTAINING CHANGELOGS
|
||
|
||
We recommend that you maintain a changelog in debian format,
|
||
here is an example:
|
||
|
||
gdam (0.934-1) unstable; urgency=low
|
||
|
||
* many bug fixes
|
||
* split into many packages
|
||
|
||
-- David Benson <daveb@ffem.org> Wed, 17 Jan 2000 13:09:36 -0800
|
||
|
||
(No spaces for each version banner; 2 spaces on each bullet;
|
||
1 space before the packager byline.)
|
||
|
||
If you don't maintain a changelog, we will generate a changelog
|
||
with just this version of the package in it.
|
||
|
||
You should specify the changelog using the C<Changelog:> directive.
|
||
|
||
=head1 EXAMPLES
|
||
|
||
Here are a few examples of common types of packages.
|
||
The pkgwrite distribution includes these packages
|
||
inside the C<examples/tiny> directory.
|
||
|
||
=head2 EXAMPLE: SINGLE-TARGET PACKAGE
|
||
|
||
The most common type of package has one set of files
|
||
it installs or uninstalls: there are no packaged bits or pieces.
|
||
(A Target in pkgwrite terminology is the installed set of files.)
|
||
|
||
Here is the pkgwrite file from the single-target example
|
||
included with the pkgwrite distribution:
|
||
|
||
Package: aa
|
||
Section: text
|
||
Group: Applications/Text
|
||
Priority: low
|
||
Home-Page: NONE
|
||
Source-Url: NONE
|
||
Author: David Benson <daveb@ffem.org>
|
||
Version: 0.0.8
|
||
Release: 1
|
||
Synopsis: test package aa
|
||
Packager: daveb
|
||
Packager-Email: daveb@ffem.org
|
||
License: NONE
|
||
Description: test package (aa).
|
||
|
||
Build: {MAIN}
|
||
|
||
Target: {MAIN}
|
||
Files: /usr/bin/dummy-*
|
||
Synopsis: test a (single-target package)
|
||
|
||
This package's name is C<aa>; this file will produce a binary RPM
|
||
named C<aa-0.0-1.$ARCH.rpm>.
|
||
|
||
=over 4
|
||
|
||
=item *
|
||
|
||
we wanted to name this C<a>, but debian packages must be at least two
|
||
letters.
|
||
|
||
=item *
|
||
|
||
$ARCH is the target architecture, for example
|
||
C<i386>, C<alpha>, or C<powerpc>.
|
||
|
||
=item *
|
||
|
||
The Target C<{MAIN}> is special, it
|
||
means "don't use any suffix" -- the package's
|
||
name is to be C<aa>. For any other Target line,
|
||
if STRING was specified, the resulting
|
||
RPM or deb would have the name C<aa-STRING>.
|
||
|
||
=item *
|
||
|
||
Each wildcard from Files lines describe a file or files to move
|
||
into that target.
|
||
|
||
=item *
|
||
|
||
Unlisted files will not wind up in any binary package.
|
||
|
||
=back
|
||
|
||
=head2 EXAMPLE: MULTI-TARGET PACKAGE
|
||
|
||
A multi-target, single-build package is a package that need
|
||
only be compiled once, but which must be separated into
|
||
several system packages, because the targets appeal to
|
||
different users or have different dependencies.
|
||
|
||
Here is the pkgwriteinfo file from the example multi-target single-build
|
||
package:
|
||
|
||
|
||
Package: bb
|
||
Section: text
|
||
Group: Applications/Text
|
||
Priority: low
|
||
Home-Page: NONE
|
||
Source-Url: NONE
|
||
Author: David Benson <daveb@ffem.org>
|
||
Version: 0.0
|
||
Release: 1
|
||
Synopsis: test package bb
|
||
Packager: daveb
|
||
Packager-Email: daveb@ffem.org
|
||
License: NONE
|
||
Description: test package (bb).
|
||
|
||
Build: {MAIN}
|
||
|
||
Target: a
|
||
Files: /usr/bin/bb-a
|
||
Synopsis: part a of package bb
|
||
Description: whatever (bb-a)
|
||
|
||
Target: b
|
||
Files: /usr/bin/bb-b
|
||
Synopsis: part b of package bb
|
||
Description: whatever (bb-b)
|
||
|
||
In this package, only a single default Build: is required.
|
||
Some packages may require the C<Configure-Flags> or
|
||
C<Configure-Envars> fields in order to compile correctly.
|
||
|
||
By default, all the targets use the C<{MAIN}> Build.
|
||
|
||
Then each package contains a default file list,
|
||
a description and a synopsis.
|
||
|
||
=head2 EXAMPLE: MULTI-BUILD PACKAGE
|
||
|
||
The most complex type of package must be built multiple times,
|
||
with different configure or make flags. Each target must then
|
||
refer to the build from which it was produced,
|
||
using the C<Which-Build> field (the default is C<{MAIN}>).
|
||
|
||
Here is the example of such a package from the C<pkgwrite>
|
||
distribution:
|
||
|
||
Package: cc
|
||
Section: text
|
||
Group: Applications/Text
|
||
Priority: low
|
||
Home-Page: NONE
|
||
Source-Url: NONE
|
||
Author: David Benson <daveb@ffem.org>
|
||
Version: 0.0
|
||
Release: 1
|
||
Synopsis: test package cc
|
||
Packager: daveb
|
||
Packager-Email: daveb@ffem.org
|
||
License: NONE
|
||
Description: test package (cc).
|
||
|
||
Build: nond
|
||
Configure-Flags: --program-suffix=-nondebug
|
||
|
||
Build: d
|
||
Configure-Flags: --program-suffix=-debug
|
||
|
||
Target: nondebug
|
||
Which-Build: nond
|
||
Files: /usr/bin/test-nondebug
|
||
Synopsis: nondebug package cc
|
||
Description: whatever (cc-nondebug)
|
||
|
||
Target: debug
|
||
Which-Build: d
|
||
Files: /usr/bin/test-debug
|
||
Synopsis: debug package cc
|
||
Description: whatever (cc-debug)
|
||
|
||
Each Build section corresponds to a complete configure, build, install phase.
|
||
In this package, the C<nond> build just wants configure to be run
|
||
configure --program-suffix=-nondebug ...
|
||
whereas for the C<d> build,
|
||
configure --program-suffix=-debug ...
|
||
(Note that the ... will be somewhat different from distribution to
|
||
distribution)
|
||
|
||
Also, it is often convenient to use the same names for the builds and
|
||
the targets. We would rename C<nond> as C<nondebug> and C<d> as C<debug>
|
||
if this were a real package -- we did this to discuss it more
|
||
conveniently.
|
||
|
||
It is perfectly possible to have more than one Target pointing to the same
|
||
Build, just as multi-target single-build packages do.
|
||
But the opposite is not allowed: a Target must specify exactly one
|
||
Build.
|
||
|
||
=head1 HARDCODED VALUES
|
||
|
||
Many lists and values are hardcoded into pkgwrite.
|
||
You may query these lists through the --query-list flag.
|
||
Here are the lists you may obtain in this manner:
|
||
|
||
=over 4
|
||
|
||
=item deb-sections
|
||
|
||
Known allowed Section: fields for debian packages.
|
||
|
||
=item rpm-groups
|
||
|
||
Known allowed Group: fields for redhat packages.
|
||
|
||
=item deb-priority
|
||
|
||
Known allowed Priority: fields for debian packages.
|
||
|
||
=back
|
||
|
||
For example to get a list of allowed values for the Section:
|
||
field, use C<pkgwrite --query-list=deb-sections>.
|
||
|
||
=head1 PKGWRITE'S pkgwriteinfo FORMAT
|
||
|
||
This (long) section describes the file
|
||
that describes the targets to build from a tarball.
|
||
This description file is called a C<pkgwriteinfo> file.
|
||
|
||
The C<pkgwriteinfo> file consists of one package description part,
|
||
then a number of Build sections,
|
||
then a number of Target sections.
|
||
|
||
=head2 PER-PACKAGE INFO
|
||
|
||
The package file should begin with a section that describes
|
||
the overall source code of the package:
|
||
|
||
Package: gdam
|
||
Section: sound
|
||
Group: Multimedia/Sound
|
||
Priority: optional
|
||
Home-Page: http://ffem.org/gdam
|
||
Source-Url: http://ffem.org/gdam/downloads/gdam-0.0.930.tar.gz
|
||
Version: 0.0.930
|
||
Release: 1
|
||
Author: David Benson <daveb@ffem.org>
|
||
|
||
Here is a description of each allowed field:
|
||
|
||
=over 4
|
||
|
||
=item Package
|
||
|
||
Name of the source package.
|
||
|
||
=item Output-Package
|
||
|
||
Sometimes, all packages from a tarball use a somewhat
|
||
different name than the tarball itself.
|
||
This is used as a bit of a hack to support co-installation
|
||
of multiple versions of a package. This is like
|
||
gtk, which has gtk1.2 and gtk2.0 packages, which can be installed concurrently.
|
||
|
||
=item Version
|
||
|
||
The version number given to this version of the package
|
||
by its maintainer.
|
||
|
||
=item Release
|
||
|
||
Increment this each time a new package is released without
|
||
a corresponding upstream version-number change.
|
||
|
||
=item Section
|
||
|
||
The debian section this package belongs in.
|
||
|
||
=item Group
|
||
|
||
The redhat group this package belongs in.
|
||
|
||
=item Priority
|
||
|
||
Priority of this package (debian-style).
|
||
|
||
=item Home-Page
|
||
|
||
A URL giving the home page for this package or project.
|
||
|
||
=item Source-Url
|
||
|
||
A URL describing how to download this package.
|
||
|
||
=item Author
|
||
|
||
An author of this package, with email optional.
|
||
|
||
=item Synopsis
|
||
|
||
Under one line summary of this target.
|
||
|
||
=item Description
|
||
|
||
Multiple-line description of this target.
|
||
|
||
=item Packager
|
||
|
||
Full name of the person who made this packaging.
|
||
|
||
=item Packager-Email
|
||
|
||
Email address at which to reach the packager.
|
||
|
||
=item Changelog
|
||
|
||
Specify the location of a Debian format changelog to include with
|
||
the package (it will be converted to another standard format, if needed)
|
||
|
||
=item Upstream-is-Packager
|
||
|
||
Whether the packager (the person running C<pkgwrite>)
|
||
is the same as the upstream maintainer of a package.
|
||
|
||
Right now, this only affects the filename used for the changelog,
|
||
it will either be C<changelog.gz> if they are the same person,
|
||
or C<changelog.Debian.gz> if they are not.
|
||
|
||
=back
|
||
|
||
=head2 PER-TARGET INFO
|
||
|
||
For each output binary package there must be a "target" section:
|
||
|
||
Target: xmms-plugins
|
||
Depends: gdam-clients-gtk, gdam-server, xmms
|
||
Synopsis: GDAM XMMS plugin support
|
||
Description: use XMMS visualization plugins with GDAM.
|
||
Files: /usr/bin/gdamxmmsvishelper
|
||
|
||
=over 4
|
||
|
||
=item Target
|
||
|
||
Name of this target. The name of the package that results will
|
||
be prepended with SOURCE-; in this example the package's name
|
||
is C<gdam-xmms-plugins>.
|
||
|
||
=item Platform-Independent
|
||
|
||
Set this to C<yes> if this package will be installable on
|
||
any architecture (it contains no system-specific or compiled code).
|
||
|
||
Set this to C<no> for packages containing compiled,
|
||
architecture-specific binaries.
|
||
|
||
=item Depends
|
||
|
||
Debian-formatted dependency lists for this package.
|
||
|
||
=item Redhat-Requires
|
||
|
||
Specify the redhat packages that this one depends on.
|
||
(We will try to compute this from the C<Depends:> lines by default;
|
||
this is just in case we cannot guess correctly.)
|
||
|
||
=item Conflicts
|
||
|
||
Debian-formatted list of packages that conflict with this one.
|
||
|
||
=item Redhat-Conflicts
|
||
|
||
Redhat-formatted list of packages that conflict with this one.
|
||
Computed from C<Conflicts:> by default.
|
||
|
||
=item Synopsis
|
||
|
||
Under one line summary of this target.
|
||
|
||
=item Man-Page
|
||
|
||
The basename of a man page, for example C<pkgwrite.1>.
|
||
It will automatically be installed into the correct
|
||
section directory based on its extension.
|
||
|
||
=item Doc
|
||
|
||
Miscellaneous documentation.
|
||
Each path is assumed to be inside the installed area.
|
||
|
||
It will be always be copied into the distribution's
|
||
documentation area, and it will be gzip'd if that is needed.
|
||
Also, whole directories will be recursively copied,
|
||
if the entry ends with a /, for example,
|
||
|
||
Doc: /usr/share/doc/gdam/example-configs/
|
||
|
||
=item Source-Doc
|
||
|
||
Miscellaneous documentation that is not normally
|
||
installed by this package's makefile:
|
||
|
||
These must be files directly from the distributed tarball.
|
||
They will be always be copied into the distribution's
|
||
documentation area and will be gzip'd if that is needed.
|
||
Whole directories will be recursively copied,
|
||
if the entry ends with a /.
|
||
|
||
=item Description
|
||
|
||
Multiple-line description of this target.
|
||
|
||
=item Which-Build
|
||
|
||
Name of the build whose files should be used.
|
||
(Defaults to C<{MAIN}>).
|
||
|
||
=item Files
|
||
|
||
A wildcard matching ordinary files to be distributed with
|
||
this target.
|
||
|
||
=back
|
||
|
||
=head2 PER-BUILD INFO
|
||
|
||
=over 4
|
||
|
||
=item Build
|
||
|
||
The name of this build. The default build should be named
|
||
C<{MAIN}>.
|
||
|
||
=item Configure-Flags
|
||
|
||
Options to be passed to the C<configure> script.
|
||
|
||
=item Configure-Envars
|
||
|
||
Space-separated environment variables to add when configuring.
|
||
|
||
=item Make-Flags
|
||
|
||
Extra parameters to the C<make> program during both
|
||
the build and the install phase.
|
||
|
||
=item Build-Flags
|
||
|
||
Extra parameters to the C<make> program during just
|
||
the build phase.
|
||
|
||
=item Install-Flags
|
||
|
||
Extra parameters to the C<make> program during just
|
||
the install phase.
|
||
|
||
=item Extra-Build-Targets
|
||
|
||
Another C<make> target that is necessary to build the package,
|
||
that is not run by the default target. This must not
|
||
require root priviledges. (You may specify
|
||
any number of C<Extra-Build-Targets:> lines.)
|
||
|
||
=item Extra-Install-Targets
|
||
|
||
Another C<make> target that is necessary to install the package,
|
||
that is not run by the default target. On Debian systems, these commands
|
||
may require root or fakeroot. (You may specify
|
||
any number of C<Extra-Install-Targets:> lines.)
|
||
|
||
=back
|
||
|
||
=head1 DEBUGGING
|
||
|
||
If you have problems, here are some hints:
|
||
|
||
=over 4
|
||
|
||
=item *
|
||
|
||
If you are familiar with one of the packaging systems (Redhat or Debian)
|
||
that is not working, try building that with either
|
||
the C<--no-cleanup> flag or equivalently have the C<DO_CLEANUP>
|
||
environment variable set to C<0>. (You can find the
|
||
directory where work was done by running: C<ls -ltr /tmp/mkpkg-*>)
|
||
|
||
=item *
|
||
|
||
Otherwise, redirect the output to a log file and read the whole thing.
|
||
Some packaging systems output a log of messages
|
||
even after a fatal error.
|
||
|
||
=item *
|
||
|
||
Then, try and cut out pieces of the C<pkgwriteinfo>
|
||
file until you locate the problem.
|
||
Report a bug if the problem is clear.
|
||
|
||
=back
|
||
|
||
=head1 COMMON PROBLEMS
|
||
|
||
Here are some trivial mistakes made often:
|
||
|
||
=over 4
|
||
|
||
=item *
|
||
|
||
C<pkgwriteinfo> is generated by C<configure> from C<pkgwriteinfo.in>.
|
||
It is easy to forget to rerun C<configure> and very slow.
|
||
|
||
I don't know of any easy solution to forgetting
|
||
at the moment, but you may just run C<config.status>
|
||
instead, in order save time.
|
||
|
||
=back
|
||
|
||
=head1 RELEASING PACKAGES
|
||
|
||
Of course, you can and should work however you want,
|
||
but for your information, I will write the testing process
|
||
I use for releasing packages.
|
||
|
||
I always make C<automake> packages that use
|
||
C<pkgwriteinfo> inside the tarball.
|
||
|
||
It is important to note that my packages always
|
||
support C<make rpm> and C<make deb> using C<pkgwrite>,
|
||
and that these targets only C<make dist> if the tarball
|
||
isn't present!
|
||
|
||
=over 4
|
||
|
||
=item 1
|
||
|
||
Check out the package from CVS. Then either use whichever
|
||
bootstrap script is provided: my recent packages use C<bootstrap>
|
||
(as recommended by the C<automake> authors), but many packages
|
||
use C<./autogen.sh>, which traditionally also runs C<./configure>.
|
||
|
||
If you used C<bootstrap>, you will need to run C<./configure>.
|
||
|
||
Then make the tarball using C<make distcheck>.
|
||
|
||
=item 2
|
||
|
||
On a RedHat machine and a Debian machine,
|
||
untar the tarball and copy that same tarball
|
||
into the PACKAGE-VERSION directory that was created.
|
||
(This way, the exact same tarball will be reused).
|
||
|
||
=item 3
|
||
|
||
On each system, run C<./configure>, then
|
||
C<make rpm> and C<make deb>, respectively.
|
||
|
||
=item 4
|
||
|
||
Copy all the returned packages, the C<dist> tarball,
|
||
into a directory which you will upload.
|
||
|
||
=item 5
|
||
|
||
Test install those packages on machines.
|
||
|
||
I also inspect them with the following commands:
|
||
|
||
System| RedHat Debian
|
||
Action |
|
||
--------------+------------------------------------------------
|
||
List of files | rpm -qpl *.rpm dpkg-deb -c *.deb
|
||
General data | rpm -qpi *.rpm dpkg-deb -e *.deb
|
||
|
||
=back
|
||
|
||
=head1 EXAMPLE AUTOMAKE FILES
|
||
|
||
TODO
|
||
|
||
=head1 AUTHOR
|
||
|
||
Written by Dave Benson <daveb@ffem.org>.
|
||
|
||
=cut
|