diff --git a/.github/workflows/go-lint.yml b/.github/workflows/go-lint.yml index 95f2217d575..f17ff4a648a 100644 --- a/.github/workflows/go-lint.yml +++ b/.github/workflows/go-lint.yml @@ -18,7 +18,9 @@ on: - "go.work" - "mise.toml" - "f3-sidecar/**" - - "interop-tests/src/tests/**" + - "interop-tests/**" + - "build.rs" + - "src/f3/go_ffi.rs" push: branches: - main @@ -27,7 +29,9 @@ on: - "go.work" - "mise.toml" - "f3-sidecar/**" - - "interop-tests/src/tests/**" + - "interop-tests/**" + - "build.rs" + - "src/f3/go_ffi.rs" jobs: # Merge groups don't support path filters, so detect the relevant changes @@ -48,13 +52,23 @@ jobs: - 'go.work' - 'mise.toml' - 'f3-sidecar/**' - - 'interop-tests/src/tests/**' + - 'interop-tests/**' + - 'build.rs' + - 'src/f3/go_ffi.rs' lint-go: name: Go lint checks needs: changes if: ${{ needs.changes.outputs.changesFound == 'true' || github.event_name == 'workflow_dispatch' }} - runs-on: ubuntu-slim + runs-on: ubuntu-24.04-arm steps: - uses: actions/checkout@v7 - uses: jdx/mise-action@v4 + - name: Apt Dependencies + uses: nick-fields/retry@v4 + with: + timeout_minutes: 5 + max_attempts: 3 + command: | + sudo apt-get install -y libclang-dev # required by rust2go's bindgen + - run: mise lint:go-ffi - run: mise lint:golang diff --git a/AGENTS.md b/AGENTS.md index 101e3826c21..3f3b743f519 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -354,6 +354,7 @@ lldb target/debugging/forest - `RUST_LOG` - Logging configuration (e.g., `debug`, `forest=trace`) - `FULLNODE_API_INFO` - RPC endpoint and authentication token - `FOREST_F3_SIDECAR_FFI_BUILD_OPT_OUT` - Disable F3 sidecar build (for debugging profile) +- `FOREST_REGENERATE_GO_FFI` - when set to `1` forces the regeneration of all Go FFI bindings based on their current Rust definitions, MUST USE when changing any `go_ffi.rs` ## Build Profiles diff --git a/CHANGELOG.md b/CHANGELOG.md index 45ac5b99b0b..e9c53af0315 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,8 @@ ### Changed +- [#7467](https://github.com/ChainSafe/forest/issues/7467): CI now verifies that the committed Go FFI bindings `interop-tests/src/tests/go_app/ffi_gen.go` and `f3-sidecar/ffi_gen.go` stay in sync with their `go_ffi.rs` sources through the `FOREST_REGENERATE_GO_FFI` environment variable, included `mise run lint:go-ffi`. + ### Removed ### Fixed diff --git a/build.rs b/build.rs index 0b9e229499f..344269476b5 100644 --- a/build.rs +++ b/build.rs @@ -19,6 +19,16 @@ fn main() { // whitelist the cfg for cargo clippy println!("cargo::rustc-check-cfg=cfg(f3sidecar)"); + println!("cargo:rerun-if-env-changed=FOREST_REGENERATE_GO_FFI"); + if is_env_truthy("FOREST_REGENERATE_GO_FFI") + && (is_docs_rs() || is_env_truthy("FOREST_F3_SIDECAR_FFI_BUILD_OPT_OUT")) + { + println!( + "cargo:warning=FOREST_REGENERATE_GO_FFI has no effect on the f3-sidecar binding: \ + the build that regenerates it is skipped by FOREST_F3_SIDECAR_FFI_BUILD_OPT_OUT or DOCS_RS" + ); + } + // Do not build f3-sidecar on docs.rs publishing // No proper version of Go compiler is available. println!("cargo:rerun-if-env-changed=FOREST_F3_SIDECAR_FFI_BUILD_OPT_OUT"); @@ -33,17 +43,22 @@ fn main() { // See std::env::set_var("GOFLAGS", "-tags=netgo"); } - rust2go::Builder::default() - .with_go_src("./f3-sidecar") - // the generated Go file has been committed to the git repository, - // uncomment to regenerate the code locally - // .with_regen_arg(rust2go::RegenArgs { - // src: "./src/f3/go_ffi.rs".into(), - // dst: "./f3-sidecar/ffi_gen.go".into(), - // without_main: true, - // ..Default::default() - // }) - .build(); + println!("cargo:rerun-if-changed=src/f3/go_ffi.rs"); + + let mut builder = rust2go::Builder::default().with_go_src("./f3-sidecar"); + + // the generated Go file has been committed to the git repository + // set the var to regenerate the file + if is_env_truthy("FOREST_REGENERATE_GO_FFI") { + builder = builder.with_regen_arg(rust2go::RegenArgs { + src: "./src/f3/go_ffi.rs".into(), + dst: "./f3-sidecar/ffi_gen.go".into(), + without_main: true, + ..Default::default() + }) + } + + builder.build(); } rpc_regression_tests_gen(); diff --git a/docs/docs/users/reference/env_variables.md b/docs/docs/users/reference/env_variables.md index c99c51f95d0..500ed39a362 100644 --- a/docs/docs/users/reference/env_variables.md +++ b/docs/docs/users/reference/env_variables.md @@ -88,6 +88,12 @@ the binary. By default, the Go f3-sidecar is built and linked into Forest binary unless environment variable `FOREST_F3_SIDECAR_FFI_BUILD_OPT_OUT=1` is set. +### `FOREST_REGENERATE_GO_FFI` + +Forces the regeneration of all Go FFI bindings in the project when `FOREST_REGENERATE_GO_FFI=1` is set. + +Used in the CI checks to ensure the currently tracked Go FFI bindings are in sync with their respective Rust definitions. + ### `FOREST_DB_DEV_MODE` By default, Forest will create a database of its current version or try to diff --git a/f3-sidecar/README.md b/f3-sidecar/README.md index 84f5cb3ed0a..03470d79090 100644 --- a/f3-sidecar/README.md +++ b/f3-sidecar/README.md @@ -64,3 +64,5 @@ environment variable `FOREST_F3_SIDECAR_FFI_BUILD_OPT_OUT=1` is set. F3 sidecar is not started by default, set `FOREST_F3_SIDECAR_FFI_ENABLED=1` to opt in. + +To generate the Go F3-sidecar FFI bindings set `FOREST_REGENERATE_GO_FFI=1` otherwise it will use current/already generated ones diff --git a/interop-tests/build.rs b/interop-tests/build.rs index a01980947b1..4c1ff392a5c 100644 --- a/interop-tests/build.rs +++ b/interop-tests/build.rs @@ -3,18 +3,32 @@ fn main() { println!("cargo::rerun-if-changed=src/tests/go_app"); + println!("cargo::rerun-if-changed=src/tests/go_ffi.rs"); + println!("cargo::rerun-if-env-changed=FOREST_REGENERATE_GO_FFI"); + unsafe { std::env::set_var("GOWORK", "off"); std::env::set_var("GOFLAGS", "-tags=netgo"); } - rust2go::Builder::default() - .with_go_src("./src/tests/go_app") - // the generated Go file has been committed to the git repository, - // uncomment to regenerate the code locally - // .with_regen_arg(rust2go::RegenArgs { - // src: "./src/tests/go_ffi.rs".into(), - // dst: "./src/tests/go_app/ffi_gen.go".into(), - // ..Default::default() - // }) - .build(); + + let mut builder = rust2go::Builder::default().with_go_src("./src/tests/go_app"); + + // the generated Go file has been committed to the git repository + // set the var to regenerate the file, CI sets this var to verify freshness. + if is_env_truthy("FOREST_REGENERATE_GO_FFI") { + builder = builder.with_regen_arg(rust2go::RegenArgs { + src: "./src/tests/go_ffi.rs".into(), + dst: "./src/tests/go_app/ffi_gen.go".into(), + ..Default::default() + }) + } + + builder.build(); +} + +fn is_env_truthy(env: &str) -> bool { + std::env::var(env) + .ok() + .map(|var| matches!(var.to_lowercase().as_str(), "1" | "true" | "yes" | "_yes_")) + .unwrap_or_default() } diff --git a/mise.toml b/mise.toml index fcbcf4e5698..0ada261a634 100644 --- a/mise.toml +++ b/mise.toml @@ -99,6 +99,16 @@ run = ''' golangci-lint run ./f3-sidecar ./interop-tests/src/tests/go_app ''' +[tasks."lint:go-ffi"] +description = "Verify the committed Go FFI bindings are in sync with their go_ffi.rs sources." +run = ''' +# Checking the (empty) lib target is enough to run `build.rs` +FOREST_REGENERATE_GO_FFI=1 cargo check -p forest-interop-tests --profile quick +FOREST_REGENERATE_GO_FFI=1 cargo check -p forest-filecoin --profile quick +git diff --exit-code -- interop-tests/src/tests/go_app/ffi_gen.go f3-sidecar/ffi_gen.go \ + || (echo "a committed ffi_gen.go is stale — run 'mise lint:go-ffi' and commit the result"; false) +''' + [tasks."lint:ruby"] description = "Lint Ruby code using rubocop." tools.ruby = "latest" diff --git a/src/f3/go_ffi.rs b/src/f3/go_ffi.rs index 35c3bbec078..52c51105721 100644 --- a/src/f3/go_ffi.rs +++ b/src/f3/go_ffi.rs @@ -1,8 +1,6 @@ // Copyright 2019-2026 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT -use crate::prelude::*; - pub mod binding { #![allow(warnings)] #![allow(clippy::indexing_slicing)] @@ -16,8 +14,8 @@ pub trait GoF3Node { jwt: String, f3_rpc_endpoint: String, initial_power_table: String, - bootstrap_epoch: ChainEpoch, - finality: ChainEpoch, + bootstrap_epoch: i64, + finality: i64, f3_root: String, ) -> bool;