1=head1 NAME 2 3CPAN::Plugin::Specfile - Proof of concept implementation of a trivial CPAN::Plugin 4 5=head1 SYNOPSIS 6 7 # once in the cpan shell 8 o conf plugin_list push CPAN::Plugin::Specfile 9 10 # make permanent 11 o conf commit 12 13 # any time in the cpan shell to write a spec file 14 test Acme::Meta 15 16 # disable 17 # if it is the last in plugin_list: 18 o conf plugin_list pop 19 # otherwise, determine the index to splice: 20 o conf plugin_list 21 # and then use splice, e.g. to splice position 3: 22 o conf plugin_list splice 3 1 23 24=head1 DESCRIPTION 25 26Implemented as a post-test hook, this plugin writes a specfile after 27every successful test run. The content is also written to the 28terminal. 29 30As a side effect, the timestamps of the written specfiles reflect the 31linear order of all dependencies. 32 33B<WARNING:> This code is just a small demo how to use the plugin 34system of the CPAN shell, not a full fledged spec file writer. Do not 35expect new features in this plugin. 36 37=head2 OPTIONS 38 39The target directory to store the spec files in can be set using C<dir> 40as in 41 42 o conf plugin_list push CPAN::Plugin::Specfile=dir,/tmp/specfiles-000042 43 44The default directory for this is the 45C<plugins/CPAN::Plugin::Specfile> directory in the I<cpan_home> 46directory. 47 48=head1 AUTHOR 49 50Andreas Koenig <andk@cpan.org>, Branislav Zahradnik <barney@cpan.org> 51 52=cut 53 54package CPAN::Plugin::Specfile; 55 56our $VERSION = '0.02'; 57 58use File::Path; 59use File::Spec; 60 61sub __accessor { 62 my ($class, $key) = @_; 63 no strict 'refs'; 64 *{$class . '::' . $key} = sub { 65 my $self = shift; 66 if (@_) { 67 $self->{$key} = shift; 68 } 69 return $self->{$key}; 70 }; 71} 72BEGIN { __PACKAGE__->__accessor($_) for qw(dir dir_default) } 73 74sub new { 75 my($class, @rest) = @_; 76 my $self = bless {}, $class; 77 while (my($arg,$val) = splice @rest, 0, 2) { 78 $self->$arg($val); 79 } 80 $self->dir_default(File::Spec->catdir($CPAN::Config->{cpan_home},"plugins",__PACKAGE__)); 81 $self; 82} 83 84sub post_test { 85 my $self = shift; 86 my $distribution_object = shift; 87 my $distribution = $distribution_object->pretty_id; 88 unless ($CPAN::META->has_inst("CPAN::DistnameInfo")){ 89 $CPAN::Frontend->mydie("CPAN::DistnameInfo not installed; cannot continue"); 90 } 91 my $d = CPAN::Shell->expand("Distribution",$distribution) 92 or $CPAN::Frontend->mydie("Unknowns distribution '$distribution'\n"); 93 my $build_dir = $d->{build_dir} or $CPAN::Frontend->mydie("Distribution has not been built yet, cannot proceed"); 94 my %contains = map {($_ => undef)} $d->containsmods; 95 my @m; 96 my $width = 16; 97 my $header = sub { 98 my($header,$value) = @_; 99 push @m, sprintf("%-s:%*s%s\n", $header, $width-length($header), "", $value); 100 }; 101 my $dni = CPAN::DistnameInfo->new($distribution); 102 my $dist = $dni->dist; 103 my $summary = CPAN::Shell->_guess_manpage($d,\%contains,$dist); 104 $header->("Name", "perl-$dist"); 105 my $version = $dni->version; 106 $header->("Version", $version); 107 $header->("Release", "1%{?dist}"); 108#Summary: Template processing system 109#Group: Development/Libraries 110#License: GPL+ or Artistic 111#URL: http://www.template-toolkit.org/ 112#Source0: http://search.cpan.org/CPAN/authors/id/A/AB/ABW/Template-Toolkit-%{version}.tar.gz 113#Patch0: Template-2.22-SREZIC-01.patch 114#BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) 115 for my $h_tuple 116 ([Summary => $summary], 117 [Group => "Development/Libraries"], 118 [License =>], 119 [URL =>], 120 [BuildRoot => "%{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)"], 121 [Requires => "perl(:MODULE_COMPAT_%(eval \"`%{__perl} -V:version`\"; echo \$version))"], 122 ) { 123 my($h,$v) = @$h_tuple; 124 $v = "unknown" unless defined $v; 125 $header->($h, $v); 126 } 127 $header->("Source0", sprintf( 128 "http://search.cpan.org/CPAN/authors/id/%s/%s/%s", 129 substr($distribution,0,1), 130 substr($distribution,0,2), 131 $distribution 132 )); 133 require POSIX; 134 my @xs = glob "$build_dir/*.xs"; # quick try 135 unless (@xs) { 136 require ExtUtils::Manifest; 137 my $manifest_file = "$build_dir/MANIFEST"; 138 my $manifest = ExtUtils::Manifest::maniread($manifest_file); 139 @xs = grep /\.xs$/, keys %$manifest; 140 } 141 if (! @xs) { 142 $header->('BuildArch', 'noarch'); 143 } 144 for my $k (sort keys %contains) { 145 my $m = CPAN::Shell->expand("Module",$k); 146 my $v = $contains{$k} = $m->cpan_version; 147 my $vspec = $v eq "undef" ? "" : " = $v"; 148 $header->("Provides", "perl($k)$vspec"); 149 } 150 if (my $prereq_pm = $d->{prereq_pm}) { 151 my %req; 152 for my $reqkey (keys %$prereq_pm) { 153 while (my($k,$v) = each %{$prereq_pm->{$reqkey}}) { 154 $req{$k} = $v; 155 } 156 } 157 if (-e "$build_dir/Build.PL" && ! exists $req{"Module::Build"}) { 158 $req{"Module::Build"} = 0; 159 } 160 for my $k (sort keys %req) { 161 next if $k eq "perl"; 162 my $v = $req{$k}; 163 my $vspec = defined $v && length $v && $v > 0 ? " >= $v" : ""; 164 $header->(BuildRequires => "perl($k)$vspec"); 165 next if $k =~ /^(Module::Build)$/; # MB is always only a 166 # BuildRequires; if we 167 # turn it into a 168 # Requires, then we 169 # would have to make it 170 # a BuildRequires 171 # everywhere we depend 172 # on *one* MB built 173 # module. 174 $header->(Requires => "perl($k)$vspec"); 175 } 176 } 177 push @m, "\n%define _use_internal_dependency_generator 0 178%define __find_requires %{nil} 179%define __find_provides %{nil} 180"; 181 push @m, "\n%description\n%{summary}.\n"; 182 push @m, "\n%prep\n%setup -q -n $dist-%{version}\n"; 183 if (-e "$build_dir/Build.PL") { 184 # see http://www.redhat.com/archives/rpm-list/2002-July/msg00110.html about RPM_BUILD_ROOT vs %{buildroot} 185 push @m, <<'EOF'; 186 187%build 188%{__perl} Build.PL --installdirs=vendor --libdoc installvendorman3dir 189./Build 190 191%install 192rm -rf $RPM_BUILD_ROOT 193./Build install destdir=$RPM_BUILD_ROOT create_packlist=0 194find $RPM_BUILD_ROOT -depth -type d -exec rmdir {} 2>/dev/null \; 195%{_fixperms} $RPM_BUILD_ROOT/* 196 197%check 198./Build test 199EOF 200 } elsif (-e "$build_dir/Makefile.PL") { 201 push @m, <<'EOF'; 202 203%build 204%{__perl} Makefile.PL INSTALLDIRS=vendor 205make %{?_smp_mflags} 206 207%install 208rm -rf $RPM_BUILD_ROOT 209make pure_install DESTDIR=$RPM_BUILD_ROOT 210find $RPM_BUILD_ROOT -type f -name .packlist -exec rm -f {} ';' 211find $RPM_BUILD_ROOT -depth -type d -exec rmdir {} 2>/dev/null ';' 212%{_fixperms} $RPM_BUILD_ROOT/* 213 214%check 215make test 216EOF 217 } else { 218 $CPAN::Frontend->mydie("'$distribution' has neither a Build.PL nor a Makefile.PL\n"); 219 } 220 push @m, "\n%clean\nrm -rf \$RPM_BUILD_ROOT\n"; 221 my $vendorlib = @xs ? "vendorarch" : "vendorlib"; 222 my $date = POSIX::strftime("%a %b %d %Y", gmtime); 223 my @doc = grep { -e "$build_dir/$_" } qw(README Changes); 224 my $exe_stanza = "\n"; 225 if (my $exe_files = $d->_exe_files) { 226 if (@$exe_files) { 227 $exe_stanza = "%{_mandir}/man1/*.1*\n"; 228 for my $e (@$exe_files) { 229 unless (CPAN->has_inst("File::Basename")) { 230 $CPAN::Frontend->mydie("File::Basename not installed, cannot continue"); 231 } 232 my $basename = File::Basename::basename($e); 233 $exe_stanza .= "/usr/bin/$basename\n"; 234 } 235 } 236 } 237 push @m, <<EOF; 238 239%files 240%defattr(-,root,root,-) 241%doc @doc 242%{perl_$vendorlib}/* 243%{_mandir}/man3/*.3* 244$exe_stanza 245%changelog 246* $date <specfile\@specfile.cpan.org> - $version-1 247- autogenerated by CPAN::Plugin::Specfile() 248 249EOF 250 251 my $ret = join "", @m; 252 $CPAN::Frontend->myprint($ret); 253 my $target_dir = $self->dir || $self->dir_default; 254 File::Path::mkpath($target_dir); 255 my $outfile = File::Spec->catfile($target_dir, "perl-$dist.spec"); 256 open my $specout, ">", $outfile 257 or $CPAN::Frontend->mydie("Could not open >$outfile: $!"); 258 print $specout $ret; 259 $CPAN::Frontend->myprint("Wrote $outfile"); 260 $ret; 261} 262 2631; 264