#!/usr/bin/python
import os, sys, argparse, random
from shell import shell, ssh



description="Build release packages for pyqtgraph."

epilog = """
Package build is done in several steps:

    * Attempt to clone branch release-x.y.z from source-repo
    * Merge release branch into master
    * Write new version numbers into the source
    * Roll over unreleased CHANGELOG entries
    * Commit and tag new release
    * Build HTML documentation
    * Build source package
    * Build deb packages (if running on Linux)
    * Build Windows exe installers

Release packages may be published by using the --publish flag:

    * Uploads release files to website
    * Pushes tagged git commit to github
    * Uploads source package to pypi

Building source packages requires:

    * 
    * 
    * python-sphinx

Building deb packages requires several dependencies:

    * build-essential
    * python-all, python3-all
    * python-stdeb, python3-stdeb
    
Note: building windows .exe files should be possible on any OS. However, 
Debian/Ubuntu systems do not include the necessary wininst*.exe files; these
must be manually copied from the Python source to the distutils/command 
submodule path (/usr/lib/pythonX.X/distutils/command). Additionally, it may be
necessary to rename (or copy / link) wininst-9.0-amd64.exe to 
wininst-6.0-amd64.exe.

"""

path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
build_dir = os.path.join(path, 'release-build')
pkg_dir = os.path.join(path, 'release-packages')

