• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2#
3# Copyright 2018 - The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""common_util
18
19This module has a collection of functions that provide helper functions to
20other modules.
21"""
22
23import fnmatch
24import inspect
25import json
26import logging
27import os
28import re
29import sys
30import time
31import xml.dom.minidom
32import zipfile
33
34from functools import partial
35from functools import wraps
36from xml.etree import ElementTree
37
38from aidegen import constant
39from aidegen.lib import errors
40from atest import atest_utils
41from atest import constants
42from atest import module_info
43
44
45COLORED_INFO = partial(atest_utils.colorize, color=constants.MAGENTA)
46COLORED_PASS = partial(atest_utils.colorize, color=constants.GREEN)
47COLORED_FAIL = partial(atest_utils.colorize, color=constants.RED)
48FAKE_MODULE_ERROR = '{} is a fake module.'
49OUTSIDE_ROOT_ERROR = '{} is outside android root.'
50PATH_NOT_EXISTS_ERROR = 'The path {} doesn\'t exist.'
51NO_MODULE_DEFINED_ERROR = 'No modules defined at {}.'
52_REBUILD_MODULE_INFO = '%s We should rebuild module-info.json file for it.'
53_ENVSETUP_NOT_RUN = ('Please run "source build/envsetup.sh" and "lunch" before '
54                     'running aidegen.')
55_LOG_FORMAT = '%(asctime)s %(filename)s:%(lineno)s:%(levelname)s: %(message)s'
56_DATE_FORMAT = '%Y-%m-%d %H:%M:%S'
57_ARG_IS_NULL_ERROR = "{0}.{1}: argument '{2}' is null."
58_ARG_TYPE_INCORRECT_ERROR = "{0}.{1}: argument '{2}': type is {3}, must be {4}."
59_IDE_UNDEFINED = constant.IDE_DICT[constant.IDE_UNDEFINED]
60_IDE_INTELLIJ = constant.IDE_DICT[constant.IDE_INTELLIJ]
61_IDE_CLION = constant.IDE_DICT[constant.IDE_CLION]
62_IDE_VSCODE = constant.IDE_DICT[constant.IDE_VSCODE]
63
64
65def time_logged(func=None, *, message='', maximum=1):
66    """Decorate a function to find out how much time it spends.
67
68    Args:
69        func: a function is to be calculated its spending time.
70        message: the message the decorated function wants to show.
71        maximum: an integer, minutes. If time exceeds the maximum time show
72                 message, otherwise doesn't.
73
74    Returns:
75        The wrapper function.
76    """
77    if func is None:
78        return partial(time_logged, message=message, maximum=maximum)
79
80    @wraps(func)
81    def wrapper(*args, **kwargs):
82        """A wrapper function."""
83
84        start = time.time()
85        try:
86            return func(*args, **kwargs)
87        finally:
88            timestamp = time.time() - start
89            logging.debug('{}.{} takes: {:.2f}s'.format(
90                func.__module__, func.__name__, timestamp))
91            if message and timestamp > maximum * 60:
92                print(message)
93
94    return wrapper
95
96
97def get_related_paths(atest_module_info, target=None):
98    """Get the relative and absolute paths of target from module-info.
99
100    Args:
101        atest_module_info: A ModuleInfo instance.
102        target: A string user input from command line. It could be several cases
103                such as:
104                1. Module name, e.g. Settings
105                2. Module path, e.g. packages/apps/Settings
106                3. Relative path, e.g. ../../packages/apps/Settings
107                4. Current directory, e.g. '.' or no argument
108                5. An empty string, which added by AIDEGen, used for generating
109                   the iml files for the whole Android repo tree.
110                   e.g.
111                   1. ~/aosp$ aidegen
112                   2. ~/aosp/frameworks/base$ aidegen -a
113                6. An absolute path, e.g. /usr/local/home/test/aosp
114
115    Return:
116        rel_path: The relative path of a module, return None if no matching
117                  module found.
118        abs_path: The absolute path of a module, return None if no matching
119                  module found.
120    """
121    rel_path = None
122    abs_path = None
123    # take the command 'aidegen .' as 'aidegen'.
124    if target == '.':
125        target = None
126    if target:
127        # For the case of whole Android repo tree.
128        if target == constant.WHOLE_ANDROID_TREE_TARGET:
129            rel_path = ''
130            abs_path = get_android_root_dir()
131        # User inputs an absolute path.
132        elif os.path.isabs(target):
133            abs_path = target
134            rel_path = os.path.relpath(abs_path, get_android_root_dir())
135        # User inputs a module name.
136        elif atest_module_info.is_module(target):
137            paths = atest_module_info.get_paths(target)
138            if paths:
139                rel_path = paths[0].strip(os.sep)
140                abs_path = os.path.join(get_android_root_dir(), rel_path)
141        # User inputs a module path or a relative path of android root folder.
142        elif (atest_module_info.get_module_names(target)
143              or os.path.isdir(os.path.join(get_android_root_dir(), target))):
144            rel_path = target.strip(os.sep)
145            abs_path = os.path.join(get_android_root_dir(), rel_path)
146        # User inputs a relative path of current directory.
147        else:
148            abs_path = os.path.abspath(os.path.join(os.getcwd(), target))
149            rel_path = os.path.relpath(abs_path, get_android_root_dir())
150    else:
151        # User doesn't input.
152        abs_path = os.getcwd()
153        if is_android_root(abs_path):
154            rel_path = ''
155        else:
156            rel_path = os.path.relpath(abs_path, get_android_root_dir())
157    return rel_path, abs_path
158
159
160def is_target_android_root(atest_module_info, targets):
161    """Check if any target is the Android root path.
162
163    Args:
164        atest_module_info: A ModuleInfo instance contains data of
165                           module-info.json.
166        targets: A list of target modules or project paths from user input.
167
168    Returns:
169        True if target is Android root, otherwise False.
170    """
171    for target in targets:
172        _, abs_path = get_related_paths(atest_module_info, target)
173        if is_android_root(abs_path):
174            return True
175    return False
176
177
178def is_android_root(abs_path):
179    """Check if an absolute path is the Android root path.
180
181    Args:
182        abs_path: The absolute path of a module.
183
184    Returns:
185        True if abs_path is Android root, otherwise False.
186    """
187    return abs_path == get_android_root_dir()
188
189
190def has_build_target(atest_module_info, rel_path):
191    """Determine if a relative path contains buildable module.
192
193    Args:
194        atest_module_info: A ModuleInfo instance contains data of
195                           module-info.json.
196        rel_path: The module path relative to android root.
197
198    Returns:
199        True if the relative path contains a build target, otherwise false.
200    """
201    return any(
202        is_source_under_relative_path(mod_path, rel_path)
203        for mod_path in atest_module_info.path_to_module_info)
204
205
206def _check_modules(atest_module_info, targets, raise_on_lost_module=True):
207    """Check if all targets are valid build targets.
208
209    Args:
210        atest_module_info: A ModuleInfo instance contains data of
211                           module-info.json.
212        targets: A list of target modules or project paths from user input.
213                When locating the path of the target, given a matched module
214                name has priority over path. Below is the priority of locating a
215                target:
216                1. Module name, e.g. Settings
217                2. Module path, e.g. packages/apps/Settings
218                3. Relative path, e.g. ../../packages/apps/Settings
219                4. Current directory, e.g. '.' or no argument
220        raise_on_lost_module: A boolean, pass to _check_module to determine if
221                ProjectPathNotExistError or NoModuleDefinedInModuleInfoError
222                should be raised.
223
224    Returns:
225        True if any _check_module return flip the True/False.
226    """
227    for target in targets:
228        if not check_module(atest_module_info, target, raise_on_lost_module):
229            return False
230    return True
231
232
233def check_module(atest_module_info, target, raise_on_lost_module=True):
234    """Check if a target is valid or it's a path containing build target.
235
236    Args:
237        atest_module_info: A ModuleInfo instance contains the data of
238                module-info.json.
239        target: A target module or project path from user input.
240                When locating the path of the target, given a matched module
241                name has priority over path. Below is the priority of locating a
242                target:
243                1. Module name, e.g. Settings
244                2. Module path, e.g. packages/apps/Settings
245                3. Relative path, e.g. ../../packages/apps/Settings
246                4. Current directory, e.g. . or no argument
247                5. An absolute path, e.g. /usr/local/home/test/aosp
248        raise_on_lost_module: A boolean, handles if ProjectPathNotExistError or
249                NoModuleDefinedInModuleInfoError should be raised.
250
251    Returns:
252        1. If there is no error _check_module always return True.
253        2. If there is an error,
254            a. When raise_on_lost_module is False, _check_module will raise the
255               error.
256            b. When raise_on_lost_module is True, _check_module will return
257               False if module's error is ProjectPathNotExistError or
258               NoModuleDefinedInModuleInfoError else raise the error.
259
260    Raises:
261        Raise ProjectPathNotExistError and NoModuleDefinedInModuleInfoError only
262        when raise_on_lost_module is True, others don't subject to the limit.
263        The rules for raising exceptions:
264        1. Absolute path of a module is None -> FakeModuleError
265        2. Module doesn't exist in repo root -> ProjectOutsideAndroidRootError
266        3. The given absolute path is not a dir -> ProjectPathNotExistError
267        4. If the given abs path doesn't contain any target and not repo root
268           -> NoModuleDefinedInModuleInfoError
269    """
270    rel_path, abs_path = get_related_paths(atest_module_info, target)
271    if not abs_path:
272        err = FAKE_MODULE_ERROR.format(target)
273        logging.error(err)
274        raise errors.FakeModuleError(err)
275    if not is_source_under_relative_path(abs_path, get_android_root_dir()):
276        err = OUTSIDE_ROOT_ERROR.format(abs_path)
277        logging.error(err)
278        raise errors.ProjectOutsideAndroidRootError(err)
279    if not os.path.isdir(abs_path):
280        err = PATH_NOT_EXISTS_ERROR.format(rel_path)
281        if raise_on_lost_module:
282            logging.error(err)
283            raise errors.ProjectPathNotExistError(err)
284        logging.debug(_REBUILD_MODULE_INFO, err)
285        return False
286    if (not has_build_target(atest_module_info, rel_path)
287            and not is_android_root(abs_path)):
288        err = NO_MODULE_DEFINED_ERROR.format(rel_path)
289        if raise_on_lost_module:
290            logging.error(err)
291            raise errors.NoModuleDefinedInModuleInfoError(err)
292        logging.debug(_REBUILD_MODULE_INFO, err)
293        return False
294    return True
295
296
297def get_abs_path(rel_path):
298    """Get absolute path from a relative path.
299
300    Args:
301        rel_path: A string, a relative path to get_android_root_dir().
302
303    Returns:
304        abs_path: A string, an absolute path starts with
305                  get_android_root_dir().
306    """
307    if not rel_path:
308        return get_android_root_dir()
309    if is_source_under_relative_path(rel_path, get_android_root_dir()):
310        return rel_path
311    return os.path.join(get_android_root_dir(), rel_path)
312
313
314def is_target(src_file, src_file_extensions):
315    """Check if src_file is a type of src_file_extensions.
316
317    Args:
318        src_file: A string of the file path to be checked.
319        src_file_extensions: A list of file types to be checked
320
321    Returns:
322        True if src_file is one of the types of src_file_extensions, otherwise
323        False.
324    """
325    return any(src_file.endswith(x) for x in src_file_extensions)
326
327
328def get_atest_module_info(targets=None):
329    """Get the right version of atest ModuleInfo instance.
330
331    The rules:
332        1. If targets is None:
333           We just want to get an atest ModuleInfo instance.
334        2. If targets isn't None:
335           Check if the targets don't exist in ModuleInfo, we'll regain a new
336           atest ModuleInfo instance by setting force_build=True and call
337           _check_modules again. If targets still don't exist, raise exceptions.
338
339    Args:
340        targets: A list of targets to be built.
341
342    Returns:
343        An atest ModuleInfo instance.
344    """
345    amodule_info = module_info.ModuleInfo()
346    if targets and not _check_modules(
347            amodule_info, targets, raise_on_lost_module=False):
348        amodule_info = module_info.ModuleInfo(force_build=True)
349        _check_modules(amodule_info, targets)
350    return amodule_info
351
352
353def get_blueprint_json_path(file_name):
354    """Assemble the path of blueprint json file.
355
356    Args:
357        file_name: A string of blueprint json file name.
358
359    Returns:
360        The path of json file.
361    """
362    return os.path.join(get_soong_out_path(), file_name)
363
364
365def back_to_cwd(func):
366    """Decorate a function change directory back to its original one.
367
368    Args:
369        func: a function is to be changed back to its original directory.
370
371    Returns:
372        The wrapper function.
373    """
374
375    @wraps(func)
376    def wrapper(*args, **kwargs):
377        """A wrapper function."""
378        cwd = os.getcwd()
379        try:
380            return func(*args, **kwargs)
381        finally:
382            os.chdir(cwd)
383
384    return wrapper
385
386
387def get_android_out_dir():
388    """Get out directory in different conditions.
389
390    Returns:
391        Android out directory path.
392    """
393    android_root_path = get_android_root_dir()
394    android_out_dir = os.getenv(constants.ANDROID_OUT_DIR)
395    out_dir_common_base = os.getenv(constant.OUT_DIR_COMMON_BASE_ENV_VAR)
396    android_out_dir_common_base = (os.path.join(
397        out_dir_common_base, os.path.basename(android_root_path))
398                                   if out_dir_common_base else None)
399    return (android_out_dir or android_out_dir_common_base
400            or constant.ANDROID_DEFAULT_OUT)
401
402
403def get_android_root_dir():
404    """Get Android root directory.
405
406    If the path doesn't exist show message to ask users to run envsetup.sh and
407    lunch first.
408
409    Returns:
410        Android root directory path.
411    """
412    android_root_path = os.environ.get(constants.ANDROID_BUILD_TOP)
413    if not android_root_path:
414        _show_env_setup_msg_and_exit()
415    return android_root_path
416
417
418def get_aidegen_root_dir():
419    """Get AIDEGen root directory.
420
421    Returns:
422        AIDEGen root directory path.
423    """
424    return os.path.join(get_android_root_dir(), constant.AIDEGEN_ROOT_PATH)
425
426
427def _show_env_setup_msg_and_exit():
428    """Show message if users didn't run envsetup.sh and lunch."""
429    print(_ENVSETUP_NOT_RUN)
430    sys.exit(0)
431
432
433def get_soong_out_path():
434    """Assemble out directory's soong path.
435
436    Returns:
437        Out directory's soong path.
438    """
439    return os.path.join(get_android_root_dir(), get_android_out_dir(), 'soong')
440
441
442def configure_logging(verbose):
443    """Configure the logger.
444
445    Args:
446        verbose: A boolean. If true, display DEBUG level logs.
447    """
448    log_format = _LOG_FORMAT
449    datefmt = _DATE_FORMAT
450    level = logging.DEBUG if verbose else logging.INFO
451    # Clear all handlers to prevent setting level not working.
452    logging.getLogger().handlers = []
453    logging.basicConfig(level=level, format=log_format, datefmt=datefmt)
454
455
456def exist_android_bp(abs_path):
457    """Check if the Android.bp exists under specific folder.
458
459    Args:
460        abs_path: An absolute path string.
461
462    Returns: A boolean, true if the Android.bp exists under the folder,
463             otherwise false.
464    """
465    return os.path.isfile(os.path.join(abs_path, constant.ANDROID_BP))
466
467
468def exist_android_mk(abs_path):
469    """Check if the Android.mk exists under specific folder.
470
471    Args:
472        abs_path: An absolute path string.
473
474    Returns: A boolean, true if the Android.mk exists under the folder,
475             otherwise false.
476    """
477    return os.path.isfile(os.path.join(abs_path, constant.ANDROID_MK))
478
479
480def is_source_under_relative_path(source, relative_path):
481    """Check if a source file is a project relative path file.
482
483    Args:
484        source: Android source file path.
485        relative_path: Relative path of the module.
486
487    Returns:
488        True if source file is a project relative path file, otherwise False.
489    """
490    return re.search(
491        constant.RE_INSIDE_PATH_CHECK.format(relative_path), source)
492
493
494def remove_user_home_path(data):
495    """Replace the user home path string with a constant string.
496
497    Args:
498        data: A string of xml content or an attributeError of error message.
499
500    Returns:
501        A string which replaced the user home path to $USER_HOME$.
502    """
503    return str(data).replace(os.path.expanduser('~'), constant.USER_HOME)
504
505
506def io_error_handle(func):
507    """Decorates a function of handling io error and raising exception.
508
509    Args:
510        func: A function is to be raised exception if writing file failed.
511
512    Returns:
513        The wrapper function.
514    """
515
516    @wraps(func)
517    def wrapper(*args, **kwargs):
518        """A wrapper function."""
519        try:
520            return func(*args, **kwargs)
521        except (OSError, IOError) as err:
522            print('{0}.{1} I/O error: {2}'.format(
523                func.__module__, func.__name__, err))
524            raise
525    return wrapper
526
527
528def check_args(**decls):
529    """Decorates a function to check its argument types.
530
531    Usage:
532        @check_args(name=str, text=str)
533        def parse_rule(name, text):
534            ...
535
536    Args:
537        decls: A dictionary with keys as arguments' names and values as
538             arguments' types.
539
540    Returns:
541        The wrapper function.
542    """
543
544    def decorator(func):
545        """A wrapper function."""
546        fmodule = func.__module__
547        fname = func.__name__
548        fparams = inspect.signature(func).parameters
549
550        @wraps(func)
551        def decorated(*args, **kwargs):
552            """A wrapper function."""
553            params = dict(zip(fparams, args))
554            for arg_name, arg_type in decls.items():
555                try:
556                    arg_val = params[arg_name]
557                except KeyError:
558                    # If arg_name can't be found in function's signature, it
559                    # might be a case of a partial function or default
560                    # parameters, we'll neglect it.
561                    if arg_name not in kwargs:
562                        continue
563                    arg_val = kwargs.get(arg_name)
564                if arg_val is None:
565                    raise TypeError(_ARG_IS_NULL_ERROR.format(
566                        fmodule, fname, arg_name))
567                if not isinstance(arg_val, arg_type):
568                    raise TypeError(_ARG_TYPE_INCORRECT_ERROR.format(
569                        fmodule, fname, arg_name, type(arg_val), arg_type))
570            return func(*args, **kwargs)
571        return decorated
572
573    return decorator
574
575
576@io_error_handle
577def dump_json_dict(json_path, data):
578    """Dumps a dictionary of data into a json file.
579
580    Args:
581        json_path: An absolute json file path string.
582        data: A dictionary of data to be written into a json file.
583    """
584    with open(json_path, 'w', encoding='utf-8') as json_file:
585        json.dump(data, json_file, indent=4)
586
587
588@io_error_handle
589def get_json_dict(json_path):
590    """Loads a json file from path and convert it into a json dictionary.
591
592    Args:
593        json_path: An absolute json file path string.
594
595    Returns:
596        A dictionary loaded from the json_path.
597    """
598    with open(json_path, 'r', encoding='utf-8') as jfile:
599        return json.load(jfile)
600
601
602@io_error_handle
603def read_file_line_to_list(file_path):
604    """Read a file line by line and write them into a list.
605
606    Args:
607        file_path: A string of a file's path.
608
609    Returns:
610        A list of the file's content by line.
611    """
612    files = []
613    with open(file_path, 'r', encoding='utf8') as infile:
614        for line in infile:
615            files.append(line.strip())
616    return files
617
618
619@io_error_handle
620def read_file_content(path, encode_type='utf8'):
621    """Read file's content.
622
623    Args:
624        path: Path of input file.
625        encode_type: A string of encoding name, default to UTF-8.
626
627    Returns:
628        String: Content of the file.
629    """
630    with open(path, 'r', encoding=encode_type) as template:
631        return template.read()
632
633
634@io_error_handle
635def file_generate(path, content):
636    """Generate file from content.
637
638    Args:
639        path: Path of target file.
640        content: String content of file.
641    """
642    if not os.path.exists(os.path.dirname(path)):
643        os.makedirs(os.path.dirname(path))
644    with open(path, 'w', encoding='utf-8') as target:
645        target.write(content)
646
647
648def get_lunch_target():
649    """Gets the Android lunch target in current console.
650
651    Returns:
652        A json format string of lunch target in current console.
653    """
654    product = os.environ.get(constant.TARGET_PRODUCT)
655    build_variant = os.environ.get(constant.TARGET_BUILD_VARIANT)
656    if product and build_variant:
657        return json.dumps(
658            {constant.LUNCH_TARGET: "-".join([product, build_variant])})
659    return None
660
661
662def get_blueprint_json_files_relative_dict():
663    """Gets a dictionary with key: environment variable, value: absolute path.
664
665    Returns:
666        A dictionary  with key: environment variable and value: absolute path of
667        the file generated by the environment variable.
668    """
669    data = {}
670    root_dir = get_android_root_dir()
671    bp_java_path = os.path.join(
672        root_dir, get_blueprint_json_path(
673            constant.BLUEPRINT_JAVA_JSONFILE_NAME))
674    data[constant.GEN_JAVA_DEPS] = bp_java_path
675    bp_cc_path = os.path.join(
676        root_dir, get_blueprint_json_path(constant.BLUEPRINT_CC_JSONFILE_NAME))
677    data[constant.GEN_CC_DEPS] = bp_cc_path
678    data[constant.GEN_COMPDB] = os.path.join(get_soong_out_path(),
679                                             constant.RELATIVE_COMPDB_PATH,
680                                             constant.COMPDB_JSONFILE_NAME)
681    data[constant.GEN_RUST] = os.path.join(
682        root_dir, get_blueprint_json_path(constant.RUST_PROJECT_JSON))
683    return data
684
685
686def to_pretty_xml(root, indent="  "):
687    """Gets pretty xml from an xml.etree.ElementTree root.
688
689    Args:
690        root: An element tree root.
691        indent: The indent of XML.
692    Returns:
693        A string of pretty xml.
694    """
695    xml_string = xml.dom.minidom.parseString(
696        ElementTree.tostring(root)).toprettyxml(indent)
697    # Remove the xml declaration since IntelliJ doesn't use it.
698    xml_string = xml_string.split("\n", 1)[1]
699    # Remove the weird newline issue from toprettyxml.
700    return os.linesep.join([s for s in xml_string.splitlines() if s.strip()])
701
702
703def to_boolean(str_bool):
704    """Converts a string to a boolean.
705
706    Args:
707        str_bool: A string in the expression of boolean type.
708
709    Returns:
710        A boolean True if the string is one of ('True', 'true', 'T', 't', '1')
711        else False.
712    """
713    return str_bool and str_bool.lower() in ('true', 't', '1')
714
715
716def find_git_root(relpath):
717    """Finds the parent directory which has a .git folder from the relpath.
718
719    Args:
720        relpath: A string of relative path.
721
722    Returns:
723        A string of the absolute path which contains a .git, otherwise, none.
724    """
725    dir_list = relpath.split(os.sep)
726    for i in range(len(dir_list), 0, -1):
727        real_path = os.path.join(get_android_root_dir(),
728                                 os.sep.join(dir_list[:i]),
729                                 constant.GIT_FOLDER_NAME)
730        if os.path.exists(real_path):
731            return os.path.dirname(real_path)
732    logging.warning('%s can\'t find its .git folder.', relpath)
733    return None
734
735
736def determine_language_ide(lang, ide, jlist=None, clist=None, rlist=None):
737    """Determines the language and IDE by the input language and IDE arguments.
738
739    If IDE and language are undefined, the priority of the language is:
740      1. Java
741      2. C/C++
742      3. Rust
743
744    Args:
745        lang: A character represents the input language.
746        ide: A character represents the input IDE.
747        jlist: A list of Android Java projects, the default value is None.
748        clist: A list of Android C/C++ projects, the default value is None.
749        rlist: A list of Android Rust projects, the default value is None.
750
751    Returns:
752        A tuple of the determined language and IDE name strings.
753    """
754    if ide == _IDE_UNDEFINED and lang == constant.LANG_UNDEFINED:
755        if jlist:
756            lang = constant.LANG_JAVA
757        elif clist:
758            lang = constant.LANG_CC
759        elif rlist:
760            lang = constant.LANG_RUST
761    if lang in (constant.LANG_UNDEFINED, constant.LANG_JAVA):
762        if ide == _IDE_UNDEFINED:
763            ide = _IDE_INTELLIJ
764        lang = constant.LANG_JAVA
765        if constant.IDE_NAME_DICT[ide] == constant.IDE_CLION:
766            lang = constant.LANG_CC
767    elif lang == constant.LANG_CC:
768        if ide == _IDE_UNDEFINED:
769            ide = _IDE_CLION
770        if constant.IDE_NAME_DICT[ide] == constant.IDE_INTELLIJ:
771            lang = constant.LANG_JAVA
772    elif lang == constant.LANG_RUST:
773        ide = _IDE_VSCODE
774    return constant.LANGUAGE_NAME_DICT[lang], constant.IDE_NAME_DICT[ide]
775
776
777def check_java_or_kotlin_file_exists(abs_path):
778    """Checks if any Java or Kotlin files exist in an abs_path directory.
779
780    Args:
781        abs_path: A string of absolute path of a directory to be checked.
782
783    Returns:
784        True if any Java or Kotlin files exist otherwise False.
785    """
786    for _, _, filenames in os.walk(abs_path):
787        for extension in (constant.JAVA_FILES, constant.KOTLIN_FILES):
788            if fnmatch.filter(filenames, extension):
789                return True
790    return False
791
792
793@io_error_handle
794def unzip_file(src, dest):
795    """Unzips the source zip file and extract it to the destination directory.
796
797    Args:
798        src: A string of the file to be unzipped.
799        dest: A string of the destination directory to be extracted to.
800    """
801    with zipfile.ZipFile(src, 'r') as zip_ref:
802        zip_ref.extractall(dest)
803