Skip to content

Commit

Permalink
no_std support
Browse files Browse the repository at this point in the history
- Disable default features in dependencies
- Use anyhow::Error::msg to map errors. Note: anyhow will itself be removed later.
- lazy_static/spin_no_std used in no_std environments
- ensure_no_std binary is built to target  thumbv7m-none-eabi to ensure that
  there are no std dependencies.  thumbv7m-none-eabi target has no std support.
- The opa-no-std feature enables only those Regorus features that work with no_std.
- Enable tests with no_std
- Update sizes of regorus binary in  README.md
- Ensure that regorus example can be built with only std
- Ensure that regorus example can be built with no_std

Signed-off-by: Anand Krishnamoorthi <[email protected]>
  • Loading branch information
anakrish committed May 12, 2024
1 parent 01fc234 commit 77eb476
Show file tree
Hide file tree
Showing 25 changed files with 344 additions and 174 deletions.
8 changes: 8 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ jobs:
- uses: actions/checkout@v3
- name: Add musl target
run: rustup target add x86_64-unknown-linux-musl
- name: Add no_std target
run: rustup target add thumbv7m-none-eabi
- name: Install musl-gcc
run: sudo apt update && sudo apt install -y musl-tools
- name: Format Check
Expand All @@ -26,6 +28,12 @@ jobs:
run: cargo build -r --all-features --verbose
- name: Build
run: cargo build -r --verbose
- name: Build no_std
run: cd tests/ensure_no_std && cargo build -r --target thumbv7m-none-eabi
- name: Test no_std
name: cargo test -r --no-default-features
- name: Build only std
name: cargo build -r --example regorus --no-default-features --features "std"
- name: Doc Tests
run: cargo test -r --doc
- name: Run tests
Expand Down
71 changes: 50 additions & 21 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ members = [
"bindings/wasm",
"bindings/java",
"bindings/ruby/ext/regorusrb",
"tests/ensure_no_std",
]

[package]
Expand All @@ -15,7 +16,7 @@ version = "0.1.5"
edition = "2021"
license-file = "LICENSE"
repository = "https://github.com/microsoft/regorus"
keywords = ["interpreter", "opa", "policy-as-code", "rego"]
keywords = ["interpreter", "no_std", "opa", "policy-as-code", "rego"]

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