ap = argparse.ArgumentParser(description=description, epilog=epilog, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument('version', help='The x.y.z version to generate release packages for. '
                                'There must be a corresponding pyqtgraph-x.y.z branch in the source repository.')
ap.add_argument('--publish', metavar='', help='Publish previously built package files (must be stored in pkg-dir/version) and tagged release commit (from build-dir).', action='store_const', const=True, default=False)
ap.add_argument('--source-repo', metavar='', help='Repository from which release and master branches will be cloned. Default is the repo containing this script.', default=path)
ap.add_argument('--build-dir', metavar='', help='Directory where packages will be staged and built. Default is source_root/release-build.', default=build_dir)
ap.add_argument('--pkg-dir', metavar='', help='Directory where packages will be stored. Default is source_root/release-packages.', default=pkg_dir)
ap.add_argument('--skip-pip-test', metavar='', help='Skip testing pip install.', action='store_const', const=True, default=False)
ap.add_argument('--no-deb', metavar='', help='Skip building Debian packages.', action='store_const', const=True, default=False)
ap.add_argument('--no-exe', metavar='', help='Skip building Windows exe installers.', action='store_const', const=True, default=False)



def build(args):
    if os.path.exists(args.build_dir):
        sys.stderr.write("Please remove the build directory %s before proceeding, or specify a different path with --build-dir.\n" % args.build_dir)
        sys.exit(-1)
    if os.path.exists(args.pkg_dir):
        sys.stderr.write("Please remove the package directory %s before proceeding, or specify a different path with --pkg-dir.\n" % args.pkg_dir)
        sys.exit(-1)
        
    # Clone source repository and tag the release branch
    shell('''
        # Clone and merge release branch into previous master
        mkdir -p {build_dir}
        cd {build_dir}
        rm -rf pyqtgraph
        git clone --depth 1 --branch master --single-branch {source_repo} pyqtgraph
        cd pyqtgraph
        git checkout -b release-{version}
        git pull {source_repo} release-{version}
        git checkout master
        git merge --no-ff --no-commit release-{version}
        
        # Write new version number into the source
        sed -i "s/__version__ = .*/__version__ = '{version}'/" pyqtgraph/__init__.py
        sed -i "s/version = .*/version = '{version}'/" doc/source/conf.py
        sed -i "s/release = .*/release = '{version}'/" doc/source/conf.py
        
        # make sure changelog mentions unreleased changes
        grep "pyqtgraph-{version}.*unreleased.*" CHANGELOG    
        sed -i "s/pyqtgraph-{version}.*unreleased.*/pyqtgraph-{version}/" CHANGELOG

        # Commit and tag new release
        git commit -a -m "PyQtGraph release {version}"
        git tag pyqtgraph-{version}

        # Build HTML documentation
        cd doc
            make clean
            make html
        cd ..
        find ./ -name "*.pyc" -delete

        # package source distribution
        python setup.py sdist

        mkdir -p {pkg_dir}
        cp dist/*.tar.gz {pkg_dir}

        # source package build complete.
    '''.format(**args.__dict__))

        
    if args.skip_pip_test:
        args.pip_test = 'skipped'
    else:
        shell('''
            # test pip install source distribution
            rm -rf release-{version}-virtenv
            virtualenv --system-site-packages release-{version}-virtenv
            . release-{version}-virtenv/bin/activate
            echo "PATH: $PATH"
            echo "ENV: $VIRTUAL_ENV" 
            pip install --no-index --no-deps dist/pyqtgraph-{version}.tar.gz
            deactivate
            
            # pip install test passed
        '''.format(**args.__dict__))
        args.pip_test = 'passed'


    if 'linux' in sys.platform and not args.no_deb: 
        shell('''
            # build deb packages
            cd {build_dir}/pyqtgraph
            python setup.py --command-packages=stdeb.command sdist_dsc
            cd deb_dist/pyqtgraph-{version}
            sed -i "s/^Depends:.*/Depends: python (>= 2.6), python-qt4 | python-pyside, python-numpy/" debian/control    
            dpkg-buildpackage
            cd ../../
            mv deb_dist {pkg_dir}/pyqtgraph-{version}-deb
            
            # deb package build complete.
        '''.format(**args.__dict__))
        args.deb_status = 'built'
    else:
        args.deb_status = 'skipped'
        

    if not args.no_exe:
        shell("""
            # Build windows executables
            cd {build_dir}/pyqtgraph
            python setup.py build bdist_wininst --plat-name=win32
            python setup.py build bdist_wininst --plat-name=win-amd64
            cp dist/*.exe {pkg_dir}
        """.format(**args.__dict__))
        args.exe_status = 'built'    
    else:
        args.exe_status = 'skipped'


    print(unindent("""

    ======== Build complete. =========

      * Source package:     built
      * Pip install test:   {pip_test}
      * Debian packages:    {deb_status}
      * Windows installers: {exe_status}
      * Package files in    {pkg_dir}

    Next steps to publish:
    
      * Test all packages
      * Run script again with --publish

    """).format(**args.__dict__))


def publish(args):


    if not os.path.isfile(os.path.expanduser('~/.pypirc')):
        print(unindent("""
            Missing ~/.pypirc file. Should look like:
            -----------------------------------------

                [distutils]
                index-servers =
                    pypi

                [pypi]
                username:your_username
                password:your_password

        """))
        sys.exit(-1)

    ### Upload everything to server
    shell("""
        cd {build_dir}/pyqtgraph
        
        # Uploading documentation..  (disabled; now hosted by readthedocs.io)
        #rsync -rv doc/build/* pyqtgraph.org:/www/code/pyqtgraph/pyqtgraph/documentation/build/

        # Uploading release packages to website
        rsync -v {pkg_dir} pyqtgraph.org:/www/code/pyqtgraph/downloads/

        # Push master to github
        git push https://github.com/pyqtgraph/pyqtgraph master:master
        
        # Push tag to github
        git push https://github.com/pyqtgraph/pyqtgraph pyqtgraph-{version}

        # Upload to pypi..
        python setup.py sdist upload

    """.format(**args.__dict__))

    print(unindent("""

    ======== Upload complete. =========

    Next steps to publish:
        - update website
        - mailing list announcement
        - new conda recipe (http://conda.pydata.org/docs/build.html)
        - contact deb maintainer (gianfranco costamagna)
        - other package maintainers?

    """).format(**args.__dict__))


def unindent(msg):
    ind = 1e6
    lines = msg.split('\n')
    for line in lines:
        if len(line.strip()) == 0:
            continue
        ind = min(ind, len(line) - len(line.lstrip()))
    return '\n'.join([line[ind:] for line in lines])


if __name__ == '__main__':
    args = ap.parse_args()
    args.build_dir = os.path.abspath(args.build_dir)
    args.pkg_dir = os.path.join(os.path.abspath(args.pkg_dir), args.version)

    if args.publish:
        publish(args)
    else:
        build(args)
