#!/usr/bin/env python # -*- coding: utf-8 -*- """ NAME build_version -- helper script to build a plugin_dbui version SYNOPSIS build_version [options] version DESCRIPTION Helper script to build a version of the plugin_dbui. The version identifier should contains alphanumeric characters including ".", "-" and "_". Push version identifier in the javascript library. Push version number in the CHANGELOG. Build debug and minified version of the javascript library. Build the web2py plugin file EXAMPLES > build_version -h AUTHOR R. Le Gac, renaud.legac@free.fr Copyright (c) 2012-2015 R. Le Gac """ import datetime import optparse import os import re import sys import tempfile import urllib from os.path import join as opj from subprocess import call # constants APP = os.path.basename(os.getcwd()) API = 'api' BUILDDIR = '../plugin_dbui_build' CHANGELOG = 'static/plugin_dbui/CHANGELOG' DBUI_W2P = 'web2py.plugin.dbui.%s.w2p' DOCS = 'static/plugin_dbui/docs' DOCSRC = 'docs' EXTJSSRC = 'static/plugin_extjs/src' JSBASE = 'static/plugin_dbui/src/App.js' JSDOC = opj(DOCS, 'jsduck') JSLIBDEBUG = 'static/plugin_dbui/dbui-debug.js' JSLIBMIN = 'static/plugin_dbui/dbui-min.js' JSLIBSRC = 'static/plugin_dbui/src' LATEX = 'latex' LATEXDOC = opj(DOCS, LATEX) NOW = datetime.datetime.now() PACK_PLUGIN_URL = 'http://localhost:8000/%s/default/pack_plugin' % APP PDF = "pdf" PDFDOC = opj(DOCS, PDF) REFERENCE = 'reference' USER = 'user' # basic commands GIT = '/usr/bin/git' JSDUCK = os.path.expandvars("$HOME/bin/jsduck") PDFLATEX = '/usr/bin/pdflatex' SENCHA = os.path.expandvars("$HOME/bin/sencha") SPHINX = '/usr/bin/sphinx-build' MSG_RELEASE = 'Enter the new release: ' def build(): """Compile the javascript code and build documentations. """ compile_js() build_html() for doc in (API, REFERENCE, USER): docsrc = opj(DOCSRC, doc) if os.path.exists(docsrc): build_pdf(doc) def build_html(): """Build HTML documentations. The source files are located in the following directory:: myapp/docs myapp/docs/api myapp/docs/reference myapp/docs/user """ print "Build the HTML documentations..." jsduck() for doc in (API, REFERENCE, USER): docsrc = opj(DOCSRC, doc) if os.path.exists(docsrc): sphinx("-b html", docsrc, opj(DOCS, doc)) def build_pdf(doc): """ Build PDF documentations... Args: doc (str): sub-directory. Possible values are api, reference, user. """ print "Build the PDF documentations..." # generate the latex sphinx("-b latex", opj(DOCSRC, doc), LATEXDOC) # the current directory cwd = os.getcwd() # find the name of the tex file os.chdir(LATEXDOC) li = [el for el in os.listdir('.') if el.endswith('.tex')] fn = (li[0] if len(li) == 1 else None) if not fn: print "\n\tNo latex file !" return # process the latex file twice call([PDFLATEX, fn]) call([PDFLATEX, fn]) # move the pdf file os.chdir(cwd) if not os.path.exists(PDFDOC): os.mkdir(PDFDOC) fin = fn.replace('.tex', '.pdf') fout = "%s_%s.pdf" % (os.path.splitext(fn)[0], doc) os.rename(opj(LATEXDOC, fin), opj(PDFDOC, fout)) # remove the latex directory call(["rm", "-rf", LATEXDOC]) print "%s documentation in" % fout, PDFDOC def commit_change_log(): """Commit CHANGELOG and App.js. """ print "Commit CHANGELOG and App.js..." if not os.path.exists(GIT): print '\n\tThe application git is missing !' print '\tSkip this step.\n' return # move to the master branch git("checkout master") # Commit modified files print 'git add', JSBASE, CHANGELOG git("add", JSBASE, CHANGELOG) print 'git commit' msg = "Start release candidate %s" % get_version() git("commit -m", msg) def compile_js(): """compile_js the javascript code and generate the debug version as well as the minified version of the dbui library. The compiler verify that the code complied with the class model and order the file in the proper way. The minified library can be build in several ways, including the Ext JS class required by the applications. In the current version, the library contains only the dbui classes and the Ext JS ones have to be loaded separately. In that sense this command is very similar to the yuicompressor one. Several compressor can be used yui, closure compiler, .... In the current version, the default yui compressor is used? This operation relies on the Sencha Cmd: http://www.sencha.com/products/sencha-cmd/download The details documantation can be found: http://docs.sencha.com/extjs/4.2.2/#!/guide/command """ print "Compile the javascript code ..." if not os.path.exists(SENCHA): print '\n\tThe application sencha is missing !' print '\tSee: http://www.sencha.com/products/sencha-cmd/download' print '\tSkip this step.\n' return # clean previous version for el in (JSLIBDEBUG, JSLIBMIN): if os.path.exists(el): os.remove(el) print 'Remove old javascript library', el # debug version of the javascript library cwd = os.getcwd() cmd = ["sencha", "-sdk", opj(cwd, EXTJSSRC), "compile", "-class", opj(cwd, JSLIBSRC), "exclude", "--namespace", "Ext", "and", "concat", opj(cwd, JSLIBDEBUG)] call(cmd) print 'Debug version of the javascript library', JSLIBDEBUG, 'is ready' # Minified version of the javascript library cmd = ["sencha", "-sdk", opj(cwd, EXTJSSRC), "compile", "-class", opj(cwd, JSLIBSRC), "exclude", "--namespace", "Ext", "and", "concat", "--yui", opj(cwd, JSLIBMIN)] call(cmd) print 'Minified version of the javascript library', JSLIBMIN, 'is ready' def get_version(): """Get the current version identifier. """ txt = open(JSBASE, 'rb').read() match = re.match(r"(.+ version: ')([\w._-]*)('.+)", txt, re.DOTALL) return match.group(2) def git(*args, **kwargs): """run any git instruction: Args: *args: list of variable arguments containing git command and their options **kwargs: keyword argument of the function ``subprocess.call``. Examples: git("add CHANGELOG foo.txt") git("add", CHANGELOG, "foo.txt") git("commit", stdout=fi) """ if not os.path.exists(GIT): print '\n\tThe application git is missing !' print '\tSkip this step.\n' return cmd = [GIT] cmd.extend(args[0].split()) if len(args) > 1: cmd.extend(args[1:]) call(cmd, **kwargs) def jsduck(): """Generate the JavaScript documentation. The HTML files are located in static/plugin_dbui/docs/jsduck """ print "Build the javascript documentation..." if not os.path.exists(JSDUCK): print '\n\tThe application jsduck is missing !' print '\tSkip this step.\n' return # create the directory if not os.path.exists(JSDOC): os.makedirs(JSDOC) # clean the directory cmd = ["rm", "-rf", JSDOC] call(cmd) # run JsDuck cmd = ["jsduck", EXTJSSRC, JSLIBSRC, \ "--output", JSDOC, \ "--title", "plugin_dbui %s" % get_version(), \ "--warnings=-all:" + EXTJSSRC] call(cmd) print "JavaScript documentation in", JSDOC def set_version(version): """Set release identifier in CHANGELOG and App.js Args: version (str): release identifier """ print "Update CHANGELOG and App.js files with the release", version, "..." # check tag in git fi = tempfile.TemporaryFile() git("tag", stdout=fi) fi.seek(0) if version in fi.read(): print "\n\tRelease %s already exit in git" % version sys.exit(1) print 'Set release', version, 'in', JSBASE txt = open(JSBASE, 'rb').read() # look for a pattern App.version = '0.8.3'; in appbase.js # split the the string in 3 parts (pre, version, post) match = re.match(r"(.+ version: ')([\w._-]*)('.+)", txt, re.DOTALL) if match.group(2) == version: msg = '\n\tVersion "%s" already exists in the appbase.js file !' print msg % version rep = raw_input('\tDo you want to continue [n]?') if rep not in ('y', 'yes'): sys.exit(1) # update the version and write a new file txt = match.group(1) + version + match.group(3) fi = open(JSBASE, 'wb') fi.write(txt) fi.close() # look for a pattern HEAD in the CHANGELOG # split the the string in 2 parts (pre HEAD, post HEAD) print 'Set release', version, 'in', CHANGELOG txt = open(CHANGELOG, 'rb').read() match = re.match("(.+HEAD\n)(.*)", txt, re.DOTALL) if match == None: print '\n\tNo HEAD tag in the CHANGELOG!\n' rep = raw_input('\tDo you want to continue [n]?') if rep not in ('y', 'yes'): sys.exit(1) # update the version and edit the CHANGELOG tpl = (match.group(1), version, NOW.strftime('%b %Y'), match.group(2)) txt = '%s\n%s (%s)\n%s' % tpl fi = open(CHANGELOG, 'wb') fi.write(txt) fi.close() call(["vim", CHANGELOG]) # cleaning fn = "%s~" % CHANGELOG if os.path.exists(fn): os.remove(fn) def sphinx(*args, **kwargs): """run the sphinx-build: Args: *args: list of variable arguments containing the sphinx-build options. **kwargs: keyword argument of the function ``subprocess.call``. Examples: sphinx("-b html sourcedir outdir") sphinx("-b html", sourcedir, outdir) """ if not os.path.exists(SPHINX): print '\n\tThe application sphinx is missing !' print '\tSkip this step.\n' return cmd = [SPHINX] cmd.extend(args[0].split()) if len(args) > 1: cmd.extend(args[1:]) call(cmd, **kwargs) def start_release(): """Start the release cycle. Set the new release number in the CHANGELOG and App.js files. Commit then in the master branch. """ print "Start the release cycle..." old_release = get_version() print "Current release is", old_release new_release = raw_input(MSG_RELEASE) if not new_release: sys.exit(0) branch = "release-%s" % new_release git("checkout", "-b", branch, "develop") set_version(new_release) commit_change_log() build() def tag(): """Tag the release in the production branch, locally and on the remote repository. """ print "Tag the release...", # move to production branch git("checkout", "production") # annotated tag version = get_version() print 'git tag', version msg = "Tag %s" % version git("tag", "-a", version, "-m", msg) # push the tag on the server git("push --tags") def web2py(): """Produce the binary file for the web2py plugin. """ print 'Build the web2py plugin binary file...' msg = 'Check that the web2py service is running [y/N]' rep = raw_input(msg) if rep != 'y': print '\n\tSkip this step.\n' return connection = urllib.urlopen(PACK_PLUGIN_URL) txt = connection.read() fn = DBUI_W2P % get_version().replace('.', '') fi = open(fn, 'wb') fi.write(txt) fi.close() # move the file to the build directory fn_build = opj(BUILDDIR, fn) if os.path.exists(fn_build): os.remove(fn_build) os.rename(fn, fn_build) print 'Binary file', fn, 'is created in ', BUILDDIR if __name__ == '__main__': # define script options OPS = optparse.OptionParser() OPS.add_option("-a", "--api-doc", action="store_true", dest="api_doc", help="build the API documentation in HTML.") OPS.add_option("-A", "--api-pdf", action="store_true", dest="api_pdf", help="build the API documentation in PDF.") OPS.add_option("-c", "--compile", action="store_true", dest="compile", help="compile the javascript library.") OPS.add_option("--commit-changelog", action="store_true", dest="changelog", help="commit CHANGELOG and App.js. " "To be used with --write-release.") OPS.add_option("-j", "--jsduck", action="store_true", dest="jsduck", help="build the JavaScript documentation.") OPS.add_option("-r", "--reference-doc", action="store_true", dest="reference_doc", help="build the reference manual in HTML.") OPS.add_option("-R", "--reference-pdf", action="store_true", dest="reference_pdf", help="build the reference manual in PDF.") OPS.add_option("-s", "--start-release", action="store_true", dest="start_release", help="start the release candidate by setting " "release number in change log and App.js. " "Commit changes in the master branch. " "Equivalent to --write-release followed by " "--commit-changelog.") OPS.add_option("-t", "--tag", action="store_true", dest="tag", help="tag the release in the production branch " "locally and on the remote repository.") OPS.add_option("-u", "--user-doc", action="store_true", dest="user_doc", help="build the user manual in HTML.") OPS.add_option("-U", "--user-pdf", action="store_true", dest="user_pdf", help="build the user manual in PDF.") OPS.add_option("-v", "--version", action="store_true", dest="version", help="get the current release identifier.") OPS.add_option("-w", "--web2py", action="store_true", dest="web2py", help="pack the plugin_dbui.") OPS.add_option("--write-release", action="store_true", dest="release", help="write the release number in CHANGELOG and App.js. " "To be used with --commit-changelog.") OPS.set_defaults(api_doc=False, api_pdf=False, changelog=False, compile=False, jsduck=False, reference_doc=False, reference_pdf=False, release=False, start_release=False, tag=False, user_doc=False, user_pdf=False, version=False) (OPT, ARGS) = OPS.parse_args() if OPT.api_doc: sphinx("-b html", opj(DOCSRC, API), opj(DOCS, API)) if OPT.api_pdf: build_pdf(API) if OPT.compile: compile_js() if OPT.changelog: commit_change_log() if OPT.jsduck: jsduck() if OPT.release: set_version(raw_input(MSG_RELEASE)) if OPT.reference_doc: sphinx("-b html", opj(DOCSRC, REFERENCE), opj(DOCS, REFERENCE)) if OPT.reference_pdf: build_pdf(REFERENCE) if OPT.start_release: start_release() if OPT.tag: tag() if OPT.user_doc: sphinx("-b html", opj(DOCSRC, USER), opj(DOCS, USER)) if OPT.user_pdf: build_pdf(USER) if OPT.version: print "\nThe current release is %s\n" % get_version() if OPT.web2py: build() web2py() sys.exit(0)