Prefer single quotes
[buildr.git] / rakelib / stage.rake
1 # Licensed to the Apache Software Foundation (ASF) under one or more
2 # contributor license agreements.  See the NOTICE file distributed with this
3 # work for additional information regarding copyright ownership.  The ASF
4 # licenses this file to you under the Apache License, Version 2.0 (the
5 # "License"); you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 #    http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
13 # License for the specific language governing permissions and limitations under
14 # the License.
15
16 require 'digest/md5'
17 require 'digest/sha1'
18
19 gpg_cmd = 'gpg2'
20
21 STAGE_DATE = ENV['STAGE_DATE'] ||  Time.now.strftime('%Y-%m-%d')
22 RC_VERSION = ENV['RC_VERSION'] || ''
23
24 task 'prepare' do |task, args|
25   gpg_arg = args.gpg || ENV['gpg']
26   user = args.user || ENV['user'] || `whoami`
27
28   # Update source files to next release number.
29   lambda do
30     current_version = spec.version.to_s.split('.').map { |v| v.to_i }.
31       zip([0, 0, 0]).map { |a| a.inject(0) { |t,i| i.nil? ? nil : t + i } }.compact.join('.')
32
33     ver_file = "lib/#{spec.name}/version.rb"
34     if File.exist?(ver_file)
35       modified = File.read(ver_file).sub(/(VERSION\s*=\s*)(['"])(.*)\2/) { |line| "#{$1}#{$2}#{current_version}#{$2}" }
36       File.open ver_file, 'w' do |file|
37         file.write modified
38       end
39       puts "[X] Removed dev suffix from version in #{ver_file}"
40     end
41   end.call
42
43   # Make sure we're doing a release from checked code.
44   lambda do
45     puts 'Checking there are no local changes ... '
46     git = `git status -s`
47     fail "Cannot release unless all local changes are in Git:\n#{git}" unless git.empty?
48     puts '[X] There are no local changes, everything is in source control'
49   end.call
50
51   # Make sure we have a valid CHANGELOG entry for this release.
52   lambda do
53     puts 'Checking that CHANGELOG indicates most recent version and today''s date ... '
54     expecting = "#{spec.version} (#{STAGE_DATE})"
55     header = File.readlines('CHANGELOG').first.chomp
56     fail "Expecting CHANGELOG to start with #{expecting}, but found #{header} instead" unless expecting == header
57     puts '[x] CHANGELOG indicates most recent version and today''s date'
58   end.call
59
60   # Make sure we have a valid CHANGELOG entry for this release.
61   lambda do
62     puts 'Checking that doc/index.textile indicates most recent version and today''s date ... '
63     expecting = "Highlights from Buildr #{spec.version} (#{STAGE_DATE})"
64     content = IO.read('doc/index.textile')
65     fail "Expecting doc/index.textile to contain #{expecting}" unless content.include?(expecting)
66     puts '[x] doc/index.textile indicates most recent version and today''s date'
67   end.call
68
69   # Need GPG to sign the packages.
70   lambda do
71     gpg_arg or fail 'Please run with gpg=<argument for gpg --local-user>'
72     gpg_ok = `gpg2 --list-keys #{gpg_arg}` rescue nil
73     unless $?.success?
74       gpg_ok = `gpg --list-keys #{gpg_arg}`
75       gpg_cmd = 'gpg'
76     end
77     fail "No GPG user #{gpg_arg}" if gpg_ok.empty?
78   end.call
79
80   task('license').invoke
81   task('addon_extensions:check').invoke
82
83   # Need Prince to generate PDF
84   lambda do
85     puts 'Checking that we have prince available ... '
86     sh 'prince --version'
87     puts '[X] We have prince available'
88   end.call
89
90   raise 'Can not run stage process under jruby' if RUBY_PLATFORM[/java/]
91   raise 'Can not run staging process under older rubies' unless RUBY_VERSION >= '1.9'
92 end
93
94 task 'stage' => %w(clobber prepare) do |task, args|
95   gpg_arg = args.gpg || ENV['gpg']
96   mkpath '_staged'
97
98   lambda do
99     puts 'Ensuring all files have appropriate group and other permissions...'
100     sh 'find . -type f | xargs chmod go+r'
101     sh 'find . -type d | xargs chmod go+rx'
102     puts '[X] File permissions updated/validated.'
103   end.call
104
105   # Start by figuring out what has changed.
106   lambda do
107     puts 'Looking for changes between this release and previous one ...'
108     pattern = /(^(\d+\.\d+(?:\.\d+)?)\s+\(\d{4}-\d{2}-\d{2}\)\s*((:?^[^\n]+\n)*))/
109     changes = File.read('CHANGELOG').scan(pattern).inject({}) { |hash, set| hash[set[1]] = set[2] ; hash }
110     current = changes[spec.version.to_s]
111     fail "No changeset found for version #{spec.version}" unless current
112     File.open '_staged/CHANGES', 'w' do |file|
113       file.write "#{spec.version} (#{STAGE_DATE})\n"
114       file.write current
115     end
116     puts '[X] Listed most recent changed in _staged/CHANGES'
117   end.call
118
119   # Create the packages (gem, tarball) and sign them. This requires user
120   # intervention so the earlier we do it the better.
121   lambda do
122     puts 'Creating and signing release packages ...'
123     task('package').invoke
124     mkpath '_staged/dist'
125     FileList['pkg/*.{gem,zip,tgz}'].each do |source|
126       pkg = source.pathmap('_staged/dist/%n%x')
127       cp source, pkg
128       bytes = File.open(pkg, 'rb') { |file| file.read }
129       File.open(pkg + '.md5', 'w') { |file| file.write Digest::MD5.hexdigest(bytes) << ' ' << File.basename(pkg) }
130       File.open(pkg + '.sha1', 'w') { |file| file.write Digest::SHA1.hexdigest(bytes) << ' ' << File.basename(pkg) }
131       sh gpg_cmd, '--local-user', gpg_arg, '--armor', '--output', pkg + '.asc', '--detach-sig', pkg, :verbose=>true
132     end
133     cp 'etc/KEYS', '_staged/dist'
134     puts '[X] Created and signed release packages in _staged/dist'
135   end.call
136
137   # The download page should link to the new binaries/sources, and we
138   # want to do that before generating the site/documentation.
139   lambda do
140     puts 'Updating download page with links to release packages ... '
141     mirror = "http://www.apache.org/dyn/closer.cgi/#{spec.name}/#{spec.version}"
142     official = "http://www.apache.org/dist/#{spec.name}/#{spec.version}"
143     rows = FileList['_staged/dist/*.{gem,tgz,zip}'].map { |pkg|
144       name, md5 = File.basename(pkg), Digest::MD5.file(pkg).to_s
145       %{| "#{name}":#{mirror}/#{name} | "#{md5}":#{official}/#{name}.md5 | "Sig":#{official}/#{name}.asc |}
146     }
147     textile = <<-TEXTILE
148 h3. #{spec.name} #{spec.version} (#{STAGE_DATE})
149
150 |_. Package |_. MD5 Checksum |_. PGP |
151 #{rows.join("\n")}
152
153 p>. ("Release signing keys":#{official}/KEYS)
154     TEXTILE
155     file_name = 'doc/download.textile'
156     print "Adding download links to #{file_name} ... "
157     modified = File.read(file_name).
158       gsub('http://www.apache.org/dist','http://archive.apache.org/dist').
159       gsub('http://www.apache.org/dyn/closer.cgi','http://archive.apache.org/dist').
160       sub(/^h2\(#dist\).*$/) { |header| "#{header}\n\n#{textile}" }
161     File.open file_name, 'w' do |file|
162       file.write modified
163     end
164     puts "[X] Updated #{file_name}"
165   end.call
166
167
168   # Now we can create the Web site, this includes running specs, coverage report, etc.
169   # This will take a while, so we want to do it as last step before upload.
170   lambda do
171     puts 'Creating new Web site'
172     task(:site).invoke
173     cp_r '_site', '_staged/site'
174     puts '[X] Created new Web site in _staged/site'
175   end.call
176
177
178   # Move everything over to https://dist.apache.org/repos/dist/dev/buildr so we can vote on it.
179   lambda do
180     puts "Uploading _staged directory ..."
181     sh "svn mkdir https://dist.apache.org/repos/dist/dev/buildr/#{spec.version}#{RC_VERSION} -m 'Creating Buildr release candidate #{spec.version}#{RC_VERSION}'"
182     sh "cd _staged; svn checkout https://dist.apache.org/repos/dist/dev/buildr/#{spec.version}#{RC_VERSION} ."
183     sh "cd _staged; svn add *"
184      sh "cd _staged; svn commit -m 'Uploading Buildr RC #{spec.version}#{RC_VERSION}'"
185     puts "[X] Uploaded _staged directory"
186   end.call
187
188
189   # Prepare a release vote email. In the distant future this will also send the
190   # email for you and vote on it.
191   lambda do
192     # Need to know who you are on Apache, local user may be different (see .ssh/config).
193     base_url = "https://dist.apache.org/repos/dist/dev/buildr/#{spec.version}#{RC_VERSION}"
194     # Need changes for this release only.
195     changelog = File.read('CHANGELOG').scan(/(^(\d+\.\d+(?:\.\d+)?)\s+\(\d{4}-\d{2}-\d{2}\)\s*((:?^[^\n]+\n)*))/)
196     changes = changelog[0][2]
197     previous_version = changelog[1][1]
198
199     email = <<-EMAIL
200 To: dev@buildr.apache.org
201 Subject: [VOTE] Buildr #{spec.version} release
202
203 We're voting on the source distributions available here:
204 #{base_url}/dist/
205
206 Specifically:
207 #{base_url}/dist/buildr-#{spec.version}.tgz
208 #{base_url}/dist/buildr-#{spec.version}.zip
209
210 The documentation generated for this release is available here:
211 #{base_url}/site/
212 #{base_url}/site/buildr.pdf
213
214 The following changes were made since #{previous_version}:
215
216 #{changes.gsub(/^/, '  ')}
217     EMAIL
218     File.open 'vote-email.txt', 'w' do |file|
219       file.write email
220     end
221     puts '[X] Created release vote email template in ''vote-email.txt'''
222     puts email
223   end.call
224 end
225
226 task('clobber') { rm_rf '_staged' }
227 task('clobber') { rm_rf 'vote-email.txt' }