# (c) Continuum Analytics, Inc. / http://continuum.io
# All Rights Reserved
#
# conda is distributed under the terms of the BSD 3-clause license.
# Consult LICENSE.txt or http://opensource.org/licenses/BSD-3-Clause.

from __future__ import absolute_import, division, print_function

import sys
from os.path import join, isdir, abspath, expanduser, exists
import shutil

from conda.cli.common import add_parser_prefix, get_prefix
from conda.cli.conda_argparse import ArgumentParser
from conda_build.main_build import args_func
from conda_build.post import mk_relative_osx
from conda_build.utils import _check_call, rec_glob

from conda.install import linked


def main():
    p = ArgumentParser(
        description="""

Install a Python package in 'development mode'.

This works by creating a conda.pth file in site-packages."""
        # TODO: Use setup.py to determine any entry-points to install.
    )

    p.add_argument(
        'source',
        action="store",
        metavar='PATH',
        nargs='+',
        help="Path to the source directory."
    )
    p.add_argument('-npf', '--no-pth-file',
                   action='store_true',
                   help=("Relink compiled extension dependencies against "
                         "libraries found in current conda env. "
                         "Do not add source to conda.pth."))
    p.add_argument('-b', '--build_ext',
                   action='store_true',
                   help=("Build extensions inplace, invoking: "
                         "python setup.py build_ext --inplace; "
                         "add to conda.pth; relink runtime libraries to "
                         "environment's lib/."))
    p.add_argument('-c', '--clean',
                   action='store_true',
                   help=("Invoke clean on setup.py: "
                         "python setup.py clean "
                         "use with build_ext to clean before building."))
    p.add_argument('-u', '--uninstall',
                   action='store_true',
                   help=("Removes package if installed in 'development mode' "
                         "by deleting path from conda.pth file. Ignore other "
                         "options - just uninstall and exit"))

    add_parser_prefix(p)
    p.set_defaults(func=execute)

    args = p.parse_args()
    args_func(args, p)


def relink_sharedobjects(pkg_path, build_prefix):
    '''
    invokes functions in post module to relink to libraries in conda env

    :param pkg_path: look for shared objects to relink in pkg_path
    :param build_prefix: path to conda environment which contains lib/. to find
        runtime libraries.

    .. note:: develop mode builds the extensions in place and makes a link to
        package in site-packages/. The build_prefix points to conda environment
        since runtime libraries should be loaded from environment's lib/. first
    '''
    # find binaries in package dir and make them relocatable
    bin_files = rec_glob(pkg_path, ['.so'])
    for b_file in bin_files:
        if sys.platform == 'darwin':
            mk_relative_osx(b_file, build_prefix)
        else:
            print("Nothing to do on Linux or Windows.")


def write_to_conda_pth(sp_dir, pkg_path):
    '''
    Append pkg_path to conda.pth in site-packages directory for current
    environment. Only add path if it doens't already exist.

    :param sp_dir: path to site-packages/. directory
    :param pkg_path: the package path to append to site-packes/. dir.
    '''
    c_file = join(sp_dir, 'conda.pth')
    with open(c_file, 'a') as f:
        with open(c_file, 'r') as cf:
            # make sure file exists, before we try to read from it hence nested
            # in append with block
            # expect conda.pth to be small so read it all in at once
            pkgs_in_dev_mode = cf.readlines()

        # only append pkg_path if it doesn't already exist in conda.pth
        if pkg_path + '\n' in pkgs_in_dev_mode:
            print("path exits, skipping " + pkg_path)
        else:
            f.write(pkg_path + '\n')
            print("added " + pkg_path)


def get_site_pkg(prefix, py_ver):
    '''
    Given the path to conda environment, find the site-packages directory

    :param prefix: path to conda environment. Look here for current
        environment's site-packages
    :returns: absolute path to site-packages directory
    '''
    # get site-packages directory
    stdlib_dir = join(prefix, 'Lib' if sys.platform == 'win32' else
                      'lib/python%s' % py_ver)
    sp_dir = join(stdlib_dir, 'site-packages')

    return sp_dir


def get_setup_py(path_):
    ''' Return full path to setup.py or exit if not found '''
    # build path points to source dir, builds are placed in the
    setup_py = join(path_, 'setup.py')

    if not exists(setup_py):
        sys.exit("No setup.py found in {0}. Exiting.".format(path_))

    return setup_py


