blob: 3a827e42a46a5e43497b8845519c98788475b0b1 [file] [log] [blame]
"""IntelliJ plugin target rule.
Creates a plugin jar with the given plugin xml and any
optional plugin xmls.
To provide optional plugin xmls, use the 'optional_plugin_xml'
rule. These will be renamed, put in the META-INF directory,
and the main plugin xml stamped with optional plugin dependencies
that point to the correct META-INF optional plugin xmls.
optional_plugin_xml(
name = "optional_python_xml",
plugin_xml = "my_optional_python_plugin.xml",
module = "com.idea.python.module.id",
)
intellij_plugin(
name = "my_plugin",
plugin_xml = ["my_plugin.xml"],
optional_plugin_xmls = [":optional_python_xml"],
deps = [
":code_deps",
],
)
"""
optional_plugin_xml_provider = provider()
def _optional_plugin_xml_impl(ctx):
attr = ctx.attr
optional_plugin_xmls = []
if ctx.file.plugin_xml:
optional_plugin_xmls.append(struct(
plugin_xml = ctx.file.plugin_xml,
module = attr.module,
))
return struct(
optional_plugin_xml_data = optional_plugin_xml_provider(
optional_plugin_xmls = optional_plugin_xmls,
),
)
optional_plugin_xml = rule(
implementation = _optional_plugin_xml_impl,
attrs = {
"plugin_xml": attr.label(mandatory=True, allow_single_file=[".xml"]),
"module": attr.string(mandatory=True),
},
)
def _merge_optional_plugin_xmls(ctx):
# Collect optional plugin xmls
module_to_xmls = {}
for target in ctx.attr.optional_plugin_xmls:
if not hasattr(target, "optional_plugin_xml_data"):
fail("optional_plugin_xmls only accepts optional_plugin_xml targets")
for xml in target.optional_plugin_xml_data.optional_plugin_xmls:
module = xml.module
plugin_xmls = module_to_xmls.setdefault(module, [])
plugin_xmls.append(xml.plugin_xml)
# Merge xmls with the same module dependency
module_to_merged_xmls = {}
for module, plugin_xmls in module_to_xmls.items():
merged_name = "merged_xml_for_" + module + "_" + ctx.label.name + ".xml"
merged_file = ctx.new_file(merged_name)
ctx.action(
executable = ctx.executable._merge_xml_binary,
arguments = ["--output", merged_file.path] + [plugin_xml.path for plugin_xml in plugin_xmls],
inputs = list(plugin_xmls),
outputs = [merged_file],
progress_message = "Merging optional xmls",
mnemonic = "MergeOptionalXmls",
)
module_to_merged_xmls[module] = merged_file
return module_to_merged_xmls
def _add_optional_dependencies_to_plugin_xml(ctx, modules):
input_plugin_xml_file = ctx.file.plugin_xml
if not modules:
return input_plugin_xml_file
# Add optional dependencies into the plugin xml
args = []
final_plugin_xml_file = ctx.new_file("final_plugin_xml_" + ctx.label.name + ".xml")
args.extend(["--plugin_xml", input_plugin_xml_file.path])
args.extend(["--output", final_plugin_xml_file.path])
for module in modules:
args.append(module)
args.append(_filename_for_module_dependency(module))
ctx.action(
executable = ctx.executable._append_optional_xml_elements,
arguments = args,
inputs = [input_plugin_xml_file],
outputs = [final_plugin_xml_file],
progress_message = "Adding optional dependencies to final plugin xml",
mnemonic = "AddModuleDependencies",
)
return final_plugin_xml_file
def _only_file(target):
return list(target.files)[0]
def _filename_for_module_dependency(module):
"""A unique filename for the optional xml dependency for a given module."""
return "optional-" + module + ".xml"
def _package_meta_inf_files(ctx, final_plugin_xml_file, module_to_merged_xmls):
jar_name = ctx.attr.jar_name
jar_file = ctx.new_file(jar_name)
args = []
args.extend(["--deploy_jar", ctx.file.deploy_jar.path])
args.extend(["--output", jar_file.path])
args.extend([final_plugin_xml_file.path, "plugin.xml"])
for module, merged_xml in module_to_merged_xmls.items():
args.append(merged_xml.path)
args.append(_filename_for_module_dependency(module))
ctx.action(
executable = ctx.executable._package_meta_inf_files,
arguments = args,
inputs = [ctx.file.deploy_jar, final_plugin_xml_file] + module_to_merged_xmls.values(),
outputs = [jar_file],
mnemonic = "PackagePluginJar",
progress_message = "Packaging plugin jar",
)
return jar_file
def _intellij_plugin_jar_impl(ctx):
module_to_merged_xmls = _merge_optional_plugin_xmls(ctx)
final_plugin_xml_file = _add_optional_dependencies_to_plugin_xml(ctx, module_to_merged_xmls.keys())
jar_file = _package_meta_inf_files(ctx, final_plugin_xml_file, module_to_merged_xmls)
files = depset([jar_file])
return struct(
files = files,
)
_intellij_plugin_jar = rule(
implementation = _intellij_plugin_jar_impl,
attrs = {
"deploy_jar": attr.label(mandatory=True, allow_single_file=[".jar"]),
"plugin_xml": attr.label(mandatory=True, allow_single_file=[".xml"]),
"optional_plugin_xmls": attr.label_list(),
"jar_name": attr.string(mandatory=True),
"_merge_xml_binary": attr.label(
default = Label("//build_defs:merge_xml"),
executable = True,
cfg = "host",
),
"_append_optional_xml_elements": attr.label(
default = Label("//build_defs:append_optional_xml_elements"),
executable = True,
cfg = "host",
),
"_package_meta_inf_files": attr.label(
default = Label("//build_defs:package_meta_inf_files"),
executable = True,
cfg = "host",
),
},
)
def intellij_plugin(name, deps, plugin_xml, optional_plugin_xmls=[], jar_name=None, **kwargs):
"""Creates an intellij plugin from the given deps and plugin.xml.
Args:
name: The name of the target
deps: Any java dependencies rolled up into the plugin jar.
plugin_xml: An xml file to be placed in META-INF/plugin.jar
optional_plugin_xmls: A list of optional_plugin_xml targets.
jar_name: The name of the final plugin jar, or <name>.jar if None
**kwargs: Any further arguments to be passed to the final target
"""
binary_name = name + "_binary"
deploy_jar = binary_name + "_deploy.jar"
native.java_binary(
name = binary_name,
runtime_deps = deps,
create_executable = 0,
)
jar_target_name = name + "_intellij_plugin_jar"
_intellij_plugin_jar(
name = jar_target_name,
deploy_jar = deploy_jar,
jar_name = jar_name or (name + ".jar"),
plugin_xml = plugin_xml,
optional_plugin_xmls = optional_plugin_xmls,
)
# included (with tag) as a hack so that IJwB can recognize this is an intellij plugin
native.java_import(
name = name,
jars = [jar_target_name],
tags = ["intellij-plugin"],
**kwargs)
def _append_optional_dependencies(name, plugin_xml, module_to_merged_xml):
"""Appends optional dependency xml elements to plugin xml."""
append_elements_tool = "//build_defs:append_optional_xml_elements"
args = [
"./$(location {append_elements_tool})",
"--plugin_xml=$(location {plugin_xml})",
"--optional_xml_files={merged_optional_xml_files}",
]
dictionary = {k: _filename_for_module_dependency(k) for k in module_to_merged_xml.keys()}
cmd = " ".join(args).format(
append_elements_tool=append_elements_tool,
plugin_xml=plugin_xml,
merged_optional_xml_files='"%s"' % str(dictionary).replace('"', '\\"'),
) + "> $@"
srcs = module_to_merged_xml.values() + [plugin_xml]
native.genrule(
name = name,
srcs = srcs,
outs = [name + ".xml"],
cmd = cmd,
tools = [append_elements_tool],
)