Skip to content

Instantly share code, notes, and snippets.

@daltonclaybrook
Last active May 27, 2025 21:02
Show Gist options
  • Save daltonclaybrook/cf59431cd84ff602996ccd7a693440bd to your computer and use it in GitHub Desktop.
Save daltonclaybrook/cf59431cd84ff602996ccd7a693440bd to your computer and use it in GitHub Desktop.
import Foundation
#if canImport(DeveloperToolsSupport)
import DeveloperToolsSupport
#endif
#if SWIFT_PACKAGE
private let resourceBundle = Foundation.Bundle.module
#else
private class ResourceBundleClass {}
private let resourceBundle = Foundation.Bundle(for: ResourceBundleClass.self)
#endif
// MARK: - Color Symbols -
@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *)
extension DeveloperToolsSupport.ColorResource {
{COLOR_CONSTANTS}
}
// MARK: - Image Symbols -
@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *)
extension DeveloperToolsSupport.ImageResource {
{IMAGE_CONSTANTS}
}
load("@build_bazel_rules_swift//swift:swift_library.bzl", "swift_library")
load("//bazel:xcassets.bzl", "xcassets_accessors")
swift_library(
name = "MyLibrary",
srcs = glob([
"**/*.swift",
]) + [
":asset_accessors",
],
visibility = ["//visibility:public"],
)
xcassets_accessors(
name = "asset_accessors",
srcs = glob([
"Resources/*.xcassets/**",
]),
out = "GeneratedAssets.swift",
)
"""
Defines the `xcassets_accessors` rule for generating accessors for colors and images in an asset catalog
"""
def _xcassets_accessor_impl(ctx):
colors = []
images = []
output = ctx.outputs.out
for src in ctx.files.srcs:
(_, _, subpath) = src.path.partition(".xcassets/")
if subpath == '':
fail("All srcs must be under an '.xcassets' folder. Invalid file:", src.path)
components = subpath.split("/")
if len(components) < 2 or components[-1] != "Contents.json":
continue # File is not a 'Contents.json' for an asset
asset = components[-2]
if asset.endswith(".colorset"):
colors.append(asset.removesuffix(".colorset"))
elif components[-2].endswith(".imageset"):
images.append(asset.removesuffix(".imageset"))
else:
continue # File is not a color or image asset
color_contents = _generate_color_contents(colors)
image_contents = _generate_image_contents(images)
ctx.actions.expand_template(
template = ctx.file._template,
output = output,
substitutions = {
"{COLOR_CONSTANTS}": color_contents,
"{IMAGE_CONSTANTS}": image_contents,
}
)
return [
DefaultInfo(files = depset([output])),
]
def _generate_color_contents(colors):
if len(colors) == 0:
return " // No colors to generate"
strings = []
for color in colors:
field = _convert_to_camel_case(color)
string = " static let {} = DeveloperToolsSupport.ColorResource(name: \"{}\", bundle: resourceBundle)".format(field, color)
strings.append(string)
return "\n".join(strings)
def _generate_image_contents(images):
if len(images) == 0:
return " // No images to generate"
strings = []
for image in images:
field = _convert_to_camel_case(image)
string = " static let {} = DeveloperToolsSupport.ImageResource(name: \"{}\", bundle: resourceBundle)".format(field, image)
strings.append(string)
return "\n".join(strings)
def _convert_to_camel_case(s):
parts = s.replace("-", " ").replace("_", " ").split(" ")
if not parts:
return s
return _lowercase_first(parts[0]) + "".join([_capitalize_first(word) for word in parts[1:]])
def _lowercase_first(s):
return s[0].lower() + s[1:] if s else s
def _capitalize_first(s):
return s[0].capitalize() + s[1:] if s else s
xcassets_accessors = rule(
doc = "Generate a Swift file containing accessors for colors and images in the provided asset catalog",
attrs = {
"srcs": attr.label_list(
doc = "The list of files under the '.xcassets' folder",
allow_files = True,
allow_empty = False,
mandatory = True,
),
"out": attr.output(
doc = "The name of the Swift output file to create",
mandatory = True,
),
"_template": attr.label(
default = "//bazel:templates/AssetAccessors.swift.tmpl",
allow_single_file = True,
),
},
implementation = _xcassets_accessor_impl,
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment