| """This file contains Bazel build rules for the Android Studio release distribution""" |
| |
| load("//build/bazel/rules/gathering:prebuilt_package_metadata.bzl", "prebuilt_package_metadata") |
| load("//build/bazel/rules/gathering:write_package_metadata.bzl", "write_package_metadata") |
| load("//tools/adt/idea/studio/rules:app-icon.bzl", "AppIconInfo", "replace_app_icon") |
| load("//tools/base/bazel:bazel.bzl", "ImlModuleInfo") |
| 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", |
| "modules": "ImlModuleInfo for modules included in this plugin", |
| "libs": "libraries to be included in this plugin", |
| "license_files": "", |
| "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", |
| "plugin_id": "The plugin id", |
| }, |
| ) |
| |
| 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", |
| }, |
| ) |
| |
| _StudioDataInfo = provider( |
| doc = "Holds IDE distribution files split by platform", |
| fields = { |
| "linux": "Linux files", |
| "mac": "Mac x86-64 files", |
| "mac_arm": "Mac aarch64 files", |
| "win": "Windows files", |
| "mappings": "A map from files to destination paths", |
| }, |
| ) |
| |
| _ConfigurationInfo = provider( |
| doc = "All the needed information to configure a studio distro (channel, flags, icons, splash screen, etc)", |
| fields = { |
| "version_type": "Nightly, Canary, Beta, etc.", |
| "version_suffix": "If None, auto computed, if provided used instead. Eg. Nightly 2024-10-10.", |
| "application_icon": "The application icon to use.", |
| "mac_app_name": "The application name on Mac, e.g. 'Android Studio Preview'", |
| "vm_options": "Custom vm options per configuration", |
| }, |
| ) |
| |
| _SearchableOptionsInfo = provider( |
| # For context: the "searchable options" for a given plugin is essentially just a list of |
| # available options exposed by that plugin in the IDE settings dialog. The list gets stored |
| # in a file which the IDE parses at runtime to optimize search queries from the user. |
| doc = "Holds searchable options generated for a given set of plugins", |
| fields = { |
| "so_jars": "A map from plugin IDs to corresponding searchable options jars", |
| }, |
| ) |
| |
| # 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", |
| } |
| |
| 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.append("@" + 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", "cpu:16": ""}, |
| 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].append(v) |
| return dict |
| |
| def _pack_modules(ctx, jar_names, modules): |
| jars = _lists_to_dict(jar_names, modules) |
| 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.append((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 _StudioDataInfo in dep: |
| dep_data = dep[_StudioDataInfo] |
| files += [(dir + "/" + dep_data.mappings[f], f) for f in platform.get(dep_data).to_list()] |
| else: |
| files += [(dir + "/" + f.basename, f) for f in dep.files.to_list()] |
| return files |
| |
| def _check_plugin(ctx, out, files, kind, id, allow_bundled_updates = False, 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) |
| check_args.add("--kind", kind) |
| check_args.add("--id", id) |
| if allow_bundled_updates: |
| check_args.add("--allow_bundled_updates") |
| if deps != None: |
| check_args.add_all("--deps", deps, omit_if_empty = False) |
| |
| 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, plugin_jars, plugin_dir): |
| files = {plugin_dir + "/lib/" + d: f for (d, f) in plugin_jars} |
| |
| 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_id = ctx.attr.name |
| plugin_dir = "plugins/" + ctx.attr.directory |
| plugin_jars = _pack_modules(ctx, ctx.attr.jars, ctx.attr.modules) |
| plugin_jars = plugin_jars + [(f.basename, f) for f in ctx.files.libs] |
| |
| # Pack searchable-options metadata. |
| so_jars = ctx.attr.searchable_options[_SearchableOptionsInfo].so_jars |
| if plugin_id in so_jars: |
| plugin_jars.append((ctx.attr.directory + ".so.jar", so_jars[plugin_id])) |
| |
| # Ensure plugin id is known at build time |
| _check_plugin( |
| ctx, |
| ctx.outputs.plugin_metadata, |
| [f for (r, f) in plugin_jars], |
| "plugin", |
| ctx.attr.name, |
| verify_deps = ctx.attr.deps, |
| ) |
| plugin_files_linux = _studio_plugin_os(ctx, LINUX, plugin_jars, plugin_dir) |
| plugin_files_mac = _studio_plugin_os(ctx, MAC, plugin_jars, plugin_dir) |
| plugin_files_mac_arm = _studio_plugin_os(ctx, MAC_ARM, plugin_jars, plugin_dir) |
| plugin_files_win = _studio_plugin_os(ctx, WIN, plugin_jars, plugin_dir) |
| |
| for lib in ctx.attr.libs: |
| if ImlModuleInfo in lib: |
| fail(lib.label.name + " is a module, yet it is listed under 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 = [depset(m[ImlModuleInfo].deps) for m in ctx.attr.modules]) |
| have = depset( |
| direct = ctx.attr.modules + ctx.attr.libs + [ctx.attr._intellij_sdk], |
| transitive = [d[PluginInfo].modules for d in ctx.attr.deps] + |
| [d[PluginInfo].libs 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(label) for label 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, |
| modules = depset(ctx.attr.modules), |
| libs = depset(ctx.attr.libs), |
| license_files = depset(ctx.files.license_files), |
| overwrite_plugin_version = True, |
| platform = ctx.attr._intellij_platform, |
| plugin_id = plugin_id, |
| ), |
| # 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), |
| "license_files": 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]), |
| "searchable_options": attr.label( |
| default = Label("//tools/adt/idea/searchable-options"), |
| providers = [_SearchableOptionsInfo], |
| ), |
| "_singlejar": attr.label( |
| default = Label("@bazel_tools//tools/jdk:singlejar"), |
| cfg = "exec", |
| executable = True, |
| ), |
| "_zipper": attr.label( |
| default = Label("@bazel_tools//tools/zip:zipper"), |
| cfg = "exec", |
| executable = True, |
| ), |
| "_check_plugin": attr.label( |
| default = Label("//tools/adt/idea/studio:check_plugin"), |
| cfg = "exec", |
| executable = True, |
| ), |
| "_intellij_platform": attr.label( |
| default = Label("//tools/base/intellij-bazel:intellij_platform"), |
| cfg = "exec", |
| ), |
| "_intellij_sdk": attr.label( |
| default = Label("@intellij//:intellij-sdk"), |
| ), |
| }, |
| outputs = { |
| "plugin_metadata": "%{name}.info", |
| }, |
| implementation = _studio_plugin_impl, |
| ) |
| |
| def _searchable_options_impl(ctx): |
| searchable_options = {} |
| searchable_options_src = {} |
| for dep, plugin in ctx.attr.searchable_options.items(): |
| if plugin not in searchable_options_src: |
| so_jar = ctx.actions.declare_file(ctx.attr.name + ".%s.so.jar" % plugin) |
| searchable_options_src[plugin] = [] |
| searchable_options[plugin] = so_jar |
| searchable_options_src[plugin].extend(dep[DefaultInfo].files.to_list()) |
| |
| for id, srcs in searchable_options_src.items(): |
| jar = searchable_options[id] |
| _zipper(ctx, "%s %s searchable options" % (id, jar), [(f.basename, f) for f in srcs], jar) |
| |
| return [ |
| DefaultInfo(files = depset(searchable_options.values())), |
| _SearchableOptionsInfo(so_jars = searchable_options), |
| ] |
| |
| _searchable_options = rule( |
| attrs = { |
| "searchable_options": attr.label_keyed_string_dict(allow_files = True), |
| "compress": attr.bool(), |
| "strip_prefix": attr.string(), |
| "_zipper": attr.label( |
| default = Label("@bazel_tools//tools/zip:zipper"), |
| cfg = "exec", |
| executable = True, |
| ), |
| }, |
| executable = False, |
| provides = [_SearchableOptionsInfo], |
| 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 _StudioDataInfo in dep: |
| 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 _StudioDataInfo in dep: |
| dep_data = dep[_StudioDataInfo] |
| linux.append(dep_data.linux) |
| mac.append(dep_data.mac) |
| mac_arm.append(dep_data.mac_arm) |
| win.append(dep_data.win) |
| mappings.update(dep_data.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 [ |
| _StudioDataInfo( |
| linux = dlinux, |
| mac = dmac, |
| mac_arm = dmac_arm, |
| win = dwin, |
| mappings = mappings, |
| ), |
| 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, |
| provides = [_StudioDataInfo], |
| 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()))) |
| return type_channel_mappings[version_type] |
| |
| def _form_version_full(ctx): |
| """Forms version_full based on code name, version type, and release number""" |
| config = ctx.attr.configuration[_ConfigurationInfo] |
| channel = _get_channel_info(config.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 config.version_suffix: |
| return code_name_and_patch_components + " " + config.version_suffix |
| |
| return (code_name_and_patch_components + |
| " " + |
| config.version_type + |
| " " + |
| str(ctx.attr.version_release_number)) |
| |
| def _form_studio_version_component(intellij_info, studio_micro): |
| """Returns the 4th component of the full 5-component build number, identifying a specific Studio release""" |
| return intellij_info.major_version[2:] + intellij_info.minor_version + studio_micro |
| |
| 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(ctx.attr.name + "." + file.basename + ".%s.template" % platform.name) |
| out = ctx.actions.declare_file(ctx.attr.name + "." + 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 |
| config = ctx.attr.configuration[_ConfigurationInfo] |
| build_txt = platform_files[platform.resource_path + "build.txt"] |
| resources_jar = platform_files[platform.base_path + "lib/resources.jar"] |
| |
| channel = _get_channel_info(config.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): |
| config = ctx.attr.configuration[_ConfigurationInfo] |
| if not ctx.file.update_message_template: |
| ctx.actions.write(output = ctx.outputs.update_message, content = "") |
| return |
| |
| channel = _get_channel_info(config.version_type) |
| |
| args = ctx.actions.args() |
| args.add("--version_file", ctx.version_file) |
| args.add_all(["--substitute", "{full_version}", _full_display_version(ctx)]) |
| args.add_all(["--substitute", "{channel}", channel]) |
| args.add("--replace_build_day") |
| _stamp(ctx, args, [ctx.version_file], ctx.file.update_message_template, ctx.outputs.update_message) |
| |
| def _stamp_platform(ctx, platform, platform_files, added_plugins): |
| config = ctx.attr.configuration[_ConfigurationInfo] |
| |
| args = ["--stamp_platform"] |
| |
| ret = {} |
| ret.update(platform_files) |
| |
| (micro, patch) = _split_version(ctx.attr.version_micro_patch) |
| version_component = _form_studio_version_component(ctx.attr.platform[IntellijInfo], micro) |
| |
| 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("--version_component", version_component) |
| 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") |
| 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("--version_micro", micro) |
| args.add("--version_patch", patch) |
| args.add("--replace_build_day") |
| args.add("--build_txt", stamped_build_txt.path) |
| |
| if ctx.attr.essential_plugins: |
| args.add_all("--essential_plugins", ctx.attr.essential_plugins) |
| |
| 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() |
| |
| system_selector = ctx.attr.selector + ctx.attr.platform[IntellijInfo].major_version + "." + ctx.attr.platform[IntellijInfo].minor_version + "." + micro |
| |
| args.add("--replace_selector", system_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", system_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", system_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", system_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("--version_component", version_component) |
| args.add("--replace_build_number") |
| args.add("--replace_selector", system_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_", system_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", system_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", system_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("--build_txt", stamped_build_txt) |
| args.add("--stamp_product_info") |
| args.add("--replace_selector", system_selector) |
| for p in added_plugins: |
| args.add_all("--added_plugin", [p[PluginInfo].plugin_id] + platform.get(p[PluginInfo].plugin_files).keys()) |
| args.use_param_file("@%s") |
| args.set_param_file_format("multiline") |
| _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"] |
| |
| (micro, _) = _split_version(ctx.attr.version_micro_patch) |
| version_component = _form_studio_version_component(ctx.attr.platform[IntellijInfo], micro) |
| |
| 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("--version_component", version_component) |
| 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 _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, added_plugins, out): |
| files = [] |
| all_files = {} |
| |
| config = ctx.attr.configuration[_ConfigurationInfo] |
| platform_prefix = config.mac_app_name + ".app/" if platform in [MAC, MAC_ARM] else "android-studio/" |
| |
| platform_files = platform.get(ctx.attr.platform[IntellijInfo].base) |
| platform_files = replace_app_icon(ctx, platform.name, platform_files, config.application_icon[AppIconInfo]) |
| plugin_files = platform.get(ctx.attr.platform[IntellijInfo].plugins) |
| |
| if ctx.attr.jre: |
| jre_data = ctx.attr.jre[_StudioDataInfo] |
| jre_files = [(jre_data.mappings[f], f) for f in platform.get(jre_data).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, added_plugins) |
| all_files.update({platform_prefix + k: v for k, v in platform_files.items()}) |
| |
| # for plugin in platform_plugins: |
| for _, this_plugin_files in plugin_files.items(): |
| 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.append((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 = config.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) |
| |
| license_files = [] |
| for p in ctx.attr.plugins: |
| 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) |
| |
| license_files.append(p[PluginInfo].license_files) |
| this_plugin_full_files = {platform_prefix + platform.base_path + k: v for k, v in this_plugin_files.items()} |
| all_files.update(this_plugin_full_files) |
| |
| files += [(platform.base_path + "license/" + f.basename, f) for f in depset([], transitive = license_files).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 _studio_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, |
| ) |
| |
| platform_by_name = {platform.name: platform for platform in [LINUX, MAC, MAC_ARM, WIN]} |
| |
| def _android_studio_impl(ctx): |
| plugin_list = [] |
| for p in ctx.attr.plugins: |
| id = p[PluginInfo].plugin_id |
| plugin_list.append(p[PluginInfo].directory + (": " + id if id else "")) |
| ctx.actions.write(ctx.outputs.plugins, "".join(["%s\n" % line for line in plugin_list])) |
| |
| 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, ctx.attr.plugins, output) |
| |
| _produce_update_message_html(ctx) |
| |
| host_platform = platform_by_name[ctx.attr.host_platform_name] |
| script = ctx.actions.declare_file("%s/%s.py" % (ctx.attr.name, ctx.attr.name)) |
| _studio_runner(ctx, ctx.attr.name, all_files[host_platform], script) |
| |
| # Leave everything that is not the main zips as implicit outputs |
| return DefaultInfo( |
| executable = script, |
| files = depset([script, ctx.outputs.manifest, ctx.outputs.update_message]), |
| ) |
| |
| _android_studio = rule( |
| attrs = { |
| "host_platform_name": attr.string(), |
| "codesign_entitlements": attr.label(allow_single_file = True), |
| "compress": attr.bool(), |
| "essential_plugins": attr.string_list(), |
| "jre": attr.label(providers = [_StudioDataInfo]), |
| "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), |
| "version_code_name": attr.string(), |
| "version_micro_patch": attr.string(), |
| "version_release_number": attr.int(), |
| "update_message_template": attr.label(allow_single_file = True), |
| "configuration": attr.label(providers = [_ConfigurationInfo]), |
| "_singlejar": attr.label( |
| default = Label("@bazel_tools//tools/jdk:singlejar"), |
| cfg = "exec", |
| 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 = "exec", |
| 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 = "exec", |
| 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 |
| # generate_package_metadata: If true, a write_package_metadata target will |
| # be created. |
| # 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, |
| plugins, |
| configurations, |
| legacy_default_configuration, |
| generate_package_metadata = False, |
| **kwargs): |
| if generate_package_metadata: |
| write_package_metadata( |
| name = name + "-metadata", |
| deps = plugins, |
| ) |
| for configuration in configurations: |
| config_name = Label(configuration).name |
| suffix = "" if config_name == legacy_default_configuration else "." + config_name |
| _android_studio( |
| name = name + suffix, |
| compress = is_release(), |
| configuration = configuration, |
| 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": "", |
| }), |
| plugins = plugins, |
| **kwargs |
| ) |
| |
| def _android_studio_configuration_impl(ctx): |
| return [_ConfigurationInfo( |
| application_icon = ctx.attr.application_icon, |
| version_type = ctx.attr.version_type, |
| version_suffix = ctx.attr.version_suffix, |
| mac_app_name = ctx.attr.mac_app_name, |
| vm_options = ctx.attr.vm_options, |
| )] |
| |
| def android_studio_configuration( |
| name, |
| flag_level, |
| enable_debug_flags = False, |
| vm_options = [], |
| **kwargs): |
| _vm_options = vm_options + [ |
| "-Dflags.configuration.level=" + flag_level, |
| "-Dflags.debug.enabled=" + ("true" if enable_debug_flags else "false"), |
| ] |
| _android_studio_configuration( |
| name = name, |
| vm_options = _vm_options, |
| **kwargs |
| ) |
| |
| _android_studio_configuration = rule( |
| attrs = { |
| "application_icon": attr.label(providers = [AppIconInfo], mandatory = True), |
| "version_type": attr.string(), |
| "version_suffix": attr.string(), |
| "mac_app_name": attr.string(mandatory = True), |
| "vm_options": attr.string_list(), |
| }, |
| implementation = _android_studio_configuration_impl, |
| ) |
| |
| def _intellij_plugin_import_impl(ctx): |
| files = {} |
| plugin_dir = "plugins/" + ctx.attr.target_dir |
| id = ctx.attr.id or ctx.attr.name |
| |
| # 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_jars = [] |
| |
| # Pack searchable-options metadata. |
| if ctx.attr.searchable_options: |
| so_jars = ctx.attr.searchable_options[_SearchableOptionsInfo].so_jars |
| if id in so_jars: |
| plugin_jars.append((ctx.attr.target_dir + ".so.jar", so_jars[id])) |
| |
| plugin_files_linux = _studio_plugin_os(ctx, LINUX, plugin_jars, plugin_dir) | files |
| plugin_files_mac = _studio_plugin_os(ctx, MAC, plugin_jars, plugin_dir) | files |
| plugin_files_mac_arm = _studio_plugin_os(ctx, MAC_ARM, plugin_jars, plugin_dir) | files |
| plugin_files_win = _studio_plugin_os(ctx, WIN, plugin_jars, plugin_dir) | files |
| |
| # buildifier: disable=native-java-common (@rules_java is not usable in this file yet) |
| # buildifier: disable=native-java-info (@rules_java is not usable in this file yet) |
| 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, ctx.attr.kind, id, allow_bundled_updates = ctx.attr.allow_bundled_updates) |
| |
| return [ |
| java_info, |
| DefaultInfo(runfiles = ctx.runfiles(files = ctx.files.files)), |
| PluginInfo( |
| directory = ctx.attr.target_dir, |
| plugin_metadata = ctx.outputs.plugin_metadata, |
| plugin_id = id, |
| modules = depset(), |
| libs = depset(ctx.attr.exports), |
| license_files = 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 = { |
| "kind": attr.string(default = "plugin", doc = "Pass 'module' if this is a plugin module inside a larger host plugin"), |
| "id": attr.string(doc = "the plugin id, if different from the target name"), |
| "allow_bundled_updates": attr.bool(doc = "whether to allow this plugin to be updated out-of-band", default = False), |
| # 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(), |
| # buildifier: disable=native-java-info (@rules_java is not usable in this file yet) |
| "exports": attr.label_list(providers = [JavaInfo], mandatory = True), |
| "searchable_options": attr.label(providers = [_SearchableOptionsInfo]), |
| "compress": attr.bool(), |
| "overwrite_plugin_version": attr.bool(), |
| "_check_plugin": attr.label( |
| default = Label("//tools/adt/idea/studio:check_plugin"), |
| cfg = "exec", |
| 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): |
| studio_data = ctx.attr.studio_data[_StudioDataInfo] |
| base_files_linux, plugin_files_linux = _intellij_platform_impl_os(ctx, LINUX, studio_data, ctx.outputs.linux_zip) |
| base_files_win, plugin_files_win = _intellij_platform_impl_os(ctx, WIN, studio_data, ctx.outputs.win_zip) |
| base_files_mac, plugin_files_mac = _intellij_platform_impl_os(ctx, MAC, studio_data, ctx.outputs.mac_zip) |
| base_files_mac_arm, plugin_files_mac_arm = _intellij_platform_impl_os(ctx, MAC_ARM, studio_data, ctx.outputs.mac_arm_zip) |
| |
| runfiles = ctx.runfiles(files = ctx.files.data) |
| return [ |
| DefaultInfo(runfiles = runfiles), |
| # buildifier: disable=native-java-common (@rules_java is not usable in this file yet) |
| # buildifier: disable=native-java-info (@rules_java is not usable in this file yet) |
| 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, |
| ), |
| ), |
| ] |
| |
| _intellij_platform = rule( |
| attrs = { |
| "major_version": attr.string(), |
| "minor_version": attr.string(), |
| # buildifier: disable=native-java-info (@rules_java is not usable in this file yet) |
| "exports": attr.label_list(providers = [JavaInfo]), |
| "data": attr.label_list(allow_files = True), |
| "studio_data": attr.label(providers = [_StudioDataInfo]), |
| "compress": attr.bool(), |
| "_zipper": attr.label( |
| default = Label("@bazel_tools//tools/zip:zipper"), |
| cfg = "exec", |
| 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", |
| }, |
| # buildifier: disable=native-java-info (@rules_java is not usable in this file yet) |
| provides = [DefaultInfo, JavaInfo, IntellijInfo], |
| implementation = _intellij_platform_impl, |
| ) |
| |
| def intellij_platform_import(name, spec): |
| """Imports a platform used only for building standalone plugins. |
| |
| Args: |
| name: the platform name |
| spec: a map of bundled plugins and associated jars |
| """ |
| _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"], |
| ) |
| |
| native.filegroup( |
| name = name + "-build-txt", |
| srcs = ["build.txt"], |
| visibility = ["//visibility:public"], |
| ) |
| |
| dir_archive( |
| name = name + "-dist", |
| dir = "", |
| dir_relative_to_repository = True, |
| files = native.glob( |
| include = ["**"], |
| exclude = ["spec.bzl", "BUILD.bazel", "WORKSPACE"], |
| ), |
| visibility = ["//visibility:public"], |
| ) |
| |
| for plugin, jars in spec.plugin_jars.items(): |
| # 'kind' indicates whether this is a top-level plugin, or a plugin module inside a host plugin. |
| kind = "module" if len(jars) == 1 and "/modules/" in jars[0] else "plugin" |
| 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, |
| id = plugin, |
| kind = kind, |
| allow_bundled_updates = True, # Since these plugins are outside our control. |
| 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, |
| vmoptions_prefix, |
| sdk_dir_name): |
| """Declares an IntelliJ Platform to be used in building a full IDE distribution. |
| |
| Args: |
| name: the platform name |
| src: the root directory |
| spec: a map of bundled plugins and associated jars |
| vmoptions_prefix: a name that is used as the prefix of the vmoptions file |
| sdk_dir_name: the name of the directory to enter to reach platform root |
| """ |
| sdk_dirs = struct( |
| windows = src + "/windows/" + sdk_dir_name, |
| darwin = src + "/darwin/" + sdk_dir_name, |
| darwin_aarch64 = src + "/darwin_aarch64/" + sdk_dir_name, |
| linux = src + "/linux/" + sdk_dir_name, |
| ) |
| |
| prebuilt_package_metadata( |
| name = name + "_prebuilt_metadata", |
| third_party_dependencies = sdk_dirs.linux + "/license/third-party-libraries.json", |
| ) |
| |
| jvm_import( |
| name = name + "_jars", |
| jars = select({ |
| "@platforms//os:windows": [sdk_dirs.windows + jar for jar in spec.jars + spec.jars_windows], |
| "//tools/base/bazel/platforms:macos-x86_64": [sdk_dirs.darwin + "/Contents" + jar for jar in spec.jars + spec.jars_darwin], |
| "//tools/base/bazel/platforms:macos-arm64": [sdk_dirs.darwin_aarch64 + "/Contents" + jar for jar in spec.jars + spec.jars_darwin_aarch64], |
| "//conditions:default": [sdk_dirs.linux + jar for jar in spec.jars + spec.jars_linux], |
| }), |
| add_exports = spec.add_exports, |
| add_opens = spec.add_opens, |
| ) |
| |
| _intellij_platform( |
| name = name, |
| major_version = spec.major_version, |
| minor_version = spec.minor_version, |
| exports = [":" + name + "_jars"], |
| compress = is_release(), |
| package_metadata = [ |
| ":" + name + "_prebuilt_metadata", |
| ], |
| 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 = [sdk_dirs.windows + "/**"], |
| exclude = [sdk_dirs.windows + "/plugins/textmate/lib/bundles/**"], |
| ), |
| "//tools/base/bazel/platforms:macos-x86_64": native.glob( |
| include = [sdk_dirs.darwin + "/**"], |
| exclude = [sdk_dirs.darwin + "/Contents/plugins/textmate/lib/bundles/**"], |
| ), |
| "//tools/base/bazel/platforms:macos-arm64": native.glob( |
| include = [sdk_dirs.darwin_aarch64 + "/**"], |
| exclude = [sdk_dirs.darwin_aarch64 + "/Contents/plugins/textmate/lib/bundles/**"], |
| ), |
| "//conditions:default": native.glob( |
| include = [sdk_dirs.linux + "/**"], |
| exclude = [sdk_dirs.linux + "/plugins/textmate/lib/bundles/**"], |
| ), |
| }), |
| ) |
| |
| ide_paths = { |
| "darwin": sdk_dirs.darwin, |
| "darwin_aarch64": sdk_dirs.darwin_aarch64, |
| "linux": sdk_dirs.linux, |
| "windows": sdk_dirs.windows, |
| } |
| |
| # buildifier: disable=native-py |
| native.py_test( |
| name = name + "_spec_test", |
| srcs = ["//tools/adt/idea/studio:intellij_test.py"], |
| main = "intellij_test.py", |
| tags = ["noci:studio-win"], |
| data = native.glob([ |
| src + "/**/lib/*.jar", |
| src + "/**/lib/modules/*.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 |
| # buildifier: disable=native-java-import (@rules_java is not usable in this file yet) |
| native.java_import( |
| name = name + "-resources-jar", |
| jars = select({ |
| "@platforms//os:windows": native.glob([sdk_dirs.windows + "/lib/resources.jar"]), |
| "//tools/base/bazel/platforms:macos-x86_64": native.glob([sdk_dirs.darwin + "/Contents/lib/resources.jar"]), |
| "//tools/base/bazel/platforms:macos-arm64": native.glob([sdk_dirs.darwin_aarch64 + "/Contents/lib/resources.jar"]), |
| "//conditions:default": native.glob([sdk_dirs.linux + "/lib/resources.jar"]), |
| }), |
| visibility = ["@intellij//:__subpackages__"], |
| ) |
| |
| # Expose build.txt from the prebuilt SDK |
| native.filegroup( |
| name = name + "-build-txt", |
| srcs = select({ |
| "@platforms//os:windows": [sdk_dirs.windows + "/build.txt"], |
| "//tools/base/bazel/platforms:macos-x86_64": [sdk_dirs.darwin + "/Contents/Resources/build.txt"], |
| "//tools/base/bazel/platforms:macos-arm64": [sdk_dirs.darwin_aarch64 + "/Contents/Resources/build.txt"], |
| "//conditions:default": [sdk_dirs.linux + "/build.txt"], |
| }), |
| visibility = ["@intellij//:__subpackages__"], |
| ) |
| |
| # Expose product-info.json. |
| native.filegroup( |
| name = name + "-product-info", |
| srcs = select({ |
| "@platforms//os:windows": [sdk_dirs.windows + "/product-info.json"], |
| "//tools/base/bazel/platforms:macos-x86_64": [sdk_dirs.darwin + "/Contents/Resources/product-info.json"], |
| "//tools/base/bazel/platforms:macos-arm64": [sdk_dirs.darwin_aarch64 + "/Contents/Resources/product-info.json"], |
| "//conditions:default": [sdk_dirs.linux + "/product-info.json"], |
| }), |
| visibility = ["@intellij//:__subpackages__"], |
| ) |
| |
| # Expose the default VM options file. |
| native.filegroup( |
| name = name + "-vm-options", |
| srcs = select({ |
| "@platforms//os:windows": [sdk_dirs.windows + "/bin/" + vmoptions_prefix + "64.exe.vmoptions"], |
| "//tools/base/bazel/platforms:macos-x86_64": [sdk_dirs.darwin + "/Contents/bin/" + vmoptions_prefix + ".vmoptions"], |
| "//tools/base/bazel/platforms:macos-arm64": [sdk_dirs.darwin_aarch64 + "/Contents/bin/" + vmoptions_prefix + ".vmoptions"], |
| "//conditions:default": [sdk_dirs.linux + "/bin/" + vmoptions_prefix + "64.vmoptions"], |
| }), |
| visibility = ["@intellij//:__subpackages__"], |
| ) |
| |
| # This gives the path to the intellij_platform rule from root. e.g., "prebuilts/studio/intellij-sdk" |
| prebuilts_dir = native.package_name() |
| dir_archive( |
| name = name + "-full-linux", |
| dir = prebuilts_dir + "/" + src + "/linux", |
| files = native.glob([sdk_dirs.linux + "/**"]), |
| visibility = ["//tools/vendor/google/aswb/third_party/java/jetbrains/protobuf:__pkg__"], |
| ) |
| |
| 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 = { |
| "%s/%s/" % (prebuilts_dir, sdk_dirs.linux): "", |
| "%s/%s/" % (prebuilts_dir, sdk_dirs.darwin): "", |
| "%s/%s/" % (prebuilts_dir, sdk_dirs.darwin_aarch64): "", |
| "%s/%s/" % (prebuilts_dir, sdk_dirs.windows): "", |
| }, |
| ) |
| |
| for plugin, jars in spec.plugin_jars.items(): |
| # 'kind' indicates whether this is a top-level plugin, or a plugin module inside a host plugin. |
| kind = "module" if len(jars) == 1 and "/modules/" in jars[0] else "plugin" |
| jars_target_name = "%s-plugin-%s_jars" % (name, plugin) |
| _gen_plugin_jars_import_target(jars_target_name, spec, sdk_dirs, plugin, jars) |
| _intellij_plugin_import( |
| name = name + "-plugin-%s" % plugin, |
| id = plugin, |
| kind = kind, |
| exports = [":" + jars_target_name], |
| visibility = ["@intellij//:__subpackages__"], |
| ) |
| |
| jvm_import( |
| name = name + "-updater", |
| jars = native.glob([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": [sdk_dirs.windows + "/lib/testFramework.jar"], |
| "//tools/base/bazel/platforms:macos-x86_64": [sdk_dirs.darwin + "/Contents/lib/testFramework.jar"], |
| "//tools/base/bazel/platforms:macos-arm64": [sdk_dirs.darwin_aarch64 + "/Contents/lib/testFramework.jar"], |
| "//conditions:default": [sdk_dirs.linux + "/lib/testFramework.jar"], |
| }), |
| visibility = ["@intellij//:__subpackages__"], |
| ) |
| |
| def _gen_plugin_jars_import_target(name, spec, sdk_dirs, 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 = [sdk_dirs.windows + "/" + jar for jar in jars + add_windows] |
| add_darwin = spec.plugin_jars_darwin[plugin] if plugin in spec.plugin_jars_darwin else [] |
| jars_darwin = [sdk_dirs.darwin + "/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 = [sdk_dirs.darwin_aarch64 + "/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 = [sdk_dirs.linux + "/" + 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, |
| }), |
| ) |