Conditional compilation for Rust build.rs script?
Asked Answered
C

3

12

The Rust language supports conditional compilation using attributes like #[cfg(test)]. Rust also supports build scripts using a build.rs file to run code as part of the build process to prepare for compilation.

I would like to use conditional compilation in Rust code to conditionally compile depending on whether we're compiling for a build script, similar to how that is possible for test builds.

Imagine the following:

#[cfg(build)]
fn main() {
    // Part of build script
}

#[cfg(not(build))]
fn main() {
    // Not part of build script, probably regular build
}

This does not work, because build is not a valid identifier here. Is it possible to do this using a different attribute, or could some other trick be used to achieve something similar?


For some context on this issue:
My goal is to generate shell completion scripts through clap at compile time. I've quite a comprehensive App definition across multiple files in the application. I'd like to use this in build.rs by including these parts using the include!(...) macro (as suggested by clap), so I don't have to define App a second time. This pulls some dependencies with it, which I'd like to exclude when used by the build.rs file as they aren't needed in that case. This is what I'm trying to make available in my build.rs script.

Cati answered 18/3, 2019 at 20:18 Comment(4)
whether we're compiling for a build script — this doesn't make sense. A build script is only contained within a special file (e.g. build.rs). There is no other context that it can be compiled in.Huberty
Thanks for your answer. I'll explain what situation I'd like to use it for: It's for generating shell completion scripts through clap at compile time. I've quite a comprehensive App definition across multiple files (in the main application). I want to pull this in in build.rs to generate the definitions. This pulls some dependencies with it, which I'd like to exclude when used by the build.rs file. (what is suggested: docs.rs/clap/2.32.0/clap/struct.App.html#examples-47) (what I want to use: github.com/timvisee/ffsend/blob/master/src/cmd/handler.rs#L51)Exciseman
which I'd like to exclude when used by the build.rs file — why? You will have to build these for the main application anywayHuberty
It requires me to define quite a few dev-dependencies. Along with that, it feels unnecessary include quite a lot of code that isn't covered in the build.rs script. I believe the script should be as simple as possible, without pulling in the whole main application. I'd like to refrain from defining App a second time, and want to prevent duplicate code.Exciseman
C
2

You can just put the build code in build.rs (or presumably have build.rs declare a mod xyz to pull in another file).

I wonder if the question you are trying to ask is whether you can reference the same code from build.rs and main.rs, and if so can that code tell if it's being called by one or the other. It seems you could switch on an environment variable set when using build.rs (using something like option_env, but possibly a nicer way might be to enable a feature in the main code from within build.rs.

(Have a read of the documentation for build scripts if you haven't already.)

Cay answered 19/3, 2019 at 4:56 Comment(2)
You can enable feature at build time by putting following line println!("cargo:rustc-cfg=feature=\"build-time\"") into the build.rs:main for example. But the scope is different. It affects current crate only, not dependencies (see github.com/rust-lang/cargo/issues/5499 for more info). Also if you enable feature in this way, it will be enabled in the build result (bin, lib) as well. So, it doesn't help in this case.Dandiprat
That's indeed one way of going about it, but it feels a little clunky. I'd prefer having an extra feature just for the build.rs script instead of the other way around. But I don't think there's a way to instruct the compiler to do so. Any logic in the build.rs script would only be evaluated after compiling it (and pulling in application sources).Exciseman
H
1

This is what works for me:

fn main() {
  if std::env::var("PROFILE").unwrap() == "debug" {
    // Here I do something that is needed for tests
    // only, not for 'release'
  }
}
Hammerhead answered 7/9, 2022 at 7:43 Comment(2)
This checks an environment variable at runtime, and has nothing to do with conditional compilation.Exciseman
@TimVisée well, it works for me exactly in compile time. Try to install this lib and you will see that build.rs is not executed (it takes at least a minute to run, while installation will be fast). However, if you check it out and do cargo test, the heavy part of build.rs will be executed. Maybe I misunderstood your intention.Hammerhead
M
0

Even though you'll get a warning and it might not be the best way to do this, you might have been looking for something along the lines of the following:

#[cfg(not(build))]
fn main() {
    println!("cargo:rustc-cfg=build");
}

#[cfg(build)]
fn main() {
    println!("Hello, world!");
}

When building this as a build script the build cfg option is not used so the first main function is ran. The output tells cargo to add a cfg option to rustc called build whenever building this crate. When the same file is then built as the bin target the build cfg option is turned on and hence the second main is the one that gets compiled.

Machinery answered 22/5, 2023 at 9:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.