patchprov 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. #!/usr/bin/perl
  2. ##
  3. ## Name:
  4. ## patchprov
  5. ##
  6. ## Description:
  7. ## Patch the provides list in the perl package PKGBUILD. Scan the appropriate
  8. ## directories under the perl source tree for directories containing dists
  9. ## similar to CPAN dists. Search the files in the distributions for VERSION
  10. ## strings, which are perl expressions. Filters these version strings through
  11. ## the perl interpreter, then transform the dist. names and versions into
  12. ## package names and versions. Finally, we cut out the "provides" array from the
  13. ## PKGBUILD and replace it with the newer version.
  14. ##
  15. ## Usage:
  16. ## patchprov [path to perl source tree] [path to PKGBUILD]
  17. ##
  18. ## Caveats:
  19. ## The path code is not platform independent and will only work in POSIX.
  20. ##
  21. ## Changelog:
  22. ## 06/10/14 JD Rewrite from scratch for perl 5.20.0 and ArchLinux.
  23. ##
  24. ## Authors:
  25. ## Justin "juster" Davis <jrcd83@gmail.com>
  26. ##
  27. use warnings;
  28. use strict;
  29. sub err
  30. {
  31. print STDERR "patchprov: error: @_\n";
  32. exit 1;
  33. }
  34. ## Extract the dist. name from its containing directory.
  35. sub path_dist
  36. {
  37. my($path) = @_;
  38. $path =~ s{^.*/}{};
  39. return $path;
  40. }
  41. ## Create a path like $path/lib/Foo/Bar.pm for Foo::Bar.
  42. sub lib_modpath
  43. {
  44. my($path, $modname) = @_;
  45. $modname =~ s{::}{/}g;
  46. return "$path/lib/$modname.pm";
  47. }
  48. ## Create a path to a file in the containing directory, named after
  49. ## the last segment of the module name, with suffix attached.
  50. sub dumb_modpath
  51. {
  52. my($path, $modname, $suffix) = @_;
  53. $modname =~ s{^.*::}{};
  54. return "$path/$modname$suffix";
  55. }
  56. ## Find a source file contained in the directory that we can scrape the
  57. ## perl versions string from.
  58. my %distmods = (
  59. 'PathTools' => 'Cwd',
  60. 'Scalar-List-Utils' => 'List::Util',
  61. 'IO-Compress' => 'IO::Compress::Gzip',
  62. );
  63. sub dist_srcpath
  64. {
  65. my($path) = @_;
  66. my $distname = path_dist($path);
  67. my $modname;
  68. if(exists $distmods{$distname}){
  69. $modname = $distmods{$distname};
  70. }else{
  71. $modname = $distname;
  72. $modname =~ s/-/::/g;
  73. }
  74. my @srcpaths = (
  75. lib_modpath($path, $modname),
  76. dumb_modpath($path, $modname, '.pm'),
  77. dumb_modpath($path, $modname, '_pm.PL'),
  78. dumb_modpath($path, '__'.$modname.'__', '.pm'),
  79. "$path/VERSION", # for podlators
  80. );
  81. for my $src (@srcpaths){
  82. return $src if(-f $src);
  83. }
  84. return undef;
  85. }
  86. ## Scrape the version string for the module file or Makefile.PL.
  87. sub scrape_verln
  88. {
  89. my($srcpath) = @_;
  90. open my $fh, '<', $srcpath or die "open: $!";
  91. while(my $ln = <$fh>){
  92. if($ln =~ s/^.*VERSION *=>? *//){
  93. close $fh;
  94. return $ln;
  95. }
  96. }
  97. close $fh;
  98. err("failed to find VERSION in $srcpath");
  99. }
  100. ## Scrape the version string from the module source file.
  101. sub scrape_modver
  102. {
  103. my($srcpath) = @_;
  104. return scrape_verln($srcpath);
  105. }
  106. ## Scrape the version string from the Makefile.PL. (for libnet)
  107. sub scrape_mkplver
  108. {
  109. my($srcpath) = @_;
  110. my $verln = scrape_verln($srcpath);
  111. $verln =~ s/,/;/;
  112. return $verln;
  113. }
  114. ## Scrape the version string from a file inside the dist dir.
  115. sub distpath_ver
  116. {
  117. my($distpath) = @_;
  118. my $srcpath = dist_srcpath($distpath);
  119. my $mkplpath = "$distpath/Makefile.PL";
  120. if(defined $srcpath){
  121. return scrape_modver($srcpath);
  122. }elsif(-f $mkplpath){
  123. return scrape_mkplver($mkplpath);
  124. }else{
  125. err("failed to scrape version from $distpath");
  126. }
  127. }
  128. ## Search the base path for the dist dirs and extract their respective
  129. ## version strings.
  130. sub find_distvers
  131. {
  132. my($basepath) = @_;
  133. opendir my $dh, $basepath or die "opendir: $!";
  134. my @dirs = grep { -d $_ } map { "$basepath/$_" } grep { !/^[.]/ } readdir $dh;
  135. closedir $dh;
  136. my @distvers;
  137. for my $dpath (@dirs){
  138. push @distvers, [ path_dist($dpath), distpath_ver($dpath) ];
  139. }
  140. return @distvers;
  141. }
  142. ## Maps an aref of dist name/perl version strings (perl expressions) to
  143. ## a package name and version string suitable for a PKGBUILD.
  144. sub pkgspec
  145. {
  146. my($dist, $ver) = @$_;
  147. $dist =~ tr/A-Z/a-z/;
  148. $ver = eval $ver;
  149. return "perl-$dist=$ver";
  150. }
  151. ## Searches the perl source dir provided for a list of packages which
  152. ## correspond to the core distributions bundled within in.
  153. sub perlcorepkgs
  154. {
  155. my($perlpath) = @_;
  156. my @dirs = ("$perlpath/cpan", "$perlpath/dist");
  157. my @provs;
  158. for my $d (@dirs){
  159. if(!-d $d){
  160. err("$d is not a valid directory");
  161. }
  162. push @provs, map pkgspec, find_distvers($d);
  163. }
  164. return @provs;
  165. }
  166. ## Formats the provided lines into a neatly formatted bash array. The first arg
  167. ## is the name of the bash variable to assign it to.
  168. sub basharray
  169. {
  170. my $vname = shift;
  171. ## Sort entries and surround with quotes.
  172. my @lns = sort map { qq{'$_'} } @_;
  173. $lns[0] = "$vname=($lns[0]";
  174. ## Indent lines for OCD geeks.
  175. if(@lns > 1){
  176. my $ind = length($vname) + 2;
  177. splice @lns, 1, @lns-1,
  178. map { (' ' x $ind) . $_ } @lns[1 .. $#lns];
  179. }
  180. $lns[$#lns] .= ')';
  181. return map { "$_\n" } @lns;
  182. }
  183. ## Patch the PKGBUILD at the given path with a new provides array, overwriting
  184. ## the old one.
  185. sub patchpb
  186. {
  187. my $pbpath = shift;
  188. open my $fh, '<', $pbpath or die "open: $!";
  189. my @lines = <$fh>;
  190. close $fh;
  191. my($i, $j);
  192. for($i = 0; $i < @lines; $i++){
  193. last if($lines[$i] =~ /^provides=/);
  194. }
  195. if($i == @lines){
  196. err("failed to find provides array in PKGBUILD");
  197. }
  198. for($j = $i; $j < @lines; $j++){
  199. last if($lines[$j] =~ /[)]/);
  200. }
  201. if($j == @lines){
  202. err("failed to find end of provides array");
  203. }
  204. splice @lines, $i, $j-$i+1,
  205. basharray('provides', grep { !/win32|next/ } @_);
  206. ## Avoid corrupting the existing PKGBUILD in case of a crash, etc.
  207. if(-f "$pbpath.$$"){
  208. err("pbpath.$$ temporary file already exists, please remove it.");
  209. }
  210. open $fh, '>', "$pbpath.$$" or die "open: $!";
  211. print $fh @lines;
  212. close $fh or die "close: $!";
  213. rename "$pbpath.$$", "$pbpath" or die "rename: $!";
  214. return;
  215. }
  216. ## Program entrypoint.
  217. sub main
  218. {
  219. if(@_ < 2){
  220. print STDERR "usage: $0 [perl source path] [PKGBUILD path]\n";
  221. exit 2;
  222. }
  223. my($perlpath, $pbpath) = @_;
  224. if(!-f $pbpath){
  225. err("$pbpath is not a valid file.");
  226. }elsif(!-d $perlpath){
  227. err("$perlpath is not a valid directory.");
  228. }else{
  229. patchpb($pbpath, perlcorepkgs($perlpath));
  230. }
  231. exit 0;
  232. }
  233. main(@ARGV);
  234. # EOF