DISCLAIMER: Following text is generated by AI, but I guided the debug process, verified the fix myself on our codebase, and double checked the below issue description.
Description
When defining a xacro_file target inside a subpackage (e.g., //my/robot/description:xacro), the build fails because the xacro python script is given an incorrect --root-dir argument. The rule calculates a --root-dir that completely omits the Bazel package path, causing xacro to look for its temporary symlinks at the execution root instead of inside the package's output directory.
Additionally, when out is not explicitly provided (such as when using the xacro_filegroup macro), the rule double-nests the output files deep inside the workspace path because it passes ctx.file.src.path into declare_file.
Expected Behavior
The xacro_file rule should successfully find its symlinked dependencies and generate the output .sdf/.urdf file regardless of how deep in the repository the BUILD file is located.
Actual Behavior
The build crashes with a Python traceback from xacro:
xacro.XacroException: No such file or directory: bazel-out/k8-opt/bin//TMP_XACRO/my_target/my/robot/description/my_file.xacro [Errno 2] No such file or directory: 'bazel-out/k8-opt/bin//TMP_XACRO/my_target/my/robot/description/my_file.xacro'
(Notice the // and the missing package path before TMP_XACRO)
Root Cause Analysis
1. The --root-dir pathing bug:
When symlinks are created, the rule uses ctx.actions.declare_file(temp_dir + "/" + input_path). Bazel automatically prepends the package's output path to this (e.g., bazel-out/k8-opt/bin/my/robot/description/TMP_XACRO/...).
However, the --root-dir passed to xacro is calculated manually by stripping the short_path from out.path:
if out.path.endswith(out.short_path):
output_path = out.path[:-len(out.short_path)] # Evaluates to purely 'bazel-out/k8-opt/bin/'
root_dir = output_path + '/' + temp_dir
This sets --root-dir to bazel-out/k8-opt/bin//TMP_XACRO/..., completely losing the my/robot/description/ package path.
2. The xacro_filegroup output bug:
At the top of the rule, out is defaulted to:
out = ctx.outputs.out or ctx.actions.declare_file(ctx.file.src.path[:-len(XACRO_EXTENSION)])
Because ctx.file.src.path includes the workspace path (e.g., my/robot/description/file.xacro), declare_file nests this again inside the package output directory: bazel-out/.../bin/my/robot/description/my/robot/description/file.sdf.
Proposed Fix
I was able to resolve both issues locally with the following changes to _xacro_impl:
Fix 1: Use .basename for default outputs
Change the default output declaration to use .basename so it doesn't double-nest paths:
out = ctx.outputs.out or ctx.actions.declare_file(ctx.file.src.basename[:-len(XACRO_EXTENSION)])
Fix 2: Derive --root-dir dynamically from the generated symlinks
Remove the hardcoded output_path block entirely, and instead extract the true absolute root directory directly from the first symlink Bazel creates:
# Recompute the primary input_path just like we did for the symlinks
input_path = ctx.file.src.path
if input_path.startswith(prefix):
input_path = input_path[len(prefix):]
# symlink_paths[0] is guaranteed to be the symlink for ctx.file.src.
# By stripping the input_path from the end, we get the exact, correct root_dir
# regardless of whether it's an internal or external module.
root_dir = symlink_paths[0].path[:-len(input_path) - 1]
I can propose an upstream PR
DISCLAIMER: Following text is generated by AI, but I guided the debug process, verified the fix myself on our codebase, and double checked the below issue description.
Description
When defining a
xacro_filetarget inside a subpackage (e.g.,//my/robot/description:xacro), the build fails because the xacro python script is given an incorrect--root-dirargument. The rule calculates a--root-dirthat completely omits the Bazel package path, causingxacroto look for its temporary symlinks at the execution root instead of inside the package's output directory.Additionally, when
outis not explicitly provided (such as when using thexacro_filegroupmacro), the rule double-nests the output files deep inside the workspace path because it passesctx.file.src.pathintodeclare_file.Expected Behavior
The
xacro_filerule should successfully find its symlinked dependencies and generate the output.sdf/.urdffile regardless of how deep in the repository theBUILDfile is located.Actual Behavior
The build crashes with a Python traceback from
xacro:(Notice the
//and the missing package path beforeTMP_XACRO)Root Cause Analysis
1. The
--root-dirpathing bug:When symlinks are created, the rule uses
ctx.actions.declare_file(temp_dir + "/" + input_path). Bazel automatically prepends the package's output path to this (e.g.,bazel-out/k8-opt/bin/my/robot/description/TMP_XACRO/...).However, the
--root-dirpassed toxacrois calculated manually by stripping theshort_pathfromout.path:This sets
--root-dirtobazel-out/k8-opt/bin//TMP_XACRO/..., completely losing themy/robot/description/package path.2. The
xacro_filegroupoutput bug:At the top of the rule,
outis defaulted to:Because
ctx.file.src.pathincludes the workspace path (e.g.,my/robot/description/file.xacro),declare_filenests this again inside the package output directory:bazel-out/.../bin/my/robot/description/my/robot/description/file.sdf.Proposed Fix
I was able to resolve both issues locally with the following changes to
_xacro_impl:Fix 1: Use
.basenamefor default outputsChange the default output declaration to use
.basenameso it doesn't double-nest paths:Fix 2: Derive
--root-dirdynamically from the generated symlinksRemove the hardcoded
output_pathblock entirely, and instead extract the true absolute root directory directly from the first symlink Bazel creates:I can propose an upstream PR