"""Experimental Skylark rules for Apple's Metal. This creates a .metallib file containing compiled Metal shaders. Note that the default behavior in Xcode is to put all metal shaders into a single "default.metallib", which can be loaded using the method newDefaultLibrary in MTLDevice. Meanwhile, the metal_library rule creates a named .metallib, which can be loaded using newLibraryWithFile:error:. Example: metal_library( name = "my_shaders", srcs = ["my_shaders.metal"], hdrs = ["my_shaders.h"], ) This produces a "my_shaders.metallib". The metal_library target can be added to the deps attribute of an objc_library. The dependent objc_library can then access the headers declared by the metal_library, if any. The metal_library target can also be added to the resources attribute as a simple data file, but in that case any declared headers are not visible to dependent objc_library rules. """ load("@build_bazel_apple_support//lib:apple_support.bzl", "apple_support") load("@bazel_skylib//lib:dicts.bzl", "dicts") # This load statement is overriding the visibility of the internal implementation of rules_apple. # This rule will be migrated to rules_apple in the future, hence the override. Please do not use # this import anywhere else. load( "@build_bazel_rules_apple//apple/internal:resources.bzl", "resources", ) def _metal_compiler_args(ctx, src, obj, minimum_os_version, copts, diagnostics, deps_dump): """Returns arguments for metal compiler.""" apple_fragment = ctx.fragments.apple platform = apple_fragment.single_arch_platform if not minimum_os_version: minimum_os_version = ctx.attr._xcode_config[apple_common.XcodeVersionConfig].minimum_os_for_platform_type( platform.platform_type, ) args = copts + [ "-arch", "air64", # TODO: choose based on target device/cpu/platform? "-emit-llvm", "-c", "-gline-tables-only", "-isysroot", apple_support.path_placeholders.sdkroot(), "-ffast-math", "-serialize-diagnostics", diagnostics.path, "-o", obj.path, "-mios-version-min=%s" % minimum_os_version, "", src.path, "-MMD", "-MT", "dependencies", "-MF", deps_dump.path, ] return args def _metal_compiler_inputs(srcs, hdrs, deps = []): """Determines the list of inputs required for a compile action.""" cc_infos = [dep[CcInfo] for dep in deps if CcInfo in dep] dep_headers = depset(transitive = [ cc_info.compilation_context.headers for cc_info in cc_infos ]) return depset(srcs + hdrs, transitive = [dep_headers]) def _metal_library_impl(ctx): """Implementation for metal_library Skylark rule.""" # A unique path for rule's outputs. objs_outputs_path = "{}.objs/".format(ctx.label.name) output_objs = [] for src in ctx.files.srcs: basename = src.basename obj = ctx.actions.declare_file(objs_outputs_path + basename + ".air") output_objs.append(obj) diagnostics = ctx.actions.declare_file(objs_outputs_path + basename + ".dia") deps_dump = ctx.actions.declare_file(objs_outputs_path + basename + ".dat") args = (["metal"] + _metal_compiler_args(ctx, src, obj, ctx.attr.minimum_os_version, ctx.attr.copts, diagnostics, deps_dump)) apple_support.run( ctx, xcode_path_resolve_level = apple_support.xcode_path_resolve_level.args, inputs = _metal_compiler_inputs(ctx.files.srcs, ctx.files.hdrs, ctx.attr.deps), outputs = [obj, diagnostics, deps_dump], mnemonic = "MetalCompile", executable = "/usr/bin/xcrun", arguments = args, use_default_shell_env = False, progress_message = ("Compiling Metal shader %s" % (basename)), ) output_lib = ctx.actions.declare_file(ctx.label.name + ".metallib") args = [ "metallib", "-split-module", "-o", output_lib.path, ] + [x.path for x in output_objs] apple_support.run( ctx, xcode_path_resolve_level = apple_support.xcode_path_resolve_level.args, inputs = output_objs, outputs = (output_lib,), mnemonic = "MetalLink", executable = "/usr/bin/xcrun", arguments = args, progress_message = ( "Linking Metal library %s" % ctx.label.name ), ) objc_provider = apple_common.new_objc_provider( providers = [x[apple_common.Objc] for x in ctx.attr.deps if apple_common.Objc in x], ) cc_infos = [dep[CcInfo] for dep in ctx.attr.deps if CcInfo in dep] if ctx.files.hdrs: cc_infos.append( CcInfo( compilation_context = cc_common.create_compilation_context( headers = depset([f for f in ctx.files.hdrs]), ), ), ) return [ DefaultInfo( files = depset([output_lib]), ), objc_provider, cc_common.merge_cc_infos(cc_infos = cc_infos), # Return the provider for the new bundling logic of rules_apple. resources.bucketize_typed([output_lib], "unprocessed"), ] METAL_LIBRARY_ATTRS = dicts.add(apple_support.action_required_attrs(), { "srcs": attr.label_list(allow_files = [".metal"], allow_empty = False), "hdrs": attr.label_list(allow_files = [".h"]), "deps": attr.label_list(providers = [["objc", CcInfo], [apple_common.Objc, CcInfo]]), "copts": attr.string_list(), "minimum_os_version": attr.string(), }) metal_library = rule( implementation = _metal_library_impl, attrs = METAL_LIBRARY_ATTRS, fragments = ["apple", "objc", "swift"], output_to_genfiles = True, ) """ Builds a Metal library. Args: srcs: Metal shader sources. hdrs: Header files used by the shader sources. deps: objc_library targets whose headers should be visible to the shaders. The header files declared in this rule are also visible to any objc_library rules that have it as a dependency, so that constants and typedefs can be shared between Metal and Objective-C code. """