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