Skip to content

Commit

Permalink
Added utility for parsing action Args param files (#2897)
Browse files Browse the repository at this point in the history
This change introduces the `action_args` crate which is something I feel
I keep writing in various repos now. It's original design is to make it
easier to pass args to built binaries. E.g.

```rust
use action_args;
use clap::Parser;
use runfiles::{rlocation, Runfiles};

#[command(version, about, long_about = None)]
struct ClapArgs {}

fn main() {
    let args = {
        let runfiles = Runfiles::create().unwrap();

        let var = std::env::var("ARGS_FILE").unwrap();
        let runfile = rlocation!(runfiles, var).unwrap();
        let text = std::fs::read_to_string(runfile).unwrap();
        let argv = action_args::parse_args(text);
        ClapArgs::parse_from(std::env::args().take(1).chain(argv))
    };

    // ...
    // ...
    // ...
}
```

This utility will likely be unnecessary should
bazelbuild/bazel#16076 ever be implemented.

Co-authored-by: Daniel Wagner-Hall <dawagner@gmail.com>
  • Loading branch information
UebelAndre and illicitonion authored Oct 1, 2024
1 parent 9390eba commit a487a8b
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 0 deletions.
13 changes: 13 additions & 0 deletions tools/action_args/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
load("//rust:defs.bzl", "rust_library", "rust_test")

rust_library(
name = "action_args",
srcs = ["action_args.rs"],
edition = "2021",
visibility = ["//visibility:public"],
)

rust_test(
name = "action_args_test",
crate = ":action_args",
)
125 changes: 125 additions & 0 deletions tools/action_args/action_args.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
//! Utilities for parsing [Args](https://bazel.build/rules/lib/builtins/Args.html) param files.

use std::path::Path;

/// The format for an [Args param file[(https://bazel.build/rules/lib/builtins/Args.html#set_param_file_format).
#[derive(Debug)]
pub enum ActionArgsFormat {
/// Each item (argument name or value) is written verbatim to the param
/// file with a newline character following it.
Multiline,

/// Same as [Self::Multiline], but the items are shell-quoted.
Shell,

/// Same as [Self::Multiline], but (1) only flags (beginning with '--')
/// are written to the param file, and (2) the values of the flags, if
/// any, are written on the same line with a '=' separator. This is the
/// format expected by the Abseil flags library.
FlagPerLine,
}

impl Default for ActionArgsFormat {
fn default() -> Self {
Self::Shell
}
}

/// Parsed [`ctx.action.args`](https://bazel.build/rules/lib/builtins/Args.html) params.
type ActionArgv = Vec<String>;

/// Parse an [Args](https://bazel.build/rules/lib/builtins/Args.html) param file string into an argv list.
pub fn parse_args_with_fmt(text: String, fmt: ActionArgsFormat) -> ActionArgv {
text.lines()
.map(|s| match fmt {
ActionArgsFormat::Shell => {
if s.starts_with('\'') && s.ends_with('\'') {
s[1..s.len() - 1].to_owned()
} else {
s.to_owned()
}
}
_ => s.to_owned(),
})
.collect()
}

/// Parse an [Args](https://bazel.build/rules/lib/builtins/Args.html) param file string into an argv list.
pub fn parse_args(text: String) -> ActionArgv {
parse_args_with_fmt(text, ActionArgsFormat::default())
}

/// Parse an [Args](https://bazel.build/rules/lib/builtins/Args.html) param file into an argv list.
pub fn try_parse_args_with_fmt(
path: &Path,
fmt: ActionArgsFormat,
) -> Result<ActionArgv, std::io::Error> {
let text = std::fs::read_to_string(path)?;
Ok(parse_args_with_fmt(text, fmt))
}

/// Parse an [Args](https://bazel.build/rules/lib/builtins/Args.html) param file into an argv list.
pub fn try_parse_args(path: &Path) -> Result<ActionArgv, std::io::Error> {
let text = std::fs::read_to_string(path)?;
Ok(parse_args(text))
}

#[cfg(test)]
mod test {
use std::path::PathBuf;

use super::*;

const TEST_ARGS: [&str; 5] = ["foo", "-bar", "'baz'", "'--qux=quux'", "--quuz='corge'"];

#[test]
fn test_multiline_string() {
let text = TEST_ARGS.join("\n");

let args = parse_args_with_fmt(text, ActionArgsFormat::Multiline);
assert_eq!(
vec!["foo", "-bar", "'baz'", "'--qux=quux'", "--quuz='corge'"],
args
)
}

#[test]
fn test_shell_string() {
let text = TEST_ARGS.join("\n");

let args = parse_args_with_fmt(text, ActionArgsFormat::Shell);
assert_eq!(
vec!["foo", "-bar", "baz", "--qux=quux", "--quuz='corge'"],
args
)
}

#[test]
fn test_flag_per_line_string() {
let text = TEST_ARGS.join("\n");

let args = parse_args_with_fmt(text, ActionArgsFormat::FlagPerLine);
assert_eq!(
vec!["foo", "-bar", "'baz'", "'--qux=quux'", "--quuz='corge'"],
args
)
}

#[test]
fn test_from_file() {
let text = TEST_ARGS.join("\n");

let test_tempdir = PathBuf::from(std::env::var("TEST_TMPDIR").unwrap());
let test_file = test_tempdir.join("test_from_file.txt");

assert!(try_parse_args(&test_file).is_err());

std::fs::write(&test_file, text).unwrap();

let args = try_parse_args(&test_file).unwrap();
assert_eq!(
vec!["foo", "-bar", "baz", "--qux=quux", "--quuz='corge'"],
args
)
}
}

0 comments on commit a487a8b

Please sign in to comment.