Documentation Index Fetch the complete documentation index at: https://docs.reasonos.com/llms.txt
Use this file to discover all available pages before exploring further.
RBS is fully extensible. You can define custom build rules for any language, tool, or workflow using native.define_rule. Custom rules are written in the RBS DSL and can be shared across projects.
Defining a rule
Use native.define_rule to create a new rule type:
def my_binary_impl ( ctx ):
"""Build a custom binary."""
dirs = ctx.bin.create_dirs()
ctx.runfiles( files = ctx.srcs)
native.create_launcher(
output_path = ctx.bin.executable,
executable = "python" ,
args = [ctx.attr.main],
workdir = "$SCRIPT_DIR/runfiles/_main" ,
)
native.define_rule(
name = "my_binary" ,
kind = "binary" ,
implementation = my_binary_impl,
attrs = {
"srcs" : attr.list( of = attr.string),
"main" : attr.string( mandatory = True ),
"deps" : attr.list( of = attr.string, default = []),
},
)
# Now use it like any built-in rule
my_binary(
name = "app" ,
srcs = [ "main.py" , "utils.py" ],
main = "main.py" ,
)
Convenience wrappers
# For binary rules
native.define_binary_rule( name = "my_binary" , implementation = impl, attrs = { ... })
# For library rules
native.define_library_rule( name = "my_library" , implementation = impl, attrs = { ... })
Parameters
Parameter Type Required Description namestring Yes Rule name — exported as a function in BUILD files kindstring No Classifier (e.g., "binary", "library", "test") implementationfunction No The build logic function receiving ctx toolchainstring No Toolchain type this rule uses attrsdict No Attribute schema for the rule fragmentslist No Required configuration fragments
Attribute types
Define the schema for rule attributes using the attr module:
Function Description attr.string()String attribute attr.string_list()List of strings attr.string_dict()Dictionary of strings attr.bool()Boolean attribute attr.int()Integer attribute attr.label()Reference to another target attr.label_list()List of target references attr.dict()Generic dictionary attr.list()Generic list
Common parameters
All attribute types support:
Parameter Type Default Description mandatorybool FalseWhether the attribute is required defaultany NoneDefault value if not specified docstring ""Documentation string
The is_dep parameter
For attr.label() and attr.label_list(), use is_dep = True to mark the attribute as a build dependency. This ensures the referenced target is built before the current rule.
native.define_rule(
name = "oci_image" ,
implementation = _oci_image_impl,
attrs = {
"binary" : attr.label( is_dep = True , doc = "Binary to package" ),
"files" : attr.label_list( is_dep = True , doc = "Additional files" ),
"config_template" : attr.label( doc = "Optional config (not a dep)" ),
},
)
The context object (ctx)
The implementation function receives a ctx object with access to build inputs, outputs, and actions.
def my_rule_impl ( ctx ):
ctx.name # Target name
ctx.package # Package path
ctx.srcs # List of source files
ctx.deps # List of dependencies
ctx.attr.main # Access custom attributes
ctx.actions — Execute commands
def my_rule_impl ( ctx ):
# Run an executable
ctx.actions.run(
executable = "python" ,
arguments = [ "compile.py" , "input.txt" ],
inputs = [ "compile.py" , "input.txt" ],
outputs = [ "output.txt" ],
mnemonic = "Compile" ,
progress_message = "Compiling..." ,
)
# Run a shell command
ctx.actions.run_shell(
command = "cat input.txt | sort > sorted.txt" ,
outputs = [ "sorted.txt" ],
description = "Sort input" ,
)
# Write a file
ctx.actions.write(
output = "config.json" ,
content = '{"version": "1.0"}' ,
is_executable = False ,
)
# Expand a template
ctx.actions.expand_template(
template = "template.conf" ,
output = "app.conf" ,
substitutions = { "APP_NAME" : ctx.name, "VERSION" : "1.0" },
)
ctx.bin — Hermetic output paths (recommended)
Provides per-target, package-isolated output paths for hermetic builds:
def my_rule_impl ( ctx ):
dirs = ctx.bin.create_dirs()
dirs.output # .rbs/bin/{platform}/{package}/{name}
dirs.run_files # .rbs/bin/{platform}/{package}/{name}/runfiles
dirs.toolchains # .rbs/bin/{platform}/{package}/{name}/toolchains
dirs.data # .rbs/bin/{platform}/{package}/{name}/data
ctx.bin.executable # Path to the main executable
ctx.bin.platform # e.g., "darwin-arm64"
ctx.bin.package # e.g., "services/api"
ctx.bin.local_dep — Resolve local dependency paths
lib_path = ctx.bin.local_dep( ":my_lib" ) # Same package
utils_path = ctx.bin.local_dep( "//libs/common:utils" ) # Different package
ctx.bin.external_dep — Resolve external dependency paths
requests_path = ctx.bin.external_dep( "requests" , "python" )
express_path = ctx.bin.external_dep( "express" , "nodejs" )
ctx.runfiles — Manage runtime files
def my_rule_impl ( ctx ):
main_dir = ctx.runfiles( files = ctx.srcs)
# Creates runfiles/_main directory with source files
tools_dir = ctx.tools.copy(
toolchain = "python3" ,
destination = ctx.outputs.dir + "/tools" ,
)
ctx.external_deps — Manage external packages
deps_dir = ctx.external_deps.copy(
dependencies = [ "@external://requests" , "@external://flask" ],
destination = ctx.outputs.dir + "/dependencies" ,
language = "python" ,
)
if ctx.external_deps.exists( name = "requests" , ecosystem = "python" ):
dep_info = ctx.external_deps.get( name = "requests" , ecosystem = "python" )
print ( "Cache dir:" , dep_info.cache_dir)
ctx.file — File operations
content = ctx.file.read( "input.txt" )
ctx.file.write( "output.txt" , "Hello World" )
if ctx.file.exists( "optional.txt" ):
ctx.file.copy( "optional.txt" , "output/optional.txt" )
ctx.file.copy_tree( "src/" , "dest/" )
ctx.dir — Directory operations
ctx.dir.create( "output/subdir" )
files = ctx.dir.list( "input_dir" )
if ctx.dir.exists( "optional_dir" ):
pass
ctx.json — JSON operations
data = ctx.json.parse( '{"key": "value"}' )
json_str = ctx.json.stringify({ "result" : "success" })
ctx.http — HTTP operations
content = ctx.http.get( "https://api.example.com/data" )
ctx.http.download( "https://example.com/file.txt" , "downloaded.txt" )
ctx.archive — Archive operations
ctx.archive.extract( "archive.zip" , "extracted/" )
Launcher scripts — native.create_launcher()
Generate cross-platform launcher scripts for binaries and tests. This is the recommended way to create executable wrappers.
Basic usage
native.create_launcher(
output_path = ctx.bin.executable,
executable = "python" ,
args = [ "main.py" ],
toolchain_paths = [ "$SCRIPT_DIR/toolchains/python*/bin" ],
workdir = "$SCRIPT_DIR/runfiles/_main" ,
)
Parameters
Parameter Type Required Description output_pathstring Yes Path to write the launcher script executablestring No Command to execute argslist No Arguments for the executable envdict No Environment variables path_env_varsdict No Path-based env vars with separators toolchain_pathslist No Glob patterns for toolchain bin dirs workdirstring No Working directory pre_commandslist No Shell commands to run before exec conditionstring No Shell condition for conditional execution fallback_cmdstring No Command if condition fails
Path-based environment variables
Different languages use different path variables. The path_env_vars parameter handles this:
path_env_vars = {
"PYTHONPATH" : {
"paths" : [ "$SCRIPT_DIR/runfiles/_main" , "$SCRIPT_DIR/runfiles" ],
"separator" : ":" ,
"append" : True ,
},
}
path_env_vars = {
"CLASSPATH" : {
"paths" : [ "$SCRIPT_DIR/runfiles/_main/classes" , "$SCRIPT_DIR/runfiles/_main/libs/*" ],
"separator" : ":" ,
"append" : False ,
},
}
path_env_vars = {
"NODE_PATH" : {
"paths" : [ "$SCRIPT_DIR/runfiles/_main/node_modules" ],
"separator" : ":" ,
"append" : True ,
},
}
Conditional execution
native.create_launcher(
output_path = ctx.bin.executable,
executable = "python" ,
args = [ "-m" , "pytest" , "test_main.py" , "-v" ],
condition = 'python -c "import pytest" 2>/dev/null' ,
fallback_cmd = "python -m unittest test_main" ,
)
Complete example — Python binary rule
def _py_binary_impl ( ctx ):
dirs = ctx.bin.create_dirs()
# Copy toolchain into target's output
ctx.file.copy_tree(
ctx.output_path.toolchains + "/python3.11" ,
dirs.toolchains + "/python3.11" ,
)
# Copy source files
ctx.runfiles( files = ctx.srcs)
# Copy external dependencies
for dep in ctx.attr.deps:
dep_path = ctx.bin.external_dep(dep, "python" )
if ctx.dir.exists(dep_path):
ctx.file.copy_tree(dep_path, dirs.run_files + "/" + dep)
# Create launcher
native.create_launcher(
output_path = ctx.bin.executable,
executable = "python" ,
args = [ctx.attr.main],
path_env_vars = {
"PYTHONPATH" : {
"paths" : [ "$SCRIPT_DIR/runfiles/_main" , "$SCRIPT_DIR/runfiles" ],
"separator" : ":" ,
"append" : True ,
},
},
toolchain_paths = [ "$SCRIPT_DIR/toolchains/python*/bin" ],
workdir = "$SCRIPT_DIR/runfiles/_main" ,
)
native.define_binary_rule(
name = "py_binary" ,
implementation = _py_binary_impl,
attrs = {
"srcs" : attr.list( of = attr.string),
"main" : attr.string( mandatory = True ),
"deps" : attr.list( of = attr.string, default = []),
},
)
External dependency resolvers
Define custom resolvers to manage how packages are downloaded and cached:
def python_pip_resolver ( package , version , cache_dir , context ):
metadata_url = "https://pypi.org/pypi/ {} / {} /json" .format(package, version)
metadata = context.json.parse(context.http.get(metadata_url))
for file_info in metadata[ "urls" ]:
if file_info[ "filename" ].endswith( ".whl" ):
wheel_path = cache_dir + "/package.whl"
context.http.download(file_info[ "url" ], wheel_path)
context.archive.extract(wheel_path, cache_dir + "/site-packages" )
return { "success" : True }
return { "success" : False }
native.define_external_dep_resolver(
name = "python_pip" ,
ecosystem = "python" ,
implementation = python_pip_resolver,
library_dirs = [ "site-packages" , "lib" ],
file_extensions = [ ".whl" , ".tar.gz" ],
cache_structure = "python/ {package} / {version} " ,
)
Next steps
Language SDKs See built-in rules for Python, Java, Node.js, and more.
Container Building Learn to build OCI container images with custom rules.