| import errno |
| import json |
| import os |
| import sys |
| |
| |
| MODULE_XML_START = """<?xml version="1.0" encoding="UTF-8"?> |
| <module type="%(type)s" version="4">""" |
| |
| MODULE_XML_END = """ |
| </module> |
| """ |
| |
| ANDROID_FACET = """ |
| <component name="FacetManager"> |
| <facet type="android" name="Android"> |
| <configuration> |
| <option name="GEN_FOLDER_RELATIVE_PATH_APT" value="%(module_gen_path)s" /> |
| <option name="GEN_FOLDER_RELATIVE_PATH_AIDL" value="%(module_gen_path)s" /> |
| <option name="MANIFEST_FILE_RELATIVE_PATH" value="%(android_manifest)s" /> |
| <option name="RES_FOLDER_RELATIVE_PATH" value="%(res)s" /> |
| <option name="ASSETS_FOLDER_RELATIVE_PATH" value="/assets" /> |
| <option name="LIBS_FOLDER_RELATIVE_PATH" value="%(libs_path)s" /> |
| <option name="USE_CUSTOM_APK_RESOURCE_FOLDER" value="false" /> |
| <option name="CUSTOM_APK_RESOURCE_FOLDER" value="" /> |
| <option name="USE_CUSTOM_COMPILER_MANIFEST" value="false" /> |
| <option name="CUSTOM_COMPILER_MANIFEST" value="" /> |
| <option name="APK_PATH" value="" /> |
| <option name="LIBRARY_PROJECT" value="%(is_android_library_project)s" /> |
| <option name="RUN_PROCESS_RESOURCES_MAVEN_TASK" value="true" /> |
| <option name="GENERATE_UNSIGNED_APK" value="false" /> |
| <option name="CUSTOM_DEBUG_KEYSTORE_PATH" value="%(keystore)s" /> |
| <option name="PACK_TEST_CODE" value="false" /> |
| <option name="RUN_PROGUARD" value="%(run_proguard)s" /> |
| <option name="PROGUARD_CFG_PATH" value="%(proguard_config)s" /> |
| <resOverlayFolders /> |
| <includeSystemProguardFile>false</includeSystemProguardFile> |
| <includeAssetsFromLibraries>true</includeAssetsFromLibraries> |
| <additionalNativeLibs /> |
| </configuration> |
| </facet> |
| </component>""" |
| |
| ALL_MODULES_XML_START = """<?xml version="1.0" encoding="UTF-8"?> |
| <project version="4"> |
| <component name="ProjectModuleManager"> |
| <modules>""" |
| |
| ALL_MODULES_XML_END = """ |
| </modules> |
| </component> |
| </project> |
| """ |
| |
| LIBRARY_XML_START = """<component name="libraryTable"> |
| <library name="%(name)s"> |
| <CLASSES> |
| <root url="jar://$PROJECT_DIR$/%(binary_jar)s!/" /> |
| </CLASSES>""" |
| |
| LIBRARY_XML_WITH_JAVADOC = """ |
| <JAVADOC> |
| <root url="%(javadoc_url)s" /> |
| </JAVADOC>""" |
| |
| LIBRARY_XML_NO_JAVADOC = """ |
| <JAVADOC />""" |
| |
| LIBRARY_XML_WITH_SOURCES = """ |
| <SOURCES> |
| <root url="jar://$PROJECT_DIR$/%(source_jar)s!/" /> |
| </SOURCES>""" |
| |
| LIBRARY_XML_NO_SOURCES = """ |
| <SOURCES />""" |
| |
| LIBRARY_XML_END = """ |
| </library> |
| </component> |
| """ |
| |
| RUN_CONFIG_XML_START = """<component name="ProjectRunConfigurationManager">""" |
| RUN_CONFIG_XML_END = "</component>" |
| |
| REMOTE_RUN_CONFIG_XML = """ |
| <configuration default="false" name="%(name)s" type="Remote" factoryName="Remote"> |
| <option name="USE_SOCKET_TRANSPORT" value="true" /> |
| <option name="SERVER_MODE" value="false" /> |
| <option name="SHMEM_ADDRESS" value="javadebug" /> |
| <option name="HOST" value="localhost" /> |
| <option name="PORT" value="5005" /> |
| <RunnerSettings RunnerId="Debug"> |
| <option name="DEBUG_PORT" value="5005" /> |
| <option name="TRANSPORT" value="0" /> |
| <option name="LOCAL" value="false" /> |
| </RunnerSettings> |
| <ConfigurationWrapper RunnerId="Debug" /> |
| <method /> |
| </configuration> |
| """ |
| |
| |
| # Files that were written by this script. |
| # If `buck project` is working properly, most of the time it will be a no-op |
| # and no files will need to be written. |
| MODIFIED_FILES = [] |
| |
| |
| def write_modules(modules): |
| """Writes one XML file for each module.""" |
| for module in modules: |
| # Build up the XML. |
| module_type = 'JAVA_MODULE' |
| if 'isIntelliJPlugin' in module and module['isIntelliJPlugin']: |
| module_type = 'PLUGIN_MODULE' |
| |
| xml = MODULE_XML_START % { |
| 'type': module_type, |
| } |
| |
| # Android facet, if appropriate. |
| if module.get('hasAndroidFacet') == True: |
| if 'keystorePath' in module: |
| keystore = 'file://$MODULE_DIR$/%s' % module['keystorePath'] |
| else: |
| keystore = '' |
| |
| if 'androidManifest' in module: |
| android_manifest = module['androidManifest'] |
| else: |
| android_manifest = '/AndroidManifest.xml' |
| |
| is_library_project = module['isAndroidLibraryProject'] |
| android_params = { |
| 'android_manifest': android_manifest, |
| 'res': '/res', |
| 'is_android_library_project': str(is_library_project).lower(), |
| 'run_proguard': 'false', |
| 'module_gen_path': module['moduleGenPath'], |
| 'proguard_config': '/proguard.cfg', |
| 'keystore': keystore, |
| 'libs_path' : '/%s' % module.get('nativeLibs', 'libs'), |
| } |
| xml += ANDROID_FACET % android_params |
| |
| # Source code and libraries component. |
| xml += '\n <component name="NewModuleRootManager" inherit-compiler-output="true">' |
| |
| # Empirically, if there are multiple source folders, then the <content> element for the |
| # buck-out/android/gen folder should be listed before the other source folders. |
| num_source_folders = len(module['sourceFolders']) |
| if num_source_folders > 1: |
| xml = add_buck_android_source_folder(xml, module) |
| |
| # Source folders. |
| xml += '\n <content url="file://$MODULE_DIR$">' |
| for source_folder in module['sourceFolders']: |
| if 'packagePrefix' in source_folder: |
| package_prefix = 'packagePrefix="%s" ' % source_folder['packagePrefix'] |
| else: |
| package_prefix = '' |
| xml += '\n <sourceFolder url="%(url)s" isTestSource="%(is_test_source)s" %(package_prefix)s/>' % { |
| 'url': source_folder['url'], |
| 'is_test_source': str(source_folder['isTestSource']).lower(), |
| 'package_prefix': package_prefix |
| } |
| for exclude_folder in module['excludeFolders']: |
| xml += '\n <excludeFolder url="%s" />' % exclude_folder['url'] |
| xml += '\n </content>' |
| |
| xml = add_annotation_generated_source_folder(xml, module) |
| |
| # Empirically, if there is one source folder, then the <content> element for the |
| # buck-out/android/gen folder should be listed after the other source folders. |
| if num_source_folders <= 1: |
| xml = add_buck_android_source_folder(xml, module) |
| |
| # Dependencies. |
| dependencies = module['dependencies'] |
| module_name = module['name'] |
| |
| # We need to filter out some of the modules in the dependency list: |
| # (1) The module may list itself as a dependency with scope="TEST", which is bad. |
| # (2) The module may list another module as a dependency with both COMPILE and TEST scopes, in |
| # which case the COMPILE scope should win. |
| |
| # compile_dependencies will be the set of names of dependent modules that do not have scope="TEST" |
| compile_dependencies = filter(lambda dep: dep['type'] == 'module' and |
| ((not ('scope' in dep)) or dep['scope'] != 'TEST'), |
| dependencies) |
| compile_dependencies = map(lambda dep: dep['moduleName'], compile_dependencies) |
| compile_dependencies = set(compile_dependencies) |
| |
| # Filter dependencies to satisfy (1) and (2) defined above. |
| filtered_dependencies = [] |
| for dep in dependencies: |
| if dep['type'] != 'module': |
| # Non-module dependencies should still be included. |
| filtered_dependencies.append(dep) |
| else: |
| # dep must be a module |
| dep_module_name = dep['moduleName'] |
| if dep_module_name == module_name: |
| # Exclude self-references! |
| continue |
| elif 'scope' in dep and dep['scope'] == 'TEST': |
| # If this is a scope="TEST" module and the module is going to be included as |
| # a scope="COMPILE" module, then exclude it. |
| if not (dep_module_name in compile_dependencies): |
| filtered_dependencies.append(dep) |
| else: |
| # Non-test modules should still be included. |
| filtered_dependencies.append(dep) |
| |
| # Now that we have filtered the dependencies, we can convert the remaining ones directly into |
| # XML. |
| excluded_deps_names = set() |
| |
| if module_type == 'PLUGIN_MODULE': |
| # all the jars below are parts of IntelliJ SDK and even though they are required |
| # for language plugins to work standalone, they cannot be included as the plugin |
| # module dependency because they would clash with IntelliJ |
| excluded_deps_names = set([ |
| 'annotations', # org/intellij/lang/annotations, org/jetbrains/annotations |
| 'extensions', # com/intellij/openapi/extensions/ |
| 'idea', # org/intellij, com/intellij |
| 'jdom', # org/jdom |
| 'junit', # junit/ |
| 'light_psi_all', # light psi library |
| 'openapi', # com/intellij/openapi |
| 'picocontainer', # org/picocontainer |
| 'trove4j', # gnu/trove |
| 'util', # com/intellij/util |
| ]) |
| |
| for dep in filtered_dependencies: |
| if 'scope' in dep: |
| dep_scope = 'scope="%s" ' % dep['scope'] |
| else: |
| dep_scope = '' |
| |
| dep_type = dep['type'] |
| if dep_type == 'library': |
| if dep['name'] in excluded_deps_names: |
| continue |
| |
| xml += '\n <orderEntry type="library" exported="" %sname="%s" level="project" />' % (dep_scope, dep['name']) |
| elif dep_type == 'module': |
| dep_module_name = dep['moduleName'] |
| |
| # TODO(mbolin): Eliminate this special-case for jackson. It exists because jackson is not |
| # an ordinary module: it is a module that functions as a library. Project.java should add it |
| # as such in project.json to eliminate this special case. |
| if dep_module_name == 'module_first_party_orca_third_party_jackson': |
| exported = 'exported="" ' |
| else: |
| exported = '' |
| xml += '\n <orderEntry type="module" module-name="%s" %s%s/>' % (dep_module_name, exported, dep_scope) |
| elif dep_type == 'inheritedJdk': |
| xml += '\n <orderEntry type="inheritedJdk" />' |
| elif dep_type == 'jdk': |
| xml += '\n <orderEntry type="jdk" jdkName="%s" jdkType="%s" />' % (dep['jdkName'], dep['jdkType']) |
| elif dep_type == 'sourceFolder': |
| xml += '\n <orderEntry type="sourceFolder" forTests="false" />' |
| |
| # Close source code and libraries component. |
| xml += '\n </component>' |
| |
| # Close XML. |
| xml += MODULE_XML_END |
| |
| # Write the module to a file. |
| write_file_if_changed(module['pathToImlFile'], xml) |
| |
| |
| def add_buck_android_source_folder(xml, module): |
| # Apparently if we write R.java and friends to a gen/ directory under buck-out/android/ then |
| # IntelliJ wants that to be included as a separate source root. |
| if 'moduleGenPath' in module: |
| xml += '\n <content url="file://$MODULE_DIR$%s">' % module['moduleGenPath'] |
| xml += '\n <sourceFolder url="file://$MODULE_DIR$%s" isTestSource="false" />' % module['moduleGenPath'] |
| xml += '\n </content>' |
| return xml |
| |
| def add_annotation_generated_source_folder(xml, module): |
| if 'annotationGenPath' in module: |
| annotation_gen_is_for_test = 'annotationGenIsForTest' in module and module['annotationGenIsForTest'] |
| is_test_source = str(annotation_gen_is_for_test).lower() |
| |
| xml += '\n <content url="file://$MODULE_DIR$%s">' % module['annotationGenPath'] |
| xml += '\n <sourceFolder url="file://$MODULE_DIR$%s" isTestSource="%s" />' % (module['annotationGenPath'], is_test_source) |
| xml += '\n </content>' |
| return xml |
| |
| def write_all_modules(modules): |
| """Writes a modules.xml file that defines all of the modules in the project.""" |
| # Build up the XML. |
| xml = ALL_MODULES_XML_START |
| |
| # Alpha-sort modules by path before writing them out. |
| # This ensures that the ordering within modules.xml is stable. |
| modules.sort(key=lambda module: module['pathToImlFile']) |
| |
| for module in modules: |
| relative_path = module['pathToImlFile'] |
| xml += '\n <module fileurl="file://$PROJECT_DIR$/%s" filepath="$PROJECT_DIR$/%s" />' % (relative_path, relative_path) |
| xml += ALL_MODULES_XML_END |
| |
| # Write the modules to a file. |
| write_file_if_changed('.idea/modules.xml', xml) |
| |
| |
| def write_libraries(libraries): |
| """Writes an XML file to define each library.""" |
| mkdir_p('.idea/libraries') |
| for library in libraries: |
| # Build up the XML. |
| name = library['name'] |
| xml = LIBRARY_XML_START % { |
| 'name': name, |
| 'binary_jar': library['binaryJar'], |
| } |
| |
| if 'javadocUrl' in library: |
| xml += LIBRARY_XML_WITH_JAVADOC % {'javadoc_url': library['javadocUrl']} |
| else: |
| xml += LIBRARY_XML_NO_JAVADOC |
| |
| if 'sourceJar' in library: |
| xml += LIBRARY_XML_WITH_SOURCES % {'source_jar': library['sourceJar']} |
| else: |
| xml += LIBRARY_XML_NO_SOURCES |
| |
| xml += LIBRARY_XML_END |
| |
| # Write the library to a file |
| write_file_if_changed('.idea/libraries/%s.xml' % name, xml) |
| |
| |
| def write_run_configs(): |
| """Writes the run configurations that should be available""" |
| mkdir_p('.idea/runConfigurations') |
| |
| xml = RUN_CONFIG_XML_START |
| xml += REMOTE_RUN_CONFIG_XML % {'name': "Debug Buck test"} |
| xml += RUN_CONFIG_XML_END |
| write_file_if_changed('.idea/runConfigurations/Debug_Buck_test.xml', xml) |
| |
| |
| def write_file_if_changed(path, content): |
| if os.path.exists(path): |
| file_content_as_string = open(path, 'r').read() |
| needs_update = content.strip() != file_content_as_string.strip() |
| else: |
| needs_update = True |
| if needs_update: |
| out = open(path, 'wb') |
| out.write(content) |
| MODIFIED_FILES.append(path) |
| |
| |
| def mkdir_p(path): |
| """Runs the equivalent of `mkdir -p` |
| Taken from http://stackoverflow.com/questions/600268/mkdir-p-functionality-in-python |
| Args: |
| path: an absolute path |
| """ |
| try: |
| os.makedirs(path) |
| except OSError as exc: |
| if exc.errno == errno.EEXIST: |
| pass |
| else: raise |
| |
| |
| if __name__ == '__main__': |
| json_file = sys.argv[1] |
| parsed_json = json.load(open(json_file, 'r')) |
| |
| libraries = parsed_json['libraries'] |
| write_libraries(libraries) |
| |
| modules = parsed_json['modules'] |
| write_modules(modules) |
| write_all_modules(modules) |
| write_run_configs() |
| |
| # Write the list of modified files to stdout |
| for path in MODIFIED_FILES: print path |