blob: 2c166da7f06206dab747f49f6ecb81b9b306e263 [file] [log] [blame]
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