| #! /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 |