{"id":17317006,"url":"https://github.com/pcj/bazel_aspects","last_synced_at":"2025-08-28T12:18:45.053Z","repository":{"id":47377285,"uuid":"70956578","full_name":"pcj/bazel_aspects","owner":"pcj","description":"Example of how to use bazel aspects.","archived":false,"fork":false,"pushed_at":"2024-05-18T00:47:41.000Z","size":10,"stargazers_count":40,"open_issues_count":2,"forks_count":4,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-02-01T07:41:23.868Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Starlark","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pcj.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-10-15T00:34:35.000Z","updated_at":"2024-11-10T17:42:03.000Z","dependencies_parsed_at":"2022-09-12T14:22:00.544Z","dependency_job_id":"a21760b7-7fb0-4e4e-ae5a-c7a13e280f29","html_url":"https://github.com/pcj/bazel_aspects","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pcj%2Fbazel_aspects","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pcj%2Fbazel_aspects/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pcj%2Fbazel_aspects/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pcj%2Fbazel_aspects/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pcj","download_url":"https://codeload.github.com/pcj/bazel_aspects/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245767361,"owners_count":20668827,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-15T13:15:10.460Z","updated_at":"2025-03-27T02:18:04.394Z","avatar_url":"https://github.com/pcj.png","language":"Starlark","funding_links":[],"categories":[],"sub_categories":[],"readme":"Bazel Aspects\n================\n\n\u003e Some people, when confronted with a problem, think \"I know, I'll use\n\u003e bazel aspects.\"  Now they have a set of problems (defined by the\n\u003e transitive closure reachable from P over K).\n\nBazel aspects are a seemingly obscure and poorly understood feature\nfor many people (including me!).  When would you use one?  What are\nthey?  How to they work?  How do you implement one?  I wrote this up\nto improve my understanding of aspects; hopefully it will help others\nbetter understand this powerful feature.  If you do learn something\nhere, please star the repo.\n\n## What is it?\n\nA kind of\n[visitor pattern](https://en.wikipedia.org/wiki/Visitor_pattern) over\nthe bazel dependency tree.\n\n## Why would you want one?\n\nAn aspect may be useful to generate some type of artifact parallel to\nthe kind normally produced by a rule.  The primary use case has\ninitially been IDE support wherein metadata files are generated that\nan IDE requires (see\nhttps://github.com/bazelbuild/e4b/tree/ae17bdebcb1733ff1cb9172043652668fd85725c/com.google.devtools.bazel.e4b/resources/tools/must/be/unique).\n\n## How do they work?\n\nWhen a rule declares an attribute that uses an aspect such as\n`attr.label(aspects = ['foo_aspect']`, bazel looks at the definition\nof the aspect to see what attributes it propogates down.  For example,\nit might say `attr_aspects = ['deps]`.\n\nWhen that rule is invoked, bazel will:\n\n1. Traverse down the dependency graph from the originating rule in\n   depth-first fashion, following edges named 'deps'.\n\n1. Apply the aspect rule to each matched rule (in this example\n   `java_library` and `java_binary`).\n\nAs an aspect implementor, your job is to:\n\n1. Get oriented to the kind of rule you are visiting via\n`aspect_ctx.rule.kind` property.\n\n2. Do something (`ctx.file_action`, `ctx.action`, etc..).\n\n3. Collect a transitive set of generated output files and pass them\n   off somewhere to be consumed (either from the command line or a\n   another rule).\n\n## How are they invoked?\n\n1. From the command line with the `--aspects` flag (see Makefile),\n   probably in conjunction with `--output_groups`.\n\n2. From a rule attribute that declares an aspect (see `java_info`\n   rule).\n\n## How do you implement one?\n\nWriting an aspect rule is similar to writing a normal rule.  There are\nsome differences in the types of attributes allowed (labels must be\nprivate for example), but the biggest hurdle is understanding the\nfunction signature for the aspect implementation, which looks like:\n\n```python\ndef _info_aspect_impl(target, aspect_ctx):\n  ...\n```\n\nLet's look at these in greater detail.  To do that, we'll write a\nfunction to print out the properties of the object using `dir`.  We\nneed special logic to exclude function names:\n\n```python\ndef _describe(name, obj, exclude):\n    \"\"\"Print the properties of the given struct obj\n    Args:\n      name: the name of the struct we are introspecting.\n      obj: the struct to introspect\n      exclude: a list of names *not* to print (function names)\n    \"\"\"\n    for k in dir(obj):\n        if hasattr(obj, k) and k not in exclude:\n            v = getattr(obj, k)\n            t = type(v)\n            print(\"%s.%s\u003c%r\u003e = %s\" % (name, k, t, v))\n```\n\n### The `target` argument\n\nLet's look at the first argument, `target`.\n\n```python\ntype(target)\nRuleConfiguredTarget\n```\n\n```python\ndir(target)\n[\"data_runfiles\", \"default_runfiles\", \"files\", \"files_to_run\", \"java\", \"label\", \"output_group\"]\n```\n\n```python\n_describe(\"target\", target, exclude = [\"output_group\"])\ntarget.data_runfiles\u003c\"runfiles\"\u003e = com.google.devtools.build.lib.analysis.Runfiles@2c9a0ae4.\ntarget.default_runfiles\u003c\"runfiles\"\u003e = com.google.devtools.build.lib.analysis.Runfiles@2c9a0ae4.\ntarget.files\u003c\"set\"\u003e = set([.../java/foo/libfoo.jar]).\ntarget.files_to_run\u003c\"FilesToRunProvider\"\u003e = com.google.devtools.build.lib.analysis.FilesToRunProvider@7d624a49.\ntarget.java\u003c\"JavaSkylarkApiProvider\"\u003e = com.google.devtools.build.lib.rules.java.JavaSkylarkApiProvider@5eda3c20.\ntarget.label\u003c\"Label\"\u003e = //java/foo:foo.\n```\n\n`output_group` is actually a function (which we can't introspect with\nour describe function, so we exclude it).  This function takes a\nsingle string argument and returns a set,\n(`target.output_group(string: name) --\u003e set()`), probably for\naccessing output groups from the target if they exist.  Not exactly\nsure what this accomplishes.\n\n### The `aspect_ctx` argument\n\nNow let's look at the second argument, `aspect_ctx`.\n\n```python\ntype(aspect_ctx)\nctx\n```\n\n```python\ndir(aspect_ctx)\n[\"action\", \"attr\", \"build_file_path\", \"check_placeholders\", \"configuration\", \"empty_action\",\n\"executable\", \"expand\", \"expand_location\", \"expand_make_variables\", \"features\", \"file\",\n\"file_action\", \"files\", \"fragments\", \"host_configuration\", \"host_fragments\", \"info_file\",\n\"label\", \"middle_man\", \"new_file\", \"outputs\", \"resolve_command\", \"rule\", \"runfiles\",\n\"template_action\", \"tokenize\", \"var\", \"version_file\", \"workspace_name\"]\n```\n\n```python\nfunction_names = [\n    \"action\",\n    \"empty_action\",\n    \"expand\",\n    \"expand_location\",\n    \"expand_make_variables\",\n    \"middle_man\",\n    \"file_action\",\n    \"resolve_command\",\n    \"runfiles\",\n    \"template_action\",\n    \"tokenize\",\n    \"new_file\",\n    \"outputs\",\n    \"check_placeholders\",\n]\n_describe(\"aspect_ctx\", aspect_ctx, exclude = function_names)\nVisiting //java:app.\naspect_ctx.attr\u003c\"struct\"\u003e = struct(characteristic = \"annotation_processing\").\naspect_ctx.build_file_path\u003c\"string\"\u003e = java/BUILD.\naspect_ctx.configuration\u003c\"configuration\"\u003e = 2ee5f82d2d3d3e70e95ce1225caf8843.\naspect_ctx.executable\u003c\"struct\"\u003e = struct().\naspect_ctx.features\u003c\"list\"\u003e = [].\naspect_ctx.file\u003c\"struct\"\u003e = struct().\naspect_ctx.files\u003c\"struct\"\u003e = struct().\naspect_ctx.fragments\u003c\"fragments\"\u003e = target: [ 'apple', 'cpp', 'java', 'jvm', 'objc'].\naspect_ctx.host_configuration\u003c\"configuration\"\u003e = 81922d9f706df1c33dcfdcc51fce58b3.\naspect_ctx.host_fragments\u003c\"fragments\"\u003e = host: [ 'apple', 'cpp', 'java', 'jvm', 'objc'].\naspect_ctx.info_file\u003c\"File\"\u003e = Artifact:[[.../stable-status.txt.\naspect_ctx.label\u003c\"Label\"\u003e = //java:app.\naspect_ctx.rule\u003c\"rule_attributes\"\u003e = com.google.devtools.build.lib.rules.SkylarkRuleContext$SkylarkRuleAttributesCollection@c53c138.\naspect_ctx.var\u003c\"dict\"\u003e = {\"ABI\": \"local\", \"ABI_GLIBC_VERSION\": \"local\", \"ANDROID_CPU\": \"armeabi\", \"AR\": \"/usr/bin/libtool\", \"BINDIR\": \"bazel-out/local-fastbuild/bin\", \"CC\": \"external/local_config_cc/cc_wrapper.sh\", \"CC_FLAGS\": \"\", \"COMPILATION_MODE\": \"fastbuild\", \"CROSSTOOLTOP\": \"external/local_config_cc\", \"C_COMPILER\": \"compiler\", \"GENDIR\": \"bazel-out/local-fastbuild/genfiles\", \"GLIBC_VERSION\": \"macosx\", \"JAVA\": \"external/local_jdk/bin/java\", \"JAVABASE\": \"external/local_jdk\", \"JAVA_TRANSLATIONS\": \"0\", \"NM\": \"/usr/bin/nm\", \"OBJCOPY\": \"/usr/bin/objcopy\", \"STACK_FRAME_UNLIMITED\": \"\", \"STRIP\": \"/usr/bin/strip\", \"TARGET_CPU\": \"darwin\"}.\naspect_ctx.version_file\u003c\"SpecialArtifact\"\u003e = Artifact:[[.../volatile-status.txt.\naspect_ctx.workspace_name\u003c\"string\"\u003e = com_github_pcj_bazel_aspect_example.\n```\n\nThe functions are mostly familiar with the exception of `middle_man`, `tokenize`, and `check_placeholders`.\nThese don't appear to be for general use.\n\n* `ctx.middle_man(label) -\u003e set(artifact)`: No idea what this is for.  For example `aspect_ctx.middle_man(\":host_jdk\")` returns `set([Artifact:[[/...]bazel-out/host/internal]_middlemen/external_Slocal_Ujdk_Cjdk-default])`\n\n* `ctx.tokenize(string) -\u003e list\u003cstring\u003e`: utility function that takes a\n  string and returns a string list, split on ?spaces.\n\n* `ctx.check_placeholders(string, list\u003cstring\u003e) -\u003e bool`: utility function\n  that takes an input string with replacement strings like `%{name}`\n  and a list of placeholder names `['name']`, and returns `True` if\n  all the placeholder names are found in the input string.\n\n## What can an aspect see?\n\nAn aspect implementation can only inspect the information provided by\nthe `ctx.rule` object: its attributes, dependencies (and their\nproviders), etc.\n\nHowever, a *parameterized aspect* can get information about the\noriginating rule, and do different control flow based on that value.\nSee the examples for the difference.\n\n# GOTCHAS\n\n1. It's critical to propogate the transitive outputs generated by an\n   aspect back up the shadow graph.  If you don't do this, you'll can\n   spend a fair amount of time scratching your head about why a\n   file_action in an aspect is not being actually produced (ask me how\n   I know).  Recall that bazel is very lazy so if you don't keep that\n   transitive chain going, bazel will prune it away.\n\n2. To be callable from the command line, it appears necessary to\n   implement 'output_groups' in your aspect.  For example\n   `--output_groups=jsons` or `--output_groups=+jsons`, it will generate\n   outputs specified in that output group.  You can supress outputs\n   that would otherwise be generated by the rule (for java, this is a\n   jar file) via `--output_groups=+jsons,-default`.\n\n# More Information\n\n* [Documentation about aspects](https://www.bazel.io/versions/master/docs/skylark/aspects.html).\n\n* [Parameterized aspect design document](https://www.bazel.io/designs/skylark/parameterized-aspects.html).\n\n* [e4b](https://github.com/bazelbuild/e4b): eclipse for bazel uses an aspect implementation.\n\n* [Tulsi](https://github.com/bazelbuild/tulsi/blob/a7ff813b1a0c5368fd38552cb1afa1354c297c42/src/TulsiGenerator/Bazel/tulsi/tulsi_aspects.bzl). As does Tulsi.\n\n* [intellij](https://github.com/bazelbuild/intellij): uses an aspect,\nbut this one implemented in Java, not Skylark.\n\n* [AspectDescriptor.java](https://github.com/bazelbuild/bazel/blob/c484f19a2cf7427887d6e4c71c8534806e1ba83e/src/main/java/com/google/devtools/build/lib/analysis/AspectDescriptor.java).\n\n* [Aspect.java](https://github.com/bazelbuild/bazel/blob/c484f19a2cf7427887d6e4c71c8534806e1ba83e/src/main/java/com/google/devtools/build/lib/packages/Aspect.java).\n\n* [AspectFunction.java](https://github.com/bazelbuild/bazel/blob/bb5901ba0474eb2ddd035502663026bcb0c05b7c/src/main/java/com/google/devtools/build/lib/skyframe/AspectFunction.java).\n\n* [SkylarkAspect.java](https://github.com/bazelbuild/bazel/blob/25b952b8fec4a3e514b4f91fbbd5e5133fcab4b7/src/main/java/com/google/devtools/build/lib/packages/SkylarkAspect.java).\n\n* [AspectClass.java](https://github.com/bazelbuild/bazel/blob/c484f19a2cf7427887d6e4c71c8534806e1ba83e/src/main/java/com/google/devtools/build/lib/packages/AspectClass.java).  Probably the best explanation of aspects right here in the javadoc comment.\n\n* [Undocumented flags?](https://github.com/bazelbuild/bazel/blob/c484f19a2cf7427887d6e4c71c8534806e1ba83e/scripts/release/relnotes_test.sh).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpcj%2Fbazel_aspects","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpcj%2Fbazel_aspects","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpcj%2Fbazel_aspects/lists"}