Created
September 12, 2024 19:01
-
-
Save alexeagle/ad8ceccf382300dd2e247095c38d6e68 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
rules_kotlin support for 'aspect configure' | |
""" | |
KT_JVM_LIBRARY = "kt_jvm_library" | |
KT_JVM_BINARY = "kt_jvm_binary" | |
RULES_KOTLIN_REPO_NAME = "io_bazel_rules_kotlin" | |
PROVIDER_NAME = "kt" | |
LANG_NAME = "kotlin" | |
aspect.register_rule_kind(KT_JVM_LIBRARY, { | |
"From": "@" + RULES_KOTLIN_REPO_NAME + "//kotlin:jvm.bzl", | |
"NonEmptyAttrs": ["srcs"], | |
"MergeableAttrs": ["srcs"], | |
"ResolveAttrs": ["deps"], | |
}) | |
aspect.register_rule_kind(KT_JVM_BINARY, { | |
"From": "@" + RULES_KOTLIN_REPO_NAME + "//kotlin:jvm.bzl", | |
"NonEmptyAttrs": ["srcs", "main_class"], | |
}) | |
def prepare(_): | |
return aspect.PrepareResult( | |
# All source files to be processed | |
sources = [ | |
aspect.SourceExtensions(".kt", ".kts"), | |
], | |
queries = { | |
"imports": aspect.AstQuery( | |
grammar = "kotlin", | |
filter = "*.kt*", | |
query = """ | |
(source_file | |
(import_list | |
(import_header (identifier) @imp (".*")? @is_star) | |
) | |
) | |
""", | |
), | |
"package_name": aspect.AstQuery( | |
grammar = "kotlin", | |
filter = "*.kt*", | |
query = """ | |
(source_file | |
(package_header (identifier) @pkg) | |
) | |
""", | |
), | |
"has_main": aspect.AstQuery( | |
grammar = "kotlin", | |
filter = "*.kt*", | |
query = """ | |
(source_file | |
(function_declaration | |
(simple_identifier) @variable.funcname | |
(#eq? @variable.funcname "main") | |
) | |
) | |
""", | |
), | |
}, | |
) | |
# ctx: | |
# rel string | |
# properties map[string]string | |
# sources []TargetSource | |
# | |
# TargetSource: | |
# path string | |
# query_results QueryResults | |
# | |
# query_results: | |
# [query_key] bool|string|None | |
def declare_targets(ctx): | |
""" | |
This function declares targets based on the context. | |
Args: | |
ctx: The context object. | |
Returns: | |
a 'DeclareTargetsResult' | |
""" | |
# Every BUILD can have 1 library and multiple binaries | |
lib = { | |
"srcs": [], | |
"packages": [], | |
"imports": [], | |
} | |
bins = [] | |
for file in ctx.sources: | |
pkg = file.query_results["package_name"][0].captures["pkg"] if "package_name" in file.query_results and len(file.query_results["package_name"]) > 0 else None | |
if "imports" not in file.query_results: | |
print(file.query_results) | |
import_paths = [ | |
i.captures["imp"] if "is_star" in i.captures and i.captures["is_star"] else i.captures["imp"][:i.captures["imp"].rindex(".")] | |
for i in file.query_results["imports"] | |
if not is_native(i.captures["imp"]) | |
] | |
# Trim the class name from the import for non-.* imports. | |
# Convert to TargetImport, exclude native imports | |
imports = [ | |
aspect.Import( | |
id = i, | |
provider = PROVIDER_NAME, | |
src = file.path, | |
) | |
for i in import_paths | |
] | |
if len(file.query_results["has_main"]) > 0: | |
bins.append({ | |
"src": file.path, | |
"imports": imports, | |
"package": aspect.Symbol( | |
id = pkg, | |
provider = PROVIDER_NAME, | |
) if pkg else None, | |
}) | |
else: | |
lib["srcs"].append(file.path) | |
lib["imports"].extend(imports) | |
if pkg: | |
lib["packages"].append(aspect.Symbol( | |
id = pkg, | |
provider = PROVIDER_NAME, | |
)) | |
lib_name = path.base(ctx.rel) if ctx.rel else ctx.repo_name | |
if len(lib["srcs"]) > 0: | |
ctx.targets.add( | |
name = lib_name, | |
kind = KT_JVM_LIBRARY, | |
attrs = { | |
"srcs": lib["srcs"], | |
"deps": lib["imports"] if len(lib["imports"]) > 0 else None, | |
}, | |
symbols = lib["packages"], | |
) | |
else: | |
ctx.targets.remove(lib_name) | |
for bin in bins: | |
no_ext = bin["src"].removesuffix(path.ext(bin["src"])) | |
ctx.targets.add( | |
name = no_ext.lower() + "_bin", | |
kind = KT_JVM_BINARY, | |
attrs = { | |
"srcs": [bin["src"]], | |
"main_class": (bin["package"].id + "." + no_ext) if bin["package"] else no_ext, | |
"deps": bin["imports"] if len(bin["imports"]) > 0 else None, | |
}, | |
symbols = [bin["package"]] if bin["package"] else [], | |
) | |
NATIVE_LIBS = [ | |
"kotlin", | |
"kotlinx", | |
# Java, see rules_jvm gazelle plugin: | |
# https://github.com/bazel-contrib/rules_jvm/blob/v0.24.0/java/gazelle/private/java/java.go#L28-L147 | |
"java", | |
"javax", | |
"com.sun", | |
"jdk", | |
"netscape.javascript", | |
"org.ietf.jgss", | |
"org.jcp.xml.dsig.internal", | |
"org.w3c.dom", | |
"org.xml.sax", | |
"sun", | |
] | |
def is_native(imp): | |
for lib in NATIVE_LIBS: | |
if imp == lib or imp.startswith(lib + "."): | |
return True | |
return False | |
aspect.register_configure_extension( | |
id = LANG_NAME, | |
properties = {}, | |
prepare = prepare, | |
declare = declare_targets, | |
) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"Maven starlark plugin" | |
# Directive name and default value from the rules_jvm gazelle plugin | |
JAVA_MAVEN_INSTALL_FILE = "java_maven_install_file" | |
DEFAULT_JAVA_MAVEN_INSTALL_FILE = "maven_install.json" | |
def prepare(ctx): | |
return aspect.PrepareResult( | |
# All source files to be processed | |
sources = [ | |
aspect.SourceExtensions(ctx.properties[JAVA_MAVEN_INSTALL_FILE]), | |
], | |
queries = { | |
"imports": aspect.JsonQuery( | |
filter = DEFAULT_JAVA_MAVEN_INSTALL_FILE, | |
query = """.dependency_tree.dependencies[] | select(.packages) | {packages,coord}""", | |
), | |
}, | |
) | |
def analyze_source(ctx): | |
for dep in ctx.source.query_results["imports"]: | |
coord = dep["coord"].rsplit(":", 1)[0].replace(".", "_").replace(":", "_") | |
for pkg in dep["packages"]: | |
ctx.add_symbol( | |
id = pkg, | |
provider_type = "java_info", | |
label = aspect.Label( | |
repo = "maven", | |
name = coord, | |
), | |
) | |
aspect.register_configure_extension( | |
id = "maven", | |
properties = { | |
JAVA_MAVEN_INSTALL_FILE: aspect.Property( | |
type = "String", | |
default = DEFAULT_JAVA_MAVEN_INSTALL_FILE, | |
), | |
}, | |
prepare = prepare, | |
analyze = analyze_source, | |
) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
BZL_LIBRARY = "bzl_library" | |
LANG_NAME = "starlark" | |
BZL_EXT = ".bzl" | |
aspect.register_rule_kind(BZL_LIBRARY, { | |
"From": "@bazel_skylib//:bzl_library.bzl", | |
"NonEmptyAttrs": ["srcs"], | |
"MergeableAttrs": ["srcs"], | |
"ResolveAttrs": ["deps"], | |
}) | |
def prepare(_): | |
return aspect.PrepareResult( | |
sources = [ | |
aspect.SourceExtensions(".bzl"), | |
], | |
queries = { | |
"loads": aspect.AstQuery( | |
query = """(module | |
(expression_statement | |
(call | |
function: (identifier) @id | |
arguments: (argument_list | |
(string) @path | |
(string) | |
) | |
) | |
(#eq? @id "load") | |
) | |
)""", | |
), | |
}, | |
) | |
def declare_targets(ctx): | |
# TODO | |
# Loop through the existing bzl_library targets in this package and | |
# delete any that are no longer needed. | |
for file in ctx.sources: | |
label = file.path.removesuffix(".bzl").replace("/", "_") | |
file_pkg = path.dirname(file.path) | |
loads = [ld.captures["path"].strip("\"") for ld in file.query_results["loads"]] | |
loads = [ld.removeprefix("//").replace(":", "/") if ld.startswith("//") else path.join(file_pkg, ld.removeprefix(":")) for ld in loads] | |
loads = [ld.strip("/") for ld in loads] | |
ctx.targets.add( | |
kind = BZL_LIBRARY, | |
name = label, | |
attrs = { | |
"srcs": [file.path], | |
"visibility": [checkInternalVisibility(ctx.rel, "//visibility:public")], | |
"deps": [ | |
aspect.Import( | |
id = ld, | |
src = file.path, | |
provider = LANG_NAME, | |
) | |
for ld in loads | |
] if len(loads) > 0 else None, | |
}, | |
# TODO | |
# load("@bazel_tools//tools/build_defs/repo:http.bzl") | |
# Note that the Go extension has a special case for it: | |
# if impLabel.Repo == "bazel_tools" { | |
# // The @bazel_tools repo is tricky because it is a part of the "shipped | |
# // with bazel" core library for interacting with the outside world. | |
symbols = [ | |
aspect.Symbol( | |
id = path.join(ctx.rel, file.path), | |
provider = LANG_NAME, | |
), | |
], | |
) | |
return {} | |
aspect.register_configure_extension( | |
id = LANG_NAME, | |
properties = {}, | |
prepare = prepare, | |
declare = declare_targets, | |
) | |
# See https://github.com/bazelbuild/bazel-skylib/blob/1.7.1/gazelle/bzl/gazelle.go#L340 | |
def checkInternalVisibility(rel, visibility): | |
i = rel.find("internal") | |
if i > 0: | |
return "//%s:__subpackages__" % rel[:i - 1] | |
elif i == 0: | |
return "//:__subpackages__" | |
i = rel.find("private") | |
if i > 0: | |
return "//%s:__subpackages__" % rel[:i - 1] | |
elif i == 0: | |
return "//:__subpackages__" | |
return visibility |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment