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"""It is an AIDEGen sub task : generate the project files. 18 19This module generate IDE project files from templates. 20 21 Typical usage example: 22 23 generate_ide_project_file(project_info) 24""" 25 26import logging 27import os 28import pathlib 29import shutil 30 31from aidegen import constant 32from aidegen.lib import common_util 33 34# FACET_SECTION is a part of iml, which defines the framework of the project. 35_FACET_SECTION = '''\ 36 <facet type="android" name="Android"> 37 <configuration /> 38 </facet>''' 39_SOURCE_FOLDER = (' <sourceFolder url=' 40 '"file://%s" isTestSource="%s" />\n') 41_CONTENT_URL = ' <content url="file://%s">\n' 42_END_CONTENT = ' </content>\n' 43_ORDER_ENTRY = (' <orderEntry type="module-library" exported="">' 44 '<library><CLASSES><root url="jar://%s!/" /></CLASSES>' 45 '<JAVADOC /><SOURCES /></library></orderEntry>\n') 46_MODULE_ORDER_ENTRY = (' <orderEntry type="module" ' 47 'module-name="%s" />') 48_MODULE_SECTION = (' <module fileurl="file:///$PROJECT_DIR$/%s.iml"' 49 ' filepath="$PROJECT_DIR$/%s.iml" />') 50_SUB_MODULES_SECTION = (' <module fileurl="file:///%s" ' 51 'filepath="%s" />') 52_VCS_SECTION = ' <mapping directory="%s" vcs="Git" />' 53_FACET_TOKEN = '@FACETS@' 54_SOURCE_TOKEN = '@SOURCES@' 55_MODULE_DEP_TOKEN = '@MODULE_DEPENDENCIES@' 56_MODULE_TOKEN = '@MODULES@' 57_VCS_TOKEN = '@VCS@' 58_JAVA_FILE_PATTERN = '%s/*.java' 59_ROOT_DIR = constant.AIDEGEN_ROOT_PATH 60_IDEA_DIR = os.path.join(_ROOT_DIR, 'templates/idea') 61_TEMPLATE_IML_PATH = os.path.join(_ROOT_DIR, 'templates/module-template.iml') 62_IDEA_FOLDER = '.idea' 63_MODULES_XML = 'modules.xml' 64_VCS_XML = 'vcs.xml' 65_TEMPLATE_MODULES_PATH = os.path.join(_IDEA_DIR, _MODULES_XML) 66_TEMPLATE_VCS_PATH = os.path.join(_IDEA_DIR, _VCS_XML) 67_DEPENDENCIES = 'dependencies' 68_DEPENDENCIES_IML = 'dependencies.iml' 69_COPYRIGHT_FOLDER = 'copyright' 70_CODE_STYLE_FOLDER = 'codeStyles' 71_COMPILE_XML = 'compiler.xml' 72_MISC_XML = 'misc.xml' 73_ANDROID_MANIFEST = 'AndroidManifest.xml' 74_IML_EXTENSION = '.iml' 75_FRAMEWORK_JAR = os.sep + 'framework.jar' 76_HIGH_PRIORITY_JARS = [_FRAMEWORK_JAR] 77_GIT_FOLDER_NAME = '.git' 78# Support gitignore by symbolic link to aidegen/data/gitignore_template. 79_GITIGNORE_FILE_NAME = '.gitignore' 80_GITIGNORE_REL_PATH = 'tools/asuite/aidegen/data/gitignore_template' 81_GITIGNORE_ABS_PATH = os.path.join(constant.ANDROID_ROOT_PATH, 82 _GITIGNORE_REL_PATH) 83# Support code style by symbolic link to aidegen/data/AndroidStyle_aidegen.xml. 84_CODE_STYLE_REL_PATH = 'tools/asuite/aidegen/data/AndroidStyle_aidegen.xml' 85_CODE_STYLE_SRC_PATH = os.path.join(constant.ANDROID_ROOT_PATH, 86 _CODE_STYLE_REL_PATH) 87 88_ECLIP_SRC_ENTRY = '<classpathentry exported="true" kind="src" path="{}"/>\n' 89_ECLIP_LIB_ENTRY = '<classpathentry exported="true" kind="lib" path="{}"/>\n' 90_ECLIP_TEMPLATE_PATH = os.path.join(_ROOT_DIR, 'templates/eclipse/eclipse.xml') 91_ECLIP_EXTENSION = '.classpath' 92_ECLIP_SRC_TOKEN = '@SRC@' 93_ECLIP_LIB_TOKEN = '@LIB@' 94_ECLIP_PROJECT_PATH = os.path.join(_ROOT_DIR, 'templates/eclipse/project.xml') 95_ECLIP_PROJECT_NAME_TOKEN = '@PROJECTNAME@' 96_ECLIP_PROJECT_EXTENSION = '.project' 97 98# b/121256503: Prevent duplicated iml names from breaking IDEA. 99# Use a map to cache in-using(already used) iml project file names. 100_USED_NAME_CACHE = dict() 101 102 103def get_unique_iml_name(abs_module_path): 104 """Create a unique iml name if needed. 105 106 If the name of last sub folder is used already, prefixing it with prior sub 107 folder names as a candidate name. If finally, it's unique, storing in 108 _USED_NAME_CACHE as: { abs_module_path:unique_name }. The cts case and UX of 109 IDE view are the main reasons why using module path strategy but not name of 110 module directly. Following is the detailed strategy: 111 1. While loop composes a sensible and shorter name, by checking unique to 112 finish the loop and finally add to cache. 113 Take ['cts', 'tests', 'app', 'ui'] an example, if 'ui' isn't occupied, 114 use it, else try 'cts_ui', then 'cts_app_ui', the worst case is whole 115 three candidate names are occupied already. 116 2. 'Else' for that while stands for no suitable name generated, so trying 117 'cts_tests_app_ui' directly. If it's still non unique, e.g., module path 118 cts/xxx/tests/app/ui occupied that name already, appending increasing 119 sequence number to get a unique name. 120 121 Args: 122 abs_module_path: Full module path string. 123 124 Return: 125 String: A unique iml name. 126 """ 127 if abs_module_path in _USED_NAME_CACHE: 128 return _USED_NAME_CACHE[abs_module_path] 129 130 uniq_name = abs_module_path.strip(os.sep).split(os.sep)[-1] 131 if any(uniq_name == name for name in _USED_NAME_CACHE.values()): 132 parent_path = os.path.relpath(abs_module_path, 133 constant.ANDROID_ROOT_PATH) 134 sub_folders = parent_path.split(os.sep) 135 zero_base_index = len(sub_folders) - 1 136 # Start compose a sensible, shorter and unique name. 137 while zero_base_index > 0: 138 uniq_name = '_'.join( 139 [sub_folders[0], '_'.join(sub_folders[zero_base_index:])]) 140 zero_base_index = zero_base_index - 1 141 if uniq_name not in _USED_NAME_CACHE.values(): 142 break 143 else: 144 # b/133393638: To handle several corner cases. 145 uniq_name_base = parent_path.strip(os.sep).replace(os.sep, '_') 146 i = 0 147 uniq_name = uniq_name_base 148 while uniq_name in _USED_NAME_CACHE.values(): 149 i = i + 1 150 uniq_name = '_'.join([uniq_name_base, str(i)]) 151 _USED_NAME_CACHE[abs_module_path] = uniq_name 152 logging.debug('Unique name for module path of %s is %s.', abs_module_path, 153 uniq_name) 154 return uniq_name 155 156 157def _generate_intellij_project_file(project_info, iml_path_list=None): 158 """Generates IntelliJ project file. 159 160 Args: 161 project_info: ProjectInfo instance. 162 iml_path_list: An optional list of submodule's iml paths, default None. 163 """ 164 source_dict = dict.fromkeys( 165 list(project_info.source_path['source_folder_path']), False) 166 source_dict.update( 167 dict.fromkeys(list(project_info.source_path['test_folder_path']), True)) 168 project_info.iml_path, _ = _generate_iml( 169 constant.ANDROID_ROOT_PATH, project_info.project_absolute_path, 170 source_dict, list(project_info.source_path['jar_path']), 171 project_info.project_relative_path) 172 _generate_modules_xml(project_info.project_absolute_path, iml_path_list) 173 project_info.git_path = _generate_vcs_xml( 174 project_info.project_absolute_path) 175 _copy_constant_project_files(project_info.project_absolute_path) 176 177 178def generate_ide_project_files(projects): 179 """Generate IDE project files by a list of ProjectInfo instances. 180 181 For multiple modules case, we call _generate_intellij_project_file to 182 generate iml file for submodules first and pass submodules' iml file paths 183 as an argument to function _generate_intellij_project_file when we generate 184 main module.iml file. In this way, we can add submodules' dependencies iml 185 and their own iml file paths to main module's module.xml. 186 187 Args: 188 projects: A list of ProjectInfo instances. 189 """ 190 # Initialization 191 _USED_NAME_CACHE.clear() 192 193 for project in projects[1:]: 194 _generate_intellij_project_file(project) 195 iml_paths = [project.iml_path for project in projects[1:]] 196 _generate_intellij_project_file(projects[0], iml_paths) 197 _merge_project_vcs_xmls(projects) 198 199 200def _generate_eclipse_project_file(project_info): 201 """Generates Eclipse project file. 202 203 Args: 204 project_info: ProjectInfo instance. 205 """ 206 module_path = project_info.project_absolute_path 207 module_name = get_unique_iml_name(module_path) 208 _generate_eclipse_project(module_name, module_path) 209 source_dict = dict.fromkeys( 210 list(project_info.source_path['source_folder_path']), False) 211 source_dict.update( 212 dict.fromkeys(list(project_info.source_path['test_folder_path']), True)) 213 project_info.iml_path = _generate_classpath( 214 project_info.project_absolute_path, list(sorted(source_dict)), 215 list(project_info.source_path['jar_path'])) 216 217 218def generate_eclipse_project_files(projects): 219 """Generate Eclipse project files by a list of ProjectInfo instances. 220 221 Args: 222 projects: A list of ProjectInfo instances. 223 """ 224 for project in projects: 225 _generate_eclipse_project_file(project) 226 227 228def _read_file_content(path): 229 """Read file's content. 230 231 Args: 232 path: Path of input file. 233 234 Returns: 235 String: Content of the file. 236 """ 237 with open(path) as template: 238 return template.read() 239 240 241def _file_generate(path, content): 242 """Generate file from content. 243 244 Args: 245 path: Path of target file. 246 content: String content of file. 247 """ 248 if not os.path.exists(os.path.dirname(path)): 249 os.makedirs(os.path.dirname(path)) 250 with open(path, 'w') as target: 251 target.write(content) 252 253 254def _copy_constant_project_files(target_path): 255 """Copy project files to target path with error handling. 256 257 This function would copy compiler.xml, misc.xml, codeStyles folder and 258 copyright folder to target folder. Since these files aren't mandatory in 259 IntelliJ, it only logs when an IOError occurred. 260 261 Args: 262 target_path: A folder path to copy content to. 263 """ 264 try: 265 _copy_to_idea_folder(target_path, _COPYRIGHT_FOLDER) 266 _copy_to_idea_folder(target_path, _CODE_STYLE_FOLDER) 267 code_style_target_path = os.path.join(target_path, _IDEA_FOLDER, 268 _CODE_STYLE_FOLDER, 'Project.xml') 269 # Base on current working directory to prepare the relevant location 270 # of the symbolic link file, and base on the symlink file location to 271 # prepare the relevant code style source path. 272 rel_target = os.path.relpath(code_style_target_path, os.getcwd()) 273 rel_source = os.path.relpath(_CODE_STYLE_SRC_PATH, 274 os.path.dirname(code_style_target_path)) 275 logging.debug('Relative target symlink path: %s.', rel_target) 276 logging.debug('Relative code style source path: %s.', rel_source) 277 os.symlink(rel_source, rel_target) 278 # Create .gitignore if it doesn't exist. 279 _generate_git_ignore(target_path) 280 shutil.copy( 281 os.path.join(_IDEA_DIR, _COMPILE_XML), 282 os.path.join(target_path, _IDEA_FOLDER, _COMPILE_XML)) 283 shutil.copy( 284 os.path.join(_IDEA_DIR, _MISC_XML), 285 os.path.join(target_path, _IDEA_FOLDER, _MISC_XML)) 286 except IOError as err: 287 logging.warning('%s can\'t copy the project files\n %s', target_path, 288 err) 289 290 291def _copy_to_idea_folder(target_path, folder_name): 292 """Copy folder to project .idea path. 293 294 Args: 295 target_path: Path of target folder. 296 folder_name: Name of target folder. 297 """ 298 target_folder_path = os.path.join(target_path, _IDEA_FOLDER, folder_name) 299 # Existing folder needs to be removed first, otherwise it will raise 300 # IOError. 301 if os.path.exists(target_folder_path): 302 shutil.rmtree(target_folder_path) 303 shutil.copytree(os.path.join(_IDEA_DIR, folder_name), target_folder_path) 304 305 306def _handle_facet(content, path): 307 """Handle facet part of iml. 308 309 If the module is an Android app, which contains AndroidManifest.xml, it 310 should have a facet of android, otherwise we don't need facet in iml. 311 312 Args: 313 content: String content of iml. 314 path: Path of the module. 315 316 Returns: 317 String: Content with facet handled. 318 """ 319 facet = '' 320 if os.path.isfile(os.path.join(path, _ANDROID_MANIFEST)): 321 facet = _FACET_SECTION 322 return content.replace(_FACET_TOKEN, facet) 323 324 325def _handle_module_dependency(root_path, content, jar_dependencies): 326 """Handle module dependency part of iml. 327 328 Args: 329 root_path: Android source tree root path. 330 content: String content of iml. 331 jar_dependencies: List of the jar path. 332 333 Returns: 334 String: Content with module dependency handled. 335 """ 336 module_library = '' 337 dependencies = [] 338 # Reorder deps in the iml generated by IntelliJ by inserting priority jars. 339 for jar_path in jar_dependencies: 340 if any((jar_path.endswith(high_priority_jar)) 341 for high_priority_jar in _HIGH_PRIORITY_JARS): 342 module_library += _ORDER_ENTRY % os.path.join(root_path, jar_path) 343 else: 344 dependencies.append(jar_path) 345 346 # IntelliJ indexes jars as dependencies from iml by the ascending order. 347 # Without sorting, the order of jar list changes everytime. Sort the jar 348 # list to keep the jar dependencies in consistency. It also can help us to 349 # discover potential issues like duplicated classes. 350 for jar_path in sorted(dependencies): 351 module_library += _ORDER_ENTRY % os.path.join(root_path, jar_path) 352 return content.replace(_MODULE_DEP_TOKEN, module_library) 353 354 355def _is_project_relative_source(source, relative_path): 356 """Check if the relative path of a file is a source relative path. 357 358 Check if the file path starts with the relative path or the relative is an 359 Android source tree root path. 360 361 Args: 362 source: The file path to be checked. 363 relative_path: The relative path to be checked. 364 365 Returns: 366 True if the file is a source relative path, otherwise False. 367 """ 368 abs_path = common_util.get_abs_path(relative_path) 369 if common_util.is_android_root(abs_path): 370 return True 371 if _is_source_under_relative_path(source, relative_path): 372 return True 373 return False 374 375 376def _handle_source_folder(root_path, content, source_dict, is_module, 377 relative_path): 378 """Handle source folder part of iml. 379 380 It would make the source folder group by content. 381 e.g. 382 <content url="file://$MODULE_DIR$/a"> 383 <sourceFolder url="file://$MODULE_DIR$/a/b" isTestSource="False" /> 384 <sourceFolder url="file://$MODULE_DIR$/a/test" isTestSource="True" /> 385 <sourceFolder url="file://$MODULE_DIR$/a/d/e" isTestSource="False" /> 386 </content> 387 388 Args: 389 root_path: Android source tree root path. 390 content: String content of iml. 391 source_dict: A dictionary of sources path with a flag to identify the 392 path is test or source folder in IntelliJ. 393 e.g. 394 {'path_a': True, 'path_b': False} 395 is_module: True if it is module iml, otherwise it is dependencies iml. 396 relative_path: Relative path of the module. 397 398 Returns: 399 String: Content with source folder handled. 400 """ 401 source_list = list(source_dict.keys()) 402 source_list.sort() 403 src_builder = [] 404 if is_module: 405 # Set the content url to module's path since it's the iml of target 406 # project which only has it's sub-folders in source_list. 407 src_builder.append( 408 _CONTENT_URL % os.path.join(root_path, relative_path)) 409 for path, is_test_flag in sorted(source_dict.items()): 410 if _is_project_relative_source(path, relative_path): 411 src_builder.append(_SOURCE_FOLDER % (os.path.join( 412 root_path, path), is_test_flag)) 413 src_builder.append(_END_CONTENT) 414 else: 415 for path, is_test_flag in sorted(source_dict.items()): 416 path = os.path.join(root_path, path) 417 src_builder.append(_CONTENT_URL % path) 418 src_builder.append(_SOURCE_FOLDER % (path, is_test_flag)) 419 src_builder.append(_END_CONTENT) 420 return content.replace(_SOURCE_TOKEN, ''.join(src_builder)) 421 422 423def _trim_same_root_source(source_list): 424 """Trim the source which has the same root. 425 426 The source list may contain lots of duplicate sources. 427 For example: 428 a/b, a/b/c, a/b/d 429 We only need to import a/b in iml, this function is used to trim redundant 430 sources. 431 432 Args: 433 source_list: Sorted list of the sources. 434 435 Returns: 436 List: The trimmed source list. 437 """ 438 tmp_source_list = [source_list[0]] 439 for src_path in source_list: 440 if ''.join([tmp_source_list[-1], 441 os.sep]) not in ''.join([src_path, os.sep]): 442 tmp_source_list.append(src_path) 443 return sorted(tmp_source_list) 444 445 446def _is_source_under_relative_path(source, relative_path): 447 """Check if a source file is a project relative path file. 448 449 Args: 450 source: Android source file path. 451 relative_path: Relative path of the module. 452 453 Returns: 454 True if source file is a project relative path file, otherwise False. 455 """ 456 return source == relative_path or source.startswith(relative_path + os.sep) 457 458 459# pylint: disable=too-many-locals 460def _generate_iml(root_path, module_path, source_dict, jar_dependencies, 461 relative_path): 462 """Generate iml file. 463 464 Args: 465 root_path: Android source tree root path. 466 module_path: Absolute path of the module. 467 source_dict: A dictionary of sources path with a flag to distinguish the 468 path is test or source folder in IntelliJ. 469 e.g. 470 {'path_a': True, 'path_b': False} 471 jar_dependencies: List of the jar path. 472 relative_path: Relative path of the module. 473 474 Returns: 475 String: The absolute paths of module iml and dependencies iml. 476 """ 477 template = _read_file_content(_TEMPLATE_IML_PATH) 478 479 # Separate module and dependencies source folder 480 project_source_dict = {} 481 for source in list(source_dict): 482 if _is_project_relative_source(source, relative_path): 483 is_test = source_dict.get(source) 484 source_dict.pop(source) 485 project_source_dict.update({source: is_test}) 486 487 # Generate module iml. 488 module_content = _handle_facet(template, module_path) 489 module_content = _handle_source_folder( 490 root_path, module_content, project_source_dict, True, relative_path) 491 # b/121256503: Prevent duplicated iml names from breaking IDEA. 492 module_name = get_unique_iml_name(module_path) 493 494 module_iml_path = os.path.join(module_path, module_name + _IML_EXTENSION) 495 496 dep_name = _get_dependencies_name(module_name) 497 dep_sect = _MODULE_ORDER_ENTRY % dep_name 498 module_content = module_content.replace(_MODULE_DEP_TOKEN, dep_sect) 499 _file_generate(module_iml_path, module_content) 500 501 # Generate dependencies iml. 502 dependencies_content = template.replace(_FACET_TOKEN, '') 503 dependencies_content = _handle_source_folder( 504 root_path, dependencies_content, source_dict, False, relative_path) 505 dependencies_content = _handle_module_dependency( 506 root_path, dependencies_content, jar_dependencies) 507 dependencies_iml_path = os.path.join(module_path, dep_name + _IML_EXTENSION) 508 _file_generate(dependencies_iml_path, dependencies_content) 509 logging.debug('Paired iml names are %s, %s', module_iml_path, 510 dependencies_iml_path) 511 # The dependencies_iml_path is use for removing the file itself in unittest. 512 return module_iml_path, dependencies_iml_path 513 514 515def _generate_classpath(module_path, source_list, jar_dependencies): 516 """Generate .classpath file. 517 518 Args: 519 module_path: Absolute path of the module. 520 source_list: A list of sources path. 521 jar_dependencies: List of the jar path. 522 523 Returns: 524 String: The absolute paths of .classpath. 525 """ 526 template = _read_file_content(_ECLIP_TEMPLATE_PATH) 527 528 src_list = [_ECLIP_SRC_ENTRY.format(s) for s in source_list] 529 template = template.replace(_ECLIP_SRC_TOKEN, ''.join(src_list)) 530 531 lib_list = [_ECLIP_LIB_ENTRY.format(j) for j in jar_dependencies] 532 template = template.replace(_ECLIP_LIB_TOKEN, ''.join(lib_list)) 533 534 classpath_path = os.path.join(module_path, _ECLIP_EXTENSION) 535 536 _file_generate(classpath_path, template) 537 538 return classpath_path 539 540 541def _generate_eclipse_project(project_name, module_path): 542 """Generate .project file of Eclipse. 543 544 Args: 545 project_name: A string of the project name. 546 module_path: Absolute path of the module. 547 """ 548 template = _read_file_content(_ECLIP_PROJECT_PATH) 549 template = template.replace(_ECLIP_PROJECT_NAME_TOKEN, project_name) 550 eclipse_project = os.path.join(module_path, _ECLIP_PROJECT_EXTENSION) 551 _file_generate(eclipse_project, template) 552 553 554def _get_dependencies_name(module_name): 555 """Get module's dependencies iml name which will be written in module.xml. 556 557 Args: 558 module_name: The name will be appended to "dependencies-". 559 560 Returns: 561 String: The joined dependencies iml file name, e.g. "dependencies-core" 562 """ 563 return '-'.join([_DEPENDENCIES, module_name]) 564 565 566def _generate_modules_xml(module_path, iml_path_list=None): 567 """Generate modules.xml file. 568 569 IntelliJ uses modules.xml to import which modules should be loaded to 570 project. Only in multiple modules case will we pass iml_path_list of 571 submodules' dependencies and their iml file paths to add them into main 572 module's module.xml file. The dependencies iml file names will be changed 573 from original dependencies.iml to dependencies-[module_name].iml, 574 e.g. dependencies-core.iml for core.iml. 575 576 Args: 577 module_path: Path of the module. 578 iml_path_list: A list of submodule iml paths. 579 """ 580 content = _read_file_content(_TEMPLATE_MODULES_PATH) 581 582 # b/121256503: Prevent duplicated iml names from breaking IDEA. 583 module_name = get_unique_iml_name(module_path) 584 585 file_name = os.path.splitext(module_name)[0] 586 dep_name = _get_dependencies_name(file_name) 587 module_list = [ 588 _MODULE_SECTION % (module_name, module_name), 589 _MODULE_SECTION % (dep_name, dep_name) 590 ] 591 if iml_path_list: 592 for iml_path in iml_path_list: 593 iml_dir, iml_name = os.path.split(iml_path) 594 dep_file = _get_dependencies_name(iml_name) 595 dep_path = os.path.join(iml_dir, dep_file) 596 module_list.append(_SUB_MODULES_SECTION % (dep_path, dep_path)) 597 module_list.append(_SUB_MODULES_SECTION % (iml_path, iml_path)) 598 module = '\n'.join(module_list) 599 content = content.replace(_MODULE_TOKEN, module) 600 target_path = os.path.join(module_path, _IDEA_FOLDER, _MODULES_XML) 601 _file_generate(target_path, content) 602 603 604def _generate_vcs_xml(module_path): 605 """Generate vcs.xml file. 606 607 IntelliJ use vcs.xml to record version control software's information. 608 Since we are using a single project file, it will only contain the 609 module itself. If there is no git folder inside, it would find it in 610 parent's folder. 611 612 Args: 613 module_path: Path of the module. 614 615 Return: 616 String: A module's git path. 617 """ 618 git_path = module_path 619 while not os.path.isdir(os.path.join(git_path, _GIT_FOLDER_NAME)): 620 git_path = str(pathlib.Path(git_path).parent) 621 if git_path == os.sep: 622 logging.warning('%s can\'t find its .git folder', module_path) 623 return None 624 _write_vcs_xml(module_path, [git_path]) 625 return git_path 626 627 628def _write_vcs_xml(module_path, git_paths): 629 """Write the git path into vcs.xml. 630 631 For main module, the vcs.xml should include all modules' git path. 632 For submodules, there is only one git path in vcs.xml. 633 634 Args: 635 module_path: Path of the module. 636 git_paths: A list of git path. 637 """ 638 _vcs_content = '\n'.join([_VCS_SECTION % p for p in git_paths if p]) 639 content = _read_file_content(_TEMPLATE_VCS_PATH) 640 content = content.replace(_VCS_TOKEN, _vcs_content) 641 target_path = os.path.join(module_path, _IDEA_FOLDER, _VCS_XML) 642 _file_generate(target_path, content) 643 644 645def _merge_project_vcs_xmls(projects): 646 """Merge sub projects' git paths into main project's vcs.xml. 647 648 After all projects' vcs.xml are generated, collect the git path of each 649 projects and write them into main project's vcs.xml. 650 651 Args: 652 projects: A list of ProjectInfo instances. 653 """ 654 main_project_absolute_path = projects[0].project_absolute_path 655 git_paths = [project.git_path for project in projects] 656 _write_vcs_xml(main_project_absolute_path, git_paths) 657 658 659def _generate_git_ignore(target_folder): 660 """Generate .gitignore file. 661 662 In target_folder, if there's no .gitignore file, uses symlink() to generate 663 one to hide project content files from git. 664 665 Args: 666 target_folder: An absolute path string of target folder. 667 """ 668 # TODO(b/133639849): Provide a common method to create symbolic link. 669 # TODO(b/133641803): Move out aidegen artifacts from Android repo. 670 try: 671 gitignore_abs_path = os.path.join(target_folder, _GITIGNORE_FILE_NAME) 672 rel_target = os.path.relpath(gitignore_abs_path, os.getcwd()) 673 rel_source = os.path.relpath(_GITIGNORE_ABS_PATH, target_folder) 674 logging.debug('Relative target symlink path: %s.', rel_target) 675 logging.debug('Relative ignore_template source path: %s.', rel_source) 676 if not os.path.exists(gitignore_abs_path): 677 os.symlink(rel_source, rel_target) 678 except OSError as err: 679 logging.error('Not support to run aidegen on Windows.\n %s', err) 680