def clean(setup_py):
    '''
    This invokes:
    $ python setup.py clean

    :param setup_py: path to setup.py
    '''
    # first call setup.py clean
    cmd = ['python', setup_py, 'clean']
    _check_call(cmd)
    print("Completed: " + " ".join(cmd))
    print("===============================================")


def build_ext(setup_py):
    '''
    Define a develop function - similar to build function
    todo: need to test on win32 and linux

    It invokes:
    $ python setup.py build_ext --inplace

    :param setup_py: path to setup.py
    '''

    # next call setup.py develop
    cmd = ['python', setup_py, 'build_ext', '--inplace']
    _check_call(cmd)
    print("Completed: " + " ".join(cmd))
    print("===============================================")


def uninstall(sp_dir, pkg_path):
    '''
    Look for pkg_path in conda.pth file in site-packages directory and remove
    it. If pkg_path is not found in conda.pth, it means package is not
    installed in 'development mode' via conda develop.

    :param sp_dir: path to site-packages/. directory
    :param pkg_path: the package path to be uninstalled.
    '''
    o_c_pth = join(sp_dir, 'conda.pth')
    n_c_pth = join(sp_dir, 'conda.pth.temp')
    found = False
    with open(n_c_pth, 'w') as new_c:
        with open(o_c_pth, 'r') as orig_c:
            for line in orig_c:
                if line != pkg_path + '\n':
                    new_c.write(line)
                else:
                    print("uninstalled: " + pkg_path)
                    found = True

    if not found:
        print("conda.pth does not contain path: " + pkg_path)
        print("package not installed via conda develop")

    shutil.move(n_c_pth, o_c_pth)


def execute(args, parser):
    prefix = get_prefix(args)
    if not isdir(prefix):
        sys.exit("""\
Error: environment does not exist: %s
#
# Use 'conda create' to create the environment first.
#""" % prefix)
    for package in linked(prefix):
        name, ver, _ = package .rsplit('-', 2)
        if name == 'python':
            py_ver = ver[:3]  # x.y
            break
    else:
        raise RuntimeError("python is not installed in %s" % prefix)

    # current environment's site-packages directory
    sp_dir = get_site_pkg(prefix, py_ver)

    for path in args.source:
        pkg_path = abspath(expanduser(path))

        if args.uninstall:
            # uninstall then exit - does not do any other operations
            uninstall(sp_dir, pkg_path)
            sys.exit(0)

        if args.clean or args.build_ext:
            setup_py = get_setup_py(pkg_path)
            if args.clean:
                clean(setup_py)
                if not args.build_ext:
                    sys.exit(0)

            # build extensions before adding to conda.pth
            if args.build_ext:
                build_ext(setup_py)

        if not args.no_pth_file:
            write_to_conda_pth(sp_dir, pkg_path)

        # go through the source looking for compiled extensions and make sure
        # they use the conda environment for loading libraries at runtime
        relink_sharedobjects(pkg_path, prefix)
        print("completed operation for: " + pkg_path)

if __name__ == '__main__':
    main()

