#!/usr/bin/perl =head1 NAME cabal2rpm - converts Haskell Cabal descriptions into RPM specs =cut # Copyright (C) 2005--07 Thomas G. Moertel. All rights reserved. # Licensed under the terms of the GNU General Public License, version # 2 or greater. See docs below. use warnings; use strict; our $VERSION = "0.20_08"; =head1 SYNOPSIS tar zxf package.tar.gz cabal2rpm package/Package.cabal > package.spec # copy tarball into ~/rpm/SOURCES here rpmbuild -ba package.spec =cut use Data::Dumper; use Text::Wrap; $Data::Dumper::Terse = 1; $Data::Dumper::Indent = 0; #============================================================================== # main #============================================================================== # dump template and exit if we are asked if (@ARGV && $ARGV[0] =~ "--?template") { print while ; exit 0; } # extract user-supplied params on the command-line args my @params = grep /=/, @ARGV; @ARGV = grep !/=/, @ARGV; # show manual page if called incorrectly unless (@ARGV) { print STDERR "Usage: cabal2rpm package.cabal [name=value...] > package.spec\n", "For complete documetion run this command:\n", "perldoc $0\n"; exit 1; } # parse the cabal spec file into stanzas my @stanzas = map para2stanza(), do { local $/ = ''; <> }; # set up a series of environments to hold package information: # cabal2rpm_vars overrides params # params overrides the .cabal file # the .cabal file overrides defaults my $cabal2rpm_vars = { cabal2rpm_version => $VERSION, cabal2rpm_rundate => strip(`date "+%a %b %d %Y"`) }; my $params = { map split(/=/, $_, 2), @params }; my $defaults = { ghc_version => default_ghc_version(), tarballroot => '@@lcname,name@@-@@version@@', buildrequires => '%{ghcver}', rpm_name => 'haskell-@@lcname@@', }; # lookup tables are listed in order of decreasing precedence my $eval_env = [ $cabal2rpm_vars, $params, @stanzas, $defaults ]; # set up some sane defaults for unspecified parameters $defaults->{command_line_flags} = do { my @clf = map Dumper($_), @params; "@clf" }; $defaults->{lcname} = lc lookup( $eval_env, "name" ); $defaults->{source} = lookup( $eval_env, "package-url" ) || '@@lcname@@-@@version@@.tar.gz'; $defaults->{ghc_version_tag} = "ghc" . do { local $_ = lookup( $eval_env, "ghc_version" ); tr/-a-z0-9//cd; $_ }; $defaults->{rpm_version} = make_rpm_version( lookup( $eval_env, "version" ) ); # special-case requires and provides params so we can drop # them directly into the spec file header for my $tag (qw(Requires Provides)) { for (lookup( $eval_env, lc $tag )) { $params->{lc $tag} = "$tag: $_" if defined $_; } } # wrap description to fill a normal screen width $params->{description} = fill("", "", eval_rule($eval_env, 'description,synopsis,summary,name') ); # read RPM spec template from DATA section of source, # expand it, and print out the results my $template = do { local $/; }; print expand_template( $eval_env, $template ); # Done! #============================================================================== # helpers #============================================================================== sub para2stanza { s/ ^(\s+)\.(\s*)$ /$1____nEwLiNe____/xmg; s/ $ \s* ^ \s+ / /xmg; # join continued lines return { map /^(.+?\s*):\s*(.*)/g ? (lc $1, fix_newlines($2)) : (), split /^/ }; } sub fix_newlines { local $_ = shift; s/\s*____nEwLiNe____\s?/\n\n/g; $_; } sub make_rpm_version { # rpm does not allow "-" in versions local $_ = shift; tr/-/_/ if defined $_; $_; } sub expand_template { my ($env, $template) = @_; 1 while $template =~ s/\@\@([^\@]+)\@\@/ eval_rule->($env,$1) /eg; return $template; } sub eval_rule { my ($env, $rule) = @_; my ($identifier_spec, $default) = split /\|/, $rule, 2; my $binding; for my $identifier (split /,/, $identifier_spec) { my $binding = lookup($env, $identifier); return $binding if defined $binding; } unless (defined $default) { print STDERR "cabal2rpm: warning - ", '@@', $identifier_spec, '@@', " expands to undefined\n"; } return defined $default ? $default : "???"; } sub lookup { my ($env, $identifier) = @_; my $binding; for (@$env) { $binding = $_->{$identifier}; last if defined $binding; } return $binding; } sub strip { local $_ = shift; s/^\s+//; s/\s+$//s; return $_; } sub default_ghc_version { local $_ = strip(`ghc -V` || "6.2.2"); s/.*\s+//; # strip all but version, which is last word return $_; } =head1 DESCRIPTION This tool converts Haskell Cabal package-descriptions (C<.cabal> files) into RPM spec files. It makes reasonable guesses at what should go in the specs, but because "Cabalized" tarballs presently do not have a standardized layout and naming conventions, you may need to make some adjustments. The syntax for using B is as follows: B< cabal2rpm> I.cabal [I=I...] E I.spec The resulting RPM spec file can be fed to B to build binary packages. B All packages built by B are targeted to a specific release of GHC from the Fedora Haskell project. Thus if you build Cabal for GHC 6.2.2, you will end up with the following RPMs: cabal-ghc622-0.5-1.i386.rpm cabal-ghc622-debuginfo-0.5-1.i386.rpm cabal-ghc622-0.5-1.src.rpm =head2 Setting template variables The optional name-value pairs given on the command line are used to define or override variables used during the expansion of the RPM-spec template. (See also L.) You may want to override the default bindings for these commonly used variables. =over 4 =item rpm_name rpm_name="mypackage" Name to give the RPM package. You might need to change this if the capitalization of the Haskell package differs from the RPM name you want. (RPM names are usually all lowercase.) The default value is the prefix "haskell-" and then the lower-case version of the Cabal package's name. =item rpm_version rpm_version="1.2" The version to give the RPM. The default value is the Cabal package's version with all dashes converted to underscores. =item rpm_release rpm_release="1.fc3" The release to give the RPM. The default value is "1". =item source source="http://my.example.com/path/to/mypackage-1.2.tar.gz" The location of the source tarball. In RPM specs, this is often a URL that ends with the source tarball, but it can also be just the tarball's name. By default it is the Cabal package's C or, if that does not exist, C<@@lcname@@-@@version@@.tar.gz> where C<@@lcname@@> expands to the lower-case version of the Cabal package's name and C<@@version@@> expands to the Cabal package's version. =item tarballroot tarballroot="mypackage" Root directory that results from expanding the package tarball. The default value is C<"@@lcname,name@@-@@version@@">, where C is the lowercase version of the Cabal package's name. =item docs docs="README Changes" Any documentation files that ought to be copied from the expanded tarball into the RPM's documentation directory. =back =head2 How template expansion works Attached to the end of this Perl program is an RPM-spec template. (You can see the template by giving the command, "B --template".) Sprinkled throughout it are variable-expansion sites. To generate the RPM spec, all of the sites are expanded until they can be expanded no further. The resulting string is the final RPM spec. The variables used in expansion are set to default values extracted from the Cabal package description that you supply and environmental conditions such as the version of GHC you are using. The default values are then overridden my your command-line bindings. Variable expansion in the template works like this. Given the following expression site, @@x1,x2,x3...|alt@@ the evaluator will first lookup I and, if it exists, substitute its value for the site and stop. Otherwise, it will continue on with I, I, and so on until it finds a value to use. If no such value exists and there is an C<|alt> clause in the expansion site, C will be used as the site's substitution; otherwise C will be used and the evaluator will emit a warning. =head1 LICENSE 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. =head1 AUTHOR Tom Moertel (tom@moertel.com) http://community.moertel.com/ 2007-09-07 =cut #============================================================================== # rpm spec-file template #============================================================================== __DATA__ # RPM spec file for @@name@@ -*-rpm-spec-*- # Generated automatically by cabal2rpm @@cabal2rpm_version@@ on @@cabal2rpm_rundate@@ # Extra flags: @@command_line_flags@@ # Report cabal2rpm bugs to Tom Moertel at tom-cabal2rpm@moertel.com %{!?ghc_version: %{expand: %%define ghc_version %(ghc -V | sed 's/.* //')}} %{!?ghcver: %{expand: %%define ghcver ghc%(echo %ghc_version | tr -d .)}} %define ghclibdir %{_libdir}/ghc-%{ghc_version} %define ghcpkg_inst ghc-pkg-%{ghc_version} -u -g -i %define pkgstore %{ghclibdir}/%{name}-%{version}-pkg %define pkgscripts %{pkgstore}/pkg-scripts Summary: @@summary,synopsis,name@@ Name: @@rpm_name,name@@-%{ghcver} Version: @@rpm_version@@ Release: @@rpm_release|1@@ License: @@license@@ Group: Development/Libraries URL: @@homepage@@ Source0: @@source@@ BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root BuildRequires: @@buildrequires@@ Requires(post,preun): %{_bindir}/ghc-pkg-%{ghc_version} @@requires|@@ @@provides|@@ %description @@description@@ %prep %setup -q -n @@tarballroot@@ %build setup=Setup.lhs [ -f $setup ] || setup=Setup.hs [ -f $setup ] || { echo "No Setup in package root! Aborting."; exit 1; } ghc-%{ghc_version} @@ghcflags|@@ --make -o Setup -package Cabal $setup ./Setup configure \ --prefix=%{pkgstore} \ --ghc --with-compiler=/usr/bin/ghc-%{ghc_version} \ --with-hc-pkg=/usr/bin/ghc-pkg-%{ghc_version} ./Setup build %install rm -rf $RPM_BUILD_ROOT ./Setup copy --copy-prefix=%{buildroot}%{pkgstore} mkdir -p %{buildroot}%{pkgstore} mkdir -p %{buildroot}%{pkgscripts} ./Setup register --gen-script ./Setup unregister --gen-script mv register.sh unregister.sh %{buildroot}%{pkgscripts} %clean rm -rf $RPM_BUILD_ROOT # SCRIPTS # # Scripts for registering/unregistering w/ GHC. Note that # the scripts are called in the following order during an upgrade: # # %pre for new version of package being installed # ... (all new files are installed) # %post for new version of package being installed # # %preun for old version of package being removed # ... (all old files are removed) # %postun for old version of package being removed %pre # in case we're upgrading to a library having the same Cabal # name+version as the currently installed library, we need to # unregister the old library first, so that the register script in # %post may succeed. (note that this command runs *before* the new # package's files are installed, and thus will execute the *previous* # version's unregister script, if the script exists in the same location # as the about-to-be-installed library's script) [ "$1" = 2 ] && %{pkgscripts}/unregister.sh >& /dev/null || : %post %{pkgscripts}/register.sh >& /dev/null %preun # always unregister the library before we remove its files # (see %postun, however) %{pkgscripts}/unregister.sh >& /dev/null || : %postun # if we're upgrading, the %preun step may have unregistered # the *new* version of the library (if it had an identical # Cabal name+version, even though the RPM release differs); # therefore, we must attempt to re-register it [ "$1" = 1 ] && %{pkgscripts}/register.sh >& /dev/null || : %files %defattr(-,root,root,-) %doc @@license-file|@@ @@docs|@@ %{ghclibdir}/* @@files|@@ %changelog * @@cabal2rpm_rundate@@ @@packager@@ <@@packager_email@@> - @@rpm_version@@-@@rpm_release|1@@ - Spec created by cabal2rpm @@cabal2rpm_version@@