"""Split a JVM binary's runtime classpath into first-party and third-party jar sets.

This exists so the Docker image can place rarely-changing third-party jars in a
lower OCI layer and frequently-changing first-party jars in a small top layer,
so most pushes only re-upload the small layer.
"""

load("@rules_java//java/common:java_info.bzl", "JavaInfo")

def _unique_name(jar):
    # short_path is unique per jar and stable across commits (it depends only on
    # the jar's own package/coordinate, not on unrelated targets), so the deps
    # layer's tar entries stay byte-identical and its blob digest stays cached.
    path = jar.short_path
    if path.startswith("../"):
        path = path[3:]
    return path.replace("+", "_").replace("/", "_").replace("~", "_")

def _impl(ctx):
    info = ctx.attr.binary[JavaInfo]

    app = []
    deps = []
    seen = {}
    for jar in sorted(info.transitive_runtime_jars.to_list(), key = lambda f: f.path):
        workspace = jar.owner.workspace_name if jar.owner else ""
        bucket = "app" if workspace == "" else "deps"
        out_name = _unique_name(jar)

        key = bucket + "/" + out_name
        if key in seen:
            fail("jar_split: duplicate output name %r for %s and %s" % (
                key,
                seen[key],
                jar.path,
            ))
        seen[key] = jar.path

        link = ctx.actions.declare_file(ctx.label.name + "/" + key)
        ctx.actions.symlink(output = link, target_file = jar)
        (app if bucket == "app" else deps).append(link)

    return [
        DefaultInfo(files = depset(app + deps)),
        OutputGroupInfo(app = depset(app), deps = depset(deps)),
    ]

jar_split = rule(
    implementation = _impl,
    doc = "Partitions a JVM binary's transitive runtime jars into 'app' " +
          "(first-party, empty workspace) and 'deps' (third-party) output groups.",
    attrs = {
        "binary": attr.label(
            mandatory = True,
            providers = [[JavaInfo]],
            doc = "A jvm binary/library target whose runtime classpath to split.",
        ),
    },
)