[((23, 8, 30, 5), 'conda.cli.conda_argparse.ArgumentParser', 'ArgumentParser', (), '', False, 'from conda.cli.conda_argparse import ArgumentParser\n'), ((61, 4, 61, 24), 'conda.cli.common.add_parser_prefix', 'add_parser_prefix', ({(61, 22, 61, 23): 'p'}, {}), '(p)', False, 'from conda.cli.common import add_parser_prefix, get_prefix\n'), ((65, 4, 65, 22), 'conda_build.main_build.args_func', 'args_func', ({(65, 14, 65, 18): 'args', (65, 20, 65, 21): 'p'}, {}), '(args, p)', False, 'from conda_build.main_build import args_func\n'), ((81, 16, 81, 43), 'conda_build.utils.rec_glob', 'rec_glob', ({(81, 25, 81, 33): 'pkg_path', (81, 35, 81, 42): "['.so']"}, {}), "(pkg_path, ['.so'])", False, 'from conda_build.utils import _check_call, rec_glob\n'), ((97, 13, 97, 38), 'os.path.join', 'join', ({(97, 18, 97, 24): 'sp_dir', (97, 26, 97, 37): '"""conda.pth"""'}, {}), "(sp_dir, 'conda.pth')", False, 'from os.path import join, isdir, abspath, expanduser, exists\n'), ((122, 17, 123, 46), 'os.path.join', 'join', ({(122, 22, 122, 28): 'prefix', (122, 30, 123, 45): "'Lib' if sys.platform == 'win32' else 'lib/python%s' % py_ver"}, {}), "(prefix, 'Lib' if sys.platform == 'win32' else 'lib/python%s' % py_ver)", False, 'from os.path import join, isdir, abspath, expanduser, exists\n'), ((124, 13, 124, 46), 'os.path.join', 'join', ({(124, 18, 124, 28): 'stdlib_dir', (124, 30, 124, 45): '"""site-packages"""'}, {}), "(stdlib_dir, 'site-packages')", False, 'from os.path import join, isdir, abspath, expanduser, exists\n'), ((132, 15, 132, 38), 'os.path.join', 'join', ({(132, 20, 132, 25): 'path_', (132, 27, 132, 37): '"""setup.py"""'}, {}), "(path_, 'setup.py')", False, 'from os.path import join, isdir, abspath, expanduser, exists\n'), ((149, 4, 149, 20), 'conda_build.utils._check_call', '_check_call', ({(149, 16, 149, 19): 'cmd'}, {}), '(cmd)', False, 'from conda_build.utils import _check_call, rec_glob\n'), ((167, 4, 167, 20), 'conda_build.utils._check_call', '_check_call', ({(167, 16, 167, 19): 'cmd'}, {}), '(cmd)', False, 'from conda_build.utils import _check_call, rec_glob\n'), ((181, 14, 181, 39), 'os.path.join', 'join', ({(181, 19, 181, 25): 'sp_dir', (181, 27, 181, 38): '"""conda.pth"""'}, {}), "(sp_dir, 'conda.pth')", False, 'from os.path import join, isdir, abspath, expanduser, exists\n'), ((182, 14, 182, 44), 'os.path.join', 'join', ({(182, 19, 182, 25): 'sp_dir', (182, 27, 182, 43): '"""conda.pth.temp"""'}, {}), "(sp_dir, 'conda.pth.temp')", False, 'from os.path import join, isdir, abspath, expanduser, exists\n'), ((197, 4, 197, 33), 'shutil.move', 'shutil.move', ({(197, 16, 197, 23): 'n_c_pth', (197, 25, 197, 32): 'o_c_pth'}, {}), '(n_c_pth, o_c_pth)', False, 'import shutil\n'), ((201, 13, 201, 29), 'conda.cli.common.get_prefix', 'get_prefix', ({(201, 24, 201, 28): 'args'}, {}), '(args)', False, 'from conda.cli.common import add_parser_prefix, get_prefix\n'), ((208, 19, 208, 33), 'conda.install.linked', 'linked', ({(208, 26, 208, 32): 'prefix'}, {}), '(prefix)', False, 'from conda.install import linked\n'), ((134, 11, 134, 27), 'os.path.exists', 'exists', ({(134, 18, 134, 26): 'setup_py'}, {}), '(setup_py)', False, 'from os.path import join, isdir, abspath, expanduser, exists\n'), ((202, 11, 202, 24), 'os.path.isdir', 'isdir', ({(202, 17, 202, 23): 'prefix'}, {}), '(prefix)', False, 'from os.path import join, isdir, abspath, expanduser, exists\n'), ((203, 8, 207, 14), 'sys.exit', 'sys.exit', ({(203, 17, 207, 13): '("""Error: environment does not exist: %s\n#\n# Use \'conda create\' to create the environment first.\n#"""\n     % prefix)'}, {}), '(\n    """Error: environment does not exist: %s\n#\n# Use \'conda create\' to create the environment first.\n#"""\n     % prefix)', False, 'import sys\n'), ((84, 12, 84, 49), 'conda_build.post.mk_relative_osx', 'mk_relative_osx', ({(84, 28, 84, 34): 'b_file', (84, 36, 84, 48): 'build_prefix'}, {}), '(b_file, build_prefix)', False, 'from conda_build.post import mk_relative_osx\n'), ((220, 27, 220, 43), 'os.path.expanduser', 'expanduser', ({(220, 38, 220, 42): 'path'}, {}), '(path)', False, 'from os.path import join, isdir, abspath, expanduser, exists\n'), ((225, 12, 225, 23), 'sys.exit', 'sys.exit', ({(225, 21, 225, 22): '(0)'}, {}), '(0)', False, 'import sys\n'), ((232, 20, 232, 31), 'sys.exit', 'sys.exit', ({(232, 29, 232, 30): '(0)'}, {}), '(0)', False, 'import sys\n')]