diff --git a/probe_rs/BUILD.bazel b/probe_rs/BUILD.bazel index acceb64..632c128 100644 --- a/probe_rs/BUILD.bazel +++ b/probe_rs/BUILD.bazel @@ -17,7 +17,7 @@ bzl_library( srcs = ["defs.bzl"], visibility = ["//visibility:public"], deps = [ - "@bazel_skylib//rules:native_binary", + "//probe_rs/private:run", ], ) diff --git a/probe_rs/defs.bzl b/probe_rs/defs.bzl index b87fa95..e4c4eff 100644 --- a/probe_rs/defs.bzl +++ b/probe_rs/defs.bzl @@ -1,6 +1,6 @@ "Public API re-exports" -load("@bazel_skylib//rules:native_binary.bzl", "native_binary") +load("//probe_rs/private:run.bzl", "probe_rs_run") def _probe_rs_run(name, elf, chip, args = [], **kwargs): """Run probe-rs on the given ELF file. @@ -10,14 +10,15 @@ def _probe_rs_run(name, elf, chip, args = [], **kwargs): elf: The ELF file to run probe-rs on. chip: probe-rs chip to use. Ex. "nRF52840_xxAA" args: Additional arguments to pass to probe-rs. - **kwargs: Additional arguments to pass to native_binary. + **kwargs: Additional arguments to pass to the underlying executable rule. """ - native_binary( + probe_rs_run( name = name, - src = "@probe_rs//:probe-rs", - data = [elf], - args = ["run", "--chip", chip, "$(locations {})".format(elf)] + args, + elf = elf, + chip = chip, + probe_rs = "@probe_rs//:probe-rs", + probe_rs_args = args, **kwargs ) diff --git a/probe_rs/private/BUILD.bazel b/probe_rs/private/BUILD.bazel index 1bf5209..1745335 100644 --- a/probe_rs/private/BUILD.bazel +++ b/probe_rs/private/BUILD.bazel @@ -6,6 +6,12 @@ bzl_library( visibility = ["//probe_rs:__subpackages__"], ) +bzl_library( + name = "run", + srcs = ["run.bzl"], + visibility = ["//probe_rs:__subpackages__"], +) + bzl_library( name = "versions", srcs = ["versions.bzl"], diff --git a/probe_rs/private/run.bzl b/probe_rs/private/run.bzl new file mode 100644 index 0000000..e5c4bf8 --- /dev/null +++ b/probe_rs/private/run.bzl @@ -0,0 +1,66 @@ +"""Executable rule for running probe-rs against an ELF built for another platform.""" + +def _quote_shell(arg): + return "'" + arg.replace("'", "'\"'\"'") + "'" + +def _quote_batch(arg): + escaped = arg.replace("^", "^^") + escaped = escaped.replace("\"", "\"\"") + return "\"" + escaped + "\"" + +def _probe_rs_run_impl(ctx): + elf = ctx.file.elf + # probe-rs is a host tool even when the ELF was built for an embedded target. + tool = ctx.executable.probe_rs + is_windows = tool.basename.endswith(".exe") + script_name = ctx.label.name + (".bat" if is_windows else ".sh") + launcher = ctx.actions.declare_file(script_name) + + args = ["run", "--chip", ctx.attr.chip, elf.short_path] + ctx.attr.probe_rs_args + + # Emit a tiny launcher so `bazel run` executes probe-rs from runfiles while + # still allowing the ELF input to stay in the target configuration. + if is_windows: + script = "\r\n".join([ + "@echo off", + "setlocal", + "{} {}".format(_quote_batch(tool.short_path), " ".join([_quote_batch(arg) for arg in args])), + "exit /b %ERRORLEVEL%", + "", + ]) + else: + script = "\n".join([ + "#!/usr/bin/env bash", + "set -euo pipefail", + "{} {}".format(_quote_shell(tool.short_path), " ".join([_quote_shell(arg) for arg in args])), + "", + ]) + + ctx.actions.write(launcher, script, is_executable = True) + + # Keep both the ELF and the host tool in runfiles so the launcher can refer + # to them by short_path on every supported host platform. + runfiles = ctx.runfiles(files = [elf, tool]) + runfiles = runfiles.merge(ctx.attr.elf[DefaultInfo].default_runfiles) + runfiles = runfiles.merge(ctx.attr.probe_rs[DefaultInfo].default_runfiles) + + return [DefaultInfo(executable = launcher, runfiles = runfiles)] + +probe_rs_run = rule( + implementation = _probe_rs_run_impl, + executable = True, + attrs = { + "elf": attr.label( + mandatory = True, + allow_single_file = True, + ), + "chip": attr.string(mandatory = True), + "probe_rs_args": attr.string_list(), + "probe_rs": attr.label( + executable = True, + # Resolve probe-rs for the execution platform rather than the ELF's + # target platform so cross-target flashing works correctly. + cfg = "exec", + ), + }, +)