Skip to content

Commit

Permalink
Add Bazel build rules (#350)
Browse files Browse the repository at this point in the history
Signed-off-by: Michael Carroll <[email protected]>
mjcarroll authored Jan 19, 2025
1 parent 6ca51e6 commit 5a39b16
Showing 27 changed files with 620 additions and 2 deletions.
1 change: 1 addition & 0 deletions .bazelignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
bazel/integration_test
1 change: 1 addition & 0 deletions .bazelversion
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
7.4.1
21 changes: 21 additions & 0 deletions .github/workflows/bazel.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Bazel CI

on: [push, pull_request, workflow_dispatch]

jobs:
test:
uses: bazel-contrib/.github/.github/workflows/bazel.yaml@v7
with:
folders: |
[
".",
"bazel/integration_test",
]
# Explicitly exclude build/test configurations where bzlmod is disabled.
# Since xacro only supports bzlmod, these will always fail.
# Remove these exclusions when workspace support is dropped.
exclude: |
[
{"folder": ".", "bzlmodEnabled": false},
{"folder": "bazel/integration_test", "bzlmodEnabled": false},
]
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
*.pyc
build
bazel-*
MODULE.bazel.lock
81 changes: 81 additions & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
load("@bazel_skylib//rules:write_file.bzl", "write_file")
load("@rules_license//rules:license.bzl", "license")
load("@rules_python//python:defs.bzl", "py_binary", "py_library", "py_test")

package(
default_applicable_licenses = [":license"],
)

licenses(["notice"])

license(
name = "license",
license_kinds = [
"@rules_license//licenses/spdx:BSD-3-Clause",
],
license_text = "LICENSE",
)

write_file(
name = "write_xacro_main",
out = "xacro_main.py",
# This is the same as scripts/xacro from upstream, except that we lose the
# unused shebang line and we use a filename that is not subject to import
# path conflicts.
content = ["import xacro; xacro.main()"],
)

py_library(
name = "xacro_lib",
srcs = [
"xacro/__init__.py",
"xacro/cli.py",
"xacro/color.py",
"xacro/substitution_args.py",
"xacro/xmlutils.py",
],
imports = ["."],
visibility = ["//visibility:public"],
deps = [
"@rules_python//python/runfiles",
"@xacro_python_dependencies//pyyaml:pkg",
],
)

py_binary(
name = "xacro_main",
srcs = ["xacro_main.py"],
main = "xacro_main.py",
deps = [":xacro_lib"],
)

alias(
name = "xacro",
actual = ":xacro_main",
visibility = ["//visibility:public"],
)

TEST_RESOURCES = glob([
"test/*.xacro",
"test/*.xml",
"test/*.yaml",
"test/subdir/**",
"test/robots/**",
])

filegroup(
name = "test_data",
srcs = TEST_RESOURCES,
data = TEST_RESOURCES,
)

py_test(
name = "test_xacro",
srcs = ["test/test_xacro.py"],
data = [":test_data"],
main = "test/test_xacro.py",
deps = [
":xacro_main",
"@rules_python//python/runfiles",
],
)
39 changes: 39 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
module(
name = "xacro",
version = "2.0.11",
)

bazel_dep(name = "rules_license", version = "1.0.0")
bazel_dep(name = "bazel_skylib", version = "1.7.1")
bazel_dep(name = "rules_python", version = "0.40.0")

PYTHON_VERSIONS = [
"3.8",
"3.9",
"3.10",
"3.11",
"3.12",
]

python = use_extension("@rules_python//python/extensions:python.bzl", "python")

[
python.toolchain(
is_default = python_version == PYTHON_VERSIONS[-1],
python_version = python_version,
)
for python_version in PYTHON_VERSIONS
]

pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip")

[
pip.parse(
hub_name = "xacro_python_dependencies",
python_version = python_version,
requirements_lock = "//bazel:requirements_lock.txt",
)
for python_version in PYTHON_VERSIONS
]

use_repo(pip, "xacro_python_dependencies")
20 changes: 20 additions & 0 deletions bazel/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
load("@rules_python//python:pip.bzl", "compile_pip_requirements")

# This rule adds a convenient way to update the requirements file.
compile_pip_requirements(
name = "requirements",
src = "requirements.in",
requirements_txt = "requirements_lock.txt",
)

bzl_library(
name = "defs",
srcs = ["defs.bzl"],
visibility = ["//visibility:public"],
)

exports_files([
"requirements.in",
"requirements_lock.txt",
])
66 changes: 66 additions & 0 deletions bazel/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Bazel

This directory contains support files and tests for the bazel build system.

In addition to supporting building xacro with bazel, this also introduces two rules for build-time generation of xacro files.

## xacro_file

Allows you to transform a single xacro file into a generated output

A simple example:

```
load("@xacro//bazel:defs.bzl", "xacro_file")
# By default, will transform input filename with .xacro removed
xacro_file(
name = "sample1",
src = "sample1.xml.xacro",
)
```

A more complex example:

```
load("@xacro//bazel:defs.bzl", "xacro_file")
# By default, will transform input filename with .xacro removed
xacro_file(
name = "complex_example",
src = "complex.xml.xacro",
# Override the default output file name
out = "my_complex_model.xml",
# Depend on the XML file that we generated in the previous step
deps = [":sample1"],
# Set extra substitution args via the command line
extra_args = ["special_argument:=foo"]
)
```

Note in the case of the more complex example, you can use bazel-specified filenames if they are specified in the `deps` field:

```
<?xml version="1.0"?>
<root xmlns:xacro="http://www.ros.org/wiki/xacro">
<!-- include a file from a bazel path -->
<xacro:include filename="//sample1.xml"/>
</root>
```

## xacro_filegroup

Allows you to transform multiple xacro files into a generated filegroup

```
xacro_filegroup(
name = "samples",
srcs = [
"sample1.xml.xacro",
"sample2.xml.xacro",
],
data = [
"box.xml",
],
)
```
116 changes: 116 additions & 0 deletions bazel/defs.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
"""Provider and rules for generating xacro output at build time."""

XacroInfo = provider(
"Provider holding the result of a xacro generation step.",
fields = ["result"],
)

XACRO_EXTENSION = ".xacro"

def _xacro_impl(ctx):
# Use declared output or derive from source name
out = ctx.outputs.out or ctx.actions.declare_file(ctx.file.src.basename[:-len(XACRO_EXTENSION)])

# Gather inputs for the xacro command
direct_inputs = [ctx.file.src] + ctx.files.data
dep_inputs = [dep[XacroInfo].result for dep in ctx.attr.deps]
all_inputs = direct_inputs + dep_inputs

# Create a temporary directory
temp_dir = "TMP_XACRO/" + ctx.label.name

symlink_paths = []
for input in all_inputs:
symlink_path = ctx.actions.declare_file(temp_dir + "/" + input.basename)
ctx.actions.symlink(
output = symlink_path,
target_file = input,
)
symlink_paths.append(symlink_path)

arguments = [
"-o",
out.path,
"--root-dir",
ctx.bin_dir.path + "/" + temp_dir,
ctx.file.src.basename,
]
arguments += ["{}:={}".format(arg, val) for arg, val in ctx.attr.arguments.items()]

ctx.actions.run(
inputs = symlink_paths,
outputs = [out],
arguments = arguments,
executable = ctx.executable._xacro,
progress_message = "Running xacro: %s -> %s" % (ctx.file.src.short_path, out.short_path),
mnemonic = "Xacro",
)

return [
XacroInfo(result = out),
DefaultInfo(
files = depset([out]),
data_runfiles = ctx.runfiles(files = [out]),
),
]

xacro_file = rule(
attrs = {
"src": attr.label(
mandatory = True,
allow_single_file = True,
),
"out": attr.output(),
"data": attr.label_list(
allow_files = True,
),
"arguments": attr.string_dict(),
"deps": attr.label_list(providers = [XacroInfo]),
"_xacro": attr.label(
default = "@xacro//:xacro",
cfg = "host",
executable = True,
),
},
implementation = _xacro_impl,
provides = [XacroInfo, DefaultInfo],
)

def xacro_filegroup(
name,
srcs = [],
data = [],
tags = [],
visibility = None):
"""Runs xacro on several input files, creating a filegroup of the output.
The output filenames will match the input filenames but with the ".xacro"
suffix removed.
Xacro is the ROS XML macro tool; http://wiki.ros.org/xacro.
Args:
name: The name of the filegroup label.
srcs: The xacro input files of this rule.
data: Optional supplemental files required by the srcs.
"""
outs = []
for src in srcs:
if not src.endswith(XACRO_EXTENSION):
fail("xacro_filegroup srcs should be named *.xacro not {}".format(
src,
))
out = src[:-len(XACRO_EXTENSION)]
outs.append(out)
xacro_file(
name = out,
src = src,
data = data,
tags = tags,
visibility = ["//visibility:private"],
)
native.filegroup(
name = name,
srcs = outs,
visibility = visibility,
)
1 change: 1 addition & 0 deletions bazel/integration_test/.bazelversion
Loading

0 comments on commit 5a39b16

Please sign in to comment.