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
2 changes: 1 addition & 1 deletion .github/workflows/check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ jobs:
working-directory: e2e/smoke
run: |
echo "${{ matrix.bazel }}" > .bazelversion
bazel build //...
bazel test //...

# ---------------------------------------------------------------------------
# Gate: single status check for branch protection.
Expand Down
145 changes: 27 additions & 118 deletions MODULE.bazel.lock

Large diffs are not rendered by default.

34 changes: 33 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Bazel rules for the [Odin programming language](https://odin-lang.org/).
- Hermetic Odin toolchain management (no system install required)
- `odin_binary` rule for compiling executables
- `odin_library` rule for sharing packages via collection imports
- `odin_test` rule for running `@(test)`-annotated Odin procedures
- bzlmod-first (no WORKSPACE file needed)
- Multi-platform support: Linux (x86-64, ARM64), macOS (x86-64, ARM64), Windows (x86-64)
- Bazel 7.x, 8.x, and 9.x compatible
Expand All @@ -35,7 +36,7 @@ register_toolchains("@odin_toolchains//:all")
Then in your `BUILD.bazel`:

```starlark
load("@rules_odin//odin:defs.bzl", "odin_binary", "odin_library")
load("@rules_odin//odin:defs.bzl", "odin_binary", "odin_library", "odin_test")

odin_library(
name = "greetings",
Expand All @@ -47,6 +48,12 @@ odin_binary(
srcs = glob(["hello/*.odin"]),
deps = [":greetings"],
)

odin_test(
name = "greetings_test",
srcs = glob(["tests/*.odin"]),
deps = [":greetings"],
)
```

Import the library in your Odin code using the collection name (the `odin_library` target name) and the package directory name:
Expand Down Expand Up @@ -79,6 +86,31 @@ Groups Odin source files into a library that can be imported by `odin_binary` ta
| --------- | ---------- | ------------ | ---------------------------------------- |
| `srcs` | label_list | **required** | Odin source files (must share a package) |

### `odin_test`

Compiles and runs Odin tests using the built-in test runner. Source files must contain at least one procedure annotated with `@(test)`. The test binary is compiled at build time (cacheable) and executed by Bazel's test runner.

| Attribute | Type | Default | Description |
| ---------------------- | ----------- | ------------ | ----------------------------------------------- |
| `srcs` | label_list | **required** | Odin source files with `@(test)` procedures |
| `deps` | label_list | `[]` | `odin_library` targets to import as collections |
| `optimization` | string | `"none"` | One of: none, minimal, speed, size, aggressive |
| `debug` | bool | `True` | Include debug symbols |
| `defines` | string_dict | `{}` | Compile-time `-define:` values (see below) |
| `extra_compiler_flags` | string_list | `[]` | Additional flags passed to the Odin compiler |
| `vet` | bool | `False` | Enable `-vet` checks |

**Test runner defines** (passed via `defines`):

| Key | Default | Description |
| ------------------------------ | ---------- | ------------------------------------- |
| `ODIN_TEST_FANCY` | `false` | ANSI progress display (auto-disabled) |
| `ODIN_TEST_THREADS` | `0` (auto) | Worker thread count |
| `ODIN_TEST_NAMES` | — | Comma-separated test filter |
| `ODIN_TEST_RANDOM_SEED` | random | Fixed seed for reproducibility |
| `ODIN_TEST_FAIL_ON_BAD_MEMORY` | `false` | Treat memory leaks as failures |
| `ODIN_TEST_LOG_LEVEL` | `info` | Minimum log level |

## Supported Odin Versions

| Version | Status |
Expand Down
8 changes: 7 additions & 1 deletion e2e/smoke/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
load("@rules_odin//odin:defs.bzl", "odin_binary", "odin_library")
load("@rules_odin//odin:defs.bzl", "odin_binary", "odin_library", "odin_test")

odin_library(
name = "greetings",
Expand All @@ -10,3 +10,9 @@ odin_binary(
srcs = glob(["hello/*.odin"]),
deps = [":greetings"],
)

odin_test(
name = "smoke_test",
srcs = glob(["tests/*.odin"]),
deps = [":greetings"],
)
492 changes: 71 additions & 421 deletions e2e/smoke/MODULE.bazel.lock

Large diffs are not rendered by default.

24 changes: 24 additions & 0 deletions e2e/smoke/tests/smoke_test.odin
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package tests

import "core:testing"
import greetings "greetings:lib"

@(test)
test_greet_returns :: proc(t: ^testing.T) {
// Verify the greetings library is importable and callable.
// greet() writes to stdout — here we just confirm no crash.
greetings.greet("Test")
}

@(test)
test_arithmetic :: proc(t: ^testing.T) {
testing.expect_value(t, 2 + 2, 4)
testing.expect_value(t, 10 - 3, 7)
testing.expect_value(t, 6 * 7, 42)
}

@(test)
test_string_length :: proc(t: ^testing.T) {
s := "hello"
testing.expect_value(t, len(s), 5)
}
6 changes: 5 additions & 1 deletion odin/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ toolchain_type(
bzl_library(
name = "defs",
srcs = ["defs.bzl"],
deps = ["//odin/private:binary_bzl"],
deps = [
"//odin/private:binary_bzl",
"//odin/private:library_bzl",
"//odin/private:test_bzl",
],
)

bzl_library(
Expand Down
6 changes: 5 additions & 1 deletion odin/defs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@

Users should load rules from this file:

load("@rules_odin//odin:defs.bzl", "odin_binary", "odin_library")
load("@rules_odin//odin:defs.bzl", "odin_binary", "odin_library", "odin_test")
"""

load("//odin/private:binary.bzl", _odin_binary = "odin_binary")
load("//odin/private:common.bzl", _OdinLibraryInfo = "OdinLibraryInfo")
load("//odin/private:library.bzl", _odin_library = "odin_library")
load("//odin/private:test.bzl", _odin_test = "odin_test")

odin_binary = _odin_binary
odin_library = _odin_library
odin_test = _odin_test
OdinLibraryInfo = _OdinLibraryInfo
19 changes: 18 additions & 1 deletion odin/private/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,27 @@ exports_files([
"odin_sdk.BUILD.bazel",
])

bzl_library(
name = "common_bzl",
srcs = ["common.bzl"],
)

bzl_library(
name = "library_bzl",
srcs = ["library.bzl"],
deps = [":common_bzl"],
)

bzl_library(
name = "binary_bzl",
srcs = ["binary.bzl"],
deps = ["//odin:toolchain"],
deps = [":common_bzl"],
)

bzl_library(
name = "test_bzl",
srcs = ["test.bzl"],
deps = [":common_bzl"],
)

bzl_library(
Expand Down
94 changes: 8 additions & 86 deletions odin/private/binary.bzl
Original file line number Diff line number Diff line change
@@ -1,103 +1,25 @@
"""Implementation of the odin_binary rule."""

load("//odin/private:common.bzl", "get_package_dir")
load("//odin/private:library.bzl", "OdinLibraryInfo")
load("//odin/private:common.bzl", "OdinLibraryInfo", "compile_odin_binary")

def _odin_binary_impl(ctx):
toolchain = ctx.toolchains["@rules_odin//odin:toolchain_type"]
odin_info = toolchain.odininfo

# Determine output binary name
# Determine output binary name.
if ctx.attr.out:
out_name = ctx.attr.out
else:
out_name = ctx.label.name + odin_info.binary_ext

out = ctx.actions.declare_file(out_name)

srcs = ctx.files.srcs
pkg_dir = get_package_dir(srcs, "odin_binary")

# Build the compiler arguments
args = ctx.actions.args()
args.add("build")
args.add(pkg_dir)
args.add("-out:" + out.path)
args.add("-build-mode:exe")

# Optimization level
if ctx.attr.optimization != "none":
args.add("-o:" + ctx.attr.optimization)

# Debug symbols
if ctx.attr.debug:
args.add("-debug")

# Vet checks
if ctx.attr.vet:
args.add("-vet")

# Compile-time defines
for key, value in ctx.attr.defines.items():
args.add("-define:{}={}".format(key, value))

# Extra compiler flags (pass-through)
for flag in ctx.attr.extra_compiler_flags:
args.add(flag)

# Collect library deps: add their source files to inputs and
# register each as a collection for Odin's import resolution.
dep_srcs = []
seen_collections = {}
for dep in ctx.attr.deps:
lib_info = dep[OdinLibraryInfo]
name = lib_info.collection_name
if name in seen_collections:
fail(
"odin_binary '{}' has duplicate collection name '{}' ".format(
ctx.label.name,
name,
) + "from deps '{}' and '{}'. ".format(
seen_collections[name],
dep.label,
) + "Odin collection names must be unique within a binary.",
)
seen_collections[name] = dep.label
dep_srcs.extend(lib_info.srcs.to_list())
args.add("-collection:{}={}".format(
name,
lib_info.collection_root,
))

# Collect all inputs: sources + library dep srcs + entire SDK
inputs = depset(
direct = srcs + dep_srcs,
transitive = [odin_info.all_files],
)

# Set ODIN_ROOT so the compiler finds core/, base/, vendor/.
env = {}
if odin_info.compiler:
env["ODIN_ROOT"] = odin_info.compiler.dirname

ctx.actions.run(
executable = odin_info.compiler,
arguments = [args],
inputs = inputs,
outputs = [out],
env = env,
mnemonic = "OdinBuild",
progress_message = "Compiling Odin binary %{label}",
use_default_shell_env = True,
)

return [
DefaultInfo(
files = depset([out]),
executable = out,
runfiles = ctx.runfiles(files = [out]),
),
]
return [compile_odin_binary(
ctx = ctx,
srcs = ctx.files.srcs,
build_mode = "exe",
out_file = out,
)]

odin_binary = rule(
implementation = _odin_binary_impl,
Expand Down
Loading
Loading