# 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))

[((720, 752), 'colcon_core.logging.colcon_logger.getChild', 'colcon_logger.getChild', (['__name__'], {}), '(__name__)\n', (742, 752), False, 'from colcon_core.logging import colcon_logger\n'), ((905, 974), 'colcon_core.plugin_system.satisfies_version', 'satisfies_version', (['TaskExtensionPoint.EXTENSION_POINT_VERSION', '"""^1.0"""'], {}), "(TaskExtensionPoint.EXTENSION_POINT_VERSION, '^1.0')\n", (922, 974), False, 'from colcon_core.plugin_system import satisfies_version\n'), ((1123, 1142), 'os.walk', 'os.walk', (['start_path'], {}), '(start_path)\n', (1130, 1142), False, 'import os\n'), ((3723, 3753), 'os.path.join', 'os.path.join', (['args.path', '"""src"""'], {}), "(args.path, 'src')\n", (3735, 3753), False, 'import os\n'), ((3784, 3820), 'os.path.join', 'os.path.join', (['args.build_base', '"""src"""'], {}), "(args.build_base, 'src')\n", (3796, 3820), False, 'import os\n'), ((4550, 4606), 'distutils.dir_util.copy_tree', 'dir_util.copy_tree', (['args.path', 'args.build_base'], {'update': '(1)'}), '(args.path, args.build_base, update=1)\n', (4568, 4606), False, 'from distutils import dir_util\n'), ((4647, 4675), 'colcon_gradle.task.gradle.has_wrapper_executable', 'has_wrapper_executable', (['args'], {}), '(args)\n', (4669, 4675), False, 'from colcon_gradle.task.gradle import has_wrapper_executable\n'), ((5651, 5697), 'os.path.join', 'os.path.join', (['args.build_base', '"""build"""', '"""libs"""'], {}), "(args.build_base, 'build', 'libs')\n", (5663, 5697), False, 'import os\n'), ((5728, 5786), 'os.path.join', 'os.path.join', (['args.install_base', '"""share"""', 'pkg.name', '"""java"""'], {}), "(args.install_base, 'share', pkg.name, 'java')\n", (5740, 5786), False, 'import os\n'), ((5808, 5855), 'os.makedirs', 'os.makedirs', (['dst_package_jar_dir'], {'exist_ok': '(True)'}), '(dst_package_jar_dir, exist_ok=True)\n', (5819, 5855), False, 'import os\n'), ((2617, 2640), 'pathlib.Path', 'Path', (['args.install_base'], {}), '(args.install_base)\n', (2621, 2640), False, 'from pathlib import Path\n'), ((2677, 2721), 'os.path.join', 'os.path.join', (['"""share"""', 'pkg.name', '"""java"""', '"""*"""'], {}), "('share', pkg.name, 'java', '*')\n", (2689, 2721), False, 'import os\n'), ((2837, 2860), 'pathlib.Path', 'Path', (['args.install_base'], {}), '(args.install_base)\n', (2841, 2860), False, 'from pathlib import Path\n'), ((2897, 2936), 'os.path.join', 'os.path.join', (['"""share"""', 'pkg.name', '"""java"""'], {}), "('share', pkg.name, 'java')\n", (2909, 2936), False, 'import os\n'), ((3454, 3526), 'colcon_core.environment.create_environment_scripts', 'create_environment_scripts', (['pkg', 'args'], {'additional_hooks': 'additional_hooks'}), '(pkg, args, additional_hooks=additional_hooks)\n', (3480, 3526), False, 'from colcon_core.environment import create_environment_scripts\n'), ((4301, 4330), 'os.path.exists', 'os.path.exists', (['prune_dirname'], {}), '(prune_dirname)\n', (4315, 4330), False, 'import os\n'), ((5372, 5424), 'colcon_core.task.run', 'run', (['self.context', 'cmd'], {'cwd': 'args.build_base', 'env': 'env'}), '(self.context, cmd, cwd=args.build_base, env=env)\n', (5375, 5424), False, 'from colcon_core.task import run\n'), ((6336, 6365), 'os.path.exists', 'os.path.exists', (['prune_dirname'], {}), '(prune_dirname)\n', (6350, 6365), False, 'import os\n'), ((6498, 6540), 'os.path.join', 'os.path.join', (['bld_package_jar_dir', '"""*.jar"""'], {}), "(bld_package_jar_dir, '*.jar')\n", (6510, 6540), False, 'import os\n'), ((6570, 6591), 'os.path.basename', 'os.path.basename', (['jar'], {}), '(jar)\n', (6586, 6591), False, 'import os\n'), ((3004, 3080), 'colcon_core.shell.get_command_environment', 'get_command_environment', (['"""build"""', 'args.build_base', 'self.context.dependencies'], {}), "('build', args.build_base, self.context.dependencies)\n", (3027, 3080), False, 'from colcon_core.shell import get_command_environment\n'), ((4190, 4239), 'os.path.join', 'os.path.join', (['dst_package_src_dir', 'prune_filename'], {}), '(dst_package_src_dir, prune_filename)\n', (4202, 4239), False, 'import os\n'), ((6225, 6274), 'os.path.join', 'os.path.join', (['dst_package_jar_dir', 'prune_filename'], {}), '(dst_package_jar_dir, prune_filename)\n', (6237, 6274), False, 'import os\n'), ((6622, 6669), 'os.path.join', 'os.path.join', (['dst_package_jar_dir', 'jar_filename'], {}), '(dst_package_jar_dir, jar_filename)\n', (6634, 6669), False, 'import os\n'), ((4362, 4410), 'os.path.join', 'os.path.join', (['dst_package_src_dir', 'prune_dirname'], {}), '(dst_package_src_dir, prune_dirname)\n', (4374, 4410), False, 'import os\n'), ((6418, 6466), 'os.path.join', 'os.path.join', (['dst_package_jar_dir', 'prune_dirname'], {}), '(dst_package_jar_dir, prune_dirname)\n', (6430, 6466), False, 'import os\n'), ((1279, 1312), 'os.path.join', 'os.path.join', (['dirname', 'subdirname'], {}), '(dirname, subdirname)\n', (1291, 1312), False, 'import os\n'), ((1469, 1500), 'os.path.join', 'os.path.join', (['dirname', 'filename'], {}), '(dirname, filename)\n', (1481, 1500), False, 'import os\n'), ((4700, 4728), 'colcon_gradle.task.gradle.get_wrapper_executable', 'get_wrapper_executable', (['args'], {}), '(args)\n', (4722, 4728), False, 'from colcon_gradle.task.gradle import get_wrapper_executable\n')]