# Copyright 2018 Esteve Fernandez
# Licensed under the Apache License, Version 2.0

from distutils import dir_util
import glob
import os
from pathlib import Path
import shutil

from colcon_core.environment import create_environment_scripts
from colcon_core.logging import colcon_logger
from colcon_core.plugin_system import satisfies_version
from colcon_core.shell import create_environment_hook
from colcon_core.shell import get_command_environment
from colcon_core.task import run
from colcon_core.task import TaskExtensionPoint
from colcon_gradle.task.gradle import get_wrapper_executable
from colcon_gradle.task.gradle import GRADLE_EXECUTABLE
from colcon_gradle.task.gradle import has_wrapper_executable


logger = colcon_logger.getChild(__name__)


class GradleBuildTask(TaskExtensionPoint):
    """Build gradle packages."""

    def __init__(self):  # noqa: D107
        super().__init__()
        satisfies_version(TaskExtensionPoint.EXTENSION_POINT_VERSION, '^1.0')

    def _build_file_tree(self, start_path):
        out_dirnames = set()
        out_filenames = set()
        for dirname, dirnames, filenames in os.walk(start_path):
            for subdirname in dirnames:
                out_dirnames.add(
                    os.path.relpath(
                        os.path.join(dirname, subdirname), start=start_path))

            for filename in filenames:
                out_filenames.add(
                    os.path.relpath(
                        os.path.join(dirname, filename), start=start_path))
        return (out_dirnames, out_filenames)

    def add_arguments(self, *, parser):  # noqa: D102
        parser.add_argument(
            '--gradle-args',
            nargs='*', metavar='*', type=str.lstrip,
            help='Pass arguments to Gradle projects. '
            'Arguments matching other options must be prefixed by a space,\n'
            'e.g. --gradle-args " --help"')
        parser.add_argument(
            '--gradle-task',
            help='Run a specific task instead of the default task')

    async def build(  # noqa: D102
        self, *, additional_hooks=None, skip_hook_creation=False
    ):
        pkg = self.context.pkg
        args = self.context.args

        logger.info(
            "Building Gradle package in '{args.path}'".format_map(locals()))

        if additional_hooks is None:
            additional_hooks = []

        # add jars and classes to CLASSPATH with wildcards
        # https://docs.oracle.com/javase/8/docs/technotes/tools/windows/classpath.html#A1100762
        additional_hooks += create_environment_hook(
            'classpath_jars', Path(args.install_base), pkg.name,
            'CLASSPATH', os.path.join('share', pkg.name, 'java', '*'),
            mode='prepend')
        additional_hooks += create_environment_hook(
            'classpath_classes', Path(args.install_base), pkg.name,
            'CLASSPATH', os.path.join('share', pkg.name, 'java'),
            mode='prepend')

        try:
            env = await get_command_environment(
                'build', args.build_base, self.context.dependencies)
        except RuntimeError as e:
            logger.error(str(e))
            return 1

        rc = await self._build(args, env)
        if rc and rc.returncode:
            return rc.returncode

        rc = await self._install(args, env)
        if rc and rc.returncode:
            return rc.returncode

        if not skip_hook_creation:
            create_environment_scripts(
                pkg, args, additional_hooks=additional_hooks)

    async def _build(self, args, env):
        self.progress('build')

        # remove anything on the destination tree but not in the source tree
        src_package_src_dir = os.path.join(args.path, 'src')
        dst_package_src_dir = os.path.join(args.build_base, 'src')

        src_dirnames, src_filenames = self._build_file_tree(
            src_package_src_dir)
        dst_dirnames, dst_filenames = self._build_file_tree(
            dst_package_src_dir)

        prune_dirnames = dst_dirnames - src_dirnames
        prune_filenames = dst_filenames - src_filenames

        for prune_filename in prune_filenames:
            os.remove(os.path.join(dst_package_src_dir, prune_filename))
        for prune_dirname in prune_dirnames:
            if os.path.exists(prune_dirname):
                shutil.rmtree(os.path.join(dst_package_src_dir, prune_dirname))

        # copy files from the source directory to the build one to avoid
        # polluting the latter during the build process
        dir_util.copy_tree(args.path, args.build_base, update=1)

        # Gradle Executable
        if has_wrapper_executable(args):
            cmd = [str(get_wrapper_executable(args).absolute())]
        elif GRADLE_EXECUTABLE is not None:
            cmd = [GRADLE_EXECUTABLE]
        else:
            raise RuntimeError(
                "Could not find 'gradle' or 'wrapper' executable")

        # Gradle Task (by default 'assemble')
        if args.gradle_task:
            cmd += [args.gradle_task]
        else:
            cmd += ['assemble']

        # Gradle Arguments
        cmd += (args.gradle_args or [])
        cmd += ['--stacktrace']

        # Add install_base to environment in GRADLE_INSTALL_PREFIX
        env['GRADLE_INSTALL_PREFIX'] = args.install_base

        # invoke build step
        return await run(
            self.context, cmd, cwd=args.build_base, env=env)

    async def _install(self, args, env):
        self.progress('install')
        pkg = self.context.pkg

        # remove anything on the destination tree but not in the build tree
        bld_package_jar_dir = os.path.join(args.build_base, 'build', 'libs')
        dst_package_jar_dir = os.path.join(
            args.install_base, 'share', pkg.name, 'java')
        os.makedirs(dst_package_jar_dir, exist_ok=True)

        bld_dirnames, bld_filenames = self._build_file_tree(
            bld_package_jar_dir)
        dst_dirnames, dst_filenames = self._build_file_tree(
            dst_package_jar_dir)

        prune_dirnames = dst_dirnames - bld_dirnames
        prune_filenames = dst_filenames - bld_filenames

        for prune_filename in prune_filenames:
            os.remove(os.path.join(dst_package_jar_dir, prune_filename))
        for prune_dirname in prune_dirnames:
            if os.path.exists(prune_dirname):
                shutil.rmtree(
                    os.path.join(dst_package_jar_dir, prune_dirname))

        for jar in glob.glob(os.path.join(bld_package_jar_dir, '*.jar')):
            jar_filename = os.path.basename(jar)
            shutil.copy2(jar, os.path.join(dst_package_jar_dir, jar_filename))

