Bazel rules for the Coco language that can use Popili to:
- Generate code from Coco packages.
- Run verification on Coco packages as part of Bazel test.
The rules are fully compatible with remote caching and execution.
- Bazel 8.0.0 or higher.
- Popili 1.5.0 or higher.
- A valid Coco/Popili license.
Follow the installation instructions from the latest release.
Add the following to your MODULE.bazel file:
bazel_dep(name = "rules_coco")
archive_override(
module_name = "rules_coco",
urls = [
"https://github.com/cocotec/rules_coco/releases/download/VERSION/rules_coco_VERSION.tar.gz",
"https://dl.cocotec.io/rules_coco/rules_coco_VERSION.tar.gz",
],
integrity = "sha256-HASH", # See releases page
)
coco = use_extension("@rules_coco//coco:extensions.bzl", "coco")
coco.toolchain(
versions = ["stable"], # Or specify explicit versions like ["1.5.1"]
c = True, # Enable C runtime (for coco_c_library)
cc = True, # Enable C++ runtime (for coco_cc_library)
)Get the exact version and integrity hash from the releases page.
[!WARNING] >
WORKSPACEmode is deprecated and will be removed in a future version. Please migrate to bzlmod.
Add the following to your WORKSPACE file:
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
name = "rules_coco",
integrity = "sha256-HASH", # See releases page
urls = [
"https://github.com/cocotec/rules_coco/releases/download/VERSION/rules_coco_VERSION.tar.gz",
"https://dl.cocotec.io/rules_coco/rules_coco_VERSION.tar.gz",
],
)
load("@rules_coco//coco:repositories.bzl", "coco_repositories")
coco_repositories(
version = "stable", # Or specify a explicit version like "1.5.1"
c = True, # Enable C runtime (for coco_c_library)
cc = True, # Enable C++ runtime (for coco_cc_library)
)Get the exact version and integrity hash from the releases page.
To use the rules, the host where the build actions executes will need to have a license. If the build runs locally, then this means that local host requires a license. If remote execution is being used, then the remote executor will need a license.
When using these rules in a CI environment you will need to provide a machine token. This can either be done by:
- (Preferred) Using workload identity federation: in this, tokens from your existing identity provider can be used to authenticate to Cocotec. This works with AWS or any OIDC-compatible identity provider, such as Github Actions and Google Cloud.
- Using a secret API token for the Cocotec services.
Contact Cocotec Support to set this up or discuss options.
rules_coco supports several different license modes:
-
action_environment: Suitable credentials will be provided in the execution environment of each action. What is supported will depend on the version of Popili, but could includeCOCOTEC_AUTH_TOKENbeing injected into the build action environment via some non-bazel mechanism.This is the recommended mode when using remote execution, as it will not interfere with bazel's cache, and also keeps the license token most secure.
-
action_file: An auth token file path is provided that is guaranteed to be available in the execution environment of each action (e.g., mounted on remote runners). The path must be specified via--@rules_coco//:auth_token_pathor in the toolchain configuration. Popili will be invoked with--machine-auth-tokenpointing to this file.This mode is suitable for remote execution environments where auth token files are pre-mounted or made available through other mechanisms. Requires popili 1.5.2 or later.
-
local_acquire: A license will be acquired on the local machine as part of the build usingCOCOTEC_AUTH_TOKEN. This is not compatible with remote execution. -
local_user: The user's existing license on this machine will be reused. This is not compatible with remote execution. -
token: The explicitly provided token should be used asCOCOTEC_AUTH_TOKEN. In this case,--@rules_coco//:license_tokenmust be set as well. This works with remote execution but it is only recommended when using workload identity federation asCOCOTEC_AUTH_TOKENis not a secret in that case.
This can be configured in three ways (in order of precedence):
-
Via command-line flag (highest precedence):
bazel build --@rules_coco//:license_source=local_acquire //... # For action_file mode, also specify the auth token path: bazel build --@rules_coco//:license_source=action_file --@rules_coco//:auth_token_path=/path/to/token //... -
In MODULE.bazel (for bzlmod users):
coco = use_extension("@rules_coco//coco:extensions.bzl", "coco") coco.toolchain( versions = ["stable"], license_source = "local_acquire", # Optional: set repository default # license_token = "...", # Optional: only needed when license_source = "token" # auth_token_path = "/path/to/token", # Optional: only needed when license_source = "action_file" )
-
In WORKSPACE (for WORKSPACE users):
coco_repositories( version = "stable", license_source = "local_acquire", # Optional: set repository default # license_token = "...", # Optional: only needed when license_source = "token" # auth_token_path = "/path/to/token", # Optional: only needed when license_source = "action_file" )
The repository-level configuration (MODULE.bazel or WORKSPACE) sets a default that applies to all builds, while the
command-line flag can be used to override it for specific builds. If neither is specified, the default is local_user.
license_token:
The license_token parameter is available in repository configuration for convenience, but:
- The
license_tokenparameter should only be used in repository configuration when using workload identity federation, where the token is not a secret. - For secret tokens, use the command-line flag instead:
--@rules_coco//:license_token=<token>, or better yet ensure that your bazel execution environment injectsCOCOTEC_AUTH_TOKENdirectly into actions.
There are several ways of setting the version of Popili that you would like to use.
-
In
WORKSPACEorMODULE.bazelyou can specify a single version inversions = [...]. -
If specifying several different versions in
WORKSPACEorMODULE.bazelyou can select your preferred one usingbazel build --@rules_coco//:version=1.5.1 -
If you wish to use different versions of Popili by using transitions:
In your
MODULE.bazel:coco = use_extension("@rules_coco//coco:extensions.bzl", "coco") coco.toolchain( c = True, cc = True, versions = ["1.5.0", "1.5.1"], # Register both versions )
Then in your
BUILD.bazel:coco_package( name = "modern", srcs = ["modern/src/Example.coco"], package = "modern/Coco.toml", ) with_popili_version( name = "modern_v150", target = ":modern", version = "1.5.0", ) coco_generate( name = "modern_cpp", language = "cpp", package = ":modern_v150", )See
e2e/multi_versionfor a full example.
The verification backend can be selected using the --@rules_coco//:verification_backend option:
local: Local verification (default). Runs on the build executor.remote: Remote verification: uses the configured remote verification service.attempt-remote: Attempt remote, fallback to local if it's not available.
For example:
bazel build --@rules_coco//:verification_backend=remote //...For each Coco.toml file add the following to your BUILD file:
load("@rules_coco//coco:defs.bzl", "coco_package")
coco_package(
name = "my_package",
srcs = glob(["*.coco"]),
manifest = "Coco.toml",
)If the Coco.toml file has dependencies then these need to be mirrored in the BUILD file:
load("@rules_coco//coco:defs.bzl", "coco_package")
coco_package(
name = "my_package",
srcs = glob(["*.coco"]),
manifest = "Coco.toml",
deps = [
"//my:other_pkg",
]
)To enable type checking as part of the build, add typecheck = True:
coco_package(
name = "my_package",
srcs = glob(["*.coco"]),
manifest = "Coco.toml",
typecheck = True, # Validates types before code generation
)When enabled, any target depending on this package (such as coco_generate) will wait for typecheck to pass.
To generate C++ code:
load("@rules_coco//coco:defs.bzl", "coco_generate")
coco_generate(
name = "my_package_cc_src",
language = "cpp",
package = ":my_package",
)This can then be compiled using cc_library like normal:
load("@rules_cc//cc:defs.bzl", "cc_library")
cc_library(
name = "my_package_cc",
srcs = [":my_package_cc_src"],
deps = [""],
)Alternatively these steps can be combined using coco_cc_library:
load("@rules_coco//coco:cc.bzl", "coco_cc_library")
coco_cc_library(
name = "my_package_cc",
generated_package = ":my_package_cc_src",
)Important
Your Coco.toml file must set generator.cpp.runtimeHeaderFileExtension to .h if you use a custom value for
generator.cpp.headerFileExtension.
To generate C code:
load("@rules_coco//coco:defs.bzl", "coco_generate")
coco_generate(
name = "my_package_c_src",
language = "c",
package = ":my_package",
)This can be compiled using coco_c_library:
load("@rules_coco//coco:c.bzl", "coco_c_library")
coco_c_library(
name = "my_package_c",
generated_package = ":my_package_c_src",
)Important
Your Coco.toml file must set generator.c.runtimeHeaderFileExtension to .h if you use a custom value for
generator.c.headerFileExtension.
Bazel has to be able to precompute the output paths of all rules. Since there are many settings in Coco.toml that
can affect output paths, these settings must be repeated in the BUILD file. If you forget to do this, you will get
errors when running coco_generate such as:
ERROR: example/BUILD:10:14: output 'example/src/Example.h' was not created
ERROR: example/BUILD:10:14: Generating cpp example_test failed: not all outputs were created or valid
If you see these errors then you need to make sure that the BUILD files contain mirrors of the settings in the
Coco.toml file. For example, suppose you have the following Coco.toml file.
[generator.cpp]
headerFileExtension = ".hpp"
implementationFileExtension = ".cpp"
fileNameMangler = "LowerUnderscore"Then this would require the following in your BUILD file:
coco_generate(
name = "my_package_cc_src",
language = "cpp",
package = ":my_package",
cpp_header_file_extension = ".hpp", # Must match Coco.toml
cpp_implementation_file_extension = ".cpp", # Must match Coco.toml
cpp_file_name_mangler = "LowerUnderscore", # Must match Coco.toml
)| Coco.toml Setting | coco_generate Attribute |
|---|---|
generator.c.headerFileExtension |
c_header_file_extension |
generator.c.implementationFileExtension |
c_implementation_file_extension |
generator.c.headerFilePrefix |
c_header_file_prefix |
generator.c.implementationFilePrefix |
c_implementation_file_prefix |
generator.c.fileNameMangler |
c_file_name_mangler |
generator.c.flatFileHierarchy |
c_flat_file_hierarchy |
generator.c.regeneratePackages |
c_regenerate_packages |
generator.cpp.headerFileExtension |
cpp_header_file_extension |
generator.cpp.implementationFileExtension |
cpp_implementation_file_extension |
generator.cpp.headerFilePrefix |
cpp_header_file_prefix |
generator.cpp.implementationFilePrefix |
cpp_implementation_file_prefix |
generator.cpp.fileNameMangler |
cpp_file_name_mangler |
generator.cpp.flatFileHierarchy |
cpp_flat_file_hierarchy |
generator.cpp.regeneratePackages |
cpp_regenerate_packages |
generator.csharp.regeneratePackages |
csharp_regenerate_packages |
To generate C# code:
load("@rules_coco//coco:defs.bzl", "coco_generate")
coco_generate(
name = "my_package_csharp_src",
language = "csharp",
package = ":my_package",
)Note: C# code generation produces .cs files but does not include compilation support. Bazel's C# rules are currently too primitive to provide a good integration. The generated C# files can be consumed by other build systems or IDEs.
These rules can be used to create bazel test targets that execute the verification when bazel test is executed.
load("@rules_coco//coco:defs.bzl", "coco_verify_test")
coco_verify_test(
name = "my_package_test",
package = ":my_package",
)Format checking can be integrated into your test suite using coco_fmt_test:
load("@rules_coco//coco:defs.bzl", "coco_fmt_test")
coco_fmt_test(
name = "my_package_fmt_test",
package = ":my_package",
)This creates two targets:
my_package_fmt_test: Test that fails if code isn't formatted (bazel test)my_package_fmt: Binary to format code in-place (bazel run)
rules_coco provides rules for generating diagrams from Coco code:
Generate component architecture diagrams showing the structure and connections of your components:
load("@rules_coco//coco:defs.bzl", "coco_architecture_diagram")
# Generate diagram for a single component
coco_architecture_diagram(
name = "my_component_arch",
package = ":my_package",
components = {
"my_component.svg": "MyComponent",
},
port_names = True,
port_types = True,
)
# Generate diagrams for multiple components
coco_architecture_diagram(
name = "all_components_arch",
package = ":my_package",
components = {
"component1.svg": "Component1",
"component2.svg": "Component2",
"component3.svg": "Component3",
},
)Generate state machine diagrams:
load("@rules_coco//coco:defs.bzl", "coco_state_diagram")
coco_state_diagram(
name = "my_state_machine",
package = ":my_package",
targets = ["MyComponent.MyMachine"],
separate_edges = False,
)Note: State diagram generation currently requires packages with a single .coco file.
Generate sequence diagrams for verification failures. You must specify the expected counterexamples as a dict mapping output filenames to target declarations:
load("@rules_coco//coco:defs.bzl", "coco_counterexample_diagram")
coco_counterexample_diagram(
name = "my_counterexamples",
package = ":my_package",
counterexamples = {
"alarm_failure.svg": "Alarm",
"safety_violation.svg": "SafetyChecker",
},
deterministic = True,
)For filtering by specific assertions, use the counterexample_options() helper:
load("@rules_coco//coco:defs.bzl", "coco_counterexample_diagram", "counterexample_options")
coco_counterexample_diagram(
name = "specific_counterexamples",
package = ":my_package",
counterexamples = {
"safety.svg": counterexample_options(
decl = "SafetyChecker",
assertion = "Well-formedness",
),
"liveness.svg": counterexample_options(
decl = "LivenessChecker",
assertion = "Implements Provided Port",
),
},
)For detailed API documentation of all rules, macros, and their attributes, see the auto-generated documentation:
- defs.md - Core rules and macros
- cc.md - C++ integration
- c.md - C integration
- extensions.md - Module extension (bzlmod setup)
- repositories.md - Repository setup (WORKSPACE mode)
- renovate.md - Using Renovate for automatic updates (including offline/firewall scenarios)
Copyright 2019- Cocotec Limited. Licensed under the Apache License, Version 2.0.