App-Magpie/lib/App/Magpie/Action/FixSpec.pm
#
# This file is part of App-Magpie
#
# This software is copyright (c) 2011 by Jerome Quelin.
#
# This is free software; you can redistribute it and/or modify it under
# the same terms as the Perl 5 programming language system itself.
#
use 5.012;
use strict;
use warnings;
package App::Magpie::Action::FixSpec;
# ABSTRACT: fixspec command implementation
$App::Magpie::Action::FixSpec::VERSION = '2.010';
use Moose;
use Parse::CPAN::Meta 1.4401; # load_file
use Path::Tiny;
use Text::Padding;
with 'App::Magpie::Role::Logging';
with 'App::Magpie::Role::RunningCommand';
sub run {
my ($self) = @_;
# check if there's a spec file to update...
my $specdir = path("SPECS");
-e $specdir or $self->log_fatal("cannot find a SPECS directory, aborting");
my @specfiles =
grep { /\.spec$/ }
$specdir->children;
scalar(@specfiles) > 0
or $self->log_fatal("could not find a spec file, aborting");
scalar(@specfiles) < 2
or $self->log_fatal("more than one spec file found, aborting");
my $specfile = shift @specfiles;
my $spec = $specfile->slurp;
$self->log( "fixing $specfile" );
# extracting tarball
$self->log_debug( "removing previous BUILD directory" );
path( "BUILD" )->remove_tree( { safe => 0 } );
$self->log_debug( "extracting tarball" );
$self->run_command( "bm -lp" ); # first just extract tarball
my $distdir = path( glob "BUILD/*" );
my $has_makefile_pl = $distdir->child("Makefile.PL")->exists;
my $has_build_pl = $distdir->child("Build.PL")->exists;
if ( $spec =~ /Makefile.PL/ && !$has_makefile_pl ) {
$self->log( "module converted to use Build.PL only" );
$spec =~ s{%\{?__perl\}? Makefile.PL INSTALLDIRS=vendor}{%__perl Build.PL --installdirs=vendor};
$spec =~ s{^%?make$}{./Build CFLAGS="%{optflags}"}m;
$spec =~ s{%?make test}{./Build test};
$spec =~ s{%makeinstall_std}{./Build install --destdir=%{buildroot}};
# writing down new spec file
$self->log_debug( "writing updated spec file" );
my $fh = $specfile->openw;
$fh->print($spec);
$fh->close;
}
if ( $spec =~ /Build.PL/ && !$has_build_pl ) {
$self->log( "module converted to use Makefile.PL only" );
$spec =~ s{%\{?__perl\}? Build.PL (--)?installdirs=vendor}{%__perl Makefile.PL INSTALLDIRS=vendor};
$spec =~ s{./Build( CFLAGS="%\{optflags\}")?$}{%make}m;
$spec =~ s{./Build test}{%make test};
$spec =~ s{./Build install.*}{%makeinstall_std};
# writing down new spec file
$self->log_debug( "writing updated spec file" );
my $fh = $specfile->openw;
$fh->print($spec);
$fh->close;
}
$self->log_debug( "generating MYMETA" );
path( "BUILD" )->remove_tree( { safe => 0 } );
$self->run_command( "bm -lc" ); # run -c to make sure MYMETA is generated
$distdir = path( glob "BUILD/*" );
my $metafile;
foreach my $meta ( "MYMETA.json", "MYMETA.yml", "META.json", "META.yml" ) {
next unless -e $distdir->child( $meta );
$metafile = $distdir->child( $meta );
last;
}
# cleaning spec file
$self->log_debug( "adding %{?perl_default_filter}" );
$spec =~ s/^Name:/%{?perl_default_filter}\n\nName:/msi if $spec !~ /perl_default_filter/;
$self->log_debug( "removing mandriva macros" );
$spec =~ s/^%if %\{mdkversion\}.*?^%endif$//msi;
$self->log_debug( "removing buildroot, not needed anymore" );
$spec =~ s/^buildroot:.*\n//mi;
$self->log_debug( "trimming empty end lines" );
$spec =~ s/\n+\z//;
# splitting up build-/requires
$self->log_debug( "splitting (build-)requires one per line" );
$spec =~ s{^((?:build)?requires):\s*(.*)$}{
my $key = $1; my $value = $2; my $str;
$str .= $key . ": $1\n" while $value =~ m{(\S+(\s*[>=<]+\s*\S+)?)\s*}g;
$str;
}mgie;
# fetching buildrequires from meta file
if ( defined $metafile ) {
$self->log_debug( "using META file to get buildrequires" );
$spec =~ s{^buildrequires:\s*perl\(.*\).*$}{}mgi;
my $meta = Parse::CPAN::Meta->load_file( $metafile );
my %br_from_meta;
if ( $meta->{"meta-spec"}{version} < 2 ) {
%br_from_meta = (
%{ $meta->{configure_requires} // {} },
%{ $meta->{build_requires} // {} },
%{ $meta->{test_requires} // {} },
%{ $meta->{requires} // {} },
);
} else {
my $prereqs = $meta->{prereqs};
%br_from_meta = (
%{ $prereqs->{configure}{requires} // {} },
%{ $prereqs->{build}{requires} // {} },
%{ $prereqs->{test}{requires} // {} },
%{ $prereqs->{runtime}{requires} // {} },
);
}
my $rpmbr;
foreach my $br ( sort keys %br_from_meta ) {
next if $br eq 'perl';
my $version = $br_from_meta{$br};
$rpmbr .= "BuildRequires: perl($br)";
if ( $version != 0 ) {
my $rpmvers = qx{ rpm -E "%perl_convert_version $version" };
$rpmbr .= " >= $rpmvers";
}
$rpmbr .= "\n";
}
if ( $spec =~ /buildrequires/i ) {
$spec =~ s{^(buildrequires:.*)$}{$rpmbr$1}mi;
} elsif ( $spec =~ /buildarch/i ) {
$spec =~ s{^(buildarch.*)$}{$rpmbr$1}mi;
} else {
$spec =~ s{^(source.*)$}{$1\n\n$rpmbr}mi;
}
}
$spec =~ s{^((?:build)?requires:.*)\n+}{$1\n}mgi;
# lining up / padding
my $pad = Text::Padding->new;
$self->log_debug( "lining up categories" );
$spec =~
s{^(Name|Version|Release|Epoch|Summary|License|Group|Url|Source\d*|Patch\d*|BuildArch|Requires|Obsoletes|Provides):\s*}
{ $pad->left( ucfirst(lc($1)) . ":", 12 ) }mgie;
$spec =~ s{^source:}{Source0:}mgi;
$spec =~ s{^patch:}{Patch0:}mgi;
$spec =~ s{^buildrequires:}{BuildRequires:}mgi;
$spec =~ s{^buildarch:}{BuildArch:}mgi;
# Module::Build::Tiny compatibility
$self->log_debug( "adding Module::Build::Tiny compatibility" );
$spec =~ s{Build.PL installdirs=vendor}{Build.PL --installdirs=vendor};
$spec =~ s{Build install destdir=%\{buildroot\}}{Build install --destdir=%{buildroot}};
# removing default %defattr
$self->log_debug( "removing default %defattr" );
$spec =~ s{^%defattr\(-,root,root\)\n?}{}mgi;
# removing default %clean section
$self->log_debug( "removing default %clean" );
$spec =~ s{%clean\s*\n(.* && )?(rm|%\{?_?_?rm\}?)\s+-rf\s+(%\{?buildroot\}?|\$buildroot)\s*\n?}{}i;
# removing %buildroot cleaning in %install
$self->log_debug( "removing %buildroot cleaning in %install" );
$spec =~ s{%install\s*\n(.* && )?(rm|%\{?_?_?rm\}?)\s+-rf\s+(%\{?buildroot\}?|\$buildroot)\s*\n?}{%install\n}i;
# updating %doc
$self->log_debug( "fetching documentation files" );
my @docfiles =
sort
grep {
( /^[A-Z]+$/ && ! /^MANIFEST/ ) ||
m{^(Change(s|log)|MYMETA.yml|META.(json|yml)|e[gx]|(ex|s)amples?|demos?)$}i
}
map { $_->basename }
$distdir->children;
if ( @docfiles ) {
$self->log_debug( "found: @docfiles" );
if ( $spec =~ /^%doc (.*)/m ) {
$self->log_debug( "updating %doc" );
$spec =~ s/^(%doc .*)$/%doc @docfiles/m;
} else {
$self->log_debug( "adding a %doc" );
$spec =~ s/^%files$/%files\n%doc @docfiles/m;
}
} else {
$self->log_debug( "no documentation found" );
}
# other things that might be worth checking...
# perl-version instead of perl(version)
# url before source
# source: http://www.cpan.org/modules/by-module/Algorithm/
# Url: http://search.cpan.org/dist/%{upstream_name}
# license
# rpmlint ?
# sorting buildrequires
# %description\n\n
# $RPM_BUILD_ROOT
# %{upstream_name} module for perl within summary
# "perl module" within summary
# "module for perl" within summary
# %{upstream_name} within description
# requires with buildrequires
# requires perl
# no %check
# %upstream instead of %{upstream...}
# perl-devel alongside noarch
# within %install et %clean: [ "%{buildroot}" != "/" ] && rm -rf %{buildroot}
# "no summary found"
# "no description found"
# make test without %check
# %modprefix
# removing extra newlines
$spec =~ s{\n{3,}}{\n\n}g;
# writing down new spec file
$self->log_debug( "writing updated spec file" );
my $fh = $specfile->openw;
$fh->print($spec);
$fh->close;
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
App::Magpie::Action::FixSpec - fixspec command implementation
=head1 VERSION
version 2.010
=head1 SYNOPSIS
my $fixspec = App::Magpie::Action::FixSpec->new;
$fixspec->run;
=head1 DESCRIPTION
This module implements the C<fixspec> action. It's in a module of its
own to be able to be C<require>-d without loading all other actions.
=head1 METHODS
=head2 run
$fixspec->run;
Fix the spec file to match a set of rules. Make sure buildrequires are
correct.
=head1 AUTHOR
Jerome Quelin <jquelin@gmail.com>
=head1 COPYRIGHT AND LICENSE
This software is copyright (c) 2011 by Jerome Quelin.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=cut