Expand All @@ -33,14 +34,15 @@ crypto = ["dep:constant_time_eq", "dep:hmac", "dep:hex", "dep:md-5", "dep:sha1",
deprecated = []
hex = ["dep:data-encoding"]
http = []
jwt = ["dep:jsonwebtoken", "dep:data-encoding"]
glob = ["dep:wax"]
graph = []
jsonschema = ["dep:jsonschema"]
jwt = ["dep:jsonwebtoken", "dep:data-encoding", "dep:itertools"]
no_std = ["lazy_static/spin_no_std"]
opa-runtime = []
regex = ["dep:regex"]
semver = ["dep:semver"]
std = ["serde_json/std"]
std = ["rand/std", "rand/std_rng", "serde_json/std"]
time = ["dep:chrono", "dep:chrono-tz"]
uuid = ["dep:uuid"]
urlquery = ["dep:url"]
Expand All @@ -67,41 +69,62 @@ full-opa = [
"yaml"
]

# Features that can be used in no_std environments.
# Note that: the spin_no_std feature in lazy_static must be specified.
opa-no-std = [
"arc",
"base64",
"base64url",
"coverage",
"crypto",
"deprecated",
"graph",
"hex",
"no_std",
"opa-runtime",
"regex",
"semver",
# Configure lazy_static to use spinlocks.
"lazy_static/spin_no_std"
]

# This feature enables some testing utils for OPA tests.
opa-testutil = []
rand = ["dep:rand"]

[dependencies]
anyhow = { version = "1.0.45", default-features=false }
anyhow = { version = "1.0.45", default-features = false }
serde = {version = "1.0.150", default-features = false, features = ["derive", "rc"] }
serde_json = { version = "1.0.89", default-features=false, features = ["alloc"] }
serde_yaml = {version = "0.9.16", optional = true }
lazy_static = "1.4.0"
rand = "0.8.5"
num = "0.4.1"
serde_json = { version = "1.0.89", default-features = false, features = ["alloc"] }
lazy_static = { version = "1.4.0", default-features = false }

# Crypto
constant_time_eq = {version = "0.3.0", optional = true}
hmac = {version = "0.12.1", optional = true}
sha2 = {version= "0.10.8", optional = true}
hex = {version = "0.4.3", optional = true}
sha1 = {version = "0.10.6", optional = true}
md-5 = {version = "0.10.6", optional = true}

data-encoding = { version = "2.4.0", optional = true }
constant_time_eq = {version = "0.3.0", optional = true, default-features = false }
hmac = {version = "0.12.1", optional = true, default-features = false}
sha2 = {version= "0.10.8", optional = true, default-features = false }
hex = {version = "0.4.3", optional = true, default-features = false, features = ["alloc"] }
sha1 = {version = "0.10.6", optional = true, default-features = false }
md-5 = {version = "0.10.6", optional = true, default-features = false }

data-encoding = { version = "2.4.0", optional = true, default-features=false, features = ["alloc"] }
scientific = { version = "0.5.2" }

regex = {version = "1.10.2", optional = true}
semver = {version = "1.0.20", optional = true}
regex = {version = "1.10.2", optional = true, default-features = false }
semver = {version = "1.0.20", optional = true, default-features = false }
wax = { version = "0.6.0", features = [], default-features = false, optional = true }
url = { version = "2.5.0", optional = true }
uuid = { version = "1.6.1", features = ["v4", "fast-rng"], optional = true }
uuid = { version = "1.6.1", default-features = false, features = ["v4", "fast-rng"], optional = true }
jsonschema = { version = "0.17.1", default-features = false, optional = true }
chrono = { version = "0.4.31", optional = true }
chrono-tz = { version = "0.8.5", optional = true }
jsonwebtoken = { version = "9.2.0", optional = true }
itertools = "0.12.1"
itertools = { version = "0.12.1", default-features = false, optional = true }

serde_yaml = {version = "0.9.16", default-features = false, optional = true }
rand = { version = "0.8.5", default-features = false, optional = true }

[dev-dependencies]
anyhow = "1.0.45"
cfg-if = "1.0.0"
clap = { version = "4.4.7", features = ["derive"] }
prettydiff = { version = "0.6.4", default-features = false }
Expand Down Expand Up @@ -131,6 +154,12 @@ name="kata"
harness=false
test=false

[[example]]
name="regorus"
harness=false
test=false
doctest=false

[package.metadata.docs.rs]
# To build locally:
# RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features --no-deps
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,14 @@ features. By default all features are enabled.
The default build of regorus example program is 6.4M:
```bash
$ cargo build -r --example regorus; strip target/release/examples/regorus; ls -lh target/release/examples/regorus
-rwxr-xr-x 1 anand staff 6.4M Jan 19 11:23 target/release/examples/regorus*
-rwxr-xr-x 1 anand staff 6.3M May 11 22:03 target/release/examples/regorus*
```


When all features except for `yaml` are disabled, the binary size drops down to 2.9M.
When all default features are disabled, the binary size drops down to 1.9M.
```bash
$ cargo build -r --example regorus --features "yaml" --no-default-features; strip target/release/examples/regorus; ls -lh target/release/examples/regorus
-rwxr-xr-x 1 anand staff 2.9M Jan 19 11:26 target/release/examples/regorus*
$ cargo build -r --example regorus --no-default-features; strip target/release/examples/regorus; ls -lh target/release/examples/regorus
-rwxr-xr-x 1 anand staff 1.9M May 11 22:04 target/release/examples/regorus*
```

Regorus passes the [OPA v0.64.0 test-suite](https://www.openpolicyagent.org/docs/latest/ir/#test-suite) barring a few
Expand Down
54 changes: 46 additions & 8 deletions examples/regorus.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,37 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use anyhow::{bail, Result};
use anyhow::{anyhow, bail, Result};

#[allow(dead_code)]
fn read_file(path: &String) -> Result<String> {
std::fs::read_to_string(path).map_err(|_| anyhow!("could not read {path}"))
}

#[allow(unused_variables)]
fn read_value_from_yaml_file(path: &String) -> Result<regorus::Value> {
#[cfg(feature = "yaml")]
return regorus::Value::from_yaml_file(path);

#[cfg(not(feature = "yaml"))]
bail!("regorus has not been built with yaml support");
}

fn read_value_from_json_file(path: &String) -> Result<regorus::Value> {
#[cfg(feature = "std")]
return regorus::Value::from_json_file(path);

#[cfg(not(feature = "std"))]
regorus::Value::from_json_str(&read_file(path)?)
}

fn add_policy_from_file(engine: &mut regorus::Engine, path: String) -> Result<()> {
#[cfg(feature = "std")]
return engine.add_policy_from_file(path);

#[cfg(not(feature = "std"))]
engine.add_policy(path.clone(), read_file(&path)?)
}

fn rego_eval(
bundles: &[String],
Expand Down Expand Up @@ -35,23 +65,23 @@ fn rego_eval(
_ => continue,
}

engine.add_policy_from_file(entry.path())?;
add_policy_from_file(&mut engine, entry.path().display().to_string())?;
}
}

// Load given files.
for file in files.iter() {
if file.ends_with(".rego") {
// Read policy file.
engine.add_policy_from_file(file)?;
add_policy_from_file(&mut engine, file.clone())?;
} else {
// Read data file.
let data = if file.ends_with(".json") {
regorus::Value::from_json_file(file)?
read_value_from_json_file(file)?
} else if file.ends_with(".yaml") {
regorus::Value::from_yaml_file(file)?
read_value_from_yaml_file(file)?
} else {
bail!("Unsupported data file `{file}`. Must be rego, json or yaml.")
bail!("Unsupported data file `{file}`. Must be rego, json or yaml.");
};

// Merge given data.
Expand All @@ -61,9 +91,9 @@ fn rego_eval(

if let Some(file) = input {
let input = if file.ends_with(".json") {
regorus::Value::from_json_file(&file)?
read_value_from_json_file(&file)?
} else if file.ends_with(".yaml") {
regorus::Value::from_yaml_file(&file)?
read_value_from_yaml_file(&file)?
} else {
bail!("Unsupported input file `{file}`. Must be json or yaml.")
};
Expand Down Expand Up @@ -95,8 +125,12 @@ fn rego_lex(file: String, verbose: bool) -> Result<()> {
use regorus::unstable::*;

// Create source.
#[cfg(feature = "std")]
let source = Source::from_file(file)?;

#[cfg(not(feature = "std"))]
let source = Source::from_contents(file.clone(), read_file(&file)?)?;

// Create lexer.
let mut lexer = Lexer::new(&source);

Expand All @@ -122,8 +156,12 @@ fn rego_parse(file: String) -> Result<()> {
use regorus::unstable::*;

// Create source.
#[cfg(feature = "std")]
let source = Source::from_file(file)?;

#[cfg(not(feature = "std"))]
let source = Source::from_contents(file.clone(), read_file(&file)?)?;

// Create a parser and parse the source.
let mut parser = Parser::new(&source)?;
let ast = parser.parse()?;
Expand Down
18 changes: 14 additions & 4 deletions scripts/pre-push
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,27 @@
set -eo pipefail

if [ -f Cargo.toml ]; then
# Run precommit checks
# Run precommit checks.
dir=$(dirname "${BASH_SOURCE[0]}")
"$dir/pre-commit"

# Ensure that the public API works
# Ensure that the public API works.
cargo test -r --doc

# Ensure that we can build with all features
# Ensure that no_std build succeeds.
# Build for a target that has no std available.
if command -v rustup > /dev/null; then
rustup target add thumbv7m-none-eabi
(cd tests/ensure_no_std; cargo build -r --target thumbv7m-none-eabi)
fi

# Ensure that we can build with only std.
cargo build -r --example regorus --no-default-features --features std

# Ensure that we can build with all features.
cargo build -r --all-features

# Ensure that all tests pass
# Ensure that all tests pass.
cargo test -r
cargo test -r --test aci
cargo test -r --test kata
Expand Down
53 changes: 0 additions & 53 deletions src/builtins/debugging.rs

This file was deleted.

Loading

0 comments on commit 77eb476

Please sign in to comment.