[((22, 9, 22, 41), 'colcon_core.logging.colcon_logger.getChild', 'colcon_logger.getChild', ({(22, 32, 22, 40): '__name__'}, {}), '(__name__)', False, 'from colcon_core.logging import colcon_logger\n'), ((30, 8, 30, 77), 'colcon_core.plugin_system.satisfies_version', 'satisfies_version', ({(30, 26, 30, 68): 'TaskExtensionPoint.EXTENSION_POINT_VERSION', (30, 70, 30, 76): '"""^1.0"""'}, {}), "(TaskExtensionPoint.EXTENSION_POINT_VERSION, '^1.0')", False, 'from colcon_core.plugin_system import satisfies_version\n'), ((35, 44, 35, 63), 'os.walk', 'os.walk', ({(35, 52, 35, 62): 'start_path'}, {}), '(start_path)', False, 'import os\n'), ((104, 30, 104, 60), 'os.path.join', 'os.path.join', ({(104, 43, 104, 52): 'args.path', (104, 54, 104, 59): '"""src"""'}, {}), "(args.path, 'src')", False, 'import os\n'), ((105, 30, 105, 66), 'os.path.join', 'os.path.join', ({(105, 43, 105, 58): 'args.build_base', (105, 60, 105, 65): '"""src"""'}, {}), "(args.build_base, 'src')", False, 'import os\n'), ((123, 8, 123, 64), 'distutils.dir_util.copy_tree', 'dir_util.copy_tree', (), '', False, 'from distutils import dir_util\n'), ((126, 11, 126, 39), 'colcon_gradle.task.gradle.has_wrapper_executable', 'has_wrapper_executable', ({(126, 34, 126, 38): 'args'}, {}), '(args)', False, 'from colcon_gradle.task.gradle import has_wrapper_executable\n'), ((156, 30, 156, 76), 'os.path.join', 'os.path.join', ({(156, 43, 156, 58): 'args.build_base', (156, 60, 156, 67): '"""build"""', (156, 69, 156, 75): '"""libs"""'}, {}), "(args.build_base, 'build', 'libs')", False, 'import os\n'), ((157, 30, 158, 57), 'os.path.join', 'os.path.join', ({(158, 12, 158, 29): 'args.install_base', (158, 31, 158, 38): '"""share"""', (158, 40, 158, 48): 'pkg.name', (158, 50, 158, 56): '"""java"""'}, {}), "(args.install_base, 'share', pkg.name, 'java')", False, 'import os\n'), ((159, 8, 159, 55), 'os.makedirs', 'os.makedirs', (), '', False, 'import os\n'), ((73, 30, 73, 53), 'pathlib.Path', 'Path', ({(73, 35, 73, 52): 'args.install_base'}, {}), '(args.install_base)', False, 'from pathlib import Path\n'), ((74, 25, 74, 69), 'os.path.join', 'os.path.join', ({(74, 38, 74, 45): '"""share"""', (74, 47, 74, 55): 'pkg.name', (74, 57, 74, 63): '"""java"""', (74, 65, 74, 68): '"""*"""'}, {}), "('share', pkg.name, 'java', '*')", False, 'import os\n'), ((77, 33, 77, 56), 'pathlib.Path', 'Path', ({(77, 38, 77, 55): 'args.install_base'}, {}), '(args.install_base)', False, 'from pathlib import Path\n'), ((78, 25, 78, 64), 'os.path.join', 'os.path.join', ({(78, 38, 78, 45): '"""share"""', (78, 47, 78, 55): 'pkg.name', (78, 57, 78, 63): '"""java"""'}, {}), "('share', pkg.name, 'java')", False, 'import os\n'), ((97, 12, 98, 61), 'colcon_core.environment.create_environment_scripts', 'create_environment_scripts', (), '', False, 'from colcon_core.environment import create_environment_scripts\n'), ((118, 15, 118, 44), 'os.path.exists', 'os.path.exists', ({(118, 30, 118, 43): 'prune_dirname'}, {}), '(prune_dirname)', False, 'import os\n'), ((148, 21, 149, 60), 'colcon_core.task.run', 'run', (), '', False, 'from colcon_core.task import run\n'), ((172, 15, 172, 44), 'os.path.exists', 'os.path.exists', ({(172, 30, 172, 43): 'prune_dirname'}, {}), '(prune_dirname)', False, 'import os\n'), ((176, 29, 176, 71), 'os.path.join', 'os.path.join', ({(176, 42, 176, 61): 'bld_package_jar_dir', (176, 63, 176, 70): '"""*.jar"""'}, {}), "(bld_package_jar_dir, '*.jar')", False, 'import os\n'), ((177, 27, 177, 48), 'os.path.basename', 'os.path.basename', ({(177, 44, 177, 47): 'jar'}, {}), '(jar)', False, 'import os\n'), ((82, 24, 83, 68), 'colcon_core.shell.get_command_environment', 'get_command_environment', ({(83, 16, 83, 23): '"""build"""', (83, 25, 83, 40): 'args.build_base', (83, 42, 83, 67): 'self.context.dependencies'}, {}), "('build', args.build_base, self.context.dependencies)", False, 'from colcon_core.shell import get_command_environment\n'), ((116, 22, 116, 71), 'os.path.join', 'os.path.join', ({(116, 35, 116, 54): 'dst_package_src_dir', (116, 56, 116, 70): 'prune_filename'}, {}), '(dst_package_src_dir, prune_filename)', False, 'import os\n'), ((170, 22, 170, 71), 'os.path.join', 'os.path.join', ({(170, 35, 170, 54): 'dst_package_jar_dir', (170, 56, 170, 70): 'prune_filename'}, {}), '(dst_package_jar_dir, prune_filename)', False, 'import os\n'), ((178, 30, 178, 77), 'os.path.join', 'os.path.join', ({(178, 43, 178, 62): 'dst_package_jar_dir', (178, 64, 178, 76): 'jar_filename'}, {}), '(dst_package_jar_dir, jar_filename)', False, 'import os\n'), ((119, 30, 119, 78), 'os.path.join', 'os.path.join', ({(119, 43, 119, 62): 'dst_package_src_dir', (119, 64, 119, 77): 'prune_dirname'}, {}), '(dst_package_src_dir, prune_dirname)', False, 'import os\n'), ((174, 20, 174, 68), 'os.path.join', 'os.path.join', ({(174, 33, 174, 52): 'dst_package_jar_dir', (174, 54, 174, 67): 'prune_dirname'}, {}), '(dst_package_jar_dir, prune_dirname)', False, 'import os\n'), ((39, 24, 39, 57), 'os.path.join', 'os.path.join', ({(39, 37, 39, 44): 'dirname', (39, 46, 39, 56): 'subdirname'}, {}), '(dirname, subdirname)', False, 'import os\n'), ((44, 24, 44, 55), 'os.path.join', 'os.path.join', ({(44, 37, 44, 44): 'dirname', (44, 46, 44, 54): 'filename'}, {}), '(dirname, filename)', False, 'import os\n'), ((127, 23, 127, 51), 'colcon_gradle.task.gradle.get_wrapper_executable', 'get_wrapper_executable', ({(127, 46, 127, 50): 'args'}, {}), '(args)', False, 'from colcon_gradle.task.gradle import get_wrapper_executable\n')]