diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 8ae9df0..6141679 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -7,12 +7,26 @@ version: 2 updates: + # All Rust/Cargo directories are grouped into a single entry so that + # when a dependency is updated, Dependabot creates ONE PR that bumps it + # across the root workspace AND every binding, preventing version skew. - package-ecosystem: "cargo" - directory: "/" # Location of package manifests + directories: + - "/" + - "/bindings/ffi" + - "/bindings/java" + - "/bindings/python" + - "/bindings/ruby" + - "/bindings/ruby/ext/regorusrb" + - "/bindings/wasm" + - "/tests/ensure_no_std" + - "/xtask" schedule: interval: "weekly" + commit-message: + prefix: "build(deps)" groups: - per-dependency: + rust-dependencies: patterns: - "*" # Ignore vendored mimalloc crates; updates are managed manually. @@ -20,82 +34,12 @@ updates: - dependency-name: "regorus-mimalloc" - dependency-name: "regorus-mimalloc-sys" - - package-ecosystem: "cargo" - directory: "/bindings/ffi" - schedule: - interval: "weekly" - groups: - per-dependency: - patterns: - - "*" - - - package-ecosystem: "cargo" - directory: "/bindings/java" - schedule: - interval: "weekly" - groups: - per-dependency: - patterns: - - "*" - - - package-ecosystem: "cargo" - directory: "/bindings/python" - schedule: - interval: "weekly" - groups: - per-dependency: - patterns: - - "*" - - - package-ecosystem: "cargo" - directory: "/bindings/ruby" - schedule: - interval: "weekly" - groups: - per-dependency: - patterns: - - "*" - - - package-ecosystem: "cargo" - directory: "/bindings/ruby/ext/regorusrb" - schedule: - interval: "weekly" - groups: - per-dependency: - patterns: - - "*" - - - package-ecosystem: "cargo" - directory: "/bindings/wasm" - schedule: - interval: "weekly" - groups: - per-dependency: - patterns: - - "*" - - - package-ecosystem: "cargo" - directory: "/tests/ensure_no_std" - schedule: - interval: "weekly" - groups: - per-dependency: - patterns: - - "*" - - - package-ecosystem: "cargo" - directory: "/xtask" - schedule: - interval: "weekly" - groups: - per-dependency: - patterns: - - "*" - - package-ecosystem: "gomod" directory: "/bindings/go" schedule: interval: "weekly" + commit-message: + prefix: "build(deps)" groups: per-dependency: patterns: @@ -105,6 +49,8 @@ updates: directory: "/bindings/java" schedule: interval: "weekly" + commit-message: + prefix: "build(deps)" groups: per-dependency: patterns: @@ -114,6 +60,8 @@ updates: directory: "/bindings/csharp" schedule: interval: "weekly" + commit-message: + prefix: "build(deps)" groups: per-dependency: patterns: @@ -123,6 +71,8 @@ updates: directory: "/bindings/python" schedule: interval: "weekly" + commit-message: + prefix: "build(deps)" groups: per-dependency: patterns: @@ -132,6 +82,8 @@ updates: directory: "/bindings/ruby" schedule: interval: "weekly" + commit-message: + prefix: "build(deps)" groups: per-dependency: patterns: @@ -141,7 +93,9 @@ updates: directory: "/" schedule: interval: "weekly" + commit-message: + prefix: "ci(deps)" groups: - per-dependency: + github-actions: patterns: - "*" diff --git a/.github/workflows/feature-matrix.yml b/.github/workflows/feature-matrix.yml new file mode 100644 index 0000000..df9de6c --- /dev/null +++ b/.github/workflows/feature-matrix.yml @@ -0,0 +1,82 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# +# Thorough weekly test of non-default feature combinations. +# Catches regressions from dependency updates and feature-gating issues +# that the fast PR CI checks (cargo check only) would miss at runtime. +name: tests/feature-matrix + +on: + workflow_dispatch: + schedule: + # Run at 3:42 AM UTC every Saturday. + - cron: "42 3 * * 6" + +env: + CARGO_TERM_COLOR: always + +jobs: + feature-matrix: + name: ${{ matrix.name }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + # Bare minimum: validates that the core interpreter works + # without any builtins or optional subsystems. + - name: minimal (std + arc) + features: std,arc + + # Common library usage pattern (issue #595): consumer enables + # std + arc + rvm and relies on indexmap/std propagation. + - name: library (std + arc + rvm) + features: std,arc,rvm + + # New default after removing mimalloc from full-opa. + # Ensures all builtins compile without the allocator. + - name: full-opa (no mimalloc) + features: std,arc,full-opa + + # Binding-style usage: full-opa with the vendored allocator. + # Mirrors how ffi/java/python/ruby bindings are built. + - name: full-opa + allocator + features: std,arc,full-opa,allocator-memory-limits + + # Selective builtins without full-opa: validates that popular + # features can be cherry-picked independently. + - name: cherry-picked builtins + features: std,arc,rvm,regex,time,semver,cache + + # Observability features only: coverage + cache without the + # heavier builtins (regex, time, etc.). + - name: observability + features: std,arc,rvm,coverage,cache + + # Azure Policy adds jsonschema + dashmap; test it compiles + # and runs on top of full-opa. + - name: azure-policy + features: std,arc,full-opa,azure_policy + + # Azure RBAC adds regex + time + net on top of full-opa. + - name: azure-rbac + features: std,arc,full-opa,azure-rbac + + # no_std with the OPA-compatible feature set: exercises the + # spin_no_std codepath and absence of std-only dependencies. + - name: no_std + features: arc,opa-no-std + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Setup Rust toolchain + uses: ./.github/actions/toolchains/rust + - name: Cache cargo + uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 + with: + shared-key: ${{ runner.os }}-regorus-features + - name: Fetch dependencies + run: cargo fetch --locked + - name: Build + run: cargo build --no-default-features --features "${{ matrix.features }}" --frozen + - name: Test + run: cargo test --no-default-features --features "${{ matrix.features }}" --frozen diff --git a/Cargo.toml b/Cargo.toml index 2dbe5d3..a75c947 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,7 @@ cache = ["dep:lru"] rvm = ["dep:postcard", "dep:indexmap"] semver = ["dep:semver"] allocator-memory-limits = ["std", "mimalloc", "mimalloc/allocator-memory-limits"] -std = ["rand/std", "rand/std_rng", "serde_json/std", "msvc_spectre_libs", "dep:parking_lot" ] +std = ["rand/std", "rand/std_rng", "serde_json/std", "indexmap?/std", "msvc_spectre_libs", "dep:parking_lot" ] time = ["dep:chrono", "dep:chrono-tz"] uuid = ["dep:uuid"] urlquery = ["dep:url"] @@ -57,8 +57,6 @@ full-opa = [ "hex", "http", "jsonschema", - "allocator-memory-limits", - "mimalloc", "net", "opa-runtime", "regex", diff --git a/benches/rvm_benchmark.rs b/benches/rvm_benchmark.rs index 2604be0..ce5092b 100644 --- a/benches/rvm_benchmark.rs +++ b/benches/rvm_benchmark.rs @@ -57,6 +57,7 @@ use regorus::{Engine, Rc, Value}; // hot path (memory_check, execution_timer_tick, instruction-limit compare). // --------------------------------------------------------------------------- +#[cfg(feature = "allocator-memory-limits")] const MEMORY_LIMIT_BYTES: u64 = 256 * 1024 * 1024; const TIME_LIMIT: Duration = Duration::from_secs(30); const TIMER_CHECK_INTERVAL: NonZeroU32 = NonZeroU32::new(16).unwrap(); @@ -360,6 +361,7 @@ fn compile_all_programs() -> Vec { /// Apply or remove production-style limits based on a boolean flag. fn configure_limits(vm: &mut RegoVM, limits: bool) { if limits { + #[cfg(feature = "allocator-memory-limits")] regorus::set_global_memory_limit(Some(MEMORY_LIMIT_BYTES)); vm.set_execution_timer_config(Some(ExecutionTimerConfig { limit: TIME_LIMIT, @@ -367,6 +369,7 @@ fn configure_limits(vm: &mut RegoVM, limits: bool) { })); vm.set_max_instructions(INSTRUCTION_LIMIT); } else { + #[cfg(feature = "allocator-memory-limits")] regorus::set_global_memory_limit(None); vm.set_execution_timer_config(None); vm.set_max_instructions(usize::MAX); diff --git a/bindings/java/Cargo.toml b/bindings/java/Cargo.toml index 0710448..a32284b 100644 --- a/bindings/java/Cargo.toml +++ b/bindings/java/Cargo.toml @@ -14,7 +14,7 @@ keywords = ["interpreter", "opa", "policy-as-code", "rego"] crate-type = ["cdylib"] [features] -default = ["ast", "cache", "coverage", "regorus/std", "regorus/full-opa"] +default = ["ast", "cache", "coverage", "regorus/std", "regorus/full-opa", "regorus/allocator-memory-limits"] coverage = ["regorus/coverage"] ast = ["regorus/ast"] cache = ["regorus/cache"] diff --git a/bindings/python/Cargo.toml b/bindings/python/Cargo.toml index 87f2c94..f265c66 100644 --- a/bindings/python/Cargo.toml +++ b/bindings/python/Cargo.toml @@ -15,7 +15,7 @@ keywords = ["interpreter", "opa", "policy-as-code", "rego"] crate-type = ["cdylib"] [features] -default = ["ast", "cache", "coverage", "regorus/std", "regorus/full-opa"] +default = ["ast", "cache", "coverage", "regorus/std", "regorus/full-opa", "regorus/allocator-memory-limits"] ast = ["regorus/ast"] cache = ["regorus/cache"] coverage = ["regorus/coverage"] diff --git a/bindings/ruby/ext/regorusrb/Cargo.toml b/bindings/ruby/ext/regorusrb/Cargo.toml index b2f1851..db89b35 100644 --- a/bindings/ruby/ext/regorusrb/Cargo.toml +++ b/bindings/ruby/ext/regorusrb/Cargo.toml @@ -11,7 +11,7 @@ crate-type = ["cdylib"] path = "src/lib.rs" [features] -default = ["ast", "cache", "coverage", "regorus/std", "regorus/full-opa"] +default = ["ast", "cache", "coverage", "regorus/std", "regorus/full-opa", "regorus/allocator-memory-limits"] ast = ["regorus/ast"] cache = ["regorus/cache"] coverage = ["regorus/coverage"] diff --git a/xtask/src/tasks/ci/mod.rs b/xtask/src/tasks/ci/mod.rs index 7250a0d..7834909 100644 --- a/xtask/src/tasks/ci/mod.rs +++ b/xtask/src/tasks/ci/mod.rs @@ -242,6 +242,10 @@ fn run_ci_suite(config: CiSuiteConfig) -> Result<()> { )?; } + // Verify that important feature subsets compile correctly. + // These catch issues like #595 where non-default combinations fail. + check_feature_combinations(&workspace, config.release, config.frozen)?; + Ok(()) } @@ -364,3 +368,48 @@ fn base_cargo_args( } args } + +/// Verify that various feature subsets compile. +/// +/// Library consumers may pick non-default feature combinations. Running +/// `cargo check` for each combination is fast and catches regressions like +/// issue #595 (indexmap/std not propagated) early. +/// +/// The weekly `feature-matrix.yml` workflow runs a superset of these with +/// full `cargo test`; these PR checks are intentionally `cargo check` only +/// to keep CI fast. +fn check_feature_combinations(workspace: &Path, release: bool, frozen: bool) -> Result<()> { + let combos: &[&str] = &[ + // Issue #595: library consumer with std + arc + rvm but no full-opa. + // Validates indexmap/std propagation via the weak-dep syntax. + "std,arc,rvm", + // full-opa without mimalloc: the new default after removing the + // vendored allocator from full-opa. All builtins, no allocator. + "std,arc,full-opa", + // Binding-style: full-opa plus the explicit allocator opt-in. + // Mirrors how ffi/java/python/ruby crates are configured. + "std,arc,full-opa,allocator-memory-limits", + // no_std codepath: exercises spin_no_std + absence of std deps. + "arc,opa-no-std", + // Cherry-picked builtins: popular features without full-opa to + // ensure individual feature gates compose correctly. + "std,arc,rvm,coverage,cache,regex,time", + ]; + + for features in combos { + let label = format!( + "cargo check --no-default-features --features {} (ci)", + features + ); + run_ci_cargo_step( + workspace, + "check", + release, + frozen, + Some(features), + &["--no-default-features"], + &label, + )?; + } + Ok(()) +}