How best to handle conditional compilation with deeply nested packages

Context

I'm trying to contribute to a project that uses bindgen to generate bindings. It has a suite of docker files for compiling on multiple distros for both x86/aarch64. The generated bindings are then used from user code.

The latest kernel headers have broken interface. My naive solution was to add conditional compilation based on the kernel/distro that's being targeted like so.

cfg_if::cfg_if! {
        if #[cfg(feature = "kernel5_8")] {
            let rq_disk = unsafe { &*(&*request).rq_disk }; // Supports Debian11
        } else {
            let rq_disk = unsafe { &*(&*request.q()?).disk };
        }
    }

Problem

The project has a workspace file that should allow building all the packages. However, the workspace hierarchy is 3 directories deep like this.

workspace root
  \_
     some-package
       \_
         some-subpackage - conditional compilation happens here

I tried running cargo build --features=some-package/some-subpackage/kernel5_8 but cargo fails because there are multiple slashes in the feature flag. So I added features to the Cargo.toml for some-package to expose the some-subpackage features like so.

[features]
default = ["some-subpackage/kernel5_15"]
kernel5_8 = ["some-subpackage/kernel5_8"]
kernel5_15 =["some-subpackage/kernel5_15"]

From the some-subpackage directory I can run cargo build --features=kernel5_10 and get the desired results.

However, from the workspace root cargo build --features=some-package/kernel5_8 fails to detect the flag, and from the some-package directory both cargo build --features=kernel5_8 and cargo build --features=some-subpackage/kernel5_8 fail to trigger the compilation condition.

Ultimately, I'd like to be able to run some form of cargo build from the workspace root.

Question

Is this a valid/good approach? I realize that's subjective, but it could be an A/B problem, and there is a more standardized way of handling this scenario that sidesteps the features issue altogether.

If it is valid, is there a way to build the entire workspace in one cargo build command and have the feature propagate?

If it's not a valid/good approach, any suggestions on what would be better or more resilient?

Thanks in advance!

This doesn't seem like it should be a cargo feature. Consider using a --cfg flag instead, e.g.

RUSTFLAGS="--cfg kernel5_8" cargo build
cfg_if::cfg_if! {
    if #[cfg(kernel5_8)] {
        let rq_disk = unsafe { &*(&*request).rq_disk }; // Supports Debian11
    } else {
        let rq_disk = unsafe { &*(&*request.q()?).disk };
    }
}

Thanks! That was exactly answer I needed.

Arguably, if some-subpackage uses a structure which may change depending on the version of Linux it is compiled for, it should be doing the if #[cfg(kernel5_8)] stuff with private types and provide a public API which is kernel-agnostic. They can even use a build script to automatically set the right kernelX_Y flags depending on the host/target kernel.

That way dependencies 3 or 4 links downstream don't need to worry about kernel-specific incompatibilities. They'll just call a function and it might choose the right way to access a field, or might even return None/Err(Unsupported) if the requested functionality isn't available on that kernel.

This is a great point, and one I honestly have been thinking of. I just needed to get the mechanics of how to detect dynamic config down. Your input was really well received though. A more elegant solution that inline ifs should be doable and it was great to get feedback that that is probably a more maintainable long term solution.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.