3
3
Requires Python 2.6
4
4
5
5
Example of invocation (use to test the script):
6
- python makerelease.py --force --retag 0.5.0 0.6.0-dev
6
+ python makerelease.py --force --retag --platform=msvc6,msvc71,msvc80,mingw -ublep 0.5.0 0.6.0-dev
7
7
8
8
Example of invocation when doing a release:
9
9
python makerelease.py 0.5.0 0.6.0-dev
15
15
import subprocess
16
16
import xml .etree .ElementTree as ElementTree
17
17
import shutil
18
+ import urllib2
19
+ import tempfile
20
+ import os
21
+ import time
18
22
from devtools import antglob , fixeol , tarball
19
23
20
24
SVN_ROOT = 'https://jsoncpp.svn.sourceforge.net/svnroot/jsoncpp/'
21
25
SVN_TAG_ROOT = SVN_ROOT + 'tags/jsoncpp'
26
+ SCONS_LOCAL_URL = 'http://sourceforge.net/projects/scons/files/scons-local/1.2.0/scons-local-1.2.0.tar.gz/download'
27
+ SOURCEFORGE_PROJECT = 'jsoncpp'
22
28
23
29
def set_version ( version ):
24
30
with open ('version' ,'wb' ) as f :
25
31
f .write ( version .strip () )
26
32
33
+ def rmdir_if_exist ( dir_path ):
34
+ if os .path .isdir ( dir_path ):
35
+ shutil .rmtree ( dir_path )
36
+
27
37
class SVNError (Exception ):
28
38
pass
29
39
@@ -89,8 +99,7 @@ def svn_export( tag_url, export_dir ):
89
99
Target directory, including its parent is created if it does not exist.
90
100
If the directory export_dir exist, it is deleted before export proceed.
91
101
"""
92
- if os .path .isdir ( export_dir ):
93
- shutil .rmtree ( export_dir )
102
+ rmdir_if_exist ( export_dir )
94
103
svn_command ( 'export' , tag_url , export_dir )
95
104
96
105
def fix_sources_eol ( dist_dir ):
@@ -111,6 +120,114 @@ def fix_sources_eol( dist_dir ):
111
120
for path in unix_sources :
112
121
fixeol .fix_source_eol ( path , is_dry_run = False , verbose = True , eol = '\n ' )
113
122
123
+ def download ( url , target_path ):
124
+ """Download file represented by url to target_path.
125
+ """
126
+ f = urllib2 .urlopen ( url )
127
+ try :
128
+ data = f .read ()
129
+ finally :
130
+ f .close ()
131
+ fout = open ( target_path , 'wb' )
132
+ try :
133
+ fout .write ( data )
134
+ finally :
135
+ fout .close ()
136
+
137
+ def check_compile ( distcheck_top_dir , platform ):
138
+ cmd = [sys .executable , 'scons.py' , 'platform=%s' % platform , 'check' ]
139
+ print 'Running:' , ' ' .join ( cmd )
140
+ log_path = os .path .join ( distcheck_top_dir , 'build-%s.log' % platform )
141
+ flog = open ( log_path , 'wb' )
142
+ try :
143
+ process = subprocess .Popen ( cmd ,
144
+ stdout = flog ,
145
+ stderr = subprocess .STDOUT ,
146
+ cwd = distcheck_top_dir )
147
+ stdout = process .communicate ()[0 ]
148
+ status = (process .returncode == 0 )
149
+ finally :
150
+ flog .close ()
151
+ return (status , log_path )
152
+
153
+ def write_tempfile ( content , ** kwargs ):
154
+ fd , path = tempfile .mkstemp ( ** kwargs )
155
+ f = os .fdopen ( fd , 'wt' )
156
+ try :
157
+ f .write ( content )
158
+ finally :
159
+ f .close ()
160
+ return path
161
+
162
+ class SFTPError (Exception ):
163
+ pass
164
+
165
+ def run_sftp_batch ( userhost , sftp , batch , retry = 0 ):
166
+ path = write_tempfile ( batch , suffix = '.sftp' , text = True )
167
+ # psftp -agent -C blep,[email protected] -batch -b batch.sftp -bc
168
+ cmd = [sftp , '-agent' , '-C' , '-batch' , '-b' , path , '-bc' , userhost ]
169
+ error = None
170
+ for retry_index in xrange (0 , max (1 ,retry )):
171
+ heading = retry_index == 0 and 'Running:' or 'Retrying:'
172
+ print heading , ' ' .join ( cmd )
173
+ process = subprocess .Popen ( cmd , stdout = subprocess .PIPE , stderr = subprocess .STDOUT )
174
+ stdout = process .communicate ()[0 ]
175
+ if process .returncode != 0 :
176
+ error = SFTPError ( 'SFTP batch failed:\n ' + stdout )
177
+ else :
178
+ break
179
+ if error :
180
+ raise error
181
+ return stdout
182
+
183
+ def sourceforge_web_synchro ( sourceforge_project , doc_dir ,
184
+ user = None , sftp = 'sftp' ):
185
+ """Notes: does not synchronize sub-directory of doc-dir.
186
+ """
187
+ userhost = '%s,%[email protected] ' % (
user ,
sourceforge_project )
188
+ stdout = run_sftp_batch ( userhost , sftp , """
189
+ cd htdocs
190
+ dir
191
+ exit
192
+ """ )
193
+ existing_paths = set ()
194
+ collect = 0
195
+ for line in stdout .split ('\n ' ):
196
+ line = line .strip ()
197
+ if not collect and line .endswith ('> dir' ):
198
+ collect = True
199
+ elif collect and line .endswith ('> exit' ):
200
+ break
201
+ elif collect == 1 :
202
+ collect = 2
203
+ elif collect == 2 :
204
+ path = line .strip ().split ()[- 1 :]
205
+ if path and path [0 ] not in ('.' , '..' ):
206
+ existing_paths .add ( path [0 ] )
207
+ upload_paths = set ( [os .path .basename (p ) for p in antglob .glob ( doc_dir )] )
208
+ paths_to_remove = existing_paths - upload_paths
209
+ if paths_to_remove :
210
+ print 'Removing the following file from web:'
211
+ print '\n ' .join ( paths_to_remove )
212
+ stdout = run_sftp_batch ( userhost , sftp , """cd htdocs
213
+ rm %s
214
+ exit""" % ' ' .join (paths_to_remove ) )
215
+ print 'Uploading %d files:' % len (upload_paths )
216
+ batch_size = 10
217
+ upload_paths = list (upload_paths )
218
+ start_time = time .time ()
219
+ for index in xrange (0 ,len (upload_paths ),batch_size ):
220
+ paths = upload_paths [index :index + batch_size ]
221
+ file_per_sec = (time .time () - start_time ) / (index + 1 )
222
+ remaining_files = len (upload_paths ) - index
223
+ remaining_sec = file_per_sec * remaining_files
224
+ print '%d/%d, ETA=%.1fs' % (index + 1 , len (upload_paths ), remaining_sec )
225
+ run_sftp_batch ( userhost , sftp , """cd htdocs
226
+ lcd %s
227
+ mput %s
228
+ exit""" % (doc_dir , ' ' .join (paths ) ), retry = 3 )
229
+
230
+
114
231
def main ():
115
232
usage = """%prog release_version next_dev_version
116
233
Update 'version' file to release_version and commit.
@@ -120,7 +237,9 @@ def main():
120
237
121
238
Performs an svn export of tag release version, and build a source tarball.
122
239
123
- Must be started in the project top directory.
240
+ Must be started in the project top directory.
241
+
242
+ Warning: --force should only be used when developping/testing the release script.
124
243
"""
125
244
from optparse import OptionParser
126
245
parser = OptionParser (usage = usage )
@@ -133,13 +252,24 @@ def main():
133
252
help = """Ignore pending commit. [Default: %default]""" )
134
253
parser .add_option ('--retag' , dest = "retag_release" , action = 'store_true' , default = False ,
135
254
help = """Overwrite release existing tag if it exist. [Default: %default]""" )
255
+ parser .add_option ('-p' , '--platforms' , dest = "platforms" , action = 'store' , default = '' ,
256
+ help = """Comma separated list of platform passed to scons for build check.""" )
257
+ parser .add_option ('--no-test' , dest = "no_test" , action = 'store' , default = False ,
258
+ help = """Skips build check.""" )
259
+ parser .add_option ('-u' , '--upload-user' , dest = "user" , action = 'store' ,
260
+ help = """Sourceforge user for SFTP documentation upload.""" )
261
+ parser .add_option ('--sftp' , dest = 'sftp' , action = 'store' , default = doxybuild .find_program ('psftp' , 'sftp' ),
262
+ help = """Path of the SFTP compatible binary used to upload the documentation.""" )
136
263
parser .enable_interspersed_args ()
137
264
options , args = parser .parse_args ()
138
265
139
266
if len (args ) < 1 :
140
267
parser .error ( 'release_version missing on command-line.' )
141
268
release_version = args [0 ]
142
269
270
+ if not options .platforms and not options .no_test :
271
+ parser .error ( 'You must specify either --platform or --no-test option.' )
272
+
143
273
if options .ignore_pending_commit :
144
274
msg = ''
145
275
else :
@@ -157,7 +287,12 @@ def main():
157
287
svn_tag_sandbox ( tag_url , 'Release ' + release_version )
158
288
159
289
print 'Generated doxygen document...'
160
- doxybuild .build_doc ( options , make_release = True )
290
+ ## doc_dirname = r'jsoncpp-api-html-0.5.0'
291
+ ## doc_tarball_path = r'e:\prg\vc\Lib\jsoncpp-trunk\dist\jsoncpp-api-html-0.5.0.tar.gz'
292
+ doc_tarball_path , doc_dirname = doxybuild .build_doc ( options , make_release = True )
293
+ doc_distcheck_dir = 'dist/doccheck'
294
+ tarball .decompress ( doc_tarball_path , doc_distcheck_dir )
295
+ doc_distcheck_top_dir = os .path .join ( doc_distcheck_dir , doc_dirname )
161
296
162
297
export_dir = 'dist/export'
163
298
svn_export ( tag_url , export_dir )
@@ -168,12 +303,40 @@ def main():
168
303
print 'Generating source tarball to' , source_tarball_path
169
304
tarball .make_tarball ( source_tarball_path , [export_dir ], export_dir , prefix_dir = source_dir )
170
305
306
+ # Decompress source tarball, download and install scons-local
171
307
distcheck_dir = 'dist/distcheck'
308
+ distcheck_top_dir = distcheck_dir + '/' + source_dir
172
309
print 'Decompressing source tarball to' , distcheck_dir
310
+ rmdir_if_exist ( distcheck_dir )
173
311
tarball .decompress ( source_tarball_path , distcheck_dir )
312
+ scons_local_path = 'dist/scons-local.tar.gz'
313
+ print 'Downloading scons-local to' , scons_local_path
314
+ download ( SCONS_LOCAL_URL , scons_local_path )
315
+ print 'Decompressing scons-local to' , distcheck_top_dir
316
+ tarball .decompress ( scons_local_path , distcheck_top_dir )
317
+
318
+ # Run compilation
319
+ print 'Compiling decompressed tarball'
320
+ all_build_status = True
321
+ for platform in options .platforms .split (',' ):
322
+ print 'Testing platform:' , platform
323
+ build_status , log_path = check_compile ( distcheck_top_dir , platform )
324
+ print 'see build log:' , log_path
325
+ print build_status and '=> ok' or '=> FAILED'
326
+ all_build_status = all_build_status and build_status
327
+ if not build_status :
328
+ print 'Testing failed on at least one platform, aborting...'
329
+ svn_remove_tag ( tag_url , 'Removing tag due to failed testing' )
330
+ sys .exit (1 )
331
+ if options .user :
332
+ print 'Uploading documentation using user' , options .user
333
+ sourceforge_web_synchro ( SOURCEFORGE_PROJECT , doc_distcheck_top_dir , user = options .user , sftp = options .sftp )
334
+ print 'Completed documentatio upload'
335
+ else :
336
+ print 'No upload user specified. Documentation was not upload.'
337
+ print 'Tarball can be found at:' , doc_tarball_path
174
338
#@todo:
175
- # ?compile & run & check
176
- # ?upload documentation
339
+ #upload source & doc tarballs
177
340
else :
178
341
sys .stderr .write ( msg + '\n ' )
179
342
0 commit comments