1919"""
2020Make a release of the C++ Driver, including steps associated with the CXX
2121project in Jira, and with the mongodb/mongo-cxx-driver GitHub repository.
22+ See releasing.md for complete release instructions.
2223"""
2324
2425# CXX Project ID - 11980
3940from distutils .version import LooseVersion
4041import os
4142import subprocess
43+ import sys
4244import tempfile
4345
4446import click # pip install Click
4547from git import Repo # pip install GitPython
4648from github import Github # pip install PyGithub
4749from jira import JIRA # pip install jira
4850
51+ if sys .version_info < (3 , 0 , 0 ):
52+ raise RuntimeError ("This script requires Python 3 or higher" )
53+
4954RELEASE_TAG_RE = re .compile ('r(?P<ver>(?P<vermaj>[0-9]+)\\ .(?P<vermin>[0-9]+)'
5055 '\\ .(?P<verpatch>[0-9]+)(?:-(?P<verpre>.*))?)' )
5156CXX_PROJ_ID = 11980
9095 help = 'Instead of building the C driver, use the one installed at this path' )
9196@click .option ('--dist-file' ,
9297 help = 'Don\' t build anything; use this C++ driver distribution tarball' )
98+ @click .option ('--skip-distcheck' ,
99+ is_flag = True ,
100+ help = "Only build the distribution tarball (do not build the driver or run tests)" )
93101@click .option ('--output-file' ,
94102 '-o' ,
95103 help = 'Send release announcement draft output to the specified file' )
@@ -111,6 +119,7 @@ def release(jira_creds_file,
111119 c_driver_build_ref ,
112120 with_c_driver ,
113121 dist_file ,
122+ skip_distcheck ,
114123 output_file ,
115124 git_revision ,
116125 dry_run ,
@@ -119,10 +128,19 @@ def release(jira_creds_file,
119128 Perform the steps associated with the release.
120129 """
121130
131+ # Read Jira credentials and GitHub token first, to check that
132+ # user has proper credentials before embarking on lengthy builds.
133+ jira_options = {'server' : 'https://jira.mongodb.org' }
134+ jira_oauth_dict = read_jira_oauth_creds (jira_creds_file )
135+ auth_jira = JIRA (jira_options , oauth = jira_oauth_dict )
136+
137+ github_token = read_github_creds (github_token_file )
138+ auth_gh = Github (github_token )
139+
122140 if not is_valid_remote (remote ):
123141 click .echo ('The the remote "{}" does not point to the mongodb/mongo-cxx-driver '
124142 'repo...exiting!' .format (remote ), err = True )
125- exit (1 )
143+ sys . exit (1 )
126144
127145 if dry_run :
128146 click .echo ('DRY RUN! No remote modifications will be made!' )
@@ -134,15 +152,15 @@ def release(jira_creds_file,
134152 if not release_tag :
135153 click .echo ('No release tag points to {}' .format (git_revision ), err = True )
136154 click .echo ('Nothing to do here...exiting!' , err = True )
137- exit (1 )
155+ sys . exit (1 )
138156
139157 if not working_dir_on_valid_branch (release_version ):
140158 # working_dir_on_valid_branch() has already produced an error message
141- exit (1 )
159+ sys . exit (1 )
142160
143161 if not release_tag_points_to_head (release_tag ):
144162 click .echo ('Tag {} does not point to HEAD...exiting!' .format (release_tag ), err = True )
145- exit (1 )
163+ sys . exit (1 )
146164
147165 is_pre_release = check_pre_release (release_tag )
148166
@@ -155,51 +173,46 @@ def release(jira_creds_file,
155173 if dist_file :
156174 if not os .path .exists (dist_file ):
157175 click .echo ('Specified distribution tarball does not exist...exiting!' , err = True )
158- exit (1 )
176+ sys . exit (1 )
159177 else :
160178 c_driver_dir = ensure_c_driver (c_driver_install_dir , c_driver_build_ref ,
161179 with_c_driver , quiet )
162180 if not c_driver_dir :
163181 click .echo ('C driver not built or not found...exiting!' , err = True )
164- exit (1 )
182+ sys . exit (1 )
165183
166- dist_file = build_distribution (release_tag , release_version , c_driver_dir , quiet )
184+ dist_file = build_distribution (release_tag , release_version , c_driver_dir , quiet ,
185+ skip_distcheck )
167186 if not dist_file :
168187 click .echo ('C++ driver distribution not built or not found...exiting!' , err = True )
169- exit (1 )
170-
171- jira_options = {'server' : 'https://jira.mongodb.org' }
172- jira_oauth_dict = read_jira_oauth_creds (jira_creds_file )
173- auth_jira = JIRA (jira_options , oauth = jira_oauth_dict )
188+ sys .exit (1 )
174189
175190 jira_vers_dict = get_jira_project_versions (auth_jira )
176191
177192 if release_version not in jira_vers_dict .keys ():
178193 click .echo ('Version "{}" not found in Jira. Cannot release!'
179194 .format (release_version ), err = True )
180- exit (1 )
195+ sys . exit (1 )
181196 if jira_vers_dict [release_version ].released :
182197 click .echo ('Version "{}" already released in Jira. Cannot release again!'
183198 .format (release_version ), err = True )
184- exit (1 )
199+ sys . exit (1 )
185200
186201 issues = get_all_issues_for_version (auth_jira , release_version )
187202
188203 if not allow_open_issues and not all_issues_closed (issues ):
189204 # all_issues_closed() has already produced an error message
190- exit (1 )
205+ sys . exit (1 )
191206
192207 release_notes_text = generate_release_notes (issues , release_version )
193208
194- github_token = read_github_creds (github_token_file )
195- auth_gh = Github (github_token )
196209 gh_repo = auth_gh .get_repo ('mongodb/mongo-cxx-driver' )
197210 gh_release_dict = get_github_releases (gh_repo )
198211
199212 if release_tag in gh_release_dict .keys ():
200213 click .echo ('Version "{}" already released in GitHub. Cannot release again!'
201214 .format (release_tag ), err = True )
202- exit (1 )
215+ sys . exit (1 )
203216
204217 if dry_run :
205218 click .echo ('DRY RUN! Not creating release for tag "{}"' .format (release_tag ))
@@ -225,7 +238,7 @@ def is_valid_remote(remote):
225238 'mongodb/mongo-cxx-driver(\\ .git)?$' )
226239 repo = Repo ('.' )
227240
228- return True if remote_re .match (list (repo .remote (remote ).urls )[0 ]) else False
241+ return bool ( remote_re .match (list (repo .remote (remote ).urls )[0 ]))
229242
230243def print_banner (git_revision ):
231244 """
@@ -312,7 +325,7 @@ def check_pre_release(tag_name):
312325
313326 release_re = re .compile ('^r[0-9]+\\ .[0-9]+\\ .[0-9]+' )
314327
315- return False if release_re .match (tag_name ) else True
328+ return not bool ( release_re .match (tag_name ))
316329
317330def ensure_c_driver (c_driver_install_dir , c_driver_build_ref , with_c_driver , quiet ):
318331 """
@@ -342,87 +355,65 @@ def build_c_driver(c_driver_install_dir, c_driver_build_ref, quiet):
342355 otherwise return None.
343356 """
344357
345- if not quiet :
346- click .echo ('Building C Driver (this could take several minutes)' )
347358 mongoc_prefix = os .path .abspath (c_driver_install_dir )
359+
360+ if not quiet :
361+ click .echo (f'Building C Driver at { mongoc_prefix } (this could take several minutes)' )
362+ click .echo ('Pass --with-c-driver to use an existing installation' )
363+
348364 env = os .environ
349365 env ['PREFIX' ] = mongoc_prefix
350366 if not c_driver_build_ref :
351367 c_driver_build_ref = 'master'
352- proc = subprocess .Popen ('./.evergreen/install_c_driver.sh ' + c_driver_build_ref ,
353- stdout = subprocess .PIPE , stderr = subprocess .PIPE , env = env ,
354- shell = True )
355- outs , errs = proc .communicate ()
356- if proc .returncode == 0 :
357- if not quiet :
358- click .echo ('C Driver build was successful.' )
359- click .echo ('Version "{}" was installed to "{}".'
360- .format (c_driver_build_ref , mongoc_prefix ))
361- return mongoc_prefix
362-
363- # The build had a non-zero result, so we log the output and emit some messages
364- click .echo ('C Driver build failed. Consult output logs.' , err = True )
365-
366- with tempfile .NamedTemporaryFile (prefix = 'c_driver_build_' , suffix = '.out' ,
367- delete = False ) as tmpfp :
368- tmpfp .write (outs )
369- click .echo ('C Driver build standard output: {}' .format (tmpfp .name ), err = True )
370-
371- with tempfile .NamedTemporaryFile (prefix = 'c_driver_build_' , suffix = '.err' ,
372- delete = False ) as tmpfp :
373- tmpfp .write (errs )
374- click .echo ('C Driver build standard error: {}' .format (tmpfp .name ), err = True )
368+ run_shell_script ('./.evergreen/install_c_driver.sh ' + c_driver_build_ref , env = env )
375369
376- return None
370+ if not quiet :
371+ click .echo ('C Driver build was successful.' )
372+ click .echo ('Version "{}" was installed to "{}".'
373+ .format (c_driver_build_ref , mongoc_prefix ))
374+ return mongoc_prefix
377375
378- def build_distribution (release_tag , release_version , c_driver_dir , quiet ):
376+ def build_distribution (release_tag , release_version , c_driver_dir , quiet , skip_distcheck ):
379377 """
380378 Perform the necessary steps to build the distribution tarball which will be
381- attached to the release in GitHub. Return the name of the distribution
379+ attached to the release in GitHub. Return the path to the distribution
382380 tarball for a successful build and return None for a failed build.
383381 """
384382
385- if not quiet :
386- click .echo ('Building C++ Driver (this could take several minutes)' )
387- if os .path .exists ('build/CMakeCache.txt' ):
388- click .echo ('Remnants of prior build found in build directory.' , err = True )
389- return None
383+ dist_file = 'build/mongo-cxx-driver-{}.tar.gz' .format (release_tag )
390384
391- env = os .environ
392- env ['MONGOC_PREFIX' ] = c_driver_dir
393-
394- proc = subprocess .Popen ('. .evergreen/find_cmake.sh;'
395- 'cd build;'
396- 'echo ' + release_version + ' > VERSION_CURRENT;'
397- '${CMAKE} -DCMAKE_BUILD_TYPE=Release '
398- '-DCMAKE_PREFIX_PATH="${MONGOC_PREFIX}" '
399- '-DENABLE_UNINSTALL=ON ..;'
400- 'make DISTCHECK_BUILD_OPTS="-j8" -j8 distcheck' ,
401- stdout = subprocess .PIPE , stderr = subprocess .PIPE , env = env ,
402- shell = True )
403- outs , errs = proc .communicate ()
385+ if not quiet :
386+ click .echo ('Building C++ distribution tarball: {}' .format (dist_file ))
404387
405- dist_file = 'mongo-cxx-driver-{}.tar.gz' .format (release_tag )
406- if proc .returncode == 0 and os .path .exists (dist_file ):
407- if not quiet :
408- click .echo ('C++ Driver build was successful.' )
409- click .echo ('Distribution file: {}' .format (dist_file ))
410- return dist_file
388+ if os .path .exists (dist_file ):
389+ click .echo ('Distribution tarball already exists: {}' .format (dist_file ))
390+ click .echo ('Refusing to build distribution tarball.' )
391+ click .echo ('To use the existing tarball, pass: --dist-file {}' .format (dist_file ), err = True )
392+ return None
411393
412- # The build had a non-zero result, so we log the output and emit some messages
413- click .echo ('C++ Driver build failed. Consult output logs.' , err = True )
394+ if os .path .exists ('build/CMakeCache.txt' ):
395+ click .echo ('Remnants of prior build found in ./build directory.' )
396+ click .echo ('Refusing to build distribution tarball.' )
397+ click .echo ('Clear ./build with "git clean -xdf ./build"' , err = True )
398+ return None
414399
415- with tempfile .NamedTemporaryFile (prefix = 'cxx_driver_build_' , suffix = '.out' ,
416- delete = False ) as tmpfp :
417- tmpfp .write (outs )
418- click .echo ('C++ Driver build standard output: {}' .format (tmpfp .name ), err = True )
400+ run_shell_script ('. .evergreen/find_cmake.sh;'
401+ 'cd build;'
402+ 'echo ' + release_version + ' > VERSION_CURRENT;'
403+ '${CMAKE} -DCMAKE_BUILD_TYPE=Release '
404+ '-DCMAKE_PREFIX_PATH="' + c_driver_dir + '" '
405+ '-DENABLE_UNINSTALL=ON ..;'
406+ 'cmake --build . --target dist' )
419407
420- with tempfile .NamedTemporaryFile (prefix = 'cxx_driver_build_' , suffix = '.err' ,
421- delete = False ) as tmpfp :
422- tmpfp .write (errs )
423- click .echo ('C++ Driver build standard error: {}' .format (tmpfp .name ), err = True )
408+ if not quiet :
409+ click .echo ('C++ Driver build was successful.' )
410+ click .echo ('Distribution file: {}' .format (dist_file ))
424411
425- return None
412+ if not skip_distcheck :
413+ click .echo ('Building C++ driver from tarball and running tests.' )
414+ click .echo ('This may take several minutes. This may be skipped with --skip_distcheck' )
415+ run_shell_script ('cmake --build build --target distcheck' )
416+ return dist_file
426417
427418def read_jira_oauth_creds (jira_creds_file ):
428419 """
@@ -590,5 +581,30 @@ def create_github_release_draft(gh_repo,
590581
591582# pylint: enable=too-many-arguments
592583
584+ def run_shell_script (script , env = None ):
585+ """
586+ Execute a shell process, and returns contents of stdout. Raise an error on failure.
587+ """
588+
589+ proc = subprocess .Popen (script ,
590+ stdout = subprocess .PIPE , stderr = subprocess .PIPE , env = env ,
591+ shell = True )
592+ outs , errs = proc .communicate ()
593+ if proc .returncode == 0 :
594+ return outs
595+
596+ with tempfile .NamedTemporaryFile (suffix = '.out' , delete = False ) as tmpfp :
597+ tmpfp .write (outs )
598+ stdout_path = tmpfp .name
599+
600+ with tempfile .NamedTemporaryFile (suffix = '.err' , delete = False ) as tmpfp :
601+ tmpfp .write (errs )
602+ stderr_path = tmpfp .name
603+
604+ raise RuntimeError (f'Script failed: { script } \n '
605+ 'Consult output logs:\n '
606+ f'stdout: { stdout_path } \n '
607+ f'stderr: { stderr_path } \n ' )
608+
593609if __name__ == '__main__' :
594610 release ()
0 commit comments