release.pl 8.2 KB

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