blob: 198005a45b66de308940f922f3332701930f3ebe [file] [log] [blame]
load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
load("//tools/adt/idea/studio/rules:app-icon.bzl", "AppIconInfo", "replace_app_icon")
load("//tools/base/bazel:bazel.bzl", "ImlModuleInfo", "iml_test")
load("//tools/base/bazel:expand_template.bzl", "expand_template_ex")
load("//tools/base/bazel:functions.bzl", "create_option_file")
load("//tools/base/bazel:jvm_import.bzl", "jvm_import")
load("//tools/base/bazel:merge_archives.bzl", "run_singlejar")
load("//tools/base/bazel:utils.bzl", "dir_archive", "is_release")
PluginInfo = provider(
doc = "Info for IntelliJ plugins, including those built by the studio_plugin rule",
fields = {
"directory": "where to place this plugin within the plugins directory",
"plugin_metadata": "metadata produced by the check_plugin tool",
"module_deps": "ImlModuleInfo for modules included in this plugin",
"lib_deps": "libraries to be included in this plugin",
"licenses": "",
"plugin_files": "A map from the final studio location to the file it goes there.",
"overwrite_plugin_version": "whether to stamp version metadata into plugin.xml",
"platform": "The platform this plugin was compiled against",
},
)
IntellijInfo = provider(
doc = "Info about the IntelliJ SDK provided by the intellij_platform rule",
fields = {
"major_version": "The major IntelliJ version.",
"minor_version": "The minor IntelliJ version.",
"base": "A map from final studio location to the file (all non plugin files).",
"plugins": "The file maps for all the bundled plugins.",
},
)
# Valid types (and their corresponding channels) which can be specified
# by the android_studio rule.
type_channel_mappings = {
"Nightly": "Dev",
"Canary": "Canary",
"Beta": "Beta",
"RC": "Beta",
"Stable": "Stable",
}
# Types considered to be EAP. This should be a subset of
# type_channel_mappings.keys().
eap_types = ["Canary", "Beta", "Dev"]
def _zipper(ctx, desc, map, out, deps = []):
files = [f for (p, f) in map if f]
zipper_files = [r + "=" + (f.path if f else "") + "\n" for r, f in map]
zipper_args = ["cC" if ctx.attr.compress else "c", out.path]
zipper_list = create_option_file(ctx, out.basename + ".res.lst", "".join(zipper_files))
zipper_args += ["@" + zipper_list.path]
ctx.actions.run(
inputs = files + [zipper_list] + deps,
outputs = [out],
executable = ctx.executable._zipper,
arguments = zipper_args,
progress_message = "Creating %s zip..." % desc,
mnemonic = "zipper",
)
def _lnzipper(ctx, desc, filemap, out, keep_symlink = True, attrs = {}, deps = []):
"""Creates a ZIP out while preserving symlinks.
Note: This action needs to run outside the sandbox to capture an accurate
representation of the workspace filesystem. Otherwise, files inside the
sandbox are created as symbolic links, and the output ZIP would only
contain entries which are sandbox symlinks."""
files = []
fileargs = []
for zip_path, f in filemap:
files.append(f)
attr = ("[" + attrs[zip_path] + "]") if zip_path in attrs else ""
fileargs.append("%s%s=%s\n" % (zip_path, attr, f.path if f else ""))
lnzipper_options = "-ca"
if keep_symlink:
lnzipper_options += "s"
if ctx.attr.compress:
lnzipper_options += "C"
args = [lnzipper_options, out.path]
argfile = create_option_file(ctx, out.basename + ".res.lst", "".join(fileargs))
args.append("@" + argfile.path)
ctx.actions.run(
inputs = files + [argfile] + deps,
outputs = [out],
executable = ctx.executable._lnzipper,
execution_requirements = {"no-sandbox": "true", "no-remote": "true"},
arguments = args,
progress_message = "lnzipping %s" % desc,
mnemonic = "lnzipper",
)
# Bazel does not support attributes of type 'dict of string -> list of labels',
# and in order to support them we must 'unpack' the dictionary to two lists
# of keys and value. The following two functions perform the mapping back and forth
def _dict_to_lists(dict):
keys = []
values = []
for k, vs in dict.items():
keys += [k] * len(vs)
values += vs
return keys, values
def _lists_to_dict(keys, values):
dict = {}
for k, v in zip(keys, values):
if k not in dict:
dict[k] = []
dict[k] += [v]
return dict
def _module_deps(ctx, jar_names, modules):
jars = _lists_to_dict(jar_names, modules)
bundled = {}
res_files = []
for j, ms in jars.items():
jar_file = ctx.actions.declare_file(j)
modules_jars = [m[ImlModuleInfo].module_jars for m in ms]
run_singlejar(ctx, modules_jars, jar_file)
res_files += [(j, jar_file)]
return res_files
def _get_linux(x):
return x.linux
LINUX = struct(
name = "linux",
jre = "jbr/",
get = _get_linux,
base_path = "",
resource_path = "",
)
def _get_mac(x):
return x.mac
MAC = struct(
name = "mac",
jre = "jbr/",
get = _get_mac,
base_path = "Contents/",
resource_path = "Contents/Resources/",
)
def _get_mac_arm(x):
return x.mac_arm
MAC_ARM = struct(
name = "mac_arm",
jre = "jbr/",
get = _get_mac_arm,
base_path = "Contents/",
resource_path = "Contents/Resources/",
)
def _get_win(x):
return x.win
WIN = struct(
name = "win",
jre = "jbr/",
get = _get_win,
base_path = "",
resource_path = "",
)
def _resource_deps(res_dirs, res, platform):
files = []
for dir, dep in zip(res_dirs, res):
if hasattr(dep, "mappings"):
files += [(dir + "/" + dep.mappings[f], f) for f in platform.get(dep).to_list()]
else:
files += [(dir + "/" + f.basename, f) for f in dep.files.to_list()]
return files
def _check_plugin(ctx, out, files, external_xmls = [], verify_id = None, verify_deps = None):
deps = None
if verify_deps != None:
deps = [dep[PluginInfo].plugin_metadata for dep in verify_deps]
check_args = ctx.actions.args()
check_args.add("--out", out)
check_args.add_all("--files", files)
if verify_id:
check_args.add("--plugin_id", verify_id)
if deps != None:
check_args.add_all("--deps", deps, omit_if_empty = False)
check_args.add_all("--external_xmls", external_xmls)
ctx.actions.run(
inputs = files + (deps if deps else []),
outputs = [out],
executable = ctx.executable._check_plugin,
arguments = [check_args],
progress_message = "Analyzing %s plugin..." % ctx.attr.name,
mnemonic = "chkplugin",
)
def _studio_plugin_os(ctx, platform, module_deps, plugin_dir):
files = {plugin_dir + "/lib/" + d: f for (d, f) in module_deps}
res = _resource_deps(ctx.attr.resources_dirs, ctx.attr.resources, platform)
files.update({plugin_dir + "/" + d: f for (d, f) in res})
return files
def _depset_subtract(depset1, depset2):
dict1 = {e1: None for e1 in depset1.to_list()}
return [e2 for e2 in depset2.to_list() if e2 not in dict1]
def _label_str(label):
if label.workspace_name:
return str(label)
else:
return "//%s:%s" % (label.package, label.name)
def _studio_plugin_impl(ctx):
plugin_dir = "plugins/" + ctx.attr.directory
module_deps = _module_deps(ctx, ctx.attr.jars, ctx.attr.modules)
module_deps = module_deps + [(f.basename, f) for f in ctx.files.libs]
_check_plugin(ctx, ctx.outputs.plugin_metadata, [f for (r, f) in module_deps], ctx.attr.external_xmls, ctx.attr.name, ctx.attr.deps)
plugin_files_linux = _studio_plugin_os(ctx, LINUX, module_deps, plugin_dir)
plugin_files_mac = _studio_plugin_os(ctx, MAC, module_deps, plugin_dir)
plugin_files_mac_arm = _studio_plugin_os(ctx, MAC_ARM, module_deps, plugin_dir)
plugin_files_win = _studio_plugin_os(ctx, WIN, module_deps, plugin_dir)
for lib in ctx.attr.libs:
if PluginInfo in lib:
fail("Plugin dependencies should be in the deps attribute, not in libs")
# Check that all modules needed by the modules in this plugin, are either present in the
# plugin or in its dependencies.
need = depset(transitive =
[m[ImlModuleInfo].module_deps for m in ctx.attr.modules] +
[m[ImlModuleInfo].plugin_deps for m in ctx.attr.modules] +
[m[ImlModuleInfo].external_deps for m in ctx.attr.modules])
have = depset(
direct = ctx.attr.modules + ctx.attr.libs,
transitive = [d[PluginInfo].module_deps for d in ctx.attr.deps] +
[d[PluginInfo].lib_deps for d in ctx.attr.deps] +
[depset(ctx.attr.deps)],
)
missing = [s.label for s in _depset_subtract(have, need)]
if missing:
error = "\n".join(["\"%s\"," % _label_str(l) for l in missing])
fail("Plugin '" + ctx.attr.name + "' has compile-time dependencies which are not on the " +
"runtime classpath in release builds.\nYou may need to edit the plugin definition at " +
str(ctx.label) + " to include the following dependencies:\n" + error)
return [
PluginInfo(
directory = ctx.attr.directory,
plugin_files = struct(
linux = plugin_files_linux,
mac = plugin_files_mac,
mac_arm = plugin_files_mac_arm,
win = plugin_files_win,
),
plugin_metadata = ctx.outputs.plugin_metadata,
module_deps = depset(ctx.attr.modules),
lib_deps = depset(ctx.attr.libs),
licenses = depset(ctx.files.licenses),
overwrite_plugin_version = True,
platform = ctx.attr._intellij_platform,
),
# Force 'chkplugin' to run by marking its output as a validation output.
# See https://bazel.build/extending/rules#validation_actions for details.
OutputGroupInfo(_validation = depset([ctx.outputs.plugin_metadata])),
]
_studio_plugin = rule(
attrs = {
"modules": attr.label_list(providers = [ImlModuleInfo], allow_empty = True),
"libs": attr.label_list(allow_files = True),
"licenses": attr.label_list(allow_files = True),
"jars": attr.string_list(),
"resources": attr.label_list(allow_files = True),
"resources_dirs": attr.string_list(),
"directory": attr.string(),
"compress": attr.bool(),
"deps": attr.label_list(providers = [PluginInfo]),
"external_xmls": attr.string_list(),
"_singlejar": attr.label(
default = Label("@bazel_tools//tools/jdk:singlejar"),
cfg = "host",
executable = True,
),
"_zipper": attr.label(
default = Label("@bazel_tools//tools/zip:zipper"),
cfg = "host",
executable = True,
),
"_check_plugin": attr.label(
default = Label("//tools/adt/idea/studio:check_plugin"),
cfg = "host",
executable = True,
),
"_intellij_platform": attr.label(
default = Label("//tools/base/intellij-bazel:intellij_platform"),
cfg = "host",
),
},
outputs = {
"plugin_metadata": "%{name}.info",
},
implementation = _studio_plugin_impl,
)
def _searchable_options_impl(ctx):
searchable_options = {}
for f in ctx.files.searchable_options:
if not f.short_path.startswith(ctx.attr.strip_prefix):
fail("File " + f.short_path + " does not have the given prefix.")
path = f.short_path[len(ctx.attr.strip_prefix):]
parts = path.split("/")
if len(parts) < 2:
fail("File does not follow the <plugin>/<jar> convention.")
plugin, jar = parts[0], parts[1]
if plugin not in searchable_options:
searchable_options[plugin] = {}
if jar not in searchable_options[plugin]:
searchable_options[plugin][jar] = []
searchable_options[plugin][jar] += [f]
so_jars = []
for plugin, jars in searchable_options.items():
for jar, so_files in jars.items():
so_jar = ctx.actions.declare_file(ctx.attr.name + ".%s.%s.zip" % (plugin, jar))
_zipper(ctx, "%s %s searchable options" % (plugin, jar), [("search/", None)] + [("search/%s" % f.basename, f) for f in so_files], so_jar)
so_jars += [("plugins/%s/lib/%s" % (plugin, jar), so_jar)]
return struct(
files = depset([f for (_, f) in so_jars]),
searchable_options = so_jars,
)
_searchable_options = rule(
attrs = {
"searchable_options": attr.label_list(allow_files = True),
"compress": attr.bool(),
"strip_prefix": attr.string(),
"_zipper": attr.label(
default = Label("@bazel_tools//tools/zip:zipper"),
cfg = "host",
executable = True,
),
},
executable = False,
implementation = _searchable_options_impl,
)
def searchable_options(name, files, **kwargs):
_searchable_options(
name = name,
compress = is_release(),
searchable_options = files,
**kwargs
)
# Build an Android Studio plugin.
# This plugin is a zip file with the final layout inside Android Studio plugin's directory.
# Args
# name: The id of the plugin (eg. intellij.android.plugin)
# directory: The directory to use inside plugins (eg. android)
# modules: A dictionary of the form
# {"name.jar": ["m1" , "m2"]}
# Where keys are the names of the jars in the libs directory, and the values
# are the list of modules that will be in that jar.
# resources: A dictionary of the form
# {"dir": <files> }
# where keys are the directories where to place the resources, and values
# is a list of files to place there (it supports studio_data rules)
def studio_plugin(
name,
directory,
modules = {},
resources = {},
**kwargs):
jars, modules_list = _dict_to_lists(modules)
resources_dirs, resources_list = _dict_to_lists(resources)
_studio_plugin(
name = name,
directory = directory,
modules = modules_list,
jars = jars,
resources = resources_list,
resources_dirs = resources_dirs,
compress = is_release(),
**kwargs
)
def _studio_data_impl(ctx):
for dep in ctx.attr.files_linux + ctx.attr.files_mac + ctx.attr.files_mac_arm + ctx.attr.files_win:
if hasattr(dep, "mappings"):
fail("studio_data does not belong on a platform specific attribute, please add " + str(dep.label) + " to \"files\" directly")
files = []
mac = []
mac_arm = []
win = []
linux = []
mappings = {}
for dep in ctx.attr.files:
if hasattr(dep, "mappings"):
linux += [dep.linux]
mac += [dep.mac]
mac_arm += [dep.mac_arm]
win += [dep.win]
mappings.update(dep.mappings)
else:
files += dep[DefaultInfo].files.to_list()
for prefix, destination in ctx.attr.mappings.items():
for src in files + ctx.files.files_mac + ctx.files.files_mac_arm + ctx.files.files_linux + ctx.files.files_win:
if src not in mappings and src.short_path.startswith(prefix):
mappings[src] = destination + src.short_path[len(prefix):]
dlinux = depset(files + ctx.files.files_linux, order = "preorder", transitive = linux)
dmac = depset(files + ctx.files.files_mac, order = "preorder", transitive = mac)
dmac_arm = depset(files + ctx.files.files_mac_arm, order = "preorder", transitive = mac_arm)
dwin = depset(files + ctx.files.files_win, order = "preorder", transitive = win)
return struct(
linux = dlinux,
mac = dmac,
mac_arm = dmac_arm,
win = dwin,
mappings = mappings,
providers = [DefaultInfo(files = depset(files))],
)
_studio_data = rule(
attrs = {
"files": attr.label_list(allow_files = True),
"files_linux": attr.label_list(allow_files = True),
"files_mac": attr.label_list(allow_files = True),
"files_mac_arm": attr.label_list(allow_files = True),
"files_win": attr.label_list(allow_files = True),
"mappings": attr.string_dict(mandatory = True),
},
executable = False,
implementation = _studio_data_impl,
)
# A specialized version of a filegroup, that groups all the given files but also provides different
# sets of files for each platform.
# This allows grouping all files of the same concept that have different platform variants.
# Args:
# files: A list of files present on all platforms
# files_{linux, mac, mac_arm, win}: A list of files for each platform
# mapping: A dictionary to map file locations and build an arbitrary file tree, in the form of
# a dictionary from current directory to new directory.
def studio_data(name, files = [], files_linux = [], files_mac = [], files_mac_arm = [], files_win = [], mappings = {}, tags = [], **kwargs):
_studio_data(
name = name,
files = files,
files_linux = files_linux,
files_mac = files_mac,
files_mac_arm = files_mac_arm,
files_win = files_win,
mappings = mappings,
tags = tags,
**kwargs
)
def _split_version(version):
"""Splits a version string into its constituent parts."""
index_of_period = version.find(".")
if index_of_period == -1:
fail('Cannot split version "%s" because no "." was found in it' % version)
micro = version[0:index_of_period]
patch = version[index_of_period + 1:]
return (micro, patch)
def _get_channel_info(version_type):
"""Maps a version type to information about a channel."""
if version_type not in type_channel_mappings:
fail('Invalid type "%s"; must be one of %s' % (version_type, str(type_channel_mappings.keys())))
channel = type_channel_mappings[version_type]
is_eap = version_type in eap_types
return channel, is_eap
def _form_version_full(ctx):
"""Forms version_full based on code name, version type, and release number"""
(channel, _) = _get_channel_info(ctx.attr.version_type)
code_name_and_patch_components = (ctx.attr.version_code_name +
" | " +
"{0}.{1}.{2}")
if channel == "Stable":
if ctx.attr.version_release_number <= 1:
return code_name_and_patch_components
return code_name_and_patch_components + " Patch " + str(ctx.attr.version_release_number - 1)
if ctx.attr.version_suffix:
return code_name_and_patch_components + " " + ctx.attr.version_suffix[BuildSettingInfo].value
return (code_name_and_patch_components +
" " +
ctx.attr.version_type +
" " +
str(ctx.attr.version_release_number))
def _full_display_version(ctx):
"""Returns the output of _form_version_full with versions applied."""
intellij_info = ctx.attr.platform[IntellijInfo]
(micro, _) = _split_version(ctx.attr.version_micro_patch)
return _form_version_full(ctx).format(intellij_info.major_version, intellij_info.minor_version, micro)
def _append(ctx, platform, files, path, lines):
if not lines:
return
file = files[path]
text = "\n".join(lines)
template = ctx.actions.declare_file(file.basename + ".%s.template" % platform.name)
out = ctx.actions.declare_file(file.basename + ".%s.append.%s" % (platform.name, file.extension))
files[path] = out
ctx.actions.write(output = template, content = "{CONTENT}")
expand_template_ex(
ctx = ctx,
template = template,
out = out,
substitutions = {
"{CONTENT}": "$(inline " + file.path + ")\n" + text + "\n",
},
files = [file],
)
def _stamp(ctx, args, srcs, src, out):
args.add("--stamp")
args.add(src)
args.add(out)
ctx.actions.run(
inputs = srcs + [src],
outputs = [out],
executable = ctx.executable._stamper,
arguments = [args],
progress_message = "Stamping %s" % src.basename,
mnemonic = "stamper",
)
def _stamp_exe(ctx, extra, srcs, src, out):
args = ctx.actions.args()
args.add(src)
args.add(out)
ctx.actions.run(
inputs = srcs + [src],
outputs = [out],
executable = ctx.executable._patch_exe,
arguments = [args, extra],
progress_message = "Patching exe %s" % src.basename,
mnemonic = "patchexe",
)
def _declare_stamped_file(ctx, files, platform, path):
original = files[path]
stamped = ctx.actions.declare_file(original.basename + ".%s.stamped.%s" % (platform.name, original.extension))
files[path] = stamped
return original, stamped
def _produce_manifest(ctx, platform, platform_files):
out = ctx.outputs.manifest
build_txt = platform_files[platform.resource_path + "build.txt"]
resources_jar = platform_files[platform.base_path + "lib/resources.jar"]
(channel, is_eap) = _get_channel_info(ctx.attr.version_type)
args = ["--out", out.path]
args += ["--build_txt", build_txt.path]
args += ["--resources_jar", resources_jar.path]
args += ["--channel", channel]
args += ["--code_name", ctx.attr.version_code_name]
ctx.actions.run(
inputs = [build_txt, resources_jar, ctx.info_file, ctx.version_file],
outputs = [out],
executable = ctx.executable._generate_build_metadata,
arguments = args,
progress_message = "Producing manifest for %s..." % ctx.attr.name,
mnemonic = "stamper",
)
def _produce_update_message_html(ctx):
if not ctx.file.update_message_template:
ctx.actions.write(output = ctx.outputs.update_message, content = "")
return
channel, _ = _get_channel_info(ctx.attr.version_type)
ctx.actions.expand_template(
template = ctx.file.update_message_template,
output = ctx.outputs.update_message,
substitutions = {
"{full_version}": _full_display_version(ctx),
"{channel}": channel,
},
)
def _stamp_platform(ctx, platform, platform_files):
args = ["--stamp_platform"]
ret = {}
ret.update(platform_files)
build_txt, stamped_build_txt = _declare_stamped_file(ctx, ret, platform, platform.resource_path + "build.txt")
args = ctx.actions.args()
args.add("--info_file", ctx.info_file)
args.add("--replace_build_number")
_stamp(ctx, args, [ctx.info_file], build_txt, stamped_build_txt)
resources_jar, stamped_resources_jar = _declare_stamped_file(ctx, ret, platform, platform.base_path + "lib/resources.jar")
(_, is_eap) = _get_channel_info(ctx.attr.version_type)
(micro, patch) = _split_version(ctx.attr.version_micro_patch)
args = ctx.actions.args()
args.add("--entry", "idea/AndroidStudioApplicationInfo.xml")
args.add("--version_file", ctx.version_file)
args.add("--version_full", _form_version_full(ctx))
args.add("--eap", "true" if is_eap else "false")
args.add("--version_micro", micro)
args.add("--version_patch", patch)
args.add("--build_txt", stamped_build_txt.path)
args.add("--stamp_app_info")
_stamp(ctx, args, [ctx.version_file, stamped_build_txt], resources_jar, stamped_resources_jar)
idea_properties, stamped_idea_properties = _declare_stamped_file(ctx, ret, platform, platform.base_path + "bin/idea.properties")
args = ctx.actions.args()
args.add("--replace_selector", ctx.attr.selector)
_stamp(ctx, args, [], idea_properties, stamped_idea_properties)
if platform == LINUX:
studio_sh, stamped_studio_sh = _declare_stamped_file(ctx, ret, platform, platform.base_path + "bin/studio.sh")
args = ctx.actions.args()
args.add("--replace_selector", ctx.attr.selector)
_stamp(ctx, args, [], studio_sh, stamped_studio_sh)
game_tools_sh, stamped_game_tools_sh = _declare_stamped_file(ctx, ret, platform, platform.base_path + "bin/game-tools.sh")
args = ctx.actions.args()
args.add("--replace_selector", ctx.attr.selector)
_stamp(ctx, args, [], game_tools_sh, stamped_game_tools_sh)
install_txt, stamped_install_txt = _declare_stamped_file(ctx, ret, platform, platform.base_path + "Install-Linux-tar.txt")
args = ctx.actions.args()
args.add("--replace_selector", ctx.attr.selector)
_stamp(ctx, args, [], install_txt, stamped_install_txt)
if platform == MAC or platform == MAC_ARM:
info_plist, stamped_info_plist = _declare_stamped_file(ctx, ret, platform, platform.base_path + "Info.plist")
args = ctx.actions.args()
args.add("--info_file", ctx.info_file)
args.add("--replace_build_number")
args.add("--replace_selector", ctx.attr.selector)
_stamp(ctx, args, [ctx.info_file], info_plist, stamped_info_plist)
if platform == WIN:
studio_exe, stamped_studio_exe = _declare_stamped_file(ctx, ret, platform, platform.base_path + "bin/studio64.exe")
args = ctx.actions.args()
args.add_all(["--replace_resource", "_ANDROID_STUDIO_SYSTEM_SELECTOR_", ctx.attr.selector])
_stamp_exe(ctx, args, [], studio_exe, stamped_studio_exe)
studio_bat, stamped_studio_bat = _declare_stamped_file(ctx, ret, platform, platform.base_path + "bin/studio.bat")
args = ctx.actions.args()
args.add("--replace_selector", ctx.attr.selector)
_stamp(ctx, args, [], studio_bat, stamped_studio_bat)
game_tools_bat, stamped_game_tools_bat = _declare_stamped_file(ctx, ret, platform, platform.base_path + "bin/game-tools.bat")
args = ctx.actions.args()
args.add("--replace_selector", ctx.attr.selector)
_stamp(ctx, args, [], game_tools_bat, stamped_game_tools_bat)
product_info_json, stamped_product_info_json = _declare_stamped_file(ctx, ret, platform, platform.resource_path + "product-info.json")
args = ctx.actions.args()
args.add("--info_file", ctx.info_file)
args.add("--build_txt", stamped_build_txt)
args.add("--stamp_product_info")
args.add("--replace_selector", ctx.attr.selector)
_stamp(ctx, args, [ctx.info_file, stamped_build_txt], product_info_json, stamped_product_info_json)
return ret
def _stamp_plugin(ctx, platform, platform_files, files, overwrite_plugin_version):
ret = {}
ret.update(files)
build_txt = platform_files[platform.resource_path + "build.txt"]
for rel, file in files.items():
if rel.endswith(".jar"):
stamped_jar = ctx.actions.declare_file(ctx.attr.name + ".plugin.%s.stamped." % platform.name + rel.replace("/", "_"))
ret[rel] = stamped_jar
args = ctx.actions.args()
args.add("--build_txt", build_txt)
args.add("--info_file", ctx.info_file)
args.add("--entry", "META-INF/plugin.xml")
args.add("--optional_entry")
args.add("--replace_build_number")
if overwrite_plugin_version:
args.add("--overwrite_plugin_version")
_stamp(ctx, args, [build_txt, ctx.info_file], file, stamped_jar)
return ret
def _android_studio_prefix(ctx, platform):
if platform == MAC or platform == MAC_ARM:
return ctx.attr.platform.platform_info.mac_bundle_name + "/"
return "android-studio/"
def _get_external_attributes(all_files):
attrs = {}
for zip_path, file in all_files.items():
# Source files are checked in with the right permissions.
# For generated files we default to -rw-r--r--
if not file.is_source:
attrs[zip_path] = "644"
if zip_path.endswith(".app/Contents/Info.plist"):
attrs[zip_path] = "664"
if (zip_path.endswith("/bin/studio.sh") or
zip_path.endswith("/bin/game-tools.sh") or
zip_path.endswith("/bin/studio64.exe") or
zip_path.endswith("/bin/studio.bat") or
zip_path.endswith("/bin/game-tools.bat")):
attrs[zip_path] = "775"
return attrs
def _android_studio_os(ctx, platform, out):
files = []
all_files = {}
platform_prefix = _android_studio_prefix(ctx, platform)
platform_files = platform.get(ctx.attr.platform[IntellijInfo].base)
if ctx.attr.application_icon:
platform_files = replace_app_icon(ctx, platform.name, platform_files, ctx.attr.application_icon[AppIconInfo])
plugin_files = platform.get(ctx.attr.platform[IntellijInfo].plugins)
if ctx.attr.jre:
jre_files = [(ctx.attr.jre.mappings[f], f) for f in platform.get(ctx.attr.jre).to_list()]
all_files.update({platform_prefix + platform.base_path + platform.jre + k: v for k, v in jre_files})
# Stamp the platform and its plugins
platform_files = _stamp_platform(ctx, platform, platform_files)
all_files.update({platform_prefix + k: v for k, v in platform_files.items()})
# for plugin in platform_plugins:
for plugin, this_plugin_files in plugin_files.items():
# TODO(b/329416516): Rework "excluding" performanceTesting plugin
if plugin == "performanceTesting":
continue
this_plugin_files = _stamp_plugin(ctx, platform, platform_files, this_plugin_files, overwrite_plugin_version = False)
all_files.update({platform_prefix + k: v for k, v in this_plugin_files.items()})
dev01 = ctx.actions.declare_file(ctx.attr.name + ".dev01." + platform.name)
ctx.actions.write(dev01, "")
files += [(platform.base_path + "license/dev01_license.txt", dev01)]
suffix = "64" if platform == LINUX else ("64.exe" if platform == WIN else "")
vm_options_path = platform_prefix + platform.base_path + "bin/studio" + suffix + ".vmoptions"
vm_options = ctx.attr.vm_options + {
LINUX: ctx.attr.vm_options_linux,
MAC: ctx.attr.vm_options_mac,
MAC_ARM: ctx.attr.vm_options_mac_arm,
WIN: ctx.attr.vm_options_win,
}[platform]
_append(ctx, platform, all_files, vm_options_path, vm_options)
properties = ctx.attr.properties + {
LINUX: ctx.attr.properties_linux,
MAC: ctx.attr.properties_mac,
MAC_ARM: ctx.attr.properties_mac_arm,
WIN: ctx.attr.properties_win,
}[platform]
_append(ctx, platform, all_files, platform_prefix + platform.base_path + "bin/idea.properties", properties)
# Add safe mode batch file based on the current platform
source_map = {
LINUX: ctx.attr.files_linux,
MAC: ctx.attr.files_mac,
MAC_ARM: ctx.attr.files_mac_arm,
WIN: ctx.attr.files_win,
}[platform]
if source_map != None:
for key in source_map:
files += [(platform.base_path + source_map[key], key.files.to_list()[0])]
so_jars = {"%s%s%s" % (platform_prefix, platform.base_path, jar): f for (jar, f) in ctx.attr.searchable_options.searchable_options}
licenses = []
for p in ctx.attr.plugins:
pkey = p[PluginInfo].directory
this_plugin_files = platform.get(p[PluginInfo].plugin_files)
this_plugin_files = _stamp_plugin(ctx, platform, platform_files, this_plugin_files, p[PluginInfo].overwrite_plugin_version)
licenses += [p[PluginInfo].licenses]
this_plugin_full_files = {platform_prefix + platform.base_path + k: v for k, v in this_plugin_files.items()}
for k, f in this_plugin_full_files.items():
if k in so_jars:
jar_with_so = ctx.actions.declare_file(k + "%s.so.jar" % platform.name)
run_singlejar(ctx, [f, so_jars[k]], jar_with_so)
this_plugin_full_files[k] = jar_with_so
all_files.update(this_plugin_full_files)
files += [(platform.base_path + "license/" + f.basename, f) for f in depset([], transitive = licenses).to_list()]
all_files.update({platform_prefix + k: v for k, v in files})
if platform == MAC or platform == MAC_ARM:
all_files.update({"_codesign/entitlements.xml": ctx.file.codesign_entitlements})
if platform == LINUX:
_produce_manifest(ctx, LINUX, platform_files)
attrs = _get_external_attributes(all_files)
_lnzipper(ctx, out.basename, all_files.items(), out, attrs = attrs, keep_symlink = platform == MAC_ARM)
return all_files
def _experimental_runner(ctx, name, target_to_file, out):
files = []
expected = []
for target, src in target_to_file.items():
dst = ctx.actions.declare_file(name + "/" + target)
files.append(dst)
expected.append(target)
ctx.actions.run_shell(
inputs = [src],
outputs = [dst],
command = "cp -f \"$1\" \"$2\"",
arguments = [src.path, dst.path],
mnemonic = "CopyFile",
progress_message = "Copying files",
use_default_shell_env = True,
)
file_list = ctx.actions.declare_file(name + "/files.lst")
ctx.actions.write(file_list, "\n".join(expected))
files.append(file_list)
# Creating runfiles would work, but we have files with spaces, and to avoid having all the needed
# files as ouputs of the list, we set them as sources of the final script.
ctx.actions.run_shell(
inputs = [ctx.file._studio_launcher] + files,
outputs = [out],
command = "cp -f \"$1\" \"$2\"",
arguments = [ctx.file._studio_launcher.path, out.path],
mnemonic = "CopyFile",
progress_message = "Copying files",
use_default_shell_env = True,
)
script_template = """\
#!/bin/bash
options=
tmp_dir=$(mktemp -d -t android-studio-XXXXXXXXXX)
if [ "$1" == "--debug" ]; then
options="$tmp_dir/.debug.vmoptions"
echo "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005" > "$options"
shift
elif [[ "$1" == "--wrapper_script_flag=--debug="* ]]; then
debug_option="$1"
options="$tmp_dir/.debug.vmoptions"
echo "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=${{debug_option##--wrapper_script_flag=--debug=}}" > "$options"
shift
fi
config_base_dir="$HOME/.studio_dev"
if [[ "$1" == "--config_base_dir="* ]]; then
config_base_dir="${{1##--config_base_dir=}}"
shift
fi
unzip -q "{zip_file}" -d "$tmp_dir"
mkdir -p "$config_base_dir/.config"
mkdir -p "$config_base_dir/.plugins"
mkdir -p "$config_base_dir/.system"
mkdir -p "$config_base_dir/.log"
echo "idea.config.path=$config_base_dir/.config" >> "$tmp_dir/.properties"
echo "idea.plugins.path=$config_base_dir/.plugins" >> "$tmp_dir/.properties"
echo "idea.system.path=$config_base_dir/.system" >> "$tmp_dir/.properties"
echo "idea.log.path=$config_base_dir/.log" >> "$tmp_dir/.properties"
properties="$tmp_dir/.properties"
if [ -z "$options" ]; then
STUDIO_PROPERTIES="$properties" {command} $args
else
STUDIO_VM_OPTIONS="$options" STUDIO_PROPERTIES="$properties" {command} $@
fi
"""
platform_by_name = {platform.name: platform for platform in [LINUX, MAC, MAC_ARM, WIN]}
def _android_studio_impl(ctx):
plugins = [plugin[PluginInfo].directory for plugin in ctx.attr.plugins]
ctx.actions.write(ctx.outputs.plugins, "".join([dir + "\n" for dir in plugins]))
outputs = {
LINUX: ctx.outputs.linux,
MAC: ctx.outputs.mac,
MAC_ARM: ctx.outputs.mac_arm,
WIN: ctx.outputs.win,
}
all_files = {}
for (platform, output) in outputs.items():
all_files[platform] = _android_studio_os(ctx, platform, output)
_produce_update_message_html(ctx)
host_platform = platform_by_name[ctx.attr.host_platform_name]
if ctx.attr.experimental_runner:
script = ctx.actions.declare_file("%s/%s.py" % (ctx.attr.name, ctx.attr.name))
_experimental_runner(
ctx,
ctx.attr.name,
all_files[host_platform],
script,
)
default_files = depset([script, ctx.outputs.manifest, ctx.outputs.update_message])
default_runfiles = None
else:
script = ctx.actions.declare_file("%s-run" % ctx.label.name)
script_content = script_template.format(
zip_file = outputs[host_platform].short_path,
command = {
LINUX: "$tmp_dir/android-studio/bin/studio.sh",
MAC: "open \"$tmp_dir/" + _android_studio_prefix(ctx, MAC) + "\"",
MAC_ARM: "open \"$tmp_dir/" + _android_studio_prefix(ctx, MAC_ARM) + "\"",
WIN: "$tmp_dir/android-studio/bin/studio64",
}[host_platform],
)
ctx.actions.write(script, script_content, is_executable = True)
runfiles = ctx.runfiles(files = [outputs[host_platform]])
default_files = depset([ctx.outputs.linux, ctx.outputs.mac, ctx.outputs.mac_arm, ctx.outputs.win, ctx.outputs.manifest, ctx.outputs.update_message])
default_runfiles = runfiles
# Leave everything that is not the main zips as implicit outputs
return DefaultInfo(
executable = script,
files = default_files,
runfiles = default_runfiles,
)
_android_studio = rule(
attrs = {
"host_platform_name": attr.string(),
"codesign_entitlements": attr.label(allow_single_file = True),
"compress": attr.bool(),
"experimental_runner": attr.bool(default = False),
"files_linux": attr.label_keyed_string_dict(allow_files = True, default = {}),
"files_mac": attr.label_keyed_string_dict(allow_files = True, default = {}),
"files_mac_arm": attr.label_keyed_string_dict(allow_files = True, default = {}),
"files_win": attr.label_keyed_string_dict(allow_files = True, default = {}),
"jre": attr.label(),
"platform": attr.label(providers = [IntellijInfo]),
"plugins": attr.label_list(providers = [PluginInfo]),
"vm_options": attr.string_list(),
"vm_options_linux": attr.string_list(),
"vm_options_mac": attr.string_list(),
"vm_options_mac_arm": attr.string_list(),
"vm_options_win": attr.string_list(),
"properties": attr.string_list(),
"properties_linux": attr.string_list(),
"properties_mac": attr.string_list(),
"properties_mac_arm": attr.string_list(),
"properties_win": attr.string_list(),
"selector": attr.string(mandatory = True),
"application_icon": attr.label(providers = [AppIconInfo]),
"searchable_options": attr.label(),
"version_code_name": attr.string(),
"version_micro_patch": attr.string(),
"version_release_number": attr.int(),
"version_type": attr.string(),
"update_message_template": attr.label(allow_single_file = True),
"version_suffix": attr.label(
providers = [BuildSettingInfo],
),
"_singlejar": attr.label(
default = Label("@bazel_tools//tools/jdk:singlejar"),
cfg = "host",
executable = True,
),
"_stamper": attr.label(
default = Label("//tools/adt/idea/studio:stamper"),
cfg = "exec",
executable = True,
),
"_generate_build_metadata": attr.label(
default = Label("//tools/adt/idea/studio:generate_build_metadata"),
cfg = "exec",
executable = True,
),
"_zipper": attr.label(
default = Label("@bazel_tools//tools/zip:zipper"),
cfg = "host",
executable = True,
),
"_lnzipper": attr.label(
default = Label("//tools/base/bazel/lnzipper:lnzipper"),
cfg = "exec",
executable = True,
),
"_expander": attr.label(
default = Label("//tools/base/bazel/expander"),
cfg = "host",
executable = True,
),
"_patch_exe": attr.label(
default = Label("//tools/vendor/google/windows-exe-patcher:patch-exe"),
cfg = "exec",
executable = True,
),
"_update_resources_jar": attr.label(
default = Label("//tools/adt/idea/studio/rules:update_resources_jar"),
cfg = "exec",
executable = True,
),
"_studio_launcher": attr.label(
allow_single_file = True,
default = Label("//tools/adt/idea/studio:studio.py"),
),
},
outputs = {
"linux": "%{name}.linux.zip",
"mac": "%{name}.mac.zip",
"mac_arm": "%{name}.mac_arm.zip",
"win": "%{name}.win.zip",
"plugins": "%{name}.plugin.lst",
"manifest": "%{name}_build_manifest.textproto",
"update_message": "%{name}_update_message.html",
},
executable = True,
implementation = _android_studio_impl,
)
# Builds a distribution of android studio.
# Args:
# platform: A studio_data target with the per-platform filegroups
# jre: If include a target with the jre to bundle in.
# plugins: A list of plugins to be bundled
# modules: A dictionary (see studio_plugin) with modules bundled at top level
# resources: A dictionary (see studio_plugin) with resources bundled at top level
# update_message_template: A file to use for the update message. The following
# substitutions are available to message templates:
# {full_version} - See _form_version_full below.
# {channel} - The channel derived from version_type.
#
# Regarding versioning information:
# - The "version_*" parameters (like "version_micro_path" and
# "version_type") are used both for Android Studio itself and for
# metadata produced by this rule (e.g. the build manifest and the
# update message).
# - The full version string is produced by "_form_version_full"
# following these rules:
# - If the version_type is "Stable", then the first release will not
# have a patch number, then every subsequent release will have a
# patch number starting at 1. In addition, the word "Stable" will
# never appear in the full version string.
# - If the version_type is anything other than "Stable", then the
# version_type will appear in the full version string, and
# version_release_number will be appended directly after.
# Examples:
# - Input: version_type = "Stable", version_release_number = 1
# - Output: "Dolphin | 2022.1.1"
# - Input: version_type = "Stable", version_release_number = 2
# - Output: "Dolphin | 2022.1.1 Patch 1"
# - Input: version_type = "Stable", version_release_number = 3
# - Output: "Dolphin | 2022.1.1 Patch 2"
# - Input: version_type = "Canary", version_release_number = 1
# - Output: "Dolphin | 2022.1.1 Canary 1"
# - Input: version_type = "Canary", version_release_number = 2
# - Output: "Dolphin | 2022.1.1 Canary 2"
# - Input: version_type = "RC", version_release_number = 3
# - Output: "Dolphin | 2022.1.1 RC 3"
#
# version_release_number may or may not match the actual patch number.
# For example, we may be on patch 12 of a Beta while still calling it
# "Beta 3", meaning we've shipped 9 Canary releases and 2 Beta releases
# before patch 12. In such a case, the release_number would be 3.
def android_studio(
name,
**kwargs):
_android_studio(
name = name,
compress = is_release(),
host_platform_name = select({
"@platforms//os:linux": LINUX.name,
"//tools/base/bazel/platforms:macos-x86_64": MAC.name,
"//tools/base/bazel/platforms:macos-arm64": MAC_ARM.name,
"@platforms//os:windows": WIN.name,
"//conditions:default": "",
}),
**kwargs
)
def _intellij_plugin_import_impl(ctx):
files = {}
plugin_dir = "plugins/" + ctx.attr.target_dir
# Note: platform plugins will have no files because they are already in intellij-sdk.
if ctx.attr.files:
for f in ctx.files.files:
if not f.short_path.startswith(ctx.attr.strip_prefix):
fail("File " + f.short_path + " does not start with prefix " + ctx.attr.strip_prefix)
relpath = f.short_path[len(ctx.attr.strip_prefix):]
files[plugin_dir + "/" + relpath] = f
plugin_files_linux = _studio_plugin_os(ctx, LINUX, [], plugin_dir) | files
plugin_files_mac = _studio_plugin_os(ctx, MAC, [], plugin_dir) | files
plugin_files_mac_arm = _studio_plugin_os(ctx, MAC_ARM, [], plugin_dir) | files
plugin_files_win = _studio_plugin_os(ctx, WIN, [], plugin_dir) | files
java_info = java_common.merge([export[JavaInfo] for export in ctx.attr.exports])
jars = java_info.runtime_output_jars
_check_plugin(ctx, ctx.outputs.plugin_metadata, jars)
return [
java_info,
DefaultInfo(runfiles = ctx.runfiles(files = ctx.files.files)),
PluginInfo(
directory = ctx.attr.target_dir,
plugin_metadata = ctx.outputs.plugin_metadata,
module_deps = depset(),
lib_deps = depset(ctx.attr.exports),
licenses = depset(),
plugin_files = struct(
linux = plugin_files_linux,
mac = plugin_files_mac,
mac_arm = plugin_files_mac_arm,
win = plugin_files_win,
),
overwrite_plugin_version = ctx.attr.overwrite_plugin_version,
),
# Force 'chkplugin' to run by marking its output as a validation output.
# See https://bazel.build/extending/rules#validation_actions for details.
OutputGroupInfo(_validation = depset([ctx.outputs.plugin_metadata])),
]
_intellij_plugin_import = rule(
attrs = {
# Note: platform plugins will have no files because they are already in intellij-sdk.
"files": attr.label_list(allow_files = True),
"strip_prefix": attr.string(),
"target_dir": attr.string(),
"resources": attr.label_list(allow_files = True),
"resources_dirs": attr.string_list(),
"exports": attr.label_list(providers = [JavaInfo], mandatory = True),
"compress": attr.bool(),
"overwrite_plugin_version": attr.bool(),
"_check_plugin": attr.label(
default = Label("//tools/adt/idea/studio:check_plugin"),
cfg = "host",
executable = True,
),
},
outputs = {
"plugin_metadata": "%{name}.info",
},
implementation = _intellij_plugin_import_impl,
)
def intellij_plugin_import(name, target_dir, exports, files = [], strip_prefix = "", resources = {}, overwrite_plugin_version = False, **kwargs):
"""This macro is for prebuilt IntelliJ plugins that are not already part of intellij-sdk."""
resources_dirs, resources_list = _dict_to_lists(resources)
_intellij_plugin_import(
name = name,
files = files,
strip_prefix = strip_prefix,
target_dir = target_dir,
resources = resources_list,
resources_dirs = resources_dirs,
exports = exports,
compress = is_release(),
overwrite_plugin_version = overwrite_plugin_version,
**kwargs
)
def _intellij_platform_impl_os(ctx, platform, data, zip_out):
files = platform.get(data).to_list()
plugin_dir = "%splugins/" % platform.base_path
base = []
plugins = {}
for file in files:
if file not in data.mappings:
fail("file %s not found in mappings" % file.path)
rel = data.mappings[file]
if not rel.startswith(plugin_dir):
# This is not a plugin file
base.append((rel, file))
continue
parts = rel[len(plugin_dir):].split("/")
if len(parts) == 0:
fail("Unexpected plugin file: " + rel)
plugin = parts[0]
if plugin not in plugins:
plugins[plugin] = []
plugins[plugin].append((rel, file))
_zipper(ctx, "base %s platform zip" % platform.name, base, zip_out)
base_files = {rel: file for rel, file in base}
plugin_files = {plugin: {rel: file for rel, file in files} for plugin, files in plugins.items()}
return base_files, plugin_files
def _intellij_platform_impl(ctx):
base_files_linux, plugin_files_linux = _intellij_platform_impl_os(ctx, LINUX, ctx.attr.studio_data, ctx.outputs.linux_zip)
base_files_win, plugin_files_win = _intellij_platform_impl_os(ctx, WIN, ctx.attr.studio_data, ctx.outputs.win_zip)
base_files_mac, plugin_files_mac = _intellij_platform_impl_os(ctx, MAC, ctx.attr.studio_data, ctx.outputs.mac_zip)
base_files_mac_arm, plugin_files_mac_arm = _intellij_platform_impl_os(ctx, MAC_ARM, ctx.attr.studio_data, ctx.outputs.mac_arm_zip)
runfiles = ctx.runfiles(files = ctx.files.data)
return struct(
providers = [
DefaultInfo(runfiles = runfiles),
java_common.merge([export[JavaInfo] for export in ctx.attr.exports]),
IntellijInfo(
major_version = ctx.attr.major_version,
minor_version = ctx.attr.minor_version,
base = struct(
linux = base_files_linux,
mac = base_files_mac,
mac_arm = base_files_mac_arm,
win = base_files_win,
),
plugins = struct(
linux = plugin_files_linux,
mac = plugin_files_mac,
mac_arm = plugin_files_mac_arm,
win = plugin_files_win,
),
),
],
platform_info = struct(
mac_bundle_name = ctx.attr.mac_bundle_name,
),
)
_intellij_platform = rule(
attrs = {
"major_version": attr.string(),
"minor_version": attr.string(),
"exports": attr.label_list(providers = [JavaInfo]),
"data": attr.label_list(allow_files = True),
"studio_data": attr.label(),
"compress": attr.bool(),
"mac_bundle_name": attr.string(),
"_zipper": attr.label(
default = Label("@bazel_tools//tools/zip:zipper"),
cfg = "host",
executable = True,
),
},
outputs = {
"linux_zip": "%{name}.linux.zip",
"win_zip": "%{name}.win.zip",
"mac_zip": "%{name}.mac.zip",
"mac_arm_zip": "%{name}.mac_arm.zip",
},
provides = [DefaultInfo, JavaInfo, IntellijInfo],
implementation = _intellij_platform_impl,
)
# For platforms that are only used to build standalone plugins against
def intellij_platform_import(name, spec):
_intellij_platform(
name = name,
exports = [":" + name + "_jars"],
studio_data = name + ".data",
visibility = ["//visibility:public"],
)
jvm_import(
name = name + "_jars",
jars = [jar[1:] for jar in spec.jars + spec.jars_linux],
visibility = ["//visibility:public"],
)
studio_data(
name = name + ".data",
)
native.filegroup(
name = name + "-product-info",
srcs = ["product-info.json"],
visibility = ["//visibility:public"],
)
for plugin, jars in spec.plugin_jars.items():
jars_target_name = "%s-plugin-%s-jars" % (name, plugin)
jvm_import(
name = jars_target_name,
jars = jars,
visibility = ["//visibility:public"],
)
intellij_plugin_import(
name = name + "-plugin-%s" % plugin,
exports = [":" + jars_target_name],
target_dir = "",
visibility = ["//visibility:public"],
)
jvm_import(
name = name + "-test-framework",
jars = ["lib/testFramework.jar"],
visibility = ["//visibility:public"],
)
def intellij_platform(
name,
src,
spec,
**kwargs):
jvm_import(
name = name + "_jars",
jars = select({
"@platforms//os:windows": [src + "/windows/android-studio" + jar for jar in spec.jars + spec.jars_windows],
"//tools/base/bazel/platforms:macos-x86_64": [src + "/darwin/android-studio/Contents" + jar for jar in spec.jars + spec.jars_darwin],
"//tools/base/bazel/platforms:macos-arm64": [src + "/darwin_aarch64/android-studio/Contents" + jar for jar in spec.jars + spec.jars_darwin_aarch64],
"//conditions:default": [src + "/linux/android-studio" + jar for jar in spec.jars + spec.jars_linux],
}),
)
_intellij_platform(
name = name,
major_version = spec.major_version,
minor_version = spec.minor_version,
exports = [":" + name + "_jars"],
compress = is_release(),
mac_bundle_name = spec.mac_bundle_name,
studio_data = name + ".data",
visibility = ["@intellij//:__subpackages__"],
# Local linux sandbox does not support spaces in names, so we exclude some files
# Otherwise we get: "link or target filename contains space"
data = select({
"@platforms//os:windows": native.glob(
include = [src + "/windows/android-studio/**"],
exclude = [src + "/windows/android-studio/plugins/textmate/lib/bundles/**"],
),
"//tools/base/bazel/platforms:macos-x86_64": native.glob(
include = [src + "/darwin/android-studio/**"],
exclude = [src + "/darwin/android-studio/Contents/plugins/textmate/lib/bundles/**"],
),
"//tools/base/bazel/platforms:macos-arm64": native.glob(
include = [src + "/darwin_aarch64/android-studio/**"],
exclude = [src + "/darwin_aarch64/android-studio/Contents/plugins/textmate/lib/bundles/**"],
),
"//conditions:default": native.glob(
include = [src + "/linux/android-studio/**"],
exclude = [src + "/linux/android-studio/plugins/textmate/lib/bundles/**"],
),
}),
)
ide_paths = {
"darwin": src + "/darwin/android-studio",
"darwin_aarch64": src + "/darwin_aarch64/android-studio",
"linux": src + "/linux/android-studio",
"windows": src + "/windows/android-studio",
}
native.py_test(
name = name + "_spec_test",
srcs = ["//tools/adt/idea/studio:intellij_test.py"],
main = "intellij_test.py",
tags = ["noci:studio-win", "no_test_mac"],
data = native.glob([src + "/**/lib/*.jar", "**/product-info.json"]),
env = {
"spec": json.encode(spec),
"intellij_paths": ",".join([k + "=" + native.package_name() + "/" + v for k, v in ide_paths.items()]),
},
deps = ["//tools/adt/idea/studio:intellij"],
)
# Expose lib/resources.jar as a separate target
native.java_import(
name = name + "-resources-jar",
jars = select({
"@platforms//os:windows": [src + "/windows/android-studio/lib/resources.jar"],
"//tools/base/bazel/platforms:macos-x86_64": [src + "/darwin/android-studio/Contents/lib/resources.jar"],
"//tools/base/bazel/platforms:macos-arm64": [src + "/darwin_aarch64/android-studio/Contents/lib/resources.jar"],
"//conditions:default": [src + "/linux/android-studio/lib/resources.jar"],
}),
visibility = ["@intellij//:__subpackages__"],
)
# Expose build.txt from the prebuilt SDK
native.filegroup(
name = name + "-build-txt",
srcs = select({
"@platforms//os:windows": [src + "/windows/android-studio/build.txt"],
"//tools/base/bazel/platforms:macos-x86_64": [src + "/darwin/android-studio/Contents/Resources/build.txt"],
"//tools/base/bazel/platforms:macos-arm64": [src + "/darwin_aarch64/android-studio/Contents/Resources/build.txt"],
"//conditions:default": [src + "/linux/android-studio/build.txt"],
}),
visibility = ["@intellij//:__subpackages__"],
)
# Expose product-info.json.
native.filegroup(
name = name + "-product-info",
srcs = select({
"@platforms//os:windows": [src + "/windows/android-studio/product-info.json"],
"//tools/base/bazel/platforms:macos-x86_64": [src + "/darwin/android-studio/Contents/Resources/product-info.json"],
"//tools/base/bazel/platforms:macos-arm64": [src + "/darwin_aarch64/android-studio/Contents/Resources/product-info.json"],
"//conditions:default": [src + "/linux/android-studio/product-info.json"],
}),
visibility = ["@intellij//:__subpackages__"],
)
# Expose the default VM options file.
native.filegroup(
name = name + "-vm-options",
srcs = select({
"@platforms//os:windows": [src + "/windows/android-studio/bin/studio64.exe.vmoptions"],
"//tools/base/bazel/platforms:macos-x86_64": [src + "/darwin/android-studio/Contents/bin/studio.vmoptions"],
"//tools/base/bazel/platforms:macos-arm64": [src + "/darwin_aarch64/android-studio/Contents/bin/studio.vmoptions"],
"//conditions:default": [src + "/linux/android-studio/bin/studio64.vmoptions"],
}),
visibility = ["@intellij//:__subpackages__"],
)
# TODO: merge this into the intellij_platform rule.
dir_archive(
name = name + "-full-linux",
dir = "prebuilts/studio/intellij-sdk/" + src + "/linux/android-studio",
files = native.glob([src + "/linux/android-studio/**"]),
visibility = ["@intellij//:__subpackages__"],
)
studio_data(
name = name + ".data",
files_linux = native.glob([src + "/linux/**"]),
files_mac = native.glob([src + "/darwin/**"]),
files_mac_arm = native.glob([src + "/darwin_aarch64/**"]),
files_win = native.glob([src + "/windows/**"]),
mappings = {
"prebuilts/studio/intellij-sdk/%s/linux/android-studio/" % src: "",
"prebuilts/studio/intellij-sdk/%s/darwin/android-studio/" % src: "",
"prebuilts/studio/intellij-sdk/%s/darwin_aarch64/android-studio/" % src: "",
"prebuilts/studio/intellij-sdk/%s/windows/android-studio/" % src: "",
},
)
for plugin, jars in spec.plugin_jars.items():
jars_target_name = "%s-plugin-%s_jars" % (name, plugin)
_gen_plugin_jars_import_target(jars_target_name, src, spec, plugin, jars)
_intellij_plugin_import(
name = name + "-plugin-%s" % plugin,
exports = [":" + jars_target_name],
visibility = ["@intellij//:__subpackages__"],
)
jvm_import(
name = name + "-updater",
jars = [src + "/updater-full.jar"],
visibility = ["@intellij//:__subpackages__"],
)
# Expose the IntelliJ test framework separately, for consumption by tests only.
jvm_import(
name = name + "-test-framework",
jars = select({
"@platforms//os:windows": [src + "/windows/android-studio/lib/testFramework.jar"],
"//tools/base/bazel/platforms:macos-x86_64": [src + "/darwin/android-studio/Contents/lib/testFramework.jar"],
"//tools/base/bazel/platforms:macos-arm64": [src + "/darwin_aarch64/android-studio/Contents/lib/testFramework.jar"],
"//conditions:default": [src + "/linux/android-studio/lib/testFramework.jar"],
}),
visibility = ["@intellij//:__subpackages__"],
)
def _gen_plugin_jars_import_target(name, src, spec, plugin, jars):
"""Generates a jvm_import target for the specified plugin."""
add_windows = spec.plugin_jars_windows[plugin] if plugin in spec.plugin_jars_windows else []
jars_windows = [src + "/windows/android-studio/" + jar for jar in jars + add_windows]
add_darwin = spec.plugin_jars_darwin[plugin] if plugin in spec.plugin_jars_darwin else []
jars_darwin = [src + "/darwin/android-studio/Contents/" + jar for jar in jars + add_darwin]
add_darwin_aarch64 = spec.plugin_jars_darwin_aarch64[plugin] if plugin in spec.plugin_jars_darwin_aarch64 else []
jars_darwin_aarch64 = [src + "/darwin_aarch64/android-studio/Contents/" + jar for jar in jars + add_darwin_aarch64]
add_linux = spec.plugin_jars_linux[plugin] if plugin in spec.plugin_jars_linux else []
jars_linux = [src + "/linux/android-studio/" + jar for jar in jars + add_linux]
jvm_import(
name = name,
jars = select({
"@platforms//os:windows": jars_windows,
"//tools/base/bazel/platforms:macos-x86_64": jars_darwin,
"//tools/base/bazel/platforms:macos-arm64": jars_darwin_aarch64,
"//conditions:default": jars_linux,
}),
)
def iml_studio_test(
name,
data = [],
**kwargs):
iml_test(
name = name,
data = select({
"@platforms//os:linux": ["//tools/adt/idea/studio:android-studio.linux.zip"],
"//tools/base/bazel/platforms:macos-x86_64": ["//tools/adt/idea/studio:android-studio.mac.zip"],
"//tools/base/bazel/platforms:macos-arm64": ["//tools/adt/idea/studio:android-studio.mac_arm.zip"],
"@platforms//os:windows": ["//tools/adt/idea/studio:android-studio.win.zip"],
"//conditions:default": [],
}) + data,
**kwargs
)