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