Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,10 @@ build
*.iml
.externalNativeBuild
.cxx

# Bazel (F-042 sample and any future workspaces)
bazel-sample/bazel-*
**/bazel-bin
**/bazel-out
**/bazel-testlogs
**/.bazeliskrc
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ systems like Buck and Bazel.
10. [JVM sample application](docs/JVM-SAMPLE.md) — multi-module pure-JVM gold standard (`jvm-application/`, F-031)
11. [Bazel adapter design](docs/BAZEL-ADAPTER.md) — target types to rules, restriction graph to visibility (F-040 design)
12. [Bazel adapter spike](bazel-adapter/README.md) — generate/check BUILD from Forma model (F-041; JVM-first)
13. [Bazel sample (experimental)](bazel-sample/) — minimal `kt_jvm_*` workspace exercising forma-core matrix + `impl` ↛ `impl` (F-042)

Configuration made easy:

Expand Down Expand Up @@ -210,6 +211,7 @@ JVM sample application + `binary` composition root: **F-031** done ([`docs/JVM-S
JVM getting-started tutorial: **F-032** done ([`docs/JVM-GETTING-STARTED.md`](docs/JVM-GETTING-STARTED.md)).
Bazel adapter mapping design (targets ↔ rules, visibility ↔ deps): **F-040** done (see [`docs/BAZEL-ADAPTER.md`](docs/BAZEL-ADAPTER.md)).
Bazel adapter spike (generate/check BUILD from portable model, JVM matrix): **F-041** done ([`bazel-adapter/`](bazel-adapter/), spike results in BAZEL-ADAPTER.md).
Minimal Bazel sample workspace (two features + library/util + binary, real `bazelisk build` + `run`): **F-042** done (see [`bazel-sample/`](bazel-sample/) + BAZEL-ADAPTER.md).

Icons made by <a href="https://www.flaticon.com/authors/freepik" title="Freepik">Freepik</a>
from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a>
2 changes: 1 addition & 1 deletion TICKETS.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Update this file when picking or finishing work. Cron workers must pick the **hi
|----|--------|-------|-------|
| F-040 | done | Design Bazel adapter mapping (targets ↔ rules, visibility ↔ deps) | `docs/BAZEL-ADAPTER.md` + cross-links; commit 45674c7 |
| F-041 | done | Spike: generate or check Bazel BUILD from forma declarations | `bazel-adapter/` generate+check via core RestrictionGraph; examples + tests green |
| F-042 | todo | Minimal Bazel sample using forma-core concepts | |
| F-042 | done | Minimal Bazel sample using forma-core concepts | `bazel-sample/` + real `bazelisk build`/`run` + docs; see PROGRESS |

## Backlog (lower priority / historical GitHub)

Expand Down
4 changes: 4 additions & 0 deletions bazel-sample/.bazelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Experimental F-042 sample uses WORKSPACE + http_archive (bzlmod had extension friction).
# Force WORKSPACE mode for reliability in this tiny tree.
common --noenable_bzlmod
build --test_output=errors
1 change: 1 addition & 0 deletions bazel-sample/.bazelversion
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
7.4.1
167 changes: 167 additions & 0 deletions bazel-sample/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# bazel-sample (F-042)

**EXPERIMENTAL / NON-PRODUCTION**

This is a minimal, self-contained Bazel workspace that exercises the same
forma-core JVM dependency matrix concepts as `jvm-application/` and the F-041
`bazel-adapter` spike.

It demonstrates:

- `api` + `impl` feature boundaries (two features: greeter, calculator)
- `library` and `util` shared modules
- `binary` as the sole composition root (multiple impls meet only here)
- `tags = ["forma:type=..."]` on every target
- Label conventions: `//feature/greeter/impl:impl`, `//binary:binary`
- **No `impl` → `impl` dependencies allowed** (enforced by model + documented in BUILD authoring)

The BUILD files are hand-authored to match the output shape produced by
`JvmBazelAdapter.generate()` from F-041 (see `bazel-adapter/examples/` and
`bazel-adapter/src/main/kotlin/tools/forma/bazel/adapter/JvmBazelAdapter.kt`).

Cross-feature code goes **only through `api`** (see `Main.kt` and the impls).
An `impl` depending on another `impl` is rejected by the `RestrictionGraph`
used in the adapter (see illegal fixture test below).

---

## Prerequisites

- JDK 17+ (use the repo helper):
```bash
source /path/to/forma/scripts/env-mac.sh
java -version # must be 17+
```
- bazelisk (recommended) or Bazel 7.x+
```bash
brew install bazelisk
```
- Internet access on first run (rules_kotlin + toolchain downloads).

---

## Layout

```
bazel-sample/
├── .bazelversion # 7.4.1
├── WORKSPACE # http_archive pins (bzlmod disabled in .bazelrc)
├── .bazelrc
├── README.md
├── binary/
│ ├── BUILD.bazel
│ └── src/main/kotlin/.../Main.kt
├── common/
│ ├── library/...
│ └── util/...
└── feature/
├── greeter/{api,impl}/...
└── calculator/{api,impl}/...
```

---

## Build and run

```bash
cd bazel-sample

# Recommended: bazelisk (uses .bazelversion)
bazelisk build //...

bazelisk run //binary:binary
```

Expected output (or very similar):

```
Hello, World! (2 + 3 = 5)
Bazel sample (forma concepts) build + run successful.
```

Using plain `bazel` (if you have Bazel 7+ installed and it matches):

```bash
bazel build //...
bazel run //binary:binary
```

---

## Dependency matrix exercised (JVM)

| Consumer type | Allowed dependency types | Notes in this sample |
|---------------|-----------------------------------|----------------------|
| `jvm.api` | api, library | greeter/calculator apis have no deps |
| `jvm.impl` | api, library, util, test-util | impls depend **only** on their api + commons. **Never another impl**. |
| `jvm.library` | util, test-util | library has none here |
| `jvm.util` | util, library | util has none here |
| `jvm.binary` | api, impl, library, util, test-util | sole place that depends on multiple impls |

See:
- `docs/DEPENDENCY-MATRIX.md`
- `docs/JVM-TARGETS.md`
- `plugins/jvm/.../JvmTargetRegistry.kt` (source of truth matrix)
- `bazel-adapter/...` for the core `RestrictionGraph` re-implementation used by the adapter

---

## Enforcement of `impl` ↛ `impl`

This rule is **core forma discipline**, not a Bazel visibility accident.

1. The `JvmBazelAdapter` (F-041) uses `RestrictionGraph.isAllowed(...)` (from `tools.forma:core`) and **never emits** an illegal edge in generated `deps`.
2. `check()` on an illegal model reports violations.

Run the adapter test that proves the illegal case is caught:

```bash
cd ../bazel-adapter
source ../scripts/env-mac.sh
./gradlew test --tests "*JvmBazelAdapterTest*illegal*"
# or simply
./gradlew test
```

Look at:
- `bazel-adapter/src/test/kotlin/tools/forma/bazel/JvmBazelAdapterTest.kt`
- `bazel-adapter/src/main/kotlin/tools/forma/bazel/sample/JvmApplicationFixture.kt` (`modelWithIllegalImplToImpl`)

In this sample tree there are **zero** `//.../impl:impl` references inside any `impl/` BUILD.

If you manually add an illegal dep to a BUILD and run the adapter check against a model, it surfaces as a violation (the same check the Gradle validators perform at configuration time).

---

## How the BUILD files were produced

- Labels and target names follow §3 of `docs/BAZEL-ADAPTER.md`
- Every target carries `tags = ["forma:type=jvm.<kind>"]`
- Visibility is narrow except for the public binary
- `kt_jvm_library` / `kt_jvm_binary` + `srcs = glob(["src/main/kotlin/**/*.kt"])`
- `main_class` on the binary
- `load("@rules_kotlin//kotlin:jvm.bzl", ...)` — note: the F-041 generator omits the load line; sample BUILDs must include it

See also the golden fragments in `bazel-adapter/examples/jvm-application-build/`.

---

## Limitations (experimental)

- No external Maven deps (no `maven_install` / bzlmod maven wiring).
- No tests (`kt_jvm_test` / test-util usage).
- No Android / `rules_android`.
- Toolchain is default (Java 11+ compatible with rules_kotlin).
- Not intended to replace `jvm-application/` Gradle build.
- Uses WORKSPACE + `--noenable_bzlmod` (bzlmod left for a later polish); pins may need bumps as Bazel/rules_kotlin evolve.

This sample exists to prove that the forma-core concepts (matrix, api contracts, composition root, no-impl-to-impl) are portable to Bazel with the mapping designed in F-040/F-041.

---

## Related

- [Bazel adapter design](../docs/BAZEL-ADAPTER.md)
- [JVM sample](../docs/JVM-SAMPLE.md)
- [forma-core API](../docs/forma-core-api.md)
- `bazel-adapter/` (generator + checker + tests)
35 changes: 35 additions & 0 deletions bazel-sample/WORKSPACE
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Experimental Bazel sample (F-042) — WORKSPACE (chosen after bzlmod rules_kotlin extension friction).
# This file + http_archive is reliable for the minimal experimental tree.

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

http_archive(
name = "bazel_skylib",
sha256 = "cd55a062e763b9349921f0f5db8c3933288dc8ba4f76dd9416aac68acee3cb94",
urls = [
"https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.5.0/bazel-skylib-1.5.0.tar.gz",
"https://github.com/bazelbuild/bazel-skylib/releases/download/1.5.0/bazel-skylib-1.5.0.tar.gz",
],
)

load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace")
bazel_skylib_workspace()

http_archive(
name = "rules_java",
sha256 = "976ef08b49c929741f201790e59e3807c72ad81f428c8bc953cdbeff5fed15eb",
urls = ["https://github.com/bazelbuild/rules_java/releases/download/7.4.0/rules_java-7.4.0.tar.gz"],
)

# rules_kotlin v1.9.0 release tarball content lives at archive root (no extra version dir).
http_archive(
name = "rules_kotlin",
sha256 = "5766f1e599acf551aa56f49dab9ab9108269b03c557496c54acaf41f98e2b8d6",
url = "https://github.com/bazelbuild/rules_kotlin/releases/download/v1.9.0/rules_kotlin-v1.9.0.tar.gz",
)

load("@rules_kotlin//kotlin:repositories.bzl", "kotlin_repositories")
kotlin_repositories()

load("@rules_kotlin//kotlin:core.bzl", "kt_register_toolchains")
kt_register_toolchains()
24 changes: 24 additions & 0 deletions bazel-sample/binary/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Hand-authored for F-042 minimal sample (mirrors JvmBazelAdapter conventions from F-041).
# gradlePath equivalent: :binary type=jvm.binary

load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_binary")

kt_jvm_binary(
name = "binary",
srcs = glob(["src/main/kotlin/**/*.kt"]),
deps = [
"//common/library:library",
"//common/util:util",
"//feature/calculator/api:api",
"//feature/calculator/impl:impl",
"//feature/greeter/api:api",
"//feature/greeter/impl:impl",
],
visibility = [
"//visibility:public",
],
main_class = "tools.forma.jvm.sample.binary.MainKt",
tags = [
"forma:type=jvm.binary",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package tools.forma.jvm.sample.binary

import tools.forma.jvm.sample.feature.greeter.api.Greeter
import tools.forma.jvm.sample.feature.greeter.impl.GreeterImpl
import tools.forma.jvm.sample.feature.calculator.api.Calculator
import tools.forma.jvm.sample.feature.calculator.impl.CalculatorImpl

/**
* Composition root demo for F-042 (Bazel sample mirroring forma-core JVM matrix).
* Wires impls (greeter + calculator) + shared library/util only at the binary.
* Cross-feature communication happens exclusively through api modules.
* impl never depends on another impl.
*/
fun main() {
val greeter: Greeter = GreeterImpl()
val calculator: Calculator = CalculatorImpl()

val greeting = greeter.greet("world")
val sum = calculator.add(2, 3)

println("$greeting (2 + 3 = $sum)")
println("Bazel sample (forma concepts) build + run successful.")
}
17 changes: 17 additions & 0 deletions bazel-sample/common/library/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Hand-authored for F-042 minimal sample (mirrors JvmBazelAdapter conventions from F-041).
# gradlePath equivalent: :common:library type=jvm.library

load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")

kt_jvm_library(
name = "library",
srcs = glob(["src/main/kotlin/**/*.kt"]),
visibility = [
"//binary:__pkg__",
"//feature/greeter/impl:__pkg__",
"//feature/calculator/impl:__pkg__",
],
tags = [
"forma:type=jvm.library",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package tools.forma.jvm.sample.common.library

object Math {
fun add(a: Int, b: Int): Int = a + b
fun multiply(a: Int, b: Int): Int = a * b
}
17 changes: 17 additions & 0 deletions bazel-sample/common/util/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Hand-authored for F-042 minimal sample (mirrors JvmBazelAdapter conventions from F-041).
# gradlePath equivalent: :common:util type=jvm.util

load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")

kt_jvm_library(
name = "util",
srcs = glob(["src/main/kotlin/**/*.kt"]),
visibility = [
"//binary:__pkg__",
"//feature/greeter/impl:__pkg__",
"//feature/calculator/impl:__pkg__",
],
tags = [
"forma:type=jvm.util",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package tools.forma.jvm.sample.common.util

fun greet(name: String): String = "Hello, ${name.lowercase().replaceFirstChar { it.uppercase() }}!"
16 changes: 16 additions & 0 deletions bazel-sample/feature/calculator/api/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Hand-authored for F-042 minimal sample (mirrors JvmBazelAdapter conventions from F-041).
# gradlePath equivalent: :feature:calculator:api type=jvm.api

load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")

kt_jvm_library(
name = "api",
srcs = glob(["src/main/kotlin/**/*.kt"]),
visibility = [
"//binary:__pkg__",
"//feature/calculator/impl:__pkg__",
],
tags = [
"forma:type=jvm.api",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package tools.forma.jvm.sample.feature.calculator.api

interface Calculator {
fun add(a: Int, b: Int): Int
}
20 changes: 20 additions & 0 deletions bazel-sample/feature/calculator/impl/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Hand-authored for F-042 minimal sample (mirrors JvmBazelAdapter conventions from F-041).
# gradlePath equivalent: :feature:calculator:impl type=jvm.impl

load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")

kt_jvm_library(
name = "impl",
srcs = glob(["src/main/kotlin/**/*.kt"]),
deps = [
"//common/library:library",
"//common/util:util",
"//feature/calculator/api:api",
],
visibility = [
"//binary:__pkg__",
],
tags = [
"forma:type=jvm.impl",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package tools.forma.jvm.sample.feature.calculator.impl

import tools.forma.jvm.sample.common.library.Math
import tools.forma.jvm.sample.feature.calculator.api.Calculator

class CalculatorImpl : Calculator {
override fun add(a: Int, b: Int): Int = Math.add(a, b)
}
Loading
Loading