Skip to content
Snippets Groups Projects
build_version.py 15.9 KiB
Newer Older
#!/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'
NOW = datetime.datetime.now()
PACK_PLUGIN_URL = 'http://localhost:8000/%s/default/pack_plugin' % APP
PDF = "pdf"
PDFDOC = opj(DOCS, PDF)

# 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)
    """Build HTML documentations.
    The source files are located in the following directory::

        myapp/docs
        myapp/docs/api
        myapp/docs/reference
        myapp/docs/user
    for doc in (API, REFERENCE, USER):
        docsrc = opj(DOCSRC, doc)
        if os.path.exists(docsrc):
            sphinx("-b html", docsrc, opj(DOCS, doc))
    Args:
        doc (str): sub-directory. Possible values are api, reference, user.

    sphinx("-b latex", opj(DOCSRC, doc), LATEXDOC)

    # the current directory
    cwd = os.getcwd()

    # find the name of the tex file
    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
    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))
    print "%s documentation in" % fout, PDFDOC
    """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),
           "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),
           "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)
    """Tag the release in the production branch, locally and on the
    remote repository.
    # move to production branch
    git("checkout", "production")
    # 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)
LE GAC Renaud's avatar
LE GAC Renaud committed
    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.")

                   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",
                   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.")
                   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.")

                   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.")

                     api_pdf=False,
                     changelog=False,
                     compile=False,
                     jsduck=False,
                     reference_doc=False,
                     reference_pdf=False,
                     release=False,
                     user_doc=False,
                     user_pdf=False,
        sphinx("-b html", opj(DOCSRC, API), opj(DOCS, API))

    if OPT.api_pdf:
        build_pdf(API)


    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.start_release:
        start_release()

    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)