Enabling testing cfg-gated proc_macro via env var

I would really appreciate a review of my approach to manually setting cfg for testing.

I have a crate which wraps nightly-only functionality (proc_macro_diagnostic). I'm happy with securely identifying the availability of the API and the need for a #![cfg(feature)] (which I do in a helper crate).

Testing is a touch more complicated than usual. As the crate is designed to be used by proc_macros, my integration tests require indirection via a proc_macro-fixture crate.

I decided to use an environment variable to allow manually disabling the cfg for testing.

build.rs

let test_flags = split_var("PROC_MACRO2_DIAGNOSTIC_TEST").unwrap_or_default();
    if test_flags.contains("no_diagnostic") {
        autocfg::emit_possibility("unstable_proc_macro_diagnostic");
        autocfg::emit_possibility("has_proc_macro_diagnostic");
    } else {
        ac.emit_unstable_feature("proc_macro_diagnostic");
    }

This allows me to simply cfg gate the main code:
lib.rs at stable · MusicalNinjaDad/proc_macro2_diagnostic

#![cfg_attr(unstable_proc_macro_diagnostic, feature(proc_macro_diagnostic))]
...
    #[cfg(has_proc_macro_diagnostic)]
    {
           ...
    }
    #[cfg(not(has_proc_macro_diagnostic))]
    {
           ...
    }

Rationale

Usual advice is to work with a feature / combination of cfg(any(not(has_proc_macro_diagnostic), all(test, feature="test_no-diagnostic"))), however:

  • that would require also adding a feature to the fixture, then driving the selection of that feature in the fixture when compiling it for the integration tests based upon the feature passed to the cargo test invocation (on the main crate). That feels overly complicated and brittle, as well as nearly impossible to explain without a whiteboard! (integration test -> fixture -> main crate)
  • test is not passed to the fixture when it is compiled for use in the try-build tests and therefore not passed back to the crate, so I can't restrict it to only test.
  • the cfg gate is more complicated
  • this also exposes the feature as part of the public API.

The main concern I see is that a feature is only exposed to the crate directly depending on me, whereas an env var is exposed to the top crate in the dependency tree. Someone could choose to read the build.rs and set the variable for some reason. Although I would not feel guilty about considering any breaking changes non-semver-relevant in that case.

(No, I can't just "test on stable", as I have a second set of nightly stuff I want to separately gate and not run into problems later when one bit is stabilised before the other)