release.pl 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. #!/usr/bin/perl
  2. # Script to automate some easy-to-mess-up parts of the PuTTY release
  3. # procedure.
  4. use strict;
  5. use warnings;
  6. use Getopt::Long;
  7. use File::Find;
  8. use File::Temp qw/tempdir/;
  9. use LWP::UserAgent;
  10. my $version = undef;
  11. my $setver = 0;
  12. my $upload = 0;
  13. my $precheck = 0;
  14. my $postcheck = 0;
  15. my $skip_ftp = 0;
  16. GetOptions("version=s" => \$version,
  17. "setver" => \$setver,
  18. "upload" => \$upload,
  19. "precheck" => \$precheck,
  20. "postcheck" => \$postcheck,
  21. "no-ftp" => \$skip_ftp)
  22. or &usage();
  23. # --setver: construct a local commit which updates the version
  24. # number, and the command-line help transcripts in the docs.
  25. if ($setver) {
  26. defined $version or die "use --version";
  27. 0 == system "git", "diff-index", "--quiet", "--cached", "HEAD"
  28. or die "index is dirty";
  29. 0 == system "git", "diff-files", "--quiet" or die "working tree is dirty";
  30. my $builddir = tempdir(DIR => ".", CLEANUP => 1);
  31. 0 == system "git archive --format=tar HEAD | ( cd $builddir && tar xf - )"
  32. or die;
  33. 0 == system "cd $builddir && ./mkfiles.pl" or die;
  34. 0 == system "cd $builddir && ./mkauto.sh" or die;
  35. 0 == system "cd $builddir && ./configure" or die;
  36. 0 == system "cd $builddir && make pscp plink RELEASE=${version}" or die;
  37. our $pscp_transcript = `cd $builddir && ./pscp --help`;
  38. $pscp_transcript =~ s/^Unidentified build/Release ${version}/m or die;
  39. $pscp_transcript =~ s/^/\\c /mg;
  40. our $plink_transcript = `cd $builddir && ./plink --help`;
  41. $plink_transcript =~ s/^Unidentified build/Release ${version}/m or die;
  42. $plink_transcript =~ s/^/\\c /mg;
  43. &transform("LATEST.VER", sub { s/^\d+\.\d+$/$version/ });
  44. our $transforming = 0;
  45. &transform("doc/pscp.but", sub {
  46. if (/^\\c.*>pscp$/) { $transforming = 1; $_ .= $pscp_transcript; }
  47. elsif (!/^\\c/) { $transforming = 0; }
  48. elsif ($transforming) { $_=""; }
  49. });
  50. $transforming = 0;
  51. &transform("doc/plink.but", sub {
  52. if (/^\\c.*>plink$/) { $transforming = 1; $_ .= $plink_transcript; }
  53. elsif (!/^\\c/) { $transforming = 0; }
  54. elsif ($transforming) { $_=""; }
  55. });
  56. &transform("Buildscr", sub {
  57. s!^(set Epoch )\d+!$1 . sprintf "%d", time/86400 - 1000!e });
  58. 0 == system ("git", "commit", "-a", "-m",
  59. "Update version number for ${version} release.") or die;
  60. exit 0;
  61. }
  62. # --upload: upload the release to all the places it should live, and
  63. # check all signatures and md5sums once it arrives there.
  64. if ($upload) {
  65. defined $version or die "use --version";
  66. # Run this inside the build.out directory.
  67. -d "maps" or die "no maps directory in cwd";
  68. -d "putty" or die "no putty directory in cwd";
  69. 0 == system("rsync", "-av", "maps/",
  70. "thyestes:src/putty-local/maps-$version")
  71. or die "could not upload link maps";
  72. for my $location (["thyestes", "www/putty/$version"],
  73. ["the", "www/putty/$version"],
  74. ["chiark", "ftp/putty-$version"]) {
  75. my ($host, $path) = @$location;
  76. 0 == system("rsync", "-av", "putty/", "$host:$path")
  77. or die "could not upload release to $host";
  78. open my $pipe, "|-", "ssh", $host, "cd $path && sh";
  79. print $pipe "set -e\n";
  80. print $pipe "pwd\n";
  81. find({ wanted => sub
  82. {
  83. if (m!^putty/(.*).gpg!) {
  84. my $file = $1;
  85. print $pipe "echo verifying $file\n";
  86. if ($file =~ /sums$/) {
  87. print $pipe "gpg --verify $file.gpg\n";
  88. } else {
  89. print $pipe "gpg --verify $file.gpg $file\n";
  90. }
  91. } elsif (m!^putty/(.*sum)s!) {
  92. print $pipe "echo checking ${1}s\n";
  93. print $pipe "grep -vF ' (installer version)' ${1}s | grep . | $1 -c\n";
  94. }
  95. }, no_chdir => 1}, "putty");
  96. print $pipe "echo all verified ok\n";
  97. close $pipe;
  98. die "VERIFICATION FAILED on $host" if $? != 0;
  99. }
  100. print "Uploaded $version OK!\n";
  101. exit 0;
  102. }
  103. # --precheck and --postcheck: attempt to download the release from its
  104. # various web and FTP locations.
  105. if ($precheck || $postcheck) {
  106. defined $version or die "use --version";
  107. # Run this inside the build.out directory, so we can check the
  108. # downloaded files against the exact contents they should have.
  109. -d "putty" or die "no putty directory in cwd";
  110. my $httpprefix = "https://the.earth.li/~sgtatham/putty/";
  111. my $ftpprefix = "ftp://ftp.chiark.greenend.org.uk/users/sgtatham/putty-";
  112. # Go through all the files in build.out.
  113. find({ wanted => sub
  114. {
  115. if (-f $_) {
  116. die unless (m!^putty/(.*)$!);
  117. my $path = $1;
  118. # Don't try to check .htaccess - web servers will
  119. # treat it weirdly.
  120. return if $path =~ m!^(.*/)?.htaccess$!;
  121. print "Checking $path\n";
  122. my $real_content = "";
  123. open my $fh, "<", $_ or die "$_: open local file: $!";
  124. $real_content .= $_ while <$fh>;
  125. close $fh;
  126. my $http_numbered = "${httpprefix}$version/$path";
  127. my $http_latest = "${httpprefix}latest/$path";
  128. my $ftp_numbered = "${ftpprefix}$version/$path";
  129. my $ftp_latest = "${ftpprefix}latest/$path";
  130. my ($http_uri, $ftp_uri);
  131. if ($precheck) {
  132. # Before the 'latest' links/redirects update,
  133. # we just download from explicitly version-
  134. # numbered URLs.
  135. $http_uri = $http_numbered;
  136. $ftp_uri = $ftp_numbered;
  137. }
  138. if ($postcheck) {
  139. # After 'latest' is updated, we're testing that
  140. # the redirects work, so we download from the
  141. # URLs with 'latest' in them.
  142. $http_uri = $http_latest;
  143. $ftp_uri = $ftp_latest;
  144. }
  145. # Now test-download the files themselves.
  146. unless ($skip_ftp) {
  147. my $ftpdata = `curl -s $ftp_uri`;
  148. printf " got %d bytes via FTP", length $ftpdata;
  149. die "FTP download for $ftp_uri did not match"
  150. if $ftpdata ne $real_content;
  151. print ", ok\n";
  152. }
  153. my $ua = LWP::UserAgent->new;
  154. my $httpresponse = $ua->get($http_uri);
  155. my $httpdata = $httpresponse->{_content};
  156. printf " got %d bytes via HTTP", length $httpdata;
  157. die "HTTP download for $http_uri did not match"
  158. if $httpdata ne $real_content;
  159. print ", ok\n";
  160. # Check content types on any files likely to go
  161. # wrong.
  162. my $ct = $httpresponse->{_headers}->{"content-type"};
  163. if (defined $ct) {
  164. printf " got content-type %s", $ct;
  165. } else {
  166. printf " got no content-type";
  167. }
  168. my $right_ct = undef;
  169. if ($path =~ m/\.(hlp|cnt|chm)$/) {
  170. $right_ct = "application/octet-stream";
  171. } elsif ($path =~ /\.gpg$/) {
  172. $right_ct = "application/pgp-signature";
  173. }
  174. if (defined $right_ct) {
  175. if ($ct ne $right_ct) {
  176. die "content-type $ct should be $right_ct";
  177. } else {
  178. print ", ok\n";
  179. }
  180. } else {
  181. print "\n";
  182. }
  183. if ($postcheck) {
  184. # Finally, if we're testing the 'latest' URL,
  185. # also check that the HTTP redirect header was
  186. # present and correct.
  187. my $redirected = $httpresponse->{_request}->{_uri};
  188. printf " redirect -> %s\n", $redirected;
  189. die "redirect header wrong for $http_uri"
  190. if $redirected ne $http_numbered;
  191. }
  192. }
  193. }, no_chdir => 1}, "putty");
  194. print "Check OK\n";
  195. exit 0;
  196. }
  197. &usage();
  198. sub transform {
  199. my ($filename, $proc) = @_;
  200. my $file;
  201. open $file, "<", $filename or die "$file: open for read: $!\n";
  202. my $data = "";
  203. while (<$file>) {
  204. $proc->();
  205. $data .= $_;
  206. }
  207. close $file;
  208. open $file, ">", $filename or die "$file: open for write: $!\n";
  209. print $file $data;
  210. close $file or die "$file: close after write: $!\n";;
  211. }
  212. sub usage {
  213. die "usage: release.pl --set-version=X.YZ\n";
  214. }