<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss xmlns:source="http://source.scripting.com/" version="2.0">
  <channel>
    <title>Adin Ćebić</title>
    <link>https://adincebic.com/</link>
    <description></description>
    
    <language>en</language>
    
    <lastBuildDate>Sun, 12 Dec 2021 10:20:49 +0100</lastBuildDate>
    <item>
      <title>A Practical Introduction to Bazel Persistent Workers</title>
      <link>https://adincebic.com/2026/05/10/a-practical-introduction-to-bazel.html</link>
      <pubDate>Sun, 10 May 2026 17:45:12 +0100</pubDate>
      
      <guid>http://adincebic.micro.blog/2026/05/10/a-practical-introduction-to-bazel.html</guid>
      <description>

&lt;p&gt;Typically, Bazel rules execute actions that usually correspond to tool processes on the host OS. Sometimes this behavior can incur startup costs, like bootstrapping a JVM or initializing a compiler. To work around that, Bazel has the concept of &lt;a href=&#34;https://bazel.build/remote/creating&#34;&gt;persistent workers&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A persistent worker is essentially a long-lived process that accepts work requests and responds with work responses. Imagine a process that keeps a compiler alive and dispatches sources to compile without paying the startup cost every time.&lt;/p&gt;

&lt;h2 id=&#34;creating-a-rule-that-leverages-workers&#34;&gt;Creating a rule that leverages workers&lt;/h2&gt;

&lt;p&gt;Because this is a fairly advanced concept in Bazel, and usually only rule authors deal with it, I tried to come up with a simple example that demonstrates it.&lt;/p&gt;

&lt;h3 id=&#34;an-uppercase-rule&#34;&gt;An uppercase rule&lt;/h3&gt;

