The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package CPAN::Packager::Builder::RPM::Spec;

# stealed from cpanflute2 because cpanflute2 isn't updated all recently.
# so I just copied it and patched.
# cpanflute2 code looks ugly, so we need to refactor it later.

use Mouse;
use File::Basename;
use File::Copy qw(copy);
use Archive::Tar;
use File::Temp qw(tempdir);
use URI::Escape qw(uri_escape);
use Cwd;
use Parse::CPAN::Meta qw(Load);
use RPM::Specfile;
use CPAN::DistnameInfo;
use Archive::Zip;
use Log::Log4perl qw(:easy);
use CPAN::Packager::MetaAnalyzer;
use CPAN::Packager::ListUtil qw(any);

has 'meta_analyzer' => (
    is      => 'rw',
    default => sub {
        CPAN::Packager::MetaAnalyzer->new;
    }
);

sub build {
    my ( $self, $args, $fullname, $no_depends ) = @_;

    # Setup some defaults
    my %defaults;

    my $orig_cmd = join( " ", $0, $fullname );

    $defaults{'outdir'}      = './';
    $defaults{'tmpdir'}      = '/tmp';
    $defaults{'release'}     = 1;
    $defaults{'installdirs'} = "";
    {
        my ( $username, $fullname ) = ( getpwuid($<) )[ 0, 6 ];
        $fullname = ( split /,/, $fullname )[0];    #/
        $defaults{'email'} = $fullname ? $fullname . ' ' : '';
        $defaults{'email'} .= '<';
        $defaults{'email'} .= $ENV{REPLYTO} || $username . '@redhat.com';
        $defaults{'email'} .= '>';
    }

    # Parse command line options
    my %options = %$args;
    my $content;

    my @requires       = @{ $options{'requires'}       || [] };
    my @build_requires = @{ $options{'build-requires'} || [] };

    if ( $options{'just-spec'} and $options{'buildall'} ) {
        print "Error: just-spec and build cannot both be specified.\n";
        exit(1);
    }

    #
    # Make sure filename was provided
    die 'file name must be provided' unless $fullname;

    #
    # If we were given a description file, make sure it exists
    if ( $options{'descfile'} ) {
        if ( !-e $options{'descfile'} ) {
            FATAL("Description file given does not exist!");
            LOGEXIT("File:  ${options{'descfile'}}");
        }
        if ( !-r $options{'descfile'} ) {
            FATAL("Description file given is not readable!");
            LOGEXIT("File:  ${options{'descfile'}}");
        }
    }

    #
    # Overide defaults if necessary, otherwise keep them.
    my $tarball          = basename($fullname);
    my $create           = $options{create} || '';
    my $email            = $options{email} || $defaults{'email'};
    my $requires         = $options{requires} || [];
    my $buildrequires    = $options{buildrequires} || [];
    my $outdir           = $options{outdir} || $defaults{'outdir'};
    my $tmpdir           = $options{tmpdir} || $defaults{'tmpdir'};
    my $noarch           = $options{noarch} || '';
    my $plat_perl_reqs   = $options{'noperlreqs'} ? 0 : 1;
    my $release          = $options{'release'} || $defaults{'release'};
    my $build_switch     = 's';
    my $use_module_build = 0;
    my @docs             = ();

    my $patchdir = $options{patchdir};

    # FIXME: this line breaks supporting Patch RPM spec field.
    $tmpdir = tempdir( CLEANUP => 1, DIR => $tmpdir );

    #
    # Set build arch - this is needed to find out where
    # the binary rpm was placed, and copy it back to the
    # current working directory.
    my $build_arch;
    if ( $options{'arch'} ) {
        $build_arch = $options{'arch'};
    }
    elsif ( $options{'noarch'} ) {
        $build_arch = 'noarch';
    }
    else {
        $build_arch = get_default_build_arch();
        if ( $build_arch eq '' ) {
            LOGEXIT("Could not get default build arch!");
        }
    }

    $build_switch = 'a' if ( defined( $options{'buildall'} ) );

    my $local_tarball = $tarball;
    $local_tarball =~ s/::/-/g;
    my $distro    = CPAN::DistnameInfo->new($local_tarball);
    my $dist_name = $distro->dist;
    $dist_name =~ s/-/::/g;
    my $version = $distro->version;
    my $name    = $options{name} || $dist_name;
    my $ver     = $options{version} || $version;

    my $tarball_top_dir = "$name-%{version}";

    die "Module name/version not parsable from $tarball"
        unless $name and $ver;

    $name =~ s/::/-/g;

    copy( $fullname, $tmpdir )
        or die "copy $fullname: $!";
    utime( ( stat($fullname) )[ 8, 9 ], "$tmpdir/$tarball" );

    my ( @files, $zip );

    if ( $distro->extension eq 'zip' ) {
        $zip   = Archive::Zip::Archive->new("$tmpdir/$tarball");
        @files = $zip->memberNames;
    }
    else {
        @files = Archive::Tar->list_archive("$tmpdir/$tarball");
    }

    if (@files) {
        $use_module_build = 1 if grep {/Build\.PL$/} @files;
        $use_module_build = 0 if grep {/Makefile\.PL$/} @files;

        if ( not exists $options{noarch} ) {
            $noarch = 1;
            $noarch = 0 if grep {/\.(xs|c|cc|C)$/} @files;
        }

        my %prefixes;
        for my $file (@files) {
            my @path_components = split m[/], $file;
            $prefixes{ $path_components[0] }++;

            if ( $path_components[-1] eq 'META.yml' ) {
                my $contents;

                if ( $distro->extension eq 'zip' ) {
                    my $member = $zip->memberNamed($file);
                    $contents = $member->contents;
                }
                else {
                    my $tar = new Archive::Tar;
                    $tar->read( "$tmpdir/$tarball", 1 );
                    $contents = $tar->get_content($file);
                }

                my $meta;
                eval {
                    $meta
                        = $self->meta_analyzer->parse_meta_from_content(
                        $contents);
                };
                unless ($@) {

                    my %build_requires_dependencies = (
                        %{ $meta->{build_requires} || {} },
                        %{ $meta->{test_requires}  || {} }
                    );
                    while ( my ( $mod, $ver )
                        = each %build_requires_dependencies )
                    {
                        unless ( $self->is_no_depends( $mod, $no_depends ) ) {
                            push @build_requires, [ "perl($mod)", $ver ];
                        }
                    }

                    my %requires_dependencies
                        = ( %{ $meta->{requires} || {} } );
                    while ( my ( $mod, $ver ) = each %requires_dependencies )
                    {
                        unless ( $self->is_no_depends( $mod, $no_depends ) ) {
                            push @requires, [ "perl($mod)", $ver ];
                        }
                    }
                }
            }

            # find docs
            if ($file =~ m,^${name}-${ver}/(
          authors?|
          change(log|s)|
          credits|
          copy(ing|right)|
          licen[cs]e|
          readme|
          todo
          )$,ix
                )
            {
                push( @docs, $1 );
            }
        }

        if ( scalar keys %prefixes == 1 ) {
            ($tarball_top_dir) = keys %prefixes;
            $tarball_top_dir =~ s/$ver/%{version}/;
        }
    }

    #
    # Get patches copied into place
    my $patchfile  = '';
    my @patchfiles = ();
    my $patch      = '';
    if ( $options{patch} ) {
        for $patch ( @{ $options{'patch'} } ) {    ## no critic
            copy( $patch, $patchdir ) or die "copy ${patch}: $!";
            utime(
                ( stat( $options{patch} ) )[ 8, 9 ],
                "$patchdir/" . basename( $options{patch} )
            );
            push @patchfiles, ( basename($patch) );
        }
    }

    #
    # Copy install scriptlets if defined to the tmp directory
    foreach my $scriptlet (qw(pre post preun postun)) {
        if ( defined( $options{$scriptlet} ) ) {
            copy( $options{$scriptlet}, $tmpdir )
                or die "copy ${options{${scriptlet}}}: $!";
            my ( $atime, $mtime ) = ( stat( $options{$scriptlet} ) )[ 8, 9 ];
            $options{$scriptlet}
                = "${tmpdir}/" . basename( $options{$scriptlet} );
            utime( $atime, $mtime, $options{$scriptlet} );
        }
    }

    my $spec = new RPM::Specfile;

    # some basic spec fields
    if ( $options{pkg_name} ) {
        $spec->name( $options{pkg_name} );
    }
    else {
        $spec->name("perl-$name");
    }
    $spec->version($ver);
    $spec->release($release);
    $spec->epoch( $options{epoch} );
    $spec->summary("$name Perl module");
    $spec->group("Development/Libraries");
    $spec->license('GPL or Artistic');
    $spec->packager( $options{'packager'} ) if $options{'packager'};
    my $clver = defined( $options{epoch} ) ? "$options{epoch}:" : '';
    $clver .= "$ver-$release";
    $spec->add_changelog_entry( $email,
        "Specfile autogenerated with command '$orig_cmd'", $clver );

    for my $req (@requires) {
        $spec->push_require($req);
    }

    for my $req (@build_requires) {
        $spec->push_buildrequire($req);
    }

    # Setup spec description.  Defaults to summary, unless
    # description file provided:
    if ( $options{'descfile'} ) {
        $spec->description( read_desc_file( $options{'descfile'} ) );
    }
    else {
        $spec->description('%{summary}.');
    }

    #
    # Setup build architecture.
    $spec->buildarch( $options{'arch'} ) if ( defined( $options{'arch'} ) );
    $spec->buildarch('noarch') if $noarch;

    #
    # Use perl requirements by default (onward and upward...).
    if ($plat_perl_reqs) {
        $spec->push_require(
            q|perl(:MODULE_COMPAT_%(eval "`%{__perl} -V:version`"; echo $version))|
        );
    }

    $spec->push_source($tarball);
    foreach $patchfile (@patchfiles) {    ## no critic
        $spec->push_patch($patchfile);
    }

    # make a URL that can actually possibly take you to the right place
    $spec->url(
        sprintf( 'http://search.cpan.org/dist/%s/', uri_escape($name) ) );

    # now we get into the multiline tags.  stolen mostly verbatim from
    # cpanflute1
    $spec->prep("%setup -q -n $tarball_top_dir $create\n");
    $spec->file_param('-f %{name}-%{version}-%{release}-filelist');
    $spec->push_file('%defattr(-,root,root,-)');
    $spec->push_file( '%doc ' . join( ' ', sort @docs ) ) if @docs;

    if ( $options{test} ) {
        if ($use_module_build) {
            $spec->check("perl Build.PL");
            $spec->check("./Build test");
        }
        else {
            $spec->check("perl Makefile.PL");
            $spec->check("make test");
        }
    }

    my $installdirs = "";

    my $makefile_pl
        = qq{CFLAGS="\$RPM_OPT_FLAGS" %{__perl} Makefile.PL < /dev/null};
    my $make_install
        = qq{make pure_install PERL_INSTALL_ROOT=\$RPM_BUILD_ROOT};
    my $make;
    if ($use_module_build) {
        if ( $options{'installdirs'} ) {
            $installdirs = "--installdirs $options{'installdirs'}";
        }

        $makefile_pl
            = qq{CFLAGS="\$RPM_OPT_FLAGS" %{__perl} Build.PL destdir=\$RPM_BUILD_ROOT $installdirs < /dev/null};
        $make_install
            = qq{./Build pure_install PERL_INSTALL_ROOT=\$RPM_BUILD_ROOT};
        $make = "./Build OPTIMIZE=\"\$RPM_OPT_FLAGS\"";
    }
    else {
        if ( $options{'installdirs'} ) {
            $installdirs = "INSTALLDIRS=$options{'installdirs'}";
        }

        $makefile_pl
            = qq{CFLAGS="\$RPM_OPT_FLAGS" %{__perl} Makefile.PL $installdirs};
        $make = "make %{?_smp_mflags} OPTIMIZE=\"\$RPM_OPT_FLAGS\"";
    }

    $spec->build(<<EOB);
$makefile_pl
$make
EOB

    $spec->clean('rm -rf $RPM_BUILD_ROOT');
    my $usr_local_sect = "";
    if ( $options{'use-usr-local'} ) {
        $usr_local_sect = q{
for dir in bin share/doc share/man; do
  if [ -d $RPM_BUILD_ROOT/usr/$dir ]; then
    mkdir -p $RPM_BUILD_ROOT/usr/local/$dir
    mv $RPM_BUILD_ROOT/usr/$dir/* $RPM_BUILD_ROOT/usr/local/$dir/
    rm -Rf $RPM_BUILD_ROOT/usr/$dir
  fi
done
  }
    }

    my $inst = q{
rm -rf $RPM_BUILD_ROOT

$make_install

find $RPM_BUILD_ROOT -type f -a \( -name perllocal.pod -o -name .packlist \
  -o \( -name '*.bs' -a -empty \) \) -exec rm -f {} ';'
find $RPM_BUILD_ROOT -type d -depth -exec rmdir {} 2>/dev/null ';'
chmod -R u+w $RPM_BUILD_ROOT/*

for brp in %{_prefix}/lib/rpm/%{_build_vendor}/brp-compress \
  %{_prefix}/lib/rpm/brp-compress
do
  [ -x $brp ] && $brp && break
done

$usr_local_sect
find $RPM_BUILD_ROOT -type f \
| sed "s@^$RPM_BUILD_ROOT@@g" \
> %{name}-%{version}-%{release}-filelist

eval `%{__perl} -V:archname -V:installsitelib -V:installvendorlib -V:installprivlib`
for d in $installsitelib $installvendorlib $installprivlib; do
  [ -z "$d" -o "$d" = "UNKNOWN" -o ! -d "$RPM_BUILD_ROOT$d" ] && continue
  find $RPM_BUILD_ROOT$d/* -type d \
  | grep -v "/$archname\(/auto\)\?$" \
  | sed "s@^$RPM_BUILD_ROOT@%dir @g" \
  >> %{name}-%{version}-%{release}-filelist
done

if [ "$(cat %{name}-%{version}-%{release}-filelist)X" = "X" ] ; then
    echo "ERROR: EMPTY FILE LIST"
    exit 1
fi
};

    $inst =~ s/\$make_install/$make_install/g;
    $inst =~ s/\$usr_local_sect/$usr_local_sect/g;
    $inst =~ s/\$options{'?(.*?)'?}/$options{$1} || ''/ge;

    $spec->install($inst);

    #
    # Add the install scriptlets if defined...
    foreach my $scriptlet (qw(pre post preun postun)) {
        if ( defined( $options{$scriptlet} ) ) {
            open( SCRIPTLET, "<${options{${scriptlet}}}" )    ## no critic
                || die
                "Could not open scriptlet ${options{${scriptlet}}} for reading!";
            local $/;    # enable slurp mode.
            $content = <SCRIPTLET>;
            close(SCRIPTLET);
            $spec->$scriptlet($content);
        }
    }

    if ( $options{'just-spec'} ) {
        return $spec->generate_specfile;
    }

    # write the spec file.  create some macros.
    $spec->write_specfile("$tmpdir/perl-$name.spec");

    open FH, ">$tmpdir/macros"    ## no critic
        or die "Can't create $tmpdir/macros: $!";

    print FH qq{
%_topdir         $tmpdir
%_builddir       %{_topdir}
%_rpmdir         $outdir
%_sourcedir      %{_topdir}
%_specdir        %{_topdir}
%_srcrpmdir      $outdir
%_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm
};

    close FH;

    open FH, ">$tmpdir/rpmrc"    ## no critic
        or die "Can't create $tmpdir/rpmrc: $!";

    my $macrofiles = qx(rpm --showrc | grep ^macrofiles | cut -f2- -d:);
    chomp $macrofiles;

    print FH qq{
include: /usr/lib/rpm/rpmrc
macrofiles: $macrofiles:$tmpdir/macros
};
    close FH;

    # Build the build command
    my @cmd;
    push @cmd, 'rpmbuild';
    push @cmd, '--rcfile', "$tmpdir/rpmrc";
    push @cmd, "-b${build_switch}";
    push @cmd, '--rmsource';
    push @cmd, '--rmspec';
    push @cmd, '--clean', "$tmpdir/perl-$name.spec";
    push @cmd, "--sign" if $options{sign};

    # perform the build, die on error
    my $retval = system(@cmd);
    $retval = $? >> 8;
    if ( $retval != 0 ) {
        die "RPM building failed!\n";
    }

    # clean up macros file
    unlink "$tmpdir/rpmrc", "$tmpdir/macros";

    # if we did a build all, lets move the rpms into our current
    # directory
    my $bin_rpm = "./perl-${name}-${ver}-${release}.${build_arch}.rpm";

    my $spec_content = $spec->generate_specfile();
    return $spec_content;
}

sub get_default_build_arch {
    my $build_arch = qx(rpm --eval %{_build_arch});
    chomp $build_arch;

    return $build_arch;
}

#
# Read in description file and return its text.
sub read_desc_file {
    my $file = shift;

    open FILE, "<$file"    ## no critic
        or die "Can't open $file for reading: $!";

    local $/ = undef;
    my $ret = <FILE>;      ## no critic

    close FILE;
    return $ret;
}

sub is_no_depends {
    my ( $self, $mod, $no_depends ) = @_;
    return any { $mod eq $_ } @$no_depends;
}

1;
__END__

=head1 NAME

CPAN::Packager::Builder::RPM::Spec - RPM spec builder

=head1 SYNOPSIS


=head1 DESCRIPTION

stealed from cpanflute2 because cpanflute2 isn't updated all recently.
so I copied it and patched.

=head1 AUTHOR

Takatoshi Kitano E<lt>kitano.tk@gmail.comE<gt>

=head1 SEE ALSO

=head1 LICENSE

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

=cut