# We use argparse rather than getopt because it allows us to setup a parser as # a separate object and then we can test input combination with an external # python script that uses the unittest module. import argparse import logging import os import pwd import sys import shutil import glob # handle_exit is a context manager which guarantees that the project temporary # directories are cleaned up when there are signals. from linaropy.handle_exit import handle_exit from datetime import datetime from linaropy.series import Series from linaropy.rn.gccclone import GCCClone from linaropy.git.clone import Clone from linaropy.rn.linaroseries import LinaroSeries from linaropy.rn.linaroseries import linaroSeriesFromBranchname from linaropy.rn.linaroseries import linaro_series_from_tag from linaropy.proj import Proj from linaropy.git.gitrepo import cd from linaropy.rn.template import RNTemplate from linaropy.rn.rnseries import RNSeries from linaropy.rn.rngen import rngen from linaropy.rninput import yninput from linaropy.rninput import finput # Terminal color codes. from linaropy.colors import * from jinja2.exceptions import TemplateSyntaxError import traceback rnProj = [] # Cleanup the temporary project directory if necessary. def rncleanup(): if not rnProj: # This will be the case if the script is run via the test driver. print "No cleanup needed" else: print_info("Cleaning up Proj dir %s if possible." % rnProj[0].projdir) rnProj[0].cleanup() def generate(track, to_date, to_series, gccsource, persist): # Delay creating the Proj directory until now so that the parser (and # parser validation functions) can be tested in the unittests without # invoking the internals of this driver. rnProj.append(Proj(prefix='rn-', persist=persist)) if persist: andpersist="(and persist) " print_info(BOLD + "Release notes will be generated %sin:" % andpersist) print_info(" %s" % rnProj[0].projdir) # This will raise an exception if gccsource is not a git repository. gccclone = GCCClone(rnProj[0], clonedir=gccsource) # use gccsource to figure out the GCC Base Version and the FSF Version # from the git commit history. logging.info("gccbaseversion is " + gccclone.get_base_version()) logging.info("fsf revision is " + gccclone.get_fsf_revision()) if gccclone.tag_exists(track): logging.info("%s is a tag. Creating Series from tag." % track) track_series = linaro_series_from_tag(track) else: logging.info("%s is a branch. Creating Series from branchname." % track) track_series = linaroSeriesFromBranchname(track) try: next_series = track_series.toNext(to_series) except TypeError: logging.error( "Next series '%s' from '%s' in an invalid progression" % (LinaroSeries.series[to_series], track_series.shorttype())) logging.error( "If this is a release series try tracking the release-candidate" " tag instead of the release branch.") sys.exit(2) if to_date != next_series.date: raise RuntimeError( 'The date passed to this driver does not equal the date computed by LinaroSeries.toNext()') rnclone = Clone( rnProj[0], remote=u'ssh://git@git.linaro.org/toolchain/release-notes.git') next_rn = RNSeries( rnProj[0], rnrepo=rnclone.clonedir(), track_series=track_series, next_series=next_series) if next_rn.update_templ_readmes(): print_info( "Please verify that your changes have been committed on the" " template branch:") next_rn.rn_template.log(1) ans = True history = finput( 'Please enter the location of the changelog csv file: ', "6.1-2016.06.csv") username=pwd.getpwuid(os.getuid()).pw_name public_html_dir= "/home/%s/public_html/" % username next_rn.update_series_readmes() while ans: next_rn.rn_series.print_log(1) # Generate the temporary output files to the projdir. with cd(rnProj[0].projdir): try: rngen(next_rn, gccclone, history) except TemplateSyntaxError: traceback.print_exc(file=sys.stdout) print_info("Please correct the template and try again.") # The default location to tell the user to look for generated files. # If there is a webserver this is overwritten if the users wants it. dest_dir=rnProj[0].projdir if os.path.isdir(public_html_dir): todir=public_html_dir + os.path.basename(dest_dir) + "/" answer = yninput( "Would you like to copy the .txt, .html, and .textile" " files to your %s directory?" % todir, "N") if answer == "y": dest_dir=todir if not os.path.isdir(dest_dir): os.mkdir(dest_dir, 0755) for file in glob.glob(r'%s/*.txt' % rnProj[0].projdir): shutil.copy(file, dest_dir) for file in glob.glob(r'%s/*.html' % rnProj[0].projdir): shutil.copy(file, dest_dir) for file in glob.glob(r'%s/*.textile' % rnProj[0].projdir): shutil.copy(file, dest_dir) # If the user asked for the files to be copied to public_html then # this will indicate that directory, otherwise dest_dir will remain # the tmp directory. print_info( "Please direct your browser to the rendered .html, .textile and" " .txt files in %s%s%s%s and make sure that they look correct." % (BOLD, RED, dest_dir, NC)) ans = next_rn.update_series_readmes() # Verify that the GCC Source is located where it says it is. class VerifyGCCSourceAction(argparse.Action): def __init__(self, option_strings, dest, nargs=None, **kwargs): if nargs is not None: raise ValueError("nargs not allowed") super(VerifyGCCSourceAction, self).__init__( option_strings, dest, **kwargs) def __call__(self, parser, namespace, values, option_string=None): logging.info("%r %r %r" % (namespace, values, option_string)) setattr(namespace, self.dest, values) logging.info("gccsource: " + values) # We simply want to test that the directory exists. We'll prove that # it is a git directory in a later step. if not os.path.isdir(values): raise IOError("The GCC source directory %s does not exist." % values) def str_to_datetime(datestr): # strptime will throw an exception if the input date is not a string or is # not parsable. if len(datestr) < 10: inputdate = datetime.strptime(datestr, "%Y.%m") else: inputdate = datetime.strptime(datestr, "%Y.%m.%d") # Force the 'day' to 15 to unify comparisons. return inputdate.replace(day=15) def create_parser(): parser = argparse.ArgumentParser( prog="rn.py", description='''Generate release notes.''' ) # Positionals are required by default. parser.add_argument( 'track', help='branchname or tag name of series to track.') parser.add_argument( '-g', '--gccsource', dest='gccsource', required=True, action=VerifyGCCSourceAction, help='location of the gcc source') parser.add_argument( '-d', '--date', dest='to_date', required=True, help='the next series date in "YYYY.MM" form.', type=str_to_datetime) parser.add_argument( '-n', '--nopersist', dest='persist', default=True, action='store_false', help='The proj dir will not persist once this program has executed.') # At least one of the following arguments are required. group = parser.add_mutually_exclusive_group(required=True) group.add_argument( '-c', '--candidate', dest='to_series', action='store_const', const=LinaroSeries.series.index("candidate")) group.add_argument( '-r', '--release', dest='to_series', action='store_const', const=LinaroSeries.series.index("release")) group.add_argument( '-s', '--snapshot', dest='to_series', action='store_const', const=LinaroSeries.series.index("snapshot")) return parser def main(): parser = create_parser() args = parser.parse_args() generate(args.track, args.to_date, args.to_series, args.gccsource, args.persist) if __name__ == '__main__': logging.basicConfig(level="WARNING") with handle_exit(rncleanup): main()