&lt;p&gt;We will write a rule that simply uppercases the text in a given file. To begin, we need to meet a few requirements. The first one is adding dependencies in our &lt;code&gt;MODULE.bazel&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;bazel_dep(name = &amp;quot;swift_argument_parser&amp;quot;, version = &amp;quot;1.7.1&amp;quot;)
bazel_dep(name = &amp;quot;rules_swift&amp;quot;, version = &amp;quot;3.6.1&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;These will come into play a bit later.&lt;/p&gt;

&lt;h3 id=&#34;creating-a-rule&#34;&gt;Creating a rule&lt;/h3&gt;

&lt;p&gt;Like I said, this is a simple rule, but the code may look a bit scary at first. Create &lt;code&gt;uppercase.bzl&lt;/code&gt; at the root of the directory:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;def _uppercase_impl(ctx):
    out = ctx.actions.declare_file(ctx.label.name + &amp;quot;.out&amp;quot;)
    args_file = ctx.actions.declare_file(ctx.label.name + &amp;quot;.worker_args&amp;quot;)

    # These are the per-action arguments. Bazel will send these to the
    # persistent worker inside each WorkRequest.
    ctx.actions.write(
        output = args_file,
        content = &amp;quot;\n&amp;quot;.join([
            &amp;quot;--input=&amp;quot; + ctx.file.src.path,
            &amp;quot;--output=&amp;quot; + out.path,
        ]),
    )

    ctx.actions.run(
        executable = ctx.executable._worker,
        inputs = [
            ctx.file.src,
            args_file,
        ],
        outputs = [out],
        arguments = [
            # For worker actions, the last argument is special:
            # it must be an @flagfile containing the per-request args.
            &amp;quot;@&amp;quot; + args_file.path,
        ],
        mnemonic = &amp;quot;UppercaseWorker&amp;quot;,
        execution_requirements = {
            &amp;quot;supports-workers&amp;quot;: &amp;quot;1&amp;quot;,
            &amp;quot;requires-worker-protocol&amp;quot;: &amp;quot;json&amp;quot;,
        },
    )

    return [DefaultInfo(files = depset([out]))]


uppercase = rule(
    implementation = _uppercase_impl,
    attrs = {
        &amp;quot;src&amp;quot;: attr.label(
            allow_single_file = True,
            mandatory = True,
        ),
        &amp;quot;_worker&amp;quot;: attr.label(
            default = &amp;quot;//tools:worker&amp;quot;,
            executable = True,
            cfg = &amp;quot;exec&amp;quot;,
        ),
    },
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The important part here is the &lt;code&gt;arguments&lt;/code&gt; list. For worker actions, Bazel treats the last argument specially when it is an &lt;code&gt;@flagfile&lt;/code&gt;. The contents of that file become the per-request arguments inside the &lt;code&gt;WorkRequest&lt;/code&gt;. Any arguments before that are considered startup arguments for the worker process.&lt;/p&gt;

&lt;p&gt;Now that the rule is in place, we need to create the actual worker binary. Because Swift is my language of choice, we will write it using Swift, but you can implement it in any language.&lt;/p&gt;

&lt;h3 id=&#34;the-swift-worker&#34;&gt;The Swift worker&lt;/h3&gt;

&lt;p&gt;Typically, I would split this out into multiple Swift files, but for the sake of simplicity, I will shove everything into one Swift file called &lt;code&gt;worker.swift&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-swift&#34;&gt;import Foundation
import ArgumentParser

struct WorkRequest: Decodable {
    var arguments: [String]?
    var requestId: Int?

    // Bazel may send other fields such as inputs, verbosity, etc.
    // JSONDecoder ignores unknown fields by default, which is what we want.
}

struct WorkResponse: Encodable {
    var requestId: Int
    var exitCode: Int
    var output: String
}

struct UppercaseArgs: ParsableArguments {
    @Option(name: .long)
    var input: String

    @Option(name: .long)
    var output: String
}

func expandArgs(_ args: [String]) throws -&amp;gt; [String] {
    var expanded: [String] = []

    for arg in args {
        if arg.hasPrefix(&amp;quot;@&amp;quot;) {
            let path = String(arg.dropFirst())
            let contents = try String(contentsOfFile: path, encoding: .utf8)

            for line in contents.split(separator: &amp;quot;\n&amp;quot;) {
                let trimmed = line.trimmingCharacters(in: .whitespacesAndNewlines)

                if !trimmed.isEmpty {
                    expanded.append(trimmed)
                }
            }
        } else {
            expanded.append(arg)
        }
    }

    return expanded
}

func runOne(_ rawArgs: [String]) throws {
    let args = try UppercaseArgs.parse(expandArgs(rawArgs))

    let inputText = try String(contentsOfFile: args.input, encoding: .utf8)

    try inputText.uppercased().write(
        toFile: args.output,
        atomically: true,
        encoding: .utf8
    )
}

func writeResponse(requestId: Int, exitCode: Int = 0, output: String = &amp;quot;&amp;quot;) {
    let response = WorkResponse(
        requestId: requestId,
        exitCode: exitCode,
        output: output
    )

    do {
        let data = try JSONEncoder().encode(response)

        FileHandle.standardOutput.write(data)
        FileHandle.standardOutput.write(Data(&amp;quot;\n&amp;quot;.utf8))
    } catch {
        // Important: do not print normal logs to stdout.
        // In worker mode, stdout is reserved for WorkResponse JSON.
        FileHandle.standardError.write(
            Data(&amp;quot;failed to encode WorkResponse: \(error)\n&amp;quot;.utf8)
        )
        exit(1)
    }
}

func persistentLoop() {
    let decoder = JSONDecoder()

    while let line = readLine() {
        do {
            let request = try decoder.decode(
                WorkRequest.self,
                from: Data(line.utf8)
            )

            let requestId = request.requestId ?? 0
            let arguments = request.arguments ?? []

            do {
                try runOne(arguments)
                writeResponse(requestId: requestId)
            } catch {
                writeResponse(
                    requestId: requestId,
                    exitCode: 1,
                    output: String(describing: error)
                )
            }
        } catch {
            writeResponse(
                requestId: 0,
                exitCode: 1,
                output: &amp;quot;failed to decode WorkRequest: \(error)&amp;quot;
            )
        }
    }
}

@main
struct Worker {
    static func main() {
        let startupArgs = Array(CommandLine.arguments.dropFirst())

        if startupArgs.contains(&amp;quot;--persistent_worker&amp;quot;) {
            persistentLoop()
        } else {
            // Non-worker fallback path. This lets the same executable still work
            // when Bazel uses local execution instead of worker execution.
            do {
                try runOne(startupArgs)
            } catch {
                FileHandle.standardError.write(Data(&amp;quot;\(error)\n&amp;quot;.utf8))
                exit(1)
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;A persistent worker has a small protocol contract with Bazel: it should accept the &lt;code&gt;--persistent_worker&lt;/code&gt; flag, read &lt;code&gt;WorkRequest&lt;/code&gt;s from stdin, and write &lt;code&gt;WorkResponse&lt;/code&gt;s to stdout. If the same binary is run without &lt;code&gt;--persistent_worker&lt;/code&gt;, it should behave like a normal one-shot tool. This fallback path is useful because Bazel may still run the action without the worker strategy.&lt;/p&gt;

&lt;p&gt;One small but important detail: in worker mode, stdout belongs to the worker protocol. If you need to log something, write it to stderr instead.&lt;/p&gt;

&lt;p&gt;I will not get into every detail here. I do expect the reader to be familiar with Swift and the general concept of Bazel workers.&lt;/p&gt;

&lt;p&gt;We are missing the actual Bazel target for the worker at &lt;code&gt;tools/BUILD.bazel&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;load(&amp;quot;@rules_swift//swift:swift_binary.bzl&amp;quot;, &amp;quot;swift_binary&amp;quot;)

swift_binary(
    name = &amp;quot;worker&amp;quot;,
    srcs = [&amp;quot;worker.swift&amp;quot;],
    visibility = [&amp;quot;//visibility:public&amp;quot;],
    deps = [&amp;quot;@swift_argument_parser//:ArgumentParser&amp;quot;],
)
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;trying-out-the-rule&#34;&gt;Trying out the rule&lt;/h3&gt;

&lt;p&gt;At the root, it is time to create a &lt;code&gt;BUILD.bazel&lt;/code&gt;, load our rule, and build it:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;load(&amp;quot;//:uppercase.bzl&amp;quot;, &amp;quot;uppercase&amp;quot;)

uppercase(
    name = &amp;quot;hello&amp;quot;,
    src = &amp;quot;hello.txt&amp;quot;,
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;hello.txt&lt;/code&gt; is just a text file that I created to demonstrate the rule.&lt;/p&gt;

&lt;h3 id=&#34;building-and-verifying&#34;&gt;Building and verifying&lt;/h3&gt;

&lt;p&gt;To try out our new rule, execute:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;bazel build :hello --spawn_strategy=worker,sandboxed --worker_verbose
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We set &lt;code&gt;--spawn_strategy=worker,sandboxed&lt;/code&gt; to make sure that our rule runs using the worker strategy and falls back to the standard &lt;code&gt;sandboxed&lt;/code&gt; strategy. The fallback is important because there are actions that run because of &lt;code&gt;rules_swift&lt;/code&gt; that do not necessarily use workers.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--worker_verbose&lt;/code&gt; is here just to make it easier to see that our worker is being used.&lt;/p&gt;

&lt;p&gt;The output should look something like this:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;INFO: Analyzed target //:hello (102 packages loaded, 649 targets configured, 2 aspect applications).
INFO: Created new non-sandboxed singleplex SwiftCompile worker (id 5, key hash -1813863811), logging to /Users/adincebic/Library/Caches/bazel/_bazel_adincebic/19f2a862cd16d28bfab74de8ca294508/bazel-workers/worker-5-SwiftCompile.log
INFO: Created new non-sandboxed singleplex UppercaseWorker worker (id 6, key hash -755134554), logging to /Users/adincebic/Library/Caches/bazel/_bazel_adincebic/19f2a862cd16d28bfab74de8ca294508/bazel-workers/worker-6-UppercaseWorker.log
INFO: Found 1 target...
Target //:hello up-to-date:
  bazel-bin/hello.out
INFO: Elapsed time: 19.851s, Critical Path: 19.03s
INFO: 60 processes: 30 internal, 26 darwin-sandbox, 4 worker.
INFO: Build completed successfully, 60 total actions
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To verify the result, inspect &lt;code&gt;bazel-bin/hello.out&lt;/code&gt;. It should contain the uppercase version of &lt;code&gt;hello.txt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And that’s it.&lt;/p&gt;

&lt;h2 id=&#34;a-few-notes&#34;&gt;A few notes&lt;/h2&gt;

&lt;p&gt;This is the simplest example I could come up with, and it comes with a few caveats:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;My worker implementation does not implement cancellation.&lt;/li&gt;
&lt;li&gt;This is a singleplex worker, meaning Bazel sends it one request at a time.&lt;/li&gt;
&lt;li&gt;The parsing logic could be more robust.&lt;/li&gt;
&lt;li&gt;The worker ignores fields like &lt;code&gt;inputs&lt;/code&gt; and &lt;code&gt;verbosity&lt;/code&gt; from &lt;code&gt;WorkRequest&lt;/code&gt;, which is fine for this example but probably not what you would do in a production worker.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;This is one of those advanced Bazel concepts that you do not run into often, even if you write your own rules, purely because it is not always needed. But if you ever need persistent workers, I hope this gets you started.&lt;/p&gt;
</description>
      <source:markdown>Typically, Bazel rules execute actions that usually correspond to tool processes on the host OS. Sometimes this behavior can incur startup costs, like bootstrapping a JVM or initializing a compiler. To work around that, Bazel has the concept of [persistent workers](https://bazel.build/remote/creating).

A persistent worker is essentially a long-lived process that accepts work requests and responds with work responses. Imagine a process that keeps a compiler alive and dispatches sources to compile without paying the startup cost every time.

## Creating a rule that leverages workers

Because this is a fairly advanced concept in Bazel, and usually only rule authors deal with it, I tried to come up with a simple example that demonstrates it.

### An uppercase rule

We will write a rule that simply uppercases the text in a given file. To begin, we need to meet a few requirements. The first one is adding dependencies in our `MODULE.bazel`:

```starlark
bazel_dep(name = &#34;swift_argument_parser&#34;, version = &#34;1.7.1&#34;)
bazel_dep(name = &#34;rules_swift&#34;, version = &#34;3.6.1&#34;)
```

These will come into play a bit later.

### Creating a rule

Like I said, this is a simple rule, but the code may look a bit scary at first. Create `uppercase.bzl` at the root of the directory:

```starlark
def _uppercase_impl(ctx):
    out = ctx.actions.declare_file(ctx.label.name + &#34;.out&#34;)
    args_file = ctx.actions.declare_file(ctx.label.name + &#34;.worker_args&#34;)

    # These are the per-action arguments. Bazel will send these to the
    # persistent worker inside each WorkRequest.
    ctx.actions.write(
        output = args_file,
        content = &#34;\n&#34;.join([
            &#34;--input=&#34; + ctx.file.src.path,
            &#34;--output=&#34; + out.path,
        ]),
    )

    ctx.actions.run(
        executable = ctx.executable._worker,
        inputs = [
            ctx.file.src,
            args_file,
        ],
        outputs = [out],
        arguments = [
            # For worker actions, the last argument is special:
            # it must be an @flagfile containing the per-request args.
            &#34;@&#34; + args_file.path,
        ],
        mnemonic = &#34;UppercaseWorker&#34;,
        execution_requirements = {
            &#34;supports-workers&#34;: &#34;1&#34;,
            &#34;requires-worker-protocol&#34;: &#34;json&#34;,
        },
    )

    return [DefaultInfo(files = depset([out]))]


uppercase = rule(
    implementation = _uppercase_impl,
    attrs = {
        &#34;src&#34;: attr.label(
            allow_single_file = True,
            mandatory = True,
        ),
        &#34;_worker&#34;: attr.label(
            default = &#34;//tools:worker&#34;,
            executable = True,
            cfg = &#34;exec&#34;,
        ),
    },
)
```

The important part here is the `arguments` list. For worker actions, Bazel treats the last argument specially when it is an `@flagfile`. The contents of that file become the per-request arguments inside the `WorkRequest`. Any arguments before that are considered startup arguments for the worker process.

Now that the rule is in place, we need to create the actual worker binary. Because Swift is my language of choice, we will write it using Swift, but you can implement it in any language.

### The Swift worker

Typically, I would split this out into multiple Swift files, but for the sake of simplicity, I will shove everything into one Swift file called `worker.swift`:

```swift
import Foundation
import ArgumentParser

struct WorkRequest: Decodable {
    var arguments: [String]?
    var requestId: Int?

    // Bazel may send other fields such as inputs, verbosity, etc.
    // JSONDecoder ignores unknown fields by default, which is what we want.
}

struct WorkResponse: Encodable {
    var requestId: Int
    var exitCode: Int
    var output: String
}

struct UppercaseArgs: ParsableArguments {
    @Option(name: .long)
    var input: String

    @Option(name: .long)
    var output: String
}

func expandArgs(_ args: [String]) throws -&gt; [String] {
    var expanded: [String] = []

    for arg in args {
        if arg.hasPrefix(&#34;@&#34;) {
            let path = String(arg.dropFirst())
            let contents = try String(contentsOfFile: path, encoding: .utf8)

            for line in contents.split(separator: &#34;\n&#34;) {
                let trimmed = line.trimmingCharacters(in: .whitespacesAndNewlines)

                if !trimmed.isEmpty {
                    expanded.append(trimmed)
                }
            }
        } else {
            expanded.append(arg)
        }
    }

    return expanded
}

func runOne(_ rawArgs: [String]) throws {
    let args = try UppercaseArgs.parse(expandArgs(rawArgs))

    let inputText = try String(contentsOfFile: args.input, encoding: .utf8)

    try inputText.uppercased().write(
        toFile: args.output,
        atomically: true,
        encoding: .utf8
    )
}

func writeResponse(requestId: Int, exitCode: Int = 0, output: String = &#34;&#34;) {
    let response = WorkResponse(
        requestId: requestId,
        exitCode: exitCode,
        output: output
    )

    do {
        let data = try JSONEncoder().encode(response)

        FileHandle.standardOutput.write(data)
        FileHandle.standardOutput.write(Data(&#34;\n&#34;.utf8))
    } catch {
        // Important: do not print normal logs to stdout.
        // In worker mode, stdout is reserved for WorkResponse JSON.
        FileHandle.standardError.write(
            Data(&#34;failed to encode WorkResponse: \(error)\n&#34;.utf8)
        )
        exit(1)
    }
}

func persistentLoop() {
    let decoder = JSONDecoder()

    while let line = readLine() {
        do {
            let request = try decoder.decode(
                WorkRequest.self,
                from: Data(line.utf8)
            )

            let requestId = request.requestId ?? 0
            let arguments = request.arguments ?? []

            do {
                try runOne(arguments)
                writeResponse(requestId: requestId)
            } catch {
                writeResponse(
                    requestId: requestId,
                    exitCode: 1,
                    output: String(describing: error)
                )
            }
        } catch {
            writeResponse(
                requestId: 0,
                exitCode: 1,
                output: &#34;failed to decode WorkRequest: \(error)&#34;
            )
        }
    }
}

@main
struct Worker {
    static func main() {
        let startupArgs = Array(CommandLine.arguments.dropFirst())

        if startupArgs.contains(&#34;--persistent_worker&#34;) {
            persistentLoop()
        } else {
            // Non-worker fallback path. This lets the same executable still work
            // when Bazel uses local execution instead of worker execution.
            do {
                try runOne(startupArgs)
            } catch {
                FileHandle.standardError.write(Data(&#34;\(error)\n&#34;.utf8))
                exit(1)
            }
        }
    }
}
```

A persistent worker has a small protocol contract with Bazel: it should accept the `--persistent_worker` flag, read `WorkRequest`s from stdin, and write `WorkResponse`s to stdout. If the same binary is run without `--persistent_worker`, it should behave like a normal one-shot tool. This fallback path is useful because Bazel may still run the action without the worker strategy.

One small but important detail: in worker mode, stdout belongs to the worker protocol. If you need to log something, write it to stderr instead.

I will not get into every detail here. I do expect the reader to be familiar with Swift and the general concept of Bazel workers.

We are missing the actual Bazel target for the worker at `tools/BUILD.bazel`:

```starlark
load(&#34;@rules_swift//swift:swift_binary.bzl&#34;, &#34;swift_binary&#34;)

swift_binary(
    name = &#34;worker&#34;,
    srcs = [&#34;worker.swift&#34;],
    visibility = [&#34;//visibility:public&#34;],
    deps = [&#34;@swift_argument_parser//:ArgumentParser&#34;],
)
```

### Trying out the rule

At the root, it is time to create a `BUILD.bazel`, load our rule, and build it:

```starlark
load(&#34;//:uppercase.bzl&#34;, &#34;uppercase&#34;)

uppercase(
    name = &#34;hello&#34;,
    src = &#34;hello.txt&#34;,
)
```

`hello.txt` is just a text file that I created to demonstrate the rule.

### Building and verifying

To try out our new rule, execute:

```bash
bazel build :hello --spawn_strategy=worker,sandboxed --worker_verbose
```

We set `--spawn_strategy=worker,sandboxed` to make sure that our rule runs using the worker strategy and falls back to the standard `sandboxed` strategy. The fallback is important because there are actions that run because of `rules_swift` that do not necessarily use workers.

`--worker_verbose` is here just to make it easier to see that our worker is being used.

The output should look something like this:

```bash
INFO: Analyzed target //:hello (102 packages loaded, 649 targets configured, 2 aspect applications).
INFO: Created new non-sandboxed singleplex SwiftCompile worker (id 5, key hash -1813863811), logging to /Users/adincebic/Library/Caches/bazel/_bazel_adincebic/19f2a862cd16d28bfab74de8ca294508/bazel-workers/worker-5-SwiftCompile.log
INFO: Created new non-sandboxed singleplex UppercaseWorker worker (id 6, key hash -755134554), logging to /Users/adincebic/Library/Caches/bazel/_bazel_adincebic/19f2a862cd16d28bfab74de8ca294508/bazel-workers/worker-6-UppercaseWorker.log
INFO: Found 1 target...
Target //:hello up-to-date:
  bazel-bin/hello.out
INFO: Elapsed time: 19.851s, Critical Path: 19.03s
INFO: 60 processes: 30 internal, 26 darwin-sandbox, 4 worker.
INFO: Build completed successfully, 60 total actions
```

To verify the result, inspect `bazel-bin/hello.out`. It should contain the uppercase version of `hello.txt`.

And that’s it.

## A few notes

This is the simplest example I could come up with, and it comes with a few caveats:

* My worker implementation does not implement cancellation.
* This is a singleplex worker, meaning Bazel sends it one request at a time.
* The parsing logic could be more robust.
* The worker ignores fields like `inputs` and `verbosity` from `WorkRequest`, which is fine for this example but probably not what you would do in a production worker.

## Conclusion

This is one of those advanced Bazel concepts that you do not run into often, even if you write your own rules, purely because it is not always needed. But if you ever need persistent workers, I hope this gets you started.
</source:markdown>
    </item>
    
    <item>
      <title>Why My Xcode Extension Kept Asking for File Permissions</title>
      <link>https://adincebic.com/2026/05/03/why-my-xcode-extension-kept.html</link>
      <pubDate>Sun, 03 May 2026 20:41:59 +0100</pubDate>
      
      <guid>http://adincebic.micro.blog/2026/05/03/why-my-xcode-extension-kept.html</guid>
      <description>

&lt;p&gt;Recently, I worked on developing an Xcode source editor extension that needed to run some of our internal code formatters. These formatters are driven by configuration files that define how the tools should be executed. Because Xcode extensions must be sandboxed, they can’t directly access arbitrary file locations, including these configuration files.&lt;/p&gt;

&lt;p&gt;To work around this, we used a container app to prompt users to select the location of the configuration files. We then created security-scoped bookmarks and passed them to the extension process. As expected, the standard way to share data between processes—such as an app and its extension—is by using Apple’s App Groups capability.&lt;/p&gt;

&lt;p&gt;After setting this up, I noticed that the extension kept prompting the user to grant access to the shared files, even though both the app and extension were part of the same app group. This was unexpected—intuitively, accessing files within your own shared container shouldn’t trigger permission prompts.&lt;/p&gt;

&lt;h2 id=&#34;the-mistake&#34;&gt;The mistake&lt;/h2&gt;

&lt;p&gt;Coming from an iOS background, I defined the app group ID like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;key&amp;gt;com.apple.security.application-groups&amp;lt;/key&amp;gt;
&amp;lt;array&amp;gt;
	&amp;lt;string&amp;gt;group.example.app&amp;lt;/string&amp;gt;
&amp;lt;/array&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;After running both the app and the extension and inspecting &lt;code&gt;~/Library/Group Containers/&lt;/code&gt;, it was clear that the shared container had been created. However, what I missed is that on macOS, App Group identifiers must be prefixed with the Team ID (for example, &lt;code&gt;TEAMID.group.example.app&lt;/code&gt;). This allows the system to correctly associate the app group with your developer account and properly link the app and its extension.&lt;/p&gt;

&lt;p&gt;Without this prefix, the container may still appear to exist, but entitlement validation and access behavior can be inconsistent—leading to issues like repeated permission prompts.&lt;/p&gt;

&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;This turned out to be one of those frustrating issues where the root cause isn’t immediately obvious, even after checking open-source projects and documentation. To be fair, Apple does document this requirement—but it’s easy to overlook, especially since iOS does not require this detail and doesn’t expose the same behavior as clearly.&lt;/p&gt;
</description>
      <source:markdown>Recently, I worked on developing an Xcode source editor extension that needed to run some of our internal code formatters. These formatters are driven by configuration files that define how the tools should be executed. Because Xcode extensions must be sandboxed, they can’t directly access arbitrary file locations, including these configuration files.

To work around this, we used a container app to prompt users to select the location of the configuration files. We then created security-scoped bookmarks and passed them to the extension process. As expected, the standard way to share data between processes—such as an app and its extension—is by using Apple’s App Groups capability.

After setting this up, I noticed that the extension kept prompting the user to grant access to the shared files, even though both the app and extension were part of the same app group. This was unexpected—intuitively, accessing files within your own shared container shouldn’t trigger permission prompts.

## The mistake

Coming from an iOS background, I defined the app group ID like this:

```
&lt;key&gt;com.apple.security.application-groups&lt;/key&gt;
&lt;array&gt;
	&lt;string&gt;group.example.app&lt;/string&gt;
&lt;/array&gt;
```

After running both the app and the extension and inspecting `~/Library/Group Containers/`, it was clear that the shared container had been created. However, what I missed is that on macOS, App Group identifiers must be prefixed with the Team ID (for example, `TEAMID.group.example.app`). This allows the system to correctly associate the app group with your developer account and properly link the app and its extension.

Without this prefix, the container may still appear to exist, but entitlement validation and access behavior can be inconsistent—leading to issues like repeated permission prompts.

## Conclusion

This turned out to be one of those frustrating issues where the root cause isn’t immediately obvious, even after checking open-source projects and documentation. To be fair, Apple does document this requirement—but it’s easy to overlook, especially since iOS does not require this detail and doesn’t expose the same behavior as clearly.
</source:markdown>
    </item>
    
    <item>
      <title>Centralizing Dependency Fetching in Bazel with the Remote Asset API</title>
      <link>https://adincebic.com/2026/04/26/centralizing-dependency-fetching-in-bazel.html</link>
      <pubDate>Sun, 26 Apr 2026 16:40:57 +0100</pubDate>
      
      <guid>http://adincebic.micro.blog/2026/04/26/centralizing-dependency-fetching-in-bazel.html</guid>
      <description>

&lt;p&gt;It has become increasingly common for major providers to experience outages—from Git servers being unavailable to failures when downloading external dependencies.&lt;/p&gt;

&lt;p&gt;There are several ways to work around this, such as internal mirrors, vendoring dependencies, and similar approaches. While effective, these solutions can feel somewhat heavy-handed.&lt;/p&gt;

&lt;h2 id=&#34;bazel-remote-asset-api&#34;&gt;Bazel Remote Asset API&lt;/h2&gt;

&lt;p&gt;The &lt;a href=&#34;https://github.com/bazelbuild/remote-apis/blob/main/build/bazel/remote/asset/v1/remote_asset.proto&#34;&gt;Bazel Remote Asset API&lt;/a&gt; provides a mechanism for managing external dependencies in a centralized way.&lt;/p&gt;

&lt;p&gt;More precisely, it maps &lt;strong&gt;external resource identifiers (such as URLs or Git repositories)&lt;/strong&gt; to &lt;strong&gt;content stored in a content-addressable storage (CAS)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In practice, this allows a server to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fetch external resources (e.g. tarballs, Git repos)&lt;/li&gt;
&lt;li&gt;Store them in CAS&lt;/li&gt;
&lt;li&gt;Serve them to clients by digest&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When used via Bazel’s remote downloader, this effectively acts as a &lt;strong&gt;download proxy/cache&lt;/strong&gt;: instead of every developer machine and CI runner downloading dependencies independently, requests go through a central service that can fetch and cache them once.&lt;/p&gt;

&lt;h2 id=&#34;how-to-use&#34;&gt;How to Use&lt;/h2&gt;

&lt;p&gt;Getting started is straightforward: pass
&lt;code&gt;--experimental_remote_downloader=SERVER_ADDRESS&lt;/code&gt;
either on the command line or in your &lt;code&gt;.bazelrc&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This configures Bazel to route external downloads through a Remote Asset API–compatible service.&lt;/p&gt;

&lt;p&gt;Before using it, ensure your remote cache/server supports the API. Many commercial solutions do, and the popular open-source &lt;a href=&#34;https://github.com/buchgr/bazel-remote&#34;&gt;bazel-remote&lt;/a&gt; supports (a subset of) it as well—though support is still marked experimental.&lt;/p&gt;

&lt;h2 id=&#34;a-note-on-the-experimental-flag&#34;&gt;A Note on the Experimental Flag&lt;/h2&gt;

&lt;p&gt;Although the flag is prefixed with &lt;code&gt;experimental&lt;/code&gt;, the feature has been available for some time and is widely used in practice. There is some good info on the &lt;a href=&#34;https://bazelbuild.slack.com/archives/CA31HN1T3/p1777051154383449&#34;&gt;Bazel Slack&lt;/a&gt; about it.&lt;/p&gt;

&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Combined with Bazel’s repository cache, the Remote Asset API provides a nice way to improve reliability when fetching external repos. It reduces reliance on third-party availability while avoiding the operational overhead of fully vendoring or mirroring all dependencies.&lt;/p&gt;
</description>
      <source:markdown>It has become increasingly common for major providers to experience outages—from Git servers being unavailable to failures when downloading external dependencies.

There are several ways to work around this, such as internal mirrors, vendoring dependencies, and similar approaches. While effective, these solutions can feel somewhat heavy-handed.

## Bazel Remote Asset API

The [Bazel Remote Asset API](https://github.com/bazelbuild/remote-apis/blob/main/build/bazel/remote/asset/v1/remote_asset.proto) provides a mechanism for managing external dependencies in a centralized way.

More precisely, it maps **external resource identifiers (such as URLs or Git repositories)** to **content stored in a content-addressable storage (CAS)**.

In practice, this allows a server to:

* Fetch external resources (e.g. tarballs, Git repos)
* Store them in CAS
* Serve them to clients by digest

When used via Bazel’s remote downloader, this effectively acts as a **download proxy/cache**: instead of every developer machine and CI runner downloading dependencies independently, requests go through a central service that can fetch and cache them once.

## How to Use

Getting started is straightforward: pass
`--experimental_remote_downloader=SERVER_ADDRESS`
either on the command line or in your `.bazelrc`.

This configures Bazel to route external downloads through a Remote Asset API–compatible service.

Before using it, ensure your remote cache/server supports the API. Many commercial solutions do, and the popular open-source [bazel-remote](https://github.com/buchgr/bazel-remote) supports (a subset of) it as well—though support is still marked experimental.

## A Note on the Experimental Flag

Although the flag is prefixed with `experimental`, the feature has been available for some time and is widely used in practice. There is some good info on the [Bazel Slack](https://bazelbuild.slack.com/archives/CA31HN1T3/p1777051154383449) about it.

## Conclusion

Combined with Bazel’s repository cache, the Remote Asset API provides a nice way to improve reliability when fetching external repos. It reduces reliance on third-party availability while avoiding the operational overhead of fully vendoring or mirroring all dependencies.
</source:markdown>
    </item>
    
    <item>
      <title>A Better Way to Ignore Files in Bazel with repo.bazel</title>
      <link>https://adincebic.com/2026/04/19/a-better-way-to-ignore.html</link>
      <pubDate>Sun, 19 Apr 2026 21:22:10 +0100</pubDate>
      
      <guid>http://adincebic.micro.blog/2026/04/19/a-better-way-to-ignore.html</guid>
      <description>

&lt;p&gt;In the Bazel world, we don’t always want it to track all the files in our repository. A typical example is ignoring the &lt;code&gt;.git&lt;/code&gt; directory, as it can grow quite large over time. Additionally, some IDE integrations like &lt;code&gt;rules_xcodeproj&lt;/code&gt; don’t work particularly well when it is present.&lt;/p&gt;

&lt;p&gt;Traditionally, to instruct Bazel to ignore directories and files, we used the &lt;code&gt;.bazelignore&lt;/code&gt; file, which requires explicitly listing paths to ignore. This works, but it has an important limitation: &lt;code&gt;.bazelignore&lt;/code&gt; does not support glob patterns. As a result, we often need to update the file whenever new directories should be ignored—and it’s easy to forget to do so.&lt;/p&gt;

&lt;h2 id=&#34;introducing-repo-bazel&#34;&gt;Introducing &lt;code&gt;repo.bazel&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;repo.bazel&lt;/code&gt; is a simple configuration file that allows us to achieve similar behavior, but with support for glob patterns. It is a relatively recent addition to Bazel, introduced around the same time as bzlmod.&lt;/p&gt;

&lt;p&gt;An example &lt;code&gt;repo.bazel&lt;/code&gt; file looks like this:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;ignore_directories([
    # Ignore all .build directories produced by Swift Package Manager
    &amp;quot;**/.build&amp;quot;,
    # Ignore Node modules directories
    &amp;quot;**/node_modules&amp;quot;,
])
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And that’s it.&lt;/p&gt;

&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;This approach builds on the same idea as &lt;code&gt;.bazelignore&lt;/code&gt;, but adds a few quality-of-life improvements—most notably, support for glob patterns.&lt;/p&gt;

&lt;p&gt;For more information, see the &lt;a href=&#34;https://bazel.build/rules/lib/globals/repo&#34;&gt;official Bazel documentation&lt;/a&gt;.&lt;/p&gt;
</description>
      <source:markdown>In the Bazel world, we don’t always want it to track all the files in our repository. A typical example is ignoring the `.git` directory, as it can grow quite large over time. Additionally, some IDE integrations like `rules_xcodeproj` don’t work particularly well when it is present.

Traditionally, to instruct Bazel to ignore directories and files, we used the `.bazelignore` file, which requires explicitly listing paths to ignore. This works, but it has an important limitation: `.bazelignore` does not support glob patterns. As a result, we often need to update the file whenever new directories should be ignored—and it’s easy to forget to do so.

## Introducing `repo.bazel`

`repo.bazel` is a simple configuration file that allows us to achieve similar behavior, but with support for glob patterns. It is a relatively recent addition to Bazel, introduced around the same time as bzlmod.

An example `repo.bazel` file looks like this:

```starlark
ignore_directories([
    # Ignore all .build directories produced by Swift Package Manager
    &#34;**/.build&#34;,
    # Ignore Node modules directories
    &#34;**/node_modules&#34;,
])
```

And that’s it.

## Conclusion

This approach builds on the same idea as `.bazelignore`, but adds a few quality-of-life improvements—most notably, support for glob patterns.

For more information, see the [official Bazel documentation](https://bazel.build/rules/lib/globals/repo).
</source:markdown>
    </item>
    
    <item>
      <title>Reconfiguring bazel downloader</title>
      <link>https://adincebic.com/2026/04/12/reconfiguring-bazel-downloader.html</link>
      <pubDate>Sun, 12 Apr 2026 15:00:36 +0100</pubDate>
      
      <guid>http://adincebic.micro.blog/2026/04/12/reconfiguring-bazel-downloader.html</guid>
      <description>

&lt;p&gt;There are many security as well as practical reasons why one might need to reconfigure Bazel&amp;rsquo;s downloading behavior. One concrete case that I ran into fairly recently was Google rate-limiting our CI for an unknown reason. To work around that, I needed to redirect the downloader to a mirror. There are many ways to achieve that, like patching individual rules (tedious), using an internal registry (doesn&amp;rsquo;t solve everything), etc.&lt;/p&gt;

&lt;h2 id=&#34;bazel-downloader-config&#34;&gt;Bazel downloader config&lt;/h2&gt;

&lt;p&gt;Bazel offers a way to configure its downloader in a very simple manner. Unfortunately, it is not very well documented, but there are various resources online as well as the actual &lt;a href=&#34;https://github.com/bazelbuild/bazel/blob/09c621e4cf5b968f4c6cdf905ab142d5961f9ddc/src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/UrlRewriterConfig.java#L66&#34;&gt;Bazel source&lt;/a&gt;, which explains it quite nicely. To enable it, we simply pass &lt;code&gt;--downloader_config=&amp;lt;path_to_file&amp;gt;&lt;/code&gt; either on the command line or in &lt;code&gt;.bazelrc&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&#34;file-structure-and-syntax&#34;&gt;File structure and syntax&lt;/h2&gt;

&lt;p&gt;The structure is easy to understand because it allows only a small set of directives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;allow host.name&lt;/code&gt; to allow a specific domain&lt;/li&gt;
&lt;li&gt;&lt;code&gt;block host.name&lt;/code&gt; to block a certain domain (also supports &lt;code&gt;block *&lt;/code&gt; to block everything except what is explicitly allowed)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rewrite pattern replacement&lt;/code&gt; to rewrite URLs using regex&lt;/li&gt;
&lt;li&gt;&lt;code&gt;all_blocked_message message&lt;/code&gt; — a message shown if all candidate URLs end up blocked&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;rewrite-directive&#34;&gt;Rewrite directive&lt;/h2&gt;

&lt;p&gt;Because all other directives are fairly self-explanatory, I will focus only on &lt;code&gt;rewrite&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;As an example, if we want to ensure that all GitHub downloads are redirected to an internal Artifactory, we could write a file like this:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-cfg&#34;&gt;rewrite github.com/(.*) internal.artifactory.example.com/$1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Of course, it is possible to define more sophisticated rewrite patterns, e.g.:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-cfg&#34;&gt;rewrite android.googlesource.com/platform/dalvik/\+archive/([0-9a-f]+)\.tar\.gz mirror.bazel.build/android.googlesource.com/platform/dalvik/+archive/$1.tar.gz
rewrite android.googlesource.com/platform/dalvik/\+archive/([0-9a-f]+)\.tar\.gz android.googlesource.com/platform/dalvik/+archive/$1.tar.gz
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This rewrites requests for &lt;code&gt;android.googlesource.com&lt;/code&gt; to &lt;code&gt;mirror.bazel.build&lt;/code&gt; for this specific &lt;code&gt;dalvik&lt;/code&gt; archive. The second rewrite directive ensures that Bazel falls back to the original URL if the mirror is unavailable.&lt;/p&gt;

&lt;h2 id=&#34;evaluation-order&#34;&gt;Evaluation order&lt;/h2&gt;

&lt;p&gt;Bazel applies the directives in the following order, regardless of their position in the file:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;rewrite&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;allow&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;block&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&#34;comments&#34;&gt;Comments&lt;/h2&gt;

&lt;p&gt;It is possible to add comments using &lt;code&gt;#&lt;/code&gt; at the beginning of a line. Keep in mind that inline comments are not supported.&lt;/p&gt;

&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Typically, this is not needed very often, but it is good to keep the option in the back of your mind so you can reach for it when needed.&lt;/p&gt;
</description>
      <source:markdown>There are many security as well as practical reasons why one might need to reconfigure Bazel&#39;s downloading behavior. One concrete case that I ran into fairly recently was Google rate-limiting our CI for an unknown reason. To work around that, I needed to redirect the downloader to a mirror. There are many ways to achieve that, like patching individual rules (tedious), using an internal registry (doesn&#39;t solve everything), etc.

## Bazel downloader config

Bazel offers a way to configure its downloader in a very simple manner. Unfortunately, it is not very well documented, but there are various resources online as well as the actual [Bazel source](https://github.com/bazelbuild/bazel/blob/09c621e4cf5b968f4c6cdf905ab142d5961f9ddc/src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/UrlRewriterConfig.java#L66), which explains it quite nicely. To enable it, we simply pass `--downloader_config=&lt;path_to_file&gt;` either on the command line or in `.bazelrc`.

## File structure and syntax

The structure is easy to understand because it allows only a small set of directives:

* `allow host.name` to allow a specific domain
* `block host.name` to block a certain domain (also supports `block *` to block everything except what is explicitly allowed)
* `rewrite pattern replacement` to rewrite URLs using regex
* `all_blocked_message message` — a message shown if all candidate URLs end up blocked

## Rewrite directive

Because all other directives are fairly self-explanatory, I will focus only on `rewrite`.

As an example, if we want to ensure that all GitHub downloads are redirected to an internal Artifactory, we could write a file like this:

```cfg
rewrite github.com/(.*) internal.artifactory.example.com/$1
```

Of course, it is possible to define more sophisticated rewrite patterns, e.g.:

```cfg
rewrite android.googlesource.com/platform/dalvik/\+archive/([0-9a-f]+)\.tar\.gz mirror.bazel.build/android.googlesource.com/platform/dalvik/+archive/$1.tar.gz
rewrite android.googlesource.com/platform/dalvik/\+archive/([0-9a-f]+)\.tar\.gz android.googlesource.com/platform/dalvik/+archive/$1.tar.gz
```

This rewrites requests for `android.googlesource.com` to `mirror.bazel.build` for this specific `dalvik` archive. The second rewrite directive ensures that Bazel falls back to the original URL if the mirror is unavailable.

## Evaluation order

Bazel applies the directives in the following order, regardless of their position in the file:

1. `rewrite`
2. `allow`
3. `block`

## Comments

It is possible to add comments using `#` at the beginning of a line. Keep in mind that inline comments are not supported.

## Conclusion

Typically, this is not needed very often, but it is good to keep the option in the back of your mind so you can reach for it when needed.
</source:markdown>
    </item>
    
    <item>
      <title>Bazel Output Groups: Producing Outputs on Demand</title>
      <link>https://adincebic.com/2026/04/05/bazel-output-groups-producing-outputs.html</link>
      <pubDate>Sun, 05 Apr 2026 19:39:47 +0100</pubDate>
      
      <guid>http://adincebic.micro.blog/2026/04/05/bazel-output-groups-producing-outputs.html</guid>
      <description>

&lt;p&gt;Typically, when writing a Bazel rule, we produce outputs using the &lt;code&gt;DefaultInfo&lt;/code&gt; provider. However, there are cases where we want to produce additional outputs only on demand.&lt;/p&gt;

&lt;h2 id=&#34;enter-output-groups&#34;&gt;Enter output groups&lt;/h2&gt;

&lt;p&gt;Simply put, output groups are a way to tell Bazel to produce different sets of outputs instead of—or in addition to—the default outputs. For example, we might want to generate debug symbols, but we don’t need them unless explicitly requested.&lt;/p&gt;

&lt;h2 id=&#34;smallest-possible-example&#34;&gt;Smallest possible example&lt;/h2&gt;

&lt;p&gt;Here is a minimal rule that demonstrates the use of output groups:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;def _impl(ctx):
    out1 = ctx.actions.declare_file(&amp;quot;main.txt&amp;quot;)
    out2 = ctx.actions.declare_file(&amp;quot;debug.txt&amp;quot;)

    ctx.actions.write(out1, &amp;quot;main output&amp;quot;)
    ctx.actions.write(out2, &amp;quot;debug output&amp;quot;)

    return [
        DefaultInfo(files = depset([out1])),
        OutputGroupInfo(
            debug = depset([out2]),
        ),
    ]

my_rule = rule(
    implementation = _impl,
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Notice how easy it is to use output groups. &lt;code&gt;OutputGroupInfo&lt;/code&gt; is just another provider—a key-value mapping where, in this case, &lt;code&gt;debug&lt;/code&gt; is the key (the output group name), and &lt;code&gt;out2&lt;/code&gt; is the value wrapped in a &lt;code&gt;depset&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&#34;requesting-the-debug-output&#34;&gt;Requesting the debug output&lt;/h2&gt;

&lt;p&gt;If we instantiate this rule in a BUILD file:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;my_rule(
    name = &amp;quot;groups&amp;quot;,
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We can build it:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;bazel build :groups
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This produces:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;INFO: Analyzed target //:groups (5 packages loaded, 7 targets configured).
INFO: Found 1 target...
Target //:groups up-to-date:
  bazel-bin/main.txt
INFO: Elapsed time: 0.119s, Critical Path: 0.00s
INFO: 2 processes: 2 internal.
INFO: Build completed successfully, 2 total actions
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The important part here is &lt;code&gt;bazel-bin/main.txt&lt;/code&gt;. This happens because we did not tell Bazel to include outputs from the &lt;code&gt;debug&lt;/code&gt; output group.&lt;/p&gt;

&lt;p&gt;To do that, we use the &lt;code&gt;--output_groups&lt;/code&gt; flag and specify the group name (in this case, &lt;code&gt;debug&lt;/code&gt;):&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;bazel build :groups --output_groups=debug
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Output:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;INFO: Analyzed target //:groups (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
Target //:groups up-to-date:
  bazel-bin/debug.txt
INFO: Elapsed time: 0.065s, Critical Path: 0.00s
INFO: 2 processes: 2 internal.
INFO: Build completed successfully, 2 total actions
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now the debug file is produced.&lt;/p&gt;

&lt;p&gt;An important detail: &lt;code&gt;debug.txt&lt;/code&gt; is produced &lt;em&gt;instead of&lt;/em&gt; &lt;code&gt;main.txt&lt;/code&gt;, not in addition to it. To request both the default outputs and an output group at the same time, use the &lt;code&gt;+&lt;/code&gt; prefix:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;bazel build :groups --output_groups=+debug
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This produces both files:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;INFO: Analyzed target //:groups (5 packages loaded, 7 targets configured).
INFO: Found 1 target...
Target //:groups up-to-date:
  bazel-bin/debug.txt
  bazel-bin/main.txt
INFO: Elapsed time: 0.108s, Critical Path: 0.00s
INFO: 3 processes: 3 internal.
INFO: Build completed successfully, 3 total actions
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Output groups are simple to use, both when defining rules and when consuming them. They’re a small feature, but an extremely useful one.&lt;/p&gt;
</description>
      <source:markdown>Typically, when writing a Bazel rule, we produce outputs using the `DefaultInfo` provider. However, there are cases where we want to produce additional outputs only on demand.

## Enter output groups

Simply put, output groups are a way to tell Bazel to produce different sets of outputs instead of—or in addition to—the default outputs. For example, we might want to generate debug symbols, but we don’t need them unless explicitly requested.

## Smallest possible example

Here is a minimal rule that demonstrates the use of output groups:

```starlark
def _impl(ctx):
    out1 = ctx.actions.declare_file(&#34;main.txt&#34;)
    out2 = ctx.actions.declare_file(&#34;debug.txt&#34;)

    ctx.actions.write(out1, &#34;main output&#34;)
    ctx.actions.write(out2, &#34;debug output&#34;)

    return [
        DefaultInfo(files = depset([out1])),
        OutputGroupInfo(
            debug = depset([out2]),
        ),
    ]

my_rule = rule(
    implementation = _impl,
)
```

Notice how easy it is to use output groups. `OutputGroupInfo` is just another provider—a key-value mapping where, in this case, `debug` is the key (the output group name), and `out2` is the value wrapped in a `depset`.

## Requesting the debug output

If we instantiate this rule in a BUILD file:

```starlark
my_rule(
    name = &#34;groups&#34;,
)
```

We can build it:

```bash
bazel build :groups
```

This produces:

```bash
INFO: Analyzed target //:groups (5 packages loaded, 7 targets configured).
INFO: Found 1 target...
Target //:groups up-to-date:
  bazel-bin/main.txt
INFO: Elapsed time: 0.119s, Critical Path: 0.00s
INFO: 2 processes: 2 internal.
INFO: Build completed successfully, 2 total actions
```

The important part here is `bazel-bin/main.txt`. This happens because we did not tell Bazel to include outputs from the `debug` output group.

To do that, we use the `--output_groups` flag and specify the group name (in this case, `debug`):

```bash
bazel build :groups --output_groups=debug
```

Output:

```bash
INFO: Analyzed target //:groups (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
Target //:groups up-to-date:
  bazel-bin/debug.txt
INFO: Elapsed time: 0.065s, Critical Path: 0.00s
INFO: 2 processes: 2 internal.
INFO: Build completed successfully, 2 total actions
```

Now the debug file is produced.

An important detail: `debug.txt` is produced *instead of* `main.txt`, not in addition to it. To request both the default outputs and an output group at the same time, use the `+` prefix:

```bash
bazel build :groups --output_groups=+debug
```

This produces both files:

```bash
INFO: Analyzed target //:groups (5 packages loaded, 7 targets configured).
INFO: Found 1 target...
Target //:groups up-to-date:
  bazel-bin/debug.txt
  bazel-bin/main.txt
INFO: Elapsed time: 0.108s, Critical Path: 0.00s
INFO: 3 processes: 3 internal.
INFO: Build completed successfully, 3 total actions
```

## Conclusion

Output groups are simple to use, both when defining rules and when consuming them. They’re a small feature, but an extremely useful one.
</source:markdown>
    </item>
    
    <item>
      <title>What Bazel Really Runs (and How to See It)</title>
      <link>https://adincebic.com/2026/03/29/what-bazel-really-runs-and.html</link>
      <pubDate>Sun, 29 Mar 2026 17:16:03 +0100</pubDate>
      
      <guid>http://adincebic.micro.blog/2026/03/29/what-bazel-really-runs-and.html</guid>
      <description>

&lt;p&gt;There comes a time when working with Bazel when we want to understand the command-line flags used to build our code. For example, you might want to see what flags are being passed to &lt;code&gt;swiftc&lt;/code&gt;. Up until Bazel 9, we would typically rely on &lt;code&gt;--subcommands&lt;/code&gt;, but it could get quite verbose.&lt;/p&gt;

&lt;h2 id=&#34;action-graph-query&#34;&gt;Action graph query&lt;/h2&gt;

&lt;p&gt;In addition to the standard &lt;code&gt;bazel query&lt;/code&gt; command, there are also &lt;code&gt;bazel cquery&lt;/code&gt; (configurable query) and &lt;code&gt;bazel aquery&lt;/code&gt; (action graph query). Each of these helps us explore different parts of the build graph. Since we’re interested in inspecting command-line flags, &lt;code&gt;aquery&lt;/code&gt; is the right tool—it exposes all declared actions, including the exact commands being executed.&lt;/p&gt;

&lt;p&gt;For a project like &lt;a href=&#34;https://github.com/mattrobmattrob/bazel-ios-swiftui-template&#34;&gt;this iOS template&lt;/a&gt;, we can explore how Swift code is compiled by running:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;bazel aquery //app:app.library --output=commands
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Which produces output like:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;bazel-out/darwin_arm64-opt-exec/bin/external/rules_swift+/tools/worker/worker swiftc -target arm64-apple-macos12.6 -sdk __BAZEL_XCODE_SDKROOT__ -file-prefix-map &#39;__BAZEL_XCODE_DEVELOPER_DIR__=/PLACEHOLDER_DEVELOPER_DIR&#39; &#39;-Xwrapped-swift=-bazel-target-label=@@//app:app.library&#39; -emit-object -output-file-map bazel-out/darwin_arm64-fastbuild/bin/app/app.library.output_file_map.json -Xfrontend -no-clang-module-breadcrumbs -emit-module-path bazel-out/darwin_arm64-fastbuild/bin/app/app.swiftmodule &#39;-enforce-exclusivity=checked&#39; -emit-const-values-path bazel-out/darwin_arm64-fastbuild/bin/app/app.library_objs/source/ContentView.swift.swiftconstvalues -Xfrontend -const-gather-protocols-file -Xfrontend external/rules_swift+/swift/toolchains/config/const_protocols_to_gather.json -DDEBUG -Onone -Xfrontend -internalize-at-link -Xfrontend -no-serialize-debugging-options -enable-testing -disable-sandbox -gline-tables-only &#39;-Xwrapped-swift=-file-prefix-pwd-is-dot&#39; -file-prefix-map &#39;__BAZEL_XCODE_DEVELOPER_DIR__=/PLACEHOLDER_DEVELOPER_DIR&#39; -file-compilation-dir . -module-cache-path bazel-out/darwin_arm64-fastbuild/bin/_swift_module_cache -Ibazel-out/darwin_arm64-fastbuild/bin/modules/Models -Ibazel-out/darwin_arm64-fastbuild/bin/modules/API &#39;-Xwrapped-swift=-macro-expansion-dir=bazel-out/darwin_arm64-fastbuild/bin/app/app.library.macro-expansions&#39; -Xcc -iquote. -Xcc -iquotebazel-out/darwin_arm64-fastbuild/bin -Xfrontend -color-diagnostics -enable-batch-mode -module-name app -index-store-path bazel-out/darwin_arm64-fastbuild/bin/app/app.library.indexstore -index-ignore-system-modules &#39;-Xwrapped-swift=-global-index-store-import-path=bazel-out/_global_index_store&#39; -enable-bare-slash-regex -Xfrontend -disable-clang-spi -enable-experimental-feature AccessLevelOnImport -parse-as-library -static -Xcc -O0 -Xcc &#39;-DDEBUG=1&#39; -Xfrontend &#39;-checked-async-objc-bridging=on&#39; app/source/ContentView.swift app/source/MainApp.swift
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;At first glance, this output looks overwhelming. But if you break it down, it’s simply Bazel invoking tools with the appropriate flags.&lt;/p&gt;

&lt;h2 id=&#34;doing-something-useful&#34;&gt;Doing something useful&lt;/h2&gt;

&lt;p&gt;While this output can help us understand what is being executed and how, it becomes much more powerful when used comparatively.&lt;/p&gt;

&lt;p&gt;One practical approach is to diff this output across ruleset versions or Bazel releases. For example:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;bazel aquery //app:app.library --output=commands &amp;gt; commands.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You can generate one file per version and use standard diffing tools to spot regressions or better understand what changed between versions.&lt;/p&gt;

&lt;h2 id=&#34;making-it-executable&#34;&gt;Making it executable&lt;/h2&gt;

&lt;p&gt;In a &lt;a href=&#34;https://www.youtube.com/watch?v=QJUTeD43QlE&#34;&gt;Bazel 9 video by aspect.build&lt;/a&gt;, Alex Eagle shared an interesting idea: turning &lt;code&gt;aquery&lt;/code&gt; output into an executable shell script.&lt;/p&gt;

&lt;p&gt;That idea is what got me intrigued. While the output isn’t directly executable, it seems feasible to get there by replacing placeholder variables, adjusting formatting, and fiddling with cwd. With a bit of effort, this could become a powerful debugging tool.&lt;/p&gt;

&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;This is a small quality-of-life improvement in Bazel 9, but it unlocks a very practical debugging technique.&lt;/p&gt;
</description>
      <source:markdown>There comes a time when working with Bazel when we want to understand the command-line flags used to build our code. For example, you might want to see what flags are being passed to `swiftc`. Up until Bazel 9, we would typically rely on `--subcommands`, but it could get quite verbose.

## Action graph query

In addition to the standard `bazel query` command, there are also `bazel cquery` (configurable query) and `bazel aquery` (action graph query). Each of these helps us explore different parts of the build graph. Since we’re interested in inspecting command-line flags, `aquery` is the right tool—it exposes all declared actions, including the exact commands being executed.

For a project like [this iOS template](https://github.com/mattrobmattrob/bazel-ios-swiftui-template), we can explore how Swift code is compiled by running:

```bash
bazel aquery //app:app.library --output=commands
```

Which produces output like:

```bash
bazel-out/darwin_arm64-opt-exec/bin/external/rules_swift+/tools/worker/worker swiftc -target arm64-apple-macos12.6 -sdk __BAZEL_XCODE_SDKROOT__ -file-prefix-map &#39;__BAZEL_XCODE_DEVELOPER_DIR__=/PLACEHOLDER_DEVELOPER_DIR&#39; &#39;-Xwrapped-swift=-bazel-target-label=@@//app:app.library&#39; -emit-object -output-file-map bazel-out/darwin_arm64-fastbuild/bin/app/app.library.output_file_map.json -Xfrontend -no-clang-module-breadcrumbs -emit-module-path bazel-out/darwin_arm64-fastbuild/bin/app/app.swiftmodule &#39;-enforce-exclusivity=checked&#39; -emit-const-values-path bazel-out/darwin_arm64-fastbuild/bin/app/app.library_objs/source/ContentView.swift.swiftconstvalues -Xfrontend -const-gather-protocols-file -Xfrontend external/rules_swift+/swift/toolchains/config/const_protocols_to_gather.json -DDEBUG -Onone -Xfrontend -internalize-at-link -Xfrontend -no-serialize-debugging-options -enable-testing -disable-sandbox -gline-tables-only &#39;-Xwrapped-swift=-file-prefix-pwd-is-dot&#39; -file-prefix-map &#39;__BAZEL_XCODE_DEVELOPER_DIR__=/PLACEHOLDER_DEVELOPER_DIR&#39; -file-compilation-dir . -module-cache-path bazel-out/darwin_arm64-fastbuild/bin/_swift_module_cache -Ibazel-out/darwin_arm64-fastbuild/bin/modules/Models -Ibazel-out/darwin_arm64-fastbuild/bin/modules/API &#39;-Xwrapped-swift=-macro-expansion-dir=bazel-out/darwin_arm64-fastbuild/bin/app/app.library.macro-expansions&#39; -Xcc -iquote. -Xcc -iquotebazel-out/darwin_arm64-fastbuild/bin -Xfrontend -color-diagnostics -enable-batch-mode -module-name app -index-store-path bazel-out/darwin_arm64-fastbuild/bin/app/app.library.indexstore -index-ignore-system-modules &#39;-Xwrapped-swift=-global-index-store-import-path=bazel-out/_global_index_store&#39; -enable-bare-slash-regex -Xfrontend -disable-clang-spi -enable-experimental-feature AccessLevelOnImport -parse-as-library -static -Xcc -O0 -Xcc &#39;-DDEBUG=1&#39; -Xfrontend &#39;-checked-async-objc-bridging=on&#39; app/source/ContentView.swift app/source/MainApp.swift
...
```

At first glance, this output looks overwhelming. But if you break it down, it’s simply Bazel invoking tools with the appropriate flags.

## Doing something useful

While this output can help us understand what is being executed and how, it becomes much more powerful when used comparatively.

One practical approach is to diff this output across ruleset versions or Bazel releases. For example:

```bash
bazel aquery //app:app.library --output=commands &gt; commands.txt
```

You can generate one file per version and use standard diffing tools to spot regressions or better understand what changed between versions.

## Making it executable

In a [Bazel 9 video by aspect.build](https://www.youtube.com/watch?v=QJUTeD43QlE), Alex Eagle shared an interesting idea: turning `aquery` output into an executable shell script.

That idea is what got me intrigued. While the output isn’t directly executable, it seems feasible to get there by replacing placeholder variables, adjusting formatting, and fiddling with cwd. With a bit of effort, this could become a powerful debugging tool.

## Conclusion

This is a small quality-of-life improvement in Bazel 9, but it unlocks a very practical debugging technique.
</source:markdown>
    </item>
    
    <item>
      <title>Bazel split transitions</title>
      <link>https://adincebic.com/2026/03/22/bazel-split-transitions.html</link>
      <pubDate>Sun, 22 Mar 2026 10:48:39 +0100</pubDate>
      
      <guid>http://adincebic.micro.blog/2026/03/22/bazel-split-transitions.html</guid>
      <description>

&lt;p&gt;A couple of articles ago, I touched on Bazel transitions. In that context, I was referring to 1:1 transitions—transitions that change the single configuration of a target. Split transitions, on the other hand, are 1:N, meaning they build the same target in multiple configurations.&lt;/p&gt;

&lt;h2 id=&#34;multi-arch-build&#34;&gt;Multi-arch build&lt;/h2&gt;

&lt;p&gt;An obvious example where split transitions are useful is multi-platform (or multi-architecture) builds. Because my background is in Swift and Apple platforms, I immediately think of device and simulator builds.&lt;/p&gt;

&lt;p&gt;You can think of a split transition as turning a single dependency edge:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;A → B
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;into multiple ones:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;A → B (device)
  → B (simulator)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In other words, a single dependency is “split” into multiple configurations.&lt;/p&gt;

&lt;h2 id=&#34;multi-platform-swift-library&#34;&gt;Multi-platform Swift library&lt;/h2&gt;

&lt;p&gt;Let’s imagine we want to build a &lt;code&gt;swift_library&lt;/code&gt; for both iOS device and simulator—for example, to validate both environments in CI or to produce multi-platform artifacts.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: In real-world Apple builds, you would typically rely on existing transition support provided by &lt;code&gt;rules_apple&lt;/code&gt; rather than defining your own. This example is intentionally simplified to illustrate how split transitions work under the hood.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To achieve this, we need to define a transition and a wrapper rule.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;def _split_transition_impl(_settings, _attr):
    return {
        &amp;quot;device&amp;quot;: {&amp;quot;//command_line_option:platforms&amp;quot;: &amp;quot;@apple_support//platforms:ios_arm64&amp;quot;},
        &amp;quot;sim&amp;quot;: {&amp;quot;//command_line_option:platforms&amp;quot;: &amp;quot;@apple_support//platforms:ios_sim_arm64&amp;quot;},
    }

split_transition = transition(
    implementation = _split_transition_impl,
    inputs = [],
    outputs = [&amp;quot;//command_line_option:platforms&amp;quot;],
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This illustrates why they’re called &lt;em&gt;split&lt;/em&gt; transitions: a single dependency edge is expanded into multiple configurations, each identified by a key (&lt;code&gt;device&lt;/code&gt;, &lt;code&gt;sim&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Next, we define a rule that applies the transition:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;def _multi_platform_swift_library(ctx):
    propagated_files = []

    for split_deps in ctx.split_attr.deps.values():
        for dep in split_deps:
            propagated_files.append(dep[DefaultInfo].files)

    return [
        DefaultInfo(
            files = depset(transitive = propagated_files),
        ),
    ]

multi_platform_swift_library = rule(
    implementation = _multi_platform_swift_library,
    attrs = {
        &amp;quot;deps&amp;quot;: attr.label_list(cfg = split_transition),
    },
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here, &lt;code&gt;ctx.split_attr.deps&lt;/code&gt; is a dictionary where each key (&lt;code&gt;device&lt;/code&gt;, &lt;code&gt;sim&lt;/code&gt;) maps to the list of dependencies built in that configuration.&lt;/p&gt;

&lt;p&gt;We simply propagate the files from the dependencies so that they are built—this keeps the example minimal while still demonstrating the transition.&lt;/p&gt;

&lt;p&gt;Finally, in &lt;code&gt;BUILD.bazel&lt;/code&gt;, we define targets using these rules:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;load(&amp;quot;@rules_swift//swift:swift_library.bzl&amp;quot;, &amp;quot;swift_library&amp;quot;)
load(&amp;quot;//:rules.bzl&amp;quot;, &amp;quot;multi_platform_swift_library&amp;quot;)

swift_library(
    name = &amp;quot;lib&amp;quot;,
    module_name = &amp;quot;Library&amp;quot;,
    srcs = [&amp;quot;main.swift&amp;quot;],
)

multi_platform_swift_library(
    name = &amp;quot;mp&amp;quot;,
    deps = [&amp;quot;:lib&amp;quot;],
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To verify that the transition has been applied, we can use Bazel’s configuration query:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;bazel cquery //:mp --transitions=full
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This will produce output similar to:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;INFO: Analyzed target //:mp (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
NoTransition -&amp;gt; //:mp (680dcaf)
  deps#//:lib#(Starlark transition:/Users/adincebic/developer.noindex/split/rules.bzl:8:30 + (TestTrimmingTransition + ConfigFeatureFlagTaggedTrimmingTransition)) -&amp;gt; 58f99a6, c9f1a2b
    platforms:[@@bazel_tools//tools:host_platform] -&amp;gt; [[@@apple_support+//platforms:ios_arm64], [@@apple_support+//platforms:ios_sim_arm64]]
  $allowlist_function_transition#@bazel_tools//tools/allowlists/function_transition_allowlist:function_transition_allowlist#(null transition) -&amp;gt; 
INFO: Elapsed time: 0.067s, Critical Path: 0.00s
INFO: 0 processes.
INFO: Build completed successfully, 0 total actions
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Notice how &lt;code&gt;//:lib&lt;/code&gt; is configured twice—once for each platform (&lt;code&gt;ios_arm64&lt;/code&gt; and &lt;code&gt;ios_sim_arm64&lt;/code&gt;). This confirms that the split transition produced multiple configurations.&lt;/p&gt;

&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Bazel transitions are a powerful tool, but they should be used sparingly. They fundamentally alter the build graph, and split transitions in particular can significantly increase its size since dependencies may be built multiple times.&lt;/p&gt;

&lt;p&gt;Conceptually, however, split transitions are no more complex than standard 1:1 transitions—they simply require using &lt;code&gt;split_attr&lt;/code&gt; instead of &lt;code&gt;attr&lt;/code&gt;, and thinking in terms of one dependency becoming many.&lt;/p&gt;
</description>
      <source:markdown>A couple of articles ago, I touched on Bazel transitions. In that context, I was referring to 1:1 transitions—transitions that change the single configuration of a target. Split transitions, on the other hand, are 1:N, meaning they build the same target in multiple configurations.

## Multi-arch build

An obvious example where split transitions are useful is multi-platform (or multi-architecture) builds. Because my background is in Swift and Apple platforms, I immediately think of device and simulator builds.

You can think of a split transition as turning a single dependency edge:

```
A → B
```

into multiple ones:

```
A → B (device)
  → B (simulator)
```

In other words, a single dependency is “split” into multiple configurations.

## Multi-platform Swift library

Let’s imagine we want to build a `swift_library` for both iOS device and simulator—for example, to validate both environments in CI or to produce multi-platform artifacts.

&gt; Note: In real-world Apple builds, you would typically rely on existing transition support provided by `rules_apple` rather than defining your own. This example is intentionally simplified to illustrate how split transitions work under the hood.

To achieve this, we need to define a transition and a wrapper rule.

```starlark
def _split_transition_impl(_settings, _attr):
    return {
        &#34;device&#34;: {&#34;//command_line_option:platforms&#34;: &#34;@apple_support//platforms:ios_arm64&#34;},
        &#34;sim&#34;: {&#34;//command_line_option:platforms&#34;: &#34;@apple_support//platforms:ios_sim_arm64&#34;},
    }

split_transition = transition(
    implementation = _split_transition_impl,
    inputs = [],
    outputs = [&#34;//command_line_option:platforms&#34;],
)
```

This illustrates why they’re called *split* transitions: a single dependency edge is expanded into multiple configurations, each identified by a key (`device`, `sim`).

Next, we define a rule that applies the transition:

```starlark
def _multi_platform_swift_library(ctx):
    propagated_files = []

    for split_deps in ctx.split_attr.deps.values():
        for dep in split_deps:
            propagated_files.append(dep[DefaultInfo].files)

    return [
        DefaultInfo(
            files = depset(transitive = propagated_files),
        ),
    ]

multi_platform_swift_library = rule(
    implementation = _multi_platform_swift_library,
    attrs = {
        &#34;deps&#34;: attr.label_list(cfg = split_transition),
    },
)
```

Here, `ctx.split_attr.deps` is a dictionary where each key (`device`, `sim`) maps to the list of dependencies built in that configuration.

We simply propagate the files from the dependencies so that they are built—this keeps the example minimal while still demonstrating the transition.

Finally, in `BUILD.bazel`, we define targets using these rules:

```starlark
load(&#34;@rules_swift//swift:swift_library.bzl&#34;, &#34;swift_library&#34;)
load(&#34;//:rules.bzl&#34;, &#34;multi_platform_swift_library&#34;)

swift_library(
    name = &#34;lib&#34;,
    module_name = &#34;Library&#34;,
    srcs = [&#34;main.swift&#34;],
)

multi_platform_swift_library(
    name = &#34;mp&#34;,
    deps = [&#34;:lib&#34;],
)
```

To verify that the transition has been applied, we can use Bazel’s configuration query:

```bash
bazel cquery //:mp --transitions=full
```

This will produce output similar to:

```bash
INFO: Analyzed target //:mp (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
NoTransition -&gt; //:mp (680dcaf)
  deps#//:lib#(Starlark transition:/Users/adincebic/developer.noindex/split/rules.bzl:8:30 + (TestTrimmingTransition + ConfigFeatureFlagTaggedTrimmingTransition)) -&gt; 58f99a6, c9f1a2b
    platforms:[@@bazel_tools//tools:host_platform] -&gt; [[@@apple_support+//platforms:ios_arm64], [@@apple_support+//platforms:ios_sim_arm64]]
  $allowlist_function_transition#@bazel_tools//tools/allowlists/function_transition_allowlist:function_transition_allowlist#(null transition) -&gt; 
INFO: Elapsed time: 0.067s, Critical Path: 0.00s
INFO: 0 processes.
INFO: Build completed successfully, 0 total actions
```

Notice how `//:lib` is configured twice—once for each platform (`ios_arm64` and `ios_sim_arm64`). This confirms that the split transition produced multiple configurations.

## Conclusion

Bazel transitions are a powerful tool, but they should be used sparingly. They fundamentally alter the build graph, and split transitions in particular can significantly increase its size since dependencies may be built multiple times.

Conceptually, however, split transitions are no more complex than standard 1:1 transitions—they simply require using `split_attr` instead of `attr`, and thinking in terms of one dependency becoming many.
</source:markdown>
    </item>
    
    <item>
      <title>How to Fix Xcode Source Editor Extensions That Don’t Appear in the Editor Menu</title>
      <link>https://adincebic.com/2026/03/15/how-to-fix-xcode-source.html</link>
      <pubDate>Sun, 15 Mar 2026 19:35:16 +0100</pubDate>
      
      <guid>http://adincebic.micro.blog/2026/03/15/how-to-fix-xcode-source.html</guid>
      <description>

&lt;p&gt;Recently I needed to develop my own Xcode source editor extension. The reasons for doing so aren’t relevant here, but the process quickly led me into an unexpected roadblock.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; In your extension target settings, set &lt;strong&gt;&lt;code&gt;XcodeKit.framework&lt;/code&gt; → “Embed without signing”&lt;/strong&gt; under the &lt;strong&gt;General&lt;/strong&gt; tab.&lt;/p&gt;

&lt;h2 id=&#34;extension-not-showing-up-in-xcode&#34;&gt;Extension not showing up in Xcode&lt;/h2&gt;

&lt;p&gt;After following Apple’s official guide for creating a source editor extension (a macOS app with an extension target), I ran the extension scheme to test it. However, the extension neither appeared in Xcode nor executed any code.&lt;/p&gt;

&lt;p&gt;Normally, a source editor extension should appear in two places:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;macOS System Settings&lt;/strong&gt; (Extensions → Xcode Source Editor)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Xcode’s Editor menu&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In my case, the extension showed up in System Settings but &lt;strong&gt;did not appear in Xcode&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&#34;asking-around&#34;&gt;Asking around&lt;/h2&gt;

&lt;p&gt;Like most developers, I immediately turned to Google and AI tools. It quickly became clear that this issue is not uncommon. Unfortunately, none of the suggested workarounds solved my problem.&lt;/p&gt;

&lt;h2 id=&#34;sifting-through-xcode-logs&#34;&gt;Sifting through Xcode logs&lt;/h2&gt;

&lt;p&gt;After exhausting the usual fixes, I wondered whether Xcode might be logging something useful while attempting to load extensions.&lt;/p&gt;

&lt;p&gt;Using the unified logging system, I started streaming Xcode logs with a predicate to filter relevant messages:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;log stream --style compact --predicate &#39;process == &amp;quot;Xcode&amp;quot; &amp;amp;&amp;amp; (eventMessage CONTAINS[c] &amp;quot;EditorExtension&amp;quot; || eventMessage CONTAINS[c] &amp;quot;XcodeKit&amp;quot;)&#39;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;After running the extension again, I noticed the following message:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Xcode Extension does not incorporate XcodeKit&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This was finally a clue.&lt;/p&gt;

&lt;h2 id=&#34;looking-at-existing-extensions&#34;&gt;Looking at existing extensions&lt;/h2&gt;

&lt;p&gt;My next step was to inspect existing open-source extensions. I looked at projects like &lt;strong&gt;SwiftFormat&lt;/strong&gt; and compared their release artifacts with the one produced by my own extension.&lt;/p&gt;

&lt;h2 id=&#34;missing-xcodekit-framework&#34;&gt;Missing &lt;code&gt;XcodeKit.framework&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;While inspecting the extension bundle, one difference stood out immediately: my extension &lt;strong&gt;was not bundling &lt;code&gt;XcodeKit.framework&lt;/code&gt;&lt;/strong&gt;, while SwiftFormat’s extension was.&lt;/p&gt;

&lt;p&gt;I also noticed that SwiftFormat’s release workflow explicitly ensures that &lt;code&gt;XcodeKit.framework&lt;/code&gt; is bundled into the extension archive.&lt;/p&gt;

&lt;h2 id=&#34;the-fix-embed-without-signing&#34;&gt;The fix: Embed without signing&lt;/h2&gt;

&lt;p&gt;It turns out the default Xcode template for source editor extensions is &lt;strong&gt;misconfigured&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;By default, &lt;code&gt;XcodeKit.framework&lt;/code&gt; is set to &lt;strong&gt;“Do Not Embed”&lt;/strong&gt; in the extension target settings. Because of this, the framework never gets bundled with the extension.&lt;/p&gt;

&lt;p&gt;Changing the setting to:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;General → Frameworks, and Libraries → &lt;code&gt;XcodeKit.framework&lt;/code&gt; → “Embed without signing”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;fixes the issue and allows Xcode to load the extension correctly.&lt;/p&gt;

&lt;h2 id=&#34;closing-thoughts&#34;&gt;Closing thoughts&lt;/h2&gt;

&lt;p&gt;Problems like this are especially frustrating when there’s little to no documentation online and even AI tools can’t identify the cause.&lt;/p&gt;

&lt;p&gt;One useful takeaway is that Apple heavily relies on the &lt;strong&gt;Unified Logging System&lt;/strong&gt; across their apps, tools and macOS in general. When something misbehaves, inspecting logs can provide a path forward.&lt;/p&gt;
</description>
      <source:markdown>Recently I needed to develop my own Xcode source editor extension. The reasons for doing so aren’t relevant here, but the process quickly led me into an unexpected roadblock.

**TL;DR:** In your extension target settings, set **`XcodeKit.framework` → “Embed without signing”** under the **General** tab.

## Extension not showing up in Xcode

After following Apple’s official guide for creating a source editor extension (a macOS app with an extension target), I ran the extension scheme to test it. However, the extension neither appeared in Xcode nor executed any code.

Normally, a source editor extension should appear in two places:

* **macOS System Settings** (Extensions → Xcode Source Editor)
* **Xcode’s Editor menu**

In my case, the extension showed up in System Settings but **did not appear in Xcode**.

## Asking around

Like most developers, I immediately turned to Google and AI tools. It quickly became clear that this issue is not uncommon. Unfortunately, none of the suggested workarounds solved my problem.

## Sifting through Xcode logs

After exhausting the usual fixes, I wondered whether Xcode might be logging something useful while attempting to load extensions.

Using the unified logging system, I started streaming Xcode logs with a predicate to filter relevant messages:

```bash
log stream --style compact --predicate &#39;process == &#34;Xcode&#34; &amp;&amp; (eventMessage CONTAINS[c] &#34;EditorExtension&#34; || eventMessage CONTAINS[c] &#34;XcodeKit&#34;)&#39;
```

After running the extension again, I noticed the following message:

&gt; Xcode Extension does not incorporate XcodeKit

This was finally a clue.

## Looking at existing extensions

My next step was to inspect existing open-source extensions. I looked at projects like **SwiftFormat** and compared their release artifacts with the one produced by my own extension.

## Missing `XcodeKit.framework`

While inspecting the extension bundle, one difference stood out immediately: my extension **was not bundling `XcodeKit.framework`**, while SwiftFormat’s extension was.

I also noticed that SwiftFormat’s release workflow explicitly ensures that `XcodeKit.framework` is bundled into the extension archive.

## The fix: Embed without signing

It turns out the default Xcode template for source editor extensions is **misconfigured**.

By default, `XcodeKit.framework` is set to **“Do Not Embed”** in the extension target settings. Because of this, the framework never gets bundled with the extension.

Changing the setting to:

**General → Frameworks, and Libraries → `XcodeKit.framework` → “Embed without signing”**

fixes the issue and allows Xcode to load the extension correctly.

## Closing thoughts

Problems like this are especially frustrating when there’s little to no documentation online and even AI tools can’t identify the cause.

One useful takeaway is that Apple heavily relies on the **Unified Logging System** across their apps, tools and macOS in general. When something misbehaves, inspecting logs can provide a path forward.
</source:markdown>
    </item>
    
    <item>
      <title>Managing Bazel Flags in Monorepos with Flagsets (PROJECT.scl)</title>
      <link>https://adincebic.com/2026/03/08/managing-bazel-flags-in-monorepos.html</link>
      <pubDate>Sun, 08 Mar 2026 14:10:15 +0100</pubDate>
      
      <guid>http://adincebic.micro.blog/2026/03/08/managing-bazel-flags-in-monorepos.html</guid>
      <description>

&lt;p&gt;Bazel&amp;rsquo;s flagsets, or &lt;code&gt;PROJECT.scl&lt;/code&gt;, are an approach aimed at solving project-specific flags in a monorepo. It leverages Starlark as its language, or more precisely, a subset of Starlark. This is in contrast to the traditional &lt;code&gt;.bazelrc&lt;/code&gt;, which uses its own line-based language and has no formal specification.&lt;/p&gt;

&lt;h2 id=&#34;current-state-of-the-art&#34;&gt;Current state of the art&lt;/h2&gt;

&lt;p&gt;At the time of writing this, we are almost certainly all using &lt;code&gt;.bazelrc&lt;/code&gt; files to hide away command line flags as well as to define configs (de facto sets of flags). My take is that &lt;code&gt;.bazelrc&lt;/code&gt; is fine and will continue to work well for many people in a multi-repo setup. However, &lt;code&gt;.bazelrc&lt;/code&gt; not only does not scale well in monorepos (yes, it is possible to compose them via &lt;code&gt;import&lt;/code&gt;) but it can also lead to incorrect builds during day-to-day development.&lt;/p&gt;

&lt;p&gt;Say we are working in a monorepo with both iOS and Android apps and we want to build iOS by executing:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;bazel build //ios:app
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;At first glance, nothing seems wrong. However, all the flags that we use for Android are also applied when building iOS, and vice versa. Granted, this can be fine, but there might be a feature flag for C++ that both platforms use in different ways. In that scenario we always need to make sure that we explicitly apply the given flag differently for each platform or find some other way.&lt;/p&gt;

&lt;p&gt;I&amp;rsquo;m sure there are many more examples one could come up with.&lt;/p&gt;

&lt;h2 id=&#34;the-solution&#34;&gt;The solution&lt;/h2&gt;

&lt;p&gt;Project-specific &lt;code&gt;PROJECT.scl&lt;/code&gt; files, aka flagsets. They were introduced at BazelCon 2025 in Bazel 9 as an experimental feature. However, they are currently not behind a feature flag, so if you are on Bazel 9 you can start using them today and help discover interesting corner cases.&lt;/p&gt;

&lt;h3 id=&#34;example-ios-app&#34;&gt;Example iOS app&lt;/h3&gt;

&lt;p&gt;Imagine we are in a monorepo where each app is its own project (separate directory) like &lt;code&gt;iOS/&lt;/code&gt;, &lt;code&gt;Android/&lt;/code&gt;, and so on. So for the iOS app we would want to define dev and store configurations which obviously build the app in different ways. We would create a &lt;code&gt;PROJECT.scl&lt;/code&gt; in the iOS subdirectory and give it the following contents:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;load(&amp;quot;//:project_proto.scl&amp;quot;, &amp;quot;buildable_unit_pb2&amp;quot;, &amp;quot;project_pb2&amp;quot;)

project = project_pb2.Project.create(
    buildable_units = [
        # Since this buildable unit sets &amp;quot;is_default = True&amp;quot;, these flags apply
        # to any target in this package or its subpackages by default. You can
        # also request these flags explicitly with &amp;quot;--scl_config=dev_config&amp;quot; or &amp;quot;--scl_config=store_config&amp;quot;.
        buildable_unit_pb2.BuildableUnit.create(
            name = &amp;quot;dev_config&amp;quot;,
            flags = [
                &amp;quot;--compilation_mode=dbg&amp;quot;,
            ],
            is_default = True,
            description = &amp;quot;Default debug configuration used for development.&amp;quot;,
        ),

        buildable_unit_pb2.BuildableUnit.create(
            name = &amp;quot;store_config&amp;quot;,
            flags = [
                &amp;quot;--compilation_mode=opt&amp;quot;,
                &#39;--ios_multi_cpus=arm64&#39;,
            ],
            description = &amp;quot;Store configuration.&amp;quot;,
        ),
    ],
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I feel like this is pretty self-explanatory and demonstrates the basic idea. When building the app, Bazel 9 will take the &lt;code&gt;PROJECT.scl&lt;/code&gt; closest to the target on the filesystem into account when applying flags. If we want to build for the store, the only thing required is to set &lt;code&gt;--scl_config=store_config&lt;/code&gt;, much like &lt;code&gt;--config&lt;/code&gt; in &lt;code&gt;.bazelrc&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&#34;enforcement-policies&#34;&gt;Enforcement policies&lt;/h3&gt;

&lt;p&gt;One of the more interesting features of flagsets is that we can define enforcement policies which basically can either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;WARN&lt;/code&gt;: warns you if you add additional flags via the command line or &lt;code&gt;.bazelrc&lt;/code&gt;. This is the default, so it doesn&amp;rsquo;t have to be explicitly specified.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;COMPATIBLE&lt;/code&gt;: disallows setting conflicting flags via the command line or &lt;code&gt;.bazelrc&lt;/code&gt;, but allows flags that do not interfere with those specified in &lt;code&gt;PROJECT.scl&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;STRICT&lt;/code&gt;: disallows setting any flags via the command line or &lt;code&gt;.bazelrc&lt;/code&gt;, so everything must be defined in the relevant &lt;code&gt;PROJECT.scl&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To set one of the policies above, we use the &lt;code&gt;enforcement_policy&lt;/code&gt; attribute on &lt;code&gt;project_pb2.Project.create&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;load(&amp;quot;//:project_proto.scl&amp;quot;, &amp;quot;buildable_unit_pb2&amp;quot;, &amp;quot;project_pb2&amp;quot;)

project = project_pb2.Project.create(
    enforcement_policy = &amp;quot;STRICT&amp;quot;,
    buildable_units = [
        buildable_unit_pb2.BuildableUnit.create(
            name = &amp;quot;dev_config&amp;quot;,
            flags = [
                &amp;quot;--compilation_mode=dbg&amp;quot;,
            ],
            is_default = True,
            description = &amp;quot;Default debug configuration used for development.&amp;quot;,
        ),

        buildable_unit_pb2.BuildableUnit.create(
            name = &amp;quot;store_config&amp;quot;,
            flags = [
                &amp;quot;--compilation_mode=opt&amp;quot;,
                &#39;--ios_multi_cpus=arm64&#39;,
            ],
            description = &amp;quot;Store configuration.&amp;quot;,
        ),
    ],
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;After building the target we would get the following output if additional flags are applied other than those in &lt;code&gt;PROJECT.scl&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;INFO: Reading project settings from //ios:PROJECT.scl.
ERROR: Cannot parse options: This build uses a project file (//:PROJECT.scl) that does not allow output-affecting flags in the command line or user bazelrc. Found [&#39;--macos_minimum_os=12.6&#39;, &#39;--flag_alias=build_python_zip=@@rules_python+//python/config_settings:build_python_zip&#39;, &#39;--ios_simulator_version=18.5&#39;, &#39;--flag_alias=incompatible_default_to_explicit_init_py=@@rules_python+//python/config_settings:incompatible_default_to_explicit_init_py&#39;, &#39;--modify_execution_info=^(BitcodeSymbolsCopy|BundleApp|BundleTreeApp|DsymDwarf|DsymLipo|GenerateAppleSymbolsFile|ObjcBinarySymbolStrip|CppLink|ObjcLink|ProcessAndSign|SignBinary|SwiftArchive|SwiftStdlibCopy)$=+no-remote,^(BundleResources|ImportedDynamicFrameworkProcessor)$=+no-remote-exec&#39;, &#39;--flag_alias=python_path=@@rules_python+//python/config_settings:python_path&#39;, &#39;--features=swift.index_while_building&#39;, &#39;--macos_minimum_os=13&#39;, &#39;--incompatible_strict_action_env&#39;, &#39;--features=swift.use_global_index_store&#39;, &#39;--flag_alias=experimental_python_import_all_repositories=@@rules_python+//python/config_settings:experimental_python_import_all_repositories&#39;, &#39;--host_macos_minimum_os=13&#39;, &#39;--features=swift.use_global_module_cache&#39;]. Please remove these flags or disable project file resolution via --noenforce_project_configs.
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;target-specific-flags&#34;&gt;Target-specific flags&lt;/h3&gt;

&lt;p&gt;Another interesting aspect of flagsets is the ability to set flags on a per-target basis. There is an attribute called &lt;code&gt;target_patterns&lt;/code&gt; on &lt;code&gt;buildable_unit_pb2.BuildableUnit.create&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;            target_patterns = [
                &amp;quot;//target_specific:one&amp;quot;,
            ],
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The following patterns are supported (taken from Bazel documentation and examples):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;//some:target&lt;/code&gt;      (specific target)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-//some:target&lt;/code&gt;     (exclude //some:target from this filter)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;//some/path/...&lt;/code&gt;    (all targets below a path)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-//some/path/...&lt;/code&gt;   (exclude all targets below a path)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;in-conclusion&#34;&gt;In conclusion&lt;/h2&gt;

&lt;p&gt;I believe this is a great step forward for improving how Bazel flags are managed. It is still very early days and there are many unanswered questions, such as what happens if a project depends on another project, and many others I probably haven&amp;rsquo;t even thought of yet. As time goes on, we will discover best practices and flagsets as a feature of Bazel will evolve to support more scenarios.&lt;/p&gt;

&lt;p&gt;To learn more about the decisions that went into the design of flagsets, please see &lt;a href=&#34;https://www.youtube.com/watch?v=kqMOwABJguc&#34;&gt;this talk on Youtube&lt;/a&gt; by the Bazel team.&lt;/p&gt;
</description>
      <source:markdown>Bazel&#39;s flagsets, or `PROJECT.scl`, are an approach aimed at solving project-specific flags in a monorepo. It leverages Starlark as its language, or more precisely, a subset of Starlark. This is in contrast to the traditional `.bazelrc`, which uses its own line-based language and has no formal specification.

## Current state of the art

At the time of writing this, we are almost certainly all using `.bazelrc` files to hide away command line flags as well as to define configs (de facto sets of flags). My take is that `.bazelrc` is fine and will continue to work well for many people in a multi-repo setup. However, `.bazelrc` not only does not scale well in monorepos (yes, it is possible to compose them via `import`) but it can also lead to incorrect builds during day-to-day development.

Say we are working in a monorepo with both iOS and Android apps and we want to build iOS by executing:

```bash
bazel build //ios:app
```

At first glance, nothing seems wrong. However, all the flags that we use for Android are also applied when building iOS, and vice versa. Granted, this can be fine, but there might be a feature flag for C++ that both platforms use in different ways. In that scenario we always need to make sure that we explicitly apply the given flag differently for each platform or find some other way.

I&#39;m sure there are many more examples one could come up with.

## The solution

Project-specific `PROJECT.scl` files, aka flagsets. They were introduced at BazelCon 2025 in Bazel 9 as an experimental feature. However, they are currently not behind a feature flag, so if you are on Bazel 9 you can start using them today and help discover interesting corner cases.

### Example iOS app

Imagine we are in a monorepo where each app is its own project (separate directory) like `iOS/`, `Android/`, and so on. So for the iOS app we would want to define dev and store configurations which obviously build the app in different ways. We would create a `PROJECT.scl` in the iOS subdirectory and give it the following contents:

```starlark
load(&#34;//:project_proto.scl&#34;, &#34;buildable_unit_pb2&#34;, &#34;project_pb2&#34;)

project = project_pb2.Project.create(
    buildable_units = [
        # Since this buildable unit sets &#34;is_default = True&#34;, these flags apply
        # to any target in this package or its subpackages by default. You can
        # also request these flags explicitly with &#34;--scl_config=dev_config&#34; or &#34;--scl_config=store_config&#34;.
        buildable_unit_pb2.BuildableUnit.create(
            name = &#34;dev_config&#34;,
            flags = [
                &#34;--compilation_mode=dbg&#34;,
            ],
            is_default = True,
            description = &#34;Default debug configuration used for development.&#34;,
        ),

        buildable_unit_pb2.BuildableUnit.create(
            name = &#34;store_config&#34;,
            flags = [
                &#34;--compilation_mode=opt&#34;,
                &#39;--ios_multi_cpus=arm64&#39;,
            ],
            description = &#34;Store configuration.&#34;,
        ),
    ],
)
```

I feel like this is pretty self-explanatory and demonstrates the basic idea. When building the app, Bazel 9 will take the `PROJECT.scl` closest to the target on the filesystem into account when applying flags. If we want to build for the store, the only thing required is to set `--scl_config=store_config`, much like `--config` in `.bazelrc`.

### Enforcement policies

One of the more interesting features of flagsets is that we can define enforcement policies which basically can either:

* `WARN`: warns you if you add additional flags via the command line or `.bazelrc`. This is the default, so it doesn&#39;t have to be explicitly specified.
* `COMPATIBLE`: disallows setting conflicting flags via the command line or `.bazelrc`, but allows flags that do not interfere with those specified in `PROJECT.scl`.
* `STRICT`: disallows setting any flags via the command line or `.bazelrc`, so everything must be defined in the relevant `PROJECT.scl`.

To set one of the policies above, we use the `enforcement_policy` attribute on `project_pb2.Project.create`:

```starlark
load(&#34;//:project_proto.scl&#34;, &#34;buildable_unit_pb2&#34;, &#34;project_pb2&#34;)

project = project_pb2.Project.create(
    enforcement_policy = &#34;STRICT&#34;,
    buildable_units = [
        buildable_unit_pb2.BuildableUnit.create(
            name = &#34;dev_config&#34;,
            flags = [
                &#34;--compilation_mode=dbg&#34;,
            ],
            is_default = True,
            description = &#34;Default debug configuration used for development.&#34;,
        ),

        buildable_unit_pb2.BuildableUnit.create(
            name = &#34;store_config&#34;,
            flags = [
                &#34;--compilation_mode=opt&#34;,
                &#39;--ios_multi_cpus=arm64&#39;,
            ],
            description = &#34;Store configuration.&#34;,
        ),
    ],
)
```

After building the target we would get the following output if additional flags are applied other than those in `PROJECT.scl`:

```bash
INFO: Reading project settings from //ios:PROJECT.scl.
ERROR: Cannot parse options: This build uses a project file (//:PROJECT.scl) that does not allow output-affecting flags in the command line or user bazelrc. Found [&#39;--macos_minimum_os=12.6&#39;, &#39;--flag_alias=build_python_zip=@@rules_python+//python/config_settings:build_python_zip&#39;, &#39;--ios_simulator_version=18.5&#39;, &#39;--flag_alias=incompatible_default_to_explicit_init_py=@@rules_python+//python/config_settings:incompatible_default_to_explicit_init_py&#39;, &#39;--modify_execution_info=^(BitcodeSymbolsCopy|BundleApp|BundleTreeApp|DsymDwarf|DsymLipo|GenerateAppleSymbolsFile|ObjcBinarySymbolStrip|CppLink|ObjcLink|ProcessAndSign|SignBinary|SwiftArchive|SwiftStdlibCopy)$=+no-remote,^(BundleResources|ImportedDynamicFrameworkProcessor)$=+no-remote-exec&#39;, &#39;--flag_alias=python_path=@@rules_python+//python/config_settings:python_path&#39;, &#39;--features=swift.index_while_building&#39;, &#39;--macos_minimum_os=13&#39;, &#39;--incompatible_strict_action_env&#39;, &#39;--features=swift.use_global_index_store&#39;, &#39;--flag_alias=experimental_python_import_all_repositories=@@rules_python+//python/config_settings:experimental_python_import_all_repositories&#39;, &#39;--host_macos_minimum_os=13&#39;, &#39;--features=swift.use_global_module_cache&#39;]. Please remove these flags or disable project file resolution via --noenforce_project_configs.
```

### Target-specific flags

Another interesting aspect of flagsets is the ability to set flags on a per-target basis. There is an attribute called `target_patterns` on `buildable_unit_pb2.BuildableUnit.create`:

```starlark
            target_patterns = [
                &#34;//target_specific:one&#34;,
            ],
```

The following patterns are supported (taken from Bazel documentation and examples):

* `//some:target`      (specific target)
* `-//some:target`     (exclude //some:target from this filter)
* `//some/path/...`    (all targets below a path)
* `-//some/path/...`   (exclude all targets below a path)

## In conclusion

I believe this is a great step forward for improving how Bazel flags are managed. It is still very early days and there are many unanswered questions, such as what happens if a project depends on another project, and many others I probably haven&#39;t even thought of yet. As time goes on, we will discover best practices and flagsets as a feature of Bazel will evolve to support more scenarios.

To learn more about the decisions that went into the design of flagsets, please see [this talk on Youtube](https://www.youtube.com/watch?v=kqMOwABJguc) by the Bazel team.
</source:markdown>
    </item>
    
    <item>
      <title>Composing Bazel rules with subrules</title>
      <link>https://adincebic.com/2026/03/01/composing-bazel-rules-with-subrules.html</link>
      <pubDate>Sun, 01 Mar 2026 12:59:58 +0100</pubDate>
      
      <guid>http://adincebic.micro.blog/2026/03/01/composing-bazel-rules-with-subrules.html</guid>
      <description>

&lt;p&gt;Bazel &lt;strong&gt;subrules&lt;/strong&gt; are a lesser-known mechanism for splitting rule functionality into smaller, reusable building blocks. They are designed to improve rule architecture by encapsulating implicit dependencies, toolchains, and action logic — without passing the entire &lt;code&gt;ctx&lt;/code&gt; (“god object”) around.&lt;/p&gt;

&lt;p&gt;In the past, we achieved similar reuse through plain Starlark helper functions. While that works, it often requires threading &lt;code&gt;ctx&lt;/code&gt; through multiple layers, which reduces encapsulation and makes refactoring harder.&lt;/p&gt;

&lt;h2 id=&#34;subrules&#34;&gt;Subrules&lt;/h2&gt;

&lt;p&gt;A subrule is similar to a rule in that it can create actions during analysis. However, it operates under strict encapsulation constraints and exposes only a reduced context API.&lt;/p&gt;

&lt;p&gt;Let’s start with a simple example.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;def _hello_subrule_impl(ctx, name):
    hello_file = ctx.actions.declare_file(ctx.label.name + name + &amp;quot;.txt&amp;quot;)
    return hello_file

hello_subrule = subrule(
    implementation = _hello_subrule_impl,
)

def _hello_impl(ctx):
    hello_file = hello_subrule(name = &amp;quot;Example&amp;quot;)

    ctx.actions.write(
        output = hello_file,
        content = &amp;quot;Hello, world!&amp;quot;,
    )

    return [
        DefaultInfo(files = depset([hello_file])),
    ]

hello = rule(
    implementation = _hello_impl,
    subrules = [hello_subrule],
)
&lt;/code&gt;&lt;/pre&gt;

&lt;ol&gt;
&lt;li&gt;We define &lt;code&gt;hello_subrule&lt;/code&gt; using &lt;code&gt;subrule()&lt;/code&gt; and provide its implementation.&lt;/li&gt;
&lt;li&gt;The implementation function receives a restricted &lt;code&gt;ctx&lt;/code&gt; (a &lt;code&gt;SubruleContext&lt;/code&gt;) and any additional parameters.&lt;/li&gt;
&lt;li&gt;When defining the &lt;code&gt;hello&lt;/code&gt; rule, we must declare &lt;code&gt;subrules = [hello_subrule]&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Inside the rule implementation, we call the subrule like a normal function:&lt;/li&gt;
&lt;/ol&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;   hello_file = hello_subrule(name = &amp;quot;Example&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We do &lt;strong&gt;not&lt;/strong&gt; call it via &lt;code&gt;ctx&lt;/code&gt;. The &lt;code&gt;ctx&lt;/code&gt; argument is automatically injected.&lt;/p&gt;

&lt;p&gt;Subrules may return arbitrary values — not just providers.&lt;/p&gt;

&lt;h2 id=&#34;important-subrules-must-be-declared&#34;&gt;Important: Subrules Must Be Declared&lt;/h2&gt;

&lt;p&gt;If you call a subrule but forget to list it in the &lt;code&gt;subrules = [...]&lt;/code&gt; parameter of the rule (or another subrule), Bazel raises a runtime error during analysis.&lt;/p&gt;

&lt;h2 id=&#34;what-you-can-and-cannot-do&#34;&gt;What You Can and Cannot Do&lt;/h2&gt;

&lt;p&gt;Subrules can create actions just like rules, but they are intentionally constrained for better encapsulation.&lt;/p&gt;

&lt;h3 id=&#34;all-attributes-must-be-private&#34;&gt;All Attributes Must Be Private&lt;/h3&gt;

&lt;p&gt;Subrules can declare only &lt;strong&gt;implicit dependencies&lt;/strong&gt;, and their attribute names must begin with an underscore.&lt;/p&gt;

&lt;p&gt;This results in an error:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;hello_subrule = subrule(
    implementation = _hello_subrule_impl,
    attrs = {
        &amp;quot;compiler&amp;quot;: attr.label(),
    },
)
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code&gt;Error in subrule: illegal attribute name &#39;compiler&#39;: subrules may only define private attributes (whose names begin with &#39;_&#39;).
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;attributes-must-be-label-or-label-list&#34;&gt;Attributes Must Be label or label_list&lt;/h3&gt;

&lt;p&gt;Subrule attributes may only be &lt;code&gt;attr.label()&lt;/code&gt; or &lt;code&gt;attr.label_list()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Other attribute types (e.g., &lt;code&gt;attr.string()&lt;/code&gt;, &lt;code&gt;attr.bool()&lt;/code&gt;) are not allowed:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;attrs = {
    &amp;quot;_compiler&amp;quot;: attr.string(),
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Produces:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Error in subrule: bad type for attribute &#39;_compiler&#39;: subrule attributes may only be label or lists of labels.
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;private-attributes-require-defaults&#34;&gt;Private attributes Require Defaults&lt;/h3&gt;

&lt;p&gt;Because subrule attributes are private implicit dependencies and cannot be set by users of the rule, they must define a default value (or a late-bound default label).&lt;/p&gt;

&lt;p&gt;They must be spelled out as implementation function arguments.&lt;/p&gt;

&lt;h3 id=&#34;the-context-is-restricted&#34;&gt;The Context Is Restricted&lt;/h3&gt;

&lt;p&gt;Subrules receive a &lt;code&gt;SubruleContext&lt;/code&gt;, not a full &lt;code&gt;RuleContext&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Attempting to access &lt;code&gt;ctx.attr&lt;/code&gt; will fail:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;def _hello_subrule_impl(ctx, name):
    print(ctx.attr.surname)
    return None
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code&gt;Error: &#39;subrule_ctx&#39; value has no field or method &#39;attr&#39;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Available Fields on subrule context:&lt;/p&gt;

&lt;p&gt;In the current API, the available members are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;actions&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fragments&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;label&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;toolchains&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;toolchains-and-execution-groups&#34;&gt;Toolchains and Execution Groups&lt;/h2&gt;

&lt;p&gt;Subrules can declare:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;subrule(
    implementation = _implementation,
    toolchains = [...],
    exec_group = ...,
    subrules = [...],
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This allows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Toolchain requirements per subrule&lt;/li&gt;
&lt;li&gt;Encapsulation of execution groups&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Only &lt;strong&gt;one exec group per subrule&lt;/strong&gt; is supported.&lt;/p&gt;

&lt;h2 id=&#34;nested-subrules&#34;&gt;Nested Subrules&lt;/h2&gt;

&lt;p&gt;A subrule may declare and call other subrules by using the &lt;code&gt;subrules = [...]&lt;/code&gt; parameter.&lt;/p&gt;

&lt;h2 id=&#34;why-subrules-matter&#34;&gt;Why Subrules Matter&lt;/h2&gt;

&lt;p&gt;Subrules address several long-standing architectural issues in Starlark rule design:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Avoid passing &lt;code&gt;ctx&lt;/code&gt; everywhere&lt;/li&gt;
&lt;li&gt;Encapsulate implicit dependencies&lt;/li&gt;
&lt;li&gt;Encapsulate toolchain resolution&lt;/li&gt;
&lt;li&gt;Improve composability&lt;/li&gt;
&lt;li&gt;Make helper-function patterns more structured&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;takeaways&#34;&gt;Takeaways&lt;/h2&gt;

&lt;p&gt;Subrules are a powerful architectural improvement for composing complex Bazel rules, just remember:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You must declare subrules using &lt;code&gt;subrules = [...]&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;You call a subrule like a function — not through &lt;code&gt;ctx&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctx&lt;/code&gt; is automatically injected.&lt;/li&gt;
&lt;li&gt;Attributes must be private and label-based.&lt;/li&gt;
&lt;li&gt;The context is intentionally restricted.&lt;/li&gt;
&lt;li&gt;Subrules operate only at analysis time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They are not replacements for existing mechanisms, but they are a much cleaner way to encapsulate reusable rule logic compared to traditional helper functions.&lt;/p&gt;

&lt;p&gt;As adoption increases, they will likely become a default way of composing complex rules.&lt;/p&gt;
</description>
      <source:markdown>Bazel **subrules** are a lesser-known mechanism for splitting rule functionality into smaller, reusable building blocks. They are designed to improve rule architecture by encapsulating implicit dependencies, toolchains, and action logic — without passing the entire `ctx` (“god object”) around.

In the past, we achieved similar reuse through plain Starlark helper functions. While that works, it often requires threading `ctx` through multiple layers, which reduces encapsulation and makes refactoring harder.

## Subrules

A subrule is similar to a rule in that it can create actions during analysis. However, it operates under strict encapsulation constraints and exposes only a reduced context API.

Let’s start with a simple example.

```starlark
def _hello_subrule_impl(ctx, name):
    hello_file = ctx.actions.declare_file(ctx.label.name + name + &#34;.txt&#34;)
    return hello_file

hello_subrule = subrule(
    implementation = _hello_subrule_impl,
)

def _hello_impl(ctx):
    hello_file = hello_subrule(name = &#34;Example&#34;)

    ctx.actions.write(
        output = hello_file,
        content = &#34;Hello, world!&#34;,
    )

    return [
        DefaultInfo(files = depset([hello_file])),
    ]

hello = rule(
    implementation = _hello_impl,
    subrules = [hello_subrule],
)
```

1. We define `hello_subrule` using `subrule()` and provide its implementation.
2. The implementation function receives a restricted `ctx` (a `SubruleContext`) and any additional parameters.
3. When defining the `hello` rule, we must declare `subrules = [hello_subrule]`.
4. Inside the rule implementation, we call the subrule like a normal function:

   ```starlark
   hello_file = hello_subrule(name = &#34;Example&#34;)
   ```

   We do **not** call it via `ctx`. The `ctx` argument is automatically injected.

Subrules may return arbitrary values — not just providers.

## Important: Subrules Must Be Declared

If you call a subrule but forget to list it in the `subrules = [...]` parameter of the rule (or another subrule), Bazel raises a runtime error during analysis.

## What You Can and Cannot Do

Subrules can create actions just like rules, but they are intentionally constrained for better encapsulation.

### All Attributes Must Be Private

Subrules can declare only **implicit dependencies**, and their attribute names must begin with an underscore.

This results in an error:

```starlark
hello_subrule = subrule(
    implementation = _hello_subrule_impl,
    attrs = {
        &#34;compiler&#34;: attr.label(),
    },
)
```

```
Error in subrule: illegal attribute name &#39;compiler&#39;: subrules may only define private attributes (whose names begin with &#39;_&#39;).
```

### Attributes Must Be label or label_list

Subrule attributes may only be `attr.label()` or `attr.label_list()`.

Other attribute types (e.g., `attr.string()`, `attr.bool()`) are not allowed:

```starlark
attrs = {
    &#34;_compiler&#34;: attr.string(),
}
```

Produces:

```
Error in subrule: bad type for attribute &#39;_compiler&#39;: subrule attributes may only be label or lists of labels.
```

### Private attributes Require Defaults

Because subrule attributes are private implicit dependencies and cannot be set by users of the rule, they must define a default value (or a late-bound default label).

They must be spelled out as implementation function arguments.

### The Context Is Restricted

Subrules receive a `SubruleContext`, not a full `RuleContext`.

Attempting to access `ctx.attr` will fail:

```starlark
def _hello_subrule_impl(ctx, name):
    print(ctx.attr.surname)
    return None
```

```
Error: &#39;subrule_ctx&#39; value has no field or method &#39;attr&#39;
```

Available Fields on subrule context:

In the current API, the available members are:

* `actions`
* `fragments`
* `label`
* `toolchains`

## Toolchains and Execution Groups

Subrules can declare:

```starlark
subrule(
    implementation = _implementation,
    toolchains = [...],
    exec_group = ...,
    subrules = [...],
)
```

This allows:

* Toolchain requirements per subrule
* Encapsulation of execution groups

Only **one exec group per subrule** is supported.

## Nested Subrules

A subrule may declare and call other subrules by using the `subrules = [...]` parameter.

## Why Subrules Matter

Subrules address several long-standing architectural issues in Starlark rule design:

* Avoid passing `ctx` everywhere
* Encapsulate implicit dependencies
* Encapsulate toolchain resolution
* Improve composability
* Make helper-function patterns more structured

## Takeaways

Subrules are a powerful architectural improvement for composing complex Bazel rules, just remember:

* You must declare subrules using `subrules = [...]`.
* You call a subrule like a function — not through `ctx`.
* `ctx` is automatically injected.
* Attributes must be private and label-based.
* The context is intentionally restricted.
* Subrules operate only at analysis time.

They are not replacements for existing mechanisms, but they are a much cleaner way to encapsulate reusable rule logic compared to traditional helper functions.

As adoption increases, they will likely become a default way of composing complex rules.
</source:markdown>
    </item>
    
    <item>
      <title>Applying Bazel Transitions to Third-Party Rules the Right Way</title>
      <link>https://adincebic.com/2026/02/22/applying-bazel-transitions-to-thirdparty.html</link>
      <pubDate>Sun, 22 Feb 2026 14:37:56 +0100</pubDate>
      
      <guid>http://adincebic.micro.blog/2026/02/22/applying-bazel-transitions-to-thirdparty.html</guid>
      <description>

&lt;p&gt;Historically, it has been tedious to apply transitions to rules we don’t control. You either had to maintain a fork, apply a patch, or wrap the rule — which is particularly annoying because you then have to manually forward the providers of the underlying rule.&lt;/p&gt;

&lt;h2 id=&#34;enter-rule-extensions&#34;&gt;Enter Rule Extensions&lt;/h2&gt;

&lt;p&gt;Bazel 8 introduced rule extensions, which allow you to augment an existing rule — somewhat similar to subclassing in object-oriented languages. Instead of copying or wrapping a rule manually, you can define a new rule that delegates to a parent rule while modifying selected behavior.&lt;/p&gt;

&lt;h2 id=&#34;a-simple-transition&#34;&gt;A Simple Transition&lt;/h2&gt;

&lt;p&gt;Let’s first create a transition that forces &lt;code&gt;opt&lt;/code&gt; as the compilation mode:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;def _opt_transition_impl(_settings, _attr):
    return {&amp;quot;//command_line_option:compilation_mode&amp;quot;: &amp;quot;opt&amp;quot;}

opt_transition = transition(
    implementation = _opt_transition_impl,
    inputs = [],
    outputs = [&amp;quot;//command_line_option:compilation_mode&amp;quot;],
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Nothing fancy — this transition forces the rule it’s applied to to always build in &lt;code&gt;opt&lt;/code&gt;, regardless of the &lt;code&gt;--compilation_mode&lt;/code&gt; specified on the command line.&lt;/p&gt;

&lt;h2 id=&#34;applying-the-transition&#34;&gt;Applying the Transition&lt;/h2&gt;

&lt;p&gt;Here I’m demonstrating this with &lt;code&gt;swift_library&lt;/code&gt;, but the concept is the same regardless of the rule:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;opt_swift_library = rule(
    implementation = lambda ctx: ctx.super(),
    parent = swift_library,
    cfg = opt_transition,
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That’s it. The new &lt;code&gt;opt_swift_library&lt;/code&gt; behaves exactly like &lt;code&gt;swift_library&lt;/code&gt;, except it always builds in &lt;code&gt;opt&lt;/code&gt; mode.&lt;/p&gt;

&lt;p&gt;For the sake of completeness, here is the entire &lt;code&gt;.bzl&lt;/code&gt; file:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;load(&amp;quot;@rules_swift//swift:swift_library.bzl&amp;quot;, &amp;quot;swift_library&amp;quot;)

def _opt_transition_impl(_settings, _attr):
    return {&amp;quot;//command_line_option:compilation_mode&amp;quot;: &amp;quot;opt&amp;quot;}

opt_transition = transition(
    implementation = _opt_transition_impl,
    inputs = [],
    outputs = [&amp;quot;//command_line_option:compilation_mode&amp;quot;],
)

opt_swift_library = rule(
    implementation = lambda ctx: ctx.super(),
    parent = swift_library,
    cfg = opt_transition,
)
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;wrapping-up&#34;&gt;Wrapping Up&lt;/h2&gt;

&lt;p&gt;There’s much more you can do with rule extensions, but being able to easily apply transitions to third-party rules is already a huge win.&lt;/p&gt;

&lt;p&gt;To learn more about what’s possible, check out &lt;a href=&#34;https://www.smileykeith.com/2025/10/31/bazel-rule-extensions/&#34;&gt;Keith’s excellent article&lt;/a&gt; on this topic.&lt;/p&gt;
</description>
      <source:markdown>Historically, it has been tedious to apply transitions to rules we don’t control. You either had to maintain a fork, apply a patch, or wrap the rule — which is particularly annoying because you then have to manually forward the providers of the underlying rule.

## Enter Rule Extensions

Bazel 8 introduced rule extensions, which allow you to augment an existing rule — somewhat similar to subclassing in object-oriented languages. Instead of copying or wrapping a rule manually, you can define a new rule that delegates to a parent rule while modifying selected behavior.

## A Simple Transition

Let’s first create a transition that forces `opt` as the compilation mode:

```starlark
def _opt_transition_impl(_settings, _attr):
    return {&#34;//command_line_option:compilation_mode&#34;: &#34;opt&#34;}

opt_transition = transition(
    implementation = _opt_transition_impl,
    inputs = [],
    outputs = [&#34;//command_line_option:compilation_mode&#34;],
)
```

Nothing fancy — this transition forces the rule it’s applied to to always build in `opt`, regardless of the `--compilation_mode` specified on the command line.

## Applying the Transition

Here I’m demonstrating this with `swift_library`, but the concept is the same regardless of the rule:

```starlark
opt_swift_library = rule(
    implementation = lambda ctx: ctx.super(),
    parent = swift_library,
    cfg = opt_transition,
)
```

That’s it. The new `opt_swift_library` behaves exactly like `swift_library`, except it always builds in `opt` mode.

For the sake of completeness, here is the entire `.bzl` file:

```starlark
load(&#34;@rules_swift//swift:swift_library.bzl&#34;, &#34;swift_library&#34;)

def _opt_transition_impl(_settings, _attr):
    return {&#34;//command_line_option:compilation_mode&#34;: &#34;opt&#34;}

opt_transition = transition(
    implementation = _opt_transition_impl,
    inputs = [],
    outputs = [&#34;//command_line_option:compilation_mode&#34;],
)

opt_swift_library = rule(
    implementation = lambda ctx: ctx.super(),
    parent = swift_library,
    cfg = opt_transition,
)
```

## Wrapping Up

There’s much more you can do with rule extensions, but being able to easily apply transitions to third-party rules is already a huge win.

To learn more about what’s possible, check out [Keith’s excellent article](https://www.smileykeith.com/2025/10/31/bazel-rule-extensions/) on this topic.
</source:markdown>
    </item>
    
    <item>
      <title>Creating Custom Command-Line Flags in Bazel</title>
      <link>https://adincebic.com/2026/02/21/creating-custom-commandline-flags-in.html</link>
      <pubDate>Sat, 21 Feb 2026 12:40:54 +0100</pubDate>
      
      <guid>http://adincebic.micro.blog/2026/02/21/creating-custom-commandline-flags-in.html</guid>
      <description>

&lt;p&gt;It’s fairly common to need to alter your builds based on a command-line flag. Maybe you want to change the distribution target for a mobile app or enable additional debug functionality. An easy way to do that is via a custom command-line flag.&lt;/p&gt;

&lt;p&gt;This is a topic where Bazel newcomers often struggle, so let’s explore how to create custom flags in Bazel.&lt;/p&gt;

&lt;h2 id=&#34;build-settings&#34;&gt;Build Settings&lt;/h2&gt;

&lt;p&gt;A build setting is a rule—just like any other rule—but with additional capabilities. Specifically, build settings allow us to define custom command-line flags that influence the build configuration.&lt;/p&gt;

&lt;h2 id=&#34;pre-defined-settings&#34;&gt;Pre-defined Settings&lt;/h2&gt;

&lt;p&gt;Because build settings are rules, we can write our own. However, &lt;a href=&#34;https://github.com/bazelbuild/bazel-skylib&#34;&gt;bazel-skylib&lt;/a&gt; provides several commonly used build settings out of the box.&lt;/p&gt;

&lt;p&gt;You can see the full list in the &lt;a href=&#34;https://github.com/bazelbuild/bazel-skylib&#34;&gt;skylib repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For simplicity, we’ll use a pre-defined setting in this article. Keep in mind, though, that you’re not limited to what skylib offers—you can implement your own build setting if needed.&lt;/p&gt;

&lt;h2 id=&#34;creating-a-flag&#34;&gt;Creating a Flag&lt;/h2&gt;

&lt;p&gt;Suppose we want to build differently for our local development environment versus what we release to the App Store. We’ll call this a &lt;em&gt;flavor&lt;/em&gt;, with two variants:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;dev&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;store&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To create a flag, we need to instantiate a build setting in &lt;code&gt;BUILD.bazel&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;load(&amp;quot;@bazel_skylib//rules:common_settings.bzl&amp;quot;, &amp;quot;string_flag&amp;quot;)

string_flag(
    name = &amp;quot;flavor&amp;quot;,
    values = [&amp;quot;dev&amp;quot;, &amp;quot;store&amp;quot;],
    build_setting_default = &amp;quot;dev&amp;quot;,
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Because this is a target, it has a label. That’s why we pass it on the command line using label syntax:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;bazel build //:my_target --//:flavor=store
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;However, defining the flag alone is not enough.&lt;/p&gt;

&lt;h2 id=&#34;reacting-to-build-settings&#34;&gt;Reacting to Build Settings&lt;/h2&gt;

&lt;p&gt;Bazel does not branch directly on build settings. Instead, it evaluates &lt;em&gt;configuration conditions&lt;/em&gt;, represented by &lt;code&gt;config_setting&lt;/code&gt; targets.&lt;/p&gt;

&lt;p&gt;So we need to associate our flag with configuration conditions:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;config_setting(
    name = &amp;quot;dev&amp;quot;,
    flag_values = {&amp;quot;:flavor&amp;quot;: &amp;quot;dev&amp;quot;},
    visibility = [&amp;quot;//visibility:public&amp;quot;],
)

config_setting(
    name = &amp;quot;store&amp;quot;,
    flag_values = {&amp;quot;:flavor&amp;quot;: &amp;quot;store&amp;quot;},
    visibility = [&amp;quot;//visibility:public&amp;quot;],
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here’s the mental model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;string_flag&lt;/code&gt; defines a configurable value.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;config_setting&lt;/code&gt; defines a configuration condition.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;select()&lt;/code&gt; switches on &lt;code&gt;config_setting&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This separation is important: &lt;code&gt;select()&lt;/code&gt; operates on &lt;code&gt;config_setting&lt;/code&gt; targets, not directly on flags.&lt;/p&gt;

&lt;h2 id=&#34;using-select&#34;&gt;Using &lt;code&gt;select()&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;Now that we have configuration conditions, we can branch using &lt;code&gt;select()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To demonstrate that our flag works, we’ll create a simple &lt;code&gt;genrule&lt;/code&gt; that writes which flavor was selected:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;genrule(
    name = &amp;quot;which_flavor&amp;quot;,
    outs = [&amp;quot;output.txt&amp;quot;],
    cmd = select({
        &amp;quot;:dev&amp;quot;: &amp;quot;echo dev &amp;gt; $@&amp;quot;,
        &amp;quot;:store&amp;quot;: &amp;quot;echo store &amp;gt; $@&amp;quot;,
    }),
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now build the target:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;bazel build :which_flavor --//:flavor=store
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You should see something like this:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;INFO: Analyzed target //:which_flavor (6 packages loaded, 10 targets configured).
INFO: Found 1 target...
Target //:which_flavor up-to-date:
  bazel-bin/output.txt
INFO: Elapsed time: 0.297s, Critical Path: 0.02s
INFO: 2 processes: 1 internal, 1 darwin-sandbox.
INFO: Build completed successfully, 2 total actions
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Opening &lt;code&gt;bazel-bin/output.txt&lt;/code&gt; will reveal:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;store
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;closing-thoughts&#34;&gt;Closing Thoughts&lt;/h2&gt;

&lt;p&gt;To create a custom command-line flag in Bazel, remember:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You need a &lt;strong&gt;build setting&lt;/strong&gt; (&lt;code&gt;string_flag&lt;/code&gt;, &lt;code&gt;bool_flag&lt;/code&gt;, etc.).&lt;/li&gt;
&lt;li&gt;You need one or more &lt;strong&gt;&lt;code&gt;config_setting&lt;/code&gt; targets&lt;/strong&gt; that describe configuration conditions.&lt;/li&gt;
&lt;li&gt;You use &lt;strong&gt;&lt;code&gt;select()&lt;/code&gt;&lt;/strong&gt; to branch on those configuration conditions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you’re not writing custom rules, using pre-defined settings from skylib is probably the right approach in most cases. If you need more flexibility, you can write your own build setting rule—but that’s a topic for another day.&lt;/p&gt;
</description>
      <source:markdown>It’s fairly common to need to alter your builds based on a command-line flag. Maybe you want to change the distribution target for a mobile app or enable additional debug functionality. An easy way to do that is via a custom command-line flag.

This is a topic where Bazel newcomers often struggle, so let’s explore how to create custom flags in Bazel.

## Build Settings

A build setting is a rule—just like any other rule—but with additional capabilities. Specifically, build settings allow us to define custom command-line flags that influence the build configuration.

## Pre-defined Settings

Because build settings are rules, we can write our own. However, [bazel-skylib](https://github.com/bazelbuild/bazel-skylib) provides several commonly used build settings out of the box.

You can see the full list in the [skylib repository](https://github.com/bazelbuild/bazel-skylib).

For simplicity, we’ll use a pre-defined setting in this article. Keep in mind, though, that you’re not limited to what skylib offers—you can implement your own build setting if needed.

## Creating a Flag

Suppose we want to build differently for our local development environment versus what we release to the App Store. We’ll call this a *flavor*, with two variants:

* `dev`
* `store`

To create a flag, we need to instantiate a build setting in `BUILD.bazel`:

```starlark
load(&#34;@bazel_skylib//rules:common_settings.bzl&#34;, &#34;string_flag&#34;)

string_flag(
    name = &#34;flavor&#34;,
    values = [&#34;dev&#34;, &#34;store&#34;],
    build_setting_default = &#34;dev&#34;,
)
```

Because this is a target, it has a label. That’s why we pass it on the command line using label syntax:

```bash
bazel build //:my_target --//:flavor=store
```

However, defining the flag alone is not enough.

## Reacting to Build Settings

Bazel does not branch directly on build settings. Instead, it evaluates *configuration conditions*, represented by `config_setting` targets.

So we need to associate our flag with configuration conditions:

```starlark
config_setting(
    name = &#34;dev&#34;,
    flag_values = {&#34;:flavor&#34;: &#34;dev&#34;},
    visibility = [&#34;//visibility:public&#34;],
)

config_setting(
    name = &#34;store&#34;,
    flag_values = {&#34;:flavor&#34;: &#34;store&#34;},
    visibility = [&#34;//visibility:public&#34;],
)
```

Here’s the mental model:

* `string_flag` defines a configurable value.
* `config_setting` defines a configuration condition.
* `select()` switches on `config_setting`.

This separation is important: `select()` operates on `config_setting` targets, not directly on flags.

## Using `select()`

Now that we have configuration conditions, we can branch using `select()`.

To demonstrate that our flag works, we’ll create a simple `genrule` that writes which flavor was selected:

```starlark
genrule(
    name = &#34;which_flavor&#34;,
    outs = [&#34;output.txt&#34;],
    cmd = select({
        &#34;:dev&#34;: &#34;echo dev &gt; $@&#34;,
        &#34;:store&#34;: &#34;echo store &gt; $@&#34;,
    }),
)
```

Now build the target:

```bash
bazel build :which_flavor --//:flavor=store
```

You should see something like this:

```bash
INFO: Analyzed target //:which_flavor (6 packages loaded, 10 targets configured).
INFO: Found 1 target...
Target //:which_flavor up-to-date:
  bazel-bin/output.txt
INFO: Elapsed time: 0.297s, Critical Path: 0.02s
INFO: 2 processes: 1 internal, 1 darwin-sandbox.
INFO: Build completed successfully, 2 total actions
```

Opening `bazel-bin/output.txt` will reveal:

```
store
```

## Closing Thoughts

To create a custom command-line flag in Bazel, remember:

1. You need a **build setting** (`string_flag`, `bool_flag`, etc.).
2. You need one or more **`config_setting` targets** that describe configuration conditions.
3. You use **`select()`** to branch on those configuration conditions.

If you’re not writing custom rules, using pre-defined settings from skylib is probably the right approach in most cases. If you need more flexibility, you can write your own build setting rule—but that’s a topic for another day.
</source:markdown>
    </item>
    
    <item>
      <title>Using features in bazel rules</title>
      <link>https://adincebic.com/2026/02/08/using-features-in-bazel-rules.html</link>
      <pubDate>Sun, 08 Feb 2026 11:02:02 +0100</pubDate>
      
      <guid>http://adincebic.micro.blog/2026/02/08/using-features-in-bazel-rules.html</guid>
      <description>

&lt;p&gt;Often we want to allow users of our Bazel rules to enable or disable functionality on an as-needed basis. While Bazel offers several mechanisms for this, &lt;strong&gt;features&lt;/strong&gt; are the simplest—and probably the right—approach for the majority of cases.&lt;/p&gt;

&lt;h2 id=&#34;using-features&#34;&gt;Using features&lt;/h2&gt;

&lt;p&gt;Features can be enabled in two ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On the command line: &lt;code&gt;--features=my_feature&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;As a rule attribute: &lt;code&gt;features = [&amp;quot;my_feature&amp;quot;]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; The command-line approach is &lt;em&gt;cumulative&lt;/em&gt;, meaning multiple features can be enabled by repeating the &lt;code&gt;--features&lt;/code&gt; flag with different values.&lt;/p&gt;

&lt;h2 id=&#34;reading-features-from-rules&#34;&gt;Reading features from rules&lt;/h2&gt;

&lt;p&gt;Say we have the following rule that writes a name to a file:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;def _hello_impl(ctx):
    content = ctx.attr.first_name
    out = ctx.actions.declare_file(ctx.label.name + &amp;quot;.txt&amp;quot;)
    ctx.actions.write(
        output = out,
        content = content,
    )
    return DefaultInfo(files = depset([out]))

hello = rule(
    implementation = _hello_impl,
    attrs = {
        &amp;quot;first_name&amp;quot;: attr.string(),
    },
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now imagine we want to ensure there is a trailing newline at the end of the file, but we want this behavior to be opt-in. The simplest way to achieve that is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check whether &lt;code&gt;ctx.features&lt;/code&gt; contains &lt;code&gt;hello.trailing_newline&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;If yes, append &lt;code&gt;\n&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Otherwise, keep the content as is&lt;/li&gt;
&lt;/ol&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;if &amp;quot;hello.trailing_newline&amp;quot; in ctx.features:
    content = ctx.attr.first_name + &amp;quot;\n&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To apply the feature from the command line:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;bazel build :hello --features=hello.trailing_newline
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Or directly on the target:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;hello(
    name = &amp;quot;hello&amp;quot;,
    first_name = &amp;quot;Adin&amp;quot;,
    features = [&amp;quot;hello.trailing_newline&amp;quot;],
)
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;disabling-features&#34;&gt;Disabling features&lt;/h2&gt;

&lt;p&gt;One neat trick about Bazel features is that they can be explicitly &lt;em&gt;disabled&lt;/em&gt;, both on the command line and via the &lt;code&gt;features&lt;/code&gt; attribute, by prefixing the feature name with &lt;code&gt;-&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example, to disable the &lt;code&gt;hello.trailing_newline&lt;/code&gt; feature:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;bazel build :hello --features=-hello.trailing_newline
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Inside the rule implementation, Bazel exposes a conveniently named &lt;code&gt;ctx.disabled_features&lt;/code&gt; list, which contains all features explicitly disabled for that target.&lt;/p&gt;

&lt;h2 id=&#34;a-few-things-to-know&#34;&gt;A few things to know&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Features are &lt;strong&gt;global&lt;/strong&gt;, not scoped to a specific rule. This is why prefixing feature names (for example, &lt;code&gt;hello.trailing_newline&lt;/code&gt;) is strongly recommended. &lt;a href=&#34;https://github.com/bazelbuild/rules_swift&#34;&gt;rules_swift&lt;/a&gt; follows the same convention.&lt;/li&gt;
&lt;li&gt;Changing &lt;code&gt;--features&lt;/code&gt; invalidates the analysis cache.&lt;/li&gt;
&lt;li&gt;Bazel also provides a &lt;code&gt;--host_features&lt;/code&gt; flag, which applies to the execution (host) configuration.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;wrapping-up&#34;&gt;Wrapping up&lt;/h2&gt;

&lt;p&gt;This is one of those simple mechanisms that turns out to be extremely useful. So next time you think about adding an extra attribute to a rule, think twice—features are likely the better choice.&lt;/p&gt;
</description>
      <source:markdown>Often we want to allow users of our Bazel rules to enable or disable functionality on an as-needed basis. While Bazel offers several mechanisms for this, **features** are the simplest—and probably the right—approach for the majority of cases.

## Using features

Features can be enabled in two ways:

* On the command line: `--features=my_feature`
* As a rule attribute: `features = [&#34;my_feature&#34;]`

**NOTE:** The command-line approach is *cumulative*, meaning multiple features can be enabled by repeating the `--features` flag with different values.

## Reading features from rules

Say we have the following rule that writes a name to a file:

```starlark
def _hello_impl(ctx):
    content = ctx.attr.first_name
    out = ctx.actions.declare_file(ctx.label.name + &#34;.txt&#34;)
    ctx.actions.write(
        output = out,
        content = content,
    )
    return DefaultInfo(files = depset([out]))

hello = rule(
    implementation = _hello_impl,
    attrs = {
        &#34;first_name&#34;: attr.string(),
    },
)
```

Now imagine we want to ensure there is a trailing newline at the end of the file, but we want this behavior to be opt-in. The simplest way to achieve that is:

1. Check whether `ctx.features` contains `hello.trailing_newline`
2. If yes, append `\n`
3. Otherwise, keep the content as is

```starlark
if &#34;hello.trailing_newline&#34; in ctx.features:
    content = ctx.attr.first_name + &#34;\n&#34;
```

To apply the feature from the command line:

```bash
bazel build :hello --features=hello.trailing_newline
```

Or directly on the target:

```starlark
hello(
    name = &#34;hello&#34;,
    first_name = &#34;Adin&#34;,
    features = [&#34;hello.trailing_newline&#34;],
)
```

## Disabling features

One neat trick about Bazel features is that they can be explicitly *disabled*, both on the command line and via the `features` attribute, by prefixing the feature name with `-`.

For example, to disable the `hello.trailing_newline` feature:

```bash
bazel build :hello --features=-hello.trailing_newline
```

Inside the rule implementation, Bazel exposes a conveniently named `ctx.disabled_features` list, which contains all features explicitly disabled for that target.

## A few things to know

* Features are **global**, not scoped to a specific rule. This is why prefixing feature names (for example, `hello.trailing_newline`) is strongly recommended. [rules_swift](https://github.com/bazelbuild/rules_swift) follows the same convention.
* Changing `--features` invalidates the analysis cache.
* Bazel also provides a `--host_features` flag, which applies to the execution (host) configuration.

## Wrapping up

This is one of those simple mechanisms that turns out to be extremely useful. So next time you think about adding an extra attribute to a rule, think twice—features are likely the better choice.
</source:markdown>
    </item>
    
    <item>
      <title>Executing actions from Bazel aspects</title>
      <link>https://adincebic.com/2026/02/01/executing-actions-from-bazel-aspects.html</link>
      <pubDate>Sun, 01 Feb 2026 09:56:10 +0100</pubDate>
      
      <guid>http://adincebic.micro.blog/2026/02/01/executing-actions-from-bazel-aspects.html</guid>
      <description>

&lt;p&gt;So far I went through writing the aspect, but I haven’t shown how to run actions from it to do something useful. Remember: we can do very little during Starlark evaluation (Bazel’s analysis phase). If we want to read files, inspect sources, or produce results, the work has to happen in the execution phase by running actions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; Before continuing, it helps to read my &lt;a href=&#34;https://adincebic.com/2026/01/25/utilizing-bazel-aspecthints-rule-attribute.html&#34;&gt;earlier&lt;/a&gt; &lt;a href=&#34;https://adincebic.com/2026/01/18/introduction-to-aspects-in-bazel.html&#34;&gt;articles&lt;/a&gt; on Bazel aspects, since I’m going to assume that background.&lt;/p&gt;

&lt;h2 id=&#34;ctx-actions&#34;&gt;ctx.actions&lt;/h2&gt;

&lt;p&gt;Every aspect and rule gets a &lt;code&gt;ctx&lt;/code&gt; object, which gives us access to &lt;code&gt;ctx.actions&lt;/code&gt; — including &lt;code&gt;run()&lt;/code&gt;. Sticking with the “unused Swift deps” example, here’s what invoking a tool looks like:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;ctx.actions.run(
    inputs = [input] + source_files,
    outputs = [out],
    executable = ctx.executable._tool,
    arguments = [arguments],
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This snippet captures the essentials of a Bazel action:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Inputs:&lt;/strong&gt; all files the tool may read&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Outputs:&lt;/strong&gt; files the tool is expected to generate&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Executable:&lt;/strong&gt; the tool binary itself&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Arguments:&lt;/strong&gt; the tool’s command-line arguments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bazel uses these declarations to compute action keys, decide when work must be re-executed, and make remote/local caching correct.&lt;/p&gt;

&lt;h2 id=&#34;inputs&#34;&gt;Inputs&lt;/h2&gt;

&lt;p&gt;Here, inputs are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a JSON file that describes what we want to be analyzed&lt;/li&gt;
&lt;li&gt;the Swift source files to scan&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;First, we build the JSON payload:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;payload = {
    &amp;quot;targetLabel&amp;quot;: str(target.label),
    &amp;quot;sources&amp;quot;: [f.path for f in source_files],
    &amp;quot;deps&amp;quot;: deps_to_analyze,
}

encoded_json = json.encode(payload)
input = ctx.actions.declare_file(ctx.label.name + &amp;quot;_input.json&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Like I described in my &lt;a href=&#34;https://adincebic.com/2026/01/04/writing-a-simple-bazel-rule.html&#34;&gt;first article&lt;/a&gt;, declaring the file is not enough — we also need an action to write it:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;ctx.actions.write(
    output = input,
    content = encoded_json,
)
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;outputs-arguments-and-tool&#34;&gt;Outputs, arguments and tool&lt;/h2&gt;

&lt;p&gt;In this case the tool produces a single JSON output that contains the unused dependency results:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;out = ctx.actions.declare_file(ctx.label.name + &amp;quot;_unused_deps.json&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;For arguments, instead of building a plain list, Bazel provides &lt;code&gt;ctx.actions.args()&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;arguments = ctx.actions.args()
arguments.add(input)
arguments.add(&amp;quot;--output&amp;quot;)
arguments.add(out)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;One reasonable question is: why bother with &lt;code&gt;Args&lt;/code&gt; instead of a list?&lt;/p&gt;

&lt;p&gt;Because command lines can get huge in real builds (compilers and linkers are the classic examples). &lt;code&gt;Args&lt;/code&gt; lets Bazel represent and expand arguments efficiently without paying the cost of building enormous Starlark lists.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;arguments = [arguments]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;There is way more information about this topic on the &lt;a href=&#34;https://bazel.build/rules/lib/builtins/Args&#34;&gt;official bazel docs page&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&#34;running-the-tool&#34;&gt;Running the tool&lt;/h2&gt;

&lt;p&gt;To run a tool from an aspect (or a rule), the two common approaches are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Private attribute&lt;/li&gt;
&lt;li&gt;Toolchains&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here I use a private attribute for simplicity (I’ve covered toolchains in a &lt;a href=&#34;https://adincebic.com/2026/01/11/bazel-toolchains-repository-rules-and.html&#34;&gt;previous post&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;We add a private attribute by prefixing it with &lt;code&gt;_&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;unused_swift_deps_aspect = aspect(
    implementation = _unused_swift_deps_impl,
    attrs = {
        &amp;quot;_tool&amp;quot;: attr.label(
            default = &amp;quot;//tools/FindUnusedSwiftDeps&amp;quot;,
            executable = True,
            cfg = &amp;quot;exec&amp;quot;,
        ),
    },
    attr_aspects = [&amp;quot;deps&amp;quot;],
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then we give it a default label (the tool target), mark it executable, and set &lt;code&gt;cfg = &amp;quot;exec&amp;quot;&lt;/code&gt; so it runs on the execution platform.&lt;/p&gt;

&lt;p&gt;Inside the implementation, you reference it as:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;tool = ctx.executable._tool
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;At that point, you can wire it into &lt;code&gt;ctx.actions.run(...)&lt;/code&gt; and Bazel will execute it as part of the build.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; I intentionally omited the code for the tool itself to keep the focus on starlark.&lt;/p&gt;

&lt;h2 id=&#34;closing-thoughts&#34;&gt;Closing thoughts&lt;/h2&gt;

&lt;p&gt;At this point it is clear how concept of aspects in bazel can be powerful. I probably won&amp;rsquo;t be writing more about aspects directly but may share some of what I wrote and found useful.&lt;/p&gt;
</description>
      <source:markdown>So far I went through writing the aspect, but I haven’t shown how to run actions from it to do something useful. Remember: we can do very little during Starlark evaluation (Bazel’s analysis phase). If we want to read files, inspect sources, or produce results, the work has to happen in the execution phase by running actions.

**NOTE:** Before continuing, it helps to read my [earlier](https://adincebic.com/2026/01/25/utilizing-bazel-aspecthints-rule-attribute.html) [articles](https://adincebic.com/2026/01/18/introduction-to-aspects-in-bazel.html) on Bazel aspects, since I’m going to assume that background.

## ctx.actions

Every aspect and rule gets a `ctx` object, which gives us access to `ctx.actions` — including `run()`. Sticking with the “unused Swift deps” example, here’s what invoking a tool looks like:

```starlark
ctx.actions.run(
    inputs = [input] + source_files,
    outputs = [out],
    executable = ctx.executable._tool,
    arguments = [arguments],
)
```

This snippet captures the essentials of a Bazel action:

* **Inputs:** all files the tool may read
* **Outputs:** files the tool is expected to generate
* **Executable:** the tool binary itself
* **Arguments:** the tool’s command-line arguments

Bazel uses these declarations to compute action keys, decide when work must be re-executed, and make remote/local caching correct.

## Inputs

Here, inputs are:

* a JSON file that describes what we want to be analyzed
* the Swift source files to scan

First, we build the JSON payload:

```starlark
payload = {
    &#34;targetLabel&#34;: str(target.label),
    &#34;sources&#34;: [f.path for f in source_files],
    &#34;deps&#34;: deps_to_analyze,
}

encoded_json = json.encode(payload)
input = ctx.actions.declare_file(ctx.label.name + &#34;_input.json&#34;)
```

Like I described in my [first article](https://adincebic.com/2026/01/04/writing-a-simple-bazel-rule.html), declaring the file is not enough — we also need an action to write it:

```starlark
ctx.actions.write(
    output = input,
    content = encoded_json,
)
```

## Outputs, arguments and tool

In this case the tool produces a single JSON output that contains the unused dependency results:

```starlark
out = ctx.actions.declare_file(ctx.label.name + &#34;_unused_deps.json&#34;)
```

For arguments, instead of building a plain list, Bazel provides `ctx.actions.args()`:

```starlark
arguments = ctx.actions.args()
arguments.add(input)
arguments.add(&#34;--output&#34;)
arguments.add(out)
```

One reasonable question is: why bother with `Args` instead of a list?

Because command lines can get huge in real builds (compilers and linkers are the classic examples). `Args` lets Bazel represent and expand arguments efficiently without paying the cost of building enormous Starlark lists.

```starlark
arguments = [arguments]
```

There is way more information about this topic on the [official bazel docs page](https://bazel.build/rules/lib/builtins/Args).

## Running the tool

To run a tool from an aspect (or a rule), the two common approaches are:

* Private attribute
* Toolchains

Here I use a private attribute for simplicity (I’ve covered toolchains in a [previous post](https://adincebic.com/2026/01/11/bazel-toolchains-repository-rules-and.html)).

We add a private attribute by prefixing it with `_`:

```starlark
unused_swift_deps_aspect = aspect(
    implementation = _unused_swift_deps_impl,
    attrs = {
        &#34;_tool&#34;: attr.label(
            default = &#34;//tools/FindUnusedSwiftDeps&#34;,
            executable = True,
            cfg = &#34;exec&#34;,
        ),
    },
    attr_aspects = [&#34;deps&#34;],
)
```

Then we give it a default label (the tool target), mark it executable, and set `cfg = &#34;exec&#34;` so it runs on the execution platform.

Inside the implementation, you reference it as:

```starlark
tool = ctx.executable._tool
```

At that point, you can wire it into `ctx.actions.run(...)` and Bazel will execute it as part of the build.

**NOTE:** I intentionally omited the code for the tool itself to keep the focus on starlark.

## Closing thoughts

At this point it is clear how concept of aspects in bazel can be powerful. I probably won&#39;t be writing more about aspects directly but may share some of what I wrote and found useful.
</source:markdown>
    </item>
    
    <item>
      <title>Utilizing Bazel aspect_hints rule attribute</title>
      <link>https://adincebic.com/2026/01/25/utilizing-bazel-aspecthints-rule-attribute.html</link>
      <pubDate>Sun, 25 Jan 2026 12:37:47 +0100</pubDate>
      
      <guid>http://adincebic.micro.blog/2026/01/25/utilizing-bazel-aspecthints-rule-attribute.html</guid>
      <description>

&lt;p&gt;In the &lt;a href=&#34;https://adincebic.com/2026/01/18/introduction-to-aspects-in-bazel.html&#34;&gt;last article&lt;/a&gt; I provided a short introduction to the concept of aspects in &lt;strong&gt;Bazel&lt;/strong&gt;. To keep the series going, today I will go over the somewhat lesser-known feature &lt;code&gt;aspect_hints&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&#34;what-is-aspect-hints&#34;&gt;What is &lt;code&gt;aspect_hints&lt;/code&gt;?&lt;/h2&gt;

&lt;p&gt;In simple terms, it is an &lt;a href=&#34;https://bazel.build/reference/be/common-definitions#common.aspect_hints&#34;&gt;implicit attribute available on every rule&lt;/a&gt; that is meant to be &lt;strong&gt;consumed by an attached aspect&lt;/strong&gt;, not by the rule implementation itself. This allows us to convey “hints” to aspects in a lightweight manner.&lt;/p&gt;

&lt;p&gt;For example, we may have an aspect that we attach to &lt;code&gt;swift_library&lt;/code&gt; to report unused deps, but for whatever reason we need a way to tell the aspect to ignore a specific dependency.&lt;/p&gt;

&lt;h2 id=&#34;unused-swift-deps-aspect&#34;&gt;&lt;code&gt;unused_swift_deps&lt;/code&gt; aspect&lt;/h2&gt;

&lt;p&gt;Below is my dummy implementation of an aspect that finds and reports unused Swift deps. It is not a real implementation, but it is enough to showcase the potential of &lt;code&gt;aspect_hints&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So in my &lt;code&gt;aspects.bzl&lt;/code&gt; I have the following:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;load(&amp;quot;@rules_swift//swift:providers.bzl&amp;quot;, &amp;quot;SwiftInfo&amp;quot;)

UnusedSwiftDepsInfo = provider(fields = [&amp;quot;report_files&amp;quot;])

def _unused_swift_deps(target, ctx):
    labels = []

    if hasattr(ctx.rule.attr, &amp;quot;deps&amp;quot;):
        for dep in ctx.rule.attr.deps:
            if SwiftInfo in dep:
                for module in dep[SwiftInfo].direct_modules:
                    labels.append(module.name)
                    labels.append(str(dep.label))

    out = ctx.actions.declare_file(&amp;quot;unused_deps_&amp;quot; + ctx.label.name + &amp;quot;.txt&amp;quot;)
    ctx.actions.write(
        output = out,
        content = &amp;quot;\n&amp;quot;.join(labels),
    )

    transitive_files = []
    for dep in ctx.rule.attr.deps:
        if UnusedSwiftDepsInfo in dep:
            transitive_files.append(dep[UnusedSwiftDepsInfo].report_files)

    all_files = depset(direct = [out], transitive = transitive_files)
    return [
        UnusedSwiftDepsInfo(report_files = all_files),
        DefaultInfo(files = all_files),
    ]

unused_swift_deps_aspect = aspect(
    implementation = _unused_swift_deps,
    attr_aspects = [&amp;quot;deps&amp;quot;],
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;All of the concepts in this code are explained in my previous article on aspects, so I won’t go over them again.&lt;/p&gt;

&lt;h2 id=&#34;writing-an-aspect-hint&#34;&gt;Writing an aspect hint&lt;/h2&gt;

&lt;p&gt;As mentioned above, now we would like the ability to instruct this aspect to ignore certain labels. The way to achieve that will feel familiar:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a provider&lt;/li&gt;
&lt;li&gt;Make an implementation function&lt;/li&gt;
&lt;li&gt;Create a rule&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;First we create a provider to be able to easily give a hint to our aspect:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;UnusedSwiftDepsHintInfo = provider(fields = [&amp;quot;ignore_deps&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then an implementation function:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;def _ignore_unused_swift_deps_hint_impl(ctx):
    return [UnusedSwiftDepsHintInfo(ignore_deps = ctx.attr.ignore_deps)]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Finally, a rule with the single job of filling the &lt;code&gt;UnusedSwiftDepsHintInfo&lt;/code&gt; provider:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;ignore_unused_swift_deps_hint = rule(
    implementation = _ignore_unused_swift_deps_hint_impl,
    attrs = {
        &amp;quot;ignore_deps&amp;quot;: attr.label_list(mandatory = True),
    },
)
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;reading-aspect-hints-from-the-aspect-implementation-function&#34;&gt;Reading &lt;code&gt;aspect_hints&lt;/code&gt; from the aspect implementation function&lt;/h2&gt;

&lt;p&gt;Earlier I stated that &lt;code&gt;aspect_hints&lt;/code&gt; is an implicit rule attribute that can be read from an attached aspect. Let’s go over how to do that.&lt;/p&gt;

&lt;p&gt;In my aspect for reporting unused Swift deps, it is simply a matter of reading the attribute like any other, checking if it carries the desired provider, and then acting upon it.&lt;/p&gt;

&lt;p&gt;Because &lt;code&gt;aspect_hints&lt;/code&gt; is a list of labels, I will iterate over it, check if it carries &lt;code&gt;UnusedSwiftDepsHintInfo&lt;/code&gt;, and skip writing the ignored labels to the report file:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;    ignored_deps = []

    for aspect_hint in getattr(ctx.rule.attr, &amp;quot;aspect_hints&amp;quot;, []):
        if UnusedSwiftDepsHintInfo in aspect_hint:
            for ignored_dep in aspect_hint[UnusedSwiftDepsHintInfo].ignore_deps:
                ignored_deps.append(ignored_dep.label)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now that we collected labels to ignore, we just need to make sure to actually skip them when reporting:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;if dep.label in ignored_deps:
    print(&amp;quot;Ignoring {} dep&amp;quot;.format(str(dep.label)))
    continue
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And that’s it.&lt;/p&gt;

&lt;h2 id=&#34;applying-it-to-the-swift-library&#34;&gt;Applying it to the &lt;code&gt;swift_library&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;Here is how my &lt;code&gt;BUILD.bazel&lt;/code&gt; looks when the newly created aspect hint is applied:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;load(&amp;quot;@rules_swift//swift:swift_library.bzl&amp;quot;, &amp;quot;swift_library&amp;quot;)
load(&amp;quot;//:aspects.bzl&amp;quot;, &amp;quot;ignore_unused_swift_deps_hint&amp;quot;)

ignore_unused_swift_deps_hint(
    name = &amp;quot;ignore_deps_hint&amp;quot;,
    ignore_deps = [&amp;quot;:lib2&amp;quot;],
)

swift_library(
    name = &amp;quot;lib&amp;quot;,
    srcs = [&amp;quot;main.swift&amp;quot;],
    aspect_hints = [&amp;quot;:ignore_deps_hint&amp;quot;],
    deps = [&amp;quot;:lib2&amp;quot;],
)

swift_library(
    name = &amp;quot;lib2&amp;quot;,
    srcs = [&amp;quot;main.swift&amp;quot;],
    deps = [&amp;quot;:lib3&amp;quot;],
)

swift_library(
    name = &amp;quot;lib3&amp;quot;,
    srcs = [&amp;quot;main.swift&amp;quot;],
    module_name = &amp;quot;CustomModuleName&amp;quot;,
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And if we build:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;bazel build :lib --aspects aspects.bzl%unused_swift_deps_aspect
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We see that our &lt;code&gt;lib2&lt;/code&gt; dep is being ignored:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;DEBUG: /Users/adincebic/developer.noindex/applying-aspects/aspects.bzl:30:30: Ignoring [@@](https://micro.blog/@)//:lib2 dep
INFO: Analyzed target //:lib (1 packages loaded, 5 targets configured, 5 aspect applications).
INFO: Found 1 target...
INFO: Elapsed time: 0.110s, Critical Path: 0.00s
INFO: 2 processes: 9 action cache hit, 2 internal.
INFO: Build completed successfully, 2 total actions
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And of course, if we inspect &lt;code&gt;bazel-bin/unused_deps_lib.txt&lt;/code&gt;, the file is empty.&lt;/p&gt;

&lt;h2 id=&#34;wrapping-up&#34;&gt;Wrapping up&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;aspect_hints&lt;/code&gt; is a relatively simple idea that allows us to achieve powerful things. I kept the example intentionally simple, but feel free to explore widely used rulesets that rely on &lt;code&gt;aspect_hints&lt;/code&gt;, like &lt;code&gt;rules_swift&lt;/code&gt; for interoperability with C and other languages.&lt;/p&gt;
</description>
      <source:markdown>In the [last article](https://adincebic.com/2026/01/18/introduction-to-aspects-in-bazel.html) I provided a short introduction to the concept of aspects in **Bazel**. To keep the series going, today I will go over the somewhat lesser-known feature `aspect_hints`.

## What is `aspect_hints`?

In simple terms, it is an [implicit attribute available on every rule](https://bazel.build/reference/be/common-definitions#common.aspect_hints) that is meant to be **consumed by an attached aspect**, not by the rule implementation itself. This allows us to convey “hints” to aspects in a lightweight manner.

For example, we may have an aspect that we attach to `swift_library` to report unused deps, but for whatever reason we need a way to tell the aspect to ignore a specific dependency.

## `unused_swift_deps` aspect

Below is my dummy implementation of an aspect that finds and reports unused Swift deps. It is not a real implementation, but it is enough to showcase the potential of `aspect_hints`.

So in my `aspects.bzl` I have the following:

```starlark
load(&#34;@rules_swift//swift:providers.bzl&#34;, &#34;SwiftInfo&#34;)

UnusedSwiftDepsInfo = provider(fields = [&#34;report_files&#34;])

def _unused_swift_deps(target, ctx):
    labels = []

    if hasattr(ctx.rule.attr, &#34;deps&#34;):
        for dep in ctx.rule.attr.deps:
            if SwiftInfo in dep:
                for module in dep[SwiftInfo].direct_modules:
                    labels.append(module.name)
                    labels.append(str(dep.label))

    out = ctx.actions.declare_file(&#34;unused_deps_&#34; + ctx.label.name + &#34;.txt&#34;)
    ctx.actions.write(
        output = out,
        content = &#34;\n&#34;.join(labels),
    )

    transitive_files = []
    for dep in ctx.rule.attr.deps:
        if UnusedSwiftDepsInfo in dep:
            transitive_files.append(dep[UnusedSwiftDepsInfo].report_files)

    all_files = depset(direct = [out], transitive = transitive_files)
    return [
        UnusedSwiftDepsInfo(report_files = all_files),
        DefaultInfo(files = all_files),
    ]

unused_swift_deps_aspect = aspect(
    implementation = _unused_swift_deps,
    attr_aspects = [&#34;deps&#34;],
)
```

All of the concepts in this code are explained in my previous article on aspects, so I won’t go over them again.

## Writing an aspect hint

As mentioned above, now we would like the ability to instruct this aspect to ignore certain labels. The way to achieve that will feel familiar:

1. Create a provider
2. Make an implementation function
3. Create a rule

First we create a provider to be able to easily give a hint to our aspect:

```starlark
UnusedSwiftDepsHintInfo = provider(fields = [&#34;ignore_deps&#34;])
```

Then an implementation function:

```starlark
def _ignore_unused_swift_deps_hint_impl(ctx):
    return [UnusedSwiftDepsHintInfo(ignore_deps = ctx.attr.ignore_deps)]
```

Finally, a rule with the single job of filling the `UnusedSwiftDepsHintInfo` provider:

```starlark
ignore_unused_swift_deps_hint = rule(
    implementation = _ignore_unused_swift_deps_hint_impl,
    attrs = {
        &#34;ignore_deps&#34;: attr.label_list(mandatory = True),
    },
)
```

## Reading `aspect_hints` from the aspect implementation function

Earlier I stated that `aspect_hints` is an implicit rule attribute that can be read from an attached aspect. Let’s go over how to do that.

In my aspect for reporting unused Swift deps, it is simply a matter of reading the attribute like any other, checking if it carries the desired provider, and then acting upon it.

Because `aspect_hints` is a list of labels, I will iterate over it, check if it carries `UnusedSwiftDepsHintInfo`, and skip writing the ignored labels to the report file:

```starlark
    ignored_deps = []

    for aspect_hint in getattr(ctx.rule.attr, &#34;aspect_hints&#34;, []):
        if UnusedSwiftDepsHintInfo in aspect_hint:
            for ignored_dep in aspect_hint[UnusedSwiftDepsHintInfo].ignore_deps:
                ignored_deps.append(ignored_dep.label)
```

Now that we collected labels to ignore, we just need to make sure to actually skip them when reporting:

```starlark
if dep.label in ignored_deps:
    print(&#34;Ignoring {} dep&#34;.format(str(dep.label)))
    continue
```

And that’s it.

## Applying it to the `swift_library`

Here is how my `BUILD.bazel` looks when the newly created aspect hint is applied:

```starlark
load(&#34;@rules_swift//swift:swift_library.bzl&#34;, &#34;swift_library&#34;)
load(&#34;//:aspects.bzl&#34;, &#34;ignore_unused_swift_deps_hint&#34;)

ignore_unused_swift_deps_hint(
    name = &#34;ignore_deps_hint&#34;,
    ignore_deps = [&#34;:lib2&#34;],
)

swift_library(
    name = &#34;lib&#34;,
    srcs = [&#34;main.swift&#34;],
    aspect_hints = [&#34;:ignore_deps_hint&#34;],
    deps = [&#34;:lib2&#34;],
)

swift_library(
    name = &#34;lib2&#34;,
    srcs = [&#34;main.swift&#34;],
    deps = [&#34;:lib3&#34;],
)

swift_library(
    name = &#34;lib3&#34;,
    srcs = [&#34;main.swift&#34;],
    module_name = &#34;CustomModuleName&#34;,
)
```

And if we build:

```starlark id=&#34;cwae1r&#34;
bazel build :lib --aspects aspects.bzl%unused_swift_deps_aspect
```

We see that our `lib2` dep is being ignored:

```
DEBUG: /Users/adincebic/developer.noindex/applying-aspects/aspects.bzl:30:30: Ignoring [@@](https://micro.blog/@)//:lib2 dep
INFO: Analyzed target //:lib (1 packages loaded, 5 targets configured, 5 aspect applications).
INFO: Found 1 target...
INFO: Elapsed time: 0.110s, Critical Path: 0.00s
INFO: 2 processes: 9 action cache hit, 2 internal.
INFO: Build completed successfully, 2 total actions
```

And of course, if we inspect `bazel-bin/unused_deps_lib.txt`, the file is empty.

## Wrapping up

`aspect_hints` is a relatively simple idea that allows us to achieve powerful things. I kept the example intentionally simple, but feel free to explore widely used rulesets that rely on `aspect_hints`, like `rules_swift` for interoperability with C and other languages.
</source:markdown>
    </item>
    
    <item>
      <title>Introduction to aspects in Bazel</title>
      <link>https://adincebic.com/2026/01/18/introduction-to-aspects-in-bazel.html</link>
      <pubDate>Sun, 18 Jan 2026 10:09:10 +0100</pubDate>
      
      <guid>http://adincebic.micro.blog/2026/01/18/introduction-to-aspects-in-bazel.html</guid>
      <description>

&lt;p&gt;Bazel’s way of attaching additional information and behavior to the build graph is called &lt;strong&gt;&lt;a href=&#34;https://bazel.build/extending/aspects&#34;&gt;aspects&lt;/a&gt;&lt;/strong&gt;. They allow us to add extra logic to rules without modifying the rule implementations themselves. Common use cases include validation actions or analysis tasks such as detecting unused dependencies. These are just a few examples; aspects enable much more complex workflows.&lt;/p&gt;

&lt;h2 id=&#34;a-note-on-using-aspects&#34;&gt;A note on using aspects&lt;/h2&gt;

&lt;p&gt;Earlier, I mentioned that aspects allow us to attach additional logic to rules without modifying rule code. This is true, but only in one of the two ways aspects can be used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Command-line invocation&lt;/strong&gt; – aspects are applied externally at build time. This is what we will focus on in this article.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Attribute attachment&lt;/strong&gt; – aspects are attached directly to rule attributes, which requires modifying the rule definition. This approach will be covered in the next article.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;writing-aspects&#34;&gt;Writing aspects&lt;/h2&gt;

&lt;p&gt;Like most things in Bazel, aspects are rule-like and generally follow this pattern:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write an implementation function that accepts two arguments: &lt;code&gt;target&lt;/code&gt; and &lt;code&gt;ctx&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Optionally execute actions&lt;/li&gt;
&lt;li&gt;Return providers&lt;/li&gt;
&lt;li&gt;Create the aspect by calling the &lt;a href=&#34;https://bazel.build/rules/lib/globals/bzl.html#aspect&#34;&gt;aspect()&lt;/a&gt; function and passing the implementation and configuration arguments&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In this example, we will write an aspect that operates on &lt;code&gt;swift_library&lt;/code&gt; targets (specifically, anything that propagates the &lt;code&gt;SwiftInfo&lt;/code&gt; provider). The aspect will generate a &lt;code&gt;.txt&lt;/code&gt; file containing the names of the Swift modules that the target depends on.&lt;/p&gt;

&lt;p&gt;Given the following target:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;swift_library(
    name = &amp;quot;lib&amp;quot;,
    deps = [&amp;quot;:lib2&amp;quot;],
    srcs = glob([&amp;quot;*.swift&amp;quot;]),
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;When the aspect is applied to this target, it will produce a text file containing &lt;code&gt;lib2&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&#34;loading-required-providers&#34;&gt;Loading required providers&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;aspects.bzl&lt;/code&gt;, we first load the &lt;code&gt;SwiftInfo&lt;/code&gt; provider from &lt;code&gt;rules_swift&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;load(&amp;quot;@rules_swift//swift:providers.bzl&amp;quot;, &amp;quot;SwiftInfo&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Next, we define our own provider to propagate the generated report files transitively:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;SwiftDepsInfo = provider(fields = [&amp;quot;report_files&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;aspect-implementation&#34;&gt;Aspect implementation&lt;/h3&gt;

&lt;p&gt;Now we can write the implementation function:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;def _swift_deps(target, ctx):
    module_names = []

    if hasattr(ctx.rule.attr, &amp;quot;deps&amp;quot;):
        for dep in ctx.rule.attr.deps:
            if SwiftInfo in dep:
                for module in dep[SwiftInfo].direct_modules:
                    module_names.append(module.name)

    out = ctx.actions.declare_file(&amp;quot;swift_deps_&amp;quot; + ctx.label.name + &amp;quot;.txt&amp;quot;)
    ctx.actions.write(
        output = out,
        content = &amp;quot;\n&amp;quot;.join(module_names),
    )

    transitive_files = []
    if hasattr(ctx.rule.attr, &amp;quot;deps&amp;quot;):
        for dep in ctx.rule.attr.deps:
            if SwiftDepsInfo in dep:
                transitive_files.append(dep[SwiftDepsInfo].report_files)

    all_files = depset(direct = [out], transitive = transitive_files)
    return [
        SwiftDepsInfo(report_files = all_files),
        DefaultInfo(files = all_files),
    ]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;So the code above does few simple things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Collects Swift module names from the &lt;code&gt;deps&lt;/code&gt; attribute via the &lt;code&gt;SwiftInfo&lt;/code&gt; provider&lt;/li&gt;
&lt;li&gt;Declares an output file and writes the module names into it&lt;/li&gt;
&lt;li&gt;Collects transitive report files so they materialize as build artifacts&lt;/li&gt;
&lt;li&gt;Returns both a custom provider (&lt;code&gt;SwiftDepsInfo&lt;/code&gt;) and &lt;code&gt;DefaultInfo&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&#34;creating-the-aspect&#34;&gt;Creating the aspect&lt;/h3&gt;

&lt;p&gt;The final step is to define the aspect itself:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;swift_deps_aspect = aspect(
    implementation = _swift_deps,
    attr_aspects = [&amp;quot;deps&amp;quot;],
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The &lt;code&gt;attr_aspects = [&amp;quot;deps&amp;quot;]&lt;/code&gt; argument tells Bazel to propagate this aspect along the &lt;code&gt;deps&lt;/code&gt; attribute. In other words, when the aspect is applied to a rule, Bazel will also apply it to every target listed in that rule’s &lt;code&gt;deps&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&#34;running-the-aspect&#34;&gt;Running the aspect&lt;/h2&gt;

&lt;p&gt;Given the following &lt;code&gt;BUILD.bazel&lt;/code&gt; file:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;load(&amp;quot;@rules_swift//swift:swift_library.bzl&amp;quot;, &amp;quot;swift_library&amp;quot;)

swift_library(
    name = &amp;quot;lib&amp;quot;,
    srcs = [&amp;quot;main.swift&amp;quot;],
    deps = [&amp;quot;:lib2&amp;quot;],
)

swift_library(
    name = &amp;quot;lib2&amp;quot;,
    srcs = [&amp;quot;main.swift&amp;quot;],
    deps = [&amp;quot;:lib3&amp;quot;],
)

swift_library(
    name = &amp;quot;lib3&amp;quot;,
    srcs = [&amp;quot;main.swift&amp;quot;],
    module_name = &amp;quot;CustomModule&amp;quot;,
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Running:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;bazel build :lib --aspects aspects.bzl%swift_deps_aspect
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;will produce three additional text files which you can locate under &lt;code&gt;bazel-bin/&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;swift_deps_lib.txt&lt;/code&gt; – contains &lt;code&gt;lib2&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;swift_deps_lib2.txt&lt;/code&gt; – contains &lt;code&gt;CustomModule&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;swift_deps_lib3.txt&lt;/code&gt; – empty, since it has no dependencies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why does &lt;code&gt;swift_deps_lib2.txt&lt;/code&gt; contain &lt;code&gt;CustomModule&lt;/code&gt; instead of &lt;code&gt;lib3&lt;/code&gt;? Because we are explicitly extracting the Swift module name from the &lt;code&gt;SwiftInfo&lt;/code&gt; provider. If instead we wanted the target name, we could use &lt;code&gt;dep.label.name&lt;/code&gt;, or &lt;code&gt;str(dep.label)&lt;/code&gt; to get the fully qualified Bazel label.&lt;/p&gt;

&lt;h2 id=&#34;going-further&#34;&gt;Going further&lt;/h2&gt;

&lt;p&gt;This article was a brief introduction to Bazel aspects, intended to set the foundation for the next one. In the next article, we will look at more concrete use cases and explore attaching aspects directly to rule attributes from Starlark as well as utilizing some of the other arguments on that &lt;a href=&#34;https://bazel.build/rules/lib/globals/bzl.html#aspect&#34;&gt;aspect()&lt;/a&gt; function.&lt;/p&gt;
</description>
      <source:markdown>Bazel’s way of attaching additional information and behavior to the build graph is called **[aspects](https://bazel.build/extending/aspects)**. They allow us to add extra logic to rules without modifying the rule implementations themselves. Common use cases include validation actions or analysis tasks such as detecting unused dependencies. These are just a few examples; aspects enable much more complex workflows.

## A note on using aspects

Earlier, I mentioned that aspects allow us to attach additional logic to rules without modifying rule code. This is true, but only in one of the two ways aspects can be used:

* **Command-line invocation** – aspects are applied externally at build time. This is what we will focus on in this article.
* **Attribute attachment** – aspects are attached directly to rule attributes, which requires modifying the rule definition. This approach will be covered in the next article.

## Writing aspects

Like most things in Bazel, aspects are rule-like and generally follow this pattern:

1. Write an implementation function that accepts two arguments: `target` and `ctx`
2. Optionally execute actions
3. Return providers
4. Create the aspect by calling the [aspect()](https://bazel.build/rules/lib/globals/bzl.html#aspect) function and passing the implementation and configuration arguments

In this example, we will write an aspect that operates on `swift_library` targets (specifically, anything that propagates the `SwiftInfo` provider). The aspect will generate a `.txt` file containing the names of the Swift modules that the target depends on.

Given the following target:

```starlark
swift_library(
    name = &#34;lib&#34;,
    deps = [&#34;:lib2&#34;],
    srcs = glob([&#34;*.swift&#34;]),
)
```

When the aspect is applied to this target, it will produce a text file containing `lib2`.

### Loading required providers

In `aspects.bzl`, we first load the `SwiftInfo` provider from `rules_swift`:

```starlark
load(&#34;@rules_swift//swift:providers.bzl&#34;, &#34;SwiftInfo&#34;)
```

Next, we define our own provider to propagate the generated report files transitively:

```starlark
SwiftDepsInfo = provider(fields = [&#34;report_files&#34;])
```

### Aspect implementation

Now we can write the implementation function:

```starlark
def _swift_deps(target, ctx):
    module_names = []

    if hasattr(ctx.rule.attr, &#34;deps&#34;):
        for dep in ctx.rule.attr.deps:
            if SwiftInfo in dep:
                for module in dep[SwiftInfo].direct_modules:
                    module_names.append(module.name)

    out = ctx.actions.declare_file(&#34;swift_deps_&#34; + ctx.label.name + &#34;.txt&#34;)
    ctx.actions.write(
        output = out,
        content = &#34;\n&#34;.join(module_names),
    )

    transitive_files = []
    if hasattr(ctx.rule.attr, &#34;deps&#34;):
        for dep in ctx.rule.attr.deps:
            if SwiftDepsInfo in dep:
                transitive_files.append(dep[SwiftDepsInfo].report_files)

    all_files = depset(direct = [out], transitive = transitive_files)
    return [
        SwiftDepsInfo(report_files = all_files),
        DefaultInfo(files = all_files),
    ]
```

So the code above does few simple things:

1. Collects Swift module names from the `deps` attribute via the `SwiftInfo` provider
2. Declares an output file and writes the module names into it
3. Collects transitive report files so they materialize as build artifacts
4. Returns both a custom provider (`SwiftDepsInfo`) and `DefaultInfo`

### Creating the aspect

The final step is to define the aspect itself:

```starlark
swift_deps_aspect = aspect(
    implementation = _swift_deps,
    attr_aspects = [&#34;deps&#34;],
)
```

**Note:** The `attr_aspects = [&#34;deps&#34;]` argument tells Bazel to propagate this aspect along the `deps` attribute. In other words, when the aspect is applied to a rule, Bazel will also apply it to every target listed in that rule’s `deps`.

## Running the aspect

Given the following `BUILD.bazel` file:

```starlark
load(&#34;@rules_swift//swift:swift_library.bzl&#34;, &#34;swift_library&#34;)

swift_library(
    name = &#34;lib&#34;,
    srcs = [&#34;main.swift&#34;],
    deps = [&#34;:lib2&#34;],
)

swift_library(
    name = &#34;lib2&#34;,
    srcs = [&#34;main.swift&#34;],
    deps = [&#34;:lib3&#34;],
)

swift_library(
    name = &#34;lib3&#34;,
    srcs = [&#34;main.swift&#34;],
    module_name = &#34;CustomModule&#34;,
)
```

Running:

```bash
bazel build :lib --aspects aspects.bzl%swift_deps_aspect
```

will produce three additional text files which you can locate under `bazel-bin/`:

* `swift_deps_lib.txt` – contains `lib2`
* `swift_deps_lib2.txt` – contains `CustomModule`
* `swift_deps_lib3.txt` – empty, since it has no dependencies

Why does `swift_deps_lib2.txt` contain `CustomModule` instead of `lib3`? Because we are explicitly extracting the Swift module name from the `SwiftInfo` provider. If instead we wanted the target name, we could use `dep.label.name`, or `str(dep.label)` to get the fully qualified Bazel label.

## Going further

This article was a brief introduction to Bazel aspects, intended to set the foundation for the next one. In the next article, we will look at more concrete use cases and explore attaching aspects directly to rule attributes from Starlark as well as utilizing some of the other arguments on that [aspect()](https://bazel.build/rules/lib/globals/bzl.html#aspect) function.
</source:markdown>
    </item>
    
    <item>
      <title>Bazel toolchains, repository rules and module extensions</title>
      <link>https://adincebic.com/2026/01/11/bazel-toolchains-repository-rules-and.html</link>
      <pubDate>Sun, 11 Jan 2026 14:23:41 +0100</pubDate>
      
      <guid>http://adincebic.micro.blog/2026/01/11/bazel-toolchains-repository-rules-and.html</guid>
      <description>

&lt;p&gt;In my &lt;a href=&#34;https://adincebic.com/2026/01/04/writing-a-simple-bazel-rule.html&#34;&gt;previous article&lt;/a&gt; I showed how to create a stupidly simple Bazel rule. While that rule was not useful in any way, shape, or form, it provided a gentle introduction to writing Bazel rules and helped build a mental model around them.&lt;/p&gt;

&lt;p&gt;This week we will look at &lt;strong&gt;toolchains&lt;/strong&gt;, which is a more advanced concept but becomes extremely important once your rules depend on external tools.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This walkthrough wires up &lt;strong&gt;macOS only&lt;/strong&gt; to keep the example small. Adding Linux or Windows is the same pattern: download a different binary and register another &lt;code&gt;toolchain(...)&lt;/code&gt; target with different compatibility constraints.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;toolchains&#34;&gt;Toolchains&lt;/h2&gt;

&lt;p&gt;A &lt;a href=&#34;https://bazel.build/extending/toolchains&#34;&gt;Bazel toolchain&lt;/a&gt; is a way of abstracting tools away from rules. Many people describe it as &lt;em&gt;dependency injection&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;More precisely: toolchains are &lt;strong&gt;dependency injection with platform-based resolution&lt;/strong&gt; — Bazel selects the right implementation based on the execution and target platforms.&lt;/p&gt;

&lt;p&gt;Instead of hard-coding a tool inside a rule, the rule asks Bazel to give it “the correct tool for this platform”.&lt;/p&gt;

&lt;h2 id=&#34;example-rules-pkl&#34;&gt;Example: &lt;code&gt;rules_pkl&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;We will create  oversimplified rule called &lt;code&gt;pkl_library&lt;/code&gt; that turns &lt;code&gt;.pkl&lt;/code&gt; files into their JSON representation using the PKL compiler. &lt;a href=&#34;https://pkl-lang.org/blog/introducing-pkl.html&#34;&gt;pkl&lt;/a&gt; is Apple&amp;rsquo;s programing language for producing configurations. There is official &lt;a href=&#34;https://github.com/apple/rules_pkl&#34;&gt;rules_pkl&lt;/a&gt; ruleset and this article doesn&amp;rsquo;t even scratch the surface of the capabilities that it offers.&lt;/p&gt;

&lt;p&gt;To do this we need:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A way to download the PKL binary&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;A toolchain&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;A rule that uses the toolchain&lt;br /&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&#34;downloading-the-pkl-binary&#34;&gt;Downloading the PKL binary&lt;/h2&gt;

&lt;p&gt;We start by writing a repository rule that downloads the PKL compiler and exposes it as a Bazel target.&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;repositories.bzl&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;def _pkl_download_impl(repository_ctx):
    repository_ctx.download(
        url = repository_ctx.attr.url,
        output = &amp;quot;pkl_bin&amp;quot;,
        executable = True,
        sha256 = repository_ctx.attr.sha256,
    )

    repository_ctx.file(
        &amp;quot;BUILD.bazel&amp;quot;,
        &amp;quot;&amp;quot;&amp;quot;
load(&amp;quot;@bazel_skylib//rules:native_binary.bzl&amp;quot;, &amp;quot;native_binary&amp;quot;)

native_binary(
    name = &amp;quot;pkl&amp;quot;,
    src = &amp;quot;pkl_bin&amp;quot;,
    out = &amp;quot;pkl&amp;quot;,
    visibility = [&amp;quot;//visibility:public&amp;quot;],
)
&amp;quot;&amp;quot;&amp;quot;,
    )

pkl_download = repository_rule(
    implementation = _pkl_download_impl,
    attrs = {
        &amp;quot;url&amp;quot;: attr.string(mandatory = True),
        &amp;quot;sha256&amp;quot;: attr.string(mandatory = True),
    },
)
````

This does two things:

* Downloads the PKL binary
* Wraps it using `native_binary`, which creates a proper Bazel executable without relying on a shell

The resulting binary is available as `@pkl_macos//:pkl`.

## Exposing the repository via a module extension

We’re still using a [repository rule](https://bazel.build/external/repo) to do the download; [bzlmod module extensions](https://bazel.build/external/extension) are just how we call that repository rule from `MODULE.bazel`.

Create `extensions.bzl`:

```starlark
load(&amp;quot;//:repositories.bzl&amp;quot;, &amp;quot;pkl_download&amp;quot;)

def _pkl_module_extension_impl(ctx):
    pkl_download(
        name = &amp;quot;pkl_macos&amp;quot;,
        url = &amp;quot;https://github.com/apple/pkl/releases/download/0.30.2/pkl-macos-aarch64&amp;quot;,
        sha256 = &amp;quot;75ca92e3eee7746e22b0f8a55bf1ee5c3ea0a78eec14586cd5618a9195707d5c&amp;quot;,
    )

pkl_extension = module_extension(
    implementation = _pkl_module_extension_impl,
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In &lt;code&gt;MODULE.bazel&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;bazel_dep(name = &amp;quot;platforms&amp;quot;, version = &amp;quot;1.0.0&amp;quot;)
bazel_dep(name = &amp;quot;bazel_skylib&amp;quot;, version = &amp;quot;1.9.0&amp;quot;)

pkl_extension = use_extension(&amp;quot;//:extensions.bzl&amp;quot;, &amp;quot;pkl_extension&amp;quot;)
use_repo(pkl_extension, &amp;quot;pkl_macos&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now the binary can be referenced as &lt;code&gt;@pkl_macos//:pkl&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&#34;creating-the-toolchain&#34;&gt;Creating the toolchain&lt;/h2&gt;

&lt;p&gt;A toolchain is just a rule that returns a &lt;code&gt;ToolchainInfo&lt;/code&gt; provider.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;toolchains.bzl&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;def _pkl_toolchain_impl(ctx):
    return [platform_common.ToolchainInfo(
        pkl_binary = ctx.executable.pkl_binary,
    )]

pkl_toolchain = rule(
    implementation = _pkl_toolchain_impl,
    attrs = {
        &amp;quot;pkl_binary&amp;quot;: attr.label(
            executable = True,
            cfg = &amp;quot;exec&amp;quot;,
            allow_files = True,
            mandatory = True,
        ),
    },
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The important part is &lt;code&gt;cfg = &amp;quot;exec&amp;quot;&lt;/code&gt;, which ensures the binary runs on the execution platform.&lt;/p&gt;

&lt;h2 id=&#34;registering-the-toolchain&#34;&gt;registering the toolchain&lt;/h2&gt;

&lt;p&gt;In &lt;code&gt;BUILD.bazel&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;load(&amp;quot;//:toolchains.bzl&amp;quot;, &amp;quot;pkl_toolchain&amp;quot;)

toolchain_type(
    name = &amp;quot;pkl_toolchain_type&amp;quot;,
    visibility = [&amp;quot;//visibility:public&amp;quot;],
)

pkl_toolchain(
    name = &amp;quot;pkl_toolchain_macos_impl&amp;quot;,
    pkl_binary = &amp;quot;@pkl_macos//:pkl&amp;quot;,
)

toolchain(
    name = &amp;quot;pkl_toolchain_macos&amp;quot;,
    toolchain = &amp;quot;:pkl_toolchain_macos_impl&amp;quot;,
    toolchain_type = &amp;quot;:pkl_toolchain_type&amp;quot;,
    exec_compatible_with = [&amp;quot;@platforms//os:macos&amp;quot;],
    target_compatible_with = [&amp;quot;@platforms//os:macos&amp;quot;],
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To register the toolchain we need to modify our &lt;code&gt;MODULE.bazel&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;register_toolchains(&amp;quot;//:pkl_toolchain&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;using-the-toolchain-in-a-rule&#34;&gt;Using the toolchain in a rule&lt;/h2&gt;

&lt;p&gt;Now we can write &lt;code&gt;pkl_library&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;rules.bzl&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;def _pkl_library_impl(ctx):
    toolchain = ctx.toolchains[&amp;quot;//:pkl_toolchain_type&amp;quot;]
    binary = toolchain.pkl_binary

    compiled_files = []
    for src in ctx.files.srcs:
        compiled_file_name = src.basename.replace(&amp;quot;.pkl&amp;quot;, &amp;quot;.json&amp;quot;)
        compiled_file = ctx.actions.declare_file(compiled_file_name)

        ctx.actions.run(
            executable = binary,
            tools = [binary],
            inputs = [src],
            outputs = [compiled_file],
            arguments = [
                &amp;quot;eval&amp;quot;,
                src.path,
                &amp;quot;--format&amp;quot;,
                &amp;quot;json&amp;quot;,
                &amp;quot;-o&amp;quot;,
                compiled_file.path,
            ],
            mnemonic = &amp;quot;PKLCompile&amp;quot;,
        )

        compiled_files.append(compiled_file)

    return [DefaultInfo(files = depset(compiled_files))]

pkl_library = rule(
    implementation = _pkl_library_impl,
    attrs = {
        &amp;quot;srcs&amp;quot;: attr.label_list(
            allow_files = [&amp;quot;.pkl&amp;quot;],
            mandatory = True,
        ),
    },
    toolchains = [&amp;quot;//:pkl_toolchain_type&amp;quot;],
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The rule never knows which concrete PKL binary is being used — it only sees the resolved toolchain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; Here instead of writing a for loop that creates new action per file is a decision that we need to make on a case by case basis. Sometimes it can be faster to run multiple actions in parallel than invoking a tool once and giving it a list of files to process. It heavily depends on the tool itself and shows us how bazel can be powerful in these scenarios.&lt;/p&gt;

&lt;h2 id=&#34;final-thoughts&#34;&gt;Final thoughts&lt;/h2&gt;

&lt;p&gt;This was a practical look at repository rules, module extensions, toolchains, and how they fit together.&lt;/p&gt;

&lt;p&gt;Toolchains are one of Bazel’s most powerful features. Once you start writing rules that depend on real tools (compilers, linters, generators), this pattern becomes unavoidable.&lt;/p&gt;

&lt;p&gt;This marks the completion of my second article where I try to give real world examples of using bazel&amp;rsquo;s various features.&lt;/p&gt;
</description>
      <source:markdown>In my [previous article](https://adincebic.com/2026/01/04/writing-a-simple-bazel-rule.html) I showed how to create a stupidly simple Bazel rule. While that rule was not useful in any way, shape, or form, it provided a gentle introduction to writing Bazel rules and helped build a mental model around them.

This week we will look at **toolchains**, which is a more advanced concept but becomes extremely important once your rules depend on external tools.

&gt; This walkthrough wires up **macOS only** to keep the example small. Adding Linux or Windows is the same pattern: download a different binary and register another `toolchain(...)` target with different compatibility constraints.

## Toolchains

A [Bazel toolchain](https://bazel.build/extending/toolchains) is a way of abstracting tools away from rules. Many people describe it as *dependency injection*.

More precisely: toolchains are **dependency injection with platform-based resolution** — Bazel selects the right implementation based on the execution and target platforms.

Instead of hard-coding a tool inside a rule, the rule asks Bazel to give it “the correct tool for this platform”.

## Example: `rules_pkl`

We will create  oversimplified rule called `pkl_library` that turns `.pkl` files into their JSON representation using the PKL compiler. [pkl](https://pkl-lang.org/blog/introducing-pkl.html) is Apple&#39;s programing language for producing configurations. There is official [rules_pkl](https://github.com/apple/rules_pkl) ruleset and this article doesn&#39;t even scratch the surface of the capabilities that it offers.

To do this we need:

1. A way to download the PKL binary  
2. A toolchain  
3. A rule that uses the toolchain  

## Downloading the PKL binary

We start by writing a repository rule that downloads the PKL compiler and exposes it as a Bazel target.

Create `repositories.bzl`:

```starlark
def _pkl_download_impl(repository_ctx):
    repository_ctx.download(
        url = repository_ctx.attr.url,
        output = &#34;pkl_bin&#34;,
        executable = True,
        sha256 = repository_ctx.attr.sha256,
    )

    repository_ctx.file(
        &#34;BUILD.bazel&#34;,
        &#34;&#34;&#34;
load(&#34;@bazel_skylib//rules:native_binary.bzl&#34;, &#34;native_binary&#34;)

native_binary(
    name = &#34;pkl&#34;,
    src = &#34;pkl_bin&#34;,
    out = &#34;pkl&#34;,
    visibility = [&#34;//visibility:public&#34;],
)
&#34;&#34;&#34;,
    )

pkl_download = repository_rule(
    implementation = _pkl_download_impl,
    attrs = {
        &#34;url&#34;: attr.string(mandatory = True),
        &#34;sha256&#34;: attr.string(mandatory = True),
    },
)
````

This does two things:

* Downloads the PKL binary
* Wraps it using `native_binary`, which creates a proper Bazel executable without relying on a shell

The resulting binary is available as `@pkl_macos//:pkl`.

## Exposing the repository via a module extension

We’re still using a [repository rule](https://bazel.build/external/repo) to do the download; [bzlmod module extensions](https://bazel.build/external/extension) are just how we call that repository rule from `MODULE.bazel`.

Create `extensions.bzl`:

```starlark
load(&#34;//:repositories.bzl&#34;, &#34;pkl_download&#34;)

def _pkl_module_extension_impl(ctx):
    pkl_download(
        name = &#34;pkl_macos&#34;,
        url = &#34;https://github.com/apple/pkl/releases/download/0.30.2/pkl-macos-aarch64&#34;,
        sha256 = &#34;75ca92e3eee7746e22b0f8a55bf1ee5c3ea0a78eec14586cd5618a9195707d5c&#34;,
    )

pkl_extension = module_extension(
    implementation = _pkl_module_extension_impl,
)
```

In `MODULE.bazel`:

```starlark
bazel_dep(name = &#34;platforms&#34;, version = &#34;1.0.0&#34;)
bazel_dep(name = &#34;bazel_skylib&#34;, version = &#34;1.9.0&#34;)

pkl_extension = use_extension(&#34;//:extensions.bzl&#34;, &#34;pkl_extension&#34;)
use_repo(pkl_extension, &#34;pkl_macos&#34;)
```

Now the binary can be referenced as `@pkl_macos//:pkl`.

## Creating the toolchain

A toolchain is just a rule that returns a `ToolchainInfo` provider.

`toolchains.bzl`:

```starlark
def _pkl_toolchain_impl(ctx):
    return [platform_common.ToolchainInfo(
        pkl_binary = ctx.executable.pkl_binary,
    )]

pkl_toolchain = rule(
    implementation = _pkl_toolchain_impl,
    attrs = {
        &#34;pkl_binary&#34;: attr.label(
            executable = True,
            cfg = &#34;exec&#34;,
            allow_files = True,
            mandatory = True,
        ),
    },
)
```

The important part is `cfg = &#34;exec&#34;`, which ensures the binary runs on the execution platform.

## registering the toolchain

In `BUILD.bazel`:

```starlark
load(&#34;//:toolchains.bzl&#34;, &#34;pkl_toolchain&#34;)

toolchain_type(
    name = &#34;pkl_toolchain_type&#34;,
    visibility = [&#34;//visibility:public&#34;],
)

pkl_toolchain(
    name = &#34;pkl_toolchain_macos_impl&#34;,
    pkl_binary = &#34;@pkl_macos//:pkl&#34;,
)

toolchain(
    name = &#34;pkl_toolchain_macos&#34;,
    toolchain = &#34;:pkl_toolchain_macos_impl&#34;,
    toolchain_type = &#34;:pkl_toolchain_type&#34;,
    exec_compatible_with = [&#34;@platforms//os:macos&#34;],
    target_compatible_with = [&#34;@platforms//os:macos&#34;],
)
```

To register the toolchain we need to modify our `MODULE.bazel`:

```starlark
register_toolchains(&#34;//:pkl_toolchain&#34;)
```

## Using the toolchain in a rule

Now we can write `pkl_library`.

Create `rules.bzl`:

```starlark
def _pkl_library_impl(ctx):
    toolchain = ctx.toolchains[&#34;//:pkl_toolchain_type&#34;]
    binary = toolchain.pkl_binary

    compiled_files = []
    for src in ctx.files.srcs:
        compiled_file_name = src.basename.replace(&#34;.pkl&#34;, &#34;.json&#34;)
        compiled_file = ctx.actions.declare_file(compiled_file_name)

        ctx.actions.run(
            executable = binary,
            tools = [binary],
            inputs = [src],
            outputs = [compiled_file],
            arguments = [
                &#34;eval&#34;,
                src.path,
                &#34;--format&#34;,
                &#34;json&#34;,
                &#34;-o&#34;,
                compiled_file.path,
            ],
            mnemonic = &#34;PKLCompile&#34;,
        )

        compiled_files.append(compiled_file)

    return [DefaultInfo(files = depset(compiled_files))]

pkl_library = rule(
    implementation = _pkl_library_impl,
    attrs = {
        &#34;srcs&#34;: attr.label_list(
            allow_files = [&#34;.pkl&#34;],
            mandatory = True,
        ),
    },
    toolchains = [&#34;//:pkl_toolchain_type&#34;],
)
```

The rule never knows which concrete PKL binary is being used — it only sees the resolved toolchain.

**NOTE:** Here instead of writing a for loop that creates new action per file is a decision that we need to make on a case by case basis. Sometimes it can be faster to run multiple actions in parallel than invoking a tool once and giving it a list of files to process. It heavily depends on the tool itself and shows us how bazel can be powerful in these scenarios.

## Final thoughts

This was a practical look at repository rules, module extensions, toolchains, and how they fit together.

Toolchains are one of Bazel’s most powerful features. Once you start writing rules that depend on real tools (compilers, linters, generators), this pattern becomes unavoidable.

This marks the completion of my second article where I try to give real world examples of using bazel&#39;s various features.
</source:markdown>
    </item>
    
    <item>
      <title>Writing a simple bazel rule</title>
      <link>https://adincebic.com/2026/01/04/writing-a-simple-bazel-rule.html</link>
      <pubDate>Sun, 04 Jan 2026 19:51:46 +0100</pubDate>
      
      <guid>http://adincebic.micro.blog/2026/01/04/writing-a-simple-bazel-rule.html</guid>
      <description>

&lt;p&gt;This article does not get into what the &lt;a href=&#34;https://bazel.build&#34;&gt;Bazel build system&lt;/a&gt; is or why you might consider using it. Instead, it focuses on explaining, in very simple terms, how to write a Bazel rule.&lt;/p&gt;

&lt;h2 id=&#34;first-things-first&#34;&gt;First things first&lt;/h2&gt;

&lt;p&gt;You need a Bazel repository, often referred to as a &lt;em&gt;workspace&lt;/em&gt;. To create one, you need a &lt;code&gt;MODULE.bazel&lt;/code&gt; file at the root of your project. This file is used to declare external dependencies, although that is not its only purpose. For now, let’s keep &lt;code&gt;MODULE.bazel&lt;/code&gt; empty.&lt;/p&gt;

&lt;p&gt;Next is a &lt;code&gt;BUILD.bazel&lt;/code&gt; file. This is where rules are &lt;em&gt;instantiated&lt;/em&gt; (used). The result of instantiating a rule is a Bazel target.
Create an empty &lt;code&gt;BUILD.bazel&lt;/code&gt; file at the root of the project as well.&lt;/p&gt;

&lt;h2 id=&#34;writing-the-rule&#34;&gt;Writing the rule&lt;/h2&gt;

&lt;p&gt;Think of a Bazel rule as a way to teach Bazel how to produce something. We will start by producing a simple text file and then make it slightly more complex.&lt;/p&gt;

&lt;p&gt;We will call this rule &lt;code&gt;hello&lt;/code&gt;. It will produce a file named &lt;code&gt;hello.txt&lt;/code&gt; containing the word &lt;code&gt;&amp;quot;hello&amp;quot;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Create a file called &lt;code&gt;hello.bzl&lt;/code&gt; with the following content:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;def _hello_impl(ctx):
    file = ctx.actions.declare_file(ctx.label.name + &amp;quot;.txt&amp;quot;)
    ctx.actions.write(
        output = file,
        content = &amp;quot;hello&amp;quot;,
    )
    return DefaultInfo(files = depset([file]))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This function is the implementation of our &lt;code&gt;hello&lt;/code&gt; rule. Notice that the function name ends with &lt;code&gt;_impl&lt;/code&gt;. This is a common Bazel convention for rule implementation functions, although it is not strictly required.&lt;/p&gt;

&lt;p&gt;The function takes a single parameter, &lt;code&gt;ctx&lt;/code&gt;. Every rule implementation receives a &lt;code&gt;ctx&lt;/code&gt; (&lt;a href=&#34;https://bazel.build/rules/lib/builtins/ctx&#34;&gt;context&lt;/a&gt;) object, which provides access to attributes, labels, and the &lt;a href=&#34;https://bazel.build/rules/lib/builtins/actions&#34;&gt;actions&lt;/a&gt; API used to interact with Bazel.&lt;/p&gt;

&lt;p&gt;Before creating an action, we declare the output file:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;file = ctx.actions.declare_file(ctx.label.name + &amp;quot;.txt&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This tells Bazel that the rule will produce a file named after the target (&lt;code&gt;hello.txt&lt;/code&gt; in our case). The returned &lt;a href=&#34;https://bazel.build/rules/lib/builtins/File&#34;&gt;file&lt;/a&gt; object represents a declared output that can be passed to actions.&lt;/p&gt;

&lt;p&gt;Next, we create an action that writes content to the file:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;ctx.actions.write(
    output = file,
    content = &amp;quot;hello&amp;quot;,
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here we explicitly tell Bazel what the output of the action is. Being explicit about outputs (and inputs, when present) is a defining characteristic of Bazel’s build model. In this example, there are no inputs—only an output.&lt;/p&gt;

&lt;p&gt;Finally, we return a result from the rule using the &lt;a href=&#34;https://bazel.build/rules/lib/providers/DefaultInfo&#34;&gt;DefaultInfo&lt;/a&gt; &lt;a href=&#34;https://bazel.build/rules/lib/providers&#34;&gt;provider&lt;/a&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;return DefaultInfo(files = depset([file]))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This makes the produced file part of the target’s default outputs. We will not go into &lt;a href=&#34;https://bazel.build/rules/lib/providers&#34;&gt;providers&lt;/a&gt; or &lt;a href=&#34;https://bazel.build/extending/depsets&#34;&gt;depset&lt;/a&gt; in this article; the official documentation covers those topics in depth.&lt;/p&gt;

&lt;p&gt;Now that the implementation function exists, we define the rule itself by calling &lt;code&gt;rule()&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;hello = rule(
    implementation = _hello_impl,
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This is enough to define a usable rule.&lt;/p&gt;

&lt;h2 id=&#34;using-the-rule&#34;&gt;Using the rule&lt;/h2&gt;

&lt;p&gt;In the previously created &lt;code&gt;BUILD.bazel&lt;/code&gt; file, we first load the rule:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;load(&amp;quot;:hello.bzl&amp;quot;, &amp;quot;hello&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then we instantiate it:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;hello(
    name = &amp;quot;hello&amp;quot;,
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now run:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;bazel build :hello
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You should see output similar to:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;INFO: Analyzed target //:hello (5 packages loaded, 7 targets configured).
INFO: Found 1 target...
Target //:hello up-to-date:
  bazel-bin/hello.txt
INFO: Elapsed time: 0.285s, Critical Path: 0.00s
INFO: 2 processes: 2 internal.
INFO: Build completed successfully, 2 total actions
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Pay attention to the line &lt;code&gt;bazel-bin/hello.txt&lt;/code&gt;. This is where Bazel exposes the output file (typically via a symlink). Open it with:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;open bazel-bin/hello.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You should see that the file contains the word &lt;code&gt;hello&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&#34;rule-attributes&#34;&gt;Rule attributes&lt;/h2&gt;

&lt;p&gt;To make this rule somewhat useful, we will add a new attribute called &lt;code&gt;content&lt;/code&gt; that replaces the hardcoded &lt;code&gt;&amp;quot;hello&amp;quot;&lt;/code&gt; string.&lt;/p&gt;

&lt;p&gt;The first step is to declare that our rule has an attribute named &lt;code&gt;content&lt;/code&gt;. We do this by providing a dictionary to the &lt;code&gt;attrs&lt;/code&gt; parameter of &lt;code&gt;rule()&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;hello = rule(
    implementation = _hello_impl,
    attrs = {
        &amp;quot;content&amp;quot;: attr.string(mandatory = True),
    },
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here we declare a mandatory string attribute named &lt;code&gt;content&lt;/code&gt;. Bazel will enforce that this attribute is provided when the rule is instantiated.&lt;/p&gt;

&lt;p&gt;Next, we read the value of the attribute in the rule implementation function. Rule attributes are accessible through &lt;code&gt;ctx.attr&lt;/code&gt;. We replace the hardcoded value with &lt;code&gt;ctx.attr.content&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;def _hello_impl(ctx):
    file = ctx.actions.declare_file(ctx.label.name + &amp;quot;.txt&amp;quot;)
    ctx.actions.write(
        output = file,
        content = ctx.attr.content,
    )
    return DefaultInfo(files = depset([file]))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Finally, we provide the attribute value when instantiating the rule in the &lt;code&gt;BUILD.bazel&lt;/code&gt; file:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-starlark&#34;&gt;hello(
    name = &amp;quot;hello&amp;quot;,
    content = &amp;quot;Hello, world!&amp;quot;,
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;After running:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;bazel build :hello
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;the file located at &lt;code&gt;bazel-bin/hello.txt&lt;/code&gt; will contain the provided text.&lt;/p&gt;

&lt;h2 id=&#34;that-s-it&#34;&gt;That&amp;rsquo;s it&lt;/h2&gt;

&lt;p&gt;This concludes my first article on the Bazel build system. I plan to expand this rule in subsequent articles to demonstrate more advanced concepts and gradually make it more useful. This also marks my first article of the year, and I plan to write one technical article every week until the year 2026 concludes.&lt;/p&gt;
</description>
      <source:markdown>This article does not get into what the [Bazel build system](https://bazel.build) is or why you might consider using it. Instead, it focuses on explaining, in very simple terms, how to write a Bazel rule.

## First things first

You need a Bazel repository, often referred to as a *workspace*. To create one, you need a `MODULE.bazel` file at the root of your project. This file is used to declare external dependencies, although that is not its only purpose. For now, let’s keep `MODULE.bazel` empty.

Next is a `BUILD.bazel` file. This is where rules are *instantiated* (used). The result of instantiating a rule is a Bazel target.
Create an empty `BUILD.bazel` file at the root of the project as well.

## Writing the rule

Think of a Bazel rule as a way to teach Bazel how to produce something. We will start by producing a simple text file and then make it slightly more complex.

We will call this rule `hello`. It will produce a file named `hello.txt` containing the word `&#34;hello&#34;`.

Create a file called `hello.bzl` with the following content:

```starlark
def _hello_impl(ctx):
    file = ctx.actions.declare_file(ctx.label.name + &#34;.txt&#34;)
    ctx.actions.write(
        output = file,
        content = &#34;hello&#34;,
    )
    return DefaultInfo(files = depset([file]))
```

This function is the implementation of our `hello` rule. Notice that the function name ends with `_impl`. This is a common Bazel convention for rule implementation functions, although it is not strictly required.

The function takes a single parameter, `ctx`. Every rule implementation receives a `ctx` ([context](https://bazel.build/rules/lib/builtins/ctx)) object, which provides access to attributes, labels, and the [actions](https://bazel.build/rules/lib/builtins/actions) API used to interact with Bazel.

Before creating an action, we declare the output file:

```starlark
file = ctx.actions.declare_file(ctx.label.name + &#34;.txt&#34;)
```

This tells Bazel that the rule will produce a file named after the target (`hello.txt` in our case). The returned [file](https://bazel.build/rules/lib/builtins/File) object represents a declared output that can be passed to actions.

Next, we create an action that writes content to the file:

```starlark
ctx.actions.write(
    output = file,
    content = &#34;hello&#34;,
)
```

Here we explicitly tell Bazel what the output of the action is. Being explicit about outputs (and inputs, when present) is a defining characteristic of Bazel’s build model. In this example, there are no inputs—only an output.

Finally, we return a result from the rule using the [DefaultInfo](https://bazel.build/rules/lib/providers/DefaultInfo) [provider](https://bazel.build/rules/lib/providers):

```starlark
return DefaultInfo(files = depset([file]))
```

This makes the produced file part of the target’s default outputs. We will not go into [providers](https://bazel.build/rules/lib/providers) or [depset](https://bazel.build/extending/depsets) in this article; the official documentation covers those topics in depth.

Now that the implementation function exists, we define the rule itself by calling `rule()`:

```starlark
hello = rule(
    implementation = _hello_impl,
)
```

This is enough to define a usable rule.

## Using the rule

In the previously created `BUILD.bazel` file, we first load the rule:

```starlark
load(&#34;:hello.bzl&#34;, &#34;hello&#34;)
```

Then we instantiate it:

```starlark
hello(
    name = &#34;hello&#34;,
)
```

Now run:

```
bazel build :hello
```

You should see output similar to:

```
INFO: Analyzed target //:hello (5 packages loaded, 7 targets configured).
INFO: Found 1 target...
Target //:hello up-to-date:
  bazel-bin/hello.txt
INFO: Elapsed time: 0.285s, Critical Path: 0.00s
INFO: 2 processes: 2 internal.
INFO: Build completed successfully, 2 total actions
```

Pay attention to the line `bazel-bin/hello.txt`. This is where Bazel exposes the output file (typically via a symlink). Open it with:

```
open bazel-bin/hello.txt
```

You should see that the file contains the word `hello`.

## Rule attributes

To make this rule somewhat useful, we will add a new attribute called `content` that replaces the hardcoded `&#34;hello&#34;` string.

The first step is to declare that our rule has an attribute named `content`. We do this by providing a dictionary to the `attrs` parameter of `rule()`:

```starlark
hello = rule(
    implementation = _hello_impl,
    attrs = {
        &#34;content&#34;: attr.string(mandatory = True),
    },
)
```

Here we declare a mandatory string attribute named `content`. Bazel will enforce that this attribute is provided when the rule is instantiated.

Next, we read the value of the attribute in the rule implementation function. Rule attributes are accessible through `ctx.attr`. We replace the hardcoded value with `ctx.attr.content`:

```starlark
def _hello_impl(ctx):
    file = ctx.actions.declare_file(ctx.label.name + &#34;.txt&#34;)
    ctx.actions.write(
        output = file,
        content = ctx.attr.content,
    )
    return DefaultInfo(files = depset([file]))
```

Finally, we provide the attribute value when instantiating the rule in the `BUILD.bazel` file:

```starlark
hello(
    name = &#34;hello&#34;,
    content = &#34;Hello, world!&#34;,
)
```

After running:

```
bazel build :hello
```

the file located at `bazel-bin/hello.txt` will contain the provided text.

## That&#39;s it

This concludes my first article on the Bazel build system. I plan to expand this rule in subsequent articles to demonstrate more advanced concepts and gradually make it more useful. This also marks my first article of the year, and I plan to write one technical article every week until the year 2026 concludes.
</source:markdown>
    </item>
    
    <item>
      <title>Reverse Engineering Apple&#39;s on-demand resource Asset Packs: How to Recreate .assetpack Files with Standard Unix Tools</title>
      <link>https://adincebic.com/2025/08/18/reverse-engineering-apples-ondemand-resource.html</link>
      <pubDate>Mon, 18 Aug 2025 14:36:33 +0100</pubDate>
      
      <guid>http://adincebic.micro.blog/2025/08/18/reverse-engineering-apples-ondemand-resource.html</guid>
      <description>

&lt;p&gt;I recently ran into a problem while integrating Apple&amp;rsquo;s on-demand resources system to &lt;a href=&#34;https://bazel.build&#34;&gt;bazel&lt;/a&gt;. Essentially I needed a way to generate &lt;code&gt;.assetpack&lt;/code&gt; archives via command line without calling into &lt;code&gt;xcodebuild&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After spending way too much time debugging this, I finally figured out exactly what Apple&amp;rsquo;s toolchain does to create these files - and more importantly, how to recreate them using standard macOS command-line tools.&lt;/p&gt;

&lt;h2 id=&#34;the-problem-asset-packs-look-like-regular-zip-files-but-aren-t&#34;&gt;The Problem: Asset Packs Look Like Regular Zip Files (But Aren&amp;rsquo;t)&lt;/h2&gt;

&lt;p&gt;When you run &lt;code&gt;file&lt;/code&gt; on an &lt;code&gt;.assetpack&lt;/code&gt;, it tells you it&amp;rsquo;s a zip archive:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;$ file my-assets.assetpack
my-assets.assetpack: Zip archive data
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;So naturally I thought I will create the expected file hierarchy and zip it:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;zip -r new-assets.assetpack some-assetpack-folder/
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Your asset pack becomes unusable. iOS will reject it, and you&amp;rsquo;ll get unhelpful errors about not being able to move the file to the NSBundle.&lt;/p&gt;

&lt;h2 id=&#34;investigating-the-differences&#34;&gt;Investigating the Differences&lt;/h2&gt;

&lt;p&gt;I used &lt;code&gt;zipinfo -v&lt;/code&gt; to examine the internal structure of both Apple&amp;rsquo;s original asset packs and my re-zipped versions:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Apple&amp;rsquo;s Original Asset Pack:&lt;/strong&gt;
- &lt;strong&gt;Compression&lt;/strong&gt;: &lt;code&gt;none (stored)&lt;/code&gt; - zero compression
- &lt;strong&gt;File ordering&lt;/strong&gt;: Very specific sequence starting with &lt;code&gt;META-INF/&lt;/code&gt;
- &lt;strong&gt;Structure&lt;/strong&gt;: Flat hierarchy with files at zip root level
- &lt;strong&gt;Metadata&lt;/strong&gt;: No extended attributes or extra fields
- &lt;strong&gt;Encoding version&lt;/strong&gt;: 3.0&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My zipped Version:&lt;/strong&gt;
- &lt;strong&gt;Compression&lt;/strong&gt;: &lt;code&gt;deflated&lt;/code&gt; - standard compression
- &lt;strong&gt;File ordering&lt;/strong&gt;: Alphabetical (zip&amp;rsquo;s default)
- &lt;strong&gt;Structure&lt;/strong&gt;: Nested bundle directory structure
- &lt;strong&gt;Metadata&lt;/strong&gt;: Full of Unix extended attributes and timestamps
- &lt;strong&gt;Encoding version&lt;/strong&gt;: 2.0&lt;/p&gt;

&lt;h2 id=&#34;the-critical-requirements&#34;&gt;The Critical Requirements&lt;/h2&gt;

&lt;p&gt;After lots of experimentation, I discovered Apple&amp;rsquo;s asset packs have five strict requirements:&lt;/p&gt;

&lt;h3 id=&#34;1-flat-hierarchy-structure&#34;&gt;1. Flat Hierarchy Structure&lt;/h3&gt;

&lt;p&gt;The contents must be at the zip root level, not nested in a bundle directory. Apple&amp;rsquo;s structure looks like:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;META-INF/
_CodeSignature/
SomeFile
Info.plist
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Not like:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;com.company.app.bundle-hash/
├── META-INF/
├── _CodeSignature/
├── SomeFile
└── Info.plist
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;2-zero-compression&#34;&gt;2. Zero Compression&lt;/h3&gt;

&lt;p&gt;Every single file must use the &lt;code&gt;store&lt;/code&gt; method (no compression). This is critical - any compressed files will cause rejection.&lt;/p&gt;

&lt;h3 id=&#34;3-specific-file-ordering&#34;&gt;3. Specific File Ordering&lt;/h3&gt;

&lt;p&gt;The central directory must have entries in this exact order:
1. &lt;code&gt;META-INF/&lt;/code&gt; (directory)
2. &lt;code&gt;META-INF/com.apple.ZipMetadata.plist&lt;/code&gt; (file)
3. &lt;code&gt;_CodeSignature/&lt;/code&gt; (directory)
4. Code signature files
5. Content files
6. &lt;code&gt;Info.plist&lt;/code&gt;&lt;/p&gt;

&lt;h3 id=&#34;4-no-extended-attributes&#34;&gt;4. No Extended Attributes&lt;/h3&gt;

&lt;p&gt;The zip must be clean of any extended attributes, Unix UID/GID info, or extra metadata fields.&lt;/p&gt;

&lt;h3 id=&#34;5-the-critical-metadata-file&#34;&gt;5. The Critical Metadata File&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;META-INF/com.apple.ZipMetadata.plist&lt;/code&gt; file must be the second entry in the zip. This file contains metadata that iOS validates.&lt;/p&gt;

&lt;h2 id=&#34;the-solution-recreating-asset-packs-correctly&#34;&gt;The Solution: Recreating Asset Packs Correctly&lt;/h2&gt;

&lt;p&gt;Here&amp;rsquo;s the call to &lt;code&gt;zip&lt;/code&gt; that packages the assetpack correctly:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;
# Navigate to the assetpack directory
cd some-assetpack-directory/

# Recreate with proper ordering and settings
(echo &amp;quot;META-INF/&amp;quot;; echo &amp;quot;META-INF/com.apple.ZipMetadata.plist&amp;quot;; find . -mindepth 1 -not -path &amp;quot;./META-INF*&amp;quot;) | zip -0 -X recreated.assetpack -@
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Let me break down what each part does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;echo &amp;quot;META-INF/&amp;quot;&lt;/code&gt; - Ensures META-INF directory is first&lt;/li&gt;
&lt;li&gt;&lt;code&gt;echo &amp;quot;META-INF/com.apple.ZipMetadata.plist&amp;quot;&lt;/code&gt; - Puts the critical metadata file second&lt;/li&gt;
&lt;li&gt;&lt;code&gt;find . -mindepth 1 -not -path &amp;quot;./META-INF*&amp;quot;&lt;/code&gt; - Adds everything else while excluding META-INF (to avoid duplicates)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;zip -0 -X ... -@&lt;/code&gt; - Creates zip with zero compression (&lt;code&gt;-0&lt;/code&gt;), no extended attributes (&lt;code&gt;-X&lt;/code&gt;), reading file list from stdin (&lt;code&gt;-@&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;the-bottom-line&#34;&gt;The Bottom Line&lt;/h2&gt;

&lt;p&gt;Apple&amp;rsquo;s asset packs aren&amp;rsquo;t just zip files - they&amp;rsquo;re zip files with very specific structural requirements. iOS validates not just the content, but the exact structure, compression settings, and file ordering of the archive.&lt;/p&gt;

&lt;p&gt;With the right zip parameters and file ordering, it is very easy to create them.&lt;/p&gt;
</description>
      <source:markdown>I recently ran into a problem while integrating Apple&#39;s on-demand resources system to [bazel](https://bazel.build). Essentially I needed a way to generate `.assetpack` archives via command line without calling into `xcodebuild`.

After spending way too much time debugging this, I finally figured out exactly what Apple&#39;s toolchain does to create these files - and more importantly, how to recreate them using standard macOS command-line tools.

## The Problem: Asset Packs Look Like Regular Zip Files (But Aren&#39;t)

When you run `file` on an `.assetpack`, it tells you it&#39;s a zip archive:

```bash
$ file my-assets.assetpack
my-assets.assetpack: Zip archive data
```

So naturally I thought I will create the expected file hierarchy and zip it:

```bash
zip -r new-assets.assetpack some-assetpack-folder/
```

Your asset pack becomes unusable. iOS will reject it, and you&#39;ll get unhelpful errors about not being able to move the file to the NSBundle.

## Investigating the Differences

I used `zipinfo -v` to examine the internal structure of both Apple&#39;s original asset packs and my re-zipped versions:

**Apple&#39;s Original Asset Pack:**
- **Compression**: `none (stored)` - zero compression
- **File ordering**: Very specific sequence starting with `META-INF/`
- **Structure**: Flat hierarchy with files at zip root level
- **Metadata**: No extended attributes or extra fields
- **Encoding version**: 3.0

**My zipped Version:**
- **Compression**: `deflated` - standard compression
- **File ordering**: Alphabetical (zip&#39;s default)
- **Structure**: Nested bundle directory structure
- **Metadata**: Full of Unix extended attributes and timestamps
- **Encoding version**: 2.0

## The Critical Requirements

After lots of experimentation, I discovered Apple&#39;s asset packs have five strict requirements:

### 1. Flat Hierarchy Structure
The contents must be at the zip root level, not nested in a bundle directory. Apple&#39;s structure looks like:
```
META-INF/
_CodeSignature/
SomeFile
Info.plist
```

Not like:
```
com.company.app.bundle-hash/
├── META-INF/
├── _CodeSignature/
├── SomeFile
└── Info.plist
```

### 2. Zero Compression
Every single file must use the `store` method (no compression). This is critical - any compressed files will cause rejection.

### 3. Specific File Ordering
The central directory must have entries in this exact order:
1. `META-INF/` (directory)
2. `META-INF/com.apple.ZipMetadata.plist` (file)
3. `_CodeSignature/` (directory)
4. Code signature files
5. Content files
6. `Info.plist`

### 4. No Extended Attributes
The zip must be clean of any extended attributes, Unix UID/GID info, or extra metadata fields.

### 5. The Critical Metadata File
The `META-INF/com.apple.ZipMetadata.plist` file must be the second entry in the zip. This file contains metadata that iOS validates.

## The Solution: Recreating Asset Packs Correctly

Here&#39;s the call to `zip` that packages the assetpack correctly:

```bash

# Navigate to the assetpack directory
cd some-assetpack-directory/

# Recreate with proper ordering and settings
(echo &#34;META-INF/&#34;; echo &#34;META-INF/com.apple.ZipMetadata.plist&#34;; find . -mindepth 1 -not -path &#34;./META-INF*&#34;) | zip -0 -X recreated.assetpack -@
```

Let me break down what each part does:

- `echo &#34;META-INF/&#34;` - Ensures META-INF directory is first
- `echo &#34;META-INF/com.apple.ZipMetadata.plist&#34;` - Puts the critical metadata file second
- `find . -mindepth 1 -not -path &#34;./META-INF*&#34;` - Adds everything else while excluding META-INF (to avoid duplicates)
- `zip -0 -X ... -@` - Creates zip with zero compression (`-0`), no extended attributes (`-X`), reading file list from stdin (`-@`)

## The Bottom Line

Apple&#39;s asset packs aren&#39;t just zip files - they&#39;re zip files with very specific structural requirements. iOS validates not just the content, but the exact structure, compression settings, and file ordering of the archive.

With the right zip parameters and file ordering, it is very easy to create them.
</source:markdown>
    </item>
    
    <item>
      <title>Integrating Conan with Xcode to manage C/C&#43;&#43; libraries</title>
      <link>https://adincebic.com/2023/09/23/integrating-conan-with.html</link>
      <pubDate>Sat, 23 Sep 2023 08:11:59 +0100</pubDate>
      
      <guid>http://adincebic.micro.blog/2023/09/23/integrating-conan-with.html</guid>
      <description>

&lt;p&gt;In my &lt;a href=&#34;https://adincebic.com/2023/09/03/linking-c-static.html&#34;&gt;last post&lt;/a&gt; I went over how to manually link C++ libraries to Xcode project. While that is useful to know, it gets tedious to maintain once you have multiple C++ dependencies. In addition, if the library you want to link does not come with built binary, you are responsible for that too which in some cases may not be fun at all.&lt;/p&gt;

&lt;h2 id=&#34;enter-conan&#34;&gt;Enter Conan&lt;/h2&gt;

&lt;p&gt;&lt;a href=&#34;https://conan.io&#34;&gt;Conan&lt;/a&gt; is a package manager for C/C++ that in addition to getting libraries, it allows for easy building of libraries for various CPU architectures which I personally find incredibly useful.&lt;/p&gt;

&lt;h2 id=&#34;why&#34;&gt;Why?&lt;/h2&gt;

&lt;p&gt;Past few weeks I spent some time building cross-platform library using C++ for iOS and Android which ended up depending on &lt;a href=&#34;https://www.cryptopp.com&#34;&gt;Crypto++&lt;/a&gt;. This meant that besides building the &lt;a href=&#34;https://www.cryptopp.com&#34;&gt;Crypto++&lt;/a&gt; from source for iOS and iOS simulator, now I needed to build it for four more architectures (armV7, armV8), x86 and x86_64) that Android runs on.&lt;/p&gt;

&lt;h2 id=&#34;integrating-conan-with-xcode&#34;&gt;Integrating Conan with Xcode&lt;/h2&gt;

&lt;p&gt;First things first, you need to make sure that you have &lt;a href=&#34;https://conan.io&#34;&gt;Conan&lt;/a&gt; installed. The easiest way is with &lt;a href=&#34;https://brew.sh&#34;&gt;Homebrew&lt;/a&gt;, simply open terminal and run &lt;code&gt;brew install conan&lt;/code&gt;.
Once that&amp;rsquo;s sorted out, change directory to where your Xcode project is and create new &amp;ldquo;conanfile.txt&amp;rdquo; file. Make sure that it contains the following:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;[requires]
cryptopp/8.8.0
[generators]
XcodeDeps
XcodeToolchain
[layout]
cmake_layout
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;this sets up &lt;a href=&#34;https://conan.io/&#34;&gt;Conan&lt;/a&gt; to look for 8.8.0 version of &lt;a href=&#34;https://www.cryptopp.com&#34;&gt;Crypto++&lt;/a&gt;. Then in the &amp;ldquo;generators&amp;rdquo; section of the file it tells Conan to generate &lt;a href=&#34;https://developer.apple.com/documentation/xcode/adding-a-build-configuration-file-to-your-project&#34;&gt;xcconfig&lt;/a&gt; files that will ultimately help us link the library.&lt;/p&gt;

&lt;p&gt;Next, it is required to create a &lt;a href=&#34;https://docs.conan.io/2.0/reference/config_files/profiles.html&#34;&gt;Conan profile&lt;/a&gt; that describes how to build the library. It contains information like which CPU architecture to build for, whether to build in debug or release mode etc. So still in directory where your Xcode project is, go ahead and create empty file and give it &amp;ldquo;simulator-profile&amp;rdquo; name. You can pick whatever name you like, this is just my preference. After that, it should contain the following:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;[settings]
arch=armv8
build_type=Debug
compiler=apple-clang
compiler.cppstd=gnu17
compiler.libcxx=libc++
compiler.version=15
os=iOS
os.version=17.0
os.sdk=iphonesimulator
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;this is pretty self-explanatory. It tells Conan to build the library for armV8 architecture using Clang version 15 and it tells what is the minimum iOs deployment target in addition to which SDK to build for.&lt;/p&gt;

&lt;h2 id=&#34;building-and-linking&#34;&gt;Building and linking&lt;/h2&gt;

&lt;p&gt;After installing Conan, setting up &amp;ldquo;conanfile.txt&amp;rdquo; and &amp;ldquo;simulator-profile&amp;rdquo; it is time to build.
Make sure your working directory in the terminal is the one that contains &amp;ldquo;conanfile.txt&amp;rdquo; and run&lt;/p&gt;

&lt;p&gt;&lt;code&gt;conan install . --build=missing --profile=simulator-profile --output-folder=conan-generated&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Here is the breakdown of the entire command:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;conan install .&lt;/code&gt; runs &amp;ldquo;install&amp;rdquo; command from conan. The &amp;ldquo;.&amp;rdquo; is used to look for &amp;ldquo;conanfile.txt&amp;rdquo; in the current working directory.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--build=missing&lt;/code&gt; explicitly tells Conan that the build for the library is missing which  makes it build the library from source, hence the word &amp;ldquo;missing&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--profile=simulator-profile&lt;/code&gt; this is passing profile file that I created earlier.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--output-folder=conan-generated&lt;/code&gt; this is the directory where Conan will generate files using generators I specified in &amp;ldquo;conanfile.txt&amp;rdquo;. I named it &amp;ldquo;conan-generated&amp;rdquo; but you can name that whatever you like, popular one is &amp;ldquo;build&amp;rdquo;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So after command ran, you should see &amp;ldquo;conan-generated&amp;rdquo; directory next to your other project files. I recommend adding &amp;ldquo;conan-generated/&amp;rdquo; to .gitignore.
All that&amp;rsquo;s left is to open your Xcode project and add &amp;ldquo;conan_config.xcconfig&amp;rdquo; file that is in &amp;ldquo;conan-generated&amp;rdquo; directory. I won&amp;rsquo;t go into specifics of using config files in Xcode, there is plenty of articles about that, like the &lt;a href=&#34;https://nshipster.com/xcconfig/&#34;&gt;one from NSHipster&lt;/a&gt;.
It&amp;rsquo;s important to note that &lt;a href=&#34;https://docs.conan.io/2/reference/tools/apple/xcodedeps.html#conan-tools-apple-xcodedeps&#34;&gt;XcodeDeps&lt;/a&gt; aggregates all files and settings. Therefore, running the command above multiple times is not only acceptable but also necessary if you want it to generate configurations for all cases. For example if you want to have library linked both for simulator and device in debug and release configurations, you should run the command with different parameters multiple times.&lt;/p&gt;

&lt;h2 id=&#34;closing-words&#34;&gt;Closing words&lt;/h2&gt;

&lt;p&gt;Even though there is a bit of learning curve and setup when it comes to Conan, it makes our lives much easier. Once you grasp the concepts you realize that integration between Conan and Xcode is fundamentally very simple. To deepen your understanding of Conan and its generators, it is best to consult &lt;a href=&#34;https://docs.conan.io/2/&#34;&gt;official Conan documentation page&lt;/a&gt;. And to explore the vast universe of C/C++ libraries available for use with Conan, &lt;a href=&#34;https://conan.io/center&#34;&gt;Conan center&lt;/a&gt; is the best place for that.&lt;/p&gt;
</description>
      <source:markdown>In my [last post](https://adincebic.com/2023/09/03/linking-c-static.html) I went over how to manually link C++ libraries to Xcode project. While that is useful to know, it gets tedious to maintain once you have multiple C++ dependencies. In addition, if the library you want to link does not come with built binary, you are responsible for that too which in some cases may not be fun at all.

## Enter Conan
[Conan](https://conan.io) is a package manager for C/C++ that in addition to getting libraries, it allows for easy building of libraries for various CPU architectures which I personally find incredibly useful.

## Why?
Past few weeks I spent some time building cross-platform library using C++ for iOS and Android which ended up depending on [Crypto++](https://www.cryptopp.com). This meant that besides building the [Crypto++](https://www.cryptopp.com) from source for iOS and iOS simulator, now I needed to build it for four more architectures (armV7, armV8), x86 and x86_64) that Android runs on.

## Integrating Conan with Xcode
First things first, you need to make sure that you have [Conan](https://conan.io) installed. The easiest way is with [Homebrew](https://brew.sh), simply open terminal and run `brew install conan`.
Once that&#39;s sorted out, change directory to where your Xcode project is and create new &#34;conanfile.txt&#34; file. Make sure that it contains the following:

```
[requires]
cryptopp/8.8.0
[generators]
XcodeDeps
XcodeToolchain
[layout]
cmake_layout
```

this sets up [Conan](https://conan.io/) to look for 8.8.0 version of [Crypto++](https://www.cryptopp.com). Then in the &#34;generators&#34; section of the file it tells Conan to generate [xcconfig](https://developer.apple.com/documentation/xcode/adding-a-build-configuration-file-to-your-project) files that will ultimately help us link the library.

Next, it is required to create a [Conan profile](https://docs.conan.io/2.0/reference/config_files/profiles.html) that describes how to build the library. It contains information like which CPU architecture to build for, whether to build in debug or release mode etc. So still in directory where your Xcode project is, go ahead and create empty file and give it &#34;simulator-profile&#34; name. You can pick whatever name you like, this is just my preference. After that, it should contain the following:

```
[settings]
arch=armv8
build_type=Debug
compiler=apple-clang
compiler.cppstd=gnu17
compiler.libcxx=libc++
compiler.version=15
os=iOS
os.version=17.0
os.sdk=iphonesimulator
```

this is pretty self-explanatory. It tells Conan to build the library for armV8 architecture using Clang version 15 and it tells what is the minimum iOs deployment target in addition to which SDK to build for.

## Building and linking
After installing Conan, setting up &#34;conanfile.txt&#34; and &#34;simulator-profile&#34; it is time to build.
Make sure your working directory in the terminal is the one that contains &#34;conanfile.txt&#34; and run

`conan install . --build=missing --profile=simulator-profile --output-folder=conan-generated`

Here is the breakdown of the entire command:

* `conan install .` runs &#34;install&#34; command from conan. The &#34;.&#34; is used to look for &#34;conanfile.txt&#34; in the current working directory.
* `--build=missing` explicitly tells Conan that the build for the library is missing which  makes it build the library from source, hence the word &#34;missing&#34;.
* `--profile=simulator-profile` this is passing profile file that I created earlier.
* `--output-folder=conan-generated` this is the directory where Conan will generate files using generators I specified in &#34;conanfile.txt&#34;. I named it &#34;conan-generated&#34; but you can name that whatever you like, popular one is &#34;build&#34;.

So after command ran, you should see &#34;conan-generated&#34; directory next to your other project files. I recommend adding &#34;conan-generated/&#34; to .gitignore.
All that&#39;s left is to open your Xcode project and add &#34;conan_config.xcconfig&#34; file that is in &#34;conan-generated&#34; directory. I won&#39;t go into specifics of using config files in Xcode, there is plenty of articles about that, like the [one from NSHipster](https://nshipster.com/xcconfig/).
It&#39;s important to note that [XcodeDeps](https://docs.conan.io/2/reference/tools/apple/xcodedeps.html#conan-tools-apple-xcodedeps) aggregates all files and settings. Therefore, running the command above multiple times is not only acceptable but also necessary if you want it to generate configurations for all cases. For example if you want to have library linked both for simulator and device in debug and release configurations, you should run the command with different parameters multiple times.

## Closing words
Even though there is a bit of learning curve and setup when it comes to Conan, it makes our lives much easier. Once you grasp the concepts you realize that integration between Conan and Xcode is fundamentally very simple. To deepen your understanding of Conan and its generators, it is best to consult [official Conan documentation page](https://docs.conan.io/2/). And to explore the vast universe of C/C++ libraries available for use with Conan, [Conan center](https://conan.io/center) is the best place for that.
</source:markdown>
    </item>
    
    <item>
      <title>Linking C&#43;&#43; static library in iOS project</title>
      <link>https://adincebic.com/2023/09/03/linking-c-static.html</link>
      <pubDate>Sun, 03 Sep 2023 14:17:05 +0100</pubDate>
      
      <guid>http://adincebic.micro.blog/2023/09/03/linking-c-static.html</guid>
      <description>

&lt;p&gt;Linking against a static C++ library in Xcode tends to get complicated. Even though the idea is simple, there are few traps that you can run into.&lt;/p&gt;

&lt;h2 id=&#34;the-idea&#34;&gt;The idea&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Write C++ code.&lt;/li&gt;
&lt;li&gt;Write interface which will be usable from Objective-C and Swift.&lt;/li&gt;
&lt;li&gt;Package it as a static &amp;ldquo;.a&amp;rdquo; library.&lt;/li&gt;
&lt;li&gt;Link it to iOS app project.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&#34;let-s-start-with-simple-c-code&#34;&gt;Let&amp;rsquo;s start with simple C++ code&lt;/h3&gt;

&lt;p&gt;We will create a class named &lt;code&gt;ExampleCode&lt;/code&gt; which will expose single function &lt;code&gt;helloWorld()&lt;/code&gt; that returns static string.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-C++&#34;&gt;    #ifndef ExampleCode_hpp
    #define ExampleCode_hpp

    #include &amp;lt;stdio.h&amp;gt;

    using namespace std;

    class ExampleCode {
    public:
        const char* helloWorld();
    };

    #endif /* ExampleCode_hpp */
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And here is the implementation:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-C++&#34;&gt;    #include &amp;quot;ExampleCode.hpp&amp;quot;

    const char* ExampleCode::helloWorld() {
        char const *str = &amp;quot;This is my library&amp;quot;;
        return str;
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;creating-interface-for-objective-c-and-swift&#34;&gt;Creating interface for Objective-C and Swift&lt;/h3&gt;

&lt;p&gt;Because Swift still doesn&amp;rsquo;t have interoperability with C++, we need to utilize Objective-C++ to achieve our goal of using the library from Swift.
In the same project for our dummy library, create a new Objective-C file with header:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-Objective-C&#34;&gt;#import &amp;lt;Foundation/Foundation.h&amp;gt;

@interface NewLibrary : NSObject
- (NSString*)hello;
@end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now for the implementation file it is important to change its .m extension to .mm because that is what makes it tap into C++ (aka makes it Objective-C++).&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-Objective-C++&#34;&gt;#import &amp;quot;NewLibrary.h&amp;quot;
#import &amp;quot;ExampleCode.hpp&amp;quot;

@implementation NewLibrary
- (NSString *)hello {
    ExampleCode* example = new ExampleCode();
    NSString* str = [NSString stringWithUTF8String:example-&amp;gt;helloWorld()];
    return str;
}
@end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Also remember &lt;code&gt;ExampleCode.hpp&lt;/code&gt; is the C++ header file that I created above, so that is why I import it here.&lt;/p&gt;

&lt;h3 id=&#34;packaging-this-code-as-static-c-a-library&#34;&gt;Packaging this code as static C++ .a library&lt;/h3&gt;

&lt;p&gt;This is fairly simple stuff, but here are the steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set your scheme to Release configuration.&lt;/li&gt;
&lt;li&gt;Build both for &amp;ldquo;Any iOS Simulator Device (arm64, x86_64)&amp;rdquo; and for &amp;ldquo;Any iOS Device (arm64)&amp;rdquo;. No, you can&amp;rsquo;t do it both at once.&lt;/li&gt;
&lt;li&gt;Find your build products in Xcode&amp;rsquo;s derived data folder. You should see two folders &amp;ldquo;Release-iphonesimulator&amp;rdquo; and &amp;ldquo;Release-iphoneos&amp;rdquo;. In there there is your &amp;ldquo;.a&amp;rdquo; library file and &amp;ldquo;include&amp;rdquo; folder containing &amp;ldquo;.h&amp;rdquo; header Objective-C file that we created earlier.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now, most of the old advice goes to say that you should use &lt;a href=&#34;https://ss64.com/osx/lipo.html&#34;&gt;lipo command&lt;/a&gt; to create universal (fat) binary. However this will only get you in trouble. Firstly, if you try creating universal binary from simulator and iOS device &amp;ldquo;.a&amp;rdquo; library files you will get an error telling you that both files are built for arm64 architecture. That is because they actually are since Apple Silicon was introduced. Secondly, don&amp;rsquo;t try to remove arm64 architecture from simulator &amp;ldquo;.a&amp;rdquo; library using &lt;code&gt;lipo -remove arm64 path-to-simulator-lib.a -output library.a&lt;/code&gt;, not because it won&amp;rsquo;t work but because it will create troubles when debugging on simulator later on.
Actually, you don&amp;rsquo;t need to do anything with those files at this point.&lt;/p&gt;

&lt;h3 id=&#34;linking-against-your-library-in-separate-ios-project&#34;&gt;Linking against your library in separate iOS project&lt;/h3&gt;

&lt;p&gt;So in a new iOS project in Xcode it is required to perform a couple of steps to link your newly created static C++ library:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Before you add your &amp;ldquo;.a&amp;rdquo; files into iOs project, it would be helpful to rename them such that you can differentiate simulator from real device &amp;ldquo;.a&amp;rdquo; libraries because they are different. You can name them something like &lt;code&gt;NewLibrary-sim.a&lt;/code&gt; and &lt;code&gt;NewLibrary-device.a&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add your &amp;ldquo;.a&amp;rdquo; files to Xcode project. Make sure that you check the box &amp;ldquo;Copy files if needed&amp;rdquo; when dropping &amp;ldquo;.a&amp;rdquo; files. Also make sure that you don&amp;rsquo;t add them to your target because we will link them conditionally in later step.&lt;/li&gt;
&lt;li&gt;Add &amp;ldquo;.h&amp;rdquo; header file. Just because you have two &amp;ldquo;.a&amp;rdquo; files, you don&amp;rsquo;t need two header files. Also make sure that you check &amp;ldquo;Copy files if needed&amp;rdquo; when dropping it into your project.&lt;/li&gt;
&lt;li&gt;In project build settings look for &amp;ldquo;other linker flags&amp;rdquo; and next to Debug and Release configurations click + icon and add two new entries, one for the simulator SDK and the other for the iOS SDK. In the entry for simulator SDK add path to your simulator .a file, you can write it like this &lt;code&gt;$(SRCROOT)/Libraries/NewLibrary-sim.a&lt;/code&gt;. &lt;code&gt;$(SRCROOT)&lt;/code&gt; gives you the path to the root of your project. And repeat the same for iOS SDK &lt;code&gt;$(SRCROOT)/Libraries/NewLibrary-device.a&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Now for the library to work, you also need to link against C++ standard library. Fortunately it is pretty straight forward. Go to build phases for your target and add &amp;ldquo;libc++.tbd&amp;rdquo; under &amp;ldquo;Link Binary With Libraries&amp;rdquo;. This is very important step and one that I see so many other articles fail to mention.&lt;/li&gt;
&lt;li&gt;Finally, because interface for our library is written in Objective-C, wee need to create bridging header. You can do that manually or you can add empty Objective-C file to your project and Xcode will offer to create bridging header for you. What ever you choose, just make sure to import your library header in bridging header to make Swift recognize public interface for your library.&lt;/li&gt;
&lt;/ol&gt;

&lt;pre&gt;&lt;code class=&#34;language-Bridging&#34;&gt;//
//  Use this file to import your target&#39;s public headers that you would like to expose to Swift.
//

#import &amp;quot;NewLibrary.h&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;At this point you should be able to build the app either for simulator or device without any issues.&lt;/p&gt;

&lt;h2 id=&#34;things-to-keep-in-mind&#34;&gt;Things to keep in mind&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Swift will get support for direct interoperability with C++ very soon. It actually already supports it but the current stable Xcode version still does not ship Swift 5.9. This means that Objective-C won&amp;rsquo;t be necessary anymore.&lt;/li&gt;
&lt;li&gt;Don&amp;rsquo;t fall into traps with removing arm64 from simulator version of your library. Also don&amp;rsquo;t go into build settings and add arm64 to &amp;ldquo;Excluded architectures&amp;rdquo;. If you do so, simulator will utilize Rosetta to run your app and debugging experience gets a lot slower and simulator starts to freeze.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Utilizing language like C++ can be very beneficial as it allows for code sharing between different platforms. However it can get challenging if you are doing it for the first time or you fail to perform one of the steps outlined in this article.&lt;/p&gt;
</description>
      <source:markdown>Linking against a static C++ library in Xcode tends to get complicated. Even though the idea is simple, there are few traps that you can run into.

## The idea
1. Write C++ code.
2. Write interface which will be usable from Objective-C and Swift.
3. Package it as a static &#34;.a&#34; library.
4. Link it to iOS app project.

### Let&#39;s start with simple C++ code
We will create a class named `ExampleCode` which will expose single function `helloWorld()` that returns static string.

```C++ header file
    #ifndef ExampleCode_hpp
    #define ExampleCode_hpp

    #include &lt;stdio.h&gt;

    using namespace std;

    class ExampleCode {
    public:
        const char* helloWorld();
    };

    #endif /* ExampleCode_hpp */
```

And here is the implementation:

```C++ .cpp file
    #include &#34;ExampleCode.hpp&#34;

    const char* ExampleCode::helloWorld() {
        char const *str = &#34;This is my library&#34;;
        return str;
    }
```

### Creating interface for Objective-C and Swift
Because Swift still doesn&#39;t have interoperability with C++, we need to utilize Objective-C++ to achieve our goal of using the library from Swift.
In the same project for our dummy library, create a new Objective-C file with header:

```Objective-C .h header file
#import &lt;Foundation/Foundation.h&gt;

@interface NewLibrary : NSObject
- (NSString*)hello;
@end
```

Now for the implementation file it is important to change its .m extension to .mm because that is what makes it tap into C++ (aka makes it Objective-C++).

```Objective-C++ .mm implementation file
#import &#34;NewLibrary.h&#34;
#import &#34;ExampleCode.hpp&#34;

@implementation NewLibrary
- (NSString *)hello {
    ExampleCode* example = new ExampleCode();
    NSString* str = [NSString stringWithUTF8String:example-&gt;helloWorld()];
    return str;
}
@end
```

Also remember `ExampleCode.hpp` is the C++ header file that I created above, so that is why I import it here.

### Packaging this code as static C++ .a library
This is fairly simple stuff, but here are the steps:

1. Set your scheme to Release configuration.
2. Build both for &#34;Any iOS Simulator Device (arm64, x86_64)&#34; and for &#34;Any iOS Device (arm64)&#34;. No, you can&#39;t do it both at once.
3. Find your build products in Xcode&#39;s derived data folder. You should see two folders &#34;Release-iphonesimulator&#34; and &#34;Release-iphoneos&#34;. In there there is your &#34;.a&#34; library file and &#34;include&#34; folder containing &#34;.h&#34; header Objective-C file that we created earlier.

Now, most of the old advice goes to say that you should use [lipo command](https://ss64.com/osx/lipo.html) to create universal (fat) binary. However this will only get you in trouble. Firstly, if you try creating universal binary from simulator and iOS device &#34;.a&#34; library files you will get an error telling you that both files are built for arm64 architecture. That is because they actually are since Apple Silicon was introduced. Secondly, don&#39;t try to remove arm64 architecture from simulator &#34;.a&#34; library using `lipo -remove arm64 path-to-simulator-lib.a -output library.a`, not because it won&#39;t work but because it will create troubles when debugging on simulator later on.
Actually, you don&#39;t need to do anything with those files at this point.

### Linking against your library in separate iOS project
So in a new iOS project in Xcode it is required to perform a couple of steps to link your newly created static C++ library:

1. Before you add your &#34;.a&#34; files into iOs project, it would be helpful to rename them such that you can differentiate simulator from real device &#34;.a&#34; libraries because they are different. You can name them something like `NewLibrary-sim.a` and `NewLibrary-device.a`.
2. Add your &#34;.a&#34; files to Xcode project. Make sure that you check the box &#34;Copy files if needed&#34; when dropping &#34;.a&#34; files. Also make sure that you don&#39;t add them to your target because we will link them conditionally in later step.
3. Add &#34;.h&#34; header file. Just because you have two &#34;.a&#34; files, you don&#39;t need two header files. Also make sure that you check &#34;Copy files if needed&#34; when dropping it into your project.
4. In project build settings look for &#34;other linker flags&#34; and next to Debug and Release configurations click + icon and add two new entries, one for the simulator SDK and the other for the iOS SDK. In the entry for simulator SDK add path to your simulator .a file, you can write it like this `$(SRCROOT)/Libraries/NewLibrary-sim.a`. `$(SRCROOT)` gives you the path to the root of your project. And repeat the same for iOS SDK `$(SRCROOT)/Libraries/NewLibrary-device.a`.
5. Now for the library to work, you also need to link against C++ standard library. Fortunately it is pretty straight forward. Go to build phases for your target and add &#34;libc++.tbd&#34; under &#34;Link Binary With Libraries&#34;. This is very important step and one that I see so many other articles fail to mention.
6. Finally, because interface for our library is written in Objective-C, wee need to create bridging header. You can do that manually or you can add empty Objective-C file to your project and Xcode will offer to create bridging header for you. What ever you choose, just make sure to import your library header in bridging header to make Swift recognize public interface for your library.

```Bridging header file
//
//  Use this file to import your target&#39;s public headers that you would like to expose to Swift.
//

#import &#34;NewLibrary.h&#34;
```

At this point you should be able to build the app either for simulator or device without any issues.

## Things to keep in mind
* Swift will get support for direct interoperability with C++ very soon. It actually already supports it but the current stable Xcode version still does not ship Swift 5.9. This means that Objective-C won&#39;t be necessary anymore.
* Don&#39;t fall into traps with removing arm64 from simulator version of your library. Also don&#39;t go into build settings and add arm64 to &#34;Excluded architectures&#34;. If you do so, simulator will utilize Rosetta to run your app and debugging experience gets a lot slower and simulator starts to freeze.

## Conclusion
Utilizing language like C++ can be very beneficial as it allows for code sharing between different platforms. However it can get challenging if you are doing it for the first time or you fail to perform one of the steps outlined in this article.
</source:markdown>
    </item>
    
    <item>
      <title>Introducing the existentialannotator: A Swift Command Line Tool that automatically marks all existential types with </title>
      <link>https://adincebic.com/2023/07/30/automatically-mark-all.html</link>
      <pubDate>Sun, 30 Jul 2023 18:14:23 +0100</pubDate>
      
      <guid>http://adincebic.micro.blog/2023/07/30/automatically-mark-all.html</guid>
      <description>

&lt;p&gt;I am pleased to present my command line tool that I hacked together on a Saturday morning, the &amp;ldquo;existentialannotator,&amp;rdquo; which can be found on &lt;a href=&#34;https://github.com/adincebic/swift-existential-annotator&#34;&gt;github&lt;/a&gt;. As the name suggests, this tool performs a specific function: scanning your Swift files, identifying all declared protocols, and annotating all existential types with &lt;code&gt;any&lt;/code&gt;. This feature will prove invaluable with the upcoming release of Swift 6. To get started, you have two options: installing it via &lt;a href=&#34;https://brew.sh&#34;&gt;Homebrew&lt;/a&gt; or obtaining the source code directly from &lt;a href=&#34;https://github.com/adincebic/swift-existential-annotator&#34;&gt;GitHub&lt;/a&gt; and building it yourself. Once installed, simply navigate to your working directory and execute the command &lt;code&gt;existentialannotator .&lt;/code&gt; to let the tool do its job.&lt;/p&gt;

&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;

&lt;p&gt;Concept of existential types in Swift has not been heavily discussed topic until recently when it was introduced as part of the &lt;a href=&#34;https://github.com/apple/swift-evolution/blob/main/proposals/0335-existential-any.md&#34;&gt;Swift Evolution&lt;/a&gt; process on GitHub. Essentially, an existential type represents the existence of any specific type without specifying that particular type explicitly. This means that certain code, like the example below, will not compile in Swift 6:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-swift&#34;&gt;protocol Vehicle {
  var batteryPercentage: Float { get }
}

struct HumanDriver {
  let vehicle: Vehicle

  func drive() {
    // Drive the vehicle
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The compilation fails because &lt;code&gt;let vehicle: Vehicle&lt;/code&gt; utilizes an existential type without being explicitly annotated with the new keyword &lt;code&gt;any&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&#34;understanding-existential-types&#34;&gt;Understanding Existential Types&lt;/h2&gt;

&lt;p&gt;In Swift, an existential type provides a way to handle values of different types in a unified manner, abstracting their specific type information. In the example above, we used the protocol &lt;code&gt;Vehicle&lt;/code&gt; instead of a concrete type, demonstrating the essence of an existential type.&lt;/p&gt;

&lt;h2 id=&#34;what-s-new-in-swift-6&#34;&gt;What&amp;rsquo;s New in Swift 6?&lt;/h2&gt;

&lt;p&gt;In Swift 6, every existential type must be marked with &lt;code&gt;any&lt;/code&gt;. Failure to do so will result in a compilation error. Consequently, the code above &lt;code&gt;let vehicle: Vehicle&lt;/code&gt; would now require the notation &lt;code&gt;let vehicle: any Vehicle&lt;/code&gt;. This is where my tool, the &lt;a href=&#34;https://github.com/adincebic/swift-existential-annotator&#34;&gt;Existential Annotator&lt;/a&gt;, comes in handy, particularly when dealing with large codebases.&lt;/p&gt;

&lt;h2 id=&#34;final-remarks&#34;&gt;Final Remarks&lt;/h2&gt;

&lt;p&gt;Although I put  this tool together on Saturday morning, it is not flawless. There are numerous potential performance improvements that could be implemented. Nonetheless, I consider this tool complete, given its limited lifespan. As we move past the initial release of Swift 6, this tool will likely lose its relevance. Nevertheless, if you believe it could benefit from enhancements, please feel free to submit a pull request.&lt;/p&gt;
</description>
      <source:markdown>I am pleased to present my command line tool that I hacked together on a Saturday morning, the &#34;existentialannotator,&#34; which can be found on [github](https://github.com/adincebic/swift-existential-annotator). As the name suggests, this tool performs a specific function: scanning your Swift files, identifying all declared protocols, and annotating all existential types with `any`. This feature will prove invaluable with the upcoming release of Swift 6. To get started, you have two options: installing it via [Homebrew](https://brew.sh) or obtaining the source code directly from [GitHub](https://github.com/adincebic/swift-existential-annotator) and building it yourself. Once installed, simply navigate to your working directory and execute the command `existentialannotator .` to let the tool do its job.

## Background

Concept of existential types in Swift has not been heavily discussed topic until recently when it was introduced as part of the [Swift Evolution](https://github.com/apple/swift-evolution/blob/main/proposals/0335-existential-any.md) process on GitHub. Essentially, an existential type represents the existence of any specific type without specifying that particular type explicitly. This means that certain code, like the example below, will not compile in Swift 6:

```swift
protocol Vehicle {
  var batteryPercentage: Float { get }
}

struct HumanDriver {
  let vehicle: Vehicle

  func drive() {
    // Drive the vehicle
  }
}
```

The compilation fails because `let vehicle: Vehicle` utilizes an existential type without being explicitly annotated with the new keyword `any`.

## Understanding Existential Types

In Swift, an existential type provides a way to handle values of different types in a unified manner, abstracting their specific type information. In the example above, we used the protocol `Vehicle` instead of a concrete type, demonstrating the essence of an existential type.

## What&#39;s New in Swift 6?

In Swift 6, every existential type must be marked with `any`. Failure to do so will result in a compilation error. Consequently, the code above `let vehicle: Vehicle` would now require the notation `let vehicle: any Vehicle`. This is where my tool, the [Existential Annotator](https://github.com/adincebic/swift-existential-annotator), comes in handy, particularly when dealing with large codebases.

## Final Remarks

Although I put  this tool together on Saturday morning, it is not flawless. There are numerous potential performance improvements that could be implemented. Nonetheless, I consider this tool complete, given its limited lifespan. As we move past the initial release of Swift 6, this tool will likely lose its relevance. Nevertheless, if you believe it could benefit from enhancements, please feel free to submit a pull request.
</source:markdown>
    </item>
    
    <item>
      <title>Using Swift withCheckedThrowingContinuation in methods without return value</title>
      <link>https://adincebic.com/2022/08/08/using-swift-withcheckedthrowingcontinuation.html</link>
      <pubDate>Mon, 08 Aug 2022 17:05:00 +0100</pubDate>
      
      <guid>http://adincebic.micro.blog/2022/08/08/using-swift-withcheckedthrowingcontinuation.html</guid>
      <description>&lt;p&gt;When refactoring old closure-based code to new &lt;a href=&#34;https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html&#34;&gt;Swift concurrency&lt;/a&gt; it is inevitable that you come accross scenario where you need to call &lt;a href=&#34;https://developer.apple.com/documentation/swift/withcheckedthrowingcontinuation(function:_:)&#34;&gt;withCheckedThrowingContinuation&lt;/a&gt; where enclosing method has no return value. In that case, you should get error in Xcode:
&lt;em&gt;Generic parameter &amp;rsquo;T&amp;rsquo; could not be inferred&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Let&amp;rsquo;s consider the following block of code&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;func fetchData() async throws {
    try await withCheckedThrowingContinuation({ continuation in
        URLSession.shared.dataTask(with: URL(string: &amp;quot;https://example.com&amp;quot;)!) { data, response, error in
            if let error = error {
                continuation.resume(throwing: error)
                return
            }
            continuation.resume()
        }.resume()
    })
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This code produces the error above because &lt;a href=&#34;https://developer.apple.com/documentation/swift/withcheckedthrowingcontinuation(function:_:)&#34;&gt;withCheckedThrowingContinuation&lt;/a&gt; method has generic parameter which compiler usuallly infers from return value of enclosing method. However, our enclosing method &lt;em&gt;fetchData&lt;/em&gt; has no return value thus compiler raises the error.&lt;/p&gt;

&lt;p&gt;Fortunately the fix is incredibly simple, just cast return type to Void&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;func fetchData() async throws {
    try await withCheckedThrowingContinuation({ continuation in
        URLSession.shared.dataTask(with: URL(string: &amp;quot;https://example.com&amp;quot;)!) { data, response, error in
            if let error = error {
                continuation.resume(throwing: error)
                return
            }
            continuation.resume()
        }.resume()
    }) as Void
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It is important to note that this approach works for all methods not just for &lt;a href=&#34;https://developer.apple.com/documentation/swift/withcheckedthrowingcontinuation(function:_:)&#34;&gt;withCheckedThrowingContinuation&lt;/a&gt; or other methods specific to &lt;a href=&#34;https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html&#34;&gt;Swift concurrency&lt;/a&gt;.&lt;/p&gt;
</description>
      <source:markdown>When refactoring old closure-based code to new [Swift concurrency](https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html) it is inevitable that you come accross scenario where you need to call [withCheckedThrowingContinuation](https://developer.apple.com/documentation/swift/withcheckedthrowingcontinuation(function:_:)) where enclosing method has no return value. In that case, you should get error in Xcode:
*Generic parameter &#39;T&#39; could not be inferred*

Let&#39;s consider the following block of code

    func fetchData() async throws {
        try await withCheckedThrowingContinuation({ continuation in
            URLSession.shared.dataTask(with: URL(string: &#34;https://example.com&#34;)!) { data, response, error in
                if let error = error {
                    continuation.resume(throwing: error)
                    return
                }
                continuation.resume()
            }.resume()
        })
    }

This code produces the error above because [withCheckedThrowingContinuation](https://developer.apple.com/documentation/swift/withcheckedthrowingcontinuation(function:_:)) method has generic parameter which compiler usuallly infers from return value of enclosing method. However, our enclosing method *fetchData* has no return value thus compiler raises the error.

Fortunately the fix is incredibly simple, just cast return type to Void

    func fetchData() async throws {
        try await withCheckedThrowingContinuation({ continuation in
            URLSession.shared.dataTask(with: URL(string: &#34;https://example.com&#34;)!) { data, response, error in
                if let error = error {
                    continuation.resume(throwing: error)
                    return
                }
                continuation.resume()
            }.resume()
        }) as Void
    }

It is important to note that this approach works for all methods not just for [withCheckedThrowingContinuation](https://developer.apple.com/documentation/swift/withcheckedthrowingcontinuation(function:_:)) or other methods specific to [Swift concurrency](https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html).
</source:markdown>
    </item>
    
    <item>
      <title>How to check if Xcode is building for previews</title>
      <link>https://adincebic.com/2022/03/28/how-to-check.html</link>
      <pubDate>Mon, 28 Mar 2022 11:11:28 +0100</pubDate>
      
      <guid>http://adincebic.micro.blog/2022/03/28/how-to-check.html</guid>
      <description>&lt;p&gt;Lately, I find myself often dealing with a lot of Xcode build phases. One common problem that I encounter is that SwiftUI previews won&amp;rsquo;t work if some build phase runs a script which messes with Xcode project file or with individual files. For example, script which sorts files alphabetically may modify Xcode project file which will prevent previews to work. To work around this problem, you can check if Xcode is building for previews and then decide whether to run the script or not.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;if [ ${ENABLE_PREVIEWS} == NO ];
then
echo &amp;quot;Running sorting script&amp;quot;
fi
&lt;/code&gt;&lt;/pre&gt;
</description>
      <source:markdown>Lately, I find myself often dealing with a lot of Xcode build phases. One common problem that I encounter is that SwiftUI previews won&#39;t work if some build phase runs a script which messes with Xcode project file or with individual files. For example, script which sorts files alphabetically may modify Xcode project file which will prevent previews to work. To work around this problem, you can check if Xcode is building for previews and then decide whether to run the script or not.

    if [ ${ENABLE_PREVIEWS} == NO ];
    then
    echo &#34;Running sorting script&#34;
    fi
 
</source:markdown>
    </item>
    
  </channel>
</rss>
