3 This program takes information about a release in the ReleaseConfig file
4 and inserts the information in various files. It helps to keep the files
5 in sync because it never forgets which files need to be updated, nor what
6 information should be inserted in each file.
8 It takes one parameter that says what the mode of update should be, which
9 may be one of 'develop', 'release', or 'post'.
11 In 'develop' mode, which is what someone would use as part of their own
12 development practices, the release type is forced to be 'dev' and the
13 patch level is the string 'yyyymmdd'. Otherwise, it's the same as the
16 In 'release' mode, the release type is taken from ReleaseConfig and the
17 patch level is calculated from the release date for non-final runs and
18 taken from the version tuple for final runs. It then inserts information
20 - The RELEASE header line in src/CHANGES.txt and src/Announce.txt.
21 - The version string at the top of src/RELEASE.txt.
22 - The version string in the 'default_version' variable in testing/framework/TestSCons.py.
23 - The copyright years in testing/framework/TestSCons.py.
24 - The unsupported and deprecated Python floors in testing/framework/TestSCons.py
25 and src/engine/SCons/Script/Main.py
26 - The version string in the filenames in README.
28 In 'post' mode, files are prepared for the next release cycle:
29 - In ReleaseConfig, the version tuple is updated for the next release
30 by incrementing the release number (either minor or micro, depending
31 on the branch) and resetting release type to 'dev'.
32 - A blank template replaces src/RELEASE.txt.
33 - A new section to accumulate changes is added to src/CHANGES.txt and
38 # Copyright The SCons Foundation
40 # Permission is hereby granted, free of charge, to any person obtaining
41 # a copy of this software and associated documentation files (the
42 # "Software"), to deal in the Software without restriction, including
43 # without limitation the rights to use, copy, modify, merge, publish,
44 # distribute, sublicense, and/or sell copies of the Software, and to
45 # permit persons to whom the Software is furnished to do so, subject to
46 # the following conditions:
48 # The above copyright notice and this permission notice shall be included
49 # in all copies or substantial portions of the Software.
51 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
52 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
53 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
54 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
55 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
56 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
57 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
66 DEBUG
= os
.environ
.get('DEBUG', 0)
70 def __init__(self
, args
):
73 self
.release_date
= time
.localtime()[:6]
75 self
.unsupported_version
= None
76 self
.deprecated_version
= None
78 # version_tuple = (3, 1, 3, 'alpha', 0)
79 self
.version_tuple
= (-1, -1, -1, 'not_set', -1)
81 self
.version_string
= "UNSET"
82 self
.version_type
= 'UNSET'
83 self
.month_year
= 'UNSET'
84 self
.copyright_years
= 'UNSET'
85 self
.new_date
= 'NEW DATE WILL BE INSERTED HERE'
89 if not self
.args
.timestamp
:
92 def read_config(self
):
93 # Get configuration information
96 with
open('ReleaseConfig') as f
:
97 release_config
= f
.read()
98 exec(release_config
, globals(), config
)
102 def process_config(self
):
104 Process and validate the config info loaded from ReleaseConfig file
108 self
.version_tuple
= self
.config
['version_tuple']
109 self
.unsupported_version
= self
.config
['unsupported_python_version']
110 self
.deprecated_version
= self
.config
['deprecated_python_version']
112 print('''ERROR: Config file must contain at least version_tuple,
113 \tunsupported_python_version, and deprecated_python_version.''')
116 if 'release_date' in self
.config
:
117 self
.release_date
= self
.config
['release_date']
118 if len(self
.release_date
) == 3:
119 self
.release_date
= self
.release_date
+ time
.localtime()[3:6]
120 if len(self
.release_date
) != 6:
121 print('''ERROR: Invalid release date''', self
.release_date
)
124 yyyy
, mm
, dd
, h
, m
, s
= self
.release_date
125 date_string
= "".join(["%.2d" % d
for d
in self
.release_date
])
127 if self
.args
.timestamp
:
128 date_string
= self
.args
.timestamp
130 if self
.args
.mode
== 'develop' and self
.version_tuple
[3] != 'a':
131 self
.version_tuple
== self
.version_tuple
[:3] + ('a', 0)
133 if len(self
.version_tuple
) > 3 and self
.version_tuple
[3] != 'final':
134 self
.version_tuple
= self
.version_tuple
[:4] + (date_string
,)
136 self
.version_string
= '.'.join(map(str, self
.version_tuple
[:4])) + date_string
138 if len(self
.version_tuple
) > 3:
139 self
.version_type
= self
.version_tuple
[3]
141 self
.version_type
= 'final'
143 if self
.version_type
not in ['a', 'b', 'rc', 'final']:
144 print(("""ERROR: `%s' is not a valid release type in version tuple;
145 \tit must be one of a, b, rc, or final""" % self
.version_type
))
149 self
.month_year
= self
.config
['month_year']
151 if self
.args
.timestamp
:
152 self
.month_year
= "MONTH YEAR"
154 self
.month_year
= time
.strftime('%B %Y', self
.release_date
+ (0, 0, 0))
157 self
.copyright_years
= self
.config
['copyright_years']
159 self
.copyright_years
= '2001 - %d' % (self
.release_date
[0] + 1)
162 print('version tuple', self
.version_tuple
)
163 print('unsupported Python version', self
.unsupported_version
)
164 print('deprecated Python version', self
.deprecated_version
)
165 print('release date', self
.release_date
)
166 print('version string', self
.version_string
)
167 print('month year', self
.month_year
)
168 print('copyright years', self
.copyright_years
)
170 def set_new_date(self
):
172 Determine the release date and the pattern to match a date
173 Mon, 05 Jun 2010 21:17:15 -0700
174 NEW DATE WILL BE INSERTED HERE
176 min = (time
.daylight
and time
.altzone
or time
.timezone
) // 60
178 min = -(min % 60 + hr
* 100)
179 self
.new_date
= (time
.strftime('%a, %d %b %Y %X', self
.release_date
+ (0, 0, 0))
188 _days
= r
'(Sun|Mon|Tue|Wed|Thu|Fri|Sat)'
189 _months
= r
'(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oce|Nov|Dec)'
190 match_date
= r
''.join([_days
, r
', \d\d ', _months
, r
' \d\d\d\d \d\d:\d\d:\d\d [+-]\d\d\d\d'])
191 match_date
= re
.compile(match_date
)
193 # Determine the pattern to match a version
195 _rel_types
= r
'[(a|b|rc)]'
196 match_pat
= r
'\d+\.\d+\.\d+\.' + _rel_types
+ r
'\.?(\d+|yyyymmdd)'
197 match_rel
= re
.compile(match_pat
)
199 def __init__(self
, file, orig
=None):
204 with
open(orig
, 'r') as f
:
205 self
.content
= f
.read()
207 # Couldn't open file; don't try to write anything in __del__
213 # so we can see if it changed
214 self
.orig
= self
.content
216 # pretend file changed
219 def sub(self
, pattern
, replacement
, count
=1):
221 self
.content
= re
.sub(pattern
, replacement
, self
.content
, count
)
223 def replace_assign(self
, name
, replacement
, count
=1):
225 self
.sub('\n' + name
+ ' = .*', '\n' + name
+ ' = ' + replacement
)
227 def replace_version(self
, count
=1):
229 self
.content
= self
.match_rel
.sub(rel_info
.version_string
, self
.content
, count
)
231 def replace_date(self
, count
=1):
233 self
.content
= self
.match_date
.sub(rel_info
.new_date
, self
.content
, count
)
237 if self
.file is not None and self
.content
!= self
.orig
:
238 print('Updating ' + self
.file + '...')
239 with
open(self
.file, 'w') as f
:
240 f
.write(self
.content
)
243 def main(args
, rel_info
):
244 if args
.mode
== 'post':
245 # Set up for the next release series.
247 if rel_info
.version_tuple
[2]:
248 # micro release, increment micro value
249 minor
= rel_info
.version_tuple
[1]
250 micro
= rel_info
.version_tuple
[2] + 1
252 # minor release, increment minor value
253 minor
= rel_info
.version_tuple
[1] + 1
255 new_tuple
= (rel_info
.version_tuple
[0], minor
, micro
, 'a', 0)
256 new_version
= '.'.join(map(str, new_tuple
[:3])) + new_tuple
[3] + 'yyyymmdd'
258 rel_info
.version_string
= new_version
260 # Update ReleaseConfig
261 t
= UpdateFile('ReleaseConfig')
263 t
.file = '/tmp/ReleaseConfig'
264 t
.replace_assign('version_tuple', str(new_tuple
))
266 # Update src/CHANGES.txt
267 t
= UpdateFile('CHANGES.txt')
269 t
.file = '/tmp/CHANGES.txt'
270 t
.sub('(\nRELEASE .*)', r
"""\nRELEASE VERSION/DATE TO BE FILLED IN LATER\n
272 - Whatever John Doe did.\n\n\1""")
274 # Update src/RELEASE.txt
275 t
= UpdateFile('RELEASE.txt',
276 os
.path
.join('template', 'RELEASE.txt'))
278 t
.file = '/tmp/RELEASE.txt'
282 for readme_file
in ['README.rst', 'README-SF.rst']:
283 t
= UpdateFile(readme_file
)
284 if DEBUG
: t
.file = os
.path
.join('/tmp/', readme_file
)
285 t
.sub('-' + t
.match_pat
+ r
'\.', '-' + rel_info
.version_string
+ '.', count
=0)
286 for suf
in ['tar', 'win32', 'zip']:
287 t
.sub(r
'-(\d+\.\d+\.\d+)\.%s' % suf
,
288 '-%s.%s' % (rel_info
.version_string
, suf
),
291 # Update testing/framework/TestSCons.py
292 t
= UpdateFile(os
.path
.join('testing', 'framework', 'TestSCons.py'))
294 t
.file = '/tmp/TestSCons.py'
295 t
.replace_assign('copyright_years', repr(rel_info
.copyright_years
))
296 t
.replace_assign('default_version', repr(rel_info
.version_string
))
297 # ??? t.replace_assign('SConsVersion', repr(version_string))
298 t
.replace_assign('python_version_unsupported', str(rel_info
.unsupported_version
))
299 t
.replace_assign('python_version_deprecated', str(rel_info
.deprecated_version
))
301 # Update Script/Main.py
302 t
= UpdateFile(os
.path
.join('SCons', 'Script', 'Main.py'))
304 t
.file = '/tmp/Main.py'
305 t
.replace_assign('unsupported_python_version', str(rel_info
.unsupported_version
))
306 t
.replace_assign('deprecated_python_version', str(rel_info
.deprecated_version
))
308 # Update doc/user/main.xml
309 docyears
= '2004 - %d' % rel_info
.release_date
[0]
310 t
= UpdateFile(os
.path
.join('doc', 'user', 'main.xml'))
312 t
.file = '/tmp/main.xml'
313 t
.sub('<pubdate>[^<]*</pubdate>', '<pubdate>Released: ' + rel_info
.new_date
+ '</pubdate>')
314 t
.sub('<year>[^<]*</year>', '<year>' + docyears
+ '</year>')
316 # Write out the last update
321 def parse_arguments():
323 Create ArgumentParser object and process arguments
326 parser
= argparse
.ArgumentParser(prog
='update-release-info.py')
327 parser
.add_argument('mode', nargs
='?', choices
=['develop', 'release', 'post'], default
='post')
328 parser
.add_argument('--verbose', dest
='verbose', action
='store_true', help='Enable verbose logging')
330 parser
.add_argument('--timestamp', dest
='timestamp', help='Override the default current timestamp')
332 args
= parser
.parse_args()
336 if __name__
== "__main__":
337 options
= parse_arguments()
338 rel_info
= ReleaseInfo(options
)
339 UpdateFile
.rel_info
= rel_info
340 main(options
, rel_info
)
344 # indent-tabs-mode:nil
346 # vim: set expandtab tabstop=4 shiftwidth=4: