Channel Based Conditional Compilation

Today I ran into an interesting problem with some rustc lints.  

The four lints below were removed on nightly

broken_intra_doc_links,
invalid_codeblock_attributes,
invalid_html_tags,
missing_crate_level_docs,
Ye Olde Lints

and replaced with

rustdoc::broken_intra_doc_links,
rustdoc::invalid_codeblock_attributes,
rustdoc::invalid_html_tags,
rustdoc::missing_crate_level_docs,
Hot Newness!

I do my local development with nightly, so normally updating these isn't a problem.  However, the library I was working on also supports the beta and stable channels, where these lints do not exist yet.  I tend to #![deny()] all lints by default, so I cannot just update the lints as the unknown_lints lint would break the beta and stable channel builds.  I could change the unknown_lints lint to warn or allow, but maybe there is a better way.


Enter rustversion

I was recently introduced to the rustversion library.  Through the use of attribute selectors, i.e. #[rustversion:stable] you can conditionally compile code based on the channel you are using.   I had previously used this library to run different tests based on the channel because the output was different on a per-channel basis.   Great stuff.

However, these attributes do not work before crate level attributes like #![deny()].   So, how can we leverage rustversion to help us solve this problem.  I did a quick search through old issues and came across this gem.   While this wasn't directly applicable to my use case, I could certainly adapt it to work.


The Solution

Configuration

[build-dependencies]
rustversion = "1"
Cargo.toml

Make sure to add rustversion to your build dependencies.

Build Script

pub fn main() {
  nightly_lints();
}

#[rustversion::nightly]
fn nightly_lints() {
  println!("cargo:rustc-cfg=nightly_lints");
}

#[rustversion::not(nightly)]
fn nightly_lints() {}
build.rs

This code leverages rustversion to generate a cargo instruction only when running on the nightly channel.  The cargo:rustc-cfg instruction tells Cargo to pass the given value as a --cfg flag to the compiler, in this case nightly_lints.

Conditional Compilation

I pulled the four lints in question out of the regular #![deny()] block and moved them into blocks as follows

#![cfg_attr(
    not(nightly_lints),
    deny(
        broken_intra_doc_links,
        invalid_codeblock_attributes,
        invalid_html_tags,
        missing_crate_level_docs,
    )
)]
#![cfg_attr(
    nightly_lints,
    deny(
        rustdoc::broken_intra_doc_links,
        rustdoc::invalid_codeblock_attributes,
        rustdoc::invalid_html_tags,
        rustdoc::missing_crate_level_docs,
    )
)]
lib.rs

Using the attribute that was defined in build.rs we are able to conditionally deny either the old style lints on the beta and stable channels, or the new style lints on nightly.   Pretty cool!

Keep in mind that this code is temporary.   Once these lints move through the channels, these blocks could be removed.

Check out vergen to see the full solution.